@thebassclef/lite 0.0.1 → 0.1.2

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 (289) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +85 -4
  3. package/dist/cli.cjs +1612 -0
  4. package/dist/cli.js +1589 -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 +2701 -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/longrun-prep-compounding-axis-check.sh +492 -0
  21. package/substrate/.claude/hooks/longrun-prep-compounding-sequence-check.sh +492 -0
  22. package/substrate/.claude/hooks/plain-english-steering.sh +156 -0
  23. package/substrate/.claude/hooks/post-skill-friction-check.sh +177 -0
  24. package/substrate/.claude/hooks/post-skill-telemetry.sh +62 -0
  25. package/substrate/.claude/hooks/pre-build-gate.sh +511 -0
  26. package/substrate/.claude/hooks/pre-commit-gate.sh +451 -0
  27. package/substrate/.claude/hooks/session-end.sh +433 -0
  28. package/substrate/.claude/hooks/session-reflection.sh +303 -0
  29. package/substrate/.claude/hooks/skill-body-grade-gate.sh +219 -0
  30. package/substrate/.claude/hooks/skill-body-intent-drift.sh +107 -0
  31. package/substrate/.claude/hooks/skill-step-list-check.sh +171 -0
  32. package/substrate/.claude/hooks/state-validate.sh +271 -0
  33. package/substrate/.claude/hooks/substrate-clarity-gate.sh +1110 -0
  34. package/substrate/.claude/hooks/temperance-gate.sh +147 -0
  35. package/substrate/.claude/hooks/testing-tier-enforce.sh +233 -0
  36. package/substrate/.claude/hooks/turn-prose-grade-measure.sh +219 -0
  37. package/substrate/.claude/hooks/turn-prose-kiss-check.sh +463 -0
  38. package/substrate/.claude/hooks/vocabulary-migration-check.sh +171 -0
  39. package/substrate/.claude/hooks/whereami-utc-gate.sh +142 -0
  40. package/substrate/.claude/luminaries/alan-cooper.md +170 -0
  41. package/substrate/.claude/luminaries/alistair-cockburn.md +140 -0
  42. package/substrate/.claude/luminaries/amazon-pr-faq.md +34 -0
  43. package/substrate/.claude/luminaries/ash-maurya.md +121 -0
  44. package/substrate/.claude/luminaries/bill-buxton.md +210 -0
  45. package/substrate/.claude/luminaries/charles-sanders-peirce.md +150 -0
  46. package/substrate/.claude/luminaries/david-ogilvy.md +192 -0
  47. package/substrate/.claude/luminaries/don-norman.md +173 -0
  48. package/substrate/.claude/luminaries/edward-tufte.md +179 -0
  49. package/substrate/.claude/luminaries/eric-evans.md +160 -0
  50. package/substrate/.claude/luminaries/frederick-brooks.md +50 -0
  51. package/substrate/.claude/luminaries/gang-of-four.md +157 -0
  52. package/substrate/.claude/luminaries/glenford-myers.md +230 -0
  53. package/substrate/.claude/luminaries/hunt-thomas.md +115 -0
  54. package/substrate/.claude/luminaries/hyrum-wright.md +94 -0
  55. package/substrate/.claude/luminaries/jason-fried-dhh.md +46 -0
  56. package/substrate/.claude/luminaries/jesse-james-garrett.md +154 -0
  57. package/substrate/.claude/luminaries/john-ousterhout.md +94 -0
  58. package/substrate/.claude/luminaries/karl-popper.md +132 -0
  59. package/substrate/.claude/luminaries/kent-beck.md +168 -0
  60. package/substrate/.claude/luminaries/linus-torvalds.md +218 -0
  61. package/substrate/.claude/luminaries/martin-fowler.md +164 -0
  62. package/substrate/.claude/luminaries/michael-feathers.md +125 -0
  63. package/substrate/.claude/luminaries/michael-nygard.md +118 -0
  64. package/substrate/.claude/luminaries/robert-c-martin.md +164 -0
  65. package/substrate/.claude/luminaries/saltzer-schroeder.md +194 -0
  66. package/substrate/.claude/luminaries/sophia-prater.md +193 -0
  67. package/substrate/.claude/luminaries/stephen-toulmin.md +131 -0
  68. package/substrate/.claude/luminaries/tony-hoare.md +170 -0
  69. package/substrate/.claude/luminaries/vaughn-vernon.md +50 -0
  70. package/substrate/.claude/luminaries/w-edwards-deming.md +158 -0
  71. package/substrate/.claude/rules/accessor-library-discipline.md +138 -0
  72. package/substrate/.claude/rules/adr-discipline.md +120 -0
  73. package/substrate/.claude/rules/api-conventions.md +125 -0
  74. package/substrate/.claude/rules/artifact-ingestion.md +179 -0
  75. package/substrate/.claude/rules/assert-only-after-verify.md +137 -0
  76. package/substrate/.claude/rules/blocked-items.md +146 -0
  77. package/substrate/.claude/rules/bootstrap-pair-discipline.md +141 -0
  78. package/substrate/.claude/rules/branching.md +28 -0
  79. package/substrate/.claude/rules/cold-adopter-harness-discipline.md +129 -0
  80. package/substrate/.claude/rules/commit-conventions.md +22 -0
  81. package/substrate/.claude/rules/compounding-axis-fresh-analysis.md +188 -0
  82. package/substrate/.claude/rules/compounding-sequence-fresh-analysis.md +188 -0
  83. package/substrate/.claude/rules/context-engineering.md +202 -0
  84. package/substrate/.claude/rules/context-management.md +85 -0
  85. package/substrate/.claude/rules/defensive-bash.md +68 -0
  86. package/substrate/.claude/rules/deferred-actions.md +233 -0
  87. package/substrate/.claude/rules/destructive-operations.md +69 -0
  88. package/substrate/.claude/rules/diagnosis.md +38 -0
  89. package/substrate/.claude/rules/github-issue-flash-tweet.md +156 -0
  90. package/substrate/.claude/rules/guardrails.md +73 -0
  91. package/substrate/.claude/rules/hook-wire-on-author.md +103 -0
  92. package/substrate/.claude/rules/identifier-leak-prevention.md +104 -0
  93. package/substrate/.claude/rules/iteration-bet-brief-completeness.md +54 -0
  94. package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +98 -0
  95. package/substrate/.claude/rules/longrun-prep-plan-doc-compression.md +89 -0
  96. package/substrate/.claude/rules/loop-discipline.md +81 -0
  97. package/substrate/.claude/rules/manual-prod-approval.md +100 -0
  98. package/substrate/.claude/rules/marker-enrichment-discipline.md +99 -0
  99. package/substrate/.claude/rules/mobile-ephemeral-session.md +109 -0
  100. package/substrate/.claude/rules/new-dependency-check.md +51 -0
  101. package/substrate/.claude/rules/oo-ad-entry-point.md +117 -0
  102. package/substrate/.claude/rules/operator-facing-prose.md +196 -0
  103. package/substrate/.claude/rules/option-label-discipline.md +108 -0
  104. package/substrate/.claude/rules/pattern-annotation.md +100 -0
  105. package/substrate/.claude/rules/plain-english-discipline.md +156 -0
  106. package/substrate/.claude/rules/plan-enumeration-needs-value-props.md +211 -0
  107. package/substrate/.claude/rules/pr-body-shape.md +317 -0
  108. package/substrate/.claude/rules/pr-strategy.md +167 -0
  109. package/substrate/.claude/rules/pr-title-shape.md +161 -0
  110. package/substrate/.claude/rules/prototype-workflow.md +65 -0
  111. package/substrate/.claude/rules/reserved-skill-names.md +123 -0
  112. package/substrate/.claude/rules/schema-management.md +49 -0
  113. package/substrate/.claude/rules/sdlc-gates.md +149 -0
  114. package/substrate/.claude/rules/security.md +37 -0
  115. package/substrate/.claude/rules/session-artifacts.md +236 -0
  116. package/substrate/.claude/rules/skill-composition-declarations.md +124 -0
  117. package/substrate/.claude/rules/skill-description-clarity.md +247 -0
  118. package/substrate/.claude/rules/skill-procedure-step-list.md +137 -0
  119. package/substrate/.claude/rules/state-schema-validation.md +162 -0
  120. package/substrate/.claude/rules/stuck-signal-diagnostic.md +140 -0
  121. package/substrate/.claude/rules/substrate-config-schema.md +98 -0
  122. package/substrate/.claude/rules/test-list-discipline.md +175 -0
  123. package/substrate/.claude/rules/test-sufficiency.md +210 -0
  124. package/substrate/.claude/rules/testing-tier-config.md +145 -0
  125. package/substrate/.claude/rules/testing.md +38 -0
  126. package/substrate/.claude/rules/turn-estimate-grounding.md +134 -0
  127. package/substrate/.claude/rules/visual-hierarchy.md +437 -0
  128. package/substrate/.claude/rules/we-dont-break-adopters.md +126 -0
  129. package/substrate/.claude/rules/whereami-load-bearing.md +202 -0
  130. package/substrate/.claude/rules/writing-craft-discipline.md +92 -0
  131. package/substrate/.claude/rules/wu-sequencing-compounds.md +145 -0
  132. package/substrate/.claude/skills/build/SKILL.md +640 -0
  133. package/substrate/.claude/skills/chronicle/SKILL.md +55 -0
  134. package/substrate/.claude/skills/clean-artifacts/SKILL.md +249 -0
  135. package/substrate/.claude/skills/decompose/SKILL.md +280 -0
  136. package/substrate/.claude/skills/diagnose/SKILL.md +297 -0
  137. package/substrate/.claude/skills/feynman/SKILL.md +90 -0
  138. package/substrate/.claude/skills/howdoi/SKILL.md +105 -0
  139. package/substrate/.claude/skills/ia-model/SKILL.md +108 -0
  140. package/substrate/.claude/skills/interaction-design/SKILL.md +112 -0
  141. package/substrate/.claude/skills/interpret-input/SKILL.md +180 -0
  142. package/substrate/.claude/skills/journal/SKILL.md +209 -0
  143. package/substrate/.claude/skills/kiss/SKILL.md +449 -0
  144. package/substrate/.claude/skills/launch/SKILL.md +915 -0
  145. package/substrate/.claude/skills/lean-canvas/SKILL.md +332 -0
  146. package/substrate/.claude/skills/longrun/SKILL.md +463 -0
  147. package/substrate/.claude/skills/luminary/SKILL.md +481 -0
  148. package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
  149. package/substrate/.claude/skills/onboard-repo/SKILL.md +1624 -0
  150. package/substrate/.claude/skills/pattern-review/SKILL.md +99 -0
  151. package/substrate/.claude/skills/personas/SKILL.md +207 -0
  152. package/substrate/.claude/skills/promote/SKILL.md +283 -0
  153. package/substrate/.claude/skills/requirement/SKILL.md +98 -0
  154. package/substrate/.claude/skills/retro/SKILL.md +117 -0
  155. package/substrate/.claude/skills/riff/SKILL.md +114 -0
  156. package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
  157. package/substrate/.claude/skills/session-end/SKILL.md +309 -0
  158. package/substrate/.claude/skills/session-log/SKILL.md +299 -0
  159. package/substrate/.claude/skills/skills/SKILL.md +228 -0
  160. package/substrate/.claude/skills/spec/SKILL.md +105 -0
  161. package/substrate/.claude/skills/sprint/SKILL.md +392 -0
  162. package/substrate/.claude/skills/stage/SKILL.md +384 -0
  163. package/substrate/.claude/skills/state-a-problem/SKILL.md +185 -0
  164. package/substrate/.claude/skills/temperance/SKILL.md +108 -0
  165. package/substrate/.claude/skills/use-case/SKILL.md +417 -0
  166. package/substrate/.claude/skills/user-stories/SKILL.md +268 -0
  167. package/substrate/.claude/skills/value-prop/SKILL.md +251 -0
  168. package/substrate/.claude/skills/verify/SKILL.md +160 -0
  169. package/substrate/.claude/skills/visual-review/SKILL.md +503 -0
  170. package/substrate/.claude/skills/whats-the-plan/SKILL.md +202 -0
  171. package/substrate/.claude/skills/whereami/SKILL.md +307 -0
  172. package/substrate/AGENTS.md +79 -0
  173. package/substrate/CLAUDE-lite.md +85 -0
  174. package/substrate/CODE_OF_CONDUCT.md +28 -0
  175. package/substrate/CONTRIBUTING.md +177 -0
  176. package/substrate/README.md +173 -0
  177. package/substrate/SECURITY.md +19 -0
  178. package/substrate/architecture/decisions/ADR-029-release-pipeline.md +79 -0
  179. package/substrate/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
  180. package/substrate/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
  181. package/substrate/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
  182. package/substrate/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
  183. package/substrate/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
  184. package/substrate/lib/clean-artifacts-sweep.sh +112 -0
  185. package/substrate/lib/code-comment-discipline.sh +144 -0
  186. package/substrate/lib/composer-preflight.sh +459 -0
  187. package/substrate/lib/hook-inject.sh +255 -0
  188. package/substrate/lib/luminary-pick.sh +96 -0
  189. package/substrate/lib/output-discipline.sh +143 -0
  190. package/substrate/lib/prose-scan-boundary.sh +171 -0
  191. package/substrate/lib/rewrite-check.sh +214 -0
  192. package/substrate/lib/state.sh +1372 -0
  193. package/substrate/lib/telemetry.sh +205 -0
  194. package/substrate/lib/tier-check.sh +187 -0
  195. package/substrate/lib/tier-dependency-audit.sh +1088 -0
  196. package/substrate/presence/install/bassclef-hook-connect.sh +178 -0
  197. package/substrate/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
  198. package/substrate/presence/install/bassclef-sync.template.sh +2076 -0
  199. package/substrate/presence/install/schedule-auto-save.cron.sh +88 -0
  200. package/substrate/presence/install/schedule-auto-save.taskscheduler.md +122 -0
  201. package/substrate/scripts/aggregate-telemetry.sh +217 -0
  202. package/substrate/scripts/analyze-tier-dependencies.sh +239 -0
  203. package/substrate/scripts/generate-lite-manifest.sh +505 -0
  204. package/substrate/scripts/generate-tier-manifest.sh +28 -0
  205. package/substrate/scripts/intent-drift-check.sh +456 -0
  206. package/substrate/scripts/lite-manifest-drift-check.sh +146 -0
  207. package/substrate/scripts/render-lite-manifest-doc.sh +150 -0
  208. package/substrate/standards/adr-template.md +86 -0
  209. package/substrate/standards/api-conventions/nextjs.md +84 -0
  210. package/substrate/standards/artifact-composition.md +209 -0
  211. package/substrate/standards/bash-hook-safety.md +246 -0
  212. package/substrate/standards/bassclef-configs-schema.md +232 -0
  213. package/substrate/standards/bassclef-evolution.md +143 -0
  214. package/substrate/standards/bassclef-internal-jargon.md +244 -0
  215. package/substrate/standards/bassclef-managed-sentinel.md +96 -0
  216. package/substrate/standards/bassclef-source-config.md +228 -0
  217. package/substrate/standards/branch-stacking.md +408 -0
  218. package/substrate/standards/code-safety-principles.md +176 -0
  219. package/substrate/standards/composer-prerequisites.md +155 -0
  220. package/substrate/standards/deferred-actions-schema.md +204 -0
  221. package/substrate/standards/dependency-discipline/cargo.md +39 -0
  222. package/substrate/standards/dependency-discipline/gem.md +43 -0
  223. package/substrate/standards/dependency-discipline/go-mod.md +41 -0
  224. package/substrate/standards/dependency-discipline/npm.md +42 -0
  225. package/substrate/standards/dependency-discipline/pip.md +42 -0
  226. package/substrate/standards/deployment-topology/ec2-tailscale.md +225 -0
  227. package/substrate/standards/deployment-topology.md +69 -0
  228. package/substrate/standards/docs-sync-allowlist.md +76 -0
  229. package/substrate/standards/domain-and-dns.md +145 -0
  230. package/substrate/standards/frontend-stack.md +67 -0
  231. package/substrate/standards/frontmatter-schema.md +154 -0
  232. package/substrate/standards/graceful-exit.md +227 -0
  233. package/substrate/standards/hook-idempotency.md +102 -0
  234. package/substrate/standards/hook-injection-discipline.md +202 -0
  235. package/substrate/standards/hook-install-class.md +215 -0
  236. package/substrate/standards/input-handler-interface.md +152 -0
  237. package/substrate/standards/lite-manifest-schema-changes.md +135 -0
  238. package/substrate/standards/luminary-matching.md +105 -0
  239. package/substrate/standards/luminary-problem-patterns.md +481 -0
  240. package/substrate/standards/migration-discipline/active-record.md +50 -0
  241. package/substrate/standards/migration-discipline/alembic.md +43 -0
  242. package/substrate/standards/migration-discipline/gorm.md +50 -0
  243. package/substrate/standards/migration-discipline/prisma.md +53 -0
  244. package/substrate/standards/migration-discipline/sqlalchemy.md +51 -0
  245. package/substrate/standards/mobile-ephemeral-session.md +167 -0
  246. package/substrate/standards/model-routing-discipline.md +160 -0
  247. package/substrate/standards/ogilvy-writing-rules.md +225 -0
  248. package/substrate/standards/opener-discipline.md +96 -0
  249. package/substrate/standards/operator-facing-prose-discipline.md +201 -0
  250. package/substrate/standards/persona-schema.md +229 -0
  251. package/substrate/standards/pluggable-luminaries.md +323 -0
  252. package/substrate/standards/pr-body-discipline.md +115 -0
  253. package/substrate/standards/preview-state-schema.md +189 -0
  254. package/substrate/standards/project-directory-layout.md +276 -0
  255. package/substrate/standards/release-tagging.md +137 -0
  256. package/substrate/standards/reserved-skill-names.md +120 -0
  257. package/substrate/standards/scannable-multi-option-output.md +261 -0
  258. package/substrate/standards/sdlc-compliance.md +286 -0
  259. package/substrate/standards/sdlc-gates/typescript.md +57 -0
  260. package/substrate/standards/secrets-lifecycle.md +210 -0
  261. package/substrate/standards/security-scanner-adapter.md +145 -0
  262. package/substrate/standards/session-board.md +256 -0
  263. package/substrate/standards/skill-output-discipline.md +90 -0
  264. package/substrate/standards/state-spine-contract.md +255 -0
  265. package/substrate/standards/state-spine.md +511 -0
  266. package/substrate/standards/steering-hints/kiss-words.md +11 -0
  267. package/substrate/standards/substrate-config-schema.md +267 -0
  268. package/substrate/standards/tech-stack-config.md +109 -0
  269. package/substrate/standards/tier-dependency-analysis.md +167 -0
  270. package/substrate/standards/tier-runtime-deps/lite.md +57 -0
  271. package/substrate/standards/tier-tag-schema.md +155 -0
  272. package/substrate/standards/two-layer-config.md +99 -0
  273. package/substrate/standards/use-case-format.md +292 -0
  274. package/substrate/standards/user-story-invest.md +268 -0
  275. package/substrate/standards/velocity-and-appetite.md +229 -0
  276. package/substrate/standards/voice-input-pattern.md +119 -0
  277. package/substrate/standards/whereami-schema.md +301 -0
  278. package/substrate/standards/worktree-management.md +211 -0
  279. package/substrate/standards/writing-guide.md +213 -0
  280. package/substrate/templates/chronicle-template.md +75 -0
  281. package/substrate/templates/deferred-action-template.md +45 -0
  282. package/substrate/templates/memory-proposal-template.md +77 -0
  283. package/substrate/templates/persona-template.md +200 -0
  284. package/substrate/templates/pr-faq.md +45 -0
  285. package/substrate/templates/secret-rotation-template.md +162 -0
  286. package/substrate/templates/spec-template.md +131 -0
  287. package/substrate/templates/use-case-template.md +194 -0
  288. package/substrate/templates/user-story-template.md +107 -0
  289. package/substrate/templates/whereami-template.md +101 -0
@@ -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,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
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # output-discipline.sh — classifier library for substrate output discipline audit.
4
+ #
5
+ # Answers per artifact: does this substrate file dispatch /kiss words on its
6
+ # operator-facing output? Returns one of four labels:
7
+ #
8
+ # PRESENT — body has /kiss dispatch pattern (dispatches /kiss words on output)
9
+ # ABSENT — body emits operator-facing prose but no /kiss dispatch found
10
+ # OPT_OUT — SKILL frontmatter declares no_operator_output: true
11
+ # NOT_APPLICABLE — artifact type does not emit operator-facing prose (rare;
12
+ # reserved for future opt-outs at type layer)
13
+ #
14
+ # The classifier reads the artifact body and checks for one of these patterns:
15
+ # - /kiss words --grade10 (canonical form per bassclef-upstream#858)
16
+ # - Dispatch .*kiss words (per audit-substrate-output-discipline SKILL Test 33)
17
+ # - /kiss dispatch (loose form)
18
+ #
19
+ # Per canvas docs/canvases/2026-07-25-substrate-output-discipline.md Stage 2.
20
+ # Ships as bet 2026-07-25a WU-A3 (bassclef-upstream#859 Iteration A).
21
+ #
22
+ # Usage — sourced by /audit-substrate-output-discipline skill + scripts/sweep-kiss-dispatch.sh.
23
+ # source lib/output-discipline.sh
24
+ # classify_artifact <artifact-type> <path> # prints label to stdout; exit 0 always
25
+ # has_kiss_dispatch <path> # exit 0 if present; exit 1 if absent
26
+ # has_opt_out <path> # exit 0 if opt-out declared; exit 1 if not
27
+ #
28
+ # Sister modules:
29
+ # - lib/mechanism-fidelity.sh (bet 27c WU-3) — sister audit classifier for rule-mechanism claims
30
+ # - scripts/intent-drift-check.sh — used by sweep script for cosine gate per edit
31
+
32
+ set -o pipefail
33
+
34
+ # === has_kiss_dispatch — bool ===
35
+ # Exit 0 if body contains a /kiss dispatch pattern; exit 1 otherwise.
36
+ #
37
+ # Per bassclef-upstream#1246 — the first alternative `/kiss words( --[a-z0-9]+)+`
38
+ # recognizes both single-flag (`/kiss words --grade10`) AND two-flag
39
+ # (`/kiss words --rewrite`, `/kiss words --grade8`, `/kiss words --rewrite --grade8`)
40
+ # forms explicitly. The Dispatch/dispatch alternatives remain as loose
41
+ # fallbacks for prose that mentions the dispatch verb without a bare invocation.
42
+ # Grace window per ADR-031 — the old single-flag form keeps passing while
43
+ # 91 skills sweep to the two-flag pointer.
44
+ has_kiss_dispatch() {
45
+ local file="$1"
46
+ [ -f "$file" ] || return 1
47
+ # Match any of the four canonical /kiss dispatch patterns
48
+ grep -qE '/kiss words( --[a-z0-9]+)+|Dispatch .{0,30}kiss words|dispatches /kiss on|dispatch .{0,30}/kiss' "$file" 2>/dev/null
49
+ }
50
+
51
+ # === has_opt_out — bool ===
52
+ # Exit 0 if SKILL frontmatter declares no_operator_output: true; exit 1 otherwise.
53
+ has_opt_out() {
54
+ local file="$1"
55
+ [ -f "$file" ] || return 1
56
+ # Extract frontmatter (between leading --- ... ---) and check for opt-out field
57
+ awk '/^---$/ { c++; if(c==2) exit; next } c==1' "$file" 2>/dev/null | grep -qE '^no_operator_output:[[:space:]]*true'
58
+ }
59
+
60
+ # === classify_artifact — return label ===
61
+ # Prints one of: PRESENT | ABSENT | OPT_OUT | NOT_APPLICABLE
62
+ # Exit 0 always (informational; not a gate).
63
+ classify_artifact() {
64
+ local artifact_type="$1"
65
+ local file="$2"
66
+
67
+ if [ ! -f "$file" ]; then
68
+ echo "NOT_APPLICABLE"
69
+ return 0
70
+ fi
71
+
72
+ # Opt-out declared in frontmatter (skills only today; extensible)
73
+ if has_opt_out "$file"; then
74
+ echo "OPT_OUT"
75
+ return 0
76
+ fi
77
+
78
+ # Check for /kiss dispatch pattern in body
79
+ if has_kiss_dispatch "$file"; then
80
+ echo "PRESENT"
81
+ else
82
+ echo "ABSENT"
83
+ fi
84
+ return 0
85
+ }
86
+
87
+ # === walk_type — enumerate all files of a given artifact type ===
88
+ # Prints one path per line. Used by audit skill to iterate.
89
+ # Usage: walk_type <artifact-type> [repo-root]
90
+ walk_type() {
91
+ local artifact_type="$1"
92
+ local repo_root="${2:-$(pwd)}"
93
+
94
+ case "$artifact_type" in
95
+ skill|skills)
96
+ find "$repo_root/.claude/skills" -maxdepth 2 -name "SKILL.md" -type f 2>/dev/null | sort
97
+ ;;
98
+ rule|rules)
99
+ find "$repo_root/.claude/rules" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
100
+ ;;
101
+ standard|standards)
102
+ find "$repo_root/standards" -maxdepth 2 -name "*.md" -type f 2>/dev/null | sort
103
+ ;;
104
+ hook|hooks)
105
+ find "$repo_root/.claude/hooks" -maxdepth 1 -name "*.sh" -type f 2>/dev/null | sort
106
+ ;;
107
+ luminary|luminaries)
108
+ find "$repo_root/.claude/luminaries" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
109
+ ;;
110
+ adr|adrs)
111
+ find "$repo_root/architecture/decisions" -maxdepth 1 -name "ADR-*.md" -type f 2>/dev/null | sort
112
+ ;;
113
+ persona|personas)
114
+ find "$repo_root/docs/personas" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
115
+ ;;
116
+ template|templates)
117
+ find "$repo_root/templates" -maxdepth 1 -name "*.md" -type f 2>/dev/null | sort
118
+ ;;
119
+ *)
120
+ return 1
121
+ ;;
122
+ esac
123
+ return 0
124
+ }
125
+
126
+ # === slug_from_path — extract slug from a file path ===
127
+ # Usage: slug_from_path <artifact-type> <path>
128
+ slug_from_path() {
129
+ local artifact_type="$1"
130
+ local file="$2"
131
+
132
+ case "$artifact_type" in
133
+ skill|skills)
134
+ basename "$(dirname "$file")"
135
+ ;;
136
+ hook|hooks)
137
+ basename "$file" .sh
138
+ ;;
139
+ *)
140
+ basename "$file" .md
141
+ ;;
142
+ esac
143
+ }
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ # lib/prose-scan-boundary.sh
5
+ #
6
+ # Deep module per @luminary john-ousterhout — narrow interface hides
7
+ # the boundary detection and counter file work behind 3 functions.
8
+ #
9
+ # Purpose — every prose scan hook (kiss, appositive, citation)
10
+ # strips fenced or echoed content BEFORE it scans, and writes counter
11
+ # state that plain-english-steering.sh injects into the next prompt.
12
+ #
13
+ # This closes #1443 canonical case — Stop side prose hooks that fire
14
+ # on verbatim BLOCKED banner echoes at session start plus operator
15
+ # picked shorthand labels. 5 rewrite cycles in 20 turns documented
16
+ # the cost during 2026-09-09b prep.
17
+ #
18
+ # Anchor luminaries:
19
+ # @luminary john-ousterhout — deep module hiding the boundary work
20
+ # @luminary donald-norman — feedback loop through the counter markers
21
+ # @luminary tony-hoare — pre and postcondition on each function
22
+ # @luminary michael-feathers — characterization tests pin behavior
23
+ #
24
+ # Consumers source this lib:
25
+ # source lib/prose-scan-boundary.sh
26
+ #
27
+ # Tests: lib/tests/prose-scan-boundary.test.sh (Tier 0 strict TDD)
28
+
29
+ # =========================================================================
30
+ # strip_scan_boundaries — remove content the scan should not read
31
+ # =========================================================================
32
+ # Interface: IProseScanBoundary (Larman)
33
+ # Inputs: $1 = message text
34
+ # Outputs: stdout — message with fenced or echoed content removed
35
+ # Exit: 0 always
36
+ #
37
+ # Removes:
38
+ # 1. BLOCKED banner blocks (session-start hook echoes; agent cannot rewrite)
39
+ # 2. <!--SCAN_SKIP-->...<!--/SCAN_SKIP--> explicit fence markers
40
+ # 3. Fenced code blocks (``` ... ```)
41
+ # 4. Inline code (`...`)
42
+ strip_scan_boundaries() {
43
+ local message_text="$1"
44
+ [ -z "$message_text" ] && return 0
45
+ # Python does the heavy lifting for regex-safe multiline strip.
46
+ # Fall back to raw text when python3 missing (fail-soft per Nygard).
47
+ if command -v python3 >/dev/null 2>&1; then
48
+ printf '%s' "$message_text" | python3 -c '
49
+ import sys
50
+ import re
51
+
52
+ text = sys.stdin.read()
53
+
54
+ # 1. Strip BLOCKED banner blocks. Banner shape:
55
+ # 🛑🛑🛑 BLOCKED 🛑🛑🛑
56
+ # ────────────
57
+ # content
58
+ # ────────────
59
+ # ACTION: resolve OR ...
60
+ # Silence is not deferral. ...
61
+ #
62
+ # Match the banner line and content until one of:
63
+ # - "Silence is not deferral" line followed by newline (canonical end)
64
+ # - Two consecutive newlines (paragraph break)
65
+ # - Next banner line
66
+ # - End of string
67
+ text = re.sub(
68
+ r"\U0001F6D1\U0001F6D1\U0001F6D1 BLOCKED \U0001F6D1\U0001F6D1\U0001F6D1"
69
+ r".*?"
70
+ r"(?:Silence is not deferral\.[^\n]*\n"
71
+ r"|\n\n(?=\S|\n|$)"
72
+ r"|(?=\U0001F6D1\U0001F6D1\U0001F6D1)"
73
+ r"|$)",
74
+ "",
75
+ text,
76
+ flags=re.DOTALL,
77
+ )
78
+
79
+ # 2. Strip explicit SCAN_SKIP fences
80
+ text = re.sub(
81
+ r"<!--SCAN_SKIP-->.*?<!--/SCAN_SKIP-->",
82
+ "",
83
+ text,
84
+ flags=re.DOTALL,
85
+ )
86
+
87
+ # 3. Strip fenced code blocks
88
+ text = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
89
+
90
+ # 4. Strip inline code
91
+ text = re.sub(r"`[^`]+`", "", text)
92
+
93
+ sys.stdout.write(text)
94
+ '
95
+ else
96
+ # No python — return raw text so consumers still work
97
+ printf '%s' "$message_text"
98
+ fi
99
+ return 0
100
+ }
101
+
102
+ # =========================================================================
103
+ # write_prose_counter — persist a per-turn counter for the steering hook
104
+ # =========================================================================
105
+ # Interface: IProseCounterWriter (Larman)
106
+ # Inputs: $1 = counter name (e.g. "kiss_hits", "appositive_hits")
107
+ # $2 = numeric count
108
+ # $3 = optional JSON payload (findings, offenders, etc.)
109
+ # Outputs: writes state/markers/turn-prose-counters/<name>.json atomically
110
+ # Exit: 0 always (fail-soft on write error per Nygard)
111
+ write_prose_counter() {
112
+ local name="$1"
113
+ local count="$2"
114
+ local payload="${3:-{\}}"
115
+ [ -z "$name" ] && return 0
116
+ [ -z "$count" ] && count=0
117
+ local repo_root
118
+ repo_root="$(_prose_scan_repo_root)"
119
+ local dir="${repo_root}/state/markers/turn-prose-counters"
120
+ local out_file="${dir}/${name}.json"
121
+ mkdir -p "$dir" 2>/dev/null || return 0
122
+ local ts
123
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "unknown")"
124
+ # Atomic write via tmp + mv
125
+ local tmp
126
+ tmp="$(mktemp "${dir}/.${name}.XXXXXX")" || return 0
127
+ cat > "$tmp" <<JSON
128
+ {
129
+ "counter": "${name}",
130
+ "count": ${count},
131
+ "updated_at": "${ts}",
132
+ "payload": ${payload}
133
+ }
134
+ JSON
135
+ mv "$tmp" "$out_file" 2>/dev/null || rm -f "$tmp"
136
+ return 0
137
+ }
138
+
139
+ # =========================================================================
140
+ # read_prose_counter — read a per-turn counter for prompt injection
141
+ # =========================================================================
142
+ # Interface: IProseCounterReader (Larman)
143
+ # Inputs: $1 = counter name
144
+ # Outputs: stdout — JSON content, or empty when marker missing
145
+ # Exit: 0 always (fail-soft per Nygard)
146
+ read_prose_counter() {
147
+ local name="$1"
148
+ [ -z "$name" ] && return 0
149
+ local repo_root
150
+ repo_root="$(_prose_scan_repo_root)"
151
+ local marker="${repo_root}/state/markers/turn-prose-counters/${name}.json"
152
+ [ -f "$marker" ] || return 0
153
+ cat "$marker" 2>/dev/null || true
154
+ return 0
155
+ }
156
+
157
+ # =========================================================================
158
+ # _prose_scan_repo_root — resolve repo root under both install classes
159
+ # =========================================================================
160
+ # Internal helper. Prefer $CLAUDE_PROJECT_DIR (project install), fall
161
+ # back to lib file's parent (operator install). Per install-class: dual
162
+ # discipline from ADR-035 substrate-as-system tenet.
163
+ _prose_scan_repo_root() {
164
+ if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR}" ]; then
165
+ echo "${CLAUDE_PROJECT_DIR}"
166
+ return 0
167
+ fi
168
+ local script_dir
169
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
170
+ cd "${script_dir}/.." && pwd
171
+ }