@thebassclef/lite 0.0.1 → 0.1.0
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/LICENSE +202 -0
- package/README.md +85 -4
- package/dist/cli.cjs +1610 -0
- package/dist/cli.js +1587 -0
- package/dist/index.cjs +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/package.json +51 -3
- package/substrate/.bassclef/lite-manifest.json +1794 -0
- package/substrate/.claude/agents/architect.md +70 -0
- package/substrate/.claude/agents/builder.md +114 -0
- package/substrate/.claude/agents/designer.md +156 -0
- package/substrate/.claude/agents/reviewer.md +88 -0
- package/substrate/.claude/hooks/artifact-ingestion-gate.sh +357 -0
- package/substrate/.claude/hooks/assert-verify-steering.sh +77 -0
- package/substrate/.claude/hooks/bassclef-source-config-validate.sh +215 -0
- package/substrate/.claude/hooks/bassclef-sync.sh +634 -0
- package/substrate/.claude/hooks/compound-noun-scrub.sh +292 -0
- package/substrate/.claude/hooks/kiss-expansion-inject.sh +69 -0
- package/substrate/.claude/hooks/plain-english-steering.sh +156 -0
- package/substrate/.claude/hooks/post-skill-friction-check.sh +177 -0
- package/substrate/.claude/hooks/post-skill-telemetry.sh +62 -0
- package/substrate/.claude/hooks/pre-build-gate.sh +511 -0
- package/substrate/.claude/hooks/pre-commit-gate.sh +452 -0
- package/substrate/.claude/hooks/session-end.sh +433 -0
- package/substrate/.claude/hooks/session-reflection.sh +303 -0
- package/substrate/.claude/hooks/skill-body-grade-gate.sh +219 -0
- package/substrate/.claude/hooks/skill-body-intent-drift.sh +107 -0
- package/substrate/.claude/hooks/skill-step-list-check.sh +171 -0
- package/substrate/.claude/hooks/state-validate.sh +271 -0
- package/substrate/.claude/hooks/substrate-clarity-gate.sh +1110 -0
- package/substrate/.claude/hooks/temperance-gate.sh +147 -0
- package/substrate/.claude/hooks/testing-tier-enforce.sh +233 -0
- package/substrate/.claude/hooks/turn-prose-grade-measure.sh +219 -0
- package/substrate/.claude/hooks/turn-prose-kiss-check.sh +434 -0
- package/substrate/.claude/hooks/vocabulary-migration-check.sh +171 -0
- package/substrate/.claude/hooks/whereami-utc-gate.sh +142 -0
- package/substrate/.claude/luminaries/alan-cooper.md +170 -0
- package/substrate/.claude/luminaries/alistair-cockburn.md +140 -0
- package/substrate/.claude/luminaries/amazon-pr-faq.md +34 -0
- package/substrate/.claude/luminaries/ash-maurya.md +121 -0
- package/substrate/.claude/luminaries/bill-buxton.md +210 -0
- package/substrate/.claude/luminaries/charles-sanders-peirce.md +150 -0
- package/substrate/.claude/luminaries/david-ogilvy.md +192 -0
- package/substrate/.claude/luminaries/don-norman.md +173 -0
- package/substrate/.claude/luminaries/edward-tufte.md +179 -0
- package/substrate/.claude/luminaries/eric-evans.md +160 -0
- package/substrate/.claude/luminaries/frederick-brooks.md +50 -0
- package/substrate/.claude/luminaries/gang-of-four.md +157 -0
- package/substrate/.claude/luminaries/jason-fried-dhh.md +46 -0
- package/substrate/.claude/luminaries/jesse-james-garrett.md +154 -0
- package/substrate/.claude/luminaries/john-ousterhout.md +94 -0
- package/substrate/.claude/luminaries/karl-popper.md +132 -0
- package/substrate/.claude/luminaries/kent-beck.md +168 -0
- package/substrate/.claude/luminaries/linus-torvalds.md +218 -0
- package/substrate/.claude/luminaries/martin-fowler.md +164 -0
- package/substrate/.claude/luminaries/michael-feathers.md +125 -0
- package/substrate/.claude/luminaries/michael-nygard.md +118 -0
- package/substrate/.claude/luminaries/robert-c-martin.md +164 -0
- package/substrate/.claude/luminaries/saltzer-schroeder.md +194 -0
- package/substrate/.claude/luminaries/sophia-prater.md +193 -0
- package/substrate/.claude/luminaries/stephen-toulmin.md +131 -0
- package/substrate/.claude/rules/artifact-ingestion.md +179 -0
- package/substrate/.claude/rules/assert-only-after-verify.md +137 -0
- package/substrate/.claude/rules/blocked-items.md +146 -0
- package/substrate/.claude/rules/branching.md +28 -0
- package/substrate/.claude/rules/commit-conventions.md +22 -0
- package/substrate/.claude/rules/context-engineering.md +202 -0
- package/substrate/.claude/rules/context-management.md +85 -0
- package/substrate/.claude/rules/destructive-operations.md +69 -0
- package/substrate/.claude/rules/diagnosis.md +38 -0
- package/substrate/.claude/rules/guardrails.md +73 -0
- package/substrate/.claude/rules/identifier-leak-prevention.md +104 -0
- package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +98 -0
- package/substrate/.claude/rules/oo-ad-entry-point.md +117 -0
- package/substrate/.claude/rules/operator-facing-prose.md +196 -0
- package/substrate/.claude/rules/plain-english-discipline.md +154 -0
- package/substrate/.claude/rules/pr-body-shape.md +317 -0
- package/substrate/.claude/rules/pr-strategy.md +167 -0
- package/substrate/.claude/rules/sdlc-gates.md +149 -0
- package/substrate/.claude/rules/session-artifacts.md +236 -0
- package/substrate/.claude/rules/state-schema-validation.md +162 -0
- package/substrate/.claude/rules/whereami-load-bearing.md +202 -0
- package/substrate/.claude/rules/writing-craft-discipline.md +92 -0
- package/substrate/.claude/skills/build/SKILL.md +640 -0
- package/substrate/.claude/skills/decompose/SKILL.md +280 -0
- package/substrate/.claude/skills/diagnose/SKILL.md +297 -0
- package/substrate/.claude/skills/howdoi/SKILL.md +105 -0
- package/substrate/.claude/skills/ia-model/SKILL.md +108 -0
- package/substrate/.claude/skills/interaction-design/SKILL.md +112 -0
- package/substrate/.claude/skills/interpret-input/SKILL.md +180 -0
- package/substrate/.claude/skills/kiss/SKILL.md +449 -0
- package/substrate/.claude/skills/launch/SKILL.md +924 -0
- package/substrate/.claude/skills/lean-canvas/SKILL.md +332 -0
- package/substrate/.claude/skills/longrun/SKILL.md +426 -0
- package/substrate/.claude/skills/luminary/SKILL.md +481 -0
- package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +177 -0
- package/substrate/.claude/skills/onboard-repo/SKILL.md +2190 -0
- package/substrate/.claude/skills/pattern-review/SKILL.md +99 -0
- package/substrate/.claude/skills/personas/SKILL.md +207 -0
- package/substrate/.claude/skills/promote/SKILL.md +283 -0
- package/substrate/.claude/skills/requirement/SKILL.md +98 -0
- package/substrate/.claude/skills/retro/SKILL.md +117 -0
- package/substrate/.claude/skills/riff/SKILL.md +114 -0
- package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +163 -0
- package/substrate/.claude/skills/session-end/SKILL.md +309 -0
- package/substrate/.claude/skills/session-log/SKILL.md +299 -0
- package/substrate/.claude/skills/skills/SKILL.md +228 -0
- package/substrate/.claude/skills/spec/SKILL.md +105 -0
- package/substrate/.claude/skills/sprint/SKILL.md +392 -0
- package/substrate/.claude/skills/stage/SKILL.md +384 -0
- package/substrate/.claude/skills/state-a-problem/SKILL.md +185 -0
- package/substrate/.claude/skills/temperance/SKILL.md +108 -0
- package/substrate/.claude/skills/use-case/SKILL.md +417 -0
- package/substrate/.claude/skills/user-stories/SKILL.md +268 -0
- package/substrate/.claude/skills/value-prop/SKILL.md +251 -0
- package/substrate/.claude/skills/verify/SKILL.md +160 -0
- package/substrate/.claude/skills/whereami/SKILL.md +307 -0
- package/substrate/AGENTS.md +79 -0
- package/substrate/CLAUDE-lite.md +85 -0
- package/substrate/CODE_OF_CONDUCT.md +28 -0
- package/substrate/CONTRIBUTING.md +177 -0
- package/substrate/README.md +173 -0
- package/substrate/SECURITY.md +19 -0
- package/substrate/architecture/decisions/ADR-029-release-pipeline.md +79 -0
- package/substrate/architecture/decisions/ADR-031-non-breaking-changes-adopter-discipline.md +139 -0
- package/substrate/architecture/decisions/ADR-032-adopter-sync-dispatcher-architecture.md +192 -0
- package/substrate/architecture/decisions/ADR-039-release-tagging-scheme.md +145 -0
- package/substrate/architecture/decisions/ADR-040-planning-skill-vocabulary-and-lite-profile.md +155 -0
- package/substrate/architecture/decisions/ADR-044-unified-skill-body-template.md +162 -0
- package/substrate/lib/clean-artifacts-sweep.sh +112 -0
- package/substrate/lib/code-comment-discipline.sh +144 -0
- package/substrate/lib/composer-preflight.sh +459 -0
- package/substrate/lib/hook-inject.sh +255 -0
- package/substrate/lib/luminary-pick.sh +96 -0
- package/substrate/lib/output-discipline.sh +143 -0
- package/substrate/lib/rewrite-check.sh +214 -0
- package/substrate/lib/state.sh +1372 -0
- package/substrate/lib/telemetry.sh +205 -0
- package/substrate/lib/tier-check.sh +138 -0
- package/substrate/lib/tier-dependency-audit.sh +933 -0
- package/substrate/presence/install/bassclef-hook-connect.sh +178 -0
- package/substrate/presence/install/bassclef-sync.dispatcher.template.sh +841 -0
- package/substrate/presence/install/bassclef-sync.template.sh +2076 -0
- package/substrate/presence/install/schedule-auto-save.cron.sh +88 -0
- package/substrate/presence/install/schedule-auto-save.taskscheduler.md +122 -0
- package/substrate/scripts/aggregate-telemetry.sh +217 -0
- package/substrate/scripts/analyze-tier-dependencies.sh +239 -0
- package/substrate/scripts/generate-lite-manifest.sh +489 -0
- package/substrate/scripts/generate-tier-manifest.sh +28 -0
- package/substrate/scripts/intent-drift-check.sh +456 -0
- package/substrate/scripts/lite-manifest-drift-check.sh +146 -0
- package/substrate/scripts/render-lite-manifest-doc.sh +150 -0
- package/substrate/standards/bassclef-configs-schema.md +232 -0
- package/substrate/standards/bassclef-evolution.md +143 -0
- package/substrate/standards/bassclef-internal-jargon.md +244 -0
- package/substrate/standards/bassclef-managed-sentinel.md +96 -0
- package/substrate/standards/bassclef-source-config.md +228 -0
- package/substrate/standards/deferred-actions-schema.md +204 -0
- package/substrate/standards/docs-sync-allowlist.md +76 -0
- package/substrate/standards/graceful-exit.md +227 -0
- package/substrate/standards/hook-idempotency.md +102 -0
- package/substrate/standards/lite-manifest-schema-changes.md +75 -0
- package/substrate/standards/luminary-problem-patterns.md +481 -0
- package/substrate/standards/ogilvy-writing-rules.md +225 -0
- package/substrate/standards/opener-discipline.md +96 -0
- package/substrate/standards/operator-facing-prose-discipline.md +201 -0
- package/substrate/standards/project-directory-layout.md +276 -0
- package/substrate/standards/release-tagging.md +137 -0
- package/substrate/standards/sdlc-compliance.md +286 -0
- package/substrate/standards/secrets-lifecycle.md +210 -0
- package/substrate/standards/security-scanner-adapter.md +145 -0
- package/substrate/standards/skill-output-discipline.md +90 -0
- package/substrate/standards/state-spine.md +511 -0
- package/substrate/standards/tech-stack-config.md +109 -0
- package/substrate/standards/tier-dependency-analysis.md +167 -0
- package/substrate/standards/tier-runtime-deps/lite.md +57 -0
- package/substrate/standards/tier-tag-schema.md +155 -0
- package/substrate/standards/whereami-schema.md +301 -0
- package/substrate/standards/writing-guide.md +213 -0
- package/substrate/templates/deferred-action-template.md +45 -0
- package/substrate/templates/whereami-template.md +101 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# install-class: dual
|
|
4
|
+
# compound-noun-scrub.sh — PreToolUse Edit|Write gate on
|
|
5
|
+
# .claude/skills/*/SKILL.md and .claude/rules/*.md.
|
|
6
|
+
#
|
|
7
|
+
# V2 — reads blocklist from standards/bassclef-internal-jargon.md
|
|
8
|
+
# (single source of truth per bassclef-upstream#782). No hardcoded
|
|
9
|
+
# allowlist. No heuristic detection. Literal match against BLOCK-tier
|
|
10
|
+
# hyphenated compounds in the wordlist file. Standard tech compounds
|
|
11
|
+
# like pre-commit, real-time, data-driven pass automatically because
|
|
12
|
+
# they are not in the blocklist. New invented compounds pass by default
|
|
13
|
+
# until the operator adds them to the wordlist.
|
|
14
|
+
#
|
|
15
|
+
# Sister to skill-body-grade-gate.sh (FKGL) and turn-prose-kiss-check.sh
|
|
16
|
+
# (which reads the same wordlist file for Stop-event prose scrub).
|
|
17
|
+
#
|
|
18
|
+
# Input (stdin): JSON with tool_name + tool_input.file_path + content/new_string
|
|
19
|
+
# Exit: 0 pass, 2 block
|
|
20
|
+
#
|
|
21
|
+
# Override: SKIP_COMPOUND_NOUN_SCRUB=1
|
|
22
|
+
# Wordlist path override (tests): BASSCLEF_JARGON_WORDLIST=<path>
|
|
23
|
+
#
|
|
24
|
+
# Per @luminary kent-beck (Tier 0 strict TDD)
|
|
25
|
+
# + @luminary donald-norman (plain-English target)
|
|
26
|
+
# + @luminary john-ousterhout (single source of truth wordlist).
|
|
27
|
+
|
|
28
|
+
set +e
|
|
29
|
+
|
|
30
|
+
# === Override ===
|
|
31
|
+
if [ "${SKIP_COMPOUND_NOUN_SCRUB:-0}" = "1" ]; then
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# === Parse stdin ===
|
|
36
|
+
STDIN_JSON=$(cat 2>/dev/null || echo '{}')
|
|
37
|
+
|
|
38
|
+
if ! echo "$STDIN_JSON" | jq -e . >/dev/null 2>&1; then
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
TOOL_NAME=$(echo "$STDIN_JSON" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
43
|
+
FILE_PATH=$(echo "$STDIN_JSON" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
|
|
44
|
+
|
|
45
|
+
# Only fire on Write or Edit
|
|
46
|
+
if [ "$TOOL_NAME" != "Write" ] && [ "$TOOL_NAME" != "Edit" ]; then
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Fire on SKILL.md, rule .md, OR bash source under scoped paths (bassclef-upstream#1051).
|
|
51
|
+
# Bash paths use ADVISORY mode by default; BASSCLEF_BASH_HEADER_STRICT=1 flips to BLOCK.
|
|
52
|
+
IS_BASH_PATH=0
|
|
53
|
+
case "$FILE_PATH" in
|
|
54
|
+
*".claude/skills/"*"/SKILL.md")
|
|
55
|
+
;;
|
|
56
|
+
*".claude/rules/"*.md)
|
|
57
|
+
;;
|
|
58
|
+
*".claude/hooks/"*.sh)
|
|
59
|
+
IS_BASH_PATH=1
|
|
60
|
+
;;
|
|
61
|
+
*"scripts/"*.sh)
|
|
62
|
+
IS_BASH_PATH=1
|
|
63
|
+
;;
|
|
64
|
+
*"lib/"*.sh)
|
|
65
|
+
IS_BASH_PATH=1
|
|
66
|
+
;;
|
|
67
|
+
*"presence/"*.sh)
|
|
68
|
+
IS_BASH_PATH=1
|
|
69
|
+
;;
|
|
70
|
+
*)
|
|
71
|
+
exit 0
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
|
|
75
|
+
# === Extract text ===
|
|
76
|
+
if [ "$TOOL_NAME" = "Write" ]; then
|
|
77
|
+
CONTENT=$(echo "$STDIN_JSON" | jq -r '.tool_input.content // ""' 2>/dev/null)
|
|
78
|
+
else
|
|
79
|
+
CONTENT=$(echo "$STDIN_JSON" | jq -r '.tool_input.new_string // ""' 2>/dev/null)
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
[ -z "$CONTENT" ] && exit 0
|
|
83
|
+
|
|
84
|
+
# === Bash path handling (bassclef-upstream#1051) ===
|
|
85
|
+
# For bash paths, extract only the header comment block and require the
|
|
86
|
+
# `# tier:` header tag. Adopter files without the tier tag pass through
|
|
87
|
+
# untouched (Linus R2.1 adopter safety).
|
|
88
|
+
if [ "$IS_BASH_PATH" = "1" ]; then
|
|
89
|
+
# Extract header comment block — starting from the first line of #-comments
|
|
90
|
+
# (skipping shebang if present), stopping at first non-# non-blank line.
|
|
91
|
+
HEADER=$(printf '%s' "$CONTENT" | awk '
|
|
92
|
+
BEGIN { started=0 }
|
|
93
|
+
NR==1 && /^#!/ { next } # skip shebang
|
|
94
|
+
/^#/ { print; started=1; next }
|
|
95
|
+
/^[[:space:]]*$/ { if (started==0) next; else print; next }
|
|
96
|
+
{ exit }
|
|
97
|
+
')
|
|
98
|
+
|
|
99
|
+
# Adopter safety — require `# tier:` marker (bassclef convention).
|
|
100
|
+
# Absent tier tag → adopter-authored, pass through silently.
|
|
101
|
+
if ! echo "$HEADER" | grep -qE '^# ?tier:'; then
|
|
102
|
+
exit 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# Replace CONTENT with just the header block for the scan
|
|
106
|
+
CONTENT="$HEADER"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# === Locate wordlist ===
|
|
110
|
+
# Test override wins; then repo-relative from hook location; then fail-safe.
|
|
111
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
112
|
+
WORDLIST="${BASSCLEF_JARGON_WORDLIST:-$SCRIPT_DIR/../../standards/bassclef-internal-jargon.md}"
|
|
113
|
+
|
|
114
|
+
if [ ! -f "$WORDLIST" ]; then
|
|
115
|
+
echo "" >&2
|
|
116
|
+
echo "compound-noun-scrub: wordlist file not found at $WORDLIST" >&2
|
|
117
|
+
echo " Fail-safe: passing through. Add wordlist file to enable blocklist scan." >&2
|
|
118
|
+
echo "" >&2
|
|
119
|
+
exit 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# === Scrub via python — reads blocklist + matches literal terms ===
|
|
123
|
+
SCRUB_OUTPUT=$(printf '%s' "$CONTENT" | WORDLIST="$WORDLIST" python3 -c '
|
|
124
|
+
import sys, re, os
|
|
125
|
+
|
|
126
|
+
text = sys.stdin.read()
|
|
127
|
+
|
|
128
|
+
# === Strip surfaces the scrub should ignore ===
|
|
129
|
+
|
|
130
|
+
# Strip YAML frontmatter
|
|
131
|
+
text = re.sub(r"^---\n.*?\n---\n", "", text, count=1, flags=re.DOTALL)
|
|
132
|
+
|
|
133
|
+
# Strip FKGL-EXEMPT blocks (matches skill-body-grade-gate.sh L77-L78 per #800)
|
|
134
|
+
text = re.sub(r"<!--\s*FKGL-EXEMPT:.*?-->.*?<!--\s*/FKGL-EXEMPT\s*-->",
|
|
135
|
+
"", text, flags=re.DOTALL)
|
|
136
|
+
|
|
137
|
+
# Strip code blocks (```...```)
|
|
138
|
+
text = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
|
|
139
|
+
|
|
140
|
+
# Strip inline backticks
|
|
141
|
+
text = re.sub(r"`[^`]*`", "", text)
|
|
142
|
+
|
|
143
|
+
# Strip URLs (http:// or https:// through whitespace)
|
|
144
|
+
text = re.sub(r"https?://\S+", "", text)
|
|
145
|
+
|
|
146
|
+
# Strip slash-prefixed refs (/skill-name, /roadmap-reconcile) — proper nouns
|
|
147
|
+
text = re.sub(r"/[a-zA-Z]+(-[a-zA-Z]+)+", "", text)
|
|
148
|
+
|
|
149
|
+
# Strip file paths (foo/bar-baz.md) — proper nouns
|
|
150
|
+
text = re.sub(r"[a-zA-Z_./-]*/[a-zA-Z_.-]+\.[a-z]+", "", text)
|
|
151
|
+
|
|
152
|
+
# === Read blocklist from wordlist file ===
|
|
153
|
+
wordlist_path = os.environ.get("WORDLIST", "")
|
|
154
|
+
blocklist = set()
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
with open(wordlist_path, "r") as f:
|
|
158
|
+
lines = f.readlines()
|
|
159
|
+
except (OSError, IOError):
|
|
160
|
+
print("PASS")
|
|
161
|
+
sys.exit(0)
|
|
162
|
+
|
|
163
|
+
# Parse markdown table — extract BLOCK-tier rows from column 1
|
|
164
|
+
# Row shape: | Term | Plain English | Tier |
|
|
165
|
+
# Tier column may be BLOCK, BLOCK-title, ADVISE, ALLOW
|
|
166
|
+
for line in lines:
|
|
167
|
+
line = line.strip()
|
|
168
|
+
if not line.startswith("|") or not line.endswith("|"):
|
|
169
|
+
continue
|
|
170
|
+
cells = [c.strip() for c in line.split("|")[1:-1]]
|
|
171
|
+
if len(cells) != 3:
|
|
172
|
+
continue
|
|
173
|
+
term_cell, _plain, tier = cells
|
|
174
|
+
|
|
175
|
+
# Only BLOCK tier — BLOCK-title is title-only scrub (different hook)
|
|
176
|
+
if tier != "BLOCK":
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Extract the term BEFORE any parenthetical context.
|
|
180
|
+
# Row example: "OR hand-roll (soft-INSTEAD escape per bassclef-upstream#375)"
|
|
181
|
+
# Take "OR hand-roll" — then extract hyphenated compounds within.
|
|
182
|
+
term_part = term_cell.split("(", 1)[0].strip()
|
|
183
|
+
|
|
184
|
+
# Extract every lowercase-alpha hyphenated compound in the term part.
|
|
185
|
+
# Compound pattern: alpha (2+ chars) then one-or-more hyphenated alpha (2+ chars).
|
|
186
|
+
compounds = re.findall(r"\b[a-z][a-z]+(?:-[a-z][a-z]+)+\b", term_part.lower())
|
|
187
|
+
for c in compounds:
|
|
188
|
+
# Skip bassclef proper nouns that leak in (bassclef-upstream, bassclef-web, etc.)
|
|
189
|
+
if c.startswith("bassclef-"):
|
|
190
|
+
continue
|
|
191
|
+
blocklist.add(c)
|
|
192
|
+
|
|
193
|
+
if not blocklist:
|
|
194
|
+
# Empty blocklist → fail safe (do not block anything)
|
|
195
|
+
print("PASS")
|
|
196
|
+
sys.exit(0)
|
|
197
|
+
|
|
198
|
+
# === Match literal blocklist terms in scan text ===
|
|
199
|
+
flagged = []
|
|
200
|
+
seen = set()
|
|
201
|
+
|
|
202
|
+
text_lower = text.lower()
|
|
203
|
+
for term in sorted(blocklist):
|
|
204
|
+
# Word-boundary match, case-insensitive already via text_lower
|
|
205
|
+
if re.search(r"\b" + re.escape(term) + r"\b", text_lower):
|
|
206
|
+
if term not in seen:
|
|
207
|
+
flagged.append(term)
|
|
208
|
+
seen.add(term)
|
|
209
|
+
|
|
210
|
+
if flagged:
|
|
211
|
+
print("BLOCK", len(flagged))
|
|
212
|
+
for f in flagged[:10]:
|
|
213
|
+
print(f" FLAG {f}")
|
|
214
|
+
else:
|
|
215
|
+
print("PASS")
|
|
216
|
+
' 2>/dev/null)
|
|
217
|
+
|
|
218
|
+
# === Parse result ===
|
|
219
|
+
FIRST_LINE=$(echo "$SCRUB_OUTPUT" | head -1)
|
|
220
|
+
VERDICT=$(echo "$FIRST_LINE" | awk '{print $1}')
|
|
221
|
+
|
|
222
|
+
case "$VERDICT" in
|
|
223
|
+
PASS|"")
|
|
224
|
+
exit 0
|
|
225
|
+
;;
|
|
226
|
+
BLOCK)
|
|
227
|
+
COUNT=$(echo "$FIRST_LINE" | awk '{print $2}')
|
|
228
|
+
;;
|
|
229
|
+
*)
|
|
230
|
+
exit 0
|
|
231
|
+
;;
|
|
232
|
+
esac
|
|
233
|
+
|
|
234
|
+
# === Determine emit mode ===
|
|
235
|
+
# Bash paths use ADVISORY (exit 0 + stderr note) unless
|
|
236
|
+
# BASSCLEF_BASH_HEADER_STRICT=1 flips to BLOCK. Non-bash paths always BLOCK.
|
|
237
|
+
EMIT_MODE="BLOCK"
|
|
238
|
+
if [ "$IS_BASH_PATH" = "1" ] && [ "${BASSCLEF_BASH_HEADER_STRICT:-0}" != "1" ]; then
|
|
239
|
+
EMIT_MODE="ADVISORY"
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# === Emit ===
|
|
243
|
+
if [ "$EMIT_MODE" = "ADVISORY" ]; then
|
|
244
|
+
# ADVISORY — bash path header jargon (bassclef-upstream#1051)
|
|
245
|
+
echo "" >&2
|
|
246
|
+
echo "⚠️ COMPOUND-NOUN-SCRUB — ADVISORY (bash header)" >&2
|
|
247
|
+
echo "" >&2
|
|
248
|
+
echo " File: $FILE_PATH" >&2
|
|
249
|
+
echo " Found $COUNT jargon compound(s) in header comment from standards/bassclef-internal-jargon.md:" >&2
|
|
250
|
+
echo "$SCRUB_OUTPUT" | grep '^ FLAG' | while read -r _ compound; do
|
|
251
|
+
echo " - $compound (see wordlist for plain-English substitute)" >&2
|
|
252
|
+
done
|
|
253
|
+
echo "" >&2
|
|
254
|
+
echo " Advisory only — exit 0. Set BASSCLEF_BASH_HEADER_STRICT=1 to make bash headers block." >&2
|
|
255
|
+
echo " Override: SKIP_COMPOUND_NOUN_SCRUB=1 (logged via trace-helper)" >&2
|
|
256
|
+
echo "" >&2
|
|
257
|
+
|
|
258
|
+
# Trace
|
|
259
|
+
if [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
|
|
260
|
+
# shellcheck disable=SC1091
|
|
261
|
+
source "$SCRIPT_DIR/trace-helper.sh"
|
|
262
|
+
trace_log "compound-noun-scrub" "ADVISORY count=$COUNT file=$FILE_PATH"
|
|
263
|
+
fi
|
|
264
|
+
exit 0
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
# === Emit BLOCK ===
|
|
268
|
+
echo "" >&2
|
|
269
|
+
echo "🛑 COMPOUND-NOUN-SCRUB — BLOCKED" >&2
|
|
270
|
+
echo "" >&2
|
|
271
|
+
echo " File: $FILE_PATH" >&2
|
|
272
|
+
echo " Found $COUNT invented compound(s) from standards/bassclef-internal-jargon.md:" >&2
|
|
273
|
+
echo "$SCRUB_OUTPUT" | grep '^ FLAG' | while read -r _ compound; do
|
|
274
|
+
echo " - $compound" >&2
|
|
275
|
+
done
|
|
276
|
+
echo "" >&2
|
|
277
|
+
echo " Cure paths:" >&2
|
|
278
|
+
echo " 1. Rewrite the sentence in plain SVO English per standards/bassclef-internal-jargon.md" >&2
|
|
279
|
+
echo " 2. If the term is genuinely needed as a proper noun, mark exempt via <!-- FKGL-EXEMPT -->" >&2
|
|
280
|
+
echo " 3. If the term should not be on the blocklist, propose a wordlist amendment" >&2
|
|
281
|
+
echo "" >&2
|
|
282
|
+
echo " Override: SKIP_COMPOUND_NOUN_SCRUB=1 (logged via trace-helper)" >&2
|
|
283
|
+
echo "" >&2
|
|
284
|
+
|
|
285
|
+
# === Trace ===
|
|
286
|
+
if [ -f "$SCRIPT_DIR/trace-helper.sh" ]; then
|
|
287
|
+
# shellcheck disable=SC1091
|
|
288
|
+
source "$SCRIPT_DIR/trace-helper.sh"
|
|
289
|
+
trace_log "compound-noun-scrub" "BLOCKED count=$COUNT file=$FILE_PATH"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
exit 2
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# install-class: dual
|
|
4
|
+
# kiss-expansion-inject.sh — UserPromptExpansion hook that injects
|
|
5
|
+
# additionalContext when /kiss expands. Per bet 2026-07-01e WU-1 +
|
|
6
|
+
# ADR-035 lines 133 + 185 (UserPromptExpansion steering surface).
|
|
7
|
+
#
|
|
8
|
+
# Sources lib/hook-inject.sh + calls emit_base_pair_reminders (shared
|
|
9
|
+
# plain-English + assert-verify content) + adds kiss-specific reminders
|
|
10
|
+
# (substitution table + reminder that /kiss preserves claim shape).
|
|
11
|
+
#
|
|
12
|
+
# Sibling pattern: assert-verify-steering.sh + plain-english-steering.sh
|
|
13
|
+
# (UserPromptSubmit siblings — background steering at prompt start).
|
|
14
|
+
# This hook fires per-invocation at skill expansion — steering couples
|
|
15
|
+
# to skill intent, not background prompt-start reminders that go stale.
|
|
16
|
+
#
|
|
17
|
+
# Override per-call: SKIP_KISS_EXPANSION_INJECT=1
|
|
18
|
+
#
|
|
19
|
+
# Wired in .claude/settings.json under UserPromptExpansion matcher "kiss".
|
|
20
|
+
# Tier 0 strict TDD: .claude/hooks/tests/kiss-expansion-inject.test.sh
|
|
21
|
+
#
|
|
22
|
+
# Anchors: @luminary saltzer-schroeder (complete mediation — every /kiss
|
|
23
|
+
# invocation gets the discipline reminder) + @luminary john-ousterhout
|
|
24
|
+
# (deep module in lib/hook-inject.sh; five callers, one implementation).
|
|
25
|
+
|
|
26
|
+
set +e
|
|
27
|
+
|
|
28
|
+
# === Source install-class-aware lib (per standards/hook-install-class.md) ===
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
for _candidate in \
|
|
31
|
+
"${SCRIPT_DIR}/../../lib/hook-inject.sh" \
|
|
32
|
+
"${HOME_OVERRIDE:-${HOME:-/}}/lib/hook-inject.sh"; do
|
|
33
|
+
if [ -f "$_candidate" ]; then
|
|
34
|
+
# shellcheck disable=SC1090
|
|
35
|
+
source "$_candidate"
|
|
36
|
+
break
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# Defensive: if lib not loaded, exit silent (fail-soft per Nygard)
|
|
41
|
+
if ! declare -f emit_additional_context >/dev/null 2>&1; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
if ! declare -f emit_base_pair_reminders >/dev/null 2>&1; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Override per-call
|
|
49
|
+
if [ "${SKIP_KISS_EXPANSION_INJECT:-0}" = "1" ]; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# === Compose steering content ===
|
|
54
|
+
BASE_PAIR="$(emit_base_pair_reminders)"
|
|
55
|
+
|
|
56
|
+
KISS_SPECIFIC="
|
|
57
|
+
|
|
58
|
+
**/kiss-specific reminders:**
|
|
59
|
+
|
|
60
|
+
- Read the substitution table at standards/bassclef-internal-jargon.md before rewriting. BLOCK-tier terms substitute per the table; ADVISE-tier terms rewrite when clean; ALLOW terms (substrate, bassclef, cameo, Studio, Band, bandleader) stay.
|
|
61
|
+
- /kiss preserves claim shape while swapping vocabulary. Assertion grounding still applies. If the input text carries a claim without cited Evidence + Why-supports, /kiss does not rescue it — surface the missing grounding.
|
|
62
|
+
- Grade level is a target, not a mask. Plain English exposes weak assertions. Prefer clarity over camouflage.
|
|
63
|
+
- Every rewrite passes back through assert-verify: the plain-English version still needs My claim + Evidence + Why the evidence supports the claim for any operator-facing assertion the input carried."
|
|
64
|
+
|
|
65
|
+
STEERING_CONTENT="${BASE_PAIR}${KISS_SPECIFIC}"
|
|
66
|
+
|
|
67
|
+
# === Emit envelope ===
|
|
68
|
+
emit_additional_context "UserPromptExpansion" "$STEERING_CONTENT"
|
|
69
|
+
exit 0
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# tier: lite
|
|
3
|
+
# install-class: dual
|
|
4
|
+
# plain-english-steering.sh — UserPromptSubmit hook that injects
|
|
5
|
+
# additionalContext steering the agent toward grade-10 plain English
|
|
6
|
+
# at source. Per bassclef-upstream#443 + bet 28a WU-5c.
|
|
7
|
+
#
|
|
8
|
+
# Adds Layer 0 to the 5-layer prose-discipline defense
|
|
9
|
+
# (per .claude/rules/operator-facing-prose.md):
|
|
10
|
+
#
|
|
11
|
+
# Layer 0 (this hook, UserPromptSubmit) — steer at source
|
|
12
|
+
# Layer 1 (substrate-clarity-gate, PreToolUse Edit on substrate paths)
|
|
13
|
+
# Layer 2 (pre-gh-pr-body-scrub, PreToolUse Bash for gh pr create/edit)
|
|
14
|
+
# Layer 3 (pre-gh-issue-body-scrub + pre-git-commit-msg-scrub)
|
|
15
|
+
# Layer 4 (turn-prose-kiss-check, Stop hook scanning assistant message)
|
|
16
|
+
# Layer 5 (CI bassclef-pr-ci.yml jobs as backstop)
|
|
17
|
+
#
|
|
18
|
+
# Override per-call: SKIP_PLAIN_ENGLISH_STEERING=1
|
|
19
|
+
#
|
|
20
|
+
# Tier 0 strict TDD: .claude/hooks/tests/plain-english-steering.test.sh
|
|
21
|
+
|
|
22
|
+
set +e
|
|
23
|
+
|
|
24
|
+
# === Source install-class-aware lib (per kiss cure pattern) ===
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
for _candidate in \
|
|
27
|
+
"${SCRIPT_DIR}/../../lib/hook-inject.sh" \
|
|
28
|
+
"${HOME_OVERRIDE:-${HOME:-/}}/lib/hook-inject.sh"; do
|
|
29
|
+
if [ -f "$_candidate" ]; then
|
|
30
|
+
# shellcheck disable=SC1090
|
|
31
|
+
source "$_candidate"
|
|
32
|
+
break
|
|
33
|
+
fi
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
# Defensive: if lib not loaded, exit silent
|
|
37
|
+
if ! declare -f emit_additional_context >/dev/null 2>&1; then
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Override
|
|
42
|
+
if [ "${SKIP_PLAIN_ENGLISH_STEERING:-0}" = "1" ]; then
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# === Read last-turn grade marker (if present) ===
|
|
47
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
48
|
+
GRADE_MARKER="${REPO_ROOT}/state/markers/turn-grade/last-turn.json"
|
|
49
|
+
FLAGS_MARKER="${REPO_ROOT}/state/markers/turn-prose-flags/last-turn.json"
|
|
50
|
+
GRADE_LINE=""
|
|
51
|
+
FALLBACK_WARNING=""
|
|
52
|
+
FLAGS_LINE=""
|
|
53
|
+
|
|
54
|
+
if [ -f "$GRADE_MARKER" ]; then
|
|
55
|
+
LAST_GRADE=$(jq -r '.grade // ""' "$GRADE_MARKER" 2>/dev/null)
|
|
56
|
+
LAST_WORDS=$(jq -r '.words // ""' "$GRADE_MARKER" 2>/dev/null)
|
|
57
|
+
SOURCE=$(jq -r '.source // "UNKNOWN"' "$GRADE_MARKER" 2>/dev/null)
|
|
58
|
+
if [ -n "$LAST_GRADE" ] && [ "$LAST_GRADE" != "null" ]; then
|
|
59
|
+
# Classify per Norman + Ogilvy guidance (ticket #935 target shift):
|
|
60
|
+
# - Aim grade 8 for 80%+ of prose (was grade 8-9 pre-#935)
|
|
61
|
+
# - Grade 10 is the ceiling for simple technical sentences
|
|
62
|
+
# - Sentences above 10 need EXPLICIT justification
|
|
63
|
+
GRADE_STATUS=$(python3 -c "
|
|
64
|
+
g = float('$LAST_GRADE')
|
|
65
|
+
if g <= 8: print('ON TARGET (grade 8 or below)')
|
|
66
|
+
elif g <= 9: print('SLIGHTLY ABOVE (target grade 8; ceiling 10) — trim one clause or drop a compound noun')
|
|
67
|
+
elif g <= 10: print('AT THE GRADE-10 CEILING — tighten sentences and drop compound nouns to land at 8')
|
|
68
|
+
else: print('ABOVE THE GRADE-10 CEILING — rewrite in short SVO. Any sentence above 10 needs a clear reason grades 8-10 could not say it')
|
|
69
|
+
" 2>/dev/null)
|
|
70
|
+
|
|
71
|
+
# Ship 1 #1312: extract top 3 offender sentences that broke grade 10.
|
|
72
|
+
# Named offenders replace aggregate score as actionable signal.
|
|
73
|
+
OFFENDERS_LINE=""
|
|
74
|
+
OFFENDERS_COUNT=$(jq -r '.top_offenders | length' "$GRADE_MARKER" 2>/dev/null)
|
|
75
|
+
if [ -n "$OFFENDERS_COUNT" ] && [ "$OFFENDERS_COUNT" != "null" ] && [ "$OFFENDERS_COUNT" -gt 0 ] 2>/dev/null; then
|
|
76
|
+
OFFENDERS_BULLETS=$(jq -r '.top_offenders[] | "- Grade \(.grade), \(.words) words: \"\(.preview)\""' "$GRADE_MARKER" 2>/dev/null)
|
|
77
|
+
OFFENDERS_LINE="
|
|
78
|
+
|
|
79
|
+
**Top ${OFFENDERS_COUNT} sentence(s) from LAST turn above grade 10 — rewrite these first this turn:**
|
|
80
|
+
${OFFENDERS_BULLETS}"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
GRADE_LINE="
|
|
84
|
+
|
|
85
|
+
**From your LAST turn (not this one): grade ${LAST_GRADE} (${LAST_WORDS} words). Target: grade 8 for 80%+ of prose. Ceiling: 10 for simple technical sentences. ${GRADE_STATUS}. THIS turn is not measured yet — self-check each sentence before I ship it.** (Per #1044 Option B: post-turn measurement + pre-turn injection means the number I see is one turn old by design.)${OFFENDERS_LINE}"
|
|
86
|
+
if [ "$SOURCE" = "FALLBACK" ]; then
|
|
87
|
+
FALLBACK_WARNING="
|
|
88
|
+
|
|
89
|
+
**textstat not installed; grade uses the hand-rolled fallback. Numbers are approximate. Run \`pip install textstat\` for the sharper score.**"
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# === Read rewrite-check flags marker (ticket #935 extension) ===
|
|
95
|
+
if [ -f "$FLAGS_MARKER" ]; then
|
|
96
|
+
FL_WORDS=$(jq -r '.total_words // 0' "$FLAGS_MARKER" 2>/dev/null)
|
|
97
|
+
FL_LONG=$(jq -r '.long_sentences // 0' "$FLAGS_MARKER" 2>/dev/null)
|
|
98
|
+
FL_PASSIVE=$(jq -r '.passive_voice // 0' "$FLAGS_MARKER" 2>/dev/null)
|
|
99
|
+
FL_HYPHEN=$(jq -r '.hyphen_words // 0' "$FLAGS_MARKER" 2>/dev/null)
|
|
100
|
+
# Only inject the flags line when something caught the writer's attention
|
|
101
|
+
if [ "$FL_LONG" != "0" ] || [ "$FL_PASSIVE" != "0" ] || [ "$FL_HYPHEN" != "0" ]; then
|
|
102
|
+
FLAGS_LINE="
|
|
103
|
+
|
|
104
|
+
**From your LAST turn (not this one) — hit counts.** Long sentences past 25 words: ${FL_LONG}. Passive voice: ${FL_PASSIVE}. Hyphen words: ${FL_HYPHEN}. THIS turn is not measured yet — I check each sentence before I ship it."
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# === Emit steering content ===
|
|
109
|
+
# Extension per ticket #935 — first-person Ogilvy compose tests
|
|
110
|
+
# grounded in standards/ogilvy-writing-rules.md (from #934 PR #938)
|
|
111
|
+
STEERING_CONTENT="**Plain English for anything the operator or an adopter reads:**
|
|
112
|
+
|
|
113
|
+
For turn output, PR body, issue body, session log, journal entry, and skill output:
|
|
114
|
+
|
|
115
|
+
- Bassclef aims for grade 8 in most prose. Grade 10 is the top limit for simple technical sentences.
|
|
116
|
+
- Above grade 10, give a clear reason grades 8-10 could not say it. A technical topic on its own is not a reason.
|
|
117
|
+
- Short sentences. Subject-verb-object.
|
|
118
|
+
- If I want to use a hyphen word (X-side, X-as-Y, N-layer X, X gate), I write what it does instead.
|
|
119
|
+
- No more than two nouns in a row without a verb between them.
|
|
120
|
+
- Drop words that only bassclef readers know — see standards/bassclef-internal-jargon.md.
|
|
121
|
+
- Keep standard tech words: API, hook, PR, schema, ORM.
|
|
122
|
+
|
|
123
|
+
**Vocabulary rename active (per ADR-040 D1; grace window through 2026-10-31):**
|
|
124
|
+
|
|
125
|
+
When I author NEW prose for the operator or an adopter, I use the new terms:
|
|
126
|
+
|
|
127
|
+
- Say **goal** not **bet**. (Historical goal IDs like \`bet 08-11a\` stay as-is.)
|
|
128
|
+
- Say **step** not **WU** or **workunit**. (Historical \`WU-2\` refs stay.)
|
|
129
|
+
- Say **in_flight_goal** not **in_flight_bet** for new whereami updates.
|
|
130
|
+
- Say **time budget** not **appetite** for new prose.
|
|
131
|
+
- Say **parent goal** and **thread** not **parent bet** and **arc** for new prose.
|
|
132
|
+
|
|
133
|
+
**Discrimination cue.** Two situations, two different rules:
|
|
134
|
+
|
|
135
|
+
1. When I AUTHOR — writing my own sentence about work in flight or scope decisions — I use the new terms (Step-N, goal, time budget). Never introduce \`WU-N\` or \`bet\` or \`appetite\` in new prose.
|
|
136
|
+
2. When I QUOTE a past goal ID or reference (\`bet 27c WU-5\`, \`bet 2026-06-20a WU-1\`) — I preserve the exact historical string verbatim inside backticks. The backticks mark the historical boundary. The reader sees \"quote\" not \"active vocabulary.\"
|
|
137
|
+
|
|
138
|
+
Rule of thumb: if I am about to type \`WU-\` in a sentence, ask — am I quoting a historical goal ID? If yes, wrap in backticks. If no, use Step-N instead.
|
|
139
|
+
|
|
140
|
+
Historical mentions (past goal IDs, closed PR bodies, archived session logs) keep the old words — read-through via \`standards/vocabulary-migration.json\`.
|
|
141
|
+
|
|
142
|
+
**Compose check this turn (per Ogilvy + Roman-Raphaelson via standards/ogilvy-writing-rules.md).**
|
|
143
|
+
Before I write each sentence, I ask:
|
|
144
|
+
|
|
145
|
+
- Natural voice — would a real person say this to a peer? If it sounds like corporate throat-clearing, rewrite.
|
|
146
|
+
- Short units — is this sentence past 25 words? If yes, split.
|
|
147
|
+
- Read-aloud — when I read this in my head, does it stumble? If yes, rewrite until it flows.
|
|
148
|
+
- Completeness — is the ONE thing the reader needs to take from this actually written down in plain language?
|
|
149
|
+
- Honesty — am I saying what I mean or hiding behind hedge words and euphemisms? Say it straight.
|
|
150
|
+
- Facts with context — for each number or fact, did I say why it matters?
|
|
151
|
+
|
|
152
|
+
Per bassclef#790 + .claude/rules/operator-facing-prose.md + .claude/rules/writing-craft-discipline.md.${GRADE_LINE}${FLAGS_LINE}${FALLBACK_WARNING}"
|
|
153
|
+
|
|
154
|
+
# Use emit_additional_context to wrap as JSON envelope
|
|
155
|
+
emit_additional_context "UserPromptSubmit" "$STEERING_CONTENT"
|
|
156
|
+
exit 0
|