@thebassclef/lite 1.1.0 → 1.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/cli.cjs +258 -63
  2. package/dist/cli.js +258 -63
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/lite/.claude/bassclef-configs.jsonc +56 -0
  7. package/dist/lite/.claude/bassclef-orientation.md +120 -0
  8. package/dist/lite/.claude/hooks/adr-discipline-check.sh +132 -0
  9. package/dist/lite/.claude/hooks/atomic-pr-check.sh +169 -0
  10. package/dist/lite/.claude/hooks/auto-save-idle.sh +51 -0
  11. package/dist/lite/.claude/hooks/bassclef-sync.sh +51 -13
  12. package/dist/lite/.claude/hooks/bet-doc-gate.sh +283 -0
  13. package/dist/lite/.claude/hooks/bug-diagnosis.sh +64 -0
  14. package/dist/lite/.claude/hooks/destructive-command-guard.sh +136 -0
  15. package/dist/lite/.claude/hooks/file-rename-discipline-check.sh +147 -0
  16. package/dist/lite/.claude/hooks/hook-classifier-canary.sh +158 -0
  17. package/dist/lite/.claude/hooks/kiss-default-check.sh +129 -0
  18. package/dist/lite/.claude/hooks/multi-tenancy-check.sh +83 -0
  19. package/dist/lite/.claude/hooks/oo-ad-entry-point-check.sh +216 -0
  20. package/dist/lite/.claude/hooks/operator-private-path-check.sh +203 -0
  21. package/dist/lite/.claude/hooks/pattern-annotation-validate.sh +142 -0
  22. package/dist/lite/.claude/hooks/plain-english-steering.sh +15 -1
  23. package/dist/lite/.claude/hooks/post-write-trace.sh +66 -0
  24. package/dist/lite/.claude/hooks/pr-body-scrub-check.sh +446 -0
  25. package/dist/lite/.claude/hooks/pre-commit-identifier-leak-scrub.sh +137 -0
  26. package/dist/lite/.claude/hooks/pre-gh-pr-body-scrub.sh +192 -0
  27. package/dist/lite/.claude/hooks/pre-gh-pr-title-scrub.sh +172 -0
  28. package/dist/lite/.claude/hooks/role-entry-gate.sh +115 -0
  29. package/dist/lite/.claude/hooks/save-state.sh +268 -0
  30. package/dist/lite/.claude/hooks/session-reflection.d/08-settings-drift.sh +8 -1
  31. package/dist/lite/.claude/hooks/session-reflection.d/10-abrupt-stop-recovery.sh +52 -0
  32. package/dist/lite/.claude/hooks/session-reflection.d/55-orientation-gate.sh +40 -4
  33. package/dist/lite/.claude/hooks/session-reflection.d/80-hook-heartbeat-check.sh +12 -5
  34. package/dist/lite/.claude/hooks/session-reflection.d/81-hook-manifest-staleness.sh +18 -2
  35. package/dist/lite/.claude/hooks/session-reflection.d/91-npm-version-drift.sh +54 -0
  36. package/dist/lite/.claude/hooks/session-reflection.d/tests/10-abrupt-stop-recovery.test.sh +95 -0
  37. package/dist/lite/.claude/hooks/session-reflection.sh +62 -15
  38. package/dist/lite/.claude/hooks/tool-failure-diagnosis.sh +50 -0
  39. package/dist/lite/.claude/luminaries/andrej-karpathy.md +165 -0
  40. package/dist/lite/.claude/luminaries/dave-gray.md +155 -0
  41. package/dist/lite/.claude/luminaries/gary-klein.md +166 -0
  42. package/dist/lite/.claude/luminaries/kaoru-ishikawa.md +162 -0
  43. package/dist/lite/.claude/luminaries/lean-product-canvas.md +33 -0
  44. package/dist/lite/.claude/luminaries/luke-wroblewski.md +224 -0
  45. package/dist/lite/.claude/luminaries/ryan-singer.md +113 -0
  46. package/dist/lite/.claude/luminaries/steve-krug.md +168 -0
  47. package/dist/lite/.claude/luminaries/tony-ulwick.md +134 -0
  48. package/dist/lite/.claude/rules/arc-level-orientation-before-leaf.md +60 -0
  49. package/dist/lite/.claude/rules/architect-review-discipline.md +71 -0
  50. package/dist/lite/.claude/rules/auto-fix-criteria.md +126 -0
  51. package/dist/lite/.claude/rules/bassclef-hook-drift-blocker.md +144 -0
  52. package/dist/lite/.claude/rules/bassclef-models-discovery.md +107 -0
  53. package/dist/lite/.claude/rules/bassclef-workflow-parity.md +104 -0
  54. package/dist/lite/.claude/rules/cost-governance.md +154 -0
  55. package/dist/lite/.claude/rules/file-rename-discipline.md +95 -0
  56. package/dist/lite/.claude/rules/mechanism-fidelity.md +88 -0
  57. package/dist/lite/.claude/rules/prototype-production-coupling.md +125 -0
  58. package/dist/lite/.claude/rules/quick-fix-criteria.md +90 -0
  59. package/dist/lite/.claude/rules/session-artifacts.md +30 -0
  60. package/dist/lite/.claude/rules/sibling-smoke-after-substrate-change.md +107 -0
  61. package/dist/lite/.claude/rules/substrate-as-system.md +67 -0
  62. package/dist/lite/.claude/rules/usability.md +22 -0
  63. package/dist/lite/.claude/settings.json +20 -0
  64. package/dist/lite/.claude/skills/agent-research-spawn/SKILL.md +100 -0
  65. package/dist/lite/.claude/skills/architect-review/SKILL.md +201 -0
  66. package/dist/lite/.claude/skills/auto-fix/SKILL.md +297 -0
  67. package/dist/lite/.claude/skills/autonomous/SKILL.md +300 -0
  68. package/dist/lite/.claude/skills/bind-subdomain/SKILL.md +232 -0
  69. package/dist/lite/.claude/skills/canvas/SKILL.md +41 -0
  70. package/dist/lite/.claude/skills/deploy-prod/SKILL.md +106 -0
  71. package/dist/lite/.claude/skills/docs-sync/SKILL.md +188 -0
  72. package/dist/lite/.claude/skills/eisenhower/SKILL.md +273 -0
  73. package/dist/lite/.claude/skills/empathy-map/SKILL.md +168 -0
  74. package/dist/lite/.claude/skills/frontend-design/SKILL.md +53 -0
  75. package/dist/lite/.claude/skills/interview-me/SKILL.md +171 -0
  76. package/dist/lite/.claude/skills/journal-export/SKILL.md +293 -0
  77. package/dist/lite/.claude/skills/jtbd-tasks/SKILL.md +203 -0
  78. package/dist/lite/.claude/skills/objectory-decompose/SKILL.md +256 -0
  79. package/dist/lite/.claude/skills/pick-luminaries/SKILL.md +100 -0
  80. package/dist/lite/.claude/skills/pre-mortem/SKILL.md +180 -0
  81. package/dist/lite/.claude/skills/prototype-gallery/SKILL.md +264 -0
  82. package/dist/lite/.claude/skills/provision-deploy-host/SKILL.md +257 -0
  83. package/dist/lite/.claude/skills/quick-fix/SKILL.md +273 -0
  84. package/dist/lite/.claude/skills/release/SKILL.md +311 -0
  85. package/dist/lite/.claude/skills/release-notes/SKILL.md +202 -0
  86. package/dist/lite/.claude/skills/rfc/SKILL.md +222 -0
  87. package/dist/lite/.claude/skills/riff-prototypes/SKILL.md +637 -0
  88. package/dist/lite/.claude/skills/secrets-bootstrap/SKILL.md +209 -0
  89. package/dist/lite/.claude/skills/skill/SKILL.md +304 -0
  90. package/dist/lite/.claude/skills/smoke-test/SKILL.md +100 -0
  91. package/dist/lite/.claude/skills/state/SKILL.md +204 -0
  92. package/dist/lite/.claude/skills/substrate-check/SKILL.md +218 -0
  93. package/dist/lite/.claude/skills/synthetic-user/SKILL.md +381 -0
  94. package/dist/lite/.claude/skills/task-scenarios/SKILL.md +105 -0
  95. package/dist/lite/.claude/skills/tier-dependency-audit/SKILL.md +139 -0
  96. package/dist/lite/.claude/skills/ux-migration/SKILL.md +424 -0
  97. package/dist/lite/.claude/skills/value-prop-canvas/SKILL.md +297 -0
  98. package/dist/lite/README.md +5 -1
  99. package/dist/lite/gitignore +6 -0
  100. package/dist/lite/lib/bassclef-dir-resolver.sh +85 -0
  101. package/dist/lite/lib/clone-failure-classifier.sh +59 -0
  102. package/dist/lite/lib/fixture-builder.sh +190 -0
  103. package/dist/lite/lib/fresh-install-check.sh +49 -0
  104. package/dist/lite/lib/hook-heartbeat.sh +0 -1
  105. package/dist/lite/lib/mechanism-fidelity.sh +569 -0
  106. package/dist/lite/lib/npm-version-check.sh +112 -0
  107. package/dist/lite/lib/preview-state.sh +229 -0
  108. package/dist/lite/lib/session-rescue.sh +0 -1
  109. package/dist/lite/scripts/generate-lite-manifest.sh +145 -4
  110. package/dist/lite/scripts/lite-manifest-drift-check.sh +5 -1
  111. package/dist/lite/standards/architect-review-discipline.md +222 -0
  112. package/dist/lite/standards/bassclef-platform.md +249 -0
  113. package/dist/lite/standards/bassclef-test-repos.md +97 -0
  114. package/dist/lite/standards/bassclef-wiring-manifest.json +25 -1
  115. package/dist/lite/standards/cold-adopter-harness.md +205 -0
  116. package/dist/lite/standards/data-management.md +135 -0
  117. package/dist/lite/standards/frontend-stack/nextjs-prisma.md +124 -0
  118. package/dist/lite/standards/lite-manifest-schema-changes.md +27 -0
  119. package/dist/lite/standards/lite-manifest.json +1456 -14
  120. package/dist/lite/standards/mechanism-fidelity.md +205 -0
  121. package/dist/lite/standards/operator-private-discipline.md +200 -0
  122. package/dist/lite/standards/operator-to-public-flow.md +195 -0
  123. package/dist/lite/standards/prototype-gallery.md +251 -0
  124. package/dist/lite/standards/pseudonym-key-schema.md +109 -0
  125. package/dist/lite/standards/sibling-validation-checklist.md +181 -0
  126. package/dist/lite/standards/slack-notifications.md +161 -0
  127. package/dist/lite/standards/synthetic-user-testing.md +173 -0
  128. package/dist/lite/standards/vendor-bindings.md +165 -0
  129. package/dist/lite/standards/vendor-plugins/frontend-design.md +66 -0
  130. package/dist/lite/standards/vpc-fit-validation.md +167 -0
  131. package/dist/lite/templates/gallery-yml.md +113 -0
  132. package/dist/lite/templates/product-canvas.md +169 -0
  133. package/dist/lite/templates/value-prop-canvas.md +180 -0
  134. package/package.json +12 -5
  135. /package/dist/lite/{whereami.md → docs/whereami.md} +0 -0
@@ -0,0 +1,192 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # pre-gh-pr-body-scrub — PreToolUse hook that runs the body scrub logic
4
+ # LOCALLY before a `gh pr create`, `gh pr edit --body`, `gh issue create`,
5
+ # or `gh issue edit --body` Bash invocation reaches GitHub. Sister to
6
+ # atomic-pr-check.sh (Bash-matcher pattern); reuses pr-body-scrub-check.sh
7
+ # (the CI scan logic) for shared rule semantics.
8
+ #
9
+ # bassclef#1176 (V1) — closed the gap PR #1172 demonstrated for PR bodies.
10
+ # bassclef#1177 (V2) — extends the same hook to gh issue bodies. The
11
+ # filename keeps the V1 name for git-history continuity; the matcher covers
12
+ # both surfaces. Commit-message bodies are covered by the sibling hook
13
+ # pre-git-commit-msg-scrub.sh.
14
+ #
15
+ # Exit codes:
16
+ # 0 — allow (non-matching Bash, no body extractable, clean body, or override)
17
+ # 2 — block (Rule 1 findings present in the extracted body)
18
+ #
19
+ # Override: SKIP_PRE_GH_PR_BODY_SCRUB=1 (logged via trace-helper)
20
+
21
+ set +e # never crash the tool call; fall through to allow on any unexpected error
22
+
23
+ INPUT=$(cat 2>/dev/null)
24
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
25
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
26
+
27
+ # Only fire on Bash
28
+ if [ "$TOOL_NAME" != "Bash" ]; then
29
+ exit 0
30
+ fi
31
+
32
+ # Match the surfaces this hook covers: gh pr create + gh pr edit (V1) and
33
+ # gh issue create + gh issue edit (V2 per bassclef#1177) and curl REST
34
+ # equivalents (V3 per bassclef#1239 — closes the curl REST bypass gap).
35
+ IS_CURL=0
36
+ case "$COMMAND" in
37
+ *"gh pr create"*|*"gh pr edit"*|*"gh issue create"*|*"gh issue edit"*)
38
+ : # match — continue to body extraction
39
+ ;;
40
+ *curl*api.github.com*/pulls*|*curl*api.github.com*/issues*)
41
+ case "$COMMAND" in
42
+ *"-X POST"*|*"-X PATCH"*|*"--request POST"*|*"--request PATCH"*)
43
+ IS_CURL=1
44
+ ;;
45
+ *)
46
+ exit 0 # read-only curl
47
+ ;;
48
+ esac
49
+ ;;
50
+ *)
51
+ exit 0
52
+ ;;
53
+ esac
54
+
55
+ # Override hatch
56
+ SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
57
+ if [ "${SKIP_PRE_GH_PR_BODY_SCRUB:-0}" = "1" ]; then
58
+ if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
59
+ source "$SCRIPT_DIR/trace-helper.sh" 2>/dev/null || true
60
+ trace_log "pre-gh-pr-body-scrub" "OVERRIDE gh pr create|edit" 2>/dev/null || true
61
+ fi
62
+ exit 0
63
+ fi
64
+
65
+ # Locate the shared scrub script
66
+ SCRUB_SCRIPT="${SCRIPT_DIR}/pr-body-scrub-check.sh"
67
+ if [ ! -f "$SCRUB_SCRIPT" ]; then
68
+ # Shared scrub missing — degrade to advisory (allow)
69
+ exit 0
70
+ fi
71
+
72
+ # Extract body content from the command. Three forms in order of precedence:
73
+ # 1. Heredoc: --body "$(cat <<EOF ... EOF)" or <<'EOF' ... EOF
74
+ # 2. --body-file <path>
75
+ # 3. Inline: --body "..." or --body '...'
76
+ TMP_BODY=$(mktemp 2>/dev/null) || exit 0
77
+ trap 'rm -f "$TMP_BODY"' EXIT
78
+
79
+ EXTRACTED=$(COMMAND="$COMMAND" IS_CURL="$IS_CURL" python3 -c '
80
+ import os, re, sys, json
81
+ cmd = os.environ.get("COMMAND", "")
82
+ is_curl = os.environ.get("IS_CURL", "0") == "1"
83
+
84
+ if is_curl:
85
+ # curl REST shape — extract body from --data @<file> or -d "<inline>"
86
+ # bassclef#1239
87
+ m = re.search(r"(?:--data|-d)\s+@(\S+)", cmd)
88
+ if m:
89
+ path = m.group(1)
90
+ try:
91
+ with open(path) as f:
92
+ payload = json.load(f)
93
+ body = payload.get("body", "")
94
+ if body:
95
+ sys.stdout.write(body)
96
+ sys.exit(0)
97
+ except (OSError, json.JSONDecodeError):
98
+ pass
99
+ m = re.search(r"(?:--data|-d)\s+\x27([^\x27]*)\x27", cmd, re.DOTALL)
100
+ if m:
101
+ try:
102
+ payload = json.loads(m.group(1))
103
+ body = payload.get("body", "")
104
+ if body:
105
+ sys.stdout.write(body)
106
+ sys.exit(0)
107
+ except json.JSONDecodeError:
108
+ pass
109
+ m = re.search(r"(?:--data|-d)\s+\"((?:[^\"\\\\]|\\\\.)*)\"", cmd, re.DOTALL)
110
+ if m:
111
+ try:
112
+ payload = json.loads(m.group(1).replace("\\\"", "\""))
113
+ body = payload.get("body", "")
114
+ if body:
115
+ sys.stdout.write(body)
116
+ sys.exit(0)
117
+ except json.JSONDecodeError:
118
+ pass
119
+ # No body extractable from curl payload — nothing to scan
120
+ sys.exit(0)
121
+
122
+ # gh shape: existing extraction paths
123
+ # 1. Heredoc body (handles quoted or unquoted marker)
124
+ m = re.search(r"cat\s*<<\s*[\x27\"]?(\w+)[\x27\"]?\s*\n(.*?)\n\1", cmd, re.DOTALL)
125
+ if m:
126
+ sys.stdout.write(m.group(2))
127
+ sys.exit(0)
128
+ # 2. --body-file <path>
129
+ m = re.search(r"--body-file\s+(\S+)", cmd)
130
+ if m:
131
+ path = m.group(1).strip("\x27\"")
132
+ try:
133
+ with open(path) as f:
134
+ sys.stdout.write(f.read())
135
+ except Exception:
136
+ pass
137
+ sys.exit(0)
138
+ # 3. Inline --body "..." (handle escaped quotes)
139
+ m = re.search(r"--body\s+\"((?:[^\"\\\\]|\\\\.)*)\"", cmd, re.DOTALL)
140
+ if m:
141
+ sys.stdout.write(m.group(1))
142
+ sys.exit(0)
143
+ m = re.search(r"--body\s+\x27((?:[^\x27\\\\]|\\\\.)*)\x27", cmd, re.DOTALL)
144
+ if m:
145
+ sys.stdout.write(m.group(1))
146
+ sys.exit(0)
147
+ # Parse failure — emit nothing; caller treats as advisory pass
148
+ ' 2>/dev/null)
149
+
150
+ if [ -z "$EXTRACTED" ]; then
151
+ # Parse failure — fall back to advisory (allow). The CI hook is the backstop.
152
+ exit 0
153
+ fi
154
+
155
+ printf '%s' "$EXTRACTED" > "$TMP_BODY"
156
+
157
+ # Extract PR title for Rule 7 (bassclef#974) — check for Closes-keyword when
158
+ # title carries feat(#NNN) or fix(#NNN). Advisory only in V1.
159
+ PR_TITLE=$(COMMAND="$COMMAND" python3 -c '
160
+ import os, re, sys
161
+ cmd = os.environ.get("COMMAND", "")
162
+ m = re.search(r"--title\s+\"([^\"]+)\"", cmd) or re.search(r"--title\s+\x27([^\x27]+)\x27", cmd)
163
+ if m:
164
+ sys.stdout.write(m.group(1))
165
+ ' 2>/dev/null)
166
+
167
+ # Run the shared scrub — exit 1 means Rule 1 findings (CI's blocking semantic)
168
+ PR_TITLE="$PR_TITLE" bash "$SCRUB_SCRIPT" "$TMP_BODY" >&2 2>&1
169
+ SCRUB_RC=$?
170
+
171
+ if [ "$SCRUB_RC" = "1" ]; then
172
+ cat >&2 <<MSG
173
+
174
+ ============================================
175
+ 🛑 pre-gh-pr-body-scrub — BLOCKED LOCALLY
176
+ ============================================
177
+
178
+ The PR body extracted from the gh command contains bassclef-internal
179
+ jargon (Rule 1). The CI hook would have caught this after push; this
180
+ local hook catches it before the call reaches GitHub.
181
+
182
+ Rewrite the body using plain-English alternatives from
183
+ standards/bassclef-internal-jargon.md.
184
+
185
+ Override (logged): SKIP_PRE_GH_PR_BODY_SCRUB=1 <command>
186
+ Per bassclef#1176.
187
+
188
+ MSG
189
+ exit 2
190
+ fi
191
+
192
+ exit 0
@@ -0,0 +1,172 @@
1
+ #!/bin/bash
2
+ # tier: lite
3
+ # pre-gh-pr-title-scrub — PreToolUse hook that runs the body scrub logic
4
+ # LOCALLY against the --title argument of a `gh pr create` or
5
+ # `gh pr edit --title` Bash invocation. Sister to pre-gh-pr-body-scrub.sh
6
+ # (bassclef#1176) and pre-git-commit-msg-scrub.sh (bassclef#1177);
7
+ # extends the same Bash-matcher + shared-scrub pattern to the title surface.
8
+ #
9
+ # bassclef#1201 — closes the gap that PR body + commit message had covered
10
+ # but PR title had not. Titles are the most-scanned text in any PR list;
11
+ # jargon there compounds across every operator who scrolls.
12
+ #
13
+ # Exit codes:
14
+ # 0 — allow (non-matching Bash, no --title in command, clean title, or override)
15
+ # 2 — block (Rule 1 findings present in the extracted title)
16
+ #
17
+ # Override: SKIP_PRE_GH_PR_TITLE_SCRUB=1 (logged via trace-helper)
18
+
19
+ set +e # never crash the tool call; fall through to allow on any unexpected error
20
+
21
+ INPUT=$(cat 2>/dev/null)
22
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
23
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null)
24
+
25
+ # Only fire on Bash
26
+ if [ "$TOOL_NAME" != "Bash" ]; then
27
+ exit 0
28
+ fi
29
+
30
+ # Match the surfaces this hook covers: gh pr create, gh pr edit, and
31
+ # curl REST equivalents (POST/PATCH against /pulls or /issues).
32
+ # bassclef#1239 — curl REST fallback used to bypass every scrub hook when
33
+ # GraphQL rate-limit exhausted gh CLI.
34
+ IS_CURL=0
35
+ case "$COMMAND" in
36
+ *"gh pr create"*|*"gh pr edit"*)
37
+ : # match — continue to title extraction
38
+ ;;
39
+ *curl*api.github.com*/pulls*|*curl*api.github.com*/issues*)
40
+ # curl REST shape — verify it's a write (POST or PATCH), not GET
41
+ case "$COMMAND" in
42
+ *"-X POST"*|*"-X PATCH"*|*"--request POST"*|*"--request PATCH"*)
43
+ IS_CURL=1
44
+ ;;
45
+ *)
46
+ exit 0 # read-only curl (GET / HEAD) — nothing to scan
47
+ ;;
48
+ esac
49
+ ;;
50
+ *)
51
+ exit 0
52
+ ;;
53
+ esac
54
+
55
+ # Override hatch
56
+ SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
57
+ if [ "${SKIP_PRE_GH_PR_TITLE_SCRUB:-0}" = "1" ]; then
58
+ if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
59
+ source "$SCRIPT_DIR/trace-helper.sh" 2>/dev/null || true
60
+ trace_log "pre-gh-pr-title-scrub" "OVERRIDE gh pr create|edit" 2>/dev/null || true
61
+ fi
62
+ exit 0
63
+ fi
64
+
65
+ # Locate the shared scrub script
66
+ SCRUB_SCRIPT="${SCRIPT_DIR}/pr-body-scrub-check.sh"
67
+ if [ ! -f "$SCRUB_SCRIPT" ]; then
68
+ # Shared scrub missing — degrade to advisory (allow)
69
+ exit 0
70
+ fi
71
+
72
+ # Extract title content from the command. Two quote forms:
73
+ # 1. Double-quoted: --title "..."
74
+ # 2. Single-quoted: --title '...'
75
+ # If no --title flag present, exit 0 (interactive title flow; nothing to scan).
76
+ TMP_TITLE=$(mktemp -t title-scrub.XXXXXX 2>/dev/null) || exit 0
77
+ trap 'rm -f "$TMP_TITLE"' EXIT
78
+
79
+ EXTRACTED=$(COMMAND="$COMMAND" IS_CURL="$IS_CURL" python3 -c '
80
+ import os, re, sys, json
81
+ cmd = os.environ.get("COMMAND", "")
82
+ is_curl = os.environ.get("IS_CURL", "0") == "1"
83
+
84
+ if is_curl:
85
+ # curl REST shape — extract title from --data @<file> or -d "<inline>"
86
+ # bassclef#1239
87
+ # First try: --data @<path>
88
+ m = re.search(r"(?:--data|-d)\s+@(\S+)", cmd)
89
+ if m:
90
+ path = m.group(1)
91
+ try:
92
+ with open(path) as f:
93
+ payload = json.load(f)
94
+ title = payload.get("title", "")
95
+ if title:
96
+ sys.stdout.write(title)
97
+ sys.exit(0)
98
+ except (OSError, json.JSONDecodeError):
99
+ pass
100
+ # Try inline -d single-quoted JSON
101
+ m = re.search(r"(?:--data|-d)\s+\x27([^\x27]*)\x27", cmd, re.DOTALL)
102
+ if m:
103
+ try:
104
+ payload = json.loads(m.group(1))
105
+ title = payload.get("title", "")
106
+ if title:
107
+ sys.stdout.write(title)
108
+ sys.exit(0)
109
+ except json.JSONDecodeError:
110
+ pass
111
+ # Try inline -d double-quoted JSON
112
+ m = re.search(r"(?:--data|-d)\s+\"((?:[^\"\\\\]|\\\\.)*)\"", cmd, re.DOTALL)
113
+ if m:
114
+ try:
115
+ payload = json.loads(m.group(1).replace("\\\"", "\""))
116
+ title = payload.get("title", "")
117
+ if title:
118
+ sys.stdout.write(title)
119
+ sys.exit(0)
120
+ except json.JSONDecodeError:
121
+ pass
122
+ # No title extractable from curl payload — nothing to scan
123
+ sys.exit(0)
124
+
125
+ # gh shape: extract --title argument
126
+ # Double-quoted --title "..."
127
+ m = re.search(r"--title\s+\"((?:[^\"\\\\]|\\\\.)*)\"", cmd, re.DOTALL)
128
+ if m:
129
+ sys.stdout.write(m.group(1))
130
+ sys.exit(0)
131
+ # Single-quoted --title '"'"'...'"'"'
132
+ m = re.search(r"--title\s+\x27((?:[^\x27\\\\]|\\\\.)*)\x27", cmd, re.DOTALL)
133
+ if m:
134
+ sys.stdout.write(m.group(1))
135
+ sys.exit(0)
136
+ # No --title flag — nothing to scan
137
+ ' 2>/dev/null)
138
+
139
+ if [ -z "$EXTRACTED" ]; then
140
+ # No title to scan (interactive flow, or --title absent) — allow
141
+ exit 0
142
+ fi
143
+
144
+ printf '%s' "$EXTRACTED" > "$TMP_TITLE"
145
+
146
+ # Run the shared scrub with SCRUB_SURFACE=title so BLOCK-title terms fire
147
+ # (bassclef#1241 WU-2 — substrate-defect: blocks title surface, not body).
148
+ SCRUB_SURFACE=title bash "$SCRUB_SCRIPT" "$TMP_TITLE" >&2 2>&1
149
+ SCRUB_RC=$?
150
+
151
+ if [ "$SCRUB_RC" = "1" ]; then
152
+ cat >&2 <<MSG
153
+
154
+ ============================================
155
+ 🛑 pre-gh-pr-title-scrub — BLOCKED LOCALLY
156
+ ============================================
157
+
158
+ The PR title extracted from the gh command contains bassclef-internal
159
+ jargon (Rule 1). Titles are the most-scanned text in any PR list;
160
+ jargon there compounds across every operator who scrolls.
161
+
162
+ Rewrite the title using plain-English alternatives from
163
+ standards/bassclef-internal-jargon.md.
164
+
165
+ Override (logged): SKIP_PRE_GH_PR_TITLE_SCRUB=1 <command>
166
+ Per bassclef#1201.
167
+
168
+ MSG
169
+ exit 2
170
+ fi
171
+
172
+ exit 0
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ # tier: lite
3
+ # role-entry-gate.sh — mode-agnostic role-entry discipline (bassclef#1145 T13)
4
+ #
5
+ # Fires on PreToolUse Edit|Write|MultiEdit. Detects work-shape from file_path
6
+ # and BLOCKs when the matching agent role marker is absent for the current
7
+ # session.
8
+ #
9
+ # MVP scope: Builder role only (source code matching). Architect / Reviewer /
10
+ # Designer role detection deferred to a follow-on ticket — the design choices
11
+ # on multi-role matching deserve their own pass.
12
+ #
13
+ # Marker convention:
14
+ # $CWD/state/markers/role/<role>/<session_id>.marker
15
+ #
16
+ # The marker is written by the agent (or the operator-invoked /role-entry
17
+ # skill, deferred to follow-on) once the role's persona has been loaded.
18
+ # This hook reads the marker; it does not write.
19
+ #
20
+ # Mode-agnostic behavior:
21
+ # - sequential mode: BLOCK message tells Claude to read the agent persona
22
+ # itself, write the marker, then proceed
23
+ # - orchestrator-gated mode: BLOCK message tells Orchestrator to route the
24
+ # next-step dispatch to the matching role agent
25
+ #
26
+ # Exit codes:
27
+ # 0 — pass (override, skip, marker present)
28
+ # 3 — BLOCK (source-code edit without Builder marker)
29
+ #
30
+ # Override: SKIP_ROLE_ENTRY=1 — logged via trace-helper (bassclef idiom).
31
+
32
+ set -u
33
+
34
+ # --- Override path ---
35
+ if [ "${SKIP_ROLE_ENTRY:-0}" = "1" ]; then
36
+ echo "SKIP_ROLE_ENTRY=1 — role-entry gate bypassed" >&2
37
+ exit 0
38
+ fi
39
+
40
+ # --- Read stdin (fail-soft on malformed JSON per bassclef#1101 pattern) ---
41
+ INPUT=$(cat)
42
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null || echo "")
43
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null || echo "")
44
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null || echo "unknown")
45
+ CWD=$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null || echo ".")
46
+
47
+ # Malformed JSON or missing tool_name → pass silently (no work-shape signal)
48
+ if [ -z "$TOOL_NAME" ]; then
49
+ exit 0
50
+ fi
51
+
52
+ # --- Tool-name matcher ---
53
+ case "$TOOL_NAME" in
54
+ Edit|Write|MultiEdit) ;;
55
+ *) exit 0 ;;
56
+ esac
57
+
58
+ # --- Path matcher: source code per common languages ---
59
+ # MVP language list per bassclef tech_stack vocabulary. Stack-bound matchers
60
+ # can be tightened later by reading tech_stack.lang from bassclef-configs.jsonc.
61
+ case "$FILE_PATH" in
62
+ *.ts|*.tsx|*.js|*.jsx|*.py|*.rb|*.go|*.rs|*.java|*.kt|*.swift|*.cs|*.cpp|*.c|*.h)
63
+ EXPECTED_ROLE="builder"
64
+ ;;
65
+ *)
66
+ # Not a source file → no role discipline applies in MVP
67
+ exit 0
68
+ ;;
69
+ esac
70
+
71
+ # --- Marker check ---
72
+ MARKER_PATH="${CWD}/state/markers/role/${EXPECTED_ROLE}/${SESSION_ID}.marker"
73
+
74
+ if [ -f "$MARKER_PATH" ]; then
75
+ # Marker present → pass. Freshness window check deferred to follow-on.
76
+ exit 0
77
+ fi
78
+
79
+ # --- BLOCK ---
80
+ cat >&2 <<BLOCK
81
+ ============================================
82
+ 🛑 role-entry-gate — BLOCKED
83
+ ============================================
84
+
85
+ About to do ${EXPECTED_ROLE}-shaped work (editing ${FILE_PATH}) but the
86
+ ${EXPECTED_ROLE} agent role has not been entered for this session.
87
+
88
+ Expected marker (absent):
89
+ ${MARKER_PATH}
90
+
91
+ Two paths forward depending on session mode:
92
+
93
+ Sequential mode (default):
94
+ 1. Read ~/.claude/agents/${EXPECTED_ROLE}.md to load luminaries + responsibilities
95
+ 2. Write the marker:
96
+ mkdir -p $(dirname "$MARKER_PATH")
97
+ cat > "$MARKER_PATH" <<EOF
98
+ {"role":"${EXPECTED_ROLE}","session_id":"${SESSION_ID}","entered_at":"\$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
99
+ EOF
100
+ 3. Re-run the edit — the marker now satisfies the gate.
101
+
102
+ Orchestrator-gated mode:
103
+ Orchestrator routes this edit to the ${EXPECTED_ROLE} agent for
104
+ sandbox execution. The role agent writes its own marker before
105
+ proceeding.
106
+
107
+ Override (logged):
108
+ SKIP_ROLE_ENTRY=1 <command>
109
+
110
+ Per bassclef#1145 T13 (this hook) and .claude/agents/${EXPECTED_ROLE}.md (the
111
+ role persona this gate routes to).
112
+ ============================================
113
+ BLOCK
114
+
115
+ exit 3