Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .agents/skills/add-github-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -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/<name>.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.<name>` (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.
47 changes: 47 additions & 0 deletions .agents/skills/add-linting-config/SKILL.md
Original file line number Diff line number Diff line change
@@ -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/<lang>/` (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/<lang>/` 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/<lang>/` 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 `<lang>Files = lib.optionals cfg.<lang> (filesForScope "<lang>");` 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/<lang>/`. 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.
46 changes: 46 additions & 0 deletions .agents/skills/add-pre-commit-hook-group/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.<lang>Hooks.enable`. Each group MUST come in two flavours:
1. A root-level variant gated on `cfg.<lang>Hooks.enable` (the existing `clippy` / `rustfmt` / `dart-format` / `ruff-check` blocks).
2. A monorepo-scoped variant produced by `scoped<Lang>Hooks` and gated on `<lang>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 = [ "<lang>" ]`** 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 `<lang>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 `<lang>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.
54 changes: 54 additions & 0 deletions .agents/skills/update-agent-instructions/SKILL.md
Original file line number Diff line number Diff line change
@@ -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/<name>/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/<name>/SKILL.md`](../../../.agents/skills) | Project skills. One directory per skill. |

## Format rules for skills

- **Layout**: `.agents/skills/<kebab-name>/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/<x>/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/<changed>/SKILL.md` then `ls` each target.
- No build is required for instruction-only changes.
42 changes: 42 additions & 0 deletions .agents/skills/update-self-ci-workflow/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.<name>` (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.
Loading
Loading