@thebassclef/lite 1.9.8 → 1.9.10
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 +6 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lite/.claude/hooks/git-stash-u-guard.sh +4 -1
- package/dist/lite/.claude/hooks/post-skill-friction-check.sh +6 -1
- package/dist/lite/.claude/hooks/pre-commit-gate.sh +49 -6
- package/dist/lite/.claude/hooks/pre-commit-identifier-leak-scrub.sh +68 -8
- package/dist/lite/.claude/hooks/pre-commit-manifest-autoregen.sh +4 -2
- package/dist/lite/.claude/hooks/save-state.sh +24 -0
- package/dist/lite/.claude/hooks/session-reflection.d/83-install-written-paths-rebuild.sh +111 -0
- package/dist/lite/.claude/hooks/state-validate.sh +8 -0
- package/dist/lite/.claude/hooks/trace-helper.sh +29 -0
- package/dist/lite/.claude/rules/install-written-paths-mediation.md +74 -0
- package/dist/lite/.claude/rules/lite-manifest-schema-change-discipline.md +30 -0
- package/dist/lite/.claude/skills/autonomous/SKILL.md +53 -7
- package/dist/lite/.claude/skills/build/SKILL.md +53 -11
- package/dist/lite/.claude/skills/deploy-prod/SKILL.md +10 -0
- package/dist/lite/.claude/skills/launch/SKILL.md +26 -0
- package/dist/lite/.claude/skills/onboard-repo/SKILL.md +59 -18
- package/dist/lite/.claude/skills/personas/SKILL.md +1 -1
- package/dist/lite/.claude/skills/release-notes/SKILL.md +24 -0
- package/dist/lite/bassclef-version.json +5 -5
- package/dist/lite/gitignore +8 -0
- package/dist/lite/lib/capability-probe.sh +39 -1
- package/dist/lite/lib/composer-preflight.sh +8 -4
- package/dist/lite/lib/deferred-actions.sh +4 -4
- package/dist/lite/lib/fixture-builder.sh +9 -9
- package/dist/lite/lib/identifier-leak-check.sh +85 -0
- package/dist/lite/lib/install-written-paths.sh +369 -0
- package/dist/lite/lib/luminary-pick.sh +35 -13
- package/dist/lite/lib/preview-state.sh +20 -20
- package/dist/lite/lib/skip-env-inline-check.sh +61 -0
- package/dist/lite/lib/state.sh +102 -39
- package/dist/lite/lib/tier-check.sh +9 -9
- package/dist/lite/lib/tier-dependency-audit.sh +23 -23
- package/dist/lite/presence/install/schedule-auto-save.taskscheduler.md +1 -1
- package/dist/lite/standards/lite-manifest-schema-changes.md +83 -0
- package/dist/lite/standards/lite-manifest.json +70 -47
- package/dist/lite/standards/state-spine.md +1 -0
- package/dist/lite/templates/spec-template.md +10 -6
- package/package.json +1 -1
- package/dist/lite/.claude/hooks/longrun-prep-compounding-axis-check.sh +0 -492
- package/dist/lite/.claude/rules/compounding-axis-fresh-analysis.md +0 -173
package/dist/lite/lib/state.sh
CHANGED
|
@@ -40,8 +40,9 @@ __state_check_binary_deps() {
|
|
|
40
40
|
if [ "${__STATE_DEPS_CHECKED:-}" = "1" ]; then
|
|
41
41
|
return 0
|
|
42
42
|
fi
|
|
43
|
+
# Baseline deps — awk + jq required unconditionally
|
|
43
44
|
local missing=""
|
|
44
|
-
for bin in awk
|
|
45
|
+
for bin in awk jq; do
|
|
45
46
|
command -v "$bin" >/dev/null 2>&1 || missing="$missing $bin"
|
|
46
47
|
done
|
|
47
48
|
if [ -n "$missing" ]; then
|
|
@@ -49,21 +50,63 @@ __state_check_binary_deps() {
|
|
|
49
50
|
STATE ACCESSOR FAILURE
|
|
50
51
|
Required binary missing from PATH:$missing
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
YAML frontmatter extraction + JSON tooling).
|
|
53
|
+
awk + jq are load-bearing for lib/state.sh accessors.
|
|
54
54
|
|
|
55
55
|
Resolution:
|
|
56
|
-
macOS: brew install gawk
|
|
57
|
-
Debian: sudo apt-get install gawk
|
|
58
|
-
|
|
59
|
-
Closes the bassclef#598 silent-failure class.
|
|
56
|
+
macOS: brew install gawk jq
|
|
57
|
+
Debian: sudo apt-get install gawk jq
|
|
60
58
|
EOF
|
|
61
59
|
return 4
|
|
62
60
|
fi
|
|
61
|
+
|
|
62
|
+
# Per cli#308: YAML parser needs EITHER yq OR python3+PyYAML.
|
|
63
|
+
# Stock macOS has python3 but not PyYAML. Pre-cli#308, that mismatch
|
|
64
|
+
# caused silent failures where __state_check_binary_deps passed but
|
|
65
|
+
# the actual yaml parse failed with ModuleNotFoundError. Now we check
|
|
66
|
+
# that at least one working parser exists.
|
|
67
|
+
__state_check_yaml_parser || return 6
|
|
63
68
|
export __STATE_DEPS_CHECKED=1
|
|
64
69
|
return 0
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
# YAML parser check (per cli#308). Returns 0 if yq available OR
|
|
73
|
+
# python3+yaml-module importable. Returns 6 if neither.
|
|
74
|
+
__state_check_yaml_parser() {
|
|
75
|
+
# Cache the result per shell process.
|
|
76
|
+
if [ "${__STATE_YAML_PARSER:-}" = "yq" ] || [ "${__STATE_YAML_PARSER:-}" = "python3" ]; then
|
|
77
|
+
return 0
|
|
78
|
+
fi
|
|
79
|
+
# Prefer yq (faster + simpler contract).
|
|
80
|
+
if command -v yq >/dev/null 2>&1; then
|
|
81
|
+
export __STATE_YAML_PARSER=yq
|
|
82
|
+
return 0
|
|
83
|
+
fi
|
|
84
|
+
# Fall back to python3+PyYAML.
|
|
85
|
+
if command -v python3 >/dev/null 2>&1 && python3 -c "import yaml" >/dev/null 2>&1; then
|
|
86
|
+
export __STATE_YAML_PARSER=python3
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
cat >&2 <<EOF
|
|
90
|
+
STATE ACCESSOR FAILURE — no YAML parser available (per cli#308)
|
|
91
|
+
|
|
92
|
+
lib/state.sh needs ONE of:
|
|
93
|
+
- yq (preferred — faster, no Python module needed)
|
|
94
|
+
- python3 + PyYAML module
|
|
95
|
+
|
|
96
|
+
Both are missing on this machine.
|
|
97
|
+
|
|
98
|
+
Resolution:
|
|
99
|
+
macOS (preferred): brew install yq
|
|
100
|
+
macOS (alt): python3 -m pip install pyyaml
|
|
101
|
+
Debian: sudo apt-get install yq OR python3 -m pip install pyyaml
|
|
102
|
+
|
|
103
|
+
Per cli#308 — this check closes the silent-failure class where
|
|
104
|
+
python3 existed but PyYAML did not, and callers reported "goal not
|
|
105
|
+
found" (wrong diagnosis).
|
|
106
|
+
EOF
|
|
107
|
+
return 6
|
|
108
|
+
}
|
|
109
|
+
|
|
67
110
|
# yq2json — extract YAML frontmatter from markdown file, emit JSON.
|
|
68
111
|
# Used by Shape B (markdown + YAML frontmatter + body) entity accessors.
|
|
69
112
|
#
|
|
@@ -86,7 +129,7 @@ EOF
|
|
|
86
129
|
# on invalid input) + @luminary michael-feathers (characterization
|
|
87
130
|
# tests RED-first via fixtures/2026-09-06-silent-empty/).
|
|
88
131
|
__state_yq2json() {
|
|
89
|
-
__state_check_binary_deps || return
|
|
132
|
+
__state_check_binary_deps || return $?
|
|
90
133
|
local file="$1"
|
|
91
134
|
if [ ! -f "$file" ]; then
|
|
92
135
|
return 1
|
|
@@ -127,7 +170,23 @@ __state_yq2json() {
|
|
|
127
170
|
|
|
128
171
|
local out
|
|
129
172
|
local ec
|
|
130
|
-
|
|
173
|
+
# Per cli#308 — prefer yq when available, fall back to python3+PyYAML.
|
|
174
|
+
# __state_check_binary_deps (called at top of this function via return 4
|
|
175
|
+
# path) already resolved __STATE_YAML_PARSER to one of the two.
|
|
176
|
+
if [ "${__STATE_YAML_PARSER:-}" = "yq" ]; then
|
|
177
|
+
out=$(awk '/^---$/{c++; next} c==1' "$file" | yq -o=json '.' 2>&1)
|
|
178
|
+
ec=$?
|
|
179
|
+
if [ "$ec" -ne 0 ]; then
|
|
180
|
+
printf 'yaml parse failed (yq): %s\n' "$out" >&2
|
|
181
|
+
return 3
|
|
182
|
+
fi
|
|
183
|
+
# yq returns `null` for empty input; coerce to `{}` for consistency
|
|
184
|
+
# with python3 path.
|
|
185
|
+
if [ "$out" = "null" ]; then
|
|
186
|
+
out="{}"
|
|
187
|
+
fi
|
|
188
|
+
else
|
|
189
|
+
out=$(awk '/^---$/{c++; next} c==1' "$file" | python3 -c "
|
|
131
190
|
import sys, yaml, json
|
|
132
191
|
try:
|
|
133
192
|
data = yaml.safe_load(sys.stdin) or {}
|
|
@@ -136,7 +195,8 @@ except Exception as e:
|
|
|
136
195
|
print(f'yaml parse failed: {e}', file=sys.stderr)
|
|
137
196
|
sys.exit(3)
|
|
138
197
|
")
|
|
139
|
-
|
|
198
|
+
ec=$?
|
|
199
|
+
fi
|
|
140
200
|
# Instrument real-parse count for #1519 tests + observability.
|
|
141
201
|
__STATE_YQ2JSON_PARSE_COUNT=$(( ${__STATE_YQ2JSON_PARSE_COUNT:-0} + 1 ))
|
|
142
202
|
if [ "$ec" -ne 0 ]; then
|
|
@@ -185,18 +245,20 @@ __state_atomic_write() {
|
|
|
185
245
|
# output) + @luminary michael-nygard (silent-fail wrap; write does not
|
|
186
246
|
# crash caller).
|
|
187
247
|
__state_yaml_write() {
|
|
188
|
-
__state_check_binary_deps || return
|
|
189
|
-
local path
|
|
248
|
+
__state_check_binary_deps || return $?
|
|
249
|
+
# Finding #6 (#2036): `local path` is a zsh-magic variable name — ties to
|
|
250
|
+
# $PATH when sourced from zsh. Renamed to `local p` throughout this file.
|
|
251
|
+
local p="$1"
|
|
190
252
|
local field="$2"
|
|
191
253
|
local value="$3"
|
|
192
|
-
if [ -z "$
|
|
254
|
+
if [ -z "$p" ] || [ -z "$field" ]; then
|
|
193
255
|
echo "__state_yaml_write: requires <path> <field> <value>" >&2
|
|
194
256
|
return 2
|
|
195
257
|
fi
|
|
196
258
|
local dir
|
|
197
|
-
dir="$(dirname "$
|
|
259
|
+
dir="$(dirname "$p")"
|
|
198
260
|
[ -d "$dir" ] || mkdir -p "$dir" || return 4
|
|
199
|
-
local tmp="${
|
|
261
|
+
local tmp="${p}.tmp.$$"
|
|
200
262
|
BASSCLEF_YAML_FIELD="$field" \
|
|
201
263
|
BASSCLEF_YAML_VALUE="$value" \
|
|
202
264
|
BASSCLEF_YAML_TMP="$tmp" \
|
|
@@ -213,7 +275,7 @@ except Exception as e:
|
|
|
213
275
|
print(f"__state_yaml_write: safe_dump failed: {e}", file=sys.stderr)
|
|
214
276
|
sys.exit(3)
|
|
215
277
|
' 2>&1 || { rm -f "$tmp"; return 3; }
|
|
216
|
-
mv "$tmp" "$
|
|
278
|
+
mv "$tmp" "$p" || { rm -f "$tmp"; return 4; }
|
|
217
279
|
}
|
|
218
280
|
|
|
219
281
|
# === Whereami (Shape A — JSON authoritative + markdown rendered) ===
|
|
@@ -239,21 +301,22 @@ state_whereami_get() {
|
|
|
239
301
|
state_whereami_set() {
|
|
240
302
|
local field="$1"
|
|
241
303
|
local value="$2"
|
|
242
|
-
local
|
|
304
|
+
local p="docs/whereami.json"
|
|
243
305
|
if [ -z "$field" ] || [ -z "$value" ]; then
|
|
244
306
|
echo "state_whereami_set: requires <field> <value>" >&2
|
|
245
307
|
return 2
|
|
246
308
|
fi
|
|
247
|
-
if [ ! -f "$
|
|
309
|
+
if [ ! -f "$p" ]; then
|
|
248
310
|
echo "state_whereami_set: docs/whereami.json does not exist" >&2
|
|
249
311
|
return 4
|
|
250
312
|
fi
|
|
251
313
|
local updated
|
|
252
|
-
# Use jq's setpath for nested field paths (e.g., 'phase.value')
|
|
314
|
+
# Use jq's setpath for nested field paths (e.g., 'phase.value').
|
|
315
|
+
# The jq `--arg path` names a jq variable (not shell) — safe to keep.
|
|
253
316
|
updated="$(jq --arg path "$field" --arg val "$value" '
|
|
254
317
|
setpath($path | split("."); $val)
|
|
255
|
-
' "$
|
|
256
|
-
__state_atomic_write "$
|
|
318
|
+
' "$p")" || return 3
|
|
319
|
+
__state_atomic_write "$p" "$updated"
|
|
257
320
|
}
|
|
258
321
|
|
|
259
322
|
# === IterationBet (Shape B — markdown + YAML frontmatter + body) ===
|
|
@@ -264,11 +327,11 @@ state_iteration_bet_get() {
|
|
|
264
327
|
echo "state_iteration_bet_get: requires <slug>" >&2
|
|
265
328
|
return 2
|
|
266
329
|
fi
|
|
267
|
-
local
|
|
268
|
-
if [ ! -f "$
|
|
330
|
+
local p="docs/iteration-bets/${slug}.md"
|
|
331
|
+
if [ ! -f "$p" ]; then
|
|
269
332
|
return 1
|
|
270
333
|
fi
|
|
271
|
-
__state_yq2json "$
|
|
334
|
+
__state_yq2json "$p"
|
|
272
335
|
}
|
|
273
336
|
|
|
274
337
|
# Special-purpose: returns the single active iteration bet (status=active),
|
|
@@ -333,25 +396,25 @@ state_roadmap_get() {
|
|
|
333
396
|
echo "state_roadmap_get: requires <slug>" >&2
|
|
334
397
|
return 2
|
|
335
398
|
fi
|
|
336
|
-
local
|
|
337
|
-
if [ ! -f "$
|
|
399
|
+
local p="docs/roadmaps/${slug}.md"
|
|
400
|
+
if [ ! -f "$p" ]; then
|
|
338
401
|
return 1
|
|
339
402
|
fi
|
|
340
|
-
__state_yq2json "$
|
|
403
|
+
__state_yq2json "$p"
|
|
341
404
|
}
|
|
342
405
|
|
|
343
406
|
# Internal: rewrite a roadmap's frontmatter from JSON, preserving the body.
|
|
344
407
|
__state_roadmap_write_frontmatter() {
|
|
345
408
|
local slug="$1"
|
|
346
409
|
local frontmatter_json="$2"
|
|
347
|
-
local
|
|
348
|
-
if [ ! -f "$
|
|
349
|
-
echo "__state_roadmap_write_frontmatter: roadmap $
|
|
410
|
+
local p="docs/roadmaps/${slug}.md"
|
|
411
|
+
if [ ! -f "$p" ]; then
|
|
412
|
+
echo "__state_roadmap_write_frontmatter: roadmap $p not found" >&2
|
|
350
413
|
return 1
|
|
351
414
|
fi
|
|
352
415
|
python3 -c "
|
|
353
416
|
import sys, yaml, json
|
|
354
|
-
path = '$
|
|
417
|
+
path = '$p'
|
|
355
418
|
fm = json.loads('''$frontmatter_json''')
|
|
356
419
|
with open(path, 'r') as f:
|
|
357
420
|
content = f.read()
|
|
@@ -726,15 +789,15 @@ state_chronicle_gate_evidence() {
|
|
|
726
789
|
return 2
|
|
727
790
|
fi
|
|
728
791
|
# Try bassclef (chronicle/) and consumer-repo (docs/chronicle/) paths
|
|
729
|
-
local
|
|
792
|
+
local p=""
|
|
730
793
|
if [ -f "chronicle/${id}.md" ]; then
|
|
731
|
-
|
|
794
|
+
p="chronicle/${id}.md"
|
|
732
795
|
elif [ -f "docs/chronicle/${id}.md" ]; then
|
|
733
|
-
|
|
796
|
+
p="docs/chronicle/${id}.md"
|
|
734
797
|
else
|
|
735
798
|
return 1
|
|
736
799
|
fi
|
|
737
|
-
__state_yq2json "$
|
|
800
|
+
__state_yq2json "$p" | jq -c '.gates_fired // []'
|
|
738
801
|
}
|
|
739
802
|
|
|
740
803
|
# List chronicle IDs (filename without .md extension), optionally limited
|
|
@@ -1081,16 +1144,16 @@ __state_platform_resolve_path() {
|
|
|
1081
1144
|
|
|
1082
1145
|
state_platform_get() {
|
|
1083
1146
|
local key="${1:-}"
|
|
1084
|
-
local
|
|
1085
|
-
|
|
1086
|
-
if [ ! -f "$
|
|
1147
|
+
local p
|
|
1148
|
+
p="$(__state_platform_resolve_path)"
|
|
1149
|
+
if [ ! -f "$p" ]; then
|
|
1087
1150
|
return 1
|
|
1088
1151
|
fi
|
|
1089
1152
|
local json
|
|
1090
1153
|
json="$(python3 -c "
|
|
1091
1154
|
import sys, yaml, json
|
|
1092
1155
|
try:
|
|
1093
|
-
with open('$
|
|
1156
|
+
with open('$p') as f:
|
|
1094
1157
|
data = yaml.safe_load(f) or {}
|
|
1095
1158
|
print(json.dumps(data, default=str))
|
|
1096
1159
|
except Exception as e:
|
|
@@ -131,35 +131,35 @@ PYEOF
|
|
|
131
131
|
# Mirrors detection shape from .claude/hooks/tier-tag-required-at-write.sh so
|
|
132
132
|
# the reader half and the writer half stay consistent.
|
|
133
133
|
resolve_file_tier() {
|
|
134
|
-
local
|
|
135
|
-
if [ ! -f "$
|
|
134
|
+
local p="$1"
|
|
135
|
+
if [ ! -f "$p" ]; then
|
|
136
136
|
return 0
|
|
137
137
|
fi
|
|
138
138
|
|
|
139
139
|
local tier=""
|
|
140
|
-
case "$
|
|
140
|
+
case "$p" in
|
|
141
141
|
*.md)
|
|
142
142
|
# Frontmatter between --- markers, first 30 lines
|
|
143
143
|
tier=$(awk '
|
|
144
144
|
/^---/ { in_fm = !in_fm; if (!in_fm) exit; next }
|
|
145
145
|
in_fm && /^tier:[[:space:]]/ { sub(/^tier:[[:space:]]*/, ""); sub(/[[:space:]]*$/, ""); print; exit }
|
|
146
146
|
NR > 30 { exit }
|
|
147
|
-
' "$
|
|
147
|
+
' "$p" 2>/dev/null)
|
|
148
148
|
;;
|
|
149
149
|
*.sh|*.py)
|
|
150
|
-
tier=$(head -10 "$
|
|
150
|
+
tier=$(head -10 "$p" 2>/dev/null | grep -E '^#[[:space:]]*tier:[[:space:]]' | head -1 | sed -E 's/^#[[:space:]]*tier:[[:space:]]*//; s/[[:space:]]*$//')
|
|
151
151
|
;;
|
|
152
152
|
*.yml|*.yaml)
|
|
153
|
-
tier=$(head -30 "$
|
|
153
|
+
tier=$(head -30 "$p" 2>/dev/null | grep -E '^#[[:space:]]*tier:[[:space:]]|^tier:[[:space:]]' | head -1 | sed -E 's/^#?[[:space:]]*tier:[[:space:]]*//; s/[[:space:]]*$//')
|
|
154
154
|
;;
|
|
155
155
|
*.json|*.jsonc)
|
|
156
|
-
tier=$(head -30 "$
|
|
156
|
+
tier=$(head -30 "$p" 2>/dev/null | grep -oE '"(_?tier)"[[:space:]]*:[[:space:]]*"[a-z]+"' | head -1 | sed -E 's/.*:[[:space:]]*"([a-z]+)"/\1/')
|
|
157
157
|
if [ -z "$tier" ]; then
|
|
158
|
-
tier=$(head -30 "$
|
|
158
|
+
tier=$(head -30 "$p" 2>/dev/null | grep -oE '//[[:space:]]*tier:[[:space:]]*[a-z]+' | head -1 | sed -E 's/.*tier:[[:space:]]*//')
|
|
159
159
|
fi
|
|
160
160
|
;;
|
|
161
161
|
*.txt)
|
|
162
|
-
tier=$(head -1 "$
|
|
162
|
+
tier=$(head -1 "$p" 2>/dev/null | grep -E '^#[[:space:]]*tier:[[:space:]]' | sed -E 's/^#[[:space:]]*tier:[[:space:]]*//; s/[[:space:]]*$//')
|
|
163
163
|
;;
|
|
164
164
|
esac
|
|
165
165
|
|
|
@@ -364,15 +364,15 @@ walk_graph_bfs() {
|
|
|
364
364
|
while [ -s "$queue_file" ] && [ "$iter" -lt "$max_iter" ]; do
|
|
365
365
|
iter=$((iter + 1))
|
|
366
366
|
local next_file; next_file="$(mktemp)"
|
|
367
|
-
while IFS= read -r
|
|
368
|
-
[ -z "$
|
|
367
|
+
while IFS= read -r p; do
|
|
368
|
+
[ -z "$p" ] && continue
|
|
369
369
|
# Skip if visited
|
|
370
|
-
if grep -Fxq "$
|
|
370
|
+
if grep -Fxq "$p" "$visited_file" 2>/dev/null; then
|
|
371
371
|
continue
|
|
372
372
|
fi
|
|
373
|
-
echo "$
|
|
373
|
+
echo "$p" >> "$visited_file"
|
|
374
374
|
# Extract references from this file
|
|
375
|
-
"$extractor" "$
|
|
375
|
+
"$extractor" "$p" 2>/dev/null | while IFS= read -r ref; do
|
|
376
376
|
[ -n "$ref" ] && echo "$ref" >> "$next_file"
|
|
377
377
|
done
|
|
378
378
|
done < "$queue_file"
|
|
@@ -504,17 +504,17 @@ classify_files() {
|
|
|
504
504
|
# tag == target tier → bucket 1 (ships correctly in this tier)
|
|
505
505
|
# no tag at all → bucket 2a (safe mechanical tag)
|
|
506
506
|
# tag == other tier → bucket 2b (chain-snap risk; per-file cure)
|
|
507
|
-
comm -12 "$intended_sorted" "$actual_sorted" | while IFS= read -r
|
|
507
|
+
comm -12 "$intended_sorted" "$actual_sorted" | while IFS= read -r p; do
|
|
508
508
|
# Look up tag for path in tags file. Returns "" for untagged (no tab)
|
|
509
509
|
# or missing entry; returns tier value when tagged.
|
|
510
510
|
local file_tag
|
|
511
|
-
file_tag="$(awk -F'\t' -v p="$
|
|
511
|
+
file_tag="$(awk -F'\t' -v p="$p" '$1 == p { print ($2 ? $2 : ""); exit }' "$tags")"
|
|
512
512
|
if [ "$file_tag" = "$tier" ]; then
|
|
513
|
-
echo "$
|
|
513
|
+
echo "$p" >> "$bucket_1"
|
|
514
514
|
elif [ -z "$file_tag" ]; then
|
|
515
|
-
echo "$
|
|
515
|
+
echo "$p" >> "$bucket_2a"
|
|
516
516
|
else
|
|
517
|
-
echo "$
|
|
517
|
+
echo "$p" >> "$bucket_2b"
|
|
518
518
|
fi
|
|
519
519
|
done
|
|
520
520
|
|
|
@@ -557,13 +557,13 @@ classify_files_combined() {
|
|
|
557
557
|
|
|
558
558
|
# Bucket 1 + Bucket 2 — split intersection by tag presence.
|
|
559
559
|
# In combined mode, ANY non-empty tag places the file in bucket 1.
|
|
560
|
-
comm -12 "$intended_sorted" "$actual_sorted" | while IFS= read -r
|
|
560
|
+
comm -12 "$intended_sorted" "$actual_sorted" | while IFS= read -r p; do
|
|
561
561
|
local file_tag
|
|
562
|
-
file_tag="$(awk -F'\t' -v p="$
|
|
562
|
+
file_tag="$(awk -F'\t' -v p="$p" '$1 == p { print ($2 ? $2 : ""); exit }' "$tags")"
|
|
563
563
|
if [ -n "$file_tag" ]; then
|
|
564
|
-
echo "$
|
|
564
|
+
echo "$p" >> "$bucket_1"
|
|
565
565
|
else
|
|
566
|
-
echo "$
|
|
566
|
+
echo "$p" >> "$bucket_2"
|
|
567
567
|
fi
|
|
568
568
|
done
|
|
569
569
|
|
|
@@ -738,12 +738,12 @@ write_manifest_combined() {
|
|
|
738
738
|
# - SDLC trace logs
|
|
739
739
|
# ---------------------------------------------------------------------------
|
|
740
740
|
_is_substrate_relevant_path() {
|
|
741
|
-
local
|
|
741
|
+
local p="$1"
|
|
742
742
|
# Normalize leading ./ prefix so patterns match consistently.
|
|
743
743
|
# Closure files mix "./foo" (from find) with "foo" (from git grep).
|
|
744
744
|
# Per #1385 walker cure — extends G3 filter beyond initial pattern set.
|
|
745
|
-
|
|
746
|
-
case "$
|
|
745
|
+
p="${p#./}"
|
|
746
|
+
case "$p" in
|
|
747
747
|
/*|../*|.git/*|*/.git/*) return 1 ;;
|
|
748
748
|
# Runtime state — walker outputs + gate markers + session state + analysis.
|
|
749
749
|
# None ship to adopters regardless of tier tag. Per #1385.
|
|
@@ -831,12 +831,12 @@ walk_graph_bfs_inbound() {
|
|
|
831
831
|
while [ -s "$queue_file" ] && [ "$iter" -lt "$max_iter" ]; do
|
|
832
832
|
iter=$((iter + 1))
|
|
833
833
|
local next_file; next_file="$(mktemp)"
|
|
834
|
-
while IFS= read -r
|
|
835
|
-
[ -z "$
|
|
836
|
-
if grep -Fxq "$
|
|
837
|
-
echo "$
|
|
838
|
-
# Look up callers of $
|
|
839
|
-
awk -F'\t' -v p="$
|
|
834
|
+
while IFS= read -r p; do
|
|
835
|
+
[ -z "$p" ] && continue
|
|
836
|
+
if grep -Fxq "$p" "$visited_file" 2>/dev/null; then continue; fi
|
|
837
|
+
echo "$p" >> "$visited_file"
|
|
838
|
+
# Look up callers of $p in the reverse index (path is column 1; caller is column 2)
|
|
839
|
+
awk -F'\t' -v p="$p" '$1 == p { print $2 }' "$rev_index" | while IFS= read -r caller; do
|
|
840
840
|
[ -n "$caller" ] && echo "$caller" >> "$next_file"
|
|
841
841
|
done
|
|
842
842
|
done < "$queue_file"
|
|
@@ -110,7 +110,7 @@ Unregister-ScheduledTask -TaskName "BassclefAutoSave" -Confirm:$false
|
|
|
110
110
|
|
|
111
111
|
## Alternatives
|
|
112
112
|
|
|
113
|
-
- **WSL2 available?** Skip this doc; use `presence/install/schedule-auto-save.cron.sh` inside WSL. Cron there watches your Windows filesystem via `/mnt/c
|
|
113
|
+
- **WSL2 available?** Skip this doc; use `presence/install/schedule-auto-save.cron.sh` inside WSL. Cron there watches your Windows filesystem via the mounted `/mnt/c/` tree under your user profile.
|
|
114
114
|
- **Prefer a .ps1 installer?** File a ticket. The current ship stays doc-only to avoid installer complexity for a population not yet observed. Path B (per-OS installers) upgrade is a follow-on.
|
|
115
115
|
|
|
116
116
|
## References
|
|
@@ -34,6 +34,89 @@ Every entry stays in this file for the life of the manifest. Never pruned. When
|
|
|
34
34
|
|
|
35
35
|
## Entries (newest first)
|
|
36
36
|
|
|
37
|
+
### cli#305 — identifier-leak scrub self-exclude + commit author check (v1.15.3 → v1.15.4, 2026-10-04)
|
|
38
|
+
|
|
39
|
+
- **Change type:** content-change (1 file: `.claude/hooks/pre-commit-identifier-leak-scrub.sh`). Check 2 now filters out hunks from the hook's own files, its test, the rule, and the pattern source. Check 3 added — scans `git var GIT_AUTHOR_IDENT` for the pattern set plus the `.local` suffix. BLOCK banner adds a resolution path pointing at the GitHub no-reply address.
|
|
40
|
+
- **Fields:** 444 entries (unchanged). Content hash shifts on the `pre-commit-identifier-leak-scrub` hook slug.
|
|
41
|
+
- **Version bump:** v1.15.3 → v1.15.4 (patch — content-only change on 1 slug; no entry count change). Rides on top of the Finding #7 bump (v1.15.3) when PRs land in order.
|
|
42
|
+
- **Rationale:** Kunal cli#305 — first commit after `bassclef init` blocked on the check's own files; author identity leaked silently when `user.email` was unset on Mac. Class A identity leak sister to cli#313 + cli#320 shipped in v1.15.2. Per PR #2062 (planned).
|
|
43
|
+
- **Downstream cure:** no action for adopters at exact-pin. Self-exclude paths are PATH-scoped, not pattern-scoped — leaks moved into non-self files still block. Author check is additive; adopters with clean emails see no change. Adopters whose git author ends in `.local` or carries a hostname pattern see the new BLOCK with a recipe pointing at `<username>@users.noreply.github.com`.
|
|
44
|
+
- **Old shape retired:** nothing retired. Check 1 (message) + Check 2 (staged content, now filtered) + Check 3 (author) chain is additive.
|
|
45
|
+
|
|
46
|
+
### #2036 Finding #7 — Playwright probe file-based fallback (v1.15.4 → v1.15.5, 2026-10-05)
|
|
47
|
+
|
|
48
|
+
- **Change type:** content-change (1 file: `lib/capability-probe.sh`). `probe_mcp_playwright()` extended with four MCP config file fallback paths after the `CLAUDE_MCP_PLAYWRIGHT` env-var fast path. Fail-soft on missing `jq` + malformed JSON.
|
|
49
|
+
- **Fields:** 444 entries (unchanged). Content hash shifts on the `capability-probe` lib slug.
|
|
50
|
+
- **Version bump:** v1.15.4 → v1.15.5 (patch — content-change on existing lib slug per policy). Rebased on top of cli#305 which landed first.
|
|
51
|
+
- **Rationale:** Kunal v1.6.4 adopter run installed Playwright MCP but did not set `CLAUDE_MCP_PLAYWRIGHT`; probe returned 1; `/riff` + `/build` skipped screenshots. Fallback now reads `.mcp.json`, project + user `.claude/settings.json`, and `~/.claude.json`. Per bassclef-upstream#2036 Finding #7 + PR #2060.
|
|
52
|
+
- **Downstream cure:** nothing — byte-for-byte unchanged when `CLAUDE_MCP_PLAYWRIGHT` is set (fast path first). Adopters without the env var set gain file-based detection on the fly.
|
|
53
|
+
- **Old shape retired:** immediately (new behavior is additive).
|
|
54
|
+
|
|
55
|
+
### cli#313 + cli#320 — identity-leak sweep in luminary-pick + personas (v1.15.1 → v1.15.2, 2026-10-04)
|
|
56
|
+
|
|
57
|
+
- **Change type:** content-change (2 files: `lib/luminary-pick.sh` + `.claude/skills/personas/SKILL.md`). luminary-pick basedir fallback moves from `$HOME/src/sunj-labs/bassclef` to `$CLAUDE_PROJECT_DIR` → `git rev-parse` → `pwd` chain, plus adds `.claude/luminaries/*.md` catalog fallback for lite tier. personas SKILL default slug changes from email-derived (`operator-[local-part]`) to plain `operator` (never from email).
|
|
58
|
+
- **Fields:** 444 entries (unchanged). Content hash shifts on the `luminary-pick` lib slug + `personas` skill slug.
|
|
59
|
+
- **Version bump:** v1.15.1 → v1.15.2 (patch; content-only on 2 slugs; no schema shape shift; no entry count change).
|
|
60
|
+
- **Rationale:** bassclef-cli#313 (luminary-pick hardcoded eng path; adopters hit "catalog dir missing" every /launch Phase 3.5) + bassclef-cli#320 (operator slug derived from git email leaked GitHub account ID into committed docs). Kunal cold-adopter smoke caught both; shared class is "engineering assumption in cold paths" (per plan doc Slot 3).
|
|
61
|
+
- **Downstream cure:** no action for adopters at exact-pin. Both changes are additive — lite tier adopters now get a working picker catalog; email-derived slugs never fire again. Operators wanting a display name beyond `operator` get an in-turn prompt instead.
|
|
62
|
+
- **Old shape retired:** nothing retired. Both old shapes stay viable (BASSCLEF_DIR env var still honored first; manual slug entry still allowed via operator reply).
|
|
63
|
+
|
|
64
|
+
### cli#307 — /build Phase 0 awk matches Step-N + WU-N (v1.15.0 → v1.15.1, 2026-10-04)
|
|
65
|
+
|
|
66
|
+
- **Change type:** content-change (1 file: `.claude/skills/build/SKILL.md`). Phase 0 safety floor awk pattern + prose at L276 updated to recognize both `### Step-N` (new vocab per ADR-040 D1) and `### WU-N` (legacy).
|
|
67
|
+
- **Fields:** 444 entries (unchanged). Content hash shifts on the `build` skill slug.
|
|
68
|
+
- **Version bump:** v1.15.0 → v1.15.1 (patch; content-only change on 1 slug; no schema shape shift; no entry count change).
|
|
69
|
+
- **Rationale:** bassclef-cli#307 — Kunal cold-adopter smoke caught the Phase 0 awk reading zero acceptance lines on Step-format specs. Partial-rename regression from ADR-040 D1 (Phase 1 grep got the rename, Phase 0 awk was missed).
|
|
70
|
+
- **Downstream cure:** no action for adopters at exact-pin — the fix is additive (both heading forms match). Step-format specs now trigger the floor as designed. WU-format specs continue to work (characterization).
|
|
71
|
+
- **Old shape retired:** nothing retired. The WU-only pattern leaves. The `(Step|WU)-` pattern stays indefinitely.
|
|
72
|
+
|
|
73
|
+
### #2036 — install-written-paths manifest + lib + rule (v1.14.4 → v1.15.0, 2026-10-03)
|
|
74
|
+
|
|
75
|
+
- **Change type:** content-add (3 new entries: `lib/install-written-paths.sh`, `.claude/rules/install-written-paths-mediation.md`, `.claude/hooks/session-reflection.d/83-install-written-paths-rebuild.sh`); content-change on `.claude/hooks/pre-commit-gate.sh` (filter block added for Kunal Finding #8 cure).
|
|
76
|
+
- **Fields:** 441 entries → 444 entries (3 adds, 1 content_hash shift).
|
|
77
|
+
- **Version bump:** v1.14.4 → v1.15.0 (content-add MINOR per discipline policy).
|
|
78
|
+
- **Rationale:** bassclef-upstream#2036 Finding #8 structural cure. See `architecture/reviews/2026-10-03.md` + plan doc `docs/next-session-plan-2026-10-04-kunal-slots-5-6.md` Slot 5.
|
|
79
|
+
- **Downstream cure:** no action for adopters at exact-pin — the new entries are additive and the lib's accessor API is new. Adopters pulling at `@thebassclef/lite@^1.15.0` get the cure automatically; the session-start fragment regenerates the install-written-paths manifest on first fresh-install session.
|
|
80
|
+
- **Old shape retired:** nothing retired. All existing entries preserved.
|
|
81
|
+
|
|
82
|
+
### #1777 — friction hook pattern adds stage|launch new skill vocabulary (v1.14.2 → v1.14.3, 2026-10-01)
|
|
83
|
+
|
|
84
|
+
- **Change type:** content-change (1 file: `post-skill-friction-check` hook)
|
|
85
|
+
- **Fields:** 439 entries → 439 entries (no add/remove). Content hash shifts on `post-skill-friction-check` slug (MAJOR_SKILLS_PATTERN regex extended with stage|launch per ADR-040 D1 rename grace window).
|
|
86
|
+
- **Version bump:** v1.14.2 → v1.14.3 (patch — content-only change; no field add/remove; no shape shift). Rebased after #523's retirement-discipline cure landed as v1.14.2.
|
|
87
|
+
- **Rationale:** PR #2028 closes #1777 (friction log never created because hook pattern matched old skill names only). Additive regex extension; grace-window compat preserved (preview|preview-build still match).
|
|
88
|
+
- **Downstream cure:** none — adopters re-sync and the hook starts firing on current-vocabulary chain dispatches. Lazy-create of `docs/session-friction-log.md` happens on first real friction case.
|
|
89
|
+
- **Old shape retired:** old pattern retired `2026-10-31` (grace window end for ADR-040 D1 vocabulary); current pattern keeps both until then.
|
|
90
|
+
|
|
91
|
+
### #523 — retirement discipline + fixture allowlist pattern (v1.14.1 → v1.14.2, 2026-10-01)
|
|
92
|
+
|
|
93
|
+
- **Change type:** content-change (2 files: `rule/lite-manifest-schema-change-discipline` + `standard/lite-manifest-schema-changes`)
|
|
94
|
+
- **Fields:** 439 entries (unchanged). Content hashes shift on `.claude/rules/lite-manifest-schema-change-discipline.md` (gains a `## Retirements` section covering retirement-class discipline alongside additions) and `standards/lite-manifest-schema-changes.md` (gains the v1.14.0 entry for #1992 that this PR backfilled plus this v1.14.2 entry).
|
|
95
|
+
- **Version bump:** v1.14.1 → v1.14.2 (patch; content-only change on 2 slugs; no schema shape shift; no entry count change).
|
|
96
|
+
- **Rationale:** sunj-labs/bassclef-upstream#523 closed. 4 consecutive main-CI failures 2026-09-29 to 09-30 (SHAs `3c997584`, `02da6b8d`, `924d489f` on `substrate-check-phase`) traced to Test 16 — the Feathers v1.6.1 snapshot guard — seeing 2 entries drop after PR #1992's compat shim retirement. Prior addition-class discipline fired a mechanical ledger-pre-flight gate; retirement-class did not. This PR extends the existing rule with a `## Retirements` section (per Ousterhout contrarian: one rule, two sections) + ships a fixture allowlist pattern (`.claude/hooks/tests/fixtures/2026-09-16-lite-catalog-closure/retired-entries.txt`) + updates Test 16 to subtract allowlist rows before asserting zero drops + backfills the v1.14.0 ledger entry #1992 missed.
|
|
97
|
+
- **Downstream cure:** none — adopters at `~1.14` get the rule content update at next sync. Any adopter authoring a retirement PR going forward follows the `## Retirements` section. Mechanical pre-commit hook (`pre-retire-discipline-check.sh`) deferred to a follow-on PR per Ousterhout (keep this PR scope-tight).
|
|
98
|
+
- **Old shape retired:** none — additive discipline extension.
|
|
99
|
+
- **Protects:** 4 upcoming retirement tickets (#266 S3 → npm move, #108 ADR-020 prototype deprecation, #1142 structural rename sweep, #1999 launch cadence rules) ship under the extended discipline.
|
|
100
|
+
|
|
101
|
+
### #1992 — compounding-axis compat shim retirement (v1.13.2 → v1.14.0, 2026-09-28)
|
|
102
|
+
|
|
103
|
+
- **Change type:** content-remove (2 entries dropped)
|
|
104
|
+
- **Fields:** 441 entries → 439 entries. Removed entries: `hook/longrun-prep-compounding-axis-check` (`.claude/hooks/longrun-prep-compounding-axis-check.sh`) + `rule/compounding-axis-fresh-analysis` (`.claude/rules/compounding-axis-fresh-analysis.md`). Both were compat shim symlinks from the 2026-07-03 compounding-axis → compounding-sequence rename per PR #611 (bet 2026-07-03b WU-2).
|
|
105
|
+
- **Version bump:** v1.13.2 → v1.14.0 (MINOR — content-remove per bump policy; adopters pinning major stay green, adopters pinning exact version must update).
|
|
106
|
+
- **Rationale:** sunj-labs/bassclef-upstream#1989 — compat shim grace window (ADR-031 default) elapsed on 2026-10-03; harness scanned each symlink twice inflating the rule set at session start. Grep across `standards/bassclef-source-consumers.json` returned zero adopter pins on the old slug, so the shim was defensive dogma rather than adopter-driven. Both symlinks + the duplicate embedding JSON deleted at SHA `9884278f`. Rename targets (`compounding-sequence-fresh-analysis.md` + `longrun-prep-compounding-sequence-check.sh`) stay live under their post-rename names.
|
|
107
|
+
- **Downstream cure:** none for adopters at `~1.13` or `~1.14`. Adopters pinning exact `manifest_version` (bassclef-web#256 + bassclef-cli#51) update pins at the next sync. Any adopter code reading `.entries[]` by legacy `compounding-axis` slug finds nothing — switch reads to `compounding-sequence` sister slugs.
|
|
108
|
+
- **Old shape retired:** immediately. ADR-031 grace window on compat shim ran its full term (2026-07-03 → 2026-10-03); no further compat preservation owed.
|
|
109
|
+
- **Retirement allowlist:** `.claude/hooks/tests/fixtures/2026-09-16-lite-catalog-closure/retired-entries.txt` carries both removed slugs so Test 16 (Feathers v1.6.1 snapshot guard) subtracts them before asserting zero drops. Backfilled by follow-on PR #<this PR> (sunj-labs/bassclef-upstream#523).
|
|
110
|
+
|
|
111
|
+
### #1777 — friction hook pattern adds stage|launch new skill vocabulary (v1.14.0 → v1.14.1, 2026-10-01)
|
|
112
|
+
|
|
113
|
+
- **Change type:** content-change (1 file: `post-skill-friction-check` hook)
|
|
114
|
+
- **Fields:** 439 entries → 439 entries (no add/remove). Content hash shifts on `post-skill-friction-check` slug (MAJOR_SKILLS_PATTERN regex extended with stage|launch per ADR-040 D1 rename grace window).
|
|
115
|
+
- **Version bump:** v1.14.0 → v1.14.1 (patch — content-only change; no field add/remove; no shape shift).
|
|
116
|
+
- **Rationale:** PR #2028 closes #1777 (friction log never created because hook pattern matched old skill names only). Additive regex extension; grace-window compat preserved (preview|preview-build still match).
|
|
117
|
+
- **Downstream cure:** none — adopters re-sync and the hook starts firing on current-vocabulary chain dispatches. Lazy-create of `docs/session-friction-log.md` happens on first real friction case.
|
|
118
|
+
- **Old shape retired:** old pattern retired `2026-10-31` (grace window end for ADR-040 D1 vocabulary); current pattern keeps both until then.
|
|
119
|
+
|
|
37
120
|
### #1961 — persistent statusline for bassclef version + sync status (v1.12.2 → v1.13.0, 2026-09-27)
|
|
38
121
|
|
|
39
122
|
- **Change type:** content-add (1 new entry: `statusline-version` lib) + content-change (2 files: `bassclef-sync` hook + `bassclef-orientation`)
|