@thebassclef/lite 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/dist/cli.cjs +65 -0
  2. package/dist/cli.js +65 -0
  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 +1 -1
@@ -89,17 +89,15 @@ _sync_install_hooks() {
89
89
  local bassclef_dir="$1"
90
90
  local user_target_dir="${2:-$HOME/.claude/hooks}"
91
91
 
92
- local hook_names=(
93
- bug-diagnosis.sh
94
- tool-failure-diagnosis.sh
95
- trace-helper.sh
96
- deploy-guard.sh
97
- memory-write-audit.sh
98
- turn-prose-kiss-check.sh
99
- session-end.sh
100
- bet-doc-gate.sh
101
- substrate-clarity-gate.sh
102
- )
92
+ # Full-bundle iteration (goal 14c Step 6). Replaces the pre-Step-6
93
+ # hardcoded 9-name list. Every hook the old list carried still installs —
94
+ # either via dispatch (when header present) or via grace-window fallback
95
+ # (when absent). RFC S-2 HIGH cure per docs/rfcs/
96
+ # RFC-tier0-tests-install-class-dispatch-review.md.
97
+ #
98
+ # Architect-review 2026-09-15c folds (L-F2, S-F1, S-F2, M-F1, N-F1) apply
99
+ # equally to this operator sync path — mirrors the adopter template's
100
+ # Section 3b shape.
103
101
 
104
102
  mkdir -p "$user_target_dir" 2>/dev/null || true
105
103
 
@@ -117,14 +115,36 @@ _sync_install_hooks() {
117
115
  fi
118
116
  fi
119
117
 
120
- for hook_name in "${hook_names[@]}"; do
121
- local hook_src="$bassclef_dir/.claude/hooks/$hook_name"
122
- if [ ! -f "$hook_src" ]; then
123
- continue
124
- fi
118
+ # Bridge env var — lib uses BASSCLEF_TRACE_LOG.
119
+ # Default location standardized to $HOME/.claude/state/trace.log per N-F2
120
+ # (2026-09-15c review). Test-supplied env still wins.
121
+ : "${BASSCLEF_TRACE_LOG:=${BASSCLEF_TRACE_FILE:-$HOME/.claude/state/trace.log}}"
122
+ export BASSCLEF_TRACE_LOG
123
+ mkdir -p "$(dirname "$BASSCLEF_TRACE_LOG")" 2>/dev/null || true
124
+
125
+ local installed=0
126
+ local skipped_no_header=0
127
+ local skipped_invalid=0
128
+ local skipped_lib_missing=0
129
+ local failed=0
130
+
131
+ # S-F2 LOW — dir-exists guard before loop
132
+ if [ ! -d "$bassclef_dir/.claude/hooks" ]; then
133
+ return 0
134
+ fi
135
+
136
+ local hook_src hook_name target has_header
137
+ for hook_src in "$bassclef_dir/.claude/hooks/"*.sh; do
138
+ [ -f "$hook_src" ] || continue # T16: glob excludes .sh.bak
139
+
140
+ hook_name=$(basename "$hook_src")
141
+ target="$user_target_dir/$hook_name"
125
142
 
126
- local has_header=0
127
- if head -10 "$hook_src" 2>/dev/null | grep -qE '^# install-class:[[:space:]]*(operator|project|dual)[[:space:]]*$'; then
143
+ # M-F1 MED — rm existing symlink at target before cp
144
+ [ -L "$target" ] && rm -f "$target"
145
+
146
+ has_header=0
147
+ if head -10 "$hook_src" 2>/dev/null | grep -qE '^# install-class:[[:space:]]*(operator|project|dual)$'; then
128
148
  has_header=1
129
149
  fi
130
150
 
@@ -134,19 +154,47 @@ _sync_install_hooks() {
134
154
  # back to legacy operator-scope copy to preserve adopter
135
155
  # contract per c4 (settings.json command strings byte-identical).
136
156
  # shellcheck disable=SC1090 # dynamic source path
137
- if ! ( source "$installer_lib" 2>/dev/null && install_by_class "$hook_src" ) >/dev/null 2>&1; then
138
- cp "$hook_src" "$user_target_dir/" 2>/dev/null || true
139
- chmod +x "$user_target_dir/$hook_name" 2>/dev/null || true
157
+ if ( source "$installer_lib" 2>/dev/null && install_by_class "$hook_src" ) >/dev/null 2>&1; then
158
+ installed=$((installed + 1))
159
+ else
160
+ cp -p "$hook_src" "$target" 2>/dev/null || true
161
+ chmod +x "$target" 2>/dev/null || true
162
+ echo "$(date -u +%FT%TZ) dispatch-fail $hook_name" >> "$BASSCLEF_TRACE_LOG"
163
+ failed=$((failed + 1))
140
164
  fi
165
+ elif [ "$has_header" = "1" ] && [ "$dispatch_available" = "0" ]; then
166
+ # S-F1 MED — dispatch lib missing; trace explicitly (not silent)
167
+ cp -p "$hook_src" "$target" 2>/dev/null || true
168
+ chmod +x "$target" 2>/dev/null || true
169
+ echo "$(date -u +%FT%TZ) dispatch-lib-missing $hook_name" >> "$BASSCLEF_TRACE_LOG"
170
+ skipped_lib_missing=$((skipped_lib_missing + 1))
141
171
  else
142
172
  # ADR-058 c2 fallback — grace window through 2026-10-31.
143
- cp "$hook_src" "$user_target_dir/" 2>/dev/null || true
144
- chmod +x "$user_target_dir/$hook_name" 2>/dev/null || true
145
- if [ "$has_header" = "0" ] && command -v trace_log >/dev/null 2>&1; then
146
- trace_log "ok" "sync-install-hooks-skipped-no-header" "$hook_name"
173
+ # L-F2 MED additive-only during grace; do NOT rm legacy user-scope copies.
174
+ cp -p "$hook_src" "$target" 2>/dev/null || true
175
+ chmod +x "$target" 2>/dev/null || true
176
+ if [ "$has_header" = "0" ]; then
177
+ echo "$(date -u +%FT%TZ) skipped-no-header $hook_name" >> "$BASSCLEF_TRACE_LOG"
178
+ skipped_no_header=$((skipped_no_header + 1))
179
+ else
180
+ echo "$(date -u +%FT%TZ) skipped-invalid-header $hook_name" >> "$BASSCLEF_TRACE_LOG"
181
+ skipped_invalid=$((skipped_invalid + 1))
147
182
  fi
148
183
  fi
149
184
  done
185
+
186
+ # N-F1 MED — install summary with reason breakdown when non-zero
187
+ local skipped_total=$((skipped_no_header + skipped_invalid + skipped_lib_missing))
188
+ if [ "$skipped_total" = "0" ] && [ "$failed" = "0" ]; then
189
+ echo "bassclef-sync: installed $installed hooks; skipped 0; failed 0"
190
+ else
191
+ local breakdown=""
192
+ [ "$skipped_no_header" -gt 0 ] && breakdown="$breakdown, $skipped_no_header grace-window"
193
+ [ "$skipped_invalid" -gt 0 ] && breakdown="$breakdown, $skipped_invalid invalid-header"
194
+ [ "$skipped_lib_missing" -gt 0 ] && breakdown="$breakdown, $skipped_lib_missing dispatch-lib-missing"
195
+ breakdown="${breakdown#, }"
196
+ echo "bassclef-sync: installed $installed hooks; skipped $skipped_total ($breakdown); failed $failed"
197
+ fi
150
198
  }
151
199
 
152
200
  # === Test-mode guard (#915) ===
@@ -184,13 +232,23 @@ fi
184
232
  # Pre-rename ../canonical fallback removed 2026-06-24 per bassclef-upstream#317
185
233
  # Phase 0: zero adopters carry the canonical/ path; operator's local
186
234
  # ~/src/sunj-labs/canonical was removed before this cleanup.
187
- BASSCLEF_DIR="$CWD/../bassclef"
235
+ #
236
+ # 2026-09-15 (goal 14c Step 6): honor env-supplied BASSCLEF_DIR when set.
237
+ # Real operator machines do not set this env var — L237 default fires as before.
238
+ # Test fixtures set BASSCLEF_DIR to point at a mock bundle; honoring the
239
+ # env unblocks characterization tests for the operator sync path.
240
+ : "${BASSCLEF_DIR:=$CWD/../bassclef}"
188
241
  BASSCLEF_REPO="https://github.com/sunj-labs/bassclef.git"
189
242
  SYNC_STATUS="up-to-date"
190
243
  SYNC_NEW=""
191
244
 
192
245
  # === 1. Clone or pull bassclef ===
193
- if [ ! -d "$BASSCLEF_DIR/.claude" ]; then
246
+ # When BASSCLEF_DIR points at a valid bundle with .claude/hooks/ AND is NOT a
247
+ # git repo, skip clone/pull — env-supplied bundle (test fixture) is authoritative.
248
+ if [ -d "$BASSCLEF_DIR/.claude/hooks" ] && [ ! -d "$BASSCLEF_DIR/.git" ]; then
249
+ # env-override fixture path; skip clone/pull
250
+ :
251
+ elif [ ! -d "$BASSCLEF_DIR/.claude" ]; then
194
252
  if git clone "$BASSCLEF_REPO" "$BASSCLEF_DIR"; then
195
253
  SYNC_STATUS="freshly cloned"
196
254
  else
@@ -0,0 +1,492 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ # longrun-prep-compounding-sequence-check.sh — Stop hook that fires when
5
+ # state/markers/turn-prose-surface/longrun_prep_proposal.marker is
6
+ # present. Scans the latest assistant message for the 5 prescribed
7
+ # column headers from /longrun SKILL Step 1.5 (compounding-sequence
8
+ # analysis). Default V2 = strict (exit 2 on missing axes); explicit
9
+ # advisory toggle preserved for backward compat.
10
+ #
11
+ # Per bassclef-upstream#426 V2 flip. Calibration cycle (one /longrun
12
+ # prep after V1 ship) demonstrated the visibility gap: V1 advisory
13
+ # emitted to stderr; agent could not see its own advisory, missed
14
+ # 2-of-5 column at next prep. Flip closes the loop per ADR-035
15
+ # substrate-as-system.
16
+ #
17
+ # Per bet 28a WU-5d (V1 ship) + bet 30b extension (V2 flip with
18
+ # tonight's prep as eat-own-dogfood evidence).
19
+ #
20
+ # Sister hook pattern to turn-prose-kiss-check.sh (Stop hook scanning
21
+ # marker-gated transcript).
22
+ #
23
+ # Override per-call: SKIP_COMPOUNDING_AXIS_CHECK=1
24
+ # Backward compat: COMPOUNDING_AXIS_TOGGLE=advisory — exit 0 + finding
25
+ # COMPOUNDING_AXIS_TOGGLE=strict — exit 2 (default; explicit opt-in)
26
+ #
27
+ # Frozen stderr contract (per bassclef-upstream#1536 Hyrum finding):
28
+ # SECTION-EMPTY guard emits exactly:
29
+ # "🛑 COMPOUNDING-AXIS — BLOCKED (SECTION-EMPTY per-shape parse boundary; V2 strict default)"
30
+ # "🛑 COMPOUNDING-AXIS — ADVISORY (SECTION-EMPTY per-shape parse boundary; backward-compat opt-out)"
31
+ # Per-shape line prefix: " 🔍 <shape>"
32
+ # Any change to these strings updates this header comment as the observable contract.
33
+ #
34
+ # Tier 0 strict TDD: .claude/hooks/tests/longrun-prep-compounding-sequence-check.test.sh
35
+
36
+ set +e
37
+
38
+ INPUT=$(cat)
39
+ TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // ""' 2>/dev/null || echo "")
40
+ CWD=$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null || echo ".")
41
+
42
+ # Override
43
+ if [ "${SKIP_COMPOUNDING_AXIS_CHECK:-0}" = "1" ]; then
44
+ exit 0
45
+ fi
46
+
47
+ # Marker gate — only fire when /longrun prep proposal is being authored
48
+ MARKER_PATH="$CWD/state/markers/turn-prose-surface/longrun_prep_proposal.marker"
49
+ if [ ! -f "$MARKER_PATH" ]; then
50
+ exit 0
51
+ fi
52
+
53
+ # Auto-clean gate (Cure A per ticket #1555) — if a prior Stop event already
54
+ # fired this hook cleanly (exit 0) during this prep session, skip subsequent
55
+ # firings. The consumed sibling marker signals "primary marker's job is done".
56
+ # Operator manually re-touches the primary marker for the next prep proposal.
57
+ # Closes the doom loop where post-prep status turns kept tripping this hook.
58
+ CONSUMED_MARKER="$CWD/state/markers/turn-prose-surface/longrun_prep_proposal.marker.consumed"
59
+ if [ -f "$CONSUMED_MARKER" ]; then
60
+ exit 0
61
+ fi
62
+
63
+ # Need transcript to scan
64
+ if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
65
+ exit 0
66
+ fi
67
+
68
+ # Extract last assistant message text (per turn-prose-kiss-check pattern)
69
+ LAST_ASSISTANT_LINE=$(grep '"type":"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1)
70
+ [ -z "$LAST_ASSISTANT_LINE" ] && exit 0
71
+
72
+ MESSAGE_TEXT=$(echo "$LAST_ASSISTANT_LINE" | jq -r '.message.content[]? | select(.type == "text") | .text' 2>/dev/null)
73
+ [ -z "$MESSAGE_TEXT" ] && exit 0
74
+
75
+ # === Check compounding-sequence column headers ===
76
+ # Per /longrun SKILL Step 1.5: option table must include columns for the
77
+ # axis set. Hook accepts EITHER of two shapes during the ADR-031 grace
78
+ # window through 2026-10-31 (bassclef#1555 PR-1):
79
+ #
80
+ # 5-axis LEGACY (original, per bet 28a WU-5d):
81
+ # Compounding surface | Compounding rate | Foundation prereq |
82
+ # Inverse-dependency | Risk class
83
+ #
84
+ # 6-axis NEW (per plan doc 2026-09-09-compounding-axis-refactor):
85
+ # Deliverable | Problem | Value prop | Turns | Risk | Shipping priority
86
+ #
87
+ # Try each axis set. The hook passes if EITHER set matches. If neither
88
+ # matches, BLOCK naming the axes missing from the closest shape (fewest
89
+ # missing entries).
90
+
91
+ check_axis_any() {
92
+ # Returns 0 if any variant appears in MESSAGE_TEXT; else 1
93
+ local variant
94
+ for variant in "$@"; do
95
+ if echo "$MESSAGE_TEXT" | grep -qi "$variant"; then
96
+ return 0
97
+ fi
98
+ done
99
+ return 1
100
+ }
101
+
102
+ # --- 5-axis LEGACY set ---
103
+ FIVE_MISSING=()
104
+ check_axis_any "Compounding surface" "Compounds at" "Where the payoff shows up" \
105
+ || FIVE_MISSING+=("Compounding surface")
106
+ check_axis_any "Compounding rate" "| *Rate" "How often it fires" \
107
+ || FIVE_MISSING+=("Compounding rate")
108
+ check_axis_any "Foundation prereq" "What must be true first" \
109
+ || FIVE_MISSING+=("Foundation prereq")
110
+ check_axis_any "Inverse-dependency" "Does this teach" \
111
+ || FIVE_MISSING+=("Inverse-dependency")
112
+ check_axis_any "Risk class" "| *Risk" "What breaks if" \
113
+ || FIVE_MISSING+=("Risk class")
114
+
115
+ # --- 6-axis NEW set (bassclef#1555 PR-1) ---
116
+ SIX_MISSING=()
117
+ check_axis_any "Deliverable" "Ships when" \
118
+ || SIX_MISSING+=("Deliverable")
119
+ check_axis_any "Problem" "Pain" \
120
+ || SIX_MISSING+=("Problem")
121
+ check_axis_any "Value prop" "value proposition" \
122
+ || SIX_MISSING+=("Value prop")
123
+ check_axis_any "Turns" "Turn count" "Turn range" "Turn estimate" \
124
+ || SIX_MISSING+=("Turns")
125
+ check_axis_any "Risk" "| *Risk" "🟢" "🟡" "🔴" \
126
+ || SIX_MISSING+=("Risk")
127
+ check_axis_any "Shipping priority" "Priority:" "\bP[1-4]\b" "\bQ[1-4]\b" \
128
+ || SIX_MISSING+=("Shipping priority")
129
+
130
+ # Pick the shape with fewer missing axes; if neither set is complete, that
131
+ # shape is what MISSING reports. If either set is complete (empty missing
132
+ # array), the message passes the header check.
133
+ if [ "${#FIVE_MISSING[@]}" -eq 0 ] || [ "${#SIX_MISSING[@]}" -eq 0 ]; then
134
+ MISSING=()
135
+ elif [ "${#FIVE_MISSING[@]}" -le "${#SIX_MISSING[@]}" ]; then
136
+ MISSING=("${FIVE_MISSING[@]}")
137
+ else
138
+ MISSING=("${SIX_MISSING[@]}")
139
+ fi
140
+
141
+ # === Per-shape distinct-value check (bassclef-upstream#564) ===
142
+ # When 2+ shape section headers appear (**Shape a**, ### Shape a, etc.),
143
+ # the rule postcondition per Hoare requires each shape section to render
144
+ # all 5 axes with distinct values. A single header row with axis names
145
+ # but no per-shape values is R8 pattern — label without work.
146
+ #
147
+ # Per luminary consultation: Ousterhout (deep modules; SKILL + rule +
148
+ # hook = one contract with matching depth), Hoare (strong postcondition),
149
+ # Feathers (characterization tests use tonight's prep messages), Saltzer
150
+ # & Schroeder (complete mediation; every axis cell mediates), Beck (TDD).
151
+ #
152
+ # Falsifies R8 flavor A (aggregate cells collapse axes into one).
153
+ # This check only fires when message-level axis names ARE present
154
+ # (avoids double-firing on pure message-level miss).
155
+
156
+ # === Per-WU compounding-sequence check (bassclef-upstream#597) ===
157
+ # When prep proposal contains a produces/consumes WU table with 2+ WU
158
+ # rows (multi-step bet), the message must also render per-step compounding
159
+ # axes — either via "Compounding value per step" heading OR per-step axis
160
+ # rendering that names each step with its 5 axes.
161
+ #
162
+ # Convention worked for weeks (bet-doc convention). Tonight's PR #567
163
+ # tightened Step 1.5 to require 5 distinct axes per shape and the
164
+ # attention shift crowded the per-step habit out. Per ADR-035
165
+ # substrate-as-system tenet, mechanize the convention so it survives
166
+ # sibling-discipline tightening.
167
+ #
168
+ # Fires only when the message passes the shape-level checks first —
169
+ # avoids double-firing when the message-level column check already
170
+ # BLOCKED.
171
+
172
+ if [ "${#MISSING[@]}" -eq 0 ]; then
173
+ # Count WU rows in produces/consumes tables. WU row markers include:
174
+ # | **1** | ... or | **WU-1** | ... or **WU-1** in bullets.
175
+ # Use a permissive regex to catch canonical bet-doc formats.
176
+ WU_ROW_COUNT=$(echo "$MESSAGE_TEXT" | grep -cE '^\|[[:space:]]*\*\*(WU-)?[0-9]+\*\*' 2>/dev/null || echo 0)
177
+ # Sanitize (grep -c can print trailing content on some macOS bash)
178
+ WU_ROW_COUNT=$(echo "$WU_ROW_COUNT" | tr -cd '0-9')
179
+ [ -z "$WU_ROW_COUNT" ] && WU_ROW_COUNT=0
180
+
181
+ if [ "$WU_ROW_COUNT" -ge 2 ]; then
182
+ # Look for either the canonical section heading OR per-step axis rendering.
183
+ HAS_PER_WU_HEADING=$(echo "$MESSAGE_TEXT" | grep -ciE '(compounding value per step|per-step compounding|compounding sequence per step|compounding.axis.*wu)' 2>/dev/null | tr -cd '0-9')
184
+ [ -z "$HAS_PER_WU_HEADING" ] && HAS_PER_WU_HEADING=0
185
+
186
+ if [ "$HAS_PER_WU_HEADING" -eq 0 ]; then
187
+ TOGGLE="${COMPOUNDING_AXIS_TOGGLE:-strict}"
188
+ if [ "$TOGGLE" = "advisory" ]; then
189
+ LABEL="ADVISORY (per-step; backward-compat opt-out)"
190
+ else
191
+ LABEL="BLOCKED (per-step compounding columns missing; V1 strict default)"
192
+ fi
193
+
194
+ echo "" >&2
195
+ echo "🛑 COMPOUNDING-AXIS — $LABEL" >&2
196
+ echo "" >&2
197
+ echo " /longrun prep proposal has $WU_ROW_COUNT WU rows in the" >&2
198
+ echo " produces/consumes table but no per-step compounding-sequence" >&2
199
+ echo " rendering (R8 pattern — convention displaced by sibling" >&2
200
+ echo " tightening in PR #567)." >&2
201
+ echo "" >&2
202
+ echo " Per bassclef-upstream#597 + ADR-035 substrate-as-system:" >&2
203
+ echo " each step section must carry the 5 axes (plain-English preferred):" >&2
204
+ echo " - Where the payoff shows up (was: Compounding surface)" >&2
205
+ echo " - How often it fires (was: Compounding rate)" >&2
206
+ echo " - What must be true first (was: Foundation prereq)" >&2
207
+ echo " - Does this teach a shape later work reuses (was: Inverse-dependency)" >&2
208
+ echo " - What breaks if we ship this half-done (was: Risk class)" >&2
209
+ echo " Both label forms accepted through 2026-10-31 per ADR-040 D4." >&2
210
+ echo "" >&2
211
+ echo " Cure: add a '## Compounding value per step' section OR render" >&2
212
+ echo " each step with its 5 axes as bullets. See" >&2
213
+ echo " .claude/rules/compounding-sequence-fresh-analysis.md for shape." >&2
214
+ echo "" >&2
215
+ echo " Override: SKIP_COMPOUNDING_AXIS_CHECK=1 (logged)" >&2
216
+ echo " Backward compat: COMPOUNDING_AXIS_TOGGLE=advisory (exit 0)" >&2
217
+ echo "" >&2
218
+
219
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
220
+ if [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
221
+ # shellcheck disable=SC1091
222
+ source "$SCRIPT_DIR/trace-helper.sh"
223
+ trace_log "compounding-sequence-check" "$LABEL per-step-missing count=$WU_ROW_COUNT"
224
+ fi
225
+
226
+ [ "$TOGGLE" = "advisory" ] && exit 0
227
+ exit 2
228
+ fi
229
+ fi
230
+
231
+ # Per bassclef-upstream#1193 — SKILL rename #830 replaced "Shape a-c"
232
+ # with "Option a-e". Regex accepts both during the grace window
233
+ # through 2026-10-31 per .claude/rules/we-dont-break-adopters.md.
234
+ # After that date, only "Option [a-z]" matches.
235
+ SHAPES=$(echo "$MESSAGE_TEXT" | grep -Eo '(Shape [a-c]|Option [a-z])\b' | sort -u)
236
+
237
+ # Per bassclef-upstream#1376 — accept per-recommended-only shape.
238
+ # When the message opens with `**Recommend:** Option X` (or Shape X),
239
+ # the reader lands the pick in the first 15 lines. Non-recommended
240
+ # options carry their axes as one-liner cells in the scan-table row,
241
+ # not as full sections. The hook trusts the Recommend line: it treats
242
+ # the labeled option as REQUIRED (section + 5 axes) and treats every
243
+ # other option as OPTIONAL (a missing section stops SECTION-EMPTY from
244
+ # firing on that option).
245
+ #
246
+ # Per Tufte data-ink (drop fields that do not change the pick) +
247
+ # Prater progressive disclosure (default minimum; expand on ask).
248
+ # Sister #959 lands the SKILL body trim; this hook amend unblocks it.
249
+ RECOMMENDED_OPTION=$(echo "$MESSAGE_TEXT" | grep -Eo '\*\*Recommend[^*]*\*\*[[:space:]]*(Shape [a-c]|Option [a-z])' | head -1 | grep -Eo '(Shape [a-c]|Option [a-z])' | head -1)
250
+ if [ -z "$SHAPES" ]; then
251
+ SHAPE_COUNT=0
252
+ else
253
+ SHAPE_COUNT=$(echo "$SHAPES" | wc -l | tr -d ' ')
254
+ fi
255
+
256
+ # Per-shape check fires when 2+ options AND for recommended-only shape
257
+ # even with 1 option (bassclef-upstream#1376) — the recommended option's
258
+ # section must carry all 5 axes with distinct values.
259
+ if [ "$SHAPE_COUNT" -ge 2 ] || [ -n "$RECOMMENDED_OPTION" ]; then
260
+ PER_SHAPE_MISSING=""
261
+ SECTION_EMPTY_SHAPES=""
262
+ while IFS= read -r shape; do
263
+ [ -z "$shape" ] && continue
264
+ # Section boundaries require a heading anchor at line start
265
+ # (H2+ heading like `## Option a`, `### Option a`, `#### Option a`,
266
+ # or bold prefix `**Option a`) so mid-body cross-references like
267
+ # "same end-state as Option b" do not trigger a false section
268
+ # boundary. Cure per bassclef-upstream#1421 Option (a). H2+ range
269
+ # per bassclef-upstream#1532 — options nested under a container H3
270
+ # anchor (e.g., `### Compounding value — recommended only` or the
271
+ # regardless of nesting depth. H1 excluded by design.
272
+ SECTION=$(echo "$MESSAGE_TEXT" | awk -v want="$shape" '
273
+ BEGIN { in_section=0 }
274
+ /^(##+ |\*\*)(Shape [a-c]|Option [a-z])/ {
275
+ if ($0 ~ want) { in_section=1; print; next }
276
+ else if (in_section) { in_section=0 }
277
+ }
278
+ in_section { print }
279
+ ')
280
+
281
+ # Hoare precondition guard (bassclef-upstream#1536) — when shape name
282
+ # matches via grep-Eo but SECTION returns empty, the heading boundary
283
+ # is missing. Fail-safe per Saltzer-Schroeder: capture + emit distinct
284
+ # diagnostic under the existing banner + exit 2. Advisory toggle preserved.
285
+ #
286
+ # Recommended-only shape carve-out (bassclef-upstream#1376) — when the
287
+ # message declares a recommended option via **Recommend:** ..., non-
288
+ # recommended options may legitimately lack a section (they carry axes
289
+ # as scan-table cells only). Skip SECTION-EMPTY for those; still fire
290
+ # SECTION-EMPTY if the RECOMMENDED option has no section.
291
+ if [ -z "$SECTION" ]; then
292
+ if [ -n "$RECOMMENDED_OPTION" ] && [ "$shape" != "$RECOMMENDED_OPTION" ]; then
293
+ continue
294
+ fi
295
+ SECTION_EMPTY_SHAPES="${SECTION_EMPTY_SHAPES} 🔍 ${shape}\n"
296
+ continue
297
+ fi
298
+
299
+ # Recommended-only shape carve-out (bassclef-upstream#1376) —
300
+ # only the recommended option must carry all 5 axes distinctly in
301
+ # per-recommended-only mode. Non-recommended options with a partial
302
+ # section skip the per-axis check.
303
+ if [ -n "$RECOMMENDED_OPTION" ] && [ "$shape" != "$RECOMMENDED_OPTION" ]; then
304
+ continue
305
+ fi
306
+
307
+ # Grace window per bet 2026-07-16b WU-2 (#734) + bassclef#1555 PR-1 —
308
+ # per-shape check accepts EITHER 5-axis LEGACY or 6-axis NEW shape.
309
+ # Determine which axis set the section uses, then verify all axes of
310
+ # that set are present. If neither set is complete for this section,
311
+ # report the set with fewer missing axes.
312
+
313
+ # Try 5-axis set on this section
314
+ FIVE_SECTION_MISSING=()
315
+ echo "$SECTION" | grep -qiE "Compounding surface|Compounds at|Where the payoff shows up" \
316
+ || FIVE_SECTION_MISSING+=("Compounding surface")
317
+ echo "$SECTION" | grep -qiE "Compounding rate|\| *Rate|How often it fires" \
318
+ || FIVE_SECTION_MISSING+=("Compounding rate")
319
+ echo "$SECTION" | grep -qiE "Foundation prereq|What must be true first" \
320
+ || FIVE_SECTION_MISSING+=("Foundation prereq")
321
+ echo "$SECTION" | grep -qiE "Inverse-dependency|Does this teach" \
322
+ || FIVE_SECTION_MISSING+=("Inverse-dependency")
323
+ echo "$SECTION" | grep -qiE "Risk class|\| *Risk|What breaks if" \
324
+ || FIVE_SECTION_MISSING+=("Risk class")
325
+
326
+ # Try 6-axis set on this section
327
+ SIX_SECTION_MISSING=()
328
+ echo "$SECTION" | grep -qiE "Deliverable|Ships when" \
329
+ || SIX_SECTION_MISSING+=("Deliverable")
330
+ echo "$SECTION" | grep -qiE "Problem|Pain" \
331
+ || SIX_SECTION_MISSING+=("Problem")
332
+ echo "$SECTION" | grep -qiE "Value prop|value proposition" \
333
+ || SIX_SECTION_MISSING+=("Value prop")
334
+ echo "$SECTION" | grep -qiE "Turns|Turn count|Turn range|Turn estimate" \
335
+ || SIX_SECTION_MISSING+=("Turns")
336
+ echo "$SECTION" | grep -qiE "Risk|🟢|🟡|🔴" \
337
+ || SIX_SECTION_MISSING+=("Risk")
338
+ echo "$SECTION" | grep -qiE "Shipping priority|Priority:|\bP[1-4]\b|\bQ[1-4]\b" \
339
+ || SIX_SECTION_MISSING+=("Shipping priority")
340
+
341
+ # Pick the shape with fewer missing per this section. If either set is
342
+ # complete for this section, that section passes.
343
+ if [ "${#FIVE_SECTION_MISSING[@]}" -eq 0 ] || [ "${#SIX_SECTION_MISSING[@]}" -eq 0 ]; then
344
+ : # section passes — one of the two shapes is complete
345
+ elif [ "${#FIVE_SECTION_MISSING[@]}" -le "${#SIX_SECTION_MISSING[@]}" ]; then
346
+ for axis_name in "${FIVE_SECTION_MISSING[@]}"; do
347
+ PER_SHAPE_MISSING="${PER_SHAPE_MISSING} - ${shape}: missing '${axis_name}'\n"
348
+ done
349
+ else
350
+ for axis_name in "${SIX_SECTION_MISSING[@]}"; do
351
+ PER_SHAPE_MISSING="${PER_SHAPE_MISSING} - ${shape}: missing '${axis_name}'\n"
352
+ done
353
+ fi
354
+ done <<< "$SHAPES"
355
+
356
+ # Hoare precondition guard emit — SECTION-EMPTY takes precedence over
357
+ # "distinct values missing" because a failed parse is a different
358
+ # failure class than missing content. Per bassclef-upstream#1536.
359
+ if [ -n "$SECTION_EMPTY_SHAPES" ]; then
360
+ TOGGLE="${COMPOUNDING_AXIS_TOGGLE:-strict}"
361
+ if [ "$TOGGLE" = "advisory" ]; then
362
+ LABEL="ADVISORY (SECTION-EMPTY per-shape parse boundary; backward-compat opt-out)"
363
+ else
364
+ LABEL="BLOCKED (SECTION-EMPTY per-shape parse boundary; V2 strict default)"
365
+ fi
366
+ echo "" >&2
367
+ echo "🛑 COMPOUNDING-AXIS — $LABEL" >&2
368
+ echo "" >&2
369
+ echo " /longrun prep proposal references shapes but SECTION extraction" >&2
370
+ echo " returned empty. Precondition failure — heading boundary not found." >&2
371
+ echo " Per Hoare precondition contract (bassclef-upstream#1536)." >&2
372
+ echo "" >&2
373
+ echo " SECTION-EMPTY shapes:" >&2
374
+ printf "$SECTION_EMPTY_SHAPES" >&2
375
+ echo "" >&2
376
+ echo " Cure: give each shape a heading anchor at H2 through H6" >&2
377
+ echo " (e.g., '#### Option a') or bold prefix ('**Option a')." >&2
378
+ echo " See fixture 2026-09-08-step-1-4c-worked-example/ for the shape." >&2
379
+ echo "" >&2
380
+ echo " Override: SKIP_COMPOUNDING_AXIS_CHECK=1 (logged)" >&2
381
+ echo " Backward compat: COMPOUNDING_AXIS_TOGGLE=advisory (exit 0)" >&2
382
+ echo "" >&2
383
+
384
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
385
+ if [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
386
+ # shellcheck disable=SC1091
387
+ source "$SCRIPT_DIR/trace-helper.sh"
388
+ trace_log "compounding-sequence-check" "$LABEL section-empty"
389
+ fi
390
+
391
+ [ "$TOGGLE" = "advisory" ] && exit 0
392
+ exit 2
393
+ fi
394
+
395
+ if [ -n "$PER_SHAPE_MISSING" ]; then
396
+ TOGGLE="${COMPOUNDING_AXIS_TOGGLE:-strict}"
397
+ if [ "$TOGGLE" = "advisory" ]; then
398
+ LABEL="ADVISORY (per-shape; backward-compat opt-out)"
399
+ else
400
+ LABEL="BLOCKED (per-shape distinct values missing; V2 strict default)"
401
+ fi
402
+
403
+ echo "" >&2
404
+ echo "🛑 COMPOUNDING-AXIS — $LABEL" >&2
405
+ echo "" >&2
406
+ echo " /longrun prep proposal has $SHAPE_COUNT shape sections but" >&2
407
+ echo " the axes do not render distinctly per shape (R8 pattern)." >&2
408
+ echo "" >&2
409
+ echo " Per bassclef-upstream#564 + luminary consultation" >&2
410
+ echo " (Ousterhout/Hoare/Feathers/Saltzer-Schroeder/Beck):" >&2
411
+ echo " each shape section must carry all 5 axes as distinct values." >&2
412
+ echo "" >&2
413
+ echo " Missing per-shape values:" >&2
414
+ printf "$PER_SHAPE_MISSING" >&2
415
+ echo "" >&2
416
+ echo " Cure: for each shape (a, b, c...), render all 5 axes with" >&2
417
+ echo " their distinct values under that shape's section." >&2
418
+ echo "" >&2
419
+ echo " Override: SKIP_COMPOUNDING_AXIS_CHECK=1 (logged)" >&2
420
+ echo " Backward compat: COMPOUNDING_AXIS_TOGGLE=advisory (exit 0)" >&2
421
+ echo "" >&2
422
+
423
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
424
+ if [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
425
+ # shellcheck disable=SC1091
426
+ source "$SCRIPT_DIR/trace-helper.sh"
427
+ trace_log "compounding-sequence-check" "$LABEL per-shape-missing"
428
+ fi
429
+
430
+ [ "$TOGGLE" = "advisory" ] && exit 0
431
+ exit 2
432
+ fi
433
+ fi
434
+ # True-clean pass — all message-level axes present, per-WU + per-shape
435
+ # checks passed. Write consumed sibling marker per Cure A (ticket #1555)
436
+ # so subsequent Stop events skip the scan for this prep session.
437
+ touch "$CONSUMED_MARKER" 2>/dev/null || true
438
+ exit 0
439
+ fi
440
+
441
+ # === Emit finding ===
442
+ # Default V2 = strict. Explicit "advisory" toggle preserves backward compat.
443
+ TOGGLE="${COMPOUNDING_AXIS_TOGGLE:-strict}"
444
+
445
+ if [ "$TOGGLE" = "advisory" ]; then
446
+ LABEL="ADVISORY (backward-compat opt-out)"
447
+ else
448
+ LABEL="BLOCKED (V2 strict default)"
449
+ fi
450
+
451
+ echo "" >&2
452
+ echo "🛑 COMPOUNDING-AXIS — $LABEL" >&2
453
+ echo "" >&2
454
+ echo " /longrun prep proposal detected (marker present)." >&2
455
+ echo " Per /longrun SKILL Step 1.5 + bet 28a WU-5d:" >&2
456
+ echo " shape proposal table must include compounding-sequence columns." >&2
457
+ echo "" >&2
458
+ echo " Missing axes in latest assistant message (plain-English preferred):" >&2
459
+ for col in "${MISSING[@]}"; do
460
+ case "$col" in
461
+ "Compounding surface") echo " - Where the payoff shows up (was: Compounding surface)" >&2 ;;
462
+ "Compounding rate") echo " - How often it fires (was: Compounding rate)" >&2 ;;
463
+ "Foundation prereq") echo " - What must be true first (was: Foundation prereq)" >&2 ;;
464
+ "Inverse-dependency") echo " - Does this teach a shape later work reuses (was: Inverse-dependency)" >&2 ;;
465
+ "Risk class") echo " - What breaks if we ship this half-done (was: Risk class)" >&2 ;;
466
+ *) echo " - $col" >&2 ;;
467
+ esac
468
+ done
469
+ echo "" >&2
470
+ echo " Cure: add these axes to the option table before operator picks." >&2
471
+ echo " Each option row carries a value per axis. Both label forms accepted" >&2
472
+ echo " through 2026-10-31 per ADR-040 D4." >&2
473
+ echo "" >&2
474
+ echo " Override: SKIP_COMPOUNDING_AXIS_CHECK=1 (logged via trace)" >&2
475
+ echo " Backward compat: COMPOUNDING_AXIS_TOGGLE=advisory (exit 0)" >&2
476
+ echo "" >&2
477
+
478
+ # Trace log
479
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
480
+ if [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
481
+ # shellcheck disable=SC1091
482
+ source "$SCRIPT_DIR/trace-helper.sh"
483
+ trace_log "compounding-sequence-check" "$LABEL missing=${MISSING[*]}"
484
+ fi
485
+
486
+ # Advisory mode (backward compat): exit 0
487
+ if [ "$TOGGLE" = "advisory" ]; then
488
+ exit 0
489
+ fi
490
+
491
+ # Strict mode (default V2): exit 2 to block the stop
492
+ exit 2