From 451e78842480f24247d235feacea8604b22777a3 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 17 Oct 2026 17:43:32 -0700 Subject: [PATCH 1/9] State the Fleet Standard for Script and Utility File Names Add a General subsection "Script and Utility File Names" to CODESTYLE.md carrying the agreed table, its clauses, and its stated departures. Add a Repo-Scoped Secrets bullet stating that a secret file's stem is lowercase snake_case, and point the python-codestyle Skill's module naming at the new section so the rule has one home. Co-Authored-By: Claude Opus 5.5 --- .../python-codestyle/references/code-style.md | 3 ++- .../.source-digests/python-codestyle | 2 +- .../python-codestyle/references/code-style.md | 3 ++- .../python-codestyle/references/code-style.md | 3 ++- CODESTYLE.md | 26 +++++++++++++++++++ GOVERNANCE.md | 1 + 6 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.agents/skills/python-codestyle/references/code-style.md b/.agents/skills/python-codestyle/references/code-style.md index 45f355bc..1941e0f3 100644 --- a/.agents/skills/python-codestyle/references/code-style.md +++ b/.agents/skills/python-codestyle/references/code-style.md @@ -64,7 +64,8 @@ ## Naming -- `snake_case` for functions, methods, variables, modules, package directories. +- `snake_case` for functions, methods, variables, modules, package directories. A module's file + name follows CODESTYLE.md's "Script and Utility File Names". - `PascalCase` for classes, type aliases, type vars, enum members. - `UPPER_SNAKE_CASE` for module-level constants. - Single leading underscore for module-private, double leading underscore for name-mangled (rare, diff --git a/.claude-plugin/fleet-skills/.source-digests/python-codestyle b/.claude-plugin/fleet-skills/.source-digests/python-codestyle index 6859deaa..07ed7b54 100644 --- a/.claude-plugin/fleet-skills/.source-digests/python-codestyle +++ b/.claude-plugin/fleet-skills/.source-digests/python-codestyle @@ -1 +1 @@ -047e89b805ed646f +bba583635a70530a diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md index 45f355bc..1941e0f3 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/references/code-style.md @@ -64,7 +64,8 @@ ## Naming -- `snake_case` for functions, methods, variables, modules, package directories. +- `snake_case` for functions, methods, variables, modules, package directories. A module's file + name follows CODESTYLE.md's "Script and Utility File Names". - `PascalCase` for classes, type aliases, type vars, enum members. - `UPPER_SNAKE_CASE` for module-level constants. - Single leading underscore for module-private, double leading underscore for name-mangled (rare, diff --git a/.github/skills/python-codestyle/references/code-style.md b/.github/skills/python-codestyle/references/code-style.md index 45f355bc..1941e0f3 100644 --- a/.github/skills/python-codestyle/references/code-style.md +++ b/.github/skills/python-codestyle/references/code-style.md @@ -64,7 +64,8 @@ ## Naming -- `snake_case` for functions, methods, variables, modules, package directories. +- `snake_case` for functions, methods, variables, modules, package directories. A module's file + name follows CODESTYLE.md's "Script and Utility File Names". - `PascalCase` for classes, type aliases, type vars, enum members. - `UPPER_SNAKE_CASE` for module-level constants. - Single leading underscore for module-private, double leading underscore for name-mangled (rare, diff --git a/CODESTYLE.md b/CODESTYLE.md index fa23048c..cf634d8b 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -41,6 +41,29 @@ A command-line script is one a person or an agent runs by name. A hook its host - **Every command-line script answers `--help` and `-h`** with the same text on stdout, then exits 0, changing nothing. That text opens on a usage line naming the script. An agent learns a script's interface by asking it, so a script that reads `--help` as an input acts in a way nobody predicted. - **An argument the script does not recognize is refused**, with a non-zero exit before any read or write. The refusal names it as an unknown or unrecognized option, argument, flag, or parameter, and PowerShell's own refusal of a parameter it cannot bind counts. An unrecognized first argument is never taken as a default mode or as a target. +### Script and Utility File Names + +A script or utility file is named by its type, so a repository never mixes conventions for one kind of file. + +| Type | Rule | Basis | +| --- | --- | --- | +| Python `.py` | lowercase snake_case | [PEP 8][pep8-names] module naming. A hyphenated module also cannot be imported | +| Shell `.sh` and PowerShell `.ps1` | lowercase-hyphenated | The Linux command convention. A cross-platform pair keeps one name, `sync-tools.sh` beside `sync-tools.ps1` | +| Windows batch `.cmd` and `.bat` | lowercase-hyphenated | The same convention as shell and PowerShell. A shim that puts a command on `PATH` takes the name of the command it provides, `sync-tools.cmd` launching `sync-tools.ps1` | +| PowerShell functions inside a `.ps1` | Verb-Noun with an approved verb (`Get-Verb`) | PSScriptAnalyzer `PSUseApprovedVerbs`. This is a command rule, not a file rule | +| Env and secret files | lowercase snake_case stem | `mail_relay.env`, `db_root_password` | +| Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | + +- **Installed commands.** An extensionless executable placed on `PATH` or into a system slot (systemd, cron, run-parts, motd) is named as a command: lowercase-hyphenated with no extension, whatever language it is written in. +- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. +- **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module or `PSScriptAnalyzerSettings.psd1`, which PSScriptAnalyzer finds by that name. It also covers any name a platform defines. +- **Directories are out of scope.** + +Departures, stated on purpose so a reader does not "fix" them back: + +- The [Google Shell Style Guide][google-shell] asks for underscores in shell file names. The fleet uses hyphens instead, matching installed Linux commands and the existing scripts. +- Microsoft's [approved verbs][ps-approved-verbs] govern command names. A `.ps1` run by path is a file rather than an exported command, so the file takes the shell convention and its functions take Verb-Noun. + ### Markdown and Spelling These apply repo-wide, in every directory: Markdown lints clean via `markdownlint-cli2` against the shared config, spelling is US English via CSpell against the shared `cspell.json`, the CI spelling gate covers `README.md` and `HISTORY.md` only, `HISTORY.md` mirrors the README's opening, and "Markdown" is a proper noun in prose. A repo excluding a subtree of its own that it does not treat as authored prose, a committed data archive, a vendored theme, or a hand-maintained record, puts a `.markdownlint-cli2.jsonc` carrying its own `ignores` beside that content rather than editing the shared root config, whose contents are fleet-fixed. Those `ignores` patterns resolve against the directory holding them rather than against the repo root, so a repo-root-relative entry there matches nothing and reports no error saying so, and excluding through the CI workflow's own negated Markdown glob input instead is a CI-only fix that leaves the same files flagged for anyone running the linter locally. The full rules are in the `comment-and-doc-style` Skill referenced above. @@ -67,7 +90,10 @@ Bash, and only where a program cannot be Python: a bootstrap that installs the i +[google-shell]: https://google.github.io/styleguide/shellguide.html [governance]: ./GOVERNANCE.md [governance-verification-discipline]: ./GOVERNANCE.md#verification-discipline +[pep8-names]: https://peps.python.org/pep-0008/#package-and-module-names +[ps-approved-verbs]: https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands [readme]: ./README.md [root]: ./.editorconfig diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 9038d571..e71c9501 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -109,6 +109,7 @@ Line-ending governance for an operational repo is in [Line Endings](#line-ending A repo whose own stacks or scripts read local runtime credentials from disk, most commonly an operational repo's Docker Compose stack, keeps them in a dotted `.secrets/` directory at the repo root. This is the repo-scoped counterpart to the host-scoped `~/.secrets/` convention a repo's own `OPERATIONS.md` may document, and it is a different thing from `spec/secrets.json`, the CI/GitHub Actions secret-name registry `spec/audit.py` cross-checks. `spec/secrets.json` governs what a workflow reads from GitHub Actions. This convention governs what a repo's own process reads from its own checkout. - **The directory is named `.secrets/`, dotted, never a bare `secrets/`.** +- **A secret file's stem is lowercase snake_case** (`homeassistant_db_password`, `mail_relay.env`), per [`CODESTYLE.md` "Script and Utility File Names"](./CODESTYLE.md#script-and-utility-file-names). - **A single opaque credential file carries no extension** (`homeassistant_db_password`, not `homeassistant_db_password.txt`), the same reason `README` and `LICENSE` carry none. It is a security property, read but never sourced, not a formatting preference. - **A structured credential keeps its format's extension** (`.json` for structured config). - **The shared env file is named for what it configures**, not a bare `.env` (`docker.env` for a repo whose stacks are Docker Compose), so a second env-shaped file added later stays unambiguous. From 255618391afbb547caa621838a5f2cb2c9e5743e Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 17 Oct 2026 17:47:22 -0700 Subject: [PATCH 2/9] Split Long Sentences and File External Links Under Their Own Group Split the installed-commands and data-file sentences, move the three third-party links into an External group with -link names, and drop the claim that hyphens match a repository's existing scripts, which does not hold in every repository carrying the file. Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index cf634d8b..d6ec6c71 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -47,22 +47,22 @@ A script or utility file is named by its type, so a repository never mixes conve | Type | Rule | Basis | | --- | --- | --- | -| Python `.py` | lowercase snake_case | [PEP 8][pep8-names] module naming. A hyphenated module also cannot be imported | +| Python `.py` | lowercase snake_case | [PEP 8][pep8-names-link] module naming. A hyphenated module also cannot be imported | | Shell `.sh` and PowerShell `.ps1` | lowercase-hyphenated | The Linux command convention. A cross-platform pair keeps one name, `sync-tools.sh` beside `sync-tools.ps1` | | Windows batch `.cmd` and `.bat` | lowercase-hyphenated | The same convention as shell and PowerShell. A shim that puts a command on `PATH` takes the name of the command it provides, `sync-tools.cmd` launching `sync-tools.ps1` | | PowerShell functions inside a `.ps1` | Verb-Noun with an approved verb (`Get-Verb`) | PSScriptAnalyzer `PSUseApprovedVerbs`. This is a command rule, not a file rule | | Env and secret files | lowercase snake_case stem | `mail_relay.env`, `db_root_password` | | Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | -- **Installed commands.** An extensionless executable placed on `PATH` or into a system slot (systemd, cron, run-parts, motd) is named as a command: lowercase-hyphenated with no extension, whatever language it is written in. +- **Installed commands.** An extensionless executable placed on `PATH` or into a system slot (systemd, cron, run-parts, motd) is named as a command. That name is lowercase-hyphenated with no extension, whatever language it is written in. - **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. -- **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module or `PSScriptAnalyzerSettings.psd1`, which PSScriptAnalyzer finds by that name. It also covers any name a platform defines. +- **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. It also covers any name a platform defines. - **Directories are out of scope.** Departures, stated on purpose so a reader does not "fix" them back: -- The [Google Shell Style Guide][google-shell] asks for underscores in shell file names. The fleet uses hyphens instead, matching installed Linux commands and the existing scripts. -- Microsoft's [approved verbs][ps-approved-verbs] govern command names. A `.ps1` run by path is a file rather than an exported command, so the file takes the shell convention and its functions take Verb-Noun. +- The [Google Shell Style Guide][google-shell-link] asks for underscores in shell file names. The fleet uses hyphens instead, matching installed Linux commands. +- Microsoft's [approved verbs][ps-approved-verbs-link] govern command names. A `.ps1` run by path is a file rather than an exported command, so the file takes the shell convention and its functions take Verb-Noun. ### Markdown and Spelling @@ -90,10 +90,13 @@ Bash, and only where a program cannot be Python: a bootstrap that installs the i -[google-shell]: https://google.github.io/styleguide/shellguide.html [governance]: ./GOVERNANCE.md [governance-verification-discipline]: ./GOVERNANCE.md#verification-discipline -[pep8-names]: https://peps.python.org/pep-0008/#package-and-module-names -[ps-approved-verbs]: https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands [readme]: ./README.md [root]: ./.editorconfig + + + +[google-shell-link]: https://google.github.io/styleguide/shellguide.html +[pep8-names-link]: https://peps.python.org/pep-0008/#package-and-module-names +[ps-approved-verbs-link]: https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands From 503b79926b623bd4d35d70e5f73429bd02c661d6 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 17 Oct 2026 17:50:11 -0700 Subject: [PATCH 3/9] Drop the Unqualified Lead Sentence From Script and Utility File Names The lead sentence said a repository never mixes conventions for one kind of file, which the section's own wrapper, installed-command, and externally-fixed exemptions contradict. Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index d6ec6c71..2584f294 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -43,8 +43,6 @@ A command-line script is one a person or an agent runs by name. A hook its host ### Script and Utility File Names -A script or utility file is named by its type, so a repository never mixes conventions for one kind of file. - | Type | Rule | Basis | | --- | --- | --- | | Python `.py` | lowercase snake_case | [PEP 8][pep8-names-link] module naming. A hyphenated module also cannot be imported | From 59ff2d9c44ddb0bfe76c900d3f660b6ba0d9b23a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 18 Oct 2026 07:49:51 -0700 Subject: [PATCH 4/9] State That an Installed Extensionless Launcher Takes the Command Name The wrapper exemption now covers only a launcher that keeps its extension, per the answer on #3199. Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index 2584f294..6bb1e2ef 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -53,7 +53,7 @@ A command-line script is one a person or an agent runs by name. A hook its host | Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | - **Installed commands.** An extensionless executable placed on `PATH` or into a system slot (systemd, cron, run-parts, motd) is named as a command. That name is lowercase-hyphenated with no extension, whatever language it is written in. -- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. +- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher that keeps its extension. An extensionless launcher installed onto `PATH` or into a system slot is an installed command, so it is `fetch-report` rather than `fetch_report`. - **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. It also covers any name a platform defines. - **Directories are out of scope.** From 8e0ea9a442f6e0baff0bcad8454f015868b1207f Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 18 Oct 2026 08:11:11 -0700 Subject: [PATCH 5/9] Name Any Launcher Installed onto PATH as a Command A launcher that keeps its extension, such as a Windows .cmd shim, is an installed command too once it is on PATH, so the wrapper exemption covers only a launcher run by its path. This removes the second answer the .cmd row gave for such a shim. Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index 6bb1e2ef..82c47c81 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -53,7 +53,7 @@ A command-line script is one a person or an agent runs by name. A hook its host | Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | - **Installed commands.** An extensionless executable placed on `PATH` or into a system slot (systemd, cron, run-parts, motd) is named as a command. That name is lowercase-hyphenated with no extension, whatever language it is written in. -- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher that keeps its extension. An extensionless launcher installed onto `PATH` or into a system slot is an installed command, so it is `fetch-report` rather than `fetch_report`. +- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher run by its path. A launcher installed onto `PATH` or into a system slot is an installed command, so it is `fetch-report`, or `fetch-report.cmd` for a Windows shim, rather than `fetch_report`. - **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. It also covers any name a platform defines. - **Directories are out of scope.** From b930cad6e8713355dc8538891b8c64906da34298 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 18 Oct 2026 08:11:24 -0700 Subject: [PATCH 6/9] Split the Installed Launcher Sentence Under the Length Limit Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index 82c47c81..54e1ecb2 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -53,7 +53,7 @@ A command-line script is one a person or an agent runs by name. A hook its host | Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | - **Installed commands.** An extensionless executable placed on `PATH` or into a system slot (systemd, cron, run-parts, motd) is named as a command. That name is lowercase-hyphenated with no extension, whatever language it is written in. -- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher run by its path. A launcher installed onto `PATH` or into a system slot is an installed command, so it is `fetch-report`, or `fetch-report.cmd` for a Windows shim, rather than `fetch_report`. +- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher run by its path. A launcher installed onto `PATH` or into a system slot is an installed command. It is `fetch-report`, or `fetch-report.cmd` for a Windows shim, rather than `fetch_report`. - **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. It also covers any name a platform defines. - **Directories are out of scope.** From e1685da7da9c047c43147938de173f6c68b44519 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 18 Oct 2026 08:12:40 -0700 Subject: [PATCH 7/9] Define an Installed Command Without Requiring It Be Extensionless The Installed commands clause now covers every executable or launcher on PATH or in a system slot, keeping an extension only where the platform needs one, so it agrees with the wrapper exemption's PATH case. Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index 54e1ecb2..59158765 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -52,7 +52,7 @@ A command-line script is one a person or an agent runs by name. A hook its host | Env and secret files | lowercase snake_case stem | `mail_relay.env`, `db_root_password` | | Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | -- **Installed commands.** An extensionless executable placed on `PATH` or into a system slot (systemd, cron, run-parts, motd) is named as a command. That name is lowercase-hyphenated with no extension, whatever language it is written in. +- **Installed commands.** An executable or launcher on `PATH` or in a system slot (systemd, cron, run-parts, motd) is named as a command, whatever its language. That name is lowercase-hyphenated. It carries no extension unless the platform needs one to run it, such as `.cmd` or `.ps1` on Windows. - **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher run by its path. A launcher installed onto `PATH` or into a system slot is an installed command. It is `fetch-report`, or `fetch-report.cmd` for a Windows shim, rather than `fetch_report`. - **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. It also covers any name a platform defines. - **Directories are out of scope.** From b59b663a13c7086aa95a0deed6e73960f388f735 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 18 Oct 2026 08:26:02 -0700 Subject: [PATCH 8/9] Point the Wrapper Exemption at the Installed Commands Rule Rather Than Listing Names Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index 59158765..1a5445f0 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -53,7 +53,7 @@ A command-line script is one a person or an agent runs by name. A hook its host | Env variable names and CI secret names | UPPER_SNAKE_CASE | The POSIX and GitHub convention | - **Installed commands.** An executable or launcher on `PATH` or in a system slot (systemd, cron, run-parts, motd) is named as a command, whatever its language. That name is lowercase-hyphenated. It carries no extension unless the platform needs one to run it, such as `.cmd` or `.ps1` on Windows. -- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher run by its path. A launcher installed onto `PATH` or into a system slot is an installed command. It is `fetch-report`, or `fetch-report.cmd` for a Windows shim, rather than `fetch_report`. +- **Wrapper exemption.** A thin launcher whose only job is to run a Python tool keeps that tool's stem, so `fetch_report.py`, `fetch_report.sh`, and `fetch_report.ps1` stay grouped together. It covers only a launcher run by its path. A launcher installed onto `PATH` or into a system slot is an installed command instead, such as `fetch-report` rather than `fetch_report`. - **Externally fixed names are exempt.** That covers Git hook names, `__init__.py`, `conftest.py`, and Pester's `.Tests.ps1` suffix, whose stem mirrors the script under test. It covers a PowerShell data file (`.psd1`) whose name a tool fixes, such as a module manifest named for its module. `PSScriptAnalyzerSettings.psd1` is another, since PSScriptAnalyzer finds it by that name. It also covers any name a platform defines. - **Directories are out of scope.** From 00df00462c98b798af64c699781b4536d46ff680 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 18 Oct 2026 08:44:17 -0700 Subject: [PATCH 9/9] Limit the Hyphenated-Module Claim to the import Statement Co-Authored-By: Claude Opus 5.5 --- CODESTYLE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODESTYLE.md b/CODESTYLE.md index 1a5445f0..da7f86f5 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -45,7 +45,7 @@ A command-line script is one a person or an agent runs by name. A hook its host | Type | Rule | Basis | | --- | --- | --- | -| Python `.py` | lowercase snake_case | [PEP 8][pep8-names-link] module naming. A hyphenated module also cannot be imported | +| Python `.py` | lowercase snake_case | [PEP 8][pep8-names-link] module naming. A hyphenated module also cannot be named in an `import` statement | | Shell `.sh` and PowerShell `.ps1` | lowercase-hyphenated | The Linux command convention. A cross-platform pair keeps one name, `sync-tools.sh` beside `sync-tools.ps1` | | Windows batch `.cmd` and `.bat` | lowercase-hyphenated | The same convention as shell and PowerShell. A shim that puts a command on `PATH` takes the name of the command it provides, `sync-tools.cmd` launching `sync-tools.ps1` | | PowerShell functions inside a `.ps1` | Verb-Noun with an approved verb (`Get-Verb`) | PSScriptAnalyzer `PSUseApprovedVerbs`. This is a command rule, not a file rule |