@thebassclef/lite 1.9.16 → 1.9.19

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 (45) 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/.claude/agents/builder.md +7 -4
  6. package/dist/lite/.claude/bassclef-orientation.md +22 -0
  7. package/dist/lite/.claude/hooks/artifact-ingestion-gate.sh +11 -3
  8. package/dist/lite/.claude/hooks/assert-verify-steering.sh +1 -19
  9. package/dist/lite/.claude/hooks/plain-english-steering.sh +86 -41
  10. package/dist/lite/.claude/hooks/pre-commit-gate.sh +10 -2
  11. package/dist/lite/.claude/hooks/pre-push-shellcheck.sh +196 -0
  12. package/dist/lite/.claude/hooks/reviewer-dispatch-check.sh +153 -0
  13. package/dist/lite/.claude/hooks/session-reflection.d/83-install-written-paths-rebuild.sh +15 -32
  14. package/dist/lite/.claude/hooks/turn-prose-density-measure.sh +109 -0
  15. package/dist/lite/.claude/hooks/turn-prose-kiss-check.sh +28 -0
  16. package/dist/lite/.claude/rules/branching.md +2 -0
  17. package/dist/lite/.claude/rules/identifier-leak-prevention.md +2 -1
  18. package/dist/lite/.claude/rules/install-written-paths-mediation.md +7 -0
  19. package/dist/lite/.claude/rules/lite-manifest-schema-change-discipline.md +23 -0
  20. package/dist/lite/.claude/rules/reviewer-dispatch.md +95 -0
  21. package/dist/lite/.claude/settings.json +10 -0
  22. package/dist/lite/.claude/skills/build/SKILL.md +81 -6
  23. package/dist/lite/.claude/skills/eisenhower/SKILL.md +26 -0
  24. package/dist/lite/.claude/skills/launch/SKILL.md +45 -2
  25. package/dist/lite/.claude/skills/onboard-repo/SKILL.md +208 -40
  26. package/dist/lite/.claude/skills/onboard-repo/claude-oauth-probe.sh +60 -0
  27. package/dist/lite/.claude/skills/onboard-repo/concise-prompt.sh +73 -0
  28. package/dist/lite/.claude/skills/onboard-repo/write-state-marker.sh +81 -0
  29. package/dist/lite/.claude/skills/pre-mortem/SKILL.md +56 -0
  30. package/dist/lite/.claude/skills/sprint/SKILL.md +24 -3
  31. package/dist/lite/.claude/skills/temperance/SKILL.md +15 -4
  32. package/dist/lite/README.md +2 -0
  33. package/dist/lite/bassclef-version.json +5 -5
  34. package/dist/lite/lib/install-written-paths.sh +250 -3
  35. package/dist/lite/lib/prose-marker.sh +203 -0
  36. package/dist/lite/lib/tier-dependency-audit.sh +52 -25
  37. package/dist/lite/presence/install/bassclef-sync.template.sh +86 -8
  38. package/dist/lite/presence/install/schedule-auto-save.cron.sh +30 -2
  39. package/dist/lite/scripts/generate-lite-manifest.sh +15 -0
  40. package/dist/lite/scripts/launch/local-serve.sh +149 -8
  41. package/dist/lite/standards/bassclef-wiring-manifest.json +12 -0
  42. package/dist/lite/standards/lite-manifest-schema-changes.md +84 -0
  43. package/dist/lite/standards/lite-manifest.json +100 -29
  44. package/dist/lite/standards/prose-marker-format.md +105 -0
  45. package/package.json +1 -1
@@ -214,13 +214,28 @@ if echo "$@" | grep -q -- '--explain'; then
214
214
  EXPLAIN_MODE=true
215
215
  fi
216
216
 
217
- # Resolve goal via accessor
217
+ # Resolve goal via accessor. #2132 — capture stderr so parse errors surface
218
+ # instead of masquerading as "not found". Map accessor return codes:
219
+ # 1 → goal file not found; 3 → goal file could not be parsed.
218
220
  source lib/state.sh
219
221
  BET_SLUG="$1"
220
- BET_JSON=$(state_iteration_bet_get "$BET_SLUG" 2>/dev/null) || {
221
- echo "/build: goal '$BET_SLUG' not found via state_iteration_bet_get" >&2
222
- exit 1
223
- }
222
+ BET_JSON=$(state_iteration_bet_get "$BET_SLUG" 2>&1)
223
+ BET_RC=$?
224
+ if [ "$BET_RC" -ne 0 ]; then
225
+ case "$BET_RC" in
226
+ 1)
227
+ echo "/build: goal file not found: $BET_SLUG" >&2
228
+ ;;
229
+ 3)
230
+ echo "/build: goal file could not be parsed: $BET_SLUG" >&2
231
+ ;;
232
+ *)
233
+ echo "/build: goal '$BET_SLUG' lookup failed (rc=$BET_RC)" >&2
234
+ ;;
235
+ esac
236
+ echo "$BET_JSON" >&2
237
+ exit "$BET_RC"
238
+ fi
224
239
 
225
240
  # Check status active
226
241
  STATUS=$(echo "$BET_JSON" | jq -r '.status // empty')
@@ -234,6 +249,15 @@ SPEC_PATH=$(echo "$BET_JSON" | jq -r '.references[]? | select(.type == "spec") |
234
249
  DECOMP_PATH=$(echo "$BET_JSON" | jq -r '.references[]? | select(.type == "decomposition") | .id // empty')
235
250
  UX_MIG_PATH=$(echo "$BET_JSON" | jq -r '.references[]? | select(.type == "ux-migration") | .id // empty')
236
251
 
252
+ # #2132 — goal MUST declare a spec reference. Empty SPEC_PATH means Phase 1
253
+ # would awk against docs/specs/.md and silently find nothing; cure refuses
254
+ # at Phase 0 instead with a clear error.
255
+ if [ -z "$SPEC_PATH" ]; then
256
+ echo "/build: goal '$BET_SLUG' has no spec reference — refusing" >&2
257
+ echo "Add a {type: spec, id: <spec-slug>} entry to goal frontmatter references[]." >&2
258
+ exit 4
259
+ fi
260
+
237
261
  # Validate references exist
238
262
  for ref_type in spec decomposition; do
239
263
  ref_id=$(echo "$BET_JSON" | jq -r --arg t "$ref_type" '.references[]? | select(.type == $t) | .id // empty')
@@ -349,6 +373,16 @@ WU_LIST=$(awk '
349
373
 
350
374
  # Each step should reference at least one story (US-NNN)
351
375
  WU_COUNT=$(echo "$WU_LIST" | grep -cE '^### (Step|WU)-' || echo 0)
376
+
377
+ # #2132 — refuse zero-step specs. The Phase 2 orphan check does not fire
378
+ # on empty WU_LIST (no steps → no orphans → silent pass). Phase 1 refuses
379
+ # so adopters see the real problem.
380
+ if [ "$WU_COUNT" = "0" ]; then
381
+ echo "/build: spec has zero steps — refusing" >&2
382
+ echo "Spec file: $SPEC_FILE" >&2
383
+ echo "Need a Steps enumerated section with Step-N or WU-N entries." >&2
384
+ exit 4
385
+ fi
352
386
  ```
353
387
 
354
388
  Build structured object: `{ bet, spec_path, decomp_path, ux_migration_path, wus[] }`.
@@ -483,6 +517,23 @@ Operator can force adopter-mode via `--adopter-mode` flag (V2). Operator can for
483
517
 
484
518
  ### Phase 3 — DryRunPlanner
485
519
 
520
+ **Read the pre-mortem marker first (per bassclef-upstream#2175 Batch B — F5).** Before enumerating steps, read `state/markers/pre-mortem/<branch-slug>.marker` if present. The marker names the risk ledger file plus top-5 risks. Surface the ledger path + top-5 risks in the dry-run output. Builder dispatches in future LIVE mode read the ledger before writing code.
521
+
522
+ <!-- FKGL-EXEMPT: bash command block; shell commands are not graded prose. -->
523
+
524
+ ```bash
525
+ BRANCH_SLUG=$(git branch --show-current | tr '/' '-')
526
+ MARKER="state/markers/pre-mortem/${BRANCH_SLUG}.marker"
527
+ if [ -f "$MARKER" ]; then
528
+ echo "Pre-mortem ledger — reading top-5 risks:"
529
+ cat "$MARKER"
530
+ fi
531
+ ```
532
+
533
+ <!-- /FKGL-EXEMPT -->
534
+
535
+ When the marker is absent, `/build` emits a one-line note that no pre-mortem fired for this branch and proceeds. `/pre-mortem` is mandatory on always-fire domains per its SKILL body; Phase 3 does not re-enforce that gate.
536
+
486
537
  Walk step enumeration; for each, plan what would happen in live mode:
487
538
 
488
539
  ```
@@ -534,6 +585,8 @@ TODO: live execution — operator-supervised first run only.
534
585
  - Operator-tap moments: 1 (prod deploy approval via GitHub Environment)
535
586
  ```
536
587
 
588
+ <!-- FKGL-EXEMPT: Phase 4 stub — technical dispatch TODO; OOAD artifact names + branch slug template resist short SVO rewrites. Per bassclef-upstream#2175 Batch A — slug canon matches Phase 3. -->
589
+
537
590
  ### Phase 4 — BuilderDispatcher (LIVE — STUBBED)
538
591
 
539
592
  **Amendment per #1171 — Builder inputs name OOAD artifacts.**
@@ -545,7 +598,7 @@ TODO: live execution — operator-supervised first run only. Would:
545
598
  dispatch Builder against step <slug> with context from spec section
546
599
  <X> + decomp matrix entry <Y> + paired OOAD artifacts (decomposition
547
600
  + interaction-design + use-case per goal doc references). Branch:
548
- feature/<goal-slug>-stack-<N>.
601
+ feature/<goal-slug>-stack-<N>-<wu-slug>.
549
602
  ```
550
603
 
551
604
  The string `TODO: live execution` is grep-able for audit. Live mode
@@ -556,6 +609,10 @@ in the goal doc `references` array — decomposition (grep for
556
609
  `type: interaction-design`), use-case (grep for `type: use-case`).
557
610
  Per #1171 — every OOAD artifact consulted for any engineering change.
558
611
 
612
+ <!-- /FKGL-EXEMPT -->
613
+
614
+ <!-- FKGL-EXEMPT: Phase 5 stub + Phase 5a Reviewer dispatch — technical TODO blocks for live-mode scaffolding. F10 cure per bassclef-upstream#2175 Batch D. -->
615
+
559
616
  ### Phase 5 — VerifyOrchestrator (LIVE — STUBBED)
560
617
 
561
618
  Tonight emits literal:
@@ -566,6 +623,24 @@ Match change type (UI smoke / unit / integration) and BLOCK on
566
623
  failure with line-numbered output.
567
624
  ```
568
625
 
626
+ ### Phase 5a — Reviewer dispatch (per bassclef-upstream#2175 Batch D — F10)
627
+
628
+ After `/verify` passes, Phase 5 adds a Reviewer dispatch step. Running `/verify` alone catches test failures. The Reviewer agent (`.claude/agents/reviewer.md`) catches spec-acceptance drift, missing-test patterns, ADR deviations, and friction the Builder did not surface. One review step per PR before merge.
629
+
630
+ Tonight emits literal:
631
+
632
+ ```
633
+ TODO: live execution — would dispatch Reviewer agent against branch <name>.
634
+ Reviewer reads: spec acceptance criteria, decomposition matrix entry, PR
635
+ body friction report. Reviewer writes: state/markers/reviewer/<branch>.md
636
+ with PASS / FAIL / NOTE findings. BLOCK merge on FAIL; NOTE findings file
637
+ as follow-on tickets.
638
+ ```
639
+
640
+ The Reviewer dispatch is MANDATORY per `.claude/rules/reviewer-dispatch.md` for any session shipping source code. Phase 5a is the Phase-level wiring. Live mode consumes the Reviewer marker before Phase 6 opens the PR.
641
+
642
+ <!-- /FKGL-EXEMPT -->
643
+
569
644
  ### Phase 5.5 — Amendment B2: /pattern-review trigger reads decomposition (per #1150 + #1167)
570
645
 
571
646
  When the goal references a decomposition doc AND that doc contains `@pattern` references, dispatch `/pattern-review` with the decomposition path as input. Skip when the decomposition names no `@pattern`. Contract per `.claude/rules/pattern-annotation.md` L57-62.
@@ -151,6 +151,32 @@ Individual items: ...
151
151
  - #127 — title — no path to advance the goal — CLOSE
152
152
  ```
153
153
 
154
+ ### Step 4b — Emit the hard stack rank
155
+
156
+ After the per-quadrant tables, emit one final table. Columns fixed:
157
+
158
+ 1. **Rank** — 1 to N, cross-repo, top-down
159
+ 2. **Ticket** — `<repo>#<number>` form
160
+ 3. **Repo** — the owning repo. Bold the repo name when work ships somewhere other than the filed repo.
161
+ 4. **Why this rank** — one plain-English sentence. Grade-8 target. Cite the Depen. tie or class fit.
162
+
163
+ Row order:
164
+
165
+ - Q1 first, then Q2, then Q3, then Q4
166
+ - Within quadrant, sort by Depen. first (independents at top), then class, then ticket number
167
+ - Flag cross-repo work in the Repo column
168
+
169
+ Example row shape:
170
+
171
+ ```
172
+ | Rank | Ticket | Repo | Why this rank |
173
+ |------|--------|-------------------|--------------------------------------|
174
+ | 1 | #2008 | **bassclef-web** | Q1 lead. Live Sam bounce. |
175
+ | 2 | #2011 | **bassclef-web** | Q1 rider. Same PR as #2008. |
176
+ ```
177
+
178
+ Per bassclef-upstream#2096 amendment 3.
179
+
154
180
  ### Step 5 — Optional follow-on
155
181
 
156
182
  If Q3 surfaces 5+ items in the same shape, the skill offers to draft one `/promote` ticket. That ticket proposes automating the class. You accept, edit, or skip.
@@ -264,6 +264,8 @@ fi
264
264
 
265
265
  **Local mode (`--local`) — bassclef#923 A2:**
266
266
 
267
+ **Issue-per-branch exemption (per bassclef-upstream#2175 Batch A — F2).** `/launch --local` creates adopter prototype branches that are exempt from the `.claude/rules/branching.md` "every branch ties to a GitHub Issue" rule. Local prototype mode exists for adopters who do not yet have a GitHub repo. No issue is required for `--local` branches. All other `/launch` modes (full, scratch, operator-mode) still require an issue.
268
+
267
269
  `/launch --local` prints a banner first. The banner shows which picker is active:
268
270
 
269
271
  ```
@@ -546,6 +548,43 @@ The gallery is THE cameo-review surface. Luminary perspectives
546
548
  agent runtime per roadmap Stage 3) consume the gallery + spec + decomp
547
549
  to produce reviews per perspective. Reviews surface in PR body.
548
550
 
551
+ ### Phase 10a — Stack-choice ask (per bassclef-upstream#2175 Batch D — F13)
552
+
553
+ <!-- FKGL-EXEMPT: Phase 10a — technical ask step with config-key contract. Short SVO loses the stack-key list. Fires before Phase 11 /ux-migration so the migration plan picks the right adapter. -->
554
+
555
+ Before Phase 11 runs, ask the operator which frontend stack the migration targets. Today `/launch` silently assumes Next.js — nine SKILL bodies lean on Next.js examples. Non-React adopters hit the silent pick at Phase 11 and must rework. The ask fires once per `/launch` dispatch; the answer writes to `substrate.config.md` key `frontend_stack` for Phase 11 to read.
556
+
557
+ Read `substrate.config.md` for `frontend_stack`. If set, skip the ask. If unset, ask:
558
+
559
+ ```
560
+ Which stack is this app?
561
+
562
+ a. Next.js — React + App Router (bassclef default; nine SKILL bodies lean on Next.js examples)
563
+ b. Vite + React — React + Vite dev server
564
+ c. Remix — React + Remix
565
+ d. SvelteKit — Svelte + Kit
566
+ e. SolidStart — Solid + Start
567
+ f. none — static HTML + CSS, no framework
568
+ g. other — operator names it
569
+
570
+ [a-g]
571
+ ```
572
+
573
+ Write the answer to `substrate.config.md` `frontend_stack`:
574
+ - `a` → `frontend_stack: nextjs`
575
+ - `b` → `frontend_stack: vite-react`
576
+ - `c` → `frontend_stack: remix`
577
+ - `d` → `frontend_stack: sveltekit`
578
+ - `e` → `frontend_stack: solidstart`
579
+ - `f` → `frontend_stack: none`
580
+ - `g` → `frontend_stack: <operator-named>`
581
+
582
+ Default on Enter → `a` (Next.js) to preserve the historical silent pick. Operator can edit `substrate.config.md` directly at any time.
583
+
584
+ Phase 11 reads `frontend_stack` and picks the matching migration adapter. When unset at Phase 11 fire, Phase 11 BLOCKs with a one-line note citing this ask.
585
+
586
+ <!-- /FKGL-EXEMPT -->
587
+
549
588
  ### Phase 11 — `/ux-migration` (chosen variant → production migration plan)
550
589
 
551
590
  ```bash
@@ -699,8 +738,8 @@ Two templates — adopter-mode and operator-mode. Default is adopter when `--loc
699
738
  After Phase 12-alt (`local-serve.sh` running) + Phase 13 (preview-state.yml on disk), emit this template directly to the adopter — no YAML edits, no frontmatter flip, no command-soup:
700
739
 
701
740
  ```
702
- I made you N directions for your family recipe wiki. Walk through them on
703
- your phone or laptop at <LOCAL_URL> and tell me which one feels right.
741
+ I made you N directions for your family recipe wiki. Walk through them
742
+ on this computer at <LOCAL_URL> and tell me which one feels right.
704
743
 
705
744
  When you've picked, just tell me — "I like variant 2" or "#3 looks
706
745
  great".
@@ -740,8 +779,12 @@ None of them feel right? Tell me what's off and I'll generate new
740
779
  directions for you. (See /launch --refine, bassclef#942.)
741
780
  ```
742
781
 
782
+ <!-- FKGL-EXEMPT: substitution instructions name three distinct placeholders; short SVO loses the mapping precision -->
743
783
  Substitute `<LOCAL_URL>` with the URL returned by Phase 12-alt. Substitute the actual variant count (`I made you N directions...`). Substitute the actual app description (`your family recipe wiki` etc.) from the InputArtifact's `intent` field.
744
784
 
785
+ **Phone or iPad reach — opt-in with `--lan`.** The default URL binds `127.0.0.1`. Only this computer can open it. The adopter may want the gallery on a phone or iPad on the same Wi-Fi. Re-run Phase 12-alt with `--lan`: `bash scripts/launch/local-serve.sh --lan <gallery-dir>`. The script binds `0.0.0.0` and prints a one-line warning. Any device on the Wi-Fi can then reach the URL. Swap the handoff line to "on your phone, laptop, or iPad at <LOCAL_URL>" when `--lan` fires.
786
+ <!-- /FKGL-EXEMPT -->
787
+
745
788
  The adopter-mode template **surfaces** (per bassclef#969):
746
789
  - The formal artifacts the chain will produce post-pick, in plain English (use case, user stories, IA, interaction design, responsibility map, build spec, migration plan)
747
790
  - The chain-ordering rationale (variants are cheap shared data; formal artifacts are expensive per-direction; cheap first → commit → expensive once)
@@ -356,7 +356,82 @@ this divider.
356
356
  Run each phase in order. Operator confirms before destructive or
357
357
  cross-repo actions. Each phase is independently re-runnable.
358
358
 
359
- #### Phase 0 — Refuse to run from main
359
+ #### Phase 0 — Preflight checks (OAuth, git identity, branch)
360
+
361
+ Phase 0 bundles three cheap preflight checks before any Phase 1 work runs. The checks fire together so adopter sees all gaps at once, not one at a time. Per bassclef-upstream#2242 preflight-first SKILL design + folds C1 + C4 from the 2026-10-14 pre-mortem.
362
+
363
+ ##### 0.1 Claude OAuth probe (per bassclef-upstream#2233)
364
+
365
+ Cold adopters have hit `/onboard-repo` with expired OAuth tokens. The skill wastes an orientation turn, then returns `Login expired · Please run /login`. The adopter re-fires the skill after login. Preflight the probe so the login prompt lands before any phase work.
366
+
367
+ Run the cheap-hello pattern (per memory `feedback_oauth_verify_via_cheap_hello`):
368
+
369
+ ```bash
370
+ if ! command -v claude >/dev/null 2>&1; then
371
+ cat >&2 <<'EOF'
372
+
373
+ 🛑 /onboard-repo preflight — Claude CLI not found on PATH.
374
+
375
+ Install Claude Code first. Then re-fire /onboard-repo.
376
+
377
+ EOF
378
+ exit 1
379
+ fi
380
+
381
+ <!-- FKGL-EXEMPT: shell command with long path reads as a single dense "sentence" to the grader; backtick-safe here per bassclef-upstream#2255 -->
382
+ if ! bash "$CLAUDE_PROJECT_DIR/.claude/skills/onboard-repo/claude-oauth-probe.sh" 10; then
383
+ cat >&2 <<'EOF'
384
+
385
+ 🛑 /onboard-repo preflight — Claude OAuth check failed.
386
+
387
+ Run `/login` first. Then re-fire `/onboard-repo`.
388
+
389
+ EOF
390
+ exit 1
391
+ fi
392
+ <!-- /FKGL-EXEMPT -->
393
+ ```
394
+
395
+ Two checks: binary on PATH, then OAuth probe. Cost: ~2s on green path. The probe is portable. Closes bassclef-upstream#2255.
396
+
397
+ ##### 0.2 Git identity preflight (per bassclef-upstream#2232)
398
+
399
+ `/onboard-repo` runs commit paths in Phase 2. Git fails with `Author identity unknown` when `user.name` or `user.email` is unset. The adopter sees the gap mid-flow and has to answer a 3-option prompt. Preflight the check so the question lands here, not later.
400
+
401
+ ```bash
402
+ GIT_NAME=$(git config --get user.name 2>/dev/null || echo "")
403
+ GIT_EMAIL=$(git config --get user.email 2>/dev/null || echo "")
404
+
405
+ if [ -z "$GIT_NAME" ] || [ -z "$GIT_EMAIL" ]; then
406
+ cat >&2 <<'EOF'
407
+
408
+ 🛑 /onboard-repo preflight — git identity not set.
409
+
410
+ Pick one option (then re-fire /onboard-repo):
411
+
412
+ 1. GitHub no-reply address (private, derived from `gh api user`):
413
+ (requires `gh auth login` first — see Prerequisites)
414
+ GH_USER=$(gh api user --jq .login)
415
+ GH_ID=$(gh api user --jq .id)
416
+ git config --global user.name "$GH_USER"
417
+ git config --global user.email "${GH_ID}+${GH_USER}@users.noreply.github.com"
418
+
419
+ 2. Your own name + email:
420
+ git config --global user.name "Your Name"
421
+ git config --global user.email "you@example.com"
422
+
423
+ 3. Fixture smoke (keep repo-local identity for test runs):
424
+ git config user.name "fixture"
425
+ git config user.email "fixture@localhost"
426
+
427
+ EOF
428
+ exit 1
429
+ fi
430
+ ```
431
+
432
+ Three options cover the common paths. GitHub no-reply is private and matches the pattern bassclef uses for its own commits.
433
+
434
+ ##### 0.3 Refuse to run from main
360
435
 
361
436
  Substrate changes go through PRs, same as feature work. If `/onboard-repo`
362
437
  runs from `main`, every scaffold (`.claude/`, `CLAUDE.md`,
@@ -427,6 +502,59 @@ operator-decision items.
427
502
 
428
503
  Idempotent API calls against the target repo via `gh`.
429
504
 
505
+ ##### 1.0a Repo-name collision helper (per bassclef-upstream#2236)
506
+
507
+ When an agent or adopter runs `gh repo create` on behalf of `/onboard-repo` and the name is taken, iterate the suffix one-by-one is slow. A cold adopter on 2026-10-13 hit three round-trips before landing on `-4`. Enumerate the taken set in ONE call. Propose the lowest free name up front.
508
+
509
+ Use this helper whenever the flow creates a new repo:
510
+
511
+ ```bash
512
+ basename="bassclef-smoke-test" # or whatever the adopter picked
513
+ owner=$(gh api user --jq .login)
514
+
515
+ # Pull every repo under $owner starting with $basename in ONE call
516
+ taken=$(gh repo list "$owner" --json name --jq "[.[].name | select(startswith(\"$basename\"))] | sort" 2>/dev/null)
517
+
518
+ # Find lowest free suffix
519
+ suffix=""
520
+ for i in '' $(seq 2 100); do
521
+ candidate="${basename}$([ -n "$i" ] && echo "-$i")"
522
+ if ! jq -e --arg c "$candidate" '.[] | select(. == $c)' <<<"$taken" >/dev/null 2>&1; then
523
+ suffix="$i"
524
+ break
525
+ fi
526
+ done
527
+
528
+ proposed="${basename}$([ -n "$suffix" ] && echo "-$suffix")"
529
+ echo "Taken under $owner matching ${basename}*: $taken"
530
+ echo "Proposed free name: $proposed"
531
+ echo "Accept '$proposed'? Or type your own:"
532
+ ```
533
+
534
+ One `gh repo list` call enumerates every conflict. One prompt proposes the next free name. Adopter accepts or renames in one turn.
535
+
536
+ ##### 1.0 Write the temperance marker up-front (per bassclef-upstream#2237)
537
+
538
+ `/onboard-repo` is the scope decision. Running it means the operator picked this plan. The `pre-build-gate.sh` BLOCK on first Edit is a false positive. The scope landed at skill dispatch, not mid-skill.
539
+
540
+ Write the marker now, before any Edit tool call fires:
541
+
542
+ ```bash
543
+ mkdir -p state/markers/temperance
544
+ BRANCH=$(git branch --show-current)
545
+ cat > "state/markers/temperance/${BRANCH}.marker" <<EOF
546
+ # Temperance marker — /onboard-repo
547
+ - date: $(date -u +%Y-%m-%d)
548
+ - branch: ${BRANCH}
549
+ - scope-decision: scaffold bassclef setup via /onboard-repo
550
+ - right-thing: adopter needs .claude/ + .gitignore + .bassclef/ wiring
551
+ - right-way: idempotent API calls + file writes contained to adopter repo
552
+ - drift-trigger: operator asks for scope outside the standard bootstrap set
553
+ EOF
554
+ ```
555
+
556
+ The marker satisfies `pre-build-gate.sh` at the right scope boundary. Phase 2's first Edit (settings.json, bassclef-configs.jsonc, .gitignore) proceeds clean. No mid-flow BLOCK banner.
557
+
430
558
  ##### 1.1 Create the 11 standard labels
431
559
 
432
560
  ```bash
@@ -462,31 +590,33 @@ immediately without waiting for a sync cycle.
462
590
 
463
591
  ##### 1.2 Protect main from force-push and deletion
464
592
 
465
- **Heads up — GitHub free-tier adopters on PRIVATE repos:** the API call
466
- below returns HTTP 403 ("Upgrade to GitHub Pro or make this repository
467
- public") on free-tier accounts, because branch protection is a paid
468
- feature for private repos. Public repos get branch protection on every
469
- plan, free included. Pick a path below BEFORE running the call.
593
+ Branch protection on private repos is a paid GitHub feature. Free plan + private repo returns HTTP 403 on the API call. Public repos get branch protection on every plan.
470
594
 
471
- Three ways to clear this step:
472
-
473
- 1. **Make the repo public** — simplest; free plan includes branch
474
- protection on public repos. Good fit when the code is open source
475
- or ships to adopters anyway.
476
- 2. **Upgrade the GitHub plan** — Pro / Team / Enterprise includes
477
- branch protection on private repos.
478
- 3. **Skip the API call** — rely on local safety (no `git push --force`
479
- habits, PR review, pre-commit hooks). Mark the repo config so later
480
- phases know branch protection is operator-managed, not substrate-
481
- managed. See the fallback block below.
595
+ Preflight the plan + visibility pair. Skip the API call cleanly when free + private (per bassclef-upstream#2235). No adopter prompt, no 403 noise:
482
596
 
483
597
  ```bash
484
598
  DEFAULT_BRANCH=$(gh repo view "$REPO" --json defaultBranchRef -q '.defaultBranchRef.name')
599
+ PLAN=$(gh api /user --jq .plan.name 2>/dev/null || echo "unknown")
600
+ VISIBILITY=$(gh repo view "$REPO" --json visibility --jq .visibility 2>/dev/null || echo "UNKNOWN")
485
601
 
486
- # Pro / public-repo path — run the API call.
487
- gh api "repos/$REPO/branches/$DEFAULT_BRANCH/protection" \
488
- --method PUT \
489
- --input - <<EOF
602
+ if [ "$PLAN" = "free" ] && [ "$VISIBILITY" = "PRIVATE" ]; then
603
+ echo "Preflight: free plan + private repo. GitHub free does not offer branch protection on private repos. Skipping API call."
604
+ mkdir -p docs
605
+ cat >> docs/onboard-notes.md <<'NOTE'
606
+ ## Branch protection — operator-managed
607
+
608
+ GitHub free plan does not offer branch protection on private repos.
609
+ This repo relies on:
610
+ - Local discipline: no `git push --force` to main.
611
+ - PR review before merge.
612
+ - Pre-commit hooks shipped by bassclef.
613
+
614
+ Revisit if the repo moves to public, or the plan upgrades to Pro+.
615
+ NOTE
616
+ else
617
+ gh api "repos/$REPO/branches/$DEFAULT_BRANCH/protection" \
618
+ --method PUT \
619
+ --input - <<EOF
490
620
  {
491
621
  "required_status_checks": null,
492
622
  "enforce_admins": false,
@@ -496,6 +626,7 @@ gh api "repos/$REPO/branches/$DEFAULT_BRANCH/protection" \
496
626
  "allow_deletions": false
497
627
  }
498
628
  EOF
629
+ fi
499
630
  ```
500
631
 
501
632
  Minimum defense: no force-push or deletion of main. PR reviews NOT
@@ -503,25 +634,6 @@ required here — agent-created PRs (bassclef-sync) need to merge
503
634
  without human intervention for the mobile-sync story. Add stricter
504
635
  rules per-repo in `.github/branch-protection.yml` if the repo warrants.
505
636
 
506
- **Free-tier private-repo fallback (path 3 above).** If the API
507
- returned 403 and the operator picked "skip and manage locally":
508
-
509
- ```bash
510
- # Record the choice so later audits know main is not API-protected.
511
- mkdir -p .claude
512
- cat >> .claude/onboard-notes.md <<'NOTE'
513
- ## Branch protection — operator-managed
514
-
515
- GitHub free-tier does not include branch protection on private repos.
516
- This repo relies on:
517
- - Local discipline: no `git push --force` to main.
518
- - PR review before merge.
519
- - Pre-commit hooks shipped by bassclef.
520
-
521
- Revisit if the repo moves to public, or the plan upgrades to Pro+.
522
- NOTE
523
- ```
524
-
525
637
  The adopter can revisit this step after switching to public OR
526
638
  upgrading the plan.
527
639
 
@@ -550,6 +662,25 @@ production deploy target.
550
662
 
551
663
  **Working directory: target repo.** All file writes happen here.
552
664
 
665
+ ##### 2.0 Opt-in prompt pattern — lead with VALUE (per bassclef-upstream#2234)
666
+
667
+ Phase 2 fires four opt-in prompts: auto-update sync (2.1), auto-save cron (2.3.10), Concise output style (2.7), and deploy-host (3.5). The cold-adopter on 2026-10-13 wrote: "need to understand value of auto-save in the text". Current prompts render as 4-option tables with recommended labels but no VALUE row.
668
+
669
+ Every opt-in prompt the agent dispatches via `AskUserQuestion` MUST prepend one plain-English sentence on what the adopter gets. Keep the multi-option schema. Add value to the option body text.
670
+
671
+ **Pattern.** Each option description starts with the value sentence, then the mechanism:
672
+
673
+ - `description: "What this is for: <one-sentence benefit>. <one-sentence mechanism>."`
674
+
675
+ **Worked examples.** Agent composes these from the SKILL body text + the ticket semantics:
676
+
677
+ - **auto_sync** → "What this is for: your repo pulls bassclef skills + rules every session so you stay current. Mechanism: SessionStart hook fetches the latest tarball."
678
+ - **auto-save cron** → "What this is for: your work saves every 20 minutes so a crash or power loss costs minutes, not hours. Mechanism: launchd job (macOS) or Task Scheduler (Windows) commits uncommitted changes."
679
+ - **Concise output style** → "What this is for: Claude Code leads replies with the result, skips step-by-step narration, and skips the closing recap. Mechanism: adds `outputStyle: Concise` to your `~/.claude/settings.json`."
680
+ - **deploy-host** → "What this is for: your repo deploys to a live URL when main merges. Mechanism: AWS Amplify app + DNS + certificate via `/provision-deploy-host`."
681
+
682
+ The agent uses these as templates. One sentence of value. One sentence of mechanism. Then the standard options list.
683
+
553
684
  ##### 2.1 Install the auto-update script (off by default per bassclef#939)
554
685
 
555
686
  ```bash
@@ -1357,6 +1488,43 @@ EOF
1357
1488
  The sync hook will add its managed symlink block on next run; this
1358
1489
  seeds the file with everything else.
1359
1490
 
1491
+ ##### 2.7 Prompt to enable Concise output style (ADR-049 opt-in)
1492
+
1493
+ **Working directory: operator home.** Writes land in `~/.claude/`, not the target repo.
1494
+
1495
+ Anthropic ships four output styles. Concise leads with the result. It skips step-by-step narration. It skips closing recap. Three bassclef steering dimensions line up with Concise. Five bassclef dimensions stay in play at the project layer. They are grade target, jargon wordlist, appositive discipline, metric feedback, writing-craft discipline. Concise at operator scope cuts per-turn reminders. It covers three bassclef dimensions on its own.
1496
+
1497
+ Fire this phase only when `concise_prompt_should_fire` returns 0. That means no declined sentinel. It also means no outputStyle set to Concise yet.
1498
+
1499
+ ```bash
1500
+ HELPER="$CLAUDE_PROJECT_DIR/.claude/skills/onboard-repo/concise-prompt.sh"
1501
+ source "$HELPER"
1502
+ concise_prompt_should_fire || { echo "Concise prompt skipped (declined or already set)."; return 0 2>/dev/null || true; }
1503
+ ```
1504
+
1505
+ Prompt the adopter with a 2-line preamble first (per bassclef-upstream#2256):
1506
+
1507
+ > **Concise is Anthropic's shorter output style.** Agents lead with the result and skip step-by-step narration. Turn off anytime via `/output-style default` or edit `~/.claude/settings.json`. Docs: https://code.claude.com/docs/en/output-styles
1508
+ >
1509
+ > Enable Concise output style? (Y/n/skip)
1510
+ > - **Y** — write `"outputStyle": "Concise"` to `~/.claude/settings.json`. jq read-modify-write. Every pre-existing field survives.
1511
+ > - **n** — keep Default style. Do not prompt again.
1512
+ > - **skip** — do not change settings. Re-prompt on next `/onboard-repo` run.
1513
+
1514
+ Default is **n** per ADR-049 opt-in default off.
1515
+
1516
+ Apply the pick via the helper:
1517
+
1518
+ ```bash
1519
+ concise_prompt_apply "$CHOICE" # Y | n | skip
1520
+ ```
1521
+
1522
+ The helper writes atomically. On **Y** it uses `jq '. + {"outputStyle": "Concise"}'` plus `mktemp` plus `mv`. No partial write ever lands. On **n** it writes `~/.claude/state/concise-prompt-declined`. On **skip** it writes `~/.claude/state/concise-prompt-skipped` so the operator sees the prompt again next run.
1523
+
1524
+ Characterization test at `.claude/skills/onboard-repo/tests/concise-prompt.test.sh` pins all three paths plus field preservation (T1-T11; 11 tests).
1525
+
1526
+ Per ADR-049 opt-in default off. The prompt defaults to **n**. The adopter must type Y to opt in. The helper NEVER writes without an explicit choice.
1527
+
1360
1528
  #### Phase 3 — Create starter docs (CLAUDE.md, whereami)
1361
1529
 
1362
1530
  **Working directory: target repo.**
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ # claude-oauth-probe.sh — portable "is Claude signed in" probe.
5
+ #
6
+ # Replaces inline `timeout 10 claude -p "say hello"` in /onboard-repo
7
+ # Phase 0.1. macOS BSD userland does not include GNU `timeout(1)`; the
8
+ # inline call returned 127 and the `!` test inverted it, firing a false
9
+ # OAUTH-FAIL banner on every macOS cold adopter. Closes bassclef-upstream#2255.
10
+ #
11
+ # Usage:
12
+ # bash claude-oauth-probe.sh [seconds-limit]
13
+ #
14
+ # Default seconds-limit: 10.
15
+ #
16
+ # Detection order:
17
+ # 1. `timeout` (GNU coreutils; Linux + CI runners)
18
+ # 2. `gtimeout` (brew coreutils on macOS)
19
+ # 3. Bash background + kill shim (portable; needs nothing external)
20
+ #
21
+ # Exit codes:
22
+ # 0 — claude -p "say hello" succeeded within the limit
23
+ # 1+ — claude failed OR hung past the limit (killed)
24
+ #
25
+ # Per @luminary michael-nygard — fail-soft on `kill` errors; killer PID handled.
26
+ # Per @luminary alan-cooper — adopter does not need to know timeout(1) is split.
27
+
28
+ set +e # never crash the caller
29
+
30
+ SECONDS_LIMIT="${1:-10}"
31
+
32
+ # Branch 1 — GNU timeout
33
+ if command -v timeout >/dev/null 2>&1; then
34
+ timeout "$SECONDS_LIMIT" claude -p "say hello" >/dev/null 2>&1
35
+ exit $?
36
+ fi
37
+
38
+ # Branch 2 — brew gtimeout
39
+ if command -v gtimeout >/dev/null 2>&1; then
40
+ gtimeout "$SECONDS_LIMIT" claude -p "say hello" >/dev/null 2>&1
41
+ exit $?
42
+ fi
43
+
44
+ # Branch 3 — bash background + kill shim (portable; no external bin)
45
+ claude -p "say hello" >/dev/null 2>&1 &
46
+ pid=$!
47
+
48
+ # Killer process — sleep then kill the probe
49
+ ( sleep "$SECONDS_LIMIT" && kill -9 "$pid" 2>/dev/null ) &
50
+ killer=$!
51
+
52
+ # Wait for probe to finish (either on its own OR killed)
53
+ wait "$pid" 2>/dev/null
54
+ rc=$?
55
+
56
+ # Clean up killer regardless of outcome
57
+ kill -9 "$killer" 2>/dev/null
58
+ wait "$killer" 2>/dev/null
59
+
60
+ exit "$rc"