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,188 @@
|
||||
#![allow(
|
||||
unused_imports,
|
||||
unused_variables,
|
||||
unused_mut,
|
||||
unreachable_code,
|
||||
dead_code
|
||||
)]
|
||||
//! Core workspace library: FS, VCS, permissions, tool config, and subsystem wiring.
|
||||
pub mod activity;
|
||||
pub mod capability;
|
||||
pub mod channel;
|
||||
pub mod config;
|
||||
pub mod daemonize;
|
||||
pub mod diag_server;
|
||||
pub mod discovery;
|
||||
pub mod envrc;
|
||||
pub mod error;
|
||||
pub mod file_system;
|
||||
pub mod folder_trust;
|
||||
pub mod foreign_sessions;
|
||||
pub mod fs_notify;
|
||||
pub mod handle;
|
||||
pub mod hub;
|
||||
pub mod hub_auth;
|
||||
pub mod hub_channel;
|
||||
pub mod hub_ids;
|
||||
pub mod hub_server;
|
||||
pub mod mcp;
|
||||
pub mod permission;
|
||||
pub mod preview_supervisor;
|
||||
pub mod project_config;
|
||||
pub mod recovery;
|
||||
pub mod rpc_envelope;
|
||||
pub mod session;
|
||||
pub mod status_config;
|
||||
pub use status_config::StatusConfig;
|
||||
pub mod trust;
|
||||
pub mod util;
|
||||
pub mod workspace_ops;
|
||||
pub mod worktree;
|
||||
pub use capability::CapabilityMode;
|
||||
pub use channel::{TransportCallResult, TransportContext, TransportError, TransportNotification};
|
||||
pub use config::{
|
||||
AgentSessionConfig, DEFAULT_EVENT_BUFFER_CAPACITY, HookSourceConfig, IsolationMode,
|
||||
MemoryConfig, SessionContextFactory, SessionTerminalBackend, WorkspaceConfig,
|
||||
};
|
||||
pub use error::{WorkspaceError, WorkspaceResult};
|
||||
pub use file_system::*;
|
||||
pub use handle::{
|
||||
DrainOutcome, DrainReason, WorkspaceHandle, connect_local_workspace, resolve_workspace_home,
|
||||
termination_grace_from_env,
|
||||
};
|
||||
pub use hub::HubConfig;
|
||||
pub use kigi_hunk_tracker::HunkTrackerHandle;
|
||||
pub use kigi_workspace_client::WorkspaceClient;
|
||||
pub use kigi_workspace_types::WorkspaceEvent;
|
||||
pub use permission::*;
|
||||
pub use session::{WorkspaceSession, WorkspaceShared};
|
||||
pub use session::{file_state, git, jj};
|
||||
pub use workspace_ops::{WorkspaceOp, WorkspaceOps};
|
||||
/// Zero-init every workspace metric family so idle panels render a `0` baseline
|
||||
/// instead of "No data". Idempotent; call once at workspace-server startup.
|
||||
pub fn init_metrics() {
|
||||
handle::init_metrics();
|
||||
session::swap_policy::init_metrics();
|
||||
permission::init_metrics();
|
||||
hub_server::init_metrics();
|
||||
}
|
||||
/// Crate-wide lock serializing every test that mutates the process-global
|
||||
/// environment (`KIGI_SHARE_DIR`, `HOME`, …). nextest isolates each test in its own
|
||||
/// process, but `cargo test --lib` shares ONE process across threads, so
|
||||
/// per-module locks don't serialize cross-module — a peer test in another module
|
||||
/// can clobber `KIGI_SHARE_DIR` mid-test. A single shared lock (used by every
|
||||
/// env-mutating test module) is required for that single-process run to be
|
||||
/// race-free.
|
||||
#[cfg(test)]
|
||||
pub(crate) static ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
/// Crate-shared RAII guard for a single process env var in tests: sets (or
|
||||
/// unsets) it on construction and restores the prior value on drop. The ONE
|
||||
/// generic env-var guard for the whole crate (replaces the per-module copies).
|
||||
///
|
||||
/// Hold it together with [`ENV_TEST_LOCK`] for the test's lifetime, acquiring
|
||||
/// the lock FIRST so it drops LAST — the env restore (this guard) then runs
|
||||
/// before the lock releases, so no peer test observes the temporary value.
|
||||
#[cfg(test)]
|
||||
pub(crate) struct TestEnvGuard {
|
||||
key: &'static str,
|
||||
prev: Option<std::ffi::OsString>,
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl TestEnvGuard {
|
||||
/// Set `key` to `val`, restoring the prior value on drop.
|
||||
pub(crate) fn set(key: &'static str, val: &std::path::Path) -> Self {
|
||||
let prev = std::env::var_os(key);
|
||||
unsafe { std::env::set_var(key, val) };
|
||||
Self { key, prev }
|
||||
}
|
||||
/// Unset `key`, restoring the prior value on drop.
|
||||
pub(crate) fn unset(key: &'static str) -> Self {
|
||||
let prev = std::env::var_os(key);
|
||||
unsafe { std::env::remove_var(key) };
|
||||
Self { key, prev }
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl Drop for TestEnvGuard {
|
||||
fn drop(&mut self) {
|
||||
match self.prev.take() {
|
||||
Some(prev) => unsafe { std::env::set_var(self.key, prev) },
|
||||
None => unsafe { std::env::remove_var(self.key) },
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Holds [`ENV_TEST_LOCK`] AND a set of [`TestEnvGuard`]s as ONE value, so a
|
||||
/// test (or fixture) can return/bind it however it likes and still be correct.
|
||||
///
|
||||
/// Struct fields drop in DECLARATION order, so `_env` (declared first) restores
|
||||
/// every env var BEFORE `_lock` (declared last) releases the lock — making the
|
||||
/// "restore before unlock" invariant compile-enforced, not convention-dependent
|
||||
/// (a single `let _ = LockedTestEnv::lock()…;` binding can't reorder it).
|
||||
///
|
||||
/// Acquire the lock first via [`lock`](Self::lock), then mutate env under it with
|
||||
/// the chained [`set`](Self::set) builder.
|
||||
#[cfg(test)]
|
||||
pub(crate) struct LockedTestEnv {
|
||||
_env: Vec<TestEnvGuard>,
|
||||
_lock: std::sync::MutexGuard<'static, ()>,
|
||||
}
|
||||
#[cfg(test)]
|
||||
impl LockedTestEnv {
|
||||
/// Acquire [`ENV_TEST_LOCK`] (held until this value drops).
|
||||
pub(crate) fn lock() -> Self {
|
||||
Self {
|
||||
_env: Vec::new(),
|
||||
_lock: ENV_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner()),
|
||||
}
|
||||
}
|
||||
/// Set `key` to `val` under the held lock, restoring the prior value on drop.
|
||||
///
|
||||
/// Intended for DISTINCT keys; the restore order across repeated `set`s of
|
||||
/// the SAME key is unspecified (guards restore in insertion order).
|
||||
pub(crate) fn set(mut self, key: &'static str, val: &std::path::Path) -> Self {
|
||||
self._env.push(TestEnvGuard::set(key, val));
|
||||
self
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod init_metrics_tests {
|
||||
/// `init_metrics()` is idempotent (a double call must not panic on
|
||||
/// re-register) and populates a `0` baseline series for each family so
|
||||
/// panels render `0` instead of "No data".
|
||||
#[test]
|
||||
fn init_metrics_is_idempotent_and_registers_baselines() {
|
||||
super::init_metrics();
|
||||
super::init_metrics();
|
||||
let families = prometheus::gather();
|
||||
let has = |name: &str, want: &[(&str, &str)]| {
|
||||
families
|
||||
.iter()
|
||||
.filter(|mf| mf.name() == name)
|
||||
.flat_map(|mf| mf.get_metric())
|
||||
.any(|m| {
|
||||
want.iter().all(|(k, v)| {
|
||||
m.get_label()
|
||||
.iter()
|
||||
.any(|l| l.name() == *k && l.value() == *v)
|
||||
})
|
||||
})
|
||||
};
|
||||
assert!(has(
|
||||
"grok_workspace_rpc_requests_total",
|
||||
&[("method", "unknown"), ("result", "error")]
|
||||
));
|
||||
assert!(has(
|
||||
"grok_workspace_drain_started_total",
|
||||
&[("reason", "sigterm")]
|
||||
));
|
||||
assert!(has(
|
||||
"grok_workspace_toolset_swap_rejected_total",
|
||||
&[("reason", "turn_active"), ("trigger", "update_tool_config")]
|
||||
));
|
||||
assert!(
|
||||
families
|
||||
.iter()
|
||||
.any(|mf| mf.name() == "grok_workspace_permission_timeout_total")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user