docs: add technical writing guide

This commit is contained in:
2026-08-26 15:55:16 -04:00
parent 32ef8edbed
commit 09d8e72065
5 changed files with 275 additions and 102 deletions

208
AGENTS.md
View File

@@ -1,110 +1,118 @@
# dotfiles-nixos # dotfiles-nixos
One flake that builds every machine the user owns. One flake builds every machine I own.
The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overlays) lives in `~/Documents/ai-artifacts/projects/dotfiles/003-dotfiles-context.md`.
## Conventions ## Repository Conventions
- Comments posted to Gitea (pull requests, issues, reviews) go out under the operator's account, so sign every one to make clear the author is the agent, not the operator. - Follow `docs/technical-writing.md` for comments, user-facing descriptions, commit prose, pull-request prose, and Markdown documentation.
End the comment with a `— Claude` sign-off. - Comments posted to the forge under the operator's account must end with `— Pi`.
(A dedicated bot account may replace this later. The sign-off identifies the agent as the author instead of the operator.
Until then, the sign-off is the only marker.) - Commit messages follow Conventional Commits, defined in `docs/conventional-commits.md`.
- Commit messages follow Conventional Commits, specified in `docs/conventional-commits.md`. Use a scope for the changed module or host, such as `fish`, `nvim`, or `neogaia`.
Scope is the module or host the change belongs to (`fish`, `nvim`, `neogaia`), omitted for repo-wide changes. - Keep commit messages about the Git history, not the forge workflow.
Keep messages free of Gitea-specific references: this repository is mirrored to GitHub, where issue and pull-request numbers resolve to unrelated things. Do not reference issue or pull-request numbers unless the commit itself requires a stable external reference.
- When a graphical application is added, give it a `window-rewrite` icon mapping in `modules/desktop/waybar.nix`. - Use `gitea-axi` for Gitea issues, pull requests, labels, reviews, comments, and milestones.
Without one its windows fall back to the generic default glyph on the workspace indicator instead of showing a recognisable per-application icon. It resolves this repository from the `origin` remote inside the checkout.
Match on the window class, which `hyprctl clients -j | jq -r '.[].class' | sort -u` lists for the running session.
## Dotfiles Conventions
### Desktop
- When adding a graphical application, add a `window-rewrite` icon mapping in `modules/desktop/waybar.nix`.
The workspace indicator uses this mapping to show an application icon instead of the generic default glyph.
Match the mapping on the window class.
Run `hyprctl clients -j | jq -r '.[].class' | sort -u` to list classes in the running session.
- Set Hyprland session environment variables in `modules/desktop/hyprland/hyprland.nix` with `env = KEY,VALUE`.
`home.sessionVariables` do **not** reach the Hyprland session because UWSM does not source `hm-session-vars.sh`.
Source cursor values from `config.stylix.cursor`.
Set `XCURSOR_*` and `HYPRCURSOR_*` to the same theme for Bibata.
- Configure the Steam desktop client as an XWayland application.
SDL Wayland selectors do not make the visible client native Wayland.
Keep fractional scaling sharp with Hyprland's `xwayland.force_zero_scaling` and Steam's `STEAM_FORCE_DESKTOPUI_SCALING`.
### Firefox
- Manage Firefox search engines through `programs.firefox.search` with `force = true`.
Hide built-in engines with `<engine>.metaData.hidden = true` because omission does not remove them.
Use current engine ids, such as `default = "ddg"` instead of `"DuckDuckGo"`.
Decode the built `search.json.mozlz4` with `mozlz4a -d <search.json.mozlz4>` to check the result.
- Avoid Home Manager Firefox extension settings unless the whole profile can use the legacy extension-storage backend.
Any non-empty `profiles.<name>.extensions.settings.<id>.settings` sets `extensions.webextensions.ExtensionStorageIDB.enabled = false` for the whole profile.
### Neovim
- Nixvim's flake input follows the root nixpkgs source, but its Home Manager module does not reuse the host's `pkgs` instance.
Keep `programs.nixvim.nixpkgs.useGlobalPackages = true`.
That setting makes Nixvim use the shared package set without warning that its source default changed.
### AI Tooling
- Use produced literals for Herdr key names that include shifted punctuation.
For example, use `prefix+<` instead of `prefix+shift+comma`.
- Treat `~/.pi/agent/skills` as generated output from Home Manager.
Do not edit skills in that directory.
A new file created there stays outside the repo.
Shared global skills come from the `skills` flake through `modules/agents/skills.nix`.
Update the `skills` flake input, then rebuild.
- Keep generated `.agents/skills/.gitignore` files from ignoring symlinked skills.
Pi skill discovery honors `.gitignore`, `.ignore`, and `.fdignore` inside scanned skill directories.
An ignored symlinked skill does not load even when `.agents/skills/<name>/SKILL.md` exists.
- Write directly to flake-managed Pi extension, prompt, and skill directories only for throwaway development or local experiments.
Home Manager activation can overwrite or hide unmanaged files in those directories.
Promote finished work into the dotfiles module before treating it as deployed.
## Gotchas ## Gotchas
### Architecture
- Guest modules are host-agnostic wiring, while host files own deployment-specific service configuration such as served volumes and host paths. - Guest modules are host-agnostic wiring, while host files own deployment-specific service configuration such as served volumes and host paths.
For example, `guests/copyparty.nix` should expose options and translate them into guest mounts/service settings, while `hosts/pikachu/default.nix` chooses `/inbox`, `/media`, and their backing paths. For example, `guests/copyparty.nix` should expose options and translate them into guest mounts/service settings, while `hosts/pikachu/default.nix` chooses `/inbox`, `/media`, and their backing paths.
- `sops.secrets.<name>.owner` must name a user known on the decrypting host. - `self.lib.registry.collect { hosts = self.nixosConfigurations; }` reads only top-level host `config.registry` declarations.
For guest-only service users, do not set the owner to the guest uid unless the host also declares that user. Registry declarations inside guest interiors live under `config.containers.<guest>.config.registry`.
Use a host-visible owner plus a shared group mode when an identity-mapped guest service can read through its group. The collected registry does not include those guest-interior declarations.
- Actual Budget's NixOS service runs `actual-server` with `WorkingDirectory = services.actual.settings.dataDir`, and the `actual-server` CLI wrapper sets `ACTUAL_CONFIG_PATH=./config.json` when that current directory contains `config.json`, overriding the NixOS module's generated config path before the server loads configuration. Publish guest values from host-side code, or extend the collector to recurse into guests.
When migrating old community-script state to the normal upstream `/data` shape, move or rewrite a copied `/data/config.json` so it cannot shadow the declarative module settings. See http://git.alexion.dev/alexion/dotfiles/issues/65.
- Subagent completion delivery is non-blocking through immediate spawn, milestone notifications, retained terminal entries, and `subagent_list` or `subagent_result` retrieval. - A NixOS module function argument default such as `registry ? config.registry` does not safely handle an unprovided module argument.
`subagent_wait` intentionally blocks the parent tool call until its condition or timeout, so do not use it merely to keep background work alive during an interactive workflow. The module system still queries `_module.args.registry`.
- Nixvim's flake input following the root nixpkgs source does not make its Home Manager module reuse the host's `pkgs` instance. Evaluation fails when that argument is absent.
Keep `programs.nixvim.nixpkgs.useGlobalPackages = true` so Nixvim uses the shared package set without warning that its source default was affected. For an optional special-argument fallback, bind module arguments through `@args`.
- This host has no `python` or `python3` command on its ordinary `PATH`. Test `args ? <name>` inside the module body.
For ad hoc Python, use Nix explicitly, such as `nix shell nixpkgs#python3 -c python3 <script>`.
- ADR bodies are immutable records of decisions as they were made, while frontmatter is mutable. ### Build Inputs
When a decision changes or its premise proves wrong, preserve the original body, update its status, and add a new ADR that supersedes it.
Filename migrations preserve references in immutable bodies through frontmatter aliases rather than rewriting those bodies. - `chaotic-nyx` must **not** follow this flake's `nixpkgs`.
- This repo pins no Nix formatter, and its committed `.nix` files are not clean under current `nixfmt-rfc-style`. Chaotic builds its packages against its own pinned `nixpkgs`.
Running `nixfmt` across a file reflows untouched code (for example `lib.nix`'s `deriveMac` list and multi-line assertion messages) and injects churn unrelated to the change. Its overlay defaults to `onTopOf = "flake-nixpkgs"`, the cache-friendly path.
Format only the lines being written or changed, matching the surrounding style by hand. That setup lets `nyx-cache.chaotic.cx` serve the CachyOS kernel instead of building it from source.
- This repo is developed on `neogaia`, which now runs the NixOS it builds. Chaotic packages do not see this flake's `unstable` or `stable` overlays.
Flakes and the chaotic substituter come from this flake's own `nix.settings`, so no `NIX_CONFIG` export or per-command `--extra-experimental-features` is needed, and building a toplevel with `boot.kernelPackages = linuxPackages_cachyos` fetches the kernel from `nyx-cache` rather than compiling it.
Both were true only while the machine still ran CachyOS against a distro Nix daemon. ### Deployment
- Git identity is declared in the flake by `modules/git.nix`, which writes `alexion <contact@alexion.dev>` — the identity all history uses — on any host enabling `modules.git`.
Every new host has to enable it, so that a host reads as a full checklist of what it carries.
It is deployed on `neogaia` and verified: a commit in a repository outside this checkout is authored `alexion <contact@alexion.dev>` with no override.
Verify it that way rather than from this checkout, whose `.git/config` carries the same identity and would mask a broken module.
`~/.gitconfig` (a second global file that outranks the flake-managed `~/.config/git/config`) currently holds only a `tea` credential helper and no `user.*`, so it does not shadow the identity, but it is undeclared and will not survive a reimage.
- The primary build/verify seam is `nix flake check`, which builds project-level checks plus `checks.x86_64-linux.<host>` system toplevels.
Cheap targeted host checks use `nix eval .#nixosConfigurations.<host>.config...`.
- chaotic-nyx must **not** follow our `nixpkgs`, and its packages are built against chaotic's own pinned nixpkgs (its overlay defaults to `onTopOf = "flake-nixpkgs"`, the cache-friendly path).
That is what lets the `nyx-cache.chaotic.cx` binary cache hit instead of compiling the CachyOS kernel from source.
The tradeoff is that chaotic packages do not see our `unstable`/`stable` overlays.
- The remote is self-hosted Gitea (`git.alexion.dev`), and the forge CLI is `gitea-axi` rather than `tea`.
`gitea-axi` resolves the repository from the `origin` remote and discovers credentials from a `tea` login whose host matches the remote, so both are implicit inside a checkout.
It is installed on `neogaia` by `modules.agents.tools.gitea-axi`, and verified: `gitea-axi` run from this checkout renders the `alexion/dotfiles` dashboard authenticated, so the claude-code `SessionStart` hook that runs it now resolves to a real binary rather than a missing one.
The package wraps the binary so `git` and `tea` are reachable without being on `PATH`, while still preferring the operator's own where present.
Credentials: `~/.config/tea/config.yml` holds a token-bearing login named `alexion`, which `gitea-axi` uses and which also opens pull requests directly with `nix run nixpkgs#tea -- pr create --login alexion --repo alexion/dotfiles --base main --head <branch> ...`.
The `--repo` flag is required on that path, since `tea` resolves `origin` only for a login whose SSH host matches.
The same token reads PR discussion, which `tea` itself does poorly: `tea pr <n> --comments` prints only the body, and `-f comments` returns no comments field at all.
Use the API instead, taking the token from `.logins[] | select(.name=="alexion") | .token`.
Review comments are **not** at `/issues/<n>/comments` — that endpoint holds only top-level discussion and is usually empty.
Inline comments need two calls: `/pulls/<n>/reviews` for the review ids, then `/pulls/<n>/reviews/<id>/comments` for the bodies, whose `path` and `diff_hunk` fields say what each one is attached to.
A review row with an empty `body` is the normal shape when the operator left only inline comments.
- `~/.claude/skills` and `~/.pi/agent/skills` are home-manager-generated (`recursive = true`), so editing a skill in place fails and a new file created there silently escapes the repo.
Shared global skills come from the `skills` flake through `modules/agents/skills.nix`, applied by a rebuild.
Claude-specific legacy skills, when kept, live under `modules/agents/claude-code/skills/<name>/`.
- Pi skill discovery honors `.gitignore`, `.ignore`, and `.fdignore` inside scanned skill directories.
A generated `.agents/skills/.gitignore` entry that ignores a symlinked skill also prevents Pi from loading that skill, even when `.agents/skills/<name>/SKILL.md` exists and the symlink target is valid.
- nixpkgs `vimPlugins.nord-nvim` is `shaunsingh/nord.nvim` (no `require("nord").setup()`).
The config wants `gbprod/nord.nvim`, which is packaged as `vimPlugins.gbprod-nord`.
- `nixos-generate-config --show-hardware-config` needs root on this machine even just to print: unprivileged it dies at `Failed to retrieve subvolume info for /`, because the root filesystem is btrfs.
- This repo's claude-code module sets sudo's credential cache to per-user (`timestamp_type=global`, 60-minute window), so an authentication made in one real terminal counts for the agent's commands.
A `PreToolUse` hook refuses privileged commands while the cache is cold, so a cold cache announces itself instead of stalling.
A privileged-command failure *without* that message is the sandbox, not the cache.
- Host GPUs: `neogaia` is Intel, `zeus` (the desktop) is **AMD**, and `raichu` (a headless server) is the only Nvidia machine.
The corrected fact also lives in artifact `006-dotfiles-hyprland-compositor-adr.md`.
- This repo's `programs.firefox` `search` (with `force = true`) writes `search.json.mozlz4`.
Omission alone does not prune a built-in engine, since Firefox reconciles its app-provided engines back in, so remove one by listing it with `<engine>.metaData.hidden = true`.
Engines are referenced by their current id, so the default is `default = "ddg"`, not `"DuckDuckGo"`.
Decode the built file with `mozlz4a -d <search.json.mozlz4>` to check the result.
- Any non-empty Home Manager Firefox `profiles.<name>.extensions.settings.<id>.settings` causes Home Manager to set `extensions.webextensions.ExtensionStorageIDB.enabled = false` globally for that profile.
This repo's Stylix Firefox `colorTheme` settings trigger it, so every extension in the profile uses the legacy extension-storage backend regardless of how it is installed.
- `home.sessionVariables` do **not** reach the Hyprland session, since UWSM does not source `hm-session-vars.sh`.
The cursor is therefore set through Hyprland's own `env = KEY,VALUE` in `modules/desktop/hyprland/hyprland.nix`, sourced from `config.stylix.cursor`.
Bibata ships XCursor format only (no `hyprcursor/` dir), rendered through Hyprland's XCursor fallback, so `XCURSOR_*` and `HYPRCURSOR_*` naming the same theme are both safe.
- `neogaia` is a wifi laptop with a btrfs root and no ZFS pools, so it cannot honestly carry `modules.network`, `modules.zfs`, or a networked/pool-mounted guest.
Enabling networkd takes over its DNS, its CachyOS `zfs-kernel` build is marked broken, and it has no bridge or pool to attach to.
Verify these against it ad hoc through `nixosConfigurations.neogaia.extendModules` (forcing a ZFS-capable `boot.kernelPackages` for the zfs case) plus `nix eval` of the derived values, never by committing the enablement.
Committed networked and pool-mounted guests belong on a wired storage host such as `pikachu`, not on `neogaia`.
- Herdr key names for shifted punctuation are not interchangeable with the physical base key plus `shift`.
The tab rename binding must use the produced literal, such as `prefix+<`, rather than `prefix+shift+comma`.
- Flake-managed Pi extension, prompt, and skill directories may still be written directly for throwaway development or local experiments.
The risk is that a later Home Manager activation can overwrite or hide those unmanaged files, so finished work must be promoted into the dotfiles module before it counts as deployed.
- Pi's tool discovery checks `~/.pi/agent/bin` before `PATH`, and downloaded generic Linux binaries there can be unusable on NixOS with the stub-ld error.
This flake patches Pi to validate local tool binaries before selecting them, so it falls back to usable `fd`/`rg` from `PATH` instead.
Stale unpatched launchers are the remaining failure mode for broken `@` autocomplete.
- Nix flake evaluation ignores untracked files in this checkout.
Keep a new auto-loaded module staged or committed until it is removed, otherwise `nix flake check` and `nixos-rebuild --flake` evaluate without it and report its options as missing.
- The current Steam desktop client is an XWayland application.
Its CEF windows do not support Ozone and Steam composites them into an SDL surface with X11 extensions, so SDL Wayland selectors do not make the visible client native Wayland.
Keep fractional scaling sharp with Hyprland's `xwayland.force_zero_scaling` and Steam's own `STEAM_FORCE_DESKTOPUI_SCALING` instead.
- AdGuard's LAN wildcard rewrite for `*.alexion.dev` points names at the reverse-proxy guest, including non-HTTP ports.
Keep `git.alexion.dev:2022` working by proxying that TCP stream through the reverse-proxy guest to Gitea.
- Remote deploy helpers that run target-side `sudo` over SSH need a TTY. - Remote deploy helpers that run target-side `sudo` over SSH need a TTY.
Use `ssh -t` unless the target command is guaranteed not to prompt for sudo credentials. Use `ssh -t` unless the target command cannot prompt for sudo credentials.
- `self.lib.registry.collect { hosts = self.nixosConfigurations; }` reads top-level host `config.registry` declarations only.
Registry declarations inside guest interiors live under `config.containers.<guest>.config.registry` and do not enter the collected fleet registry unless host-side code also publishes them or the collector is explicitly extended to recurse into guests. ### Host Environment
- A NixOS module function argument default such as `registry ? config.registry` is not a safe fallback for an unprovided module argument.
The module system still queries `_module.args.registry` and fails when it is absent. - This host has no `python` or `python3` command on its ordinary `PATH`.
When an optional special-argument fallback is intentionally needed, bind the module arguments through `@args` and test `args ? <name>` inside the module body. For one-off Python commands, use Nix explicitly, such as `nix shell nixpkgs#python3 -c python3 <script>`.
- `nixos-generate-config --show-hardware-config` needs root on this machine, even when it only prints.
Without root, it fails with `Failed to retrieve subvolume info for /`.
The root filesystem uses btrfs.
- Nix flake evaluation ignores untracked files in this checkout.
Stage or commit new auto-loaded modules before evaluating the flake.
Otherwise, `nix flake check` and `nixos-rebuild --flake` cannot see those modules.
They can then report missing options that exist only in the working tree.
### Secrets
- `sops.secrets.<name>.owner` must name a user known on the decrypting host.
Do not set the owner to a guest-only service user unless the host also declares that user.
Use a host-visible owner plus a shared group mode when the guest service can read the decrypted secret file through its group.
### Service Migrations
- Actual Budget's NixOS service runs `actual-server` with `WorkingDirectory = services.actual.settings.dataDir`.
The `actual-server` CLI wrapper sets `ACTUAL_CONFIG_PATH=./config.json` when the working directory contains `config.json`.
That environment variable overrides the config path generated by the NixOS module.
During migration from old community-script state, move or rewrite a copied `/data/config.json`.
Otherwise, the copied file shadows the declarative module settings.

55
docs/dictionary.md Normal file
View File

@@ -0,0 +1,55 @@
# Technical Writing and Domain Dictionary
This dictionary defines project-specific controlled vocabulary, prohibited terms, and domain allowlists.
It applies to comments, user-facing descriptions, commit prose, pull-request prose, and Markdown documentation in this repository.
ASD-STE100 Simplified Technical English partly informs these rules.
This repository uses those ideas as a local style discipline.
It does not claim formal ASD-STE100 compliance.
## Strictly Banned Fluff and Latin Terms
Do not use these Latin abbreviations, conversational filler words, and subjective qualifiers.
Remove them or use the approved replacements.
| Disapproved Term | Approved Replacement | Notes |
| :--- | :--- | :--- |
| `i.e.` | `that is` | Latin abbreviation. Unclear to non-native speakers. |
| `e.g.` | `for example` | Latin abbreviation. |
| `etc.` | `and so forth`, `and others` | Latin abbreviation. Vague and non-exhaustive. |
| `vs.` / `versus` | `compared to`, `against` | Latin abbreviation. |
| `via` | `by using`, `through`, `with` | Latin term. Prefer direct prepositions. |
| `ad hoc` | `one-off`, `for this case` | Latin term. Prefer direct wording. |
| `simply` | omit | Subjective filler. Code and concepts are not inherently simple. |
| `just` | omit | Minimizing filler word. |
| `obviously` | omit | Subjective qualifier. |
| `clearly` | omit | Subjective qualifier. |
| `easily` | omit | Subjective qualifier. |
| `properly` | `correctly`, or specify exact behavior | Vague and subjective. State the exact condition. |
| `as necessary` | specify exact condition | Ambiguous. Define the condition explicitly. |
| `very` / `quite` | omit | Empty intensifiers. |
| `basically` | omit | Conversational filler. |
| `actually` | omit | Conversational filler. |
| `really` | omit | Empty intensifier. |
| `furthermore` / `moreover` | `also`, `and` | Overly formal transitional filler. |
## Technical and Domain Allowed Language
Use only approved project-specific terms as controlled vocabulary in documentation and comments.
Add a term here when repeated project prose needs it.
Use the simplest accurate word when a term is not listed.
| Approved Domain Term | Category | Description and Permitted Context |
| :--- | :--- | :--- |
| agent | Agent tooling | An AI coding assistant acting in the operator's environment. |
| operator | Agent tooling | The human driving the development process. Use when distinguishing the human from the agent acting on their behalf. |
| guest | dotfiles | A systemd-nspawn container managed by this flake. Use for configured containers under `guests/`. |
| host | dotfiles | A machine with a NixOS configuration built by this flake. |
| module | dotfiles | In this repository, a self-contained configuration under `modules/`, usually scoped to one program, service, or subsystem. Use `Nix module` for the language-level module-system concept. |
| registry | dotfiles | A typed configuration namespace whose entries can be collected across hosts and reused by other configuration. |
| Conventional Commits | Git | A commit-message format defined in `docs/conventional-commits.md`. |
| forge | Git | A web service that hosts Git repositories, pull requests, issues, and code review comments. Use when the exact provider does not matter. |
| flake | Nix | A Nix project with pinned inputs and declared outputs. Use for the repository-level build unit defined by `flake.nix`. |
| Nix module | Nix | A Nix language unit that declares or sets options through the Nix module system. |
| overlay | Nix | A function that modifies a Nix package set by adding or replacing packages. |
| workspace | Window management | A named or numbered group of windows in a graphical session. Some desktop environments call this a virtual desktop. |

109
docs/technical-writing.md Normal file
View File

@@ -0,0 +1,109 @@
# Technical Writing
This guide defines writing rules for comments, user-facing descriptions, commit prose, pull-request prose, and Markdown documentation.
These rules apply throughout this repository.
ASD-STE100 Simplified Technical English partly informs these rules.
This repository uses those ideas as a local style discipline.
It does not claim formal ASD-STE100 compliance.
Read the local dictionary with this guide.
In this guide, “the dictionary” means `docs/dictionary.md`.
The dictionary defines banned terms and the strict domain allowlist.
## Structural Conciseness and Grammar Rules
- **Clear and concise style:** Use direct, concise writing.
Use simple present tense for factual descriptions.
Do not use uncertainty words such as `could`, `might`, and `should` unless uncertainty is the subject.
Use each domain word with the meaning assigned in the dictionary.
Avoid: `The tool should usually handle the input correctly.`
Use: `The tool validates the input.`
- **Avoid jargon and inflated language:** Use common technical words.
Avoid unnecessary jargon in three categories:
1. Linguistic and grammatical meta-jargon.
Avoid: `modal auxiliaries`
Use: `uncertain words`
2. Inflated corporate phrasing.
Avoid: `utilize`
Use: `use`
Avoid: `commence`
Use: `start`
3. Unapproved domain inventions.
Do not use project-specific terms unless the dictionary defines them.
General technical words do not need dictionary entries.
Add a common word to the dictionary when it has a project-specific meaning.
If a term is vague or overloaded, choose one precise term.
List avoided alternatives in the dictionary.
Choose the simplest word that accurately communicates the meaning.
- **Sentence length limits:** Code comments, user-facing descriptions, and procedural instructions have a 20-word limit.
Descriptive Markdown paragraphs have a 25-word limit.
Do not put multiple sentences on one physical line.
Wrap a sentence across multiple physical lines when readability requires it.
Split long sentences into multiple concise sentences.
Avoid: `The command configures the service with the generated settings and restarts it after the file changes.`
Use: `The command configures the service with the generated settings.`
Use: `It restarts the service after the file changes.`
- **Active voice:** Always use active voice.
The subject must perform the action.
Do not use passive voice.
Avoid: `Stale entries are invalidated by the cache.`
Use: `The cache invalidates stale entries.`
- **Imperative mood:** Procedural instructions and task steps must start with an imperative verb.
Summary fragments for declarations use third-person singular.
Avoid: `The token should be validated before use.`
Use for a task step: `Validate the token before use.`
Use for a declaration summary: `Validates the token before use.`
- **Noun clusters:** Do not write more than three consecutive nouns without a preposition or hyphen.
Avoid: `session cache entry lookup`
Use: `lookup of session cache entries`
- **One idea per sentence:** Express only one instruction or core concept per sentence.
Do not combine multiple operations in one compound sentence.
Avoid: `Validate the input and restart the service when validation succeeds.`
Use: `Validate the input.`
Use: `Restart the service when validation succeeds.`
- **Code token exemption:** Text in Markdown backticks, URLs, and code blocks counts as one word.
Sentence-length limits and noun-cluster limits do not apply to these tokens.
Example: `curl --fail --silent https://example.com/health` counts as one word.
- **Cause and time precision:** Use `because` for causality.
Use `since` only for passage of time.
Use `as` only for simultaneous action.
Avoid: `Since the token is expired, validation fails.`
Use: `Because the token is expired, validation fails.`
## In-Code Documentation Conventions
- **Declaration coverage and exemptions:** Document public interfaces, configuration options, fields, and non-trivial public methods.
Omit comments on obvious declarations when the name and context are self-explanatory.
Avoid: `Enable this option.`
Use: `Enables automatic cleanup after a successful run.`
- **Declaration non-redundancy:** Comments must explain purpose, preconditions, invariants, or non-obvious consequences.
Do not repeat information that the name, type, path, or parameter list already conveys.
Avoid: `The timeout option sets the timeout.`
Use: `Controls how long the command waits before it reports failure.`
- **Multi-line declaration comments:** Start multi-line declaration comments with one summary sentence.
Put a blank line between the summary and detail paragraphs.
Use detail paragraphs for preconditions, invariants, examples, and non-obvious consequences.
Avoid:
```text
The caller must load the signing key before calling this method.
Validates the token and returns false for expired tokens.
```
Use:
```text
Validates the token.
The caller must load the signing key before calling this method.
Expired tokens return false instead of an error.
```
- **Code symbol references:** Enclose identifiers, paths, commands, option names, and file names in Markdown backticks.
Avoid: Run npm test before editing package.json.
Use: Run `npm test` before editing `package.json`.
- **Boolean descriptions:** Start boolean descriptions with the behavior verb.
Use `True if` only when the value reports state instead of enabling behavior.
Avoid: `Flag that specifies whether retries are enabled.`
Use: `Enables retry behavior.`
Use for state: `True if the token is expired.`

View File

@@ -7,6 +7,7 @@ diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-
const result = spawnSync(cmd, ["--version"], { stdio: "pipe" }); const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
- // Check for ENOENT error (command not found) - // Check for ENOENT error (command not found)
- return result.error === undefined || result.error === null; - return result.error === undefined || result.error === null;
+ // Downloaded generic Linux binaries can exist but fail on NixOS before they print a version.
+ return (result.error === undefined || result.error === null) && result.status === 0; + return (result.error === undefined || result.error === null) && result.status === 0;
} catch { } catch {
return false; return false;

View File

@@ -181,8 +181,8 @@ in
}; };
}; };
# gbprod/nord.nvim, set up in ./config.lua. # `gbprod-nord` provides the `require("nord").setup()` API used in `./config.lua`.
# nixvim's colorschemes.nord is a different plugin. # nixpkgs packages `shaunsingh/nord.nvim` as `nord-nvim`, which is a different plugin.
extraPlugins = [ pkgs.vimPlugins.gbprod-nord ]; extraPlugins = [ pkgs.vimPlugins.gbprod-nord ];
extraConfigLua = builtins.readFile ./config.lua; extraConfigLua = builtins.readFile ./config.lua;