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,278 @@
//! Replay-trace verification harness for the turn-end TodoGate.
//!
//! Reads synthetic JSON fixtures from `tests/fixtures/synthetic_*.json`,
//! walks each turn, and at every assistant `end-of-turn` snapshot asserts
//! that `evaluate_todo_gate` returns the decision the fixture declares.
//!
//! Pure-function integration test — no `SessionActor`, no completion
//! stream. Complements unit tests of the pure function with
//! data-driven trace-replay coverage.
//!
//! Data-driven: dropping a new `synthetic_*.json` fixture into
//! `tests/fixtures/` enrolls it in the harness automatically.
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use kigi_shell::session::{
CollectedTodoGateInput, TodoGateDecision, TodoGateReason, evaluate_todo_gate,
};
use kigi_shell::tools::todo::TodoStatus;
/// Closed canonical set of shipped fixtures. Adding a fixture
/// here is the one required Rust-side change when a new failure shape
/// is enrolled — `canonical_fixtures_present` asserts set equality
/// against this list so a missing-or-extra fixture fails the harness.
const CANONICAL_FIXTURES: &[&str] = &[
"synthetic_clean_completion.json",
"synthetic_pr_babysit_partial_backing.json",
"synthetic_stranded_narration.json",
];
#[derive(Debug, Deserialize)]
struct Fixture {
name: String,
#[allow(dead_code)] // human-readable; surfaced only on assertion failure
description: String,
turns: Vec<Turn>,
}
#[derive(Debug, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum Turn {
#[allow(dead_code)] // user turns are walked but never gate-evaluated
User(UserTurn),
Assistant(AssistantTurn),
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct UserTurn {
turn_index: usize,
content: String,
}
#[derive(Debug, Deserialize)]
struct AssistantTurn {
turn_index: usize,
#[allow(dead_code)] // present for fixture clarity; the gate does not consult it
tool_calls_emitted: Vec<serde_json::Value>,
todo_state_after_turn: Vec<TodoSnapshot>,
backing_task_count: usize,
expected_gate_decision: ExpectedGateDecision,
#[serde(default)]
expected_reason: Option<ExpectedReason>,
#[serde(default)]
expected_reminder_contains: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct TodoSnapshot {
#[allow(dead_code)] // id is preserved for fixture readability
id: String,
status: TodoStatus,
content: String,
}
/// Typed mirror of the fixture's `expected_gate_decision` field. A
/// closed enum (not `String`) catches typos at deserialize time rather
/// than silently passing the wrong assertion branch.
#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
enum ExpectedGateDecision {
Nudge,
Continue,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
enum ExpectedReason {
InFlight,
}
impl From<TodoGateReason> for ExpectedReason {
fn from(reason: TodoGateReason) -> Self {
match reason {
TodoGateReason::InFlight => Self::InFlight,
}
}
}
fn fixtures_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
/// Every `synthetic_*.json` in `tests/fixtures/`, sorted for stable
/// output. Directory-iteration IO errors panic with diagnostic context
/// rather than being silently dropped (a permissioned-out fixture must
/// not vanish from the set unnoticed).
fn discover_fixtures() -> Vec<PathBuf> {
let dir = fixtures_dir();
let entries = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("read fixtures dir {}: {e}", dir.display()));
let mut paths: Vec<PathBuf> = entries
.map(|entry| {
entry
.unwrap_or_else(|e| panic!("read entry in {}: {e}", dir.display()))
.path()
})
.filter(|path| {
path.file_name()
.and_then(|n| n.to_str())
.is_some_and(|name| name.starts_with("synthetic_") && name.ends_with(".json"))
})
.collect();
paths.sort();
assert!(
!paths.is_empty(),
"no synthetic_*.json fixtures found in {}",
dir.display()
);
paths
}
fn load_fixture(path: &Path) -> Fixture {
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
serde_json::from_slice(&bytes).unwrap_or_else(|e| panic!("parse {}: {e}", path.display()))
}
/// Consume a fixture's assistant-turn snapshot into a
/// `CollectedTodoGateInput`. Moves todo `content` strings out of the
/// snapshot — no clones. Calling `as_input()` on the result also
/// re-exercises the production "first N in_progress are backed"
/// partition heuristic.
fn collected_from(
snapshots: Vec<TodoSnapshot>,
backing_task_count: usize,
) -> CollectedTodoGateInput {
let todos = snapshots
.into_iter()
.map(|t| (t.id, t.content, t.status))
.collect();
CollectedTodoGateInput {
todos,
backing_task_count,
}
}
/// Run the gate against one assistant turn. `fixture_name` is only used
/// in panic messages so failures point straight at the offending JSON.
fn check_assistant_turn(fixture_name: &str, turn: AssistantTurn) {
let AssistantTurn {
turn_index,
tool_calls_emitted: _,
todo_state_after_turn,
backing_task_count,
expected_gate_decision,
expected_reason,
expected_reminder_contains,
} = turn;
let collected = collected_from(todo_state_after_turn, backing_task_count);
let input = collected.as_input();
let decision = evaluate_todo_gate(&input);
match (expected_gate_decision, decision) {
(ExpectedGateDecision::Continue, TodoGateDecision::Continue) => {
assert!(
expected_reason.is_none(),
"fixture {fixture_name} turn {turn_index} declares `continue` with \
`expected_reason` — a continue decision has no reason",
);
assert!(
expected_reminder_contains.is_empty(),
"fixture {fixture_name} turn {turn_index} declares `continue` with \
`expected_reminder_contains` — a continue decision emits no reminder",
);
}
(ExpectedGateDecision::Nudge, TodoGateDecision::Nudge { reminder, reason }) => {
if let Some(expected) = expected_reason {
assert_eq!(
expected,
ExpectedReason::from(reason),
"fixture {fixture_name} turn {turn_index}: gate reason mismatch",
);
}
for needle in &expected_reminder_contains {
assert!(
reminder.contains(needle.as_str()),
"fixture {fixture_name} turn {turn_index}: reminder missing substring \
{needle:?}.\nFull reminder:\n{reminder}",
);
}
}
(expected, TodoGateDecision::Continue) => {
panic!("fixture {fixture_name} turn {turn_index}: expected {expected:?}, got Continue",)
}
(expected, TodoGateDecision::Nudge { reason, .. }) => panic!(
"fixture {fixture_name} turn {turn_index}: expected {expected:?}, got Nudge({reason:?})",
),
}
}
#[test]
fn replay_all_synthetic_fixtures() {
for path in discover_fixtures() {
let fixture = load_fixture(&path);
let Fixture {
name,
description: _,
turns,
} = fixture;
let mut saw_assistant = false;
for turn in turns {
match turn {
Turn::User(_) => {}
Turn::Assistant(at) => {
saw_assistant = true;
check_assistant_turn(&name, at);
}
}
}
assert!(
saw_assistant,
"fixture {name} ({}) has no assistant turns to evaluate",
path.display(),
);
}
}
/// Set-equality guard: the on-disk fixture set must exactly equal
/// [`CANONICAL_FIXTURES`]. Adding a fixture without updating the
/// constant — or losing one — fails the harness. Closes the
/// "open-ended presence check" gap.
#[test]
fn canonical_fixtures_match_disk() {
let actual: BTreeSet<String> = discover_fixtures()
.iter()
.map(|p| {
p.file_name()
.and_then(|n| n.to_str())
.unwrap_or_else(|| panic!("non-UTF8 fixture path: {}", p.display()))
.to_string()
})
.collect();
let expected: BTreeSet<String> = CANONICAL_FIXTURES
.iter()
.map(|s| (*s).to_string())
.collect();
assert_eq!(actual, expected, "fixture set drift vs CANONICAL_FIXTURES");
}
/// Compile-time guard: `ExpectedGateDecision` must stay a closed
/// two-variant enum so a fixture typo fails to load instead of
/// silently passing the wrong branch.
#[test]
fn expected_decision_is_closed() {
let parsed: ExpectedGateDecision = serde_json::from_str(r#""nudge""#).unwrap();
assert_eq!(parsed, ExpectedGateDecision::Nudge);
let parsed: ExpectedGateDecision = serde_json::from_str(r#""continue""#).unwrap();
assert_eq!(parsed, ExpectedGateDecision::Continue);
let err = serde_json::from_str::<ExpectedGateDecision>(r#""maybe""#).unwrap_err();
assert!(
err.to_string().contains("unknown variant"),
"expected unknown-variant error, got: {err}"
);
}