@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,152 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # lib/hook-heartbeat.sh — hook liveness marker helper.
4
+ #
5
+ # Deep module per @luminary john-ousterhout. Two-function interface
6
+ # hides the marker path convention, atomic write pattern, format
7
+ # schema, directory creation, and silent-fail behavior.
8
+ #
9
+ # tier: upstream
10
+ # install-class: dual
11
+ #
12
+ # Interface (what callers know):
13
+ # heartbeat_mark <hook-name>
14
+ # — call from any critical hook on entry; records fire timestamp
15
+ # — silent-fails; never crashes the caller
16
+ #
17
+ # heartbeat_check <hook-name> <max-age-seconds>
18
+ # — returns 0 when marker exists AND is younger than max-age
19
+ # — returns 1 when marker exists AND is older than max-age (stale)
20
+ # — returns 2 when marker file does not exist
21
+ #
22
+ # Hidden (what the module encapsulates):
23
+ # - Marker path: state/markers/hook-heartbeat/<hook-name>.marker
24
+ # (overridable via HEARTBEAT_MARKER_DIR env var for test isolation)
25
+ # - Format: single-line ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ)
26
+ # - Atomic write: temp file + mv (survives concurrent writes)
27
+ # - Directory creation: mkdir -p on first mark
28
+ # - Silent-fail: any I/O failure returns 0 from heartbeat_mark
29
+ #
30
+ # Consumers:
31
+ # - Critical hooks call heartbeat_mark on entry (WU-3 of bet 2026-07-31d)
32
+ # - Session-start reflection module calls heartbeat_check per hook
33
+ # against expected cadence from standards/hook-cadence.json
34
+ #
35
+ # Rules governed by:
36
+ # - .claude/rules/testing-tier-config.md (Tier 0 strict TDD; lib/*.sh)
37
+ # - .claude/rules/mechanism-fidelity.md (interface honored end to end)
38
+ # - .claude/rules/substrate-as-system.md (runtime cadence axis)
39
+ #
40
+ # Anchor luminaries:
41
+ # - @luminary john-ousterhout (deep module)
42
+ # - @luminary michael-nygard (heartbeat = steady-state pattern)
43
+ # - @luminary jerome-saltzer-and-michael-schroeder (complete mediation)
44
+ # - @luminary michael-feathers (characterization pins observed shape)
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Internal helpers (leading underscore per convention)
48
+ # ---------------------------------------------------------------------------
49
+
50
+ # Resolve the marker directory. Test isolation via HEARTBEAT_MARKER_DIR env.
51
+ _heartbeat_dir() {
52
+ if [ -n "${HEARTBEAT_MARKER_DIR:-}" ]; then
53
+ printf '%s' "$HEARTBEAT_MARKER_DIR"
54
+ return 0
55
+ fi
56
+ # Default: <repo-root>/state/markers/hook-heartbeat
57
+ local repo_root
58
+ if [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
59
+ repo_root="$CLAUDE_PROJECT_DIR"
60
+ else
61
+ # Fallback: walk up from this file
62
+ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)"
63
+ fi
64
+ printf '%s' "$repo_root/state/markers/hook-heartbeat"
65
+ }
66
+
67
+ # ISO 8601 UTC timestamp. macOS + Linux compatible.
68
+ _heartbeat_iso_now() {
69
+ date -u +%Y-%m-%dT%H:%M:%SZ
70
+ }
71
+
72
+ # Marker mtime (epoch seconds). Portable macOS + Linux.
73
+ # GNU stat first (Linux default; `stat -f` on GNU means filesystem info,
74
+ # NOT modification time — silently returns nonsense with exit 0). BSD
75
+ # stat second (macOS). Fallback echoes 0 (treats as ancient).
76
+ _heartbeat_mtime() {
77
+ local file="$1"
78
+ stat -c %Y "$file" 2>/dev/null || stat -f %m "$file" 2>/dev/null || echo 0
79
+ }
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Public interface
83
+ # ---------------------------------------------------------------------------
84
+
85
+ # heartbeat_mark <hook-name>
86
+ #
87
+ # Records that <hook-name> fired. Writes an ISO 8601 UTC timestamp to
88
+ # state/markers/hook-heartbeat/<hook-name>.marker via a temp-file-and-mv
89
+ # atomic write. Silent-fails on any error so the caller never crashes.
90
+ heartbeat_mark() {
91
+ local hook_name="${1:-}"
92
+ if [ -z "$hook_name" ]; then
93
+ return 0
94
+ fi
95
+
96
+ local dir marker tmp
97
+ dir="$(_heartbeat_dir)"
98
+ marker="$dir/${hook_name}.marker"
99
+
100
+ # Ensure directory exists. Silent-fail on any error.
101
+ mkdir -p "$dir" 2>/dev/null || return 0
102
+
103
+ # Atomic write via temp + mv. mktemp under the same dir so mv is atomic
104
+ # on the same filesystem. Silent-fail if mktemp cannot create the temp.
105
+ tmp="$(mktemp "${dir}/.${hook_name}.marker.XXXXXX" 2>/dev/null)" || return 0
106
+
107
+ # Write timestamp. If any of these fail, clean up + silent-fail.
108
+ if ! _heartbeat_iso_now > "$tmp" 2>/dev/null; then
109
+ rm -f "$tmp" 2>/dev/null
110
+ return 0
111
+ fi
112
+
113
+ if ! mv "$tmp" "$marker" 2>/dev/null; then
114
+ rm -f "$tmp" 2>/dev/null
115
+ return 0
116
+ fi
117
+
118
+ return 0
119
+ }
120
+
121
+ # heartbeat_check <hook-name> <max-age-seconds>
122
+ #
123
+ # Returns:
124
+ # 0 — marker exists AND is younger than max-age (fresh)
125
+ # 1 — marker exists AND is older than max-age (stale)
126
+ # 2 — marker file does not exist (never fired, or purged)
127
+ heartbeat_check() {
128
+ local hook_name="${1:-}"
129
+ local max_age="${2:-0}"
130
+ if [ -z "$hook_name" ]; then
131
+ return 2
132
+ fi
133
+
134
+ local dir marker
135
+ dir="$(_heartbeat_dir)"
136
+ marker="$dir/${hook_name}.marker"
137
+
138
+ if [ ! -f "$marker" ]; then
139
+ return 2
140
+ fi
141
+
142
+ local mtime now age
143
+ mtime="$(_heartbeat_mtime "$marker")"
144
+ now="$(date -u +%s)"
145
+ age=$((now - mtime))
146
+
147
+ if [ "$age" -le "$max_age" ]; then
148
+ return 0
149
+ fi
150
+
151
+ return 1
152
+ }
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ # lib/hook-inject.sh — shared helper for hooks that inject additionalContext
5
+ # per standards/hook-injection-discipline.md and resolve project resources
6
+ # per standards/hook-install-class.md.
7
+ #
8
+ # This library is SOURCED by hooks, not executed. It does not set -e/-u
9
+ # globally (would affect callers); functions enforce their own contracts.
10
+ #
11
+ # Authority: ADR-035 (substrate-as-system + hooks-as-steering-surfaces)
12
+ # Tier 0 strict TDD: lib/tests/hook-inject.test.sh
13
+ # Anchors: @luminary john-ousterhout (deep modules) + @luminary michael-nygard
14
+ # (fail-soft fallback chain) + @luminary tony-hoare (pre/post contracts)
15
+ #
16
+ # Exposed functions:
17
+ # emit_additional_context <event> <content> # JSON envelope to stdout
18
+ # emit_base_pair_reminders # plain-English + assert-verify reminder text
19
+ # resolve_standards_path <filename> # CLAUDE_PROJECT_DIR → env → HOME → fail
20
+ # resolve_config_path # same chain for bassclef-configs.jsonc
21
+ # extract_block_terms <jargon-file> # BLOCK-tier rows from wordlist
22
+ #
23
+ # Env vars consumed:
24
+ # CLAUDE_PROJECT_DIR — project root (Claude Code sets this)
25
+ # KISS_STANDARDS_FILE — override for standards path resolution
26
+ # BASSCLEF_CONFIG_FILE — override for config path resolution
27
+ # HOME_OVERRIDE — test-only; replaces $HOME for fixture isolation
28
+ #
29
+ # Per standards/hook-injection-discipline.md:
30
+ # - additionalContext capped at 10,000 chars (Anthropic limit)
31
+ # - JSON envelope shape: {"hookSpecificOutput": {"hookEventName": "...", "additionalContext": "..."}}
32
+ # - Fail-soft: all resolution helpers return non-zero with structured stderr; never silently pass
33
+
34
+ # === Constants ===
35
+ HOOK_INJECT_MAX_CONTENT=10000
36
+
37
+ # === Resolve $HOME with test-override support ===
38
+ __hook_inject_home() {
39
+ echo "${HOME_OVERRIDE:-$HOME}"
40
+ }
41
+
42
+ # === emit_additional_context ===
43
+ # Args:
44
+ # $1 — event name (e.g., UserPromptSubmit, Stop)
45
+ # $2 — content (the additionalContext string)
46
+ # Stdout: JSON envelope per Anthropic's hookSpecificOutput shape
47
+ # Exit: 0 success; 2 missing/invalid args
48
+ emit_additional_context() {
49
+ local event="${1:-}"
50
+ local content="${2-}" # NOT :- so explicit empty string is allowed (test case)
51
+
52
+ # Validate event arg
53
+ if [ -z "$event" ]; then
54
+ echo "emit_additional_context: missing event arg" >&2
55
+ echo " Usage: emit_additional_context <event-name> <content>" >&2
56
+ return 2
57
+ fi
58
+
59
+ # Reject event names with spaces (Anthropic events are single tokens)
60
+ if echo "$event" | grep -q ' '; then
61
+ echo "emit_additional_context: invalid event name '$event' (spaces not allowed)" >&2
62
+ echo " Anthropic hook events are single tokens like UserPromptSubmit, Stop, PostToolUse" >&2
63
+ return 2
64
+ fi
65
+
66
+ # Validate content arg presence (allow empty string but require the arg)
67
+ if [ $# -lt 2 ]; then
68
+ echo "emit_additional_context: missing content arg" >&2
69
+ echo " Usage: emit_additional_context <event-name> <content>" >&2
70
+ return 2
71
+ fi
72
+
73
+ # Truncate if exceeds Anthropic 10K cap
74
+ local truncated=0
75
+ if [ ${#content} -gt $HOOK_INJECT_MAX_CONTENT ]; then
76
+ content="${content:0:$HOOK_INJECT_MAX_CONTENT}"
77
+ truncated=1
78
+ fi
79
+
80
+ # Emit JSON envelope via jq for proper escaping
81
+ if command -v jq >/dev/null 2>&1; then
82
+ jq -n \
83
+ --arg event "$event" \
84
+ --arg content "$content" \
85
+ '{hookSpecificOutput: {hookEventName: $event, additionalContext: $content}}'
86
+ else
87
+ # jq-missing fallback: emit minimally-escaped JSON
88
+ # (lib design assumes jq present per standards/bash-hook-safety.md;
89
+ # this fallback exists so a jq-missing system still produces parseable output)
90
+ local esc_event="${event//\"/\\\"}"
91
+ local esc_content="${content//\"/\\\"}"
92
+ esc_content="${esc_content//$'\n'/\\n}"
93
+ printf '{"hookSpecificOutput":{"hookEventName":"%s","additionalContext":"%s"}}\n' \
94
+ "$esc_event" "$esc_content"
95
+ fi
96
+
97
+ if [ $truncated -eq 1 ]; then
98
+ echo "emit_additional_context: content truncated to $HOOK_INJECT_MAX_CONTENT chars (Anthropic limit)" >&2
99
+ fi
100
+
101
+ return 0
102
+ }
103
+
104
+ # === emit_base_pair_reminders ===
105
+ # Emits the shared base-pair reminder text for the 5 UserPromptExpansion
106
+ # hooks per bet 2026-07-01e. Content covers both R8 cures:
107
+ # 1. Plain-English discipline (per .claude/rules/operator-facing-prose.md)
108
+ # 2. Assert-verify discipline (per .claude/rules/assert-only-after-verify.md)
109
+ #
110
+ # Callers prepend this to their skill-specific content, then pass the
111
+ # combined string to emit_additional_context.
112
+ #
113
+ # Stdout: reminder text (no envelope; caller wraps)
114
+ # Exit: 0 always
115
+ emit_base_pair_reminders() {
116
+ cat <<'EOF'
117
+ **Plain check:** Before I make a claim in this turn, I read the source first and cite it. If I cannot cite the source, I do not ship the claim.
118
+
119
+ **Base-pair reminders — plain-English + assert-verify:**
120
+
121
+ Plain-English (per .claude/rules/operator-facing-prose.md):
122
+ - Grade-10 reading level. Subject-verb-object. Short sentences.
123
+ - Drop bassclef-internal jargon (composer, primitive, load-bearing, blast radius, tier-preset, operationalize, wirings). See standards/bassclef-internal-jargon.md for the substitution table.
124
+ - Keep standard tech vocab: API, hook, PR, schema, ORM.
125
+
126
+ Assert-verify (per .claude/rules/assert-only-after-verify.md):
127
+ - Every operator-facing assertion carries three parts: My claim + Evidence + Why the evidence supports the claim.
128
+ - Evidence means the source read inline (file path, ticket #, git SHA).
129
+ - Why the evidence supports the claim means one sentence tying the source to the claim.
130
+ - Optional sharpeners: Qualifier (Toulmin), Rebuttal (Popper), Alternatives (Peirce).
131
+ - If you cannot fill all three, do not ship the assertion. Read the source first.
132
+ - Toulmin's original vocabulary (claim / data / warrant) stays as the research citation in .claude/luminaries/stephen-toulmin.md.
133
+ EOF
134
+ return 0
135
+ }
136
+
137
+ # === resolve_standards_path ===
138
+ # Resolves a standards/ file via project → env → user → fail fallback.
139
+ # Args:
140
+ # $1 — relative filename under standards/ (e.g., "bassclef-internal-jargon.md")
141
+ # Stdout: resolved absolute path
142
+ # Exit: 0 success; 1 all layers fail; 2 missing arg
143
+ resolve_standards_path() {
144
+ local filename="${1:-}"
145
+ if [ -z "$filename" ]; then
146
+ echo "resolve_standards_path: missing filename arg" >&2
147
+ return 2
148
+ fi
149
+
150
+ local project_dir="${CLAUDE_PROJECT_DIR:-}"
151
+ local env_override="${KISS_STANDARDS_FILE:-}"
152
+ local home_dir
153
+ home_dir="$(__hook_inject_home)"
154
+
155
+ # Layer 1: project
156
+ if [ -n "$project_dir" ] && [ -f "$project_dir/standards/$filename" ]; then
157
+ echo "$project_dir/standards/$filename"
158
+ return 0
159
+ fi
160
+
161
+ # Layer 2: env override (only if env file actually exists; otherwise fall through)
162
+ if [ -n "$env_override" ] && [ -f "$env_override" ]; then
163
+ echo "$env_override"
164
+ return 0
165
+ fi
166
+
167
+ # Layer 3: user-level
168
+ if [ -f "$home_dir/standards/$filename" ]; then
169
+ echo "$home_dir/standards/$filename"
170
+ return 0
171
+ fi
172
+
173
+ # All layers failed — structured stderr with the fallback chain attempted
174
+ cat >&2 <<EOF
175
+ resolve_standards_path: '$filename' not found in any fallback layer
176
+ Layer 1 (project): ${project_dir:-<CLAUDE_PROJECT_DIR unset>}/standards/$filename
177
+ Layer 2 (env KISS_STANDARDS_FILE): ${env_override:-<unset>}
178
+ Layer 3 (user): $home_dir/standards/$filename
179
+ Per standards/hook-injection-discipline.md fail-soft contract.
180
+ EOF
181
+ return 1
182
+ }
183
+
184
+ # === resolve_config_path ===
185
+ # Resolves bassclef-configs.jsonc via project → env → user → fail.
186
+ # Stdout: resolved absolute path
187
+ # Exit: 0 success; 1 all layers fail
188
+ resolve_config_path() {
189
+ local project_dir="${CLAUDE_PROJECT_DIR:-}"
190
+ local env_override="${BASSCLEF_CONFIG_FILE:-}"
191
+ local home_dir
192
+ home_dir="$(__hook_inject_home)"
193
+
194
+ # Layer 1: project
195
+ if [ -n "$project_dir" ] && [ -f "$project_dir/.claude/bassclef-configs.jsonc" ]; then
196
+ echo "$project_dir/.claude/bassclef-configs.jsonc"
197
+ return 0
198
+ fi
199
+
200
+ # Layer 2: env override
201
+ if [ -n "$env_override" ] && [ -f "$env_override" ]; then
202
+ echo "$env_override"
203
+ return 0
204
+ fi
205
+
206
+ # Layer 3: user-level
207
+ if [ -f "$home_dir/.claude/bassclef-configs.jsonc" ]; then
208
+ echo "$home_dir/.claude/bassclef-configs.jsonc"
209
+ return 0
210
+ fi
211
+
212
+ cat >&2 <<EOF
213
+ resolve_config_path: bassclef-configs.jsonc not found in any fallback layer
214
+ Layer 1 (project): ${project_dir:-<CLAUDE_PROJECT_DIR unset>}/.claude/bassclef-configs.jsonc
215
+ Layer 2 (env BASSCLEF_CONFIG_FILE): ${env_override:-<unset>}
216
+ Layer 3 (user): $home_dir/.claude/bassclef-configs.jsonc
217
+ Per standards/hook-injection-discipline.md fail-soft contract.
218
+ EOF
219
+ return 1
220
+ }
221
+
222
+ # === extract_block_terms ===
223
+ # Parses a bassclef-internal-jargon.md style file + returns BLOCK-tier terms.
224
+ # Args:
225
+ # $1 — path to the jargon file
226
+ # Stdout: one term per line (BLOCK rows only)
227
+ # Exit: 0 success (even if zero terms found); 1 file missing; 2 missing arg
228
+ extract_block_terms() {
229
+ local file="${1:-}"
230
+ if [ -z "$file" ]; then
231
+ echo "extract_block_terms: missing file arg" >&2
232
+ return 2
233
+ fi
234
+ if [ ! -f "$file" ]; then
235
+ echo "extract_block_terms: file not found: $file" >&2
236
+ return 1
237
+ fi
238
+
239
+ # Parse markdown table rows: extract column 2 (term) where column 3 (tier) == BLOCK
240
+ # Format: | term | TIER | replacement |
241
+ awk -F'|' '
242
+ /BLOCK *\|/ {
243
+ # Get tier column (3rd field after split — col 1 is leading empty)
244
+ tier = $3
245
+ gsub(/^ +| +$/, "", tier)
246
+ if (tier == "BLOCK") {
247
+ term = $2
248
+ gsub(/^ +| +$/, "", term)
249
+ # Skip header rows (term column says "term" literally)
250
+ if (term != "term" && term != "") print term
251
+ }
252
+ }
253
+ ' "$file"
254
+ return 0
255
+ }
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ #
5
+ # lib/hook-installer.sh — installs a hook file at its declared install-class scope
6
+ #
7
+ # @pattern patterns/code/gof/strategy.md
8
+ # Picks route (operator | project | dual) by install-class value.
9
+ #
10
+ # @pattern patterns/code/ousterhout/deep-module.md
11
+ # One function, one argument. Targets resolved inside the module.
12
+ #
13
+ # Contract (Hoare):
14
+ # - Precondition: hook_path exists AND is readable AND declares
15
+ # one of operator|project|dual in its # install-class: header
16
+ # - Postcondition (success): file present at every target the class names
17
+ # AND every source-graph dependent resolves AND trace logs the action
18
+ # - Postcondition (failure): non-zero exit AND stderr names the missing target
19
+ # - Silent-fail per ADR-050 R7: permission denied writes trace + exits 0
20
+ #
21
+ # Per docs/canvases/2026-09-15-install-class-dispatch.md
22
+ # Per docs/use-cases/UC-lib-hook-installer.md
23
+
24
+ set -euo pipefail
25
+
26
+ # Resolve dependent lib scripts relative to this file
27
+ _HOOK_INSTALLER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
28
+
29
+ # shellcheck source=/dev/null
30
+ if [ -f "$_HOOK_INSTALLER_DIR/hook-header-parser.sh" ]; then
31
+ source "$_HOOK_INSTALLER_DIR/hook-header-parser.sh"
32
+ fi
33
+ # Source the real walker (replaces the stub defined below if present)
34
+ # shellcheck source=/dev/null
35
+ if [ -f "$_HOOK_INSTALLER_DIR/source-graph-walker.sh" ]; then
36
+ source "$_HOOK_INSTALLER_DIR/source-graph-walker.sh"
37
+ fi
38
+
39
+ # Trace log location — honors BASSCLEF_TRACE_LOG env override (used by tests)
40
+ _hook_installer_trace_log() {
41
+ if [ -n "${BASSCLEF_TRACE_LOG:-}" ]; then
42
+ echo "$BASSCLEF_TRACE_LOG"
43
+ elif [ -n "${REPO_ROOT:-}" ]; then
44
+ echo "$REPO_ROOT/state/trace.log"
45
+ else
46
+ echo "/tmp/bassclef-hook-installer-trace.log"
47
+ fi
48
+ }
49
+
50
+ # Trace helper — best-effort append; falls back to stderr on file error
51
+ _hook_installer_trace() {
52
+ local line="$*"
53
+ local log
54
+ log=$(_hook_installer_trace_log)
55
+ {
56
+ mkdir -p "$(dirname "$log")" 2>/dev/null || true
57
+ echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ') $line" >> "$log"
58
+ } 2>/dev/null || echo "TRACE: $line" >&2
59
+ }
60
+
61
+ # Home target — honors HOME_OVERRIDE env (used by tests)
62
+ _hook_installer_home() {
63
+ if [ -n "${HOME_OVERRIDE:-}" ]; then
64
+ echo "$HOME_OVERRIDE"
65
+ else
66
+ echo "$HOME"
67
+ fi
68
+ }
69
+
70
+ # Content-hash compare — sha256sum works on both macOS + Linux via shasum
71
+ _hook_installer_content_hash() {
72
+ if command -v sha256sum >/dev/null 2>&1; then
73
+ sha256sum "$1" | awk '{print $1}'
74
+ elif command -v shasum >/dev/null 2>&1; then
75
+ shasum -a 256 "$1" | awk '{print $1}'
76
+ else
77
+ # Fallback — content diff via cmp
78
+ echo "no-hash-tool"
79
+ fi
80
+ }
81
+
82
+ # Copy hook_file to target_dir; idempotent on content-hash match
83
+ # Silent-fail on permission denied per ADR-050 R7
84
+ _hook_installer_copy() {
85
+ local hook_file="$1"
86
+ local target_dir="$2"
87
+ local target_file
88
+ target_file="$target_dir/$(basename "$hook_file")"
89
+
90
+ # Create target dir — silent-fail if permission denied
91
+ if ! mkdir -p "$target_dir" 2>/dev/null; then
92
+ _hook_installer_trace "silent-fail permission-denied create $target_dir"
93
+ return 0
94
+ fi
95
+
96
+ # Check write permission on target dir
97
+ if [ ! -w "$target_dir" ]; then
98
+ _hook_installer_trace "silent-fail permission-denied write $target_dir"
99
+ return 0
100
+ fi
101
+
102
+ # Idempotency — skip when content-hash matches
103
+ if [ -f "$target_file" ]; then
104
+ local src_hash tgt_hash
105
+ src_hash=$(_hook_installer_content_hash "$hook_file")
106
+ tgt_hash=$(_hook_installer_content_hash "$target_file")
107
+ if [ "$src_hash" = "$tgt_hash" ] && [ "$src_hash" != "no-hash-tool" ]; then
108
+ _hook_installer_trace "no-op idempotent $hook_file -> $target_file"
109
+ return 0
110
+ fi
111
+ fi
112
+
113
+ # Copy — silent-fail on permission denied; -p preserves mode (executable bit)
114
+ if cp -p "$hook_file" "$target_file" 2>/dev/null; then
115
+ _hook_installer_trace "install $hook_file -> $target_file"
116
+ return 0
117
+ else
118
+ _hook_installer_trace "silent-fail permission-denied copy $hook_file -> $target_file"
119
+ return 0
120
+ fi
121
+ }
122
+
123
+ # walk_source_dependents comes from lib/source-graph-walker.sh sourced at
124
+ # L34-37. The naive fallback that lived here through v0.41.1 retired at
125
+ # goal 2026-09-14c Step 5.0 per #1663 + ADR-058 c5 — one walker, one
126
+ # implementation. If source-graph-walker.sh is missing (misconfigured
127
+ # install), install_by_class will exit 1 at the walker check line below
128
+ # with the resolved-to-nothing error path — better than a silent fallback
129
+ # that scans differently from the real walker.
130
+
131
+ # install_by_class(hook_path) → 0 on success, non-zero on failure
132
+ install_by_class() {
133
+ local hook_path="$1"
134
+
135
+ # Precondition: hook_path exists
136
+ if [ ! -f "$hook_path" ]; then
137
+ echo "install_by_class: hook_path not found: $hook_path" >&2
138
+ _hook_installer_trace "error hook-not-found $hook_path"
139
+ return 1
140
+ fi
141
+
142
+ # Parse install-class header
143
+ local install_class
144
+ install_class=$(parse_install_class "$hook_path" 2>/dev/null || true)
145
+
146
+ if [ -z "$install_class" ]; then
147
+ echo "install_by_class: missing or malformed # install-class: header in $hook_path" >&2
148
+ _hook_installer_trace "error missing-header $hook_path"
149
+ return 1
150
+ fi
151
+
152
+ # Walker check — Step 4 will extract real walker
153
+ local missing_deps
154
+ missing_deps=$(walk_source_dependents "$hook_path" 2>/dev/null || true)
155
+ if [ -n "$missing_deps" ]; then
156
+ echo "install_by_class: missing dependent(s) for $hook_path:" >&2
157
+ echo "$missing_deps" >&2
158
+ _hook_installer_trace "error missing-deps $hook_path"
159
+ return 1
160
+ fi
161
+
162
+ # Resolve targets — install to $HOME and/or $CLAUDE_PROJECT_DIR based on class
163
+ local home_target project_target
164
+ home_target="$(_hook_installer_home)/.claude/hooks"
165
+
166
+ # Project target — CLAUDE_PROJECT_DIR must be set for project + dual
167
+ if [ "$install_class" = "project" ] || [ "$install_class" = "dual" ]; then
168
+ if [ -z "${CLAUDE_PROJECT_DIR:-}" ]; then
169
+ echo "install_by_class: CLAUDE_PROJECT_DIR unset — cannot install $install_class hook $hook_path" >&2
170
+ _hook_installer_trace "error CLAUDE_PROJECT_DIR-unset $hook_path"
171
+ return 1
172
+ fi
173
+ project_target="$CLAUDE_PROJECT_DIR/.claude/hooks"
174
+ fi
175
+
176
+ # Dispatch — copy to declared target(s)
177
+ case "$install_class" in
178
+ operator)
179
+ _hook_installer_copy "$hook_path" "$home_target"
180
+ ;;
181
+ project)
182
+ _hook_installer_copy "$hook_path" "$project_target"
183
+ ;;
184
+ dual)
185
+ _hook_installer_copy "$hook_path" "$home_target"
186
+ _hook_installer_copy "$hook_path" "$project_target"
187
+ ;;
188
+ esac
189
+
190
+ return 0
191
+ }
192
+
193
+ # Export for callers that source this file
194
+ export -f install_by_class 2>/dev/null || true
195
+ export -f walk_source_dependents 2>/dev/null || true
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # andon-allow: docs/operator-private/[a-z-]+/20[0-9][0-9]-[0-9]{2}
4
+ #
5
+ # lib/luminary-pick.sh — bassclef catalog helper for the in-turn luminary picker.
6
+ #
7
+ # Shape A per docs/decompositions/2026-09-03-picker-harness-llm.md.
8
+ # Consult at docs/operator-private/consultations/2026-09-03-picker-shape.md
9
+ # picked Shape A 5-0 (Cooper, Linus, Ousterhout, Nygard, Feathers).
10
+ #
11
+ # The pick itself lives in each caller SKILL body:
12
+ # .claude/skills/pick-luminaries/SKILL.md
13
+ # .claude/skills/launch/SKILL.md
14
+ # .claude/skills/stage/SKILL.md
15
+ #
16
+ # Claude reads the catalog list from luminary_pick_catalog() below,
17
+ # reasons over the intent inside the current turn, writes the JSON.
18
+ # No shell-out. No separate API key required.
19
+ #
20
+ # This lib provides:
21
+ # - luminary_pick_catalog() — read catalog dir; emit sorted slug list
22
+ # - luminary_pick() — grace-window stub preserving signature through
23
+ # 2026-10-31 per ADR-031 we-dont-break-adopters
24
+ # - luminary_pick_status() — mode report for adopter debugging
25
+ #
26
+ # Usage from a caller SKILL body:
27
+ #
28
+ # source "${BASSCLEF_DIR:-$HOME/src/sunj-labs/bassclef}/lib/luminary-pick.sh"
29
+ # CATALOG=$(luminary_pick_catalog)
30
+ # # then Claude reasons over $CATALOG + intent, writes JSON in-turn
31
+ #
32
+ # Output JSON shape (produced by SKILL body prose, not by this lib):
33
+ # {
34
+ # "matched_luminaries": ["slug1", "slug2", ...],
35
+ # "rationale": "One short sentence per pick",
36
+ # "picker_version": "bassclef-harness-pick-2.0",
37
+ # "provider": "claude-code-harness"
38
+ # }
39
+
40
+ set -euo pipefail
41
+
42
+ # Read catalog dir and emit one line per luminary.
43
+ # Format: "<slug> (<domain>) — <name>"
44
+ # Sort: alphabetical by slug.
45
+ # Return: 0 on success (including empty catalog); non-zero if dir missing.
46
+ luminary_pick_catalog() {
47
+ local basedir="${BASSCLEF_DIR:-$HOME/src/sunj-labs/bassclef}"
48
+ local catalog_dir="${basedir}/state/luminary-implementations"
49
+
50
+ if [ ! -d "$catalog_dir" ]; then
51
+ echo "[luminary-pick-catalog] catalog dir missing: $catalog_dir" >&2
52
+ return 2
53
+ fi
54
+
55
+ (
56
+ shopt -s nullglob
57
+ set +e
58
+ for f in "$catalog_dir"/*.json; do
59
+ jq -r '"\(.luminary_slug) (\(.primary_domain // "unknown")) — \(.name // .luminary_slug)"' "$f" 2>/dev/null || true
60
+ done | sort -u
61
+ )
62
+ return 0
63
+ }
64
+
65
+ # Grace-window stub preserving the old function signature.
66
+ # Adopter scripts that source this lib and call luminary_pick continue
67
+ # to run. They see a stderr note pointing at the new home. Exit 0 so
68
+ # scripts checking exit code do not error.
69
+ # Retires 2026-10-31 per ADR-031.
70
+ luminary_pick() {
71
+ echo "[luminary-pick] moved to caller SKILL body prose per web#193." >&2
72
+ echo "[luminary-pick] adopters: see .claude/skills/pick-luminaries/SKILL.md" >&2
73
+ echo "[luminary-pick] grace-window stub retires 2026-10-31 per ADR-031." >&2
74
+ return 0
75
+ }
76
+
77
+ # Report picker mode. Kept for adopter debugging.
78
+ luminary_pick_status() {
79
+ echo "LIVE — in-turn picker per SKILL body prose (Shape A, web#193)"
80
+ return 0
81
+ }
82
+
83
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
84
+ case "${1:-}" in
85
+ catalog) luminary_pick_catalog ;;
86
+ status) luminary_pick_status ;;
87
+ *)
88
+ echo "Usage: $0 catalog # emit luminary catalog list"
89
+ echo " $0 status # report picker mode"
90
+ echo ""
91
+ echo "The pick itself moved to caller SKILL body prose per web#193."
92
+ echo "See .claude/skills/pick-luminaries/SKILL.md."
93
+ exit 0
94
+ ;;
95
+ esac
96
+ fi