M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
//! Code-navigation eligibility gating and codebase-index management for [`MvpAgent`].
|
||||
//! Co-located child of `mvp_agent` (`use super::*`).
|
||||
|
||||
use super::*;
|
||||
|
||||
impl MvpAgent {
|
||||
/// Parse the `x.ai/codeNavigation.enabled` capability from an initialize
|
||||
/// request. Returns `false` if the field is absent or not `true`.
|
||||
pub(crate) fn parse_code_nav_capability(init: &acp::InitializeRequest) -> bool {
|
||||
init.client_capabilities
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.get("x.ai/codeNavigation"))
|
||||
.and_then(|v| v.get("enabled"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Start (or reuse) the codebase index for an eligible code-nav request.
|
||||
///
|
||||
/// Returns `Some((handle, was_newly_started))` on success or `None` when
|
||||
/// config/git-root checks prevent starting. The bool is the authoritative
|
||||
/// "first spawn vs reuse" signal threaded up from `CodebaseIndexManager`.
|
||||
///
|
||||
/// This is the narrow `pub(crate)` entry point for lazy index startup
|
||||
/// from `extensions/code_nav.rs`. Callers must verify eligibility with
|
||||
/// [`code_nav_eligibility_for_request`] before calling this.
|
||||
pub(crate) fn start_codebase_index_for_code_nav(
|
||||
&self,
|
||||
session_id: Option<&acp::SessionId>,
|
||||
cwd: &std::path::Path,
|
||||
) -> Option<(std::sync::Arc<kigi_codebase_graph::IndexManagerHandle>, bool)> {
|
||||
let (handle, was_newly_started) = self.resolve_codebase_index(cwd)?;
|
||||
// Pin the index to the requesting session so the Weak in
|
||||
// CodebaseIndexManager doesn't orphan it immediately.
|
||||
if let Some(sid) = session_id {
|
||||
self.session_index_claims
|
||||
.borrow_mut()
|
||||
.insert(sid.clone(), std::sync::Arc::clone(&handle));
|
||||
}
|
||||
Some((handle, was_newly_started))
|
||||
}
|
||||
|
||||
/// Core eligibility check — pure function that accepts explicit client
|
||||
/// context rather than reading global agent state.
|
||||
///
|
||||
/// This is the single place that applies all four gates. Call it via
|
||||
/// [`code_nav_eligibility_for_request`] (leader-mode safe) or
|
||||
/// [`code_nav_eligibility`] (global state, non-leader use only).
|
||||
pub(super) fn code_nav_eligibility_inner(
|
||||
&self,
|
||||
cwd: &std::path::Path,
|
||||
client_type: ClientType,
|
||||
code_nav_enabled: bool,
|
||||
) -> Result<(), CodeNavEligibility> {
|
||||
use crate::agent::config::CodebaseIndexingSetting;
|
||||
|
||||
// Gate 1: client type
|
||||
if !matches!(client_type, ClientType::GrokWeb) {
|
||||
tracing::info!(
|
||||
client_type = ?client_type,
|
||||
gate = "client_type",
|
||||
skip_reason = "client_not_web",
|
||||
"code-nav eligibility check: skipping (client type not eligible)"
|
||||
);
|
||||
return Err(CodeNavEligibility::ClientNotWeb);
|
||||
}
|
||||
|
||||
// Gate 2: capability advertised
|
||||
if !code_nav_enabled {
|
||||
tracing::info!(
|
||||
gate = "capability",
|
||||
skip_reason = "capability_not_advertised",
|
||||
"code-nav eligibility check: skipping (x.ai/codeNavigation.enabled not advertised)"
|
||||
);
|
||||
return Err(CodeNavEligibility::CapabilityNotAdvertised);
|
||||
}
|
||||
|
||||
// Gate 3: config
|
||||
let setting = self.cfg.borrow().features.codebase_indexing.clone();
|
||||
if let CodebaseIndexingSetting::Enabled(false) = &setting {
|
||||
tracing::info!(
|
||||
gate = "config",
|
||||
skip_reason = "disabled_by_config",
|
||||
"code-nav eligibility check: skipping (codebase_indexing disabled in config)"
|
||||
);
|
||||
return Err(CodeNavEligibility::DisabledByConfig);
|
||||
}
|
||||
|
||||
// Gate 4: git root / config globs
|
||||
let git_root = kigi_workspace::session::git::find_git_root_from_path(cwd).ok();
|
||||
match &setting {
|
||||
CodebaseIndexingSetting::Enabled(true) => {
|
||||
if git_root.is_none() {
|
||||
tracing::info!(
|
||||
cwd = %cwd.display(),
|
||||
gate = "git_root",
|
||||
skip_reason = "not_git_repo",
|
||||
"code-nav eligibility check: skipping (not inside a git repo)"
|
||||
);
|
||||
return Err(CodeNavEligibility::NotGitRepo);
|
||||
}
|
||||
}
|
||||
CodebaseIndexingSetting::Patterns(_) => {
|
||||
let check_path = git_root.as_deref().unwrap_or(cwd);
|
||||
if !setting.should_index(check_path) {
|
||||
tracing::info!(
|
||||
cwd = %cwd.display(),
|
||||
gate = "config_globs",
|
||||
skip_reason = "disabled_by_config",
|
||||
"code-nav eligibility check: skipping (not matched by config globs)"
|
||||
);
|
||||
return Err(CodeNavEligibility::DisabledByConfig);
|
||||
}
|
||||
}
|
||||
CodebaseIndexingSetting::Enabled(false) => {} // handled above
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check eligibility using per-session context (leader-mode safe).
|
||||
///
|
||||
/// When `session_id` is provided, reads the session's own client type
|
||||
/// and code-nav capability — the values that were in effect when that
|
||||
/// specific client created the session. This is correct in leader mode
|
||||
/// where multiple clients share one agent process and `initialize()` is
|
||||
/// called once per connection; the global fields on `MvpAgent` reflect
|
||||
/// only the **last** client to call `initialize()`.
|
||||
///
|
||||
/// Falls back to global agent state when no session_id is given.
|
||||
pub fn code_nav_eligibility_for_request(
|
||||
&self,
|
||||
session_id: Option<&acp::SessionId>,
|
||||
cwd: &std::path::Path,
|
||||
) -> Result<(), CodeNavEligibility> {
|
||||
let session_id = match session_id {
|
||||
Some(sid) => sid,
|
||||
// No session_id: per-client capability cannot be determined without a
|
||||
// session. Reject with SessionRequired rather than fall back to shared
|
||||
// global state. Callers must provide sessionId for x.ai/code/* requests.
|
||||
None => return Err(CodeNavEligibility::SessionRequired),
|
||||
};
|
||||
|
||||
let sessions = self.sessions.borrow();
|
||||
let (client_type, code_nav_enabled) = if let Some(handle) = sessions.get(session_id) {
|
||||
let ct = crate::http::client_type_from_origin(handle.origin_client.as_ref());
|
||||
(ct, handle.code_nav_enabled)
|
||||
} else {
|
||||
// Session not found (evicted/unknown): reject rather than silently
|
||||
// falling back to shared global state — that would reintroduce the
|
||||
// last-client-wins bug for stale session IDs in leader mode.
|
||||
return Err(CodeNavEligibility::SessionRequired);
|
||||
};
|
||||
drop(sessions);
|
||||
self.code_nav_eligibility_inner(cwd, client_type, code_nav_enabled)
|
||||
}
|
||||
|
||||
/// Check eligibility using the stored initialize_request context.
|
||||
///
|
||||
/// **Not safe in leader mode** — reads the last `initialize()` call's
|
||||
/// client_type and capability. Prefer [`code_nav_eligibility_for_request`]
|
||||
/// when a session_id is available.
|
||||
pub fn code_nav_eligibility(&self, cwd: &std::path::Path) -> Result<(), CodeNavEligibility> {
|
||||
let client_type = *self.client_type.borrow();
|
||||
let code_nav_enabled = self.code_nav_enabled.get();
|
||||
self.code_nav_eligibility_inner(cwd, client_type, code_nav_enabled)
|
||||
}
|
||||
|
||||
/// Resolve and get-or-create the codebase index for `cwd`, applying config
|
||||
/// and git-root eligibility checks.
|
||||
///
|
||||
/// Returns `Some((handle, was_newly_started))` when an index is available,
|
||||
/// `None` when config or git-root checks rule it out. The bool is the
|
||||
/// authoritative "was this a first spawn?" signal from the manager.
|
||||
pub(super) fn resolve_codebase_index(
|
||||
&self,
|
||||
cwd: &std::path::Path,
|
||||
) -> Option<(std::sync::Arc<kigi_codebase_graph::IndexManagerHandle>, bool)> {
|
||||
use crate::agent::config::CodebaseIndexingSetting;
|
||||
|
||||
let setting = self.cfg.borrow().features.codebase_indexing.clone();
|
||||
let git_root = kigi_workspace::session::git::find_git_root_from_path(cwd).ok();
|
||||
|
||||
match (&setting, &git_root) {
|
||||
(CodebaseIndexingSetting::Enabled(false), _) => {
|
||||
tracing::info!(
|
||||
cwd = %cwd.display(),
|
||||
skip_reason = "disabled_by_config",
|
||||
"code-nav: skipping index creation (disabled in config)"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
(CodebaseIndexingSetting::Enabled(true), None) => {
|
||||
tracing::info!(
|
||||
cwd = %cwd.display(),
|
||||
skip_reason = "not_git_repo",
|
||||
"code-nav: skipping index creation (not inside a git repo)"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
(CodebaseIndexingSetting::Patterns(_), _) => {
|
||||
let check_path = git_root.as_deref().unwrap_or(cwd);
|
||||
if !setting.should_index(check_path) {
|
||||
tracing::info!(
|
||||
cwd = %cwd.display(),
|
||||
skip_reason = "disabled_by_config",
|
||||
"code-nav: skipping index creation (not matched by config globs)"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
(CodebaseIndexingSetting::Enabled(true), Some(_)) => {}
|
||||
}
|
||||
|
||||
let target = git_root.unwrap_or_else(|| cwd.to_path_buf());
|
||||
// get_or_create returns the authoritative (handle, was_newly_started) pair.
|
||||
// Log only on actual first spawn so reuse requests are not misleadingly
|
||||
// labelled as "starting".
|
||||
let (handle, was_newly_started) = self.get_or_create_codebase_index(target.clone());
|
||||
if was_newly_started {
|
||||
tracing::info!(
|
||||
cwd = %cwd.display(),
|
||||
index_target = %target.display(),
|
||||
event = "index_first_spawn",
|
||||
"code-nav: first lazy spawn of codebase index"
|
||||
);
|
||||
}
|
||||
Some((handle, was_newly_started))
|
||||
}
|
||||
|
||||
pub(super) fn indexed_roots_for(&self, cwd: &std::path::Path) -> Vec<String> {
|
||||
if self.get_codebase_index(cwd).is_some() {
|
||||
return vec![cwd.to_string_lossy().into_owned()];
|
||||
}
|
||||
if let Ok(git_root) = kigi_workspace::session::git::find_git_root_from_path(cwd)
|
||||
&& self.get_codebase_index(&git_root).is_some()
|
||||
{
|
||||
return vec![git_root.to_string_lossy().into_owned()];
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Returns `(handle, was_newly_started)` — the bool is the authoritative
|
||||
/// "did this call spawn a new actor?" bit from `CodebaseIndexManager::get_or_create`.
|
||||
pub(super) fn get_or_create_codebase_index(
|
||||
&self,
|
||||
cwd: PathBuf,
|
||||
) -> (std::sync::Arc<kigi_codebase_graph::IndexManagerHandle>, bool) {
|
||||
self.codebase_indexes.lock().get_or_create(cwd)
|
||||
}
|
||||
|
||||
/// Get an existing codebase index for the given cwd.
|
||||
/// Returns None if no index exists for this cwd.
|
||||
pub fn get_codebase_index(
|
||||
&self,
|
||||
cwd: &std::path::Path,
|
||||
) -> Option<std::sync::Arc<kigi_codebase_graph::IndexManagerHandle>> {
|
||||
self.codebase_indexes.lock().get(cwd)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user