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,242 @@
//! E2E: a submitted prompt is durably recorded in the per-CWD
//! `prompt_history.jsonl` and survives quitting the TUI — via a fast double
//! Ctrl+C (the reported repro, recalled after a `--continue` resume) and via a
//! real OS SIGINT routed through the same graceful quit.
//!
//! Drives the real pager binary through a PTY against the shared mock
//! inference server (isolated `$HOME`), exercising the full
//! pager -> shell -> queue_input -> append path plus the graceful-quit teardown.
//!
//! Coverage note: both paths wait for the turn to land before quitting, so
//! `queue_input` (and its now-awaited append) has already run. This is an
//! end-to-end durability + recall check, not a probe of the old detached-append
//! race — that race is closed structurally by awaiting the append in
//! `queue_input` and is covered by the `prompt_history` unit test. The
//! deterministic regression catch here is the SIGINT path exiting 0 (pre-fix it
//! was `process::exit(130)`).
//!
//! ```bash
//! cargo test -p kigi-pager-pty-harness --test prompt_history_durable_quit \
//! -- --ignored --nocapture
//! ```
use std::path::{Path, PathBuf};
use std::time::Duration;
use anyhow::{Context, Result, bail};
use kigi_pager_pty_harness::{ContentController, PtyHarness, keys, pager_binary};
const ROWS: u16 = 50;
const COLS: u16 = 120;
const CANARY: &str = "REGRESSIONCANARY42";
#[cfg(unix)]
const SIGINT_CANARY: &str = "SIGINTCANARY7";
const ACK: &str = "ACKSENTINEL";
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore] // opt-in: spawns the real pager binary in a PTY (CI runs with --ignored)
async fn prompt_history_durable_after_double_ctrl_c_and_recallable_on_resume() {
run().await.expect("prompt-history durable-quit e2e");
}
/// A real OS SIGINT (not an injected Ctrl+C key byte) must route through the
/// same graceful quit: the prompt stays durable and the process exits 0.
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore] // opt-in: spawns the real pager binary in a PTY (CI runs with --ignored)
async fn prompt_history_durable_after_real_sigint_graceful_quit() {
run_sigint().await.expect("sigint graceful-quit e2e");
}
async fn run() -> Result<()> {
let content = ContentController::start()
.await
.context("start mock server")?;
content.set_response(format!("{ACK} acknowledged."));
let project = tempfile::tempdir().context("project dir")?;
std::fs::create_dir_all(project.path().join(".git")).context("create .git")?;
let binary = pager_binary().context("resolve pager binary")?;
// 1) Submit a prompt and let the turn settle (proves the shell reached
// queue_input, where the append happens), then quit with double Ctrl+C.
let mut first = submit_and_settle(&binary, &content, project.path(), CANARY)
.context("submit canary in first pager")?;
// Double Ctrl+C: first arms the quit confirmation on the empty prompt, the
// second confirms -> same graceful Action::Quit as `/exit`.
let pre = first.raw_output().len();
first.inject_keys(keys::CTRL_C).context("ctrl-c arm")?;
first.update(Duration::from_millis(250));
first.inject_keys(keys::CTRL_C).context("ctrl-c confirm")?;
// Drain output until the child exits so the post-`pre` suffix holds the full
// graceful teardown (incl. the show-cursor restore) for the assertions below.
first.update(Duration::from_secs(10));
let code = first.wait_exit_code(Duration::from_secs(10));
assert_eq!(
code,
Some(0),
"double Ctrl+C should exit via the graceful quit (exit 0), got {code:?}"
);
assert!(
terminal_restored(&first, pre),
"terminal not restored (no show-cursor after quit) on the double-Ctrl+C path"
);
drop(first);
// 2) Durability: the prompt must be on disk after the quit.
assert_prompt_durable(content.home(), CANARY)?;
// 3) Recall across restart: `--continue` resumes the session and replays it.
let mut resumed = PtyHarness::spawn_with_content_in_dir(
&binary,
ROWS,
COLS,
&content,
&["--continue"],
Some(project.path()),
)
.context("spawn resumed pager")?;
resumed
.wait_for_text(CANARY, Duration::from_secs(20))
.context("resumed session replayed the prior prompt")?;
assert!(
!resumed.contains_text("panicked"),
"pager panicked on resume:\n{}",
resumed.screen_contents()
);
// Up-arrow opens the history overlay; smoke-check it doesn't crash and the
// recalled prompt stays reachable.
resumed.inject_keys(keys::UP).context("press Up")?;
resumed.update(Duration::from_millis(500));
assert!(
resumed.contains_text(CANARY),
"canary not reachable after Up:\n{}",
resumed.screen_contents()
);
assert!(
!resumed.contains_text("panicked"),
"pager panicked after Up:\n{}",
resumed.screen_contents()
);
resumed.quit().context("quit resumed pager")?;
Ok(())
}
/// Real-SIGINT variant of [`run`]: deliver an OS signal to the pager child and
/// assert the same graceful-quit outcome (durable prompt, clean exit).
#[cfg(unix)]
async fn run_sigint() -> Result<()> {
let content = ContentController::start()
.await
.context("start mock server")?;
content.set_response(format!("{ACK} acknowledged."));
let project = tempfile::tempdir().context("project dir")?;
std::fs::create_dir_all(project.path().join(".git")).context("create .git")?;
let binary = pager_binary().context("resolve pager binary")?;
let mut first = submit_and_settle(&binary, &content, project.path(), SIGINT_CANARY)
.context("submit canary before SIGINT")?;
// A real SIGINT, not an injected 0x03 key byte (raw mode delivers that as a
// key event — the double-Ctrl+C path above), drives the OS-signal path.
let pre = first.raw_output().len();
first.send_signal(libc::SIGINT).context("send SIGINT")?;
// Drain output until the child exits so the post-`pre` suffix holds the full
// graceful teardown (incl. the show-cursor restore) for the assertions below.
first.update(Duration::from_secs(10));
// Pre-fix the SIGINT handler called std::process::exit(130); routing it
// through the graceful quit exits 0 — the deterministic Part-B regression catch.
let code = first.wait_exit_code(Duration::from_secs(10));
assert_eq!(
code,
Some(0),
"real SIGINT should route through the graceful quit (exit 0), got {code:?}"
);
assert!(
terminal_restored(&first, pre),
"terminal not restored (no show-cursor after SIGINT) on the SIGINT path"
);
drop(first);
// Durability: the prompt must be on disk despite the signal-driven quit.
assert_prompt_durable(content.home(), SIGINT_CANARY)?;
Ok(())
}
/// Spawn the pager in `project`, submit `canary`, then wait for the turn to
/// render + settle to idle. Waiting past `queue_input` is deliberate: it
/// guarantees the prompt reached the shell (so the durability assertion is
/// meaningful), at the cost of not reproducing the sub-millisecond
/// detached-append race (closed by awaiting the append; see the unit test).
fn submit_and_settle(
binary: &Path,
content: &ContentController,
project: &Path,
canary: &str,
) -> Result<PtyHarness> {
let mut pager =
PtyHarness::spawn_with_content_in_dir(binary, ROWS, COLS, content, &[], Some(project))
.context("spawn pager")?;
pager
.wait_for_text("Quit", Duration::from_secs(20))
.context("welcome screen")?;
pager
.inject_keys(canary.as_bytes())
.context("type canary")?;
pager.inject_keys(keys::ENTER).context("submit prompt")?;
pager
.wait_for_text(ACK, Duration::from_secs(30))
.context("turn response rendered")?;
pager.update(Duration::from_millis(1000)); // let the short turn finish (idle)
Ok(pager)
}
/// Whether the pager emitted the show-cursor restore (`ESC [ ?25h`) after byte
/// offset `since`. Scanning only the post-quit suffix avoids matching the
/// show-cursor that normal rendering emits mid-session.
fn terminal_restored(h: &PtyHarness, since: usize) -> bool {
const SHOW_CURSOR: &[u8] = b"\x1b[?25h";
let raw = h.raw_output();
raw[since.min(raw.len())..]
.windows(SHOW_CURSOR.len())
.any(|w| w == SHOW_CURSOR)
}
/// Assert the per-CWD `prompt_history.jsonl` durably recorded `canary`.
fn assert_prompt_durable(home: &Path, canary: &str) -> Result<()> {
let hist = find_prompt_history(home).context("locate prompt_history.jsonl")?;
let body =
std::fs::read_to_string(&hist).with_context(|| format!("read {}", hist.display()))?;
eprintln!("[e2e] prompt_history.jsonl @ {}:\n{body}", hist.display());
assert!(
body.contains(canary),
"prompt_history.jsonl is missing the submitted prompt after the quit:\n{body}"
);
Ok(())
}
/// The per-CWD history file lives at `<home>/.kigi/sessions/<enc-cwd>/prompt_history.jsonl`.
fn find_prompt_history(home: &Path) -> Result<PathBuf> {
let root = home.join(".kigi").join("sessions");
for cwd_ent in std::fs::read_dir(&root).with_context(|| format!("read {}", root.display()))? {
let cwd_ent = cwd_ent?;
if !cwd_ent.file_type()?.is_dir() {
continue;
}
let candidate = cwd_ent.path().join("prompt_history.jsonl");
if candidate.is_file() {
return Ok(candidate);
}
}
bail!("no prompt_history.jsonl found under {}", root.display())
}