From 340ce3bfab464bd456540c596ff8f38e9291e89a Mon Sep 17 00:00:00 2001 From: "melodic-standards-sync[bot]" <300666570+melodic-standards-sync[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:41:22 +0000 Subject: [PATCH] chore: sync standards components (b36766d0c95ae0b0b422ccd5affce47c6faa1e14) --- .claude/cloud-bootstrap.sh | 277 +++++++++++++++++++++++++++++-------- .editorconfig | 67 +++++++++ .editorconfig-checker.json | 33 +++++ .gitattributes | 61 ++++++++ .gitleaks.toml | 14 ++ .markdownlint-cli2.jsonc | 41 ++++++ _typos.toml | 58 ++++++++ lychee.toml | 83 +++++++++++ 8 files changed, 580 insertions(+), 54 deletions(-) create mode 100644 .editorconfig create mode 100644 .editorconfig-checker.json create mode 100644 .gitattributes create mode 100644 .gitleaks.toml create mode 100644 .markdownlint-cli2.jsonc create mode 100644 _typos.toml create mode 100644 lychee.toml diff --git a/.claude/cloud-bootstrap.sh b/.claude/cloud-bootstrap.sh index aff57ae..0808629 100755 --- a/.claude/cloud-bootstrap.sh +++ b/.claude/cloud-bootstrap.sh @@ -1,20 +1,35 @@ #!/usr/bin/env bash -# Cloud bootstrap: install the plugin catalog this repo enables. -# Two callers, cloud sessions only (both set CLAUDE_CODE_REMOTE=true): -# 1. The account environment's setup script, after clone and BEFORE the -# session process launches. Claude Code builds its plugin registry at -# process start and never re-reads it, so this pre-launch call is the -# only path that gets plugins loaded at turn one. -# 2. The SessionStart hook (startup|resume), as per-session drift repair — -# the environment cache can be ~7 days stale. Plugins installed from -# this path go live at the next resume, not in the current session. -# Guard contract (verified 2026-08-15): the session VM carries -# CLAUDE_CODE_REMOTE=true and it is never "true" locally, per -# https://code.claude.com/docs/en/cloud-environments#setup-scripts-vs-sessionstart-hooks -# Declaring a marketplace is gated on workspace trust and cloud sessions arrive -# untrusted, so the declaration alone can load nothing there. Hooks run untrusted. -# Idempotent and best effort: a failed plugin costs its skills, not the session. -# Bash 3.2 compatible, so the default macOS shell runs it unchanged. +# Canonical repo cloud bootstrap (SSOT). Materialized to each fleet repo's +# .claude/cloud-bootstrap.sh by distribution/sync-manifest.yml, so edits land +# here by pull request and fan out as reviewed sync PRs — never by patching a +# repo's materialized copy. Repo-specific work does not belong here: a repo +# enriches through its own .claude/cloud-bootstrap.local.sh (run below, +# never synced), or takes the component locally-owned in the manifest to +# customize the whole file. +# +# Two callers, both with CLAUDE_CODE_REMOTE=true: +# 1. The account environments' setup scripts, after clone and before the +# session process launches. Claude Code builds its plugin/command/skill +# registry at process start and never re-reads it, so this pre-launch +# call is the only path that gets plugins loaded at turn one. +# 2. The SessionStart hook (startup|resume), as drift repair — the +# environment cache can be ~7 days stale. Plugins it installs go live +# at the next resume, not in the session that ran the hook. +# Outside cloud sessions this exits immediately: declaring a marketplace is +# gated on workspace trust, and on trusted local machines the marketplace and +# enabledPlugins declared in settings.json load on their own. Cloud sessions +# arrive untrusted, so there the declaration alone can load nothing. +# Idempotent and best effort: a failed step costs a tool or a plugin, never +# the session. +# +# Everything below is data-driven from the repo's own manifests — .node-version, +# package-lock.json, global.json, .claude/settings.json — so this file carries +# no repo names, no pinned versions, and no marketplace identifiers. +# +# Both callers run `bash `, so the interpreter is whatever `bash` +# resolves to rather than the shebang's. Stock macOS still ships bash 3.2, which +# has no `mapfile`, and errors on an empty "${array[@]}" under `set -u` before +# 4.4. Both are avoided here: newline-delimited strings, no arrays. set -euo pipefail [[ "${CLAUDE_CODE_REMOTE:-}" == "true" ]] || exit 0 @@ -22,50 +37,204 @@ set -euo pipefail repo_root="${CLAUDE_PROJECT_DIR:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)}" cd -- "$repo_root" +# Environment-snapshot inventory line: the shared environment's setup script +# writes its version + build time to this stamp as its last action. Logging it +# from every session makes "which snapshot is this account booting" visible +# without a per-account audit; a missing stamp means an unmanaged environment +# or an interrupted cache build. +if [[ -f /opt/melodic-env-setup.done ]]; then + echo "cloud-bootstrap: env snapshot $(cat /opt/melodic-env-setup.done)" >&2 +else + echo "cloud-bootstrap: no env setup stamp (unmanaged environment or interrupted cache build)" >&2 +fi + +# --- Repo toolchain --------------------------------------------------------- +# Ahead of the plugin-CLI guards below on purpose: those `command -v` checks +# exit 0 when `claude` or `jq` is missing, and the toolchain must not be +# collateral damage of an unrelated CLI being absent. The cloud VM is a fresh +# Ubuntu image shipping Node 20/21/22 and no .NET, so without this a session +# builds and lints on the wrong toolchain — the failure a live cloud +# verification run confirmed across the fleet. The shared environment's setup +# script pre-installs a warm cache of common pins; this stage is the +# correctness guarantee and must not assume the cache installed anything. +# +# Subshell with its own errexit posture: this file runs under `set -e`, and a +# failed optional install must cost a toolchain, never the session. The +# subshell ends in an explicit `exit 0` rather than being wrapped in +# `|| true` — wrapping would put every call inside it in an `||` context, +# which is what .shellcheckrc's check-set-e-suppressed (SC2310) exists to flag. +( + set +e + + toolchain_warn() { printf 'cloud-bootstrap: %s\n' "$*" >&2; } + + # env_line — append to the session env file once. Dedup-guarded + # because SessionStart fires again on resume. + env_line() { + [[ -n "${CLAUDE_ENV_FILE:-}" ]] || return 0 + grep -qxF "$1" "$CLAUDE_ENV_FILE" 2>/dev/null || printf '%s\n' "$1" >>"$CLAUDE_ENV_FILE" + } + + # Node from .node-version (VM nvm at /opt/nvm; image ships 20/21/22). + if [[ -f .node-version ]]; then + node_pin="$(tr -d '[:space:]' <.node-version)" + if [[ "$(node --version 2>/dev/null)" != "v$node_pin" ]]; then + export NVM_DIR="${NVM_DIR:-/opt/nvm}" + if [[ -s "$NVM_DIR/nvm.sh" ]]; then + set +u # nvm.sh reads intentionally-unset variables + # shellcheck disable=SC1091 + . "$NVM_DIR/nvm.sh" + if nvm install "$node_pin" >/dev/null 2>&1; then + nvm alias default "$node_pin" >/dev/null 2>&1 || + toolchain_warn "Node $node_pin installed but could not be aliased default" + else + toolchain_warn "Node $node_pin install failed; continuing on $(node --version 2>/dev/null || echo 'no node')" + fi + set -u + else + toolchain_warn "nvm not found at $NVM_DIR; Node $node_pin unavailable" + fi + fi + if node_bin="$(command -v node 2>/dev/null)"; then + # shellcheck disable=SC2016 + env_line "export PATH=\"$(dirname -- "$node_bin"):$PWD/node_modules/.bin:\$PATH\"" + fi + fi + + # npm dependencies from the root lockfile, skipped when already in sync. + # Additional lockfile locations are a repo concern: install them from + # .claude/cloud-bootstrap.local.sh. + if [[ -f package-lock.json ]]; then + if [[ ! -f node_modules/.package-lock.json ]] || + [[ package-lock.json -nt node_modules/.package-lock.json ]]; then + npm ci --no-audit --no-fund >/dev/null 2>&1 || + toolchain_warn 'npm ci failed; node_modules is unavailable this session' + fi + fi + + # .NET SDK exactly as global.json pins, repo-local. + if [[ -f global.json ]] && command -v jq >/dev/null 2>&1; then + sdk="$(jq -r '.sdk.version // empty' global.json 2>/dev/null)" + if [[ -n "$sdk" ]]; then + # -F: the version is a literal, and its dots are not regex wildcards. + if [[ ! -x .dotnet/dotnet ]] || ! .dotnet/dotnet --list-sdks 2>/dev/null | grep -qF "$sdk "; then + installer=/tmp/dotnet-install.sh + # The cloud egress proxy can return an error body with HTTP 200 from + # dot.net, which `curl -f` cannot catch (-f only trips on >= 400), so a + # real installer's shebang is checked before it is executed. + # --proto/--proto-redir pin the redirect chain (dot.net -> aka.ms -> + # builds.dotnet.microsoft.com) to HTTPS end to end. + if curl -fsSL --proto '=https' --proto-redir '=https' \ + --retry 2 --retry-delay 3 https://dot.net/v1/dotnet-install.sh -o "$installer" 2>/dev/null && + [[ -s "$installer" ]] && + head -c 2 "$installer" 2>/dev/null | grep -q '^#!' && + bash "$installer" --version "$sdk" --install-dir .dotnet >/dev/null 2>&1; then + : + else + toolchain_warn "dotnet $sdk install failed — check the environment's network allowlist (dot.net, aka.ms, builds.dotnet.microsoft.com, download.visualstudio.microsoft.com)" + fi + rm -f "$installer" + fi + if [[ -x .dotnet/dotnet ]]; then + env_line "export DOTNET_ROOT=\"$PWD/.dotnet\"" + # shellcheck disable=SC2016 + env_line "export PATH=\"$PWD/.dotnet:\$PATH\"" + fi + fi + fi + + # Git history: base-ref diffs (several plugin suites use origin/main) break + # on the shallow single-branch cloud clone — deepen it and make origin/main + # resolve. The explicit destination refspec matters: in a single-branch + # clone a bare `fetch origin main` only writes FETCH_HEAD and never creates + # refs/remotes/origin/main. `main` is the fleet's default branch. + git_dir="$(git rev-parse --git-dir 2>/dev/null)" + if [[ -n "$git_dir" && -f "$git_dir/shallow" ]]; then + git fetch --quiet --unshallow 2>/dev/null || + toolchain_warn 'could not unshallow; base-ref diffs may fail' + fi + git fetch --quiet origin "+main:refs/remotes/origin/main" 2>/dev/null || + toolchain_warn 'could not fetch origin/main' + + # --- Repo extension (enrich seam) ----------------------------------------- + # A repo appends its own setup — extra lockfiles, pinned hygiene binaries, + # symlinks — in this committed, never-synced sibling. Same contract as this + # file: idempotent, best effort, bash-3.2-safe. Deliberately inside this + # subshell so it inherits the nvm-selected Node on PATH and the + # warn-never-fatal posture, plus this script's environment + # (CLAUDE_CODE_REMOTE, CLAUDE_PROJECT_DIR, CLAUDE_ENV_FILE when the + # SessionStart hook is the caller). Its failure costs the extension, never + # the session. + if [[ -f .claude/cloud-bootstrap.local.sh ]]; then + if bash .claude/cloud-bootstrap.local.sh >&2; then + toolchain_warn 'local extension completed' + else + toolchain_warn 'WARN local extension failed' + fi + fi + + exit 0 +) + +# --- Plugins ---------------------------------------------------------------- +# Data-driven from the repo's committed .claude/settings.json — every declared +# marketplace is registered and every enabledPlugins entry set to true is +# installed, whichever marketplace it names. A repo that declares nothing gets +# nothing. Explicit installs also sidestep the platform rule that adding a +# marketplace never auto-installs externally-sourced plugins. command -v claude >/dev/null 2>&1 || exit 0 command -v jq >/dev/null 2>&1 || exit 0 -marketplace="melodic-software" -source_repo="melodic-software/claude-code-plugins" +settings='.claude/settings.json' +[[ -f "$settings" ]] || exit 0 -if ! claude plugin marketplace list --json 2>/dev/null | - jq -e --arg n "$marketplace" 'any(.[]; .name == $n)' >/dev/null; then - claude plugin marketplace add "$source_repo" --scope user >/dev/null || { - echo "cloud-bootstrap: could not add the $marketplace marketplace" >&2 - exit 0 - } -fi +registered="$(claude plugin marketplace list --json 2>/dev/null | + jq -r '.[].name' 2>/dev/null || true)" +declared=$( + jq -r '(.extraKnownMarketplaces // {}) | to_entries[] + | [.key, (.value.source.repo // .value.source.path // .value.source.url // "")] + | @tsv' "$settings" 2>/dev/null || true +) +while IFS=$'\t' read -r mp_name mp_target; do + [[ -n "$mp_name" ]] || continue + if [[ $'\n'"$registered"$'\n' == *$'\n'"$mp_name"$'\n'* ]]; then continue; fi + if [[ -z "$mp_target" ]]; then + echo "cloud-bootstrap: marketplace $mp_name declares no repo/path/url source; skipped" >&2 + elif claude plugin marketplace add "$mp_target" --scope user >/dev/null 2>&1; then + echo "cloud-bootstrap: marketplace $mp_name registered ($mp_target)" >&2 + else + echo "cloud-bootstrap: WARN marketplace add failed: $mp_name ($mp_target)" >&2 + fi +done </dev/null +wanted=$( + jq -r '.enabledPlugins // {} | to_entries[] + | select(.value == true) | .key' "$settings" 2>/dev/null || true ) +have=$(claude plugin list --json 2>/dev/null | jq -r '.[].id' 2>/dev/null || true) -# A space-delimited string, not an array: Bash 3.2 under `set -u` treats -# "${have[*]}" on an empty array as unbound. -have=" " +enabled=0 +installed=0 while IFS= read -r id; do - [[ -n "$id" ]] || continue - have="$have$id " -done < <(claude plugin list --json 2>/dev/null | jq -r '.[].id' 2>/dev/null) + [[ -n "$id" ]] || continue + enabled=$((enabled + 1)) + if [[ $'\n'"$have"$'\n' == *$'\n'"$id"$'\n'* ]]; then continue; fi + if claude plugin install "$id" --scope user -y >/dev/null 2>&1; then + installed=$((installed + 1)) + else + echo "cloud-bootstrap: install failed: $id" >&2 + fi +done </dev/null 2>&1; then - installed=$((installed + 1)) - else - echo "cloud-bootstrap: install failed: $id" >&2 - fi -done -echo "cloud-bootstrap: ${#wanted[@]} enabled, $installed newly installed" +echo "cloud-bootstrap: $enabled enabled, $installed newly installed" >&2 + +# When the SessionStart hook is the caller, stdout is parsed as hook output — +# that is why every summary above goes to stderr — and this line asks for a +# skills re-scan for whatever the harness can pick up mid-session (the plugin +# registry itself is only rebuilt at the next process start). From the +# pre-launch caller it lands harmlessly in the setup log. +printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"SessionStart","reloadSkills":true}}' diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9f226bc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,67 @@ +root = true + +# Language/framework-agnostic base. Covers universal defaults plus per-type +# sections for the file classes that appear across the platform: Markdown, shell, +# PowerShell, config/serialization (JSON/YAML/TOML), JS/TS, Go, Windows batch, git +# config, and lockfiles. A repository may add a deeper project-local +# `.editorconfig` for narrower source sections; analyzer components do not +# duplicate these universal text defaults. +# +# .gitattributes is the single authority for line endings (it transforms bytes +# on checkout); end_of_line here is an editor hint only. + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +end_of_line = lf + +# Markdown — trailing whitespace is significant (two trailing spaces = a hard +# line break); indentation is variable per CommonMark. +[*.{md,markdown}] +trim_trailing_whitespace = false +indent_size = unset + +# Config / serialization / data +[*.{json,jsonc,yml,yaml,toml}] +indent_size = 2 + +# JavaScript / TypeScript — 2-space, matching the shared Biome formatter policy +# (the single owner of JS/TS formatting). IndentSize is disabled in the checker, +# so this is an +# editor hint that keeps editors aligned with Biome. +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}] +indent_size = 2 + +# Go — gofmt is the sole formatter and indents with tabs, so the space default +# does not apply; indent enforcement is disabled here and deferred to gofmt +# (mirrors the JS/TS deferral to Biome). +[*.go] +indent_style = unset +indent_size = unset + +# Shell +[*.{sh,bash}] +indent_size = 2 + +# Windows batch — cmd.exe requires CRLF. +[*.{cmd,bat}] +end_of_line = crlf + +# Git config — git writes (and idiomatically uses) tab indentation under each +# section, so the space default does not apply. Covers the plain file, gitconfig +# includes, and chezmoi-style source forms (dot_gitconfig, dot_gitconfig.tmpl). +[{.gitconfig,*.gitconfig,dot_gitconfig*}] +indent_style = tab + +# Lockfiles — generated; suppress formatting enforcement. packages.lock.json is +# NuGet's lock file (written by restore with platform line endings and no final +# newline — hence end_of_line is unset too; .gitattributes still normalizes the +# committed bytes to LF). +[{package-lock.json,packages.lock.json,*.lock}] +end_of_line = unset +indent_size = unset +insert_final_newline = unset +trim_trailing_whitespace = unset diff --git a/.editorconfig-checker.json b/.editorconfig-checker.json new file mode 100644 index 0000000..e5f13a8 --- /dev/null +++ b/.editorconfig-checker.json @@ -0,0 +1,33 @@ +{ + "_comment": "Root-canonical editorconfig-checker policy (keys per editorconfig-checker 3.x). Validates files against the repo-root .editorconfig. Line endings are NOT checked here — .gitattributes is the single authority for EOL, so EndOfLine is disabled to avoid double-enforcement. IndentSize and MaxLineLength are disabled because indent width and line length are owned by per-language formatters and are IDE hints, not hard rules. Exclude[] entries are universal regular expressions; managed consumers do not edit this file and pass repository-specific excludes with -exclude (which combines additively). Version is intentionally blank so the config adopts cleanly on any 3.x engine; consumers pin the engine in CI.", + "Version": "", + "Verbose": false, + "Debug": false, + "IgnoreDefaults": false, + "SpacesAfterTabs": false, + "NoColor": false, + "Exclude": [ + "bin/", + "build/", + "\\.git/", + "\\.ruff_cache/", + "\\.lock$", + "\\.min\\.", + "node_modules/", + "obj/", + "package-lock\\.json$", + "packages\\.lock\\.json$", + "\\.venv/" + ], + "AllowedContentTypes": [], + "PassedFiles": [], + "Disable": { + "Charset": false, + "EndOfLine": true, + "Indentation": false, + "InsertFinalNewline": false, + "TrimTrailingWhitespace": false, + "IndentSize": true, + "MaxLineLength": true + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9056107 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,61 @@ +# Language/framework-agnostic base. `* text=auto eol=lf` normalizes text to LF in +# the repository AND checks it out LF on every platform (deterministic, regardless +# of a developer's core.autocrlf). Windows-native types below override to CRLF. A +# later line overrides an earlier one, per attribute. This file is the single +# authority for line endings (editorconfig end_of_line is an editor hint only). + +* text=auto eol=lf + +############################################################################### +# Richer diffs (line endings inherit the LF default above). +############################################################################### + +*.md diff=markdown +*.sh diff=bash +*.bash diff=bash + +############################################################################### +# PowerShell — LF is deliberate. Verified to run on both PowerShell 7 and +# Windows PowerShell 5.1. The PowerShell repo pins .ps1 to eol=lf; the popular +# community gitattributes template pins crlf — they target different eras. Listed +# explicitly so it is not "corrected" to crlf without a requirement to round-trip +# pre-existing CRLF-signed scripts unchanged. +############################################################################### + +*.ps1 text eol=lf +*.psm1 text eol=lf +*.psd1 text eol=lf + +############################################################################### +# Windows-native — cmd.exe requires CRLF (overrides the LF default above). +############################################################################### + +*.cmd text eol=crlf +*.bat text eol=crlf + +############################################################################### +# Lockfiles — tracked, but suppress noisy diffs (regenerate to resolve). +############################################################################### + +package-lock.json -diff +packages.lock.json -diff +*.lock -diff + +############################################################################### +# Binary — no line-ending conversion, no diff. +############################################################################### + +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.pdf binary +*.zip binary +*.gz binary +*.7z binary +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..913245e --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,14 @@ +# gitleaks — https://github.com/gitleaks/gitleaks +# Config reference: https://github.com/gitleaks/gitleaks#configuration +# +# Root-canonical policy: inherit the upstream default ruleset and add nothing +# repo-specific. Managed consumers do not edit this file. Intentional findings +# use a repository-owned .gitleaksignore or inline `gitleaks:allow` comment. + +# Floor the engine version: silences the "no minVersion specified" debug +# warning (v8.29.0+) and pins the feature baseline this config relies on +# (`[extend].useDefault`, `[[allowlists]]`). +minVersion = "v8.25.0" + +[extend] +useDefault = true diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..cc24fcf --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,41 @@ +{ + // GitHub Flavored Markdown (GFM) ruleset for markdownlint-cli2. + // The $schema URL pins to the markdownlint-cli2 devDependency of + // melodic-software/standards (its package.json), where this file is authored + // and synced into consumers from. Authoring-time validation only — it makes + // no claim about which markdownlint-cli2 version a consumer runs. In standards, + // bump it alongside that dependency; markdownlint-schema-pin.test.sh fails the + // standards build if the two drift apart. + // Rules reference: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md + // All rules are enabled by default; only deviations are listed below. + // Globs are passed by the caller (CLI / CI), not declared here. + "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/v0.23.2/schema/markdownlint-cli2-config-schema.json", + "ignores": [ + // Build, dependency, and cache trees — not authored markdown. + "**/node_modules/**", + "**/.venv/**", + "**/bin/**", + "**/obj/**" + ], + "config": { + // --- GFM-aligned style --- + "MD003": { "style": "atx" }, // ATX headings (# Heading) + "MD004": { "style": "dash" }, // Dash unordered-list bullets + "MD049": { "style": "asterisk" }, // *emphasis* + "MD050": { "style": "asterisk" }, // **strong** + "MD046": { "style": "fenced" }, // Fenced (not indented) code blocks + "MD048": { "style": "backtick" }, // Backtick (not tilde) code fences + "MD024": { "siblings_only": true }, // Allow duplicate headings under different parents + "MD060": false, // Table column style — disabled; allows any pipe spacing (its default flags tables matching no supported style) + + // --- Relaxed for GFM / prose --- + "MD013": false, // No hard line-length limit (tables and code exceed 80) + "MD025": false, // Allow multiple top-level (H1) sections + "MD028": false, // Allow blank lines between adjacent blockquotes + "MD033": false, // Allow inline HTML (
, ,
, ...) + "MD034": false, // Allow bare URLs (GFM autolinks them) + "MD036": false, // Allow bold text used as a pseudo-heading + "MD040": false, // Fenced code need not declare a language + "MD041": false // First line need not be a top-level heading (frontmatter) + } +} diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..1c8d2ef --- /dev/null +++ b/_typos.toml @@ -0,0 +1,58 @@ +# typos — https://github.com/crate-ci/typos +# Config reference: https://github.com/crate-ci/typos/blob/master/docs/reference.md +# Identifiers vs words: https://github.com/crate-ci/typos/blob/master/docs/design.md +# +# Root-canonical policy synced verbatim to managed consumers (read-only there). +# typos already respects .gitignore and skips binary files, so the lists below +# stay minimal. + +[default] +extend-ignore-re = [ + # Inline ignore directives. typos has no built-in pragma, so this config + # blesses a convention: silence a single line, the following line, or a + # block, without polluting the global allow-lists. Shell / JS comment forms. + "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", + # \r?\n tolerates a CRLF line ending, matching the CRLF-awareness of the sibling + # directives above (?Rm) and below (?s); a bare \n silently no-ops on a CRLF file. + "(#|//)\\s*spellchecker:ignore-next-line\\r?\\n.*", + "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on", + # Markdown / HTML comment form of the block directive — invisible in render. + "(?s).*?", + # Braced GUID / UUID literals — typos skips uniform-case UUIDs natively, but + # a mixed-case one trips the word splitter on its hex segments (e.g. `Ba54` + # splits to `Ba` -> "By"). Matches the canonical 8-4-4-4-12 brace form. + "\\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\}", +] + +# Domain words / proper nouns (case-insensitive, [default.extend-words]) and +# whole-token identifiers (case-sensitive, below): the base carries the +# CONSTELLATION-WIDE UNION. This file is synced verbatim to consumers and typos +# has no config layering, so a repo-specific exception cannot survive as a +# local edit — route a broadly valid exception here via an upstream PR; a repo +# with genuinely different policy owns the complete component locally. +# Each entry only skips its exact token, so the union is inert outside the +# repos that need it; keep every entry annotated. +[default.extend-identifiers] +# Win32 APPBARDATA variable ($abd) — dotfiles set-windows-prefs (taskbar auto-hide). +abd = "abd" +# Business Associate Agreements (HIPAA) — provisioning. +BAAs = "BAAs" +# Scansion vocalization for a stressed syllable ("DUM da DUM da") — songwriting +# plugin, prosody/meter craft docs. Corrected to "DUMB" by default. Case-sensitive +# and whole-token, so lowercase "dum" and any word containing it are unaffected. +DUM = "DUM" +# Features on Demand (Windows optional-feature packaging) — provisioning. +FoD = "FoD" +# NIC power-management registry value name — provisioning. +PnPCapabilities = "PnPCapabilities" +# Jasmine/Jest suite-skip global (the x-prefixed describe) — claude-code-plugins +# testing detector and any consumer with JS tests. Corrected to "describe" by +# default; whole-token, so prose "describe" is unaffected. +xdescribe = "xdescribe" + +[files] +# typos respects .gitignore by default, so only tracked paths that should never +# be spell-checked belong here. Minified bundles are the near-universal case. +extend-exclude = [ + "*.min.*", +] diff --git a/lychee.toml b/lychee.toml new file mode 100644 index 0000000..a02d1fa --- /dev/null +++ b/lychee.toml @@ -0,0 +1,83 @@ +# lychee ruleset — repo-agnostic link-checking config. +# The offline CI lane runs with --offline (external URLs skipped); the online +# advisory lane reuses this file with the network enabled. +# Ref: https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml + +# Verify #fragment/anchor targets resolve, not just the file path ("full" +# checks both anchor and text fragments). +include_fragments = "full" + +# A 429 is the SERVER rate-limiting the checker, never evidence the link is +# dead — and it lands on whichever host the shared CI runner IP happens to be +# throttled against that run, so it names a different healthy URL each time. The +# range restates lychee's own documented default (`--accept` help: +# `[default: 100..=103,200..=299]`, read from the pinned binary rather than +# recalled) so adding 429 widens the accepted set instead of silently narrowing +# it — `accept` REPLACES the default, it does not extend it. +# +# Deliberately NOT an exclude entry per rate-limited host: excluding a live URL +# to silence a transient throttle stops checking a link that is fine, and the +# next run just throttles a different one. +accept = ["100..=103", "200..=299", "429"] + +# Build/dependency trees — not authored content. Values are regex (single-quoted +# TOML literals, so backslashes are not double-escaped). +exclude_path = [ + 'node_modules', + '\.venv', + '(^|/)bin/', + '(^|/)obj/', +] + +# URL excludes for the online lane (the offline lane skips URLs entirely): +# exact bot-blocked URLs, auth-walled private repos, loopback, placeholders, and +# hosts the checker cannot complete a TLS handshake against. Every entry is a +# CHECKER-side limitation on a link verified to be alive — never a dead link +# silenced. Each one below records the verification that earned it, so a future +# reader can re-test rather than trust the list. +exclude = [ + # ACM blocks automated clients outright: both URLs answer 403 even with a full + # browser User-Agent (verified 2026-07-30), so no header tuning reaches them. + # A DOI link is also the most stable citation form there is — the failure is + # entirely the checker being refused at the door. + '^https?://dl\.acm\.org/doi/10\.1145/3611643\.3613871/?([?#].*)?$', + '^https?://queue\.acm\.org/detail\.cfm\?id=3454124$', + # ISO blocks automated clients outright: 403 to the checker (verified + # 2026-08-11), so the failure is entirely the checker being refused at the + # door — the cited standard page is the stable canonical link. + '^https?://www\.iso\.org/standard/78907\.html/?([?#].*)?$', + # 403 to the checker, 200 with a browser User-Agent (verified 2026-07-30) — + # the documented bot-block case this list already exists for. + '^https?://docs\.genius\.com/?([?#].*)?$', + # Reported by the checker as "SSL certificate not trusted". The chain verifies + # locally — `openssl s_client` returns `Verify return code: 0 (ok)` against a + # Cloudflare TLS Issuing ECC CA intermediate, and curl fetches it 200 under + # strict verification (both 2026-07-30) — so this is the runner or checker + # trust store failing to complete an ECC chain, not an untrustworthy host. + # Re-test before removing; a genuinely bad chain would fail locally too. + '^https?://www\.ntia\.gov/files/ntia/publications/sbom_minimum_elements_report\.pdf$', + '^https?://(www\.)?x\.com/', + '^https?://twitter\.com/', + '^https?://(www\.)?linkedin\.com/', + '^https?://bsky\.app/', + '^https?://(www\.)?medium\.com/@ziobrando/the-rise-and-fall-of-the-dungeon-master-c2d511eed12f/?([?#].*)?$', + '^https?://(www\.)?medium\.com/fortmatic/postmortem-service-disruption-from-expired-ssl-certificate-a993a59272a0/?([?#].*)?$', + '^https?://dev\.mysql\.com/doc/refman/8\.4/en/innodb-transaction-isolation-levels\.html/?([?#].*)?$', + '^https?://help\.miro\.com/hc/en-us/articles/31624028247058/?([?#].*)?$', + '^https?://isdown\.app/status/anthropic/?([?#].*)?$', + '^https?://(www\.)?npmjs\.com/package/(firecrawl-cli|@mirohq/miro-api)/?([?#].*)?$', + '^https?://www\.w3\.org/International/wiki/WorkingWithTimeZones/?([?#].*)?$', + '^https?://localhost', + '^https?://127\.0\.0\.1', + '^https?://example\.(com|org)', + # PRIVATE GitHub repos: valid links for authenticated collaborators, but the + # online lane's credential is the calling repo's GITHUB_TOKEN (or nothing + # locally), which cannot read any other private repo, so GitHub answers 404. + # Listed per repo, not per owner, so links to public siblings stay checked. + # Keep in sync with actual visibility: add newly created private repos here; + # drop an entry when its repo goes public. The optional \.git arm covers + # HTTPS clone URLs: the dot after the repo name would otherwise miss the + # [/#?] boundary. + '^https?://github\.com/melodic-software/(dotfiles|github-iac|itinerary-planner|knowledge-corpus|medley-archive|medley|melodic-main-archive|provisioning|songwriting)(\.git)?([/#?]|$)', + '^https?://raw\.githubusercontent\.com/melodic-software/(dotfiles|github-iac|itinerary-planner|knowledge-corpus|medley-archive|medley|melodic-main-archive|provisioning|songwriting)/', +]