§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:
2026-07-18 02:48:46 -04:00
parent 86e3724310
commit 6f31415ed6
1056 changed files with 8410 additions and 18307 deletions
@@ -643,7 +643,7 @@ mod tests {
// background colour on the label span so a regression that drops
// the chip-vs-modal colour alignment gets caught.
//
// Use the unquantized `groknight()` theme directly so warning and
// Use the unquantized `kiginight()` theme directly so warning and
// bg_base remain distinguishable in the test env — `Theme::current()`
// collapses both to ANSI `Reset` on 16-colour terminals, which
// would defeat the assertion.
@@ -654,7 +654,7 @@ mod tests {
2,
0,
);
let t = Theme::groknight();
let t = Theme::kiginight();
let line = goal_status_line(&g, &t, false, 0, None, 0);
// The label span is the one whose content starts with "Goal:".
let label_span = line
@@ -677,7 +677,7 @@ mod tests {
2,
0,
);
let t = Theme::groknight();
let t = Theme::kiginight();
let line = goal_status_line(&g, &t, false, 0, None, 0);
let label_span = line
.spans
@@ -811,7 +811,7 @@ mod tests {
#[test]
fn mcp_status_line_uses_dim_directory_color() {
// The chip must render in `theme.gray_dim` to match the directory path.
let t = Theme::groknight();
let t = Theme::kiginight();
let progress = McpInitProgress {
total: 2,
connected: 0,
@@ -197,7 +197,7 @@ pub struct AgentsModalState {
/// Snapshot of bundle catalog used to merge persona lists.
bundle: BundleState,
/// Resolved startup agent name (same chain as shell: `[agent]`, `KIGI_AGENT`,
/// model `agentType`, then `grok-build`).
/// model `agentType`, then `kigi`).
pub default_agent: String,
/// Agent running in the current session (`session/info` `agentName`).
pub active_agent: Option<String>,
@@ -211,12 +211,12 @@ pub struct AgentsModalState {
pub persona_expanded: std::collections::HashSet<usize>,
}
/// Built-in agent names that should be shown to the user.
/// Skips internal variants (GrokBuildConcise, GrokBuildPlan,
/// GrokBuildPlanNoSubagents, GrokBuildAskUser, Codex, Opencode,
/// CursorExtended, GrokBuildOrchestrator).
/// Skips internal variants (KigiConcise, KigiPlan,
/// KigiPlanNoSubagents, KigiAskUser, Codex, Opencode,
/// CursorExtended, KigiOrchestrator).
fn user_visible_builtins() -> &'static [BuiltinAgentName] {
&[
BuiltinAgentName::GrokBuild,
BuiltinAgentName::Kigi,
BuiltinAgentName::GeneralPurpose,
BuiltinAgentName::Explore,
BuiltinAgentName::Plan,
@@ -1476,11 +1476,11 @@ mod tests {
DashboardRowId::TopLevel(AgentId(0)),
fields("Response", &["hello"]),
);
panel.model_name = Some("Grok 4 Fast".to_string());
panel.model_name = Some("Kigi 4 Fast".to_string());
panel.auto_approve = true;
let bottom = badge_row(&panel, 6);
assert!(
bottom.contains("Grok 4 Fast"),
bottom.contains("Kigi 4 Fast"),
"model on bottom border: {bottom:?}"
);
assert!(
@@ -1497,10 +1497,10 @@ mod tests {
];
q.request_id = Some(1);
let mut qpanel = PeekPanelState::new(DashboardRowId::TopLevel(AgentId(0)), q);
qpanel.model_name = Some("Grok 4 Fast".to_string());
qpanel.model_name = Some("Kigi 4 Fast".to_string());
let qbottom = badge_row(&qpanel, 8);
assert!(
qbottom.contains("Grok 4 Fast"),
qbottom.contains("Kigi 4 Fast"),
"model shows in question/approval mode: {qbottom:?}",
);
@@ -1509,7 +1509,7 @@ mod tests {
DashboardRowId::TopLevel(AgentId(0)),
fields("Response", &["hi"]),
);
plain.model_name = Some("Grok 4 Fast".to_string());
plain.model_name = Some("Kigi 4 Fast".to_string());
plain.auto_approve = false;
let plain_bottom = badge_row(&plain, 6);
assert!(
@@ -1523,7 +1523,7 @@ mod tests {
DashboardRowId::TopLevel(AgentId(0)),
fields("Response", &["hi"]),
);
planp.model_name = Some("Grok 4 Fast".to_string());
planp.model_name = Some("Kigi 4 Fast".to_string());
planp.plan_mode = true;
let plan_bottom = badge_row(&planp, 6);
assert!(
@@ -2494,7 +2494,7 @@ fn render_empty_state(buf: &mut Buffer, area: Rect, theme: &Theme, loading: bool
/// chrome reflects what Enter will do: dispatch a new session vs.
/// enqueue / send a prompt to the currently-selected agent.
/// Paint a short right-aligned feedback badge onto the dispatch box's
/// **top border** (e.g. `✗ Session no longer exists`, `✓ Theme: Grok
/// **top border** (e.g. `✗ Session no longer exists`, `✓ Theme: Kigi
/// Day`), in a neutral accent colour. The message is painted VERBATIM:
/// it already carries its own status glyph — errors are built via
/// [`DashboardState::set_error_toast`] (`✗`), while successes / info
@@ -4661,7 +4661,7 @@ mod tests {
let mut state = DashboardState::new();
// A distinct absolute path outside $HOME (rendered verbatim) that
// differs from the process cwd. No git cache entry → no branch span.
state.cwd = std::path::PathBuf::from("/grok-staged-cwd-marker");
state.cwd = std::path::PathBuf::from("/kigi-staged-cwd-marker");
let mut buf = Buffer::empty(area);
render_header(&mut buf, area, &theme, &rows, &mut state);
@@ -4670,7 +4670,7 @@ mod tests {
.map(|x| buf[(x, 0)].symbol().to_string())
.collect();
assert!(
top_row.contains("/grok-staged-cwd-marker"),
top_row.contains("/kigi-staged-cwd-marker"),
"header must render the staged cwd, not the process cwd; got: {top_row:?}",
);
}
@@ -5629,12 +5629,12 @@ mod tests {
let theme = Theme::current();
let mut state = DashboardState::new();
let check = crate::glyphs::check_mark();
state.error_toast = Some(format!("{check} Theme: Grok Day"));
state.error_toast = Some(format!("{check} Theme: Kigi Day"));
let _ = render_dispatch(&mut buf, Rect::new(0, 0, 80, 3), &theme, &mut state, None);
let top_row: String = (0..80).map(|x| buf[(x, 0)].symbol().to_string()).collect();
assert!(
top_row.contains(&format!("{check} Theme: Grok Day")),
top_row.contains(&format!("{check} Theme: Kigi Day")),
"badge must paint the message verbatim, got: {top_row:?}",
);
assert!(
@@ -7051,7 +7051,7 @@ mod tests {
let area = Rect::new(0, 0, 80, 24);
// A temp dir with a `.git` child so the toggle is eligible (hermetic,
// unlike depending on the test's real cwd being a repo).
let repo = std::env::temp_dir().join("grok-loc-wt-toggle-repo-test");
let repo = std::env::temp_dir().join("kigi-loc-wt-toggle-repo-test");
std::fs::create_dir_all(repo.join(".git")).expect("mk .git");
let mut modal =
LocationPickerState::new(vec![], repo.clone(), std::collections::HashMap::new());
@@ -7111,7 +7111,7 @@ mod tests {
let area = Rect::new(0, 0, 80, 24);
let mut modal = LocationPickerState::new(
vec![],
std::path::PathBuf::from("/grok-not-a-repo-xyz-12345"),
std::path::PathBuf::from("/kigi-not-a-repo-xyz-12345"),
std::collections::HashMap::new(),
);
let mut buf = Buffer::empty(area);
@@ -1665,12 +1665,12 @@ mod tests {
fn append_roster_rows_uses_model_id_as_secondary_line() {
let empty = std::collections::BTreeSet::new();
let entry = RosterEntry {
model_id: Some("grok-4.5".to_string()),
model_id: Some("kigi-4.5".to_string()),
..roster_entry_with("m", Some("Fix the bug"), RosterActivity::Dormant)
};
let rows = collect_roster(&[entry], &empty);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].secondary_line.as_deref(), Some("grok-4.5"));
assert_eq!(rows[0].secondary_line.as_deref(), Some("kigi-4.5"));
}
/// Without a model id there's genuinely nothing to show, so the
/// second line stays empty rather than rendering a placeholder.
@@ -1742,7 +1742,7 @@ mod tests {
/// the list uncluttered).
#[test]
fn idle_local_agent_without_message_has_blank_secondary() {
let agent = make_idle_agent_with_model(Some("grok-4.5"));
let agent = make_idle_agent_with_model(Some("kigi-4.5"));
assert_eq!(classify_top_level(&agent), RowState::Idle);
let row = top_level_row(AgentId(0), &agent, false, false, None);
assert_eq!(
@@ -4445,7 +4445,7 @@ pub fn load_persisted_from_path(path: &std::path::Path) -> Option<PersistedDashb
/// + rename, so concurrent readers never observe a half-truncated file.
pub fn write_persisted(p: &PersistedDashboard) -> std::io::Result<()> {
let path = config_path()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "no grok home"))?;
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "no kigi home"))?;
write_persisted_to_path(&path, p)
}
@@ -4788,8 +4788,8 @@ mod tests {
#[test]
fn compact_cwd_strips_home() {
let p = Path::new("/Users/alice/projects/grok");
assert_eq!(compact_cwd(p, Some("/Users/alice")), "~/projects/grok");
let p = Path::new("/Users/alice/projects/kigi");
assert_eq!(compact_cwd(p, Some("/Users/alice")), "~/projects/kigi");
}
#[test]
@@ -170,11 +170,11 @@ pub fn plugin_group(plugin: &kigi_hooks_plugins_types::PluginInfo) -> PluginGrou
use kigi_hooks_plugins_types::{PluginOrigin, PluginScope};
match &plugin.origin {
Some(PluginOrigin::ProjectGrok) => PluginGroup::new(0, "origin:project", "Project"),
Some(PluginOrigin::ProjectKigi) => PluginGroup::new(0, "origin:project", "Project"),
Some(PluginOrigin::ProjectClaude) => {
PluginGroup::new(1, "origin:project-claude", "Project (Claude)")
}
Some(PluginOrigin::UserGrok) => PluginGroup::new(2, "origin:user", "User"),
Some(PluginOrigin::UserKigi) => PluginGroup::new(2, "origin:user", "User"),
Some(PluginOrigin::UserClaude)
| Some(PluginOrigin::ClaudeInstalled { marketplace: None }) => {
PluginGroup::new(3, "origin:user-claude", "User (Claude)")
@@ -192,7 +192,7 @@ pub fn plugin_group(plugin: &kigi_hooks_plugins_types::PluginInfo) -> PluginGrou
..
}) => PluginGroup {
rank: 5,
key: format!("grok-mp:{source}"),
key: format!("kigi-mp:{source}"),
label: source.clone(),
},
Some(PluginOrigin::MarketplaceInstall {
@@ -208,7 +208,7 @@ pub fn plugin_group(plugin: &kigi_hooks_plugins_types::PluginInfo) -> PluginGrou
}
Some(source) => PluginGroup {
rank: 5,
key: format!("grok-mp:{source}"),
key: format!("kigi-mp:{source}"),
label: source.to_string(),
},
None => PluginGroup::new(2, "origin:user", "User"),
@@ -2021,9 +2021,9 @@ pub(crate) fn mcp_section_children_hidden(
/// Returns `(label, is_custom)` where `is_custom` means the source was added
/// via hooks-paths and can be removed.
pub fn derive_source_label(source_dir: &str) -> (String, bool) {
let grok = kigi_config::kigi_home();
let kigi = kigi_config::kigi_home();
let source_path = std::path::Path::new(source_dir);
// Plugin / installed-plugin dirs, under the user grok home (KIGI_SHARE_DIR-aware)
// Plugin / installed-plugin dirs, under the user kigi home (KIGI_SHARE_DIR-aware)
// or a project-scoped `{cwd}/.kigi/<subdir>/`. Returns the first path
// component after the subdir (the plugin's install directory name).
let plugin_name = |subdir: &str| -> Option<String> {
@@ -2033,8 +2033,8 @@ pub fn derive_source_label(source_dir: &str) -> (String, bool) {
.map(|c| c.as_os_str().to_string_lossy().into_owned())
.filter(|s| !s.is_empty())
};
// User grok home (KIGI_SHARE_DIR-aware).
if let Ok(rest) = source_path.strip_prefix(grok.join(subdir))
// User kigi home (KIGI_SHARE_DIR-aware).
if let Ok(rest) = source_path.strip_prefix(kigi.join(subdir))
&& let Some(name) = first_comp(rest)
{
return Some(name);
@@ -2054,7 +2054,7 @@ pub fn derive_source_label(source_dir: &str) -> (String, bool) {
return (format!("Plugin: {name}"), false);
}
// Global hooks under $KIGI_SHARE_DIR/hooks
let global_hooks = grok.join("hooks");
let global_hooks = kigi.join("hooks");
let global_str = global_hooks.display().to_string();
if source_dir == global_str || source_dir.starts_with(&format!("{global_str}/")) {
return ("Global hooks".into(), false);
@@ -2069,7 +2069,7 @@ pub fn derive_source_label(source_dir: &str) -> (String, bool) {
}
// Custom directory — removable
let display = {
if let Ok(rest) = source_path.strip_prefix(&grok) {
if let Ok(rest) = source_path.strip_prefix(&kigi) {
let prefix = crate::util::display_kigi_home_prefix();
let rest_str = rest.to_string_lossy();
let rest_trimmed = rest_str.strip_prefix('/').unwrap_or(&rest_str);
@@ -2138,7 +2138,7 @@ fn skill_source_str(skill: &SkillInfo) -> String {
match cs {
kigi_tools::types::config_source::ConfigSource::User { path } => {
if crate::util::is_under_user_kigi_home(path) {
crate::util::display_user_grok_path("skills")
crate::util::display_user_kigi_path("skills")
} else if path.display().to_string().contains("/.claude/") {
"~/.claude/skills".into()
} else {
@@ -3461,7 +3461,7 @@ mod tests {
fn derive_source_label_detects_project_scoped_plugins() {
// Regression: project-scoped `{cwd}/.kigi/plugins/<name>/` must label as
// a (non-removable) plugin, not a removable "Custom" source. The user
// grok-home branch is KIGI_SHARE_DIR-aware; this covers the project fallback.
// kigi-home branch is KIGI_SHARE_DIR-aware; this covers the project fallback.
let (label, is_custom) = derive_source_label("/repo/work/.kigi/plugins/my-plugin/hooks");
assert_eq!(label, "Plugin: my-plugin");
assert!(!is_custom);
@@ -4844,14 +4844,14 @@ mod tests {
fn plugin_group_maps_each_origin_variant() {
use kigi_hooks_plugins_types::PluginOrigin;
for (origin, rank, key, label) in [
(PluginOrigin::ProjectGrok, 0, "origin:project", "Project"),
(PluginOrigin::ProjectKigi, 0, "origin:project", "Project"),
(
PluginOrigin::ProjectClaude,
1,
"origin:project-claude",
"Project (Claude)",
),
(PluginOrigin::UserGrok, 2, "origin:user", "User"),
(PluginOrigin::UserKigi, 2, "origin:user", "User"),
(
PluginOrigin::UserClaude,
3,
@@ -4878,7 +4878,7 @@ mod tests {
git_url: Some("https://example.com/r.git".into()),
},
5,
"grok-mp:xAI Official",
"kigi-mp:xAI Official",
"xAI Official",
),
(
@@ -4938,7 +4938,7 @@ mod tests {
let mut mp = make_plugin("mp-tool");
mp.marketplace_source = Some("xAI Official".into());
let group = plugin_group(&mp);
assert_eq!(group.key, "grok-mp:xAI Official");
assert_eq!(group.key, "kigi-mp:xAI Official");
assert_eq!(group.label, "xAI Official");
let mut direct = make_plugin("direct-tool");
@@ -4955,7 +4955,7 @@ mod tests {
assert_eq!(plugin_group(&unknown).key, "origin:user");
unknown.marketplace_source = Some("xAI Official".into());
assert_eq!(plugin_group(&unknown).key, "grok-mp:xAI Official");
assert_eq!(plugin_group(&unknown).key, "kigi-mp:xAI Official");
}
#[test]
@@ -4968,7 +4968,7 @@ mod tests {
marketplace: "claude-market".into(),
},
),
make_plugin_with_origin("user-tool", PluginOrigin::UserGrok),
make_plugin_with_origin("user-tool", PluginOrigin::UserKigi),
make_plugin_with_origin("claude-tool", PluginOrigin::UserClaude),
]);
let buf = render_plugins_into_buffer(&mut state, 100, 40);
@@ -5001,7 +5001,7 @@ mod tests {
fn plugins_render_multiple_plugins_under_one_group() {
use kigi_hooks_plugins_types::PluginOrigin;
let mut state = plugins_modal_state(vec![
make_plugin_with_origin("solo-tool", PluginOrigin::UserGrok),
make_plugin_with_origin("solo-tool", PluginOrigin::UserKigi),
make_plugin_with_origin(
"catalog-tool",
PluginOrigin::ClaudeMarketplace {
@@ -5057,7 +5057,7 @@ mod tests {
#[test]
fn plugins_collapsed_group_hides_rows_and_search_forces_open() {
use kigi_hooks_plugins_types::PluginOrigin;
let mut plugin = make_plugin_with_origin("user-tool", PluginOrigin::UserGrok);
let mut plugin = make_plugin_with_origin("user-tool", PluginOrigin::UserKigi);
plugin.root = "/opt/p1".into();
let mut state = plugins_modal_state(vec![plugin]);
state.plugins_collapsed_groups.insert("origin:user".into());
@@ -5097,7 +5097,7 @@ mod tests {
let mut disabled = make_plugin_with_origin("off-tool", PluginOrigin::UserClaude);
disabled.enabled = false;
let mut state = plugins_modal_state(vec![
make_plugin_with_origin("user-tool", PluginOrigin::UserGrok),
make_plugin_with_origin("user-tool", PluginOrigin::UserKigi),
disabled,
]);
state.plugins_filter = StatusFilter::Disabled;
+1 -1
View File
@@ -182,7 +182,7 @@ mod tests {
/// both cfgs (the dev half is pinned by the constant's shape, the same
/// limitation as the `/debug` visibility test).
#[test]
fn grok_fps_env_enables_hud_where_dev_overlay_absent() {
fn kigi_fps_env_enables_hud_where_dev_overlay_absent() {
for truthy in ["1", "full", " "] {
assert_eq!(
FpsHud::with_env(Some(truthy.into())).enabled(),
@@ -1473,11 +1473,11 @@ mod tests {
#[test]
fn render_per_model_breakdown_shows_each_model() {
let mut goal = make_goal();
goal.live_tokens_by_model = vec![("grok-4".into(), 12_300), ("grok-3-mini".into(), 8_000)];
goal.live_tokens_by_model = vec![("kigi-4".into(), 12_300), ("kigi-3-mini".into(), 8_000)];
let text = render_to_text(&goal);
assert!(text.contains("grok-4"), "first model must render:\n{text}");
assert!(text.contains("kigi-4"), "first model must render:\n{text}");
assert!(
text.contains("grok-3-mini"),
text.contains("kigi-3-mini"),
"second model must render:\n{text}"
);
// Compact token formatting is reused.
@@ -230,7 +230,7 @@ pub fn convert_list_response(resp: McpsListResponse) -> Vec<McpServerInfo> {
servers
}
/// Patch a single server row in-place from an `x.ai/mcp/server_status`
/// Patch a single server row in-place from an `kigi/mcp/server_status`
/// push.
///
/// Finds the row by `name` and updates its `status` (and optionally its
+14 -14
View File
@@ -958,17 +958,17 @@ pub fn apply_doc_mouse_scroll(kind: crossterm::event::MouseEventKind, scroll: &m
}
const DOCS_USER_GUIDE_REL: &str = "docs/user-guide";
/// Prefer keeping the on-disk path when width is tight.
fn fit_docs_ask_grok_tip(docs_path: &str, width: usize) -> String {
fn fit_docs_ask_kigi_tip(docs_path: &str, width: usize) -> String {
use crate::render::line_utils::truncate_str;
if width == 0 {
return String::new();
}
let long =
format!("Tip · Ask Grok about the docs ({docs_path}) — e.g. \"how do I set up MCP?\"");
format!("Tip · Ask Kigi about the docs ({docs_path}) — e.g. \"how do I set up MCP?\"");
if long.width() <= width {
return long;
}
let short = format!("Tip · Ask Grok about the docs · {docs_path}");
let short = format!("Tip · Ask Kigi about the docs · {docs_path}");
if short.width() <= width {
return short;
}
@@ -1054,8 +1054,8 @@ pub fn render_doc_picker_overlay(
};
let (picker_area, tip_area) = split_content_for_tip_footer(mca.content);
if let Some(tip_rect) = tip_area {
let docs_path = crate::util::display_user_grok_path(DOCS_USER_GUIDE_REL);
let tip_line = fit_docs_ask_grok_tip(&docs_path, tip_rect.width as usize);
let docs_path = crate::util::display_user_kigi_path(DOCS_USER_GUIDE_REL);
let tip_line = fit_docs_ask_kigi_tip(&docs_path, tip_rect.width as usize);
render_centered_tip_footer(buf, tip_rect, theme, &tip_line);
}
const NARROW_THRESHOLD: u16 = 70;
@@ -1276,7 +1276,7 @@ mod palette_tests {
#[cfg(test)]
mod doc_picker_tip_tests {
use super::{
ActiveModal, DOCS_USER_GUIDE_REL, fit_docs_ask_grok_tip, howto_list_modal,
ActiveModal, DOCS_USER_GUIDE_REL, fit_docs_ask_kigi_tip, howto_list_modal,
render_doc_picker_overlay,
};
use ratatui::buffer::Buffer;
@@ -1284,16 +1284,16 @@ mod doc_picker_tip_tests {
use unicode_width::UnicodeWidthStr;
#[test]
fn fit_docs_tip_prefers_path_and_never_overflows() {
let path = crate::util::display_user_grok_path(DOCS_USER_GUIDE_REL);
let path = crate::util::display_user_kigi_path(DOCS_USER_GUIDE_REL);
let long =
format!("Tip · Ask Grok about the docs ({path}) — e.g. \"how do I set up MCP?\"");
let short = format!("Tip · Ask Grok about the docs · {path}");
format!("Tip · Ask Kigi about the docs ({path}) — e.g. \"how do I set up MCP?\"");
let short = format!("Tip · Ask Kigi about the docs · {path}");
let path_only = format!("Tip · {path}");
assert_eq!(fit_docs_ask_grok_tip(&path, long.width()), long);
assert_eq!(fit_docs_ask_grok_tip(&path, short.width()), short);
assert_eq!(fit_docs_ask_grok_tip(&path, path_only.width()), path_only);
assert_eq!(fit_docs_ask_kigi_tip(&path, long.width()), long);
assert_eq!(fit_docs_ask_kigi_tip(&path, short.width()), short);
assert_eq!(fit_docs_ask_kigi_tip(&path, path_only.width()), path_only);
for w in [5usize, 12, 20, 30] {
let line = fit_docs_ask_grok_tip(&path, w);
let line = fit_docs_ask_kigi_tip(&path, w);
assert!(line.width() <= w, "overflow at {w}: {line:?}");
}
}
@@ -1328,7 +1328,7 @@ mod doc_picker_tip_tests {
all.push('\n');
}
assert!(
all.contains("Tip") && all.contains("Ask Grok"),
all.contains("Tip") && all.contains("Ask Kigi"),
"missing tip footer:\n{all}"
);
assert!(
@@ -572,7 +572,7 @@ fn render_tab_bar(
// not `theme.gray_dim`. At ANSI16 `gray_dim` collapses to the
// softer slot (silver on White) which leaves text at ~1.2:1
// contrast — fine for the modal frame's one-cell border line
// but unreadable as text glyphs on grokday.
// but unreadable as text glyphs on kigiday.
let style = if is_active {
if state.tabs_focused && !is_embedded {
Style::default()
@@ -1210,7 +1210,7 @@ mod tests {
use ratatui::layout::Rect;
let theme = Theme::current();
const TEXT: &str = "Tip · Ask Grok about the docs";
const TEXT: &str = "Tip · Ask Kigi about the docs";
let row_text = |width: u16| {
let area = Rect {
x: 0,
@@ -1229,7 +1229,7 @@ mod tests {
let start = wide.find("Tip").expect("Tip");
let trailing = wide.chars().rev().take_while(|c| *c == ' ').count();
assert!(start.abs_diff(trailing) <= 1, "not centered: {wide:?}");
assert!(wide.contains("Ask Grok about the docs"));
assert!(wide.contains("Ask Kigi about the docs"));
let tiny = row_text(10);
assert!(tiny.contains("Tip"));
@@ -1614,7 +1614,7 @@ fn char_wrap(s: &str, width: usize) -> Vec<String> {
/// RejectOnce renders as a freeform input row (matching question view style)
/// using the same 1-based shortcut number as its prefix:
/// ```text
/// 3 [ ] Tell Grok what to do differently
/// 3 [ ] Tell Kigi what to do differently
/// 3 [x] my followup message preview...
/// ```
///
@@ -2216,7 +2216,7 @@ mod tests {
state.description = vec![
"{".to_string(),
" \"issue\": \"ABC-123\",".to_string(),
" \"body\": \"hello from grok\"".to_string(),
" \"body\": \"hello from kigi\"".to_string(),
"}".to_string(),
];
state.options = vec![acp::PermissionOption::new(
@@ -2242,7 +2242,7 @@ mod tests {
"args JSON not rendered:\n{text}"
);
assert!(
text.contains("\"body\": \"hello from grok\""),
text.contains("\"body\": \"hello from kigi\""),
"args JSON not rendered:\n{text}"
);
// Option row still visible below the args.
@@ -1,7 +1,7 @@
use agent_client_protocol as acp;
use kigi_acp_lib::AcpResult;
pub use kigi_tools::implementations::grok_build::exit_plan_mode::{
pub use kigi_tools::implementations::kigi::exit_plan_mode::{
ExitPlanModeExtRequest, ExitPlanModeExtResponse,
};
@@ -153,10 +153,10 @@ pub fn suggestion_size(text: &str) -> (usize, usize) {
}
/// Resolve the client-side model hint sent with the suggestion request:
/// env override > `grok-build-0.1` when the catalog offers it > `None`.
/// env override > `kigi-0.1` when the catalog offers it > `None`.
///
/// The hint is one tier of the shell-side resolution (env > config.toml >
/// remote settings > this hint > `grok-build-0.1` default): the shell
/// remote settings > this hint > `kigi-0.1` default): the shell
/// catalog-guards the effective model and skips the request entirely when
/// it is not sampleable — the session model is never used for suggestion
/// calls.
@@ -12,7 +12,7 @@
//! ← top vpad (configurable)
//! type here, text wraps ← prefix + TextArea
//! continuation of long input... ← TextArea continuation
//! grok-3 · yolo ← info line (optional)
//! kigi-3 · yolo ← info line (optional)
//! ```
//!
//! The accent line (┃) and selection box are rendered by the caller.
@@ -1099,7 +1099,7 @@ impl PromptWidget {
{
// The row's trailing space is its args separator; absorb
// an existing plain-text one so accepting mid-token in
// `/mod grok-4` yields `/model grok-4`, not `/model grok-4`
// `/mod kigi-4` yields `/model kigi-4`, not `/model kigi-4`
// — absorb (rather than trim the insert) so the cursor
// lands after the separator, in the args phase. Never
// absorb an element's byte: a chip's leading space is chip
@@ -1125,7 +1125,7 @@ impl PromptWidget {
false
}
} else {
// Replace the args range (e.g., "gr" → "Grok 4 Fast").
// Replace the args range (e.g., "gr" → "Kigi 4 Fast").
if let Some(ref range) = snap.args_range
&& range.end <= text_len
&& self.textarea.text().is_char_boundary(range.start)
@@ -3012,7 +3012,7 @@ impl PromptWidget {
}
}
// Bottom divider: ╰──────────grok-3 · flags──╯
// Bottom divider: ╰──────────kigi-3 · flags──╯
// Guard on actual allocated height, not requested `info_block`: during
// resize the layout may squeeze the info block to 0 rows, leaving
// chunks[2].y past the buffer boundary.
@@ -251,7 +251,7 @@
TerminalName::Rio,
TerminalName::Foot,
TerminalName::JetBrains,
TerminalName::GrokDesktop,
TerminalName::KigiDesktop,
TerminalName::Vte,
TerminalName::Terminator,
TerminalName::WindowsTerminal,
@@ -277,7 +277,7 @@
| TerminalName::Rio
| TerminalName::Foot
| TerminalName::JetBrains
| TerminalName::GrokDesktop
| TerminalName::KigiDesktop
| TerminalName::Vte
| TerminalName::Terminator
| TerminalName::WindowsTerminal
@@ -397,7 +397,7 @@
use std::path::PathBuf;
let mut img = test_image();
img.source_path = Some(PathBuf::from("/tmp/grok-test-image.png"));
img.source_path = Some(PathBuf::from("/tmp/kigi-test-image.png"));
let mut pw = ghostty_prompt();
pw.insert_image(img).unwrap();
@@ -407,12 +407,12 @@
"chip text should be path-free: {full:?}"
);
assert!(
!full.contains("/tmp/grok-test-image.png"),
!full.contains("/tmp/kigi-test-image.png"),
"source path must not appear in the buffer chip: {full:?}"
);
assert_eq!(
pw.images[0].source_path.as_deref(),
Some(std::path::Path::new("/tmp/grok-test-image.png")),
Some(std::path::Path::new("/tmp/kigi-test-image.png")),
"source_path retained on the PastedImage record"
);
@@ -420,7 +420,7 @@
let selected = pw.textarea.selected_text().expect("selected text");
assert!(selected.contains("[Image #1]"));
assert!(
!selected.contains("/tmp/grok-test-image.png"),
!selected.contains("/tmp/kigi-test-image.png"),
"select-all must not copy the filepath from the chip"
);
}
@@ -1559,7 +1559,7 @@
let models = crate::acp::model_state::ModelState::default();
// Cursor inside the command token with args already present.
pw.textarea.insert_str("/mod grok-4");
pw.textarea.insert_str("/mod kigi-4");
pw.textarea.set_cursor(3);
pw.refresh_slash(&models);
@@ -1577,7 +1577,7 @@
assert!(pw.accept_slash_completion(&models));
assert_eq!(
pw.textarea.text(),
"/model grok-4",
"/model kigi-4",
"the row's trailing space must not stack on the existing separator"
);
// Absorb (not trim-the-insert): the cursor must land after the
@@ -1664,25 +1664,25 @@
let mut pw = PromptWidget::new();
let mut models = crate::acp::model_state::ModelState::default();
let model_id = agent_client_protocol::ModelId::new(Arc::from("grok-4.5"));
let model_id = agent_client_protocol::ModelId::new(Arc::from("kigi-4.5"));
models.available.insert(
model_id.clone(),
agent_client_protocol::ModelInfo::new(model_id, "Grok 4.5".to_string()),
agent_client_protocol::ModelInfo::new(model_id, "Kigi 4.5".to_string()),
);
// Type "/model gr" and position cursor at end (in args).
pw.textarea.insert_str("/model gr");
// Type "/model ki" and position cursor at end (in args).
pw.textarea.insert_str("/model ki");
pw.refresh_slash(&models);
let snap = pw.slash_snapshot();
assert!(snap.open, "arg suggestions should be open");
assert!(snap.args_range.is_some());
// Accept arg completion → should replace "gr" with "Grok 4.5".
// Accept arg completion → should replace "ki" with "Kigi 4.5".
pw.accept_slash_completion(&models);
let text = pw.textarea.text().to_string();
assert!(
text.contains("Grok 4.5"),
text.contains("Kigi 4.5"),
"arg should be replaced, got: {:?}",
text
);
@@ -4303,7 +4303,7 @@
// -- Predicted-next-prompt suggestion through PromptWidget ----------------
/// Widget with an active gate and a loaded suggestion — the state right
/// after a turn ends with `x.ai/suggestPrompt` resolved.
/// after a turn ends with `kigi/suggestPrompt` resolved.
fn widget_with_prompt_suggestion(text: &str) -> PromptWidget {
let mut pw = PromptWidget::new();
pw.prompt_suggestion_active = true;
@@ -14,7 +14,7 @@ use std::time::Instant;
use kigi_acp_lib::AcpResult;
use kigi_markdown::StreamingMarkdownRenderer;
pub use kigi_tools::implementations::grok_build::ask_user_question::{
pub use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionMode, Question, QuestionOption,
};
use ratatui::buffer::Buffer;
@@ -68,7 +68,7 @@ pub enum QuestionFocus {
}
/// Pager-internal origin for a locally-opened question (one that was NOT
/// driven by an ACP `x.ai/ask_user_question` request).
/// driven by an ACP `kigi/ask_user_question` request).
///
/// Drives what `submit_question_answers` returns when the user submits.
/// Mutually exclusive with `QuestionViewState.response_tx`: a local
@@ -118,7 +118,7 @@ pub enum LocalQuestionKind {
/// Complete state for the question view overlay.
///
/// Created when an `x.ai/ask_user_question` ext-method request arrives;
/// Created when an `kigi/ask_user_question` ext-method request arrives;
/// destroyed on submit, skip, or cancel.
///
/// Not `Clone` because it owns a `oneshot::Sender` for the ACP response.
@@ -171,7 +171,7 @@ pub struct QuestionViewState {
/// `Some(1)` = Skip interview.
pub bottom_panel_index: Option<usize>,
/// `Some` when this question was opened locally (e.g. by `/fork`)
/// instead of by an ACP `x.ai/ask_user_question` request. `None` for
/// instead of by an ACP `kigi/ask_user_question` request. `None` for
/// ACP questions (preserves today's behaviour).
///
/// Mutually exclusive with `response_tx`: a local question never has
@@ -210,7 +210,7 @@ impl QuestionViewState {
/// Create a new question view state with an ACP response sender.
///
/// Called by the `ExtMethod` handler when a blocking `x.ai/ask_user_question`
/// Called by the `ExtMethod` handler when a blocking `kigi/ask_user_question`
/// request arrives from the shell coordinator.
pub fn with_response_tx(
tool_call_id: String,
@@ -724,7 +724,7 @@ impl QuestionViewState {
/// the current freeform text so the caller can load it into the prompt.
///
/// No-op returning an empty string when `no_freeform` is set: such
/// questions (e.g. the SuperGrok upsell) have no freeform row, so
/// questions (e.g. the subscription upsell) have no freeform row, so
/// `InputMode` must be unreachable. Callers gate on `no_freeform` /
/// [`Self::is_on_freeform_row`] too; this is defense in depth.
pub fn activate_freeform_input(&mut self) -> String {
@@ -812,10 +812,9 @@ impl QuestionViewState {
/// - Notes included when freeform text is non-empty and selected.
pub fn build_accepted_response(
&self,
) -> kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionExtResponse
{
) -> kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionExtResponse {
use indexmap::IndexMap;
use kigi_tools::implementations::grok_build::ask_user_question::{
use kigi_tools::implementations::kigi::ask_user_question::{
AskUserQuestionExtResponse, QuestionAnnotation,
};
use std::collections::HashMap;
@@ -899,7 +898,7 @@ impl QuestionViewState {
/// double-send.
pub fn send_ext_response(
&mut self,
response: kigi_tools::implementations::grok_build::ask_user_question::AskUserQuestionExtResponse,
response: kigi_tools::implementations::kigi::ask_user_question::AskUserQuestionExtResponse,
) -> bool {
let Some(tx) = self.response_tx.take() else {
return false;
@@ -2545,7 +2544,7 @@ mod tests {
// ── no_freeform ────────────────────────────────────────────────────
/// `no_freeform` questions (e.g. the SuperGrok upsell) have no "Other"
/// `no_freeform` questions (e.g. the subscription upsell) have no "Other"
/// row, so activating freeform input must be impossible: focus stays in
/// Navigation and nothing gets marked selected. Regression test for the
/// upsell modal letting the user type after clicking under the last
@@ -43,7 +43,7 @@ pub enum QueueRowOrigin {
/// any idle-drained prompt). Edits mutate the local queue directly.
Local,
/// A server-authoritative `shared_prompt_queues` entry (plain prompt queued
/// while running). Edits route to the agent as `x.ai/queue/*`.
/// while running). Edits route to the agent as `kigi/queue/*`.
Server,
}
@@ -385,7 +385,7 @@ pub struct QueuePane {
/// Theme kind at the last render. Used to detect a theme switch and
/// refresh `list_style`, whose `selection_bg` is captured from the theme
/// (otherwise the focused-row highlight keeps the previous theme's
/// `bg_highlight` — e.g. GrokNight's dark band leaking into GrokDay).
/// `bg_highlight` — e.g. KigiNight's dark band leaking into KigiDay).
last_theme: ThemeKind,
/// Shared visibility/focus state.
pub overlay: OverlayState,
@@ -827,8 +827,8 @@ impl QueuePane {
// Detect a theme switch and refresh the list chrome style. Its
// `selection_bg` (the focused-row highlight) is captured from the
// theme's `bg_highlight`; without this it would keep the theme active
// at construction (default GrokNight, dark) after the user switches —
// painting a dark band on a light GrokDay canvas.
// at construction (default KigiNight, dark) after the user switches —
// painting a dark band on a light KigiDay canvas.
let current_theme = Theme::current_kind();
if current_theme != self.last_theme {
self.last_theme = current_theme;
@@ -3,7 +3,7 @@
//! A compact top-right overlay painting a per-frame snapshot of the scroll
//! state machine ([`MouseScrollState::debug_snapshot`]) plus the active
//! scrollback's viewport facts, inside a REAL session with the REAL event
//! loop. Recipe: `KIGI_FPS=1 KIGI_SCROLL_DEBUG=1 grok --resume <session>`,
//! loop. Recipe: `KIGI_FPS=1 KIGI_SCROLL_DEBUG=1 kigi --resume <session>`,
//! then flip `scroll_mode` / `scroll_lines` / `invert_scroll` /
//! `scroll_speed` in `/settings` to compare variants live. For event-exact
//! capture beyond this per-frame sampling, add `KIGI_SCROLL_LOG=1` — the
@@ -157,7 +157,7 @@ impl SourceFilter {
/// Returns `true` if a session with the given `source` string passes the filter.
///
/// grok.com conversations carry `source == "conversation"` and live remotely,
/// kimi.com conversations carry `source == "conversation"` and live remotely,
/// so they pass the `Remote` filter (and `All`) but not `Local`.
pub fn matches(self, source: &str) -> bool {
match self {
@@ -1192,7 +1192,7 @@ mod tests {
assert!(!SourceFilter::Remote.matches("local"));
assert!(!SourceFilter::Remote.matches("cursor"));
// grok.com conversations are remote: visible under All + Remote, not Local.
// kimi.com conversations are remote: visible under All + Remote, not Local.
assert!(SourceFilter::All.matches("conversation"));
assert!(SourceFilter::Remote.matches("conversation"));
assert!(!SourceFilter::Local.matches("conversation"));
@@ -1285,8 +1285,8 @@ fn render_row_list_with_search_bar(
fn render_docs_footer(buf: &mut Buffer, area: Rect, theme: &Theme) {
const LONG: &str =
"Tip · Ask Grok: \"change theme to grokday\" or \"what does compact mode do?\"";
const SHORT: &str = "Tip · Ask Grok to change a setting";
"Tip · Ask Kigi: \"change theme to kigiday\" or \"what does compact mode do?\"";
const SHORT: &str = "Tip · Ask Kigi to change a setting";
let text = modal_window::fit_tip_line(&[LONG, SHORT], area.width as usize);
modal_window::render_centered_tip_footer(buf, area, theme, text.as_ref());
}
@@ -1743,7 +1743,7 @@ const PICKER_MARKER_W: u16 = 1;
/// The chooser already scrolls within the viewport when the focused choice
/// falls off-screen (`picker_scroll_offset`); this limit exists so catalogs
/// stay intentionally curated rather than unbounded. Sized to fit the full
/// Grok STT language list (25 codes + client-only `auto` = 26) with headroom.
/// Kigi STT language list (25 codes + client-only `auto` = 26) with headroom.
pub(crate) const MAX_PICKER_CHOICES: usize = 32;
/// Render the shared sub-pane header (bold title row + word-wrapped description)
@@ -6572,8 +6572,8 @@ mod tests {
let registry = SettingsRegistry::from_entries(vec![synthetic_meta]);
let snapshot = PagerLocalSnapshot {
available_models: vec![(
"Grok Test".to_string(),
acp::ModelId::new(Arc::from("grok-test")),
"Kigi Test".to_string(),
acp::ModelId::new(Arc::from("kigi-test")),
)],
..PagerLocalSnapshot::default()
};
@@ -6596,7 +6596,7 @@ mod tests {
/// buffers that fit entirely within the visible window.
#[test]
fn render_editing_value_cursor_at_logical_position_when_buffer_fits() {
let mut s = editor_render_fixture("Grok Test", 4); // cursor between "Grok" and " Test"
let mut s = editor_render_fixture("Kigi Test", 4); // cursor between "Kigi" and " Test"
let area = Rect {
x: 0,
y: 0,
@@ -7280,9 +7280,9 @@ mod tests {
#[test]
fn picking_enum_esc_dispatches_preview_revert_for_each_key() {
let cases: &[(&str, &str)] = &[
("theme", "groknight"),
("auto_dark_theme", "groknight"),
("auto_light_theme", "grokday"),
("theme", "kiginight"),
("auto_dark_theme", "kiginight"),
("auto_light_theme", "kigiday"),
];
for &(key, original) in cases {
let mut s = make_state();
@@ -7331,18 +7331,18 @@ mod tests {
s.mode = SettingsModalMode::PickingEnum {
key: "theme",
choices_idx: 0,
original_value: SettingValue::Enum("groknight"),
original_value: SettingValue::Enum("kiginight"),
supports_preview: true,
};
let outcome = handle_settings_key(&mut s, &KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
match outcome {
SettingsKeyOutcome::Action(Action::PreviewTheme(name)) => {
assert_eq!(
name, "groknight",
name, "kiginight",
"Esc revert must dispatch the original canonical"
);
}
other => panic!("expected Action::PreviewTheme(\"groknight\") on Esc, got {other:?}"),
other => panic!("expected Action::PreviewTheme(\"kiginight\") on Esc, got {other:?}"),
}
assert!(matches!(s.mode, SettingsModalMode::Browse));
}
@@ -9000,14 +9000,14 @@ mod tests {
assert!(
validation_error.is_some(),
"validation_error must be Some for unknown model 'a' \
(catalog has 'Grok 4 Fast' only)",
(catalog has 'Kigi 4 Fast' only)",
);
}
_ => panic!("mode must remain EditingValue after char input"),
}
// Enter on a buffer that fails the KnownModel validator
// (catalog has 'Grok 4 Fast'; "a" doesn't match) is
// (catalog has 'Kigi 4 Fast'; "a" doesn't match) is
// Unchanged — commit refused.
let outcome =
handle_settings_key(&mut s, &KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
@@ -9053,8 +9053,8 @@ mod tests {
let mut s = make_state();
s.mode = SettingsModalMode::EditingValue {
key: "default_model",
buffer: "grok-4".to_string(),
cursor_byte: "grok-4".len(),
buffer: "kigi-4".to_string(),
cursor_byte: "kigi-4".len(),
validation_error: None,
};
let outcome = handle_settings_key(&mut s, &KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
@@ -9880,7 +9880,7 @@ mod tests {
}
// -- User-feedback follow-up: always reserve a blank line between
// the "Tip · Ask Grok…" docs footer and the keybindings hints.
// the "Tip · Ask Kigi…" docs footer and the keybindings hints.
//
// Before this fix, when the hints wrapped to 2 lines (narrow modal
// widths) the chrome's 2-row footer was fully consumed by hint
@@ -10683,7 +10683,7 @@ mod tests {
);
// SHORT path: width that fits SHORT but not LONG.
// SHORT = "Tip · Ask Grok to change a setting" (34 cells);
// SHORT = "Tip · Ask Kigi to change a setting" (34 cells);
// LONG ≈ 73 cells. width=40 lands in the SHORT band.
let (row_short, tip_start_short, trailing_short) = render(40);
assert!(
@@ -10691,8 +10691,8 @@ mod tests {
"width=40 must render SHORT path (contains `change a setting`): {row_short:?}",
);
assert!(
!row_short.contains("grokday"),
"width=40 must NOT render LONG path (no `grokday`): {row_short:?}",
!row_short.contains("kigiday"),
"width=40 must NOT render LONG path (no `kigiday`): {row_short:?}",
);
assert!(
tip_start_short.abs_diff(trailing_short) <= 1,
@@ -10736,7 +10736,7 @@ mod tests {
let mut tip_y: Option<u16> = None;
for y in 0..area.height {
let txt = buf_row_text(&buf, y, area.x, area.width);
if txt.contains("Tip") && txt.contains("Ask Grok") {
if txt.contains("Tip") && txt.contains("Ask Kigi") {
tip_y = Some(y);
break;
}
@@ -11044,17 +11044,17 @@ mod tests {
// For preview-supporting enums (theme), the breadcrumb-
// click revert dispatches `Action::PreviewTheme(original)`.
// The original canonical for the default theme is
// `"groknight"`. Tightened from the previous `Action(_) |
// `"kiginight"`. Tightened from the previous `Action(_) |
// Changed` to lock in the revert contract.
match outcome {
SettingsKeyOutcome::Action(Action::PreviewTheme(orig)) => {
assert_eq!(
orig, "groknight",
orig, "kiginight",
"breadcrumb-click revert must carry the original canonical",
);
}
other => panic!(
"expected Action(PreviewTheme(\"groknight\")) — the keyboard \
"expected Action(PreviewTheme(\"kiginight\")) — the keyboard \
Esc-equivalent revert got {other:?}",
),
}
@@ -11098,7 +11098,7 @@ mod tests {
}
other => panic!("expected PickingEnum, got {other:?}"),
};
// Pick a different index. The default theme is `groknight`
// Pick a different index. The default theme is `kiginight`
// (index 1 per the registry); advance to index 0 to ensure
// we're navigating to a different value.
let target_idx = if advanced_idx == 0 { 1 } else { 0 };
@@ -11158,11 +11158,11 @@ mod tests {
key, "theme",
"OpenResetConfirm key must be the active picker setting",
);
// Default theme is `groknight`; entering the picker
// captures `original_value = current value = groknight`,
// Default theme is `kiginight`; entering the picker
// captures `original_value = current value = kiginight`,
// so the revert dispatches with that canonical.
assert_eq!(
orig, "groknight",
orig, "kiginight",
"PreviewTheme revert must carry the original canonical",
);
}
@@ -11628,9 +11628,9 @@ mod tests {
/// regardless of how much the bg tokens differ in luma.
///
/// The previous name `_title_bg_is_darker_than_content_bg` was
/// misleading: on dark themes (GrokNight, TokyoNight, RosePine
/// misleading: on dark themes (KigiNight, TokyoNight, RosePine
/// Moon) `bg_visual` is actually *lighter* than `bg_highlight`;
/// only on the GrokDay light theme is title darker. The
/// only on the KigiDay light theme is title darker. The
/// contract that the rendering code actually relies on is "title
/// uses the heavier / more-saturated `bg_visual` token, content
/// uses `bg_highlight`, plus an UNDERLINED title modifier for
@@ -11686,14 +11686,14 @@ mod tests {
// raw theme directly so this assertion survives `NO_COLOR`
// / 256-color quantization.
let raw_theme = match crate::theme::Theme::current_kind() {
crate::theme::ThemeKind::GrokNight => crate::theme::Theme::groknight(),
crate::theme::ThemeKind::KigiNight => crate::theme::Theme::kiginight(),
crate::theme::ThemeKind::TokyoNight => crate::theme::Theme::tokyonight(),
crate::theme::ThemeKind::GrokDay => crate::theme::Theme::grokday(),
crate::theme::ThemeKind::KigiDay => crate::theme::Theme::kigiday(),
crate::theme::ThemeKind::RosePineMoon => crate::theme::Theme::rosepine_moon(),
// Resolved via `Theme::current()` rather than a constructor
// because `theme::oscura` is a private module.
crate::theme::ThemeKind::OscuraMidnight => crate::theme::Theme::current(),
crate::theme::ThemeKind::Auto => crate::theme::Theme::groknight(),
crate::theme::ThemeKind::Auto => crate::theme::Theme::kiginight(),
};
assert_ne!(
raw_theme.bg_visual, raw_theme.bg_highlight,
@@ -7,7 +7,7 @@
//! unnecessary network requests.
//!
//! ACP integration: on text change (after debounce), sends an
//! `x.ai/suggest` request through the Effect pipeline. Stale responses
//! `kigi/suggest` request through the Effect pipeline. Stale responses
//! are discarded via generation tracking.
/// Source of a shell command suggestion.
@@ -46,14 +46,14 @@ pub(crate) struct GhostTextState {
pub(crate) generation: u64,
}
/// Parsed ghost suggestion from an ACP `x.ai/suggest` response.
/// Parsed ghost suggestion from an ACP `kigi/suggest` response.
#[derive(Debug, Clone)]
pub struct GhostSuggestionParsed {
pub suffix: String,
pub source: SuggestionSource,
}
/// A single completion item from an ACP `x.ai/suggest` response.
/// A single completion item from an ACP `kigi/suggest` response.
// `Default` (empty item) exists for downstream test fixtures — functional-
// update construction (`..Default::default()`) keeps out-of-crate literals
// (e.g. kigi-pager-minimal's) compiling when optional fields are added.
@@ -87,7 +87,7 @@ impl CompletionItemParsed {
}
}
/// Parsed response from an ACP `x.ai/suggest` request.
/// Parsed response from an ACP `kigi/suggest` request.
#[derive(Debug, Clone)]
pub struct SuggestResponseParsed {
pub ghost: Option<GhostSuggestionParsed>,
@@ -96,7 +96,7 @@ pub struct SuggestResponseParsed {
}
impl SuggestResponseParsed {
/// Parse a raw JSON value from an ACP `x.ai/suggest` response.
/// Parse a raw JSON value from an ACP `kigi/suggest` response.
pub fn from_json(value: &serde_json::Value) -> Option<Self> {
let result = value.get("result").unwrap_or(value);
let generation = result.get("generation")?.as_u64()?;
@@ -197,7 +197,7 @@ pub enum SuggestionAction {
Debounce { generation: u64 },
}
/// Wire `limit` for `x.ai/suggest` fetches. Matches the shell file
/// Wire `limit` for `kigi/suggest` fetches. Matches the shell file
/// provider's ranked-result cap (`MAX_RESULTS` in the shell crate's
/// `file_provider.rs`): the provider ranks BEFORE capping, the dropdown
/// renders 6 rows and scrolls the rest. Both fetch sites (Tab and the
@@ -326,7 +326,7 @@ pub struct SuggestionController {
/// Whether AI-powered suggestions are enabled.
/// Resolved at construction from `KIGI_SUGGESTIONS_AI` env var.
pub ai_enabled: bool,
/// Model to use for AI suggestions. Sent in the `x.ai/suggest` request.
/// Model to use for AI suggestions. Sent in the `kigi/suggest` request.
/// Resolved at construction from `KIGI_SUGGESTIONS_AI_MODEL` env var.
pub ai_model: Option<String>,
}
@@ -734,7 +734,7 @@ impl SuggestionController {
generation == self.generation
}
/// Called when an ACP `x.ai/suggest` response arrives, with the text and
/// Called when an ACP `kigi/suggest` response arrives, with the text and
/// cursor the request was built from (the anchor item `replace_range`
/// offsets index into, and the position Tab targets). Takes ownership to
/// avoid copying strings. Discards stale responses.
@@ -1112,7 +1112,7 @@ fn tab_decision_rangeless_rows_never_get_token_semantics() {
let mut sc = decision_controller("ls | gr");
sc.dropdown.items = vec![
rangeless,
span_item("grokfile", 5..7, SuggestionSource::FilePath),
span_item("kigifile", 5..7, SuggestionSource::FilePath),
];
assert_eq!(sc.tab_decision("ls | gr", "ls | gr".len()), TabAction::Open);
@@ -2791,7 +2791,7 @@ mod tests {
fn entry_label_includes_meta() {
let mut info = make_info();
info.persona = Some("researcher".into());
info.model = Some("grok-3".into());
info.model = Some("kigi-3".into());
let entry = TaskEntry::from_subagent(&info);
let label = match &entry {
TaskEntry::Agent { label, .. } => label.as_str(),
@@ -2802,7 +2802,7 @@ mod tests {
"label should contain capitalized persona: {label}",
);
assert!(
label.contains("grok-3"),
label.contains("kigi-3"),
"label should contain model: {label}",
);
}