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,218 @@
//! Differential test: the anstyle-parse-based `split_into_line_segments`
//! against a reference copy of the previous termwiz-based implementation.
//!
//! Production code uses `anstyle-parse` for ANSI line splitting. This test
//! embeds the prior termwiz-based splitter as a reference and asserts
//! identical observable output so the rewrite cannot drift. `termwiz` is a
//! dev-dependency only (powers this test; not linked into shipped binaries).
//!
//! Inputs are restricted to what the production splitter actually sees:
//! complete escape sequences (the old implementation `debug_assert`ed on
//! trailing incomplete ones) and no raw C1 controls encoded as UTF-8 (termwiz
//! maps e.g. U+0085 to a control action while VTE prints it; that corner was
//! unspecified before and is not exercised by terminal output we render).
use kigi_ratatui_inline::split_into_line_segments;
// ─── Reference: the previous termwiz-based implementation, verbatim ────────
struct RefSegment<'a> {
content: &'a str,
ends_with_crlf: bool,
}
fn reference_split<'a>(input: &'a str, term_width: usize) -> Vec<RefSegment<'a>> {
use termwiz::escape::{Action, ControlCode, parser::Parser};
use unicode_width::UnicodeWidthChar as _;
let mut parser = Parser::new();
let mut remaining_bytes = input.as_bytes();
let mut segments = Vec::<RefSegment>::new();
let mut segment_start = 0_usize;
let mut segment_end = 0_usize;
let mut visual_width = 0_usize;
let mut has_visual = false;
let mut prev_is_cr = false;
macro_rules! push_segment {
($end:expr, $crlf:expr) => {
#[allow(unused_assignments)]
{
segments.push(RefSegment {
content: &input[segment_start..$end],
ends_with_crlf: $crlf,
});
visual_width = 0;
has_visual = false;
}
};
}
while let Some((ansi_action, consumed)) = parser.parse_first(remaining_bytes) {
remaining_bytes = &remaining_bytes[consumed..];
let mut is_cr = false;
match ansi_action {
Action::Control(ControlCode::LineFeed) => {
push_segment!(segment_end - usize::from(prev_is_cr), true);
segment_end += consumed;
segment_start = segment_end;
}
Action::Control(ControlCode::CarriageReturn) => {
segment_end += consumed;
visual_width = 0;
is_cr = true;
}
Action::Print(ch) => {
let char_width = ch.width().unwrap_or(0);
let new_width = visual_width + char_width;
if new_width > term_width && has_visual {
push_segment!(segment_end, false);
segment_start = segment_end;
segment_end += consumed;
visual_width = char_width;
has_visual = true;
if char_width > term_width {
push_segment!(segment_end, false);
segment_start = segment_end;
}
} else {
segment_end += consumed;
visual_width = new_width;
has_visual = true;
}
}
Action::PrintString(_) => unreachable!(),
_ => {
segment_end += consumed;
}
}
prev_is_cr = is_cr;
}
assert!(remaining_bytes.is_empty(), "{remaining_bytes:?}");
if segment_end > segment_start {
let input_start = input.as_ptr();
if let Some(last) = segments.last_mut() {
let last_start = last.content.as_ptr();
let last_end = unsafe { last_start.add(last.content.len()) };
if !last.ends_with_crlf && !has_visual {
assert_eq!(segment_start, (last_end as usize - input_start as usize));
let last_offset = last_start as usize - input_start as usize;
last.content = &input[last_offset..];
} else {
push_segment!(segment_end, false);
}
} else {
push_segment!(segment_end, false);
}
}
segments
}
// ─── Comparison harness ─────────────────────────────────────────────────────
#[track_caller]
fn assert_same(input: &str, widths: &[usize]) {
for &width in widths {
let actual = split_into_line_segments(input, width);
let expected = reference_split(input, width);
let actual_view: Vec<(&str, bool)> = actual
.iter()
.map(|s| (s.content, s.ends_with_crlf))
.collect();
let expected_view: Vec<(&str, bool)> = expected
.iter()
.map(|s| (s.content, s.ends_with_crlf))
.collect();
assert_eq!(
actual_view, expected_view,
"divergence for width {width}, input: {input:?}"
);
}
}
const WIDTHS: &[usize] = &[1, 2, 3, 5, 8, 10, 20, 80, 200];
#[test]
fn corpus_matches_reference() {
let corpus: &[&str] = &[
"",
"hello",
"hello world, this is a longer line that will wrap several times",
"line1\nline2\nline3",
"line1\r\nline2\r\n",
"12345\r67",
"\r\r\n\n\r",
"😊😊😊 emoji wall 😊😊😊",
"hello 你好 混合 width",
"\x1b[31mred\x1b[0m plain \x1b[1;32;44mstyled\x1b[m",
"\x1b[31m\x1b[1m\x1b[4mnested styles no text\x1b[0m",
"12345678\x1b[0m90",
"text\x1b]8;;https://example.com\x07link\x1b]8;;\x07 after",
"osc title\x1b]0;window title\x07body",
"cursor \x1b[2Amoves \x1b[10;20H everywhere",
"tab\tand\x08backspace and \x07bell",
"\x1b[38;5;196mext colors\x1b[38;2;10;20;30m truecolor\x1b[0m",
"interrupted \x1b[3\nmid-sequence",
"\x1b[31m\nstyle then newline",
"trailing style 12345678\x1b[0m",
"\x1b(Bcharset\x1b)0 escapes",
"zero\u{200b}width\u{fe0f}chars",
"combining a\u{0301}e\u{0301} accents",
];
for input in corpus {
assert_same(input, WIDTHS);
}
}
/// Deterministic pseudo-random ANSI soup (xorshift, no extra deps).
#[test]
fn randomized_ansi_soup_matches_reference() {
let mut state = 0x243F_6A88_85A3_08D3_u64; // seed: pi digits
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
const PIECES: &[&str] = &[
"word",
"a",
"longer-token",
" ",
" ",
"\n",
"\r",
"\r\n",
"😊",
"你好",
"é",
"\u{200b}",
"\x1b[31m",
"\x1b[0m",
"\x1b[1;44;38;5;10m",
"\x1b[2K",
"\x1b[10D",
"\x1b]0;title\x07",
"\x1b]8;;http://x\x07",
"\t",
"\x07",
];
for _ in 0..2000 {
let mut input = String::new();
let len = (next() % 30) as usize;
for _ in 0..len {
input.push_str(PIECES[(next() % PIECES.len() as u64) as usize]);
}
let width = 1 + (next() % 40) as usize;
assert_same(&input, &[width]);
}
}