feat(skills): add rust style skill #65

Merged
alexion merged 3 commits from feat/rust-style-skill into main 2026-09-17 09:58:02 -04:00
31 changed files with 293 additions and 98 deletions

View File

@@ -1,31 +0,0 @@
---
description: Review Nix changes against repository Nix style rules
argument-hint: "[target]"
---
Use the `nix-style` skill.
Audit ${ARGUMENTS:-the staged diff} for violations of the repository Nix style rules.
Report only actionable findings.
Order findings by severity.
Do not report preferences unless a loaded rule states them.
Use this format for each finding:
```text
`code-path:line`
> `smallest reviewed-code excerpt that shows the violation`
`rule-file:line`: Finding title.
Summary: why the code breaks the rule
Fix: suggested fix
```
Keep the code excerpt to one to three lines when possible.
Use a longer excerpt only when the violation needs surrounding context.
Use `...` to omit unrelated lines from a multi-line excerpt.
Use a skill-relative rule citation, such as `let.md:7`.
Make the fix direct.
Do not include alternatives when one fix best follows the cited rule.
It is acceptable to report no findings when the reviewed Nix already follows the loaded rules.

42
.pi/prompts/review.md Normal file
View File

@@ -0,0 +1,42 @@
---
description: Review changes against repository style rules
argument-hint: "[target]"
---
Review target: `${ARGUMENTS:-the staged diff}`.
Audit the target for violations of the repository style rules.
Choose skills from the reviewed files:
- Use the `nix-style` skill for `*.nix` files.
- Use the `rust-style` skill for `*.rs` files.
- Use the `rust-style` skill for `Cargo.toml` files.
- Use the `rust-style` skill for Bazel files that define Rust targets.
If the target includes multiple matching file types, use all matching skills.
If the target is a diff, choose skills from the changed files.
Do not use a style skill when no reviewed file matches it.
Report only actionable findings.
Order findings by severity.
Do not report preferences unless a loaded rule states them.
Use this format for each finding:
```text
`code-path:line`
> `smallest reviewed-code excerpt that shows the violation`
`skill::rule-file:line`: Finding title.
Summary: why the code breaks the rule
Fix: suggested fix
```
Keep the code excerpt to one to three lines when possible.
Use a longer excerpt only when the violation needs surrounding context.
Use `...` to omit unrelated lines from a multi-line excerpt.
Use a style-skill rule citation, such as `rust::files.md:23` or `nix::let.md:7`.
Make the fix direct.
Do not include alternatives when one fix best follows the cited rule.
It is acceptable to report no findings when the target follows the loaded rules.

View File

@@ -1 +1 @@
../../src/skills/nix-style ../../src/skills/style/nix

1
.pi/skills/rust-style Symbolic link
View File

@@ -0,0 +1 @@
../../src/skills/style/rust

5
Cargo.lock generated
View File

@@ -57,6 +57,7 @@ name = "ariadne"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"monorepo",
"serde", "serde",
"serde_json", "serde_json",
] ]
@@ -135,6 +136,10 @@ version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "monorepo"
version = "0.1.0"
[[package]] [[package]]
name = "once_cell_polyfill" name = "once_cell_polyfill"
version = "1.70.2" version = "1.70.2"

View File

@@ -2,5 +2,12 @@
members = [ members = [
"src/ariadne", "src/ariadne",
"src/machoke", "src/machoke",
"src/monorepo",
] ]
resolver = "3" resolver = "3"
[workspace.dependencies]
clap = "4"
monorepo = { path = "src/monorepo" }
serde = "1.0"
serde_json = "1.0"

View File

@@ -3,6 +3,8 @@
- machoke - machoke
- falinks - falinks
- dotfiles - dotfiles
- xatu
- nix-style / refactor
# project ideas # project ideas
@@ -18,8 +20,8 @@
- amazon / actualbudget exporter - amazon / actualbudget exporter
- vim-obsidian-markdown - vim-obsidian-markdown
- dictionary cli tool - dictionary cli tool
- repo-local Nix style linter
- kitchen.md - kitchen.md
- Rust project creator
# migration # migration
@@ -102,6 +104,9 @@
- better remote deployment - better remote deployment
- expose remote deployment as a flake app, replacing the Fish-only `deploy-to` workflow - expose remote deployment as a flake app, replacing the Fish-only `deploy-to` workflow
- centralize shared system group names, starting with the `storage` group - centralize shared system group names, starting with the `storage` group
- Investigate Rust Bazel target structure.
Prefer clear per-file dependency edges, but `rust_library` maps each target to a crate.
Decide whether wrapper rules or another pattern can improve graph clarity without forcing artificial crate boundaries.
## nix refactor ## nix refactor
@@ -109,9 +114,9 @@ Review Nix files in entrypoint order:
- [x] `flake.nix` - [x] `flake.nix`
- [x] `src/dotfiles/default.nix` - [x] `src/dotfiles/default.nix`
- [ ] `src/dotfiles/util.nix` - [x] `src/dotfiles/util.nix`
- [ ] `src/dotfiles/formatter.nix` - [x] `src/dotfiles/formatter.nix`
- [ ] `src/dotfiles/apps.nix` - [x] `src/dotfiles/apps.nix`
- [ ] `src/dotfiles/registry.nix` - [ ] `src/dotfiles/registry.nix`
- [ ] `configs/nixos/registry/address.nix` - [ ] `configs/nixos/registry/address.nix`
- [ ] `configs/nixos/registry/env-var.nix` - [ ] `configs/nixos/registry/env-var.nix`

View File

@@ -4,8 +4,8 @@ rust_library(
name = "ariadne_lib", name = "ariadne_lib",
crate_name = "ariadne", crate_name = "ariadne",
srcs = [ srcs = [
"ariadne.rs",
"clew.rs", "clew.rs",
"lib.rs",
"pipeline.rs", "pipeline.rs",
], ],
deps = [ deps = [
@@ -17,9 +17,10 @@ rust_library(
rust_binary( rust_binary(
name = "ariadne", name = "ariadne",
crate_name = "ariadne_cli", crate_name = "ariadne_cli",
srcs = ["main.rs"], srcs = ["ariadne_main.rs"],
deps = [ deps = [
":ariadne_lib", ":ariadne_lib",
"//src/monorepo:monorepo",
"//third_party/crates:clap", "//third_party/crates:clap",
], ],
) )

View File

@@ -4,13 +4,14 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[lib] [lib]
path = "lib.rs" path = "ariadne.rs"
[[bin]] [[bin]]
name = "ariadne" name = "ariadne"
path = "main.rs" path = "ariadne_main.rs"
[dependencies] [dependencies]
clap = { version = "4", features = ["derive"] } clap = { workspace = true, features = ["derive"] }
serde = { version = "1.0", features = ["derive"] } monorepo = { workspace = true }
serde_json = "1.0" serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }

View File

@@ -17,7 +17,7 @@ struct Args {
impl From<Args> for ariadne::Config { impl From<Args> for ariadne::Config {
fn from(args: Args) -> Self { fn from(args: Args) -> Self {
ariadne::Config { ariadne::Config {
output_dir: resolve_workspace_path(args.output_dir), output_dir: monorepo::workspace::resolve_workspace_path(args.output_dir),
force: args.force, force: args.force,
target_stage: args.target_stage, target_stage: args.target_stage,
} }
@@ -32,19 +32,6 @@ fn parse_stage(value: &str) -> Result<ariadne::pipeline::Stage, String> {
.ok_or_else(|| format!("unknown stage: {value}")) .ok_or_else(|| format!("unknown stage: {value}"))
} }
// TODO: Move this into a shared Rust utility crate.
fn resolve_workspace_path(path: PathBuf) -> PathBuf {
if path.is_absolute() {
return path;
}
if let Some(workspace) = std::env::var_os("BUILD_WORKSPACE_DIRECTORY") {
return PathBuf::from(workspace).join(path);
}
path
}
fn main() -> ariadne::Result<()> { fn main() -> ariadne::Result<()> {
let config: ariadne::Config = Args::parse().into(); let config: ariadne::Config = Args::parse().into();
ariadne::run(&config) ariadne::run(&config)

View File

@@ -3,11 +3,11 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
rust_library( rust_library(
name = "machoke_lib", name = "machoke_lib",
crate_name = "machoke", crate_name = "machoke",
srcs = ["lib.rs"], srcs = ["machoke.rs"],
) )
rust_test( rust_test(
name = "machoke_test", name = "machoke_test",
srcs = ["lib_test.rs"], srcs = ["machoke_test.rs"],
deps = [":machoke_lib"], deps = [":machoke_lib"],
) )

View File

@@ -4,10 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[lib] [lib]
path = "lib.rs" path = "machoke.rs"
[[test]]
name = "lib_test"
path = "lib_test.rs"
[dependencies] [dependencies]

17
src/monorepo/BUILD Normal file
View File

@@ -0,0 +1,17 @@
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
rust_library(
name = "monorepo",
crate_name = "monorepo",
srcs = [
"monorepo.rs",
"workspace.rs",
],
visibility = ["//visibility:public"],
)
rust_test(
name = "workspace_test",
srcs = ["workspace_test.rs"],
deps = [":monorepo"],
)

9
src/monorepo/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "monorepo"
version = "0.1.0"
edition = "2024"
[lib]
path = "monorepo.rs"
[dependencies]

1
src/monorepo/monorepo.rs Normal file
View File

@@ -0,0 +1 @@
pub mod workspace;

20
src/monorepo/workspace.rs Normal file
View File

@@ -0,0 +1,20 @@
use std::path::PathBuf;
pub fn resolve_workspace_path(path: PathBuf) -> PathBuf {
resolve_workspace_path_impl(
path,
std::env::var_os("BUILD_WORKSPACE_DIRECTORY").map(PathBuf::from),
)
}
fn resolve_workspace_path_impl(path: PathBuf, workspace: Option<PathBuf>) -> PathBuf {
if path.is_absolute() {
return path;
}
if let Some(workspace) = workspace {
return workspace.join(path);
}
path
}

View File

@@ -0,0 +1,52 @@
use std::path::PathBuf;
use monorepo::workspace::resolve_workspace_path;
const BUILD_WORKSPACE_DIRECTORY: &str = "BUILD_WORKSPACE_DIRECTORY";
#[derive(Debug)]
struct Case {
name: &'static str,
workspace: Option<&'static str>,
path: &'static str,
expected: &'static str,
}
#[test]
fn resolve_workspace_path_cases() {
let cases = [
Case {
name: "absolute path returns unchanged path",
workspace: Some("/workspace"),
path: "/tmp/output",
expected: "/tmp/output",
},
Case {
name: "relative path joins workspace path",
workspace: Some("/workspace"),
path: "output/graph",
expected: "/workspace/output/graph",
},
Case {
name: "relative path without workspace returns unchanged path",
workspace: None,
path: "output/graph",
expected: "output/graph",
},
];
for case in cases {
unsafe {
if let Some(workspace) = case.workspace {
std::env::set_var(BUILD_WORKSPACE_DIRECTORY, workspace);
} else {
std::env::remove_var(BUILD_WORKSPACE_DIRECTORY);
}
}
let resolved = resolve_workspace_path(PathBuf::from(case.path));
assert_eq!(resolved, PathBuf::from(case.expected), "{}", case.name);
}
}

View File

View File

@@ -6,6 +6,7 @@ This directory contains monorepo-owned agent skill sources.
```text ```text
src/skills/ Monorepo-owned skill sources src/skills/ Monorepo-owned skill sources
src/skills/<group>/<skill>/ Grouped monorepo skill sources
.pi/skills/ Monorepo-wide enabled Pi skill symlinks .pi/skills/ Monorepo-wide enabled Pi skill symlinks
src/<project>/skills/ Project-owned skill sources src/<project>/skills/ Project-owned skill sources
src/<project>/.pi/skills/ Project-specific enabled Pi skill symlinks src/<project>/.pi/skills/ Project-specific enabled Pi skill symlinks

View File

@@ -1,34 +0,0 @@
---
name: nix-style
description: Use when creating, editing, or reviewing Nix files in this repository.
---
# nix-style
Use this skill as the source of truth for repository Nix conventions.
## Load rules
Read every Markdown file directly under [`rules/`](rules/) before applying this skill.
Resolve [`rules/`](rules/) relative to this `SKILL.md`.
Finish when all discovered rule files have been read.
## Apply rules
Apply the loaded rules when editing or reviewing Nix files.
When loaded rules conflict, follow the most specific rule.
After editing Nix files, run:
```bash
nix run .#fix
```
Finish when `nix run .#fix` succeeds and the Nix change follows the loaded rules.
If the command fails, report the failure.
## Maintain rules
When adding or changing files under [`rules/`](rules/), read [`RULE-FORMAT.md`](RULE-FORMAT.md) first.
Finish when the changed rule files follow that format.

21
src/skills/style/STYLE.md Normal file
View File

@@ -0,0 +1,21 @@
# Style skill instructions
Use this document for skills that own repository style conventions.
## Load rules
Read every Markdown file directly under `rules/` before applying the skill.
Resolve `rules/` relative to the skill's `SKILL.md`.
Finish when all discovered rule files have been read.
## Apply rules
Apply the loaded rules when editing or reviewing files covered by the skill.
When loaded rules conflict, follow the most specific rule.
## Maintain rules
When adding or changing files under `rules/`, read `src/skills/style/RULE-FORMAT.md` first.
Finish when the changed rule files follow that format.

View File

@@ -0,0 +1,18 @@
---
name: nix-style
description: Use when creating, editing, or reviewing Nix files in this repository.
---
# nix-style
Use this skill as the source of truth for repository Nix conventions.
Follow the shared style skill instructions in `src/skills/style/STYLE.md`.
After editing Nix files, run:
```bash
nix run .#fix
```
Finish when `nix run .#fix` succeeds and the Nix change follows the loaded rules.
If the command fails, report the failure.

View File

@@ -0,0 +1,9 @@
---
name: rust-style
description: Use when creating, editing, or reviewing Rust files in this repository.
---
# rust-style
Use this skill as the source of truth for repository Rust conventions.
Follow the shared style skill instructions in `src/skills/style/STYLE.md`.

View File

@@ -0,0 +1,39 @@
# Build rules
## Use workspace dependency versions
### Rule
Declare Rust dependency versions in the repository root `Cargo.toml` under `[workspace.dependencies]`.
Reference workspace dependencies from package `Cargo.toml` files with `workspace = true`.
Do not declare dependency versions in package `Cargo.toml` files unless the operator explicitly requests a package-local version.
Add package-local dependency features only when the feature set is specific to that package.
### Rationale
The repository has one Cargo workspace and one `Cargo.lock`.
Centralized dependency versions keep package manifests consistent with the workspace lockfile.
## Declare Rust tests in Bazel
### Rule
Declare Rust test files in the nearest Bazel `BUILD` file with `rust_test`.
Do not add `[[test]]` entries to `Cargo.toml` unless the operator explicitly requests Cargo test declarations.
### Rationale
Bazel is the source of truth for Rust test targets in this repository.
Keeping test declarations in Bazel avoids duplicate test target lists.
## Use one source input style per `rust_test`
### Rule
Do not set both `crate` and `srcs` on a `rust_test` target.
Use exactly one input style for each Rust test target.
### Rationale
`rules_rust` rejects `rust_test` targets that set both `crate` and `srcs`.
Keeping one input style per target makes the test declaration valid and unambiguous.

View File

@@ -0,0 +1,28 @@
# File rules
## Avoid generic Rust file names
### Rule
Do not name Rust source files `lib.rs` or `main.rs`.
Name library crate root files after the crate they define.
Name binary crate root files after the binary they define, or use a `_main.rs` suffix.
Set explicit Cargo paths and Bazel `srcs` when needed.
### Rationale
Generic root names make file search results and review context less clear.
Descriptive file names show which crate or binary owns the code.
## Keep Rust tests in separate files
### Rule
Put Rust tests in separate `*_test.rs` files.
Name each test file after the Rust source file or crate root that it tests.
Do not use generic test file names such as `lib_test.rs` or `main_test.rs`.
Do not add inline `#[cfg(test)]` modules.
### Rationale
Separate test files keep production modules focused and match the repository's Bazel test layout.