@thebassclef/lite 1.9.2 → 1.9.4

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/README.md CHANGED
@@ -63,7 +63,7 @@ your browser.
63
63
 
64
64
  ## Current release
65
65
 
66
- <!-- version-start -->1.9.2<!-- version-end -->
66
+ <!-- version-start -->1.9.4<!-- version-end -->
67
67
 
68
68
  See [CHANGELOG.md](CHANGELOG.md) for the full changelog. Adopter-facing
69
69
  release notes live at [docs.bassclef.dev/docs/whats-new/releases](https://docs.bassclef.dev/docs/whats-new/releases).
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const version = "1.9.2";
3
+ const version = "1.9.4";
4
4
  exports.version = version;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version: "1.9.2";
1
+ export declare const version: "1.9.4";
2
2
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "1.9.2";
1
+ const version = "1.9.4";
2
2
  export {
3
3
  version
4
4
  };
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "v1.5.2",
3
- "marker_tag": "release-2026-09-24-4dc71e87",
4
- "release_date": "2026-09-24T21:29:57Z",
5
- "release_sha": "4dc71e87",
6
- "release_tag": "release-2026-09-24-4dc71e87"
2
+ "version": "v1.6.0",
3
+ "marker_tag": "release-2026-09-26-db89d305",
4
+ "release_date": "2026-09-26T12:19:56Z",
5
+ "release_sha": "db89d305",
6
+ "release_tag": "release-2026-09-26-db89d305"
7
7
  }
@@ -8,14 +8,17 @@
8
8
  # file at $HOME/.claude/hooks/, not a symlink to a checkout).
9
9
  #
10
10
  # The resolver probes four paths in order per Chain-of-Responsibility:
11
- # 1. Peer-checkout — $CWD/../bassclef with sentinel file present
12
- # 2. $HOME/bassclef — tarball adopter path with sentinel file present
13
- # 3. Bundled path — walk up from $HOOK_REAL_PATH with sentinel present
11
+ # 1. Bundled path — walk up from $HOOK_REAL_PATH with sentinel present
12
+ # (was Check 3 pre-#1954; moved to Check 1 for #1953 shadow class cure —
13
+ # adopters with adjacent bassclef checkouts now get the npm install path)
14
+ # 2. Peer-checkout — $CWD/../bassclef with sentinel file present
15
+ # 3. $HOME/bassclef — tarball adopter path with sentinel file present
14
16
  # 4. Fallback — $CWD (adopter repo); safer than $HOME because the files
15
17
  # fragments want ship into the repo via bassclef init
16
18
  #
17
19
  # The sentinel file is presence/install/bassclef-hook-connect.sh. Every
18
20
  # probe requires it — an empty peer dir or empty $HOME/bassclef does NOT win.
21
+ # Nygard R1 fold — bundled probe fail-through preserves peer-checkout adopters.
19
22
  #
20
23
  # Silent contract: never crashes, never writes to stderr on the resolve path.
21
24
  # Consumers (13 fragments plus session-reflection.sh coordinator) treat the
@@ -42,17 +45,9 @@ resolve_bassclef_dir() {
42
45
  local home_dir="${HOME_OVERRIDE:-${HOME:-/nonexistent}}"
43
46
  local sentinel="presence/install/bassclef-hook-connect.sh"
44
47
 
45
- # 1. Peer-checkout — sibling bassclef dir next to $CWD
46
- if [ -d "$cwd/../bassclef" ] && [ -f "$cwd/../bassclef/$sentinel" ]; then
47
- ( cd "$cwd/../bassclef" 2>/dev/null && pwd ) && return 0
48
- fi
49
-
50
- # 2. $HOME/bassclef — tarball adopter path
51
- if [ -d "$home_dir/bassclef" ] && [ -f "$home_dir/bassclef/$sentinel" ]; then
52
- printf '%s\n' "$home_dir/bassclef" && return 0
53
- fi
54
-
55
- # 3. Bundled path — walk up two levels from dispatcher
48
+ # 1. Bundled path — walk up two levels from dispatcher (SWAPPED to Check 1
49
+ # per #1954 Layer 1a — closes #1953 shadow class where an adjacent
50
+ # bassclef checkout at $CWD/../bassclef was winning over the npm install).
56
51
  if [ -n "$hook_path" ]; then
57
52
  local candidate
58
53
  candidate="$( cd "$(dirname "$hook_path")/../.." 2>/dev/null && pwd )" || candidate=""
@@ -61,6 +56,17 @@ resolve_bassclef_dir() {
61
56
  fi
62
57
  fi
63
58
 
59
+ # 2. Peer-checkout — sibling bassclef dir next to $CWD (Nygard R1 fold —
60
+ # preserves peer-checkout adopters when bundled probe fails).
61
+ if [ -d "$cwd/../bassclef" ] && [ -f "$cwd/../bassclef/$sentinel" ]; then
62
+ ( cd "$cwd/../bassclef" 2>/dev/null && pwd ) && return 0
63
+ fi
64
+
65
+ # 3. $HOME/bassclef — tarball adopter path
66
+ if [ -d "$home_dir/bassclef" ] && [ -f "$home_dir/bassclef/$sentinel" ]; then
67
+ printf '%s\n' "$home_dir/bassclef" && return 0
68
+ fi
69
+
64
70
  # 4. Fallback — $CWD (adopter repo). Safer than $HOME because the files
65
71
  # fragments want (init manifest, presence/install/*) ship into the repo.
66
72
  ( cd "$cwd" 2>/dev/null && pwd ) || printf '%s\n' "$cwd"
@@ -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,78 @@ 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
+
244
+ # === Filter-on-read (bassclef-upstream#1954) ===
245
+ # For each bassclef hook command, verify the target file exists at
246
+ # $bassclef_root/.claude/hooks/<basename>. Skip any entry whose target is
247
+ # missing. Log every skip to stderr. Emit warn banner over threshold.
248
+ # Saltzer-Schroeder R4 fold — matches both $CLAUDE_PROJECT_DIR- and
249
+ # $HOME-prefixed commands via basename extraction from .claude/hooks/ path.
250
+ # Nygard R2 mitigation — [ -f ] with symlink follow.
251
+ local present_re=""
252
+ local skip_count=0
253
+ local skipped_targets_file="$adopter_cwd/.bassclef-hook-connect-skipped.$$.tmp"
254
+ : > "$skipped_targets_file"
255
+ local present_basenames_file="$adopter_cwd/.bassclef-hook-connect-present.$$.tmp"
256
+ : > "$present_basenames_file"
257
+ local bhc_cmd bhc_base bhc_target
258
+ while IFS= read -r bhc_cmd; do
259
+ [ -z "$bhc_cmd" ] && continue
260
+ bhc_base=$(echo "$bhc_cmd" | grep -oE '\.claude/hooks/[^"'"'"'[:space:]\\]+\.sh' | head -1 | sed 's#\.claude/hooks/##')
261
+ if [ -z "$bhc_base" ]; then
262
+ continue
263
+ fi
264
+ bhc_target="$bassclef_root/.claude/hooks/$bhc_base"
265
+ if [ -f "$bhc_target" ] || [ -L "$bhc_target" ]; then
266
+ echo "$bhc_base" >> "$present_basenames_file"
267
+ else
268
+ echo "$bhc_target" >> "$skipped_targets_file"
269
+ skip_count=$((skip_count + 1))
270
+ echo "[bassclef-hook-connect] skip — target missing at $bhc_target" >&2
271
+ fi
272
+ done < <(jq -r '.hooks // {} | to_entries[] | .value[]? | .hooks[]? | .command // empty' "$bassclef_settings" 2>/dev/null)
273
+
274
+ if [ -s "$present_basenames_file" ]; then
275
+ present_re=$(sort -u "$present_basenames_file" | sed 's/\.sh$//' | paste -sd'|' -)
276
+ fi
277
+
278
+ # Warn banner over threshold. Threshold=0 disables banner but per-skip
279
+ # stderr above still fires.
280
+ local warn_threshold="${BASSCLEF_HOOK_CONNECT_SKIP_WARN:-5}"
281
+ if [ "$skip_count" -gt 0 ] && [ "$warn_threshold" -gt 0 ] && [ "$skip_count" -gt "$warn_threshold" ]; then
282
+ echo "[bassclef-hook-connect] WARN — $skip_count entries skipped (target files missing). Top 3:" >&2
283
+ sort -u "$skipped_targets_file" | head -3 | sed 's/^/ - /' >&2
284
+ echo " Set BASSCLEF_HOOK_CONNECT_SKIP_WARN=N to change threshold, or 0 to disable banner." >&2
285
+ fi
286
+ rm -f "$present_basenames_file" "$skipped_targets_file"
287
+
128
288
  # Merge every hook entry from every event type. Preserves operator-owned
129
289
  # entries. Adds bassclef entries under matching (event, matcher) pairs
130
290
  # without duplicating commands. Skips Lane 3 hooks at $CLAUDE_PROJECT_DIR
131
- # per <consumer-repo>#1262.
291
+ # per <consumer-repo>#1262. Filters off-tier entries when tier_re non-empty
292
+ # per bassclef-upstream#1946. Filters missing-target entries when
293
+ # present_re non-empty per bassclef-upstream#1954.
132
294
  local merged
133
- merged=$(jq --slurpfile bassclef "$bassclef_settings" --arg lane3re "$lane3_re" '
295
+ merged=$(jq --slurpfile bassclef "$bassclef_settings" --arg lane3re "$lane3_re" --arg tierre "$tier_re" --arg presentre "$present_re" '
134
296
  . as $adopter
135
297
  | ($bassclef[0].hooks // {}) as $bhooks
136
298
  | .hooks //= {}
@@ -142,6 +304,20 @@ bassclef_hook_connect_apply() {
142
304
  ($bentry.matcher // "") as $bmatcher |
143
305
  ($bentry.hooks // [] | map(select(
144
306
  (.command // "") | test("\\$CLAUDE_PROJECT_DIR/\\.claude/hooks/(" + $lane3re + ")\\.sh") | not
307
+ )) | map(select(
308
+ # Tier filter: keep only entries whose command basename is in the
309
+ # tier manifest set. Empty tier_re means no filter (fallback).
310
+ ($tierre == "") or
311
+ ((.command // "") | test("\\.claude/hooks/(" + $tierre + ")\\.sh"))
312
+ )) | map(select(
313
+ # Filter-on-read (bassclef-upstream#1954): keep only entries whose
314
+ # target hook file exists at $bassclef_root/.claude/hooks/<basename>.
315
+ # Empty present_re means no filter (fallback merge-all — matches
316
+ # the tier filter fallback shape). Non-.claude/hooks commands pass
317
+ # through (not our target class).
318
+ ($presentre == "") or
319
+ ((.command // "") | test("\\.claude/hooks/[a-zA-Z0-9._-]+\\.sh") | not) or
320
+ ((.command // "") | test("\\.claude/hooks/(" + $presentre + ")\\.sh"))
145
321
  ))) as $bhookslist |
146
322
  if ($bhookslist | length) == 0 then .
147
323
  elif any(.hooks[$event][]; (.matcher // "") == $bmatcher) then
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tier": "lite",
3
- "manifest_version": "1.11.1",
4
- "generated_at": "2026-09-24T21:01:31Z",
3
+ "manifest_version": "1.11.2",
4
+ "generated_at": "2026-09-26T11:18:11Z",
5
5
  "entries": [
6
6
  {
7
7
  "slug": "029-release-pipeline",
@@ -736,7 +736,7 @@
736
736
  "type": "lib",
737
737
  "path": "lib/bassclef-dir-resolver.sh",
738
738
  "tier": "lite",
739
- "content_hash": "sha256:10d42c54a64415f7e9b4f2a6701d57ffc89cc9d59ec0fec9ddc2af3c178855a2",
739
+ "content_hash": "sha256:7b0f95e282983cca1edcf27083526edabc964e72cd0ef3596c7c8df915e24f92",
740
740
  "description": "bassclef-dir-resolver.sh — resolve BASSCLEF_DIR across install layouts."
741
741
  },
742
742
  {
@@ -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:a50c3ca715f4f786e2bcbbd804923711f34fe29924aa7a487808934612993ec6",
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.2",
3
+ "version": "1.9.4",
4
4
  "description": "Bassclef CLI — install and upgrade bassclef in your project with two commands.",
5
5
  "keywords": [
6
6
  "bassclef",