@thebassclef/lite 1.9.1 → 1.9.3

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.
@@ -0,0 +1,168 @@
1
+ #!/bin/bash
2
+ # tier: upstream
3
+ # fixtures: mock-git-repos-inline
4
+ # Tier 0 test for adopter message channel (bassclef-upstream#1926).
5
+ #
6
+ # Per @luminary alan-cooper Sam+Louis persona + @luminary donald-norman signifier
7
+ # + @luminary linus-torvalds adopter contract.
8
+ #
9
+ # Source under test: .claude/hooks/session-reflection.d/01-adopter-message-channel.sh
10
+ #
11
+ # test-list (per .claude/rules/test-list-discipline.md — RED first, GREEN as they land):
12
+ #
13
+ # Fragment exists + executable
14
+ # [ ] T00: fragment exists at expected path and is executable
15
+ #
16
+ # Fresh install gate — all 3 files absent → banner fires
17
+ # [ ] T01: fresh install (no whereami, no substrate.config, no .claude/settings) → welcome text
18
+ #
19
+ # Onboarded gate — any of the 3 files present → silent
20
+ # [ ] T02: whereami.md present → silent
21
+ # [ ] T03: substrate.config.md present → silent
22
+ # [ ] T04: .claude/settings.json present → silent
23
+ # [ ] T08: bassclef itself (all 3 files present) → silent (Torvalds T1 fold)
24
+ #
25
+ # Override
26
+ # [ ] T05: SKIP_ADOPTER_MESSAGES=1 → silent (bypass on fresh install)
27
+ #
28
+ # Message body (Cooper C2 fold)
29
+ # [ ] T06: welcome text contains "Welcome to bassclef"
30
+ # [ ] T07: welcome text contains "/onboard-repo"
31
+
32
+ set -u
33
+
34
+ REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
35
+ HOOK="${REPO_ROOT}/.claude/hooks/session-reflection.d/01-adopter-message-channel.sh"
36
+ PASS=0
37
+ FAIL=0
38
+ FAILED_TESTS=()
39
+
40
+ assert() {
41
+ local name="$1"
42
+ local ok="$2"
43
+ local why="$3"
44
+ if [ "$ok" = "1" ]; then
45
+ PASS=$((PASS + 1))
46
+ echo " PASS: $name"
47
+ else
48
+ FAIL=$((FAIL + 1))
49
+ FAILED_TESTS+=("$name — $why")
50
+ echo " FAIL: $name — $why"
51
+ fi
52
+ }
53
+
54
+ TMPROOT="$(mktemp -d -t adopter-message-channel.XXXXXX)"
55
+ cleanup() { rm -rf "$TMPROOT"; }
56
+ trap cleanup EXIT
57
+
58
+ # --- T00: fragment exists + executable ---
59
+ if [ -x "$HOOK" ]; then
60
+ assert "T00: fragment exists + executable" 1 ""
61
+ else
62
+ assert "T00: fragment exists + executable" 0 "not found or not executable: $HOOK"
63
+ echo ""
64
+ echo "=== Results ==="
65
+ echo "PASS: $PASS"
66
+ echo "FAIL: $FAIL"
67
+ exit 1
68
+ fi
69
+
70
+ # --- Fixture builder ---
71
+ run_hook_in() {
72
+ local tmpdir="$1"
73
+ (cd "$tmpdir" && bash "$HOOK" 2>&1)
74
+ }
75
+
76
+ # --- T01: fresh install (no state files) → welcome text ---
77
+ SC_FRESH="$TMPROOT/fresh"
78
+ mkdir -p "$SC_FRESH"
79
+ OUT_T01=$(run_hook_in "$SC_FRESH")
80
+ if echo "$OUT_T01" | grep -qE "Welcome to bassclef"; then
81
+ assert "T01: fresh install emits welcome text" 1 ""
82
+ else
83
+ assert "T01: fresh install emits welcome text" 0 "no welcome text in: $OUT_T01"
84
+ fi
85
+
86
+ # --- T02: whereami.md present → silent ---
87
+ SC_WHEREAMI="$TMPROOT/whereami-present"
88
+ mkdir -p "$SC_WHEREAMI/docs"
89
+ echo "---" > "$SC_WHEREAMI/docs/whereami.md"
90
+ OUT_T02=$(run_hook_in "$SC_WHEREAMI")
91
+ if [ -z "$OUT_T02" ]; then
92
+ assert "T02: whereami.md present → silent" 1 ""
93
+ else
94
+ assert "T02: whereami.md present → silent" 0 "unexpected output: $OUT_T02"
95
+ fi
96
+
97
+ # --- T03: substrate.config.md present → silent ---
98
+ SC_CONFIG="$TMPROOT/config-present"
99
+ mkdir -p "$SC_CONFIG"
100
+ echo "---" > "$SC_CONFIG/substrate.config.md"
101
+ OUT_T03=$(run_hook_in "$SC_CONFIG")
102
+ if [ -z "$OUT_T03" ]; then
103
+ assert "T03: substrate.config.md present → silent" 1 ""
104
+ else
105
+ assert "T03: substrate.config.md present → silent" 0 "unexpected output: $OUT_T03"
106
+ fi
107
+
108
+ # --- T04: .claude/settings.json present → silent ---
109
+ SC_SETTINGS="$TMPROOT/settings-present"
110
+ mkdir -p "$SC_SETTINGS/.claude"
111
+ echo '{}' > "$SC_SETTINGS/.claude/settings.json"
112
+ OUT_T04=$(run_hook_in "$SC_SETTINGS")
113
+ if [ -z "$OUT_T04" ]; then
114
+ assert "T04: .claude/settings.json present → silent" 1 ""
115
+ else
116
+ assert "T04: .claude/settings.json present → silent" 0 "unexpected output: $OUT_T04"
117
+ fi
118
+
119
+ # --- T05: SKIP_ADOPTER_MESSAGES=1 bypass on fresh install ---
120
+ SC_SKIP="$TMPROOT/skip"
121
+ mkdir -p "$SC_SKIP"
122
+ OUT_T05=$(SKIP_ADOPTER_MESSAGES=1 run_hook_in "$SC_SKIP")
123
+ if [ -z "$OUT_T05" ] || echo "$OUT_T05" | grep -qE "SKIP_ADOPTER_MESSAGES"; then
124
+ assert "T05: SKIP_ADOPTER_MESSAGES=1 bypasses" 1 ""
125
+ else
126
+ assert "T05: SKIP_ADOPTER_MESSAGES=1 bypasses" 0 "banner fired: $OUT_T05"
127
+ fi
128
+
129
+ # --- T06: welcome text contains "Welcome to bassclef" ---
130
+ if echo "$OUT_T01" | grep -qE "Welcome to bassclef"; then
131
+ assert "T06: welcome contains 'Welcome to bassclef'" 1 ""
132
+ else
133
+ assert "T06: welcome contains 'Welcome to bassclef'" 0 "missing in: $OUT_T01"
134
+ fi
135
+
136
+ # --- T07: welcome text contains "/onboard-repo" ---
137
+ if echo "$OUT_T01" | grep -qE "/onboard-repo"; then
138
+ assert "T07: welcome contains '/onboard-repo'" 1 ""
139
+ else
140
+ assert "T07: welcome contains '/onboard-repo'" 0 "missing in: $OUT_T01"
141
+ fi
142
+
143
+ # --- T08: bassclef itself (all 3 files present) → silent ---
144
+ SC_BASSCLEF="$TMPROOT/bassclef"
145
+ mkdir -p "$SC_BASSCLEF/docs" "$SC_BASSCLEF/.claude"
146
+ echo "---" > "$SC_BASSCLEF/docs/whereami.md"
147
+ echo "---" > "$SC_BASSCLEF/substrate.config.md"
148
+ echo '{}' > "$SC_BASSCLEF/.claude/settings.json"
149
+ OUT_T08=$(run_hook_in "$SC_BASSCLEF")
150
+ if [ -z "$OUT_T08" ]; then
151
+ assert "T08: bassclef itself (all 3 present) → silent" 1 ""
152
+ else
153
+ assert "T08: bassclef itself (all 3 present) → silent" 0 "unexpected: $OUT_T08"
154
+ fi
155
+
156
+ # --- Summary ---
157
+ echo ""
158
+ echo "=== Results ==="
159
+ echo "PASS: $PASS"
160
+ echo "FAIL: $FAIL"
161
+ if [ "$FAIL" -gt 0 ]; then
162
+ echo ""
163
+ echo "Failed tests:"
164
+ for t in "${FAILED_TESTS[@]}"; do
165
+ echo " - $t"
166
+ done
167
+ exit 1
168
+ fi
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "v1.5.1",
3
- "marker_tag": "release-2026-09-24-c4fa9e65",
4
- "release_date": "2026-09-24T16:59:30Z",
5
- "release_sha": "c4fa9e65",
6
- "release_tag": "release-2026-09-24-c4fa9e65"
2
+ "version": "v1.5.2",
3
+ "marker_tag": "release-2026-09-25-26328f36",
4
+ "release_date": "2026-09-25T17:01:41Z",
5
+ "release_sha": "26328f36",
6
+ "release_tag": "release-2026-09-25-26328f36"
7
7
  }
@@ -31,6 +31,102 @@
31
31
  # If this list changes, update sync.template.sh L777 in the same PR.
32
32
  LANE_3_HOOKS=(vendored-dispatcher-refresh.sh session-reflection.sh session-start-recap-inject.sh)
33
33
 
34
+ # _bhc_resolve_adopter_tier <adopter_cwd> — private helper (per RFC Ousterhout 3a).
35
+ # Precedence: BASSCLEF_TIER env → .bassclef-source.json `tier` field →
36
+ # .claude/bassclef-configs.jsonc `adopter.tier` → empty.
37
+ # Prints tier value to stdout; empty on no-tier-declared.
38
+ _bhc_resolve_adopter_tier() {
39
+ local adopter_cwd="$1"
40
+
41
+ # 1. Env override (test-only + peer coordination)
42
+ if [ -n "${BASSCLEF_TIER:-}" ]; then
43
+ echo "$BASSCLEF_TIER"
44
+ return 0
45
+ fi
46
+
47
+ # 2. .bassclef-source.json tier field
48
+ local source_json="$adopter_cwd/.bassclef-source.json"
49
+ if [ -f "$source_json" ] && command -v jq >/dev/null 2>&1; then
50
+ local t
51
+ t=$(jq -r '.tier // empty' "$source_json" 2>/dev/null)
52
+ if [ -n "$t" ]; then
53
+ echo "$t"
54
+ return 0
55
+ fi
56
+ fi
57
+
58
+ # 3. .claude/bassclef-configs.jsonc adopter.tier — reuse lib/tier-check.sh
59
+ # precedent: strip comments + jsonc → json parse via python3.
60
+ local configs="$adopter_cwd/.claude/bassclef-configs.jsonc"
61
+ if [ -f "$configs" ] && command -v python3 >/dev/null 2>&1; then
62
+ local t
63
+ t=$(python3 - "$configs" 2>/dev/null <<'PYEOF'
64
+ import json, re, sys
65
+ try:
66
+ with open(sys.argv[1]) as f:
67
+ content = f.read()
68
+ content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
69
+ content = re.sub(r'//[^\n]*', '', content)
70
+ d = json.loads(content)
71
+ t = d.get('adopter', {}).get('tier', '') or d.get('tier', '')
72
+ if t:
73
+ print(t)
74
+ except Exception:
75
+ pass
76
+ PYEOF
77
+ )
78
+ if [ -n "$t" ]; then
79
+ echo "$t"
80
+ return 0
81
+ fi
82
+ fi
83
+
84
+ # 4. No tier declared
85
+ return 0
86
+ }
87
+
88
+ # _bhc_tier_rank <tier> — bassclef tier ordering per ADR-052 D6.
89
+ # Higher rank = broader hook set. lite < standard < ultra < upstream.
90
+ # Prints rank integer to stdout; 0 on unknown.
91
+ _bhc_tier_rank() {
92
+ case "$1" in
93
+ lite) echo 1 ;;
94
+ standard) echo 2 ;;
95
+ ultra) echo 3 ;;
96
+ upstream) echo 4 ;;
97
+ *) echo 0 ;;
98
+ esac
99
+ }
100
+
101
+ # _bhc_load_tier_hook_basenames <bassclef_root> <tier> — private helper (per RFC Ousterhout 3a).
102
+ # Prints newline-delimited set of hook basenames in the tier's manifest.
103
+ # Two source patterns per adopter-simulator-lite Assertion 7 pattern:
104
+ # 1. Direct hook — `.claude/hooks/<name>.sh` → basename `<name>.sh`
105
+ # 2. Install template — `presence/install/<name>.template.sh` → basename `<name>.sh`
106
+ # Empty output when manifest missing or malformed (fail-soft per Nygard).
107
+ _bhc_load_tier_hook_basenames() {
108
+ local bassclef_root="$1"
109
+ local tier="$2"
110
+ local manifest="$bassclef_root/${tier}-manifest.json"
111
+
112
+ if [ ! -f "$manifest" ]; then
113
+ return 0
114
+ fi
115
+
116
+ if ! command -v jq >/dev/null 2>&1; then
117
+ return 0
118
+ fi
119
+
120
+ if ! jq -e . "$manifest" >/dev/null 2>&1; then
121
+ return 0
122
+ fi
123
+
124
+ {
125
+ jq -r '.entries[]? | .path | select(test("^\\.claude/hooks/[^/]+\\.sh$"))' "$manifest" 2>/dev/null | sed -E 's#^\.claude/hooks/##'
126
+ jq -r '.entries[]? | .path | select(test("^presence/install/[^/]+\\.template\\.sh$"))' "$manifest" 2>/dev/null | sed -E 's#^presence/install/##; s#\.template\.sh$#.sh#'
127
+ } | sort -u
128
+ }
129
+
34
130
  # bassclef_hook_connect_apply <adopter_cwd> [<bassclef_root>]
35
131
  #
36
132
  # Reads $bassclef_root/.claude/settings.json (or $BASSCLEF_SOURCE_ROOT if
@@ -125,12 +221,33 @@ bassclef_hook_connect_apply() {
125
221
  local lane3_re
126
222
  lane3_re=$(printf '%s\n' "${LANE_3_HOOKS[@]}" | sed 's/\.sh$//' | paste -sd'|' -)
127
223
 
224
+ # === Tier filter (bassclef-upstream#1946) ===
225
+ # Resolve adopter tier + load in-tier hook basenames. Empty tier OR empty
226
+ # basename set → fallback merge-all (grace preserved per ADR-031); stderr
227
+ # signal for observability.
228
+ local adopter_tier tier_basenames tier_re
229
+ adopter_tier=$(_bhc_resolve_adopter_tier "$adopter_cwd" 2>/dev/null || true)
230
+ tier_basenames=""
231
+ tier_re=""
232
+ if [ -n "$adopter_tier" ]; then
233
+ tier_basenames=$(_bhc_load_tier_hook_basenames "$bassclef_root" "$adopter_tier" 2>/dev/null || true)
234
+ if [ -z "$tier_basenames" ]; then
235
+ echo "[bassclef-hook-connect] tier=$adopter_tier but manifest missing/empty — merging all wires" >&2
236
+ else
237
+ # Build regex alternation from basenames (strip .sh for pattern building).
238
+ tier_re=$(printf '%s\n' "$tier_basenames" | sed 's/\.sh$//' | paste -sd'|' -)
239
+ fi
240
+ else
241
+ echo "[bassclef-hook-connect] no tier declared in .bassclef-source.json — merging all wires (grace window). Set tier: lite to filter." >&2
242
+ fi
243
+
128
244
  # Merge every hook entry from every event type. Preserves operator-owned
129
245
  # entries. Adds bassclef entries under matching (event, matcher) pairs
130
246
  # without duplicating commands. Skips Lane 3 hooks at $CLAUDE_PROJECT_DIR
131
- # per <consumer-repo>#1262.
247
+ # per <consumer-repo>#1262. Filters off-tier entries when tier_re non-empty
248
+ # per bassclef-upstream#1946.
132
249
  local merged
133
- merged=$(jq --slurpfile bassclef "$bassclef_settings" --arg lane3re "$lane3_re" '
250
+ merged=$(jq --slurpfile bassclef "$bassclef_settings" --arg lane3re "$lane3_re" --arg tierre "$tier_re" '
134
251
  . as $adopter
135
252
  | ($bassclef[0].hooks // {}) as $bhooks
136
253
  | .hooks //= {}
@@ -142,6 +259,11 @@ bassclef_hook_connect_apply() {
142
259
  ($bentry.matcher // "") as $bmatcher |
143
260
  ($bentry.hooks // [] | map(select(
144
261
  (.command // "") | test("\\$CLAUDE_PROJECT_DIR/\\.claude/hooks/(" + $lane3re + ")\\.sh") | not
262
+ )) | map(select(
263
+ # Tier filter: keep only entries whose command basename is in the
264
+ # tier manifest set. Empty tier_re means no filter (fallback).
265
+ ($tierre == "") or
266
+ ((.command // "") | test("\\.claude/hooks/(" + $tierre + ")\\.sh"))
145
267
  ))) as $bhookslist |
146
268
  if ($bhookslist | length) == 0 then .
147
269
  elif any(.hooks[$event][]; (.matcher // "") == $bmatcher) then
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tier": "lite",
3
3
  "manifest_version": "1.11.1",
4
- "generated_at": "2026-09-24T15:21:28Z",
4
+ "generated_at": "2026-09-25T15:57:21Z",
5
5
  "entries": [
6
6
  {
7
7
  "slug": "029-release-pipeline",
@@ -1303,7 +1303,7 @@
1303
1303
  "type": "presence-template",
1304
1304
  "path": "presence/install/bassclef-hook-connect.sh",
1305
1305
  "tier": "lite",
1306
- "content_hash": "sha256:7d029e13d72fa66157105b18684920d12e0b3c4f9d4866c0baa1c3b8227b745c",
1306
+ "content_hash": "sha256:7ac3148028a5f2fd1ca35b84812c664d2ff10b475026f278c78c490f6b12fa21",
1307
1307
  "description": "description: Dynamic-derive hook connect — closes bassclef-upstream#625."
1308
1308
  },
1309
1309
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thebassclef/lite",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "Bassclef CLI — install and upgrade bassclef in your project with two commands.",
5
5
  "keywords": [
6
6
  "bassclef",
@@ -8,7 +8,7 @@
8
8
  "cli",
9
9
  "scaffolding"
10
10
  ],
11
- "homepage": "https://github.com/sunj-labs/bassclef-cli",
11
+ "homepage": "https://docs.bassclef.dev",
12
12
  "repository": {
13
13
  "type": "git",
14
14
  "url": "git+https://github.com/sunj-labs/bassclef-cli.git"