@thebassclef/lite 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +85 -4
  3. package/dist/cli.cjs +1610 -0
  4. package/dist/cli.js +1587 -0
  5. package/dist/index.cjs +4 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +4 -0
  8. package/package.json +51 -3
  9. package/substrate/.bassclef/lite-manifest.json +1794 -0
  10. package/substrate/.claude/agents/architect.md +70 -0
  11. package/substrate/.claude/agents/builder.md +114 -0
  12. package/substrate/.claude/agents/designer.md +156 -0
  13. package/substrate/.claude/agents/reviewer.md +88 -0
  14. package/substrate/.claude/hooks/artifact-ingestion-gate.sh +357 -0
  15. package/substrate/.claude/hooks/assert-verify-steering.sh +77 -0
  16. package/substrate/.claude/hooks/bassclef-source-config-validate.sh +215 -0
  17. package/substrate/.claude/hooks/bassclef-sync.sh +634 -0
  18. package/substrate/.claude/hooks/compound-noun-scrub.sh +292 -0
  19. package/substrate/.claude/hooks/kiss-expansion-inject.sh +69 -0
  20. package/substrate/.claude/hooks/plain-english-steering.sh +156 -0
  21. package/substrate/.claude/hooks/post-skill-friction-check.sh +177 -0
  22. package/substrate/.claude/hooks/post-skill-telemetry.sh +62 -0
  23. package/substrate/.claude/hooks/pre-build-gate.sh +511 -0
  24. package/substrate/.claude/hooks/pre-commit-gate.sh +452 -0
  25. package/substrate/.claude/hooks/session-end.sh +433 -0
  26. package/substrate/.claude/hooks/session-reflection.sh +303 -0
  27. package/substrate/.claude/hooks/skill-body-grade-gate.sh +219 -0
  28. package/substrate/.claude/hooks/skill-body-intent-drift.sh +107 -0
  29. package/substrate/.claude/hooks/skill-step-list-check.sh +171 -0
  30. package/substrate/.claude/hooks/state-validate.sh +271 -0
  31. package/substrate/.claude/hooks/substrate-clarity-gate.sh +1110 -0
  32. package/substrate/.claude/hooks/temperance-gate.sh +147 -0
  33. package/substrate/.claude/hooks/testing-tier-enforce.sh +233 -0
  34. package/substrate/.claude/hooks/turn-prose-grade-measure.sh +219 -0
  35. package/substrate/.claude/hooks/turn-prose-kiss-check.sh +434 -0
  36. package/substrate/.claude/hooks/vocabulary-migration-check.sh +171 -0
  37. package/substrate/.claude/hooks/whereami-utc-gate.sh +142 -0
  38. package/substrate/.claude/luminaries/alan-cooper.md +170 -0
  39. package/substrate/.claude/luminaries/alistair-cockburn.md +140 -0
  40. package/substrate/.claude/luminaries/amazon-pr-faq.md +34 -0
  41. package/substrate/.claude/luminaries/ash-maurya.md +121 -0
  42. package/substrate/.claude/luminaries/bill-buxton.md +210 -0
  43. package/substrate/.claude/luminaries/charles-sanders-peirce.md +150 -0
  44. package/substrate/.claude/luminaries/david-ogilvy.md +192 -0
  45. package/substrate/.claude/luminaries/don-norman.md +173 -0
  46. package/substrate/.claude/luminaries/edward-tufte.md +179 -0
  47. package/substrate/.claude/luminaries/eric-evans.md +160 -0
  48. package/substrate/.claude/luminaries/frederick-brooks.md +50 -0
  49. package/substrate/.claude/luminaries/gang-of-four.md +157 -0
  50. package/substrate/.claude/luminaries/jason-fried-dhh.md +46 -0
  51. package/substrate/.claude/luminaries/jesse-james-garrett.md +154 -0
  52. package/substrate/.claude/luminaries/john-ousterhout.md +94 -0
  53. package/substrate/.claude/luminaries/karl-popper.md +132 -0
  54. package/substrate/.claude/luminaries/kent-beck.md +168 -0
  55. package/substrate/.claude/luminaries/linus-torvalds.md +218 -0
  56. package/substrate/.claude/luminaries/martin-fowler.md +164 -0
  57. package/substrate/.claude/luminaries/michael-feathers.md +125 -0
  58. package/substrate/.claude/luminaries/michael-nygard.md +118 -0
  59. package/substrate/.claude/luminaries/robert-c-martin.md +164 -0
  60. package/substrate/.claude/luminaries/saltzer-schroeder.md +194 -0
  61. package/substrate/.claude/luminaries/sophia-prater.md +193 -0
  62. package/substrate/.claude/luminaries/stephen-toulmin.md +131 -0
  63. package/substrate/.claude/rules/artifact-ingestion.md +179 -0
  64. package/substrate/.claude/rules/assert-only-after-verify.md +137 -0
  65. package/substrate/.claude/rules/blocked-items.md +146 -0
  66. package/substrate/.claude/rules/branching.md +28 -0
  67. package/substrate/.claude/rules/commit-conventions.md +22 -0
  68. package/substrate/.claude/rules/context-engineering.md +202 -0
  69. package/substrate/.claude/rules/context-management.md +85 -0
  70. package/substrate/.claude/rules/destructive-operations.md +69 -0
  71. package/substrate/.claude/rules/diagnosis.md +38 -0
  72. package/substrate/.claude/rules/guardrails.md +73 -0
  73. package/substrate/.claude/rules/identifier-leak-prevention.md +104 -0
  74. package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +98 -0
  75. package/substrate/.claude/rules/oo-ad-entry-point.md +117 -0
  76. package/substrate/.claude/rules/operator-facing-prose.md +196 -0
  77. package/substrate/.claude/rules/plain-english-discipline.md +154 -0
  78. package/substrate/.claude/rules/pr-body-shape.md +317 -0
  79. package/substrate/.claude/rules/pr-strategy.md +167 -0
  80. package/substrate/.claude/rules/sdlc-gates.md +149 -0
  81. package/substrate/.claude/rules/session-artifacts.md +236 -0
  82. package/substrate/.claude/rules/state-schema-validation.md +162 -0
  83. package/substrate/.claude/rules/whereami-load-bearing.md +202 -0
  84. package/substrate/.claude/rules/writing-craft-discipline.md +92 -0
  85. package/substrate/.claude/skills/build/SKILL.md +640 -0
  86. package/substrate/.claude/skills/decompose/SKILL.md +280 -0
  87. package/substrate/.claude/skills/diagnose/SKILL.md +297 -0
  88. package/substrate/.claude/skills/howdoi/SKILL.md +105 -0
  89. package/substrate/.claude/skills/ia-model/SKILL.md +108 -0
  90. package/substrate/.claude/skills/interaction-design/SKILL.md +112 -0
  91. package/substrate/.claude/skills/interpret-input/SKILL.md +180 -0
  92. package/substrate/.claude/skills/kiss/SKILL.md +449 -0
  93. package/substrate/.claude/skills/launch/SKILL.md +924 -0
  94. package/substrate/.claude/skills/lean-canvas/SKILL.md +332 -0
  95. package/substrate/.claude/skills/longrun/SKILL.md +426 -0
  96. package/substrate/.claude/skills/luminary/SKILL.md +481 -0
  97. package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
  98. package/substrate/.claude/skills/onboard-repo/SKILL.md +2190 -0
  99. package/substrate/.claude/skills/pattern-review/SKILL.md +99 -0
  100. package/substrate/.claude/skills/personas/SKILL.md +207 -0
  101. package/substrate/.claude/skills/promote/SKILL.md +283 -0
  102. package/substrate/.claude/skills/requirement/SKILL.md +98 -0
  103. package/substrate/.claude/skills/retro/SKILL.md +117 -0
  104. package/substrate/.claude/skills/riff/SKILL.md +114 -0
  105. package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
  106. package/substrate/.claude/skills/session-end/SKILL.md +309 -0
  107. package/substrate/.claude/skills/session-log/SKILL.md +299 -0
  108. package/substrate/.claude/skills/skills/SKILL.md +228 -0
  109. package/substrate/.claude/skills/spec/SKILL.md +105 -0
  110. package/substrate/.claude/skills/sprint/SKILL.md +392 -0
  111. package/substrate/.claude/skills/stage/SKILL.md +384 -0
  112. package/substrate/.claude/skills/state-a-problem/SKILL.md +185 -0
  113. package/substrate/.claude/skills/temperance/SKILL.md +108 -0
  114. package/substrate/.claude/skills/use-case/SKILL.md +417 -0
  115. package/substrate/.claude/skills/user-stories/SKILL.md +268 -0
  116. package/substrate/.claude/skills/value-prop/SKILL.md +251 -0
  117. package/substrate/.claude/skills/verify/SKILL.md +160 -0
  118. package/substrate/.claude/skills/whereami/SKILL.md +307 -0
  119. package/substrate/AGENTS.md +79 -0
  120. package/substrate/CLAUDE-lite.md +85 -0
  121. package/substrate/CODE_OF_CONDUCT.md +28 -0
  122. package/substrate/CONTRIBUTING.md +177 -0
  123. package/substrate/README.md +173 -0
  124. package/substrate/SECURITY.md +19 -0
  125. package/substrate/architecture/decisions/ADR-029-release-pipeline.md +79 -0
  126. package/substrate/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
  127. package/substrate/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
  128. package/substrate/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
  129. package/substrate/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
  130. package/substrate/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
  131. package/substrate/lib/clean-artifacts-sweep.sh +112 -0
  132. package/substrate/lib/code-comment-discipline.sh +144 -0
  133. package/substrate/lib/composer-preflight.sh +459 -0
  134. package/substrate/lib/hook-inject.sh +255 -0
  135. package/substrate/lib/luminary-pick.sh +96 -0
  136. package/substrate/lib/output-discipline.sh +143 -0
  137. package/substrate/lib/rewrite-check.sh +214 -0
  138. package/substrate/lib/state.sh +1372 -0
  139. package/substrate/lib/telemetry.sh +205 -0
  140. package/substrate/lib/tier-check.sh +138 -0
  141. package/substrate/lib/tier-dependency-audit.sh +933 -0
  142. package/substrate/presence/install/bassclef-hook-connect.sh +178 -0
  143. package/substrate/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
  144. package/substrate/presence/install/bassclef-sync.template.sh +2076 -0
  145. package/substrate/presence/install/schedule-auto-save.cron.sh +88 -0
  146. package/substrate/presence/install/schedule-auto-save.taskscheduler.md +122 -0
  147. package/substrate/scripts/aggregate-telemetry.sh +217 -0
  148. package/substrate/scripts/analyze-tier-dependencies.sh +239 -0
  149. package/substrate/scripts/generate-lite-manifest.sh +489 -0
  150. package/substrate/scripts/generate-tier-manifest.sh +28 -0
  151. package/substrate/scripts/intent-drift-check.sh +456 -0
  152. package/substrate/scripts/lite-manifest-drift-check.sh +146 -0
  153. package/substrate/scripts/render-lite-manifest-doc.sh +150 -0
  154. package/substrate/standards/bassclef-configs-schema.md +232 -0
  155. package/substrate/standards/bassclef-evolution.md +143 -0
  156. package/substrate/standards/bassclef-internal-jargon.md +244 -0
  157. package/substrate/standards/bassclef-managed-sentinel.md +96 -0
  158. package/substrate/standards/bassclef-source-config.md +228 -0
  159. package/substrate/standards/deferred-actions-schema.md +204 -0
  160. package/substrate/standards/docs-sync-allowlist.md +76 -0
  161. package/substrate/standards/graceful-exit.md +227 -0
  162. package/substrate/standards/hook-idempotency.md +102 -0
  163. package/substrate/standards/lite-manifest-schema-changes.md +75 -0
  164. package/substrate/standards/luminary-problem-patterns.md +481 -0
  165. package/substrate/standards/ogilvy-writing-rules.md +225 -0
  166. package/substrate/standards/opener-discipline.md +96 -0
  167. package/substrate/standards/operator-facing-prose-discipline.md +201 -0
  168. package/substrate/standards/project-directory-layout.md +276 -0
  169. package/substrate/standards/release-tagging.md +137 -0
  170. package/substrate/standards/sdlc-compliance.md +286 -0
  171. package/substrate/standards/secrets-lifecycle.md +210 -0
  172. package/substrate/standards/security-scanner-adapter.md +145 -0
  173. package/substrate/standards/skill-output-discipline.md +90 -0
  174. package/substrate/standards/state-spine.md +511 -0
  175. package/substrate/standards/tech-stack-config.md +109 -0
  176. package/substrate/standards/tier-dependency-analysis.md +167 -0
  177. package/substrate/standards/tier-runtime-deps/lite.md +57 -0
  178. package/substrate/standards/tier-tag-schema.md +155 -0
  179. package/substrate/standards/whereami-schema.md +301 -0
  180. package/substrate/standards/writing-guide.md +213 -0
  181. package/substrate/templates/deferred-action-template.md +45 -0
  182. package/substrate/templates/whereami-template.md +101 -0
@@ -0,0 +1,434 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ # Turn-prose /kiss check — Stop hook that scans the most recent assistant
5
+ # message in the transcript against bassclef-internal jargon BLOCK terms.
6
+ #
7
+ # Per bassclef#1051: closes the bassclef#1027 "no hook can reach turn prose"
8
+ # gap. Stop hooks receive transcript_path in stdin and CAN read the most
9
+ # recent assistant message.
10
+ #
11
+ # Sibling pattern: .claude/hooks/pr-body-scrub-check.sh
12
+ # Single source of truth for BLOCK terms: standards/bassclef-internal-jargon.md
13
+ # Allowlist per bassclef#763: substrate, bassclef, cameo, Studio, Band, bandleader
14
+ #
15
+ # Input (stdin): JSON with transcript_path + session_id
16
+ # {"transcript_path": "/path/to/transcript.jsonl", "session_id": "..."}
17
+ #
18
+ # Toggle (env or .claude/bassclef-configs.jsonc):
19
+ # PROSE_DISCIPLINE_TOGGLE=true — advisory (exit 0, findings to stderr) [DEFAULT]
20
+ # PROSE_DISCIPLINE_TOGGLE=strict — strict (exit 2, blocks stop, forces rewrite)
21
+ # PROSE_DISCIPLINE_TOGGLE=false — silent (exit 0, no scan)
22
+ #
23
+ # Override per-call:
24
+ # SKIP_TURN_PROSE_KISS=1 — skip scan, exit 0
25
+ #
26
+ # Exit codes:
27
+ # 0 — pass (no findings, advisory mode, toggle off, or override set)
28
+ # 2 — block stop (strict mode + findings present)
29
+ #
30
+ # Reads:
31
+ # - $TRANSCRIPT_PATH (jsonl, one event per line)
32
+ # - standards/bassclef-internal-jargon.md (BLOCK terms)
33
+ # - .claude/bassclef-configs.jsonc (toggle when env not set)
34
+
35
+ set +e
36
+
37
+ # === Override path ===
38
+ if [ "${SKIP_TURN_PROSE_KISS:-0}" = "1" ]; then
39
+ exit 0
40
+ fi
41
+
42
+ # === Source install-class-aware resolver lib (per standards/hook-install-class.md) ===
43
+ # This hook is `dual` — runs under both project install (<repo>/.claude/hooks/)
44
+ # and operator install ($HOME/.claude/hooks/). Pre-cure: SCRIPT_DIR/../../standards
45
+ # was hardcoded; operator-install fell back to an 8-term defensive list. Cure:
46
+ # resolve_standards_path + resolve_config_path from lib/hook-inject.sh.
47
+ # Lib lookup layered: project-relative → HOME-relative (bassclef-sync convention).
48
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
49
+
50
+ # === Hook liveness heartbeat (WU-3 of bet 2026-07-31d; closes #1002) ===
51
+ # Silent-fail — a missing lib never crashes the hook.
52
+ {
53
+ for _hb_c in "${SCRIPT_DIR}/../../lib/hook-heartbeat.sh" "${HOME:-/}/lib/hook-heartbeat.sh"; do
54
+ [ -f "$_hb_c" ] && source "$_hb_c" && heartbeat_mark "turn-prose-kiss-check" && break
55
+ done
56
+ unset _hb_c
57
+ } 2>/dev/null || true
58
+
59
+ for _candidate in \
60
+ "${SCRIPT_DIR}/../../lib/hook-inject.sh" \
61
+ "${HOME_OVERRIDE:-${HOME:-/}}/lib/hook-inject.sh"; do
62
+ if [ -f "$_candidate" ]; then
63
+ # shellcheck disable=SC1090
64
+ source "$_candidate"
65
+ break
66
+ fi
67
+ done
68
+ unset _candidate
69
+
70
+ # === Parse stdin JSON ===
71
+ STDIN_JSON=$(cat 2>/dev/null || echo '{}')
72
+ TRANSCRIPT_PATH=$(echo "$STDIN_JSON" | jq -r '.transcript_path // ""' 2>/dev/null)
73
+
74
+ # Graceful skip if no transcript path provided
75
+ if [ -z "$TRANSCRIPT_PATH" ]; then
76
+ exit 0
77
+ fi
78
+
79
+ # Graceful skip if transcript file missing
80
+ if [ ! -f "$TRANSCRIPT_PATH" ]; then
81
+ exit 0
82
+ fi
83
+
84
+ # === Resolve config file (install-class-aware via lib) ===
85
+ if declare -f resolve_config_path >/dev/null 2>&1; then
86
+ CONFIG_FILE=$(resolve_config_path 2>/dev/null) || CONFIG_FILE=""
87
+ else
88
+ CONFIG_FILE="${SCRIPT_DIR}/../bassclef-configs.jsonc"
89
+ fi
90
+
91
+ # === Resolve toggle ===
92
+ TOGGLE="${PROSE_DISCIPLINE_TOGGLE:-}"
93
+ if [ -z "$TOGGLE" ]; then
94
+ if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
95
+ # Strip // comments before jq parses (jsonc is JSON + comments)
96
+ TOGGLE=$(sed 's|//.*||g' "$CONFIG_FILE" 2>/dev/null \
97
+ | jq -r '.prose_discipline.kiss_words_turn_prose // "true"' 2>/dev/null)
98
+ fi
99
+ TOGGLE="${TOGGLE:-true}"
100
+ fi
101
+
102
+ # Boolean true normalizes to "true" (advisory). false → silent. "strict" → strict.
103
+ case "$TOGGLE" in
104
+ true|"true") MODE="advisory" ;;
105
+ strict|"strict") MODE="strict" ;;
106
+ false|"false") exit 0 ;;
107
+ *) MODE="advisory" ;;
108
+ esac
109
+
110
+ # bassclef#1171 — read per-place strict escalation list. When global mode is
111
+ # advisory but the message matches a named place pattern AND has BLOCK findings,
112
+ # the hook escalates that message to strict (exit 2).
113
+ # Env var precedence over config: explicit empty string (env set to "") means
114
+ # "no places, do not consult config". Unset means "fall back to config".
115
+ if [ -n "${PROSE_DISCIPLINE_STRICT_PLACES+set}" ]; then
116
+ STRICT_PLACES="$PROSE_DISCIPLINE_STRICT_PLACES"
117
+ else
118
+ STRICT_PLACES=""
119
+ if [ -f "$CONFIG_FILE" ]; then
120
+ STRICT_PLACES=$(sed 's|//.*||g' "$CONFIG_FILE" 2>/dev/null \
121
+ | jq -r '.prose_discipline.kiss_words_strict_places // [] | join(",")' 2>/dev/null)
122
+ fi
123
+ fi
124
+
125
+ # === Extract most recent assistant message ===
126
+ # Transcript is JSONL: one event per line. Last line with type=assistant.
127
+ # Content is an array of content blocks; collect text from:
128
+ # - "text" blocks (assistant prose to operator)
129
+ # - "tool_use" blocks where name == "AskUserQuestion" (option text the
130
+ # operator reads — same operator-facing surface as prose). Pulls the
131
+ # question, each option label, and each option description.
132
+ # Other tool_use blocks (Bash, Read, Edit, etc.) are NOT scanned — their
133
+ # inputs are not operator-facing prose.
134
+ LAST_ASSISTANT_LINE=$(grep '"type":"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1)
135
+ if [ -z "$LAST_ASSISTANT_LINE" ]; then
136
+ # No assistant message in transcript yet — graceful skip
137
+ exit 0
138
+ fi
139
+
140
+ MESSAGE_TEXT=$(echo "$LAST_ASSISTANT_LINE" \
141
+ | jq -r '.message.content[]? |
142
+ if .type == "text" then
143
+ .text
144
+ elif .type == "tool_use" and .name == "AskUserQuestion" then
145
+ [
146
+ (.input.questions[]?
147
+ | (.question // ""),
148
+ (.options[]? | (.label // ""), (.description // "")))
149
+ ] | join("\n")
150
+ else
151
+ empty
152
+ end' 2>/dev/null)
153
+ if [ -z "$MESSAGE_TEXT" ]; then
154
+ exit 0
155
+ fi
156
+
157
+ # === Strip code blocks + inline backtick spans + URLs ===
158
+ # Same logic as pr-body-scrub-check.sh — technical refs are not prose
159
+ SCRUBBED=$(echo "$MESSAGE_TEXT" | awk '
160
+ BEGIN { in_code = 0 }
161
+ /^```/ { in_code = !in_code; next }
162
+ in_code { next }
163
+ { print }
164
+ ' | sed -E 's/`[^`]*`//g; s|https?://[^ )]+||g')
165
+
166
+ # === Load BLOCK terms from standards/bassclef-internal-jargon.md ===
167
+ # Pattern matches pr-body-scrub-check.sh — read BLOCK-tier rows from the table.
168
+ # Path resolution via lib's resolve_standards_path (project → env → user → fail).
169
+ # Falls back to legacy SCRIPT_DIR-relative path when none of the lib's 3 layers
170
+ # resolve — preserves project-class behavior under direct invocation without
171
+ # CLAUDE_PROJECT_DIR set (operator-install always sets CLAUDE_PROJECT_DIR).
172
+ STANDARDS_FILE=""
173
+ if declare -f resolve_standards_path >/dev/null 2>&1; then
174
+ STANDARDS_FILE=$(resolve_standards_path "bassclef-internal-jargon.md" 2>/dev/null) || STANDARDS_FILE=""
175
+ fi
176
+ if [ -z "$STANDARDS_FILE" ] || [ ! -f "$STANDARDS_FILE" ]; then
177
+ STANDARDS_FILE="${KISS_STANDARDS_FILE:-${SCRIPT_DIR}/../../standards/bassclef-internal-jargon.md}"
178
+ fi
179
+
180
+ JARGON_TERMS=()
181
+ if [ -f "$STANDARDS_FILE" ]; then
182
+ while IFS= read -r line; do
183
+ JARGON_TERMS+=("$line")
184
+ done < <(awk -F'|' '
185
+ /^\| [a-zA-Z0-9-]+ / && /BLOCK *\|$/ {
186
+ gsub(/^ +| +$/, "", $2)
187
+ print $2
188
+ }
189
+ ' "$STANDARDS_FILE")
190
+ fi
191
+
192
+ # Defensive fallback if standards file missing or empty
193
+ if [ ${#JARGON_TERMS[@]} -eq 0 ]; then
194
+ JARGON_TERMS=(
195
+ "composer"
196
+ "primitive"
197
+ "tier-preset"
198
+ "load-bearing"
199
+ "blast radius"
200
+ "compose-with"
201
+ "scope-bounded"
202
+ "operationalize"
203
+ )
204
+ fi
205
+
206
+ # === Scan for BLOCK terms ===
207
+ FINDINGS=""
208
+ FINDING_COUNT=0
209
+ for term in "${JARGON_TERMS[@]}"; do
210
+ # Case-insensitive whole-word match
211
+ count=$(echo "$SCRUBBED" | grep -ioE "\b${term}\b" 2>/dev/null | wc -l | tr -d ' ')
212
+ if [ "$count" -gt 0 ]; then
213
+ FINDINGS="${FINDINGS}- \`${term}\` (${count}x)"$'\n'
214
+ FINDING_COUNT=$((FINDING_COUNT + count))
215
+ fi
216
+ done
217
+
218
+ # === Scan for hyphenated-proper-noun + structural-metaphor suffix (bassclef#101) ===
219
+ # Pattern: Capitalized-Word + "-" + Capitalized-Word (one or more hyphen pairs)
220
+ # + space + one of (triangle | pyramid | spectrum | hexagon | gate)
221
+ # Catches dense compound phrases like "Saltzer-Schroeder triangle",
222
+ # "Linus-Torvalds gate", "Beck-Cunningham pyramid" — academic/architectural
223
+ # jargon shape that slips past individual-word checks.
224
+ # Negative cases preserved: "API gateway" (not hyphenated proper noun),
225
+ # "circuit breaker" (suffix not in list).
226
+ PROPER_NOUN_SHAPE_MATCHES=$(echo "$SCRUBBED" | grep -oE \
227
+ '[A-Z][a-z]+(-[A-Z][a-z]+)+ (triangle|pyramid|spectrum|hexagon|gate)\b' \
228
+ 2>/dev/null)
229
+ if [ -n "$PROPER_NOUN_SHAPE_MATCHES" ]; then
230
+ shape_count=$(echo "$PROPER_NOUN_SHAPE_MATCHES" | wc -l | tr -d ' ')
231
+ FINDINGS="${FINDINGS}- dense compound shape (${shape_count}x) — proper-noun + structural-metaphor — see bassclef#101 — rewrite in plain English"$'\n'
232
+ FINDING_COUNT=$((FINDING_COUNT + shape_count))
233
+ fi
234
+
235
+ # === Scan for option-label Greek (bassclef#1286) ===
236
+ # Pattern: (option|shape|pick|tier|approach|step|item|path|alternative) + whitespace + Greek letter
237
+ # Greek lowercase α-ω: α-ω ; Greek uppercase Α-Ω: Α-Ω
238
+ # Using ripgrep-compatible PCRE class via grep -P (GNU grep) when available; falling
239
+ # back to a literal alternation of common Greek letters for portability.
240
+ OPTION_LABEL_GREEK_MATCHES=$(echo "$SCRUBBED" | grep -ioE \
241
+ '(option|shape|pick|tier|approach|step|item|path|alternative)[[:space:]]+(α|β|γ|δ|ε|ζ|η|θ|ι|κ|λ|μ|ν|ξ|ο|π|ρ|σ|τ|υ|φ|χ|ψ|ω|Α|Β|Γ|Δ|Ε|Ζ|Η|Θ|Ι|Κ|Λ|Μ|Ν|Ξ|Ο|Π|Ρ|Σ|Τ|Υ|Φ|Χ|Ψ|Ω)' \
242
+ 2>/dev/null)
243
+ if [ -n "$OPTION_LABEL_GREEK_MATCHES" ]; then
244
+ option_label_count=$(echo "$OPTION_LABEL_GREEK_MATCHES" | wc -l | tr -d ' ')
245
+ FINDINGS="${FINDINGS}- option-label Greek (${option_label_count}x) — see bassclef#1286 + .claude/rules/option-label-discipline.md — use a/b/c or 1/2/3 instead"$'\n'
246
+ FINDING_COUNT=$((FINDING_COUNT + option_label_count))
247
+ fi
248
+
249
+ # === Scan for retired WU-N vocab in new prose (bassclef-web#202) ===
250
+ # ADR-040 D1 renamed "WU-N" → "Step N". Grace window through 2026-10-31 preserves
251
+ # READ compatibility via standards/vocabulary-migration.json — historical refs in
252
+ # closed PRs, past chronicles, memory files stay as-is. NEW authored prose uses
253
+ # the new terms. Backticked refs like `WU-3` are stripped by SCRUBBED at L164 so
254
+ # quoted historical goal IDs pass through cleanly. Only unbacked WU-N in prose fires.
255
+ WU_N_VOCAB_MATCHES=$(echo "$SCRUBBED" | grep -oE '\bWU-[0-9]+\b' 2>/dev/null)
256
+ if [ -n "$WU_N_VOCAB_MATCHES" ]; then
257
+ wu_n_count=$(echo "$WU_N_VOCAB_MATCHES" | wc -l | tr -d ' ')
258
+ FINDINGS="${FINDINGS}- retired WU-N vocab (${wu_n_count}x) — see bassclef-web#202 + ADR-040 D1 rename table — say Step N in new prose; wrap historical goal IDs in backticks"$'\n'
259
+ FINDING_COUNT=$((FINDING_COUNT + wu_n_count))
260
+ fi
261
+
262
+ # === Per-place strict escalation (bassclef#1171) ===
263
+ # When global mode is advisory + BLOCK findings exist + the message matches
264
+ # a named strict-place pattern, escalate that message to strict (exit 2).
265
+ #
266
+ # Two detection paths per place:
267
+ # 1. Marker file at state/markers/turn-prose-surface/<surface>.marker —
268
+ # the SKILL.md procedure touches this before posting; precise. (bassclef#1304)
269
+ # 2. Content pattern grep on SCRUBBED — heuristic fallback for the V1
270
+ # 3 places (closeout_blocks, summary_tables, next_pickup).
271
+ EFFECTIVE_MODE="$MODE"
272
+ ESCALATING_PLACE=""
273
+
274
+ # Repo root for marker lookup
275
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
276
+ SURFACE_MARKER_DIR="${REPO_ROOT}/state/markers/turn-prose-surface"
277
+
278
+ if [ "$MODE" = "advisory" ] && [ "$FINDING_COUNT" -gt 0 ] && [ -n "$STRICT_PLACES" ]; then
279
+ IFS=',' read -ra PLACE_ARRAY <<< "$STRICT_PLACES"
280
+ for place in "${PLACE_ARRAY[@]}"; do
281
+ place_trimmed=$(echo "$place" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
282
+
283
+ # Path 1: marker-file detection (precise; bassclef#1304)
284
+ if [ -f "${SURFACE_MARKER_DIR}/${place_trimmed}.marker" ]; then
285
+ EFFECTIVE_MODE="strict"
286
+ ESCALATING_PLACE="${place_trimmed} (marker)"
287
+ break
288
+ fi
289
+
290
+ # Path 2: content-pattern heuristics (V1 fallback)
291
+ case "$place_trimmed" in
292
+ closeout_blocks)
293
+ if echo "$SCRUBBED" | grep -qE '^## Closeout audit'; then
294
+ EFFECTIVE_MODE="strict"
295
+ ESCALATING_PLACE="closeout_blocks"
296
+ break
297
+ fi
298
+ ;;
299
+ summary_tables)
300
+ if echo "$SCRUBBED" | grep -qE '^## Status[[:space:]]*$'; then
301
+ EFFECTIVE_MODE="strict"
302
+ ESCALATING_PLACE="summary_tables"
303
+ break
304
+ fi
305
+ ;;
306
+ next_pickup)
307
+ if echo "$SCRUBBED" | grep -qE '^## Next pickup'; then
308
+ EFFECTIVE_MODE="strict"
309
+ ESCALATING_PLACE="next_pickup"
310
+ break
311
+ fi
312
+ ;;
313
+ esac
314
+ done
315
+ fi
316
+
317
+ # === Skill-dispatch citation check (V1 ADVISORY — bassclef-upstream#374) ===
318
+ # When a /longrun-surface marker exists at state/markers/turn-prose-surface/
319
+ # <surface>.marker, scan the message for a skill-dispatch citation per
320
+ # operator-facing-prose.md Rule 3. Emit ADVISORY if citation missing.
321
+ # Named surfaces: longrun_prep_proposal, longrun_checkpoint, longrun_closeout, status_update.
322
+ SKILL_CITATION_MISSING=""
323
+ for surface in longrun_prep_proposal longrun_checkpoint longrun_closeout status_update; do
324
+ if [ -f "${SURFACE_MARKER_DIR}/${surface}.marker" ]; then
325
+ # Citation patterns: backtick-wrapped /skill mention, or named discipline blocks.
326
+ if ! echo "$MESSAGE_TEXT" | grep -qE '(via `/[a-z][a-z -]*`|per `/[a-z][a-z -]*`|\(via /[a-z-]+\)|drafted by `/[a-z][a-z -]*`|## /temperance \+ /luminary \+ /loop discipline|\*\*/temperance\*\*|\*\*/luminary\*\*|\*\*Flash \(via `|via /[a-z-]+ )'; then
327
+ SKILL_CITATION_MISSING="${SKILL_CITATION_MISSING}${surface} "
328
+ fi
329
+ fi
330
+ done
331
+
332
+ if [ -n "$SKILL_CITATION_MISSING" ]; then
333
+ cat >&2 <<EOF
334
+ [turn-prose-kiss-check: ADVISORY — skill-dispatch citation missing on surface(s): ${SKILL_CITATION_MISSING}]
335
+ Per .claude/rules/operator-facing-prose.md Rule 3 (bassclef-upstream#374):
336
+ every /longrun-dispatched session-conversation surface declares the
337
+ dispatched skill via inline citation (e.g., "via \`/value-prop flash\`",
338
+ "drafted by \`/state-a-problem brief\`") or a short discipline-marker line.
339
+ V1 advisory; V2 (deferred) BLOCKs net-new surfaces without citation.
340
+ EOF
341
+ fi
342
+
343
+ # === Appositive presence check (V1 ADVISORY — bassclef-upstream#1313) ===
344
+ # Per .claude/rules/operator-facing-prose.md Rule 1 broader appositive
345
+ # amendment. Scans for shorthand patterns and checks each match for an
346
+ # adjacent appositive (parenthetical, comma phrase, em-dash) within 40
347
+ # characters. Emits ADVISORY line per shorthand without adjacent
348
+ # appositive. Structural check only — operator judgment catches
349
+ # shorthand-inside-appositive.
350
+ #
351
+ # Override per-call: SKIP_APPOSITIVE_CHECK=1
352
+ if [ "${SKIP_APPOSITIVE_CHECK:-0}" != "1" ]; then
353
+ APPOSITIVE_FINDINGS=$(printf '%s' "$MESSAGE_TEXT" | python3 -c '
354
+ import re, sys
355
+
356
+ text = sys.stdin.read()
357
+
358
+ # Shorthand patterns — session-invented or bassclef substrate refs
359
+ patterns = [
360
+ (r"(?<![a-zA-Z0-9_/])#(\d+)\b", "ticket"),
361
+ (r"\bBucket\s+[A-Z]\d?[a-z]?\b", "Bucket label"),
362
+ (r"\bOption\s+[a-z]\b", "Option letter"),
363
+ (r"\bShip\s+\d+\b", "Ship label"),
364
+ (r"\bWave\s+\d+\b", "Wave label"),
365
+ ]
366
+
367
+ # Appositive shapes within 40 chars after the shorthand:
368
+ # \s*( — optional whitespace then
369
+ # \( — parenthetical
370
+ # |,\s+\w — comma + space + word
371
+ # |—\s*\w — em-dash + word
372
+ # |:\s+\w — colon + word
373
+ # |\s—\s+\w — spaced em-dash + word
374
+ # )
375
+ appositive_re = re.compile(r"^\s*(\(|,\s+\w|—\s*\w|:\s+\w|-\s+\w)")
376
+
377
+ flagged = []
378
+ for pattern, label in patterns:
379
+ for m in re.finditer(pattern, text):
380
+ end = m.end()
381
+ following = text[end:end+40]
382
+ if not appositive_re.match(following):
383
+ # Extract surrounding context for the finding
384
+ context_start = max(0, m.start() - 10)
385
+ context_end = min(len(text), m.end() + 30)
386
+ snippet = text[context_start:context_end].replace("\n", " ")
387
+ flagged.append(f"{label} \"{m.group(0)}\" — no adjacent appositive (context: \"...{snippet}...\")")
388
+
389
+ for f in flagged[:5]: # cap at 5 findings per turn to avoid noise
390
+ print(f)
391
+ ' 2>/dev/null)
392
+
393
+ if [ -n "$APPOSITIVE_FINDINGS" ]; then
394
+ cat >&2 <<EOF
395
+ [turn-prose-kiss-check: ADVISORY — appositive discipline (bassclef-upstream#1313)]
396
+ Shorthand without adjacent appositive found:
397
+
398
+ ${APPOSITIVE_FINDINGS}
399
+
400
+ Per .claude/rules/operator-facing-prose.md Rule 1 broader appositive amendment:
401
+ every shorthand at any mention benefits from a comma phrase, parenthetical,
402
+ or em-dash appositive that renames or explains it. Appositive text must be
403
+ plain-language for a cold reader (not more shorthand).
404
+ Override one-time: SKIP_APPOSITIVE_CHECK=1.
405
+ V1 advisory; V2 (deferred) BLOCKs after calibration.
406
+ EOF
407
+ fi
408
+ fi
409
+
410
+ # === Emit findings + exit per mode ===
411
+ if [ "$FINDING_COUNT" -gt 0 ]; then
412
+ PLACE_NOTE=""
413
+ if [ -n "$ESCALATING_PLACE" ]; then
414
+ PLACE_NOTE=$'\n''Escalated to strict by named place: '"${ESCALATING_PLACE}"' (bassclef#1171).'
415
+ fi
416
+ cat >&2 <<EOF
417
+ 🛑 /kiss turn-prose check — ${FINDING_COUNT} BLOCK-term finding(s) in your most recent message:
418
+
419
+ ${FINDINGS}
420
+ Rewrite using plain-English alternatives from standards/bassclef-internal-jargon.md.
421
+ Reserved allowlist holds: substrate, bassclef, cameo, Studio, Band, bandleader.
422
+
423
+ Mode: ${EFFECTIVE_MODE}. Toggle: .claude/bassclef-configs.jsonc → prose_discipline.kiss_words_turn_prose.${PLACE_NOTE}
424
+ Override one-time: SKIP_TURN_PROSE_KISS=1.
425
+
426
+ Closes bassclef#1027 wrong-premise (hook surfaces CAN reach turn prose via Stop event).
427
+ EOF
428
+ if [ "$EFFECTIVE_MODE" = "strict" ]; then
429
+ exit 2
430
+ fi
431
+ exit 0
432
+ fi
433
+
434
+ exit 0
@@ -0,0 +1,171 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ # vocabulary-migration-check.sh — PreToolUse Edit|Write gate on
5
+ # .claude/skills/*/SKILL.md. Reads old vocabulary terms from
6
+ # standards/vocabulary-migration.json and BLOCKs when a SKILL edit
7
+ # contains an old term outside code blocks and HTML comments.
8
+ #
9
+ # Fills the gap #764 named — /skill's new-composition path runs
10
+ # vocabulary checks, but direct Edit calls on existing SKILL bodies
11
+ # bypass them. This hook adds a dedicated gate at the edit surface.
12
+ #
13
+ # Composes with:
14
+ # - skill-body-grade-gate.sh (grade cap)
15
+ # - compound-noun-scrub.sh (hyphenated jargon)
16
+ # - substrate-clarity-gate.sh (INSTEAD-block + description shape)
17
+ #
18
+ # Input (stdin): JSON — tool_name, tool_input.file_path, tool_input.new_string / .content
19
+ # Exit: 0 pass, 2 block
20
+ #
21
+ # Override per-call: SKIP_VOCABULARY_MIGRATION=1
22
+ #
23
+ # Per @luminary tony-hoare (precondition contracts)
24
+ # + @luminary kent-beck (Tier 0 strict TDD)
25
+
26
+ set +e
27
+
28
+ # === Override path ===
29
+ if [ "${SKIP_VOCABULARY_MIGRATION:-0}" = "1" ]; then
30
+ exit 0
31
+ fi
32
+
33
+ # === Parse stdin JSON ===
34
+ STDIN_JSON=$(cat 2>/dev/null || echo '{}')
35
+
36
+ # Bail on unparseable JSON
37
+ if ! echo "$STDIN_JSON" | jq -e . >/dev/null 2>&1; then
38
+ exit 0
39
+ fi
40
+
41
+ TOOL_NAME=$(echo "$STDIN_JSON" | jq -r '.tool_name // ""' 2>/dev/null)
42
+ FILE_PATH=$(echo "$STDIN_JSON" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
43
+
44
+ # Only fire on Write or Edit
45
+ if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
46
+ exit 0
47
+ fi
48
+
49
+ # Only fire on .claude/skills/*/SKILL.md
50
+ case "$FILE_PATH" in
51
+ *".claude/skills/"*"/SKILL.md")
52
+ ;;
53
+ *)
54
+ exit 0
55
+ ;;
56
+ esac
57
+
58
+ # === Locate vocabulary-migration.json ===
59
+ # Try project-relative path first, then via hook-inject helpers if available.
60
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
61
+ MIGRATION_FILE=""
62
+ for candidate in \
63
+ "${CLAUDE_PROJECT_DIR:-}/standards/vocabulary-migration.json" \
64
+ "${SCRIPT_DIR}/../../standards/vocabulary-migration.json" \
65
+ "$(pwd)/standards/vocabulary-migration.json"; do
66
+ if [ -n "$candidate" ] && [ -f "$candidate" ]; then
67
+ MIGRATION_FILE="$candidate"
68
+ break
69
+ fi
70
+ done
71
+
72
+ # If we cannot find the migration file, pass through (soft dep)
73
+ if [ -z "$MIGRATION_FILE" ]; then
74
+ exit 0
75
+ fi
76
+
77
+ # === Extract old terms + new mappings ===
78
+ # Read old→new pairs into an associative array-ish shell layout.
79
+ # Format: OLD_TERMS is a newline-separated list of "old|new" pairs.
80
+ OLD_TERMS=$(jq -r '.renames[] | "\(.old)|\(.new)"' "$MIGRATION_FILE" 2>/dev/null)
81
+ if [ -z "$OLD_TERMS" ]; then
82
+ exit 0
83
+ fi
84
+
85
+ # === Extract candidate content ===
86
+ # For Edit: new_string is what lands in the file.
87
+ # For Write: content is the whole file.
88
+ CONTENT=$(echo "$STDIN_JSON" | jq -r '.tool_input.new_string // .tool_input.content // ""' 2>/dev/null)
89
+ if [ -z "$CONTENT" ]; then
90
+ exit 0
91
+ fi
92
+
93
+ # === Strip YAML frontmatter, code blocks, inline code, HTML comments, file paths ===
94
+ # YAML frontmatter (first --- ... --- block) is structured metadata, not prose
95
+ STRIPPED=$(echo "$CONTENT" | awk '
96
+ BEGIN { in_fm = 0; fm_done = 0 }
97
+ NR == 1 && /^---[[:space:]]*$/ { in_fm = 1; next }
98
+ in_fm && /^---[[:space:]]*$/ { in_fm = 0; fm_done = 1; next }
99
+ in_fm { next }
100
+ { print }
101
+ ')
102
+ STRIPPED=$(echo "$STRIPPED" | awk '
103
+ BEGIN { in_code = 0 }
104
+ /^```/ { in_code = !in_code; next }
105
+ in_code { next }
106
+ { print }
107
+ ')
108
+ # Strip inline code (backtick-wrapped)
109
+ STRIPPED=$(echo "$STRIPPED" | sed -E 's/`[^`]*`//g')
110
+ # Strip HTML comments (single-line variant covers most cases)
111
+ STRIPPED=$(echo "$STRIPPED" | sed -E 's/<!--[^>]*-->//g')
112
+ # Strip file paths (contain slashes + extensions)
113
+ STRIPPED=$(echo "$STRIPPED" | sed -E 's|[a-zA-Z0-9_./-]*\.(md\|json\|sh\|yml\|py\|js\|jsonc)||g')
114
+
115
+ # === Scan for old terms ===
116
+ FOUND_HITS=""
117
+ while IFS='|' read -r OLD NEW; do
118
+ [ -z "$OLD" ] && continue
119
+ # Skip 1-2 letter renames (false positive risk)
120
+ if [ "${#OLD}" -lt 3 ]; then
121
+ continue
122
+ fi
123
+ # Context-aware check for "shape" — only flag when it labels an operator choice
124
+ # Per standards/vocabulary-migration.json L77: rename applies ONLY when 'shape'
125
+ # labels an operator pick like "shape a", "shape b", "pick shape α"
126
+ if [ "$OLD" = "shape" ]; then
127
+ if echo "$STRIPPED" | grep -qiE "\\bshape [a-fA-F1-9αβγδε]\\b|pick shape\\b|choose shape\\b|shape [ABCDEF]\\b"; then
128
+ FOUND_HITS="${FOUND_HITS}${OLD} → ${NEW} (labels an operator choice)\n"
129
+ fi
130
+ continue
131
+ fi
132
+ # Standard word-boundary match for other terms
133
+ if echo "$STRIPPED" | grep -qiE "\\b${OLD}\\b"; then
134
+ FOUND_HITS="${FOUND_HITS}${OLD} → ${NEW}\n"
135
+ fi
136
+ done <<< "$OLD_TERMS"
137
+
138
+ if [ -z "$FOUND_HITS" ]; then
139
+ exit 0
140
+ fi
141
+
142
+ # === BLOCK ===
143
+ cat >&2 <<EOF
144
+
145
+ ============================================
146
+ 🛑 VOCABULARY-MIGRATION GATE — BLOCKED 🛑
147
+ ============================================
148
+
149
+ File: $FILE_PATH
150
+ Tool: $TOOL_NAME
151
+
152
+ Old vocabulary term(s) found in edit. Rename per
153
+ standards/vocabulary-migration.json (ADR-040 D1):
154
+
155
+ $(printf "$FOUND_HITS" | sed 's/^/ - /')
156
+
157
+ Cure paths:
158
+ 1. Rewrite the sentence using the new term (preferred)
159
+ 2. If the old term is a verbatim quote from a historical artifact,
160
+ wrap in an HTML comment or code fence
161
+ 3. Override with SKIP_VOCABULARY_MIGRATION=1 (logged) — rare
162
+
163
+ Per .claude/rules/we-dont-break-adopters.md — grace window on
164
+ old vocabulary through 2026-10-31; but new writes should land in
165
+ the new vocabulary.
166
+
167
+ Per ADR-040 Decision 1 (planning-skill vocabulary rewrite).
168
+ ============================================
169
+ EOF
170
+
171
+ exit 2