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:
2026-07-17 05:31:01 -04:00
commit d6c20fc13f
2612 changed files with 1353757 additions and 0 deletions
@@ -0,0 +1,142 @@
//! Shared take/apply for `[[version_overrides]]` / `[[campaigns]]` arrays.
use serde::de::DeserializeOwned;
use crate::deep_merge_toml;
pub type PatchPath = &'static [&'static str];
#[derive(Debug, Clone)]
pub struct ConfigOverrideEntry<M> {
pub meta: M,
pub patch: toml::Table,
}
/// Strip `key` from the root table; each element is `M` + remaining keys as patch.
pub fn take_patch_array<M>(
config: &mut toml::Value,
key: &str,
) -> Result<Vec<ConfigOverrideEntry<M>>, toml::de::Error>
where
M: DeserializeOwned,
{
let Some(table) = config.as_table_mut() else {
return Ok(Vec::new());
};
let Some(array_value) = table.remove(key) else {
return Ok(Vec::new());
};
#[derive(serde::Deserialize)]
struct FlatEntry<M> {
#[serde(flatten)]
meta: M,
#[serde(flatten)]
patch: toml::Table,
}
let entries: Vec<FlatEntry<M>> = array_value.try_into()?;
Ok(entries
.into_iter()
.map(|e| ConfigOverrideEntry {
meta: e.meta,
patch: e.patch,
})
.collect())
}
/// Whether `patch` affects the value at `path`: it sets a value there (any leaf
/// under it counts), **or** it sets a non-table ancestor — deep-merge replaces
/// the whole subtree in that case, so every leaf beneath is touched (a patch
/// like `models = "oops"` wipes `models.default` and must still be dismissable
/// / flagged as driving it).
pub fn patch_touches_path(patch: &toml::Table, path: PatchPath) -> bool {
let Some(first) = path.first() else {
return false;
};
let Some(mut cur) = patch.get(*first) else {
return false;
};
for seg in path.iter().skip(1) {
match cur.as_table() {
Some(t) => match t.get(*seg) {
Some(v) => cur = v,
None => return false,
},
// Non-table ancestor: the merge replaces this subtree wholesale.
None => return true,
}
}
true
}
/// Whether `patch` touches any of `paths`.
pub fn patch_touches_any(patch: &toml::Table, paths: &[PatchPath]) -> bool {
paths.iter().any(|p| patch_touches_path(patch, p))
}
/// Keys stripped from every applied patch so an override can't re-introduce a
/// nested `version_overrides`/`campaigns` array (recursive re-injection). This
/// const owns the recursive-injection keys for every override kind; [`apply_patches`]
/// takes the strip list as a parameter so the strip step itself stays key-agnostic.
pub const PATCH_STRIP_KEYS: &[&str] = &["version_overrides", "campaigns"];
/// Deep-merge each patch in iteration order (later wins on a leaf), stripping
/// `strip_keys` from every patch first.
pub fn apply_patches(
config: &mut toml::Value,
patches: impl IntoIterator<Item = toml::Table>,
strip_keys: &[&str],
) {
for mut patch in patches {
for key in strip_keys {
patch.remove(*key);
}
deep_merge_toml(config, &toml::Value::Table(patch));
}
}
#[cfg(test)]
mod tests {
use super::*;
fn table(s: &str) -> toml::Table {
toml::from_str(s).unwrap()
}
/// A patch that replaces a parent table with a scalar (`models = "oops"`)
/// wipes every leaf beneath it on merge, so it must count as touching those
/// leaves — otherwise the campaign that destroyed `models.default` would be
/// neither dismissable nor flagged as driving the field.
#[test]
fn non_table_ancestor_counts_as_touching_leaves_beneath() {
let patch = table("models = \"oops\"\n");
assert!(patch_touches_path(&patch, &["models", "default"]));
assert!(patch_touches_path(&patch, &["models"]));
// Sibling sections are unaffected.
assert!(!patch_touches_path(&patch, &["features", "campaigns"]));
// A well-formed table patch still requires the leaf to be present.
let tbl = table("[models]\ndefault = \"m\"\n");
assert!(patch_touches_path(&tbl, &["models", "default"]));
assert!(!patch_touches_path(&tbl, &["models", "other"]));
}
#[test]
fn apply_patches_strips_requested_keys() {
let mut cfg = toml::Value::Table(table("[models]\ndefault = \"old\"\n"));
let patch = table("[models]\ndefault = \"new\"\n");
apply_patches(&mut cfg, std::iter::once(patch), PATCH_STRIP_KEYS);
assert_eq!(cfg["models"]["default"].as_str(), Some("new"));
// Top-level strip keys are removed before merge.
let mut cfg2 = toml::Value::Table(toml::Table::new());
let mut p = toml::Table::new();
p.insert("version_overrides".into(), toml::Value::Array(vec![]));
p.insert("campaigns".into(), toml::Value::Array(vec![]));
p.insert("keep".into(), toml::Value::Boolean(true));
apply_patches(&mut cfg2, std::iter::once(p), PATCH_STRIP_KEYS);
assert!(cfg2.get("version_overrides").is_none());
assert!(cfg2.get("campaigns").is_none());
assert_eq!(cfg2["keep"].as_bool(), Some(true));
}
}