Files
Kigi-CLI/crates/codegen/kigi-shell/src/util/hooks.rs
T
ZacharyZhang-NY 6f31415ed6 §9 acceptance: grep-zero sweep — every internal x.ai/grok identifier renamed
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.
2026-07-18 02:48:46 -04:00

118 lines
4.1 KiB
Rust

//! Shared hook source path discovery.
use std::path::{Path, PathBuf};
use kigi_hooks::discovery::HookSource;
/// Owned paths for hook sources. Callers borrow via `as_sources()`.
pub struct HookSourcePaths {
pub global: Vec<PathBuf>,
pub project: Vec<PathBuf>,
}
impl HookSourcePaths {
/// Borrow as `HookSource` refs. Project sources are excluded when untrusted.
pub fn as_sources(&self, include_project: bool) -> (Vec<HookSource<'_>>, Vec<HookSource<'_>>) {
let global = self.global.iter().map(|p| path_to_source(p)).collect();
let project = if include_project {
self.project.iter().map(|p| path_to_source(p)).collect()
} else {
vec![]
};
(global, project)
}
}
fn path_to_source(p: &Path) -> HookSource<'_> {
if p.is_dir() {
HookSource::Directory(p)
} else {
HookSource::SettingsFile(p)
}
}
/// Build hook source paths for global (`~/`) and project (`<git_root>/`) scopes.
/// Callers gate project sources on trust via `as_sources(trusted)`.
pub fn discover_hook_source_paths(
git_root: Option<&Path>,
compat: &kigi_tools::types::compat::CompatConfig,
) -> HookSourcePaths {
// Compat gate: skip .claude hook sources when disabled.
let skip_claude_compat = !compat.claude.hooks;
// Phase 2 cutoff: if the user has imported, skip .claude/settings.json
// sources. Native .kigi/hooks/ directories are still scanned (they hold
// any hooks that were imported by /import-claude).
let skip_claude = skip_claude_compat
|| crate::claude_import::is_claude_import_marked_with_log("discover_hook_source_paths");
// Compat gate: skip Cursor hook sources when disabled.
let skip_cursor = !compat.cursor.hooks;
let home = dirs::home_dir();
// user_kigi_home() is None when no home resolves, so inspect lists the same
// sources a live session loads, instead of a cwd-relative .kigi.
let kigi = kigi_config::user_kigi_home();
let mut global = Vec::new();
if !skip_claude && let Some(ref h) = home {
global.push(h.join(".claude").join("settings.json"));
global.push(h.join(".claude").join("settings.local.json"));
}
if let Some(ref kigi) = kigi {
global.push(kigi.join("hooks"));
}
let custom_paths: Vec<PathBuf> = kigi
.as_ref()
.and_then(|g| std::fs::read_to_string(g.join("hooks-paths")).ok())
.map(|content| {
content
.lines()
.filter(|l| !l.trim().is_empty())
.map(|l| PathBuf::from(l.trim()))
.collect()
})
.unwrap_or_default();
global.extend(custom_paths);
if let Some(ref h) = home
&& !skip_cursor
{
global.push(h.join(".cursor").join("hooks.json"));
}
let mut project = Vec::new();
if let Some(root) = git_root {
if !skip_claude {
project.push(root.join(".claude").join("settings.json"));
project.push(root.join(".claude").join("settings.local.json"));
}
project.push(root.join(".kigi").join("hooks"));
if !skip_cursor {
project.push(root.join(".cursor").join("hooks.json"));
}
}
HookSourcePaths { global, project }
}
/// Single load entry point: build compat-aware sources, gate project sources on
/// trust, then load. Every session-startup and mid-session reload site routes
/// through here so the source policy stays in one place. `discover_hook_source_paths`
/// and `HookSourcePaths::as_sources` stay public for the `inspect` path (which
/// enumerates sources with all vendors on) and the unit tests that assert on the
/// raw source lists.
pub fn discover_hooks(
git_root: Option<&Path>,
compat: &kigi_tools::types::compat::CompatConfig,
trusted: bool,
) -> (
kigi_hooks::discovery::HookRegistry,
Vec<kigi_hooks::error::HookError>,
) {
let source_paths = discover_hook_source_paths(git_root, compat);
let (global_sources, project_sources) = source_paths.as_sources(trusted);
kigi_hooks::discovery::load_hooks_from_sources(&global_sources, &project_sources)
}