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
48 changes: 48 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# kimetsu pre-commit gate — keeps every commit formatted and lint-clean.
#
# Activate once per clone:
# git config core.hooksPath .githooks
# (or run ./scripts/install-hooks.sh)
#
# Mirrors the CI quality gate (fmt + clippy) so red CI is caught locally.
# The full test suite + security audit run in CI on PR, not here, to keep
# commits fast.
#
# Escape hatches:
# SKIP_CLIPPY=1 git commit ... # fmt only (faster)
# git commit --no-verify ... # skip the hook entirely (discouraged)

set -euo pipefail

# Only gate Rust changes; let doc/config-only commits through fast.
if ! git diff --cached --name-only --diff-filter=ACMR | grep -qE '\.rs$|(^|/)Cargo\.(toml|lock)$'; then
exit 0
fi

if ! command -v cargo >/dev/null 2>&1; then
echo "pre-commit: cargo not found on PATH — skipping Rust checks." >&2
exit 0
fi

echo "pre-commit: cargo fmt --all --check"
if ! cargo fmt --all --check; then
echo "" >&2
echo "pre-commit: formatting check failed. Run 'cargo fmt --all' and re-stage." >&2
exit 1
fi

if [ "${SKIP_CLIPPY:-0}" = "1" ]; then
echo "pre-commit: SKIP_CLIPPY=1 set — skipping clippy."
exit 0
fi

# Clippy is advisory at the hook stage (main has a small pre-existing lint
# backlog). It runs and prints findings but does not block the commit, so
# you see new lints you introduced without being blocked by old ones.
# CI runs the same check on the PR. Once the backlog is cleared, switch
# this to a blocking `cargo clippy --workspace -- -D warnings`.
echo "pre-commit: cargo clippy --workspace (advisory)"
cargo clippy --workspace 2>&1 | grep -E "^warning:|^error:" || true

echo "pre-commit: OK (fmt enforced; clippy advisory)"
119 changes: 119 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Continuous integration: quality + security gate for every PR and for
# pushes to the long-lived branches (main, develop).
#
# Jobs (all required for a green PR):
# fmt — rustfmt check, no diffs allowed
# clippy — clippy with warnings denied (-D warnings)
# test — full workspace test suite
# audit — RUSTSEC advisory scan over the dependency tree
# deny — license / banned-crate / duplicate policy (cargo-deny)
#
# Release/publish lives in release.yml (tag-driven); this file is the
# pre-merge gate that keeps main + develop clean and secure.

name: ci

on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]

# Cancel superseded runs on the same ref to save CI minutes.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
fmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all --check

clippy:
name: clippy
runs-on: ubuntu-latest
# Advisory for now: main carries a small pre-existing lint backlog.
# The job runs and surfaces warnings on every PR; once the backlog is
# cleared in a dedicated cleanup PR, drop `continue-on-error` and add
# `-D warnings` to make this a hard gate.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-clippy
- run: cargo clippy --workspace --all-targets

test:
name: test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# Ubuntu is the primary gate; macOS guards the Apple-silicon
# build that ships in releases. Windows is exercised by the
# release workflow's build matrix.
os: [ubuntu-latest, macos-14]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-test-${{ matrix.os }}
# Library crates default to the lean (FTS-only) feature set, so the
# workspace test build does not pull the ONNX runtime. This keeps
# the gate fast and dependency-light; the embeddings flavor is
# built + smoke-tested in release.yml.
#
# The brain suite exercises the per-user GlobalUser brain at
# $HOME/.kimetsu and takes a writer lock on ~/.kimetsu/project.lock.
# Run multi-threaded (cargo's default), concurrent tests contend on
# that shared lock and one panics. Point HOME/TMP/GIT ceiling at a
# throwaway dir under the runner temp and serialize so each test gets
# an isolated project root.
- name: cargo test (isolated, serial)
shell: bash
run: |
ISO="${RUNNER_TEMP:-/tmp}/kimetsu-test-home"
mkdir -p "$ISO"
export HOME="$ISO" TMPDIR="$ISO" TMP="$ISO" TEMP="$ISO"
export GIT_CEILING_DIRECTORIES="$ISO"
cargo test --workspace --locked -- --test-threads=1

audit:
name: cargo-audit (RUSTSEC)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}

deny:
name: cargo-deny (licenses + bans)
runs-on: ubuntu-latest
# Advisory until the policy in deny.toml is verified green against the
# full dependency tree. Flip to a hard gate by removing this line once
# a PR has shown it passing.
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check bans licenses sources
76 changes: 76 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Contributing to kimetsu

Thanks for helping improve kimetsu. This repo keeps a clean, secure
`main` and `develop` through automated checks at two points: locally on
every commit, and in CI on every pull request.

## Branches

- **`main`** — released, stable. Protected; changes land via PR.
- **`develop`** — integration branch for in-flight work. Branch your
feature work off `develop` and open PRs back into it.
- Release tags (`vX.Y.Z`) are cut from `main` and drive the publish
pipeline (`.github/workflows/release.yml`).

```
feature/your-thing -> develop -> main -> tag vX.Y.Z
```

## One-time setup

Enable the pre-commit hook after cloning:

```bash
./scripts/install-hooks.sh # sets core.hooksPath -> .githooks
```

The hook enforces `cargo fmt --all --check` (blocking) and runs
`cargo clippy --workspace` in advisory mode (prints findings, does not
block) on staged Rust changes. Escape hatches when you need them:

- `SKIP_CLIPPY=1 git commit …` — formatting only (faster).
- `git commit --no-verify …` — skip the hook (discouraged; CI will still
catch issues).

## The checks

Both the local hook and CI enforce the same quality bar so a green
local commit means a green PR.

| Check | Local (pre-commit) | CI (PR + push to main/develop) | Blocking? |
|-------|:------------------:|:------------------------------:|:---------:|
| `cargo fmt --all --check` | ✅ | ✅ | yes |
| `cargo clippy --workspace` | ✅ (advisory) | ✅ | advisory* |
| `cargo test --workspace` | — | ✅ (ubuntu + macOS) | yes |
| `cargo-audit` (RUSTSEC advisories) | — | ✅ | yes |
| `cargo-deny` (licenses + bans) | — | ✅ | advisory* |

\* clippy and cargo-deny run on every PR but do not block merges yet
(`continue-on-error` in `ci.yml`). main carries a small pre-existing
clippy backlog; once it's cleared in a dedicated cleanup PR, flip these to
hard gates (clippy: add `-D warnings`; deny: remove `continue-on-error`).

Run the full suite locally before opening a PR:

```bash
cargo fmt --all --check
cargo clippy --workspace --all-targets # advisory; aim to keep it quiet
cargo test --workspace
cargo deny check # optional: cargo install cargo-deny
```

## Pull requests

- Target `develop` for features/fixes; target `main` only for releases
or hotfixes.
- Keep PRs focused; describe what changed and why.
- All required CI checks must pass before merge.
- Don't commit secrets. The brain redacts known token shapes, but treat
`.env`, credentials, and API keys as never-commit.

## Security

- Dependency vulnerabilities are caught by the `cargo-audit` CI job
(RUSTSEC database). If it flags an advisory, bump or replace the
affected crate before merging.
- Report security issues privately rather than in a public issue.
8 changes: 3 additions & 5 deletions crates/kimetsu-agent/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,8 @@ fn kimetsu_all_tool_definitions() -> Vec<ToolDefinition> {
turn that you used; multiple citations per turn are fine. \
Pass the `memory_id` exactly as it appeared in your retrieved \
context (look for `memory:<id>` in capsule expansion handles \
or the `id` field of memory capsules).".to_string(),
or the `id` field of memory capsules)."
.to_string(),
input_schema: json!({
"type": "object",
"properties": {
Expand Down Expand Up @@ -3642,10 +3643,7 @@ mod tests {
assert!(g.get("error").is_some(), "glob allowed {path}");
let v = kimetsu_view_image(&mut runtime, &json!({"path": path}));
assert!(v.get("error").is_some(), "view_image allowed {path}");
let s = kimetsu_search_files(
&mut runtime,
&json!({"pattern": "secret", "path": path}),
);
let s = kimetsu_search_files(&mut runtime, &json!({"pattern": "secret", "path": path}));
assert!(s.get("error").is_some(), "search_files allowed {path}");
}
// apply_patch with an absolute diff target is rejected before execution.
Expand Down
16 changes: 7 additions & 9 deletions crates/kimetsu-brain/src/ambient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,7 @@ mod tests {
..Default::default()
};
let suffix = render_as_query_suffix(&ctx);
assert!(
suffix.contains("src/embeddings.rs"),
"got {suffix}"
);
assert!(suffix.contains("src/embeddings.rs"), "got {suffix}");
assert!(
suffix.contains("crates/kimetsu-brain/src/ambient.rs"),
"got {suffix}"
Expand Down Expand Up @@ -453,10 +450,7 @@ mod tests {
// Build a fake workspace with `.kimetsu/` content next to
// real source files; the walker must surface the source
// files only.
let root = std::env::temp_dir().join(format!(
"kimetsu-ambient-test-{}",
ulid::Ulid::new()
));
let root = std::env::temp_dir().join(format!("kimetsu-ambient-test-{}", ulid::Ulid::new()));
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::create_dir_all(root.join(".kimetsu/runs")).unwrap();
std::fs::write(root.join("src/a.rs"), "// a").unwrap();
Expand All @@ -469,7 +463,11 @@ mod tests {
".kimetsu/ entries should be filtered out: {:?}",
files
);
assert!(files.iter().any(|p| p.ends_with("a.rs") || p.ends_with("b.rs")));
assert!(
files
.iter()
.any(|p| p.ends_with("a.rs") || p.ends_with("b.rs"))
);
let _ = std::fs::remove_dir_all(&root);
}
}
Loading
Loading