@thebassclef/lite 1.9.8 → 1.9.9

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 +6 -2
  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/hooks/git-stash-u-guard.sh +4 -1
  6. package/dist/lite/.claude/hooks/post-skill-friction-check.sh +6 -1
  7. package/dist/lite/.claude/hooks/pre-commit-gate.sh +18 -6
  8. package/dist/lite/.claude/hooks/pre-commit-identifier-leak-scrub.sh +10 -6
  9. package/dist/lite/.claude/hooks/pre-commit-manifest-autoregen.sh +4 -2
  10. package/dist/lite/.claude/hooks/save-state.sh +24 -0
  11. package/dist/lite/.claude/hooks/trace-helper.sh +29 -0
  12. package/dist/lite/.claude/rules/lite-manifest-schema-change-discipline.md +30 -0
  13. package/dist/lite/.claude/skills/build/SKILL.md +19 -7
  14. package/dist/lite/.claude/skills/onboard-repo/SKILL.md +41 -0
  15. package/dist/lite/.claude/skills/release-notes/SKILL.md +24 -0
  16. package/dist/lite/bassclef-version.json +5 -5
  17. package/dist/lite/gitignore +8 -0
  18. package/dist/lite/lib/deferred-actions.sh +4 -4
  19. package/dist/lite/lib/fixture-builder.sh +9 -9
  20. package/dist/lite/lib/identifier-leak-check.sh +85 -0
  21. package/dist/lite/lib/preview-state.sh +20 -20
  22. package/dist/lite/lib/skip-env-inline-check.sh +61 -0
  23. package/dist/lite/lib/state.sh +31 -28
  24. package/dist/lite/lib/tier-check.sh +9 -9
  25. package/dist/lite/lib/tier-dependency-audit.sh +23 -23
  26. package/dist/lite/presence/install/schedule-auto-save.taskscheduler.md +1 -1
  27. package/dist/lite/standards/lite-manifest-schema-changes.md +38 -0
  28. package/dist/lite/standards/lite-manifest.json +38 -38
  29. package/dist/lite/templates/spec-template.md +10 -6
  30. package/package.json +1 -1
  31. package/dist/lite/.claude/hooks/longrun-prep-compounding-axis-check.sh +0 -492
  32. package/dist/lite/.claude/rules/compounding-axis-fresh-analysis.md +0 -173
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @thebassclef/lite
2
2
 
3
- Install bassclef in your project. Two commands.
3
+ **Security posture.** @thebassclef/lite ships with zero install-time scripts, SLSA v1 build provenance, Apache-2.0 license, and zero runtime dependencies. The CLI writes to `~/.claude` and `$CLAUDE_PROJECT_DIR` at runtime, not at install. See [SECURITY.md](SECURITY.md) for disclosure.
4
+
5
+ ## Install
6
+
7
+ Two commands.
4
8
 
5
9
  ```bash
6
10
  npm install -g @thebassclef/lite
@@ -63,7 +67,7 @@ your browser.
63
67
 
64
68
  ## Current release
65
69
 
66
- <!-- version-start -->1.9.8<!-- version-end -->
70
+ <!-- version-start -->1.9.9<!-- version-end -->
67
71
 
68
72
  See [CHANGELOG.md](CHANGELOG.md) for the full changelog. Adopter-facing
69
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.8";
3
+ const version = "1.9.9";
4
4
  exports.version = version;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const version: "1.9.8";
1
+ export declare const version: "1.9.9";
2
2
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "1.9.8";
1
+ const version = "1.9.9";
2
2
  export {
3
3
  version
4
4
  };
@@ -50,7 +50,10 @@ if ! echo "$CMD" | grep -qE '(^|[[:space:]])git[[:space:]]+stash([[:space:]]+pus
50
50
  fi
51
51
 
52
52
  # --- Override — SKIP_GIT_STASH_U_GUARD=1 bypasses (logged) ---
53
- if [ "${SKIP_GIT_STASH_U_GUARD:-0}" = "1" ]; then
53
+ # Honors inline SKIP per #2036 Finding #4 (recipe must reach the hook).
54
+ # shellcheck source=/dev/null
55
+ source "${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}/lib/skip-env-inline-check.sh" 2>/dev/null || true
56
+ if declare -F skip_var_set >/dev/null && skip_var_set "SKIP_GIT_STASH_U_GUARD" "$CMD"; then
54
57
  TRACE_LOG="${BASSCLEF_TRACE_LOG:-${BASSCLEF_TRACE_FILE:-$HOME/.claude/state/trace.log}}"
55
58
  mkdir -p "$(dirname "$TRACE_LOG")" 2>/dev/null || true
56
59
  echo "$(date -u +%FT%TZ) SKIP_GIT_STASH_U_GUARD $CMD" >> "$TRACE_LOG" 2>/dev/null || true
@@ -45,7 +45,12 @@ SKILL_NAME=$(jq <<< "$TOOL_INPUT" -r '.skill // ""' 2>/dev/null)
45
45
 
46
46
  # Major composer skills — these chain other skills or do heavy work.
47
47
  # Friction surfaces best at their boundaries.
48
- MAJOR_SKILLS_PATTERN="^(preview|preview-build|build|shape|longrun|decompose|spec|architect-review|frontend-design|prototype-variants|prototype-gallery|interpret-input|objectory-decompose|ux-migration|ia-model|interaction-design|use-case|user-stories|jtbd-tasks|task-scenarios)$"
48
+ # Per ADR-040 D1 vocabulary rename (grace window through 2026-10-31 per
49
+ # .claude/rules/we-dont-break-adopters.md): new names stage/launch added;
50
+ # old names preview/preview-build preserved for grace-window compat.
51
+ # Fix for bassclef-upstream#1777 (friction log never created because current
52
+ # vocabulary skipped the gate).
53
+ MAJOR_SKILLS_PATTERN="^(preview|preview-build|stage|launch|build|shape|longrun|decompose|spec|architect-review|frontend-design|prototype-variants|prototype-gallery|interpret-input|objectory-decompose|ux-migration|ia-model|interaction-design|use-case|user-stories|jtbd-tasks|task-scenarios)$"
49
54
 
50
55
  if ! echo "$SKILL_NAME" | grep -qE "$MAJOR_SKILLS_PATTERN"; then
51
56
  exit 0
@@ -41,7 +41,16 @@ trace_log "pre-commit" "git commit"
41
41
  # Exempt: rules/hooks/skills/standards/docs/strategy/chronicle/scripts/tests
42
42
  # (places where the policy itself is documented or test fixtures live).
43
43
  # Override: SKIP_OPERATOR_PATHS=1 git commit (logged via trace-helper)
44
- if [ "${SKIP_OPERATOR_PATHS:-0}" != "1" ]; then
44
+ #
45
+ # Env-reach fix per #2036 Finding #4: the inline recipe sets the var in
46
+ # the git subshell env, not the hook env. Source the helper lib so the
47
+ # recipe reaches the check reliably across harness setups.
48
+ # shellcheck source=/dev/null
49
+ source "${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}/lib/skip-env-inline-check.sh" 2>/dev/null || true
50
+ if declare -F skip_var_set >/dev/null && skip_var_set "SKIP_OPERATOR_PATHS" "$COMMAND"; then
51
+ # Override honored; skip the check.
52
+ :
53
+ else
45
54
  STAGED_NONEXEMPT=$(git diff --cached --name-only --diff-filter=AM 2>/dev/null | \
46
55
  grep -vE '^\.claude/(rules|hooks|skills|luminaries|agents)/' | \
47
56
  grep -vE '^standards/' | \
@@ -50,7 +59,8 @@ if [ "${SKIP_OPERATOR_PATHS:-0}" != "1" ]; then
50
59
  grep -vE '^docs/' | \
51
60
  grep -vE '^chronicle/' | \
52
61
  grep -vE '^strategy/' | \
53
- grep -vE '^scripts/tests/' || true)
62
+ grep -vE '^scripts/tests/' | \
63
+ grep -vE '^lib/tests/' || true)
54
64
  if [ -n "$STAGED_NONEXEMPT" ]; then
55
65
  LEAKED=""
56
66
  while IFS= read -r f; do
@@ -110,7 +120,8 @@ fi
110
120
  # fast-fail layer per Saltzer-Schroeder complete mediation.
111
121
  #
112
122
  # Override: SKIP_COLD_ADOPTER_HARNESS=1 git commit (logged via trace-helper)
113
- if [ "${SKIP_COLD_ADOPTER_HARNESS:-0}" != "1" ]; then
123
+ # Env-reach via skip_var_set helper per #2036 Finding #4.
124
+ if ! { declare -F skip_var_set >/dev/null && skip_var_set "SKIP_COLD_ADOPTER_HARNESS" "$COMMAND"; }; then
114
125
  # The 12-pattern matcher mirrors .claude/rules/cold-adopter-harness-discipline.md
115
126
  # § "When this rule fires". Keep these patterns in sync; a future WU may
116
127
  # centralize the list via a sourced library function.
@@ -173,7 +184,8 @@ fi
173
184
  # state/markers/lead-lens-signoff/<branch-slug>.marker
174
185
  # Discipline lives in .claude/rules/loop-discipline.md Step 0.5 + 2a + 5.5.
175
186
  # Override: SKIP_LEAD_LENS_SIGNOFF=1 git commit (logged via trace-helper)
176
- if [ "${SKIP_LEAD_LENS_SIGNOFF:-0}" != "1" ]; then
187
+ # Env-reach via skip_var_set helper per #2036 Finding #4.
188
+ if ! { declare -F skip_var_set >/dev/null && skip_var_set "SKIP_LEAD_LENS_SIGNOFF" "$COMMAND"; }; then
177
189
  # Reuse the substrate-affecting matcher shape from cold-adopter fast-path.
178
190
  STAGED_SUBSTRATE_LL=$(git diff --cached --name-only --diff-filter=AM 2>/dev/null | grep -E \
179
191
  -e '^\.claude/hooks/[^/]+\.sh$' \
@@ -305,7 +317,7 @@ fi
305
317
 
306
318
  # --- Reserved skill names check (collision with Claude Code built-ins) ---
307
319
  # See .claude/rules/reserved-skill-names.md + standards/reserved-skill-names.md
308
- if [ "${SKIP_RESERVED_NAMES:-0}" != "1" ]; then
320
+ if ! { declare -F skip_var_set >/dev/null && skip_var_set "SKIP_RESERVED_NAMES" "$COMMAND"; }; then
309
321
  STAGED_NEW_SKILLS=$(git diff --cached --name-only --diff-filter=A -- '.claude/skills/*/SKILL.md' 2>/dev/null || true)
310
322
  if [ -n "$STAGED_NEW_SKILLS" ]; then
311
323
  # Extract reserved names from the standards table (Name column values inside backticks)
@@ -351,7 +363,7 @@ fi
351
363
  # counter, silencing the compliance signal (the failure mode that motivated
352
364
  # this gate). Block commits to chronicle/*.md or docs/chronicle/*.md that
353
365
  # lack the section. Override: SKIP_CHRONICLE_GATE_EVIDENCE=1 git commit.
354
- if [ "${SKIP_CHRONICLE_GATE_EVIDENCE:-0}" != "1" ]; then
366
+ if ! { declare -F skip_var_set >/dev/null && skip_var_set "SKIP_CHRONICLE_GATE_EVIDENCE" "$COMMAND"; }; then
355
367
  STAGED_CHRONICLES=$(git diff --cached --name-only --diff-filter=AM -- 'chronicle/*.md' 'docs/chronicle/*.md' 2>/dev/null || true)
356
368
  # Source spine accessor library when available — preferred frontmatter
357
369
  # check route per state-spine-contract.md. Graceful fallback for repos
@@ -33,12 +33,6 @@
33
33
 
34
34
  set -u
35
35
 
36
- # Override path — logged via trace-helper per bassclef discipline
37
- if [ "${SKIP_IDENTIFIER_LEAK_SCRUB:-0}" = "1" ]; then
38
- echo " identifier-leak-scrub: SKIP_IDENTIFIER_LEAK_SCRUB=1 — bypassing scrub" >&2
39
- exit 0
40
- fi
41
-
42
36
  # R-N1 fail-soft: if grep is unavailable, warn to stderr and exit 0
43
37
  if ! command -v grep >/dev/null 2>&1; then
44
38
  echo " identifier-leak-scrub: warn — grep unavailable; skipping scrub" >&2
@@ -59,6 +53,16 @@ else
59
53
  COMMAND=$(echo "$INPUT" | grep -oE '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/^"command"[[:space:]]*:[[:space:]]*"//' | sed 's/"$//')
60
54
  fi
61
55
 
56
+ # Override path — honors inline SKIP_FOO=1 cmd per #2036 Finding #4.
57
+ # PreToolUse hooks run in Claude Code's env, not the git subshell env.
58
+ # The helper lib parses $COMMAND for the inline prefix + falls back to env.
59
+ # shellcheck source=/dev/null
60
+ source "${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}/lib/skip-env-inline-check.sh" 2>/dev/null || true
61
+ if declare -F skip_var_set >/dev/null && skip_var_set "SKIP_IDENTIFIER_LEAK_SCRUB" "$COMMAND"; then
62
+ echo " identifier-leak-scrub: SKIP_IDENTIFIER_LEAK_SCRUB=1 — bypassing scrub" >&2
63
+ exit 0
64
+ fi
65
+
62
66
  # Pass through non-git-commit commands
63
67
  if ! echo "$COMMAND" | grep -qE '^[[:space:]]*git[[:space:]]+commit'; then
64
68
  exit 0
@@ -38,8 +38,10 @@ if ! echo "$COMMAND" | grep -q 'git commit'; then
38
38
  exit 0
39
39
  fi
40
40
 
41
- # Override path
42
- if [ "${SKIP_MANIFEST_AUTOREGEN:-0}" = "1" ]; then
41
+ # Override path — honors inline SKIP_FOO=1 cmd per #2036 Finding #4.
42
+ # shellcheck source=/dev/null
43
+ source "${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}/lib/skip-env-inline-check.sh" 2>/dev/null || true
44
+ if declare -F skip_var_set >/dev/null && skip_var_set "SKIP_MANIFEST_AUTOREGEN" "$COMMAND"; then
43
45
  # Best-effort trace log; ignore failure so hook stays exit 0
44
46
  if command -v bash >/dev/null 2>&1 && [ -f ".claude/hooks/trace-helper.sh" ]; then
45
47
  bash .claude/hooks/trace-helper.sh "skip" "manifest-autoregen" "SKIP_MANIFEST_AUTOREGEN=1" 2>/dev/null || true
@@ -216,6 +216,30 @@ if [ -n "$DIRTY" ]; then
216
216
  git add 'state/' ':!state/markers' ':!state/markers/**' 2>/dev/null || true
217
217
  git add .claude/projects/*/memory/ 2>/dev/null || true
218
218
 
219
+ STAGED=$(git diff --cached --name-only 2>/dev/null)
220
+ if [ -n "$STAGED" ]; then
221
+ # Operator-identifier leak check per #2036 Finding #1.
222
+ # save-state runs `git commit` directly, bypassing the PreToolUse
223
+ # pre-commit-gate.sh CCF-3 check. Even if CCF-3 fired, its grep
224
+ # exempts docs/ paths — so docs/sdlc-traces/*.log carrying absolute
225
+ # /Users/<name>/ paths would still get committed.
226
+ # Shared lib so this check matches pre-commit-gate CCF-3 shape.
227
+ REPO_ROOT_FOR_LIB="$(git rev-parse --show-toplevel 2>/dev/null)"
228
+ LEAK_LIB="${REPO_ROOT_FOR_LIB}/lib/identifier-leak-check.sh"
229
+ if [ -f "$LEAK_LIB" ]; then
230
+ # shellcheck source=/dev/null
231
+ source "$LEAK_LIB"
232
+ if ! check_staged_for_leaks; then
233
+ log "BLOCKED: auto-save detected operator paths in staged content; resetting stage"
234
+ git reset HEAD -- . 2>/dev/null || true
235
+ # Fall through — SESSION_LOCK cleanup below still fires, push is skipped
236
+ # because STAGED check below sees empty stage.
237
+ STAGED=""
238
+ fi
239
+ fi
240
+ fi
241
+
242
+ # Re-check STAGED after possible reset
219
243
  STAGED=$(git diff --cached --name-only 2>/dev/null)
220
244
  if [ -n "$STAGED" ]; then
221
245
  TIMESTAMP=$(date +%Y-%m-%dT%H:%M:%S)
@@ -5,9 +5,28 @@
5
5
  #
6
6
  # Usage: source this file, then call:
7
7
  # trace_log "hook-name" "trigger-description"
8
+ #
9
+ # Privacy — relative paths (#2036 Finding #1 — adopter report 2026-10-02):
10
+ # The ${trigger} field can carry filesystem paths from caller args
11
+ # (e.g., $FILE_PATH from PreToolUse hooks). We strip repo-root prefix
12
+ # so adopter traces never leak /Users/<name>/ or /home/<name>/ paths
13
+ # to adopter git. Fallback chain: $CLAUDE_PROJECT_DIR → git rev-parse
14
+ # --show-toplevel → no-strip (silent — trigger lands as-is).
8
15
 
9
16
  TRACE_DIR="docs/sdlc-traces"
10
17
 
18
+ # _trace_repo_root — resolve repo root for prefix-strip.
19
+ # Precondition fallback per @luminary tony-hoare — Hoare R1 risk ledger.
20
+ _trace_repo_root() {
21
+ if [ -n "${CLAUDE_PROJECT_DIR:-}" ]; then
22
+ echo "$CLAUDE_PROJECT_DIR"
23
+ return 0
24
+ fi
25
+ local root
26
+ root=$(git rev-parse --show-toplevel 2>/dev/null) || return 1
27
+ echo "$root"
28
+ }
29
+
11
30
  trace_log() {
12
31
  local hook="$1"
13
32
  local trigger="$2"
@@ -17,6 +36,16 @@ trace_log() {
17
36
  today=$(date +%Y-%m-%d)
18
37
  local log_file="${TRACE_DIR}/${today}.log"
19
38
 
39
+ # Strip repo-root prefix from trigger before writing.
40
+ # Only strips when root is a non-empty prefix of trigger — leaves
41
+ # unrelated absolute paths (/opt/anthropic/..., /usr/bin/...) intact.
42
+ local root
43
+ root=$(_trace_repo_root 2>/dev/null) || root=""
44
+ if [ -n "$root" ] && [ "$root" != "/" ]; then
45
+ trigger="${trigger//$root\//}"
46
+ trigger="${trigger//$root/}"
47
+ fi
48
+
20
49
  mkdir -p "$TRACE_DIR" 2>/dev/null || true
21
50
  echo "${timestamp} | ${hook} | ${trigger} | — | —" >> "$log_file" 2>/dev/null || true
22
51
  }
@@ -36,11 +36,41 @@ When a PR changes the manifest schema OR output shape:
36
36
  3. **Downstream cure named** — the entry names what adopters need to do. If nothing (backward-compatible add), say so explicitly. If code change (like the v0.36.0 `upstream_commit` removal), name the pattern.
37
37
  4. **Release script pre-flight** — before every `/release`, the script reads the ledger and confirms an entry exists for the outgoing `manifest_version`. Missing entry BLOCKs the cascade.
38
38
 
39
+ ## Retirements
40
+
41
+ Retirement-class changes (content-remove: a file previously shipped in the lite manifest gets deleted intentionally) ship with the same discipline shape as additions. Three paired artifacts, same PR:
42
+
43
+ 1. **Ledger entry** under this ledger's `## Entries` section naming the removed slugs, the deletion SHA, the version bump (MINOR per content-remove policy), and the downstream cure (what adopters at an exact pin do).
44
+ 2. **Allowlist row** in `.claude/hooks/tests/fixtures/2026-09-16-lite-catalog-closure/retired-entries.txt` naming each removed `<type>/<slug>` + `retired-in=<SHA>` + `reason=<one-line>`. The Feathers v1.6.1 snapshot guard (Test 16 in `.claude/hooks/tests/generate-lite-manifest.test.sh`) subtracts allowlist rows before asserting zero drops. Without the allowlist row the test fails and main CI goes red.
45
+ 3. **Discipline amendment** when a new retirement class surfaces that this section does not cover (e.g., tier retirement, field retirement). Extend this `## Retirements` section rather than author a new rule, per Ousterhout's complexity-is-incremental principle.
46
+
47
+ When this rule fires on retirement:
48
+
49
+ - Any PR that deletes a file listed in the current `lite-manifest.json` under `.entries[].path`
50
+ - Any PR that retires a schema field that previously shipped as REQUIRED
51
+
52
+ Skips (same as additions):
53
+
54
+ - Content-only changes where the file stays but its content_hash shifts
55
+ - Comment-only edits
56
+
57
+ **Anchor luminaries for retirement:**
58
+
59
+ - `@luminary linus-torvalds` — retirement of a tier-tagged adopter-observable file is a userspace break; the ledger + allowlist + release-step6 check ship in one PR
60
+ - `@luminary hyrum-wright` — observable behavior becomes dependency with enough adopters; the 2 removed slugs were cited in rule bodies, sister docs, and the fixture snapshot; every retirement needs observable-surface enumeration first
61
+ - `@luminary michael-nygard` — current ledger discipline for additions is methodology at author time; retirement needs the same mechanical floor plus a stability-pattern gate (release-step6 pre-flight)
62
+ - `@luminary michael-feathers` — the fixture snapshot is the characterization test; allowlist is the author-time hook that keeps it green
63
+ - `@luminary vaughn-vernon` — `retired-entries.txt` is the anticorruption layer between the frozen snapshot and the current manifest
64
+ - `@luminary john-ousterhout` — contrarian: extend the existing rule with this section, do not author a new rule; the mechanical pre-commit hook (`pre-retire-discipline-check.sh`) is deferred to a follow-on PR so this one stays scope-tight
65
+
39
66
  ## Anti-patterns
40
67
 
41
68
  **Schema field added but no ledger entry.** Release script pre-flight BLOCKs.
42
69
  INSTEAD: add the entry in the same PR.
43
70
 
71
+ **File removed from the lite manifest without an allowlist row + ledger entry.** Test 16 (Feathers v1.6.1 snapshot guard) sees the drop and main CI goes red at the next run.
72
+ INSTEAD: append a row to `retired-entries.txt` naming `<type>/<slug> retired-in=<SHA> reason=<one-line>` AND add a `## Retirements`-aware entry to `standards/lite-manifest-schema-changes.md` in the same PR.
73
+
44
74
  **Ledger entry written but wrong version bump.** Adopters plan their pin update against the wrong version.
45
75
  INSTEAD: correct the bump before merge. AJV validation of the entry format catches inconsistency.
46
76
 
@@ -330,14 +330,25 @@ Parse the validated goal's structure:
330
330
  ```bash
331
331
  # Already have BET_JSON, SPEC_PATH, DECOMP_PATH, UX_MIG_PATH from Phase 0
332
332
  # Read spec body for step enumeration. Per bassclef#367, spec MUST have
333
- # the exact heading `## Steps enumerated` (pinned in /spec SKILL.md
334
- # rules + templates/spec-template.md). /launch's Phase 9
335
- # emits this heading by contract. Specs that lack the section → exit 4.
333
+ # the heading `## Steps enumerated` (pinned in /spec SKILL.md rules +
334
+ # templates/spec-template.md). /launch's Phase 9 emits this heading by
335
+ # contract. Specs that lack the section → exit 4.
336
+ #
337
+ # Finding #2 + #3 cure (#2036): the previous awk range
338
+ # `/^## Steps enumerated/,/^## /` closed on its own start line because
339
+ # `^## ` matches `^## Steps enumerated`. Flag-based extraction fixes it.
340
+ # Grace window per ADR-040 D1 (through 2026-10-31): accept both the new
341
+ # "Steps enumerated" / "### Step-N" and the historical "Workunits
342
+ # enumerated" / "### WU-N" headings so legacy specs still build.
336
343
  SPEC_FILE="docs/specs/${SPEC_PATH}.md"
337
- WU_LIST=$(awk '/^## Steps enumerated/,/^## /' "$SPEC_FILE" | grep -E '^### WU-')
344
+ WU_LIST=$(awk '
345
+ /^## (Steps|Workunits) enumerated/ { in_section=1; next }
346
+ /^## / { in_section=0 }
347
+ in_section
348
+ ' "$SPEC_FILE" | grep -E '^### (Step|WU)-')
338
349
 
339
- # Each WU should reference at least one story (US-NNN)
340
- WU_COUNT=$(echo "$WU_LIST" | grep -c '^### WU-' || echo 0)
350
+ # Each step should reference at least one story (US-NNN)
351
+ WU_COUNT=$(echo "$WU_LIST" | grep -cE '^### (Step|WU)-' || echo 0)
341
352
  ```
342
353
 
343
354
  Build structured object: `{ bet, spec_path, decomp_path, ux_migration_path, wus[] }`.
@@ -348,7 +359,8 @@ Verify each step traces to at least one story. Refuse if any step lacks
348
359
  story traceability.
349
360
 
350
361
  ```bash
351
- ORPHAN_WUS=$(echo "$WU_LIST" | grep -v 'US-' || true)
362
+ # Keep only step headings; drop body lines before the US-NNN check.
363
+ ORPHAN_WUS=$(echo "$WU_LIST" | grep -E '^### (Step|WU)-' | grep -v 'US-' || true)
352
364
  if [ -n "$ORPHAN_WUS" ]; then
353
365
  echo "/build: step enumeration lacks story traceability (bassclef#358 violation)" >&2
354
366
  echo "Orphan steps (no US-NNN ref):" >&2
@@ -462,9 +462,28 @@ immediately without waiting for a sync cycle.
462
462
 
463
463
  ##### 1.2 Protect main from force-push and deletion
464
464
 
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.
470
+
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.
482
+
465
483
  ```bash
466
484
  DEFAULT_BRANCH=$(gh repo view "$REPO" --json defaultBranchRef -q '.defaultBranchRef.name')
467
485
 
486
+ # Pro / public-repo path — run the API call.
468
487
  gh api "repos/$REPO/branches/$DEFAULT_BRANCH/protection" \
469
488
  --method PUT \
470
489
  --input - <<EOF
@@ -484,6 +503,28 @@ required here — agent-created PRs (bassclef-sync) need to merge
484
503
  without human intervention for the mobile-sync story. Add stricter
485
504
  rules per-repo in `.github/branch-protection.yml` if the repo warrants.
486
505
 
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
+ The adopter can revisit this step after switching to public OR
526
+ upgrading the plan.
527
+
487
528
  ##### 1.3 Add the production approval gate (if this repo deploys to prod)
488
529
 
489
530
  If the repo will deploy to production, per `manual-prod-approval.md`:
@@ -108,6 +108,30 @@ Rules for external notes:
108
108
  - No feature names that aren't visible to users
109
109
  - Test: could someone who's never seen the codebase understand this?
110
110
 
111
+ ### 3.5. Run `/ogilvy-writing-audit` on each body
112
+
113
+ Before saving the drafted bodies to disk, run `/ogilvy-writing-audit`
114
+ on each one. The skill scans against Ogilvy + Roman-Raphaelson's
115
+ rules (see `standards/ogilvy-writing-rules.md`) and returns
116
+ severity-tagged findings.
117
+
118
+ ```bash
119
+ /ogilvy-writing-audit docs/release-notes/YYYY-MM-DD-internal.md
120
+ /ogilvy-writing-audit docs/release-notes/YYYY-MM-DD-external.md
121
+ ```
122
+
123
+ Fix every 🔴 and 🟡 finding before step 4. When a 🟡 ships without
124
+ a fix, cite the audit in the commit body and name why.
125
+
126
+ Why this step. Release notes land in reader surfaces that must read
127
+ clean — the GitHub Release artifact plus the Google Doc the team
128
+ reviews. Jargon and grade-13 prose creep in without a mechanical
129
+ check. PR #408 ran this discipline by hand across 8 entries, and
130
+ PR #412 formalizes it so every future entry inherits the floor.
131
+
132
+ Cross-ref: `.claude/rules/session-artifacts.md` §Release notes names
133
+ this step as the discipline anchor.
134
+
111
135
  ### 4. Save locally
112
136
 
113
137
  ```
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "v1.6.4",
3
- "marker_tag": "release-2026-09-28-21212e17",
4
- "release_date": "2026-09-28T01:33:41Z",
5
- "release_sha": "21212e17",
6
- "release_tag": "release-2026-09-28-21212e17"
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"
7
7
  }
@@ -31,6 +31,14 @@ state/luminary-implementations/
31
31
  .claude/hooks/logs/
32
32
  .claude/hooks/*.trace
33
33
 
34
+ # SDLC traces (operator-private; runtime artifacts carry operator paths)
35
+ # Per #2036 Finding #1 (adopter report 2026-10-02) — auto-save was
36
+ # committing /Users/<name>/ paths to adopter git every fire. Shipping this
37
+ # line stops future adopter installs from leaking. Existing installs run
38
+ # `git rm --cached -r docs/sdlc-traces/` one time to untrack files
39
+ # already in history.
40
+ docs/sdlc-traces/
41
+
34
42
  # Build output
35
43
  dist/
36
44
 
@@ -72,9 +72,9 @@ _da_resolve_path() {
72
72
  da_get_field() {
73
73
  local id="$1"
74
74
  local field="$2"
75
- local path
76
- path="$(_da_resolve_path "$id")"
77
- [[ -f "$path" ]] || { echo "not-found: $path" >&2; return 1; }
75
+ local p
76
+ p="$(_da_resolve_path "$id")"
77
+ [[ -f "$p" ]] || { echo "not-found: $p" >&2; return 1; }
78
78
  # Read frontmatter only (between two `---` lines)
79
79
  awk -v f="$field" '
80
80
  BEGIN { in_fm = 0; seen_open = 0 }
@@ -89,7 +89,7 @@ da_get_field() {
89
89
  print
90
90
  exit
91
91
  }
92
- ' "$path"
92
+ ' "$p"
93
93
  }
94
94
 
95
95
  da_resolve() {
@@ -82,16 +82,16 @@ fixture_cleanup() {
82
82
  if [ "${SKIP_FIXTURE_BUILDER:-0}" = "1" ]; then
83
83
  return 0
84
84
  fi
85
- local path="${1:?fixture_cleanup requires path}"
86
- if [ ! -e "$path" ]; then
85
+ local p="${1:?fixture_cleanup requires path}"
86
+ if [ ! -e "$p" ]; then
87
87
  # Idempotent — nothing to do
88
88
  return 0
89
89
  fi
90
- if [ ! -f "$path/$_FIXTURE_SENTINEL" ]; then
91
- echo "fixture_cleanup: refusing to remove path without sentinel: $path" >&2
90
+ if [ ! -f "$p/$_FIXTURE_SENTINEL" ]; then
91
+ echo "fixture_cleanup: refusing to remove path without sentinel: $p" >&2
92
92
  return 1
93
93
  fi
94
- rm -rf "$path"
94
+ rm -rf "$p"
95
95
  return 0
96
96
  }
97
97
 
@@ -105,10 +105,10 @@ fixture_cleanup_all() {
105
105
  return 0
106
106
  fi
107
107
  local IFS=$'\n'
108
- local path
109
- for path in $_FIXTURE_REGISTRY; do
110
- [ -n "$path" ] || continue
111
- fixture_cleanup "$path" 2>/dev/null || true
108
+ local p
109
+ for p in $_FIXTURE_REGISTRY; do
110
+ [ -n "$p" ] || continue
111
+ fixture_cleanup "$p" 2>/dev/null || true
112
112
  done
113
113
  _FIXTURE_REGISTRY=""
114
114
  }
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # Operator-identifier leak check for staged git content.
4
+ #
5
+ # Shared across:
6
+ # - .claude/hooks/save-state.sh (inline before `git commit`;
7
+ # bypasses pre-commit-gate.sh because save-state runs git
8
+ # directly, not through the agent's PreToolUse path)
9
+ # - future: .claude/hooks/pre-commit-gate.sh CCF-3 (unification
10
+ # deferred to a follow-on; shape intentionally matches CCF-3
11
+ # today so the two can merge without semantic drift)
12
+ #
13
+ # Rejects staged content carrying absolute operator paths matching
14
+ # /Users/<name>/ or /home/<name>/. Mirrors pre-commit-gate.sh:50-53
15
+ # exemptions so policy docs that quote the pattern don't trip.
16
+ #
17
+ # Per #2036 Finding #1 (adopter report 2026-10-02).
18
+ # Per @luminary nygard — stability pattern (circuit breaker on commit).
19
+ # Per @luminary hoare — precondition contract before `git commit`.
20
+
21
+ # check_staged_for_leaks — scan git-staged content for operator paths.
22
+ #
23
+ # Returns 0 when staged content is clean OR when every leaking file
24
+ # sits in an exempt path (policy docs, rules, luminaries).
25
+ # Returns 1 when staged content outside exempt paths carries a leak.
26
+ # On exit 1, every leaking file is named on stderr with line number
27
+ # and the matched pattern.
28
+ check_staged_for_leaks() {
29
+ # Staged non-exempt files. Match pre-commit-gate.sh:46-53 exemption shape
30
+ # so the two gates agree on which paths carry policy docs legitimately
31
+ # quoting operator paths.
32
+ local staged
33
+ staged=$(git diff --cached --name-only --diff-filter=AM 2>/dev/null | \
34
+ grep -vE '^\.claude/(rules|hooks|skills|luminaries|agents)/' | \
35
+ grep -vE '^standards/' | \
36
+ grep -vE '^architecture/' | \
37
+ grep -vE '^design/' | \
38
+ grep -vE '^docs/standards/' | \
39
+ grep -vE '^docs/rules/' | \
40
+ grep -vE '^chronicle/' | \
41
+ grep -vE '^strategy/' | \
42
+ grep -vE '^scripts/tests/' || true)
43
+
44
+ [ -z "$staged" ] && return 0
45
+
46
+ local leaked=""
47
+ while IFS= read -r f; do
48
+ [ -z "$f" ] && continue
49
+ [ ! -f "$f" ] && continue
50
+ local matches
51
+ matches=$(grep -nE '/Users/[A-Za-z0-9_.-]+|/home/[A-Za-z0-9_.-]+' "$f" 2>/dev/null || true)
52
+ if [ -n "$matches" ]; then
53
+ leaked="${leaked}
54
+ ${f}:
55
+ ${matches}
56
+ "
57
+ fi
58
+ done <<< "$staged"
59
+
60
+ if [ -n "$leaked" ]; then
61
+ {
62
+ echo ""
63
+ echo "============================================"
64
+ echo "BLOCKED: operator path in staged content (#2036 Finding #1)"
65
+ echo "============================================"
66
+ echo "$leaked"
67
+ echo ""
68
+ echo "Operator-specific paths (/Users/<name>, /home/<name>) must NOT"
69
+ echo "ship in adopter git. Common cause: docs/sdlc-traces/*.log"
70
+ echo "carrying absolute paths from PreToolUse hook callers."
71
+ echo ""
72
+ echo "Fixes:"
73
+ echo " 1. Delete the staged file if it's a trace log auto-generated"
74
+ echo " (gitignored going forward per #2036 Finding #1 cure)"
75
+ echo " 2. Rewrite the path as repo-relative"
76
+ echo " 3. Move policy docs under docs/standards/ or docs/rules/ (exempt)"
77
+ echo ""
78
+ echo "Policy: standards/two-layer-config.md (bassclef#740)"
79
+ echo "============================================"
80
+ } >&2
81
+ return 1
82
+ fi
83
+
84
+ return 0
85
+ }