@thebassclef/lite 1.3.0 → 1.4.1

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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/lite/.bassclef-source.json +2 -2
  6. package/dist/lite/.claude/hooks/_lib/wirings.sh +237 -0
  7. package/dist/lite/.claude/hooks/pre-commit-manifest-autoregen.sh +187 -0
  8. package/dist/lite/.claude/hooks/session-reflection.d/08-settings-drift.sh +66 -6
  9. package/dist/lite/.claude/hooks/session-reflection.d/20-artifact-staleness.sh +25 -5
  10. package/dist/lite/.claude/hooks/session-reflection.d/30-metrics-staleness.sh +11 -0
  11. package/dist/lite/.claude/hooks/session-reflection.d/60-deferred-actions.sh +2 -1
  12. package/dist/lite/.claude/hooks/session-reflection.d/80-hook-heartbeat-check.sh +20 -0
  13. package/dist/lite/.claude/hooks/session-reflection.d/tests/20-artifact-staleness.test.sh +89 -0
  14. package/dist/lite/.claude/hooks/session-reflection.sh +12 -1
  15. package/dist/lite/.claude/luminaries/david-farley.md +115 -0
  16. package/dist/lite/.claude/luminaries/jez-humble.md +124 -0
  17. package/dist/lite/.claude/luminaries/martin-fowler.md +18 -0
  18. package/dist/lite/.claude/skills/longrun/SKILL.md +51 -0
  19. package/dist/lite/.claude/skills/onboard-repo/SKILL.md +39 -11
  20. package/dist/lite/.claude/skills/provision-deploy-host/SKILL.md +2 -2
  21. package/dist/lite/lib/ancestor-claude-check.sh +101 -0
  22. package/dist/lite/lib/fixture-builder.sh +324 -0
  23. package/dist/lite/lib/workflow-metrics.sh +166 -0
  24. package/dist/lite/presence/install/bassclef-configs.template.jsonc +76 -0
  25. package/dist/lite/presence/install/bassclef-sync.template.sh +7 -4
  26. package/dist/lite/scripts/generate-lite-manifest.sh +151 -1
  27. package/dist/lite/scripts/workflow-metrics-query.sh +57 -0
  28. package/dist/lite/standards/lite-manifest-schema-changes.md +24 -0
  29. package/dist/lite/standards/lite-manifest.json +79 -68
  30. package/package.json +1 -1
  31. package/dist/lite/.claude/skills/journal-export/SKILL.md +0 -293
  32. package/dist/lite/.claude/skills/release/SKILL.md +0 -311
@@ -0,0 +1,101 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ #
4
+ # ancestor-claude-check.sh — walks up from a starting directory to $HOME
5
+ # (or a provided ceiling) and echoes each ancestor .claude/ directory found.
6
+ # Empty output = no ancestor .claude/ directories.
7
+ #
8
+ # Claude Code walks up from cwd loading .claude/ at every ancestor level.
9
+ # A stale ancestor .claude/skills/ produces duplicate + often-outdated skill
10
+ # entries in every child repo. Cold adopter can hit this on first install if
11
+ # they have any legacy .claude/ in a parent dir. /onboard-repo Phase X calls
12
+ # this helper to surface the class before proceeding.
13
+ #
14
+ # Contract:
15
+ # Args: $1 = starting directory (required; must exist)
16
+ # $2 = ceiling directory (optional; defaults to $HOME)
17
+ # Output: one absolute path per line, each an ancestor .claude/ directory
18
+ # between $1 (exclusive) and the ceiling (inclusive).
19
+ # Exit: 0 on clean walk (empty output OR one-or-more matches)
20
+ # 1 on error (missing start dir, unreadable ancestor)
21
+ #
22
+ # Adopter cure hint printed to stderr per match — non-blocking:
23
+ # "cure: mv <path> <path>.stale-<date>"
24
+ #
25
+ # Ships bassclef-upstream#573. Sister to sibling ticket that adds the manual
26
+ # audit step to the onboarding checklist.
27
+ #
28
+ # Anchors:
29
+ # @luminary saltzer-schroeder — complete mediation (every ancestor level checked)
30
+ # @luminary donald-norman — signifier + cure hint (adopter sees the issue + fix)
31
+
32
+ set -euo pipefail
33
+
34
+ _ancestor_claude_check_usage() {
35
+ cat >&2 <<'EOF'
36
+ ancestor-claude-check.sh — walks up from <start> to <ceiling> reporting ancestor .claude/ dirs
37
+
38
+ Usage:
39
+ bash lib/ancestor-claude-check.sh <start-dir> [ceiling-dir]
40
+
41
+ Args:
42
+ <start-dir> directory to start walking up from (required, must exist)
43
+ [ceiling-dir] stop above this dir (default: $HOME)
44
+
45
+ Output:
46
+ One absolute path per line for each ancestor .claude/ dir found.
47
+ Empty output = no ancestor .claude/ dirs (clean).
48
+
49
+ Exit:
50
+ 0 = clean walk (regardless of match count)
51
+ 1 = error (missing start dir OR unreadable ancestor)
52
+
53
+ Cure hint per match echoed to stderr, non-blocking.
54
+ EOF
55
+ }
56
+
57
+ ancestor_claude_check() {
58
+ local start="${1:-}"
59
+ local ceiling="${2:-${HOME}}"
60
+
61
+ if [ -z "$start" ]; then
62
+ _ancestor_claude_check_usage
63
+ return 1
64
+ fi
65
+
66
+ if [ ! -d "$start" ]; then
67
+ echo "ancestor-claude-check: start dir not found: $start" >&2
68
+ return 1
69
+ fi
70
+
71
+ # Canonicalize both paths so comparison + traversal work under symlinks
72
+ local start_abs ceiling_abs
73
+ start_abs=$(cd "$start" && pwd -P)
74
+ ceiling_abs=$(cd "$ceiling" 2>/dev/null && pwd -P) || {
75
+ echo "ancestor-claude-check: ceiling dir not found: $ceiling" >&2
76
+ return 1
77
+ }
78
+
79
+ # Walk from parent of start up to (and including) ceiling
80
+ local cur="$start_abs"
81
+ local today
82
+ today=$(date -u +%Y-%m-%d)
83
+ while [ "$cur" != "/" ]; do
84
+ cur=$(dirname "$cur")
85
+ if [ -d "$cur/.claude" ]; then
86
+ echo "$cur/.claude"
87
+ echo "cure: mv \"$cur/.claude\" \"$cur/.claude.stale-$today\"" >&2
88
+ fi
89
+ # Stop after processing ceiling — do not walk above it
90
+ if [ "$cur" = "$ceiling_abs" ]; then
91
+ break
92
+ fi
93
+ done
94
+
95
+ return 0
96
+ }
97
+
98
+ # When invoked directly (not sourced), run against argv
99
+ if [ "${BASH_SOURCE[0]:-}" = "${0}" ]; then
100
+ ancestor_claude_check "$@"
101
+ fi
@@ -12,6 +12,20 @@
12
12
  # fixture_setup_substrate_sibling(fixture_dir, substrate_name)
13
13
  # fixture_inject_pre_rename_refs(adopter_dir, canonical_substrate_dir)
14
14
  #
15
+ # Cold-adopter-harness fixture (per bassclef-upstream#1789 goal 2026-09-19b):
16
+ # fixture_setup_cold_adopter_harness(target) → echo cache path; runs
17
+ # harness once per shard, caches
18
+ # stdout+stderr for reuse
19
+ # harness_check_output(target) → echo cached content
20
+ # harness_cache_path(target) → echo cache path (no side effects)
21
+ # harness_exit_code(target) → echo harness exit code from run
22
+ # fixture_cleanup_cold_adopter_harness() → sweep harness fixture cache
23
+ #
24
+ # The harness fixture uses BASSCLEF_HARNESS_FIXTURE_ROOT env var if set,
25
+ # otherwise $TMPDIR/bassclef-harness-fixture-$$ (per-shard PID scope).
26
+ # Cache key is a short SHA of the canonicalized target path so different
27
+ # targets do not collide.
28
+ #
15
29
  # Override: SKIP_FIXTURE_BUILDER=1 — every public function exits 0 silently
16
30
  # (fixture_init echoes empty path).
17
31
  # Logged via trace-helper per bassclef hook idiom.
@@ -188,3 +202,313 @@ fixture_inject_pre_rename_refs() {
188
202
  EOF
189
203
  return 0
190
204
  }
205
+
206
+ # =============================================================================
207
+ # Cold-adopter-harness fixture — bassclef-upstream#1789 goal 2026-09-19b
208
+ # Runs the cold-adopter harness once per shard, caches stdout+stderr, exposes
209
+ # an accessor. Tests grep cached content instead of re-invoking harness.
210
+ # =============================================================================
211
+
212
+ # _harness_fixture_root — echo the cache root path
213
+ # Precedence:
214
+ # 1. BASSCLEF_HARNESS_FIXTURE_ROOT env (explicit override — tests, prewarm job)
215
+ # 2. SHARD_INDEX env (CI matrix — per-shard scope so tests in the SAME shard
216
+ # SHARE cache; without this, each test's own PID gives a fresh cache and
217
+ # the fixture saves nothing across tests)
218
+ # 3. $$ (current shell PID — local single-test runs, per-process isolated)
219
+ _harness_fixture_root() {
220
+ if [ -n "${BASSCLEF_HARNESS_FIXTURE_ROOT:-}" ]; then
221
+ echo "$BASSCLEF_HARNESS_FIXTURE_ROOT"
222
+ elif [ -n "${SHARD_INDEX:-}" ]; then
223
+ echo "${TMPDIR:-/tmp}/bassclef-harness-fixture-shard-${SHARD_INDEX}"
224
+ else
225
+ echo "${TMPDIR:-/tmp}/bassclef-harness-fixture-$$"
226
+ fi
227
+ }
228
+
229
+ # harness_cache_path <target>
230
+ # Echo the cache file path for this target (public accessor). No side effects.
231
+ # Target need not exist — the path is a function of the canonicalized target
232
+ # string (or the raw string when canonicalization fails).
233
+ harness_cache_path() {
234
+ local target="${1:?harness_cache_path requires target}"
235
+ # Canonicalize if possible; fall back to raw string when target doesn't exist
236
+ local canonical
237
+ canonical=$(cd "$target" 2>/dev/null && pwd) || canonical="$target"
238
+ local root
239
+ root=$(_harness_fixture_root)
240
+ local hash
241
+ # Use shasum on macOS + Linux; fall back to md5 hash if shasum unavailable
242
+ if command -v shasum >/dev/null 2>&1; then
243
+ hash=$(echo "$canonical" | shasum -a 256 | cut -c1-16)
244
+ elif command -v sha256sum >/dev/null 2>&1; then
245
+ hash=$(echo "$canonical" | sha256sum | cut -c1-16)
246
+ else
247
+ # Last resort — path basename + length; not collision-proof but works
248
+ hash=$(echo "$canonical" | tr -c 'a-zA-Z0-9' '_' | tail -c 16)
249
+ fi
250
+ echo "$root/harness-$hash.out"
251
+ }
252
+
253
+ # -----------------------------------------------------------------------------
254
+ # fixture_setup_cold_adopter_harness <target>
255
+ # Run the cold-adopter harness once per shard against <target>. Cache stdout
256
+ # + stderr to a per-target file. Second call reuses cache. Echoes cache path.
257
+ # Override SKIP_FIXTURE_BUILDER=1 — return 0 silently.
258
+ # -----------------------------------------------------------------------------
259
+ fixture_setup_cold_adopter_harness() {
260
+ if [ "${SKIP_FIXTURE_BUILDER:-0}" = "1" ]; then
261
+ return 0
262
+ fi
263
+ local target="${1:?fixture_setup_cold_adopter_harness requires target}"
264
+ # Canonicalize target path so different spellings share cache
265
+ local target_canonical
266
+ target_canonical=$(cd "$target" 2>/dev/null && pwd) || {
267
+ echo "fixture_setup_cold_adopter_harness: target does not exist: $target" >&2
268
+ return 1
269
+ }
270
+ local root
271
+ root=$(_harness_fixture_root)
272
+ mkdir -p "$root"
273
+ local cache_file
274
+ cache_file=$(harness_cache_path "$target_canonical")
275
+ local elapsed_marker="${cache_file}.elapsed"
276
+ local exit_marker="${cache_file}.exit"
277
+
278
+ # Reuse cache if present — cache lifetime is per-shard by design
279
+ if [ -f "$cache_file" ]; then
280
+ echo "$cache_file"
281
+ return 0
282
+ fi
283
+
284
+ # Locate the harness — derived from this lib's location
285
+ local this_lib="${BASH_SOURCE[0]}"
286
+ local lib_dir
287
+ lib_dir=$(cd "$(dirname "$this_lib")" && pwd)
288
+ local repo
289
+ repo=$(cd "$lib_dir/.." && pwd)
290
+ local harness="$repo/scripts/cold-adopter-harness-sync.sh"
291
+
292
+ if [ ! -f "$harness" ]; then
293
+ echo "fixture_setup_cold_adopter_harness: harness not found at $harness" >&2
294
+ return 1
295
+ fi
296
+
297
+ # Write to temp file + atomic rename so concurrent writers (rare — same PID
298
+ # scope) do not leave a partial cache readable to other callers.
299
+ local tmp_file="${cache_file}.tmp.$$"
300
+ local start end exit_code
301
+ # K1 instrumentation — record elapsed setup time
302
+ start=$(date +%s)
303
+ bash "$harness" --mode sync --target "$target_canonical" > "$tmp_file" 2>&1
304
+ exit_code=$?
305
+ end=$(date +%s)
306
+
307
+ # Atomic promotion — first writer wins; race loser removes their tmp file.
308
+ # POSIX rename is atomic within a filesystem. -n avoids clobbering a
309
+ # concurrent winner's cache (BSD + GNU mv both support -n since 2007).
310
+ if [ ! -f "$cache_file" ]; then
311
+ mv "$tmp_file" "$cache_file"
312
+ echo "$exit_code" > "$exit_marker"
313
+ echo "$((end - start))" > "$elapsed_marker"
314
+ else
315
+ rm -f "$tmp_file"
316
+ fi
317
+
318
+ echo "$cache_file"
319
+ }
320
+
321
+ # -----------------------------------------------------------------------------
322
+ # harness_exit_code <target>
323
+ # Echo the harness exit code from the cached run. Empty if cache absent.
324
+ # Callers check this before trusting cache content — a non-zero exit means
325
+ # the harness bailed out and content may be partial.
326
+ # Override SKIP_FIXTURE_BUILDER=1 — echo nothing, return 0.
327
+ # -----------------------------------------------------------------------------
328
+ harness_exit_code() {
329
+ if [ "${SKIP_FIXTURE_BUILDER:-0}" = "1" ]; then
330
+ return 0
331
+ fi
332
+ local target="${1:?harness_exit_code requires target}"
333
+ local canonical
334
+ canonical=$(cd "$target" 2>/dev/null && pwd) || canonical="$target"
335
+ local cache_file
336
+ cache_file=$(harness_cache_path "$canonical")
337
+ local exit_marker="${cache_file}.exit"
338
+ if [ -f "$exit_marker" ]; then
339
+ cat "$exit_marker"
340
+ fi
341
+ }
342
+
343
+ # -----------------------------------------------------------------------------
344
+ # harness_check_output <target>
345
+ # Echo the cached harness stdout+stderr for <target>. Empty output if cache
346
+ # not yet built (caller should invoke fixture_setup_cold_adopter_harness first).
347
+ # Override SKIP_FIXTURE_BUILDER=1 — echo nothing, return 0.
348
+ # -----------------------------------------------------------------------------
349
+ harness_check_output() {
350
+ if [ "${SKIP_FIXTURE_BUILDER:-0}" = "1" ]; then
351
+ return 0
352
+ fi
353
+ local target="${1:?harness_check_output requires target}"
354
+ local target_canonical
355
+ target_canonical=$(cd "$target" 2>/dev/null && pwd) || return 0
356
+ local cache_file
357
+ cache_file=$(harness_cache_path "$target_canonical")
358
+ if [ -f "$cache_file" ]; then
359
+ cat "$cache_file"
360
+ fi
361
+ }
362
+
363
+ # -----------------------------------------------------------------------------
364
+ # fixture_cleanup_cold_adopter_harness
365
+ # Sweep the harness fixture cache. Idempotent.
366
+ # Override SKIP_FIXTURE_BUILDER=1 — return 0 silently.
367
+ # -----------------------------------------------------------------------------
368
+ fixture_cleanup_cold_adopter_harness() {
369
+ if [ "${SKIP_FIXTURE_BUILDER:-0}" = "1" ]; then
370
+ return 0
371
+ fi
372
+ local root
373
+ root=$(_harness_fixture_root)
374
+ if [ -d "$root" ]; then
375
+ rm -f "$root"/harness-*.out "$root"/harness-*.elapsed "$root"/harness-*.exit "$root"/harness-*.tmp.* 2>/dev/null || true
376
+ fi
377
+ return 0
378
+ }
379
+
380
+ # -----------------------------------------------------------------------------
381
+ # harness_adopter_shape_cache_path <source_target>
382
+ # Echo the cache file path for adopter-shape output derived from <source_target>.
383
+ # Public accessor. No side effects. Same shape as harness_cache_path but with
384
+ # "-adopter-shape" suffix so upstream + adopter-shape caches never collide.
385
+ # -----------------------------------------------------------------------------
386
+ harness_adopter_shape_cache_path() {
387
+ local target="${1:?harness_adopter_shape_cache_path requires source target}"
388
+ local canonical
389
+ canonical=$(cd "$target" 2>/dev/null && pwd) || canonical="$target"
390
+ local root
391
+ root=$(_harness_fixture_root)
392
+ local hash
393
+ if command -v shasum >/dev/null 2>&1; then
394
+ hash=$(echo "$canonical" | shasum -a 256 | cut -c1-16)
395
+ elif command -v sha256sum >/dev/null 2>&1; then
396
+ hash=$(echo "$canonical" | sha256sum | cut -c1-16)
397
+ else
398
+ hash=$(echo "$canonical" | tr -c 'a-zA-Z0-9' '_' | tail -c 16)
399
+ fi
400
+ echo "$root/harness-adopter-shape-$hash.out"
401
+ }
402
+
403
+ # -----------------------------------------------------------------------------
404
+ # fixture_setup_cold_adopter_shape <source_target>
405
+ # Build one synthetic adopter-shape tmpdir per shard from <source_target>
406
+ # (cp -R substrate + rm tests dirs), run the harness once against it, cache
407
+ # stdout + stderr. Second call reuses cache. Echoes cache path.
408
+ #
409
+ # bassclef-upstream#1800 — closes the shard 1 duplicate for
410
+ # cold-adopter-harness-adopter-mode.test.sh Test 1. Test 4 keeps its own
411
+ # tmpdir invocation because rule injection invalidates this fixture's cache;
412
+ # a rule-parameterized variant tracked as follow-on.
413
+ #
414
+ # Override SKIP_FIXTURE_BUILDER=1 — return 0 silently.
415
+ # -----------------------------------------------------------------------------
416
+ fixture_setup_cold_adopter_shape() {
417
+ if [ "${SKIP_FIXTURE_BUILDER:-0}" = "1" ]; then
418
+ return 0
419
+ fi
420
+ local source_target="${1:?fixture_setup_cold_adopter_shape requires source target}"
421
+ local source_canonical
422
+ source_canonical=$(cd "$source_target" 2>/dev/null && pwd) || {
423
+ echo "fixture_setup_cold_adopter_shape: source target does not exist: $source_target" >&2
424
+ return 1
425
+ }
426
+ local root
427
+ root=$(_harness_fixture_root)
428
+ mkdir -p "$root"
429
+ local cache_file
430
+ cache_file=$(harness_adopter_shape_cache_path "$source_canonical")
431
+ local elapsed_marker="${cache_file}.elapsed"
432
+ local exit_marker="${cache_file}.exit"
433
+
434
+ # Reuse cache if present — cache lifetime is per-shard by design
435
+ if [ -f "$cache_file" ]; then
436
+ echo "$cache_file"
437
+ return 0
438
+ fi
439
+
440
+ # Locate the harness — derived from this lib's location
441
+ local this_lib="${BASH_SOURCE[0]}"
442
+ local lib_dir
443
+ lib_dir=$(cd "$(dirname "$this_lib")" && pwd)
444
+ local repo
445
+ repo=$(cd "$lib_dir/.." && pwd)
446
+ local harness="$repo/scripts/cold-adopter-harness-sync.sh"
447
+
448
+ if [ ! -f "$harness" ]; then
449
+ echo "fixture_setup_cold_adopter_shape: harness not found at $harness" >&2
450
+ return 1
451
+ fi
452
+
453
+ # Build the adopter-shape tmpdir — cp -R substrate + rm tests dirs
454
+ local adopter_tmpdir
455
+ adopter_tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/adopter-shape-fixture-XXXXXX")
456
+ # cp only the surfaces the harness reads; suppress errors on optional dirs
457
+ cp -R \
458
+ "$source_canonical/.claude" \
459
+ "$source_canonical/scripts" \
460
+ "$source_canonical/lib" \
461
+ "$source_canonical/standards" \
462
+ "$source_canonical/architecture" \
463
+ "$source_canonical/presence" \
464
+ "$adopter_tmpdir/" 2>/dev/null || true
465
+ # rm tests dirs to force adopter-shape (0 tests)
466
+ rm -rf \
467
+ "$adopter_tmpdir/.claude/hooks/tests" \
468
+ "$adopter_tmpdir/scripts/tests" \
469
+ "$adopter_tmpdir/lib/tests" 2>/dev/null || true
470
+
471
+ # Run harness with SHAPE_D_BLOCK=0 to match adopter-mode.test.sh semantics
472
+ # (forces ADVISORY output so DEAD-LETTER surfaces without failing the process)
473
+ local tmp_file="${cache_file}.tmp.$$"
474
+ local start end exit_code
475
+ start=$(date +%s)
476
+ SHAPE_D_BLOCK=0 bash "$harness" --mode sync --target "$adopter_tmpdir" > "$tmp_file" 2>&1
477
+ exit_code=$?
478
+ end=$(date +%s)
479
+
480
+ # Cleanup the tmpdir — cache holds the output; tmpdir was ephemeral
481
+ rm -rf "$adopter_tmpdir" 2>/dev/null || true
482
+
483
+ # Atomic promotion — first writer wins; race loser removes their tmp file
484
+ if [ ! -f "$cache_file" ]; then
485
+ mv "$tmp_file" "$cache_file"
486
+ echo "$exit_code" > "$exit_marker"
487
+ echo "$((end - start))" > "$elapsed_marker"
488
+ else
489
+ rm -f "$tmp_file"
490
+ fi
491
+
492
+ echo "$cache_file"
493
+ }
494
+
495
+ # -----------------------------------------------------------------------------
496
+ # harness_check_output_adopter_shape <source_target>
497
+ # Echo the cached harness stdout+stderr for the adopter-shape variant of
498
+ # <source_target>. Empty output if cache not yet built (caller should invoke
499
+ # fixture_setup_cold_adopter_shape first).
500
+ # Override SKIP_FIXTURE_BUILDER=1 — echo nothing, return 0.
501
+ # -----------------------------------------------------------------------------
502
+ harness_check_output_adopter_shape() {
503
+ if [ "${SKIP_FIXTURE_BUILDER:-0}" = "1" ]; then
504
+ return 0
505
+ fi
506
+ local target="${1:?harness_check_output_adopter_shape requires source target}"
507
+ local canonical
508
+ canonical=$(cd "$target" 2>/dev/null && pwd) || return 0
509
+ local cache_file
510
+ cache_file=$(harness_adopter_shape_cache_path "$canonical")
511
+ if [ -f "$cache_file" ]; then
512
+ cat "$cache_file"
513
+ fi
514
+ }
@@ -0,0 +1,166 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # Workflow metrics measurement library — Module B4 per
4
+ # docs/decompositions/2026-09-18-ci-cd-subsystem.md.
5
+ #
6
+ # Records CI workflow run durations + shard breakdowns. Enables Phase
7
+ # checkpoint sampling per canvas activity diagram. Every downstream
8
+ # Phase 1-4 cure references this lib to prove KPI moves.
9
+ #
10
+ # Closes sunj-labs/bassclef-upstream#1307 (parent epic #1750, rank 0).
11
+ #
12
+ # Anchor luminaries:
13
+ # @luminary nicole-forsgren — DORA metrics
14
+ # @luminary john-ousterhout — deep module (hides store shape)
15
+ # @luminary michael-nygard — fail-soft on missing dir / jq / disk
16
+ #
17
+ # Storage: atomic file per run at
18
+ # ${STORE_DIR}/YYYY-MM-DD-<workflow>-<run_id>.json
19
+ #
20
+ # Usage:
21
+ # source lib/workflow-metrics.sh
22
+ # record_metric <workflow> <run_id> <duration_seconds> <shard_breakdown> [store_dir]
23
+ # query_metrics [store_dir] [workflow_filter]
24
+ # compute_median [store_dir] [workflow_filter]
25
+ #
26
+ # Fail-soft everywhere — missing dir, missing jq, disk full all return
27
+ # non-error exits. Missing metrics never turn CI red.
28
+
29
+ # Default store dir
30
+ _workflow_metrics_default_store() {
31
+ local repo_root
32
+ repo_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")
33
+ echo "$repo_root/state/workflow-metrics"
34
+ }
35
+
36
+ # record_metric <workflow> <run_id> <duration_seconds> <shard_breakdown> [store_dir]
37
+ #
38
+ # Writes a JSON entry to $store_dir/YYYY-MM-DD-<workflow>-<run_id>.json.
39
+ # Fail-soft in every branch — dir creation, jq missing, disk full all
40
+ # return exit 0.
41
+ record_metric() {
42
+ local workflow="${1:-}"
43
+ local run_id="${2:-}"
44
+ local duration="${3:-}"
45
+ local shard_breakdown="${4:-}"
46
+ local store_dir="${5:-$(_workflow_metrics_default_store)}"
47
+
48
+ # Fail-soft on missing args — never break the calling workflow
49
+ if [ -z "$workflow" ] || [ -z "$run_id" ] || [ -z "$duration" ]; then
50
+ return 0
51
+ fi
52
+
53
+ # Fail-soft on dir creation failure
54
+ mkdir -p "$store_dir" 2>/dev/null || return 0
55
+
56
+ local date_prefix
57
+ date_prefix=$(date -u +"%Y-%m-%d")
58
+ local timestamp
59
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
60
+
61
+ local file_path="$store_dir/${date_prefix}-${workflow}-${run_id}.json"
62
+
63
+ # Build JSON. Prefer jq for correctness; fall back to raw string.
64
+ if command -v jq >/dev/null 2>&1; then
65
+ jq -n \
66
+ --arg workflow "$workflow" \
67
+ --arg run_id "$run_id" \
68
+ --argjson duration_seconds "$duration" \
69
+ --arg shard_breakdown "$shard_breakdown" \
70
+ --arg timestamp "$timestamp" \
71
+ '{workflow: $workflow, run_id: $run_id, duration_seconds: $duration_seconds, shard_breakdown: $shard_breakdown, timestamp: $timestamp}' \
72
+ > "$file_path" 2>/dev/null || return 0
73
+ else
74
+ # Fallback — construct JSON by hand
75
+ printf '{"workflow":"%s","run_id":"%s","duration_seconds":%s,"shard_breakdown":"%s","timestamp":"%s"}\n' \
76
+ "$workflow" "$run_id" "$duration" "$shard_breakdown" "$timestamp" > "$file_path" 2>/dev/null || return 0
77
+ fi
78
+
79
+ return 0
80
+ }
81
+
82
+ # query_metrics [store_dir] [workflow_filter]
83
+ #
84
+ # Emits one line per entry to stdout: run_id duration_seconds workflow
85
+ # If workflow_filter is set, only entries matching that workflow are emitted.
86
+ # Empty store or missing dir returns empty output + exit 0.
87
+ query_metrics() {
88
+ local store_dir="${1:-$(_workflow_metrics_default_store)}"
89
+ local workflow_filter="${2:-}"
90
+
91
+ # Missing dir → empty output
92
+ if [ ! -d "$store_dir" ]; then
93
+ return 0
94
+ fi
95
+
96
+ # Find all JSON files under store_dir; fail-soft on empty
97
+ local files
98
+ files=$(find "$store_dir" -maxdepth 1 -name '*.json' -type f 2>/dev/null)
99
+ if [ -z "$files" ]; then
100
+ return 0
101
+ fi
102
+
103
+ local file workflow run_id duration
104
+ while IFS= read -r file; do
105
+ [ -z "$file" ] && continue
106
+ if command -v jq >/dev/null 2>&1; then
107
+ workflow=$(jq -r '.workflow // ""' "$file" 2>/dev/null)
108
+ run_id=$(jq -r '.run_id // ""' "$file" 2>/dev/null)
109
+ duration=$(jq -r '.duration_seconds // 0' "$file" 2>/dev/null)
110
+ else
111
+ workflow=$(grep -o '"workflow":"[^"]*"' "$file" | cut -d'"' -f4)
112
+ run_id=$(grep -o '"run_id":"[^"]*"' "$file" | cut -d'"' -f4)
113
+ duration=$(grep -o '"duration_seconds":[0-9]*' "$file" | cut -d':' -f2)
114
+ fi
115
+
116
+ if [ -n "$workflow_filter" ] && [ "$workflow" != "$workflow_filter" ]; then
117
+ continue
118
+ fi
119
+
120
+ echo "$run_id $duration $workflow"
121
+ done <<< "$files"
122
+
123
+ return 0
124
+ }
125
+
126
+ # compute_median [store_dir] [workflow_filter]
127
+ #
128
+ # Emits the median duration for entries in the store, optionally filtered
129
+ # by workflow. Empty store returns 0.
130
+ compute_median() {
131
+ local store_dir="${1:-$(_workflow_metrics_default_store)}"
132
+ local workflow_filter="${2:-}"
133
+
134
+ local durations
135
+ durations=$(query_metrics "$store_dir" "$workflow_filter" | awk '{print $2}' | sort -n)
136
+
137
+ if [ -z "$durations" ]; then
138
+ echo "0"
139
+ return 0
140
+ fi
141
+
142
+ local count
143
+ count=$(echo "$durations" | wc -l | tr -d ' ')
144
+
145
+ if [ "$count" -eq 0 ]; then
146
+ echo "0"
147
+ return 0
148
+ fi
149
+
150
+ local mid
151
+ if [ $((count % 2)) -eq 1 ]; then
152
+ # Odd count — middle element
153
+ mid=$(( (count + 1) / 2 ))
154
+ echo "$durations" | sed -n "${mid}p"
155
+ else
156
+ # Even count — average of two middle elements
157
+ local upper=$((count / 2 + 1))
158
+ local lower=$((count / 2))
159
+ local u l
160
+ u=$(echo "$durations" | sed -n "${upper}p")
161
+ l=$(echo "$durations" | sed -n "${lower}p")
162
+ echo $(( (u + l) / 2 ))
163
+ fi
164
+
165
+ return 0
166
+ }
@@ -0,0 +1,76 @@
1
+ { "tier": "lite",
2
+ // Adopter-facing default for `.claude/bassclef-configs.jsonc`.
3
+ // Ships via lite manifest per bassclef-cli#104. `bassclef init` copies this
4
+ // file to `.claude/bassclef-configs.jsonc` on first-run. Adopters edit it
5
+ // to tune per-repo behavior.
6
+ //
7
+ // Full field catalog + validation: standards/state-spine/schemas/bassclef-configs.schema.json
8
+ // Field reference doc: standards/bassclef-configs-schema.md
9
+ //
10
+ // Every field below is optional. Absent fields fall back to substrate defaults.
11
+ // ----- tech stack -----
12
+ // Declares your primary framework so per-stack skills (SDLC gates,
13
+ // dependency check, migration discipline) load the right sibling docs.
14
+ // Common values: "nextjs", "vite-react", "remix", "fastapi", "rails",
15
+ // "express", "go-http", or omit for framework-agnostic.
16
+ //
17
+ // "tech_stack": {
18
+ // "frontend": "nextjs",
19
+ // "pkg_manager": "pnpm",
20
+ // "orm": "prisma",
21
+ // "lang": "typescript"
22
+ // },
23
+
24
+ // ----- prose discipline -----
25
+ // Controls the /kiss words Stop hook. Values:
26
+ // true — advisory (default; findings to stderr, session continues)
27
+ // "strict" — blocks the stop on jargon findings, forces a rewrite turn
28
+ // false — disabled
29
+ //
30
+ // "prose_discipline": {
31
+ // "kiss_words_turn_prose": true
32
+ // },
33
+
34
+ // ----- testing tier per path -----
35
+ // Extends .claude/rules/testing-tier-config.md with per-repo path matchers.
36
+ // Tier 0 = strict TDD (test mtime <= source mtime, BLOCK at pre-commit)
37
+ // Tier 1 = test-with (WARN at pre-commit if new source has no matching test)
38
+ // Tier 2 = smoke (advisory)
39
+ // Tier 3 = manual verify (silent)
40
+ //
41
+ // "testing": {
42
+ // "global_floor": 1,
43
+ // "path_matchers": [
44
+ // { "match": "src/middleware.ts", "tier": 0 },
45
+ // { "match": "src/lib/auth/**", "tier": 0 },
46
+ // { "match": "src/app/**", "tier": 2 }
47
+ // ]
48
+ // },
49
+
50
+ // ----- /promote target -----
51
+ // Where /promote sends substrate proposals. Two shapes:
52
+ // "<owner>/<repo>" — gh issue create --repo <value>
53
+ // "email:<address>" — mailto: URL printed for you to send
54
+ // Absent — /promote falls back to current-repo filing.
55
+ //
56
+ // "promote_target": "your-org/your-substrate-fork",
57
+
58
+ // ----- longrun orchestrator merge mode -----
59
+ // Controls how /longrun handles PR merges within its bet scope.
60
+ // "operator-gated" — every PR pauses for your review (default)
61
+ // "agent-merges-within-scope" — orchestrator merges autonomously within
62
+ // bet scope; hard_ceilings stay blocking
63
+ //
64
+ // "longrun": {
65
+ // "orchestrator_merge": {
66
+ // "mode": "operator-gated",
67
+ // "hard_ceilings": ["auth", "schema", "security", "prod-deploy", "blast-radius-floor"]
68
+ // }
69
+ // },
70
+
71
+ // ----- adopter defaults -----
72
+ // Per-adopter tunings. See standards/bassclef-configs-schema.md
73
+ // for the full field list.
74
+ //
75
+ // "adopter_defaults": {}
76
+ }