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
50 changes: 20 additions & 30 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# 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.
Expand Down Expand Up @@ -76,25 +75,21 @@ jobs:
- 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.
# `cargo test --workspace` feature-unifies the whole graph: a
# consumer crate (kimetsu-cli) enables `kimetsu-brain/embeddings`,
# so kimetsu-brain -- and its own test binary -- is built WITH the
# ONNX/fastembed flavor, not the lean default. Tests must therefore
# be embedder-agnostic; they are. (The lean FTS-only flavor is built
# + smoke-tested separately 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)
# KIMETSU_USER_BRAIN=0 keeps the suite hermetic: brain tests never
# touch the runner's real ~/.kimetsu. --test-threads=1 keeps the
# run deterministic (no cross-test races on shared on-disk state).
- name: cargo test
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
env:
KIMETSU_USER_BRAIN: "0"
run: cargo test --workspace --locked -- --test-threads=1

audit:
name: cargo-audit (RUSTSEC)
Expand All @@ -104,16 +99,11 @@ jobs:
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
# RUSTSEC-2024-0436: `paste` is unmaintained (an informational
# "unmaintained" notice, not an exploitable vulnerability). It is a
# transitive proc-macro dep with no first-party usage and no drop-in
# upgrade path. cargo-audit reports zero actual vulnerabilities;
# this ignore keeps the job green on the informational notice.
# Re-evaluate when an upstream dependency drops `paste`.
ignore: RUSTSEC-2024-0436

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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,9 @@
# and historical planning docs that don't belong in the user-facing
# kimetsu repo. See github.com/RodCor/kimetsu-bench (private).
/bench/
.claude/*.lock
.claude/*.lock
# fastembed model cache created by tests/runs
.fastembed_cache/
**/.fastembed_cache/
.claude/
.codex/
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ TESTS (12 new in brain — 10 conflict module + 1 project integration + 1 wrappe
detect_and_record_noop_writes_nothing
resolve_conflict_rejects_invalid_resolution_strings
project::tests (1)
add_memory_under_noop_embedder_writes_no_conflicts
add_memory_distinct_texts_no_conflicts
End-to-end regression: NoopEmbedder path produces zero
conflicts, exercises list_conflicts + resolve_conflict
wrappers (unknown id returns false, invalid resolution
Expand Down
2 changes: 1 addition & 1 deletion crates/kimetsu-brain/src/embeddings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ mod tests {

#[test]
fn encode_decode_embedding_round_trip() {
let vec = vec![0.1f32, -0.2, 3.14, -0.000_001, 42.0];
let vec = vec![0.1f32, -0.2, 3.125, -0.000_001, 42.0];
let blob = encode_embedding(&vec);
assert_eq!(blob.len(), vec.len() * 4);
let back = decode_embedding(&blob, Some(vec.len())).expect("decode");
Expand Down
37 changes: 21 additions & 16 deletions crates/kimetsu-brain/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2871,27 +2871,32 @@ mod tests {
fs::remove_dir_all(root).expect("remove temp project");
}

/// v0.5.2: end-to-end regression for the lean (NoopEmbedder)
/// build. `add_memory` must NOT write any rows to
/// `memory_conflicts` when the embedder is a no-op, and the
/// `list_conflicts` / `resolve_conflict` wrappers must return
/// the empty/false answer rather than panicking on a missing
/// table or a malformed query.
/// End-to-end regression for the add -> list_conflicts ->
/// resolve_conflict plumbing. It must be AGNOSTIC to which
/// embedder backs the build: `cargo test --workspace`
/// feature-unifies `embeddings` into this crate (kimetsu-cli
/// enables `kimetsu-brain/embeddings`), so
/// `open_default_embedder()` returns the real fastembed model
/// here, not the noop. The two memories below are therefore on
/// unrelated topics: cosine stays well under the 0.82 conflict
/// threshold for any real embedder, and the noop build trivially
/// records zero -- so `list_conflicts` is deterministically empty
/// either way.
///
/// Real semantic-conflict detection is exercised exhaustively
/// in `crate::conflict::tests` with a StubEmbedder; this test
/// guards the project-level plumbing only.
/// Real near-duplicate semantic detection is exercised
/// exhaustively in `crate::conflict::tests` with a StubEmbedder;
/// this test guards the project-level plumbing only.
#[test]
fn add_memory_under_noop_embedder_writes_no_conflicts() {
fn add_memory_distinct_texts_no_conflicts() {
with_user_brain_disabled(|| {
let root = std::env::temp_dir().join(format!("kimetsu-test-{}", Ulid::new()));
fs::create_dir_all(&root).expect("create temp project");
init_project(&root, false).expect("init project");

// Two near-duplicate memories. Under NoopEmbedder no
// conflict is detected; the two rows simply both exist
// (and don't collide via the dedup gate because their
// normalized texts differ).
// Two memories on unrelated topics: neither the noop nor
// a real embedder flags them as conflicting (cosine well
// under the 0.82 threshold), and they don't collide via
// the exact-text dedup gate, so both rows simply coexist.
let _m1 = add_memory(
&root,
MemoryScope::GlobalUser,
Expand All @@ -2903,14 +2908,14 @@ mod tests {
&root,
MemoryScope::GlobalUser,
MemoryKind::Preference,
"Prefer anyhow for library error types.",
"Cache HTTP responses with a one-hour TTL.",
)
.expect("add m2");

let open = list_conflicts(&root, 50).expect("list_conflicts");
assert!(
open.is_empty(),
"noop embedder must not generate conflicts; got {} rows",
"distinct-topic memories must not conflict; got {} rows",
open.len()
);

Expand Down
27 changes: 13 additions & 14 deletions crates/kimetsu-e2e/tests/conflicts.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! v0.5.3 e2e: v0.5.2 conflict-detection surface end-to-end.
//!
//! Conflict detection at ingest is embedder-gated; the lean default
//! build uses NoopEmbedder so conflict scans are no-ops. To prove the
//! end-to-end UX (table + list_conflicts + resolve_conflict) we
//! manually seed two memories + a conflict row via the public
//! `kimetsu_brain::conflict` API, then exercise the
//! `kimetsu_brain::project` wrappers the CLI / MCP surfaces sit on.
//! Conflict detection at ingest is embedder-gated. The workspace test
//! build can enable a real embedder through feature unification, so these
//! tests use unrelated seed memories and manually add the conflict row via
//! the public `kimetsu_brain::conflict` API. That keeps the project wrapper
//! assertions deterministic while conflict detection itself stays covered by
//! kimetsu-brain unit tests.
//!
//! This catches:
//! * Schema drift on `memory_conflicts` (column rename / type
Expand All @@ -25,10 +25,9 @@ fn list_and_resolve_conflict_wrappers_compose_against_a_real_project() {
with_user_brain_disabled(|| {
let project = TempProject::init("conflicts_resolve");

// Two contradictory preferences. Real prod code would hit
// these via add_memory's detect_and_record path under an
// embeddings build; here we seed both the memories and the
// conflict row directly to keep the test lean+deterministic.
// Use unrelated memories so the embeddings-enabled workspace build
// cannot auto-record a second conflict before this test manually
// seeds the one project-wrapper row it wants to exercise.
let new_id = project::add_memory(
project.root(),
MemoryScope::Repo,
Expand All @@ -40,15 +39,15 @@ fn list_and_resolve_conflict_wrappers_compose_against_a_real_project() {
project.root(),
MemoryScope::Repo,
MemoryKind::Preference,
"Prefer thiserror for library error types.",
"Cache HTTP responses with a one-hour TTL.",
)
.expect("add existing memory");

let conn = project.open_brain();
let hit = ConflictHit {
existing_memory_id: existing_id.clone(),
existing_kind: "preference".to_string(),
existing_text: "Prefer thiserror for library error types.".to_string(),
existing_text: "Cache HTTP responses with a one-hour TTL.".to_string(),
similarity: 0.92,
};
let conflict_id =
Expand Down Expand Up @@ -129,15 +128,15 @@ fn re_resolving_same_conflict_is_idempotent_through_project_wrapper() {
project.root(),
MemoryScope::Repo,
MemoryKind::Preference,
"Use spaces for indentation.",
"Rotate API keys every ninety days.",
)
.expect("add existing");

let conn = project.open_brain();
let hit = ConflictHit {
existing_memory_id: existing_id.clone(),
existing_kind: "preference".to_string(),
existing_text: "Use spaces for indentation.".to_string(),
existing_text: "Rotate API keys every ninety days.".to_string(),
similarity: 0.88,
};
let conflict_id =
Expand Down
41 changes: 0 additions & 41 deletions deny.toml

This file was deleted.

Loading