48 crates, 859 tests green (+58 kei-pet tests, was 801 at v0.35.0).
Full substrate pipeline test: all 8 agents launched via kei-agent-runtime
prepare → composed capability-fragment prompts → Agent tool invocations.
Zero file conflicts across disjoint scopes. Every agent self-verified
and landed files direct to main.
## A. memory (4 tests) — persistent conversations
- src/memory.rs — (user_id, pet_name)-scoped conversation log
- SQLite via rusqlite, index (user_id, pet_name, ts DESC)
- record_interaction / recent / search with LIKE-escape
## B. evolution (3 tests) — version diff + fork chain
- src/evolution.rs — PersonaVersion { version, parent_version, manifest }
- diff(old, new) → Vec<Change> (tone / directness / initiative / forbidden / humor)
- fork_version increments + links parent
## C. wizard (5 markdown phases) — /pet-init skill
- skills/pet-init/SKILL.md + 4 phases (identity / voice / edge / emit)
- AskUserQuestion-driven, no TOML editing for end users
- Writes ~/.claude/pet/<user_id>.toml + calls kei-pet keygen if needed
## D. templates (3 tests + 5 presets) — role-based personas
- templates/{friend,tutor,coach,therapist-companion,productivity-partner}.toml
- src/templates.rs — PetTemplate enum + load_template + list_templates
- Schema-enum mapping documented (dry→engineering-meta, etc) — schema.rs
expansion is future work
## E. bridge (3 tests) — /spawn-agent pet overlay
- src/bridge.rs — compose_prompt_with_pet(base + persona overlay + task)
- skills/spawn-agent/phase-3-pet-overlay.md — interactive pet selector
## F. recall (4 tests) — "have we discussed this before?"
- src/recall.rs — wraps kei_dna_index::precedent with body_sha8()
- SHA-256 first 4 bytes → 8 hex lowercase (matches kei_shared width)
- Fetches started_ts per hit for honest sort-by-recency
## G. reflect (7 tests) — self-reflection threshold proposals
- src/reflect.rs — CorrectionSignal + ProposedChange
- Thresholds: 3× too_verbose → SetDirectness, 2× forbidden_topic → AddForbidden, etc
- Idempotent: no-op if manifest already in desired state
## H. fleet (6 tests) — multi-pet per user
- src/fleet.rs — PetFleet { user_id, pets, active_pet }
- add_pet / switch_active / load_fleet with toml persistence
- shared_memory_key vs per_pet_memory_key — one user scopes multiple pets
## Known follow-ups (not blockers)
- Phase-4-emit of /spawn-agent should read PET_MANIFEST_PATH from new
phase-3-pet-overlay and pass to kei-spawn (wiring next wave)
- SKILL.md for spawn-agent should list new pet-overlay phase
- Schema enum expansion: humor_style "dry/witty", directness "direct/
gentle/blunt", initiative "proactive/nudge" as first-class variants
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.6 KiB
Rust
83 lines
2.6 KiB
Rust
//! Integration tests for `bridge::compose_prompt_with_pet`.
|
|
//!
|
|
//! Fixtures load from `examples/full.toml` via `include_str!` — this is the
|
|
//! only reliable way to test against a known-good manifest until a shared
|
|
//! `templates` module exists.
|
|
|
|
use kei_pet::{parse, AgentSpawnRequest, compose_prompt_with_pet};
|
|
|
|
const FULL: &str = include_str!("../examples/full.toml");
|
|
|
|
fn base_req(with_pet: bool) -> AgentSpawnRequest {
|
|
let pet = if with_pet {
|
|
Some(parse(FULL).expect("examples/full.toml must validate"))
|
|
} else {
|
|
None
|
|
};
|
|
AgentSpawnRequest {
|
|
role: "code-implementer".to_string(),
|
|
pet_manifest: pet,
|
|
task_body: "Refactor the foo module into three cubes.".to_string(),
|
|
base_prompt: "You are a senior Rust engineer.".to_string(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compose_prompt_without_pet_returns_base_plus_body() {
|
|
let req = base_req(false);
|
|
let out = compose_prompt_with_pet(&req);
|
|
|
|
// Must contain both the base prompt and the task body verbatim.
|
|
assert!(
|
|
out.contains("You are a senior Rust engineer."),
|
|
"base prompt missing from composed output:\n{out}"
|
|
);
|
|
assert!(
|
|
out.contains("Refactor the foo module into three cubes."),
|
|
"task body missing from composed output:\n{out}"
|
|
);
|
|
|
|
// Must NOT contain the persona-overlay header.
|
|
assert!(
|
|
!out.contains("## Persona overlay"),
|
|
"persona overlay section leaked in without a manifest:\n{out}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn compose_prompt_with_pet_includes_voice_tone_string() {
|
|
let req = base_req(true);
|
|
let out = compose_prompt_with_pet(&req);
|
|
|
|
// full.toml: tone_primary = "dry" → overlay emits "Primary tone: dry."
|
|
assert!(
|
|
out.contains("Primary tone: dry"),
|
|
"expected primary tone 'dry' in overlay output:\n{out}"
|
|
);
|
|
// Header must appear exactly once — overlay was injected.
|
|
assert!(
|
|
out.contains("## Persona overlay"),
|
|
"persona overlay header missing when manifest present:\n{out}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pet_forbidden_topics_appear_in_system_prompt() {
|
|
let req = base_req(true);
|
|
let out = compose_prompt_with_pet(&req);
|
|
|
|
// full.toml: forbidden.topics = ["politics", "crypto-hype"]
|
|
assert!(
|
|
out.contains("politics"),
|
|
"forbidden topic 'politics' not surfaced by overlay:\n{out}"
|
|
);
|
|
assert!(
|
|
out.contains("crypto-hype"),
|
|
"forbidden topic 'crypto-hype' not surfaced by overlay:\n{out}"
|
|
);
|
|
// And the "Never engage with" lead-in from overlay.rs must be present.
|
|
assert!(
|
|
out.contains("Never engage with:"),
|
|
"forbidden-topics lead-in phrase missing:\n{out}"
|
|
);
|
|
}
|