use std::io::IsTerminal;
use std::path::{Path, PathBuf};
use include_dir::{Dir, include_dir};
static SKILL: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/embedded/skill");
const SKILL_NAME: &str = "teksilo";
pub const BRIEF: &str = r##"# Teksilo — read this before writing GUI code in this app
This app depends on **Teksilo**, a pure-Rust desktop GUI framework: a retained
widget tree, SwiftUI-style layout negotiation, `Signal`/`Prop` reactivity,
AccessKit accessibility, wgpu rendering.
`cargo teksilo` is a cargo subcommand that answers for the exact Teksilo
version **this app resolved** — read from its dependency graph, never from
whatever is newest. Install it at that version:
```bash
cargo install cargo-teksilo --version <the teksilo version this app pins>
```
**There is a floor: this tool's first release is 0.13.0.** It is newer than the
framework it serves, so an app on teksilo 0.12 or older has no matching tool —
that version of it was never published, and no `--version 0.12.x` will resolve.
Such an app has to move to teksilo 0.13 first. Until it does, this briefing
describes a command that cannot run here: use `cargo doc -p teksilo-widgets`
and the compiler, and say the version is unserved rather than answering from
memory.
It refuses to answer when its own minor or major differs from the app's
Teksilo, and warns when only the patch differs. That is deliberate:
`SplitView` was deleted outright in favour of `Splitter` between two minors,
and a wrong answer reads exactly like a right one.
## The five commands
| Command | What it gives you |
| --- | --- |
| `cargo teksilo symbol <Name>` | The exact public API of a type — its module header, its `pub` items with their `///` docs, and the builder methods from its inherent `impl` blocks. Run it **before** writing against a type; never invent builder methods. |
| `cargo teksilo search "<question>"` | Retrieval over Teksilo's hand-written guides and worked example crates. Neither reaches this app any other way: the guides ship in no crate, and every example is `publish = false`. Reach for it when the question is conceptual. |
| `cargo teksilo show <path>` | One of those documents in full, offline — or just the lines a hit cited (`--lines 166-172`, 1-based and inclusive). `--list` prints every available path. |
| `cargo teksilo probe` | Writes a Python automation harness into `scripts/teksilo_probe/`, for driving the running app through its automation bridge and asserting on what is on screen. |
| `cargo teksilo setup` | The harness, plus this briefing re-installed for every agent configured here. |
## The rule that matters most
**Read a search result with `cargo teksilo show <path>`. Do not fetch it from
GitHub.** The path a hit prints is real in the *framework* repository and
absent from this one, so both tempting moves are wrong: opening it locally
finds nothing, and `blob/main/` — or the published book — serves `main`, which
is a different Teksilo from the one this app pinned. `show` is offline and
version-matched.
## Working in this app
1. **Conceptual question** ("which data model do I want?", "how does
drag-and-drop escalate to the OS?") → `cargo teksilo search "<question>"`,
then `cargo teksilo show <path>` to read the hit in full rather than
working from its snippet.
2. **Before using a type** → `cargo teksilo symbol <Name>`.
3. **Compile:** `cargo check -p <app-crate>`. If it fails twice on the same
item, re-extract that type's API before a third attempt — the mental model
is wrong, not the compiler.
4. **Verify behaviour** where it matters: headless widget tests for layout and
state logic, a probe for "the user clicks this and that happens on screen".
If `cargo teksilo` is not installed, fall back to
`cargo doc -p teksilo-widgets --no-deps --open`, or docs.rs at the pinned
version, and to the compiler. Both are slower and the second is
version-approximate — say so rather than guessing.
"##;
pub const BEGIN: &str = "<!-- BEGIN teksilo -->";
pub const END: &str = "<!-- END teksilo -->";
pub fn region_block() -> String {
format!("{BEGIN}\n{}\n{END}", BRIEF.trim_end())
}
pub fn upsert_region(existing: &str, block: &str) -> String {
if let Some(start) = existing.find(BEGIN)
&& let Some(end_offset) = existing[start..].find(END)
{
let end = start + end_offset + END.len();
let mut out = String::with_capacity(existing.len() + block.len());
out.push_str(&existing[..start]);
out.push_str(block);
out.push_str(&existing[end..]);
return out;
}
let mut out = existing.to_string();
if !out.is_empty() {
if !out.ends_with('\n') {
out.push('\n');
}
if !out.ends_with("\n\n") {
out.push('\n');
}
}
out.push_str(block);
out.push('\n');
out
}
#[derive(Debug, thiserror::Error)]
pub enum SetupError {
#[error("could not install the agent instructions: {0}")]
Io(#[from] std::io::Error),
#[error(transparent)]
Probe(#[from] crate::probe::ProbeError),
#[error(
"stdin is not a terminal, so there is nobody to answer this prompt.\n\
Pass {hint} to run non-interactively."
)]
NotATerminal { hint: &'static str },
#[error("could not find a home directory (neither $HOME nor %USERPROFILE% is set)")]
NoHome,
#[error(
"{0} exists but is not valid UTF-8, so its teksilo section cannot be edited \
without discarding the rest of the file.\n\
This tool owns only the text between its markers; fix the file's encoding \
and re-run."
)]
NotUtf8(PathBuf),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scope {
Project,
User,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Form {
Skill,
OwnFile { frontmatter: &'static str },
Region,
}
impl Form {
pub fn describe(&self) -> &'static str {
match self {
Form::Skill => "the full skill (4 files)",
Form::OwnFile { .. } => "a rules file (brief)",
Form::Region => "a `teksilo` section (brief)",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Target {
pub agent: &'static str,
pub marker: &'static str,
pub path: PathBuf,
pub form: Form,
}
const CURSOR_FRONTMATTER: &str = "\
---
description: Teksilo GUI framework — use `cargo teksilo` for version-matched API and docs
globs: **/*.rs
alwaysApply: false
---
";
const WINDSURF_FRONTMATTER: &str = "\
---
trigger: glob
globs: **/*.rs
description: Teksilo GUI framework — use `cargo teksilo` for version-matched API and docs
---
";
enum Shape {
Fixed { path: &'static str, form: Form },
Cline,
}
struct Vendor {
agent: &'static str,
marker: &'static str,
shape: Shape,
}
const VENDORS: &[Vendor] = &[
Vendor {
agent: "Claude Code",
marker: ".claude/",
shape: Shape::Fixed {
path: ".claude/skills/teksilo",
form: Form::Skill,
},
},
Vendor {
agent: "Cursor",
marker: ".cursor/",
shape: Shape::Fixed {
path: ".cursor/rules/teksilo.mdc",
form: Form::OwnFile {
frontmatter: CURSOR_FRONTMATTER,
},
},
},
Vendor {
agent: "Windsurf",
marker: ".windsurf/",
shape: Shape::Fixed {
path: ".windsurf/rules/teksilo.md",
form: Form::OwnFile {
frontmatter: WINDSURF_FRONTMATTER,
},
},
},
Vendor {
agent: "Cline",
marker: ".clinerules/",
shape: Shape::Cline,
},
Vendor {
agent: "GitHub Copilot",
marker: ".github/",
shape: Shape::Fixed {
path: ".github/copilot-instructions.md",
form: Form::Region,
},
},
Vendor {
agent: "Codex / AGENTS.md",
marker: "AGENTS.md",
shape: Shape::Fixed {
path: "AGENTS.md",
form: Form::Region,
},
},
];
fn cline_layout(root: &Path) -> Option<(&'static str, PathBuf, PathBuf, Form)> {
let legacy = root.join(".clinerules");
if legacy.is_dir() {
let target = legacy.join("teksilo.md");
return Some((
".clinerules/",
legacy,
target,
Form::OwnFile { frontmatter: "" },
));
}
if legacy.is_file() {
return Some((".clinerules", legacy.clone(), legacy, Form::Region));
}
let modern = root.join(".cline");
if modern.is_dir() {
let target = modern.join("rules").join("teksilo.md");
return Some((".cline/", modern, target, Form::OwnFile { frontmatter: "" }));
}
None
}
pub fn find_project_root(start: &Path) -> Option<PathBuf> {
let mut dir = Some(start);
while let Some(d) = dir {
if d.join("Cargo.toml").is_file() {
return Some(d.to_path_buf());
}
dir = d.parent();
}
None
}
pub struct ProjectCandidate {
pub target: Target,
pub marker_path: PathBuf,
}
impl ProjectCandidate {
pub fn detected(&self) -> bool {
self.marker_path.exists()
}
}
pub fn project_candidates(root: &Path) -> Vec<ProjectCandidate> {
VENDORS
.iter()
.map(|vendor| match &vendor.shape {
Shape::Fixed { path, form } => ProjectCandidate {
target: Target {
agent: vendor.agent,
marker: vendor.marker,
path: root.join(path),
form: *form,
},
marker_path: root.join(vendor.marker.trim_end_matches('/')),
},
Shape::Cline => match cline_layout(root) {
Some((marker, marker_path, path, form)) => ProjectCandidate {
target: Target {
agent: vendor.agent,
marker,
path,
form,
},
marker_path,
},
None => ProjectCandidate {
target: Target {
agent: vendor.agent,
marker: vendor.marker,
path: root.join(".clinerules/teksilo.md"),
form: Form::OwnFile { frontmatter: "" },
},
marker_path: root.join(".clinerules"),
},
},
})
.collect()
}
pub fn project_targets(root: &Path) -> Vec<Target> {
project_candidates(root)
.into_iter()
.filter(ProjectCandidate::detected)
.map(|c| c.target)
.collect()
}
pub fn project_undetected(root: &Path) -> Vec<(&'static str, &'static str)> {
project_candidates(root)
.into_iter()
.filter(|c| !c.detected())
.map(|c| (c.target.agent, c.target.marker))
.collect()
}
pub fn vibe_home(home: &Path) -> PathBuf {
match std::env::var_os("VIBE_HOME") {
Some(v) if !v.is_empty() => PathBuf::from(v),
_ => home.join(".vibe"),
}
}
pub fn opencode_config(home: &Path) -> PathBuf {
match std::env::var_os("XDG_CONFIG_HOME") {
Some(v) if !v.is_empty() => PathBuf::from(v).join("opencode"),
_ => home.join(".config").join("opencode"),
}
}
pub struct UserCandidate {
pub target: Target,
pub dir: PathBuf,
}
impl UserCandidate {
pub fn detected(&self) -> bool {
self.dir.is_dir()
}
}
pub fn user_candidates(home: &Path, vibe: &Path, opencode: &Path) -> Vec<UserCandidate> {
vec![
UserCandidate {
target: Target {
agent: "Claude Code",
marker: "~/.claude/",
path: home.join(".claude").join("skills").join(SKILL_NAME),
form: Form::Skill,
},
dir: home.join(".claude"),
},
UserCandidate {
target: Target {
agent: "Mistral Vibe",
marker: "~/.vibe/",
path: vibe.join("AGENTS.md"),
form: Form::Region,
},
dir: vibe.to_path_buf(),
},
UserCandidate {
target: Target {
agent: "opencode",
marker: "~/.config/opencode/",
path: opencode.join("AGENTS.md"),
form: Form::Region,
},
dir: opencode.to_path_buf(),
},
]
}
pub fn user_targets(home: &Path) -> Vec<Target> {
user_targets_in(home, &vibe_home(home), &opencode_config(home))
}
pub fn user_targets_in(home: &Path, vibe: &Path, opencode: &Path) -> Vec<Target> {
user_candidates(home, vibe, opencode)
.into_iter()
.filter(UserCandidate::detected)
.map(|c| c.target)
.collect()
}
pub fn user_undetected(home: &Path) -> Vec<(&'static str, PathBuf)> {
user_undetected_in(home, &vibe_home(home), &opencode_config(home))
}
pub fn user_undetected_in(
home: &Path,
vibe: &Path,
opencode: &Path,
) -> Vec<(&'static str, PathBuf)> {
user_candidates(home, vibe, opencode)
.into_iter()
.filter(|c| !c.detected())
.map(|c| (c.target.agent, c.dir))
.collect()
}
pub const USER_NOT_APPLICABLE: &[(&str, &str)] = &[
(
"Cursor",
"user rules are edited in Customize → Rules, not stored as a file",
),
(
"GitHub Copilot",
"personal instructions live in your github.com settings",
),
(
"Windsurf",
"one global file, ~/.codeium/windsurf/memories/global_rules.md — paste the brief there by hand",
),
(
"Codex / AGENTS.md",
"a repository-root AGENTS.md is per-repository by definition",
),
];
pub fn user_scope_note() -> String {
let mut out = String::from("The rest keep no user-level file this tool can write:\n");
for (agent, why) in USER_NOT_APPLICABLE {
out.push_str(&format!(" {agent:<18}{why}.\n"));
}
out.push_str(&format!(
"Run `cargo teksilo setup` inside each project for those {}.",
USER_NOT_APPLICABLE.len()
));
out
}
pub const ACTIVATION_CAVEAT: &str = "\
These are files on disk. An agent picks them up on its own terms —\n\
some ask you to trust the folder first, and a gitignored instruction\n\
file is skipped silently.";
pub fn home_dir() -> Result<PathBuf, SetupError> {
std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" })
.map(PathBuf::from)
.ok_or(SetupError::NoHome)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Change {
Created,
Updated,
Unchanged,
}
impl Change {
pub fn describe(&self) -> &'static str {
match self {
Change::Created => "created",
Change::Updated => "updated",
Change::Unchanged => "unchanged",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Outcome {
pub agent: &'static str,
pub path: PathBuf,
pub change: Change,
pub files: usize,
}
fn own_file(frontmatter: &str) -> String {
if frontmatter.is_empty() {
BRIEF.to_string()
} else {
format!("{frontmatter}\n{BRIEF}")
}
}
fn read_shared(path: &Path) -> Result<String, SetupError> {
match std::fs::read(path) {
Ok(bytes) => String::from_utf8(bytes).map_err(|_| SetupError::NotUtf8(path.to_path_buf())),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
Err(e) => Err(e.into()),
}
}
pub fn apply(target: &Target) -> Result<Outcome, SetupError> {
let (change, files) = match target.form {
Form::Skill => install_skill(&target.path)?,
Form::OwnFile { frontmatter } => {
(write_if_changed(&target.path, &own_file(frontmatter))?, 1)
}
Form::Region => {
let existing = read_shared(&target.path)?;
let updated = upsert_region(&existing, ®ion_block());
(write_if_changed(&target.path, &updated)?, 1)
}
};
Ok(Outcome {
agent: target.agent,
path: target.path.clone(),
change,
files,
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Presence {
Current,
Stale,
Absent,
Blocked(&'static str),
}
pub fn inspect(target: &Target) -> Presence {
match target.form {
Form::Skill => inspect_skill(&target.path),
Form::OwnFile { frontmatter } => match std::fs::read(&target.path) {
Ok(current) if current == own_file(frontmatter).as_bytes() => Presence::Current,
Ok(_) => Presence::Stale,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Presence::Absent,
Err(_) => Presence::Blocked("cannot be read"),
},
Form::Region => {
let existing = match read_shared(&target.path) {
Ok(text) => text,
Err(SetupError::NotUtf8(_)) => return Presence::Blocked("is not valid UTF-8"),
Err(_) => return Presence::Blocked("cannot be read"),
};
if !existing.contains(BEGIN) {
return Presence::Absent;
}
if existing == upsert_region(&existing, ®ion_block()) {
Presence::Current
} else {
Presence::Stale
}
}
}
}
fn inspect_skill(dest: &Path) -> Presence {
if !dest.exists() {
return Presence::Absent;
}
let mut wanted = Vec::new();
collect(&SKILL, Path::new(""), &mut wanted);
let mut any = false;
let mut all = true;
for (relative, bytes) in &wanted {
match std::fs::read(dest.join(relative)) {
Ok(current) if current == *bytes => any = true,
_ => all = false,
}
}
if !any {
return Presence::Absent;
}
let keep: Vec<&Path> = wanted.iter().map(|(p, _)| p.as_path()).collect();
if all && !has_strays(dest, dest, &keep) {
Presence::Current
} else {
Presence::Stale
}
}
fn has_strays(root: &Path, dir: &Path, keep: &[&Path]) -> bool {
let Ok(entries) = std::fs::read_dir(dir) else {
return true;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if has_strays(root, &path, keep) {
return true;
}
} else if let Ok(relative) = path.strip_prefix(root)
&& !is_wanted(relative, keep)
{
return true;
}
}
false
}
fn is_wanted(relative: &Path, keep: &[&Path]) -> bool {
let found = relative.as_os_str().to_string_lossy();
keep.iter()
.any(|k| k.as_os_str().to_string_lossy().eq_ignore_ascii_case(&found))
}
fn write_if_changed(path: &Path, content: &str) -> Result<Change, SetupError> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
match std::fs::read(path) {
Ok(current) if current == content.as_bytes() => Ok(Change::Unchanged),
Ok(_) => {
std::fs::write(path, content)?;
Ok(Change::Updated)
}
Err(_) => {
std::fs::write(path, content)?;
Ok(Change::Created)
}
}
}
fn install_skill(dest: &Path) -> Result<(Change, usize), SetupError> {
let existed = dest.exists();
let mut wanted = Vec::new();
collect(&SKILL, Path::new(""), &mut wanted);
let mut moved = false;
for (relative, bytes) in &wanted {
let target = dest.join(relative);
if let Some(parent) = target.parent() {
std::fs::create_dir_all(parent)?;
}
match std::fs::read(&target) {
Ok(current) if current == *bytes => {}
_ => {
std::fs::write(&target, bytes)?;
moved = true;
}
}
}
let keep: Vec<&Path> = wanted.iter().map(|(p, _)| p.as_path()).collect();
if existed {
moved |= remove_strays(dest, dest, &keep)?;
}
let change = match (existed, moved) {
(false, _) => Change::Created,
(true, true) => Change::Updated,
(true, false) => Change::Unchanged,
};
Ok((change, wanted.len()))
}
fn collect<'a>(dir: &Dir<'a>, prefix: &Path, out: &mut Vec<(PathBuf, &'a [u8])>) {
for file in dir.files() {
let name = file.path().file_name().unwrap_or_default();
out.push((prefix.join(name), file.contents()));
}
for sub in dir.dirs() {
let name = sub.path().file_name().unwrap_or_default();
collect(sub, &prefix.join(name), out);
}
}
fn remove_strays(root: &Path, dir: &Path, keep: &[&Path]) -> Result<bool, SetupError> {
let mut removed = false;
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
removed |= remove_strays(root, &path, keep)?;
if std::fs::read_dir(&path)?.next().is_none() {
std::fs::remove_dir(&path)?;
}
} else if let Ok(relative) = path.strip_prefix(root)
&& !is_wanted(relative, keep)
{
std::fs::remove_file(&path)?;
removed = true;
}
}
Ok(removed)
}
pub fn require_interactive(hint: &'static str) -> Result<(), SetupError> {
if std::io::stdin().is_terminal() {
Ok(())
} else {
Err(SetupError::NotATerminal { hint })
}
}
pub fn confirm(question: &str, hint: &'static str) -> Result<bool, SetupError> {
use std::io::Write;
require_interactive(hint)?;
print!("{question} [y/N] ");
std::io::stdout().flush()?;
let mut answer = String::new();
std::io::stdin().read_line(&mut answer)?;
Ok(matches!(
answer.trim().to_ascii_lowercase().as_str(),
"y" | "yes"
))
}
#[cfg(test)]
mod tests {
use super::*;
fn temp() -> tempfile::TempDir {
tempfile::tempdir().unwrap()
}
#[test]
fn a_missing_directory_is_not_invented() {
let t = temp();
assert!(project_targets(t.path()).is_empty());
assert!(
!t.path().join(".claude").exists(),
"must not create anything"
);
assert!(!t.path().join("AGENTS.md").exists());
}
#[test]
fn each_vendor_is_detected_by_its_own_marker() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
std::fs::create_dir_all(t.path().join(".cursor")).unwrap();
std::fs::create_dir_all(t.path().join(".windsurf")).unwrap();
std::fs::create_dir_all(t.path().join(".cline")).unwrap();
std::fs::create_dir_all(t.path().join(".github")).unwrap();
std::fs::write(t.path().join("AGENTS.md"), "# Agents\n").unwrap();
let found = project_targets(t.path());
let agents: Vec<_> = found.iter().map(|t| t.agent).collect();
assert_eq!(
agents,
[
"Claude Code",
"Cursor",
"Windsurf",
"Cline",
"GitHub Copilot",
"Codex / AGENTS.md"
]
);
assert!(project_undetected(t.path()).is_empty());
}
#[test]
fn two_vendors_in_one_project_both_get_a_target() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
std::fs::create_dir_all(t.path().join(".github")).unwrap();
let found = project_targets(t.path());
assert_eq!(found.len(), 2);
assert_eq!(found[0].form, Form::Skill);
assert_eq!(found[1].form, Form::Region);
assert_eq!(
project_undetected(t.path())
.iter()
.map(|(a, _)| *a)
.collect::<Vec<_>>(),
["Cursor", "Windsurf", "Cline", "Codex / AGENTS.md"]
);
}
#[test]
fn project_scope_never_resolves_a_path_under_home() {
let t = temp();
let home = temp();
std::fs::create_dir_all(home.path().join(".claude")).unwrap();
assert!(project_targets(t.path()).is_empty());
for target in project_targets(t.path()) {
assert!(target.path.starts_with(t.path()));
}
}
fn user_dirs(home: &Path) -> (PathBuf, PathBuf) {
(home.join(".vibe"), home.join(".config/opencode"))
}
#[test]
fn a_home_with_nothing_in_it_gets_nothing_written() {
let home = temp();
let (vibe, opencode) = user_dirs(home.path());
assert!(user_targets_in(home.path(), &vibe, &opencode).is_empty());
assert!(!vibe.exists());
assert!(!opencode.exists());
assert!(!home.path().join(".claude").exists());
let missing: Vec<_> = user_undetected_in(home.path(), &vibe, &opencode)
.into_iter()
.map(|(a, _)| a)
.collect();
assert_eq!(missing, ["Claude Code", "Mistral Vibe", "opencode"]);
}
#[test]
fn each_user_agent_is_detected_by_its_own_directory() {
let home = temp();
let (vibe, opencode) = user_dirs(home.path());
std::fs::create_dir_all(home.path().join(".claude")).unwrap();
std::fs::create_dir_all(&vibe).unwrap();
std::fs::create_dir_all(&opencode).unwrap();
let found = user_targets_in(home.path(), &vibe, &opencode);
assert_eq!(
found.iter().map(|t| t.agent).collect::<Vec<_>>(),
["Claude Code", "Mistral Vibe", "opencode"]
);
assert_eq!(found[0].path, home.path().join(".claude/skills/teksilo"));
assert_eq!(found[0].form, Form::Skill);
assert_eq!(found[1].path, vibe.join("AGENTS.md"));
assert_eq!(found[1].form, Form::Region);
assert_eq!(found[2].path, opencode.join("AGENTS.md"));
assert_eq!(found[2].form, Form::Region);
assert!(user_undetected_in(home.path(), &vibe, &opencode).is_empty());
}
#[test]
fn one_installed_agent_does_not_pull_in_the_other_two() {
let home = temp();
let (vibe, opencode) = user_dirs(home.path());
std::fs::create_dir_all(&vibe).unwrap();
let found = user_targets_in(home.path(), &vibe, &opencode);
assert_eq!(found.len(), 1);
assert_eq!(found[0].agent, "Mistral Vibe");
assert_eq!(
user_undetected_in(home.path(), &vibe, &opencode)
.into_iter()
.map(|(a, _)| a)
.collect::<Vec<_>>(),
["Claude Code", "opencode"]
);
}
#[test]
fn a_relocated_config_directory_is_where_the_file_lands() {
let elsewhere = temp();
let home = temp();
let vibe = elsewhere.path().join("vibe-state");
let opencode = elsewhere.path().join("xdg/opencode");
std::fs::create_dir_all(&vibe).unwrap();
std::fs::create_dir_all(&opencode).unwrap();
let found = user_targets_in(home.path(), &vibe, &opencode);
assert_eq!(found.len(), 2);
assert_eq!(found[0].path, vibe.join("AGENTS.md"));
assert_eq!(found[1].path, opencode.join("AGENTS.md"));
assert!(
!home.path().join(".vibe").exists() && !home.path().join(".config").exists(),
"the default locations must not be touched when the env moved them"
);
}
#[test]
fn the_defaults_are_the_documented_ones() {
let home = Path::new("/home/someone");
assert!(vibe_home(home).ends_with(".vibe") || std::env::var_os("VIBE_HOME").is_some());
assert!(
opencode_config(home).ends_with("opencode"),
"opencode always lives in a directory of its own name"
);
}
#[test]
fn the_project_root_is_the_nearest_manifest_above() {
let t = temp();
std::fs::write(t.path().join("Cargo.toml"), "[package]\n").unwrap();
let deep = t.path().join("crates/app/src");
std::fs::create_dir_all(&deep).unwrap();
assert_eq!(
find_project_root(&deep).unwrap().canonicalize().unwrap(),
t.path().canonicalize().unwrap()
);
}
#[test]
fn no_manifest_anywhere_is_none_rather_than_a_guess() {
let t = temp();
assert_eq!(find_project_root(t.path()), None);
}
#[test]
fn a_region_is_appended_to_a_file_that_has_none() {
let out = upsert_region(
"# My project\n\nRules here.\n",
"<!-- BEGIN teksilo -->\nX\n<!-- END teksilo -->",
);
assert!(out.starts_with("# My project\n\nRules here.\n\n"));
assert!(out.contains(BEGIN) && out.contains(END));
}
#[test]
fn a_second_run_changes_nothing() {
let block = region_block();
let once = upsert_region("# My project\n", &block);
let twice = upsert_region(&once, &block);
assert_eq!(once, twice, "setup must be idempotent on a shared file");
}
#[test]
fn nothing_outside_the_markers_is_disturbed() {
let before = format!("head\n\n{BEGIN}\nstale brief\n{END}\n\ntail\n");
let after = upsert_region(&before, &format!("{BEGIN}\nfresh brief\n{END}"));
assert_eq!(
after,
format!("head\n\n{BEGIN}\nfresh brief\n{END}\n\ntail\n")
);
}
#[test]
fn an_empty_file_gets_no_leading_blank_line() {
let out = upsert_region("", ®ion_block());
assert!(out.starts_with(BEGIN));
}
fn cline_target(root: &Path) -> Option<Target> {
project_targets(root)
.into_iter()
.find(|t| t.agent == "Cline")
}
#[test]
fn clinerules_wins_over_cline_because_it_is_the_one_that_is_read() {
let t = temp();
std::fs::create_dir_all(t.path().join(".clinerules")).unwrap();
std::fs::create_dir_all(t.path().join(".cline")).unwrap();
let target = cline_target(t.path()).expect("detected");
assert_eq!(
target.path,
t.path().join(".clinerules/teksilo.md"),
"with both present, write the one every surface reads"
);
apply(&target).unwrap();
assert!(
!t.path().join(".cline/rules").exists(),
"and do not also write the one that may be ignored"
);
}
#[test]
fn a_cline_only_project_is_honoured_rather_than_given_a_new_directory() {
let t = temp();
std::fs::create_dir_all(t.path().join(".cline")).unwrap();
let target = cline_target(t.path()).expect("detected");
assert_eq!(target.path, t.path().join(".cline/rules/teksilo.md"));
assert_eq!(target.form, Form::OwnFile { frontmatter: "" });
apply(&target).unwrap();
let written = std::fs::read_to_string(&target.path).unwrap();
assert!(
written.starts_with("# Teksilo"),
"no frontmatter: a Cline rule without one is always active"
);
assert_eq!(inspect(&target), Presence::Current);
assert!(
!t.path().join(".clinerules").exists(),
"must not invent the other layout beside the one in use"
);
}
#[test]
fn a_legacy_clinerules_file_gets_a_region_not_a_child_path() {
let t = temp();
let path = t.path().join(".clinerules");
std::fs::write(&path, "# My own rules\n\nAlways use tabs.\n").unwrap();
let target = cline_target(t.path()).expect("detected");
assert_eq!(target.path, path);
assert_eq!(target.form, Form::Region);
apply(&target).unwrap();
let after = std::fs::read_to_string(&path).unwrap();
assert!(after.contains("Always use tabs."), "their rules survive");
assert!(after.contains(BEGIN), "and ours is appended in a region");
assert_eq!(apply(&target).unwrap().change, Change::Unchanged);
}
#[test]
fn a_project_with_no_cline_anything_is_not_detected_and_names_the_modern_dir() {
let t = temp();
assert!(cline_target(t.path()).is_none());
let missing = project_undetected(t.path());
assert!(
missing.contains(&("Cline", ".clinerules/")),
"the label must name the directory every Cline surface reads, and \
the one Cline's own UI creates: {missing:?}"
);
assert!(!t.path().join(".cline").exists());
assert!(!t.path().join(".clinerules").exists());
}
#[test]
fn a_shared_file_that_is_not_utf8_stops_setup_instead_of_being_destroyed() {
let t = temp();
let path = t.path().join("AGENTS.md");
let original = b"# My project rules\n\nNever delete this. \xff\n";
std::fs::write(&path, original).unwrap();
let target = project_targets(t.path())
.into_iter()
.find(|t| t.form == Form::Region)
.unwrap();
assert!(
matches!(inspect(&target), Presence::Blocked(_)),
"an undecodable shared file is not `Absent` — setup cannot install here"
);
assert!(
matches!(apply(&target), Err(SetupError::NotUtf8(_))),
"setup must refuse rather than rewrite a file it cannot read whole"
);
assert_eq!(
std::fs::read(&path).unwrap(),
original,
"the project's own bytes must survive untouched"
);
}
#[test]
fn a_missing_shared_file_is_still_created() {
let t = temp();
std::fs::write(t.path().join("AGENTS.md"), "seed").unwrap();
let target = project_targets(t.path())
.into_iter()
.find(|t| t.form == Form::Region)
.unwrap();
std::fs::remove_file(&target.path).unwrap();
assert_eq!(inspect(&target), Presence::Absent);
assert_eq!(apply(&target).unwrap().change, Change::Created);
assert_eq!(inspect(&target), Presence::Current);
}
#[test]
fn a_differently_cased_skill_file_is_not_deleted() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
let target = project_targets(t.path()).remove(0);
apply(&target).unwrap();
let upper = target.path.join("SKILL.md");
let body = std::fs::read(&upper).unwrap();
std::fs::remove_file(&upper).unwrap();
std::fs::write(target.path.join("skill.md"), &body).unwrap();
apply(&target).unwrap();
assert!(
std::fs::read(target.path.join("SKILL.md")).is_ok(),
"the skill's own SKILL.md must survive a reinstall"
);
}
#[test]
fn a_real_stray_is_still_removed() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
let target = project_targets(t.path()).remove(0);
apply(&target).unwrap();
let stray = target.path.join("reference/from_an_older_release.md");
std::fs::write(&stray, b"x").unwrap();
assert_eq!(inspect(&target), Presence::Stale);
assert_eq!(apply(&target).unwrap().change, Change::Updated);
assert!(!stray.exists(), "a genuine leftover is still pruned");
}
fn one_of_each_form(root: &Path) -> Vec<Target> {
std::fs::create_dir_all(root.join(".claude")).unwrap();
std::fs::create_dir_all(root.join(".cursor")).unwrap();
std::fs::write(root.join("AGENTS.md"), "# Ours\n\nKeep this.\n").unwrap();
let targets = project_targets(root);
let forms: Vec<Form> = targets.iter().map(|t| t.form).collect();
assert!(forms.contains(&Form::Skill), "need a Skill target");
assert!(forms.contains(&Form::Region), "need a Region target");
assert!(
forms.iter().any(|f| matches!(f, Form::OwnFile { .. })),
"need an OwnFile target"
);
targets
}
#[test]
fn inspect_matches_apply() {
let t = temp();
let targets = one_of_each_form(t.path());
for target in &targets {
assert_eq!(
inspect(target),
Presence::Absent,
"{}: nothing of ours is installed yet",
target.agent
);
}
for target in &targets {
let change = apply(target).unwrap().change;
assert_ne!(change, Change::Unchanged, "{}: first write", target.agent);
assert_eq!(
inspect(target),
Presence::Current,
"{}: current immediately after apply",
target.agent
);
assert_eq!(
apply(target).unwrap().change,
Change::Unchanged,
"{}: apply agrees it is current",
target.agent
);
}
}
#[test]
fn an_older_releases_content_inspects_as_stale_for_every_form() {
let t = temp();
let targets = one_of_each_form(t.path());
for target in &targets {
apply(target).unwrap();
}
for target in &targets {
match target.form {
Form::Skill => {
std::fs::write(target.path.join("SKILL.md"), b"from 0.9").unwrap();
}
Form::OwnFile { .. } => {
std::fs::write(&target.path, b"from 0.9").unwrap();
}
Form::Region => {
let stale = format!("# Ours\n\n{BEGIN}\nfrom 0.9\n{END}\n");
std::fs::write(&target.path, stale).unwrap();
}
}
assert_eq!(
inspect(target),
Presence::Stale,
"{}: an older release's content",
target.agent
);
assert_eq!(
apply(target).unwrap().change,
Change::Updated,
"{}: apply rewrites it",
target.agent
);
}
}
#[test]
fn a_stray_file_makes_the_skill_stale_but_an_empty_directory_does_not() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
let target = project_targets(t.path()).remove(0);
apply(&target).unwrap();
assert_eq!(inspect(&target), Presence::Current);
std::fs::create_dir_all(target.path.join("leftover")).unwrap();
assert_eq!(
inspect(&target),
Presence::Current,
"an empty leftover directory is not a change apply would report"
);
assert_eq!(apply(&target).unwrap().change, Change::Unchanged);
std::fs::write(target.path.join("from_0_9.md"), b"x").unwrap();
assert_eq!(inspect(&target), Presence::Stale);
assert_eq!(apply(&target).unwrap().change, Change::Updated);
}
#[test]
fn a_shared_file_without_our_markers_is_absent_not_stale() {
let t = temp();
std::fs::write(t.path().join("AGENTS.md"), "# Someone else's\n").unwrap();
let target = project_targets(t.path())
.into_iter()
.find(|t| t.form == Form::Region)
.unwrap();
assert_eq!(inspect(&target), Presence::Absent);
}
#[test]
fn a_foreign_directory_at_the_skill_path_is_absent_not_stale() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude/skills/teksilo")).unwrap();
std::fs::write(
t.path().join(".claude/skills/teksilo/unrelated.md"),
b"not ours",
)
.unwrap();
let target = project_targets(t.path()).remove(0);
assert_eq!(inspect(&target), Presence::Absent);
}
#[test]
fn inspect_writes_nothing() {
let t = temp();
std::fs::create_dir_all(t.path().join(".cursor")).unwrap();
let target = project_targets(t.path()).remove(0);
assert_eq!(inspect(&target), Presence::Absent);
assert!(
!t.path().join(".cursor/rules").exists(),
"inspecting must not create the directory apply would"
);
}
#[test]
fn the_user_scope_note_lists_every_not_applicable_agent() {
let note = user_scope_note();
for (agent, why) in USER_NOT_APPLICABLE {
assert!(note.contains(agent), "note omits {agent}");
assert!(note.contains(why), "note omits why for {agent}");
}
assert!(
note.contains(&format!("those {}", USER_NOT_APPLICABLE.len())),
"the count must be derived, not typed: {note}"
);
}
#[test]
fn installing_writes_the_whole_skill_tree() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
let targets = project_targets(t.path());
let done = apply(&targets[0]).unwrap();
let root = t.path().join(".claude/skills/teksilo");
assert!(root.join("SKILL.md").is_file());
assert!(root.join("reference/teksu.md").is_file());
assert!(root.join("reference/automation.md").is_file());
assert!(root.join("reference/teksilo_app_guide.md").is_file());
assert_eq!(done.change, Change::Created);
assert!(done.files >= 4);
}
#[test]
fn reinstalling_removes_a_file_the_previous_version_shipped() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
let targets = project_targets(t.path());
apply(&targets[0]).unwrap();
let stale = t.path().join(".claude/skills/teksilo/reference/gone.md");
std::fs::write(&stale, b"from an older release").unwrap();
let again = apply(&targets[0]).unwrap();
assert!(
!stale.exists(),
"a stale file must not shadow the new skill"
);
assert_eq!(again.change, Change::Updated);
}
#[test]
fn an_unchanged_reinstall_reports_unchanged() {
let t = temp();
std::fs::create_dir_all(t.path().join(".claude")).unwrap();
std::fs::create_dir_all(t.path().join(".cursor")).unwrap();
std::fs::write(t.path().join("AGENTS.md"), "# Agents\n").unwrap();
let targets = project_targets(t.path());
for target in &targets {
apply(target).unwrap();
}
for target in &targets {
assert_eq!(
apply(target).unwrap().change,
Change::Unchanged,
"{} re-wrote itself",
target.agent
);
}
}
#[test]
fn a_rules_file_carries_its_vendors_frontmatter_and_the_brief() {
let t = temp();
std::fs::create_dir_all(t.path().join(".cursor")).unwrap();
std::fs::create_dir_all(t.path().join(".windsurf")).unwrap();
for target in project_targets(t.path()) {
apply(&target).unwrap();
}
let cursor = std::fs::read_to_string(t.path().join(".cursor/rules/teksilo.mdc")).unwrap();
assert!(cursor.starts_with("---\ndescription:"));
assert!(cursor.contains("alwaysApply: false"));
assert!(cursor.contains("cargo teksilo show"));
let windsurf =
std::fs::read_to_string(t.path().join(".windsurf/rules/teksilo.md")).unwrap();
assert!(windsurf.starts_with("---\ntrigger: glob"));
assert!(windsurf.contains("cargo teksilo show"));
}
#[test]
fn the_brief_stands_alone() {
assert!(!BRIEF.contains("SKILL.md"));
assert!(!BRIEF.contains("reference/"));
assert!(BRIEF.contains("cargo teksilo symbol"));
assert!(BRIEF.contains("cargo teksilo search"));
assert!(BRIEF.contains("cargo teksilo show"));
assert!(BRIEF.contains("cargo teksilo probe"));
assert!(BRIEF.contains("cargo teksilo setup"));
assert!(
BRIEF.contains("blob/main"),
"the GitHub warning is the point"
);
}
#[test]
fn the_embedded_skill_is_the_real_one() {
let skill = SKILL
.get_file("SKILL.md")
.expect("SKILL.md must be embedded");
let text = std::str::from_utf8(skill.contents()).unwrap();
assert!(text.contains("name: teksilo"));
assert!(text.contains("user_invocable: true"));
assert!(text.len() > 5_000, "suspiciously small skill");
}
}