@thebassclef/lite 1.9.15 → 1.9.18
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/agents/builder.md +7 -4
- package/dist/lite/.claude/bassclef-orientation.md +22 -0
- package/dist/lite/.claude/hooks/artifact-ingestion-gate.sh +11 -3
- package/dist/lite/.claude/hooks/assert-verify-steering.sh +1 -19
- package/dist/lite/.claude/hooks/plain-english-steering.sh +86 -41
- package/dist/lite/.claude/hooks/pre-commit-gate.sh +10 -2
- package/dist/lite/.claude/hooks/pre-push-shellcheck.sh +144 -0
- package/dist/lite/.claude/hooks/reviewer-dispatch-check.sh +153 -0
- package/dist/lite/.claude/hooks/session-reflection.d/83-install-written-paths-rebuild.sh +15 -32
- package/dist/lite/.claude/hooks/turn-prose-density-measure.sh +109 -0
- package/dist/lite/.claude/hooks/turn-prose-kiss-check.sh +28 -0
- package/dist/lite/.claude/rules/branching.md +2 -0
- package/dist/lite/.claude/rules/identifier-leak-prevention.md +2 -1
- package/dist/lite/.claude/rules/lite-manifest-schema-change-discipline.md +23 -0
- package/dist/lite/.claude/rules/reviewer-dispatch.md +95 -0
- package/dist/lite/.claude/settings.json +10 -0
- package/dist/lite/.claude/skills/build/SKILL.md +81 -6
- package/dist/lite/.claude/skills/eisenhower/SKILL.md +26 -0
- package/dist/lite/.claude/skills/launch/SKILL.md +45 -2
- package/dist/lite/.claude/skills/onboard-repo/SKILL.md +204 -40
- package/dist/lite/.claude/skills/onboard-repo/concise-prompt.sh +73 -0
- package/dist/lite/.claude/skills/onboard-repo/write-state-marker.sh +81 -0
- package/dist/lite/.claude/skills/pre-mortem/SKILL.md +56 -0
- package/dist/lite/.claude/skills/sprint/SKILL.md +24 -3
- package/dist/lite/.claude/skills/temperance/SKILL.md +15 -4
- package/dist/lite/README.md +2 -0
- package/dist/lite/bassclef-version.json +5 -5
- package/dist/lite/lib/install-written-paths.sh +250 -3
- package/dist/lite/lib/prose-marker.sh +203 -0
- package/dist/lite/lib/tier-dependency-audit.sh +52 -25
- package/dist/lite/presence/install/bassclef-sync.template.sh +86 -8
- package/dist/lite/scripts/generate-lite-manifest.sh +15 -0
- package/dist/lite/scripts/launch/local-serve.sh +149 -8
- package/dist/lite/standards/bassclef-wiring-manifest.json +12 -0
- package/dist/lite/standards/lite-manifest-schema-changes.md +49 -0
- package/dist/lite/standards/lite-manifest.json +90 -27
- package/dist/lite/standards/prose-marker-format.md +105 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ your browser.
|
|
|
67
67
|
|
|
68
68
|
## Current release
|
|
69
69
|
|
|
70
|
-
<!-- version-start -->1.9.
|
|
70
|
+
<!-- version-start -->1.9.18<!-- version-end -->
|
|
71
71
|
|
|
72
72
|
See [CHANGELOG.md](CHANGELOG.md) for the full changelog. Adopter-facing
|
|
73
73
|
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.18";
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ tier: lite
|
|
|
3
3
|
name: Builder
|
|
4
4
|
description: "Executes against spec. Respects ADR constraints. Commits with conventional commits. Doesn't redesign mid-build. Raises blockers immediately."
|
|
5
5
|
tools: Read, Write, Edit, Glob, Grep, Bash
|
|
6
|
-
model: claude-sonnet-
|
|
6
|
+
model: claude-sonnet-5
|
|
7
7
|
model_tier: balanced
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -16,15 +16,18 @@ You execute. You don't redesign mid-build. You raise blockers immediately rather
|
|
|
16
16
|
- Construction (primary)
|
|
17
17
|
|
|
18
18
|
## Responsibilities
|
|
19
|
-
- Run /pattern-review BEFORE implementing
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
- Run /pattern-review BEFORE implementing **when a pattern catalog exists** —
|
|
20
|
+
check `state/pattern-implementations/` or `patterns/` for a catalog. If
|
|
21
|
+
present, review GoF patterns, Fowler refactoring smells, SOLID principles,
|
|
22
|
+
DDD alignment. If absent, skip the ask — do not block on a missing catalog.
|
|
23
|
+
Per bassclef-upstream#2175 Batch C (F8).
|
|
22
24
|
- Implement against spec acceptance criteria + pattern guidance
|
|
23
25
|
- Respect ADR constraints — flag violations rather than working around them
|
|
24
26
|
- Commit with conventional commits
|
|
25
27
|
- Open PRs with context, not just diffs
|
|
26
28
|
- Run /temperance before non-trivial tasks
|
|
27
29
|
- Run /verify after each task before committing
|
|
30
|
+
- **Friction report before marking a step done (per bassclef-upstream#2175 Batch C — F9).** Before stating a step is complete, read `docs/session-friction-log.md` for new entries from this step. Report friction count in the PR body under a Friction section. Escalate `BLOCK`-tagged entries to Architect.
|
|
28
31
|
|
|
29
32
|
## Decision authority
|
|
30
33
|
Implementation approach within ADR constraints. Escalate if a constraint must be broken.
|
|
@@ -146,6 +146,28 @@ Pick the tier that matches your trust + budget. Upgrade path is additive — no
|
|
|
146
146
|
- `/luminary` — design-lens catalog
|
|
147
147
|
- `/hooks` — mechanical-enforcement layer
|
|
148
148
|
|
|
149
|
+
## Concise output style (opt-in)
|
|
150
|
+
|
|
151
|
+
Anthropic ships four output styles besides Default — Proactive, Concise, Explanatory, Learning. See [Output styles docs](https://code.claude.com/docs/en/output-styles). The Concise style (v2.1.237+) leads replies with the result, skips step-by-step narration, and skips closing recaps. Simple questions answer in one to three sentences. Full-length output stays for error reports, failing tests, security warnings, and destructive-action confirmations.
|
|
152
|
+
|
|
153
|
+
Concise covers prose dimensions bassclef already steers via `operator-facing-prose.md` + `writing-craft-discipline.md` + visual hierarchy. Enabling it at operator-user scope reduces per-turn hook-injected reminders for those dimensions. The bassclef-specific layers (grade target, jargon wordlist, appositive discipline, claim + evidence + warrant shape, per-turn metric feedback) stay as the written contract. Your project CLAUDE.md and rules stay loaded regardless of the style — Concise shapes response form, not what the model reads about your project. The before-and-after measure for Concise plus bassclef combined lands in bassclef-upstream#2197 Step 1 baseline.
|
|
154
|
+
|
|
155
|
+
Bassclef does not set the output style at project scope. Project settings would override adopter choice. To enable, add this field to your user-scope `~/.claude/settings.json`:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"outputStyle": "Concise"
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The value is case-sensitive — `"Concise"` with a capital C. A value that does not match an output-style name exactly falls back to Default silently. The `/output-style concise` command (v2.1.269+) is case-insensitive and offers the same result.
|
|
164
|
+
|
|
165
|
+
Switching styles mid-session takes effect at the next message and invalidates one prompt-cache slot per the [prompt-caching docs](https://code.claude.com/docs/en/prompt-caching#changing-output-style). After the first message post-switch, cache resumes.
|
|
166
|
+
|
|
167
|
+
See `docs/audits/2026-10-12-anthropic-builtins-vs-bassclef-steering.md` for the overlap analysis — which bassclef dimensions Concise covers, which bassclef dimensions stay bassclef's own.
|
|
168
|
+
|
|
169
|
+
Ships bassclef-upstream#2205.
|
|
170
|
+
|
|
149
171
|
## Statusline (opt-in)
|
|
150
172
|
|
|
151
173
|
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.
|
|
@@ -84,16 +84,24 @@ elif [ "$TOOL_NAME" = "Edit" ]; then
|
|
|
84
84
|
${NEW_STRING}"
|
|
85
85
|
fi
|
|
86
86
|
|
|
87
|
-
# Check for Sources-read block (case-insensitive, tolerant of heading depth
|
|
87
|
+
# Check for Sources-read block (case-insensitive, tolerant of heading depth,
|
|
88
|
+
# tolerant of leading whitespace per bassclef-upstream#2142).
|
|
88
89
|
# Accepted forms:
|
|
89
|
-
# ## Sources read
|
|
90
|
+
# ## Sources read (markdown artifact at col 0)
|
|
90
91
|
# ### Sources read
|
|
91
92
|
# ## Sources Read
|
|
92
93
|
# ## SOURCES READ
|
|
94
|
+
# ## Sources read (indented inside HTML comment per #2142)
|
|
93
95
|
# Also accepts an explicit from-scratch rationale:
|
|
94
96
|
# "No prior artifacts" (per rule)
|
|
97
|
+
#
|
|
98
|
+
# Prior anchor `^#{1,6}` required the hash to sit at column 0. HTML prototype
|
|
99
|
+
# files produced by /launch --local carry sources inside a top-of-file
|
|
100
|
+
# `<!-- ... -->` comment where the hash is naturally indented. The anchor
|
|
101
|
+
# removal keeps match specificity (hash prefix + exact phrase) without
|
|
102
|
+
# false-positive risk on arbitrary prose.
|
|
95
103
|
HAS_SOURCES_READ=0
|
|
96
|
-
if echo "$FUTURE_CONTENT" | grep -qiE '
|
|
104
|
+
if echo "$FUTURE_CONTENT" | grep -qiE '#{1,6}[[:space:]]+sources[[:space:]]+read'; then
|
|
97
105
|
HAS_SOURCES_READ=1
|
|
98
106
|
fi
|
|
99
107
|
if echo "$FUTURE_CONTENT" | grep -qi 'no prior artifacts'; then
|
|
@@ -52,25 +52,7 @@ if [ "${SKIP_ASSERT_VERIFY_STEERING:-0}" = "1" ]; then
|
|
|
52
52
|
fi
|
|
53
53
|
|
|
54
54
|
# === Steering content ===
|
|
55
|
-
STEERING_CONTENT="**Plain check:** Before I make a claim
|
|
56
|
-
|
|
57
|
-
**Check my claims before I write them:**
|
|
58
|
-
|
|
59
|
-
For any claim to the operator — a finding, a rating, a fix I recommend, why I think X caused Y, how many turns something will take, or a strong-word claim (wrong, broken, should, must, always) — I fill three parts:
|
|
60
|
-
|
|
61
|
-
1. **My claim** — the assertion in one sentence.
|
|
62
|
-
2. **Evidence** — the source I read, cited inline (file path, ticket number, git SHA, chronicle filename, standard section).
|
|
63
|
-
3. **Why the evidence supports the claim** — one sentence tying the source to the claim.
|
|
64
|
-
|
|
65
|
-
Add more when it helps:
|
|
66
|
-
|
|
67
|
-
4. **Confidence plus how many observations back it** — from Toulmin, *The Uses of Argument* (1958).
|
|
68
|
-
5. **What would falsify the claim** — from Popper, *The Logic of Scientific Discovery* (1959). A claim that cannot be falsified is a guess.
|
|
69
|
-
6. **Two or three alternative explanations that fit the same data** — from Peirce, *Deduction, Induction, and Hypothesis* (1878). Name each option before you pick one.
|
|
70
|
-
|
|
71
|
-
If I cannot fill all three, I do not ship the claim. I read the source first.
|
|
72
|
-
|
|
73
|
-
Per bassclef#559 and .claude/rules/assert-only-after-verify.md."
|
|
55
|
+
STEERING_CONTENT="**Plain check:** Before I make a claim, I read the source first and cite it. Every operator-facing claim carries three parts — **My claim** (one sentence) + **Evidence** (source cited inline — file path, ticket, SHA) + **Why the evidence supports the claim** (one sentence). Add Toulmin confidence, Popper falsifiability, or Peirce alternatives when they help. If any part is missing, I do not ship — read the source first. Full shape at .claude/rules/assert-only-after-verify.md (bassclef#559)."
|
|
74
56
|
|
|
75
57
|
# === Emit envelope ===
|
|
76
58
|
emit_additional_context "UserPromptSubmit" "$STEERING_CONTENT"
|
|
@@ -33,6 +33,21 @@ for _candidate in \
|
|
|
33
33
|
fi
|
|
34
34
|
done
|
|
35
35
|
|
|
36
|
+
# === Source lib/prose-marker.sh (Longrun 2 Step 2d — scorer stack reader) ===
|
|
37
|
+
# Provides read_prose_markers for density + appositive scorer markers.
|
|
38
|
+
# Silent-fail — missing lib = no scorer line injection; existing grade +
|
|
39
|
+
# flags paths unchanged.
|
|
40
|
+
for _candidate in \
|
|
41
|
+
"${SCRIPT_DIR}/../../lib/prose-marker.sh" \
|
|
42
|
+
"${HOME_OVERRIDE:-${HOME:-/}}/lib/prose-marker.sh"; do
|
|
43
|
+
if [ -f "$_candidate" ]; then
|
|
44
|
+
# shellcheck disable=SC1090
|
|
45
|
+
source "$_candidate"
|
|
46
|
+
break
|
|
47
|
+
fi
|
|
48
|
+
done
|
|
49
|
+
unset _candidate
|
|
50
|
+
|
|
36
51
|
# Defensive: if lib not loaded, exit silent
|
|
37
52
|
if ! declare -f emit_additional_context >/dev/null 2>&1; then
|
|
38
53
|
exit 0
|
|
@@ -119,51 +134,81 @@ if [ -f "$FLAGS_MARKER" ]; then
|
|
|
119
134
|
fi
|
|
120
135
|
fi
|
|
121
136
|
|
|
122
|
-
# ===
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
When I author NEW prose for the operator or an adopter, I use the new terms:
|
|
140
|
-
|
|
141
|
-
- Say **goal** not **bet**. (Historical goal IDs like \`bet 08-11a\` stay as-is.)
|
|
142
|
-
- Say **step** not **WU** or **workunit**. (Historical \`WU-2\` refs stay.)
|
|
143
|
-
- Say **in_flight_goal** not **in_flight_bet** for new whereami updates.
|
|
144
|
-
- Say **time budget** not **appetite** for new prose.
|
|
145
|
-
- Say **parent goal** and **thread** not **parent bet** and **arc** for new prose.
|
|
146
|
-
|
|
147
|
-
**Discrimination cue.** Two situations, two different rules:
|
|
148
|
-
|
|
149
|
-
1. When I AUTHOR — writing my own sentence about work in flight or scope decisions — I use the new terms (Step-N, goal, time budget). Never introduce \`WU-N\` or \`bet\` or \`appetite\` in new prose.
|
|
150
|
-
2. When I QUOTE a past goal ID or reference (\`bet 27c WU-5\`, \`bet 2026-06-20a WU-1\`) — I preserve the exact historical string verbatim inside backticks. The backticks mark the historical boundary. The reader sees \"quote\" not \"active vocabulary.\"
|
|
151
|
-
|
|
152
|
-
Rule of thumb: if I am about to type \`WU-\` in a sentence, ask — am I quoting a historical goal ID? If yes, wrap in backticks. If no, use Step-N instead.
|
|
137
|
+
# === Resolve scorer_track opt-in flag (Longrun 2 Step 2f — ADR-049) ===
|
|
138
|
+
# Default false per ADR-049 opt-in. Config at .claude/bassclef-configs.jsonc
|
|
139
|
+
# field prose_discipline.scorer_track. Env PROSE_DISCIPLINE_SCORER_TRACK
|
|
140
|
+
# overrides the config.
|
|
141
|
+
SCORER_TRACK="${PROSE_DISCIPLINE_SCORER_TRACK:-}"
|
|
142
|
+
if [ -z "$SCORER_TRACK" ]; then
|
|
143
|
+
if declare -f resolve_config_path >/dev/null 2>&1; then
|
|
144
|
+
CONFIG_FILE_FOR_SCORER=$(resolve_config_path 2>/dev/null) || CONFIG_FILE_FOR_SCORER=""
|
|
145
|
+
else
|
|
146
|
+
CONFIG_FILE_FOR_SCORER="${SCRIPT_DIR}/../bassclef-configs.jsonc"
|
|
147
|
+
fi
|
|
148
|
+
if [ -n "$CONFIG_FILE_FOR_SCORER" ] && [ -f "$CONFIG_FILE_FOR_SCORER" ]; then
|
|
149
|
+
SCORER_TRACK=$(sed 's|//.*||g' "$CONFIG_FILE_FOR_SCORER" 2>/dev/null \
|
|
150
|
+
| jq -r '.prose_discipline.scorer_track // false' 2>/dev/null)
|
|
151
|
+
fi
|
|
152
|
+
SCORER_TRACK="${SCORER_TRACK:-false}"
|
|
153
|
+
fi
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
# === Read scorer-stack markers (Longrun 2 Step 2d — density + appositive) ===
|
|
156
|
+
# Reads turn-prose-density + turn-prose-appositive markers via lib. Builds
|
|
157
|
+
# a one-line scorer digest with glyph legend per standards/prose-marker-format.md v1.0.
|
|
158
|
+
# Silent-fail — missing lib or markers = empty SCORER_LINE; existing paths unchanged.
|
|
159
|
+
# Gated by SCORER_TRACK opt-in flag (Step 2f).
|
|
160
|
+
SCORER_LINE=""
|
|
161
|
+
if [ "$SCORER_TRACK" = "true" ] && declare -f read_prose_markers >/dev/null 2>&1; then
|
|
162
|
+
SCORER_JSON=$(read_prose_markers "turn-prose-density" "turn-prose-appositive" 2>/dev/null)
|
|
163
|
+
if [ -n "$SCORER_JSON" ]; then
|
|
164
|
+
SCORER_LINE=$(echo "$SCORER_JSON" | python3 -c '
|
|
165
|
+
import json, sys
|
|
166
|
+
try:
|
|
167
|
+
data = json.loads(sys.stdin.read())
|
|
168
|
+
except Exception:
|
|
169
|
+
sys.exit(0)
|
|
170
|
+
parts = []
|
|
171
|
+
# density scorer — green=0 amber=3 per standards/prose-marker-format.md
|
|
172
|
+
d = data.get("turn-prose-density")
|
|
173
|
+
if d and isinstance(d, dict) and "value" in d:
|
|
174
|
+
v = d["value"]
|
|
175
|
+
glyph = "🟢" if v <= 0 else ("🟡" if v <= 3 else "🔴")
|
|
176
|
+
parts.append(f"density {v} {glyph}")
|
|
177
|
+
# appositive scorer — green=0 amber=2
|
|
178
|
+
a = data.get("turn-prose-appositive")
|
|
179
|
+
if a and isinstance(a, dict) and "value" in a:
|
|
180
|
+
v = a["value"]
|
|
181
|
+
glyph = "🟢" if v <= 0 else ("🟡" if v <= 2 else "🔴")
|
|
182
|
+
parts.append(f"appositive misses {v} {glyph}")
|
|
183
|
+
if parts:
|
|
184
|
+
print(" · ".join(parts))
|
|
185
|
+
' 2>/dev/null)
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
155
188
|
|
|
156
|
-
|
|
157
|
-
|
|
189
|
+
# Format the scorer line as a steering block addition when non-empty
|
|
190
|
+
SCORER_BLOCK=""
|
|
191
|
+
if [ -n "$SCORER_LINE" ]; then
|
|
192
|
+
SCORER_BLOCK="
|
|
158
193
|
|
|
159
|
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
|
|
164
|
-
- Facts with context — for each number or fact, did I say why it matters?
|
|
194
|
+
**Scorer stack — $SCORER_LINE.**
|
|
195
|
+
- Density counts odd compound words — jargon that stacks.
|
|
196
|
+
- Appositive misses count shortcuts I used without saying what they mean.
|
|
197
|
+
- Thresholds at standards/prose-marker-format.md v1.0."
|
|
198
|
+
fi
|
|
165
199
|
|
|
166
|
-
|
|
200
|
+
# === Emit steering content ===
|
|
201
|
+
# Extension per ticket #935 — first-person Ogilvy compose tests
|
|
202
|
+
# grounded in standards/ogilvy-writing-rules.md (from #934 PR #938)
|
|
203
|
+
# Scorer-stack SCORER_BLOCK added at Longrun 2 Step 2d (#2197).
|
|
204
|
+
STEERING_CONTENT="**Plain English for operator-facing prose.**
|
|
205
|
+
- Target grade 8. Ceiling grade 10 for simple tech sentences.
|
|
206
|
+
- Short sentences. Subject-verb-object.
|
|
207
|
+
- Drop bassclef-internal jargon. Full list at standards/bassclef-internal-jargon.md.
|
|
208
|
+
- Keep standard tech words — API, hook, PR, schema.
|
|
209
|
+
- Vocabulary rename per ADR-040 D1 (grace through 2026-10-31): say goal not bet, step not WU, time budget not appetite, parent goal + thread not parent bet + arc.
|
|
210
|
+
- Historical goal IDs stay verbatim in backticks.
|
|
211
|
+
- Compose check (Ogilvy, six tests): natural voice · short units · read-aloud · completeness · honesty · facts with context. Full guidance at .claude/rules/writing-craft-discipline.md and .claude/rules/operator-facing-prose.md.${GRADE_LINE}${FLAGS_LINE}${SCORER_BLOCK}${FALLBACK_WARNING}"
|
|
167
212
|
|
|
168
213
|
# Use emit_additional_context to wrap as JSON envelope
|
|
169
214
|
emit_additional_context "UserPromptSubmit" "$STEERING_CONTENT"
|
|
@@ -23,8 +23,16 @@ INPUT=$(cat)
|
|
|
23
23
|
# stdin (exit 5 from jq).
|
|
24
24
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
|
25
25
|
|
|
26
|
-
# Only fire on git commit commands
|
|
27
|
-
|
|
26
|
+
# Only fire on git commit commands.
|
|
27
|
+
# Per bassclef-upstream#2139 — the prior matcher `grep -q 'git commit'` had no
|
|
28
|
+
# command-position anchor, so a heredoc body containing the words "git commit"
|
|
29
|
+
# fired the hook on cat/echo writes. The anchored regex below matches `git commit`
|
|
30
|
+
# only when it sits at a shell command position: start-of-line, after a chain
|
|
31
|
+
# operator (`&&`, `||`, `|`, `;`), or after an env-assignment prefix
|
|
32
|
+
# (`VAR=val git commit`). Grep is line-oriented, so each line of a multi-line
|
|
33
|
+
# heredoc is checked independently; body text with "git commit" as prose does
|
|
34
|
+
# not match any anchor and the hook skips.
|
|
35
|
+
if ! echo "$COMMAND" | grep -qE '(^|&&|\|\||[|;])[[:space:]]*([A-Za-z_][A-Za-z_0-9]*=[^[:space:]]*[[:space:]]+)*git[[:space:]]+commit([[:space:]]|$)'; then
|
|
28
36
|
exit 0
|
|
29
37
|
fi
|
|
30
38
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# install-class: dual
|
|
4
|
+
# pre-push-shellcheck — PreToolUse Bash hook that scans .sh files going
|
|
5
|
+
# out at `git push` and BLOCKs on un-allowlisted shellcheck findings.
|
|
6
|
+
#
|
|
7
|
+
# Catches shellcheck SC2164 and sister findings at author time so the
|
|
8
|
+
# CI shellcheck gate (.github/workflows/shellcheck.yml) does not fire
|
|
9
|
+
# red on a fresh PR. Closes bassclef-upstream#2108.
|
|
10
|
+
#
|
|
11
|
+
# Reuses the existing allowlist at .claude/hooks/shellcheck-allowlist.txt
|
|
12
|
+
# (format: SC<code>:<path>). Scans files changed between upstream HEAD
|
|
13
|
+
# and local HEAD; falls back to origin/main when no upstream is set.
|
|
14
|
+
#
|
|
15
|
+
# Exit codes:
|
|
16
|
+
# 0 — pass OR fail-soft (shellcheck missing / jq missing / no .sh changed)
|
|
17
|
+
# 2 — BLOCK (un-allowlisted shellcheck finding above severity=warning)
|
|
18
|
+
#
|
|
19
|
+
# Override: SKIP_PRE_PUSH_SHELLCHECK=1 git push ...
|
|
20
|
+
# (logged via trace-helper when available)
|
|
21
|
+
#
|
|
22
|
+
# Per @luminary jerome-saltzer-and-michael-schroeder — complete mediation.
|
|
23
|
+
# Per @luminary michael-nygard — fail-soft on missing binaries.
|
|
24
|
+
# Per @luminary michael-feathers — reuses CI's shellcheck JSON + allowlist shape.
|
|
25
|
+
|
|
26
|
+
set +e # never crash the tool call
|
|
27
|
+
|
|
28
|
+
INPUT=$(cat 2>/dev/null)
|
|
29
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
30
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
|
|
31
|
+
|
|
32
|
+
# Filter: Bash only
|
|
33
|
+
[ "$TOOL_NAME" = "Bash" ] || exit 0
|
|
34
|
+
|
|
35
|
+
# Filter: git push only
|
|
36
|
+
case "$COMMAND" in
|
|
37
|
+
*"git push"*) : ;;
|
|
38
|
+
*) exit 0 ;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
# Override
|
|
42
|
+
if [ "${SKIP_PRE_PUSH_SHELLCHECK:-}" = "1" ]; then
|
|
43
|
+
if [ -f "${CLAUDE_PROJECT_DIR:-.}/lib/trace-helper.sh" ]; then
|
|
44
|
+
# shellcheck disable=SC1091
|
|
45
|
+
source "${CLAUDE_PROJECT_DIR:-.}/lib/trace-helper.sh" 2>/dev/null || true
|
|
46
|
+
command -v trace_record >/dev/null 2>&1 \
|
|
47
|
+
&& trace_record "pre-push-shellcheck" "override" "SKIP_PRE_PUSH_SHELLCHECK=1" 2>/dev/null || true
|
|
48
|
+
fi
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Fail-soft on missing binaries
|
|
53
|
+
if ! command -v shellcheck >/dev/null 2>&1; then
|
|
54
|
+
echo "pre-push-shellcheck: shellcheck not installed; skipping scan" >&2
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
57
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
58
|
+
echo "pre-push-shellcheck: jq not installed; skipping scan" >&2
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
63
|
+
cd "$PROJECT_DIR" || exit 0
|
|
64
|
+
|
|
65
|
+
# Figure out which .sh files are going out
|
|
66
|
+
UPSTREAM=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)
|
|
67
|
+
if [ -z "$UPSTREAM" ]; then
|
|
68
|
+
UPSTREAM="origin/main"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
CHANGED_FILES=$(git diff --name-only "$UPSTREAM"..HEAD 2>/dev/null | grep -E '\.sh$' || true)
|
|
72
|
+
|
|
73
|
+
if [ -z "$CHANGED_FILES" ]; then
|
|
74
|
+
exit 0
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Only scan files that still exist (skip renamed / deleted)
|
|
78
|
+
EXISTING_FILES=()
|
|
79
|
+
while IFS= read -r f; do
|
|
80
|
+
[ -z "$f" ] && continue
|
|
81
|
+
[ -f "$f" ] && EXISTING_FILES+=("$f")
|
|
82
|
+
done <<< "$CHANGED_FILES"
|
|
83
|
+
|
|
84
|
+
if [ "${#EXISTING_FILES[@]}" -eq 0 ]; then
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Run shellcheck on changed .sh files — exit non-zero means findings exist
|
|
89
|
+
# (expected); do NOT fall back to "[]" on non-zero or the hook silently passes.
|
|
90
|
+
FINDINGS_JSON=$(shellcheck --format=json --severity=warning "${EXISTING_FILES[@]}" 2>/dev/null)
|
|
91
|
+
|
|
92
|
+
# Guard against malformed JSON (empty output or non-array)
|
|
93
|
+
if [ -z "$FINDINGS_JSON" ] || ! echo "$FINDINGS_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
94
|
+
FINDINGS_JSON="[]"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
COUNT=$(echo "$FINDINGS_JSON" | jq 'length' 2>/dev/null || echo "0")
|
|
98
|
+
[ "$COUNT" = "0" ] && exit 0
|
|
99
|
+
|
|
100
|
+
# Apply allowlist filter — reuse existing allowlist file
|
|
101
|
+
ALLOWLIST="$PROJECT_DIR/.claude/hooks/shellcheck-allowlist.txt"
|
|
102
|
+
ALLOW_FILTER="[]"
|
|
103
|
+
if [ -f "$ALLOWLIST" ]; then
|
|
104
|
+
CANDIDATE=$(grep -v '^#' "$ALLOWLIST" | grep -v '^[[:space:]]*$' \
|
|
105
|
+
| jq -R -s -c 'split("\n") | map(select(length > 0))' 2>/dev/null || echo "[]")
|
|
106
|
+
if echo "$CANDIDATE" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
107
|
+
ALLOW_FILTER="$CANDIDATE"
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
UNALLOWLISTED=$(echo "$FINDINGS_JSON" | jq --argjson allow "$ALLOW_FILTER" '
|
|
112
|
+
map(select(
|
|
113
|
+
("SC" + (.code | tostring) + ":" + ((.file // "") | ltrimstr("./"))) as $key |
|
|
114
|
+
([$key] | inside($allow)) | not
|
|
115
|
+
))
|
|
116
|
+
' 2>/dev/null || echo "[]")
|
|
117
|
+
|
|
118
|
+
if ! echo "$UNALLOWLISTED" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
119
|
+
UNALLOWLISTED="[]"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
UCOUNT=$(echo "$UNALLOWLISTED" | jq 'length' 2>/dev/null || echo "0")
|
|
123
|
+
[ "$UCOUNT" = "0" ] && exit 0
|
|
124
|
+
|
|
125
|
+
# BLOCK
|
|
126
|
+
cat >&2 <<EOF
|
|
127
|
+
|
|
128
|
+
🛑 pre-push-shellcheck — BLOCKED
|
|
129
|
+
|
|
130
|
+
$UCOUNT un-allowlisted shellcheck finding(s) in files going out:
|
|
131
|
+
|
|
132
|
+
EOF
|
|
133
|
+
echo "$UNALLOWLISTED" | jq -r '.[] | " \(.file):\(.line):\(.column) SC\(.code): \(.message)"' >&2
|
|
134
|
+
cat >&2 <<EOF
|
|
135
|
+
|
|
136
|
+
Resolution paths:
|
|
137
|
+
1. Fix the finding in each listed file (preferred — e.g. 'cd \"\$dir\" || exit')
|
|
138
|
+
2. Allowlist the SC<code>:<path> pair in .claude/hooks/shellcheck-allowlist.txt
|
|
139
|
+
3. SKIP_PRE_PUSH_SHELLCHECK=1 git push ... (logged; rare)
|
|
140
|
+
|
|
141
|
+
Per .claude/rules/defensive-bash.md + bassclef-upstream#2108.
|
|
142
|
+
|
|
143
|
+
EOF
|
|
144
|
+
exit 2
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# reviewer-dispatch-check.sh — Stop hook on code-shipping sessions
|
|
4
|
+
#
|
|
5
|
+
# Refuses to let a session end if it shipped source code (>0 src/ file
|
|
6
|
+
# changes per git diff) without a reviewer marker at
|
|
7
|
+
# state/markers/reviewer/<session-id>.md or *.md naming the session.
|
|
8
|
+
#
|
|
9
|
+
# Mode-agnostic: orchestrator-gated dispatches Reviewer agent; sequential
|
|
10
|
+
# mode self-invokes Reviewer responsibilities and writes the marker.
|
|
11
|
+
#
|
|
12
|
+
# Per bassclef#1146 + WU-12 of bet 2026-06-20e.
|
|
13
|
+
# Override: SKIP_REVIEWER_CHECK=1 (logged via trace-helper).
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# ============================================================================
|
|
18
|
+
# Read stdin JSON from Claude Code harness (Stop event payload)
|
|
19
|
+
# ============================================================================
|
|
20
|
+
PAYLOAD=$(cat 2>/dev/null || echo '{}')
|
|
21
|
+
|
|
22
|
+
# ============================================================================
|
|
23
|
+
# Override check (FIRST — before any work)
|
|
24
|
+
# ============================================================================
|
|
25
|
+
if [ "${SKIP_REVIEWER_CHECK:-0}" = "1" ]; then
|
|
26
|
+
TRACE_HELPER="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/trace-helper.sh"
|
|
27
|
+
if [ -f "$TRACE_HELPER" ]; then
|
|
28
|
+
bash "$TRACE_HELPER" override "reviewer-dispatch-check" "SKIP_REVIEWER_CHECK=1" 2>/dev/null || true
|
|
29
|
+
fi
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# ============================================================================
|
|
34
|
+
# Repo root + check if we're in a git repo
|
|
35
|
+
# ============================================================================
|
|
36
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
37
|
+
|
|
38
|
+
# If REVIEWER_CHECK_REPO is set, override (for tests)
|
|
39
|
+
if [ -n "${REVIEWER_CHECK_REPO:-}" ]; then
|
|
40
|
+
REPO_ROOT="$REVIEWER_CHECK_REPO"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if ! (cd "$REPO_ROOT" && git rev-parse --git-dir >/dev/null 2>&1); then
|
|
44
|
+
# Not a git repo — skip
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ============================================================================
|
|
49
|
+
# Detect source-file changes in this session
|
|
50
|
+
# Uses git diff against origin/main as proxy for session changes; this is a
|
|
51
|
+
# heuristic — a more precise version would track session-start ref.
|
|
52
|
+
# ============================================================================
|
|
53
|
+
cd "$REPO_ROOT"
|
|
54
|
+
|
|
55
|
+
# Define source-file path matchers — files that count as "shipped code"
|
|
56
|
+
SOURCE_MATCHERS=(
|
|
57
|
+
'src/'
|
|
58
|
+
'apps/'
|
|
59
|
+
'packages/'
|
|
60
|
+
'lib/'
|
|
61
|
+
'app/'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Count source files changed (uncommitted + committed-since-origin)
|
|
65
|
+
SHIPPED_SOURCE_COUNT=0
|
|
66
|
+
|
|
67
|
+
# Uncommitted changes
|
|
68
|
+
for matcher in "${SOURCE_MATCHERS[@]}"; do
|
|
69
|
+
count=$(git diff --name-only -- "$matcher" 2>/dev/null | wc -l | tr -d ' ')
|
|
70
|
+
SHIPPED_SOURCE_COUNT=$((SHIPPED_SOURCE_COUNT + count))
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# Committed-since-origin/main
|
|
74
|
+
if git rev-parse origin/main >/dev/null 2>&1; then
|
|
75
|
+
for matcher in "${SOURCE_MATCHERS[@]}"; do
|
|
76
|
+
count=$(git diff --name-only origin/main..HEAD -- "$matcher" 2>/dev/null | wc -l | tr -d ' ')
|
|
77
|
+
SHIPPED_SOURCE_COUNT=$((SHIPPED_SOURCE_COUNT + count))
|
|
78
|
+
done
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# No source files changed — session is non-code (docs, config, etc.); pass
|
|
82
|
+
if [ "$SHIPPED_SOURCE_COUNT" -eq 0 ]; then
|
|
83
|
+
exit 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# ============================================================================
|
|
87
|
+
# Marker check: state/markers/reviewer/*.md
|
|
88
|
+
# ============================================================================
|
|
89
|
+
MARKER_DIR="$REPO_ROOT/state/markers/reviewer"
|
|
90
|
+
|
|
91
|
+
if [ -d "$MARKER_DIR" ] && find "$MARKER_DIR" -maxdepth 1 -name "*.md" 2>/dev/null | grep -q .; then
|
|
92
|
+
# At least one reviewer marker exists — pass
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ============================================================================
|
|
97
|
+
# Block: source shipped + no reviewer marker
|
|
98
|
+
# ============================================================================
|
|
99
|
+
cat >&2 <<EOF
|
|
100
|
+
🛑 reviewer-dispatch-check — BLOCK (bassclef#1146)
|
|
101
|
+
|
|
102
|
+
This session shipped source code (${SHIPPED_SOURCE_COUNT} source files
|
|
103
|
+
changed across src/ apps/ packages/ lib/ app/) but no reviewer marker
|
|
104
|
+
exists at state/markers/reviewer/.
|
|
105
|
+
|
|
106
|
+
Reviewer responsibilities (per ~/.claude/agents/reviewer.md):
|
|
107
|
+
- Verify spec acceptance criteria met
|
|
108
|
+
- Verify tests present + green
|
|
109
|
+
- Check for TDD test-after pattern (Kent Beck)
|
|
110
|
+
- Verify ADR compliance for architectural decisions
|
|
111
|
+
- Verify Designer sign-off for UX changes
|
|
112
|
+
- Verify /architect-review invoked for Construction work
|
|
113
|
+
|
|
114
|
+
Resolve via one of:
|
|
115
|
+
|
|
116
|
+
1. Orchestrator-gated mode — dispatch Reviewer agent + capture marker:
|
|
117
|
+
/reviewer-pass
|
|
118
|
+
(or invoke via your orchestrator's handoff to Reviewer)
|
|
119
|
+
|
|
120
|
+
2. Sequential mode — self-invoke Reviewer responsibilities + write marker:
|
|
121
|
+
mkdir -p $MARKER_DIR
|
|
122
|
+
cat > $MARKER_DIR/<session-id>.md <<MARKER
|
|
123
|
+
# Reviewer pass — <session-id>
|
|
124
|
+
|
|
125
|
+
mode: sequential
|
|
126
|
+
reviewed_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
127
|
+
source_files_changed: $SHIPPED_SOURCE_COUNT
|
|
128
|
+
|
|
129
|
+
## Checks
|
|
130
|
+
- spec acceptance criteria: PASS | FAIL | N/A
|
|
131
|
+
- tests present + green: PASS | FAIL
|
|
132
|
+
- test-after pattern check: NONE FOUND | FOUND (list)
|
|
133
|
+
- ADR compliance: PASS | N/A
|
|
134
|
+
- Designer sign-off: PRESENT | N/A
|
|
135
|
+
- architect-review: INVOKED | N/A
|
|
136
|
+
|
|
137
|
+
## Findings
|
|
138
|
+
- <severity>: <description> # BLOCK / WARN / NOTE
|
|
139
|
+
|
|
140
|
+
acknowledged: true
|
|
141
|
+
MARKER
|
|
142
|
+
|
|
143
|
+
3. Defer to next session — write marker NOW with "deferred to next session"
|
|
144
|
+
note + commit; resolve at next session start per deferred-actions protocol.
|
|
145
|
+
|
|
146
|
+
Override (logged): SKIP_REVIEWER_CHECK=1 <command>
|
|
147
|
+
|
|
148
|
+
When to use override:
|
|
149
|
+
- Documentation-only session that accidentally touched a src/ comment
|
|
150
|
+
- Emergency rescue when hook itself misclassifies
|
|
151
|
+
EOF
|
|
152
|
+
|
|
153
|
+
exit 2
|