@thebassclef/lite 1.9.9 → 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 CHANGED
@@ -67,7 +67,7 @@ your browser.
67
67
 
68
68
  ## Current release
69
69
 
70
- <!-- version-start -->1.9.9<!-- version-end -->
70
+ <!-- version-start -->1.9.10<!-- version-end -->
71
71
 
72
72
  See [CHANGELOG.md](CHANGELOG.md) for the full changelog. Adopter-facing
73
73
  release notes live at [docs.bassclef.dev/docs/whats-new/releases](https://docs.bassclef.dev/docs/whats-new/releases).
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const version = "1.9.9";
3
+ const version = "1.9.10";
4
4
  exports.version = version;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version: "1.9.9";
1
+ export declare const version: "1.9.10";
2
2
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "1.9.9";
1
+ const version = "1.9.10";
2
2
  export {
3
3
  version
4
4
  };
@@ -201,6 +201,37 @@ if ! { declare -F skip_var_set >/dev/null && skip_var_set "SKIP_LEAD_LENS_SIGNOF
201
201
  -e '^\.github/workflows/bassclef-.*\.yml$' \
202
202
  -e '^scripts/[^/]+\.sh$' \
203
203
  || true)
204
+ # Install-written-paths filter per bassclef-upstream#2036 Finding #8 (Kunal).
205
+ # Rule: .claude/rules/install-written-paths-mediation.md
206
+ # Any staged substrate path registered as install-written AND unchanged since
207
+ # install is dropped from the author-edit set. Lead-lens gate fires only on
208
+ # author-edited substrate paths.
209
+ if [ -n "$STAGED_SUBSTRATE_LL" ]; then
210
+ IWP_LIB=""
211
+ if [ -f "lib/install-written-paths.sh" ]; then
212
+ IWP_LIB="lib/install-written-paths.sh"
213
+ elif [ -f "$BASSCLEF_DIR/lib/install-written-paths.sh" ]; then
214
+ IWP_LIB="$BASSCLEF_DIR/lib/install-written-paths.sh"
215
+ fi
216
+ if [ -n "$IWP_LIB" ]; then
217
+ # shellcheck source=/dev/null
218
+ . "$IWP_LIB" 2>/dev/null || true
219
+ if declare -F is_install_written_path >/dev/null 2>&1; then
220
+ FILTERED_LL=""
221
+ while IFS= read -r _iwp_f; do
222
+ [ -z "$_iwp_f" ] && continue
223
+ if is_install_written_path "$_iwp_f" 2>/dev/null; then
224
+ trace_log "pre-commit" "lead-lens skip install-written path=$_iwp_f"
225
+ continue
226
+ fi
227
+ FILTERED_LL="${FILTERED_LL}${FILTERED_LL:+$'\n'}$_iwp_f"
228
+ done <<EOF
229
+ $STAGED_SUBSTRATE_LL
230
+ EOF
231
+ STAGED_SUBSTRATE_LL="$FILTERED_LL"
232
+ fi
233
+ fi
234
+ fi
204
235
  if [ -n "$STAGED_SUBSTRATE_LL" ]; then
205
236
  LL_BRANCH_SLUG=$(git branch --show-current 2>/dev/null | tr '/' '-')
206
237
  PREM_M="state/markers/pre-mortem/${LL_BRANCH_SLUG}.marker"
@@ -97,8 +97,36 @@ fi
97
97
  # Filter to '+' lines only so a commit that REMOVES a pre-existing leak
98
98
  # does not trip on the removed-content line. `+++` file headers stripped.
99
99
  # Per sunj-labs/bassclef-upstream#1575.
100
+ #
101
+ # Self-exclude per sunj-labs/bassclef-cli#305: the hook, its test, the
102
+ # rule, and the pattern-source file all carry pattern-looking strings
103
+ # as documentation. Scanning them blocks the first commit after a fresh
104
+ # bassclef init and teaches adopters to skip the check on day one.
105
+ # Non-self paths with the same patterns STILL block (Test 19).
106
+ SELF_EXCLUDE_PATHS=(
107
+ ".claude/hooks/pre-commit-identifier-leak-scrub.sh"
108
+ ".claude/hooks/tests/pre-commit-identifier-leak-scrub.test.sh"
109
+ ".claude/rules/identifier-leak-prevention.md"
110
+ "standards/identifier-leak-patterns.txt"
111
+ )
100
112
  if [ -z "$BLOCK_REASON" ]; then
101
- STAGED_DIFF=$(git diff --cached 2>/dev/null | grep '^+' | grep -v '^+++' || echo "")
113
+ STAGED_FILES=$(git diff --cached --name-only 2>/dev/null || echo "")
114
+ STAGED_DIFF=""
115
+ while IFS= read -r file; do
116
+ [ -z "$file" ] && continue
117
+ skip=0
118
+ for sp in "${SELF_EXCLUDE_PATHS[@]}"; do
119
+ if [ "$file" = "$sp" ]; then
120
+ skip=1
121
+ break
122
+ fi
123
+ done
124
+ [ "$skip" = "1" ] && continue
125
+ file_diff=$(git diff --cached -- "$file" 2>/dev/null | grep '^+' | grep -v '^+++' || echo "")
126
+ if [ -n "$file_diff" ]; then
127
+ STAGED_DIFF+="$file_diff"$'\n'
128
+ fi
129
+ done <<< "$STAGED_FILES"
102
130
  if [ -n "$STAGED_DIFF" ]; then
103
131
  MATCH=$(echo "$STAGED_DIFF" | grep -oE "$IDENTIFIER_PATTERNS" | head -1)
104
132
  if [ -n "$MATCH" ]; then
@@ -108,6 +136,29 @@ if [ -z "$BLOCK_REASON" ]; then
108
136
  fi
109
137
  fi
110
138
 
139
+ # Check 3 — git author identity (per sunj-labs/bassclef-cli#305)
140
+ # On a Mac without user.email set, git builds the author from username +
141
+ # hostname (e.g., `<user>@<host>.local`). The address ships in every commit.
142
+ # Scan GIT_AUTHOR_IDENT against the pattern set plus the `.local` suffix.
143
+ # Fail-soft when git is missing or the ident cannot be resolved.
144
+ if [ -z "$BLOCK_REASON" ] && command -v git >/dev/null 2>&1; then
145
+ AUTHOR_IDENT=$(git var GIT_AUTHOR_IDENT 2>/dev/null || true)
146
+ if [ -n "$AUTHOR_IDENT" ]; then
147
+ # Format: "Name <email> timestamp timezone"
148
+ AUTHOR_EMAIL=$(echo "$AUTHOR_IDENT" | grep -oE '<[^>]+>' | tr -d '<>')
149
+ if [ -n "$AUTHOR_EMAIL" ]; then
150
+ MATCH=$(echo "$AUTHOR_EMAIL" | grep -oE "$IDENTIFIER_PATTERNS" | head -1)
151
+ if [ -z "$MATCH" ] && echo "$AUTHOR_EMAIL" | grep -qE '\.local$'; then
152
+ MATCH="$AUTHOR_EMAIL"
153
+ fi
154
+ if [ -n "$MATCH" ]; then
155
+ BLOCK_REASON="commit author"
156
+ BLOCK_MATCH="$MATCH"
157
+ fi
158
+ fi
159
+ fi
160
+ fi
161
+
111
162
  # BLOCK if any pattern matched
112
163
  if [ -n "$BLOCK_REASON" ]; then
113
164
  cat >&2 <<EOF
@@ -128,7 +179,12 @@ Resolution paths:
128
179
  1. Rewrite the commit message or staged content to drop the identifier
129
180
  - Replace machine names with placeholders like <machine> or the branch
130
181
  - Replace absolute paths with relative paths or \$HOME references
131
- 2. Override (rare — for legitimate cases like DON'T-do-this documentation):
182
+ 2. If the match is in your commit author identity:
183
+ - Set a stable identity: git config --global user.email \\
184
+ <username>@users.noreply.github.com
185
+ - And: git config --global user.name "<Your Name>"
186
+ - Then retry the commit; the author will no longer include the host.
187
+ 3. Override (rare — for legitimate cases like DON'T-do-this documentation):
132
188
  SKIP_IDENTIFIER_LEAK_SCRUB=1 git commit ...
133
189
 
134
190
  See .claude/rules/identifier-leak-prevention.md for full discipline.
@@ -0,0 +1,111 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # install-class: dual
4
+ #
5
+ # Session-start fragment: rebuild install-written-paths manifest when missing/stale.
6
+ # Per bassclef-upstream#2036 Finding #8 (Kunal Slot 1 cure).
7
+ #
8
+ # Walks lite-manifest.json. Registers each shipped file as install-written
9
+ # via the lib. Runs when state/install-written-paths.json is missing OR
10
+ # when any file in lite-manifest is newer than the manifest.
11
+ #
12
+ # Fail-soft per Nygard — logs advisory and continues on error.
13
+ # Override: SKIP_INSTALL_WRITTEN_PATHS_REBUILD=1
14
+
15
+ # Env-reach safe guard per bassclef-upstream#2036 Finding #4.
16
+ # Fragment is sourced by session-reflection.sh; use return (not exit) so parent survives.
17
+ # shellcheck disable=SC2317
18
+ if [ -n "${SKIP_INSTALL_WRITTEN_PATHS_REBUILD:-}" ]; then
19
+ return 0 2>/dev/null
20
+ fi
21
+
22
+ _iwp_fragment_log() {
23
+ if command -v trace_log >/dev/null 2>&1; then
24
+ trace_log "install-written-paths-rebuild" "$*"
25
+ fi
26
+ }
27
+
28
+ _iwp_fragment_main() {
29
+ local project_root="${CLAUDE_PROJECT_DIR:-$(pwd)}"
30
+ local lite_manifest="$project_root/lite-manifest.json"
31
+ local write_manifest="$project_root/state/install-written-paths.json"
32
+ local lib="$project_root/lib/install-written-paths.sh"
33
+
34
+ # Fallback to BASSCLEF_DIR when CLAUDE_PROJECT_DIR doesn't carry bassclef
35
+ if [ ! -f "$lite_manifest" ] && [ -n "${BASSCLEF_DIR:-}" ]; then
36
+ lite_manifest="$BASSCLEF_DIR/lite-manifest.json"
37
+ lib="$BASSCLEF_DIR/lib/install-written-paths.sh"
38
+ fi
39
+
40
+ # Skip when inputs missing (fail-soft)
41
+ if [ ! -f "$lib" ]; then
42
+ _iwp_fragment_log "skip reason=no-lib"
43
+ return 0
44
+ fi
45
+
46
+ if [ ! -f "$lite_manifest" ]; then
47
+ _iwp_fragment_log "skip reason=no-lite-manifest"
48
+ return 0
49
+ fi
50
+
51
+ if ! command -v jq >/dev/null 2>&1; then
52
+ _iwp_fragment_log "skip reason=no-jq"
53
+ return 0
54
+ fi
55
+
56
+ # Decide whether to rebuild: missing OR older than lite-manifest
57
+ local rebuild=0
58
+ if [ ! -f "$write_manifest" ]; then
59
+ rebuild=1
60
+ elif [ "$lite_manifest" -nt "$write_manifest" ]; then
61
+ rebuild=1
62
+ fi
63
+
64
+ if [ "$rebuild" -eq 0 ]; then
65
+ _iwp_fragment_log "skip reason=manifest-fresh"
66
+ return 0
67
+ fi
68
+
69
+ # Source lib — subshell so caller scope stays clean
70
+ (
71
+ # shellcheck source=/dev/null
72
+ . "$lib" 2>/dev/null || exit 0
73
+
74
+ if ! declare -F install_written_paths_register >/dev/null 2>&1; then
75
+ _iwp_fragment_log "skip reason=lib-missing-verb"
76
+ exit 0
77
+ fi
78
+
79
+ # Reset manifest to empty, then walk lite-manifest entries
80
+ rm -f "$write_manifest" 2>/dev/null || true
81
+ install_written_paths_rebuild 2>/dev/null || true
82
+
83
+ local registered=0
84
+ local skipped=0
85
+
86
+ # Each lite-manifest entry has .path field — register when file exists on disk
87
+ while IFS= read -r path; do
88
+ [ -z "$path" ] && continue
89
+ local full
90
+ case "$path" in
91
+ /*) full="$path" ;;
92
+ *) full="$project_root/$path" ;;
93
+ esac
94
+ if [ -f "$full" ]; then
95
+ if install_written_paths_register "$path" "bassclef-sync-template" 2>/dev/null; then
96
+ registered=$((registered + 1))
97
+ else
98
+ skipped=$((skipped + 1))
99
+ fi
100
+ else
101
+ skipped=$((skipped + 1))
102
+ fi
103
+ done < <(jq -r '.entries[]?.path // empty' "$lite_manifest" 2>/dev/null)
104
+
105
+ _iwp_fragment_log "rebuild complete registered=$registered skipped=$skipped"
106
+ ) || true
107
+
108
+ return 0
109
+ }
110
+
111
+ _iwp_fragment_main
@@ -61,6 +61,14 @@ SCHEMA_DIR="${PROJECT_DIR}/standards/state-spine/schemas"
61
61
 
62
62
  # If schema dir doesn't exist, this repo doesn't have spine v0 yet — pass through
63
63
  if [ ! -d "$SCHEMA_DIR" ]; then
64
+ # Per cli#316 — honest skip. Previously exited 0 silently; adopters +
65
+ # agents on lite couldn't tell "validated" from "not checked". /launch
66
+ # and /objectory-decompose SKILLs promise this hook will BLOCK bad
67
+ # frontmatter. On lite, where schemas don't ship, this message makes
68
+ # the skip visible so the promise doesn't lie.
69
+ if [ "${STATE_VALIDATE_QUIET:-0}" != "1" ]; then
70
+ echo "state-validate: schemas not available (standards/state-spine/schemas missing at $SCHEMA_DIR); validation skipped at this tier — set STATE_VALIDATE_QUIET=1 to silence" >&2
71
+ fi
64
72
  exit 0
65
73
  fi
66
74
 
@@ -0,0 +1,74 @@
1
+ ---
2
+ tier: lite
3
+ ---
4
+
5
+ # Install-Written-Paths — single accessor mediation
6
+
7
+ Discipline hooks that would fire on files written by install writers (`bassclef init`, `bassclef-sync.sh`, `bassclef-hook-connect`, `schedule-auto-save`, template scripts) MUST consult `lib/install-written-paths.sh` via the single accessor `is_install_written_path <path>` before firing on each staged file.
8
+
9
+ The lib + manifest ship per bassclef-upstream#2036 Finding #8 (Kunal cold-adopter report, 2026-10-02). Structural cure for Class A (install-leak surfaces).
10
+
11
+ ## When this rule fires
12
+
13
+ Any discipline hook that currently fires on file content without discriminating install-written from author-edited. Candidate hooks:
14
+
15
+ - `.claude/hooks/pre-commit-gate.sh` — lead-lens 3-marker gate section (consumer wired per this cure's V1 PR)
16
+ - `.claude/hooks/pre-commit-identifier-leak-scrub.sh` — identifier-leak sister per Kunal #1 (future adopter per R2 risk ledger)
17
+ - Any new discipline hook authored under the loop discipline (`.claude/rules/loop-discipline.md`) that assumes author-edit
18
+
19
+ ## What the rule requires
20
+
21
+ Before firing discipline on a staged path, each discipline hook:
22
+
23
+ 1. Sources `lib/install-written-paths.sh`.
24
+ 2. Calls `is_install_written_path "$staged_path"`.
25
+ 3. When return is 0 (true — path is install-written AND content hash matches), SKIP discipline for that path. Emit a trace line via `trace_log` for audit.
26
+ 4. When return is 1 (false — path not in manifest OR hash drifted), FIRE discipline as today.
27
+
28
+ When all staged paths return 0, the hook returns pass without discipline. When at least one returns 1, the hook fires discipline on the author-edited paths.
29
+
30
+ ## Anti-patterns
31
+
32
+ ### Reimplementing the discrimination
33
+
34
+ Hook authors who grep `.bassclef-source.json` or hard-code install path lists duplicate the lib. Future rename events or new install writers break them silently.
35
+
36
+ INSTEAD: source the lib. Compose with the shared discipline.
37
+
38
+ ### Skipping the lib for sister hooks
39
+
40
+ Discipline hooks that fire on install-written content but don't consult the lib produce Kunal-pattern false fires on cold adopters.
41
+
42
+ INSTEAD: add the consultation at the first opportunity. Per @luminary michael-feathers legacy-code retrofit — sister hooks consult the lib when next touched for other work.
43
+
44
+ ## Composes with
45
+
46
+ - `.claude/rules/bootstrap-pair-discipline.md` — this rule + lib + schema + hook consumption + session-start fragment ship paired
47
+ - `.claude/rules/state-schema-validation.md` — schema at `standards/state-spine/schemas/install-written-paths.schema.json` validates on write
48
+ - `.claude/rules/we-dont-break-adopters.md` — additive; existing SKIP_* paths preserved
49
+ - `.claude/rules/sibling-smoke-after-substrate-change.md` — post-merge sibling smoke confirms Kunal #8 cured end-to-end
50
+ - @luminary linus-torvalds — adopter contract (manifest IS the adopter-visible surface this cure protects)
51
+ - @luminary michael-feathers — characterization tests (lib tests pin actual install-vs-author discrimination)
52
+ - @luminary john-ousterhout — deep module (narrow 5-verb interface hides schema + hash + I/O complexity)
53
+ - @luminary michael-nygard — fail-soft (missing manifest, malformed JSON, missing hash tool → return "not install-written" + advisory to stderr)
54
+
55
+ ## Override path
56
+
57
+ `SKIP_INSTALL_WRITTEN_PATHS=1` — the lib's `is_install_written_path` returns 1 (false) unconditionally when this env var is set. Hooks fire discipline normally. Logged via `trace_log`.
58
+
59
+ INSTEAD of overriding routinely: trust the lib. Overrides are for one-shot debugging where the manifest-based path has misclassified.
60
+
61
+ ## V1 scope — this cure's PR
62
+
63
+ - `lib/install-written-paths.sh` + Tier 0 tests
64
+ - `standards/state-spine/schemas/install-written-paths.schema.json` + state-spine.md Shape C entry
65
+ - `.claude/hooks/pre-commit-gate.sh` — lead-lens 3-marker gate section amended to consult the lib
66
+ - `presence/install/bassclef-hook-connect.sh` — registers each path on write
67
+ - `presence/install/bassclef-sync.template.sh` — registers each path on sync
68
+ - `.claude/hooks/session-reflection.d/83-install-written-paths-rebuild.sh` — regenerates manifest when missing at session-start
69
+
70
+ ## Deferred to follow-on
71
+
72
+ - `bassclef init` npm-side registration (lands in bassclef-cli; sources the lib via package symlink)
73
+ - `pre-commit-identifier-leak-scrub.sh` consultation (sister hook adoption per Feathers legacy-code retrofit)
74
+ - Any other hook that assumes author-edit — audit at next /architect-review
@@ -15,15 +15,19 @@ requires:
15
15
 
16
16
  # Autonomous Development
17
17
 
18
- Entry point for autonomous and semi-autonomous development sessions.
19
- Invoke at any point in any repo that inherits bassclef.
18
+ <!-- FKGL-EXEMPT: cli#331 cure — orientation block names skill scope + tier split. Short SVO loses the tier contract. -->
19
+ Entry point for autonomous SDLC sessions. Use in any repo inheriting bassclef.
20
20
 
21
- This file is a thin dispatcher. Deep procedural detail lives in:
21
+ **Lite adopters:** this file is self-contained. The `## Lite procedure` block below has the minimum steps to run `/autonomous start`. No `strategy/` file is needed at lite tier.
22
22
 
23
- - `strategy/autonomous-dry-run.md` — read-only boot-sequence preview
24
- - `strategy/autonomous-boot-sequence.md` — `/autonomous start` scaffold +
25
- activate, including Step 0–9, Construction automation A–E, pre-build
26
- gates, branch stacking, context management, transition hierarchy
23
+ **Standard and ultra adopters:** deeper procedure lives in these standard-tier files:
24
+
25
+ - `strategy/autonomous-dry-run.md` — read-only boot preview
26
+ - `strategy/autonomous-boot-sequence.md` — full `/autonomous start` steps 0-9 + Construction automation A-E
27
+ - `strategy/agent-choreography.md` — multi-agent operational guide
28
+
29
+ Per cli#331 — closes the "lite skill points at files lite doesn't ship" gap.
30
+ <!-- /FKGL-EXEMPT -->
27
31
 
28
32
  ## Usage
29
33
 
@@ -263,6 +267,48 @@ silently proceed with defaults.
263
267
 
264
268
  ---
265
269
 
270
+ ## Lite procedure — self-contained minimum (cli#331)
271
+
272
+ <!-- FKGL-EXEMPT: cli#331 cure — procedure block for lite adopters. Short SVO loses step contract. -->
273
+ Lite adopters run `/autonomous start` with this block only. No `strategy/` file needed.
274
+
275
+ **Step 0 — validate args.** Accept `operator-gated` or `orchestrator-gated` for gating. Accept `sequential` or `parallel` for mode. Reject anything else with a helpful error.
276
+
277
+ **Step 1 — ask the operator two choices.** Gating (default: operator-gated). Mode (default: sequential). Use AskUserQuestion.
278
+
279
+ **Step 2 — check readiness.** Run through this checklist. Scaffold missing items as you go:
280
+ - `substrate.config.md` present → if missing, dispatch `/onboard-repo` first
281
+ - `docs/whereami.md` present → if missing, dispatch `/onboard-repo` first
282
+ - Current iteration goal doc at `docs/iteration-bets/<slug>.md` → if missing, ask operator for the goal; create a minimal doc
283
+ - Session lock free → if active and >30 min old, clear with announcement
284
+ - Chronicle fresh (last 24h) → if stale, note "will write at session-end"
285
+
286
+ **Step 3 — risk register (optional on lite).** `docs/risk-register.md` is optional at lite tier. Create one only when operator asks or when mode is orchestrator-gated. Skip the check otherwise.
287
+
288
+ **Step 4 — budget ceiling (optional on lite).** Ask the operator "how many turns are you OK spending on this session?" Default to "proceed until operator says stop" when operator skips. Record whatever they say in session memory.
289
+
290
+ **Step 5 — confirm scope.** Echo the goal, the two choices, and the ceiling (or "no ceiling"). Ask: "Ready to start?" Wait for yes.
291
+
292
+ **Step 6 — execute the SDLC chain.** Fire the agents in order per gating choice:
293
+ - **operator-gated:** pause between each agent for operator approval. Agents: Shaper → PM → Architect → Designer → Builder → Reviewer → Deployer → Closer. Operator picks which fire.
294
+ - **orchestrator-gated:** chain the agents without pausing. Each writes a handoff doc. Builder commits code. Reviewer runs `/verify`. PRs open for operator merge (or admin-merge within scope).
295
+
296
+ **Step 7 — handle Construction.** For code work:
297
+ - Parse scope into GitHub issues (one issue per discrete change)
298
+ - Write stack manifest at `docs/stacks/<slug>.md` if more than one branch
299
+ - Create branches with issue numbers in name (not placeholder — per bassclef#295)
300
+ - Dispatch Builder per branch; fire `/temperance` + `/diagnose` + `/verify` per branch
301
+ - Open PR per branch; squash merge after Reviewer PASS
302
+
303
+ **Step 8 — close the session.** Dispatch `/session-end` which handles chronicle write, whereami bump, journal entry.
304
+
305
+ Lite adopters at any point can escalate to operator-gated mode mid-session. Say "pause for me to review" and the next agent handoff waits.
306
+
307
+ **Deep procedure for standard+ adopters.** Standard+ and ultra adopters have the strategy/ files above. Those cover Construction automation A-E, pre-build gates, branch stacking, context management, transition hierarchy, and the full choreography rules. Read them for the complete design.
308
+ <!-- /FKGL-EXEMPT -->
309
+
310
+ ---
311
+
266
312
  ## Rules
267
313
 
268
314
  - Never start work without operator confirmation
@@ -255,7 +255,7 @@ FLOOR_PATTERN='src/lib/auth/|src/middleware\.ts|prisma/schema\.prisma|prisma/mig
255
255
  ACCEPTANCE_CONTENT=$(awk '
256
256
  /^```/{in_code = !in_code; next}
257
257
  in_code{next}
258
- /^### WU-/{in_wu=1; next}
258
+ /^### (Step|WU)-/{in_wu=1; next}
259
259
  /^## /{in_wu=0}
260
260
  in_wu && /^- \[ \]/ {print}
261
261
  ' "docs/specs/${SPEC_PATH}.md" "docs/decompositions/${DECOMP_PATH}.md" 2>/dev/null)
@@ -273,8 +273,8 @@ fi
273
273
  ```
274
274
 
275
275
  **Why this is precise (per bassclef#366):** the scan only looks at
276
- step acceptance criteria (lines starting `- [ ]` under `### WU-N`
277
- headings, outside code blocks) plus file-path-shaped references in
276
+ step acceptance criteria (lines starting `- [ ]` under `### Step-N`
277
+ or `### WU-N` headings, outside code blocks) plus file-path-shaped references in
278
278
  the goal's frontmatter. Prose paragraphs that DESCRIBE the floor (like
279
279
  this section) don't trigger refusal. Code blocks that document the
280
280
  floor don't trigger refusal. Only structured ACTIONS the agent would
@@ -590,7 +590,26 @@ already open. Operator merges inline.
590
590
 
591
591
  ### Phase 7 — ApprovalGateLink (LIVE — STUBBED)
592
592
 
593
- Tonight emits literal:
593
+ <!-- FKGL-EXEMPT: cli#332 cure — Phase 7 branches on hosting_platform value. Three code-shape outputs. Technical stub-shape dispatch pattern. -->
594
+
595
+ Phase 7 branches on `hosting_platform` read from `substrate.config.md`:
596
+
597
+ **When `hosting_platform: none`** (lite adopters, operator-only tools, local-first apps) — emit:
598
+
599
+ ```
600
+ TODO: live execution — built, not deployed. hosting_platform=none in
601
+ substrate.config.md. The code is reviewed and committed. Running it is
602
+ your choice.
603
+
604
+ To add hosting later:
605
+ - Edit substrate.config.md: set hosting_platform to one of
606
+ github-pages | netlify | cloudflare-pages | vercel | amplify
607
+ - Run /onboard-repo --with-deploy-host to wire the host
608
+ - Then /build Phase 7 will dispatch /deploy-prod via GitHub Environment
609
+ + required-reviewer rail per manual-prod-approval.md.
610
+ ```
611
+
612
+ **When `hosting_platform` is set to a known value** — emit:
594
613
 
595
614
  ```
596
615
  TODO: live execution — when all steps merged + CI green, would dispatch
@@ -599,6 +618,17 @@ required-reviewer rail per manual-prod-approval.md. Operator approves
599
618
  prod deploy via GitHub UI from phone. /build NEVER bypasses this rail.
600
619
  ```
601
620
 
621
+ **When `hosting_platform` is unset or unrecognized** — emit:
622
+
623
+ ```
624
+ TODO: live execution — hosting_platform not set in substrate.config.md.
625
+ Can't deploy without a target. Set hosting_platform to one of none |
626
+ github-pages | netlify | cloudflare-pages | vercel | amplify, then re-run.
627
+ ```
628
+
629
+ Per cli#332 — closes the "lite promises deployed app without a deploy path" honesty gap.
630
+ <!-- /FKGL-EXEMPT -->
631
+
602
632
  <!-- FKGL-EXEMPT: Phase 7.5 references paths + skip criteria per architect-review SKILL; technical dispatch stub matches sibling STUB pattern -->
603
633
  ### Phase 7.5 — /architect-review auto-dispatch (LIVE — STUBBED, per #1437 Step 2 wiring)
604
634
 
@@ -17,8 +17,18 @@ requires:
17
17
 
18
18
  # Deploy to Production
19
19
 
20
+ <!-- FKGL-EXEMPT: cli#332 cure — tier contract block names scope + override path. Short SVO loses host-override contract. -->
20
21
  Standard deployment procedure. Run after code is reviewed and ready to ship.
21
22
 
23
+ **Tier contract (per cli#332):**
24
+
25
+ - This skill ships at `tier: lite` as a generic 5-step template: test → build → push → CI → verify.
26
+ - The template body is host-agnostic. Host-specific commands (AWS, Vercel, Netlify, Fly, Railway, GitHub Pages) are not shipped here.
27
+ - When `substrate.config.md` sets `hosting_platform` to a known value, standard-tier adopters add a host-specific wrapper that calls this skill AND runs the host commands. Lite adopters without `hosting_platform` run the template steps + stop.
28
+ - `/build` Phase 7 dispatches this skill only when `hosting_platform` is set to a known value — see /build Phase 7 branching logic per cli#332.
29
+ - `manual-prod-approval.md` applies in all tiers: GitHub Environment `prod` with required reviewer MUST be configured before any prod deploy fires.
30
+ <!-- /FKGL-EXEMPT -->
31
+
22
32
  ## Procedure
23
33
 
24
34
  ### Step 1: Pre-flight
@@ -309,9 +309,35 @@ Per `standards/voice-input-pattern.md` + Phase V Hey-Alexa MVD goal (`docs/itera
309
309
  - Existing text-mode + brownfield-mode behavior unchanged when flag absent
310
310
  - Composes with `--brownfield` (rare; voice-driven brownfield refactor is plausible but not the MVD path)
311
311
 
312
+ ### Phase 0b — hosting_platform ask (per cli#332)
313
+
314
+ <!-- FKGL-EXEMPT: cli#332 cure — hosting_platform ask step. Short SVO loses the config-key contract. -->
315
+ Read `substrate.config.md` for the `hosting_platform` key. If unset or missing, ask the operator once:
316
+
317
+ ```
318
+ Where should this run?
319
+ a. local only (default — develop + preview on your machine)
320
+ b. a static host (GitHub Pages / Netlify / Cloudflare Pages / Vercel)
321
+ c. a server host (Amplify / Fly.io / Railway)
322
+ d. I'll deploy myself — just build the plan
323
+ ```
324
+
325
+ Write the answer to `substrate.config.md` `hosting_platform`:
326
+ - `a` → `hosting_platform: none`
327
+ - `b` → prompt for which static host; write that value (`github-pages` / `netlify` / `cloudflare-pages` / `vercel`)
328
+ - `c` → prompt for which server host; write that value (`amplify` / `fly` / `railway`)
329
+ - `d` → `hosting_platform: operator-managed`
330
+
331
+ If already set, skip the question. The answer flows to /build Phase 7 which branches on this value per cli#332.
332
+
333
+ Per cli#332 — closes "Nothing asks where the app will run" at the earliest surface in the chain.
334
+ <!-- /FKGL-EXEMPT -->
335
+
312
336
  ### Phase 1 — `/interpret-input`
313
337
 
338
+ <!-- FKGL-EXEMPT: pre-existing text-mode + brownfield-mode description — technical dispatch contract per bassclef#459 sub-goal 1. -->
314
339
  Dispatches `/interpret-input` with the type detected by Phase 0. Text mode preserves the existing text-mode behavior; brownfield mode invokes the Phase C extractor (LR2 WU-1, bassclef#459 sub-goal 1) which walks the repo and populates structural hints (entities, actors, routes, README excerpt).
340
+ <!-- /FKGL-EXEMPT -->
315
341
 
316
342
  ```bash
317
343
  # Generate sprint slug + InputArtifact path
@@ -433,25 +433,25 @@ Idempotent API calls against the target repo via `gh`.
433
433
  # Read from git remote — don't infer from cwd basename (cwd can be renamed)
434
434
  REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
435
435
 
436
- declare -A LABELS=(
437
- [bassclef-evolution]="A6E0F2:Candidate for promotion to bassclef"
438
- [agent-user-proposed]="D4A5F5:Proposed by agent in an app repo"
439
- [bassclef-sync]="8FD4A4:Related to bassclef substrate syncing"
440
- [review-required]="F5B94A:Blocks — human review required"
441
- [bug]="E4443A:Defect"
442
- [enhancement]="A2EEEF:New or improved functionality"
443
- [security]="FF0000:Security-relevant"
444
- [infra]="C9D1D9:Infrastructure / workflows / tooling"
445
- [priority-high]="E4443A:High priority"
446
- [priority-medium]="F5B94A:Medium priority"
447
- [question]="D876E3:Needs operator input or design discussion"
448
- )
449
-
450
- for NAME in "${!LABELS[@]}"; do
451
- COLOR="${LABELS[$NAME]%%:*}"
452
- DESC="${LABELS[$NAME]#*:}"
436
+ # Per cli#306 — use while-read loop over pipe-delimited heredoc instead
437
+ # of `declare -A`. macOS ships bash 3.2 which has no associative arrays.
438
+ # Format per line: NAME|COLOR|DESCRIPTION
439
+ while IFS='|' read -r NAME COLOR DESC; do
440
+ [ -z "$NAME" ] && continue
453
441
  gh label create "$NAME" --repo "$REPO" --color "$COLOR" --description "$DESC" --force >/dev/null
454
- done
442
+ done <<'EOF'
443
+ bassclef-evolution|A6E0F2|Candidate for promotion to bassclef
444
+ agent-user-proposed|D4A5F5|Proposed by agent in an app repo
445
+ bassclef-sync|8FD4A4|Related to bassclef substrate syncing
446
+ review-required|F5B94A|Blocks — human review required
447
+ bug|E4443A|Defect
448
+ enhancement|A2EEEF|New or improved functionality
449
+ security|FF0000|Security-relevant
450
+ infra|C9D1D9|Infrastructure / workflows / tooling
451
+ priority-high|E4443A|High priority
452
+ priority-medium|F5B94A|Medium priority
453
+ question|D876E3|Needs operator input or design discussion
454
+ EOF
455
455
  ```
456
456
 
457
457
  Uses `--force` so the call is idempotent (updates existing label or
@@ -87,7 +87,7 @@ When no persona files exist in the repo, auto-populate a minimal persona file fo
87
87
 
88
88
  - `CLAUDE.md` — read the "What this repo is" / "Active initiative" sections for goal context
89
89
  - `substrate.config.md` — read `agents:` and `deploy_targets:` for role context
90
- - If a git `user.email` exists, use it to derive a rough slug (`operator-[local-part]`), otherwise `operator`
90
+ - Default slug: `operator`. Do not derive it from `user.email`. GitHub no-reply addresses carry the account ID and handle. The slug then leaks into committed docs (cli#320). Ask for a display name in-turn if the operator wants one.
91
91
 
92
92
  Emit the operator default to the conversation (not to disk — operator decides whether to persist it):
93
93
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "v1.6.6",
3
- "marker_tag": "release-2026-10-03-89ceaa15",
4
- "release_date": "2026-10-03T10:51:10Z",
5
- "release_sha": "89ceaa15",
6
- "release_tag": "release-2026-10-03-89ceaa15"
3
+ "marker_tag": "release-2026-10-05-484c6f3e",
4
+ "release_date": "2026-10-05T03:42:33Z",
5
+ "release_sha": "484c6f3e",
6
+ "release_tag": "release-2026-10-05-484c6f3e"
7
7
  }