The PRD's first acceptance gate now holds: grep -RinE '\bx\.ai\b|grok' crates/ --include='*.rs' → 0 matches (exempt: NOTICE and third-party license archives, README provenance, and the required 'Based on Grok Build Open Source' attribution, now sourced from version_attribution.txt). Wire-visible renames (both sides in this repo, changed in lockstep): - Auth method id 'grok.com' → 'kimi-code' (AuthMethodKind::KimiCode). - Every x.ai/* and _x.ai/* ACP ext method and meta key → kigi/* / _kigi/* (~200 names; grokShell → kigiShell). Session-file replay keeps a read-side alias for the legacy '_x.ai/session/update' method so existing updates.jsonl histories load; writes emit only the new name (both directions test-pinned). - Agent types grok-build* → kigi* with a documented legacy-prefix alias at resolution time so persisted sessions keep resolving. - ToolNamespace/BuiltinAgentName GrokBuild* → Kigi* (wire snake_case kigi/kigi_concise/kigi_hashline; schema regenerated); grok_build implementation dirs renamed to kigi*. - x-grok-* headers → x-kigi-*, __GROK_* sentinels → __KIGI_*, themes grokday/groknight → kigiday/kiginight (old persisted values fall back to the default theme), web_fetch allowlist xAI hosts → kimi.com + moonshot platforms, changelog CDN → this repo, grok-build changelog archives deleted. - BYOK default endpoint removed: [endpoints] api_base_url is now truly optional with NO default — consumers fail fast with the flag name when unset (no silent x.ai egress). Mock harnesses inject it explicitly. - System-prompt identity fixed: 'released by xAI' → 'an unofficial community CLI for Kimi' (template + regenerated encrypted form). Also repaired pre-existing grok-era test debt found by the sweep: the stale trace_classify default-model pin, the grok-pager UA label test, pty-harness stale-binary reuse and non-hermetic moonshot routing (a PTY test could previously reach the real api.moonshot.cn), and the outdated oauth fixture scope key. Gates: §9 grep 0; fmt clean; workspace check/clippy 0/0 (-D warnings); FULL cargo test --workspace: 234 suites, 21,961 passed, 0 failed; deny advisories ok.
116 lines
4.6 KiB
Rust
116 lines
4.6 KiB
Rust
//! Project config-file discovery: locating repo-local `.mcp.json` and
|
|
//! `.kigi/config.toml` files by walking from `cwd` up to the git root.
|
|
//!
|
|
//! These pure `git2` + filesystem walks are shared by the shell's config
|
|
//! loaders and the folder-trust gate's `repo_configs_present`.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use kigi_agent::repo::RepoDirChain;
|
|
|
|
/// Filename of the project-local MCP server config.
|
|
pub const MCP_JSON_FILENAME: &str = ".mcp.json";
|
|
|
|
/// Candidate `.mcp.json` paths from repo root to `cwd`, whether or not they exist.
|
|
/// Useful for file watching so newly created files are detected after startup.
|
|
pub fn mcp_json_candidate_paths(cwd: &Path) -> Vec<PathBuf> {
|
|
mcp_json_candidate_paths_in(&RepoDirChain::resolve(cwd).dirs)
|
|
}
|
|
|
|
/// [`mcp_json_candidate_paths`] over a precomputed cwd→git-root dir chain
|
|
/// ([`RepoDirChain`]), repo-root-first. Private: only this file's two callers.
|
|
fn mcp_json_candidate_paths_in(chain_dirs: &[PathBuf]) -> Vec<PathBuf> {
|
|
chain_dirs
|
|
.iter()
|
|
.rev()
|
|
.map(|dir| dir.join(MCP_JSON_FILENAME))
|
|
.collect()
|
|
}
|
|
|
|
/// Find existing `.mcp.json` files from `cwd` up to the git root (repo-root-first order).
|
|
pub fn find_mcp_json_files(cwd: &Path) -> Vec<PathBuf> {
|
|
find_mcp_json_files_in(&RepoDirChain::resolve(cwd).dirs)
|
|
}
|
|
|
|
/// [`find_mcp_json_files`] over a precomputed dir chain. See [`RepoDirChain`].
|
|
/// `pub(crate)` — the gate (`repo_configs_present`) reaches it within this crate.
|
|
pub(crate) fn find_mcp_json_files_in(chain_dirs: &[PathBuf]) -> Vec<PathBuf> {
|
|
mcp_json_candidate_paths_in(chain_dirs)
|
|
.into_iter()
|
|
.filter(|path| path.is_file())
|
|
.collect()
|
|
}
|
|
|
|
/// True when `config_path` is `$KIGI_SHARE_DIR/config.toml` (user tier, not project).
|
|
fn is_user_kigi_config_file(config_path: &Path) -> bool {
|
|
let Some(user_home) = kigi_config::user_kigi_home() else {
|
|
return false;
|
|
};
|
|
let user_config = user_home.join("config.toml");
|
|
if config_path == user_config.as_path() {
|
|
return true;
|
|
}
|
|
let Ok(canonical_config) = dunce::canonicalize(config_path) else {
|
|
return false;
|
|
};
|
|
let canonical_user = dunce::canonicalize(&user_config).unwrap_or(user_config);
|
|
canonical_config == canonical_user
|
|
}
|
|
|
|
/// Find all `.kigi/config.toml` files from `cwd` upward to the git repo root.
|
|
/// Returns paths ordered from repo root (lowest priority) to cwd (highest priority),
|
|
/// matching the convention used by skills and AGENTS.md discovery.
|
|
///
|
|
/// If no git repo is found, only checks `cwd/.kigi/config.toml`. Excludes the
|
|
/// user-global config so `cwd == $HOME` does not treat `~/.kigi/config.toml` as
|
|
/// a project overlay.
|
|
pub fn find_project_configs(cwd: &Path) -> Vec<PathBuf> {
|
|
find_project_configs_in(&RepoDirChain::resolve(cwd).dirs)
|
|
}
|
|
|
|
/// [`find_project_configs`] over a precomputed cwd→git-root dir chain
|
|
/// ([`RepoDirChain`]), repo-root-first. Excludes the user-global config so
|
|
/// `cwd == $HOME` does not treat `~/.kigi/config.toml` as a project overlay.
|
|
/// `pub(crate)` — the gate (`repo_configs_present`) reaches it within this crate.
|
|
pub(crate) fn find_project_configs_in(chain_dirs: &[PathBuf]) -> Vec<PathBuf> {
|
|
// `dirs` is cwd-first; reverse so repo root comes first (lowest priority)
|
|
// and cwd last (highest), matching skills/AGENTS.md discovery order.
|
|
chain_dirs
|
|
.iter()
|
|
.rev()
|
|
.map(|dir| dir.join(".kigi").join("config.toml"))
|
|
.filter(|config_path| config_path.is_file() && !is_user_kigi_config_file(config_path))
|
|
.collect()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn find_project_configs_excludes_user_kigi_config_file() {
|
|
let Some(user_home) = kigi_config::user_kigi_home() else {
|
|
return;
|
|
};
|
|
let user_config = user_home.join("config.toml");
|
|
if user_config.is_file() {
|
|
#[allow(deprecated)]
|
|
let home = std::env::home_dir().expect("home dir");
|
|
let from_home = find_project_configs(&home);
|
|
assert!(
|
|
!from_home.iter().any(|p| is_user_kigi_config_file(p)),
|
|
"user config leaked into project configs: {from_home:?}"
|
|
);
|
|
assert!(is_user_kigi_config_file(&user_config));
|
|
}
|
|
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let project = tmp.path().join("repo");
|
|
std::fs::create_dir_all(project.join(".kigi")).unwrap();
|
|
std::fs::write(project.join(".kigi/config.toml"), "# project\n").unwrap();
|
|
let found = find_project_configs(&project);
|
|
assert_eq!(found.len(), 1);
|
|
assert!(!is_user_kigi_config_file(&found[0]));
|
|
}
|
|
}
|