@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.
- package/LICENSE +202 -0
- package/README.md +85 -4
- package/dist/cli.cjs +1612 -0
- package/dist/cli.js +1589 -0
- package/dist/index.cjs +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/package.json +51 -3
- package/substrate/.bassclef/lite-manifest.json +2701 -0
- package/substrate/.claude/agents/architect.md +70 -0
- package/substrate/.claude/agents/builder.md +114 -0
- package/substrate/.claude/agents/designer.md +156 -0
- package/substrate/.claude/agents/reviewer.md +88 -0
- package/substrate/.claude/hooks/artifact-ingestion-gate.sh +357 -0
- package/substrate/.claude/hooks/assert-verify-steering.sh +77 -0
- package/substrate/.claude/hooks/bassclef-source-config-validate.sh +215 -0
- package/substrate/.claude/hooks/bassclef-sync.sh +634 -0
- package/substrate/.claude/hooks/compound-noun-scrub.sh +292 -0
- package/substrate/.claude/hooks/kiss-expansion-inject.sh +69 -0
- package/substrate/.claude/hooks/longrun-prep-compounding-axis-check.sh +492 -0
- package/substrate/.claude/hooks/longrun-prep-compounding-sequence-check.sh +492 -0
- package/substrate/.claude/hooks/plain-english-steering.sh +156 -0
- package/substrate/.claude/hooks/post-skill-friction-check.sh +177 -0
- package/substrate/.claude/hooks/post-skill-telemetry.sh +62 -0
- package/substrate/.claude/hooks/pre-build-gate.sh +511 -0
- package/substrate/.claude/hooks/pre-commit-gate.sh +451 -0
- package/substrate/.claude/hooks/session-end.sh +433 -0
- package/substrate/.claude/hooks/session-reflection.sh +303 -0
- package/substrate/.claude/hooks/skill-body-grade-gate.sh +219 -0
- package/substrate/.claude/hooks/skill-body-intent-drift.sh +107 -0
- package/substrate/.claude/hooks/skill-step-list-check.sh +171 -0
- package/substrate/.claude/hooks/state-validate.sh +271 -0
- package/substrate/.claude/hooks/substrate-clarity-gate.sh +1110 -0
- package/substrate/.claude/hooks/temperance-gate.sh +147 -0
- package/substrate/.claude/hooks/testing-tier-enforce.sh +233 -0
- package/substrate/.claude/hooks/turn-prose-grade-measure.sh +219 -0
- package/substrate/.claude/hooks/turn-prose-kiss-check.sh +463 -0
- package/substrate/.claude/hooks/vocabulary-migration-check.sh +171 -0
- package/substrate/.claude/hooks/whereami-utc-gate.sh +142 -0
- package/substrate/.claude/luminaries/alan-cooper.md +170 -0
- package/substrate/.claude/luminaries/alistair-cockburn.md +140 -0
- package/substrate/.claude/luminaries/amazon-pr-faq.md +34 -0
- package/substrate/.claude/luminaries/ash-maurya.md +121 -0
- package/substrate/.claude/luminaries/bill-buxton.md +210 -0
- package/substrate/.claude/luminaries/charles-sanders-peirce.md +150 -0
- package/substrate/.claude/luminaries/david-ogilvy.md +192 -0
- package/substrate/.claude/luminaries/don-norman.md +173 -0
- package/substrate/.claude/luminaries/edward-tufte.md +179 -0
- package/substrate/.claude/luminaries/eric-evans.md +160 -0
- package/substrate/.claude/luminaries/frederick-brooks.md +50 -0
- package/substrate/.claude/luminaries/gang-of-four.md +157 -0
- package/substrate/.claude/luminaries/glenford-myers.md +230 -0
- package/substrate/.claude/luminaries/hunt-thomas.md +115 -0
- package/substrate/.claude/luminaries/hyrum-wright.md +94 -0
- package/substrate/.claude/luminaries/jason-fried-dhh.md +46 -0
- package/substrate/.claude/luminaries/jesse-james-garrett.md +154 -0
- package/substrate/.claude/luminaries/john-ousterhout.md +94 -0
- package/substrate/.claude/luminaries/karl-popper.md +132 -0
- package/substrate/.claude/luminaries/kent-beck.md +168 -0
- package/substrate/.claude/luminaries/linus-torvalds.md +218 -0
- package/substrate/.claude/luminaries/martin-fowler.md +164 -0
- package/substrate/.claude/luminaries/michael-feathers.md +125 -0
- package/substrate/.claude/luminaries/michael-nygard.md +118 -0
- package/substrate/.claude/luminaries/robert-c-martin.md +164 -0
- package/substrate/.claude/luminaries/saltzer-schroeder.md +194 -0
- package/substrate/.claude/luminaries/sophia-prater.md +193 -0
- package/substrate/.claude/luminaries/stephen-toulmin.md +131 -0
- package/substrate/.claude/luminaries/tony-hoare.md +170 -0
- package/substrate/.claude/luminaries/vaughn-vernon.md +50 -0
- package/substrate/.claude/luminaries/w-edwards-deming.md +158 -0
- package/substrate/.claude/rules/accessor-library-discipline.md +138 -0
- package/substrate/.claude/rules/adr-discipline.md +120 -0
- package/substrate/.claude/rules/api-conventions.md +125 -0
- package/substrate/.claude/rules/artifact-ingestion.md +179 -0
- package/substrate/.claude/rules/assert-only-after-verify.md +137 -0
- package/substrate/.claude/rules/blocked-items.md +146 -0
- package/substrate/.claude/rules/bootstrap-pair-discipline.md +141 -0
- package/substrate/.claude/rules/branching.md +28 -0
- package/substrate/.claude/rules/cold-adopter-harness-discipline.md +129 -0
- package/substrate/.claude/rules/commit-conventions.md +22 -0
- package/substrate/.claude/rules/compounding-axis-fresh-analysis.md +188 -0
- package/substrate/.claude/rules/compounding-sequence-fresh-analysis.md +188 -0
- package/substrate/.claude/rules/context-engineering.md +202 -0
- package/substrate/.claude/rules/context-management.md +85 -0
- package/substrate/.claude/rules/defensive-bash.md +68 -0
- package/substrate/.claude/rules/deferred-actions.md +233 -0
- package/substrate/.claude/rules/destructive-operations.md +69 -0
- package/substrate/.claude/rules/diagnosis.md +38 -0
- package/substrate/.claude/rules/github-issue-flash-tweet.md +156 -0
- package/substrate/.claude/rules/guardrails.md +73 -0
- package/substrate/.claude/rules/hook-wire-on-author.md +103 -0
- package/substrate/.claude/rules/identifier-leak-prevention.md +104 -0
- package/substrate/.claude/rules/iteration-bet-brief-completeness.md +54 -0
- package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +98 -0
- package/substrate/.claude/rules/longrun-prep-plan-doc-compression.md +89 -0
- package/substrate/.claude/rules/loop-discipline.md +81 -0
- package/substrate/.claude/rules/manual-prod-approval.md +100 -0
- package/substrate/.claude/rules/marker-enrichment-discipline.md +99 -0
- package/substrate/.claude/rules/mobile-ephemeral-session.md +109 -0
- package/substrate/.claude/rules/new-dependency-check.md +51 -0
- package/substrate/.claude/rules/oo-ad-entry-point.md +117 -0
- package/substrate/.claude/rules/operator-facing-prose.md +196 -0
- package/substrate/.claude/rules/option-label-discipline.md +108 -0
- package/substrate/.claude/rules/pattern-annotation.md +100 -0
- package/substrate/.claude/rules/plain-english-discipline.md +156 -0
- package/substrate/.claude/rules/plan-enumeration-needs-value-props.md +211 -0
- package/substrate/.claude/rules/pr-body-shape.md +317 -0
- package/substrate/.claude/rules/pr-strategy.md +167 -0
- package/substrate/.claude/rules/pr-title-shape.md +161 -0
- package/substrate/.claude/rules/prototype-workflow.md +65 -0
- package/substrate/.claude/rules/reserved-skill-names.md +123 -0
- package/substrate/.claude/rules/schema-management.md +49 -0
- package/substrate/.claude/rules/sdlc-gates.md +149 -0
- package/substrate/.claude/rules/security.md +37 -0
- package/substrate/.claude/rules/session-artifacts.md +236 -0
- package/substrate/.claude/rules/skill-composition-declarations.md +124 -0
- package/substrate/.claude/rules/skill-description-clarity.md +247 -0
- package/substrate/.claude/rules/skill-procedure-step-list.md +137 -0
- package/substrate/.claude/rules/state-schema-validation.md +162 -0
- package/substrate/.claude/rules/stuck-signal-diagnostic.md +140 -0
- package/substrate/.claude/rules/substrate-config-schema.md +98 -0
- package/substrate/.claude/rules/test-list-discipline.md +175 -0
- package/substrate/.claude/rules/test-sufficiency.md +210 -0
- package/substrate/.claude/rules/testing-tier-config.md +145 -0
- package/substrate/.claude/rules/testing.md +38 -0
- package/substrate/.claude/rules/turn-estimate-grounding.md +134 -0
- package/substrate/.claude/rules/visual-hierarchy.md +437 -0
- package/substrate/.claude/rules/we-dont-break-adopters.md +126 -0
- package/substrate/.claude/rules/whereami-load-bearing.md +202 -0
- package/substrate/.claude/rules/writing-craft-discipline.md +92 -0
- package/substrate/.claude/rules/wu-sequencing-compounds.md +145 -0
- package/substrate/.claude/skills/build/SKILL.md +640 -0
- package/substrate/.claude/skills/chronicle/SKILL.md +55 -0
- package/substrate/.claude/skills/clean-artifacts/SKILL.md +249 -0
- package/substrate/.claude/skills/decompose/SKILL.md +280 -0
- package/substrate/.claude/skills/diagnose/SKILL.md +297 -0
- package/substrate/.claude/skills/feynman/SKILL.md +90 -0
- package/substrate/.claude/skills/howdoi/SKILL.md +105 -0
- package/substrate/.claude/skills/ia-model/SKILL.md +108 -0
- package/substrate/.claude/skills/interaction-design/SKILL.md +112 -0
- package/substrate/.claude/skills/interpret-input/SKILL.md +180 -0
- package/substrate/.claude/skills/journal/SKILL.md +209 -0
- package/substrate/.claude/skills/kiss/SKILL.md +449 -0
- package/substrate/.claude/skills/launch/SKILL.md +915 -0
- package/substrate/.claude/skills/lean-canvas/SKILL.md +332 -0
- package/substrate/.claude/skills/longrun/SKILL.md +463 -0
- package/substrate/.claude/skills/luminary/SKILL.md +481 -0
- package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
- package/substrate/.claude/skills/onboard-repo/SKILL.md +1624 -0
- package/substrate/.claude/skills/pattern-review/SKILL.md +99 -0
- package/substrate/.claude/skills/personas/SKILL.md +207 -0
- package/substrate/.claude/skills/promote/SKILL.md +283 -0
- package/substrate/.claude/skills/requirement/SKILL.md +98 -0
- package/substrate/.claude/skills/retro/SKILL.md +117 -0
- package/substrate/.claude/skills/riff/SKILL.md +114 -0
- package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
- package/substrate/.claude/skills/session-end/SKILL.md +309 -0
- package/substrate/.claude/skills/session-log/SKILL.md +299 -0
- package/substrate/.claude/skills/skills/SKILL.md +228 -0
- package/substrate/.claude/skills/spec/SKILL.md +105 -0
- package/substrate/.claude/skills/sprint/SKILL.md +392 -0
- package/substrate/.claude/skills/stage/SKILL.md +384 -0
- package/substrate/.claude/skills/state-a-problem/SKILL.md +185 -0
- package/substrate/.claude/skills/temperance/SKILL.md +108 -0
- package/substrate/.claude/skills/use-case/SKILL.md +417 -0
- package/substrate/.claude/skills/user-stories/SKILL.md +268 -0
- package/substrate/.claude/skills/value-prop/SKILL.md +251 -0
- package/substrate/.claude/skills/verify/SKILL.md +160 -0
- package/substrate/.claude/skills/visual-review/SKILL.md +503 -0
- package/substrate/.claude/skills/whats-the-plan/SKILL.md +202 -0
- package/substrate/.claude/skills/whereami/SKILL.md +307 -0
- package/substrate/AGENTS.md +79 -0
- package/substrate/CLAUDE-lite.md +85 -0
- package/substrate/CODE_OF_CONDUCT.md +28 -0
- package/substrate/CONTRIBUTING.md +177 -0
- package/substrate/README.md +173 -0
- package/substrate/SECURITY.md +19 -0
- package/substrate/architecture/decisions/ADR-029-release-pipeline.md +79 -0
- package/substrate/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
- package/substrate/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
- package/substrate/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
- package/substrate/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
- package/substrate/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
- package/substrate/lib/clean-artifacts-sweep.sh +112 -0
- package/substrate/lib/code-comment-discipline.sh +144 -0
- package/substrate/lib/composer-preflight.sh +459 -0
- package/substrate/lib/hook-inject.sh +255 -0
- package/substrate/lib/luminary-pick.sh +96 -0
- package/substrate/lib/output-discipline.sh +143 -0
- package/substrate/lib/prose-scan-boundary.sh +171 -0
- package/substrate/lib/rewrite-check.sh +214 -0
- package/substrate/lib/state.sh +1372 -0
- package/substrate/lib/telemetry.sh +205 -0
- package/substrate/lib/tier-check.sh +187 -0
- package/substrate/lib/tier-dependency-audit.sh +1088 -0
- package/substrate/presence/install/bassclef-hook-connect.sh +178 -0
- package/substrate/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
- package/substrate/presence/install/bassclef-sync.template.sh +2076 -0
- package/substrate/presence/install/schedule-auto-save.cron.sh +88 -0
- package/substrate/presence/install/schedule-auto-save.taskscheduler.md +122 -0
- package/substrate/scripts/aggregate-telemetry.sh +217 -0
- package/substrate/scripts/analyze-tier-dependencies.sh +239 -0
- package/substrate/scripts/generate-lite-manifest.sh +505 -0
- package/substrate/scripts/generate-tier-manifest.sh +28 -0
- package/substrate/scripts/intent-drift-check.sh +456 -0
- package/substrate/scripts/lite-manifest-drift-check.sh +146 -0
- package/substrate/scripts/render-lite-manifest-doc.sh +150 -0
- package/substrate/standards/adr-template.md +86 -0
- package/substrate/standards/api-conventions/nextjs.md +84 -0
- package/substrate/standards/artifact-composition.md +209 -0
- package/substrate/standards/bash-hook-safety.md +246 -0
- package/substrate/standards/bassclef-configs-schema.md +232 -0
- package/substrate/standards/bassclef-evolution.md +143 -0
- package/substrate/standards/bassclef-internal-jargon.md +244 -0
- package/substrate/standards/bassclef-managed-sentinel.md +96 -0
- package/substrate/standards/bassclef-source-config.md +228 -0
- package/substrate/standards/branch-stacking.md +408 -0
- package/substrate/standards/code-safety-principles.md +176 -0
- package/substrate/standards/composer-prerequisites.md +155 -0
- package/substrate/standards/deferred-actions-schema.md +204 -0
- package/substrate/standards/dependency-discipline/cargo.md +39 -0
- package/substrate/standards/dependency-discipline/gem.md +43 -0
- package/substrate/standards/dependency-discipline/go-mod.md +41 -0
- package/substrate/standards/dependency-discipline/npm.md +42 -0
- package/substrate/standards/dependency-discipline/pip.md +42 -0
- package/substrate/standards/deployment-topology/ec2-tailscale.md +225 -0
- package/substrate/standards/deployment-topology.md +69 -0
- package/substrate/standards/docs-sync-allowlist.md +76 -0
- package/substrate/standards/domain-and-dns.md +145 -0
- package/substrate/standards/frontend-stack.md +67 -0
- package/substrate/standards/frontmatter-schema.md +154 -0
- package/substrate/standards/graceful-exit.md +227 -0
- package/substrate/standards/hook-idempotency.md +102 -0
- package/substrate/standards/hook-injection-discipline.md +202 -0
- package/substrate/standards/hook-install-class.md +215 -0
- package/substrate/standards/input-handler-interface.md +152 -0
- package/substrate/standards/lite-manifest-schema-changes.md +135 -0
- package/substrate/standards/luminary-matching.md +105 -0
- package/substrate/standards/luminary-problem-patterns.md +481 -0
- package/substrate/standards/migration-discipline/active-record.md +50 -0
- package/substrate/standards/migration-discipline/alembic.md +43 -0
- package/substrate/standards/migration-discipline/gorm.md +50 -0
- package/substrate/standards/migration-discipline/prisma.md +53 -0
- package/substrate/standards/migration-discipline/sqlalchemy.md +51 -0
- package/substrate/standards/mobile-ephemeral-session.md +167 -0
- package/substrate/standards/model-routing-discipline.md +160 -0
- package/substrate/standards/ogilvy-writing-rules.md +225 -0
- package/substrate/standards/opener-discipline.md +96 -0
- package/substrate/standards/operator-facing-prose-discipline.md +201 -0
- package/substrate/standards/persona-schema.md +229 -0
- package/substrate/standards/pluggable-luminaries.md +323 -0
- package/substrate/standards/pr-body-discipline.md +115 -0
- package/substrate/standards/preview-state-schema.md +189 -0
- package/substrate/standards/project-directory-layout.md +276 -0
- package/substrate/standards/release-tagging.md +137 -0
- package/substrate/standards/reserved-skill-names.md +120 -0
- package/substrate/standards/scannable-multi-option-output.md +261 -0
- package/substrate/standards/sdlc-compliance.md +286 -0
- package/substrate/standards/sdlc-gates/typescript.md +57 -0
- package/substrate/standards/secrets-lifecycle.md +210 -0
- package/substrate/standards/security-scanner-adapter.md +145 -0
- package/substrate/standards/session-board.md +256 -0
- package/substrate/standards/skill-output-discipline.md +90 -0
- package/substrate/standards/state-spine-contract.md +255 -0
- package/substrate/standards/state-spine.md +511 -0
- package/substrate/standards/steering-hints/kiss-words.md +11 -0
- package/substrate/standards/substrate-config-schema.md +267 -0
- package/substrate/standards/tech-stack-config.md +109 -0
- package/substrate/standards/tier-dependency-analysis.md +167 -0
- package/substrate/standards/tier-runtime-deps/lite.md +57 -0
- package/substrate/standards/tier-tag-schema.md +155 -0
- package/substrate/standards/two-layer-config.md +99 -0
- package/substrate/standards/use-case-format.md +292 -0
- package/substrate/standards/user-story-invest.md +268 -0
- package/substrate/standards/velocity-and-appetite.md +229 -0
- package/substrate/standards/voice-input-pattern.md +119 -0
- package/substrate/standards/whereami-schema.md +301 -0
- package/substrate/standards/worktree-management.md +211 -0
- package/substrate/standards/writing-guide.md +213 -0
- package/substrate/templates/chronicle-template.md +75 -0
- package/substrate/templates/deferred-action-template.md +45 -0
- package/substrate/templates/memory-proposal-template.md +77 -0
- package/substrate/templates/persona-template.md +200 -0
- package/substrate/templates/pr-faq.md +45 -0
- package/substrate/templates/secret-rotation-template.md +162 -0
- package/substrate/templates/spec-template.md +131 -0
- package/substrate/templates/use-case-template.md +194 -0
- package/substrate/templates/user-story-template.md +107 -0
- package/substrate/templates/whereami-template.md +101 -0
|
@@ -0,0 +1,463 @@
|
|
|
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
|
+
# === Source lib/prose-scan-boundary.sh (deep module per #1443) ===
|
|
71
|
+
# Strips BLOCKED banner echoes plus SCAN_SKIP fences before scan. Also
|
|
72
|
+
# provides write_prose_counter for the steering hook to read.
|
|
73
|
+
# Silent-fail: no lib = no boundary strip; hook still scans (backward
|
|
74
|
+
# compat with pre-lib behavior).
|
|
75
|
+
for _candidate in \
|
|
76
|
+
"${SCRIPT_DIR}/../../lib/prose-scan-boundary.sh" \
|
|
77
|
+
"${HOME_OVERRIDE:-${HOME:-/}}/lib/prose-scan-boundary.sh"; do
|
|
78
|
+
if [ -f "$_candidate" ]; then
|
|
79
|
+
# shellcheck disable=SC1090
|
|
80
|
+
source "$_candidate"
|
|
81
|
+
break
|
|
82
|
+
fi
|
|
83
|
+
done
|
|
84
|
+
unset _candidate
|
|
85
|
+
|
|
86
|
+
# === Parse stdin JSON ===
|
|
87
|
+
STDIN_JSON=$(cat 2>/dev/null || echo '{}')
|
|
88
|
+
TRANSCRIPT_PATH=$(echo "$STDIN_JSON" | jq -r '.transcript_path // ""' 2>/dev/null)
|
|
89
|
+
|
|
90
|
+
# Graceful skip if no transcript path provided
|
|
91
|
+
if [ -z "$TRANSCRIPT_PATH" ]; then
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Graceful skip if transcript file missing
|
|
96
|
+
if [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# === Resolve config file (install-class-aware via lib) ===
|
|
101
|
+
if declare -f resolve_config_path >/dev/null 2>&1; then
|
|
102
|
+
CONFIG_FILE=$(resolve_config_path 2>/dev/null) || CONFIG_FILE=""
|
|
103
|
+
else
|
|
104
|
+
CONFIG_FILE="${SCRIPT_DIR}/../bassclef-configs.jsonc"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# === Resolve toggle ===
|
|
108
|
+
TOGGLE="${PROSE_DISCIPLINE_TOGGLE:-}"
|
|
109
|
+
if [ -z "$TOGGLE" ]; then
|
|
110
|
+
if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
|
|
111
|
+
# Strip // comments before jq parses (jsonc is JSON + comments)
|
|
112
|
+
TOGGLE=$(sed 's|//.*||g' "$CONFIG_FILE" 2>/dev/null \
|
|
113
|
+
| jq -r '.prose_discipline.kiss_words_turn_prose // "true"' 2>/dev/null)
|
|
114
|
+
fi
|
|
115
|
+
TOGGLE="${TOGGLE:-true}"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Boolean true normalizes to "true" (advisory). false → silent. "strict" → strict.
|
|
119
|
+
case "$TOGGLE" in
|
|
120
|
+
true|"true") MODE="advisory" ;;
|
|
121
|
+
strict|"strict") MODE="strict" ;;
|
|
122
|
+
false|"false") exit 0 ;;
|
|
123
|
+
*) MODE="advisory" ;;
|
|
124
|
+
esac
|
|
125
|
+
|
|
126
|
+
# bassclef#1171 — read per-place strict escalation list. When global mode is
|
|
127
|
+
# advisory but the message matches a named place pattern AND has BLOCK findings,
|
|
128
|
+
# the hook escalates that message to strict (exit 2).
|
|
129
|
+
# Env var precedence over config: explicit empty string (env set to "") means
|
|
130
|
+
# "no places, do not consult config". Unset means "fall back to config".
|
|
131
|
+
if [ -n "${PROSE_DISCIPLINE_STRICT_PLACES+set}" ]; then
|
|
132
|
+
STRICT_PLACES="$PROSE_DISCIPLINE_STRICT_PLACES"
|
|
133
|
+
else
|
|
134
|
+
STRICT_PLACES=""
|
|
135
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
136
|
+
STRICT_PLACES=$(sed 's|//.*||g' "$CONFIG_FILE" 2>/dev/null \
|
|
137
|
+
| jq -r '.prose_discipline.kiss_words_strict_places // [] | join(",")' 2>/dev/null)
|
|
138
|
+
fi
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# === Extract most recent assistant message ===
|
|
142
|
+
# Transcript is JSONL: one event per line. Last line with type=assistant.
|
|
143
|
+
# Content is an array of content blocks; collect text from:
|
|
144
|
+
# - "text" blocks (assistant prose to operator)
|
|
145
|
+
# - "tool_use" blocks where name == "AskUserQuestion" (option text the
|
|
146
|
+
# operator reads — same operator-facing surface as prose). Pulls the
|
|
147
|
+
# question, each option label, and each option description.
|
|
148
|
+
# Other tool_use blocks (Bash, Read, Edit, etc.) are NOT scanned — their
|
|
149
|
+
# inputs are not operator-facing prose.
|
|
150
|
+
LAST_ASSISTANT_LINE=$(grep '"type":"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -1)
|
|
151
|
+
if [ -z "$LAST_ASSISTANT_LINE" ]; then
|
|
152
|
+
# No assistant message in transcript yet — graceful skip
|
|
153
|
+
exit 0
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
MESSAGE_TEXT=$(echo "$LAST_ASSISTANT_LINE" \
|
|
157
|
+
| jq -r '.message.content[]? |
|
|
158
|
+
if .type == "text" then
|
|
159
|
+
.text
|
|
160
|
+
elif .type == "tool_use" and .name == "AskUserQuestion" then
|
|
161
|
+
[
|
|
162
|
+
(.input.questions[]?
|
|
163
|
+
| (.question // ""),
|
|
164
|
+
(.options[]? | (.label // ""), (.description // "")))
|
|
165
|
+
] | join("\n")
|
|
166
|
+
else
|
|
167
|
+
empty
|
|
168
|
+
end' 2>/dev/null)
|
|
169
|
+
if [ -z "$MESSAGE_TEXT" ]; then
|
|
170
|
+
exit 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# === Boundary strip (lib/prose-scan-boundary.sh; #1443 canonical case) ===
|
|
174
|
+
# Strip BLOCKED banner echoes (session-start hook output) plus SCAN_SKIP
|
|
175
|
+
# fences before scan. Prior shape scanned banner text as if agent authored
|
|
176
|
+
# it — 5 rewrite cycles in 20 turns of prep (2026-09-09b session).
|
|
177
|
+
#
|
|
178
|
+
# Note: MESSAGE_TEXT_STRIPPED feeds SCRUBBED (jargon/appositive scan).
|
|
179
|
+
# MESSAGE_TEXT stays raw for citation check (needs backticks) and appositive
|
|
180
|
+
# scan on shorthand (needs original context).
|
|
181
|
+
MESSAGE_TEXT_STRIPPED="$MESSAGE_TEXT"
|
|
182
|
+
if declare -f strip_scan_boundaries >/dev/null 2>&1; then
|
|
183
|
+
MESSAGE_TEXT_STRIPPED=$(strip_scan_boundaries "$MESSAGE_TEXT" 2>/dev/null || echo "$MESSAGE_TEXT")
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
# === Strip code blocks + inline backtick spans + URLs ===
|
|
187
|
+
# Same logic as pr-body-scrub-check.sh — technical refs are not prose
|
|
188
|
+
SCRUBBED=$(echo "$MESSAGE_TEXT_STRIPPED" | awk '
|
|
189
|
+
BEGIN { in_code = 0 }
|
|
190
|
+
/^```/ { in_code = !in_code; next }
|
|
191
|
+
in_code { next }
|
|
192
|
+
{ print }
|
|
193
|
+
' | sed -E 's/`[^`]*`//g; s|https?://[^ )]+||g')
|
|
194
|
+
|
|
195
|
+
# === Load BLOCK terms from standards/bassclef-internal-jargon.md ===
|
|
196
|
+
# Pattern matches pr-body-scrub-check.sh — read BLOCK-tier rows from the table.
|
|
197
|
+
# Path resolution via lib's resolve_standards_path (project → env → user → fail).
|
|
198
|
+
# Falls back to legacy SCRIPT_DIR-relative path when none of the lib's 3 layers
|
|
199
|
+
# resolve — preserves project-class behavior under direct invocation without
|
|
200
|
+
# CLAUDE_PROJECT_DIR set (operator-install always sets CLAUDE_PROJECT_DIR).
|
|
201
|
+
STANDARDS_FILE=""
|
|
202
|
+
if declare -f resolve_standards_path >/dev/null 2>&1; then
|
|
203
|
+
STANDARDS_FILE=$(resolve_standards_path "bassclef-internal-jargon.md" 2>/dev/null) || STANDARDS_FILE=""
|
|
204
|
+
fi
|
|
205
|
+
if [ -z "$STANDARDS_FILE" ] || [ ! -f "$STANDARDS_FILE" ]; then
|
|
206
|
+
STANDARDS_FILE="${KISS_STANDARDS_FILE:-${SCRIPT_DIR}/../../standards/bassclef-internal-jargon.md}"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
JARGON_TERMS=()
|
|
210
|
+
if [ -f "$STANDARDS_FILE" ]; then
|
|
211
|
+
while IFS= read -r line; do
|
|
212
|
+
JARGON_TERMS+=("$line")
|
|
213
|
+
done < <(awk -F'|' '
|
|
214
|
+
/^\| [a-zA-Z0-9-]+ / && /BLOCK *\|$/ {
|
|
215
|
+
gsub(/^ +| +$/, "", $2)
|
|
216
|
+
print $2
|
|
217
|
+
}
|
|
218
|
+
' "$STANDARDS_FILE")
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
# Defensive fallback if standards file missing or empty
|
|
222
|
+
if [ ${#JARGON_TERMS[@]} -eq 0 ]; then
|
|
223
|
+
JARGON_TERMS=(
|
|
224
|
+
"composer"
|
|
225
|
+
"primitive"
|
|
226
|
+
"tier-preset"
|
|
227
|
+
"load-bearing"
|
|
228
|
+
"blast radius"
|
|
229
|
+
"compose-with"
|
|
230
|
+
"scope-bounded"
|
|
231
|
+
"operationalize"
|
|
232
|
+
)
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
# === Scan for BLOCK terms ===
|
|
236
|
+
FINDINGS=""
|
|
237
|
+
FINDING_COUNT=0
|
|
238
|
+
for term in "${JARGON_TERMS[@]}"; do
|
|
239
|
+
# Case-insensitive whole-word match
|
|
240
|
+
count=$(echo "$SCRUBBED" | grep -ioE "\b${term}\b" 2>/dev/null | wc -l | tr -d ' ')
|
|
241
|
+
if [ "$count" -gt 0 ]; then
|
|
242
|
+
FINDINGS="${FINDINGS}- \`${term}\` (${count}x)"$'\n'
|
|
243
|
+
FINDING_COUNT=$((FINDING_COUNT + count))
|
|
244
|
+
fi
|
|
245
|
+
done
|
|
246
|
+
|
|
247
|
+
# === Scan for hyphenated-proper-noun + structural-metaphor suffix (bassclef#101) ===
|
|
248
|
+
# Pattern: Capitalized-Word + "-" + Capitalized-Word (one or more hyphen pairs)
|
|
249
|
+
# + space + one of (triangle | pyramid | spectrum | hexagon | gate)
|
|
250
|
+
# Catches dense compound phrases like "Saltzer-Schroeder triangle",
|
|
251
|
+
# "Linus-Torvalds gate", "Beck-Cunningham pyramid" — academic/architectural
|
|
252
|
+
# jargon shape that slips past individual-word checks.
|
|
253
|
+
# Negative cases preserved: "API gateway" (not hyphenated proper noun),
|
|
254
|
+
# "circuit breaker" (suffix not in list).
|
|
255
|
+
PROPER_NOUN_SHAPE_MATCHES=$(echo "$SCRUBBED" | grep -oE \
|
|
256
|
+
'[A-Z][a-z]+(-[A-Z][a-z]+)+ (triangle|pyramid|spectrum|hexagon|gate)\b' \
|
|
257
|
+
2>/dev/null)
|
|
258
|
+
if [ -n "$PROPER_NOUN_SHAPE_MATCHES" ]; then
|
|
259
|
+
shape_count=$(echo "$PROPER_NOUN_SHAPE_MATCHES" | wc -l | tr -d ' ')
|
|
260
|
+
FINDINGS="${FINDINGS}- dense compound shape (${shape_count}x) — proper-noun + structural-metaphor — see bassclef#101 — rewrite in plain English"$'\n'
|
|
261
|
+
FINDING_COUNT=$((FINDING_COUNT + shape_count))
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
# === Scan for option-label Greek (bassclef#1286) ===
|
|
265
|
+
# Pattern: (option|shape|pick|tier|approach|step|item|path|alternative) + whitespace + Greek letter
|
|
266
|
+
# Greek lowercase α-ω: α-ω ; Greek uppercase Α-Ω: Α-Ω
|
|
267
|
+
# Using ripgrep-compatible PCRE class via grep -P (GNU grep) when available; falling
|
|
268
|
+
# back to a literal alternation of common Greek letters for portability.
|
|
269
|
+
OPTION_LABEL_GREEK_MATCHES=$(echo "$SCRUBBED" | grep -ioE \
|
|
270
|
+
'(option|shape|pick|tier|approach|step|item|path|alternative)[[:space:]]+(α|β|γ|δ|ε|ζ|η|θ|ι|κ|λ|μ|ν|ξ|ο|π|ρ|σ|τ|υ|φ|χ|ψ|ω|Α|Β|Γ|Δ|Ε|Ζ|Η|Θ|Ι|Κ|Λ|Μ|Ν|Ξ|Ο|Π|Ρ|Σ|Τ|Υ|Φ|Χ|Ψ|Ω)' \
|
|
271
|
+
2>/dev/null)
|
|
272
|
+
if [ -n "$OPTION_LABEL_GREEK_MATCHES" ]; then
|
|
273
|
+
option_label_count=$(echo "$OPTION_LABEL_GREEK_MATCHES" | wc -l | tr -d ' ')
|
|
274
|
+
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'
|
|
275
|
+
FINDING_COUNT=$((FINDING_COUNT + option_label_count))
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
# === Scan for retired WU-N vocab in new prose (bassclef-web#202) ===
|
|
279
|
+
# ADR-040 D1 renamed "WU-N" → "Step N". Grace window through 2026-10-31 preserves
|
|
280
|
+
# READ compatibility via standards/vocabulary-migration.json — historical refs in
|
|
281
|
+
# closed PRs, past chronicles, memory files stay as-is. NEW authored prose uses
|
|
282
|
+
# the new terms. Backticked refs like `WU-3` are stripped by SCRUBBED at L164 so
|
|
283
|
+
# quoted historical goal IDs pass through cleanly. Only unbacked WU-N in prose fires.
|
|
284
|
+
WU_N_VOCAB_MATCHES=$(echo "$SCRUBBED" | grep -oE '\bWU-[0-9]+\b' 2>/dev/null)
|
|
285
|
+
if [ -n "$WU_N_VOCAB_MATCHES" ]; then
|
|
286
|
+
wu_n_count=$(echo "$WU_N_VOCAB_MATCHES" | wc -l | tr -d ' ')
|
|
287
|
+
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'
|
|
288
|
+
FINDING_COUNT=$((FINDING_COUNT + wu_n_count))
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
# === Per-place strict escalation (bassclef#1171) ===
|
|
292
|
+
# When global mode is advisory + BLOCK findings exist + the message matches
|
|
293
|
+
# a named strict-place pattern, escalate that message to strict (exit 2).
|
|
294
|
+
#
|
|
295
|
+
# Two detection paths per place:
|
|
296
|
+
# 1. Marker file at state/markers/turn-prose-surface/<surface>.marker —
|
|
297
|
+
# the SKILL.md procedure touches this before posting; precise. (bassclef#1304)
|
|
298
|
+
# 2. Content pattern grep on SCRUBBED — heuristic fallback for the V1
|
|
299
|
+
# 3 places (closeout_blocks, summary_tables, next_pickup).
|
|
300
|
+
EFFECTIVE_MODE="$MODE"
|
|
301
|
+
ESCALATING_PLACE=""
|
|
302
|
+
|
|
303
|
+
# Repo root for marker lookup
|
|
304
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
305
|
+
SURFACE_MARKER_DIR="${REPO_ROOT}/state/markers/turn-prose-surface"
|
|
306
|
+
|
|
307
|
+
if [ "$MODE" = "advisory" ] && [ "$FINDING_COUNT" -gt 0 ] && [ -n "$STRICT_PLACES" ]; then
|
|
308
|
+
IFS=',' read -ra PLACE_ARRAY <<< "$STRICT_PLACES"
|
|
309
|
+
for place in "${PLACE_ARRAY[@]}"; do
|
|
310
|
+
place_trimmed=$(echo "$place" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
311
|
+
|
|
312
|
+
# Path 1: marker-file detection (precise; bassclef#1304)
|
|
313
|
+
if [ -f "${SURFACE_MARKER_DIR}/${place_trimmed}.marker" ]; then
|
|
314
|
+
EFFECTIVE_MODE="strict"
|
|
315
|
+
ESCALATING_PLACE="${place_trimmed} (marker)"
|
|
316
|
+
break
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
# Path 2: content-pattern heuristics (V1 fallback)
|
|
320
|
+
case "$place_trimmed" in
|
|
321
|
+
closeout_blocks)
|
|
322
|
+
if echo "$SCRUBBED" | grep -qE '^## Closeout audit'; then
|
|
323
|
+
EFFECTIVE_MODE="strict"
|
|
324
|
+
ESCALATING_PLACE="closeout_blocks"
|
|
325
|
+
break
|
|
326
|
+
fi
|
|
327
|
+
;;
|
|
328
|
+
summary_tables)
|
|
329
|
+
if echo "$SCRUBBED" | grep -qE '^## Status[[:space:]]*$'; then
|
|
330
|
+
EFFECTIVE_MODE="strict"
|
|
331
|
+
ESCALATING_PLACE="summary_tables"
|
|
332
|
+
break
|
|
333
|
+
fi
|
|
334
|
+
;;
|
|
335
|
+
next_pickup)
|
|
336
|
+
if echo "$SCRUBBED" | grep -qE '^## Next pickup'; then
|
|
337
|
+
EFFECTIVE_MODE="strict"
|
|
338
|
+
ESCALATING_PLACE="next_pickup"
|
|
339
|
+
break
|
|
340
|
+
fi
|
|
341
|
+
;;
|
|
342
|
+
esac
|
|
343
|
+
done
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
# === Skill-dispatch citation check (V1 ADVISORY — bassclef-upstream#374) ===
|
|
347
|
+
# When a /longrun-surface marker exists at state/markers/turn-prose-surface/
|
|
348
|
+
# <surface>.marker, scan the message for a skill-dispatch citation per
|
|
349
|
+
# operator-facing-prose.md Rule 3. Emit ADVISORY if citation missing.
|
|
350
|
+
# Named surfaces: longrun_prep_proposal, longrun_checkpoint, longrun_closeout, status_update.
|
|
351
|
+
SKILL_CITATION_MISSING=""
|
|
352
|
+
for surface in longrun_prep_proposal longrun_checkpoint longrun_closeout status_update; do
|
|
353
|
+
if [ -f "${SURFACE_MARKER_DIR}/${surface}.marker" ]; then
|
|
354
|
+
# Citation patterns: backtick-wrapped /skill mention, or named discipline blocks.
|
|
355
|
+
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
|
|
356
|
+
SKILL_CITATION_MISSING="${SKILL_CITATION_MISSING}${surface} "
|
|
357
|
+
fi
|
|
358
|
+
fi
|
|
359
|
+
done
|
|
360
|
+
|
|
361
|
+
if [ -n "$SKILL_CITATION_MISSING" ]; then
|
|
362
|
+
cat >&2 <<EOF
|
|
363
|
+
[turn-prose-kiss-check: ADVISORY — skill-dispatch citation missing on surface(s): ${SKILL_CITATION_MISSING}]
|
|
364
|
+
Per .claude/rules/operator-facing-prose.md Rule 3 (bassclef-upstream#374):
|
|
365
|
+
every /longrun-dispatched session-conversation surface declares the
|
|
366
|
+
dispatched skill via inline citation (e.g., "via \`/value-prop flash\`",
|
|
367
|
+
"drafted by \`/state-a-problem brief\`") or a short discipline-marker line.
|
|
368
|
+
V1 advisory; V2 (deferred) BLOCKs net-new surfaces without citation.
|
|
369
|
+
EOF
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# === Appositive presence check (V1 ADVISORY — bassclef-upstream#1313) ===
|
|
373
|
+
# Per .claude/rules/operator-facing-prose.md Rule 1 broader appositive
|
|
374
|
+
# amendment. Scans for shorthand patterns and checks each match for an
|
|
375
|
+
# adjacent appositive (parenthetical, comma phrase, em-dash) within 40
|
|
376
|
+
# characters. Emits ADVISORY line per shorthand without adjacent
|
|
377
|
+
# appositive. Structural check only — operator judgment catches
|
|
378
|
+
# shorthand-inside-appositive.
|
|
379
|
+
#
|
|
380
|
+
# Override per-call: SKIP_APPOSITIVE_CHECK=1
|
|
381
|
+
if [ "${SKIP_APPOSITIVE_CHECK:-0}" != "1" ]; then
|
|
382
|
+
APPOSITIVE_FINDINGS=$(printf '%s' "$MESSAGE_TEXT" | python3 -c '
|
|
383
|
+
import re, sys
|
|
384
|
+
|
|
385
|
+
text = sys.stdin.read()
|
|
386
|
+
|
|
387
|
+
# Shorthand patterns — session-invented or bassclef substrate refs
|
|
388
|
+
patterns = [
|
|
389
|
+
(r"(?<![a-zA-Z0-9_/])#(\d+)\b", "ticket"),
|
|
390
|
+
(r"\bBucket\s+[A-Z]\d?[a-z]?\b", "Bucket label"),
|
|
391
|
+
(r"\bOption\s+[a-z]\b", "Option letter"),
|
|
392
|
+
(r"\bShip\s+\d+\b", "Ship label"),
|
|
393
|
+
(r"\bWave\s+\d+\b", "Wave label"),
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
# Appositive shapes within 40 chars after the shorthand:
|
|
397
|
+
# \s*( — optional whitespace then
|
|
398
|
+
# \( — parenthetical
|
|
399
|
+
# |,\s+\w — comma + space + word
|
|
400
|
+
# |—\s*\w — em-dash + word
|
|
401
|
+
# |:\s+\w — colon + word
|
|
402
|
+
# |\s—\s+\w — spaced em-dash + word
|
|
403
|
+
# )
|
|
404
|
+
appositive_re = re.compile(r"^\s*(\(|,\s+\w|—\s*\w|:\s+\w|-\s+\w)")
|
|
405
|
+
|
|
406
|
+
flagged = []
|
|
407
|
+
for pattern, label in patterns:
|
|
408
|
+
for m in re.finditer(pattern, text):
|
|
409
|
+
end = m.end()
|
|
410
|
+
following = text[end:end+40]
|
|
411
|
+
if not appositive_re.match(following):
|
|
412
|
+
# Extract surrounding context for the finding
|
|
413
|
+
context_start = max(0, m.start() - 10)
|
|
414
|
+
context_end = min(len(text), m.end() + 30)
|
|
415
|
+
snippet = text[context_start:context_end].replace("\n", " ")
|
|
416
|
+
flagged.append(f"{label} \"{m.group(0)}\" — no adjacent appositive (context: \"...{snippet}...\")")
|
|
417
|
+
|
|
418
|
+
for f in flagged[:5]: # cap at 5 findings per turn to avoid noise
|
|
419
|
+
print(f)
|
|
420
|
+
' 2>/dev/null)
|
|
421
|
+
|
|
422
|
+
if [ -n "$APPOSITIVE_FINDINGS" ]; then
|
|
423
|
+
cat >&2 <<EOF
|
|
424
|
+
[turn-prose-kiss-check: ADVISORY — appositive discipline (bassclef-upstream#1313)]
|
|
425
|
+
Shorthand without adjacent appositive found:
|
|
426
|
+
|
|
427
|
+
${APPOSITIVE_FINDINGS}
|
|
428
|
+
|
|
429
|
+
Per .claude/rules/operator-facing-prose.md Rule 1 broader appositive amendment:
|
|
430
|
+
every shorthand at any mention benefits from a comma phrase, parenthetical,
|
|
431
|
+
or em-dash appositive that renames or explains it. Appositive text must be
|
|
432
|
+
plain-language for a cold reader (not more shorthand).
|
|
433
|
+
Override one-time: SKIP_APPOSITIVE_CHECK=1.
|
|
434
|
+
V1 advisory; V2 (deferred) BLOCKs after calibration.
|
|
435
|
+
EOF
|
|
436
|
+
fi
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
# === Emit findings + exit per mode ===
|
|
440
|
+
if [ "$FINDING_COUNT" -gt 0 ]; then
|
|
441
|
+
PLACE_NOTE=""
|
|
442
|
+
if [ -n "$ESCALATING_PLACE" ]; then
|
|
443
|
+
PLACE_NOTE=$'\n''Escalated to strict by named place: '"${ESCALATING_PLACE}"' (bassclef#1171).'
|
|
444
|
+
fi
|
|
445
|
+
cat >&2 <<EOF
|
|
446
|
+
🛑 /kiss turn-prose check — ${FINDING_COUNT} BLOCK-term finding(s) in your most recent message:
|
|
447
|
+
|
|
448
|
+
${FINDINGS}
|
|
449
|
+
Rewrite using plain-English alternatives from standards/bassclef-internal-jargon.md.
|
|
450
|
+
Reserved allowlist holds: substrate, bassclef, cameo, Studio, Band, bandleader.
|
|
451
|
+
|
|
452
|
+
Mode: ${EFFECTIVE_MODE}. Toggle: .claude/bassclef-configs.jsonc → prose_discipline.kiss_words_turn_prose.${PLACE_NOTE}
|
|
453
|
+
Override one-time: SKIP_TURN_PROSE_KISS=1.
|
|
454
|
+
|
|
455
|
+
Closes bassclef#1027 wrong-premise (hook surfaces CAN reach turn prose via Stop event).
|
|
456
|
+
EOF
|
|
457
|
+
if [ "$EFFECTIVE_MODE" = "strict" ]; then
|
|
458
|
+
exit 2
|
|
459
|
+
fi
|
|
460
|
+
exit 0
|
|
461
|
+
fi
|
|
462
|
+
|
|
463
|
+
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
|