@thebassclef/lite 1.0.4 → 1.1.1

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 (275) hide show
  1. package/dist/cli.cjs +298 -55
  2. package/dist/cli.js +298 -55
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/lite/.claude/agents/architect.md +70 -0
  7. package/dist/lite/.claude/agents/builder.md +114 -0
  8. package/dist/lite/.claude/agents/designer.md +156 -0
  9. package/dist/lite/.claude/agents/reviewer.md +88 -0
  10. package/dist/lite/.claude/hooks/bassclef-sync.sh +85 -27
  11. package/dist/lite/.claude/hooks/longrun-prep-compounding-axis-check.sh +492 -0
  12. package/dist/lite/.claude/hooks/longrun-prep-render-check.sh +236 -0
  13. package/dist/lite/.claude/hooks/persona-name-scrub-check.sh +105 -0
  14. package/dist/lite/.claude/hooks/skill-step-list-check.sh +171 -0
  15. package/dist/lite/.claude/luminaries/alan-cooper.md +170 -0
  16. package/dist/lite/.claude/luminaries/alistair-cockburn.md +140 -0
  17. package/dist/lite/.claude/luminaries/amazon-pr-faq.md +34 -0
  18. package/dist/lite/.claude/luminaries/andreas-zeller.md +74 -0
  19. package/dist/lite/.claude/luminaries/ash-maurya.md +121 -0
  20. package/dist/lite/.claude/luminaries/bill-buxton.md +210 -0
  21. package/dist/lite/.claude/luminaries/charles-sanders-peirce.md +150 -0
  22. package/dist/lite/.claude/luminaries/david-ogilvy.md +192 -0
  23. package/dist/lite/.claude/luminaries/don-norman.md +173 -0
  24. package/dist/lite/.claude/luminaries/edward-tufte.md +179 -0
  25. package/dist/lite/.claude/luminaries/eric-evans.md +160 -0
  26. package/dist/lite/.claude/luminaries/frederick-brooks.md +50 -0
  27. package/dist/lite/.claude/luminaries/gang-of-four.md +157 -0
  28. package/dist/lite/.claude/luminaries/glenford-myers.md +230 -0
  29. package/dist/lite/.claude/luminaries/hunt-thomas.md +115 -0
  30. package/dist/lite/.claude/luminaries/hyrum-wright.md +94 -0
  31. package/dist/lite/.claude/luminaries/jason-fried-dhh.md +46 -0
  32. package/dist/lite/.claude/luminaries/jesse-james-garrett.md +154 -0
  33. package/dist/lite/.claude/luminaries/john-ousterhout.md +94 -0
  34. package/dist/lite/.claude/luminaries/karl-popper.md +132 -0
  35. package/dist/lite/.claude/luminaries/kent-beck.md +168 -0
  36. package/dist/lite/.claude/luminaries/linus-torvalds.md +218 -0
  37. package/dist/lite/.claude/luminaries/martin-fowler.md +164 -0
  38. package/dist/lite/.claude/luminaries/michael-feathers.md +125 -0
  39. package/dist/lite/.claude/luminaries/michael-nygard.md +118 -0
  40. package/dist/lite/.claude/luminaries/robert-c-martin.md +164 -0
  41. package/dist/lite/.claude/luminaries/saltzer-schroeder.md +194 -0
  42. package/dist/lite/.claude/luminaries/sophia-prater.md +193 -0
  43. package/dist/lite/.claude/luminaries/stephen-toulmin.md +131 -0
  44. package/dist/lite/.claude/luminaries/tony-hoare.md +170 -0
  45. package/dist/lite/.claude/luminaries/vaughn-vernon.md +50 -0
  46. package/dist/lite/.claude/luminaries/w-edwards-deming.md +158 -0
  47. package/dist/lite/.claude/rules/accessor-library-discipline.md +110 -0
  48. package/dist/lite/.claude/rules/adr-discipline.md +96 -0
  49. package/dist/lite/.claude/rules/api-conventions.md +115 -0
  50. package/dist/lite/.claude/rules/artifact-ingestion.md +137 -0
  51. package/dist/lite/.claude/rules/assert-only-after-verify.md +111 -0
  52. package/dist/lite/.claude/rules/blocked-items.md +139 -0
  53. package/dist/lite/.claude/rules/bootstrap-pair-discipline.md +106 -0
  54. package/dist/lite/.claude/rules/branching.md +28 -0
  55. package/dist/lite/.claude/rules/cold-adopter-harness-discipline.md +93 -0
  56. package/dist/lite/.claude/rules/commit-conventions.md +22 -0
  57. package/dist/lite/.claude/rules/compounding-axis-fresh-analysis.md +173 -0
  58. package/dist/lite/.claude/rules/compounding-sequence-fresh-analysis.md +173 -0
  59. package/dist/lite/.claude/rules/context-engineering.md +155 -0
  60. package/dist/lite/.claude/rules/context-management.md +85 -0
  61. package/dist/lite/.claude/rules/defensive-bash.md +52 -0
  62. package/dist/lite/.claude/rules/deferred-actions.md +233 -0
  63. package/dist/lite/.claude/rules/destructive-operations.md +69 -0
  64. package/dist/lite/.claude/rules/diagnosis.md +38 -0
  65. package/dist/lite/.claude/rules/github-issue-flash-tweet.md +114 -0
  66. package/dist/lite/.claude/rules/guardrails.md +73 -0
  67. package/dist/lite/.claude/rules/hook-wire-on-author.md +77 -0
  68. package/dist/lite/.claude/rules/identifier-leak-prevention.md +73 -0
  69. package/dist/lite/.claude/rules/iteration-bet-brief-completeness.md +43 -0
  70. package/dist/lite/.claude/rules/lite-manifest-schema-change-discipline.md +79 -0
  71. package/dist/lite/.claude/rules/longrun-prep-plan-doc-compression.md +77 -0
  72. package/dist/lite/.claude/rules/loop-discipline.md +71 -0
  73. package/dist/lite/.claude/rules/manual-prod-approval.md +100 -0
  74. package/dist/lite/.claude/rules/marker-enrichment-discipline.md +72 -0
  75. package/dist/lite/.claude/rules/mobile-ephemeral-session.md +109 -0
  76. package/dist/lite/.claude/rules/new-dependency-check.md +44 -0
  77. package/dist/lite/.claude/rules/oo-ad-entry-point.md +95 -0
  78. package/dist/lite/.claude/rules/operator-facing-prose.md +177 -0
  79. package/dist/lite/.claude/rules/option-label-discipline.md +82 -0
  80. package/dist/lite/.claude/rules/pattern-annotation.md +83 -0
  81. package/dist/lite/.claude/rules/persona-name-leak-scrub.md +90 -0
  82. package/dist/lite/.claude/rules/plain-english-discipline.md +132 -0
  83. package/dist/lite/.claude/rules/plan-enumeration-needs-value-props.md +177 -0
  84. package/dist/lite/.claude/rules/pr-body-shape.md +195 -0
  85. package/dist/lite/.claude/rules/pr-strategy.md +142 -0
  86. package/dist/lite/.claude/rules/pr-title-shape.md +90 -0
  87. package/dist/lite/.claude/rules/prototype-workflow.md +53 -0
  88. package/dist/lite/.claude/rules/reserved-skill-names.md +103 -0
  89. package/dist/lite/.claude/rules/schema-management.md +42 -0
  90. package/dist/lite/.claude/rules/sdlc-gates.md +149 -0
  91. package/dist/lite/.claude/rules/security.md +37 -0
  92. package/dist/lite/.claude/rules/session-artifacts.md +236 -0
  93. package/dist/lite/.claude/rules/skill-composition-declarations.md +105 -0
  94. package/dist/lite/.claude/rules/skill-description-clarity.md +196 -0
  95. package/dist/lite/.claude/rules/skill-procedure-step-list.md +119 -0
  96. package/dist/lite/.claude/rules/sprint-prep-preset-picker.md +76 -0
  97. package/dist/lite/.claude/rules/state-schema-validation.md +156 -0
  98. package/dist/lite/.claude/rules/stuck-signal-diagnostic.md +111 -0
  99. package/dist/lite/.claude/rules/substrate-config-schema.md +85 -0
  100. package/dist/lite/.claude/rules/test-list-discipline.md +129 -0
  101. package/dist/lite/.claude/rules/test-sufficiency.md +186 -0
  102. package/dist/lite/.claude/rules/testing-tier-config.md +125 -0
  103. package/dist/lite/.claude/rules/testing.md +38 -0
  104. package/dist/lite/.claude/rules/turn-estimate-grounding.md +82 -0
  105. package/dist/lite/.claude/rules/visual-hierarchy.md +427 -0
  106. package/dist/lite/.claude/rules/we-dont-break-adopters.md +103 -0
  107. package/dist/lite/.claude/rules/whereami-load-bearing.md +184 -0
  108. package/dist/lite/.claude/rules/writing-craft-discipline.md +72 -0
  109. package/dist/lite/.claude/rules/wu-sequencing-compounds.md +119 -0
  110. package/dist/lite/.claude/skills/build/SKILL.md +640 -0
  111. package/dist/lite/.claude/skills/chronicle/SKILL.md +55 -0
  112. package/dist/lite/.claude/skills/clean-artifacts/SKILL.md +249 -0
  113. package/dist/lite/.claude/skills/decompose/SKILL.md +280 -0
  114. package/dist/lite/.claude/skills/diagnose/SKILL.md +297 -0
  115. package/dist/lite/.claude/skills/feynman/SKILL.md +90 -0
  116. package/dist/lite/.claude/skills/howdoi/SKILL.md +105 -0
  117. package/dist/lite/.claude/skills/ia-model/SKILL.md +108 -0
  118. package/dist/lite/.claude/skills/interaction-design/SKILL.md +112 -0
  119. package/dist/lite/.claude/skills/interpret-input/SKILL.md +180 -0
  120. package/dist/lite/.claude/skills/journal/SKILL.md +209 -0
  121. package/dist/lite/.claude/skills/kiss/SKILL.md +449 -0
  122. package/dist/lite/.claude/skills/launch/SKILL.md +915 -0
  123. package/dist/lite/.claude/skills/lean-canvas/SKILL.md +332 -0
  124. package/dist/lite/.claude/skills/longrun/SKILL.md +512 -0
  125. package/dist/lite/.claude/skills/luminary/SKILL.md +481 -0
  126. package/dist/lite/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
  127. package/dist/lite/.claude/skills/onboard-repo/SKILL.md +1640 -0
  128. package/dist/lite/.claude/skills/pattern-review/SKILL.md +99 -0
  129. package/dist/lite/.claude/skills/personas/SKILL.md +207 -0
  130. package/dist/lite/.claude/skills/promote/SKILL.md +283 -0
  131. package/dist/lite/.claude/skills/requirement/SKILL.md +98 -0
  132. package/dist/lite/.claude/skills/retro/SKILL.md +117 -0
  133. package/dist/lite/.claude/skills/riff/SKILL.md +114 -0
  134. package/dist/lite/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
  135. package/dist/lite/.claude/skills/session-end/SKILL.md +309 -0
  136. package/dist/lite/.claude/skills/session-log/SKILL.md +299 -0
  137. package/dist/lite/.claude/skills/skills/SKILL.md +228 -0
  138. package/dist/lite/.claude/skills/spec/SKILL.md +105 -0
  139. package/dist/lite/.claude/skills/sprint/SKILL.md +418 -0
  140. package/dist/lite/.claude/skills/stage/SKILL.md +384 -0
  141. package/dist/lite/.claude/skills/state-a-problem/SKILL.md +185 -0
  142. package/dist/lite/.claude/skills/temperance/SKILL.md +108 -0
  143. package/dist/lite/.claude/skills/use-case/SKILL.md +417 -0
  144. package/dist/lite/.claude/skills/user-stories/SKILL.md +268 -0
  145. package/dist/lite/.claude/skills/value-prop/SKILL.md +251 -0
  146. package/dist/lite/.claude/skills/verify/SKILL.md +160 -0
  147. package/dist/lite/.claude/skills/visual-review/SKILL.md +503 -0
  148. package/dist/lite/.claude/skills/whats-the-plan/SKILL.md +202 -0
  149. package/dist/lite/.claude/skills/whereami/SKILL.md +307 -0
  150. package/dist/lite/AGENTS.md +79 -0
  151. package/dist/lite/CLAUDE-lite.md +85 -0
  152. package/dist/lite/CODE_OF_CONDUCT.md +28 -0
  153. package/dist/lite/CONTRIBUTING.md +177 -0
  154. package/dist/lite/README.md +173 -0
  155. package/dist/lite/SECURITY.md +19 -0
  156. package/dist/lite/architecture/decisions/ADR-029-release-pipeline.md +79 -0
  157. package/dist/lite/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
  158. package/dist/lite/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
  159. package/dist/lite/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
  160. package/dist/lite/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
  161. package/dist/lite/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
  162. package/dist/lite/lib/clean-artifacts-sweep.sh +112 -0
  163. package/dist/lite/lib/code-comment-discipline.sh +144 -0
  164. package/dist/lite/lib/composer-preflight.sh +459 -0
  165. package/dist/lite/lib/deferred-actions.sh +146 -0
  166. package/dist/lite/lib/hook-header-parser.sh +61 -0
  167. package/dist/lite/lib/hook-heartbeat.sh +152 -0
  168. package/dist/lite/lib/hook-inject.sh +255 -0
  169. package/dist/lite/lib/hook-installer.sh +195 -0
  170. package/dist/lite/lib/luminary-pick.sh +96 -0
  171. package/dist/lite/lib/output-discipline.sh +143 -0
  172. package/dist/lite/lib/prose-scan-boundary.sh +171 -0
  173. package/dist/lite/lib/rewrite-check.sh +214 -0
  174. package/dist/lite/lib/session-rescue.sh +151 -0
  175. package/dist/lite/lib/source-graph-walker.sh +277 -0
  176. package/dist/lite/lib/state.sh +1372 -0
  177. package/dist/lite/lib/telemetry.sh +205 -0
  178. package/dist/lite/lib/tier-check.sh +187 -0
  179. package/dist/lite/lib/tier-dependency-audit.sh +1088 -0
  180. package/dist/lite/presence/install/bassclef-hook-connect.sh +178 -0
  181. package/dist/lite/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
  182. package/dist/lite/presence/install/bassclef-sync.template.sh +2278 -0
  183. package/dist/lite/presence/install/schedule-auto-save.cron.sh +88 -0
  184. package/dist/lite/presence/install/schedule-auto-save.taskscheduler.md +122 -0
  185. package/dist/lite/scripts/aggregate-telemetry.sh +217 -0
  186. package/dist/lite/scripts/analyze-tier-dependencies.sh +239 -0
  187. package/dist/lite/scripts/generate-lite-manifest.sh +505 -0
  188. package/dist/lite/scripts/generate-tier-manifest.sh +28 -0
  189. package/dist/lite/scripts/intent-drift-check.sh +456 -0
  190. package/dist/lite/scripts/lite-manifest-drift-check.sh +146 -0
  191. package/dist/lite/scripts/render-lite-manifest-doc.sh +150 -0
  192. package/dist/lite/standards/adr-template.md +86 -0
  193. package/dist/lite/standards/api-conventions/nextjs.md +84 -0
  194. package/dist/lite/standards/artifact-composition.md +209 -0
  195. package/dist/lite/standards/bash-hook-safety.md +246 -0
  196. package/dist/lite/standards/bassclef-configs-schema.md +232 -0
  197. package/dist/lite/standards/bassclef-evolution.md +143 -0
  198. package/dist/lite/standards/bassclef-internal-jargon.md +244 -0
  199. package/dist/lite/standards/bassclef-managed-sentinel.md +96 -0
  200. package/dist/lite/standards/bassclef-source-config.md +228 -0
  201. package/dist/lite/standards/branch-stacking.md +408 -0
  202. package/dist/lite/standards/code-safety-principles.md +176 -0
  203. package/dist/lite/standards/composer-prerequisites.md +155 -0
  204. package/dist/lite/standards/deferred-actions-schema.md +204 -0
  205. package/dist/lite/standards/dependency-discipline/cargo.md +39 -0
  206. package/dist/lite/standards/dependency-discipline/gem.md +43 -0
  207. package/dist/lite/standards/dependency-discipline/go-mod.md +41 -0
  208. package/dist/lite/standards/dependency-discipline/npm.md +42 -0
  209. package/dist/lite/standards/dependency-discipline/pip.md +42 -0
  210. package/dist/lite/standards/deployment-topology/ec2-tailscale.md +225 -0
  211. package/dist/lite/standards/deployment-topology.md +69 -0
  212. package/dist/lite/standards/docs-sync-allowlist.md +76 -0
  213. package/dist/lite/standards/domain-and-dns.md +145 -0
  214. package/dist/lite/standards/frontend-stack.md +67 -0
  215. package/dist/lite/standards/frontmatter-schema.md +154 -0
  216. package/dist/lite/standards/graceful-exit.md +227 -0
  217. package/dist/lite/standards/hook-idempotency.md +102 -0
  218. package/dist/lite/standards/hook-injection-discipline.md +202 -0
  219. package/dist/lite/standards/hook-install-class.md +215 -0
  220. package/dist/lite/standards/input-handler-interface.md +152 -0
  221. package/dist/lite/standards/lite-manifest-schema-changes.md +180 -0
  222. package/dist/lite/standards/lite-manifest.json +2797 -0
  223. package/dist/lite/standards/luminary-matching.md +105 -0
  224. package/dist/lite/standards/luminary-problem-patterns.md +481 -0
  225. package/dist/lite/standards/migration-discipline/active-record.md +50 -0
  226. package/dist/lite/standards/migration-discipline/alembic.md +43 -0
  227. package/dist/lite/standards/migration-discipline/gorm.md +50 -0
  228. package/dist/lite/standards/migration-discipline/prisma.md +53 -0
  229. package/dist/lite/standards/migration-discipline/sqlalchemy.md +51 -0
  230. package/dist/lite/standards/mobile-ephemeral-session.md +167 -0
  231. package/dist/lite/standards/model-routing-discipline.md +160 -0
  232. package/dist/lite/standards/ogilvy-writing-rules.md +225 -0
  233. package/dist/lite/standards/opener-discipline.md +96 -0
  234. package/dist/lite/standards/operator-facing-prose-discipline.md +201 -0
  235. package/dist/lite/standards/persona-schema.md +229 -0
  236. package/dist/lite/standards/pluggable-luminaries.md +323 -0
  237. package/dist/lite/standards/pr-body-discipline.md +115 -0
  238. package/dist/lite/standards/preview-state-schema.md +189 -0
  239. package/dist/lite/standards/project-directory-layout.md +276 -0
  240. package/dist/lite/standards/release-tagging.md +137 -0
  241. package/dist/lite/standards/reserved-skill-names.md +120 -0
  242. package/dist/lite/standards/scannable-multi-option-output.md +265 -0
  243. package/dist/lite/standards/sdlc-compliance.md +286 -0
  244. package/dist/lite/standards/sdlc-gates/typescript.md +57 -0
  245. package/dist/lite/standards/secrets-lifecycle.md +210 -0
  246. package/dist/lite/standards/security-scanner-adapter.md +145 -0
  247. package/dist/lite/standards/session-board.md +256 -0
  248. package/dist/lite/standards/skill-output-discipline.md +121 -0
  249. package/dist/lite/standards/state-spine-contract.md +255 -0
  250. package/dist/lite/standards/state-spine.md +511 -0
  251. package/dist/lite/standards/steering-hints/kiss-words.md +11 -0
  252. package/dist/lite/standards/substrate-config-schema.md +267 -0
  253. package/dist/lite/standards/tech-stack-config.md +109 -0
  254. package/dist/lite/standards/tier-dependency-analysis.md +167 -0
  255. package/dist/lite/standards/tier-runtime-deps/lite.md +57 -0
  256. package/dist/lite/standards/tier-tag-schema.md +155 -0
  257. package/dist/lite/standards/two-layer-config.md +99 -0
  258. package/dist/lite/standards/use-case-format.md +292 -0
  259. package/dist/lite/standards/user-story-invest.md +268 -0
  260. package/dist/lite/standards/velocity-and-appetite.md +229 -0
  261. package/dist/lite/standards/voice-input-pattern.md +119 -0
  262. package/dist/lite/standards/whereami-schema.md +301 -0
  263. package/dist/lite/standards/worktree-management.md +211 -0
  264. package/dist/lite/standards/writing-guide.md +213 -0
  265. package/dist/lite/templates/chronicle-template.md +75 -0
  266. package/dist/lite/templates/deferred-action-template.md +45 -0
  267. package/dist/lite/templates/memory-proposal-template.md +77 -0
  268. package/dist/lite/templates/persona-template.md +200 -0
  269. package/dist/lite/templates/pr-faq.md +45 -0
  270. package/dist/lite/templates/secret-rotation-template.md +162 -0
  271. package/dist/lite/templates/spec-template.md +131 -0
  272. package/dist/lite/templates/use-case-template.md +194 -0
  273. package/dist/lite/templates/user-story-template.md +107 -0
  274. package/dist/lite/templates/whereami-template.md +101 -0
  275. package/package.json +12 -5
@@ -0,0 +1,841 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # description: Retagged standard → lite per #1301 concentric rule (ADR-052 D1) —
4
+ # Retagged standard → lite per #1301 concentric rule (ADR-052 D1) —
5
+ # this dispatcher IS the sync entry point every adopter vendors into
6
+ # .claude/hooks/bassclef-sync.sh. Without ship at lite, lite adopters
7
+ # have no sync at all. Closes #1301 sister file.
8
+ # BASSCLEF_SYNC_VERSION=thin-pointer-2026-07-28a
9
+ # ^^ DO NOT REMOVE — used by bassclef's drift check + Layer 3 self-heal
10
+ # detection (per ADR-032). Version string format: descriptor-YYYY-MM-DD.
11
+ #
12
+ # Thin-pointer bassclef-sync dispatcher.
13
+ #
14
+ # Source of truth for the dispatcher consumers vendor into their
15
+ # .claude/hooks/bassclef-sync.sh. See standards/bassclef-source-config.md
16
+ # for the .bassclef-source.json schema this dispatcher reads.
17
+ #
18
+ # Two paths, tried in order:
19
+ #
20
+ # 1. Sibling fast-path (local dev, desktop, multi-repo workspaces)
21
+ # — if $CWD/../bassclef/presence/install/bassclef-sync.template.sh
22
+ # exists, exec it directly. Always-current because the file is
23
+ # read fresh on every fire.
24
+ #
25
+ # 2. Curl fallback (Cloud Env, mobile, fresh sandboxes, CI)
26
+ # — read .bassclef-source.json for upstream URL + auth metadata,
27
+ # fetch the tarball, extract, exec the included template.
28
+ #
29
+ # When neither path works, the dispatcher prints a plain-language
30
+ # explanation with three resolution paths (public adopter / local dev
31
+ # / private fork). Always exits 0 so SessionStart hooks don't crash;
32
+ # consumers that want CI fail-fast behavior should pre-check inside
33
+ # their workflow.
34
+ #
35
+ # Versioning: bump BASSCLEF_SYNC_VERSION when the dispatcher's
36
+ # semantics change. Format: YYYY-MM-DD or descriptor-YYYY-MM-DD.
37
+ #
38
+ # thin-pointer-2026-07-09: pointer-file rename cure (bet 2026-07-09a).
39
+ # Adopters onboarded before 2026-06-20 rename carry
40
+ # .canonical-source.json. Dispatcher now reads new
41
+ # name first, falls back to old, and self-heals
42
+ # (writes new name from old content; removes old on
43
+ # second cycle per Hoare two-step atomic). Fires
44
+ # before auto_sync check + Path 2 curl fallback.
45
+ # Overrides: SKIP_SELF_HEAL_POINTER=1 skips write.
46
+ # Prior version: thin-pointer-2026-06-29c.
47
+ # thin-pointer-2026-06-29c: fallback message names the cure (bet 29e WU-10).
48
+ # emit_error() now lists option 4 — the curl
49
+ # one-liner that runs migrate-adopter-references.sh
50
+ # for adopters whose tree carries leftover
51
+ # canonical-* references from the 2026-06-20 rename
52
+ # event. Discoverable cure inside the error itself;
53
+ # the adopter does not need to read the wiki.
54
+ # Prior version: thin-pointer-2026-06-29b.
55
+ # thin-pointer-2026-06-29b: auto-invocation added (bet 29e WU-4). Detection
56
+ # step now runs the migrate script automatically
57
+ # via curl-pipe + continues to Path 1/Path 2.
58
+ # SKIP_AUTO_INVOKE=1 falls back to WU-2's BLOCKED
59
+ # behavior. Passes BASSCLEF_DISPATCHER_TEMPLATE
60
+ # to migrate when sibling template available.
61
+ # Prior version: thin-pointer-2026-06-29.
62
+ # thin-pointer-2026-06-29: canonical-* reference detection step added
63
+ # (bet 29e WU-2). Scans adopter .claude/settings.json
64
+ # + vendored bassclef-sync.sh for refs surviving the
65
+ # 2026-06-20 rename. Emits BLOCKED + exits 0. WU-4 will
66
+ # replace exit with auto-invocation of migrate-adopter-
67
+ # references.sh. Override: SKIP_CANONICAL_DETECTION=1.
68
+ # Prior version: thin-pointer-2026-06-22.
69
+ # thin-pointer-2026-06-22: version-detection helper added per ADR-032
70
+ # (Layer 2 two-stage rocket). __bassclef_dispatcher_version
71
+ # function exposes BASSCLEF_SYNC_VERSION to external
72
+ # tooling (Layer 3 self-heal in WU-4). No protocol
73
+ # break; purely additive. Prior version: thin-pointer-2026-06-13.
74
+ # thin-pointer-2026-06-13: auto_sync flag check — dispatcher short-
75
+ # circuits when .bassclef-source.json has
76
+ # auto_sync:false or the field absent. Per
77
+ # bassclef#939. Prior version: 2026-05-15.
78
+ # thin-pointer-2026-05-15: friendly error messages with three
79
+ # resolution paths. Closes bassclef#715
80
+ # Layer A. Prior version: 2026-05-02.
81
+
82
+ # === Version-detection helper (per ADR-032) ===
83
+ # Layer 3 self-heal (WU-4 of bet 2026-06-22b) calls this to detect
84
+ # bootstrap version drift. External tooling can also grep
85
+ # BASSCLEF_SYNC_VERSION from line 3, but the function gives a stable
86
+ # programmatic interface that survives header-comment reformatting.
87
+ __bassclef_dispatcher_version() {
88
+ echo "thin-pointer-2026-07-09"
89
+ }
90
+
91
+ # === Pointer-file fallback + self-heal (bet 2026-07-09a) ===
92
+ #
93
+ # Every adopter onboarded before the 2026-06-20 canonical → bassclef
94
+ # rename carries .canonical-source.json. The dispatcher previously read
95
+ # .bassclef-source.json only; stale adopters saw substrate refuse to
96
+ # load. These two functions cure the class:
97
+ #
98
+ # bassclef_sync_resolve_pointer_file(cwd)
99
+ # Echoes the path of the pointer file to use. Prefers new name;
100
+ # falls back to old name; echoes empty when neither exists.
101
+ #
102
+ # bassclef_sync_self_heal_pointer(cwd)
103
+ # Two-step atomic rename per Tony Hoare. First cycle: writes new
104
+ # name from old content, keeps old file for safety. Second cycle:
105
+ # removes old file when new already exists. Never deletes new.
106
+ # Honors SKIP_SELF_HEAL_POINTER=1 for one-shot recovery scenarios.
107
+ #
108
+ # Composing luminaries:
109
+ # @luminary linus-torvalds — we-dont-break-adopters (ADR-031)
110
+ # @luminary jerome-saltzer-and-michael-schroeder — complete mediation
111
+ # @luminary tony-hoare — pre/post conditions on two-step rename
112
+ # @luminary michael-feathers — characterization tests using real Quorum
113
+ #
114
+ # Tests: .claude/hooks/tests/bassclef-sync-pointer-fallback.test.sh
115
+ # Refs: bassclef-upstream#679
116
+ bassclef_sync_resolve_pointer_file() {
117
+ local cwd="${1:-$CWD}"
118
+ local new="$cwd/.bassclef-source.json"
119
+ local old="$cwd/.canonical-source.json"
120
+ if [ -f "$new" ]; then
121
+ echo "$new"
122
+ elif [ -f "$old" ]; then
123
+ echo "$old"
124
+ else
125
+ echo ""
126
+ fi
127
+ }
128
+
129
+ bassclef_sync_self_heal_pointer() {
130
+ local cwd="${1:-$CWD}"
131
+ if [ "${SKIP_SELF_HEAL_POINTER:-0}" = "1" ]; then
132
+ return 0
133
+ fi
134
+ local new="$cwd/.bassclef-source.json"
135
+ local old="$cwd/.canonical-source.json"
136
+ # Second cycle: both exist → remove old, keep new (Hoare postcondition)
137
+ if [ -f "$new" ] && [ -f "$old" ]; then
138
+ rm -f "$old"
139
+ return 0
140
+ fi
141
+ # First cycle: only old exists → write new from old content, keep old for safety
142
+ if [ ! -f "$new" ] && [ -f "$old" ]; then
143
+ cp "$old" "$new" 2>/dev/null || return 0
144
+ # Emit a one-line note so operator sees the self-heal firing
145
+ echo "bassclef-sync: renamed .canonical-source.json → .bassclef-source.json (auto-heal; original kept for one session)" >&2
146
+ return 0
147
+ fi
148
+ # Only new exists OR neither exists → no-op
149
+ return 0
150
+ }
151
+
152
+ # bassclef_sync_self_heal_pointer_content(cwd)
153
+ # Rewrites drift in .bassclef-source.json content on adopters onboarded
154
+ # before the 2026-06-20 canonical → bassclef rename.
155
+ # - source_url: sunj-labs/canonical -> sunj-labs/bassclef
156
+ # - token_env: CANONICAL_TOKEN -> BASSCLEF_TOKEN
157
+ # Preserves auth_method (Bearer during beta), auto_sync flag, and every
158
+ # other field. Idempotent — no-op when content already uses new names.
159
+ # Honors SKIP_SELF_HEAL_POINTER_CONTENT=1 for one-shot recovery cases.
160
+ # Per bet 2026-07-09c WU-2 (bassclef-upstream#680). Sister to
161
+ # bassclef_sync_self_heal_pointer (filename cure from bet 09a).
162
+ bassclef_sync_self_heal_pointer_content() {
163
+ local cwd="${1:-$CWD}"
164
+ if [ "${SKIP_SELF_HEAL_POINTER_CONTENT:-0}" = "1" ]; then
165
+ return 0
166
+ fi
167
+ local pointer="$cwd/.bassclef-source.json"
168
+ if [ ! -f "$pointer" ]; then
169
+ return 0
170
+ fi
171
+ # Detect drift — cheap grep before invoking jq
172
+ local drift=0
173
+ if grep -q "sunj-labs/canonical" "$pointer" 2>/dev/null; then
174
+ drift=1
175
+ fi
176
+ if grep -q "CANONICAL_TOKEN" "$pointer" 2>/dev/null; then
177
+ drift=1
178
+ fi
179
+ if [ "$drift" = "0" ]; then
180
+ return 0
181
+ fi
182
+ # Rewrite via jq — preserves JSON structure and untouched fields
183
+ local new_json
184
+ new_json=$(jq '
185
+ (if (.source_url // "" | contains("sunj-labs/canonical"))
186
+ then .source_url = (.source_url | sub("sunj-labs/canonical"; "sunj-labs/bassclef"))
187
+ else . end)
188
+ | (if (.token_env // "") == "CANONICAL_TOKEN"
189
+ then .token_env = "BASSCLEF_TOKEN"
190
+ else . end)
191
+ | (if (.comment // "" | contains("sunj-labs/canonical"))
192
+ then .comment = (.comment | gsub("sunj-labs/canonical"; "sunj-labs/bassclef"))
193
+ else . end)
194
+ ' "$pointer" 2>/dev/null)
195
+ if [ -n "$new_json" ]; then
196
+ echo "$new_json" > "$pointer.tmp" 2>/dev/null && mv "$pointer.tmp" "$pointer" 2>/dev/null
197
+ echo "bassclef-sync: cured .bassclef-source.json content drift (source_url + token_env → bassclef names)" >&2
198
+ fi
199
+ return 0
200
+ }
201
+
202
+ # bassclef_sync_self_heal_claude_md_content(cwd)
203
+ # Inserts @.claude/bassclef-orientation.md after the first heading in
204
+ # adopter CLAUDE.md when missing. Rewrites stale sunj-labs/canonical
205
+ # URL references + ../canonical path references. Preserves adopter-
206
+ # authored sections. Backs up to CLAUDE.md.bak.<ts> before edit.
207
+ # Idempotent — no-op on cured content.
208
+ # Honors SKIP_SELF_HEAL_CLAUDE_MD_CONTENT=1 for adopters who want to
209
+ # own their CLAUDE.md fully.
210
+ # Per bet 2026-07-09c WU-3 (bassclef-upstream#687 Path 2). Sister to
211
+ # bassclef_sync_self_heal_pointer_content (WU-2 pointer content cure).
212
+ bassclef_sync_self_heal_claude_md_content() {
213
+ local cwd="${1:-$CWD}"
214
+ if [ "${SKIP_SELF_HEAL_CLAUDE_MD_CONTENT:-0}" = "1" ]; then
215
+ return 0
216
+ fi
217
+ local file="$cwd/CLAUDE.md"
218
+ if [ ! -f "$file" ]; then
219
+ return 0
220
+ fi
221
+ local drift_import=0
222
+ local drift_url=0
223
+ local drift_path=0
224
+ if ! grep -q "@.claude/bassclef-orientation.md" "$file" 2>/dev/null; then
225
+ drift_import=1
226
+ fi
227
+ if grep -q "sunj-labs/canonical" "$file" 2>/dev/null; then
228
+ drift_url=1
229
+ fi
230
+ if grep -q "\\.\\./canonical" "$file" 2>/dev/null; then
231
+ drift_path=1
232
+ fi
233
+ if [ "$drift_import" = "0" ] && [ "$drift_url" = "0" ] && [ "$drift_path" = "0" ]; then
234
+ return 0
235
+ fi
236
+ local backup_ts
237
+ backup_ts=$(date +%s)
238
+ cp "$file" "${file}.bak.${backup_ts}" 2>/dev/null
239
+ if [ "$drift_import" = "1" ]; then
240
+ awk 'BEGIN{added=0} /^# / && added==0 {print; print ""; print "@.claude/bassclef-orientation.md"; added=1; next} {print}' \
241
+ "$file" > "${file}.tmp" 2>/dev/null && mv "${file}.tmp" "$file" 2>/dev/null
242
+ fi
243
+ sed 's|sunj-labs/canonical|sunj-labs/bassclef|g; s|\.\./canonical|../bassclef|g' \
244
+ "$file" > "${file}.tmp" 2>/dev/null && mv "${file}.tmp" "$file" 2>/dev/null
245
+ echo "bassclef-sync: cured CLAUDE.md content drift (@import + canonical references → bassclef; backup at ${file}.bak.${backup_ts})" >&2
246
+ return 0
247
+ }
248
+
249
+ INPUT=$(cat)
250
+ CWD=$(echo "$INPUT" | jq -r '.cwd // "."')
251
+ cd "$CWD"
252
+
253
+ # Opt-out sentinel (bassclef#119): repos that must NOT inherit substrate.
254
+ if [ -f "$CWD/.no-bassclef-sync" ]; then
255
+ echo "Bassclef substrate sync: skipped (.no-bassclef-sync sentinel present)."
256
+ exit 0
257
+ fi
258
+
259
+ # === SELF_MODE detection (#929 — closes dispatcher-level bypass of #915) ===
260
+ # Sister to Section 0a in .claude/hooks/bassclef-sync.sh (which shipped in
261
+ # #915 PR #920 and guards the sync flow). This guard sits earlier in the
262
+ # call chain — at the dispatcher — so canonical-* auto-invoke + auto_sync
263
+ # fetch + Path 1 / Path 2 branches ALL skip when the current repo is the
264
+ # substrate source (bassclef or bassclef-upstream).
265
+ #
266
+ # Rationale: session 26g SessionStart re-symlinked ~63 hooks in
267
+ # bassclef-upstream despite #915. The project hook's SELF_MODE fired but
268
+ # only AFTER the dispatcher had already invoked the migrate script + fired
269
+ # other paths that touched the tree. Guard early = complete mediation.
270
+ #
271
+ # Env override SKIP_SELF_MODE_DETECTION=1 forces SELF_MODE=0 regardless
272
+ # of remote — for adopter forks of bassclef-upstream that legitimately
273
+ # want the sync flow.
274
+ #
275
+ # Non-breaking for adopters per ADR-031: SELF_MODE returns 0 for any repo
276
+ # whose git remote is NOT sunj-labs/bassclef or sunj-labs/bassclef-upstream.
277
+ # All 5 existing adopters keep their normal recovery + sync paths.
278
+ __bassclef_dispatcher_detect_self_mode() {
279
+ local cwd="${1:-.}"
280
+
281
+ # Env override wins
282
+ if [ "${SKIP_SELF_MODE_DETECTION:-0}" = "1" ]; then
283
+ echo "0"
284
+ return 0
285
+ fi
286
+
287
+ # No git → not substrate source
288
+ command -v git >/dev/null 2>&1 || { echo "0"; return 0; }
289
+
290
+ # Get remote origin URL
291
+ local remote_url
292
+ remote_url=$(git -C "$cwd" config --get remote.origin.url 2>/dev/null)
293
+ if [ -z "$remote_url" ]; then
294
+ echo "0"
295
+ return 0
296
+ fi
297
+
298
+ # Match sunj-labs/(bassclef|bassclef-upstream) with optional .git suffix
299
+ # HTTPS: https://github.com/sunj-labs/bassclef.git
300
+ # SSH: git@github.com:sunj-labs/bassclef-upstream.git
301
+ if echo "$remote_url" | grep -qE 'sunj-labs/(bassclef|bassclef-upstream)(\.git)?$'; then
302
+ echo "1"
303
+ return 0
304
+ fi
305
+
306
+ echo "0"
307
+ return 0
308
+ }
309
+
310
+ __DISPATCHER_SELF_MODE=$(__bassclef_dispatcher_detect_self_mode "$CWD")
311
+ if [ "$__DISPATCHER_SELF_MODE" = "1" ]; then
312
+ # #895: park user-scope orphans that shadow project scope before exit.
313
+ # Without this, pre-existing symlinks planted by sibling syncs (POA,
314
+ # twoDo, utilities) shadow bassclef-upstream's project scope in the
315
+ # Claude Code menu — 92 skills appear twice. The parking helper leaves
316
+ # real user-scope dirs (operator-authored content) untouched per its
317
+ # v3 contract. Adopters never hit this path (SELF_MODE=0 for their
318
+ # remotes per __bassclef_dispatcher_detect_self_mode).
319
+ #
320
+ # Override: SKIP_SELF_MODE_PARKING=1 for one-shot recovery scenarios.
321
+ if [ "${SKIP_SELF_MODE_PARKING:-0}" != "1" ]; then
322
+ __parking_helper="$CWD/scripts/bassclef-orphan-parking.sh"
323
+ if [ -x "$__parking_helper" ]; then
324
+ for __surface in skills agents hooks; do
325
+ __user_surface="$HOME/.claude/$__surface"
326
+ __project_surface="$CWD/.claude/$__surface"
327
+ if [ -d "$__user_surface" ] && [ -d "$__project_surface" ]; then
328
+ # v4 (bassclef-upstream#1077): pass BOTH user + project settings.json
329
+ # as variadic 5th+ args. Parking helper unions the $HOME wirings
330
+ # across both files. Any hook wired at $HOME by either settings
331
+ # scope is preserved. Missing paths silently skip (helper v4 contract).
332
+ bash "$__parking_helper" "$__user_surface" "$__project_surface" "$HOME/.claude" "$__surface" "$HOME/.claude/settings.json" "$CWD/.claude/settings.json" 2>/dev/null || true
333
+ fi
334
+ done
335
+ fi
336
+ fi
337
+ echo "Bassclef dispatcher SELF_MODE — skipping sync in substrate source repo." >&2
338
+ echo " (Override with SKIP_SELF_MODE_DETECTION=1 for adopter forks;" >&2
339
+ echo " SKIP_SELF_MODE_PARKING=1 to skip user-scope orphan parking.)" >&2
340
+ exit 0
341
+ fi
342
+
343
+ # === auto_sync flag check (bassclef#939) ===
344
+ #
345
+ # Per bassclef#939, fresh consumers default to OFF — the dispatcher
346
+ # only fetches when the consumer explicitly opts in via auto_sync: true
347
+ # in .bassclef-source.json. Cold adopters get a one-line note + the
348
+ # opt-in path instead of opaque auto-fetch.
349
+ #
350
+ # Default behavior on absence (file or field): OFF.
351
+ #
352
+ # Self-heal first (bet 2026-07-09a): stale adopters carrying
353
+ # .canonical-source.json get the file renamed to .bassclef-source.json
354
+ # so this pre-check sees the correct file. Second cycle drops the old.
355
+ bassclef_sync_self_heal_pointer "$CWD"
356
+ # Content-drift cure runs AFTER filename cure so the file we rewrite exists at the new name.
357
+ bassclef_sync_self_heal_pointer_content "$CWD"
358
+ # CLAUDE.md content cure runs at same lifecycle point — before Path 1 / Path 2 pick.
359
+ bassclef_sync_self_heal_claude_md_content "$CWD"
360
+ SOURCE_FILE_PRECHECK=$(bassclef_sync_resolve_pointer_file "$CWD")
361
+ if [ -n "$SOURCE_FILE_PRECHECK" ] && [ -f "$SOURCE_FILE_PRECHECK" ]; then
362
+ AUTO_SYNC=$(jq -r '.auto_sync // false' "$SOURCE_FILE_PRECHECK" 2>/dev/null)
363
+ if [ "$AUTO_SYNC" != "true" ]; then
364
+ cat <<NOTE
365
+ ### bassclef-sync — auto-update is OFF
366
+
367
+ Bassclef can keep your skills, rules, and agents current at every
368
+ session start by pulling its latest substrate. It's off by default
369
+ for fresh repos so nothing fetches without your say-so.
370
+
371
+ Turn on anytime by saying "enable auto-sync" or by setting:
372
+
373
+ "auto_sync": true
374
+
375
+ in .bassclef-source.json. Run a manual sync once with:
376
+
377
+ bash .claude/hooks/bassclef-sync.sh
378
+
379
+ More: https://github.com/sunj-labs/bassclef/blob/main/standards/bassclef-source-config.md
380
+
381
+ NOTE
382
+ exit 0
383
+ fi
384
+ fi
385
+
386
+ # === Plain-language error printer ===
387
+ #
388
+ # Args: $1 = short heading, $2 = context paragraph (one or more sentences).
389
+ # Always exits 0 to keep SessionStart hooks non-blocking.
390
+ emit_error() {
391
+ local heading="$1"
392
+ local context="$2"
393
+ cat <<HEADER
394
+
395
+ ### bassclef-sync — $heading
396
+
397
+ $context
398
+
399
+ Three ways to fix this:
400
+
401
+ 1) PUBLIC adopters (most common)
402
+ Edit .bassclef-source.json to point at the public release tarball:
403
+
404
+ {
405
+ "source_url": "https://github.com/sunj-labs/bassclef/releases/latest/download/substrate.tar.gz",
406
+ "auth_method": "none",
407
+ "token_env": "",
408
+ "comment": "Public bassclef substrate. No auth needed."
409
+ }
410
+
411
+ Pre-launch note: while bassclef is still private, the public
412
+ release URL above may not yet exist. Until it does, use option 3.
413
+
414
+ 2) LOCAL DEV — clone bassclef alongside this repo
415
+
416
+ cd ../ && git clone https://github.com/sunj-labs/bassclef.git
417
+
418
+ Re-run your session. The dispatcher uses the local checkout via
419
+ the sibling fast-path; no auth needed.
420
+
421
+ 3) PRIVATE / pre-launch — create a fine-grained GitHub PAT
422
+
423
+ a) GitHub -> Settings -> Developer settings -> Personal access tokens
424
+ -> Fine-grained -> Generate new token
425
+ b) Resource owner: sunj-labs
426
+ c) Repository access: select sunj-labs/bassclef only
427
+ d) Permissions -> Repository -> Contents -> Read
428
+ e) Set the token as BASSCLEF_TOKEN in your environment:
429
+ CI: gh secret set BASSCLEF_TOKEN -R <your-org/your-repo>
430
+ Local: export BASSCLEF_TOKEN=<paste>
431
+ Cloud Code: set BASSCLEF_TOKEN in your environment config
432
+
433
+ 4) STALE REFERENCES — your repo installed bassclef before 2026-06-29
434
+
435
+ If your tree carries leftover canonical-* references from the
436
+ 2026-06-20 rename event, refresh them in place by running the
437
+ migrate script directly:
438
+
439
+ curl -sL https://raw.githubusercontent.com/sunj-labs/bassclef/main/scripts/migrate-adopter-references.sh \\
440
+ | bash -s -- --apply "\$CWD"
441
+
442
+ Open a fresh Claude Code session afterward. The fresh dispatcher
443
+ carries detection that runs the cure automatically when needed.
444
+
445
+ More: https://github.com/sunj-labs/bassclef/blob/main/standards/bassclef-source-config.md
446
+ https://github.com/sunj-labs/bassclef/wiki/Troubleshooting#substrate-fails-to-load-and-you-do-not-see-the-self-heal-banner
447
+
448
+ HEADER
449
+ exit 0
450
+ }
451
+
452
+ # === Recovery script resolver + checksum verifier ===
453
+ #
454
+ # Per goal 2026-08-27 step 7 PR #1 — cures external security review HIGH
455
+ # finding on the self-heal curl|bash fallback (source audit tracked in
456
+ # operator-only artifact; not shipped to public bassclef).
457
+ #
458
+ # Two functions cover Adam's Options A (bundle local) + B (checksum on curl).
459
+ #
460
+ # @pattern strategy (RecoveryScriptResolver picks one of three sources)
461
+ # @pattern guard-clause (verify_checksum fails loud on mismatch)
462
+
463
+ # resolve_recovery_script — prints one of:
464
+ # LOCAL:<path> env override set + file exists
465
+ # SIBLING:<path> sibling checkout has the script
466
+ # CURL:<url>|<sha> neither available; use curl with checksum
467
+ #
468
+ # Priority chain per docs/decompositions/2026-08-27-step-7-pr-1-dispatcher-cure-responsibilities.md
469
+ resolve_recovery_script() {
470
+ # Priority 1 — env override
471
+ if [ -n "${BASSCLEF_RECOVERY_SCRIPT_PATH:-}" ] && [ -f "$BASSCLEF_RECOVERY_SCRIPT_PATH" ]; then
472
+ printf 'LOCAL:%s\n' "$BASSCLEF_RECOVERY_SCRIPT_PATH"
473
+ return 0
474
+ fi
475
+
476
+ # Priority 2 — sibling checkout (matches SIBLING_TEMPLATE pattern at L594)
477
+ local sibling_script="${CWD:-$(pwd)}/../bassclef/scripts/migrate-adopter-references.sh"
478
+ if [ -f "$sibling_script" ]; then
479
+ printf 'SIBLING:%s\n' "$sibling_script"
480
+ return 0
481
+ fi
482
+
483
+ # Priority 3 — curl fallback with checksum sidecar
484
+ local recovery_url="https://raw.githubusercontent.com/sunj-labs/bassclef/main/scripts/migrate-adopter-references.sh"
485
+ local sha_url="${recovery_url}.sha256"
486
+ printf 'CURL:%s|%s\n' "$recovery_url" "$sha_url"
487
+ return 0
488
+ }
489
+
490
+ # verify_checksum <file> <expected_sha256>
491
+ # Returns 0 on match; non-zero + loud stderr on mismatch or missing tool.
492
+ # @luminary saltzer-schroeder — complete mediation; no silent pass.
493
+ verify_checksum() {
494
+ local file="$1"
495
+ local expected="$2"
496
+ local computed=""
497
+
498
+ if command -v shasum >/dev/null 2>&1; then
499
+ computed=$(shasum -a 256 "$file" 2>/dev/null | awk '{print $1}')
500
+ elif command -v sha256sum >/dev/null 2>&1; then
501
+ computed=$(sha256sum "$file" 2>/dev/null | awk '{print $1}')
502
+ else
503
+ echo "verify_checksum: neither shasum nor sha256sum found in PATH" >&2
504
+ echo "verify_checksum: install one before continuing" >&2
505
+ return 2
506
+ fi
507
+
508
+ if [ -z "$computed" ]; then
509
+ echo "verify_checksum: could not compute SHA-256 of $file" >&2
510
+ return 3
511
+ fi
512
+
513
+ if [ "$computed" = "$expected" ]; then
514
+ return 0
515
+ fi
516
+
517
+ echo "verify_checksum: SHA-256 mismatch on $file" >&2
518
+ echo "verify_checksum: expected: $expected" >&2
519
+ echo "verify_checksum: computed: $computed" >&2
520
+ return 1
521
+ }
522
+
523
+ # === Canonical-* reference detection (per bet 29e WU-2) ===
524
+ #
525
+ # Self-heal for adopters carrying canonical-* references from the
526
+ # 2026-06-20 rename. Scans the adopter .claude/ tree at session-start
527
+ # BEFORE Path 1 / Path 2. When canonical-* refs are found, emits a
528
+ # BLOCKED block naming the recovery command (curl one-liner shipped
529
+ # in WU-3 of bet 29e) and exits 0 so SessionStart stays non-blocking.
530
+ #
531
+ # WU-2 emits BLOCKED + exits. WU-4 will rewrite this block to auto-
532
+ # invoke the recovery and continue the dispatcher chain.
533
+ #
534
+ # Override: SKIP_CANONICAL_DETECTION=1 bypasses the scan. Reserved for
535
+ # migration scripts; not for routine adopter use.
536
+ #
537
+ # Primary lens: @luminary michael-nygard — self-healing integration
538
+ # point per Release It! Stability Patterns. Composing lenses:
539
+ # @luminary jerome-saltzer-and-michael-schroeder (complete mediation —
540
+ # every session-start sees the scan), @luminary tony-hoare (when no
541
+ # canonical refs found, scan is silent; postcondition holds), @luminary
542
+ # john-ousterhout (deep dispatcher hides migration complexity).
543
+ if [ "${SKIP_CANONICAL_DETECTION:-0}" != "1" ]; then
544
+ CANONICAL_FINDINGS=()
545
+
546
+ # Surface 2: settings.json additionalDirectories contains "canonical"
547
+ SETTINGS_PATH="$CWD/.claude/settings.json"
548
+ if [ -f "$SETTINGS_PATH" ]; then
549
+ STALE_DIRS=$(jq -r '.permissions.additionalDirectories // [] | .[] | select(test("canonical"))' "$SETTINGS_PATH" 2>/dev/null || true)
550
+ if [ -n "$STALE_DIRS" ]; then
551
+ CANONICAL_FINDINGS+=(".claude/settings.json additionalDirectories contains canonical")
552
+ fi
553
+ fi
554
+
555
+ # Surface 3: vendored dispatcher BASSCLEF_SYNC_VERSION drift
556
+ # (per ADR-032 Layer 3 self-heal; mirrors migrate-adopter-references.sh Surface 3)
557
+ # Read the current version from the helper to avoid drift between the
558
+ # comment header, the function, and this constant.
559
+ CURRENT_VERSION=$(__bassclef_dispatcher_version)
560
+ for CANDIDATE in "$CWD/.claude/hooks/bassclef-sync.sh" "$CWD/.claude/hooks/canonical-sync.sh"; do
561
+ if [ -f "$CANDIDATE" ] && [ ! -L "$CANDIDATE" ]; then
562
+ VENDORED_VERSION=$(grep -m1 'BASSCLEF_SYNC_VERSION=' "$CANDIDATE" 2>/dev/null | sed 's/.*BASSCLEF_SYNC_VERSION=\([^ ]*\).*/\1/' | head -1)
563
+ if [ -z "$VENDORED_VERSION" ] || [[ "$VENDORED_VERSION" < "$CURRENT_VERSION" ]]; then
564
+ BASENAME=$(basename "$CANDIDATE")
565
+ DISPLAY_VERSION="${VENDORED_VERSION:-<missing>}"
566
+ CANONICAL_FINDINGS+=(".claude/hooks/$BASENAME stale (BASSCLEF_SYNC_VERSION=$DISPLAY_VERSION; current=$CURRENT_VERSION)")
567
+ fi
568
+ break
569
+ fi
570
+ done
571
+
572
+ if [ "${#CANONICAL_FINDINGS[@]}" -gt 0 ]; then
573
+ RECOVERY_URL="https://raw.githubusercontent.com/sunj-labs/bassclef/main/scripts/migrate-adopter-references.sh"
574
+ SIBLING_DISPATCHER_TEMPLATE="$CWD/../bassclef/presence/install/bassclef-sync.dispatcher.template.sh"
575
+ # Per bet 30a WU-5 (F7 cure): the migrate script reads three env vars
576
+ # for path overrides. Local layout sibling files for each.
577
+ SIBLING_RENAME_MAP="$CWD/../bassclef/standards/file-renames.json"
578
+ SIBLING_STATUSLINE_TEMPLATE="$CWD/../bassclef/presence/cli/bassclef-statusline.dispatcher.sh"
579
+
580
+ # Decide auto-invoke vs BLOCKED-only path (per bet 29e WU-4)
581
+ AUTO_INVOKE_READY=0
582
+ if [ "${SKIP_AUTO_INVOKE:-0}" != "1" ]; then
583
+ if [ -n "${BASSCLEF_RECOVERY_SCRIPT_PATH:-}" ] && [ -f "$BASSCLEF_RECOVERY_SCRIPT_PATH" ]; then
584
+ AUTO_INVOKE_READY=1
585
+ elif command -v curl >/dev/null 2>&1; then
586
+ AUTO_INVOKE_READY=1
587
+ fi
588
+ fi
589
+
590
+ if [ "$AUTO_INVOKE_READY" = "1" ]; then
591
+ # === Leg 3 of self-healing — auto-invocation (WU-4) ===
592
+ # Detection found canonical-* refs; run the migrate script
593
+ # automatically; continue the dispatcher chain afterward so the
594
+ # adopter sees one clean session-start (no second paste needed).
595
+ echo ""
596
+ echo "🩹 bassclef self-heal — canonical-* references detected; running recovery automatically"
597
+ for finding in "${CANONICAL_FINDINGS[@]}"; do
598
+ echo " - $finding"
599
+ done
600
+
601
+ # Source the migrate script (env-var override for tests, curl in prod).
602
+ # Per bet 30a WU-5 (F7 cure): pass all three env vars the migrate
603
+ # script reads — DISPATCHER_TEMPLATE + RENAME_MAP + STATUSLINE_TEMPLATE
604
+ # — when their sibling files exist. Without these, the script's
605
+ # fallback expansion `$(cd "$(dirname "$0")/..")` resolves wrong
606
+ # under curl-pipe ($0 = bash), and Surfaces 5 + 6 silently no-op.
607
+ # POA was broken for 8+ days because only the dispatcher template
608
+ # env var was passed.
609
+ ENV_ARGS=()
610
+ [ -f "$SIBLING_DISPATCHER_TEMPLATE" ] && ENV_ARGS+=("BASSCLEF_DISPATCHER_TEMPLATE=$SIBLING_DISPATCHER_TEMPLATE")
611
+ [ -f "$SIBLING_RENAME_MAP" ] && ENV_ARGS+=("BASSCLEF_RENAME_MAP=$SIBLING_RENAME_MAP")
612
+ [ -f "$SIBLING_STATUSLINE_TEMPLATE" ] && ENV_ARGS+=("BASSCLEF_STATUSLINE_TEMPLATE=$SIBLING_STATUSLINE_TEMPLATE")
613
+
614
+ # Per bet 2026-08-27 step 7 PR #1 — resolver picks source with priority
615
+ # env override → sibling → curl+checksum. Verifier gates curl path.
616
+ RESOLVED=$(resolve_recovery_script)
617
+ RESOLVED_KIND="${RESOLVED%%:*}"
618
+ RESOLVED_REST="${RESOLVED#*:}"
619
+
620
+ case "$RESOLVED_KIND" in
621
+ LOCAL|SIBLING)
622
+ echo "🩹 recovery source: $RESOLVED_KIND ($RESOLVED_REST)" >&2
623
+ if [ "${#ENV_ARGS[@]}" -gt 0 ]; then
624
+ cat "$RESOLVED_REST" | env "${ENV_ARGS[@]}" bash -s -- --apply "$CWD" || true
625
+ else
626
+ cat "$RESOLVED_REST" | bash -s -- --apply "$CWD" || true
627
+ fi
628
+ ;;
629
+ CURL)
630
+ RECOVERY_URL_RESOLVED="${RESOLVED_REST%|*}"
631
+ SHA_URL="${RESOLVED_REST#*|}"
632
+ echo "🩹 recovery source: CURL ($RECOVERY_URL_RESOLVED)" >&2
633
+ TMP_SCRIPT=$(mktemp -t bassclef-recovery.XXXXXX.sh) || {
634
+ echo "⚠ bassclef self-heal: mktemp failed; skipping recovery" >&2
635
+ true
636
+ }
637
+ TMP_SHA=$(mktemp -t bassclef-recovery.XXXXXX.sha) || {
638
+ rm -f "$TMP_SCRIPT" 2>/dev/null
639
+ echo "⚠ bassclef self-heal: mktemp failed; skipping recovery" >&2
640
+ true
641
+ }
642
+ # Fetch both files; abort verify path if either fetch fails
643
+ if curl -sL --fail "$RECOVERY_URL_RESOLVED" -o "$TMP_SCRIPT" 2>/dev/null \
644
+ && curl -sL --fail "$SHA_URL" -o "$TMP_SHA" 2>/dev/null; then
645
+ EXPECTED_SHA=$(awk '{print $1}' "$TMP_SHA")
646
+ if verify_checksum "$TMP_SCRIPT" "$EXPECTED_SHA"; then
647
+ echo "🩹 recovery: checksum verified; executing" >&2
648
+ if [ "${#ENV_ARGS[@]}" -gt 0 ]; then
649
+ cat "$TMP_SCRIPT" | env "${ENV_ARGS[@]}" bash -s -- --apply "$CWD" || true
650
+ else
651
+ cat "$TMP_SCRIPT" | bash -s -- --apply "$CWD" || true
652
+ fi
653
+ else
654
+ echo "🛑 recovery: SHA-256 mismatch; refusing to execute" >&2
655
+ echo " URL: $RECOVERY_URL_RESOLVED" >&2
656
+ echo " To retry manually after investigating:" >&2
657
+ echo " SKIP_CANONICAL_DETECTION=1 <re-run session>" >&2
658
+ fi
659
+ else
660
+ echo "⚠ bassclef self-heal: fetch failed for recovery or checksum" >&2
661
+ echo " Falls back to manual recovery via BLOCKED path below" >&2
662
+ fi
663
+ rm -f "$TMP_SCRIPT" "$TMP_SHA" 2>/dev/null
664
+ ;;
665
+ *)
666
+ echo "⚠ bassclef self-heal: resolver returned unknown kind ($RESOLVED_KIND); skipping" >&2
667
+ ;;
668
+ esac
669
+
670
+ echo "🩹 bassclef self-heal: recovery applied; continuing"
671
+ echo ""
672
+ # Fall through to Path 1 / Path 2 — substrate now loadable
673
+ else
674
+ # === Fall back: BLOCKED + exit (WU-2 behavior) ===
675
+ # SKIP_AUTO_INVOKE=1 set, OR neither curl nor BASSCLEF_RECOVERY_SCRIPT_PATH
676
+ # available. Surface the recovery shape and stop here.
677
+ cat <<DETECT
678
+
679
+ 🛑 BLOCKED: bassclef substrate — canonical-* references detected
680
+
681
+ Your adopter tree carries references from before the 2026-06-20
682
+ canonical→bassclef rename. Bassclef cannot load substrate cleanly
683
+ until these are repaired.
684
+
685
+ Found:
686
+ DETECT
687
+ for finding in "${CANONICAL_FINDINGS[@]}"; do
688
+ echo " - $finding"
689
+ done
690
+ cat <<RECOVERY
691
+
692
+ Recovery — one paste, no extra setup:
693
+
694
+ curl -sL $RECOVERY_URL | bash -s -- --apply "$CWD"
695
+
696
+ After it runs, open a fresh Claude Code session in this repo.
697
+
698
+ To skip this check once (override): SKIP_CANONICAL_DETECTION=1
699
+ To skip auto-invocation (manual recovery): SKIP_AUTO_INVOKE=1
700
+
701
+ Refs: scripts/migrate-adopter-references.sh, .claude/rules/we-dont-break-adopters.md
702
+
703
+ RECOVERY
704
+ exit 0
705
+ fi
706
+ fi
707
+ fi
708
+
709
+ # === Path 1. Sibling fast-path ===
710
+ SIBLING_TEMPLATE="$CWD/../bassclef/presence/install/bassclef-sync.template.sh"
711
+ if [ -f "$SIBLING_TEMPLATE" ]; then
712
+ echo "$INPUT" | bash "$SIBLING_TEMPLATE"
713
+ exit $?
714
+ fi
715
+
716
+ # === Path 2. Curl fallback ===
717
+ # Uses the same resolver as auto_sync pre-check so old-name adopters
718
+ # still get here (self-heal already fired above, but resolve for safety
719
+ # in case self-heal was skipped via SKIP_SELF_HEAL_POINTER).
720
+ SOURCE_FILE=$(bassclef_sync_resolve_pointer_file "$CWD")
721
+ if [ -z "$SOURCE_FILE" ] || [ ! -f "$SOURCE_FILE" ]; then
722
+ emit_error "couldn't find bassclef substrate" \
723
+ "This repo doesn't have a sibling bassclef checkout at ../bassclef AND
724
+ doesn't have a .bassclef-source.json file to fetch from a remote source.
725
+
726
+ The dispatcher needs ONE of those to load bassclef's substrate (skills,
727
+ rules, hooks, luminaries) into your session."
728
+ fi
729
+
730
+ SOURCE_URL=$(jq -r '.source_url // empty' "$SOURCE_FILE")
731
+ AUTH_METHOD=$(jq -r '.auth_method // "none"' "$SOURCE_FILE")
732
+ TOKEN_ENV=$(jq -r '.token_env // "BASSCLEF_TOKEN"' "$SOURCE_FILE")
733
+
734
+ if [ -z "$SOURCE_URL" ]; then
735
+ emit_error ".bassclef-source.json is missing source_url" \
736
+ "Your .bassclef-source.json file exists but doesn't specify where to fetch
737
+ bassclef from. The 'source_url' field is required.
738
+
739
+ See standards/bassclef-source-config.md for the schema and provider
740
+ examples (GitHub public release, GitHub private repo, Azure DevOps, GitLab)."
741
+ fi
742
+
743
+ # Resolve auth header from env-stored token.
744
+ TOKEN_VALUE=""
745
+ AUTH_HEADER=""
746
+ if [ "$AUTH_METHOD" != "none" ]; then
747
+ TOKEN_VALUE=$(printenv "$TOKEN_ENV" 2>/dev/null || true)
748
+ if [ -z "$TOKEN_VALUE" ]; then
749
+ emit_error "auth token not set in environment" \
750
+ "Your .bassclef-source.json uses auth_method '$AUTH_METHOD' and expects the
751
+ '$TOKEN_ENV' environment variable to hold the token. That variable isn't set
752
+ in this environment.
753
+
754
+ If you're in CI: the secret needs to be configured on the repo
755
+ (gh secret set $TOKEN_ENV -R <repo>).
756
+ If you're on a laptop: export $TOKEN_ENV=<token> in your shell.
757
+ If you're in Cloud Code: set $TOKEN_ENV in your environment config.
758
+
759
+ If you don't have a token yet, follow option 3 below to create one."
760
+ fi
761
+ case "$AUTH_METHOD" in
762
+ Bearer) AUTH_HEADER="Authorization: Bearer $TOKEN_VALUE" ;;
763
+ Basic) AUTH_HEADER="Authorization: Basic $(printf ':%s' "$TOKEN_VALUE" | base64)" ;;
764
+ *) AUTH_HEADER="Authorization: $AUTH_METHOD $TOKEN_VALUE" ;;
765
+ esac
766
+ fi
767
+
768
+ # Fetch tarball
769
+ TARBALL="/tmp/bassclef-thin-pointer-$$.tar.gz"
770
+ if [ -n "$AUTH_HEADER" ]; then
771
+ HTTP_CODE=$(curl -sS -L -H "$AUTH_HEADER" -w "%{http_code}" -o "$TARBALL" "$SOURCE_URL" 2>/dev/null || echo "000")
772
+ else
773
+ HTTP_CODE=$(curl -sS -L -w "%{http_code}" -o "$TARBALL" "$SOURCE_URL" 2>/dev/null || echo "000")
774
+ fi
775
+
776
+ if [ "$HTTP_CODE" != "200" ]; then
777
+ rm -f "$TARBALL" 2>/dev/null
778
+ emit_error "fetch returned HTTP $HTTP_CODE" \
779
+ "The dispatcher tried to download bassclef from:
780
+
781
+ $SOURCE_URL
782
+
783
+ The server returned HTTP $HTTP_CODE instead of 200. Common causes:
784
+
785
+ HTTP 401: token is invalid or expired. Regenerate it.
786
+ HTTP 403: token is valid but doesn't have the required permission
787
+ (needs Contents:read on the bassclef repo).
788
+ HTTP 404: source_url is wrong, OR the bassclef repo / release
789
+ doesn't exist at that URL.
790
+ HTTP 000: network couldn't reach the host. Check DNS / firewall /
791
+ whether you're offline."
792
+ fi
793
+
794
+ # Extract to a stable per-user location.
795
+ BASSCLEF_FRESH="$HOME/bassclef-fresh"
796
+ rm -rf "$BASSCLEF_FRESH" 2>/dev/null
797
+ mkdir -p "$BASSCLEF_FRESH"
798
+ if ! tar -xzf "$TARBALL" -C "$BASSCLEF_FRESH" --strip-components=1 2>/dev/null; then
799
+ rm -f "$TARBALL"
800
+ emit_error "couldn't extract the downloaded tarball" \
801
+ "The dispatcher downloaded the file from $SOURCE_URL but tar -xzf failed.
802
+
803
+ This usually means the response wasn't a real tarball. Check:
804
+
805
+ - Is source_url correct? It should return a gzipped tarball, not HTML.
806
+ - Is a network proxy / SSO portal injecting a login page instead?
807
+ - To inspect what the server is actually sending, run:
808
+
809
+ curl -sSL -H \"Authorization: ...\" \"$SOURCE_URL\" | file -
810
+
811
+ If the output says 'HTML document' instead of 'gzip compressed data',
812
+ you're being intercepted."
813
+ fi
814
+ rm -f "$TARBALL"
815
+
816
+ # Exec the big template.
817
+ #
818
+ # Export the extracted tarball root so §3c-hooks (dynamic-derive hook connect,
819
+ # per bassclef-upstream#625) can read bassclef's own .claude/settings.json.
820
+ # The template sources presence/install/bassclef-hook-connect.sh and calls
821
+ # bassclef_hook_connect_apply "$CWD" — the function reads
822
+ # $BASSCLEF_SOURCE_ROOT/.claude/settings.json to learn the current hook list
823
+ # across every event type, then merges every entry additively into the
824
+ # adopter's settings.json.
825
+ export BASSCLEF_SOURCE_ROOT="$BASSCLEF_FRESH"
826
+
827
+ FALLBACK_TEMPLATE="$BASSCLEF_FRESH/presence/install/bassclef-sync.template.sh"
828
+ if [ ! -f "$FALLBACK_TEMPLATE" ]; then
829
+ emit_error "tarball is missing bassclef-sync.template.sh" \
830
+ "The dispatcher downloaded and extracted the tarball, but it doesn't
831
+ contain presence/install/bassclef-sync.template.sh — bassclef's
832
+ main sync logic.
833
+
834
+ This usually means source_url points at a tarball that isn't bassclef
835
+ (e.g., a different repo, or a branch / tag that's missing the template
836
+ file). Verify the URL in .bassclef-source.json points at bassclef's
837
+ main branch or a release that includes strategy/templates/."
838
+ fi
839
+
840
+ echo "$INPUT" | bash "$FALLBACK_TEMPLATE"
841
+ exit $?