diff --git a/plugins/typos-format/.claude-plugin/plugin.json b/plugins/typos-format/.claude-plugin/plugin.json index 91c08b5f0..d7f5237db 100644 --- a/plugins/typos-format/.claude-plugin/plugin.json +++ b/plugins/typos-format/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "typos-format", - "version": "0.1.0", - "description": "Auto-fix spelling typos on edit via typos-cli, only when a typos config governs the repo — using the consuming repo's own typos configuration.", + "version": "0.2.0", + "description": "Auto-fix spelling typos on edit via typos-cli, unconditionally — honoring the consuming repo's own typos configuration when one is present.", "author": { "name": "Melodic Software", "email": "info@melodicsoftware.com" @@ -19,7 +19,7 @@ "typos_format_enabled": { "type": "boolean", "title": "typos-format hook", - "description": "Run typos --write-changes on edit of a file, when a typos config governs the repo", + "description": "Run typos --write-changes on edit of a file, unconditionally", "default": true } } diff --git a/plugins/typos-format/CHANGELOG.md b/plugins/typos-format/CHANGELOG.md index b509715e6..15d7b452e 100644 --- a/plugins/typos-format/CHANGELOG.md +++ b/plugins/typos-format/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to the `typos-format` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.2.0] + +### Changed + +- **Removed the opt-in config-gate.** The hook now runs `typos --write-changes` + unconditionally on every edited file, matching `markdown-format`'s + unconditional pattern instead of `ruff-format`'s config-gated one. typos + ships a built-in spelling dictionary and runs with zero configuration; the + ancestor walk-up looking for a governing `typos.toml`/`_typos.toml`/ + `.typos.toml`/`Cargo.toml`/`pyproject.toml` was only ever an activation + switch the hook implemented itself — it made the hook a silent no-op on any + repo without a hand-authored typos config, defeating the plugin's + zero-config auto-fix purpose. typos' own config discovery is unaffected: a + governing config, when present, is still found and honored by typos itself + (for allowlist/exclude purposes) — the hook just no longer gates activation + on one existing. This is a behavior change for existing installs — a repo + with no typos config now has its files actively rewritten on edit instead + of being left untouched — hence the minor version bump. + ## [0.1.0] ### Added diff --git a/plugins/typos-format/README.md b/plugins/typos-format/README.md index ca467c6d7..85eefde44 100644 --- a/plugins/typos-format/README.md +++ b/plugins/typos-format/README.md @@ -6,22 +6,22 @@ On every `Write` or `Edit` it runs [typos](https://github.com/crate-ci/typos)'s Claude as advisory context — including remediation guidance for allowlisting a false positive. -It uses **your repository's own typos configuration**. It ships no rules of -its own and runs only when your repo has opted into typos. +It runs unconditionally — typos ships a built-in spelling dictionary and +needs no configuration to be useful. If **your repository has its own typos +configuration** (`typos.toml`, `_typos.toml`, `.typos.toml`, `Cargo.toml` with +`[workspace.metadata.typos]`/`[package.metadata.typos]`, or `pyproject.toml` +with `[tool.typos]`), typos discovers and honors it automatically to widen +its allowlist or excludes — but no such config is required for the plugin to +do its job. ## Behavior -- **Opt-in on a typos config.** typos runs **only when a `typos.toml`, - `_typos.toml`, `.typos.toml`, `Cargo.toml` (with - `[workspace.metadata.typos]`/`[package.metadata.typos]`), or `pyproject.toml` - with a `[tool.typos]` section governs the edited file**, found by walking up - from the file to the repository root, in that precedence order — the same - discovery typos itself uses. A repo without a typos config is left untouched - rather than checked against typos' built-in dictionary, so the plugin never - imposes a check you did not choose. +- **Unconditional.** typos runs on **every** edited file, whether or not the + repo has a typos config. A config, when present, is discovered by typos + itself (not this hook) to widen the allowlist/excludes — it is never an + activation switch. - **No extension filter.** Unlike sibling formatter plugins (Ruff, Markdown), - typos is language-agnostic — it runs on any edited file, gated only by the - config opt-in above. + typos is language-agnostic — it runs on any edited file. - **Fix in place.** `typos --write-changes` applies every correction it has confidence in. Residual findings — an entry with no known correction (e.g. a blank-correction `extend-words` entry marking a term "disallowed") — @@ -59,10 +59,11 @@ locking/ordering primitive exists in Claude Code today. per-repo dependency-manager convention — it is a standalone Rust binary, installed at the machine level (cargo, Homebrew, Conda, pacman, or a pre-built binary). typos is never downloaded on the fly; if it is not - present while a typos config governs the repo, the hook skips with a - visible once-per-session notice. [Install typos](https://github.com/crate-ci/typos#install). -- A **typos config** (`typos.toml`, `_typos.toml`, `.typos.toml`, or an - equivalent `Cargo.toml`/`pyproject.toml` section) in the repo — the opt-in. + present, the hook skips with a visible once-per-session notice. + [Install typos](https://github.com/crate-ci/typos#install). + +A typos config in the repo is optional — typos runs with its built-in +dictionary either way; a config only widens what it allows or excludes. The hook itself runs on Bash 3.2+. Telemetry timing uses `EPOCHREALTIME` (Bash 5.0+); on older bash the telemetry envelope is skipped while typo @@ -79,10 +80,10 @@ Then verify prerequisites with `/typos-format:setup check`. ## Configuration -The rules themselves are never configured here — they come from the typos -config already in your repository, which the plugin reads automatically. To -change the rules (allowlist a false positive, ignore a pattern), edit that -file. +The rules themselves are never configured here — typos runs with its +built-in dictionary by default, and if your repository has a typos config, +typos reads it automatically to widen the allowlist or excludes. To change +the rules (allowlist a false positive, ignore a pattern), edit that file. One `userConfig` option tunes the hook itself: diff --git a/plugins/typos-format/hooks/typos-format.sh b/plugins/typos-format/hooks/typos-format.sh index c994c5f96..86c5c4d1c 100755 --- a/plugins/typos-format/hooks/typos-format.sh +++ b/plugins/typos-format/hooks/typos-format.sh @@ -8,17 +8,20 @@ # entry) findings surface via additionalContext but never block the edit. A # commit hook or CI is the hard gate. # -# Opt-in: typos runs ONLY when a typos configuration governs the edited file — -# found by walking up from the file to the repo root, checking (in precedence -# order, per crate-ci/typos' own docs at -# https://github.com/crate-ci/typos/blob/master/docs/reference.md) -# typos.toml, _typos.toml, .typos.toml, Cargo.toml (with -# [workspace.metadata.typos] or [package.metadata.typos]), or pyproject.toml -# (with [tool.typos]). A repo that has not adopted a typos config is left -# untouched rather than checked against typos' built-in defaults, so the -# plugin never imposes a style it did not choose. The typos binary is resolved -# from PATH only — never downloaded (typos is a standalone Rust binary with no -# per-repo dependency-manager convention, unlike ruff's .venv). +# Unconditional: typos ships a built-in spelling dictionary and runs with zero +# configuration, so this hook runs on every edited file regardless of whether +# the repo has a typos config — matching markdown-format's unconditional +# pattern rather than ruff-format's config-gated one (Ruff/Biome require +# project-specific configuration to be useful; typos does not). When a typos +# config (typos.toml, _typos.toml, .typos.toml, Cargo.toml with +# [workspace.metadata.typos]/[package.metadata.typos], or pyproject.toml with +# [tool.typos]) IS present, typos discovers and honors it itself — per +# crate-ci/typos' own docs at +# https://github.com/crate-ci/typos/blob/master/docs/reference.md — to widen +# its allowlist or excludes; this hook does no config discovery of its own. +# The typos binary is resolved from PATH only — never downloaded (typos is a +# standalone Rust binary with no per-repo dependency-manager convention, +# unlike ruff's .venv). # # Hook-precision: this convention is fleet-wide by intent (its owner doc says # "every plugin hook follows" it), even though today's CI-audited enforcement @@ -82,8 +85,8 @@ FILE=$(printf '%s' "$INPUT" | hook::read_file_path) || exit 0 TOOL=$(printf '%s' "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) -# Resolve repo root early — used to bound the config opt-in walk and to compute -# the schema-required repo-relative path in data.file. +# Resolve repo root early — used to compute the schema-required repo-relative +# path in data.file and to anchor RUN_DIR below. REPO_ROOT="$(hook::repo_root "$(dirname "$FILE")")" # Repo-relative path: schema requires "relative to the consuming repo root". # On Windows Git Bash, git rev-parse --show-toplevel returns a drive-letter path @@ -122,52 +125,20 @@ emit_skipped() { exit 0 } -# Resolve the file's directory in `pwd` form once. Both walks below start here. +# Resolve the file's directory in `pwd` form once — feeds RUN_DIR/TYPOS_ARG below. FILE_DIR_POSIX="$(cd "$(dirname "$FILE")" 2>/dev/null && pwd)" || FILE_DIR_POSIX="" root="$(cd "$REPO_ROOT" 2>/dev/null && pwd)" || root="" -# Consumer opt-in: a typos configuration that governs the edited file. Walk up -# from the file's directory to the repo root, stopping at the FIRST config -# found — typos itself resolves the closest config per-directory (checking all -# recognized names together before ascending, per crate-ci/typos' -# Config::from_dir), so the closest hit is exactly the config that will govern -# the run. Same-directory precedence mirrors typos' own documented order: -# typos.toml > _typos.toml > .typos.toml > Cargo.toml (workspace/package -# metadata.typos) > pyproject.toml ([tool.typos]). Absence of any config is the -# opt-out: the file is left untouched. -CONFIG_FOUND="" -dir="$FILE_DIR_POSIX" -while [[ -n "$dir" ]]; do - for name in typos.toml _typos.toml .typos.toml; do - [[ -f "$dir/$name" ]] && CONFIG_FOUND="$dir/$name" && break - done - if [[ -z "$CONFIG_FOUND" && -f "$dir/Cargo.toml" ]] && - grep -qE '^[[:space:]]*\[(workspace|package)\.metadata\.typos(\]|[.])' "$dir/Cargo.toml" 2>/dev/null; then - CONFIG_FOUND="$dir/Cargo.toml" - fi - if [[ -z "$CONFIG_FOUND" && -f "$dir/pyproject.toml" ]] && - grep -qE '^[[:space:]]*\[tool\.typos(\]|[.])' "$dir/pyproject.toml" 2>/dev/null; then - CONFIG_FOUND="$dir/pyproject.toml" - fi - [[ -n "$CONFIG_FOUND" ]] && break - [[ -n "$root" && "$dir" == "$root" ]] && break - parent="$(dirname "$dir")" - [[ "$parent" == "$dir" ]] && break # reached filesystem root - dir="$parent" -done - -[[ -n "$CONFIG_FOUND" ]] || emit_skipped - # Resolve the typos binary from PATH — never downloaded (typos is a standalone # Rust binary; no per-repo dependency-manager convention exists for it, unlike # ruff's .venv or markdownlint's node_modules). TYPOS_BIN="$(command -v typos 2>/dev/null)" || TYPOS_BIN="" -# The repo opted in via a typos config but no binary is available → visible -# once-per-session skip notice, not a silent gap (dim-9 doctrine). +# No binary available → visible once-per-session skip notice, not a silent gap +# (dim-9 doctrine). if [[ -z "$TYPOS_BIN" ]]; then if hook::notice_once "typos-format-typos" "$INPUT"; then - hook::emit_skip_notice PostToolUse "typos-format: a typos config governs this repo but no 'typos' binary was found on PATH — spell-check skipped for this session. Install: https://github.com/crate-ci/typos#install" + hook::emit_skip_notice PostToolUse "typos-format: no 'typos' binary was found on PATH — spell-check skipped for this session. Install: https://github.com/crate-ci/typos#install" fi emit_skipped fi @@ -178,9 +149,8 @@ fi # line, not the process CWD (verified empirically: running from the repo root # with a relative subdirectory path still discovers and honors that # subdirectory's own config) — so running from repo root here does not change -# which config governs; it matches the walk above regardless of nesting depth. -# Falls back to the absolute path when the repo root did not resolve or the -# file is outside it. +# which config (if any) governs, regardless of nesting depth. Falls back to +# the absolute path when the repo root did not resolve or the file is outside it. TYPOS_ARG="$FILE" RUN_DIR="${root:-$FILE_DIR_POSIX}" if [[ -n "$root" && -n "$FILE_REL" && "$FILE_REL" != "$FILE" ]]; then @@ -240,7 +210,7 @@ fi # judgment was made. Surface the diagnostic via additionalContext (NOT stderr — # an advisory hook's exit-0 stderr can trip a false "Hook Error" label). Record # as "skipped" (typos never ran to judgment), the same status as the -# no-config / no-binary paths. +# no-binary path. hook::ctx_reset hook::ctx_append "typos-format: typos failed for $(basename "$FILE") (no diagnostics; tool break, not a finding):" while IFS= read -r line; do diff --git a/plugins/typos-format/hooks/typos-format.test.sh b/plugins/typos-format/hooks/typos-format.test.sh index a868cd8a2..548c1f718 100755 --- a/plugins/typos-format/hooks/typos-format.test.sh +++ b/plugins/typos-format/hooks/typos-format.test.sh @@ -1,12 +1,13 @@ #!/usr/bin/env bash # Black-box contract test for typos-format.sh (the typos-format plugin hook). # -# Proves WIRING: the hook fires on any file (no extension filter), gates on a -# consumer typos config (present -> run, absent -> leave bytes untouched), -# honors typos.toml > _typos.toml > .typos.toml > Cargo.toml > pyproject.toml -# precedence, applies typos' safe corrections in place, surfaces residual -# (unfixable) findings via additionalContext with remediation guidance, -# honors the kill switch, and emits a schema-valid telemetry envelope. +# Proves WIRING: the hook fires on any file (no extension filter) UNCONDITIONALLY +# -- with or without a consumer typos config present -- honors typos' own +# native typos.toml > _typos.toml > .typos.toml > Cargo.toml > pyproject.toml +# discovery/precedence when a config IS present, applies typos' safe +# corrections in place, surfaces residual (unfixable) findings via +# additionalContext with remediation guidance, honors the kill switch, and +# emits a schema-valid telemetry envelope. # # Self-contained: builds throwaway git repos with runtime-generated fixtures. # The hook is invoked from an UNRELATED cwd so any reliance on the caller's @@ -113,40 +114,37 @@ run_hook_env() { ) } -# --- Case 1: opt-in gate OFF (no typos config) -> file left untouched ------- -# A repo with typos installed but NO config must not have its files checked -# against typos' built-in dictionary. +# --- Case 1: NO typos config anywhere -> hook still runs and fixes the typo - +# The activation gate is gone: typos ships a built-in dictionary and runs with +# zero configuration, so a repo with typos installed but NO config must still +# have its files checked and fixed (not left untouched). REPO_NO="$WORK/no-config" new_typos_repo "$REPO_NO" NO_CONFIG printf 'this has teh typo\n' >"$REPO_NO/doc.txt" # spellchecker:disable-line -BEFORE_NO="$(cat "$REPO_NO/doc.txt")" OUT=$(run_hook "$REPO_NO/doc.txt") RC=$? -if [[ $RC -eq 0 && -z "$OUT" ]]; then ok "gate OFF (no config) -> exit 0, silent"; else fail "gate OFF not silent (rc=$RC out=$OUT)"; fi -if [[ "$(cat "$REPO_NO/doc.txt")" == "$BEFORE_NO" ]]; then ok "gate OFF -> file left untouched"; else fail "gate OFF -> file was rewritten"; fi - -# --- Case 1b: pyproject.toml WITHOUT [tool.typos] does NOT opt in ----------- -REPO_PP="$WORK/pyproject-plain" -new_typos_repo "$REPO_PP" NO_CONFIG -printf '[project]\nname = "t"\n' >"$REPO_PP/pyproject.toml" -printf 'this has teh typo\n' >"$REPO_PP/plain.txt" # spellchecker:disable-line -BEFORE_PP="$(cat "$REPO_PP/plain.txt")" -OUT=$(run_hook "$REPO_PP/plain.txt") -RC=$? -if [[ $RC -eq 0 && -z "$OUT" ]]; then ok "pyproject without [tool.typos] -> exit 0, silent (no opt-in)"; else fail "plain pyproject opted in (rc=$RC out=$OUT)"; fi -if [[ "$(cat "$REPO_PP/plain.txt")" == "$BEFORE_PP" ]]; then ok "pyproject without [tool.typos] -> file left untouched"; else fail "plain pyproject -> file was rewritten"; fi +if [[ $RC -eq 0 ]]; then ok "no config anywhere -> exit 0 (advisory)"; else fail "no config anywhere exit $RC"; fi +if grep -q ' the ' "$REPO_NO/doc.txt"; then + ok "no config anywhere -> typos autofixed unconditionally (no opt-in gate)" +else + fail "no config anywhere -> file not fixed: $(cat "$REPO_NO/doc.txt")" +fi -# --- Case 1c: pyproject.toml WITH [tool.typos] opts in ---------------------- +# --- Case 1c: pyproject.toml WITH [tool.typos] present -> still fixed ------ +# Config presence is no longer an activation switch either way; confirms a +# governing pyproject.toml section doesn't interfere with the unconditional run. REPO_PT="$WORK/pyproject-typos" new_typos_repo "$REPO_PT" NO_CONFIG printf '[project]\nname = "t"\n\n[tool.typos]\n' >"$REPO_PT/pyproject.toml" printf 'this has teh typo\n' >"$REPO_PT/opt.txt" # spellchecker:disable-line OUT=$(run_hook "$REPO_PT/opt.txt") RC=$? -if [[ $RC -eq 0 ]]; then ok "pyproject with [tool.typos] -> exit 0"; else fail "pyproject opt-in exit $RC"; fi -if grep -q ' the ' "$REPO_PT/opt.txt"; then ok "pyproject with [tool.typos] -> file fixed"; else fail "pyproject opt-in -> not fixed: $(cat "$REPO_PT/opt.txt")"; fi +if [[ $RC -eq 0 ]]; then ok "pyproject with [tool.typos] -> exit 0"; else fail "pyproject with [tool.typos] exit $RC"; fi +if grep -q ' the ' "$REPO_PT/opt.txt"; then ok "pyproject with [tool.typos] -> file fixed"; else fail "pyproject with [tool.typos] -> not fixed: $(cat "$REPO_PT/opt.txt")"; fi -# --- Case 1d: typos.toml takes precedence over _typos.toml in the same dir -- +# --- Case 1d: typos' own precedence — typos.toml wins over _typos.toml ----- +# This is typos' NATIVE config discovery (Config::from_dir), not anything the +# hook implements — the hook no longer does its own config walk at all. REPO_PREC="$WORK/precedence" new_typos_repo "$REPO_PREC" NO_CONFIG printf '[default.extend-words]\nfooone = "correctone"\n' >"$REPO_PREC/_typos.toml" @@ -154,12 +152,12 @@ printf '[default.extend-words]\nfootwo = "correcttwo"\n' >"$REPO_PREC/typos.toml printf 'this has fooone and footwo words\n' >"$REPO_PREC/p.txt" run_hook "$REPO_PREC/p.txt" >/dev/null if grep -q 'correcttwo' "$REPO_PREC/p.txt" && grep -q 'fooone' "$REPO_PREC/p.txt"; then - ok "typos.toml wins over _typos.toml in the same directory" + ok "typos.toml wins over _typos.toml in the same directory (typos' own precedence)" else fail "config precedence wrong: $(cat "$REPO_PREC/p.txt")" fi -# --- Case 2: gate ON + clean file -> exit 0, empty stdout ------------------- +# --- Case 2: config present + clean file -> exit 0, empty stdout ----------- REPO="$WORK/consumer" new_typos_repo "$REPO" printf 'this is a clean document\n' >"$REPO/clean.txt" @@ -168,16 +166,16 @@ RC=$? if [[ $RC -eq 0 ]]; then ok "clean file -> exit 0"; else fail "clean file exit $RC"; fi if [[ -z "$OUT" ]]; then ok "clean file -> empty stdout"; else fail "clean file stdout not empty: $OUT"; fi -# --- Case 3: gate ON + fixable typo -> autofixed in place ------------------- +# --- Case 3: config present + fixable typo -> autofixed in place ----------- mkdir -p "$REPO/src" printf 'this has teh typo\n' >"$REPO/src/fix.txt" # spellchecker:disable-line OUT=$(run_hook "$REPO/src/fix.txt") RC=$? if [[ $RC -eq 0 ]]; then ok "fixable typo -> exit 0 (advisory)"; else fail "fixable typo exit $RC"; fi if grep -q ' the ' "$REPO/src/fix.txt"; then - ok "gate ON (subdir file) -> typos autofixed" + ok "config present (subdir file) -> typos autofixed" else - fail "gate ON -> file not fixed: $(cat "$REPO/src/fix.txt")" + fail "config present -> file not fixed: $(cat "$REPO/src/fix.txt")" fi # --- Case 3b: config nested BELOW repo root (root itself has no config) ----- @@ -197,7 +195,7 @@ else fail "nested config not applied: $(cat "$REPO_NEST/packages/pkg/file.txt")" fi -# --- Case 4: gate ON + unfixable finding -> advisory context with remediation +# --- Case 4: config present + unfixable finding -> advisory context with remediation mkdir -p "$REPO/lib" printf 'this has a disallowme term\n' >"$REPO/lib/lint.txt" OUT=$(run_hook "$REPO/lib/lint.txt") @@ -302,24 +300,25 @@ else fi rm -f "$TEL" -# --- Stub sink + gate OFF -> status skipped ----------------------------------- -printf 'this has teh typo\n' >"$REPO_NO/tel2.txt" # spellchecker:disable-line +# --- Stub sink + no config present -> status ok (still runs) ----------------- +printf 'this is a clean telemetry check with no config\n' >"$REPO_NO/tel2.txt" TELS="$(mktemp)" SINKS="$(make_sink "cat >\"$TELS\"")" run_hook_env "$REPO_NO/tel2.txt" PATH="$(dirname "$REAL_TYPOS"):$PATH" CLAUDE_PLUGIN_OPTION_TYPOS_FORMAT_ENABLED=true HOOK_TELEMETRY_SINK="$SINKS" >/dev/null wait_for_sink "$TELS" if [[ -s "$TELS" ]]; then - if [[ "$(jq -r '.status' "$TELS")" == "skipped" ]]; then ok "telemetry/gate-off: status skipped"; else fail "telemetry/gate-off: status=$(jq -r '.status' "$TELS")"; fi - if [[ "$(jq '.data.findings | length' "$TELS")" -eq 0 ]]; then ok "telemetry/gate-off: findings empty array"; else fail "telemetry/gate-off: findings not empty"; fi + if [[ "$(jq -r '.status' "$TELS")" == "ok" ]]; then ok "telemetry/no-config: status ok (unconditional run)"; else fail "telemetry/no-config: status=$(jq -r '.status' "$TELS")"; fi + if [[ "$(jq '.data.findings | length' "$TELS")" -eq 0 ]]; then ok "telemetry/no-config: findings empty array"; else fail "telemetry/no-config: findings not empty"; fi else - fail "telemetry/gate-off: no envelope written" + fail "telemetry/no-config: no envelope written" fi rm -f "$TELS" # --- Missing-tool visibility (dim-9 doctrine) -------------------------------- -# Fake-bin dir of exec wrappers (no typos): a repo with a governing typos -# config but no binary must produce a visible once-per-session skip notice on -# both channels, silent on the second run. jq removal then exercises the +# Fake-bin dir of exec wrappers (no typos): with no 'typos' binary on PATH the +# hook must produce a visible once-per-session skip notice on both channels +# (regardless of whether a typos config exists — activation no longer depends +# on one), silent on the second run. jq removal then exercises the # input-parsing gate. FAKEBIN="$(mktemp -d -p "$WORK" fakebin.XXXXXX)" for t in bash jq git dirname basename cat env printf mktemp mkdir find tr awk grep sed uname sleep cygpath realpath readlink; do @@ -345,8 +344,8 @@ run_nt() { OUT_NT=$(run_nt) RC_NT=$? if [[ $RC_NT -eq 0 ]]; then ok "typos-absent -> exit 0"; else fail "typos-absent exit $RC_NT"; fi -if jq -e '(.systemMessage | contains("typos")) and (.hookSpecificOutput.additionalContext | contains("typos config"))' <<<"$OUT_NT" >/dev/null 2>&1; then - ok "typos-absent with governing config -> visible notice on both channels" +if jq -e '(.systemMessage | contains("typos")) and (.hookSpecificOutput.additionalContext | contains("was found on PATH"))' <<<"$OUT_NT" >/dev/null 2>&1; then + ok "typos-absent -> visible notice on both channels" else fail "typos-absent: notice missing or malformed: $OUT_NT" fi diff --git a/plugins/typos-format/skills/setup/SKILL.md b/plugins/typos-format/skills/setup/SKILL.md index 7bdea248c..e4d05b0c1 100644 --- a/plugins/typos-format/skills/setup/SKILL.md +++ b/plugins/typos-format/skills/setup/SKILL.md @@ -39,16 +39,17 @@ restores the FAIL semantics. once-per-session notice instead of fixing typos. 3. **typos binary** — `command -v typos` (the hook resolves PATH only — no `.venv`-style per-repo convention). Report the resolved path and `typos --version` output when found. - FAIL when absent while a typos config governs the repo; the hook then emits a visible - once-per-session skip notice instead of running. -4. **Consumer typos config** — mirror the hook's opt-in walk: it stops at the FIRST - (closest) governing config found walking from the edited file's directory up to the repo - root, checking (in precedence order) `typos.toml`, `_typos.toml`, `.typos.toml`, a - `Cargo.toml` with `[workspace.metadata.typos]`/`[package.metadata.typos]`, or a - `pyproject.toml` with `[tool.typos]` (read the hook for the exact names and the section - test — its test is the authority). Report the governing config the walk discovers, or - INFO that none exists — absence is the opt-out by design, so the plugin is inert (INFO, - not FAIL), matching the README's "ships no rules of its own" stance. + FAIL when absent; the hook then emits a visible once-per-session skip notice instead of + running. +4. **Consumer typos config (optional)** — the hook runs unconditionally, so this is not a + gate; report it for informational value only. typos itself discovers the closest governing + config walking from the edited file's directory up to the repo root, checking (in + precedence order) `typos.toml`, `_typos.toml`, `.typos.toml`, a `Cargo.toml` with + `[workspace.metadata.typos]`/`[package.metadata.typos]`, or a `pyproject.toml` with + `[tool.typos]` (read the hook for the exact names and the section test — its test is the + authority). Report the governing config found, or INFO that none exists — absence is not + a problem: typos still runs against its built-in dictionary, matching the README's + "ships no rules of its own" stance. 5. **Hook toggle** — report the effective `typos_format_enabled` value: `${user_config.typos_format_enabled}` (unexpanded or empty means default `true`). 6. **Hook registration** — INFO: confirm the plugin is enabled for this project