@thebassclef/lite 1.9.4 → 1.9.5
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 +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.claude/bassclef-orientation.md +20 -0
- package/dist/lite/.claude/hooks/session-reflection.sh +23 -0
- package/dist/lite/.claude/rules/blocked-items.md +18 -0
- package/dist/lite/.claude/skills/longrun/SKILL.md +66 -0
- package/dist/lite/.claude/skills/onboard-repo/SKILL.md +33 -0
- package/dist/lite/bassclef-version.json +5 -5
- package/dist/lite/lib/ancestor-claude-check.sh +26 -4
- package/dist/lite/lib/capability-probe.sh +11 -17
- package/dist/lite/lib/statusline-version.sh +103 -0
- package/dist/lite/presence/cli/bassclef-statusline.dispatcher.sh +26 -7
- package/dist/lite/presence/install/bassclef-sync.template.sh +43 -0
- package/dist/lite/scripts/generate-lite-manifest.sh +12 -6
- package/dist/lite/scripts/interpret-input/extract-repo.sh +239 -0
- package/dist/lite/scripts/interpret-input.sh +212 -0
- package/dist/lite/scripts/launch/local-serve-stop.sh +41 -0
- package/dist/lite/scripts/launch/local-serve.sh +122 -0
- package/dist/lite/scripts/launch/parse-input-args.sh +177 -0
- package/dist/lite/scripts/longrun-preset-detect.sh +87 -0
- package/dist/lite/standards/lite-manifest-schema-changes.md +36 -0
- package/dist/lite/standards/lite-manifest.json +69 -13
- package/dist/lite/standards/session-start-banner-discipline.md +18 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ your browser.
|
|
|
63
63
|
|
|
64
64
|
## Current release
|
|
65
65
|
|
|
66
|
-
<!-- version-start -->1.9.
|
|
66
|
+
<!-- version-start -->1.9.5<!-- 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
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version: "1.9.
|
|
1
|
+
export declare const version: "1.9.5";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -145,3 +145,23 @@ Pick the tier that matches your trust + budget. Upgrade path is additive — no
|
|
|
145
145
|
- `/skills` — phase-grouped skill catalog
|
|
146
146
|
- `/luminary` — design-lens catalog
|
|
147
147
|
- `/hooks` — mechanical-enforcement layer
|
|
148
|
+
|
|
149
|
+
## Statusline (opt-in)
|
|
150
|
+
|
|
151
|
+
Bassclef ships a statusline reader at `lib/statusline-version.sh`. When enabled, terminal Claude Code sessions render `bassclef v1.6.2 · up-to-date` at the bottom of every prompt. The reader consults `state/bassclef-sync-status.json` (written by `bassclef-sync.sh` at end-of-run) and `bassclef-version.json` at the repo root.
|
|
152
|
+
|
|
153
|
+
Bassclef does not set the statusline at project scope. Adopter statusline choices survive untouched. To enable, add this block to your user-scope `~/.claude/settings.json`:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"statusLine": {
|
|
158
|
+
"command": "bash /path/to/your/repo/lib/statusline-version.sh"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Replace `/path/to/your/repo` with the actual repo root. Multiple repos work — the reader resolves `CLAUDE_PROJECT_DIR` at each invocation.
|
|
164
|
+
|
|
165
|
+
Fallback behavior: if the state file is absent, the reader emits `bassclef · initializing`. If the version file is absent OR either file is malformed, the reader emits `bassclef · not synced`. Every path exits 0 with no stderr on the happy path.
|
|
166
|
+
|
|
167
|
+
Ships bassclef-upstream#1961.
|
|
@@ -111,6 +111,29 @@ blocked_banner() {
|
|
|
111
111
|
echo ""
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
# Vendor-swap constant — the AI in the harness running bassclef. Adopter
|
|
115
|
+
# swaps via BASSCLEF_AI_NAME env var; default reads as "Claude". Per
|
|
116
|
+
# #1930 Category A design lock (2026-09-27T19:20:00Z ticket comment).
|
|
117
|
+
AI_NAME="${BASSCLEF_AI_NAME:-Claude}"
|
|
118
|
+
|
|
119
|
+
# guardrail_banner — sibling of blocked_banner() with softer tone for
|
|
120
|
+
# discipline gates (chronicle stale, architect-review overdue, etc.).
|
|
121
|
+
# Label ⚙ BASSCLEF DISCIPLINE GATE signals "pause + apply discipline",
|
|
122
|
+
# not "something broke". Per #1930 Category A. Callers stay on
|
|
123
|
+
# blocked_banner() until each is migrated one at a time; this helper
|
|
124
|
+
# ships additive.
|
|
125
|
+
guardrail_banner() {
|
|
126
|
+
local hook_name="$1"
|
|
127
|
+
local step="$2"
|
|
128
|
+
local retry="$3"
|
|
129
|
+
echo ""
|
|
130
|
+
echo "⚙ BASSCLEF DISCIPLINE GATE — $hook_name"
|
|
131
|
+
echo "────────────────────────────────────────────"
|
|
132
|
+
echo "bassclef is steering $AI_NAME to $step — $retry"
|
|
133
|
+
echo "────────────────────────────────────────────"
|
|
134
|
+
echo ""
|
|
135
|
+
}
|
|
136
|
+
|
|
114
137
|
# RFC-0008 F6 — this block is steering aimed at the agent, but the adopter
|
|
115
138
|
# reads it too. Three imperatives and two shouted negations, before a single
|
|
116
139
|
# check has run, read as "you already did something wrong". Say who it is
|
|
@@ -8,6 +8,24 @@ description: "Session-start hook output may contain lines prefixed with BLOCKED:
|
|
|
8
8
|
Session-start hook output may contain lines prefixed with `BLOCKED:`.
|
|
9
9
|
These are not suggestions. They are mandatory scope.
|
|
10
10
|
|
|
11
|
+
## Banner header shapes — two siblings
|
|
12
|
+
|
|
13
|
+
Two banner shapes signal a blocked item. Both are valid discipline-gate
|
|
14
|
+
signals. Machine readers grep the `BLOCKED:` prefix on body lines; both
|
|
15
|
+
shapes preserve that prefix.
|
|
16
|
+
|
|
17
|
+
- `🛑🛑🛑 BLOCKED 🛑🛑🛑` — historical shape from `blocked_banner()`.
|
|
18
|
+
Continues to fire for real defects (schema violation, dangling ref,
|
|
19
|
+
state corruption) and for gate hooks not yet migrated.
|
|
20
|
+
- `⚙ BASSCLEF DISCIPLINE GATE — <hook>` — softer shape from
|
|
21
|
+
`guardrail_banner()`. Fires for discipline gates that ask the agent
|
|
22
|
+
to pause and apply discipline (chronicle stale, architect-review
|
|
23
|
+
overdue, temperance missing). Per sunj-labs/bassclef-upstream#1930
|
|
24
|
+
Category A.
|
|
25
|
+
|
|
26
|
+
The agent treats both header shapes as siblings and follows the same
|
|
27
|
+
resolve-or-explicitly-defer protocol regardless of which shape fires.
|
|
28
|
+
|
|
11
29
|
## Protocol
|
|
12
30
|
|
|
13
31
|
1. **Before proposing any new work**, scan session-start output for
|
|
@@ -251,6 +251,72 @@ Rule: `.claude/rules/longrun-prep-plan-doc-compression.md`. Anchor luminaries: @
|
|
|
251
251
|
Rationale: `/longrun prep` output ran too long. One shape did not fit every situation. Operator asked for a shape that reads clean in under 15 seconds. #1598 filed 2026-09-10; design session at 2026-09-12 picked the three presets under Path 2.
|
|
252
252
|
<!-- /FKGL-EXEMPT -->
|
|
253
253
|
|
|
254
|
+
<!-- FKGL-EXEMPT: Step 0.86 taxonomy — technical scope-class reference; short SVO loses precision on the class boundaries per bassclef-upstream#1978 -->
|
|
255
|
+
**Step 0.86 — Scope class taxonomy (per bassclef-upstream#1978).** After the preset picks discipline shape, `/longrun` picks discipline depth per ticket. Preset (converged / exploratory / reversible-small) and class (a / b / c / d) are **orthogonal** — preset shapes how prep renders; class shapes how each ticket runs. A single `/longrun` may ship class-a and class-b tickets in the same converged prep.
|
|
256
|
+
|
|
257
|
+
**Class label is optional.** When the ticket body or plan doc names a class, `/longrun` reads it. When neither names one, `/longrun` infers from the ticket body plus the preset (converged → class b or c; exploratory → class a; reversible-small → class a). Prior invocations without labels keep working through the grace window per `.claude/rules/we-dont-break-adopters.md`.
|
|
258
|
+
|
|
259
|
+
## Scope class taxonomy
|
|
260
|
+
|
|
261
|
+
Three base classes plus one modifier. Every ticket in scope carries one base class label. Class d layers on top when the ticket crosses a repo boundary.
|
|
262
|
+
|
|
263
|
+
| Class | Fires when | Steps | Base ceremony |
|
|
264
|
+
|---|---|---|---|
|
|
265
|
+
| **class a — small fix** | ≤20 turns; single file; no adopter surface change | 5 | OOAD casual (commit body sentence) + `/luminary` lead + Beck RED-first + impl + `/verify` + PR |
|
|
266
|
+
| **class b — script or hook extension** | adopter-facing script body change OR hook body change | 6 | brief use case + `/luminary consult` (lead + 1 supporting) + `/pre-mortem light` (2 lenses × 3-5 risks) + Beck RED-first + impl + `/verify` + PR |
|
|
267
|
+
| **class c — new building block** | new hook / lib / skill / rule / standard / ADR | 8 | fully-dressed use case + `/decompose` + `/luminary consult` (lead + 2 supporting) + `/pre-mortem light` (3 lenses × 5-8 risks) + `/rfc adversarial` (3 outside lenses) + bootstrap pair + Beck RED-first + impl + `/verify` + PR + CI + admin merge |
|
|
268
|
+
| **class d — cross-repo cure** | additive on top of a/b/c; ticket touches sibling repo | +3 | names sibling repos + release cascade + peer coordination via SendMessage + cold-adopter smoke post-merge |
|
|
269
|
+
|
|
270
|
+
Ceremony rows cross-reference `.claude/rules/oo-ad-entry-point.md` matrix (existing ceremony-per-code-class rows). Class labels here map 1:1 to matrix rows there.
|
|
271
|
+
|
|
272
|
+
## Discipline overrides
|
|
273
|
+
|
|
274
|
+
Three named override patterns fire regardless of base class:
|
|
275
|
+
|
|
276
|
+
- **/rfc adversarial when 2+ sister rules touched.** When the ticket changes behavior at 2+ rules (e.g., `.claude/rules/oo-ad-entry-point.md` + `.claude/rules/bootstrap-pair-discipline.md` + `.claude/rules/loop-discipline.md` together), dispatch `/rfc adversarial` with 3 outside lenses. Fold or reject per RFC council synthesis.
|
|
277
|
+
- **Elevated consult when new decision surface.** When the ticket introduces a new operator-facing decision menu, picker, gate, OR class boundary, elevate `/luminary consult` from base (1 supporting) to elevated (2 supporting). The new decision surface deserves a wider lens set.
|
|
278
|
+
- **Sibling smoke when adopter-observable Tier 0.** When the ticket touches adopter-observable Tier 0 substrate (e.g., `.claude/hooks/`, `.claude/skills/`, sync template), fire sibling smoke on a downstream adopter after PR merges but before release cascade. Runs the changed hook or skill on a real cold adopter clone; smokes the acceptance criteria.
|
|
279
|
+
|
|
280
|
+
Every override composes with the base class — never replaces it. A class-b ticket with 2+ sister rules touched runs class-b discipline plus `/rfc adversarial`.
|
|
281
|
+
|
|
282
|
+
## Luminary tier ladder
|
|
283
|
+
|
|
284
|
+
`/longrun` runs on whatever tier the adopter's credentials support. Three-row fallback:
|
|
285
|
+
|
|
286
|
+
| Tier | Prereq keys | Behavior | Notify shape |
|
|
287
|
+
|---|---|---|---|
|
|
288
|
+
| **ultra** | ANTHROPIC + VOYAGE | LLM picker + Voyage embeddings for luminary re-rank | stderr: `[longrun] tier=ultra (Voyage shortlist + Anthropic judge)`; inline: none |
|
|
289
|
+
| **standard** | ANTHROPIC only | LLM picker against full catalog | stderr: `[longrun] tier=standard (Anthropic full catalog)`; inline: none |
|
|
290
|
+
| **lite** | none | deterministic stub; skill dispatches without LLM signal | stderr: `[longrun] tier=lite (deterministic fallback; add ANTHROPIC_API_KEY for LLM picking)`; inline: one-line reminder that tier=lite fired |
|
|
291
|
+
|
|
292
|
+
Tier detection lives in `lib/tier-check.sh`. This ladder is guidance for readers; the actual detection stays in the lib per @luminary david-parnas (information hiding — the ladder is what changes; mechanism stays hidden).
|
|
293
|
+
|
|
294
|
+
## Class d cross-repo cure
|
|
295
|
+
|
|
296
|
+
Class d is a **modifier**, not a base class. It layers on top of a/b/c whenever the ticket crosses a repo boundary. Three obligations fire in addition to the base class discipline:
|
|
297
|
+
|
|
298
|
+
- **Name sibling repos.** In the PR body's Refs section, name every sibling repo the cure touches (e.g., `bassclef-cli#274`, `bassclef-web#256`). Cite the sister ticket URL when the sibling holds its own.
|
|
299
|
+
- **Release cascade.** After the upstream PR merges, run `/release` dry-run + andon + live to ship the fix downstream. Per `.claude/skills/release/SKILL.md`.
|
|
300
|
+
- **Peer coordination via SendMessage.** After the release page goes live, send a `SendMessage` to each affected sibling repo agent with the release URL + rebase-pin ask + smoke test ask. Address by `uds:/tmp/cc-socks/*.sock` per bassclef agent-to-agent protocol.
|
|
301
|
+
- **Cold-adopter smoke post-merge.** For every substrate change that reaches an adopter, verify on a fresh cold-adopter install (peer runs docker smoke against the new package pinning the fresh release tag).
|
|
302
|
+
|
|
303
|
+
## Refs
|
|
304
|
+
|
|
305
|
+
- `.claude/rules/oo-ad-entry-point.md` — ceremony-per-code-class matrix; class labels here map 1:1
|
|
306
|
+
- `.claude/rules/bootstrap-pair-discipline.md` — every new primitive ships with its paired bootstrap
|
|
307
|
+
- `.claude/rules/loop-discipline.md` — 6-step + 3-substep cycle per PR
|
|
308
|
+
- `.claude/rules/we-dont-break-adopters.md` — grace window keeps unlabeled prior invocations working
|
|
309
|
+
- `docs/rfcs/2026-09-27-longrun-4class-taxonomy.md` — 3-outside-lens RFC (Torvalds + Feathers + Parnas)
|
|
310
|
+
- `docs/risk-ledgers/2026-09-27-amend-1978-longrun-4class-taxonomy.md` — 3-lens pre-mortem (Ousterhout + Cooper + Prater)
|
|
311
|
+
- `docs/use-cases/UC-skill-longrun-4class-taxonomy.md` — brief use case
|
|
312
|
+
- @luminary john-ousterhout — lead (deep module — one lookup for discipline depth)
|
|
313
|
+
- @luminary alan-cooper — supporting (satisficing default — 3 base classes fit the scan ceiling)
|
|
314
|
+
- @luminary sophia-prater — supporting (progressive disclosure — orientation → labels → mechanisms)
|
|
315
|
+
- @luminary linus-torvalds — outside lens (adopter contract preserved via grace window)
|
|
316
|
+
- @luminary michael-feathers — outside lens (characterization test on the amended shape)
|
|
317
|
+
- @luminary david-parnas — outside lens (SKILL body hides mechanism paths behind rule cross-references)
|
|
318
|
+
<!-- /FKGL-EXEMPT -->
|
|
319
|
+
|
|
254
320
|
**Step 0.87 — Amendment A1: OOAD chain read at pre-flight (per #1150 + #1167 + #1414).** When the goal doc references touch adopter app paths (`src/`, `apps/`, `packages/`, `app/`) OR substrate paths (`lib/`, `scripts/`, `.claude/hooks/`, `.claude/skills/`, `.claude/rules/`, `.claude/luminaries/`, `standards/state-spine/schemas/`, `presence/install/`) OR scope prose names UI or code work, read the design chain per `.claude/bassclef-orientation.md` L100-108. Ceremony depth scales per `.claude/rules/oo-ad-entry-point.md` matrix — script bug fix = skip tier; script extension = brief use case; hook + lib = fully-dressed:
|
|
255
321
|
|
|
256
322
|
```
|
|
@@ -34,6 +34,39 @@ Run once per new consumer repo.
|
|
|
34
34
|
- Re-onboarding a repo that pre-dates the bassclef substrate
|
|
35
35
|
- Refreshing substrate wiring in an existing consumer (idempotent)
|
|
36
36
|
|
|
37
|
+
## First-run greeting (state-detected)
|
|
38
|
+
|
|
39
|
+
When the adopter just ran `bassclef init` and no other setup has advanced, the two most common paths are (a) share on GitHub and (b) top up local files. The full 4-option picker below covers every case; on a fresh install it reads longer than it needs to.
|
|
40
|
+
|
|
41
|
+
**State detection.** The greeting fires when ALL three hold:
|
|
42
|
+
|
|
43
|
+
- `.bassclef/init.manifest.json` exists at the repo root AND its `mtime` is under 24 hours old
|
|
44
|
+
- `git remote get-url origin` fails OR no upstream is configured on the current branch
|
|
45
|
+
- Current branch is `main` (or the repo's default)
|
|
46
|
+
|
|
47
|
+
When any condition fails, the greeting is silent and the full picker below fires unchanged. This preserves the current path for older repos, existing remotes, and feature branches.
|
|
48
|
+
|
|
49
|
+
**The greeting.** When state matches, emit:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Just ran bassclef init — welcome. Two most common paths from here:
|
|
53
|
+
|
|
54
|
+
1. I'll set up the GitHub remote and standard wiring.
|
|
55
|
+
(Same as Path A in the full picker.)
|
|
56
|
+
2. I'll top up the missing files on a `chore/bassclef-bootstrap`
|
|
57
|
+
branch. No remote work.
|
|
58
|
+
(Same as Path C in the full picker.)
|
|
59
|
+
|
|
60
|
+
Neither of these? Type `skip` for the full picker (adds `browse` and
|
|
61
|
+
`just me on this Mac` greenfield-from-intent options).
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Routing.** Answer `1` runs Path A. Answer `2` runs Path C. Answer `skip` — or any other input — falls through to the current 4-option picker below.
|
|
65
|
+
|
|
66
|
+
Anchor luminaries — @luminary don-norman (signifier + affordance) + @luminary alan-cooper (state detection matches the fresh-init user model).
|
|
67
|
+
|
|
68
|
+
Per `docs/use-cases/UC-skill-onboard-repo-friendlier-lead.md`.
|
|
69
|
+
|
|
37
70
|
## Which preset fits your setup?
|
|
38
71
|
|
|
39
72
|
Pick the shape that matches how you plan to work. The picker is the first fork. The procedure below runs the phases that match your pick.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "v1.6.
|
|
3
|
-
"marker_tag": "release-2026-09-
|
|
4
|
-
"release_date": "2026-09-
|
|
5
|
-
"release_sha": "
|
|
6
|
-
"release_tag": "release-2026-09-
|
|
2
|
+
"version": "v1.6.3",
|
|
3
|
+
"marker_tag": "release-2026-09-27-39c21000",
|
|
4
|
+
"release_date": "2026-09-27T23:49:28Z",
|
|
5
|
+
"release_sha": "39c21000",
|
|
6
|
+
"release_tag": "release-2026-09-27-39c21000"
|
|
7
7
|
}
|
|
@@ -22,8 +22,13 @@
|
|
|
22
22
|
# Adopter cure hint printed to stderr per match — non-blocking:
|
|
23
23
|
# "cure: mv <path> <path>.stale-<date>"
|
|
24
24
|
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
25
|
+
# Guard (bassclef-upstream#1958): the ceiling match on $HOME/.claude is
|
|
26
|
+
# suppressed. $HOME/.claude is Claude Code's per-user config home; suggesting
|
|
27
|
+
# `mv` on it would wipe adopter settings. Mid-walk cures for stale ancestor
|
|
28
|
+
# .claude/ dirs still fire. Env override BASSCLEF_ANCESTOR_CHECK_SKIP_HOME_CLAUDE=0
|
|
29
|
+
# disables the guard (used only by regression tests).
|
|
30
|
+
#
|
|
31
|
+
# Ships bassclef-upstream#573. Guard shipped bassclef-upstream#1958.
|
|
27
32
|
#
|
|
28
33
|
# Anchors:
|
|
29
34
|
# @luminary saltzer-schroeder — complete mediation (every ancestor level checked)
|
|
@@ -76,6 +81,19 @@ ancestor_claude_check() {
|
|
|
76
81
|
return 1
|
|
77
82
|
}
|
|
78
83
|
|
|
84
|
+
# Guard against destructive suggestions on $HOME/.claude (bassclef-upstream#1958).
|
|
85
|
+
# Claude Code stores per-user config under $HOME/.claude for every install.
|
|
86
|
+
# Suggesting `mv` on that path would wipe adopter settings. When the walker
|
|
87
|
+
# hits the canonicalized $HOME, skip both stdout and stderr for the .claude/
|
|
88
|
+
# match; mid-walk cures still fire. Env override
|
|
89
|
+
# BASSCLEF_ANCESTOR_CHECK_SKIP_HOME_CLAUDE=0 disables the guard (regression
|
|
90
|
+
# path for tests). Default = guard on (Nygard fail-safe default).
|
|
91
|
+
local guard_home_claude="${BASSCLEF_ANCESTOR_CHECK_SKIP_HOME_CLAUDE:-1}"
|
|
92
|
+
local protect_abs=""
|
|
93
|
+
if [ "$guard_home_claude" = "1" ] && [ -n "${HOME:-}" ] && [ -d "${HOME:-}" ]; then
|
|
94
|
+
protect_abs=$(cd "$HOME" && pwd -P)
|
|
95
|
+
fi
|
|
96
|
+
|
|
79
97
|
# Walk from parent of start up to (and including) ceiling
|
|
80
98
|
local cur="$start_abs"
|
|
81
99
|
local today
|
|
@@ -83,8 +101,12 @@ ancestor_claude_check() {
|
|
|
83
101
|
while [ "$cur" != "/" ]; do
|
|
84
102
|
cur=$(dirname "$cur")
|
|
85
103
|
if [ -d "$cur/.claude" ]; then
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
if [ -n "$protect_abs" ] && [ "$cur" = "$protect_abs" ]; then
|
|
105
|
+
: # guarded — $cur is canonicalized $HOME; skip emit per bassclef-upstream#1958
|
|
106
|
+
else
|
|
107
|
+
echo "$cur/.claude"
|
|
108
|
+
echo "cure: mv \"$cur/.claude\" \"$cur/.claude.stale-$today\"" >&2
|
|
109
|
+
fi
|
|
88
110
|
fi
|
|
89
111
|
# Stop after processing ceiling — do not walk above it
|
|
90
112
|
if [ "$cur" = "$ceiling_abs" ]; then
|
|
@@ -121,20 +121,14 @@ announce_mode() {
|
|
|
121
121
|
echo "${skill}: running in \`${mode}\` mode — ${reason}. Say \`--mode=<other>\` to override." >&2
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
# --- Lib self-check
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return 1
|
|
136
|
-
fi
|
|
137
|
-
return 0
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
__bassclef_capability_probe_self_check || true
|
|
124
|
+
# --- Lib self-check removed (bassclef-upstream#1962) -------------------------
|
|
125
|
+
# The runtime self-check at this location fired a false WARN against cold
|
|
126
|
+
# `@thebassclef/lite@1.6.2` on bassclef-cli-aa smoke — every named function
|
|
127
|
+
# was defined earlier in the same file yet `type -t` returned non-zero. Root
|
|
128
|
+
# cause not reproduced in bassclef-upstream root. The Tier 0 test at
|
|
129
|
+
# lib/tests/capability-probe.test.sh T02 already asserts every function
|
|
130
|
+
# resolves after source. That test is a stricter signal (blocks merge on
|
|
131
|
+
# regression) and lands more precisely (names missing functions) than the
|
|
132
|
+
# runtime WARN. Per @luminary michael-nygard — a warning that fires on healthy
|
|
133
|
+
# state erodes trust. Per @luminary michael-feathers — the characterization
|
|
134
|
+
# test is the single source of truth for the contract.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# lib/statusline-version.sh — bassclef statusline reader.
|
|
4
|
+
#
|
|
5
|
+
# Emits one plain-text line under 80 characters describing bassclef substrate
|
|
6
|
+
# version + sync status. Called by Claude Code's statusLine.command hook every
|
|
7
|
+
# ~300 ms; must be fast (< 20 ms typical) and fail-soft (exit 0 on every path).
|
|
8
|
+
#
|
|
9
|
+
# Reads:
|
|
10
|
+
# $REPO_ROOT/bassclef-version.json — repo-root version file
|
|
11
|
+
# $REPO_ROOT/state/bassclef-sync-status.json — sync state (written by
|
|
12
|
+
# bassclef-sync.sh at end-of-run)
|
|
13
|
+
#
|
|
14
|
+
# Output shapes:
|
|
15
|
+
# bassclef v<version> · <sync_status> — happy path (both files valid)
|
|
16
|
+
# bassclef · initializing — state file absent
|
|
17
|
+
# bassclef · not synced — version file absent OR either
|
|
18
|
+
# file malformed JSON
|
|
19
|
+
#
|
|
20
|
+
# Adopter opt-in — user-scope ~/.claude/settings.json:
|
|
21
|
+
# {
|
|
22
|
+
# "statusLine": {
|
|
23
|
+
# "command": "bash /path/to/repo/lib/statusline-version.sh"
|
|
24
|
+
# }
|
|
25
|
+
# }
|
|
26
|
+
#
|
|
27
|
+
# Ships bassclef-upstream#1961.
|
|
28
|
+
# Anchors:
|
|
29
|
+
# @luminary don-norman — signifier (always-visible substrate state)
|
|
30
|
+
# @luminary alan-cooper — persistent status over modal announcement
|
|
31
|
+
# @luminary michael-nygard — fail-soft; exit 0 on every path
|
|
32
|
+
|
|
33
|
+
set +e # fail-soft on every read; never crash the statusline
|
|
34
|
+
|
|
35
|
+
# --- Resolve repo root ---
|
|
36
|
+
_resolve_repo_root() {
|
|
37
|
+
if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -d "${CLAUDE_PROJECT_DIR:-}" ]; then
|
|
38
|
+
echo "$CLAUDE_PROJECT_DIR"
|
|
39
|
+
return
|
|
40
|
+
fi
|
|
41
|
+
local root
|
|
42
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
43
|
+
if [ -n "$root" ] && [ -d "$root" ]; then
|
|
44
|
+
echo "$root"
|
|
45
|
+
return
|
|
46
|
+
fi
|
|
47
|
+
pwd
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# --- Emit under 80-char cap ---
|
|
51
|
+
_emit() {
|
|
52
|
+
local line="$1"
|
|
53
|
+
# Cap at 80 chars per pre-mortem R1 fold.
|
|
54
|
+
if [ "${#line}" -gt 80 ]; then
|
|
55
|
+
line="${line:0:77}..."
|
|
56
|
+
fi
|
|
57
|
+
echo "$line"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# --- Main ---
|
|
61
|
+
statusline_version() {
|
|
62
|
+
local root
|
|
63
|
+
root=$(_resolve_repo_root)
|
|
64
|
+
|
|
65
|
+
local version_file="$root/bassclef-version.json"
|
|
66
|
+
local status_file="$root/state/bassclef-sync-status.json"
|
|
67
|
+
|
|
68
|
+
# Version file — required for happy path
|
|
69
|
+
if [ ! -f "$version_file" ]; then
|
|
70
|
+
_emit "bassclef · not synced"
|
|
71
|
+
return 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
local version
|
|
75
|
+
version=$(jq -r '.version // ""' "$version_file" 2>/dev/null)
|
|
76
|
+
if [ -z "$version" ]; then
|
|
77
|
+
# jq returned empty (malformed JSON or missing .version)
|
|
78
|
+
_emit "bassclef · not synced"
|
|
79
|
+
return 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# State file — absent means "sync has not run this session"
|
|
83
|
+
if [ ! -f "$status_file" ]; then
|
|
84
|
+
_emit "bassclef · initializing"
|
|
85
|
+
return 0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
local sync_status
|
|
89
|
+
sync_status=$(jq -r '.sync_status // ""' "$status_file" 2>/dev/null)
|
|
90
|
+
if [ -z "$sync_status" ]; then
|
|
91
|
+
# Malformed state file OR missing .sync_status field
|
|
92
|
+
_emit "bassclef · not synced"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
_emit "bassclef ${version} · ${sync_status}"
|
|
97
|
+
return 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# When invoked directly (not sourced), emit the line.
|
|
101
|
+
if [ "${BASH_SOURCE[0]:-}" = "${0}" ]; then
|
|
102
|
+
statusline_version
|
|
103
|
+
fi
|
|
@@ -48,21 +48,40 @@ __bassclef_statusline_version() {
|
|
|
48
48
|
echo "thin-pointer-statusline-2026-06-22"
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
# Read
|
|
52
|
-
|
|
51
|
+
# Read stdin only when it is a pipe. When stdin is a TTY (operator direct-
|
|
52
|
+
# invokes for debugging), `cat` would block forever. Per bassclef-upstream#1981
|
|
53
|
+
# defect A2: treat terminal stdin as empty input.
|
|
54
|
+
if [ -t 0 ]; then
|
|
55
|
+
INPUT="{}"
|
|
56
|
+
else
|
|
57
|
+
INPUT=$(cat 2>/dev/null || echo "{}")
|
|
58
|
+
fi
|
|
53
59
|
|
|
54
|
-
# Resolve this dispatcher's directory for the script-relative
|
|
55
|
-
# `cd $(dirname); pwd` is portable across macOS
|
|
56
|
-
#
|
|
57
|
-
|
|
60
|
+
# Resolve this dispatcher's directory + canonical path for the script-relative
|
|
61
|
+
# fallback + self-loop guard. `cd $(dirname); pwd -P` is portable across macOS
|
|
62
|
+
# (BSD readlink lacks -f) and Linux, and resolves symlinks so a symlinked
|
|
63
|
+
# install still matches the underlying rich-impl file.
|
|
64
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
65
|
+
SELF_PATH="$SCRIPT_DIR/$(basename "${BASH_SOURCE[0]}")"
|
|
58
66
|
|
|
59
|
-
# Find rich impl via sibling fast-path, then script-relative fallback
|
|
67
|
+
# Find rich impl via sibling fast-path, then script-relative fallback. Self-
|
|
68
|
+
# loop guard per bassclef-upstream#1981 defect A1: skip any candidate that
|
|
69
|
+
# canonicalizes to this dispatcher's own path. Real adopter case — `bassclef
|
|
70
|
+
# init` installs this dispatcher AS `~/.claude/bassclef-statusline.sh` (same
|
|
71
|
+
# basename as the rich impl in the tarball). Without the guard, the script-
|
|
72
|
+
# relative candidate resolves to the dispatcher itself; `bash $CANDIDATE`
|
|
73
|
+
# then exec's the dispatcher forever.
|
|
60
74
|
RICH_IMPL=""
|
|
61
75
|
for CANDIDATE in \
|
|
62
76
|
"$HOME/src/sunj-labs/bassclef/presence/cli/bassclef-statusline.sh" \
|
|
63
77
|
"$HOME/src/sunj-labs/bassclef-upstream/presence/cli/bassclef-statusline.sh" \
|
|
64
78
|
"$SCRIPT_DIR/bassclef-statusline.sh"; do
|
|
65
79
|
if [ -x "$CANDIDATE" ]; then
|
|
80
|
+
# Canonicalize the candidate; skip when it matches SELF_PATH.
|
|
81
|
+
CAND_ABS="$(cd "$(dirname "$CANDIDATE")" 2>/dev/null && pwd -P)/$(basename "$CANDIDATE")"
|
|
82
|
+
if [ "$CAND_ABS" = "$SELF_PATH" ]; then
|
|
83
|
+
continue
|
|
84
|
+
fi
|
|
66
85
|
RICH_IMPL="$CANDIDATE"
|
|
67
86
|
break
|
|
68
87
|
fi
|
|
@@ -292,6 +292,42 @@ _template_emit_install_postcondition() {
|
|
|
292
292
|
fi
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
# === #1981 — sync state file writer (parity with operator hook L688-707) ===
|
|
296
|
+
# Persist sync state so lib/statusline-version.sh reader can render
|
|
297
|
+
# `bassclef v<X> · <sync_status>` on the adopter terminal statusline every
|
|
298
|
+
# ~300ms tick. Mirrors the operator-scope write block at
|
|
299
|
+
# .claude/hooks/bassclef-sync.sh L688-707. Both hooks must produce the same
|
|
300
|
+
# postcondition per ADR-032 two-stage rocket.
|
|
301
|
+
#
|
|
302
|
+
# Fail-soft: jq missing OR write path unwritable → file simply doesn't land;
|
|
303
|
+
# reader falls back to `bassclef · initializing` per lib/statusline-version.sh L83-85.
|
|
304
|
+
# Never break the sync summary that follows.
|
|
305
|
+
#
|
|
306
|
+
# Defined ABOVE the BASSCLEF_SYNC_TEST_MODE guard so tests can source + invoke.
|
|
307
|
+
#
|
|
308
|
+
# Anchor luminaries — @luminary michael-nygard (bounded-time observable state),
|
|
309
|
+
# @luminary michael-feathers (mirrors the operator hook exactly — characterization
|
|
310
|
+
# parity), @luminary jerome-saltzer-and-michael-schroeder (complete mediation
|
|
311
|
+
# across both operator + adopter paths).
|
|
312
|
+
_template_write_sync_status_file() {
|
|
313
|
+
local cwd="$1"
|
|
314
|
+
local sync_status="$2"
|
|
315
|
+
local status_file="${cwd}/state/bassclef-sync-status.json"
|
|
316
|
+
local version release_tag
|
|
317
|
+
version=$(jq -r '.version // ""' "${cwd}/bassclef-version.json" 2>/dev/null || echo "")
|
|
318
|
+
release_tag=$(jq -r '.release_tag // ""' "${cwd}/bassclef-version.json" 2>/dev/null || echo "")
|
|
319
|
+
mkdir -p "$(dirname "$status_file")" 2>/dev/null || return 0
|
|
320
|
+
cat > "$status_file" 2>/dev/null <<EOF
|
|
321
|
+
{
|
|
322
|
+
"version": "${version}",
|
|
323
|
+
"release_tag": "${release_tag}",
|
|
324
|
+
"sync_status": "${sync_status}",
|
|
325
|
+
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
326
|
+
}
|
|
327
|
+
EOF
|
|
328
|
+
return 0
|
|
329
|
+
}
|
|
330
|
+
|
|
295
331
|
# === Test-mode early return (#897 Cure A) ===
|
|
296
332
|
# Tests source this template to access helper functions (see
|
|
297
333
|
# .claude/hooks/tests/bassclef-sync-*.test.sh). Early return under
|
|
@@ -1936,6 +1972,13 @@ if [ -f "$HOOK_SELF" ] && [ -f "$HOOK_BASSCLEF" ]; then
|
|
|
1936
1972
|
esac
|
|
1937
1973
|
fi
|
|
1938
1974
|
|
|
1975
|
+
# --- Persist sync state for statusline reader (bassclef-upstream#1981) ---
|
|
1976
|
+
# Mirrors operator hook L688-707. Adopters who opt in to
|
|
1977
|
+
# lib/statusline-version.sh at ~/.claude/settings.json read this file at every
|
|
1978
|
+
# prompt to render `bassclef v<X> · <sync_status>`. Written here at end-of-run;
|
|
1979
|
+
# sole writer for the adopter path. Fail-soft — never break sync summary.
|
|
1980
|
+
{ _template_write_sync_status_file "$CWD" "$SYNC_STATUS"; } 2>/dev/null || true
|
|
1981
|
+
|
|
1939
1982
|
echo ""
|
|
1940
1983
|
echo "### BASSCLEF SUBSTRATE — SYNCED"
|
|
1941
1984
|
echo ""
|
|
@@ -479,13 +479,15 @@ done < <(find "$REPO_ROOT/.claude/embeddings" -type f -name '*.json' 2>/dev/null
|
|
|
479
479
|
LIB_FILES=()
|
|
480
480
|
while IFS= read -r f; do
|
|
481
481
|
LIB_FILES+=("$f")
|
|
482
|
-
done < <(find "$REPO_ROOT/lib" -
|
|
482
|
+
done < <(find "$REPO_ROOT/lib" -type f -name '*.sh' \
|
|
483
|
+
-not -path '*/tests/*' -not -path '*/fixtures/*' 2>/dev/null | sort)
|
|
483
484
|
|
|
484
485
|
# Script files (bash) — per #1392 Shape 3; adopter runtime needs scripts/bassclef-*.sh helpers
|
|
485
486
|
SCRIPT_FILES=()
|
|
486
487
|
while IFS= read -r f; do
|
|
487
488
|
SCRIPT_FILES+=("$f")
|
|
488
|
-
done < <(find "$REPO_ROOT/scripts" -
|
|
489
|
+
done < <(find "$REPO_ROOT/scripts" -type f -name '*.sh' \
|
|
490
|
+
-not -path '*/tests/*' -not -path '*/fixtures/*' 2>/dev/null | sort)
|
|
489
491
|
|
|
490
492
|
# Hook fragments (bash) — per goal 2026-09-16a (bassclef-upstream#1690): session-reflection.sh
|
|
491
493
|
# iterates session-reflection.d/*.sh at SessionStart; fragments ship as type=hook.
|
|
@@ -506,25 +508,29 @@ done < <(find "$REPO_ROOT/.claude" -maxdepth 1 -type f -name '*.md' 2>/dev/null
|
|
|
506
508
|
PRESENCE_TEMPLATE_SH_FILES=()
|
|
507
509
|
while IFS= read -r f; do
|
|
508
510
|
PRESENCE_TEMPLATE_SH_FILES+=("$f")
|
|
509
|
-
done < <(find "$REPO_ROOT/presence/install" -
|
|
511
|
+
done < <(find "$REPO_ROOT/presence/install" -type f -name '*.sh' \
|
|
512
|
+
-not -path '*/tests/*' -not -path '*/fixtures/*' 2>/dev/null | sort)
|
|
510
513
|
|
|
511
514
|
PRESENCE_TEMPLATE_MD_FILES=()
|
|
512
515
|
while IFS= read -r f; do
|
|
513
516
|
PRESENCE_TEMPLATE_MD_FILES+=("$f")
|
|
514
|
-
done < <(find "$REPO_ROOT/presence/install" -
|
|
517
|
+
done < <(find "$REPO_ROOT/presence/install" -type f -name '*.md' \
|
|
518
|
+
-not -path '*/tests/*' -not -path '*/fixtures/*' 2>/dev/null | sort)
|
|
515
519
|
|
|
516
520
|
# Presence templates (jsonc) — Ship 1 (bassclef-cli#104) — configs template
|
|
517
521
|
# ships so `bassclef init` can copy it to adopter's `.claude/bassclef-configs.jsonc`.
|
|
518
522
|
PRESENCE_TEMPLATE_JSONC_FILES=()
|
|
519
523
|
while IFS= read -r f; do
|
|
520
524
|
PRESENCE_TEMPLATE_JSONC_FILES+=("$f")
|
|
521
|
-
done < <(find "$REPO_ROOT/presence/install" -
|
|
525
|
+
done < <(find "$REPO_ROOT/presence/install" -type f -name '*.jsonc' \
|
|
526
|
+
-not -path '*/tests/*' -not -path '*/fixtures/*' 2>/dev/null | sort)
|
|
522
527
|
|
|
523
528
|
# Templates (md) — per #1392 Shape 3; chronicle-template + iteration-goal etc adopters copy
|
|
524
529
|
TEMPLATE_FILES=()
|
|
525
530
|
while IFS= read -r f; do
|
|
526
531
|
TEMPLATE_FILES+=("$f")
|
|
527
|
-
done < <(find "$REPO_ROOT/templates" -
|
|
532
|
+
done < <(find "$REPO_ROOT/templates" -type f -name '*.md' \
|
|
533
|
+
-not -path '*/tests/*' -not -path '*/fixtures/*' 2>/dev/null | sort)
|
|
528
534
|
|
|
529
535
|
# Root docs (md) — per #1392 Shape 3; README + CLAUDE + AGENTS adopters read on install
|
|
530
536
|
ROOT_DOC_FILES=()
|