Skip to content
Merged
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
164 changes: 146 additions & 18 deletions .ghk/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,16 @@ fn sh(cmd: ShCmd, opts: ShOptions) -> Result<ShOutput> {
cleanup: Option<&Path>,
) -> Result<ShOutput> {
opts.apply(&mut c);
let output = c.output()?;
let output = match c.output() {
Ok(o) => o,
Err(e) => {
if let Some(p) = cleanup {
let _ = std::fs::remove_file(p);
}

return Err(format!("Failed to execute command: {}\nError: {}", desc, e).into());
}
};
if let Some(p) = cleanup {
let _ = std::fs::remove_file(p);
}
Expand Down Expand Up @@ -421,31 +430,107 @@ macro_rules! fmt_task {
};
}

// Helper to extract packages from changed files
fn extract_matrix_packages(ctx: &Context, files: &[PathBuf]) -> Vec<String> {
let interesting = filter!(files, ["*.rs", "Cargo.toml"]);

let manifests: HashSet<PathBuf> = interesting
.iter()
.filter_map(|f| find_upwards_bounded(f, &["Cargo.toml"], &ctx.root))
.map(|m| if m.is_absolute() { m } else { ctx.root.join(m) })
.collect();

let mut packages: Vec<String> = manifests
.iter()
.filter_map(|m| package_name_from_manifest(m))
.collect();
packages.sort();
packages.dedup();

packages
.into_iter()
.filter(|p| matrix_has_package(ctx, p))
.collect()
}

// Rust tasks
define_task!(
RustFixTask,
name = "rust_fix",
depends_on = [],
run = |ctx: &Context, files: &[PathBuf]| {
let interesting = filter!(files, ["*.rs", "Cargo.toml"]);
let filtered = extract_matrix_packages(ctx, files);

if filtered.is_empty() {
return Ok(interesting);
}

let mut argv: Vec<std::ffi::OsString> = vec![
"run".into(),
"-q".into(),
"-p".into(),
"xtask".into(),
"--".into(),
"matrix".into(),
];
for p in &filtered {
argv.push("-p".into());
argv.push(p.into());
}
argv.push("--command".into());
argv.push("fix".into());

let mut opts = pipe_opts().clone();
opts.cwd = Some(ctx.root.clone());
sh(ShCmd::Argv("cargo".into(), argv), opts)?;

Ok(interesting)
}
);

define_task!(
RustClippyTask,
name = "rust_clippy",
depends_on = [],
depends_on = ["rust_fix"],
run = |ctx: &Context, files: &[PathBuf]| {
let interesting = filter!(files, ["*.rs", "Cargo.toml"]);
let filtered = extract_matrix_packages(ctx, files);

let manifests: HashSet<PathBuf> = interesting
.iter()
.filter_map(|f| find_upwards_bounded(f, &["Cargo.toml"], &ctx.root))
.map(|m| if m.is_absolute() { m } else { ctx.root.join(m) })
.collect();
if filtered.is_empty() {
return Ok(interesting);
}

let mut packages: Vec<String> = manifests
.iter()
.filter_map(|m| package_name_from_manifest(m))
.collect();
packages.sort();
packages.dedup();
let mut argv: Vec<std::ffi::OsString> = vec![
"run".into(),
"-q".into(),
"-p".into(),
"xtask".into(),
"--".into(),
"matrix".into(),
];
for p in &filtered {
argv.push("-p".into());
argv.push(p.into());
}
argv.push("--command".into());
argv.push("clippy".into());

let filtered: Vec<String> = packages
.into_iter()
.filter(|p| matrix_has_package(ctx, p))
.collect();
let mut opts = pipe_opts().clone();
opts.cwd = Some(ctx.root.clone());
sh(ShCmd::Argv("cargo".into(), argv), opts)?;

Ok(interesting)
}
);

define_task!(
RustCheckTask,
name = "rust_check",
depends_on = ["rust_clippy"],
run = |ctx: &Context, files: &[PathBuf]| {
let interesting = filter!(files, ["*.rs", "Cargo.toml"]);
let filtered = extract_matrix_packages(ctx, files);

if filtered.is_empty() {
return Ok(interesting);
Expand All @@ -464,7 +549,35 @@ define_task!(
argv.push(p.into());
}
argv.push("--command".into());
argv.push("fix".into());
argv.push("check".into());

let mut opts = pipe_opts().clone();
opts.cwd = Some(ctx.root.clone());
sh(ShCmd::Argv("cargo".into(), argv), opts)?;

Ok(interesting)
}
);

define_task!(
RustCheckWorkspace,
name = "rust_check_workspace",
depends_on = [],
run = |ctx: &Context, files: &[PathBuf]| {
let interesting = filter!(files, ["*.rs", "Cargo.toml", "release-plz.toml"]);

if interesting.is_empty() {
return Ok(interesting);
}

let argv: Vec<std::ffi::OsString> = vec![
"run".into(),
"-q".into(),
"-p".into(),
"xtask".into(),
"--".into(),
"check-workspace".into(),
];

let mut opts = pipe_opts().clone();
opts.cwd = Some(ctx.root.clone());
Expand Down Expand Up @@ -514,6 +627,18 @@ fn main() {
})
.init();

// Expand PATH to include common tool installation locations
if let (Ok(current_path), Ok(home)) = (env::var("PATH"), env::var("HOME")) {
let extra_paths = vec![
format!("{}/.deno/bin", home),
format!("{}/.cargo/bin", home),
format!("{}/go/bin", home),
format!("{}/.local/bin", home),
];
let expanded_path = format!("{}:{}", extra_paths.join(":"), current_path);
env::set_var("PATH", expanded_path);
}

if let Err(e) = run() {
log::error!("[pre-commit] error: {e}");
std::process::exit(1);
Expand Down Expand Up @@ -583,7 +708,10 @@ fn run() -> Result<()> {
};

let tasks: Vec<Box<dyn Task>> = vec![
Box::new(RustCheckWorkspace::default()),
Box::new(RustFixTask::default()),
Box::new(RustClippyTask::default()),
Box::new(RustCheckTask::default()),
Box::new(RustFmtTask::default()),
Box::new(ShellFmtTask::default()),
Box::new(TomlFmtTask::default()),
Expand Down
173 changes: 173 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# AGENTS

## Scope

- **Do** keep changes small, targeted, and easy to validate.
- **Do** prefer clear, deterministic behavior over cleverness.
- **Do not** introduce new build systems or heavy dependencies without a strong
reason.

## Repo norms

### Style and tone

Documentation in ZeroOS favors:

- Short sections with descriptive headings
- Bullet lists for requirements and tradeoffs
- Concrete commands that can be copied and run
- Minimal hype; explain what a feature does and why it exists

Code favors:

- Explicit error handling (fail-fast)
- Modularity and compile-time configuration
- Avoiding “magic” behavior and global side effects

### Commits

We follow **Conventional Commits** (see `docs/publish-workflow.md`). Use types
like `feat:`, `fix:`, `docs:`, `refactor:`, `test:`.

**Never** add `Co-Authored-By` or similar AI attribution lines to commit
messages.

## How to validate changes

`cargo matrix` and `cargo massage` are workspace-provided cargo aliases (see
`.cargo/config.toml`), not built-in Cargo subcommands.

Prefer validating in this order:

1. **Format**
- `cargo matrix fix`
- `cargo matrix fmt`
2. **Lint / check**
- `cargo matrix clippy`
- `cargo matrix check`
3. **Tests**
- `cargo matrix test`

For a quick “do the reasonable thing” pass:

- `cargo massage`

Note: `cargo matrix` and `cargo massage` are workspace aliases defined in
`.cargo/config.toml`.

If you touch one crate only, it’s fine to run:

- `cargo test -p <crate-name>` (when that crate has host tests)
- `cargo matrix check -p <crate-name>` / `cargo matrix clippy -p <crate-name>` /
`cargo matrix test -p <crate-name>` (when you want to match the curated matrix
targets/features)

If validation fails, narrow the scope (single crate or target), fix, then re-run
the smallest relevant command.

## Adding or modifying crates

- Add new crates under `crates/`.
- Add them to the workspace in the root `Cargo.toml`.
- If you want the crate covered by `cargo matrix` (and CI), add it to
`matrix.yaml` with the right target(s) and feature sets.
- Prefer `workspace = true` dependencies where possible.
- If a crate is intended to be published, ensure it’s configured in
`release-plz.toml`.

Notes:

- You usually **do not** need to touch `matrix.yaml` for doc-only changes,
formatting-only changes, or changes isolated to a host tool that is already in
the matrix.
- You **should** update `matrix.yaml` when adding a new crate or when changing a
crate’s supported targets/features in a way that should be enforced by CI.

## Editing guidelines for agents

### Rust crate structure

- Keep `main.rs` minimal: argument parsing / logging setup / calling into the
crate.
- If you expect a binary to grow multiple subcommands, it’s fine to start with a
`cli.rs` + `commands/*` layout early to keep `main.rs` as glue.
- Keep `lib.rs` as a small facade: `mod ...;` plus `pub use ...` for the
intended public API.
- Put real logic in focused modules (e.g. `types.rs`, `parse.rs`, `analyze.rs`,
`render.rs`).
- Prefer module-level feature/target gates where possible
(`#[cfg(...)] mod foo;`) to keep boundaries clear.
- Use `cfg_if` when conditional compilation would otherwise create
nested/duplicated `#[cfg]` attributes.
- In `no_std` crates, be strict about dependencies and allocation: keep
guest/runtime crates `no_std` unless there is a strong reason.

### Architecture and dependencies

ZeroOS is intentionally layered and mostly `#![no_std]`. Keep dependencies
pointing “downward” and avoid cycles.

#### High-level layering

From lowest-level to highest-level:

- **Foundation**: `crates/zeroos-foundation` (core registries, shared
coordination)
- **Arch / OS / Runtime**: `crates/zeroos-arch-*`, `crates/zeroos-os-*`,
`crates/zeroos-runtime-*`
- **Subsystems**: allocators, VFS core, devices, scheduler, RNG
(`crates/zeroos-allocator-*`, `crates/zeroos-vfs-core`,
`crates/zeroos-device-*`, `crates/zeroos-scheduler-*`, `crates/zeroos-rng`)
- **Facade**: `crates/zeroos` (feature-gated wiring across the layers)
- **Platforms / Examples**: `platforms/*`, `examples/*` (integration glue and
demos)
- **Host tools**: `xtask/`, `crates/cargo-matrix`, `crates/elf-report`,
`platforms/spike-build` (these may use `std`)

#### Dependency rules of thumb

- `zeroos-foundation` should stay minimal and must not depend on higher layers
(no devices, no platform code).
- `crates/zeroos-*` guest/runtime crates should remain `no_std` unless there is
a strong reason.
- Devices should depend on VFS interfaces (`zeroos-vfs-core`) and/or foundation
traits — not on platforms/examples.
- Platforms and examples may depend on `zeroos` (facade) and selected features;
avoid pulling platform code into core crates.
- Host tools must not be depended on by guest crates.

When adding a new crate, be explicit about which layer it lives in and which
crates it is allowed to depend on.

### Be conservative

- Avoid reformatting unrelated code.
- Preserve public APIs unless the task explicitly requires a breaking change.
- Keep the patch focused: fewer files, smaller diffs.

### Be explicit about behavior

When changing behavior, include one of:

- a unit test
- a small smoke test command (pick the most relevant one), e.g.:
- `./build-fibonacci.sh` (no-std guest sanity)
- `./build-std-smoke.sh` (std/musl runtime sanity)
- `./build-c-smoke.sh` (C toolchain sanity)
- `./build-backtrace.sh` (backtrace capture + symbolization)
- a doc note in `docs/` if it affects developers/integrators

### Determinism and security

- Avoid introducing nondeterministic behavior (time, randomness,
environment-dependent output) unless it is explicitly plumbed as committed
input.
- Prefer memory-safe, bounds-checked parsing.

### Unsafe Rust policy

- Keep `unsafe` small, localized, and intentional—especially in guest/runtime
crates.
- Prefer safe abstractions with a narrow `unsafe` core.
- Every `unsafe` block should have a brief comment describing the safety
invariants (what must be true for it to be sound).
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
always include AGENTS.md.
Loading
Loading