diff --git a/.agents/skills/add-github-workflow/SKILL.md b/.agents/skills/add-github-workflow/SKILL.md new file mode 100644 index 0000000..62e3326 --- /dev/null +++ b/.agents/skills/add-github-workflow/SKILL.md @@ -0,0 +1,74 @@ +--- +name: add-github-workflow +description: Adds a new generated GitHub Actions workflow to the Famedly engineering-standards flake. Use when adding any `.github/workflows/*.yml` to consumer repos, when adding a new entry under `famedly.github.workflows.*`, or when the user asks to add or create a CI/CD workflow in this repository. +--- + +# Add a GitHub workflow + +All workflow YAML in `engineering-standards` is **generated** from per-workflow Nix modules via [`synapdeck/github-actions-nix`](https://github.com/synapdeck/github-actions-nix). Never write `.github/workflows/*.yml` by hand and never use `workflow_call` reusable workflows. + +## Where it lives + +Create `nix/modules/workflows/definitions/.nix`. The filename (without `.nix`) becomes the workflow option name and the generated YAML filename. The loader in `nix/modules/workflows/default.nix` auto-discovers everything in that directory via `builtins.readDir`, so no central registration is needed. + +## Module shape + +Each definition is a flake-parts module that receives `{ inputs, repoRoot, workflowsLib, famedlyConfig, config, lib, ... }`. The parent already provides `enable` and `extraManagedFiles` options; your file sets `config.definition` (and may add per-workflow options). + +Reference the smallest existing example, [`nix/modules/workflows/definitions/general-checks.nix`](../../../nix/modules/workflows/definitions/general-checks.nix), as a template: + +```nix +{ workflowsLib, famedlyConfig, ... }: +let + av = famedlyConfig.standards.actionVersions; + inherit (workflowsLib) ciConcurrency; +in +{ + config.definition = { + name = "My workflow"; + on.pullRequest = { }; + permissions.contents = "read"; + concurrency = ciConcurrency; + jobs.my_job = { + runsOn = "ubuntu-latest"; + steps = [ + { uses = "actions/checkout@${av.checkout}"; } + ]; + }; + }; +} +``` + +## Required conventions + +- **SHA-pin every action.** Resolve via `famedlyConfig.standards.actionVersions.` (defined in [`nix/action-versions-data.nix`](../../../nix/action-versions-data.nix) and exposed by [`nix/modules/action-versions.nix`](../../../nix/modules/action-versions.nix)). If the action you need is not in the list, add it there first. +- **No `workflow_call`.** If your workflow must run after another, use `triggerMode = "workflowRun"` with a `triggerWorkflow = "Upstream Name"` option. See [`definitions/docker.nix`](../../../nix/modules/workflows/definitions/docker.nix) and [`definitions/review-app.nix`](../../../nix/modules/workflows/definitions/review-app.nix) for the pattern. +- **Use `workflowsLib` helpers** (`ciConcurrency`, etc.) instead of duplicating boilerplate. + +## Per-workflow options + +If the workflow accepts user configuration, add an `options` block in the same file: + +```nix +{ lib, config, ... }: +{ + options.myThing = lib.mkOption { type = lib.types.str; default = "x"; }; + config.definition = { /* uses config.myThing */ }; +} +``` + +The parent `submoduleWith` merges this with the default `enable` / `definition` / `extraManagedFiles` options. + +## Templates and docs + +- If the new workflow should be enabled by default in any template, edit the matching `nix/templates/*/flake.nix`. +- Update user-facing documentation (workflow table in `docs/adopting.md`, `README.md` "What it provides" if relevant) by following [`../update-user-docs/SKILL.md`](../update-user-docs/SKILL.md). + +## Verification + +```sh +nix flake show # workflow appears under githubActions.workflows +nix flake check -L # actionlint + dogfood checks must pass +``` + +Then enable it in a template (or this repo) and run `nix run .#regenerateStandards` to confirm the YAML emerges as expected. diff --git a/.agents/skills/add-linting-config/SKILL.md b/.agents/skills/add-linting-config/SKILL.md new file mode 100644 index 0000000..f45c00e --- /dev/null +++ b/.agents/skills/add-linting-config/SKILL.md @@ -0,0 +1,47 @@ +--- +name: add-linting-config +description: Adds or updates a language linting configuration distributed by Famedly engineering-standards. Use when adding a file under `linting//` (for example `clippy.toml`, `analysis_options.yaml`, `ruff.toml`, `eslint.config.base.mjs`), when extending `famedly.standards.linting.*`, or when introducing a new language scope. +--- + +# Add or update a linting config + +Linting configs are raw files that get copied verbatim from `linting//` into the consumer repo root by [`nix/modules/linting.nix`](../../../nix/modules/linting.nix). The module auto-discovers every file in the matching directory — there is no per-file registration step. + +## Where to put files + +| Scope | Directory | Enable flag | +|-------|-----------|-------------| +| Dart | `linting/dart/` | `famedly.standards.linting.dart` | +| Flutter | `linting/flutter/` | `famedly.standards.linting.flutter` | +| Rust | `linting/rust/` | `famedly.standards.linting.rust` | +| Python | `linting/python/` | `famedly.standards.linting.python` | +| TypeScript | `linting/typescript/` | `famedly.standards.linting.typescript` | +| Dart package (in `dart-package/`) and Flutter package (in `flutter-package/`) | `linting/dart-package/`, `linting/flutter-package/` | (consumed by the dart-package / flutter-package modules) | +| Editorconfig | `linting/editorconfig` (a single file, not a directory) | `famedly.standards.infrastructure.editorconfig` | +| REUSE | `linting/reuse/REUSE.toml` | `famedly.standards.preCommitHooks.fossHooks.enable` | + +## File-naming convention (initialOnly) + +[`nix/modules/linting.nix`](../../../nix/modules/linting.nix) treats a file literally named `analysis_options.yaml` as `initialOnly` — it is created on first regen and then left alone so the consumer can edit it. Everything else is overwritten on every regen. + +If you need a Dart/Flutter file that is always managed alongside the user-editable one, name it `analysis_options.standards.yaml` (the existing pattern). For other languages, simply do not name your file `analysis_options.yaml`. + +## Adding a new language scope + +1. Create `linting//` and drop the config files. +2. Extend the `famedly.standards.linting` options in [`nix/modules/linting.nix`](../../../nix/modules/linting.nix), mirroring the existing `dart` / `rust` / `python` boolean pattern, and add the corresponding `Files = lib.optionals cfg. (filesForScope "");` line plus the concat in `config.famedly.standards._internal.managedFiles`. +3. If the language also needs a pre-commit hook group, follow the [add-pre-commit-hook-group](../add-pre-commit-hook-group/SKILL.md) skill in the same change. +4. Update user-facing documentation (`famedly.standards.*` and linting tables in `docs/adopting.md`) by following [`../update-user-docs/SKILL.md`](../update-user-docs/SKILL.md). + +## Updating an existing config + +Just edit the file under `linting//`. No Nix changes are needed unless you renamed the destination, in which case verify the consumer-side filename via the `dest = name;` mapping in `nix/modules/linting.nix`. + +## Verification + +```sh +nix flake check -L +nix flake show +``` + +For behaviour-altering changes, also run `nix run .#regenerateStandards` against a template under `nix/templates/` and inspect the diff. diff --git a/.agents/skills/add-pre-commit-hook-group/SKILL.md b/.agents/skills/add-pre-commit-hook-group/SKILL.md new file mode 100644 index 0000000..b0c5a03 --- /dev/null +++ b/.agents/skills/add-pre-commit-hook-group/SKILL.md @@ -0,0 +1,46 @@ +--- +name: add-pre-commit-hook-group +description: Adds a new pre-commit hook or hook group to the Famedly engineering-standards distribution. Use when extending `famedly.standards.preCommitHooks.*`, when adding a hook in `nix/modules/pre-commit-hooks.nix`, or when adding language-specific lint or format hooks (clippy, rustfmt, dart format, ruff, etc.). +--- + +# Add a pre-commit hook (group) + +All hooks live in [`nix/modules/pre-commit-hooks.nix`](../../../nix/modules/pre-commit-hooks.nix) and run via `git-hooks.nix`. The same definition powers both the local `nix develop` shellHook and the CI `pre-commit` derivation, so each hook is the single source of truth. + +## Single hook vs language group + +- **Base hooks** (always on when `preCommitHooks.enable = true`) live in the unconditional attribute set: `fix-byte-order-marker`, `check-yaml`, `typos`, `nixfmt-rfc-style`, etc. Add to that block for cross-language hooks. +- **Language groups** are gated on `cfg.Hooks.enable`. Each group MUST come in two flavours: + 1. A root-level variant gated on `cfg.Hooks.enable` (the existing `clippy` / `rustfmt` / `dart-format` / `ruff-check` blocks). + 2. A monorepo-scoped variant produced by `scopedHooks` and gated on `Projects != {}`. + Mirror the existing Rust / Dart / Python pairs — do not ship a hook that only works at the repo root. + +## Required conventions + +- **Pin tool entries to absolute Nix store paths.** Use `${dartBin}`, `${rustfmtBin}`, `${cargoClippyBin}`, `${cargoBin}`, `${lib.getExe pkgs.ruff}`. Bare `cargo`/`dart` entries break under `+toolchain` / PATH ambiguity. The toolchain bindings are defined near the top of `pre-commit-hooks.nix`. +- **Set `pass_filenames = false`** for whole-workspace checks (clippy, dart analyze, dart_code_linter), otherwise pre-commit re-invokes the tool per file. +- **Set `types = [ "" ]`** so the hook only fires on relevant files. +- **Restrict to the project directory in monorepo variants** via `files = "^${dir}/"` (see how `scopedRustHooks` / `scopedDartHooks` build `filesAttr`). + +## Skip lists + +Per-project hook IDs MUST be added to `HookIdsToSkip`. The `pre-commit` `checks.*` derivation skips those IDs (`SKIP=…` env var) so CI does not try to run a project-scoped hook outside its directory. Look at `dartHookIdsToSkip` / `rustHookIdsToSkip` and extend them when you add a new scoped hook ID. + +## Tooling availability + +- If the hook needs a tool that is not already provided by the dev shell, add the package to `devShells.famedly-standards` in [`nix/modules/devshell.nix`](../../../nix/modules/devshell.nix). The CI hook derivation reuses the same toolset. +- If the tool ships with `git-hooks.nix`, prefer the built-in definition (`clippy.enable = true`, `rustfmt.enable = true`, `dart-format.enable = true`, `reuse.enable = true`, …) and only override `entry`/`settings` where necessary. + +## Options + docs + +- Add an `Hooks.enable` option (and any tunables) inside `options.famedly.standards.preCommitHooks`. Default to `false`. +- Update user-facing documentation (pre-commit hooks table in `docs/adopting.md`) by following [`../update-user-docs/SKILL.md`](../update-user-docs/SKILL.md). + +## Verification + +```sh +nix flake check -L # builds the `pre-commit` derivation; runs every hook +nix develop -c famedly-lint # interactive feedback while iterating +``` + +Then enable the new option in a template under [`nix/templates/`](../../../nix/templates) (or this repo) and re-run the checks to confirm both root and monorepo variants behave correctly. diff --git a/.agents/skills/update-agent-instructions/SKILL.md b/.agents/skills/update-agent-instructions/SKILL.md new file mode 100644 index 0000000..5812ec6 --- /dev/null +++ b/.agents/skills/update-agent-instructions/SKILL.md @@ -0,0 +1,54 @@ +--- +name: update-agent-instructions +description: Updates Famedly engineering-standards agent-facing instructions (`AGENTS.md`, `CLAUDE.md`, `.agents/skills/*/SKILL.md`) to match the current repo state. Use when adding, removing or renaming a skill, when the AGENTS.md layout map / golden rules / foot-guns drift from reality, when CLAUDE.md needs adjusting, or when the user asks to update agent instructions. +--- + +# Update agent-facing instructions + +The agent-instruction surface is small and layered: [`AGENTS.md`](../../../AGENTS.md) is the canonical document, [`CLAUDE.md`](../../../CLAUDE.md) is a vendor-specific stub that imports it, and each [`.agents/skills//SKILL.md`](../../../.agents/skills) is an operational recipe for one recurring task. + +## Surface owned by this skill + +| File | Purpose | +|------|---------| +| [`AGENTS.md`](../../../AGENTS.md) | Canonical, vendor-neutral. Sections: 1 purpose, 2 layout map, 3 golden rules, 4 change-recipe skill index, 5 verification, 6 dev shell tools, 7 commit hygiene, 8 foot-guns. | +| [`CLAUDE.md`](../../../CLAUDE.md) | Stub only. Must remain exactly `@AGENTS.md` (the Claude Code import directive). Do not expand it. | +| [`.agents/skills//SKILL.md`](../../../.agents/skills) | Project skills. One directory per skill. | + +## Format rules for skills + +- **Layout**: `.agents/skills//SKILL.md`. No supporting files unless genuinely required. +- **Frontmatter**: `name` (kebab-case, ≤64 chars, MUST match the directory name) and `description` (third person, includes WHAT and WHEN, includes trigger terms the user is likely to say). +- **Body**: well under 500 lines. Use markdown tables for enumerations. Reference reality with relative markdown links — from a skill at `.agents/skills//SKILL.md` the repo root is `../../../`. +- **Voice**: vendor-neutral. No Claude / Cursor / OpenAI branding inside the skill body. The skills serve every agent that respects the spec. + +## Drift audit (sources of truth) + +| AGENTS.md / CLAUDE.md location | Source of truth | Enumerate with | +|--------------------------------|-----------------|----------------| +| Section 4 skill-index table in `AGENTS.md` | Sub-directories of `.agents/skills/` | `ls -1 .agents/skills/` | +| Section 2 layout map in `AGENTS.md` | Top-level repo entries plus contents of `nix/modules/` | `ls -1` and `ls -1 nix/modules/` | +| Section 3 golden rules / Section 8 foot-guns | The actual checks in `flake.nix` and the conventions encoded in `nix/modules/` (e.g. `_internal.managedFiles`, action SHA pinning, `workflow_call` ban) | Code review — re-derive when modules change | +| `CLAUDE.md` | The Claude Code import-directive convention | `cat CLAUDE.md` MUST equal `@AGENTS.md\n` | +| Every relative link inside any changed `SKILL.md` | The actual file at the target path | Spot-check with `ls` or by clicking | + +## Procedure + +1. Identify which agent-instruction surface changed: `git diff --name-only $(git merge-base HEAD origin/main)..HEAD -- AGENTS.md CLAUDE.md '.agents/skills/**'`. +2. If a new skill was added or removed: update the section 4 table in [`AGENTS.md`](../../../AGENTS.md). Verify the new directory has `SKILL.md`, frontmatter `name` matches the directory, and `description` is third person with WHAT and WHEN. +3. If `nix/modules/` layout changed (file added, removed, or renamed): update the section 2 layout map in `AGENTS.md`. +4. If a new check was added in [`flake.nix`](../../../flake.nix), or a new convention was encoded in `nix/modules/`: re-evaluate sections 3 (golden rules) and 8 (foot-guns) and add or update bullets. +5. If user-facing docs were also affected, follow [`../update-user-docs/SKILL.md`](../update-user-docs/SKILL.md) in the same change. + +## Conventions + +- AGENTS.md is the only canonical instruction file. Do not duplicate its content into `CLAUDE.md` or per-skill bodies — link instead. +- Skills are operational (procedure + verification), not encyclopaedic. Push background into `AGENTS.md`; push user-facing reference into `docs/adopting.md`. +- Keep skill descriptions concise but trigger-rich: the description is what the agent loads at discovery time, the body is only loaded on invocation. + +## Verification + +- Run the audit commands above and confirm enumerations match the AGENTS.md tables. +- `cat CLAUDE.md` MUST output exactly `@AGENTS.md` (one line). +- Every relative link in a changed skill MUST resolve. Quick check: `rg -o '\]\(\.\.[^)]+\)' .agents/skills//SKILL.md` then `ls` each target. +- No build is required for instruction-only changes. diff --git a/.agents/skills/update-self-ci-workflow/SKILL.md b/.agents/skills/update-self-ci-workflow/SKILL.md new file mode 100644 index 0000000..3c0d55c --- /dev/null +++ b/.agents/skills/update-self-ci-workflow/SKILL.md @@ -0,0 +1,42 @@ +--- +name: update-self-ci-workflow +description: Modifies the dogfooded CI workflow that the engineering-standards repo runs against itself. Use when changing `nix/modules/workflows/definitions/ci.nix`, when an agent notices a diff between `.github/workflows/ci.yml` and the Nix module output, or when the `ci-workflow-dogfood` check is failing. +--- + +# Update the dogfooded self-CI workflow + +`engineering-standards` runs *its own* CI through the same Nix-generated workflow it ships to consumer repos. The generated `.github/workflows/ci.yml` is regenerated from [`nix/modules/workflows/definitions/ci.nix`](../../../nix/modules/workflows/definitions/ci.nix) and a `ci-workflow-dogfood` check in [`flake.nix`](../../../flake.nix) `diff`s the two and fails on drift. That drift check is intentional. + +## Procedure + +1. Edit only [`nix/modules/workflows/definitions/ci.nix`](../../../nix/modules/workflows/definitions/ci.nix). Never edit `.github/workflows/ci.yml` directly — its header explicitly forbids it. +2. Regenerate: + + ```sh + nix run .#regenerateStandards + ``` + + This rewrites `.github/workflows/ci.yml` from the new module output and updates [`.engineering-standards-manifest`](../../../.engineering-standards-manifest) if managed-file paths changed. +3. Stage **both** the Nix change and the regenerated YAML in the same commit. Splitting them across commits will leave intermediate revisions failing `ci-workflow-dogfood`. + +## What to keep in mind while editing `ci.nix` + +- SHA-pin every action via `famedlyConfig.standards.actionVersions.` (resolved from [`nix/action-versions-data.nix`](../../../nix/action-versions-data.nix)). +- Keep the entrypoint a single `nix flake check -L` invocation — that contract is what every consumer repo also runs. +- Cachix steps (`cachix/install-nix-action`, `cachix/cachix-action`) are required and use the `famedly` cache + the `CACHIX_*_FAMEDLY` secrets. Do not remove them. +- Use `workflowsLib.ciConcurrency` rather than hand-writing the concurrency block. + +## Adding a new managed file alongside the workflow + +If your change introduces an additional managed file (for example a composite action under `.github/actions/`): + +- Push it onto `extraManagedFiles` for the `ci` workflow option (see how other definitions do this). +- Re-run `nix run .#regenerateStandards`. Verify the new file appears in `.engineering-standards-manifest` and any removed entries are cleaned up. + +## Verification + +```sh +nix flake check -L +``` + +The `ci-workflow-dogfood` derivation MUST pass. If it does not, the regenerated YAML on disk does not match the module output — re-run `nix run .#regenerateStandards` and re-stage the result. diff --git a/.agents/skills/update-user-docs/SKILL.md b/.agents/skills/update-user-docs/SKILL.md new file mode 100644 index 0000000..c33fde0 --- /dev/null +++ b/.agents/skills/update-user-docs/SKILL.md @@ -0,0 +1,50 @@ +--- +name: update-user-docs +description: Updates Famedly engineering-standards user-facing documentation (`README.md`, `docs/adopting.md`, `docs/README.md`, `CHANGELOG.md`) to match the current state of `famedly.standards.*`, `famedly.github.workflows.*`, linting scopes, and pre-commit hooks. Use when adding or renaming options, workflows, hooks or linting scopes, when the user asks to update or audit the docs, or when documentation drift is suspected. +--- + +# Update user-facing documentation + +The user-facing documentation surface is small and authoritative. There is exactly one canonical reference ([`docs/adopting.md`](../../../docs/adopting.md)); everything else points at it. Drift is prevented by re-deriving the canonical lists from source files before editing. + +## Surface owned by this skill + +| File | Purpose | +|------|---------| +| [`README.md`](../../../README.md) | Project overview, quick start, "What it provides" table. | +| [`docs/adopting.md`](../../../docs/adopting.md) | Canonical reference: `famedly.standards.*` table, `famedly.github.workflows.*` table, pre-commit hooks table, monorepo `projects.*` options, troubleshooting. | +| [`docs/README.md`](../../../docs/README.md) | Doc index. | +| [`CHANGELOG.md`](../../../CHANGELOG.md) | Keep-a-Changelog format. Only user-facing behaviour changes. | + +## Drift audit (sources of truth) + +Before editing, run the audit relevant to your change. Each item lists the source-of-truth enumeration and the doc location it must match. + +| Doc location | Source of truth | Enumerate with | +|--------------|-----------------|----------------| +| Workflow table in `docs/adopting.md` | `nix/modules/workflows/definitions/*.nix` | `ls nix/modules/workflows/definitions/*.nix` | +| `famedly.standards.*` option table | `lib.mkOption` / `lib.mkEnableOption` declarations under `nix/modules/` (excluding `_internal.*`) | `rg -n 'mkOption\|mkEnableOption' nix/modules/` | +| Pre-commit hooks table | Hook definitions in `nix/modules/pre-commit-hooks.nix` | `rg -n '\.enable = true\| = \{' nix/modules/pre-commit-hooks.nix` | +| Linting scope rows | Sub-directories of `linting/` | `ls -1 linting/` | +| Templates listed in `README.md` quick-start | Sub-directories of `nix/templates/` | `ls -1 nix/templates/` | + +## Procedure + +1. Identify which sources changed: `git diff --name-only $(git merge-base HEAD origin/main)..HEAD`. +2. Run the relevant drift-audit row(s) above and diff the enumeration against the doc table. Note any pre-existing gaps. +3. Edit only the affected rows / paragraphs. Do not restructure unrelated sections. +4. If the change is user-facing, add a `CHANGELOG.md` entry under the next unreleased section (create one if missing). Do not bump versions — releases are a separate workstream. +5. If the same change also added, removed or renamed a project skill, follow [`../update-agent-instructions/SKILL.md`](../update-agent-instructions/SKILL.md) in the same change. + +## Conventions + +- Markdown only, no HTML. Tables for option / workflow / hook enumerations. +- British English (matches existing prose in `docs/adopting.md`). +- File paths: markdown links in prose (`[docs/adopting.md](docs/adopting.md)`); identifiers like `famedly.standards.*` or `flake.nix` in backticks. +- Additive edits unless the user explicitly asked for restructuring. +- Do not duplicate content between `README.md` and `docs/adopting.md` — `README.md` summarises and links, `docs/adopting.md` enumerates. + +## Verification + +- Re-run every drift-audit command above for the affected category. Each enumeration MUST be a subset of (and ideally equal to) the corresponding doc table. +- `nix flake check -L` should still pass — doc-only changes do not affect derivations, but a green check confirms nothing else regressed. diff --git a/.cursor/skills b/.cursor/skills new file mode 120000 index 0000000..ac17315 --- /dev/null +++ b/.cursor/skills @@ -0,0 +1 @@ +../.agents/skills/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index faccf25..3db8748 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ result-* # macOS .DS_Store + +# Claude local +.claude/settings.local.json \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..84a5e28 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,85 @@ +# AGENTS.md + +Vendor-neutral instructions for AI/coding agents working **inside this repository**. + +User-facing documentation for *consumer* repos that adopt these standards lives in [README.md](README.md) and [docs/adopting.md](docs/adopting.md). This file is exclusively about modifying *this* repo. + +## 1. Repository purpose + +`engineering-standards` is the source-of-truth Nix flake distribution for Famedly's engineering standards. It exposes a `flake-parts` module that consumer repos import, configures via `famedly.standards.*` and `famedly.github.workflows.*`, and then materialises into their working tree via `nix run .#regenerateStandards`. CI in any consumer repo is just `nix flake check`. + +What ships from here: + +- Linting configurations under [linting/](linting) (Dart, Flutter, Rust, Python, TypeScript, editorconfig, REUSE). +- Pre-commit hooks via [`nix/modules/pre-commit-hooks.nix`](nix/modules/pre-commit-hooks.nix) (`git-hooks.nix`). +- GitHub Actions workflows generated from [`nix/modules/workflows/definitions/`](nix/modules/workflows/definitions) via [`synapdeck/github-actions-nix`](https://github.com/synapdeck/github-actions-nix) — no `workflow_call` reusable workflows. +- Templates for `nix flake init -t` under [`nix/templates/`](nix/templates). +- Placeholder AI rules under [`ai-rules/`](ai-rules) wired up via [`nix/modules/rules.nix`](nix/modules/rules.nix). + +## 2. Layout map + +| Path | Purpose | +|------|---------| +| [`flake.nix`](flake.nix) | Entry point. Dogfoods itself: enables `famedly.standards.preCommitHooks` and `famedly.github.workflows.ci`. | +| [`nix/modules/`](nix/modules) | `flake-parts` modules: `default.nix`, `linting.nix`, `infrastructure.nix`, `devshell.nix`, `rules.nix`, `dart.nix`, `rust.nix`, `projects.nix`, `pre-commit-hooks.nix`, `compat.nix`, `action-versions.nix`. | +| [`nix/modules/workflows/`](nix/modules/workflows) | Workflow plumbing: `default.nix` (auto-discovers definitions), `lib.nix`, `definitions/.nix` (one file per workflow, name = filename). | +| [`linting//`](linting) | Raw config files copied verbatim into consumer repo roots by `nix/modules/linting.nix`. | +| [`nix/templates/{rust,dart,flutter,flutter-rust}/`](nix/templates) | `nix flake init -t` targets and canonical examples of consumer config. | +| [`nix/packages/`](nix/packages) | Dart / Flutter SDK derivations + `update-sdk-versions.py`. | +| [`nix/checks/`](nix/checks), [`nix/tests/default.nix`](nix/tests/default.nix) | Derivations evaluated by `nix flake check`. | +| [`ai-rules/{global,rust,dart}/`](ai-rules) | Placeholder `.mdc` files. The rules module currently emits a placeholder `CLAUDE.md` into *consumer* repos when `rules.enable = true`. | +| [`docs/adopting.md`](docs/adopting.md) | Sole user-facing reference. Keep in sync with every option / workflow / hook change. | +| [`.engineering-standards-manifest`](.engineering-standards-manifest) | List of files this repo currently materialises into itself. Rewritten by `regenerateStandards`. | + +## 3. Golden rules + +- **Never hand-edit a generated file.** Every entry in `.engineering-standards-manifest` (currently just `.github/workflows/ci.yml`) is rewritten by `nix run .#regenerateStandards`. The `ci-workflow-dogfood` check in `flake.nix` fails on drift. +- **All workflow YAML is generated** from `nix/modules/workflows/definitions/.nix` via `github-actions-nix`. Do not write raw `.github/workflows/*.yml`. Do not introduce `workflow_call` reusable workflows; use `triggerMode = "workflowRun"` for cross-workflow triggering. +- **SHA-pin every third-party action.** See [`nix/action-versions-data.nix`](nix/action-versions-data.nix) and [`nix/modules/action-versions.nix`](nix/modules/action-versions.nix). Never use floating `@v4` tags. +- **Document every new option** under `famedly.standards.*` or `famedly.github.workflows.*` by updating the tables in [`docs/adopting.md`](docs/adopting.md) in the same change. +- **Register every new managed file** by pushing onto `famedly.standards._internal.managedFiles` (`{ src; dest; initialOnly?; }`). That list is the only thing `regenerateStandards` walks for both writes and cleanup. +- **Templates must keep working.** Any breaking option rename requires corresponding edits in [`nix/templates/*/flake.nix`](nix/templates). +- **Do not touch `flake.lock` by hand.** If an input bump is genuinely needed, run `nix flake update `. +- **This repo is Nix-only.** Do not invoke `cargo`, `dart`, `pub`, `pip`, `npm` directly — go through the dev shell or via Nix-built derivations. + +## 4. Change recipes (Agent Skills) + +Recurring tasks are captured as project-scoped Agent Skills under [`.agents/skills/`](.agents/skills) (`.cursor/skills` in the checkout is a symlink to the same tree for Cursor). For any of these tasks, read and follow the matching skill rather than improvising. Skills are auto-discoverable by their frontmatter `description`; this table is the human-readable index. + +| Task | Skill | +|------|-------| +| Add a new GitHub workflow definition | [`.agents/skills/add-github-workflow/SKILL.md`](.agents/skills/add-github-workflow/SKILL.md) | +| Add or update a linting config (`linting//…`) | [`.agents/skills/add-linting-config/SKILL.md`](.agents/skills/add-linting-config/SKILL.md) | +| Add a pre-commit hook or hook group | [`.agents/skills/add-pre-commit-hook-group/SKILL.md`](.agents/skills/add-pre-commit-hook-group/SKILL.md) | +| Change this repo's own (dogfooded) CI workflow | [`.agents/skills/update-self-ci-workflow/SKILL.md`](.agents/skills/update-self-ci-workflow/SKILL.md) | +| Update user-facing documentation (`README.md`, `docs/adopting.md`, `CHANGELOG.md`) | [`.agents/skills/update-user-docs/SKILL.md`](.agents/skills/update-user-docs/SKILL.md) | +| Update agent instructions (`AGENTS.md`, `CLAUDE.md`, any `SKILL.md`) | [`.agents/skills/update-agent-instructions/SKILL.md`](.agents/skills/update-agent-instructions/SKILL.md) | + +## 5. Verification (mandatory before finishing) + +Run, in order: + +1. `nix run .#regenerateStandards` — only if a module that emits managed files was touched. Commit any resulting diff in the same change. +2. `nix flake check -L` (or `nix develop -c famedly-check`). Runs `statix`, `deadnix`, `nix-modules-parse`, `ci-workflow-dogfood`, the `nix/tests/default.nix` evaluation tests, the `pre-commit` hook suite, and `treefmt`. +3. `nix develop -c famedly-lint` — fast hook-only feedback while iterating. +4. `nix flake show` — confirm new packages, apps, checks, and workflows appear. + +## 6. Dev shell tools + +`nix develop` provides `nil`, `nixfmt`, `prettier`, `statix`, `deadnix`, and `nix-output-monitor`. Formatting is driven by `treefmt-nix` (nixfmt-rfc-style + prettier). Do not introduce additional formatters. + +## 7. Commit hygiene + +- Use Conventional Commits, matching the existing [`CHANGELOG.md`](CHANGELOG.md) style (`feat:`, `fix:`, `chore:`, …). +- Do not bundle unrelated `.engineering-standards-manifest` or generated YAML changes into feature commits — those files should only move when the Nix module producing them changes. +- Do not commit `.claude/settings.local.json` (already in [`.gitignore`](.gitignore)). + +## 8. What NOT to do + +- Edit any file that starts with `# This file is automatically generated from Nix configuration.` +- Add floating GitHub Action versions (always SHA-pin via `action-versions-data.nix`). +- Bypass `famedly.standards._internal.managedFiles` when emitting files from a module. +- Hand-roll `workflow_call` reusable workflows. +- Run `cargo` / `dart` / `pub` directly in this repo. +- Enable `famedly.standards.preCommitHooks.fossHooks` for *this* repo — it is intentionally disabled in [`flake.nix`](flake.nix) (REUSE compliance is a consumer concern, not an engineering-standards-repo concern). +- Fill in [`ai-rules/*/placeholder.mdc`](ai-rules) as a side-effect of unrelated work — those are consumer-shipped rules and a separate workstream. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3edeb30..a8659c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Format: [Keep a Changelog](https://keepachangelog.com/). +## [Unreleased] + +### Fixed + +- Aligned `docs/adopting.md` and `README.md` with the current Nix modules: lint outputs (`ruff.toml`, Rust filenames), `rules` output, pre-commit hook groups, `dart-ci` options/defaults, and documented `famedly.standards.rust.*` plus `actionVersions` / `workflowRef`. + ## [1.0.0] – 2026-03-16 First Nix-first release: flake module `famedly.standards.*`, SHA-pinned reusable GitHub Actions, templates (`rust`, `dart`, `flutter`, `flutter-rust`), Dart package `engineering_standards_lints` under `linting/dart-package`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md index 01f4ebe..9d43631 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ See **[docs/adopting.md](docs/adopting.md)** for existing repos, configuration r | Feature | Output | |---------|--------| -| `linting` | `analysis_options.yaml`, `deny.toml`, `pyproject.toml`, … | +| `linting` | `analysis_options.yaml`, `clippy.toml`, `ruff.toml`, `eslint.config.base.mjs`, … | | `preCommitHooks` | git hooks (typos, reuse, clippy, dart, ruff, …) | | `infrastructure` | `.editorconfig`, `.github/dependabot.yml` | | `devShell` | `famedly-*` CLI (see above) | -| `rules` | `.cursor/rules/…`, `CLAUDE.md` | +| `rules` | placeholder `CLAUDE.md` (full rules not shipped yet) | | Workflows | `ci`, `general-checks`, `dart-ci` (multi-package), `rust-ci`, `docker` (multi-arch/simple), `review-app`, `github-pages`, `hookd-deploy`, `release`, `publish-crate`, `publish-pub`, `docker-backend`, `docker-bake`, `ansible-ci`, `ai-review`, `fast-forward`, `add-to-project`, `update-engineering-standards` | | `projects` | monorepo: per-folder lint/hooks/dependabot | diff --git a/docs/adopting.md b/docs/adopting.md index 4603129..46e3255 100644 --- a/docs/adopting.md +++ b/docs/adopting.md @@ -145,10 +145,10 @@ When `devShell.enable = true`, a `.envrc` is generated automatically by `famedly | Group | Enabled when | Hooks | |-------|-------------|-------| -| Base | `preCommitHooks.enable` | byte-order-marker, case-conflicts, merge-conflicts, symlinks, yaml, toml, json, eof-fixer, mixed-line-endings, trailing-whitespace, typos | +| Base | `preCommitHooks.enable` | byte-order-marker, case-conflicts, merge-conflicts, symlinks, yaml, toml, json, eof-fixer, mixed-line-endings, trailing-whitespace, typos, nixfmt (RFC style) | | FOSS | `fossHooks.enable` (default) | reuse | -| Rust | `rustHooks.enable` | clippy, rustfmt | -| Dart | `dartHooks.enable` | dart format, dart analyze `--fatal-infos` | +| Rust | `rustHooks.enable` | clippy, rustfmt (root); monorepo `projects` add scoped `cargo check` / lockfile validation per Rust project | +| Dart | `dartHooks.enable` | dart format, `dart analyze --fatal-infos`, import_sorter, commented-out code check, dart_code_linter (when present in `pubspec.yaml`) | | Python | `pythonHooks.enable` | ruff check, ruff format | ### Managed files @@ -163,14 +163,14 @@ When `devShell.enable = true`, a `.envrc` is generated automatically by `famedly | Option | Type | Default | Description | |--------|------|---------|-------------| -| `rules.enable` | bool | `false` | Generate `.cursor/rules/standards/`, `CLAUDE.md` | -| `rules.extraScopes` | listOf enum | `[]` | `"dart"`, `"flutter"`, `"nix"`, `"rust"`, `"python"`, `"typescript"` | +| `rules.enable` | bool | `false` | Generate placeholder `CLAUDE.md` in the consumer repo (full Cursor rules not shipped yet) | +| `rules.extraScopes` | listOf enum | `[]` | `"dart"`, `"flutter"`, `"nix"`, `"rust"`, `"python"`, `"typescript"` — reserved; currently a no-op | | `linting.enable` | bool | `false` | Master switch for lint configs | | `linting.dart` | bool | `false` | `analysis_options.yaml` (Dart) | | `linting.flutter` | bool | `false` | `analysis_options.yaml` (Flutter) | -| `linting.rust` | bool | `false` | `deny.toml`, `rustfmt.toml` | -| `linting.python` | bool | `false` | `pyproject.toml` (ruff) | -| `linting.typescript` | bool | `false` | TypeScript lint config | +| `linting.rust` | bool | `false` | `clippy.toml`, `rustfmt.toml`, `deny.toml`, `cargo-lints.toml` | +| `linting.python` | bool | `false` | `ruff.toml`, `ruff.base.toml` | +| `linting.typescript` | bool | `false` | `eslint.config.base.mjs` | | `preCommitHooks.enable` | bool | `false` | git-hooks.nix suite | | `preCommitHooks.fossHooks.enable` | bool | `true` | REUSE compliance hook | | `preCommitHooks.fossHooks.copyright` | str | `"Famedly GmbH"` | SPDX copyright holder | @@ -191,6 +191,35 @@ When `devShell.enable = true`, a `.envrc` is generated automatically by `famedly | `dart.enable` | bool | `false` | Dart/Flutter dev shell, auto-enables `dart-ci` | | `dart.flutter` | bool | `false` | Flutter SDK statt Dart SDK | | `dart.dartSdk` | nullOr package | `null` | Override SDK package | +| `actionVersions` | attrsOf str | (from flake) | GitHub Action commit SHAs; populated from `nix/action-versions-data.nix` inside engineering-standards | +| `workflowRef` | str | `"main"` | Deprecated; unused. Kept so older consumer flakes keep evaluating | + +### `famedly.standards.rust.*` + +All `rust.checks.*` and `rust.package` / `rust.docker` / `rust.devShell` options below apply only when `rust.enable = true`. + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `rust.enable` | bool | `false` | Crane-based checks, packages, and optional dev shell integration | +| `rust.craneLib` | raw | (required) | Crane library, typically `(inputs.crane.mkLib pkgs).overrideToolchain …` | +| `rust.src` | path | (required) | Cargo workspace root (`cleanCargoSource` / `cleanSource` applied internally) | +| `rust.openssl` | bool | `true` | Add `openssl` / `pkg-config` and set `OPENSSL_NO_VENDOR` | +| `rust.extraBuildInputs` | listOf package | `[]` | Extra `buildInputs` for crane derivations | +| `rust.extraNativeBuildInputs` | listOf package | `[]` | Extra `nativeBuildInputs` | +| `rust.cargoExtraArgs` | str | `""` | Extra cargo arguments for all crane steps (e.g. `--features …`) | +| `rust.checks.clippy.enable` | bool | `true` | `cargo clippy` | +| `rust.checks.clippy.useTestSrc` | bool | `false` | Use full source for clippy when tests use `include_str!` / `include_bytes!` | +| `rust.checks.clippy.extraArgs` | str | `--all-features --all-targets -- --deny warnings` | Arguments passed to `cargo clippy` | +| `rust.checks.fmt.enable` | bool | `true` | `cargo fmt` check | +| `rust.checks.nextest.enable` | bool | `true` | `cargo nextest` | +| `rust.checks.nextest.extraArgs` | str | `""` | Extra arguments for nextest | +| `rust.checks.deny.enable` | bool | `true` | `cargo deny` | +| `rust.package.enable` | bool | `true` | Build `packages.default` (release binary) | +| `rust.docker.enable` | bool | `false` | Build OCI image from default package | +| `rust.docker.name` | str | `""` | Image name (defaults to package `pname`) | +| `rust.docker.tag` | str | `"latest"` | Image tag | +| `rust.devShell.enable` | bool | `true` | Merge Rust toolchain into `devShells.default` | +| `rust.devShell.extraPackages` | listOf package | `[]` | Extra packages in the combined dev shell | ### `famedly.github.workflows.*` @@ -205,7 +234,7 @@ All workflows have `enable` (bool, default `false`). | `ai-review` | `model` | | `release` | `draft` | | `rust-ci` | `runner`, `container`, `features`, `packages`, `additionalPackages`, `coverage`, `typos`, `cargoDeny` | -| `dart-ci` | `packages` (attrsOf: `directory`, `sdk`, `test`, `coverage`, `coverageFlags`) | +| `dart-ci` | Top-level or per-`packages.*`: `directory`, `sdk`, `importSorter`, `dependencyValidator`, `dartCodeLinter`, `translationsCleaner`, `commentedCodeCheck`, `test`, `coverage`, `coverageFlags`, `testInDevShell`; multi-package: `packages` (attrsOf submodule) | | `publish-crate` | `packages`, `features`, `extraTagPatterns` | | `publish-pub` | — | | `docker` | `mode` (`multi-arch`/`simple`), `imageName`, `registry`, `triggerMode` (`direct`/`workflowRun`), `triggerWorkflow`, `buildArgs`, `pushOnlyOnTags`, `registryUser`, `registryPasswordSecret`, `context`, `dockerfile` | @@ -232,7 +261,7 @@ dart-ci = { }; ``` -Per-package options: `directory` (str, `""`), `sdk` (enum, `"flutter"`), `test` (bool, `true`), `coverage` (bool, `true`), `coverageFlags` (str, `""`). +Per-package options: `directory` (str, `""`), `sdk` (`"dart"` or `"flutter"`, default `"flutter"`), `importSorter`, `dependencyValidator`, `dartCodeLinter`, `translationsCleaner`, `commentedCodeCheck` (bool, default `true` each), `test` (bool, default `false`), `coverage` (bool, default `false`), `coverageFlags` (str, `""`), `testInDevShell` (bool, default `false`). ### `workflow_run` triggers diff --git a/nix/modules/pre-commit-hooks.nix b/nix/modules/pre-commit-hooks.nix index 5982489..cab0ae6 100644 --- a/nix/modules/pre-commit-hooks.nix +++ b/nix/modules/pre-commit-hooks.nix @@ -11,7 +11,7 @@ # Base — BOM, case-conflicts, merge-conflicts, YAML/TOML/JSON, etc. # FOSS — REUSE license compliance # Rust — clippy, rustfmt, cargo lockfile -# Dart — dart format, dart analyze, import_sorter, commented-out code, dart_code_linter +# Dart — dart format, dart analyze, import_sorter, commented-out code, dart_code_linter (root); project-scoped hooks mirror these per directory # Python — ruff check, ruff format # # Monorepo projects (via famedly.standards.projects) automatically diff --git a/nix/modules/rules.nix b/nix/modules/rules.nix index ef6181d..d8ee945 100644 --- a/nix/modules/rules.nix +++ b/nix/modules/rules.nix @@ -24,7 +24,7 @@ in { options.famedly.standards.rules = { - enable = lib.mkEnableOption "AI rules (Cursor rules + CLAUDE.md)"; + enable = lib.mkEnableOption "placeholder consumer-repo CLAUDE.md (full AI rules not yet shipped)"; extraScopes = lib.mkOption { type = lib.types.listOf (