§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.
This commit is contained in:
@@ -3,7 +3,7 @@ license = "Apache-2.0"
|
||||
name = "kigi-hooks"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Runtime hook system for Grok — file-based discovery, command execution, and policy enforcement"
|
||||
description = "Runtime hook system for Kigi — file-based discovery, command execution, and policy enforcement"
|
||||
|
||||
[dependencies]
|
||||
fastrand = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Hook Examples
|
||||
|
||||
Sample hooks for Grok. Copy to `~/.kigi/hooks/` to enable globally, or to `<project>/.kigi/hooks/` for project-scoped hooks (requires `/hooks-trust`).
|
||||
Sample hooks for Kigi. Copy to `~/.kigi/hooks/` to enable globally, or to `<project>/.kigi/hooks/` for project-scoped hooks (requires `/hooks-trust`).
|
||||
|
||||
## Available Examples
|
||||
|
||||
@@ -93,7 +93,7 @@ Hook files use the Claude-compatible JSON format:
|
||||
```
|
||||
|
||||
- **Event names:** `SessionStart`, `PreToolUse`, `PostToolUse`, `SessionEnd`
|
||||
- **Matcher:** regex on tool name. Claude names like `Bash`, `Read`, `Edit` are auto-expanded to also match Grok names (`run_terminal_cmd`, `read_file`, `search_replace`)
|
||||
- **Matcher:** regex on tool name. Claude names like `Bash`, `Read`, `Edit` are auto-expanded to also match Kigi names (`run_terminal_cmd`, `read_file`, `search_replace`)
|
||||
- **Timeout:** in seconds (default: 5)
|
||||
- **Command:** path to script (relative to hook file directory) or inline shell command
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ def main() -> None:
|
||||
command = extract_command(envelope)
|
||||
if command is None or not command_is_recursive(command):
|
||||
sys.exit(0) # nothing to block -> silent allow
|
||||
# Deny. Emit the grok-native decision (read by this repo's runner) and the
|
||||
# Deny. Emit the kigi-native decision (read by this repo's runner) and the
|
||||
# Claude-style hookSpecificOutput for forward-compatibility, put the reason
|
||||
# on stderr for runners that surface it there, and exit 2 so any exit-code
|
||||
# based runner blocks too.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# session-log.sh — append session events to an audit log
|
||||
#
|
||||
# Reads the hook envelope from stdin and appends a one-line JSON entry
|
||||
# to ~/.grok/session-audit.log with event name, session ID, cwd, and
|
||||
# to ~/.kigi/session-audit.log with event name, session ID, cwd, and
|
||||
# timestamp.
|
||||
|
||||
INPUT=$(cat)
|
||||
@@ -12,7 +12,7 @@ SESSION=$(echo "$INPUT" | grep -o '"sessionId":"[^"]*"' | sed 's/"sessionId":"//
|
||||
CWD=$(echo "$INPUT" | grep -o '"cwd":"[^"]*"' | sed 's/"cwd":"//;s/"$//')
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
LOG_FILE="${HOME}/.grok/session-audit.log"
|
||||
LOG_FILE="${HOME}/.kigi/session-audit.log"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
echo "{\"timestamp\":\"${TIMESTAMP}\",\"event\":\"${EVENT}\",\"session\":\"${SESSION}\",\"cwd\":\"${CWD}\"}" >> "$LOG_FILE"
|
||||
@@ -2,7 +2,7 @@
|
||||
# tool-logger.sh — log tool calls to a local activity file
|
||||
#
|
||||
# Reads the hook envelope from stdin and appends a one-line JSON entry
|
||||
# to ~/.grok/tool-activity.log with event name, tool name, and timestamp.
|
||||
# to ~/.kigi/tool-activity.log with event name, tool name, and timestamp.
|
||||
# `toolName` is the resolved tool (e.g. `linear__save_issue` for MCP calls).
|
||||
|
||||
INPUT=$(cat)
|
||||
@@ -12,7 +12,7 @@ TOOL=$(echo "$INPUT" | grep -o '"toolName":"[^"]*"' | head -1 | sed 's/"toolName
|
||||
BACKGROUNDED=$(echo "$INPUT" | grep -o '"isBackgrounded":[a-z]*' | sed 's/"isBackgrounded"://')
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
LOG_FILE="${HOME}/.grok/tool-activity.log"
|
||||
LOG_FILE="${HOME}/.kigi/tool-activity.log"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
echo "{\"timestamp\":\"${TIMESTAMP}\",\"event\":\"${EVENT}\",\"tool\":\"${TOOL}\",\"backgrounded\":${BACKGROUNDED:-false}}" >> "$LOG_FILE"
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::matcher::HookMatcher;
|
||||
#[derive(Debug)]
|
||||
pub struct HooksMap {
|
||||
pub events: HashMap<HookEventName, Vec<MatcherGroup>>,
|
||||
/// Event names present in the JSON but not recognized by Grok.
|
||||
/// Event names present in the JSON but not recognized by Kigi.
|
||||
pub skipped_events: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -650,7 +650,7 @@ mod tests {
|
||||
fn load_from_settings_file_no_hooks_key() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let settings = dir.path().join("settings.json");
|
||||
std::fs::write(&settings, r#"{"theme": "dark", "model": "grok-3"}"#).unwrap();
|
||||
std::fs::write(&settings, r#"{"theme": "dark", "model": "kigi-3"}"#).unwrap();
|
||||
|
||||
let (registry, errors) =
|
||||
load_hooks_from_sources(&[HookSource::SettingsFile(&settings)], &[]);
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct PreToolUseResult {
|
||||
/// Hook failures (timeouts, crashes, command-not-found, env-var
|
||||
/// pre-spawn refusals, malformed output) are **fail-open**: the failure
|
||||
/// is logged and surfaced in the per-hook results for the UI scrollback,
|
||||
/// but the tool call continues as if the hook had allowed it. Grok
|
||||
/// but the tool call continues as if the hook had allowed it. Kigi
|
||||
/// runs in protected environments where induced-failure bypass of
|
||||
/// security hooks is not part of the threat model; the previous
|
||||
/// fail-closed posture over-blocked innocent tool calls when
|
||||
|
||||
@@ -63,7 +63,7 @@ use std::collections::HashMap;
|
||||
/// "Apple logo" PUA char) plus a long magic ASCII prefix. The full
|
||||
/// sentinel string adds 128 bits of per-call entropy as a hex suffix
|
||||
/// followed by another `U+F8FF` char.
|
||||
const SENTINEL_PREFIX: &str = "\u{f8ff}__GROK_HOOKS_MASK_";
|
||||
const SENTINEL_PREFIX: &str = "\u{f8ff}__KIGI_HOOKS_MASK_";
|
||||
const SENTINEL_SUFFIX: &str = "__\u{f8ff}";
|
||||
|
||||
/// Build a per-call sentinel string used to hide modifier-form
|
||||
@@ -704,7 +704,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// An earlier sentinel was a fixed string
|
||||
/// `"\u{f8ff}__GROK_HOOKS_MASK__\u{f8ff}"`. A user-supplied
|
||||
/// `"\u{f8ff}__KIGI_HOOKS_MASK__\u{f8ff}"`. A user-supplied
|
||||
/// `extra_env` value containing that exact byte sequence would
|
||||
/// have been silently rewritten to `${` by the unmask step. The
|
||||
/// per-call randomized sentinel removes this hazard. This
|
||||
@@ -713,7 +713,7 @@ mod tests {
|
||||
/// though the input also references that variable through `${VAL}`.
|
||||
#[test]
|
||||
fn expand_preserves_pre_existing_legacy_fixed_sentinel_in_extra() {
|
||||
let legacy_sentinel = "\u{f8ff}__GROK_HOOKS_MASK__\u{f8ff}";
|
||||
let legacy_sentinel = "\u{f8ff}__KIGI_HOOKS_MASK__\u{f8ff}";
|
||||
let mut extra = HashMap::new();
|
||||
// Value embeds the legacy sentinel followed by what would
|
||||
// have been parsed as an identifier+brace if the unmask
|
||||
|
||||
@@ -529,7 +529,7 @@ mod tests {
|
||||
prompt_id: None,
|
||||
payload: HookPayload::SessionStart {
|
||||
source: "new".into(),
|
||||
model_id: Some("grok-3".into()),
|
||||
model_id: Some("kigi-3".into()),
|
||||
agent_type: None,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//! # kigi-hooks
|
||||
//!
|
||||
//! Runtime hook system for Grok — file-based discovery, command execution,
|
||||
//! Runtime hook system for Kigi — file-based discovery, command execution,
|
||||
//! and policy enforcement.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! This crate provides a minimal hooks system for Grok. Hooks are discovered
|
||||
//! This crate provides a minimal hooks system for Kigi. Hooks are discovered
|
||||
//! from dedicated directories (`~/.kigi/hooks/` and `<git-worktree-root>/.kigi/hooks/`),
|
||||
//! defined in JSON files (compatible settings format), and executed as child processes.
|
||||
//!
|
||||
|
||||
@@ -6,9 +6,9 @@ use regex::Regex;
|
||||
///
|
||||
/// - an empty pattern or `"*"` matches every tool;
|
||||
/// - a "simple" pattern (only `[A-Za-z0-9_|]`, i.e. a plain name or `|`-list) is an
|
||||
/// **exact** match against each name (after external→Grok alias expansion), NOT a regex;
|
||||
/// **exact** match against each name (after external→Kigi alias expansion), NOT a regex;
|
||||
/// - anything else is an **unanchored** regex (also tested against the tool's external
|
||||
/// alias names, so e.g. `^Bash$` matches the Grok tool `run_terminal_command`).
|
||||
/// alias names, so e.g. `^Bash$` matches the Kigi tool `run_terminal_command`).
|
||||
///
|
||||
/// The simple-vs-regex split is deliberate: it avoids anchoring a `|`-alternation (a
|
||||
/// naive `^a|b|c$` anchors only the first/last term and silently over-matches). Whitespace
|
||||
@@ -61,8 +61,8 @@ fn is_simple_form(pattern: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Expand a simple-form pattern into the exact set of names it matches: each `|`-term
|
||||
/// plus any Grok tool names that term aliases (so `"Bash"` also matches
|
||||
/// `run_terminal_command`), per the shared external-name to Grok registry in
|
||||
/// plus any Kigi tool names that term aliases (so `"Bash"` also matches
|
||||
/// `run_terminal_command`), per the shared external-name to Kigi registry in
|
||||
/// `kigi-tools`. Empty terms and duplicates are dropped.
|
||||
fn exact_names(pattern: &str) -> Vec<String> {
|
||||
let mut names: Vec<String> = Vec::new();
|
||||
@@ -155,22 +155,22 @@ mod tests {
|
||||
// ── External tool-name aliases ────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn claude_bash_matches_grok_tool() {
|
||||
fn claude_bash_matches_kigi_tool() {
|
||||
let m = HookMatcher::new("Bash").unwrap();
|
||||
assert!(m.is_match("Bash")); // external alias name
|
||||
assert!(m.is_match("run_terminal_command")); // Grok name
|
||||
assert!(m.is_match("run_terminal_command")); // Kigi name
|
||||
assert!(!m.is_match("read_file"));
|
||||
// Bug-fix regression: exact, not prefix.
|
||||
assert!(!m.is_match("run_terminal_command_v2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_edit_write_matches_grok_tool_exactly() {
|
||||
fn claude_edit_write_matches_kigi_tool_exactly() {
|
||||
let m = HookMatcher::new("Edit|Write").unwrap();
|
||||
assert!(m.is_match("Edit"));
|
||||
assert!(m.is_match("Write"));
|
||||
assert!(m.is_match("search_replace")); // Grok equivalent
|
||||
assert!(m.is_match("hashline_edit")); // second Grok alias
|
||||
assert!(m.is_match("search_replace")); // Kigi equivalent
|
||||
assert!(m.is_match("hashline_edit")); // second Kigi alias
|
||||
assert!(!m.is_match("read_file"));
|
||||
// The old anchoring bug matched these; the exact-list mode must not.
|
||||
assert!(!m.is_match("Editorial"));
|
||||
@@ -178,7 +178,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_read_matches_grok_tool() {
|
||||
fn claude_read_matches_kigi_tool() {
|
||||
let m = HookMatcher::new("Read").unwrap();
|
||||
assert!(m.is_match("Read"));
|
||||
assert!(m.is_match("read_file"));
|
||||
@@ -186,8 +186,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_against_claude_alias_matches_grok_tool() {
|
||||
// A regex written against an external alias still matches the Grok tool
|
||||
fn regex_against_claude_alias_matches_kigi_tool() {
|
||||
// A regex written against an external alias still matches the Kigi tool
|
||||
// (legacy alias-name expansion).
|
||||
let m = HookMatcher::new("^Bash$").unwrap();
|
||||
assert!(m.is_match("run_terminal_command"));
|
||||
|
||||
@@ -87,7 +87,7 @@ pub async fn run_command_hook(
|
||||
let mut cmd = if is_shell_command {
|
||||
// Refuse to spawn when the command interpolates an env var that
|
||||
// we can't resolve from any of: the runner's always-set vars, the
|
||||
// per-hook extra_env (plugin vars), or Grok's own process env. The
|
||||
// per-hook extra_env (plugin vars), or Kigi's own process env. The
|
||||
// alternative is letting sh expand the var to empty -- which then
|
||||
// produces a broken command, exits 127, and (for PreToolUse hooks)
|
||||
// fails closed with an opaque "exit code 127" reason. Catching it
|
||||
@@ -287,7 +287,7 @@ pub(crate) const RUNNER_ALWAYS_SET_ENV: &[&str] = &[
|
||||
/// * the runner's always-set env vars (see [`RUNNER_ALWAYS_SET_ENV`]),
|
||||
/// * the per-hook `extra_env` map (set by the plugin adapter for plugin
|
||||
/// hooks),
|
||||
/// * the Grok process's own environment (which is inherited by the child),
|
||||
/// * the Kigi process's own environment (which is inherited by the child),
|
||||
/// * local shell assignments inside the command itself (e.g. an
|
||||
/// `INPUT=$(cat)` earlier in the string defines `INPUT` for the rest of
|
||||
/// the command).
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
|
||||
// exist only to migrate prior grants out of the legacy file.
|
||||
|
||||
/// Path to the legacy project-hook trust file
|
||||
/// (`<user_kigi_home>/trusted-hook-projects`), or `None` when no user grok home
|
||||
/// (`<user_kigi_home>/trusted-hook-projects`), or `None` when no user kigi home
|
||||
/// resolves. Retained only for the one-time migration into folder-trust.
|
||||
pub fn legacy_trust_file_path() -> Option<PathBuf> {
|
||||
Some(kigi_config::user_kigi_home()?.join("trusted-hook-projects"))
|
||||
@@ -59,7 +59,7 @@ fn is_hook_disabled_with_file(hook_name: &str, file: &Path) -> bool {
|
||||
/// Disable a hook by name. Adds to .
|
||||
pub fn disable_hook(hook_name: &str) -> Result<(), String> {
|
||||
let file = disabled_hooks_file_path()
|
||||
.ok_or_else(|| "no user grok home (set $KIGI_SHARE_DIR or $HOME)".to_string())?;
|
||||
.ok_or_else(|| "no user kigi home (set $KIGI_SHARE_DIR or $HOME)".to_string())?;
|
||||
disable_hook_with_file(hook_name, &file)
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ fn enable_hook_with_file(hook_name: &str, file: &Path) -> Result<bool, String> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Returns the path to `$KIGI_SHARE_DIR/disabled-hooks`, or `None` when no user grok
|
||||
/// Returns the path to `$KIGI_SHARE_DIR/disabled-hooks`, or `None` when no user kigi
|
||||
/// home resolves.
|
||||
fn disabled_hooks_file_path() -> Option<PathBuf> {
|
||||
Some(kigi_config::user_kigi_home()?.join("disabled-hooks"))
|
||||
@@ -134,9 +134,9 @@ mod tests {
|
||||
|
||||
/// Each test creates its own legacy file in its own temp dir -- no shared state.
|
||||
fn trust_file_in(dir: &Path) -> PathBuf {
|
||||
let grok_dir = dir.join(".kigi");
|
||||
std::fs::create_dir_all(&grok_dir).unwrap();
|
||||
grok_dir.join("trusted-hook-projects")
|
||||
let kigi_dir = dir.join(".kigi");
|
||||
std::fs::create_dir_all(&kigi_dir).unwrap();
|
||||
kigi_dir.join("trusted-hook-projects")
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user