diff --git a/crates/build/kigi-proto-build/src/find_protoc.rs b/crates/build/kigi-proto-build/src/find_protoc.rs
index 8c605df..a77b1c8 100644
--- a/crates/build/kigi-proto-build/src/find_protoc.rs
+++ b/crates/build/kigi-proto-build/src/find_protoc.rs
@@ -24,22 +24,14 @@ fn is_github_actions() -> bool {
env::var_os("GITHUB_ACTIONS").is_some()
}
-/// Find `protoc` command.
+/// Locate `protoc`.
///
-/// Search order:
-/// 1. `$PROTOC` environment variable (set by Bazel `build_script_env` or user override)
-/// 2. `bin/protoc` walking up parent directories (dotslash wrapper for local dev)
-/// 3. `protoc` on `$PATH` (system install or other tooling)
-///
-/// When `bin/protoc` exists but fails to execute (e.g. the dotslash wrapper running
-/// in Bazel remote execution where `dotslash` is not installed), the error is not fatal —
-/// we fall through to the PATH-based lookup instead.
-///
-/// Returns `Ok(None)` if not found and not in a strict environment (GitHub Actions).
+/// Search order: `$PROTOC`, then `bin/protoc` walking parents (dotslash
+/// wrapper), then `$PATH`. A non-executable `bin/protoc` (e.g. dotslash
+/// missing under Bazel remote execution) is non-fatal — lookup continues
+/// on `$PATH`. Returns `Ok(None)` when missing outside GitHub Actions.
pub fn find_protoc() -> anyhow::Result> {
- // 1. Check the PROTOC env var first. This is the standard override used by prost-build
- // and is set by Bazel cargo_build_script build_script_env to point at a hermetic
- // protoc binary instead of the dotslash wrapper.
+ // `$PROTOC` is the prost-build override; Bazel sets it to a hermetic binary.
if let Ok(protoc_env) = env::var("PROTOC") {
let protoc = PathBuf::from(&protoc_env);
if protoc.try_exists()? {
@@ -48,20 +40,17 @@ pub fn find_protoc() -> anyhow::Result > {
}
}
- // 2. Walk up directories looking for bin/protoc (dotslash wrapper).
let cwd = env::current_dir()?;
let mut dir = cwd.clone();
let mut dir_rel = PathBuf::new();
loop {
- // Return relative path to make build more deterministic.
+ // Relative path keeps cargo rerun fingerprints stable across machines.
let protoc = dir_rel.join("bin/protoc");
if protoc.try_exists()? {
match check_protoc_good(&protoc) {
Ok(()) => return Ok(Some(protoc)),
Err(e) => {
- // bin/protoc exists but can't execute — likely the dotslash wrapper
- // in an environment without dotslash (e.g. Bazel remote execution).
- // Fall through to PATH-based lookup below.
+ // Dotslash wrapper present but not runnable — try PATH next.
eprintln!(
"bin/protoc found at `{}` but failed to execute: {e:#}; \
trying protoc from PATH as fallback",
@@ -77,12 +66,10 @@ pub fn find_protoc() -> anyhow::Result > {
dir_rel.push("..");
}
- // 3. Try protoc from PATH (system install or other tooling).
if check_protoc_good(Path::new("protoc")).is_ok() {
return Ok(Some(PathBuf::from("protoc")));
}
- // 4. Not found anywhere.
if is_github_actions() {
return Err(anyhow::anyhow!(
"`protoc` not found (checked $PROTOC env, bin/protoc, and PATH)"
diff --git a/crates/build/kigi-proto-build/src/lib.rs b/crates/build/kigi-proto-build/src/lib.rs
index 9584dfa..63cc43f 100644
--- a/crates/build/kigi-proto-build/src/lib.rs
+++ b/crates/build/kigi-proto-build/src/lib.rs
@@ -5,22 +5,16 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::{env, fs, iter};
-/// Find the protoc well-known types include directory.
+/// Resolve protoc's well-known-types include dir (`../include` next to `bin/protoc`).
///
-/// When PROTOC is set (e.g., in Bazel), the include directory is typically
-/// at `../include` relative to the `bin/protoc` binary. For example:
-/// - PROTOC = `/path/to/external/protoc_linux_x86_64/bin/protoc`
-/// - Include = `/path/to/external/protoc_linux_x86_64/include`
-///
-/// This is needed because Bazel places the protoc binary and include files
-/// in separate locations within the sandbox, and protoc doesn't automatically
-/// find them without an explicit -I flag.
+/// Bazel keeps the binary and includes in separate sandbox paths; protoc will
+/// not find them without an explicit `-I`.
fn find_protoc_include_dir(protoc: Option<&Path>) -> Option {
let protoc = protoc?;
- // protoc is typically at .../bin/protoc, so include is at .../include
- let parent = protoc.parent()?; // .../bin
- let grandparent = parent.parent()?; // .../
+ // Layout: `.../bin/protoc` → sibling `.../include`.
+ let parent = protoc.parent()?;
+ let grandparent = parent.parent()?;
let include_dir = grandparent.join("include");
if include_dir.is_dir() {
@@ -72,10 +66,8 @@ impl XaiProtoBuilder {
self
}
- /// Serialize JSON using the original proto field names (snake_case) instead
- /// of the proto3-JSON default (camelCase). Deserialization still accepts
- /// both casings, so this is backward-compatible with already-stored
- /// camelCase documents.
+ /// Emit JSON with original proto field names (snake_case) instead of
+ /// proto3-JSON camelCase. Deserialization still accepts both casings.
pub fn pbjson_preserve_proto_field_names(mut self) -> Self {
self.pbjson_preserve_proto_field_names = true;
self
@@ -93,10 +85,8 @@ impl XaiProtoBuilder {
self.map_builder(|b| b.field_attribute(path, attr))
}
- // tonic-build generation of `rerun-if-changed` is lazy and incorrect.
- // - everything is invalidated when anything inside include directories is changed
- // - also they compute paths incorrectly: assuming paths are relative to current directory
- // rather than
+ // tonic-build's `rerun-if-changed` is lazy and wrong: any include-dir
+ // touch invalidates everything, and paths are treated as CWD-relative.
fn emit_rerun_if_changed<'a>(
protoc: Option<&Path>,
protoc_include_dir: Option<&Path>,
@@ -112,11 +102,9 @@ impl XaiProtoBuilder {
);
}
- // Can only process one input file when using --dependency_out=FILE.
- // Both protoc outputs go to real files: /dev/stdout and /dev/null do
- // not exist on Windows (the release build failed on exactly this).
- // OUT_DIR is always set for build scripts; deterministic names make
- // reruns overwrite instead of accumulate.
+ // `--dependency_out` accepts one input per invocation. Write real
+ // files (not /dev/stdout|/dev/null — missing on Windows). OUT_DIR
+ // names stay stable so reruns overwrite rather than accumulate.
let scratch_dir = env::var_os("OUT_DIR")
.map(PathBuf::from)
.unwrap_or_else(env::temp_dir);
@@ -135,9 +123,7 @@ impl XaiProtoBuilder {
descriptor_file.display()
));
- // Add protoc's well-known types include directory first (if found).
- // This is needed for Bazel sandboxed builds where protoc and its
- // include files are in different locations.
+ // Well-known types first so Bazel sandboxes resolve them.
if let Some(include_dir) = protoc_include_dir {
command.arg(format!(
"-I{}",
@@ -162,9 +148,8 @@ impl XaiProtoBuilder {
let output = fs::read_to_string(&dep_file)
.with_context(|| format!("read protoc dependency file {}", dep_file.display()))?;
- // Make-style `.d` format: `: dep1 dep2 …`.
- // Compare with normalized separators — protoc may spell the
- // target path with forward slashes even on Windows.
+ // Make-style `.d`: `: dep1 dep2 …`.
+ // Normalize separators — protoc may emit `/` even on Windows.
let mut lines = output.lines();
let first_line = lines.next().context("protoc dependency output is empty")?;
let normalized_first = first_line.replace('\\', "/");
@@ -179,9 +164,7 @@ impl XaiProtoBuilder {
for line in iter::once(rem).chain(lines) {
let line = line.trim();
let line = line.strip_suffix("\\").unwrap_or(line);
- // Depending on absolute paths like
- // /Users/user/homebrew/Cellar/protobuf/29.1/include/google/protobuf/timestamp.proto
- // is valid, but we want to have output more deterministic.
+ // Skip host-absolute well-known includes so fingerprints stay portable.
if line.contains("/include/google/protobuf/") {
continue;
}
@@ -224,14 +207,10 @@ impl XaiProtoBuilder {
let protoc = find_protoc::find_protoc()?;
- // Use fixed version of `protoc` binary.
if let Some(protoc) = &protoc {
config.protoc_executable(protoc);
}
- // Find the protoc's well-known types include directory.
- // This is needed for Bazel sandboxed builds where protoc and its
- // include files are placed in different sandbox locations.
let protoc_include_dir = find_protoc_include_dir(protoc.as_deref());
let mut builder = builder.emit_rerun_if_changed(false);
@@ -256,8 +235,7 @@ impl XaiProtoBuilder {
None
};
- // Build the full includes list, prepending the protoc include directory
- // if found (for well-known types like google/protobuf/timestamp.proto).
+ // Prepend protoc includes so well-known types resolve under Bazel.
let all_includes: Vec<&Path> = protoc_include_dir
.as_deref()
.into_iter()
diff --git a/crates/codegen/kigi-acp-lib/src/channel.rs b/crates/codegen/kigi-acp-lib/src/channel.rs
index 314f5e4..8c4b9a9 100644
--- a/crates/codegen/kigi-acp-lib/src/channel.rs
+++ b/crates/codegen/kigi-acp-lib/src/channel.rs
@@ -78,7 +78,8 @@ mod acp_send_failure_tests {
#[tokio::test]
async fn send_failed_when_receiver_dropped_before_send() {
let (tx, rx) = mpsc::unbounded_channel::();
- drop(rx); // no peer listening -> enqueue fails
+ // no peer listening -> enqueue fails
+ drop(rx);
let err = acp_send(ext_request(), &tx).await.unwrap_err();
assert_eq!(
acp_channel_failure(&err),
diff --git a/crates/codegen/kigi-acp-lib/src/common.rs b/crates/codegen/kigi-acp-lib/src/common.rs
index bcf3fe1..721b69f 100644
--- a/crates/codegen/kigi-acp-lib/src/common.rs
+++ b/crates/codegen/kigi-acp-lib/src/common.rs
@@ -20,24 +20,24 @@ pub fn acp_internal_error(message: impl Into) -> acp::Error {
/// The two distinct ways an [`acp_send`](crate::acp_send) round-trip can fail
/// when the underlying channel is closed. Both surface as a JSON-RPC
-/// `INTERNAL_ERROR` (so existing callers and the wire format are unaffected);
-/// this typed discriminant — carried in the error's `data` — lets callers tell
-/// them apart WITHOUT substring-matching the human-readable `message`.
+/// `INTERNAL_ERROR`; this typed discriminant — carried in the error's `data` —
+/// lets callers tell them apart without substring-matching the human-readable
+/// `message`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AcpChannelFailure {
- /// The request could not be ENQUEUED: the receiver half (the peer's
+ /// The request could not be enqueued: the receiver half (the peer's
/// connection task) is already gone, so no peer is listening — e.g. a
/// headless run with no client wired.
SendFailed,
- /// The request was enqueued but the RESPONSE channel was dropped before a
+ /// The request was enqueued but the response channel was dropped before a
/// reply arrived: a peer received the request, then went away (disconnect /
/// process exit) without answering.
RecvFailed,
}
impl AcpChannelFailure {
- /// `data` object key under which [`acp_send`](crate::acp_send) records the
- /// kind. Namespaced so it can never collide with other `with_data` payloads.
+ /// `data` object key under which the kind is recorded. Namespaced so it can
+ /// never collide with other `with_data` payloads.
const DATA_KEY: &'static str = "xaiAcpChannelFailure";
const fn tag(self) -> &'static str {
@@ -58,8 +58,8 @@ impl AcpChannelFailure {
/// Build the channel-closed error for [`acp_send`](crate::acp_send), tagging it
/// with a typed [`AcpChannelFailure`] discriminant in `data`. The error `code`
-/// stays `INTERNAL_ERROR`, so this is purely additive for callers that just
-/// propagate the error.
+/// stays `INTERNAL_ERROR` so callers that merely propagate the error are
+/// unaffected.
pub(crate) fn acp_channel_failure_error(
message: impl Into,
kind: AcpChannelFailure,
@@ -68,8 +68,8 @@ pub(crate) fn acp_channel_failure_error(
}
/// Recover the [`AcpChannelFailure`] kind from an error, or `None` if the error
-/// did not originate from [`acp_send`](crate::acp_send)'s channel-closed paths
-/// (or predates the tag). Consumers use this instead of inspecting `message`.
+/// did not originate from [`acp_send`](crate::acp_send)'s channel-closed paths.
+/// Consumers use this instead of inspecting `message`.
pub fn acp_channel_failure(err: &acp::Error) -> Option {
err.data
.as_ref()
@@ -78,10 +78,9 @@ pub fn acp_channel_failure(err: &acp::Error) -> Option {
.and_then(AcpChannelFailure::from_tag)
}
-/// Compact single-line JSON for gateway debug traces. Plain (uncolored)
-/// output: this feeds `tracing::debug!`, which typically lands in log files
-/// where ANSI colors are noise. Replaces the former `colored_json`-backed
-/// `color_json` (dropped to shrink the shipped dependency tree).
+/// Compact single-line JSON for gateway debug traces. Output is uncolored: it
+/// feeds `tracing::debug!`, which typically lands in log files where ANSI
+/// escapes are noise.
#[doc(hidden)]
pub fn compact_json(value: &T) -> String {
serde_json::to_string(value).unwrap_or_default()
diff --git a/crates/codegen/kigi-acp-lib/src/gateway.rs b/crates/codegen/kigi-acp-lib/src/gateway.rs
index 23a797c..8258377 100644
--- a/crates/codegen/kigi-acp-lib/src/gateway.rs
+++ b/crates/codegen/kigi-acp-lib/src/gateway.rs
@@ -613,7 +613,8 @@ mod tests {
})
.collect();
- // Gate-open point; then concurrent producer emits live updates.
+ // Phase 2: a concurrent producer emits live updates while the
+ // replay completions are still draining.
let live_sender = sender.clone();
let producer = tokio::task::spawn_local(async move {
for i in 0..LIVE {
@@ -624,15 +625,14 @@ mod tests {
}
});
- // Drain replay completions while producer runs.
for rx in completions {
let _ = rx.await;
}
- // Mark response boundary.
log.borrow_mut().push("RESPONSE".into());
- // Let producer and gateway finish remaining live updates.
+ // Give the producer and the gateway loop room to flush the
+ // remaining live updates before inspecting the log.
let _ = producer.await;
for _ in 0..LIVE + 5 {
tokio::task::yield_now().await;
@@ -644,7 +644,6 @@ mod tests {
.position(|s| s == "RESPONSE")
.expect("RESPONSE marker must be in the log");
- // (1) Delta notifications are all present and before RESPONSE.
for i in 0..DELTA {
let tag = format!("delta-{i}");
let pos = log
@@ -657,7 +656,6 @@ mod tests {
);
}
- // (2) Delta notifications preserve enqueue order.
let delta_positions: Vec = (0..DELTA)
.map(|i| log.iter().position(|s| s == &format!("delta-{i}")).unwrap())
.collect();
@@ -670,7 +668,6 @@ mod tests {
);
}
- // (3) No live updates are lost.
for i in 0..LIVE {
let tag = format!("live-{i}");
assert!(
@@ -679,7 +676,6 @@ mod tests {
);
}
- // (4) Live updates do not precede replay delta.
let last_delta = *delta_positions.last().unwrap();
for i in 0..LIVE {
let tag = format!("live-{i}");
diff --git a/crates/codegen/kigi-acp-lib/src/line_reader.rs b/crates/codegen/kigi-acp-lib/src/line_reader.rs
index 0e21703..556bf90 100644
--- a/crates/codegen/kigi-acp-lib/src/line_reader.rs
+++ b/crates/codegen/kigi-acp-lib/src/line_reader.rs
@@ -126,7 +126,7 @@ impl AsyncRead for LineBufferedRead {
Poll::Ready(Ok(n))
}
Poll::Ready(Some(Err(e))) => Poll::Ready(Err(e)),
- Poll::Ready(None) => Poll::Ready(Ok(0)), // EOF
+ Poll::Ready(None) => Poll::Ready(Ok(0)),
Poll::Pending => Poll::Pending,
}
}
@@ -146,7 +146,7 @@ async fn read_line_capped(
let (consumed, done) = {
let available = reader.fill_buf().await?;
if available.is_empty() {
- return Ok(buf.len()); // EOF
+ return Ok(buf.len());
}
match available.iter().position(|&b| b == b'\n') {
Some(pos) => {
@@ -283,15 +283,12 @@ mod tests {
let mut reader = LineBufferedRead::spawn_local(source);
let mut small_buf = [0u8; 3];
- // First read: "abc"
let n = reader.read(&mut small_buf).await.unwrap();
assert_eq!(&small_buf[..n], b"abc");
- // Second read: "def"
let n = reader.read(&mut small_buf).await.unwrap();
assert_eq!(&small_buf[..n], b"def");
- // Third read: "\n"
let n = reader.read(&mut small_buf).await.unwrap();
assert_eq!(&small_buf[..n], b"\n");
diff --git a/crates/codegen/kigi-acp-lib/src/message.rs b/crates/codegen/kigi-acp-lib/src/message.rs
index 4de28c2..38e0308 100644
--- a/crates/codegen/kigi-acp-lib/src/message.rs
+++ b/crates/codegen/kigi-acp-lib/src/message.rs
@@ -26,16 +26,20 @@ pub trait AcpSide {
/// Marker type representing the agent's view of the ACP connection (as one side of that connection).
impl AcpSide for acp::AgentSide {
- type InMessage = AcpAgentMessage; // inbound messages = messages meant *for* the agent
- type OutMessage = AcpClientMessage; // outbound messages = messages meant *for* the client
+ // inbound messages = messages meant *for* the agent
+ type InMessage = AcpAgentMessage;
+ // outbound messages = messages meant *for* the client
+ type OutMessage = AcpClientMessage;
type OtherSide = acp::ClientSide;
const NAME: &'static str = "agent";
}
/// Marker type representing the agent's view of the ACP connection (as one side of that connection).
impl AcpSide for acp::ClientSide {
- type InMessage = AcpClientMessage; // inbound messages = messages meant *for* the client
- type OutMessage = AcpAgentMessage; // outbound messages = messages meant *for* the agent
+ // inbound messages = messages meant *for* the client
+ type InMessage = AcpClientMessage;
+ // outbound messages = messages meant *for* the agent
+ type OutMessage = AcpAgentMessage;
type OtherSide = acp::AgentSide;
const NAME: &'static str = "client";
}
@@ -241,7 +245,8 @@ mod client {
pub fn route_to_client(
self,
- client: impl acp::Client + 'static, // note: acp::Client is auto-implemented for Rc/Arc
+ // note: acp::Client is auto-implemented for Rc/Arc
+ client: impl acp::Client + 'static,
spawn: impl Fn(LocalBoxFuture<'static, ()>) + 'static,
) {
match self {
@@ -540,7 +545,8 @@ mod agent {
pub fn route_to_agent(
self,
- agent: impl acp::Agent + 'static, // note: acp::Agent is auto-implemented for Rc/Arc
+ // note: acp::Agent is auto-implemented for Rc/Arc
+ agent: impl acp::Agent + 'static,
spawn: impl Fn(LocalBoxFuture<'static, ()>) + 'static,
) {
match self {
diff --git a/crates/codegen/kigi-acp-lib/src/normalize.rs b/crates/codegen/kigi-acp-lib/src/normalize.rs
index 02ebc3c..8a3cde5 100644
--- a/crates/codegen/kigi-acp-lib/src/normalize.rs
+++ b/crates/codegen/kigi-acp-lib/src/normalize.rs
@@ -33,7 +33,7 @@
/// `\u2028` and surrogate pairs in text).
///
/// Any line that fails both parses passes through byte-identical —
-/// deliberately: the acp crate keeps ownership of garbage handling.
+/// Deliberately: the acp crate keeps ownership of garbage handling.
pub(crate) fn normalize_json_line(line: Vec) -> Vec {
if !line.windows(2).any(|w| w == br"\/") {
return line;
diff --git a/crates/codegen/kigi-acp-lib/src/stdin_reader.rs b/crates/codegen/kigi-acp-lib/src/stdin_reader.rs
index bc64df3..8e64476 100644
--- a/crates/codegen/kigi-acp-lib/src/stdin_reader.rs
+++ b/crates/codegen/kigi-acp-lib/src/stdin_reader.rs
@@ -138,7 +138,8 @@ fn isolate_process_stdin() -> Option {
use std::os::windows::io::FromRawHandle as _;
// Win32 constants (inlined to avoid a dependency).
- const STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6; // (DWORD)-10
+ // (DWORD)-10
+ const STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6;
const DUPLICATE_SAME_ACCESS: u32 = 0x0000_0002;
const GENERIC_READ: u32 = 0x8000_0000;
const FILE_SHARE_READ: u32 = 0x0000_0001;
@@ -186,7 +187,8 @@ fn isolate_process_stdin() -> Option {
process,
&mut duplicate,
0,
- 0, // not inheritable
+ // not inheritable
+ 0,
DUPLICATE_SAME_ACCESS,
) == 0
{
diff --git a/crates/codegen/kigi-agent-lifecycle/src/local/contributors/command.rs b/crates/codegen/kigi-agent-lifecycle/src/local/contributors/command.rs
index 854624e..1a12f17 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/local/contributors/command.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/local/contributors/command.rs
@@ -4,8 +4,9 @@ use crate::send::contributors::command::{
CommandAction, CommandContributor, CommandInvocation, CommandSpec,
};
-/// `?Send` twin of [`CommandContributor`] for single-threaded hosts like kigi build's TUI agent, whose session state is `Rc`/`RefCell`-based and can
-/// never satisfy the `Send` bounds the send flavor bakes into its boxed hook futures.
+/// `?Send` twin of [`CommandContributor`] for single-threaded hosts like kigi build's TUI agent,
+/// whose session state is `Rc`/`RefCell`-based and can never satisfy the `Send` bounds the send
+/// flavor bakes into its boxed hook futures.
#[async_trait(?Send)]
pub trait LocalCommandContributor {
fn advertised_commands(&self) -> Vec;
@@ -14,7 +15,8 @@ pub trait LocalCommandContributor {
-> Result;
}
-/// Send contributors work in single-threaded hosts as-is, so shared logic implements [`CommandContributor`] once and both hosts can register it.
+/// Send contributors work in single-threaded hosts as-is, so shared logic implements
+/// [`CommandContributor`] once and both hosts can register it.
#[async_trait(?Send)]
impl LocalCommandContributor for T {
fn advertised_commands(&self) -> Vec {
diff --git a/crates/codegen/kigi-agent-lifecycle/src/local/contributors/session_lifecycle.rs b/crates/codegen/kigi-agent-lifecycle/src/local/contributors/session_lifecycle.rs
index 99d5b86..8261cff 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/local/contributors/session_lifecycle.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/local/contributors/session_lifecycle.rs
@@ -5,7 +5,8 @@ use crate::send::contributors::session_lifecycle::{SessionIdleInput, SessionLife
/// `?Send` twin of [`SessionLifecycleContributor`].
#[async_trait(?Send)]
pub trait LocalSessionLifecycleContributor {
- /// Fired when the session settles idle (no running turn or queued work); the host owns the check.
+ /// Fired when the session settles idle (no running turn or queued work); the host owns the
+ /// check.
async fn on_session_idle(&self, _input: &SessionIdleInput) {}
}
diff --git a/crates/codegen/kigi-agent-lifecycle/src/local/contributors/turn_input.rs b/crates/codegen/kigi-agent-lifecycle/src/local/contributors/turn_input.rs
index 82ffcfe..26fcbca 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/local/contributors/turn_input.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/local/contributors/turn_input.rs
@@ -4,8 +4,9 @@ use crate::send::contributors::turn_input::{
TurnInputContext, TurnInputContributor, TurnInputFragment,
};
-/// `?Send` twin of [`TurnInputContributor`] for single-threaded hosts like kigi build's TUI agent, whose session state is `Rc`/`RefCell`-based
-/// and can never satisfy the `Send` bounds the send flavor bakes into its boxed hook futures.
+/// `?Send` twin of [`TurnInputContributor`] for single-threaded hosts like kigi build's TUI agent,
+/// whose session state is `Rc`/`RefCell`-based and can never satisfy the `Send` bounds the send
+/// flavor bakes into its boxed hook futures.
#[async_trait(?Send)]
pub trait LocalTurnInputContributor {
async fn contribute_turn_input(&self, _input: &TurnInputContext) -> Vec {
@@ -13,7 +14,8 @@ pub trait LocalTurnInputContributor {
}
}
-/// Send contributors are usable in single-threaded hosts as-is, so shared logic implements [`TurnInputContributor`] once for both hosts.
+/// Send contributors are usable in single-threaded hosts as-is, so shared logic implements
+/// [`TurnInputContributor`] once for both hosts.
#[async_trait(?Send)]
impl LocalTurnInputContributor for T {
async fn contribute_turn_input(&self, input: &TurnInputContext) -> Vec {
diff --git a/crates/codegen/kigi-agent-lifecycle/src/local/registry.rs b/crates/codegen/kigi-agent-lifecycle/src/local/registry.rs
index c79e59c..aabb62a 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/local/registry.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/local/registry.rs
@@ -6,7 +6,6 @@ use crate::local::contributors::{
LocalTurnLifecycleContributor,
};
-/// Mutable registry used while hosts register typed runtime contributions.
#[derive(Default)]
pub struct LocalExtensionRegistryBuilder {
turn_lifecycle_contributors: Vec>,
@@ -67,7 +66,6 @@ impl LocalExtensionRegistryBuilder {
}
}
-/// Immutable typed registry produced after extensions are installed.
#[derive(Default)]
pub struct LocalExtensionRegistry {
turn_lifecycle_contributors: Vec>,
@@ -94,7 +92,6 @@ impl LocalExtensionRegistry {
&self.command_contributors
}
- /// The one contributor owning `name`, or `None` when no extension advertised it.
pub fn command_handler(&self, name: &str) -> Option<&Rc> {
self.command_handlers.get(name)
}
diff --git a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/command.rs b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/command.rs
index 69f1cda..e07e2cf 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/command.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/command.rs
@@ -10,7 +10,8 @@ pub struct CommandSpec {
/// A parsed `/name args` invocation. The host owns parsing and routes it to the command's one owner.
pub struct CommandInvocation<'a> {
pub name: &'a str,
- pub args: &'a str, // Whitespace-trimmed; empty for a bare `/name`.
+ // Whitespace-trimmed; empty for a bare `/name`.
+ pub args: &'a str,
}
/// What a handled command does to the turn; rejections travel as the `Err` reason.
diff --git a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/session_lifecycle.rs b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/session_lifecycle.rs
index 16f3289..3ae3a59 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/session_lifecycle.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/session_lifecycle.rs
@@ -1,10 +1,9 @@
use async_trait::async_trait;
-/// Input supplied when the host observes the session settling idle.
pub struct SessionIdleInput;
#[async_trait]
pub trait SessionLifecycleContributor: Send + Sync {
- /// Fired when the session settles idle (no running turn or queued work); the host owns the check.
+ /// Idle means no running turn and no queued work; the host owns that check.
async fn on_session_idle(&self, _input: &SessionIdleInput) {}
}
diff --git a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_input.rs b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_input.rs
index 5ba7841..d7448d2 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_input.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_input.rs
@@ -1,20 +1,17 @@
use async_trait::async_trait;
-/// Turn facts supplied when the host pulls extension input at its sampling chokepoint.
pub struct TurnInputContext {
- /// Stable host-owned turn identifier.
pub turn_id: String,
/// True when the harness produced the turn (auto-wake, drain, cron, continuation), not the user.
pub synthetic: bool,
}
-/// A model-visible input fragment contributed into the active turn. The host owns wrapping, origin stamping, and placement.
+/// Raw fragment text: the host owns wrapping, origin stamping, and placement.
pub struct TurnInputFragment {
pub text: String,
}
-/// Contributes model-visible input fragments into the active turn when the host pulls at its sampling chokepoint.
-/// Fragments land in the same turn, never a new one.
+/// Fragments land in the turn the host is already sampling, never a new one.
#[async_trait]
pub trait TurnInputContributor: Send + Sync {
async fn contribute_turn_input(&self, _input: &TurnInputContext) -> Vec {
diff --git a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_lifecycle.rs b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_lifecycle.rs
index 689f7ba..efab415 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_lifecycle.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/send/contributors/turn_lifecycle.rs
@@ -1,6 +1,5 @@
use async_trait::async_trait;
-/// Input supplied when the host starts a turn.
pub struct TurnStartInput {
/// True when the harness produced the turn (auto-wake, drain, cron, continuation), not the user.
pub synthetic: bool,
@@ -12,19 +11,16 @@ impl TurnStartInput {
}
}
-/// Input supplied when the host completes a turn.
pub struct TurnDoneInput;
-/// Why the host aborted the turn instead of completing it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TurnAbortReason {
/// The client went away mid-turn.
Disconnected,
- /// The user interrupted the turn before it completed.
+ /// The user cancelled mid-turn.
Interrupted,
}
-/// Input supplied when the host aborts a turn.
pub struct TurnAbortInput {
pub reason: TurnAbortReason,
}
@@ -35,7 +31,6 @@ impl TurnAbortInput {
}
}
-/// Input supplied when the host observes an error for a turn.
pub struct TurnErrorInput<'a> {
pub message: &'a str,
}
diff --git a/crates/codegen/kigi-agent-lifecycle/src/send/registry.rs b/crates/codegen/kigi-agent-lifecycle/src/send/registry.rs
index dd06e3e..9863eb5 100644
--- a/crates/codegen/kigi-agent-lifecycle/src/send/registry.rs
+++ b/crates/codegen/kigi-agent-lifecycle/src/send/registry.rs
@@ -5,7 +5,6 @@ use crate::send::contributors::{
CommandContributor, SessionLifecycleContributor, TurnInputContributor, TurnLifecycleContributor,
};
-/// Mutable registry used while hosts register typed runtime contributions.
#[derive(Default)]
pub struct ExtensionRegistryBuilder {
turn_lifecycle_contributors: Vec>,
@@ -38,8 +37,8 @@ impl ExtensionRegistryBuilder {
self.command_contributors.push(contributor);
}
- /// Routes each advertised command to its one owner. Duplicate names are a composition bug:
- /// first registration wins, panics in debug builds, logs in release.
+ /// Two extensions advertising one command name is a composition bug, so it trips a
+ /// `debug_assert`; release builds keep the first registration and log the loser.
pub fn build(self) -> ExtensionRegistry {
let mut command_handlers: HashMap> = HashMap::new();
for contributor in &self.command_contributors {
@@ -63,7 +62,6 @@ impl ExtensionRegistryBuilder {
}
}
-/// Immutable typed registry produced after extensions are installed.
#[derive(Default)]
pub struct ExtensionRegistry {
turn_lifecycle_contributors: Vec>,
@@ -90,7 +88,6 @@ impl ExtensionRegistry {
&self.command_contributors
}
- /// The one contributor owning `name`, or `None` when no extension advertised it.
pub fn command_handler(&self, name: &str) -> Option<&Arc> {
self.command_handlers.get(name)
}
diff --git a/crates/codegen/kigi-agent/src/agent.rs b/crates/codegen/kigi-agent/src/agent.rs
index 1473d3c..058af9b 100644
--- a/crates/codegen/kigi-agent/src/agent.rs
+++ b/crates/codegen/kigi-agent/src/agent.rs
@@ -77,7 +77,7 @@ impl Agent {
}
}
- // ── From definition ──────────────────────────────────────────────
+ // From definition
/// Agent name (unique identifier).
pub fn name(&self) -> &str {
@@ -99,14 +99,12 @@ impl Agent {
&self.definition.permission_mode
}
- /// Completion requirement, if any.
pub fn completion_requirement(&self) -> Option<&CompletionRequirement> {
self.definition.completion_requirement.as_ref()
}
- // ── Session-level ────────────────────────────────────────────────
+ // Session-level
- /// The rendered system prompt.
pub fn system_prompt(&self) -> &str {
&self.system_prompt
}
@@ -123,12 +121,10 @@ impl Agent {
&self.tool_bridge
}
- /// Compaction policy.
pub fn compaction_policy(&self) -> &CompactionPolicy {
&self.compaction_policy
}
- /// Reminder policy.
pub fn reminder_policy(&self) -> &ReminderPolicy {
&self.reminder_policy
}
@@ -216,7 +212,7 @@ impl Agent {
/// Does NOT rebuild the tool registry or re-render prompts.
/// Used for mid-session mode switching.
pub async fn update_policies_from_definition(&self, _def: &AgentDefinition) {
- // TODO: completion requirements and retry configs are now part of
+ // TODO: completion requirements and retry configs are part of
// ToolServerConfig and handled at registry finalization time.
// Mid-session policy updates are not yet supported in the new architecture.
}
diff --git a/crates/codegen/kigi-agent/src/compaction.rs b/crates/codegen/kigi-agent/src/compaction.rs
index ee7dbf5..e0100ce 100644
--- a/crates/codegen/kigi-agent/src/compaction.rs
+++ b/crates/codegen/kigi-agent/src/compaction.rs
@@ -1,24 +1,18 @@
//! Compaction policy — threshold, model, and memory flush configuration.
-/// Session-level compaction policy.
-///
-/// Controls when and how the session's conversation is compacted
-/// to free up context window space, and whether a memory flush
-/// runs before each compaction.
+/// Controls when and how the session's conversation is compacted to free up
+/// context window space, and whether a memory flush runs before each compaction.
#[derive(Debug, Clone)]
pub struct CompactionPolicy {
/// Percentage of context window that triggers auto-compaction.
- /// E.g., 85 means compact when 85% of the context window is used.
pub auto_compact_threshold_percent: u32,
- /// Model to use for generating the compaction summary.
- /// None = use the session's current model.
+ /// `None` uses the session's current model.
pub compact_model: Option,
- /// Whether to run a memory flush turn before each compaction.
- /// When enabled, the session actor asks the model to summarize
- /// important information from the conversation before it's compacted.
- /// Requires the memory system to be enabled.
+ /// Run a memory flush turn before each compaction: the session actor asks
+ /// the model to summarize important information from the conversation
+ /// before it is discarded. Requires the memory system to be enabled.
pub memory_flush_enabled: bool,
/// Per-compaction wall-clock budget (seconds); a generation exceeding it is
@@ -27,9 +21,9 @@ pub struct CompactionPolicy {
/// Prefire two-pass compaction: when usage approaches the threshold,
/// speculatively summarize the history prefix in the background (pass 1);
- /// at compaction, summarize NOTE₁ + the recent tail (pass 2). Resolved from
- /// config (`two_pass_compaction` flag) at session build; `false` keeps the
- /// legacy single-pass path. Default `false` (real sessions set it from config).
+ /// at compaction, summarize NOTE₁ + the recent tail (pass 2). `false`
+ /// selects the single-pass path. Real sessions resolve this from the
+ /// `two_pass_compaction` config flag at session build.
pub two_pass_enabled: bool,
}
diff --git a/crates/codegen/kigi-agent/src/config.rs b/crates/codegen/kigi-agent/src/config.rs
index fb5e09e..fff2cf2 100644
--- a/crates/codegen/kigi-agent/src/config.rs
+++ b/crates/codegen/kigi-agent/src/config.rs
@@ -1381,10 +1381,10 @@ impl AgentDefinition {
///
/// Used by the runtime turn-end TodoGate to gate firing on sessions
/// whose prompt actually references the rules the gate's reminder
- /// text invokes. The block has been removed from every built-in
- /// template, so this returns `false` unconditionally. Kept as a
- /// helper so the gate's call-site stays stable in case the block
- /// is reintroduced behind a future flag.
+ /// text invokes. No built-in template carries the block, so this
+ /// returns `false` unconditionally. Kept as a helper so the gate's
+ /// call-site stays stable in case the block is reintroduced behind
+ /// a future flag.
pub fn carries_task_completion_discipline(
&self,
_audience: crate::prompt::context::PromptAudience,
diff --git a/crates/codegen/kigi-agent/src/discovery.rs b/crates/codegen/kigi-agent/src/discovery.rs
index a51b32c..740e97c 100644
--- a/crates/codegen/kigi-agent/src/discovery.rs
+++ b/crates/codegen/kigi-agent/src/discovery.rs
@@ -39,7 +39,7 @@ pub fn project_agent_dirs_in(chain_dirs: &[PathBuf]) -> Vec {
crate::repo::existing_subdirs_along(chain_dirs, PROJECT_AGENT_SUBDIRS)
}
-// ── Subagent entry types ─────────────────────────────────────────────
+// Subagent entry types
/// A subagent entry for the Task tool description and spawn-time validation.
#[derive(Debug, Clone)]
@@ -61,7 +61,7 @@ pub enum SubagentSource {
UserDefined { scope: AgentScope },
}
-// ── all_subagents ────────────────────────────────────────────────────
+// all_subagents
/// Build the complete list of enabled subagents.
///
@@ -102,7 +102,6 @@ fn merge_subagents(
}
}
- // 1. Seed with built-in subagents
let mut entries: Vec = BuiltinAgentName::subagent_variants()
.iter()
.map(|b| {
@@ -117,7 +116,6 @@ fn merge_subagents(
})
.collect();
- // 2. Merge in discovered user-defined agents.
//
// IMPORTANT: Only project-level agents can shadow built-ins. This matches
// the runtime spawn precedence in by_name_in_cwd():
@@ -173,7 +171,6 @@ fn merge_subagents(
}
}
- // 3. Filter by toggle (omitted = enabled)
entries
.into_iter()
.filter(|e| toggle.get(&e.name).copied().unwrap_or(true))
@@ -359,7 +356,7 @@ fn source_from_agent_def(def: &AgentDefinition) -> ConfigSource {
}
}
-// ── Plugin-aware variants ─────────────────────────────────────────────
+// Plugin-aware variants
/// Build the complete list of enabled subagents, including plugin agents.
pub fn all_subagents_with_plugins(
@@ -1031,7 +1028,7 @@ mod tests {
assert_eq!(def.scope, AgentScope::BuiltIn);
}
- // ── all_subagents / merge_subagents tests ───────────────────────
+ // all_subagents / merge_subagents tests
/// Helper: build a minimal synthetic AgentDefinition for testing merge logic.
fn synthetic_agent(name: &str, desc: &str, scope: AgentScope) -> AgentDefinition {
@@ -1110,7 +1107,8 @@ mod tests {
AgentScope::Project,
)];
let entries = merge_subagents(discovered, &HashMap::new());
- assert_eq!(entries.len(), 4); // 3 built-ins + 1 user
+ // 3 built-ins + 1 user
+ assert_eq!(entries.len(), 4);
let cr = entries.iter().find(|e| e.name == "code-reviewer").unwrap();
assert_eq!(cr.description, "Reviews code");
assert_eq!(
@@ -1131,7 +1129,8 @@ mod tests {
)];
let toggle = HashMap::from([("code-reviewer".to_string(), false)]);
let entries = merge_subagents(discovered, &toggle);
- assert_eq!(entries.len(), 3); // only built-ins
+ // only built-ins
+ assert_eq!(entries.len(), 3);
assert!(entries.iter().all(|e| e.name != "code-reviewer"));
}
@@ -1143,7 +1142,8 @@ mod tests {
AgentScope::Project,
)];
let entries = merge_subagents(discovered, &HashMap::new());
- assert_eq!(entries.len(), 3); // still 3 — replaced, not appended
+ // still 3 — replaced, not appended
+ assert_eq!(entries.len(), 3);
let explore = entries.iter().find(|e| e.name == "explore").unwrap();
assert_eq!(explore.description, "Custom explore agent");
assert_eq!(
@@ -1180,7 +1180,8 @@ mod tests {
AgentScope::User,
)];
let entries = merge_subagents(discovered, &HashMap::new());
- assert_eq!(entries.len(), 3); // still 3 built-ins
+ // still 3 built-ins
+ assert_eq!(entries.len(), 3);
let explore = entries.iter().find(|e| e.name == "explore").unwrap();
// Should still be the built-in, not the user-level agent
assert!(
@@ -1215,7 +1216,8 @@ mod tests {
AgentScope::User,
)];
let entries = merge_subagents(discovered, &HashMap::new());
- assert_eq!(entries.len(), 4); // 3 built-ins + 1 user
+ // 3 built-ins + 1 user
+ assert_eq!(entries.len(), 4);
// Verify ordering: built-ins first, then user
assert!(matches!(&entries[0].source, SubagentSource::Builtin(_)));
assert!(matches!(&entries[1].source, SubagentSource::Builtin(_)));
@@ -1262,7 +1264,8 @@ mod tests {
// Simulate: discover() skips invalid files (returns empty for that file).
// So if a user's explore.md is invalid, discover() won't include it,
// and the built-in explore remains.
- let discovered = vec![]; // no valid user agents discovered
+ // no valid user agents discovered
+ let discovered = vec![];
let entries = merge_subagents(discovered, &HashMap::new());
assert_eq!(entries.len(), 3);
let explore = entries.iter().find(|e| e.name == "explore").unwrap();
diff --git a/crates/codegen/kigi-agent/src/error.rs b/crates/codegen/kigi-agent/src/error.rs
index 300533e..707a232 100644
--- a/crates/codegen/kigi-agent/src/error.rs
+++ b/crates/codegen/kigi-agent/src/error.rs
@@ -1,36 +1,29 @@
//! Error types for agent construction.
-/// Errors that can occur during Agent construction.
#[derive(Debug, thiserror::Error)]
pub enum AgentBuildError {
- /// Failed to parse the agent definition file (bad YAML frontmatter,
- /// missing closing `---`, or invalid Markdown structure).
+ /// Bad YAML frontmatter, a missing closing `---`, or invalid Markdown
+ /// structure in the definition file.
#[error("failed to parse agent definition: {0}")]
ParseError(String),
- /// Required fields are missing from the definition (name, description).
#[error("missing required field in agent definition: {0}")]
MissingField(String),
- /// A tool name override references a tool that doesn't exist in the
- /// registry (typo in the definition's `toolNameOverrides`).
+ /// Usually a typo in the definition's `toolNameOverrides`.
#[error("tool name override references nonexistent tool '{0}'")]
UnknownToolOverride(String),
- /// IO error during AGENTS.md or skills discovery.
#[error("IO error during agent construction: {0}")]
IoError(#[from] std::io::Error),
- /// MiniJinja template rendering failed (extend or full mode).
- /// Includes line numbers and context from the template.
+ /// Carries template line numbers and surrounding context.
#[error("template rendering error: {0}")]
MiniJinjaError(#[from] minijinja::Error),
- /// Tool registry error (e.g., unsatisfied requirements during finalization).
#[error("tool error: {0}")]
ToolError(String),
- /// A configuration value is present but invalid (e.g. `max_turns = 0`).
#[error("invalid configuration: {0}")]
InvalidConfig(String),
}
diff --git a/crates/codegen/kigi-agent/src/lib.rs b/crates/codegen/kigi-agent/src/lib.rs
index edad2a2..9a3443f 100644
--- a/crates/codegen/kigi-agent/src/lib.rs
+++ b/crates/codegen/kigi-agent/src/lib.rs
@@ -1,6 +1,5 @@
//! Agent builder, definition parsing, and system prompt assembly.
//!
-//! This crate extracts a first-class `Agent` type from `kigi-shell`.
//! An `Agent` bundles tools, system prompt, system-reminder policy,
//! compaction policy, and model configuration into a single, portable
//! object that any host can consume.
diff --git a/crates/codegen/kigi-agent/src/plugins/discovery.rs b/crates/codegen/kigi-agent/src/plugins/discovery.rs
index 177d143..051a6ff 100644
--- a/crates/codegen/kigi-agent/src/plugins/discovery.rs
+++ b/crates/codegen/kigi-agent/src/plugins/discovery.rs
@@ -21,7 +21,7 @@ use sha2::{Digest, Sha256};
use super::manifest::{ManifestLoadResult, PluginManifest, load_manifest, name_from_dirname};
use super::trust::TrustStore;
-// ── Public types ──────────────────────────────────────────────────────
+// Public types
/// Where a plugin was discovered from.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -204,12 +204,12 @@ impl DiscoveryConfig {
}
}
-// ── Discovery entry point ─────────────────────────────────────────────
+// Discovery entry point
/// User plugin directories in priority order: `$KIGI_SHARE_DIR/plugins` then
/// `~/.claude/plugins`.
///
-/// Unlike agent discovery, plugins are intentionally NOT discovered from a
+/// Unlike agent discovery, plugins are deliberately NOT discovered from a
/// legacy `~/.kigi/plugins`: plugin trust, persisted plugin-data, and install
/// paths all resolve under `kigi_home()`, so a plugin scanned from the legacy
/// tree would appear untrusted and lose its persisted state. Keeping plugins on
@@ -483,7 +483,7 @@ pub fn discover_plugins(
candidates
}
-// ── Internal helpers ──────────────────────────────────────────────────
+// Internal helpers
/// Scan a plugins parent directory (e.g. `~/.kigi/plugins/`) and collect
/// each subdirectory as a plugin candidate.
@@ -510,7 +510,8 @@ fn scan_plugin_dir(
let mut subdirs: Vec = entries
.filter_map(|e| e.ok())
- .filter(|e| e.path().is_dir()) // follows symlinks
+ // follows symlinks
+ .filter(|e| e.path().is_dir())
.map(|e| e.path())
.collect();
@@ -787,7 +788,7 @@ fn resolve_name_conflicts(candidates: &mut Vec) {
}
}
-// ── Compat installed_plugins.json types ───────────────────────────────
+// Compat installed_plugins.json types
/// Compat `installed_plugins.json` format.
#[derive(serde::Deserialize)]
@@ -1351,7 +1352,7 @@ mod tests {
let parts: Vec<&str> = id.0.split('/').collect();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0], "user");
- assert_eq!(parts[1].len(), 8); // 8 hex chars
+ assert_eq!(parts[1].len(), 8);
assert_eq!(parts[2], "my-plugin");
}
diff --git a/crates/codegen/kigi-agent/src/plugins/git_install.rs b/crates/codegen/kigi-agent/src/plugins/git_install.rs
index 0a4e28f..b0574a6 100644
--- a/crates/codegen/kigi-agent/src/plugins/git_install.rs
+++ b/crates/codegen/kigi-agent/src/plugins/git_install.rs
@@ -561,7 +561,7 @@ pub struct UpdateResult {
/// Status of an update attempt.
pub enum UpdateStatus {
- /// Repo was updated successfully.
+ /// Repo updated successfully.
Updated(UpdateResult),
/// Repo is pinned to a tag or commit — no automatic update.
Pinned { ref_name: String },
@@ -633,7 +633,7 @@ pub fn update_repo(repo_key: &str, repo: &InstalledRepo) -> Result,
- // ── Component path overrides (supplement convention dirs) ──────
+ // Component path overrides (supplement convention dirs)
#[serde(default)]
pub skills: Option,
#[serde(default)]
@@ -247,7 +247,7 @@ impl PluginManifest {
/// Log informational messages about manifest features.
///
- /// Called during discovery. Inline hooks and MCP servers are now
+ /// Called during discovery. Inline hooks and MCP servers are
/// fully supported; this method logs when they are detected.
pub fn warn_unsupported_features(&self, plugin_name: &str) {
if self.inline_hooks().is_some() {
@@ -287,7 +287,7 @@ fn resolve_dirs(
}
}
-// ── Manifest loading ──────────────────────────────────────────────────
+// Manifest loading
/// Manifest search order within a plugin directory.
const MANIFEST_PATHS: &[&str] = &[
@@ -375,7 +375,7 @@ pub fn normalize_inline_mcp_servers(value: &serde_json::Value) -> serde_json::Va
serde_json::json!({ "mcpServers": inner })
}
-// ── Errors ────────────────────────────────────────────────────────────
+// Errors
#[derive(Debug, thiserror::Error)]
pub enum ManifestError {
@@ -530,7 +530,8 @@ mod tests {
);
assert_eq!(
name_from_dirname(Path::new("/path/to/---")),
- None // all hyphens after trim
+ // all hyphens after trim
+ None
);
}
diff --git a/crates/codegen/kigi-agent/src/plugins/marketplace.rs b/crates/codegen/kigi-agent/src/plugins/marketplace.rs
index 2a3d452..543b04e 100644
--- a/crates/codegen/kigi-agent/src/plugins/marketplace.rs
+++ b/crates/codegen/kigi-agent/src/plugins/marketplace.rs
@@ -147,7 +147,7 @@ pub fn load_enabled_disabled_plugins(path: &Path) -> (Vec, Vec)
parse_enabled_disabled_plugins(&json)
}
-// ── Compat known_marketplaces.json ────────────────────────────────────
+// Compat known_marketplaces.json
/// Entry in `~/.claude/plugins/known_marketplaces.json`.
#[derive(serde::Deserialize)]
diff --git a/crates/codegen/kigi-agent/src/plugins/mod.rs b/crates/codegen/kigi-agent/src/plugins/mod.rs
index 4801670..82bc8f7 100644
--- a/crates/codegen/kigi-agent/src/plugins/mod.rs
+++ b/crates/codegen/kigi-agent/src/plugins/mod.rs
@@ -1,15 +1,9 @@
-//! Plugin system — discover, load, and manage plugins (including compat layouts).
+//! Plugin discovery, loading, and registry.
//!
//! A plugin is a self-contained directory that bundles skills, agents,
-//! MCP server configs, and hooks into a namespaced unit. Plugins can
+//! MCP server configs, and hooks into a namespaced unit. Plugins can
//! live under `~/.kigi/plugins/`, `.kigi/plugins/` (project-level),
//! or be passed via `--plugin-dir` on the CLI.
-//!
-//! This module handles:
-//! - `manifest` — parsing `plugin.json` manifests
-//! - `discovery` — scanning the filesystem for plugin directories
-//! - `trust` — project-plugin trust management
-//! - `registry` — in-memory registry of active plugins
pub mod discovery;
pub mod git_install;
diff --git a/crates/codegen/kigi-agent/src/plugins/registry.rs b/crates/codegen/kigi-agent/src/plugins/registry.rs
index 9dc5a20..742f2ac 100644
--- a/crates/codegen/kigi-agent/src/plugins/registry.rs
+++ b/crates/codegen/kigi-agent/src/plugins/registry.rs
@@ -301,7 +301,7 @@ impl PluginRegistry {
}
}
-// ── Shared handle for cross-thread reload ─────────────────────────────
+// Shared handle for cross-thread reload
/// Thread-safe handle for plugin registry lifecycle.
///
@@ -456,7 +456,7 @@ impl SharedPluginRegistryHandle {
}
}
-// ── Component counting helpers ────────────────────────────────────────
+// Component counting helpers
/// Collect the SKILL.md paths that load from the given skill dirs.
///
@@ -793,9 +793,11 @@ mod tests {
&["enabled-plugin".to_string()],
);
- assert_eq!(reg.len(), 2); // Both in registry
+ // Both in registry
+ assert_eq!(reg.len(), 2);
let active = reg.active_plugins();
- assert_eq!(active.len(), 1); // Only enabled one is active
+ // Only enabled one is active
+ assert_eq!(active.len(), 1);
assert_eq!(active[0].name, "enabled-plugin");
// Disabled one is in list but marked disabled
@@ -876,9 +878,12 @@ mod tests {
let reg = PluginRegistry::from_discovered(plugins, &[], &[]);
let list = reg.list();
- assert_eq!(list[0].name, "alpha"); // CliOverride = 0
- assert_eq!(list[1].name, "beta"); // Project = 1
- assert_eq!(list[2].name, "zebra"); // User = 2
+ // CliOverride = 0
+ assert_eq!(list[0].name, "alpha");
+ // Project = 1
+ assert_eq!(list[1].name, "beta");
+ // User = 2
+ assert_eq!(list[2].name, "zebra");
}
#[test]
@@ -935,7 +940,7 @@ mod tests {
assert_eq!(reg.mcp_server_owner("my-server"), Some("mcp-plugin"));
}
- // ── Combined disabled + untrusted scenarios ─────────────────
+ // Combined disabled + untrusted scenarios
#[test]
fn disabled_project_plugin_excluded_from_active_and_enabled() {
@@ -961,7 +966,7 @@ mod tests {
let bad = reg.get("bad-plugin").unwrap();
assert!(!bad.enabled);
- // trusted is now propagated from discovery (was false for Project scope)
+ // trusted is propagated from discovery (was false for Project scope)
assert!(!bad.trusted);
}
@@ -1166,7 +1171,7 @@ mod tests {
assert_eq!(config.disabled.len(), 2);
}
- // ── Security: trust propagation from discovery ──────────────
+ // Security: trust propagation from discovery
#[test]
fn untrusted_project_plugin_excluded_from_active_even_when_enabled() {
@@ -1174,12 +1179,14 @@ mod tests {
// pre-populated enabledPlugins) but NOT trusted. It must NOT
// appear in active_plugins() so its hooks never fire.
let plugins = vec![
- make_discovered("malicious", PluginScope::Project, false), // untrusted
+ // untrusted
+ make_discovered("malicious", PluginScope::Project, false),
];
let reg = PluginRegistry::from_discovered(
plugins,
&[],
- &["malicious".to_string()], // attacker got it into enabled list
+ // attacker got it into enabled list
+ &["malicious".to_string()],
);
// Plugin is enabled but not trusted
diff --git a/crates/codegen/kigi-agent/src/plugins/trust.rs b/crates/codegen/kigi-agent/src/plugins/trust.rs
index 490fd29..aefa69e 100644
--- a/crates/codegen/kigi-agent/src/plugins/trust.rs
+++ b/crates/codegen/kigi-agent/src/plugins/trust.rs
@@ -129,7 +129,8 @@ impl TrustStore {
})?;
if !self.trusted.remove(&canonical) {
- return Ok(()); // wasn't trusted
+ // wasn't trusted
+ return Ok(());
}
// Rewrite the entire file without the revoked path
@@ -176,7 +177,7 @@ impl TrustStore {
}
}
- // ── Internal ──────────────────────────────────────────────────────
+ // Internal
fn read_trust_file(path: &Path) -> HashSet {
let file = match std::fs::File::open(path) {
@@ -208,7 +209,7 @@ impl TrustStore {
}
}
-// ── Errors ────────────────────────────────────────────────────────────
+// Errors
#[derive(Debug, thiserror::Error)]
pub enum TrustError {
@@ -320,7 +321,8 @@ mod tests {
// This test checks the logic but can't easily mock $HOME.
// We verify the function exists and returns a boolean.
let result = TrustStore::is_config_path_auto_trusted(Path::new("/nonexistent/path"));
- assert!(!result); // nonexistent path can't be canonicalized
+ // nonexistent path can't be canonicalized
+ assert!(!result);
}
#[test]
diff --git a/crates/codegen/kigi-agent/src/prompt/agents_md.rs b/crates/codegen/kigi-agent/src/prompt/agents_md.rs
index 2bb5ad8..dfeec10 100644
--- a/crates/codegen/kigi-agent/src/prompt/agents_md.rs
+++ b/crates/codegen/kigi-agent/src/prompt/agents_md.rs
@@ -239,7 +239,7 @@ mod tests {
git2::Repository::init(path).unwrap();
}
- // ── find_agent_files unit tests ─────────────────────────────────
+ // find_agent_files unit tests
#[test]
fn find_agent_files_finds_agents_md() {
@@ -320,7 +320,7 @@ mod tests {
assert!(files[1].to_string_lossy().contains("style.md"));
}
- // ── format_agents_md_section tests ──────────────────────────────
+ // format_agents_md_section tests
#[test]
fn format_agents_md_section_empty_returns_none() {
@@ -370,7 +370,7 @@ mod tests {
);
}
- // ── Feature 2: Workspace user AGENTS.md via read_agents_config ───
+ // Feature 2: Workspace user AGENTS.md via read_agents_config
#[tokio::test]
async fn read_agents_config_includes_workspace_user_agents_md() {
@@ -537,7 +537,7 @@ mod tests {
assert!(!section.contains("globs:"));
}
- // ── .claude/CLAUDE.md integration tests ─────────────────────────
+ // .claude/CLAUDE.md integration tests
#[tokio::test]
async fn read_agents_config_discovers_claude_subdir_claude_md() {
diff --git a/crates/codegen/kigi-agent/src/prompt/context.rs b/crates/codegen/kigi-agent/src/prompt/context.rs
index 29b0d32..963969c 100644
--- a/crates/codegen/kigi-agent/src/prompt/context.rs
+++ b/crates/codegen/kigi-agent/src/prompt/context.rs
@@ -224,9 +224,9 @@ impl PromptContext {
}
/// Format the personas section content.
///
- /// Always returns `None` — the `persona` parameter has been removed
- /// from the task tool input, so persona summaries are no longer
- /// injected into the conversation.
+ /// Always returns `None` — the task tool input carries no `persona`
+ /// parameter, so persona summaries are never injected into the
+ /// conversation.
pub fn format_personas_section(&self) -> Option {
None
}
diff --git a/crates/codegen/kigi-agent/src/prompt/ignore.rs b/crates/codegen/kigi-agent/src/prompt/ignore.rs
index 01d42ce..7ac0b9f 100644
--- a/crates/codegen/kigi-agent/src/prompt/ignore.rs
+++ b/crates/codegen/kigi-agent/src/prompt/ignore.rs
@@ -4,7 +4,6 @@ use ignore::gitignore::{Gitignore, GitignoreBuilder};
use std::path::{Path, PathBuf};
pub fn build_gitignore(repo_root: Option<&Path>) -> Option {
- // No repo root → no gitignore rules to apply.
let root = repo_root?;
let mut builder = GitignoreBuilder::new(root);
diff --git a/crates/codegen/kigi-agent/src/prompt/skills.rs b/crates/codegen/kigi-agent/src/prompt/skills.rs
index 4c0c983..cba359e 100644
--- a/crates/codegen/kigi-agent/src/prompt/skills.rs
+++ b/crates/codegen/kigi-agent/src/prompt/skills.rs
@@ -726,7 +726,7 @@ mod tests {
fs::write(dir.join("SKILL.md"), content).unwrap();
}
- // ── Server-synced skills (injected server_skill_dirs) ────────────────
+ // Server-synced skills (injected server_skill_dirs)
#[tokio::test]
async fn server_skills_discovered_and_shadowed_by_local() {
@@ -826,7 +826,7 @@ mod tests {
);
}
- // ── Feature 3: Recursive skill reading ──────────────────────────────
+ // Feature 3: Recursive skill reading
#[test]
fn find_skill_paths_flat_layout() {
@@ -960,7 +960,7 @@ mod tests {
assert!(path_strs.iter().any(|p| p.contains("child/SKILL.md")));
}
- // ── extract_first_paragraph ──────────────────────────────────────
+ // extract_first_paragraph
#[test]
fn first_paragraph_simple() {
@@ -1001,7 +1001,7 @@ mod tests {
assert!(extract_first_paragraph(body).is_none());
}
- // ── UTF-8 safe body truncation ──────────────────────────────────
+ // UTF-8 safe body truncation
#[test]
fn description_fallback_does_not_panic_on_multibyte_boundary() {
@@ -1012,10 +1012,12 @@ mod tests {
// Strategy: fill with ASCII up to near the limit, then pack 4-byte
// emoji right at the boundary.
let prefix = "# Heading\n\n";
- let filler_len = MAX_BODY_PEEK_BYTES - prefix.len() - 4; // leave room for emoji at boundary
+ // leave room for emoji at boundary
+ let filler_len = MAX_BODY_PEEK_BYTES - prefix.len() - 4;
let filler = "a".repeat(filler_len);
// Each emoji is 4 bytes. Place several so one straddles the 2048 mark.
- let emoji_run = "\u{1F600}".repeat(10); // 40 bytes of emoji
+ // 40 bytes of emoji
+ let emoji_run = "\u{1F600}".repeat(10);
let body = format!("{prefix}{filler}{emoji_run}");
assert!(body.len() > MAX_BODY_PEEK_BYTES, "body must exceed limit");
@@ -1041,7 +1043,8 @@ mod tests {
// Body (after frontmatter): heading + paragraph with multibyte chars
// exceeding 2048 bytes.
- let long_paragraph = "\u{00E9}".repeat(MAX_BODY_PEEK_BYTES); // 2-byte chars
+ // 2-byte chars
+ let long_paragraph = "\u{00E9}".repeat(MAX_BODY_PEEK_BYTES);
let content = format!("---\nname: emoji-skill\n---\n# Test\n\n{long_paragraph}\n");
fs::write(skill_dir.join("SKILL.md"), &content).unwrap();
@@ -1055,7 +1058,7 @@ mod tests {
);
}
- // ── Frontmatter parsing (existing coverage + regression) ─────────
+ // Frontmatter parsing (existing coverage + regression)
#[test]
fn parse_valid_frontmatter() {
@@ -1122,7 +1125,7 @@ mod tests {
assert!(parsed.effort.is_none());
}
- // ── agentskills.io spec parity ────────────────────────────────
+ // agentskills.io spec parity
#[test]
fn parse_license_and_compatibility() {
@@ -1296,7 +1299,7 @@ mod tests {
));
}
- // ── Feature 1: Workspace user skills via list_skills ─────────────
+ // Feature 1: Workspace user skills via list_skills
/// Helper: initialize a bare git repo at `path` so git2::Repository::discover works.
fn init_git_repo(path: &Path) {
@@ -1423,7 +1426,7 @@ mod tests {
);
}
- // ── collect_config_skills ────────────────────────────────────────
+ // collect_config_skills
#[test]
fn collect_config_skills_from_directory() {
@@ -1530,7 +1533,7 @@ mod tests {
}
}
- // ── filter_skills ────────────────────────────────────────────────
+ // filter_skills
fn make_skill(name: &str, path: &str) -> SkillInfo {
SkillInfo {
@@ -1622,7 +1625,7 @@ mod tests {
assert_eq!(skills[0].plugin_name.as_deref(), Some("plugin-dev"));
}
- // ── Manifest `skills` entries pointing directly at skill dirs ──
+ // Manifest `skills` entries pointing directly at skill dirs
fn make_registry_with_skill_dirs(
name: &str,
@@ -2006,11 +2009,11 @@ mod tests {
);
}
- // discover_skills_for_paths and dedup_by_canonical_path tests removed --
- // these functions now live in kigi-tools::implementations::skills::discovery
- // and kigi-tools::types::skill_discovery_tracker, tested there.
+ // discover_skills_for_paths and dedup_by_canonical_path live in
+ // kigi-tools::implementations::skills::discovery and
+ // kigi-tools::types::skill_discovery_tracker, and are tested there.
- // ── Disabled skills marking ─────────────────────────────────────
+ // Disabled skills marking
#[tokio::test]
async fn disabled_config_marks_skill_enabled_false() {
@@ -2091,7 +2094,7 @@ mod tests {
);
}
- // ── Bundled skills discovery ─────────────────────────────────────
+ // Bundled skills discovery
#[tokio::test]
async fn bundled_skills_are_discovered() {
@@ -2180,7 +2183,7 @@ mod tests {
);
}
- // ── Command file discovery ────────────────────────────────────────
+ // Command file discovery
/// Regression: project `.claude/commands` often sits under a full `.claude/**`
/// gitignore with only `!.claude/skills/**` re-included (local-only vendor
@@ -2312,7 +2315,7 @@ mod tests {
assert!(deploy[0].path.contains("SKILL.md"));
}
- // ── Plugin skill identity ─────────────────────────────
+ // Plugin skill identity
fn min_plugin(name: &str) -> crate::plugins::LoadedPlugin {
use crate::plugins::discovery::PluginId;
@@ -2430,7 +2433,7 @@ mod tests {
);
}
- // ── collect_skill_config_dirs vendor gating ────────────
+ // collect_skill_config_dirs vendor gating
#[test]
fn collect_skill_config_dirs_gates_vendor_dirs() {
@@ -2461,7 +2464,7 @@ mod tests {
assert!(ends_with(&dirs, ".kigi"), "kigi must remain: {dirs:?}");
}
- // ── Same-scope frontmatter-name collisions (copied skill dirs) ──────
+ // Same-scope frontmatter-name collisions (copied skill dirs)
fn named_skill(name: &str, path: &str, scope: SkillScope) -> SkillInfo {
SkillInfo {
diff --git a/crates/codegen/kigi-agent/src/prompt/subagent_prompts.rs b/crates/codegen/kigi-agent/src/prompt/subagent_prompts.rs
index 49824d0..7ba0426 100644
--- a/crates/codegen/kigi-agent/src/prompt/subagent_prompts.rs
+++ b/crates/codegen/kigi-agent/src/prompt/subagent_prompts.rs
@@ -1,26 +1,10 @@
//! System prompts for built-in subagent profiles.
//!
-//!
-//! ## Tool name resolution
-//!
-//! All tool names in these prompts use the `${{ tools.by_kind.* }}` template
-//! syntax from the `TemplateRenderer`. When the prompt is rendered via
-//! `PromptContext::render()` → `ToolBridge::render_prompt()`, MiniJinja
-//! resolves each variable to the current session's tool names.
-//!
-//! This means:
-//! - Tool names are NEVER hardcoded — they adapt to name overrides and
-//! alternate tool namespaces
-//! - If a tool kind is absent from the renderer's context, MiniJinja
-//! resolves it to an empty string (templates can also use
-//! `${%- if tools.by_kind.X %}` conditionals to hide entire sections)
-//!
-//! Tool-kind mapping (common names → ToolKind):
-//! Read → `${{ tools.by_kind.read }}`
-//! Write/Edit → `${{ tools.by_kind.edit }}`
-//! Glob → `${{ tools.by_kind.list }}`
-//! Grep → `${{ tools.by_kind.search }}`
-//! Bash → `${{ tools.by_kind.execute }}`
-//! WebSearch → `${{ tools.by_kind.web_search }}`
+//! Tool names inside these prompts are never hardcoded: they are
+//! `${{ tools.by_kind.* }}` template variables that MiniJinja resolves to the
+//! session's actual tool names during `ToolBridge::render_prompt()`, so they
+//! follow name overrides and alternate namespaces. A kind that is absent from
+//! the renderer context resolves to an empty string, which is why prompts guard
+//! whole sections with `${%- if tools.by_kind.X %}`.
pub use kigi_tool_types::{EXPLORE_PROMPT, GENERAL_PURPOSE_PROMPT, PLAN_PROMPT};
diff --git a/crates/codegen/kigi-agent/src/prompt/template.rs b/crates/codegen/kigi-agent/src/prompt/template.rs
index 470311a..acdc83e 100644
--- a/crates/codegen/kigi-agent/src/prompt/template.rs
+++ b/crates/codegen/kigi-agent/src/prompt/template.rs
@@ -149,7 +149,7 @@ mod tests {
.expect("codex template render failed")
}
- // ── Variable substitution ───────────────────────────────────────
+ // Variable substitution
#[test]
fn test_variable_substitution_tool_kind() {
@@ -171,7 +171,7 @@ mod tests {
assert_eq!(result, "OS: macos, Shell: /bin/zsh");
}
- // ── Conditionals ────────────────────────────────────────────────
+ // Conditionals
#[test]
fn test_conditional_tool_present() {
@@ -205,7 +205,7 @@ mod tests {
assert_eq!(result, "Use {{ literal_braces }} in prose.");
}
- // ── Tool name overrides ─────────────────────────────────────────
+ // Tool name overrides
#[test]
fn test_tool_name_override() {
@@ -225,7 +225,7 @@ mod tests {
assert_eq!(result, "Use view_file and Edit.");
}
- // ── Base template rendering ─────────────────────────────────────
+ // Base template rendering
#[test]
fn test_base_template_renders() {
@@ -355,7 +355,7 @@ mod tests {
);
}
- // ── Required sections regression ────────────────────────────────
+ // Required sections regression
#[test]
fn test_base_template_contains_required_sections() {
@@ -380,7 +380,7 @@ mod tests {
);
}
- // ── Mid-session mode switching ──────────────────────────────────
+ // Mid-session mode switching
#[test]
fn test_mid_session_switch_concise_to_full() {
@@ -430,7 +430,7 @@ mod tests {
);
}
- // ── Determinism ─────────────────────────────────────────────────
+ // Determinism
#[test]
fn test_prompt_deterministic_across_renders() {
@@ -451,7 +451,7 @@ mod tests {
assert_eq!(a, b, "Full mode rendering must be deterministic");
}
- // ── Disabled tools ──────────────────────────────────────────────
+ // Disabled tools
#[test]
fn test_disabled_tools_omit_sections() {
@@ -469,11 +469,11 @@ mod tests {
);
}
- // ── Memory section ──────────────────────────────────────────────
+ // Memory section
#[test]
fn test_memory_enabled_does_not_render_memory_section() {
- // The section was removed from the minimal base prompt.
+ // The section is absent from the minimal base prompt.
// Even when the memory tools are registered AND memory_enabled=true,
// the trimmed template must not render a memory section. (Complements
// test_memory_disabled_omits_memory_section, which covers the default.)
@@ -514,7 +514,7 @@ mod tests {
);
}
- // ── Web search disabled ─────────────────────────────────────────
+ // Web search disabled
#[test]
fn test_web_search_disabled_renders_without_crash() {
@@ -534,7 +534,7 @@ mod tests {
);
}
- // ── Apply-patch template rendering ───────────────────────────────────
+ // Apply-patch template rendering
#[test]
fn test_apply_patch_template_renders() {
@@ -634,9 +634,9 @@ mod tests {
assert_eq!(a, b, "Subagent template rendering must be deterministic");
}
- // ── Task completion discipline ─────────────────────────────────
+ // Task completion discipline
//
- // The `` block was removed from both
+ // The `` block is absent from both
// base and subagent templates. These tests pin the deletion so the
// block doesn't accidentally come back, and so the runtime TodoGate
// doesn't start firing reminders that reference a non-existent
@@ -681,7 +681,7 @@ mod tests {
assert_template_size_under(&prompt, "subagent");
}
- // ── Guard invariant ─────────────────────────────────────────────
+ // Guard invariant
// Every `${{ tools.by_kind.X }}` must sit inside a `${%- if ... %}`
// whose condition requires X (contains `tools.by_kind.X` at a word
// boundary, with no top-level ` or `). If violated, X could render
@@ -770,12 +770,12 @@ mod tests {
assert_guards(&apply_patch_template(), "apply_patch_prompt.md");
}
- // ── Combination sweep ───────────────────────────────────────────
+ // Combination sweep
// Belt-and-braces: renders the base template across tool-kind subsets
// and asserts no raw template tokens leak. The static guard test above
// is the authoritative check; this one just catches syntax drift.
- // ── is_non_interactive gating ──────────────────────────────────
+ // is_non_interactive gating
// Headless / SDK / stdio / generic-ACP sessions have no human typing
// into a TUI prompt, so the `! ` shell-prefix tip and the
// `` TUI pointer are noise. Those sections must drop out
@@ -783,7 +783,7 @@ mod tests {
#[test]
fn interactive_renders_shell_prefix_tip_and_user_guide() {
- // The `! ` shell-prefix tip was removed from the minimal
+ // The `! ` shell-prefix tip is absent from the minimal
// prompt. The block still renders for interactive
// sessions only, so that's what we assert here.
let mut p = default_placeholders();
diff --git a/crates/codegen/kigi-agent/src/prompt/user_message.rs b/crates/codegen/kigi-agent/src/prompt/user_message.rs
index cf0c077..338fff6 100644
--- a/crates/codegen/kigi-agent/src/prompt/user_message.rs
+++ b/crates/codegen/kigi-agent/src/prompt/user_message.rs
@@ -334,7 +334,7 @@ mod tests {
assert_eq!(original, loaded);
}
}
- /// A status under the cap passes through unchanged (trim is a no-op for
+ /// A status under the cap passes through `unchanged` (trim is a no-op for
/// real `git status --short --branch` output, which starts with `##`).
#[test]
fn normalize_git_status_passthrough_under_limit() {
diff --git a/crates/codegen/kigi-agent/src/prompt/workspace_user.rs b/crates/codegen/kigi-agent/src/prompt/workspace_user.rs
index 112e9ae..690042a 100644
--- a/crates/codegen/kigi-agent/src/prompt/workspace_user.rs
+++ b/crates/codegen/kigi-agent/src/prompt/workspace_user.rs
@@ -48,7 +48,7 @@ mod tests {
use super::*;
use std::fs;
- // ── resolve_workspace_user_dir (pure, no env vars) ───────────────
+ // resolve_workspace_user_dir (pure, no env vars)
#[test]
fn resolve_returns_none_for_empty_root() {
@@ -126,7 +126,7 @@ mod tests {
assert_eq!(result, Some(user_dir));
}
- // ── workspace_user_relpath ───────────────────────────────────────
+ // workspace_user_relpath
#[test]
fn bare_username_is_nested_under_x() {
diff --git a/crates/codegen/kigi-agent/src/repo.rs b/crates/codegen/kigi-agent/src/repo.rs
index b7e02f4..30b4c12 100644
--- a/crates/codegen/kigi-agent/src/repo.rs
+++ b/crates/codegen/kigi-agent/src/repo.rs
@@ -1,10 +1,8 @@
//! Shared git-repo dir-chain primitive.
//!
-//! One `git2` discovery + one cwd→root walk, reused across the many repo-local
-//! config marker checks the folder-trust gate runs back-to-back. Lives in its
-//! own module (rather than `discovery`) because it is a generic repo-walk
-//! primitive consumed cross-crate by `kigi-workspace`, not agent-definition
-//! discovery.
+//! Lives in its own module rather than `discovery` because it is a generic
+//! repo-walk primitive consumed cross-crate by `kigi-workspace`, not
+//! agent-definition discovery.
use std::path::{Path, PathBuf};
@@ -14,42 +12,28 @@ use std::path::{Path, PathBuf};
///
/// The folder-trust gate's `repo_configs_present` probes a dozen repo-local
/// code-exec markers (`.mcp.json`, `.kigi/config.toml`, `.claude/settings.json`,
-/// project plugin/agent dirs, …) back-to-back on the agent startup path. Each
-/// marker walker used to run its own `discover` + cwd→root walk; sharing one
-/// `RepoDirChain` collapses that to a single traversal (each redundant syscall
-/// is taxed 10-100x on Windows, and on a non-git dir each `discover` walks to
-/// the filesystem root). Both the gate and the real loaders consume the same
-/// chain via `*_in` walker variants, so detection can't drift from loading.
-///
-/// The public cwd-taking delegators (`find_project_configs`,
-/// `project_plugin_dirs`, `project_agent_dirs`, …) now resolve through this
-/// chain too, so their non-gate callers (config watcher, reloader, the mcp/
-/// config loaders, inspect, upload, mcp_doctor) gain the per-level canonicalize
-/// below. That is deliberate: all those callers are cold (startup / file-change /
-/// session-setup / manual commands), never per-keystroke, and the canonical stop
-/// is strictly more correct.
+/// project plugin/agent dirs, …) back-to-back on the agent startup path, so a
+/// per-walker discovery + walk is a real cost: each redundant syscall is taxed
+/// 10-100x on Windows, and on a non-git dir each `discover` walks to the
+/// filesystem root. Both the gate and the real loaders consume the same chain
+/// via `*_in` walker variants, so detection can't drift from loading.
///
/// Outside a git repo `git_root` is `None` and `dirs` is just `[cwd]`, matching
/// every walker's no-repo branch (probe `cwd` only).
#[derive(Debug, Clone)]
pub struct RepoDirChain {
- /// Git worktree root (`workdir`), or `None` when `cwd` is not inside a repo.
pub git_root: Option,
- /// `cwd` up to and including `git_root`, cwd-first (`[cwd]` with no repo).
pub dirs: Vec,
}
impl RepoDirChain {
- /// Resolve the chain for `cwd`: ONE `git2` discovery + ONE upward walk.
pub fn resolve(cwd: &Path) -> Self {
let git_root = git2::Repository::discover(cwd)
.ok()
.and_then(|repo| repo.workdir().map(|p| p.to_path_buf()))
- // Home-is-a-git-repo (dotfiles in $HOME): a discovery that walks up
- // to $HOME must NOT treat the whole home subtree as one repo, or
- // home-level `.kigi`/`.mcp.json`/plugins would look repo-local. Drop
- // it so cwd is handled as no-repo (probe cwd only). Home is compared
- // canonically to match the symlink handling in the walk below.
+ // Dotfiles in $HOME make home itself a repo; treating that subtree
+ // as repo-local would promote home-level `.kigi`/`.mcp.json`/plugins
+ // to project config. Dropping the root makes cwd behave as no-repo.
.filter(|root| !is_home_dir(root));
let mut dirs = Vec::new();
@@ -57,11 +41,10 @@ impl RepoDirChain {
// Canonicalize only for the stop test so a symlinked cwd/ancestor
// still halts AT the worktree root instead of over-walking to the
// filesystem root; pushed dirs keep their original spelling (callers
- // `join` markers onto them, which resolve the same either way). The
- // per-level canonicalize is required to stop at root through a
- // symlinked ancestor while keeping raw spelling — do NOT reduce to a
- // 2-call `starts_with` variant (it would mis-handle a mid-chain
- // absolute symlink and reintroduce the over-walk).
+ // `join` markers onto them, which resolve the same either way).
+ // Canonicalizing per level is what makes that stop reliable — a
+ // 2-call `starts_with` variant mis-handles a mid-chain absolute
+ // symlink and over-walks.
let root_canonical = dunce::canonicalize(root).unwrap_or_else(|_| root.clone());
let mut current = Some(cwd.to_path_buf());
while let Some(dir) = current {
@@ -81,9 +64,9 @@ impl RepoDirChain {
}
}
-/// Whether `path` canonicalizes to the user's home directory. Local (not reused
-/// from `kigi-workspace`, which depends on THIS crate) to keep the dep edge
-/// one-way; backs the home-is-dotfiles guard in [`RepoDirChain::resolve`].
+/// Whether `path` canonicalizes to the user's home directory. Duplicated here
+/// instead of reused from `kigi-workspace`, which depends on THIS crate, to keep
+/// the dep edge one-way.
fn is_home_dir(path: &Path) -> bool {
let Some(home) = dirs::home_dir() else {
return false;
@@ -94,8 +77,7 @@ fn is_home_dir(path: &Path) -> bool {
/// Existing `/` directories under each dir of a precomputed
/// cwd→git-root chain ([`RepoDirChain::dirs`]), in chain order (cwd-first, then
-/// each `subdirs` entry in order). Shared body for the project plugin/agent dir
-/// walkers so the byte-identical double-loop lives in one place.
+/// each `subdirs` entry in order).
pub(crate) fn existing_subdirs_along(chain_dirs: &[PathBuf], subdirs: &[&str]) -> Vec {
let mut found = Vec::new();
for dir in chain_dirs {
@@ -114,8 +96,8 @@ mod tests {
use super::*;
use serial_test::serial;
- /// RAII guard: set an env var, restore the prior value (or unset) on drop,
- /// so a test never leaves process-global env pointing at a dropped tempdir.
+ /// Restores the prior value (or unsets) on drop, so a test never leaves
+ /// process-global env pointing at a dropped tempdir.
struct EnvVarGuard {
key: &'static str,
prev: Option,
@@ -140,8 +122,6 @@ mod tests {
#[test]
fn resolve_in_repo_yields_cwd_to_root_chain() {
- // A git-init'd tmp with a 2-deep subdir: the chain is cwd→root inclusive,
- // cwd-first, in the dirs' original spelling, and `git_root` is the root.
let tmp = tempfile::tempdir().unwrap();
git2::Repository::init(tmp.path()).unwrap();
let nested = tmp.path().join("a").join("b");
@@ -156,8 +136,8 @@ mod tests {
tmp.path().to_path_buf(),
]
);
- // `git_root` is the canonical worktree root (git2's `workdir`); compare by
- // canonical form so a `/tmp`→`/private/tmp` symlink doesn't fail the test.
+ // git2's `workdir` is canonical, so compare canonically or a
+ // `/tmp`→`/private/tmp` symlink fails the test.
let root = chain.git_root.expect("inside a repo");
assert_eq!(
dunce::canonicalize(&root).unwrap(),
@@ -167,10 +147,9 @@ mod tests {
#[test]
fn resolve_outside_repo_is_cwd_only() {
- // A non-git tmp: no discovery hit, so the chain is just `[cwd]` and there
- // is no git root. Only assert the no-repo shape when the temp dir is
- // genuinely outside any repo (a dev/CI checkout may place $TMPDIR inside
- // a larger git worktree).
+ // Only assert the no-repo shape when the temp dir is genuinely outside
+ // any repo: a dev/CI checkout may place $TMPDIR inside a larger git
+ // worktree.
let tmp = tempfile::tempdir().unwrap();
let plain = tmp.path().join("plain");
std::fs::create_dir_all(&plain).unwrap();
@@ -184,10 +163,8 @@ mod tests {
#[test]
#[serial(home_env)]
fn resolve_treats_home_git_repo_as_no_repo() {
- // Home-is-a-git-repo (dotfiles in $HOME): discovery walks up to $HOME,
- // but the guard drops that root so a subdir resolves as no-repo (probe
- // cwd only) instead of spanning the whole home subtree. $HOME is guarded
- // (dirs::home_dir reads it) and canonicalized to match the guard.
+ // $HOME is process-global (`dirs::home_dir` reads it) so it needs the
+ // guard, and canonicalized to match the comparison in `is_home_dir`.
let tmp = tempfile::tempdir().unwrap();
let home = dunce::canonicalize(tmp.path()).unwrap();
git2::Repository::init(&home).unwrap();
@@ -203,8 +180,8 @@ mod tests {
#[test]
#[serial(home_env)]
fn resolve_keeps_non_home_git_root() {
- // The guard is home-EXACT: a git root that is NOT $HOME still resolves
- // normally (no over-trigger), so $HOME points at an unrelated dir here.
+ // The guard is home-EXACT, so $HOME points at an unrelated dir here to
+ // prove a non-home git root still resolves normally.
let home = tempfile::tempdir().unwrap();
let _home_guard = EnvVarGuard::set("HOME", home.path());
let repo = tempfile::tempdir().unwrap();
diff --git a/crates/codegen/kigi-agent/src/system_reminder.rs b/crates/codegen/kigi-agent/src/system_reminder.rs
index eaae9eb..4f7ff01 100644
--- a/crates/codegen/kigi-agent/src/system_reminder.rs
+++ b/crates/codegen/kigi-agent/src/system_reminder.rs
@@ -1,22 +1,14 @@
//! Reminder policy — wraps kigi-tools reminder config.
-/// Default per-prompt fire cap for the runtime turn-end TodoGate. Used
-/// only as the default for `TodoGateConfig`; the runtime consumer reads
-/// the live value from `ReminderPolicy.todo_gate.max_fires_per_prompt`,
-/// so this constant is NOT a hardcoded cap.
+/// Seeds `TodoGateConfig::max_fires_per_prompt`; the gate reads the live value
+/// from `ReminderPolicy.todo_gate`, never this constant.
pub const DEFAULT_TODO_GATE_MAX_FIRES: u32 = 2;
/// Session-level system reminder policy.
-///
-/// Controls whether system reminders are enabled and configures
-/// the TodoNudge and TodoGate behavior.
#[derive(Debug, Clone)]
pub struct ReminderPolicy {
- /// Whether system reminders are enabled at all.
pub enabled: bool,
- /// Configuration for the periodic TodoWrite nudge reminder.
pub todo_nudge: TodoNudgeConfig,
- /// Configuration for the runtime turn-end TodoGate.
pub todo_gate: TodoGateConfig,
}
@@ -30,17 +22,13 @@ impl Default for ReminderPolicy {
}
}
-/// Configuration for the TodoWrite nudge reminder.
-///
-/// The system will remind the model to use `todo_write` when it
-/// hasn't done so within a configurable number of turns.
+/// Reminds the model to call `todo_write` once it has gone
+/// `turns_since_todo_write` turns without one, then stays quiet for
+/// `turns_between_reminders` turns.
#[derive(Debug, Clone)]
pub struct TodoNudgeConfig {
- /// Whether the TodoNudge reminder is enabled.
pub enabled: bool,
- /// Number of turns since last `todo_write` call before nudging.
pub turns_since_todo_write: u32,
- /// Minimum turns between nudge reminders.
pub turns_between_reminders: u32,
}
@@ -54,24 +42,19 @@ impl Default for TodoNudgeConfig {
}
}
-/// Configuration for the runtime turn-end TodoGate.
-///
-/// The gate inspects `TodoState` after every content-only assistant
-/// message and forces another turn via `` injection
-/// if pending/unbacked-in-progress todos remain — see
+/// Turn-end gate: inspects `TodoState` after every content-only assistant
+/// message and forces another turn via `` injection if
+/// pending/unbacked-in-progress todos remain — see
/// `kigi-shell::session::acp_session::evaluate_todo_gate`.
///
-/// **Disabled by default.** Operators opt in via the remote
-/// `todo_gate_enabled = true` remote settings key, or via the
-/// `--todo-gate` CLI flag (session-scoped force-enable, highest
-/// precedence).
+/// **Disabled by default.** Operators opt in via the `todo_gate_enabled`
+/// remote settings key, or via the `--todo-gate` CLI flag (session-scoped
+/// force-enable, highest precedence).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TodoGateConfig {
- /// Whether the gate runs at all.
pub enabled: bool,
- /// Hard cap on how many times the gate may fire per user prompt
- /// before the next turn is allowed to end with `TurnOutcome::Completed`.
- /// Bounds the worst-case extra inference cost.
+ /// Past this many fires per user prompt the next turn is allowed to end
+ /// with `TurnOutcome::Completed`, bounding worst-case extra inference cost.
pub max_fires_per_prompt: u32,
}
@@ -108,16 +91,11 @@ mod tests {
"TodoGate ships disabled; remote/local opt-in required"
);
assert_eq!(policy.todo_gate.max_fires_per_prompt, 2);
- // The two reminder mechanisms are independent — flipping one
- // must not change the other (regression guard).
assert!(policy.todo_nudge.enabled);
}
#[test]
fn todo_gate_enable_does_not_disturb_nudge() {
- // Remote opt-in (or `[reminder.todo_gate] enabled = true` local
- // config) flips the gate to on without touching the periodic
- // TodoNudge as a side-effect.
let mut policy = ReminderPolicy::default();
policy.todo_gate.enabled = true;
assert!(policy.todo_gate.enabled);
diff --git a/crates/codegen/kigi-auth/src/auth_provider.rs b/crates/codegen/kigi-auth/src/auth_provider.rs
index 23f010d..8edb5fe 100644
--- a/crates/codegen/kigi-auth/src/auth_provider.rs
+++ b/crates/codegen/kigi-auth/src/auth_provider.rs
@@ -7,17 +7,16 @@ use reqwest::RequestBuilder;
use crate::visibility::HttpAuth;
-/// Snapshot of the currently effective credentials. Used by callers
-/// that build their own header maps (the OTel OTLP exporter) or that
-/// need the bearer prefix for 401-attribution telemetry.
+/// Snapshot of the currently effective credentials, for callers that build
+/// their own header maps (the OTel OTLP exporter) or that need the bearer
+/// prefix for 401-attribution telemetry.
#[derive(Clone, Debug, Default)]
pub struct CredentialSnapshot {
- /// Bearer token. `None` when no auth is configured (CI / `--api-key` headless).
+ /// `None` when no auth is configured (CI / `--api-key` headless).
pub token: Option,
- /// User identifier matching the bearer token's owner. `None` when no auth
- /// is configured or when the underlying provider has no concept of user
- /// identity (`StaticAuthCredentialProvider`). Read by the OTel layer to
- /// populate the `user.id` resource attribute.
+ /// Owner of `token`. `None` when no auth is configured or when the
+ /// provider has no concept of user identity
+ /// (`StaticAuthCredentialProvider`).
pub user_id: Option,
/// `uuidv5(NAMESPACE_OID, deployment_key)`, set only for deployment-key auth.
pub deployment_id: Option,
@@ -29,49 +28,42 @@ pub struct CredentialSnapshot {
///
/// Supertrait of `HttpAuth` so a single impl satisfies both this trait
/// (refresh-aware snapshot + 401 recovery) and the visibility seam
-/// (header construction). Callers add headers via `HttpAuth::apply`.
+/// (header construction).
#[async_trait::async_trait]
pub trait AuthCredentialProvider: HttpAuth + Send + Sync + 'static {
- /// Return the current credential snapshot. Implementations should
- /// issue a cheap disk re-read (`AuthManager::refresh`) before
- /// snapshotting so callers see updates from sibling processes
- /// (`kigi-desktop`, `kigi login`). The `token` field MUST mirror
- /// the bearer that `HttpAuth::apply` would send on the wire so
- /// 401-attribution prefixes match the actual request.
+ /// Implementations should issue a cheap disk re-read
+ /// (`AuthManager::refresh`) before snapshotting so callers see updates
+ /// from sibling processes (`kigi-desktop`, `kigi login`). The `token`
+ /// field MUST mirror the bearer that `HttpAuth::apply` would send on the
+ /// wire so 401-attribution prefixes match the actual request.
fn snapshot(&self) -> CredentialSnapshot;
- /// Attempt to obtain a fresh token. Returns `true` if a different
- /// token was obtained -- caller should retry the failed request once.
- /// Returns `false` if no refresher is configured or refresh failed.
+ /// `true` if a different token was obtained, meaning the caller should
+ /// retry the failed request once; `false` if no refresher is configured
+ /// or the refresh failed.
async fn refresh_after_unauthorized(&self) -> bool;
/// Whether the provider holds a credential worth a real outbound attempt —
- /// an unexpired token (in memory or on disk), or a static key. Default
- /// `true` always attempts.
+ /// an unexpired token (in memory or on disk), or a static key.
fn has_usable_credential(&self) -> bool {
true
}
}
-/// Static credential provider. Used by tests and by callers that pass a
-/// raw `&str` token with no `AuthManager` available.
+/// Non-refreshing provider for tests and for callers that pass a raw `&str`
+/// token with no `AuthManager` available.
///
-/// `apply()` delegates to the underlying `HttpAuth::apply()`.
-/// `refresh_after_unauthorized()` always returns `false`.
-///
-/// `bearer` is the wire bearer the inner `HttpAuth` will send in the
-/// `Authorization` header. Stored alongside the inner so `snapshot().token`
-/// returns the same prefix that goes out on the wire (used by
-/// 401-attribution telemetry). `None` when no bearer is configured.
+/// `bearer` duplicates whatever `inner` stamps into the `Authorization`
+/// header; it exists so `snapshot().token` reports the same prefix that goes
+/// out on the wire, which 401-attribution telemetry relies on.
pub struct StaticAuthCredentialProvider {
inner: Box,
bearer: Option,
}
impl StaticAuthCredentialProvider {
- /// Wrap `inner` so callers see it as an `AuthCredentialProvider`. Pass
- /// the bearer token that `inner.apply()` will send in the `Authorization`
- /// header so `snapshot().token` reflects the wire bearer truthfully.
+ /// `bearer` must be the token `inner.apply()` sends, or `snapshot()` will
+ /// misreport the wire credential.
pub fn new(inner: Box, bearer: Option) -> Self {
Self { inner, bearer }
}
diff --git a/crates/codegen/kigi-auth/src/retry_middleware.rs b/crates/codegen/kigi-auth/src/retry_middleware.rs
index 317e874..36bdb04 100644
--- a/crates/codegen/kigi-auth/src/retry_middleware.rs
+++ b/crates/codegen/kigi-auth/src/retry_middleware.rs
@@ -1,5 +1,4 @@
//! `reqwest-middleware` layer: stamps auth headers and retries on 401.
-//! Gated behind the `middleware` cargo feature.
use std::sync::Arc;
@@ -52,6 +51,8 @@ impl Middleware for AuthRetryMiddleware {
if resp.status() != StatusCode::UNAUTHORIZED || self.max_retries == 0 {
return Ok(resp);
}
+ // Streaming bodies do not clone, so such requests cannot be replayed
+ // and the 401 stands.
let Some(backup) = backup else {
return Ok(resp);
};
@@ -154,7 +155,7 @@ mod tests {
m.assert_async().await;
}
- /// Simulates a real auth manager: starts with stale token, refresh swaps to fresh.
+ /// Starts with a stale token; refresh swaps in the fresh one.
struct SimulatedAuthManager {
token: Mutex>,
fresh_token: String,
diff --git a/crates/codegen/kigi-auth/src/visibility.rs b/crates/codegen/kigi-auth/src/visibility.rs
index 65c4e14..1956930 100644
--- a/crates/codegen/kigi-auth/src/visibility.rs
+++ b/crates/codegen/kigi-auth/src/visibility.rs
@@ -1,7 +1,7 @@
-/// Apply auth headers to outbound visibility requests.
-/// Implemented by `kigi-shell::util::kigi_auth_credentials::KigiAuthCredentials`
-/// to keep credential construction owned by shell while letting data-collector
-/// build the request without reaching back into shell types.
+/// Applies auth headers to outbound visibility requests. Implemented by
+/// `kigi-shell::util::kigi_auth_credentials::KigiAuthCredentials`, keeping
+/// credential construction owned by shell while data-collector builds the
+/// request without reaching back into shell types.
pub trait HttpAuth: Send + Sync {
fn apply(&self, builder: reqwest::RequestBuilder, base_url: &str) -> reqwest::RequestBuilder;
}
diff --git a/crates/codegen/kigi-bin/src/main.rs b/crates/codegen/kigi-bin/src/main.rs
index 9ac40f8..c55b96c 100644
--- a/crates/codegen/kigi-bin/src/main.rs
+++ b/crates/codegen/kigi-bin/src/main.rs
@@ -702,7 +702,7 @@ async fn run_agent_command(
}
}
}
- // Fire-and-forget model-catalog warmup (nothing joins the handle now that
+ // Fire-and-forget model-catalog warmup (nothing joins the handle because
// the xAI settings fetch it used to carry is gone).
drop(kigi_shell::agent::models::start_early_prefetch(None));
kigi_shell::agent::mvp_agent::warm_async_http_client();
@@ -1966,7 +1966,7 @@ mod tests {
assert!(s.last_session_id.is_none());
}
/// An UNCONFIRMED `session/new` (leader died before its response) must not
- /// be replayed — its id was never assigned — but previously loaded
+ /// be replayed — its id was never assigned — but earlier loaded
/// sessions still restore.
#[tokio::test]
async fn replay_after_unconfirmed_session_new_restores_prior_sessions() {
diff --git a/crates/codegen/kigi-chat-state/src/actor/mod.rs b/crates/codegen/kigi-chat-state/src/actor/mod.rs
index 059e1ed..f38a760 100644
--- a/crates/codegen/kigi-chat-state/src/actor/mod.rs
+++ b/crates/codegen/kigi-chat-state/src/actor/mod.rs
@@ -116,7 +116,7 @@ impl ChatStateActor {
/// Dispatch a command to the appropriate mutation or query handler.
fn handle_command(&mut self, cmd: ChatStateCommand) {
match cmd {
- // ═══ Mutations ═══
+ // Mutations
ChatStateCommand::PushUserMessage { item } => {
self.push_user_message(item);
}
@@ -240,7 +240,7 @@ impl ChatStateActor {
self.repair_dangling_after_harness_halt(class);
}
- // ═══ Queries ═══
+ // Queries
//
// Read queries are pure reads — repair only at write boundaries:
// `ChatState::new()` (startup) and `push_user_message()` (new turn).
@@ -318,7 +318,7 @@ impl ChatStateActor {
self.truncate_to_prompt_index(target_prompt_index);
self.state.turn_capture = None;
self.state.prompt_usage = None;
- // `harness_trace_buffer` / `harness_trace_turns` intentionally
+ // `harness_trace_buffer` / `harness_trace_turns` deliberately
// survive a rewind: the goal planner / verifier subagents
// genuinely ran, so their sealed trace turns stay uploadable as
// siblings even when the live turn that triggered them is undone.
@@ -358,7 +358,7 @@ impl ChatStateActor {
let _ = reply.send(std::mem::take(&mut self.state.harness_trace_turns));
}
- // ─── Narrow targeted queries ──────────────────────────────────
+ // Narrow targeted queries
ChatStateCommand::GetConversationLen { reply } => {
let _ = reply.send(self.get_conversation_len());
}
diff --git a/crates/codegen/kigi-chat-state/src/actor/queries.rs b/crates/codegen/kigi-chat-state/src/actor/queries.rs
index c98ef2d..8b3c69a 100644
--- a/crates/codegen/kigi-chat-state/src/actor/queries.rs
+++ b/crates/codegen/kigi-chat-state/src/actor/queries.rs
@@ -123,7 +123,7 @@ impl ChatStateActor {
.unwrap_or_default()
}
- // ─── Narrow targeted queries ─────────────────────────────────────────────
+ // Narrow targeted queries
/// Return the number of items in the conversation.
pub(super) fn get_conversation_len(&self) -> usize {
diff --git a/crates/codegen/kigi-chat-state/src/actor/request_builder.rs b/crates/codegen/kigi-chat-state/src/actor/request_builder.rs
index 4f18478..6b5ed53 100644
--- a/crates/codegen/kigi-chat-state/src/actor/request_builder.rs
+++ b/crates/codegen/kigi-chat-state/src/actor/request_builder.rs
@@ -148,9 +148,7 @@ impl ChatStateActor {
}
}
-// ============================================================================
// Pruning (standalone functions, no actor state needed)
-// ============================================================================
/// Check whether pruning should run based on context utilization.
///
@@ -208,9 +206,7 @@ pub(crate) fn prune_conversation(conversation: &mut [ConversationItem], config:
}
}
-// ============================================================================
// Image size-gated compaction (request-copy only)
-// ============================================================================
/// Replaces an inline image evicted to keep the request body under the proxy's
/// 50 MB limit. Phrased so the model treats the image as gone rather than
@@ -376,7 +372,7 @@ fn conversation_body_bytes(conversation: &[ConversationItem]) -> usize {
/// always retain the newest images, an image only transitions image →
/// placeholder as *newer/larger* payloads push the body past the limit, never
/// placeholder → image within a stable prefix. (Token compaction removes old
-/// turns wholesale and can free room to restore a previously-evicted image,
+/// turns wholesale and can free room to restore an earlier-evicted image,
/// but that already rewrites the prefix and invalidates the server-side prompt
/// cache, so the restore is free.)
///
@@ -450,19 +446,17 @@ pub(crate) fn compact_images_to_byte_budget(
}
}
-// ============================================================================
// Memory reminder injection
-// ============================================================================
use crate::types::MEMORY_CONTEXT_OPEN_TAG;
/// Upsert a memory reminder into the conversation's system message.
///
-/// If the first item is a `System` message, any previously injected memory
-/// reminder section is replaced in-place; otherwise the reminder is appended.
+/// If the first item is a `System` message, any existing memory reminder
+/// section is replaced in-place; otherwise the reminder is appended.
/// If no system message exists, a new `System` item is prepended.
///
-/// Returns `true` when the conversation was changed.
+/// Returns `true` when the conversation changed.
pub(super) fn inject_memory_reminder(items: &mut Vec, reminder: &str) -> bool {
let reminder = reminder.trim();
if reminder.is_empty() {
@@ -505,9 +499,7 @@ fn upsert_memory_reminder_text(system_prompt: &mut std::sync::Arc, reminder
}
}
-// ============================================================================
// String helpers
-// ============================================================================
fn safe_char_slice(s: &str, start: usize, count: usize) -> String {
s.chars().skip(start).take(count).collect()
@@ -529,9 +521,12 @@ mod tests {
fn should_prune_gating() {
use std::num::NonZeroU64;
let cw = NonZeroU64::new(10000).unwrap();
- assert!(!should_prune(1000, cw)); // 10%
- assert!(should_prune(6000, cw)); // 60%
- assert!(!should_prune(5000, cw)); // 50% exact (> not >=)
+ // 10%
+ assert!(!should_prune(1000, cw));
+ // 60%
+ assert!(should_prune(6000, cw));
+ // 50% exact (> not >=)
+ assert!(!should_prune(5000, cw));
}
#[test]
@@ -558,7 +553,8 @@ mod tests {
assert!(sys.content.contains("Remember: user likes rust"));
assert!(sys.content.starts_with("You are helpful."));
}
- assert_eq!(items.len(), 2); // no new item added
+ // no new item added
+ assert_eq!(items.len(), 2);
}
#[test]
@@ -569,7 +565,7 @@ mod tests {
assert!(matches!(&items[0], ConversationItem::System(_)));
}
- // -- image size-gated compaction tests --
+ // image size-gated compaction tests
/// A user message with a small fixed inline image.
fn user_with_image(text: &str) -> ConversationItem {
@@ -661,8 +657,10 @@ mod tests {
// dropping a *batch* of the oldest, not just the one image needed to
// clear the trigger. This is the hysteresis that keeps the prefix
// cache-warm for the following turns.
- let img_bytes = 1_000_000usize; // ~1 MB url each
- let n = (IMAGE_COMPACT_TRIGGER_BYTES / img_bytes) + 2; // body just over trigger
+ // ~1 MB url each
+ let img_bytes = 1_000_000usize;
+ // body just over trigger
+ let n = (IMAGE_COMPACT_TRIGGER_BYTES / img_bytes) + 2;
let mut conv: Vec = (0..n)
.map(|i| user_with_image_of_bytes(&format!("i{i}"), img_bytes))
.collect();
@@ -726,7 +724,7 @@ mod tests {
assert!(has_placeholder(&conv[0]));
}
- // -- conversation_body_bytes tests --
+ // conversation_body_bytes tests
#[test]
fn conversation_body_bytes_empty_is_json_array() {
@@ -777,7 +775,7 @@ mod tests {
assert!(conversation_body_bytes(&conv) >= IMAGE_COMPACT_TRIGGER_BYTES);
}
- // -- edge cases: exactness, boundaries, ordering --
+ // edge cases: exactness, boundaries, ordering
#[test]
fn body_bytes_parity_multi_image_unicode_escaping() {
diff --git a/crates/codegen/kigi-chat-state/src/actor/state.rs b/crates/codegen/kigi-chat-state/src/actor/state.rs
index 74c822e..54067c3 100644
--- a/crates/codegen/kigi-chat-state/src/actor/state.rs
+++ b/crates/codegen/kigi-chat-state/src/actor/state.rs
@@ -137,7 +137,7 @@ pub(crate) struct ChatState {
/// Opaque credential secrets (api key, optional extra auth, client version).
/// Stored opaquely — the actor never interprets them.
pub credentials: Credentials,
- /// Bytes/4 estimate of tokens added since the last `record_token_usage`.
+ /// Bytes/4 estimate of tokens accumulated since the last `record_token_usage`.
/// Used by `check_preflight_overflow` to detect context window overflows
/// between model responses.
pub estimated_tokens_since_model: u64,
@@ -304,7 +304,8 @@ mod tests {
fn new_state_has_correct_defaults() {
let state = ChatState::new(vec![], test_sampling_config());
assert_eq!(state.prompt_index, 0);
- assert_eq!(state.total_tokens, 0); // empty conversation → 0
+ // empty conversation → 0
+ assert_eq!(state.total_tokens, 0);
assert!(state.conversation.is_empty());
assert!(state.agent_edited_paths.is_empty());
assert!(state.prompt_texts.is_empty());
@@ -333,7 +334,8 @@ mod tests {
ConversationItem::tool_result("call-1", "w".repeat(4000).as_str()),
];
let state = ChatState::new(items, test_sampling_config());
- assert_eq!(state.total_tokens, 4000); // 4 * (4000/4)
+ // 4 * (4000/4)
+ assert_eq!(state.total_tokens, 4000);
}
#[test]
diff --git a/crates/codegen/kigi-chat-state/src/actor/tests.rs b/crates/codegen/kigi-chat-state/src/actor/tests.rs
index 9277de4..4463ff2 100644
--- a/crates/codegen/kigi-chat-state/src/actor/tests.rs
+++ b/crates/codegen/kigi-chat-state/src/actor/tests.rs
@@ -91,9 +91,7 @@ impl TestHarness {
}
}
-// ============================================================================
// Lifecycle tests
-// ============================================================================
#[tokio::test]
async fn actor_spawns_and_shuts_down_via_cancellation() {
@@ -121,9 +119,7 @@ async fn actor_shuts_down_when_all_handles_dropped() {
tokio::time::sleep(Duration::from_millis(50)).await;
}
-// ============================================================================
// Mutation tests
-// ============================================================================
#[tokio::test]
async fn push_user_message_appends_and_persists() {
@@ -319,9 +315,10 @@ async fn estimated_tokens_tracks_tool_result_delta() {
.push_tool_result(ConversationItem::tool_result("call-1", "x".repeat(4000)));
let estimated = h.handle.get_estimated_total_tokens().await;
- assert_eq!(estimated, 101_000); // 100K model-reported + 1K delta
+ // 100K model-reported + 1K delta
+ assert_eq!(estimated, 101_000);
- // model-reported total_tokens is unchanged
+ // model-reported total_tokens is `unchanged`
let actual = h.handle.get_total_tokens().await;
assert_eq!(actual, 100_000);
}
@@ -359,7 +356,7 @@ async fn estimated_tokens_tracks_synthetic_user_message_delta() {
"expected ~1.1M tokens estimated, got {estimated}",
);
- // model-reported `total_tokens` is unchanged — only the delta moved.
+ // model-reported `total_tokens` is `unchanged` — only the delta moved.
assert_eq!(h.handle.get_total_tokens().await, 100_000);
}
@@ -451,7 +448,7 @@ async fn replace_conversation_persists_and_emits_reset() {
h.handle.push_user_message(ConversationItem::user("b"));
// Drain the two Message records
- let _ = h.handle.get_conversation().await; // sync point
+ let _ = h.handle.get_conversation().await;
h.drain_persistence();
let new_items = vec![ConversationItem::system("compacted")];
@@ -721,9 +718,7 @@ async fn restore_snapshot_restores_all_fields() {
assert_eq!(tokens, 500);
}
-// ============================================================================
// Query tests
-// ============================================================================
#[tokio::test]
async fn get_conversation_returns_current_state() {
@@ -765,7 +760,8 @@ async fn replace_system_head_noop_when_head_matches_modulo_newline() {
ConversationItem::system("same\n"),
ConversationItem::user("hi"),
]);
- let _ = h.drain_persistence(); // clear any seed writes
+ // clear any seed writes
+ let _ = h.drain_persistence();
let changed = h.handle.replace_system_head("same").await;
assert_eq!(
changed,
@@ -878,9 +874,7 @@ async fn check_auto_compact_triggers_at_threshold() {
assert_eq!(t.utilization_percent, 86);
}
-// ============================================================================
// Edge-case / integration tests
-// ============================================================================
#[tokio::test]
async fn record_agent_edited_path_deduplicates() {
@@ -974,19 +968,19 @@ async fn truncate_removes_items_after_target_prompt_index() {
// Build 3 turns: system + 3x (user + assistant)
h.handle.push_user_message(ConversationItem::system("sys"));
h.handle.push_user_message(ConversationItem::user("q1"));
- h.handle.increment_prompt_index(); // 1
+ h.handle.increment_prompt_index();
h.handle.cache_prompt_text("q1".to_string());
h.handle
.push_assistant_response(ConversationItem::assistant("a1"));
h.handle.push_user_message(ConversationItem::user("q2"));
- h.handle.increment_prompt_index(); // 2
+ h.handle.increment_prompt_index();
h.handle.cache_prompt_text("q2".to_string());
h.handle
.push_assistant_response(ConversationItem::assistant("a2"));
h.handle.push_user_message(ConversationItem::user("q3"));
- h.handle.increment_prompt_index(); // 3
+ h.handle.increment_prompt_index();
h.handle.cache_prompt_text("q3".to_string());
h.handle
.push_assistant_response(ConversationItem::assistant("a3"));
@@ -1000,7 +994,8 @@ async fn truncate_removes_items_after_target_prompt_index() {
h.handle.truncate_to_prompt_index(1).await;
let conv = h.handle.get_conversation().await;
- assert_eq!(conv.len(), 3); // sys + q1 + a1
+ // sys + q1 + a1
+ assert_eq!(conv.len(), 3);
let idx = h.handle.get_prompt_index().await;
assert_eq!(idx, 1);
@@ -1032,7 +1027,8 @@ async fn truncate_to_zero_keeps_only_system() {
h.handle.truncate_to_prompt_index(0).await;
let conv = h.handle.get_conversation().await;
- assert_eq!(conv.len(), 1); // just "sys"
+ // just "sys"
+ assert_eq!(conv.len(), 1);
assert!(matches!(&conv[0], ConversationItem::System(_)));
assert_eq!(h.handle.get_prompt_index().await, 0);
}
@@ -1040,7 +1036,7 @@ async fn truncate_to_zero_keeps_only_system() {
#[tokio::test]
async fn truncate_is_noop_when_already_at_target() {
let mut h = TestHarness::new();
- h.handle.increment_prompt_index(); // 1
+ h.handle.increment_prompt_index();
let _ = h.handle.get_prompt_index().await;
h.drain_events();
@@ -1056,9 +1052,7 @@ async fn truncate_is_noop_when_already_at_target() {
assert!(events.is_empty());
}
-// ============================================================================
// Snapshot/restore comprehensive tests
-// ============================================================================
#[tokio::test]
async fn snapshot_restore_preserves_all_fields() {
@@ -1139,9 +1133,7 @@ async fn with_initial_conversation_preserves_items() {
assert_eq!(conv.len(), 2);
}
-// ============================================================================
// BuildConversationRequest tests
-// ============================================================================
#[tokio::test]
async fn build_request_includes_all_messages() {
@@ -1234,7 +1226,8 @@ async fn build_request_injects_memory_when_no_system() {
.await
.unwrap();
- assert_eq!(request.items.len(), 2); // new System + original User
+ // new System + original User
+ assert_eq!(request.items.len(), 2);
assert!(matches!(&request.items[0], ConversationItem::System(_)));
}
@@ -1341,11 +1334,12 @@ async fn build_request_does_not_mutate_actor_state() {
.await
.unwrap();
- // Actor's own conversation should be unchanged
+ // Actor's own conversation should be `unchanged`
let conv = h.handle.get_conversation().await;
assert_eq!(conv.len(), 2);
if let ConversationItem::System(ref sys) = conv[0] {
- assert_eq!(sys.content.as_ref(), "sys"); // no memory injected into original
+ // no memory injected into original
+ assert_eq!(sys.content.as_ref(), "sys");
}
}
@@ -1424,9 +1418,7 @@ async fn build_request_with_multiple_tool_calls_and_results() {
assert_eq!(request.items.len(), 6);
}
-// ============================================================================
// Parallel tool calls with mixed accept/reject
-// ============================================================================
/// Simulates the exact sequence that `kigi-shell`'s `execute_tool_calls`
/// produces when the model emits 3 parallel tool calls and:
@@ -1451,7 +1443,7 @@ async fn parallel_tool_calls_accept_first_reject_second_skip_third() {
let h = TestHarness::new();
- // ── Turn setup ──────────────────────────────────────────────────────
+ // Turn setup
// System prompt
h.handle.push_user_message(ConversationItem::system(
"You are a helpful coding assistant.",
@@ -1464,7 +1456,7 @@ async fn parallel_tool_calls_accept_first_reject_second_skip_third() {
h.handle.increment_prompt_index();
- // ── Model response: 3 parallel tool calls ───────────────────────────
+ // Model response: 3 parallel tool calls
// The model's single assistant message contains all 3 tool calls.
// In the real code, this is built from the streaming response and pushed
// via `push_assistant_response`.
@@ -1493,7 +1485,7 @@ async fn parallel_tool_calls_accept_first_reject_second_skip_third() {
});
h.handle.push_assistant_response(assistant_with_tools);
- // ── Tool execution results (simulating execute_tool_calls) ──────────
+ // Tool execution results (simulating execute_tool_calls)
// Tool #1: read_file — user accepted, tool executed successfully
h.handle.push_tool_result(ConversationItem::tool_result(
@@ -1517,7 +1509,7 @@ async fn parallel_tool_calls_accept_first_reject_second_skip_third() {
"Tool execution cancelled due to earlier permission rejection for tool `run_terminal_cmd`",
));
- // ── Verify the conversation state ───────────────────────────────────
+ // Verify the conversation state
let conv = h.handle.get_conversation().await;
// Expected: System + User + Assistant(3 calls) + 3 ToolResults = 6 items
@@ -1731,9 +1723,7 @@ async fn parallel_tool_calls_with_rejection_persists_all_items() {
);
}
-// ============================================================================
// Race condition: cancellation mid-tool-execution → dangling calls on reload
-// ============================================================================
/// Simulates the race condition where:
/// 1. Model emits 3 parallel tool calls (single assistant message)
@@ -1972,9 +1962,7 @@ async fn all_tool_calls_dangling_after_crash() {
assert_eq!(request.items.len(), 6);
}
-// ============================================================================
// Live-session cancellation: user cancels mid-tool-execution (no restart)
-// ============================================================================
/// Simulates an in-session abort where:
/// 1. Model emits 3 parallel tool calls → assistant pushed to conversation
@@ -1984,7 +1972,7 @@ async fn all_tool_calls_dangling_after_crash() {
///
/// This is different from the reload scenario: `ChatState::new` doesn't run
/// again because the actor is still alive. The fix is that `push_user_message`
-/// now calls `repair_dangling_tool_calls` before appending the new user
+/// calls `repair_dangling_tool_calls` before appending the new user
/// message, so the conversation is cleaned up in-place.
#[tokio::test]
async fn live_cancel_before_any_tool_execution_repairs_on_next_user_message() {
@@ -1992,14 +1980,14 @@ async fn live_cancel_before_any_tool_execution_repairs_on_next_user_message() {
let h = TestHarness::new();
- // ── Turn 1: normal conversation ─────────────────────────────────────
+ // Turn 1: normal conversation
h.handle
.push_user_message(ConversationItem::system("You are a helpful assistant."));
h.handle.push_user_message(ConversationItem::user("Hello"));
h.handle
.push_assistant_response(ConversationItem::assistant("Hi! How can I help?"));
- // ── Turn 2: model wants 3 tool calls, user cancels immediately ──────
+ // Turn 2: model wants 3 tool calls, user cancels immediately
h.handle
.push_user_message(ConversationItem::user("Read, edit, and test everything"));
@@ -2023,7 +2011,7 @@ async fn live_cancel_before_any_tool_execution_repairs_on_next_user_message() {
},
]));
- // *** USER CANCELS HERE (Ctrl+C) ***
+ // USER CANCELS HERE (Ctrl+C)
// The tokio task is aborted. execute_tool_calls never ran.
// Zero ToolResult items pushed. The conversation has dangling calls.
@@ -2127,7 +2115,7 @@ async fn live_cancel_after_partial_tool_results_repairs_remaining() {
"file contents here",
));
- // *** USER CANCELS HERE — tool #2 and #3 never executed ***
+ // USER CANCELS HERE — tool #2 and #3 never executed
// User types a new prompt
h.handle.push_user_message(ConversationItem::user(
@@ -2178,7 +2166,6 @@ async fn live_cancel_after_partial_tool_results_repairs_remaining() {
}
// Turn message capture tests
-// ============================================================================
#[tokio::test]
async fn turn_capture_collects_all_message_types() {
@@ -2537,7 +2524,6 @@ async fn turn_capture_survives_integrity_repair_prefix_shrink() {
// Capture starts after the 7-item prefix: turn_start_offset == 7.
h.handle.begin_turn_capture();
- // First turn item lands while the prefix duplicates are still present.
h.handle
.push_assistant_response(ConversationItem::assistant("turn-1"));
@@ -2636,9 +2622,7 @@ async fn turn_capture_survives_persisted_memory_reminder_prepend() {
));
}
-// ============================================================================
// Narrow targeted query tests
-// ============================================================================
#[tokio::test]
async fn get_conversation_len_empty() {
@@ -2795,7 +2779,7 @@ async fn get_conversation_item_at_does_not_mutate_state() {
assert_eq!(conv.len(), 2);
}
-// ── Multimodal regression tests for get_first_user_text() ────────────────────
+// Multimodal regression tests for get_first_user_text()
/// Confirms that `get_first_user_text()` returns `None` when the first content
/// part of the first user message is an image (not text). This preserves the
@@ -2805,7 +2789,6 @@ async fn get_first_user_text_image_first_returns_none() {
use kigi_sampling_types::{ContentPart, UserItem};
let h = TestHarness::new();
- // First message: image-only user message (no text part)
h.handle.push_user_message(ConversationItem::User(UserItem {
content: vec![ContentPart::Image {
url: "data:image/png;base64,abc".into(),
@@ -2865,7 +2848,7 @@ async fn get_first_user_text_text_then_image_returns_text() {
assert_eq!(text.as_deref(), Some("look at this"));
}
-// ── Tests for GetLastUserQueryText, GetConversationCounts, GetSystemMessage ───
+// Tests for GetLastUserQueryText, GetConversationCounts, GetSystemMessage
#[tokio::test]
async fn get_last_user_query_text_empty_conversation() {
@@ -2935,18 +2918,17 @@ async fn get_system_message_returns_first_system() {
assert!(matches!(sys, ConversationItem::System(s) if s.content.as_ref() == "You are helpful."));
}
-// ============================================================================
// Subagent bootstrap regression tests
//
// These verify that `replace_conversation` correctly syncs the system prompt
// into a ChatStateActor that was spawned before the prompt was built — the
// exact sequence used by `spawn_session_actor` for subagents.
-// ============================================================================
#[tokio::test]
async fn fresh_subagent_bootstrap_has_system_message_after_replace() {
// Simulate a fresh (non-forked) subagent: actor starts with an empty conversation.
- let h = TestHarness::new(); // spawns with vec![]
+ // spawns with vec![]
+ let h = TestHarness::new();
// At this point the actor has no system message, mirroring the bug.
assert!(h.handle.get_system_message().await.is_none());
@@ -3005,9 +2987,7 @@ async fn forked_subagent_bootstrap_replaces_parent_system_message() {
assert_eq!(conv.len(), 3);
}
-// ============================================================================
// In-memory retained pruning tests (PR3)
-// ============================================================================
/// Helper: push N complete turns (user + assistant + tool-result) so the
/// conversation grows to a predictable length.
@@ -3165,8 +3145,10 @@ async fn prune_retained_bounds_long_session_footprint() {
use crate::persistence::MockChatPersistence;
use crate::types::PruningConfig;
- const TURNS: usize = 50; // enough turns to clear many old tool results
- const CONTENT_LEN: usize = 50_000; // 50 KB per tool result
+ // enough turns to clear many old tool results
+ const TURNS: usize = 50;
+ // 50 KB per tool result
+ const CONTENT_LEN: usize = 50_000;
const PLACEHOLDER_LEN: usize = "[Tool result omitted — too old]".len();
let (mock, _rx) = MockChatPersistence::new();
@@ -3324,7 +3306,8 @@ async fn prune_retained_synthetic_user_does_not_advance_age() {
// Three real turns, each with a large tool result.
for i in 0..3usize {
handle.push_user_message(ConversationItem::user(format!("real q{i}")));
- handle.increment_prompt_index(); // prompt_index = i+1
+ // prompt_index = i+1
+ handle.increment_prompt_index();
handle.push_assistant_response(ConversationItem::assistant(format!("a{i}")));
handle.push_tool_result(ConversationItem::tool_result(
format!("call_{i}"),
@@ -3339,7 +3322,8 @@ async fn prune_retained_synthetic_user_does_not_advance_age() {
// Fourth real turn starts: prompt_index → 4, pruning fires inside push_user_message.
handle.push_user_message(ConversationItem::user("real q3"));
- handle.increment_prompt_index(); // prompt_index = 4
+ // prompt_index = 4
+ handle.increment_prompt_index();
// Sync
let conv = handle.get_conversation().await;
@@ -3616,7 +3600,6 @@ async fn context_window_downgrade_triggers_auto_compact() {
"api_backend must not change"
);
- // Now auto-compact sees the 128k window and fires
let trigger = h.handle.check_auto_compact_needed(85).await;
assert!(
trigger.is_some(),
@@ -3633,14 +3616,13 @@ async fn context_window_downgrade_triggers_auto_compact() {
);
}
-// ============================================================================
// KV Cache Prefix Stability Tests
//
// These test `build_conversation_request()` output prefix stability through
// the full pipeline -- pruning, memory injection, image pruning, snapshot
// restore. Prefix stability within a compaction epoch is the invariant that
// keeps the inference engine's prefix / KV cache hitting. The sibling-Reasoning refactor
-// deleted the placeholder/splice machinery these tests previously had to work
+// deleted the placeholder/splice machinery these tests earlier had to work
// around.
//
// These target the refactored sibling-Reasoning shape:
@@ -3649,7 +3631,6 @@ async fn context_window_downgrade_triggers_auto_compact() {
// - Reasoning lives as `ConversationItem::Reasoning(rs::ReasoningItem)`
// siblings; the From<&ConversationRequest> for rs::CreateResponse impl
// emits them inline in `input` order.
-// ============================================================================
/// Serialize a ConversationRequest using only the public
/// `From<&ConversationRequest> for rs::CreateResponse` trait impl.
@@ -4049,8 +4030,6 @@ async fn prefix_stable_after_image_pruning() {
// Image stripping mutates the old user turn's content, so full
// byte-level prefix stability cannot hold at that item. We verify:
- // 1. System prompt preserved
- // 2. Items grew
// 3. Text items appear in the same relative order
let body1 = serialize_via_public_api(&req1);
let body2 = serialize_via_public_api(&req2);
@@ -4164,7 +4143,8 @@ async fn prefix_stable_after_tool_result_pruning() {
h.handle
.push_tool_result(ConversationItem::tool_result("c2", "y".repeat(500)));
h.handle.push_user_message(ConversationItem::user("q3"));
- h.handle.record_token_usage(6000); // > 50% of 10k context
+ // > 50% of 10k context
+ h.handle.record_token_usage(6000);
let req2 = h
.handle
@@ -4319,9 +4299,7 @@ async fn prefix_stable_after_session_resume() {
);
}
-// ============================================================================
// Out-of-band history repair (kigi/session/repair)
-// ============================================================================
/// Bricked-session shape: an orphaned tool result survives load (the eager
/// repairs only fix dangling calls) and 400s on every request. The
diff --git a/crates/codegen/kigi-chat-state/src/commands.rs b/crates/codegen/kigi-chat-state/src/commands.rs
index db467e0..78d913b 100644
--- a/crates/codegen/kigi-chat-state/src/commands.rs
+++ b/crates/codegen/kigi-chat-state/src/commands.rs
@@ -37,7 +37,7 @@ impl std::error::Error for RepairHistoryBlocked {}
/// Commands sent to the ChatStateActor via mpsc channel.
pub enum ChatStateCommand {
- // ═══ Mutations (fire-and-forget) ═══
+ // Mutations (fire-and-forget)
/// Push a user message into the conversation.
PushUserMessage { item: ConversationItem },
@@ -64,7 +64,7 @@ pub enum ChatStateCommand {
RecordTokenUsage { total_tokens: u64 },
/// Stash the per-turn `TokenUsage` from the most recent model response.
- /// Overwrites any previously stashed value.
+ /// Overwrites any earlier stashed value.
RecordLastTurnUsage { usage: TokenUsage },
RecordModelCallUsage {
@@ -176,7 +176,7 @@ pub enum ChatStateCommand {
/// Repair dangling tool calls after a harness-initiated halt.
RepairDanglingAfterHarnessHalt { class: &'static str },
- // ═══ Queries (request/response via oneshot) ═══
+ // Queries (request/response via oneshot)
/// Build a ConversationRequest ready to send to the API.
/// Clones the conversation, prunes old tool results, repairs dangling
/// tool calls, injects memory reminder, and assembles the request.
@@ -280,7 +280,7 @@ pub enum ChatStateCommand {
reply: oneshot::Sender>>,
},
- // ═══ Narrow targeted queries (avoid full-conversation clone) ═══
+ // Narrow targeted queries (avoid full-conversation clone)
/// Get the number of items in the conversation.
/// Cheaper than `GetConversation` when only the length is needed.
GetConversationLen { reply: oneshot::Sender },
diff --git a/crates/codegen/kigi-chat-state/src/compaction_mode.rs b/crates/codegen/kigi-chat-state/src/compaction_mode.rs
index 01718ee..78511e0 100644
--- a/crates/codegen/kigi-chat-state/src/compaction_mode.rs
+++ b/crates/codegen/kigi-chat-state/src/compaction_mode.rs
@@ -32,7 +32,7 @@ impl CompactionMode {
}
}
- /// Replace the detail level if this is `Segments`, else unchanged. Lets the
+ /// Replace the detail level if this is `Segments`, else `unchanged`. Lets the
/// resolver attach the separately-resolved `KIGI_COMPACTION_DETAIL`.
pub fn with_segment_detail(self, detail: CompactionDetail) -> Self {
match self {
diff --git a/crates/codegen/kigi-chat-state/src/compaction_transcript.rs b/crates/codegen/kigi-chat-state/src/compaction_transcript.rs
index f3221d0..5a4bcfd 100644
--- a/crates/codegen/kigi-chat-state/src/compaction_transcript.rs
+++ b/crates/codegen/kigi-chat-state/src/compaction_transcript.rs
@@ -77,7 +77,7 @@ pub const INDEX_HEADER: &str = "# Compaction Segment Index\n\n\
| Segment | File | Turns | Approx bytes | Keywords |\n\
|---|---|---|---|---|\n";
-/// Zero-padded segment number, e.g. `007`. The single source of the pad width.
+/// Zero-`padded` segment number, e.g. `007`. The single source of the pad width.
fn segment_label(index: u64) -> String {
format!("{index:03}")
}
@@ -724,7 +724,7 @@ mod tests {
assert_eq!(classify_compaction_path("compaction/notes.md"), None);
}
- // --- Parity with the Python implementation's own test vectors (compaction_utils_test.py) ---
+ // Parity with the Python implementation's own test vectors (compaction_utils_test.py)
/// Keyword extraction: the Python `TestExtractKeywords` vectors (bare `8.`
/// headers, stopword filtering, dedup, no-section-8 fallback) plus our
diff --git a/crates/codegen/kigi-chat-state/src/compaction_utils.rs b/crates/codegen/kigi-chat-state/src/compaction_utils.rs
index 462d49a..d12a987 100644
--- a/crates/codegen/kigi-chat-state/src/compaction_utils.rs
+++ b/crates/codegen/kigi-chat-state/src/compaction_utils.rs
@@ -299,7 +299,7 @@ pub fn extract_last_user_query(conversation: &[ConversationItem]) -> Option String {
/// A markdown "**Analysis**"-style header has no opening `` tag for
/// step 1 to catch; it ends at an orphan ` `. Everything up to and
/// including the *last* `` is dropped, so a scratchpad that itself
-/// quotes `` mid-reasoning is still removed whole. The peel is
+/// quotes `` mid-reasoning is still stripped whole. The peel is
/// skipped when the block already starts with a numbered section — including a
/// markdown-decorated one like `## 1.` or `**1.**` — so a `` merely
/// echoed inside a real section never truncates the summary. Any leftover
diff --git a/crates/codegen/kigi-chat-state/src/handle.rs b/crates/codegen/kigi-chat-state/src/handle.rs
index 5901978..cf58497 100644
--- a/crates/codegen/kigi-chat-state/src/handle.rs
+++ b/crates/codegen/kigi-chat-state/src/handle.rs
@@ -34,7 +34,7 @@ impl ChatStateHandle {
Self { cmd_tx }
}
- // ═══ Fire-and-forget mutations ═══
+ // Fire-and-forget mutations
/// Push a user message into the conversation.
pub fn push_user_message(&self, item: ConversationItem) {
@@ -238,7 +238,6 @@ impl ChatStateHandle {
.send(ChatStateCommand::UpdateCredentials { credentials });
}
- /// Restore from a snapshot.
pub fn restore_snapshot(&self, snapshot: ChatStateSnapshot) {
let _ = self
.cmd_tx
@@ -280,7 +279,7 @@ impl ChatStateHandle {
.send(ChatStateCommand::RepairDanglingAfterHarnessHalt { class });
}
- // ═══ Async queries (via oneshot) ═══
+ // Async queries (via oneshot)
/// Send a query to the actor and await the reply.
///
@@ -419,7 +418,6 @@ impl ChatStateHandle {
.unwrap_or(0)
}
- /// Get sampling config.
pub async fn get_sampling_config(&self) -> Option {
self.query("GetSamplingConfig", |reply| {
ChatStateCommand::GetSamplingConfig { reply }
@@ -501,7 +499,6 @@ impl ChatStateHandle {
.unwrap_or_default()
}
- /// Check if auto-compact is needed.
pub async fn check_auto_compact_needed(
&self,
threshold_percent: u8,
@@ -516,7 +513,7 @@ impl ChatStateHandle {
.flatten()
}
- // ═══ Narrow targeted queries ═══
+ // Narrow targeted queries
/// Get the number of items in the conversation.
///
diff --git a/crates/codegen/kigi-chat-state/src/lib.rs b/crates/codegen/kigi-chat-state/src/lib.rs
index d6e6bb9..61f6ec5 100644
--- a/crates/codegen/kigi-chat-state/src/lib.rs
+++ b/crates/codegen/kigi-chat-state/src/lib.rs
@@ -1,8 +1,7 @@
//! kigi-chat-state — Actor-based chat state management for xAI agents.
//!
-//! This crate extracts conversation state management from `kigi-shell`'s
-//! `acp_session.rs` into a standalone actor. It follows the same actor pattern
-//! as `kigi-hunk-tracker`:
+//! Holds the conversation state driven by `kigi-shell`'s `acp_session.rs`,
+//! following the same actor pattern as `kigi-hunk-tracker`:
//!
//! ```text
//! ┌────────────────┐ ┌──────────────────────────────────────┐
@@ -35,7 +34,6 @@ pub mod persistence;
pub mod types;
pub mod usage;
-// Re-export main types for convenience
pub use actor::ChatStateActor;
pub use actor::state::{
estimate_conversation_tokens, estimate_item_tokens, estimate_messages_tokens,
diff --git a/crates/codegen/kigi-chat-state/src/persistence.rs b/crates/codegen/kigi-chat-state/src/persistence.rs
index 93c2eb0..e057da5 100644
--- a/crates/codegen/kigi-chat-state/src/persistence.rs
+++ b/crates/codegen/kigi-chat-state/src/persistence.rs
@@ -27,9 +27,7 @@ pub trait ChatPersistence: Send + 'static {
fn flush(&mut self);
}
-// ============================================================================
// Mock (test double) — channel-based, no locks, no atomics
-// ============================================================================
/// A record of a persistence call, sent over a channel to the test.
#[derive(Debug, Clone)]
@@ -101,9 +99,7 @@ impl ChatPersistence for MockChatPersistence {
}
}
-// ============================================================================
// Null (noop) — for benchmarks / scenarios where persistence is unwanted
-// ============================================================================
/// No-op implementation: discards everything (for benchmarks / noop scenarios).
pub struct NullChatPersistence;
diff --git a/crates/codegen/kigi-chat-state/src/types.rs b/crates/codegen/kigi-chat-state/src/types.rs
index 8aed961..d72dd88 100644
--- a/crates/codegen/kigi-chat-state/src/types.rs
+++ b/crates/codegen/kigi-chat-state/src/types.rs
@@ -13,54 +13,46 @@ use serde::{Deserialize, Serialize};
/// an injected block.
pub const MEMORY_CONTEXT_OPEN_TAG: &str = "";
-/// Closing tag paired with [`MEMORY_CONTEXT_OPEN_TAG`].
pub const MEMORY_CONTEXT_CLOSE_TAG: &str = " ";
/// Configuration for the ChatStateActor at spawn time.
#[derive(Debug, Clone)]
pub struct ChatStateConfig {
- /// Initial conversation items to populate the state with.
pub initial_conversation: Vec,
- /// Sampling configuration (model, context window, etc.).
pub sampling_config: SamplingConfig,
}
/// Immutable snapshot of the actor's state (for forking, rewind).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatStateSnapshot {
- /// The full conversation history.
pub conversation: Vec,
- /// Current sampling configuration.
pub sampling_config: SamplingConfig,
- /// Current prompt index (incremented per user turn).
+ /// Incremented per user turn.
pub prompt_index: usize,
- /// Accumulated token usage.
pub total_tokens: u64,
/// Bytes/4 estimate of the conversation as of the last `record_token_usage`.
- /// `0` means unknown (pre-field snapshot); restore re-estimates instead.
+ /// `0` means unknown (snapshot written without the field); restore
+ /// re-estimates instead.
#[serde(default)]
pub estimate_at_last_response: u64,
- /// File paths the agent has edited.
pub agent_edited_paths: BTreeSet,
- /// Cached prompt texts for rewind preview.
+ /// Cached for rewind preview.
pub prompt_texts: Vec,
- /// Timestamp when the current stream started (epoch ms).
+ /// Epoch ms.
pub stream_start_ms: Option,
- /// Timestamp when the current turn started (epoch ms).
+ /// Epoch ms.
pub turn_start_ms: Option,
- /// Prompt index at which the last compaction occurred.
pub last_compaction_prompt_index: Option,
- /// Opaque credential secrets (API key, optional extra auth, client version).
#[serde(default)]
pub credentials: Credentials,
}
-/// Metadata for session notifications (timing info).
+/// Timing metadata for session notifications.
#[derive(Debug, Clone)]
pub struct NotificationMeta {
- /// Timestamp when the current stream started (epoch ms).
+ /// Epoch ms.
pub stream_start_ms: Option,
- /// Timestamp when the current turn started (epoch ms).
+ /// Epoch ms.
pub turn_start_ms: Option,
}
@@ -70,7 +62,6 @@ pub struct NotificationMeta {
/// Two modes: soft trim (keep head + tail) and hard clear (replace entirely).
#[derive(Debug, Clone)]
pub struct PruningConfig {
- /// Whether pruning is enabled.
pub enabled: bool,
/// Number of recent turns whose tool results are never pruned.
pub keep_last_n_turns: usize,
@@ -116,9 +107,7 @@ pub enum AuthType {
/// The actor just stores and returns them — it never interprets them.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Credentials {
- /// API key for authentication.
pub api_key: Option,
- /// Whether this is a session token (refreshable) or user-provided api key.
#[serde(default)]
pub auth_type: AuthType,
/// Optional extra auth material forwarded with requests when present.
@@ -130,7 +119,7 @@ pub struct Credentials {
/// Produced by `TakeTurnMessages` after a `BeginTurnCapture`/message-push cycle.
#[derive(Debug, Clone)]
pub struct TurnCapture {
- /// The ordered sequence of messages appended during this turn.
+ /// In the order they were appended.
pub messages: Vec,
/// Whether compaction (conversation replacement) occurred mid-turn.
pub compaction_occurred: bool,
@@ -142,24 +131,18 @@ pub struct TurnCapture {
/// when only role counts and total length are needed (e.g. for telemetry).
#[derive(Debug, Clone, Default)]
pub struct ConversationCounts {
- /// Total number of items in the conversation.
pub total: usize,
- /// Number of `User` items.
pub user: usize,
- /// Number of `Assistant` items.
pub assistant: usize,
- /// Number of `ToolResult` items.
pub tool_result: usize,
}
/// Info returned when auto-compact threshold is exceeded.
#[derive(Debug, Clone)]
pub struct AutoCompactTrigger {
- /// Current total token count.
pub total_tokens: u64,
- /// Model's context window size.
pub context_window: NonZeroU64,
- /// Current utilization as a percentage (0–100).
+ /// 0–100.
pub utilization_percent: u8,
}
diff --git a/crates/codegen/kigi-codebase-graph/src/bin/bench_file_listing.rs b/crates/codegen/kigi-codebase-graph/src/bin/bench_file_listing.rs
index 21bd16e..6f85609 100644
--- a/crates/codegen/kigi-codebase-graph/src/bin/bench_file_listing.rs
+++ b/crates/codegen/kigi-codebase-graph/src/bin/bench_file_listing.rs
@@ -45,7 +45,6 @@ fn main() {
println!("git2 (index only): {} files in {:?}", files.len(), elapsed);
}
_ => {
- // Run all three methods multiple times for comparison
println!("Benchmarking file listing for: {}", root_path.display());
println!();
@@ -56,7 +55,6 @@ fn main() {
let _ = collect_files_git2(root_path, ®istry);
let _ = collect_files_git2_index_only(root_path, ®istry);
- // CLI benchmark
let mut cli_times = Vec::with_capacity(iterations);
let mut cli_count = 0;
for _ in 0..iterations {
@@ -66,7 +64,6 @@ fn main() {
cli_count = files.len();
}
- // git2 benchmark (with untracked)
let mut git2_times = Vec::with_capacity(iterations);
let mut git2_count = 0;
for _ in 0..iterations {
@@ -76,7 +73,6 @@ fn main() {
git2_count = files.len();
}
- // git2 index-only benchmark
let mut git2_index_times = Vec::with_capacity(iterations);
let mut git2_index_count = 0;
for _ in 0..iterations {
@@ -86,7 +82,6 @@ fn main() {
git2_index_count = files.len();
}
- // Print results
let cli_avg = cli_times.iter().sum::() / iterations as u32;
let git2_avg = git2_times.iter().sum::() / iterations as u32;
let git2_index_avg =
@@ -117,9 +112,7 @@ fn main() {
}
}
-/// Collect files using git CLI (original approach)
fn collect_files_cli(root_path: &Path, registry: &LanguageRegistry) -> Vec {
- // Get tracked files
let tracked_output = Command::new("git")
.args(["ls-files"])
.current_dir(root_path)
@@ -130,7 +123,6 @@ fn collect_files_cli(root_path: &Path, registry: &LanguageRegistry) -> Vec return vec![],
};
- // Get untracked files
let untracked_output = Command::new("git")
.args(["ls-files", "--others", "--exclude-standard"])
.current_dir(root_path)
@@ -159,7 +151,6 @@ fn collect_files_cli(root_path: &Path, registry: &LanguageRegistry) -> Vec Vec {
let repo = match Repository::open(root_path) {
Ok(r) => r,
@@ -183,7 +174,6 @@ fn collect_files_git2(root_path: &Path, registry: &LanguageRegistry) -> Vec Vec) -> PathBuf {
custom_cache
.map(|p| p.to_path_buf())
.unwrap_or_else(|| get_cache_path(repo_path))
}
-/// Load index from cache or build if necessary.
fn load_or_build_index(repo_path: &Path, cache_path: &Path) -> ScopeGraphIndex {
if let Ok(index) = load_index(cache_path) {
println!("Loaded index from cache: {}", cache_path.display());
@@ -204,7 +202,6 @@ fn load_or_build_index(repo_path: &Path, cache_path: &Path) -> ScopeGraphIndex {
files, defs, refs, elapsed
);
- // Save to cache
if let Err(e) = save_index(cache_path, &index) {
println!("Warning: Failed to save cache: {}", e);
} else {
@@ -260,7 +257,6 @@ fn cmd_definition(
let navigator = Navigator::new(index);
let result = match (file, row, col, symbol) {
- // Position-based lookup
(Some(file_path), Some(r), Some(c), _) => {
let abs_path = if file_path.is_absolute() {
file_path
@@ -276,7 +272,6 @@ fn cmd_definition(
}
}
}
- // Symbol-based lookup
(_, _, _, Some(sym)) => navigator.goto_definition_by_name(&sym, None),
_ => {
println!("Error: Must provide either --file, --row, --col OR --symbol");
@@ -310,7 +305,6 @@ fn cmd_references(
let navigator = Navigator::new(index);
let result = match (file, row, col, symbol) {
- // Position-based lookup
(Some(file_path), Some(r), Some(c), _) => {
let abs_path = if file_path.is_absolute() {
file_path
@@ -326,7 +320,6 @@ fn cmd_references(
}
}
}
- // Symbol-based lookup
(_, _, _, Some(sym)) => navigator.goto_references_by_name(&sym, None, include_definition),
_ => {
println!("Error: Must provide either --file, --row, --col OR --symbol");
@@ -361,7 +354,6 @@ fn cmd_stats(path: &Path, custom_cache: Option<&Path>) {
println!(" References: {}", refs);
println!(" Aliases: {}", index.alias_count());
- // Top symbols by reference count
let ref_counts = index.top_referenced_symbols(10);
println!("\nTop 10 most referenced symbols:");
diff --git a/crates/codegen/kigi-codebase-graph/src/index_manager.rs b/crates/codegen/kigi-codebase-graph/src/index_manager.rs
index b44c6e4..9b48617 100644
--- a/crates/codegen/kigi-codebase-graph/src/index_manager.rs
+++ b/crates/codegen/kigi-codebase-graph/src/index_manager.rs
@@ -149,7 +149,6 @@ pub enum IndexCommand {
BackgroundRefresh {
/// Files that need reindexing (stale or new)
stale_files: Vec,
- /// Files that were deleted
deleted_files: Vec,
},
/// Get the number of indexed files (lightweight, no clone)
@@ -330,7 +329,7 @@ impl IndexManagerHandle {
self.command_tx.send(IndexCommand::Shutdown)
}
- // ========== Async Query APIs ==========
+ // Async Query APIs
/// Go to definition at the given position (async).
///
@@ -417,7 +416,7 @@ impl IndexManagerHandle {
Ok(rx.await.expect("IndexManager dropped before responding"))
}
- // ========== Blocking Query APIs ==========
+ // Blocking Query APIs
/// Go to definition at the given position (blocking).
pub fn goto_definition_blocking(
@@ -518,7 +517,6 @@ impl IndexManagerConfig {
}
}
- /// Set the cache path.
pub fn with_cache_path(mut self, path: PathBuf) -> Self {
self.cache_path = Some(path);
self
@@ -1349,12 +1347,12 @@ fn background_index_refresh(
if cached_meta.is_stale(path_ref) {
// Check if file exists or is deleted
if path_ref.exists() {
- Some((Some(path.clone()), None)) // Stale
+ Some((Some(path.clone()), None))
} else {
- Some((None, Some(path.clone()))) // Deleted
+ Some((None, Some(path.clone())))
}
} else {
- None // Up to date
+ None
}
})
.fold(
@@ -1391,8 +1389,10 @@ fn background_index_refresh(
let registry = crate::languages::LanguageRegistry::new();
let new_files: Vec = ignore::WalkBuilder::new(&root_path)
- .hidden(true) // Skip hidden files/dirs
- .git_ignore(true) // Respect .gitignore
+ // Skip hidden files/dirs
+ .hidden(true)
+ // Respect .gitignore
+ .git_ignore(true)
.git_global(true)
.git_exclude(true)
.build()
@@ -1530,7 +1530,7 @@ impl CoalescedEvents {
fn add(&mut self, event: FileEvent) {
// Renames are special: they carry two paths. Process the "to" path
- // as Created (it needs indexing) and the "from" as Removed.
+ // as `Created` (it needs indexing) and the "from" as `Removed`.
if event.kind == FileEventKind::Renamed && event.paths.len() >= 2 {
self.insert(event.paths[0].clone(), FileEventKind::Removed);
self.insert(event.paths[1].clone(), FileEventKind::Created);
@@ -1551,11 +1551,11 @@ impl CoalescedEvents {
Entry::Occupied(mut e) => {
let prev = *e.get();
match (prev, kind) {
- // Created/Modified then Removed → cancel both
+ // `Created`/`Modified` then `Removed` → cancel both
(FileEventKind::Created | FileEventKind::Modified, FileEventKind::Removed) => {
e.remove();
}
- // Removed then Created/Modified → file replaced, treat as Created
+ // `Removed` then `Created`/`Modified` → file replaced, treat as `Created`
(FileEventKind::Removed, FileEventKind::Created | FileEventKind::Modified) => {
e.insert(FileEventKind::Created);
}
@@ -1649,8 +1649,10 @@ fn is_identifier_like(node: &tree_sitter::Node<'_>) -> bool {
|| kind == "field_identifier"
|| kind == "shorthand_property_identifier"
|| kind == "shorthand_property_identifier_pattern"
- || kind == "attribute" // Python
- || kind == "package_identifier" // Go
+ // Python
+ || kind == "attribute"
+ // Go
+ || kind == "package_identifier"
}
#[cfg(test)]
@@ -1830,7 +1832,8 @@ mod tests {
let dir = tempdir().unwrap();
let file_path = dir.path().join("huge.rs");
// Write a file larger than MAX_INDEXABLE_FILE_SIZE
- let content = "fn a() {}\n".repeat(600_000); // ~6MB
+ // ~6MB
+ let content = "fn a() {}\n".repeat(600_000);
fs::write(&file_path, &content).unwrap();
let config = IndexManagerConfig::new(dir.path().to_path_buf())
@@ -1891,7 +1894,8 @@ mod tests {
fs::write(dir.path().join("binary.rs"), &binary).unwrap();
// Oversized file — should be skipped
- let big = "fn big() {}\n".repeat(500_000); // ~6MB
+ // ~6MB
+ let big = "fn big() {}\n".repeat(500_000);
fs::write(dir.path().join("huge.rs"), &big).unwrap();
let index = IndexBuilder::new().build(dir.path()).unwrap();
@@ -1930,12 +1934,13 @@ mod tests {
let stats = handle.get_stats().unwrap();
assert_eq!(stats.files, 1);
- assert!(stats.definitions >= 2); // hello + world
+ // hello + world
+ assert!(stats.definitions >= 2);
handle.shutdown().unwrap();
}
- // ========== CoalescedEvents tests ==========
+ // CoalescedEvents tests
#[test]
fn test_coalesce_create_then_remove_cancels() {
@@ -2003,7 +2008,7 @@ mod tests {
let mut c = CoalescedEvents::new();
c.add(FileEvent::renamed("/a.rs".into(), "/b.rs".into()));
c.add(FileEvent::removed("/b.rs".into()));
- // /a.rs should still be Removed, /b.rs Created+Removed = cancelled
+ // /a.rs should still be `Removed`, /b.rs `Created`+`Removed` = cancelled
assert_eq!(c.events.len(), 1);
assert_eq!(c.events[&PathBuf::from("/a.rs")], FileEventKind::Removed);
}
@@ -2013,7 +2018,7 @@ mod tests {
let mut c = CoalescedEvents::new();
c.add(FileEvent::renamed("/a.rs".into(), "/b.rs".into()));
c.add(FileEvent::modified("/b.rs".into()));
- // /a.rs Removed, /b.rs Created+Modified → Modified (last writer wins)
+ // /a.rs `Removed`, /b.rs `Created`+`Modified` → `Modified` (last writer wins)
assert_eq!(c.events.len(), 2);
assert_eq!(c.events[&PathBuf::from("/a.rs")], FileEventKind::Removed);
assert_eq!(c.events[&PathBuf::from("/b.rs")], FileEventKind::Modified);
diff --git a/crates/codegen/kigi-codebase-graph/src/interner.rs b/crates/codegen/kigi-codebase-graph/src/interner.rs
index 62d3763..8f6a1d3 100644
--- a/crates/codegen/kigi-codebase-graph/src/interner.rs
+++ b/crates/codegen/kigi-codebase-graph/src/interner.rs
@@ -52,7 +52,6 @@ impl StringId {
Self(id)
}
- /// Get the raw u32 value.
#[inline]
pub const fn as_u32(self) -> u32 {
self.0
@@ -209,7 +208,6 @@ impl StringInterner {
self.offsets.is_empty()
}
- /// Total bytes used by the arena.
#[inline]
pub fn arena_bytes(&self) -> usize {
self.arena.len()
@@ -271,7 +269,7 @@ impl StringInterner {
///
/// After a bulk build the arena and offsets Vecs may hold up to 2× their
/// actual content due to doubling growth. Calling this reclaims that
- /// wasted heap. The lookup table is intentionally left unshrunk because
+ /// wasted heap. The lookup table is deliberately left unshrunk because
/// it benefits from load-factor headroom.
///
/// This is an internal maintenance hook called by `ScopeGraphIndex::compact()`.
@@ -313,7 +311,7 @@ mod tests {
let id1 = interner.intern("src");
let id2 = interner.intern("lib");
- let id3 = interner.intern("src"); // duplicate
+ let id3 = interner.intern("src");
assert_eq!(id1, id3);
assert_ne!(id1, id2);
@@ -348,7 +346,8 @@ mod tests {
// Invalid UTF-8
let invalid_utf8: &[u8] = &[0x80, 0x81, 0x82];
let id2 = interner.intern_bytes(invalid_utf8);
- assert_eq!(interner.get(id2), None); // Not valid UTF-8
+ // Not valid UTF-8
+ assert_eq!(interner.get(id2), None);
assert_eq!(interner.get_bytes(id2), Some(invalid_utf8));
// Duplicate bytes return same ID
diff --git a/crates/codegen/kigi-codebase-graph/src/languages/javascript.rs b/crates/codegen/kigi-codebase-graph/src/languages/javascript.rs
index 10b6df3..31d5054 100644
--- a/crates/codegen/kigi-codebase-graph/src/languages/javascript.rs
+++ b/crates/codegen/kigi-codebase-graph/src/languages/javascript.rs
@@ -1,5 +1,3 @@
-//! JavaScript/JSX language configuration.
-
use crate::languages::types::TSLanguageConfig;
pub fn js_lang() -> TSLanguageConfig {
diff --git a/crates/codegen/kigi-codebase-graph/src/languages/mod.rs b/crates/codegen/kigi-codebase-graph/src/languages/mod.rs
index 605a8bc..3f02fde 100644
--- a/crates/codegen/kigi-codebase-graph/src/languages/mod.rs
+++ b/crates/codegen/kigi-codebase-graph/src/languages/mod.rs
@@ -114,7 +114,7 @@ impl LanguageRegistry {
/// Compute a hash of all tree-sitter queries across all languages.
///
/// This is used to detect when queries change, which should trigger
- /// a rebuild of the index even if file contents haven't changed.
+ /// a rebuild of the index even if file contents are `unchanged`.
///
/// The hash is computed by:
/// 1. Sorting languages by their primary ID for deterministic ordering
diff --git a/crates/codegen/kigi-codebase-graph/src/languages/python.rs b/crates/codegen/kigi-codebase-graph/src/languages/python.rs
index 5cf16b1..8bc1a1b 100644
--- a/crates/codegen/kigi-codebase-graph/src/languages/python.rs
+++ b/crates/codegen/kigi-codebase-graph/src/languages/python.rs
@@ -1,5 +1,3 @@
-//! Python language configuration.
-
use crate::languages::types::TSLanguageConfig;
pub fn python_lang() -> TSLanguageConfig {
@@ -12,7 +10,6 @@ pub fn python_lang() -> TSLanguageConfig {
"variable".to_owned(),
"module".to_owned(),
]],
- // Python definitions query
r#"
; Class definitions
(class_definition
diff --git a/crates/codegen/kigi-codebase-graph/src/languages/ts.rs b/crates/codegen/kigi-codebase-graph/src/languages/ts.rs
index 2142d06..8134632 100644
--- a/crates/codegen/kigi-codebase-graph/src/languages/ts.rs
+++ b/crates/codegen/kigi-codebase-graph/src/languages/ts.rs
@@ -19,7 +19,6 @@ pub fn ts_lang() -> TSLanguageConfig {
"const".to_owned(),
"let".to_owned(),
]],
- // Comprehensive TypeScript query with full type coverage
r#"
;; === DEFINITIONS ===
diff --git a/crates/codegen/kigi-codebase-graph/src/languages/types.rs b/crates/codegen/kigi-codebase-graph/src/languages/types.rs
index 9409340..220c3c0 100644
--- a/crates/codegen/kigi-codebase-graph/src/languages/types.rs
+++ b/crates/codegen/kigi-codebase-graph/src/languages/types.rs
@@ -30,7 +30,6 @@ impl TSLanguageConfig {
}
}
- /// Get the language IDs.
pub fn language_ids(&self) -> &[String] {
&self.language_ids
}
@@ -43,17 +42,14 @@ impl TSLanguageConfig {
.unwrap_or("unknown")
}
- /// Get the file extensions.
pub fn file_extensions(&self) -> &[String] {
&self.file_extensions
}
- /// Get the namespaces.
pub fn namespaces(&self) -> &[Vec] {
&self.namespaces
}
- /// Get the file definition queries.
pub fn file_definition_queries(&self) -> &str {
&self.file_definition_queries
}
diff --git a/crates/codegen/kigi-codebase-graph/src/manager/builder.rs b/crates/codegen/kigi-codebase-graph/src/manager/builder.rs
index c2848f5..3b69d00 100644
--- a/crates/codegen/kigi-codebase-graph/src/manager/builder.rs
+++ b/crates/codegen/kigi-codebase-graph/src/manager/builder.rs
@@ -235,7 +235,8 @@ impl IndexBuilder {
.git_ignore(self.respect_gitignore)
.git_global(self.respect_gitignore)
.git_exclude(self.respect_gitignore)
- .threads(self.num_threads.min(12)) // Use parallel walking (capped at 12)
+ // Use parallel walking (capped at 12)
+ .threads(self.num_threads.min(12))
.build_parallel();
walker.run(|| {
@@ -309,7 +310,6 @@ impl IndexBuilder {
//
// New approach: for each batch of build_batch_size files:
// 1. Parse in parallel (par_chunks preserves thread-local cache locality)
- // 2. Merge the batch into the index
// 3. Drop the batch before starting the next one
// Peak = O(build_batch_size) symbols + growing index simultaneously.
for batch in file_paths.chunks(build_batch_size) {
diff --git a/crates/codegen/kigi-codebase-graph/src/manager/cache.rs b/crates/codegen/kigi-codebase-graph/src/manager/cache.rs
index d44a821..b9c8378 100644
--- a/crates/codegen/kigi-codebase-graph/src/manager/cache.rs
+++ b/crates/codegen/kigi-codebase-graph/src/manager/cache.rs
@@ -1,25 +1,21 @@
//! Index caching for fast loading.
//!
-//! Uses a custom binary format with magic bytes "SGIX" for the new interned format.
-//! Automatically detects and skips legacy bincode format (returns error so caller can rebuild).
+//! The on-disk format is a custom binary layout tagged with the magic bytes
+//! "SGIX". Caches written by the earlier bincode format are detected and
+//! rejected rather than parsed, so the caller rebuilds from source.
use std::path::Path;
use crate::scope_graph::ScopeGraphIndex;
-/// Default cache file name.
pub const CACHE_FILE_NAME: &str = ".goto_index.bin";
-/// Error type for cache operations.
#[derive(Debug)]
pub enum CacheError {
- /// IO error.
IoError(std::io::Error),
- /// Serialization error.
SerializeError(String),
- /// Deserialization error.
DeserializeError(String),
- /// Legacy format detected (caller should rebuild).
+ /// A bincode-era cache was found; the caller is expected to rebuild.
LegacyFormat,
}
@@ -42,19 +38,14 @@ impl From for CacheError {
}
}
-/// Result type for cache operations.
pub type Result = std::result::Result;
-/// Get the default cache path for a repository.
pub fn get_cache_path(root_path: &Path) -> std::path::PathBuf {
root_path.join(CACHE_FILE_NAME)
}
-/// Load an index from cache.
-///
-/// Uses the new binary format with magic bytes "SGIX".
-/// Returns `CacheError::LegacyFormat` if the file uses the old bincode format,
-/// signaling to the caller that a rebuild is needed.
+/// Returns `CacheError::LegacyFormat` for a bincode-format cache, signaling to
+/// the caller that a rebuild is needed.
pub fn load_index(cache_path: &Path) -> Result {
if !cache_path.exists() {
return Err(CacheError::IoError(std::io::Error::new(
@@ -63,11 +54,10 @@ pub fn load_index(cache_path: &Path) -> Result {
)));
}
- // Use ScopeGraphIndex::load which handles format detection
match ScopeGraphIndex::load(cache_path) {
Ok(Some(index)) => Ok(index),
+ // `Ok(None)` is how the loader reports a legacy-format file.
Ok(None) => {
- // None means legacy format was detected
tracing::info!(
cache_path = %cache_path.display(),
"Legacy cache format detected, will rebuild"
@@ -78,15 +68,12 @@ pub fn load_index(cache_path: &Path) -> Result {
}
}
-/// Save an index to cache using the new binary format.
pub fn save_index(cache_path: &Path, index: &ScopeGraphIndex) -> Result<()> {
index.save(cache_path).map_err(CacheError::IoError)
}
-/// Save an index to cache asynchronously (in a background thread).
-///
-/// Returns immediately and spawns a thread to do the actual saving.
-/// Useful for saving the index without blocking the main thread.
+/// Saves on a detached thread: the caller gets no join handle and no result,
+/// so a failed write is only visible in the logs.
pub fn save_index_async(cache_path: std::path::PathBuf, index: ScopeGraphIndex) {
std::thread::spawn(move || {
if let Err(e) = save_index(&cache_path, &index) {
@@ -95,12 +82,11 @@ pub fn save_index_async(cache_path: std::path::PathBuf, index: ScopeGraphIndex)
});
}
-/// Check if a cache exists and return its metadata.
pub fn cache_exists(cache_path: &Path) -> bool {
cache_path.exists()
}
-/// Get cache file size in bytes.
+/// Size of the cache file in bytes, or `None` if it cannot be stat'd.
pub fn cache_size(cache_path: &Path) -> Option {
std::fs::metadata(cache_path).ok().map(|m| m.len())
}
diff --git a/crates/codegen/kigi-codebase-graph/src/manager/lock.rs b/crates/codegen/kigi-codebase-graph/src/manager/lock.rs
index 2892424..0e7793b 100644
--- a/crates/codegen/kigi-codebase-graph/src/manager/lock.rs
+++ b/crates/codegen/kigi-codebase-graph/src/manager/lock.rs
@@ -52,7 +52,8 @@ impl IndexOperation {
/// Whether this operation requires exclusive access.
pub fn is_exclusive(&self) -> bool {
match self {
- Self::Load => false, // Shared/read access
+ // Shared/read access
+ Self::Load => false,
Self::Save | Self::Build | Self::BackgroundRefresh => true,
}
}
@@ -77,8 +78,10 @@ impl std::fmt::Display for IndexOperation {
/// In-memory lock state for same-process deduplication.
struct InMemoryLockState {
operation: IndexOperation,
- readers: usize, // Count for shared locks
- exclusive: bool, // Whether an exclusive lock is held
+ // Count for shared locks
+ readers: usize,
+ // Whether an exclusive lock is held
+ exclusive: bool,
}
/// Global registry of in-memory locks (same process).
@@ -299,7 +302,6 @@ fn try_acquire_in_memory_lock(workspace: &Path, operation: IndexOperation) -> bo
true
}
-/// Release an in-memory lock.
fn release_in_memory_lock(workspace: &Path, operation: IndexOperation) {
// Use entry API for atomic check-and-modify
if let dashmap::mapref::entry::Entry::Occupied(mut entry) =
@@ -439,7 +441,6 @@ mod tests {
// Drop first lock
drop(guard1);
- // Now second should succeed
let guard3 = try_lock(workspace, IndexOperation::Build);
assert!(guard3.is_acquired());
}
@@ -490,7 +491,6 @@ mod tests {
// Drop shared lock
drop(guard1);
- // Now exclusive should succeed
let guard3 = try_lock(workspace, IndexOperation::Build);
assert!(guard3.is_acquired());
}
diff --git a/crates/codegen/kigi-codebase-graph/src/manager/mod.rs b/crates/codegen/kigi-codebase-graph/src/manager/mod.rs
index 271e350..cf390ad 100644
--- a/crates/codegen/kigi-codebase-graph/src/manager/mod.rs
+++ b/crates/codegen/kigi-codebase-graph/src/manager/mod.rs
@@ -1,4 +1,4 @@
-//! Index management: building, caching, locking, and updating.
+//! Index management: building, caching, and workspace locking.
mod builder;
pub mod cache;
diff --git a/crates/codegen/kigi-codebase-graph/src/navigation.rs b/crates/codegen/kigi-codebase-graph/src/navigation.rs
index aa20670..99b5ddb 100644
--- a/crates/codegen/kigi-codebase-graph/src/navigation.rs
+++ b/crates/codegen/kigi-codebase-graph/src/navigation.rs
@@ -238,7 +238,7 @@ impl Navigator {
///
/// # Arguments
/// * `file_path` - Path to the file
- /// * `row` - 1-indexed line number
+ /// * `row` - 1-indexed line number
/// * `col` - 1-indexed column number
/// * `include_definition` - Whether to include the definition location in results
///
@@ -388,8 +388,8 @@ fn is_identifier_like(node: &tree_sitter::Node<'_>) -> bool {
| "field_identifier"
| "shorthand_property_identifier"
| "shorthand_property_identifier_pattern"
- | "attribute" // Python
- | "package_identifier" // Go
+ | "attribute"
+ | "package_identifier"
)
}
diff --git a/crates/codegen/kigi-codebase-graph/src/scope_graph/edges.rs b/crates/codegen/kigi-codebase-graph/src/scope_graph/edges.rs
index 14ebee2..68a905b 100644
--- a/crates/codegen/kigi-codebase-graph/src/scope_graph/edges.rs
+++ b/crates/codegen/kigi-codebase-graph/src/scope_graph/edges.rs
@@ -2,21 +2,21 @@
use serde::{Deserialize, Serialize};
-/// Describes the relation between two nodes in the ScopeGraph.
+/// Edge weight in the ScopeGraph. Every variant is directed source-to-target,
+/// in the order its name reads.
#[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Debug)]
pub enum EdgeKind {
- /// The edge weight from a nested scope to its parent scope.
+ /// Nested scope to its parent scope.
ScopeToScope,
- /// The edge weight from a definition to its definition scope.
+ /// Definition to the scope that owns it, which for a hoisted def is the
+ /// parent of the scope it was written in.
DefToScope,
- /// The edge weight from an import to its definition scope.
+ /// Import to its defining scope.
ImportToScope,
- /// The edge weight from a reference to its definition.
RefToDef,
- /// The edge weight from a reference to its import.
RefToImport,
}
diff --git a/crates/codegen/kigi-codebase-graph/src/scope_graph/graph.rs b/crates/codegen/kigi-codebase-graph/src/scope_graph/graph.rs
index 6dcc9a9..b3549c5 100644
--- a/crates/codegen/kigi-codebase-graph/src/scope_graph/graph.rs
+++ b/crates/codegen/kigi-codebase-graph/src/scope_graph/graph.rs
@@ -44,7 +44,7 @@ pub type ExtractedSymbols = (
/// even if file contents haven't changed.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub enum QueryVersion {
- /// Legacy format - index was built before query versioning was added.
+ /// Legacy format - index was built without query versioning.
/// This triggers a rebuild since we don't know what queries were used.
/// Default for backwards compatibility with old cached indexes.
#[default]
@@ -394,7 +394,6 @@ impl ScopeGraph {
})
}
- /// Find all references to a given name
pub fn find_references(&self, name: &str, src: &[u8]) -> Vec {
self.graph
.node_indices()
@@ -703,9 +702,7 @@ impl ScopeGraphIndex {
}
}
- // ========================================================================
// String interning helpers
- // ========================================================================
/// Intern a string and return its ID.
#[inline]
@@ -725,9 +722,7 @@ impl ScopeGraphIndex {
self.interner.get_id(s)
}
- // ========================================================================
// File metadata operations
- // ========================================================================
/// Update file metadata (size and mtime) for staleness tracking.
pub fn update_file_meta(&mut self, path: &Path) {
@@ -751,9 +746,7 @@ impl ScopeGraphIndex {
}
}
- // ========================================================================
// Alias operations
- // ========================================================================
/// Register an alias relationship: alias_name is an alias for original_name
pub fn add_alias(&mut self, alias_name: &str, original_name: &str) {
@@ -771,9 +764,7 @@ impl ScopeGraphIndex {
self.add_alias(&alias_name, &original_name);
}
- // ========================================================================
// Symbol insertion (for builder/manager use)
- // ========================================================================
/// Add a definition occurrence for a symbol.
pub fn add_definition(&mut self, symbol: &str, path: &str, line: usize) {
@@ -847,9 +838,7 @@ impl ScopeGraphIndex {
.filter_map(|(&id, meta)| self.get_str(id).map(|path| (path, meta)))
}
- // ========================================================================
// File operations
- // ========================================================================
/// Add a file's scope graph to the index
pub fn add_file(&mut self, file_path: PathBuf, graph: ScopeGraph, src: &[u8]) {
@@ -996,9 +985,7 @@ impl ScopeGraphIndex {
self.file_meta.len()
}
- // ========================================================================
// Query operations
- // ========================================================================
/// Find where a symbol is defined (includes resolving aliases)
pub fn find_definitions(&self, symbol: &str) -> Vec<(&str, usize)> {
@@ -1284,9 +1271,7 @@ impl ScopeGraphIndex {
.collect()
}
- // ========================================================================
// Statistics and metadata
- // ========================================================================
/// Get statistics: (files_count, total_definitions, total_references).
///
@@ -1301,7 +1286,6 @@ impl ScopeGraphIndex {
)
}
- /// Get alias count
pub fn alias_count(&self) -> usize {
self.aliases.len()
}
@@ -1356,9 +1340,7 @@ impl ScopeGraphIndex {
self.interner.shrink_to_fit();
}
- // ========================================================================
// Binary serialization (custom format with magic bytes)
- // ========================================================================
/// Save the index to a file in binary format.
pub fn save(&self, path: &Path) -> io::Result<()> {
@@ -1617,7 +1599,8 @@ impl ScopeGraphIndex {
Ok(Self {
interner,
- graphs: HashMap::new(), // Not serialized
+ // Not serialized
+ graphs: HashMap::new(),
definitions,
references,
aliases,
@@ -1716,7 +1699,8 @@ mod tests {
index.compact();
let (f1, d1, r1) = index.stats();
- index.compact(); // second call must be a no-op
+ // second call must be a no-op
+ index.compact();
let (f2, d2, r2) = index.stats();
assert_eq!(f1, f2);
diff --git a/crates/codegen/kigi-codebase-graph/src/scope_graph/mod.rs b/crates/codegen/kigi-codebase-graph/src/scope_graph/mod.rs
index 24af5cd..d4c9071 100644
--- a/crates/codegen/kigi-codebase-graph/src/scope_graph/mod.rs
+++ b/crates/codegen/kigi-codebase-graph/src/scope_graph/mod.rs
@@ -16,17 +16,12 @@ pub use nodes::{LocalDef, LocalImport, LocalScope, NodeKind, Reference, Symbol,
use crate::languages::TSLanguageConfig;
-/// Result of building a scope graph, including alias pairs.
pub struct ScopeGraphResult {
- /// The scope graph for the file.
pub graph: ScopeGraph,
- /// Alias pairs: (alias_name, original_name).
+ /// Each pair is `(alias_name, original_name)`.
pub aliases: Vec<(String, String)>,
}
-/// Build a ScopeGraph from tree-sitter query and source.
-///
-/// This is a convenience wrapper around `scope_graph_from_definitions_query`.
pub fn build_scope_graph(
query: &tree_sitter::Query,
root_node: tree_sitter::Node<'_>,
diff --git a/crates/codegen/kigi-codebase-graph/src/scope_graph/nodes.rs b/crates/codegen/kigi-codebase-graph/src/scope_graph/nodes.rs
index b60bc6d..e8c9489 100644
--- a/crates/codegen/kigi-codebase-graph/src/scope_graph/nodes.rs
+++ b/crates/codegen/kigi-codebase-graph/src/scope_graph/nodes.rs
@@ -89,7 +89,6 @@ impl LocalDef {
&src[self.range.start_byte()..self.range.end_byte()]
}
- /// Get the scope range.
pub fn scope_range(&self) -> &Range {
&self.scope.range
}
diff --git a/crates/codegen/kigi-codebase-graph/src/types/file_event.rs b/crates/codegen/kigi-codebase-graph/src/types/file_event.rs
index 3451f70..4eca1a0 100644
--- a/crates/codegen/kigi-codebase-graph/src/types/file_event.rs
+++ b/crates/codegen/kigi-codebase-graph/src/types/file_event.rs
@@ -25,7 +25,6 @@ pub enum FileEvent {
/// A file was renamed/moved.
Renamed {
- /// Original path.
from: PathBuf,
/// New path.
to: PathBuf,
@@ -49,7 +48,8 @@ impl FileEvent {
FileEvent::Created { .. } => true,
FileEvent::Modified { .. } => true,
FileEvent::Deleted { .. } => false,
- FileEvent::Renamed { .. } => false, // Only path update needed
+ // Only path update needed
+ FileEvent::Renamed { .. } => false,
}
}
diff --git a/crates/codegen/kigi-codebase-graph/src/types/location.rs b/crates/codegen/kigi-codebase-graph/src/types/location.rs
index d8f3e8f..7d33107 100644
--- a/crates/codegen/kigi-codebase-graph/src/types/location.rs
+++ b/crates/codegen/kigi-codebase-graph/src/types/location.rs
@@ -49,7 +49,6 @@ impl Location {
}
}
- /// Get the file path.
pub fn file_path(&self) -> &PathBuf {
&self.file_path
}
diff --git a/crates/codegen/kigi-codebase-graph/src/types/mod.rs b/crates/codegen/kigi-codebase-graph/src/types/mod.rs
index 4cd9b6c..eca24ff 100644
--- a/crates/codegen/kigi-codebase-graph/src/types/mod.rs
+++ b/crates/codegen/kigi-codebase-graph/src/types/mod.rs
@@ -113,7 +113,8 @@ impl FileMeta {
let current = Self::from_metadata(&meta);
*self != current
}
- Err(_) => true, // File deleted or inaccessible
+ // File deleted or inaccessible
+ Err(_) => true,
}
}
}
diff --git a/crates/codegen/kigi-codebase-graph/src/types/range.rs b/crates/codegen/kigi-codebase-graph/src/types/range.rs
index 77aa300..59cc893 100644
--- a/crates/codegen/kigi-codebase-graph/src/types/range.rs
+++ b/crates/codegen/kigi-codebase-graph/src/types/range.rs
@@ -70,7 +70,6 @@ impl Position {
self.character
}
- /// Get the byte offset.
pub fn byte_offset(&self) -> usize {
self.byte_offset
}
@@ -80,7 +79,6 @@ impl Position {
self.byte_offset
}
- /// Set the byte offset.
pub fn set_byte_offset(&mut self, byte_offset: usize) {
self.byte_offset = byte_offset;
}
@@ -120,7 +118,6 @@ impl Position {
}
}
- /// Move to the next line.
pub fn move_to_next_line(mut self) -> Self {
self.line += 1;
self.character = 0;
@@ -188,12 +185,10 @@ impl Range {
Self::for_tree_node(node)
}
- /// Get the start position.
pub fn start_position(&self) -> Position {
self.start_position
}
- /// Get the end position.
pub fn end_position(&self) -> Position {
self.end_position
}
@@ -208,12 +203,10 @@ impl Range {
&self.end_position
}
- /// Set the start position.
pub fn set_start_position(&mut self, position: Position) {
self.start_position = position;
}
- /// Set the end position.
pub fn set_end_position(&mut self, position: Position) {
self.end_position = position;
}
diff --git a/crates/codegen/kigi-codebase-graph/tests/incremental_memory.rs b/crates/codegen/kigi-codebase-graph/tests/incremental_memory.rs
index db1fb3b..c451175 100644
--- a/crates/codegen/kigi-codebase-graph/tests/incremental_memory.rs
+++ b/crates/codegen/kigi-codebase-graph/tests/incremental_memory.rs
@@ -1,29 +1,20 @@
//! Isolated RSS test for incremental reindexing.
//!
-//! This test lives in its own integration-test file (and therefore its own
-//! Bazel `rust_test` target / process) so that its whole-process RSS samples
-//! are not polluted by the other allocation-heavy tests in
-//! `memory_integration.rs` (e.g. `test_fresh_build_rss`,
-//! `test_build_batch_peak_rss_is_bounded`, `test_compact_reduces_rss_vs_uncompacted`).
+//! `libtest` runs a test binary's tests concurrently across `num_cpus` threads,
+//! but VmRSS is measured per-*process*. Sharing a binary with the other
+//! allocation-heavy tests in `memory_integration.rs` made this test observe
+//! their allocator churn, intermittently pushing the measured incremental
+//! growth delta over the 20 MB budget on aarch64 fastbuild CI (~31 MB).
//!
-//! Background: `libtest` runs tests in a single binary concurrently across
-//! `num_cpus` threads, and VmRSS is measured per-*process*. When this test
-//! ran inside `memory_integration.rs` it observed allocator churn from the
-//! other tests on the same process, intermittently pushing the measured
-//! "incremental growth" delta over the 20 MB budget on aarch64 fastbuild CI
-//! (`run_1_of_2` and `run_2_of_2` both failed at ~31 MB).
-//!
-//! Keep this file to a single test. If you need to add another RSS-sensitive
-//! test, give it its own file too rather than reintroducing the
-//! noisy-neighbor problem.
+//! Hence its own integration-test file, and therefore its own Bazel
+//! `rust_test` target and process. Keep this file to a single test; any other
+//! RSS-sensitive test needs a file of its own rather than a noisy neighbor.
use kigi_codebase_graph::{FileEvent, IndexManager, IndexManagerConfig};
use std::fs;
use std::path::Path;
use tempfile::tempdir;
-/// Read current process RSS in bytes. Supports Linux and macOS.
-/// Returns `None` on unsupported platforms.
fn rss_bytes() -> Option {
#[cfg(target_os = "linux")]
{
@@ -65,7 +56,6 @@ fn fmt_rss(rss: Option) -> String {
rss.map_or("N/A".to_string(), |v| format!("{:.1}MB", v))
}
-/// Create N Rust source files in `dir`, each with `defs_per_file` function defs.
fn create_rust_files(dir: &Path, count: usize, defs_per_file: usize) {
for i in 0..count {
let mut content = String::new();
@@ -122,7 +112,6 @@ fn test_bulk_incremental_indexing_memory() {
);
println!("RSS after incremental: {}", fmt_rss(rss_after_incremental));
- // Incremental reindexing should not grow memory significantly.
if let (Some(after_inc), Some(after_build)) = (rss_after_incremental, rss_after_build) {
let growth = after_inc - after_build;
assert!(
diff --git a/crates/codegen/kigi-codebase-graph/tests/memory_integration.rs b/crates/codegen/kigi-codebase-graph/tests/memory_integration.rs
index 2a07709..1688d04 100644
--- a/crates/codegen/kigi-codebase-graph/tests/memory_integration.rs
+++ b/crates/codegen/kigi-codebase-graph/tests/memory_integration.rs
@@ -80,9 +80,7 @@ fn create_binary_files(dir: &Path, count: usize, size: usize) {
}
}
-// =========================================================================
// Tests
-// =========================================================================
#[test]
#[serial_test::serial]
@@ -221,11 +219,14 @@ fn test_builder_skips_binary_and_oversized_in_bulk() {
let root = dir.path();
// Mix of valid, binary, and oversized files
- create_rust_files(root, 100, 5); // 100 valid files
- create_binary_files(root, 50, 10_000); // 50 binary files
+ // 100 valid files
+ create_rust_files(root, 100, 5);
+ // 50 binary files
+ create_binary_files(root, 50, 10_000);
// One oversized file
- let big = "fn x() {}\n".repeat(600_000); // ~6MB
+ // ~6MB
+ let big = "fn x() {}\n".repeat(600_000);
fs::write(root.join("oversized.rs"), &big).unwrap();
drop(big);
@@ -234,7 +235,8 @@ fn test_builder_skips_binary_and_oversized_in_bulk() {
// Only the 100 valid files should be indexed
assert_eq!(files, 100);
- assert!(defs >= 500); // 100 files × 5 defs
+ // 100 files × 5 defs
+ assert!(defs >= 500);
}
/// Measure RSS growth from a single `get_snapshot()` call on a representative index.
@@ -248,7 +250,8 @@ fn test_builder_skips_binary_and_oversized_in_bulk() {
fn test_single_snapshot_rss() {
let dir = tempdir().unwrap();
let root = dir.path();
- create_rust_files(root, 500, 10); // 500 files, 5 000 defs
+ // 500 files, 5 000 defs
+ create_rust_files(root, 500, 10);
let config = IndexManagerConfig::new(root.to_path_buf())
.without_cache_load()
@@ -353,7 +356,8 @@ fn test_repeated_snapshots_rss_bounded() {
fn test_fresh_build_rss() {
let dir = tempdir().unwrap();
let root = dir.path();
- create_rust_files(root, 500, 10); // 500 files, 5 000 defs
+ // 500 files, 5 000 defs
+ create_rust_files(root, 500, 10);
let rss_before = rss_mb();
@@ -451,7 +455,8 @@ fn test_cache_load_rss() {
fn test_build_batch_size_produces_correct_index() {
let dir = tempdir().unwrap();
let root = dir.path();
- create_rust_files(root, 200, 5); // 200 files, 1 000 defs
+ // 200 files, 1 000 defs
+ create_rust_files(root, 200, 5);
// Build with a very small batch size (10 files per merge batch)
let batched = IndexBuilder::new()
@@ -587,9 +592,7 @@ fn test_build_batch_peak_rss_is_bounded() {
assert_eq!(b_refs, u_refs, "reference count must match");
}
-// =============================================================================
// Structural compaction tests
-// =============================================================================
/// Verify that an index survives a save/load round-trip after compact().
///
@@ -601,7 +604,8 @@ fn test_build_batch_peak_rss_is_bounded() {
fn test_compact_then_save_load_roundtrip() {
let dir = tempdir().unwrap();
let root = dir.path();
- create_rust_files(root, 50, 4); // 50 files, 200 defs
+ // 50 files, 200 defs
+ create_rust_files(root, 50, 4);
// build() calls compact() internally via build_fast()
let original = IndexBuilder::new().build(root).unwrap();
diff --git a/crates/codegen/kigi-config-types/src/flags.rs b/crates/codegen/kigi-config-types/src/flags.rs
index cb1a1b0..d1d9372 100644
--- a/crates/codegen/kigi-config-types/src/flags.rs
+++ b/crates/codegen/kigi-config-types/src/flags.rs
@@ -1,5 +1,8 @@
-//! Config-value resolution leaf types and per-model laziness config,
-//! extracted from kigi-shell for dependency inversion.
+//! Config-value resolution leaf types and per-model laziness config.
+//!
+//! They live outside kigi-shell so crates below it (kigi-memory,
+//! kigi-shared, kigi-workspace) can share them without depending on the
+//! shell.
use kigi_config::env_bool;
@@ -18,7 +21,6 @@ pub enum ConfigSource {
Default,
}
-/// A resolved config value with its source for diagnostics.
#[derive(Debug, Clone)]
pub struct Resolved {
pub value: T,
@@ -156,9 +158,8 @@ pub struct LazinessDetectorPerModelConfig {
pub min_confidence: Option,
/// When `Some(true)` (or `None` — the default), the classifier sees
/// the assistant's plain-text reasoning as `[assistant reasoning]`
- /// lines. `Some(false)` drops them (the pre-2026-05 behavior).
- /// `None` defers to the harness default (`LAZINESS_INCLUDE_REASONING`,
- /// currently `true`).
+ /// lines; `Some(false)` drops them. `None` defers to the harness
+ /// default (`LAZINESS_INCLUDE_REASONING`, currently `true`).
#[serde(default)]
pub include_reasoning: Option,
}
diff --git a/crates/codegen/kigi-config-types/src/mcp.rs b/crates/codegen/kigi-config-types/src/mcp.rs
index e830b2e..b8bdd74 100644
--- a/crates/codegen/kigi-config-types/src/mcp.rs
+++ b/crates/codegen/kigi-config-types/src/mcp.rs
@@ -1,5 +1,4 @@
-//! MCP server configuration value types, extracted from kigi-shell
-//! (config dependency inversion).
+//! MCP server configuration value types.
use agent_client_protocol as acp;
use indexmap::IndexMap;
@@ -8,14 +7,10 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
-/// serde default helper. Kept module-local rather than shared — the `pool`
-/// module keeps its own copy for `PoolConfig`.
fn default_true() -> bool {
true
}
-/// Read an MCP OAuth client secret from the named env var. Moved here with
-/// `McpServerConfig` (its only caller).
fn resolve_oauth_client_secret(env_var: Option<&String>) -> Option {
let env_var = env_var?;
match std::env::var(env_var) {
@@ -56,10 +51,8 @@ pub enum McpServerTransportConfig {
/// OAuth client ID for providers that don't support Dynamic Client Registration.
#[serde(default, skip_serializing_if = "Option::is_none")]
oauth_client_id: Option,
- /// Name of the env var holding the OAuth client secret (for BYO credentials).
#[serde(default, skip_serializing_if = "Option::is_none")]
oauth_client_secret_env_var: Option,
- /// OAuth scopes to request during authorization.
#[serde(default, skip_serializing_if = "Option::is_none")]
oauth_scopes: Option>,
},
@@ -176,7 +169,6 @@ impl McpServerConfig {
})
.unwrap_or_default();
- // Add bearer token from environment variable if specified
if let Some(env_var) = bearer_token_env_var {
match std::env::var(env_var) {
Ok(token) => {
@@ -213,7 +205,7 @@ impl McpServerConfig {
}
}
- /// Extract OAuth configuration for this server, if any OAuth fields are set.
+ /// Inline `oauth_*` transport fields take precedence over the `oauth` block.
pub fn oauth_config(&self) -> Option {
if let McpServerTransportConfig::StreamableHttp {
oauth_client_id,
@@ -260,7 +252,7 @@ pub struct RelaySyncConfig {
}
impl RelaySyncConfig {
- /// Check if relay sync is enabled. Env var takes precedence over config.
+ /// `KIGI_RELAY_SYNC_ENABLED` overrides the configured value.
pub fn is_enabled(&self) -> bool {
if let Ok(env_val) = std::env::var("KIGI_RELAY_SYNC_ENABLED") {
return env_val.eq_ignore_ascii_case("true") || env_val == "1";
diff --git a/crates/codegen/kigi-config-types/src/memory.rs b/crates/codegen/kigi-config-types/src/memory.rs
index da01d01..90b3f36 100644
--- a/crates/codegen/kigi-config-types/src/memory.rs
+++ b/crates/codegen/kigi-config-types/src/memory.rs
@@ -450,7 +450,8 @@ mod tests {
fn effective_half_life_converts_legacy_recency_decay() {
let mut s = MemorySearchConfig::default();
s.temporal_decay.enabled = false;
- s.recency_decay = 0.5; // non-default → converted
+ // non-default → converted
+ s.recency_decay = 0.5;
let hl = s.effective_half_life_days().unwrap();
assert!(
(hl - 1.0).abs() < 1e-9,
diff --git a/crates/codegen/kigi-config-types/src/permission.rs b/crates/codegen/kigi-config-types/src/permission.rs
index b214bd6..5837dee 100644
--- a/crates/codegen/kigi-config-types/src/permission.rs
+++ b/crates/codegen/kigi-config-types/src/permission.rs
@@ -32,7 +32,7 @@ pub enum PatternMode {
/// Action to take when rule matches.
///
-/// CWE-1188: Default changed from Allow to Deny so that omitting the
+/// CWE-1188: the default is Deny rather than Allow, so that omitting the
/// `action` field in a TOML permission rule does not silently create a
/// catch-all allow rule.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
diff --git a/crates/codegen/kigi-config-types/src/pool.rs b/crates/codegen/kigi-config-types/src/pool.rs
index 0d05c6b..d47caa0 100644
--- a/crates/codegen/kigi-config-types/src/pool.rs
+++ b/crates/codegen/kigi-config-types/src/pool.rs
@@ -1,5 +1,4 @@
-//! Worktree-pool configuration value type, extracted from kigi-shell
-//! (config dependency inversion).
+//! Worktree-pool configuration value type.
use serde::{Deserialize, Serialize};
@@ -18,27 +17,23 @@ use serde::{Deserialize, Serialize};
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoolConfig {
- /// Whether the pool is enabled at all.
- /// Can be set to false to disable pooling regardless of repo size.
- /// Default: true (auto-detect based on file_count_threshold)
+ /// When false, pooling is off regardless of repo size; otherwise
+ /// `file_count_threshold` decides.
#[serde(default = "default_true")]
pub enabled: bool,
- /// Number of worktrees to keep ready in the pool.
- /// 2 is the minimum useful value when forks need parallel worktrees.
- /// Default: 2
+ /// Number of worktrees to keep ready. 2 is the minimum useful value when
+ /// forks need parallel worktrees.
#[serde(default = "default_pool_size")]
pub pool_size: usize,
- /// Minimum number of tracked files for the pool to activate.
- /// Below this threshold, on-demand creation is fast enough.
- /// Default: 50_000
+ /// Minimum number of tracked files for the pool to activate. Below this,
+ /// on-demand creation is fast enough.
#[serde(default = "default_file_count_threshold")]
pub file_count_threshold: usize,
- /// Number of threads to use for worktree creation when populating the pool.
- /// This can speed up pool population on large repos, but also increases resource usage.
- /// Default: 3.
+ /// Threads used to populate the pool. Higher values speed up population on
+ /// large repos at the cost of more concurrent resource use.
#[serde(default = "default_pool_parallelism")]
pub parallelism: usize,
}
diff --git a/crates/codegen/kigi-config/src/campaigns.rs b/crates/codegen/kigi-config/src/campaigns.rs
index 23f4c96..e80fb86 100644
--- a/crates/codegen/kigi-config/src/campaigns.rs
+++ b/crates/codegen/kigi-config/src/campaigns.rs
@@ -59,7 +59,6 @@ pub fn build_campaign_entries(
tracing::warn!(layer, "campaigns: entry missing id; skipped");
continue;
};
- // Skip no-op entries (id only, no fields to overlay).
if entry.patch.is_empty() {
continue;
}
@@ -180,8 +179,8 @@ mod tests {
#[test]
fn apply_highest_priority_wins_on_leaf_conflict() {
- // Two *distinct* ids both set models.default; the higher-priority source
- // (earlier in the merged list) must win the leaf.
+ // Two *distinct* ids both set models.default, so dedup by id does not
+ // apply and the leaf conflict is settled by apply order alone.
let req = [CampaignEntry {
id: "req".into(),
patch: models_default_patch("from-req"),
@@ -200,8 +199,6 @@ mod tests {
#[test]
fn build_campaign_entries_skips_missing_id() {
- // A `None` id and a whitespace-only id are both dropped (with a warn);
- // only the entry carrying a real id survives.
let taken = vec![
ConfigOverrideEntry {
meta: CampaignMeta { id: None },
@@ -236,9 +233,8 @@ mod tests {
let entries = take_campaign_entries(&mut layer, "user");
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].id, "c1");
- // The id key (either spelling) must be consumed by the meta, never
- // land in the patch — a leaked key would deep-merge a junk top-level
- // `id` into every effective config.
+ // A leaked id key would deep-merge a junk top-level `id` into every
+ // effective config.
assert!(
entries[0].patch.get("id").is_none()
&& entries[0].patch.get("campaign_id").is_none(),
@@ -273,7 +269,6 @@ mod tests {
#[test]
fn effective_config_honors_dismiss() {
use crate::loader::ConfigLayers;
- // A dismissed campaign id stops overriding; the user's stored value returns.
let mut layers = ConfigLayers {
user: parse("[models]\ndefault = \"user-old\"\n"),
..Default::default()
diff --git a/crates/codegen/kigi-config/src/lib.rs b/crates/codegen/kigi-config/src/lib.rs
index 245b3e7..96e7c75 100644
--- a/crates/codegen/kigi-config/src/lib.rs
+++ b/crates/codegen/kigi-config/src/lib.rs
@@ -25,8 +25,6 @@ pub mod signed_policy;
mod validation;
pub mod version_overrides;
-// Only the cross-crate campaign surface is re-exported at the root; the rest stays
-// reachable via the `pub mod` paths for in-crate use without widening the API.
pub use campaigns::{
CampaignEntry, CampaignOverrides, filter_active_campaigns, ids_touching_paths,
};
diff --git a/crates/codegen/kigi-config/src/loader.rs b/crates/codegen/kigi-config/src/loader.rs
index 86b3eb3..b908be4 100644
--- a/crates/codegen/kigi-config/src/loader.rs
+++ b/crates/codegen/kigi-config/src/loader.rs
@@ -84,7 +84,6 @@ pub fn load_from_disk() -> std::io::Result {
load_user_config_layer(user_kigi_home().as_deref(), "config.toml")
}
-/// Managed config filename, shared by the loaders in this module.
pub const MANAGED_CONFIG_FILENAME: &str = "managed_config.toml";
pub fn load_managed_config() -> std::io::Result {
@@ -111,7 +110,6 @@ pub fn load_system_managed_config() -> std::io::Result {
Ok(v)
}
-/// One managed-config layer: the parsed TOML and the file it came from.
#[derive(Debug, Clone)]
pub struct ManagedConfigLayer {
pub value: toml::Value,
@@ -377,7 +375,6 @@ pub struct CampaignsState {
pub dismissed_ids: Vec,
}
-/// Path to `$KIGI_SHARE_DIR/campaigns_state.json` under `home`.
pub fn campaigns_state_path(home: &std::path::Path) -> std::path::PathBuf {
home.join(CAMPAIGNS_STATE_FILE)
}
diff --git a/crates/codegen/kigi-config/src/macos_managed.rs b/crates/codegen/kigi-config/src/macos_managed.rs
index a03a4f8..37588e4 100644
--- a/crates/codegen/kigi-config/src/macos_managed.rs
+++ b/crates/codegen/kigi-config/src/macos_managed.rs
@@ -1,6 +1,6 @@
//! macOS MDM managed-preferences layer.
//!
-//! Admins push a device profile with standard-base64 (padded) TOML under
+//! Admins push a device profile with standard-base64 (`padded`) TOML under
//! preference domain `ai.x.kigi` (`requirements_toml_base64`). Only admin-*forced*
//! values are read, so a local user can't forge it via their own preference
//! domain; trusted on every launch, independent of network/cache. `None` off macOS.
diff --git a/crates/codegen/kigi-config/src/managed_cache.rs b/crates/codegen/kigi-config/src/managed_cache.rs
index 81c7da8..cc0873a 100644
--- a/crates/codegen/kigi-config/src/managed_cache.rs
+++ b/crates/codegen/kigi-config/src/managed_cache.rs
@@ -425,7 +425,8 @@ fn managed_config_stale_at(home: Option<&Path>, identity: &ServingIdentity) -> b
return false;
};
let Some(cache) = read_managed_config_cache(home) else {
- return true; // no marker → never synced → stale
+ // no marker → never synced → stale
+ return true;
};
if cache_unusable_for(&cache, home, identity) {
return true;
diff --git a/crates/codegen/kigi-config/src/managed_cache/tests.rs b/crates/codegen/kigi-config/src/managed_cache/tests.rs
index e3c8246..7c166cc 100644
--- a/crates/codegen/kigi-config/src/managed_cache/tests.rs
+++ b/crates/codegen/kigi-config/src/managed_cache/tests.rs
@@ -65,7 +65,8 @@ fn signed_verdict_does_not_skip_deploy_key_fingerprint() {
// opted-in cache.
assert!(managed_policy_compromised_decision(
SignedVerdict::Trusted,
- true, // deploy-key fingerprint mismatch
+ // deploy-key fingerprint mismatch
+ true,
Some(&opted_in),
home,
&dkey("fp-local")
diff --git a/crates/codegen/kigi-config/src/paths.rs b/crates/codegen/kigi-config/src/paths.rs
index 9f7d728..ef73bdf 100644
--- a/crates/codegen/kigi-config/src/paths.rs
+++ b/crates/codegen/kigi-config/src/paths.rs
@@ -146,9 +146,6 @@ pub fn decode_cwd_from_dirname(dir: &std::path::Path) -> Option {
.map(|s| s.trim().to_string())
}
-/// Build the CWD-level session directory path:
-/// `kigi_home()/sessions/{encode_cwd_dirname(cwd)}`.
-///
/// Does **not** create the directory on disk — use [`ensure_sessions_cwd_dir`]
/// when the directory must exist.
pub fn sessions_cwd_dir(cwd: &str) -> PathBuf {
@@ -181,10 +178,8 @@ pub fn ensure_sessions_cwd_dir(cwd: &str) -> std::io::Result {
Ok(dir)
}
-/// Generate a URL-safe slug from a string.
-///
-/// Lowercases, replaces non-alphanumeric chars with `-`, collapses
-/// consecutive dashes, and truncates to `max_len` characters.
+/// Output is ASCII-only, so `max_len` bounds the result in bytes as well as
+/// chars — [`encode_cwd_dirname`] relies on that for its length guarantee.
fn slugify(input: &str, max_len: usize) -> String {
let mut result = String::with_capacity(input.len());
let mut prev_dash = false;
diff --git a/crates/codegen/kigi-config/src/shell.rs b/crates/codegen/kigi-config/src/shell.rs
index 396b8ff..47d6fdb 100644
--- a/crates/codegen/kigi-config/src/shell.rs
+++ b/crates/codegen/kigi-config/src/shell.rs
@@ -342,9 +342,7 @@ fn invocation_for(shell: &WindowsShell, command: &str) -> ShellInvocation {
}
}
-// =============================================================================
// Unix shell resolution
-// =============================================================================
//
// Locates an absolute path to a bash/zsh binary on Unix:
//
diff --git a/crates/codegen/kigi-config/src/signed_policy/tests.rs b/crates/codegen/kigi-config/src/signed_policy/tests.rs
index 0607400..451ef32 100644
--- a/crates/codegen/kigi-config/src/signed_policy/tests.rs
+++ b/crates/codegen/kigi-config/src/signed_policy/tests.rs
@@ -650,7 +650,8 @@ fn signed_cache_compromised_respects_signed_opt_out() {
let dir = tempfile::tempdir().unwrap();
let home = dir.path();
let (kp, pubkey) = test_keypair();
- let p = payload(); // fail_closed = false
+ // fail_closed = false
+ let p = payload();
write_policy(home, &p);
write_sidecar(home, &sign(&kp, &p)).unwrap();
diff --git a/crates/codegen/kigi-config/src/validation.rs b/crates/codegen/kigi-config/src/validation.rs
index 8972352..6fa6bbc 100644
--- a/crates/codegen/kigi-config/src/validation.rs
+++ b/crates/codegen/kigi-config/src/validation.rs
@@ -58,7 +58,6 @@ impl RequirementsSource {
}
}
-/// One requirements layer: the parsed TOML and where it came from.
#[derive(Debug, Clone)]
pub struct RequirementsLayer {
pub value: toml::Value,
@@ -171,7 +170,6 @@ pub(crate) fn mdm_requirements_value() -> Option {
)
}
-/// Errors from validating requirements layers at startup.
#[derive(Debug, thiserror::Error)]
pub enum RequirementsError {
#[error(
diff --git a/crates/codegen/kigi-config/src/version_overrides.rs b/crates/codegen/kigi-config/src/version_overrides.rs
index 45070ea..dd52b93 100644
--- a/crates/codegen/kigi-config/src/version_overrides.rs
+++ b/crates/codegen/kigi-config/src/version_overrides.rs
@@ -143,14 +143,20 @@ mod tests {
);
cfg["x"].as_integer() == Some(1)
}
- assert!(applies(Some("1.7.0"), None, "1.7.0")); // min inclusive
- assert!(applies(Some("1.0.0"), Some("1.7.0"), "1.7.0")); // max inclusive
- assert!(!applies(Some("1.7.0"), None, "1.6.0")); // below min
- assert!(!applies(Some("1.0.0"), Some("1.5.0"), "2.0.0")); // above max
- assert!(applies(Some("1.7.0"), None, "99.0.0")); // unbounded above
- assert!(applies(None, Some("2.0.0"), "1.5.0")); // max-only, within
- assert!(!applies(None, Some("2.0.0"), "2.0.1")); // max-only, above
- assert!(applies(None, None, "1.0.0")); // unbounded both = always
+ // min inclusive
+ assert!(applies(Some("1.7.0"), None, "1.7.0"));
+ // max inclusive
+ assert!(applies(Some("1.0.0"), Some("1.7.0"), "1.7.0"));
+ assert!(!applies(Some("1.7.0"), None, "1.6.0"));
+ assert!(!applies(Some("1.0.0"), Some("1.5.0"), "2.0.0"));
+ // unbounded above
+ assert!(applies(Some("1.7.0"), None, "99.0.0"));
+ // max-only, within
+ assert!(applies(None, Some("2.0.0"), "1.5.0"));
+ // max-only, above
+ assert!(!applies(None, Some("2.0.0"), "2.0.1"));
+ // unbounded both = always
+ assert!(applies(None, None, "1.0.0"));
}
#[test]
diff --git a/crates/codegen/kigi-crash-handler/src/format.rs b/crates/codegen/kigi-crash-handler/src/format.rs
index 50f2597..2bd84dc 100644
--- a/crates/codegen/kigi-crash-handler/src/format.rs
+++ b/crates/codegen/kigi-crash-handler/src/format.rs
@@ -12,7 +12,7 @@ pub const VERSION: u8 = 1;
/// Maximum backtrace frames captured in the signal handler.
pub const MAX_FRAMES: usize = 64;
-/// Length of the null-padded version string field.
+/// Length of the null-`padded` version string field.
pub const VERSION_STRING_LEN: usize = 32;
/// Fixed header size (before the variable-length frames array).
@@ -26,7 +26,7 @@ pub const VERSION_STRING_LEN: usize = 32;
/// - pid: 4 bytes (u32, little-endian)
/// - timestamp: 8 bytes (u64, little-endian)
/// - n_frames: 2 bytes (u16, little-endian)
-/// - app_version: 32 bytes (null-padded UTF-8)
+/// - app_version: 32 bytes (null-`padded` UTF-8)
pub const HEADER_SIZE: usize = 4 + 1 + 1 + 4 + 8 + 4 + 8 + 2 + VERSION_STRING_LEN;
/// Total maximum file size: header + 64 frames * 8 bytes each.
@@ -176,8 +176,9 @@ mod tests {
unsafe {
let mut offset = writer::write_header(
&mut buf,
- 10, // SIGBUS on macOS
- 2, // BUS_ADRERR
+ // SIGBUS on macOS
+ 10,
+ 2,
0x7f8a_1234_0000,
42,
1_712_678_587,
diff --git a/crates/codegen/kigi-crash-handler/src/handler.rs b/crates/codegen/kigi-crash-handler/src/handler.rs
index 8577906..1629707 100644
--- a/crates/codegen/kigi-crash-handler/src/handler.rs
+++ b/crates/codegen/kigi-crash-handler/src/handler.rs
@@ -18,7 +18,7 @@ mod imp {
use crate::format::{self, MAX_FILE_SIZE, MAX_FRAMES};
use crate::terminal;
- // ── Platform-specific ucontext access ────────────────────────────────
+ // Platform-specific ucontext access
//
// The libc crate does not expose ucontext_t on macOS. We define minimal
// repr(C) types covering only the fields we need (PC and frame pointer).
@@ -47,7 +47,8 @@ mod imp {
let uc = ctx as *const libc::ucontext_t;
let mc = &(*uc).uc_mcontext;
let ip = mc.pc as usize;
- let fp = mc.regs[29] as usize; // x29 = frame pointer
+ // x29 = frame pointer
+ let fp = mc.regs[29] as usize;
return (ip, fp);
}
@@ -57,9 +58,10 @@ mod imp {
{
#[repr(C)]
struct Arm64ThreadState {
- regs: [u64; 29], // x0-x28
- fp: u64, // x29
- lr: u64, // x30
+ // x0-x28
+ regs: [u64; 29],
+ fp: u64,
+ lr: u64,
sp: u64,
pc: u64,
cpsr: u32,
@@ -67,7 +69,8 @@ mod imp {
}
#[repr(C)]
struct MachMcontext {
- _es: [u8; 16], // __darwin_arm_exception_state64 (far:u64 + esr:u32 + exception:u32)
+ // __darwin_arm_exception_state64 (far:u64 + esr:u32 + exception:u32)
+ _es: [u8; 16],
ss: Arm64ThreadState,
// neon state follows but we don't need it
}
@@ -119,7 +122,8 @@ mod imp {
}
#[repr(C)]
struct MachMcontext {
- _es: [u8; 16], // __darwin_x86_exception_state64
+ // __darwin_x86_exception_state64
+ _es: [u8; 16],
ss: X86ThreadState,
}
#[repr(C)]
@@ -568,9 +572,9 @@ mod win {
/// Map Windows exception code to a Unix signal number for the blob format.
fn exception_to_signal(code: i32) -> u8 {
match code {
- EXCEPTION_IN_PAGE_ERROR => 7, // SIGBUS
- EXCEPTION_ILLEGAL_INSTRUCTION => 4, // SIGILL
- _ => 11, // SIGSEGV
+ EXCEPTION_IN_PAGE_ERROR => 7,
+ EXCEPTION_ILLEGAL_INSTRUCTION => 4,
+ _ => 11,
}
}
diff --git a/crates/codegen/kigi-crash-handler/src/lib.rs b/crates/codegen/kigi-crash-handler/src/lib.rs
index 2eb4221..4d7a658 100644
--- a/crates/codegen/kigi-crash-handler/src/lib.rs
+++ b/crates/codegen/kigi-crash-handler/src/lib.rs
@@ -38,31 +38,23 @@ pub use symbolicate::ResolvedFrame;
const MAX_HISTORY: usize = 5;
-/// Configuration for the crash handler.
pub struct CrashHandlerConfig {
- /// Application version string (e.g. "0.1.169-alpha.2").
pub app_version: String,
- /// Directory where crash dumps are written.
/// Created if it does not exist.
pub crash_dir: PathBuf,
}
-/// Information about a crash from the previous session.
#[derive(Debug)]
pub struct CrashReport {
- /// Human-readable signal name (e.g. "SIGBUS (Bus error)").
pub signal_name: &'static str,
/// The `si_code` from `siginfo_t`.
pub si_code: i32,
- /// The faulting memory address.
pub faulting_address: u64,
- /// Unix timestamp of the crash.
+ /// Unix seconds.
pub timestamp: u64,
/// Application version at crash time.
pub app_version: String,
- /// Symbolicated backtrace frames.
pub backtrace: Vec,
- /// Path to the saved human-readable crash report.
pub report_path: PathBuf,
}
@@ -121,14 +113,12 @@ pub fn check_previous_crash(crash_dir: &Path) -> Option {
let frames = symbolicate::resolve_frames(&blob);
let report_text = symbolicate::format_report(&blob, &frames);
- // Write the human-readable report.
let report_path = crash_dir.join("last-crash-report.txt");
let _ = std::fs::write(&report_path, &report_text);
- // Archive to history/ (keep last MAX_HISTORY).
archive_report(crash_dir, &report_text, blob.timestamp);
- // Remove the binary blob so it's not re-processed.
+ // Remove the binary blob so the next startup does not report it again.
let _ = std::fs::remove_file(&crash_file);
Some(CrashReport {
@@ -149,7 +139,9 @@ fn archive_report(crash_dir: &Path, report_text: &str, timestamp: u64) {
let filename = format!("crash-{}.txt", timestamp);
let _ = std::fs::write(history_dir.join(&filename), report_text);
- // Prune old reports beyond MAX_HISTORY.
+ // `crash-.txt` names are fixed width for the foreseeable
+ // future, so lexicographic order is chronological order and the oldest
+ // reports sort to the front.
if let Ok(mut entries) = std::fs::read_dir(&history_dir) {
let mut files: Vec = entries
.by_ref()
diff --git a/crates/codegen/kigi-crash-handler/src/symbolicate.rs b/crates/codegen/kigi-crash-handler/src/symbolicate.rs
index 1c30858..4e175c2 100644
--- a/crates/codegen/kigi-crash-handler/src/symbolicate.rs
+++ b/crates/codegen/kigi-crash-handler/src/symbolicate.rs
@@ -6,7 +6,6 @@
use crate::format::CrashBlob;
-/// A resolved backtrace frame.
#[derive(Debug, Clone)]
pub struct ResolvedFrame {
pub ip: usize,
@@ -15,13 +14,9 @@ pub struct ResolvedFrame {
pub lineno: Option,
}
-/// Resolve raw instruction pointers from a crash blob into symbol names.
-///
-/// Uses the `backtrace` crate's `resolve` function. This works best when
-/// the binary has debug info or at least a symbol table. For stripped
-/// release binaries, symbol names may still be available (e.g.
-/// `my_app::render::draw_frame`) but file/line info will
-/// be missing.
+/// Resolution quality depends on what the binary carries: with debug info
+/// frames get file and line, while a stripped release binary may still yield
+/// symbol names (e.g. `my_app::render::draw_frame`) but no file/line.
pub fn resolve_frames(blob: &CrashBlob) -> Vec {
blob.frames
.iter()
@@ -46,7 +41,6 @@ pub fn resolve_frames(blob: &CrashBlob) -> Vec {
.collect()
}
-/// Format a crash report as human-readable text.
pub fn format_report(blob: &CrashBlob, frames: &[ResolvedFrame]) -> String {
let mut out = String::with_capacity(4096);
@@ -62,7 +56,8 @@ pub fn format_report(blob: &CrashBlob, frames: &[ResolvedFrame]) -> String {
out.push_str(&format!("PID: {}\n", blob.pid));
out.push_str(&format!("Version: {}\n", blob.app_version));
- // Format timestamp as ISO 8601 (best-effort without chrono dependency).
+ // Raw unix seconds: a calendar-formatted time would cost a date-time
+ // dependency for a report that is read alongside other unix timestamps.
out.push_str(&format!("Time: {} (unix)\n", blob.timestamp));
out.push_str(&format!("\nBacktrace ({} frames):\n", frames.len()));
diff --git a/crates/codegen/kigi-crash-handler/src/terminal.rs b/crates/codegen/kigi-crash-handler/src/terminal.rs
index 3d3595a..0056462 100644
--- a/crates/codegen/kigi-crash-handler/src/terminal.rs
+++ b/crates/codegen/kigi-crash-handler/src/terminal.rs
@@ -3,14 +3,12 @@
//! See (DEC
//! Private Mode Reset / "Mouse Tracking" section) for the full spec.
-// -----------------------------------------------------------------------
// Canonical list of DEC private modes we enable.
//
// Every mode the pager enables must appear here so that *all* teardown
// paths (normal exit, panic hook, signal handler) disable the same set.
//
// Mode Purpose Enabled by
-// ---- ------- ----------
// ?1000 Normal mouse tracking (X11 press/release) EnableMouseCapture
// ?1002 Button-event mouse tracking (cell-motion held) EnableMouseCapture
// ?1003 All-motion mouse tracking (any movement) EnableMouseCapture
@@ -22,7 +20,6 @@
// ?1049 Alternate screen buffer EnterAlternateScreen
// ?2026 Synchronized update BeginSynchronizedUpdate
// CSI (std::process::ExitStatus,
)
}
-// ── Subprocess entry point ──────────────────────────────────────────────
+// Subprocess entry point
/// This test is `#[ignore]`d so it only runs when invoked as a subprocess
/// by the parent test via `run_scenario`. The `CRASH_TEST_SCENARIO` env
@@ -43,7 +43,8 @@ fn run_scenario(scenario: &str, crash_dir: &Path) -> (std::process::ExitStatus,
fn subprocess_entry() {
let scenario = match std::env::var("CRASH_TEST_SCENARIO") {
Ok(s) => s,
- Err(_) => return, // not a subprocess invocation
+ // not a subprocess invocation
+ Err(_) => return,
};
let crash_dir = std::env::var("CRASH_TEST_DIR").expect("CRASH_TEST_DIR");
let crash_dir = std::path::PathBuf::from(crash_dir);
@@ -138,7 +139,7 @@ fn subprocess_entry() {
}
}
-// ── Parent test cases ───────────────────────────────────────────────────
+// Parent test cases
#[test]
fn handler_does_not_interfere_with_tokio_runtime() {
diff --git a/crates/codegen/kigi-env/src/lib.rs b/crates/codegen/kigi-env/src/lib.rs
index 5728265..ee7b092 100644
--- a/crates/codegen/kigi-env/src/lib.rs
+++ b/crates/codegen/kigi-env/src/lib.rs
@@ -5,7 +5,6 @@
//! Each value resolves as an env-var override when set, else the compiled
//! production default.
-/// The complete set of first-party endpoints.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KigiEndpoints {
/// Kimi Code subscription inference API (OpenAI chat/completions compatible).
@@ -40,26 +39,18 @@ fn resolve(var: &str, compiled: &'static str) -> String {
}
}
-/// Subscription inference base URL: `KIGI_CODE_BASE_URL` override when set,
-/// else the compiled production endpoint.
pub fn coding_api_base_url() -> String {
resolve(CODE_BASE_URL_ENV, PRODUCTION_ENDPOINTS.coding_api_base_url)
}
-/// OAuth host: `KIGI_OAUTH_HOST` override when set, else the compiled
-/// production endpoint.
pub fn oauth_host() -> String {
resolve(OAUTH_HOST_ENV, PRODUCTION_ENDPOINTS.oauth_host)
}
-/// GitHub Releases API endpoint for the self-updater:
-/// `KIGI_UPDATE_BASE_URL` override when set, else the compiled production
-/// endpoint. The updater's channel/rollback semantics layer on top of it.
pub fn update_base_url() -> String {
resolve(UPDATE_BASE_URL_ENV, PRODUCTION_ENDPOINTS.update_base_url)
}
-/// Subscription upgrade page shown in rate-limit and upsell surfaces.
pub fn upgrade_page_url() -> &'static str {
PRODUCTION_ENDPOINTS.upgrade_page_url
}
@@ -70,8 +61,8 @@ fn env_lock() -> std::sync::MutexGuard<'static, ()> {
ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner())
}
-/// RAII env-var override for tests: constructors snapshot the prior value
-/// under [`ENV_LOCK`], `Drop` restores it, panics included.
+/// RAII env-var override for tests: the constructors snapshot the prior value
+/// under [`ENV_LOCK`] and `Drop` restores it, panics included.
pub struct EnvVarGuard {
key: &'static str,
prev: Option,
@@ -101,7 +92,7 @@ impl EnvVarGuard {
}
}
- /// Update the value while still holding the env lock.
+ /// Overwrite the value without releasing [`ENV_LOCK`].
pub fn set_value(&self, value: &str) {
unsafe { std::env::set_var(self.key, value) };
}
diff --git a/crates/codegen/kigi-fast-worktree/src/api.rs b/crates/codegen/kigi-fast-worktree/src/api.rs
index 0473162..9efc951 100644
--- a/crates/codegen/kigi-fast-worktree/src/api.rs
+++ b/crates/codegen/kigi-fast-worktree/src/api.rs
@@ -14,9 +14,7 @@ use crate::copy::CopyStats;
pub use crate::copy::DirtyFilesReport;
use crate::copy::ParallelCopyConfig;
-// ============================================================================
// BtrfsDelegate – delegate privileged btrfs ops to an external service
-// ============================================================================
/// Result from a delegated btrfs snapshot creation.
#[derive(Debug, Clone)]
@@ -64,7 +62,7 @@ pub trait BtrfsDelegate: Send + Sync {
anyhow::bail!("overlay mount delegation not supported by this delegate")
}
- /// Unmount an overlay worktree previously mounted via [`Self::mount_overlay`]
+ /// Unmount an overlay worktree that was mounted via [`Self::mount_overlay`]
/// (in the caller's mount namespace).
fn unmount_overlay(&self, target: &Path) -> Result<()> {
let _ = target;
@@ -353,7 +351,7 @@ impl WorktreeBuilder {
/// modes when the source is on a BTRFS subvolume. This method is only
/// needed to *force* or *disable* that auto-detection.
pub fn btrfs_mode(self, mode: BtrfsMode) -> Self {
- // BtrfsMode is now handled inside execute.rs based on CreationMode.
+ // BtrfsMode is handled inside execute.rs based on CreationMode.
// This method is kept for backward compatibility with the CLI.
tracing::warn!(
?mode,
@@ -665,7 +663,8 @@ fn remove_worktree_from_disk(
worktree_path.display()
))?;
}
- Err(_) => {} // nothing at the path
+ // nothing at the path
+ Err(_) => {}
}
// Deregister: remove the `.git/worktrees//` directory.
@@ -995,7 +994,8 @@ fn try_btrfs_remove(
// Case 2 & 3: Check if the worktree path is a btrfs subvolume.
let btrfs_info = match btrfs::is_btrfs_subvolume(worktree_path) {
Ok(Some(info)) => info,
- Ok(None) => return Ok(None), // Not a btrfs subvolume, fall back
+ // Not a btrfs subvolume, fall back
+ Ok(None) => return Ok(None),
Err(e) => {
tracing::debug!(
path = %worktree_path.display(),
@@ -1672,7 +1672,7 @@ pub mod gc {
}
// Dry run: count the candidate without touching disk or DB. Skip
// a missing path — a real run sweeps it to dead first, so it's
- // already counted in dead_removed (don't double-count here).
+ // already counted in `dead_removed` (don't double-count here).
if opts.dry_run {
if path.exists() {
report.expired_removed += 1;
@@ -2652,7 +2652,7 @@ mod tests {
super_options: String::new(),
}];
- // `snapshot_path` is intentionally never created, so the privileged
+ // `snapshot_path` is deliberately never created, so the privileged
// `btrfs subvolume delete` is gated out (btrfs is unavailable in CI; real
// subvolume deletion is exercised only on a btrfs-capable host). The
// discriminating signals here are the metadata + dir cleanup.
@@ -2705,7 +2705,7 @@ mod tests {
super_options: String::new(),
}];
- // NOTE: `snapshot_path` is intentionally never created here, so the
+ // NOTE: `snapshot_path` is deliberately never created here, so the
// privileged `btrfs subvolume delete` is gated out (btrfs is unavailable
// in CI). This test covers the symlink-vs-dir branch selection and the
// symlink + metadata cleanup; the real subvolume deletion is exercised
@@ -2962,7 +2962,7 @@ mod tests {
head_commit: None,
session_id: None,
creator_pid: Some(my_pid),
- created_at: 1, // very old
+ created_at: 1,
last_accessed_at: None,
status: crate::db::WorktreeStatus::Alive,
metadata: None,
@@ -2972,7 +2972,7 @@ mod tests {
// sweep_dead will mark it dead (path doesn't exist),
// but gc with max_age should still check liveness for expiry.
// Since the path doesn't exist, sweep_dead marks it dead first,
- // then dead_removed cleans it. Let's use a real existing path instead.
+ // then `dead_removed` cleans it. Let's use a real existing path instead.
let dir = tmp.path().join("real-wt");
std::fs::create_dir(&dir).unwrap();
let mut record2 = record.clone();
@@ -2983,7 +2983,8 @@ mod tests {
let report = gc::gc_worktrees(
&db,
&gc::GcOptions {
- max_age_secs: Some(0), // everything is expired
+ // everything is expired
+ max_age_secs: Some(0),
force: false,
dry_run: false,
},
@@ -3028,7 +3029,8 @@ mod tests {
)
.unwrap();
- assert_eq!(report.dead_removed, 1); // counted as would-be-removed
+ // counted as would-be-removed
+ assert_eq!(report.dead_removed, 1);
// Dry run must NOT mutate: the record is still present AND still
// Alive (it was never swept to Dead).
let all = db
@@ -3059,8 +3061,8 @@ mod tests {
git_ref: None,
head_commit: None,
session_id: None,
- creator_pid: Some(std::process::id()), // our own PID
- created_at: 1, // very old
+ creator_pid: Some(std::process::id()),
+ created_at: 1,
last_accessed_at: None,
status: crate::db::WorktreeStatus::Alive,
metadata: None,
@@ -3138,8 +3140,10 @@ mod tests {
git_ref: None,
head_commit: None,
session_id: None,
- creator_pid: None, // no liveness guard: isolate the age logic
- created_at: 1, // both are old by creation time
+ // no liveness guard: isolate the age logic
+ creator_pid: None,
+ // both are old by creation time
+ created_at: 1,
last_accessed_at: None,
status: crate::db::WorktreeStatus::Alive,
metadata: None,
@@ -3147,14 +3151,16 @@ mod tests {
db.register(&crate::db::WorktreeRecord {
id: "fresh".to_string(),
path: fresh.clone(),
- last_accessed_at: Some(i64::MAX), // touched within the window
+ // touched within the window
+ last_accessed_at: Some(i64::MAX),
..base.clone()
})
.unwrap();
db.register(&crate::db::WorktreeRecord {
id: "stale".to_string(),
path: stale.clone(),
- last_accessed_at: Some(1), // never re-touched
+ // never re-touched
+ last_accessed_at: Some(1),
..base
})
.unwrap();
@@ -3199,8 +3205,10 @@ mod tests {
git_ref: None,
head_commit: None,
session_id: None,
- creator_pid: None, // creator gone: only the CWD guard can protect it
- created_at: 1, // very old → expired
+ // creator gone: only the CWD guard can protect it
+ creator_pid: None,
+ // very old → expired
+ created_at: 1,
last_accessed_at: None,
status: crate::db::WorktreeStatus::Alive,
metadata: None,
@@ -3267,8 +3275,9 @@ mod tests {
git_ref: None,
head_commit: None,
session_id: None,
- creator_pid: None, // no liveness guard
- created_at: 1, // very old
+ // no liveness guard
+ creator_pid: None,
+ created_at: 1,
last_accessed_at: None,
status: crate::db::WorktreeStatus::Alive,
metadata: None,
@@ -3300,7 +3309,7 @@ mod tests {
fn gc_dry_run_missing_and_expired_counted_once() {
// A record that is Alive, has a MISSING path, AND is expired must be
// counted EXACTLY once (a real run sweeps it to dead and unregisters
- // it before the expired loop). It belongs to dead_removed, not both.
+ // it before the expired loop). It belongs to `dead_removed`, not both.
let tmp = tempfile::TempDir::new().unwrap();
let db = db_at(&tmp);
@@ -3315,7 +3324,8 @@ mod tests {
head_commit: None,
session_id: None,
creator_pid: None,
- created_at: 1, // very old → expired
+ // very old → expired
+ created_at: 1,
last_accessed_at: None,
status: crate::db::WorktreeStatus::Alive,
metadata: None,
@@ -3344,7 +3354,7 @@ mod tests {
#[test]
fn gc_expired_failed_removal_keeps_record() {
- // When the expired worktree can't be removed, expired_removed must
+ // When the expired worktree can't be removed, `expired_removed` must
// NOT be counted and the DB record must survive (so it stays
// visible to a later gc).
let tmp = tempfile::TempDir::new().unwrap();
@@ -3520,7 +3530,8 @@ mod tests {
head_commit: None,
session_id: None,
creator_pid: None,
- created_at: 1, // very old → expired
+ // very old → expired
+ created_at: 1,
last_accessed_at: None,
status: crate::db::WorktreeStatus::Alive,
metadata: None,
diff --git a/crates/codegen/kigi-fast-worktree/src/bin/cli.rs b/crates/codegen/kigi-fast-worktree/src/bin/cli.rs
index f3a79d5..5504dfb 100644
--- a/crates/codegen/kigi-fast-worktree/src/bin/cli.rs
+++ b/crates/codegen/kigi-fast-worktree/src/bin/cli.rs
@@ -15,7 +15,6 @@ use tracing::{Level, info};
use kigi_fast_worktree::{BtrfsMode, IgnoredFilesMode, WorkingTreeMode, WorktreeBuilder};
-/// CLI enum for BTRFS mode selection
#[derive(Clone, Debug, Default, ValueEnum)]
enum CliBtrfsMode {
/// Auto-detect: use BTRFS snapshot if source is on a BTRFS subvolume
@@ -98,7 +97,6 @@ enum Commands {
fn main() -> Result<()> {
let cli = Cli::parse();
- // Initialize tracing
let level = if cli.verbose {
Level::DEBUG
} else {
@@ -167,7 +165,7 @@ fn main() -> Result<()> {
println!(" Path: {}", result.worktree_path.display());
println!(" Commit: {}", &result.commit[..12]);
- // For snapshot methods (btrfs/overlay), files_copied will be 0
+ // Snapshot methods (btrfs/overlay) copy nothing, so files_copied is 0.
if result.unignored_copy.files_copied > 0 {
println!(
" Files: {} copied, {} dirs",
diff --git a/crates/codegen/kigi-fast-worktree/src/bin/pool_perf_bench.rs b/crates/codegen/kigi-fast-worktree/src/bin/pool_perf_bench.rs
index f7292e5..f4cfc3b 100644
--- a/crates/codegen/kigi-fast-worktree/src/bin/pool_perf_bench.rs
+++ b/crates/codegen/kigi-fast-worktree/src/bin/pool_perf_bench.rs
@@ -23,9 +23,7 @@ use clap::Parser;
use kigi_fast_worktree::{CreationMode, WorktreeBuilder, WorktreeSync, remove_worktree};
-// ============================================================================
// CLI
-// ============================================================================
#[derive(Parser)]
#[command(name = "pool-perf-bench")]
@@ -60,9 +58,7 @@ struct Cli {
json: bool,
}
-// ============================================================================
// Timing structs
-// ============================================================================
#[derive(Debug, Clone)]
struct PhaseTiming {
@@ -97,9 +93,7 @@ struct BenchmarkSummary {
bottleneck: (String, f64),
}
-// ============================================================================
// Phase runners
-// ============================================================================
/// Phase 1: Create a linked worktree via GitCheckout mode (what the pool fill task does)
fn phase_create(source: &Path, dest: &Path, parallelism: usize) -> Result {
@@ -306,9 +300,7 @@ fn phase_cleanup(_source: &Path, worktree: &Path) -> Result {
})
}
-// ============================================================================
// A/B mode: two worktrees concurrently
-// ============================================================================
fn run_ab_iteration(
source: &Path,
@@ -499,9 +491,7 @@ fn run_single_iteration(
})
}
-// ============================================================================
// Helpers
-// ============================================================================
fn count_tracked_files(source: &Path) -> Result {
kigi_fast_worktree::count_tracked_files(source)
@@ -553,9 +543,7 @@ fn compute_summary(iterations: &[IterationResult]) -> BenchmarkSummary {
}
}
-// ============================================================================
// Output
-// ============================================================================
fn print_iteration(result: &IterationResult) {
println!();
@@ -683,9 +671,7 @@ fn print_json(result: &BenchmarkResult) {
println!("}}");
}
-// ============================================================================
// Main
-// ============================================================================
fn main() -> Result<()> {
let cli = Cli::parse();
diff --git a/crates/codegen/kigi-fast-worktree/src/btrfs/detect.rs b/crates/codegen/kigi-fast-worktree/src/btrfs/detect.rs
index 2302ce5..c181df1 100644
--- a/crates/codegen/kigi-fast-worktree/src/btrfs/detect.rs
+++ b/crates/codegen/kigi-fast-worktree/src/btrfs/detect.rs
@@ -153,7 +153,8 @@ fn resolve_bind_mount_source(target: &Path) -> Result> {
}
// Found our mount point
- let root = parts[3]; // The root within the filesystem
+ // The root within the filesystem
+ let root = parts[3];
let fstype_idx = parts.iter().position(|&p| p == "-").map(|i| i + 1);
if let Some(fstype_idx) = fstype_idx {
@@ -664,7 +665,7 @@ mod tests {
);
}
- // ─── Unit tests for resolve_via_subvol_mount ─────────────────────────
+ // Unit tests for resolve_via_subvol_mount
#[test]
fn test_resolve_via_subvol_mount_exact_match() {
diff --git a/crates/codegen/kigi-fast-worktree/src/btrfs/snapshot.rs b/crates/codegen/kigi-fast-worktree/src/btrfs/snapshot.rs
index 79ecfdb..00a055d 100644
--- a/crates/codegen/kigi-fast-worktree/src/btrfs/snapshot.rs
+++ b/crates/codegen/kigi-fast-worktree/src/btrfs/snapshot.rs
@@ -234,9 +234,11 @@ pub fn create_snapshot_with_symlink(btrfs_info: &BtrfsInfo, dest: &Path) -> Resu
/// the identical layout.
pub fn snapshot_dest_path(btrfs_mount: &Path, subvolume_root: &Path, dest: &Path) -> PathBuf {
let subdir = if btrfs_mount == subvolume_root {
- BTRFS_SNAPSHOT_SUBDIRS[1] // ".kigi-snapshots"
+ // ".kigi-snapshots"
+ BTRFS_SNAPSHOT_SUBDIRS[1]
} else {
- BTRFS_SNAPSHOT_SUBDIRS[0] // "worktrees"
+ // "worktrees"
+ BTRFS_SNAPSHOT_SUBDIRS[0]
};
let basename = dest
.file_name()
diff --git a/crates/codegen/kigi-fast-worktree/src/copy/cow.rs b/crates/codegen/kigi-fast-worktree/src/copy/cow.rs
index addc37b..09d082b 100644
--- a/crates/codegen/kigi-fast-worktree/src/copy/cow.rs
+++ b/crates/codegen/kigi-fast-worktree/src/copy/cow.rs
@@ -104,7 +104,7 @@ mod tests {
let dst = temp.path().join("link");
std::fs::write(&dst, "stale").unwrap();
- // Target is intentionally dangling; it must still be created.
+ // Target is deliberately dangling; it must still be created.
replace_symlink(Path::new("does-not-exist"), &dst).unwrap();
let meta = std::fs::symlink_metadata(&dst).unwrap();
diff --git a/crates/codegen/kigi-fast-worktree/src/copy/engine.rs b/crates/codegen/kigi-fast-worktree/src/copy/engine.rs
index 00e5157..7e43f0a 100644
--- a/crates/codegen/kigi-fast-worktree/src/copy/engine.rs
+++ b/crates/codegen/kigi-fast-worktree/src/copy/engine.rs
@@ -98,16 +98,20 @@ pub(crate) fn copy_parallel(
// Deep directory trees (15+ levels) amplify this significantly.
let mut builder = WalkBuilder::new(source);
builder
- .hidden(false) // Include hidden files.
+ // Include hidden files.
+ .hidden(false)
.git_ignore(config.respect_gitignore)
- .git_global(false) // Never use global gitignore (~/.config/git/ignore) —
+ // Never use global gitignore (~/.config/git/ignore) —
+ .git_global(false)
// it contains personal preferences irrelevant to worktree creation.
- .git_exclude(false) // Never use .git/info/exclude — external tooling
+ // Never use .git/info/exclude — external tooling
+ .git_exclude(false)
// can append broad patterns (*.min.js, *.zip) that
// incorrectly skip git-tracked files. The `ignore` crate doesn't
// check tracking status, so tracked files matching these patterns
// get silently dropped during the copy.
- .threads(num_workers) // Limit walker parallelism to avoid FD exhaustion
+ // Limit walker parallelism to avoid FD exhaustion
+ .threads(num_workers)
.filter_entry(|entry| {
// Always skip .git directory.
entry.file_name() != ".git"
@@ -274,7 +278,8 @@ mod tests {
assert!(dest.path().join("file1.txt").exists());
assert!(dest.path().join("file2.txt").exists());
assert!(dest.path().join("subdir/file3.txt").exists());
- assert_eq!(result.copied_paths.len(), 4); // 3 files + 1 dir
+ // 3 files + 1 dir
+ assert_eq!(result.copied_paths.len(), 4);
}
#[test]
diff --git a/crates/codegen/kigi-fast-worktree/src/copy/gitdir.rs b/crates/codegen/kigi-fast-worktree/src/copy/gitdir.rs
index fc447de..efa59c2 100644
--- a/crates/codegen/kigi-fast-worktree/src/copy/gitdir.rs
+++ b/crates/codegen/kigi-fast-worktree/src/copy/gitdir.rs
@@ -3,8 +3,8 @@
//! Copies essential git internal files using reflink (CoW) when supported,
//! skipping transient state, lock files, and stale worktree registrations.
//!
-//! The `objects/` directory (often the largest subtree) is copied in parallel
-//! using a thread pool for better throughput on SSDs.
+//! The tree is walked once to enumerate entries, then the copies are sharded
+//! across scoped threads for throughput on SSDs.
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
@@ -13,7 +13,6 @@ use anyhow::{Context, Result};
use crate::copy::cow::clone_file;
-/// Statistics from copying the `.git/` directory.
#[derive(Clone, Debug, Default)]
pub(crate) struct GitDirCopyStats {
pub files_copied: u64,
@@ -27,9 +26,7 @@ pub(crate) struct GitDirCopyStats {
/// These are either transient state (merge/rebase in-progress markers) or
/// linked-worktree metadata that would be stale in the copy.
const SKIP_TOP_LEVEL: &[&str] = &[
- // Linked worktree registrations — stale in a standalone copy
"worktrees",
- // Transient HEAD-like state files
"FETCH_HEAD",
"ORIG_HEAD",
"MERGE_HEAD",
@@ -38,11 +35,9 @@ const SKIP_TOP_LEVEL: &[&str] = &[
"REBASE_HEAD",
"AUTO_MERGE",
"BISECT_LOG",
- // In-progress multi-step operation state
"sequencer",
"rebase-merge",
"rebase-apply",
- // GC state
"gc.log",
// fsmonitor daemon state — a host-local Unix-domain IPC socket
// (`fsmonitor--daemon.ipc`, which cannot be reflinked/copied) plus its
@@ -52,27 +47,16 @@ const SKIP_TOP_LEVEL: &[&str] = &[
"fsmonitor--daemon.ipc",
];
-/// A work item for the parallel copy pool.
struct CopyWork {
source: PathBuf,
dest: PathBuf,
}
-/// Copy `.git/` directory contents using CoW, skipping unnecessary entries.
+/// Build a standalone git repository's `.git/` at `dest_git` by selectively
+/// copying from `source_git`, using reflink (CoW) where the filesystem
+/// supports it and falling back to a regular copy otherwise.
///
-/// Creates a standalone git repository's `.git/` at `dest_git` by selectively
-/// copying from `source_git`. Files are copied using reflink (CoW) when the
-/// filesystem supports it, falling back to regular copy otherwise.
-///
-/// The `objects/` subtree is copied in parallel (it's typically the largest
-/// part and has no ordering dependencies). Other top-level entries are copied
-/// sequentially.
-///
-/// Skips:
-/// - Lock files (`*.lock`) at any depth
-/// - Stale worktree registrations (`worktrees/`)
-/// - Transient state files (`MERGE_HEAD`, `CHERRY_PICK_HEAD`, etc.)
-/// - In-progress rebase/cherry-pick state (`sequencer/`, `rebase-merge/`)
+/// Lock files at any depth and the [`SKIP_TOP_LEVEL`] entries are left behind.
pub(crate) fn copy_git_dir(source_git: &Path, dest_git: &Path) -> Result {
copy_git_dir_with_workers(source_git, dest_git, num_cpus::get())
}
@@ -95,8 +79,6 @@ fn copy_git_dir_with_workers(
let symlinks_copied = AtomicU64::new(0);
let entries_skipped = AtomicU64::new(0);
- // First pass: collect work items for parallel copy.
- // We collect all (source, dest) pairs, then process them in parallel.
let mut work_items: Vec = Vec::new();
collect_work_recursive(
source_git,
@@ -107,7 +89,6 @@ fn copy_git_dir_with_workers(
&entries_skipped,
)?;
- // Process file copies in parallel using scoped threads.
let num_workers = max_workers.min(work_items.len().max(1));
if num_workers <= 1 || work_items.len() < 64 {
@@ -182,10 +163,9 @@ fn copy_git_dir_with_workers(
Ok(stats)
}
-/// Recursively collect work items (files/symlinks to copy), creating directories eagerly.
-///
-/// Directories are created immediately (they must exist before files are written),
-/// but file copies are deferred to the work list for parallel processing.
+/// Walk `source`, creating every directory eagerly (they must exist before the
+/// copy workers write into them) while deferring files and symlinks to
+/// `work_items` for parallel copying.
fn collect_work_recursive(
source: &Path,
dest: &Path,
@@ -230,7 +210,6 @@ fn collect_work_recursive(
entries_skipped,
)?;
} else if file_type.is_file() || file_type.is_symlink() {
- // Regular file or symlink — add to work list.
work_items.push(CopyWork {
source: source_path,
dest: dest_path,
@@ -249,14 +228,12 @@ fn collect_work_recursive(
Ok(())
}
-/// Copy a single file or symlink entry.
fn copy_single_entry(
source_path: &Path,
dest_path: &Path,
files_copied: &AtomicU64,
symlinks_copied: &AtomicU64,
) -> Result<()> {
- // Check if it's a symlink by querying symlink metadata.
let metadata = std::fs::symlink_metadata(source_path)
.with_context(|| format!("failed to stat {}", source_path.display()))?;
@@ -301,13 +278,10 @@ fn copy_single_entry(
Ok(())
}
-/// Decide whether to skip a `.git/` entry based on its name and depth.
fn should_skip(name: &str, depth: usize) -> bool {
- // Skip lock files at any depth
if name.ends_with(".lock") {
return true;
}
- // Skip known top-level entries
if depth == 0 && SKIP_TOP_LEVEL.contains(&name) {
return true;
}
@@ -325,7 +299,6 @@ mod tests {
let source_git = temp.path().join("source/.git");
let dest_git = temp.path().join("dest/.git");
- // Create a minimal .git structure
std::fs::create_dir_all(source_git.join("objects/pack")).unwrap();
std::fs::create_dir_all(source_git.join("refs/heads")).unwrap();
std::fs::write(source_git.join("HEAD"), "ref: refs/heads/main\n").unwrap();
@@ -539,7 +512,6 @@ mod tests {
let err = copy_git_dir_with_workers(&source_git, &dest_git, 4)
.expect_err("a failed .git/ entry copy must propagate as an error");
- // The error names the failing entry, not some unrelated setup failure.
let chain = format!("{err:#}");
assert!(
chain.contains("obj0"),
diff --git a/crates/codegen/kigi-fast-worktree/src/copy/shard.rs b/crates/codegen/kigi-fast-worktree/src/copy/shard.rs
index a04159d..eb0c99a 100644
--- a/crates/codegen/kigi-fast-worktree/src/copy/shard.rs
+++ b/crates/codegen/kigi-fast-worktree/src/copy/shard.rs
@@ -17,17 +17,13 @@ fn rapidhash_path(path: &Path) -> u64 {
rapidhash_v3(bytes)
}
-/// Compute the shard index for a path based on its parent directory.
-///
-/// Files in the same directory will always be assigned to the same shard,
-/// which avoids lock contention when creating parent directories.
+/// Sharded on the parent directory, so files in the same directory always land
+/// in the same shard and never contend on creating their parent.
pub(crate) fn shard_for_path(path: &Path, num_shards: usize) -> usize {
let parent = path.parent().unwrap_or(path);
(rapidhash_path(parent) as usize) % num_shards
}
-/// Deterministic 16-hex-char (full 64-bit) hash of a path's full bytes.
-///
/// Disambiguates same-basename worktrees that share a basename-derived key (btrfs
/// snapshot name, worktree DB id). Full 64 bits keep a collision astronomically
/// unlikely.
@@ -53,7 +49,6 @@ mod tests {
let shard2 = shard_for_path(&file2, num_shards);
let shard3 = shard_for_path(&file3, num_shards);
- // All files in src/ should go to the same shard
assert_eq!(shard1, shard2);
assert_eq!(shard2, shard3);
}
@@ -65,10 +60,10 @@ mod tests {
let num_shards = 8;
- // Different directories may (but don't have to) produce different shards
+ // Different directories may collide onto one shard, so there is nothing
+ // to assert beyond "does not panic".
let _shard1 = shard_for_path(&file1, num_shards);
let _shard2 = shard_for_path(&file2, num_shards);
- // Just verify it doesn't panic
}
#[test]
diff --git a/crates/codegen/kigi-fast-worktree/src/copy/skip.rs b/crates/codegen/kigi-fast-worktree/src/copy/skip.rs
index eae665f..a08eac3 100644
--- a/crates/codegen/kigi-fast-worktree/src/copy/skip.rs
+++ b/crates/codegen/kigi-fast-worktree/src/copy/skip.rs
@@ -7,7 +7,6 @@ use anyhow::Result;
use dashmap::DashSet;
use ignore::{WalkBuilder, WalkState};
-/// Build a globset matcher for skip patterns.
pub(crate) fn build_skip_matcher(patterns: &[String]) -> Result {
let mut builder = globset::GlobSetBuilder::new();
for pattern in patterns {
@@ -16,10 +15,10 @@ pub(crate) fn build_skip_matcher(patterns: &[String]) -> Result>>,
- /// Whether to respect `.gitignore` rules
pub respect_gitignore: bool,
- /// Additional patterns to skip (glob patterns)
+ /// Globs, applied on top of `skip_files`.
pub skip_patterns: Vec,
}
diff --git a/crates/codegen/kigi-fast-worktree/src/copy/worker.rs b/crates/codegen/kigi-fast-worktree/src/copy/worker.rs
index 56affd1..19c0a7a 100644
--- a/crates/codegen/kigi-fast-worktree/src/copy/worker.rs
+++ b/crates/codegen/kigi-fast-worktree/src/copy/worker.rs
@@ -23,7 +23,6 @@ pub(crate) struct WorkerCtx {
}
pub(crate) fn run_worker(rx: crossbeam::channel::Receiver, ctx: WorkerCtx) {
- // Track created directories to avoid redundant mkdir calls.
let mut created_dirs: HashSet = HashSet::new();
for entry in rx {
@@ -59,7 +58,8 @@ fn process_entry(
issues: &std::sync::Mutex>,
file_metadata: &DashMap,
) -> bool {
- // Ensure parent directory exists.
+ // `created_dirs.insert` yields false for a parent this worker already made,
+ // short-circuiting the chain so the mkdir syscall is skipped.
if let Some(parent) = dst.parent()
&& !parent.as_os_str().is_empty()
&& created_dirs.insert(parent.to_path_buf())
@@ -116,7 +116,6 @@ fn process_entry(
Ok(()) => {
files_copied.fetch_add(1, Ordering::Relaxed);
- // Collect file metadata for index updates
if let Ok(metadata) = std::fs::metadata(dst) {
file_metadata.insert(entry.rel_path.clone(), metadata);
}
diff --git a/crates/codegen/kigi-fast-worktree/src/db/mod.rs b/crates/codegen/kigi-fast-worktree/src/db/mod.rs
index 765053b..c374a16 100644
--- a/crates/codegen/kigi-fast-worktree/src/db/mod.rs
+++ b/crates/codegen/kigi-fast-worktree/src/db/mod.rs
@@ -138,7 +138,7 @@ impl WorktreeDb {
.with_context(|| format!("failed to open worktree DB: {}", path.display()))?;
let db = Self { conn };
db.set_journal_mode(journal_mode)?;
- // Normal statement timeout, now that the conversion budget is done.
+ // Normal statement timeout, because the conversion budget is done.
db.conn
.busy_timeout(std::time::Duration::from_millis(5000))?;
db.init_schema()?;
diff --git a/crates/codegen/kigi-fast-worktree/src/db/queries.rs b/crates/codegen/kigi-fast-worktree/src/db/queries.rs
index 5f82598..b75c336 100644
--- a/crates/codegen/kigi-fast-worktree/src/db/queries.rs
+++ b/crates/codegen/kigi-fast-worktree/src/db/queries.rs
@@ -166,6 +166,7 @@ pub fn list(conn: &Connection, filter: &ListFilter) -> Result = Vec::with_capacity(idx);
if let Some(ref s) = status_str {
params.push(s);
diff --git a/crates/codegen/kigi-fast-worktree/src/db/tests.rs b/crates/codegen/kigi-fast-worktree/src/db/tests.rs
index 84cb940..8433cb5 100644
--- a/crates/codegen/kigi-fast-worktree/src/db/tests.rs
+++ b/crates/codegen/kigi-fast-worktree/src/db/tests.rs
@@ -68,7 +68,8 @@ fn unregister_by_id() {
assert!(db.unregister("a").unwrap());
assert!(db.get("a").unwrap().is_none());
- assert!(!db.unregister("a").unwrap()); // second call returns false
+ // second call returns false
+ assert!(!db.unregister("a").unwrap());
}
#[test]
@@ -629,7 +630,7 @@ fn network_mode_uses_fresh_per_host_truncate_db() {
let db = WorktreeDb::open_at_with_journal_mode(&path, JournalMode::Truncate).unwrap();
assert_eq!(journal_mode(&db), "truncate");
- // Fresh per-host DB: legacy rows are intentionally not visible.
+ // Fresh per-host DB: legacy rows are deliberately not visible.
assert!(db.get("wt-legacy").unwrap().is_none());
db.register(&make_record("wt-nfs", "/tmp/wt-nfs", WorktreeKind::Manual))
.unwrap();
diff --git a/crates/codegen/kigi-fast-worktree/src/discovery.rs b/crates/codegen/kigi-fast-worktree/src/discovery.rs
index 12ef971..3d821bc 100644
--- a/crates/codegen/kigi-fast-worktree/src/discovery.rs
+++ b/crates/codegen/kigi-fast-worktree/src/discovery.rs
@@ -299,7 +299,6 @@ mod tests {
assert!(db.get(&wt_a.to_string_lossy()).unwrap().is_some());
assert!(db.get(&wt_b.to_string_lossy()).unwrap().is_some());
- // Idempotent: a second rebuild finds both already tracked, skips neither.
let report2 = rebuild_worktree_db(&db, kigi_home).unwrap();
assert_eq!(report2.registered, 0);
assert_eq!(report2.already_tracked, 2);
diff --git a/crates/codegen/kigi-fast-worktree/src/git/checkout.rs b/crates/codegen/kigi-fast-worktree/src/git/checkout.rs
index 1df76ad..261eb6b 100644
--- a/crates/codegen/kigi-fast-worktree/src/git/checkout.rs
+++ b/crates/codegen/kigi-fast-worktree/src/git/checkout.rs
@@ -445,7 +445,7 @@ fn rehydrate_worktree_from_ref_inner(
) -> Result {
let dest_str = dest.to_string_lossy();
- // A previously-disposed worktree can leave a stale registration for this
+ // An earlier-disposed worktree can leave a stale registration for this
// path; prune it so re-adding the original `subagent-` dir succeeds.
snapshot_git(source_repo, &["worktree", "prune"], &[])?;
diff --git a/crates/codegen/kigi-fast-worktree/src/git/discovery.rs b/crates/codegen/kigi-fast-worktree/src/git/discovery.rs
index 441d9f3..90e5093 100644
--- a/crates/codegen/kigi-fast-worktree/src/git/discovery.rs
+++ b/crates/codegen/kigi-fast-worktree/src/git/discovery.rs
@@ -145,7 +145,8 @@ mod tests {
git_commit_all(temp.path(), "initial");
let commit = get_head_commit(temp.path()).unwrap();
- assert_eq!(commit.len(), 40); // SHA-1 hex string
+ // SHA-1 hex string
+ assert_eq!(commit.len(), 40);
assert!(commit.chars().all(|c| c.is_ascii_hexdigit()));
}
}
diff --git a/crates/codegen/kigi-fast-worktree/src/git/mod.rs b/crates/codegen/kigi-fast-worktree/src/git/mod.rs
index ff868cc..658dd1e 100644
--- a/crates/codegen/kigi-fast-worktree/src/git/mod.rs
+++ b/crates/codegen/kigi-fast-worktree/src/git/mod.rs
@@ -1,7 +1,4 @@
//! Git operations used by fast worktree creation.
-//!
-//! This module isolates git-specific functionality (worktree creation, status, index refresh)
-//! from filesystem copy logic and orchestration.
pub(crate) mod checkout;
pub(crate) mod discovery;
diff --git a/crates/codegen/kigi-fast-worktree/src/git/worktree.rs b/crates/codegen/kigi-fast-worktree/src/git/worktree.rs
index 7f12fc1..9b35f2a 100644
--- a/crates/codegen/kigi-fast-worktree/src/git/worktree.rs
+++ b/crates/codegen/kigi-fast-worktree/src/git/worktree.rs
@@ -1,12 +1,9 @@
-//! Git worktree operations.
-
use std::path::Path;
use anyhow::{Context, Result};
use crate::git::checkout::git_command;
-/// Create a git worktree with `--no-checkout`. Blocking.
pub(crate) fn worktree_add_no_checkout(source: &Path, dest: &str, git_ref: &str) -> Result<()> {
let output = git_command()
.current_dir(source)
diff --git a/crates/codegen/kigi-fast-worktree/src/lib.rs b/crates/codegen/kigi-fast-worktree/src/lib.rs
index d5ede8a..c7327c2 100644
--- a/crates/codegen/kigi-fast-worktree/src/lib.rs
+++ b/crates/codegen/kigi-fast-worktree/src/lib.rs
@@ -52,11 +52,10 @@ pub use sync::{SourceDirtyState, SyncReport, WorktreeSync, collect_source_dirty_
#[cfg(target_os = "linux")]
pub use worktree::execute::cleanup_snapshot_git_state;
-/// Count the number of tracked files in a git repository's index.
+/// Count the tracked files in a git repository's index.
///
-/// Reads the index header via `gix`, which contains the entry count — this
-/// is an O(1) read (no directory walk). Useful for deciding whether a repo
-/// is large enough to benefit from worktree pooling.
+/// Reads the entry count out of the index header via `gix`, so this is an
+/// O(1) read with no directory walk.
pub fn count_tracked_files(repo_path: &std::path::Path) -> anyhow::Result {
let repo = gix::discover(repo_path)
.map_err(|e| anyhow::anyhow!("failed to discover git repo: {e}"))?;
diff --git a/crates/codegen/kigi-fast-worktree/src/mount_info.rs b/crates/codegen/kigi-fast-worktree/src/mount_info.rs
index 844ec19..b8af508 100644
--- a/crates/codegen/kigi-fast-worktree/src/mount_info.rs
+++ b/crates/codegen/kigi-fast-worktree/src/mount_info.rs
@@ -15,7 +15,6 @@ use anyhow::{Context, Result};
/// ```
#[derive(Debug, Clone)]
pub struct MountEntry {
- /// Mount ID.
#[allow(dead_code)]
pub mount_id: u32,
/// Parent mount ID.
@@ -243,7 +242,7 @@ pub fn is_fuse_mount(entries: &[MountEntry], path: &Path) -> bool {
})
}
-// ── Internal helpers ─────────────────────────────────────────────────────
+// Internal helpers
/// Parse a single mountinfo line.
fn parse_line(line: &str) -> Option {
diff --git a/crates/codegen/kigi-fast-worktree/src/overlay/detect.rs b/crates/codegen/kigi-fast-worktree/src/overlay/detect.rs
index f28086b..d411521 100644
--- a/crates/codegen/kigi-fast-worktree/src/overlay/detect.rs
+++ b/crates/codegen/kigi-fast-worktree/src/overlay/detect.rs
@@ -30,10 +30,10 @@ pub struct OverlayInfo {
pub overlay_root: PathBuf,
}
-/// Detect if `path` is on a FUSE+overlayfs stack with btrfs upper.
+/// Detect whether `path` is on a FUSE+overlayfs stack with a btrfs upper.
///
-/// Returns `Ok(Some(OverlayInfo))` if all conditions are met, `Ok(None)` otherwise.
-/// Handles `EIO`/`ENOTCONN` from a crashed FUSE daemon gracefully by returning `Ok(None)`.
+/// A crashed FUSE daemon (`EIO`/`ENOTCONN`) is not an error here: it yields
+/// `Ok(None)` like any other unsuitable mount.
pub fn detect_fuse_overlay(path: &Path) -> Result> {
let entries = match mount_info::parse_mountinfo() {
Ok(entries) => entries,
@@ -46,12 +46,12 @@ pub fn detect_fuse_overlay(path: &Path) -> Result > {
detect_fuse_overlay_from_entries(path, &entries)
}
-/// Testable version that takes pre-parsed entries.
+/// Split out from `detect_fuse_overlay` so tests can drive it with synthetic
+/// mountinfo instead of the host's real mount table.
pub(crate) fn detect_fuse_overlay_from_entries(
path: &Path,
entries: &[mount_info::MountEntry],
) -> Result > {
- // Step 1: Find overlay mount containing this path.
let overlay = match mount_info::find_overlay_mount(entries, path) {
Some(info) => info,
None => {
@@ -60,7 +60,6 @@ pub(crate) fn detect_fuse_overlay_from_entries(
}
};
- // Step 2: Verify the lower layer is a FUSE mount.
if !mount_info::is_fuse_mount(entries, &overlay.lower_dir) {
tracing::debug!(
lower = %overlay.lower_dir.display(),
@@ -69,12 +68,12 @@ pub(crate) fn detect_fuse_overlay_from_entries(
return Ok(None);
}
- // Step 3: Verify the upper layer is on btrfs.
let upper_on_btrfs = match crate::btrfs::is_btrfs(&overlay.upper_dir) {
Ok(true) => true,
Ok(false) => false,
Err(e) => {
- // EIO / ENOTCONN from crashed FUSE — treat as "not available"
+ // EIO/ENOTCONN from a crashed FUSE daemon lands here; treat any
+ // probe failure as "not btrfs" rather than failing detection.
tracing::debug!(
upper = %overlay.upper_dir.display(),
error = %e,
@@ -92,7 +91,6 @@ pub(crate) fn detect_fuse_overlay_from_entries(
return Ok(None);
}
- // Derive overlay_root — parent of upper_dir (sibling of upper/ and work/).
let overlay_root = overlay
.upper_dir
.parent()
@@ -151,7 +149,6 @@ mod tests {
#[test]
fn test_detect_overlay_without_fuse_lower() {
- // Overlay where lower is ext4, not FUSE — should return None.
let mountinfo = "\
22 1 8:1 / / rw - ext4 /dev/sda1 rw
30 22 8:2 / /lower rw - ext4 /dev/sda2 rw
@@ -176,15 +173,11 @@ mod tests {
#[test]
fn test_overlay_info_fields() {
- // We can't run the btrfs check in unit tests (no btrfs fs), but we
- // can verify the parsing portion works by calling the internal function
- // and checking that step 3 (btrfs) is the failing point.
+ // The sample upper path does not exist on the test host, so the btrfs
+ // probe fails and detection stops short of `Some`. This only covers
+ // the mountinfo parsing path; the result is deliberately unasserted.
let entries = parse_mountinfo_from(FUSE_OVERLAY_MOUNTINFO);
- // This will return None because the sample upper path doesn't exist,
- // so is_btrfs will fail — but that's expected in a unit test.
let result = detect_fuse_overlay_from_entries(Path::new("/workspace/repo"), &entries);
assert!(result.is_ok());
- // On a system without the actual btrfs mount, this returns None.
- // On a host with a live FUSE+overlay stack it would return Some.
}
}
diff --git a/crates/codegen/kigi-fast-worktree/src/overlay/snapshot.rs b/crates/codegen/kigi-fast-worktree/src/overlay/snapshot.rs
index 65c8ce4..152e519 100644
--- a/crates/codegen/kigi-fast-worktree/src/overlay/snapshot.rs
+++ b/crates/codegen/kigi-fast-worktree/src/overlay/snapshot.rs
@@ -513,7 +513,7 @@ pub fn cleanup_orphaned_overlay_snapshots() -> crate::api::CleanupReport {
report
}
-// ── Internal helpers ─────────────────────────────────────────────────────
+// Internal helpers
/// Mount overlayfs using `libc::mount()` syscall.
fn mount_overlay(lower: &Path, upper: &Path, work: &Path, target: &Path) -> Result<()> {
diff --git a/crates/codegen/kigi-fast-worktree/src/sync.rs b/crates/codegen/kigi-fast-worktree/src/sync.rs
index 0df0756..2da7da1 100644
--- a/crates/codegen/kigi-fast-worktree/src/sync.rs
+++ b/crates/codegen/kigi-fast-worktree/src/sync.rs
@@ -95,7 +95,7 @@ pub struct SyncReport {
/// Whether dirty sync was skipped because pre-computed state was empty.
pub dirty_skipped: bool,
- // ── Per-phase timing (milliseconds) ─────────────────────────────────
+ // Per-phase timing (milliseconds)
/// Time to resolve HEAD commits on source + worktree (gix).
pub head_resolve_ms: u64,
/// Time for `git reset --hard` (0 if HEAD didn't move).
@@ -1310,14 +1310,12 @@ mod tests {
);
}
- // ========================================================================
// skip_clean=true tests (pool path)
//
// The worktree pool calls sync_worktree_opts(copy_dirty, skip_clean=true)
// because pool worktrees are known-clean (freshly created or just
// released). These tests verify that commits, dirty files, and untracked
// files are correctly replicated through that code path.
- // ========================================================================
#[test]
fn test_skip_clean_commit_replication() {
@@ -1513,7 +1511,7 @@ mod tests {
let worktree = create_linked_worktree(&source, "wt1");
- // --- First sync: source advanced ---
+ // First sync: source advanced
std::fs::write(source.join("file.txt"), "v2").unwrap();
git_commit_all(&source, "second");
@@ -1525,7 +1523,7 @@ mod tests {
"v2"
);
- // --- Simulate release: reset --hard + clean (what the pool does) ---
+ // Simulate release: reset --hard + clean (what the pool does)
Command::new("git")
.current_dir(&worktree)
.args(["reset", "--hard"])
@@ -1537,7 +1535,7 @@ mod tests {
.output()
.unwrap();
- // --- Second sync: source advanced again with dirty state ---
+ // Second sync: source advanced again with dirty state
std::fs::write(source.join("file.txt"), "v3").unwrap();
git_commit_all(&source, "third");
std::fs::write(source.join("file.txt"), "v3-dirty").unwrap();
@@ -1666,7 +1664,7 @@ mod tests {
);
}
- // ── sync_from_precomputed tests ──
+ // sync_from_precomputed tests
#[test]
fn test_sync_from_precomputed_none_skips_dirty() {
diff --git a/crates/codegen/kigi-fast-worktree/src/worktree/execute.rs b/crates/codegen/kigi-fast-worktree/src/worktree/execute.rs
index 3838f19..2183fae 100644
--- a/crates/codegen/kigi-fast-worktree/src/worktree/execute.rs
+++ b/crates/codegen/kigi-fast-worktree/src/worktree/execute.rs
@@ -277,7 +277,6 @@ fn execute_create_worktree_dispatch(plan: WorktreePlan) -> Result Result Result {
num_workers: effective_ignored_parallelism,
channel_buffer,
skip_files: Some(Arc::new(already_copied)),
- respect_gitignore: false, // We want all files.
+ // We want all files.
+ respect_gitignore: false,
skip_patterns,
};
diff --git a/crates/codegen/kigi-fast-worktree/src/worktree/mod.rs b/crates/codegen/kigi-fast-worktree/src/worktree/mod.rs
index f7cf25a..e29b141 100644
--- a/crates/codegen/kigi-fast-worktree/src/worktree/mod.rs
+++ b/crates/codegen/kigi-fast-worktree/src/worktree/mod.rs
@@ -973,7 +973,7 @@ mod tests {
let _ = std::fs::remove_dir_all(&repo_path);
}
- // ─── Standalone mode tests ───────────────────────────────────────────
+ // Standalone mode tests
#[test]
fn test_standalone_worktree_simple() {
@@ -1218,7 +1218,7 @@ mod tests {
assert!(result.ignored_copy.is_some());
}
- // ─── Cancellation / partial-creation cleanup tests ───────────────────
+ // Cancellation / partial-creation cleanup tests
#[test]
fn test_linked_cancel_after_worktree_add_deregisters() {
@@ -1307,7 +1307,8 @@ mod tests {
let result = WorktreeBuilder::new(repo_path.clone(), dest.clone())
.creation_mode(CreationMode::Linked)
.ignored_files_mode(IgnoredFilesMode::Copy {
- skip_patterns: vec!["[".to_string()], // invalid glob → build fails
+ // invalid glob → build fails
+ skip_patterns: vec!["[".to_string()],
})
.create();
assert!(result.is_err(), "invalid skip glob must fail creation");
diff --git a/crates/codegen/kigi-fast-worktree/src/worktree/plan.rs b/crates/codegen/kigi-fast-worktree/src/worktree/plan.rs
index 3b19bff..415b881 100644
--- a/crates/codegen/kigi-fast-worktree/src/worktree/plan.rs
+++ b/crates/codegen/kigi-fast-worktree/src/worktree/plan.rs
@@ -1,6 +1,4 @@
//! Worktree execution planning.
-//!
-//! `WorktreePlan` makes the worktree creation pipeline explicit and testable.
use std::path::PathBuf;
use std::sync::Arc;
@@ -9,9 +7,10 @@ use tokio_util::sync::CancellationToken;
use crate::{BtrfsDelegate, CreationMode, IgnoredFilesMode, WorkingTreeMode};
+// Debug cannot be derived: `Arc` is not Debug, so there is a
+// hand-written impl below.
#[derive(Clone)]
pub(crate) struct WorktreePlan {
- // Note: manual Debug impl below (Arc isn't Debug)
pub source: PathBuf,
pub dest: PathBuf,
pub git_ref: String,
@@ -20,13 +19,12 @@ pub(crate) struct WorktreePlan {
pub working_tree: WorkingTreeMode,
pub ignored_files: IgnoredFilesMode,
pub ignored_parallelism: usize,
- /// Strategy for worktree creation (linked, standalone, or git checkout).
pub creation_mode: CreationMode,
- /// Cancellation token for aborting file copy mid-flight.
+ /// Aborts the file copy mid-flight.
pub cancellation_token: CancellationToken,
- /// Optional delegate for privileged btrfs operations (used when the caller
- /// lacks CAP_SYS_ADMIN, e.g., inside a bwrap sandbox).
- /// Only read on Linux (in `try_btrfs_delegate`).
+ /// Performs privileged btrfs operations when the caller lacks
+ /// CAP_SYS_ADMIN, e.g. inside a bwrap sandbox. Only read on Linux, in
+ /// `try_btrfs_delegate`, hence the `dead_code` allowance elsewhere.
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
pub btrfs_delegate: Option>,
}
diff --git a/crates/codegen/kigi-fast-worktree/tests/overlay_integration.rs b/crates/codegen/kigi-fast-worktree/tests/overlay_integration.rs
index c862359..dfb3404 100644
--- a/crates/codegen/kigi-fast-worktree/tests/overlay_integration.rs
+++ b/crates/codegen/kigi-fast-worktree/tests/overlay_integration.rs
@@ -30,7 +30,7 @@ use kigi_fast_worktree::{
remove_worktree,
};
-// ── Test infrastructure ──────────────────────────────────────────────────
+// Test infrastructure
/// Parsed overlay environment, or `None` when the FUSE+overlay+btrfs
/// stack is not available (CI, local laptop, hosts without the stack).
@@ -234,8 +234,6 @@ fn force_unmount(path: &Path) {
}
}
-// ── 1. Overlay = FUSE + btrfs subvolume ──────────────────────────────────
-
/// Verify detection: the source repo's overlay has a FUSE lower and btrfs upper.
#[test]
fn test_detect_fuse_overlay_on_source_repo() {
@@ -424,8 +422,6 @@ fn test_overlay_mount_fuse_lower_btrfs_upper() {
let _ = std::fs::remove_dir_all(&worktrees_dir);
}
-// ── 2. Worktree = FUSE + snapshot ────────────────────────────────────────
-
/// Create a worktree via `WorktreeBuilder` on the overlay source repo and
/// verify it produced a valid git worktree with zero files copied (overlay
/// snapshot path).
@@ -460,13 +456,11 @@ fn test_worktree_builder_uses_overlay_snapshot() {
result.unignored_copy.files_copied
);
- // 3. Commit should be non-empty.
assert!(
!result.commit.is_empty(),
"worktree should have a HEAD commit"
);
- // 4. Worktree should be a valid git repo.
let status = std::process::Command::new("git")
.current_dir(&result.worktree_path)
.args(["rev-parse", "--git-dir"])
@@ -477,7 +471,6 @@ fn test_worktree_builder_uses_overlay_snapshot() {
"worktree should be a valid git repo"
);
- // 5. Worktree should have repo files.
assert!(
result.worktree_path.join(".git").exists(),
"worktree should have .git"
@@ -633,8 +626,6 @@ fn test_overlay_worktree_git_status() {
let _ = remove_worktree(&result.worktree_path);
}
-// ── 3. Manual cleanup ────────────────────────────────────────────────────
-
/// `remove_worktree` on an overlay worktree should unmount + delete snapshot.
#[test]
fn test_remove_worktree_cleans_overlay() {
@@ -725,7 +716,6 @@ fn test_cleanup_worktrees_in_removes_overlay_worktrees() {
assert!(r_a.worktree_path.exists());
assert!(r_b.worktree_path.exists());
- // Now clean up via cleanup_worktrees_in on the parent dir that contains .git
// The worktrees have .git so cleanup_worktrees_in should find them.
let report: CleanupReport = cleanup_worktrees_in(&cleanup_dir);
@@ -813,7 +803,7 @@ fn test_cleanup_orphaned_overlay_snapshots() {
"orphaned metadata should be deleted after cleanup"
);
- // Note: report.removed counts ALL orphans cleaned up, which may include
+ // Note: `report.removed` tallies ALL orphans cleaned up, which may include
// orphans from other tests or previous runs. We just verify our snapshot is gone.
}
diff --git a/crates/codegen/kigi-file-utils/src/events/log.rs b/crates/codegen/kigi-file-utils/src/events/log.rs
index 7d748ea..9f052a2 100644
--- a/crates/codegen/kigi-file-utils/src/events/log.rs
+++ b/crates/codegen/kigi-file-utils/src/events/log.rs
@@ -54,7 +54,8 @@ impl EventWriter {
Self {
inner: Arc::new(EventWriterInner {
file: Mutex::new(None),
- error_logged: AtomicBool::new(true), // suppress error logging
+ // suppress error logging
+ error_logged: AtomicBool::new(true),
}),
}
}
diff --git a/crates/codegen/kigi-file-utils/src/events/tracker.rs b/crates/codegen/kigi-file-utils/src/events/tracker.rs
index 358fc5c..3796ed3 100644
--- a/crates/codegen/kigi-file-utils/src/events/tracker.rs
+++ b/crates/codegen/kigi-file-utils/src/events/tracker.rs
@@ -169,7 +169,7 @@ impl EventTracker {
self.pending_interrupt_reminder.replace(false)
}
- /// Emit PhaseChanged(PermissionPrompt) → PermissionRequested.
+ /// Emit `PhaseChanged`(PermissionPrompt) → PermissionRequested.
/// Returns the Instant for `permission_resolved()` to compute wait_ms.
pub fn permission_requested(&self, tool_name: &str) -> Instant {
self.emit(Event::PhaseChanged {
diff --git a/crates/codegen/kigi-file-utils/src/events/types.rs b/crates/codegen/kigi-file-utils/src/events/types.rs
index 57f3520..e99dfe5 100644
--- a/crates/codegen/kigi-file-utils/src/events/types.rs
+++ b/crates/codegen/kigi-file-utils/src/events/types.rs
@@ -339,7 +339,7 @@ pub enum Event {
pattern: &'static str,
},
- // ── MCP Diagnostics ──────────────────────────────────────────
+ // MCP Diagnostics
McpConfigResolved {
servers: Vec,
disabled: Vec,
diff --git a/crates/codegen/kigi-file-utils/src/lib.rs b/crates/codegen/kigi-file-utils/src/lib.rs
index 2acfeab..e20cca1 100644
--- a/crates/codegen/kigi-file-utils/src/lib.rs
+++ b/crates/codegen/kigi-file-utils/src/lib.rs
@@ -41,7 +41,6 @@ pub const SKIP_DIR_NAMES: &[&str] = &[
".ruff_cache",
];
-/// [`SKIP_DIR_NAMES`] as a set for O(1) membership checks.
pub fn skip_dir_set() -> &'static std::collections::HashSet<&'static str> {
use std::collections::HashSet;
use std::sync::LazyLock;
@@ -50,15 +49,14 @@ pub fn skip_dir_set() -> &'static std::collections::HashSet<&'static str> {
&SET
}
-/// Compute SHA256 hash of content as a hex string.
pub fn sha256_hex(content: &[u8]) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(content);
format!("{:x}", hasher.finalize())
}
-/// Compute SHA256 hash of a file by streaming, without loading entire file into memory.
-/// If `max_bytes` is set (> 0), only hash up to that many bytes.
+/// Streams the file instead of loading it into memory; `max_bytes` caps how
+/// much of it is hashed.
pub fn sha256_hex_from_file(
path: &std::path::Path,
max_bytes: Option,
diff --git a/crates/codegen/kigi-file-utils/src/s3.rs b/crates/codegen/kigi-file-utils/src/s3.rs
index 22e21a9..f45fb7a 100644
--- a/crates/codegen/kigi-file-utils/src/s3.rs
+++ b/crates/codegen/kigi-file-utils/src/s3.rs
@@ -124,7 +124,7 @@ pub(crate) async fn build_s3_client(
/// Static access-key credentials for presigning S3 URLs.
///
-/// `Debug` is intentionally redacted — the struct holds plaintext secrets.
+/// `Debug` is deliberately redacted — the struct holds plaintext secrets.
#[derive(Clone)]
pub struct S3StaticCredentials {
pub access_key_id: String,
diff --git a/crates/codegen/kigi-file-utils/src/trace_context.rs b/crates/codegen/kigi-file-utils/src/trace_context.rs
index ef87317..7c6e968 100644
--- a/crates/codegen/kigi-file-utils/src/trace_context.rs
+++ b/crates/codegen/kigi-file-utils/src/trace_context.rs
@@ -247,7 +247,8 @@ mod tests {
trace_id,
span_id,
TraceFlags::SAMPLED,
- true, // is_remote
+ // is_remote
+ true,
TraceState::default(),
);
diff --git a/crates/codegen/kigi-file-utils/src/workspace_classifier.rs b/crates/codegen/kigi-file-utils/src/workspace_classifier.rs
index 50c16eb..4d25f3f 100644
--- a/crates/codegen/kigi-file-utils/src/workspace_classifier.rs
+++ b/crates/codegen/kigi-file-utils/src/workspace_classifier.rs
@@ -25,6 +25,8 @@ pub fn is_project_dir(cwd: &Path) -> bool {
return false;
}
+ // A repo checked out anywhere counts as a project, even under a system or
+ // excluded directory, so this check deliberately precedes the exclusions.
if cwd.ancestors().any(|p| p.join(".git").exists()) {
return true;
}
diff --git a/crates/codegen/kigi-fsnotify/benches/startup.rs b/crates/codegen/kigi-fsnotify/benches/startup.rs
index 31d952a..872905a 100644
--- a/crates/codegen/kigi-fsnotify/benches/startup.rs
+++ b/crates/codegen/kigi-fsnotify/benches/startup.rs
@@ -3,7 +3,7 @@
//! All scenarios build ~12k total dirs so inotify-watch creation cost is
//! comparable across them:
//!
-//! - `favorable` — most dirs live in a gitignored `target/` the new code skips.
+//! - `favorable` — most dirs live in a gitignored `target/` that fan-out skips.
//! - `fanout_w48_with_target` — 48 non-ignored top-level children PLUS a
//! gitignored `target/`: a realistic moderate-width repo that fans out and
//! skips the build dir (net win).
@@ -38,8 +38,8 @@ fn make_dirs(base: &Path, count: usize) {
}
}
-/// Favorable: three watched subtrees plus a large gitignored `target/` holding
-/// ~2/3 of the dirs. Kept at ~`TOTAL_DIRS` for comparability with the others.
+/// Three watched subtrees plus a large gitignored `target/` holding ~2/3 of
+/// the dirs, totalling ~`TOTAL_DIRS` for comparability with the other shapes.
fn build_favorable_tree() -> TempDir {
let temp = TempDir::new().unwrap();
let root = temp.path();
diff --git a/crates/codegen/kigi-fsnotify/examples/watch_stats.rs b/crates/codegen/kigi-fsnotify/examples/watch_stats.rs
index e5a7fa9..7996efc 100644
--- a/crates/codegen/kigi-fsnotify/examples/watch_stats.rs
+++ b/crates/codegen/kigi-fsnotify/examples/watch_stats.rs
@@ -105,7 +105,7 @@ fn gen_large(root: &Path) {
make_dirs(&root.join("target"), 34_000, 50);
}
-/// Ground truth: total inotify watches held by this process (Linux).
+/// Kernel-side ground truth for the crate's own accounting; always 0 off Linux.
fn inotify_watches() -> usize {
#[cfg(target_os = "linux")]
{
diff --git a/crates/codegen/kigi-fsnotify/src/paths.rs b/crates/codegen/kigi-fsnotify/src/paths.rs
index 7fd97f1..45a374b 100644
--- a/crates/codegen/kigi-fsnotify/src/paths.rs
+++ b/crates/codegen/kigi-fsnotify/src/paths.rs
@@ -62,16 +62,13 @@ mod tests {
#[test]
fn classify_returns_none() {
let g = "/r/.git";
- // Excluded git internals.
assert_eq!(classify("/r/.git/COMMIT_EDITMSG", g), None);
assert_eq!(classify("/r/.git/MERGE_HEAD", g), None);
assert_eq!(classify("/r/.git/objects/ab/1234", g), None);
assert_eq!(classify("/r/.git/index.lock", g), None);
- // Workspace files.
assert_eq!(classify("/r/src/main.rs", g), None);
// Substring false-positive prevented by strip_prefix.
assert_eq!(classify("/r/.git-backup/HEAD", g), None);
- // Path under a different git_dir.
assert_eq!(classify("/other/.git/HEAD", g), None);
}
diff --git a/crates/codegen/kigi-fsnotify/src/source.rs b/crates/codegen/kigi-fsnotify/src/source.rs
index 8d797f5..e28f3ff 100644
--- a/crates/codegen/kigi-fsnotify/src/source.rs
+++ b/crates/codegen/kigi-fsnotify/src/source.rs
@@ -442,7 +442,7 @@ async fn event_loop(
) {
let mut state = LockState::Idle;
let mut stale_warn = StaleWarn::default();
- // Baseline for the next op's head_changed: the head last observed while
+ // Baseline for the next op's `head_changed`: the head last observed while
// no op was running. Fast ops complete their whole lock cycle inside one
// debounce batch, so the batch-time head is already post-op; this keeps
// the pre-op value.
@@ -539,16 +539,16 @@ fn process_event(
}
// Accepted race: if a settle expires while the next op's lock event is
// still in the debounce window, the baseline recorded here is already
- // that op's post-op head, so its Completed can read head_changed:false.
- // Self-healing: buffered FilesChanged force the consumer's rebuild, and
+ // that op's post-op head, so its Completed can read `head_changed`:false.
+ // Self-healing: buffered `FilesChanged` force the consumer's rebuild, and
// the hunk refresh has its own head_oid/index-mtime check.
if matches!(state, LockState::Idle | LockState::Cooldown { .. }) {
*last_idle_head = head_now;
}
- // While in_op: suppress GitMetaChanged (one wake on Completed, not N).
+ // While in_op: suppress `GitMetaChanged` (one wake on Completed, not N).
// Settling is in_op — the inter-cycle HEAD moves of a merged op must not
- // leak as meta wakes — but not in_cooldown: FilesChanged keeps flowing
+ // leak as meta wakes — but not in_cooldown: `FilesChanged` keeps flowing
// during Locked/Settling (consumer buffers); Cooldown drops it.
let in_op = matches!(
state,
@@ -1078,9 +1078,7 @@ mod tests {
);
}
- // ========================================================================
// Sapling (`.sl`) — the existing VCS-agnostic lock machine, fed `.sl` facts.
- // ========================================================================
/// Two distinct 20-byte working-copy parents (p1) for head-change tests.
const SL_P1_A: [u8; 20] = [0x11; 20];
@@ -1221,7 +1219,7 @@ mod tests {
assert!(!lock_present(&VcsDirs::default()));
// Degraded: a present `.sl` with an unreadable dirstate stays Some("|")
- // (head_changed:false path), not the no-repo None branch.
+ // (`head_changed`:false path), not the no-repo None branch.
let degraded = make_fake_sl_repo_no_lock();
std::fs::remove_file(degraded.path().join(".sl/dirstate")).unwrap();
assert_eq!(read_head(&sl_vcs(°raded)), Some("|".to_string()));
@@ -1282,7 +1280,7 @@ mod tests {
#[test]
fn workspace_file_surfaces_when_root_under_unrelated_sl_ancestor() {
// A watch root under an unrelated `.sl` ancestor must not suppress its
- // workspace files: a normal file still surfaces as FilesChanged.
+ // workspace files: a normal file still surfaces as `FilesChanged`.
let vcs = VcsDirs {
git_dir: None,
sl_dir: Some(PathBuf::from("/x/.sl/proj/.sl")),
@@ -1415,7 +1413,7 @@ mod tests {
cd(),
&out_tx,
);
- // Exact: only Started (no stray FilesChanged from the wlock path).
+ // Exact: only Started (no stray `FilesChanged` from the wlock path).
assert_eq!(collect_events(&mut rx), vec![FsEvent::GitOperationStarted]);
// p1 moves while wlock is held, then wlock is released; Completed
diff --git a/crates/codegen/kigi-fsnotify/src/state.rs b/crates/codegen/kigi-fsnotify/src/state.rs
index a40958e..edff176 100644
--- a/crates/codegen/kigi-fsnotify/src/state.rs
+++ b/crates/codegen/kigi-fsnotify/src/state.rs
@@ -226,7 +226,7 @@ mod tests {
}
/// Settle expiry emits exactly one Completed comparing the first pick's
- /// pre-op HEAD against the final HEAD (head_changed spans the merged op).
+ /// pre-op HEAD against the final HEAD (`head_changed` spans the merged op).
#[test]
fn settling_expiry_emits_completed_spanning_merged_op() {
let now = Instant::now();
@@ -300,7 +300,7 @@ mod tests {
}
/// Regression: timer-arm `drive()` must report Started so the consumer's
- /// `in_op` flag flips; otherwise FilesChanged events skip buffering.
+ /// `in_op` flag flips; otherwise `FilesChanged` events skip buffering.
#[test]
fn cooldown_to_locked_when_lock_reappears_at_timer_fire() {
let now = Instant::now();
diff --git a/crates/codegen/kigi-fsnotify/src/watcher.rs b/crates/codegen/kigi-fsnotify/src/watcher.rs
index 180553d..2e6c2bd 100644
--- a/crates/codegen/kigi-fsnotify/src/watcher.rs
+++ b/crates/codegen/kigi-fsnotify/src/watcher.rs
@@ -101,7 +101,7 @@ fn is_git_path_for_watcher(path: &Path) -> bool {
}
/// Sapling analogue of [`is_git_path_for_watcher`]: lets **only** `.sl/wlock`
-/// through. `.sl/dirstate` is intentionally not watched — it is read on demand,
+/// through. `.sl/dirstate` is deliberately not watched — it is read on demand,
/// because a read-only `sl status` rewrites dirstate without moving the parent,
/// so watching it would turn every status into a refresh storm. Forward-slash
/// only, like its git sibling.
@@ -466,7 +466,8 @@ fn scan_per_dir_updates(
let is_dir = p.symlink_metadata().is_ok_and(|m| m.file_type().is_dir());
if is_dir {
if structural {
- pruned.push(p.clone()); // Re-arm a possibly-dead watch.
+ // Re-arm a possibly-dead watch.
+ pruned.push(p.clone());
}
added.push(p.clone());
} else {
@@ -550,7 +551,8 @@ fn passes_custom_globs(
/// symlinks (so watches can't leave the workspace via a symlinked dir).
fn ignore_walker(root: &Path, max_depth: Option) -> ignore::Walk {
WalkBuilder::new(root)
- .hidden(false) // Let gitignore, not the leading dot, decide.
+ // Let gitignore, not the leading dot, decide.
+ .hidden(false)
.git_ignore(true)
.git_global(true)
.git_exclude(true)
@@ -594,7 +596,8 @@ fn select_top_level_watch_dirs_capped(
let mut dirs = Vec::new();
for entry in ignore_walker(root, Some(1)).flatten() {
if entry.depth() == 0 {
- continue; // `root` itself.
+ // `root` itself.
+ continue;
}
let path = entry.path();
if !entry.file_type().is_some_and(|ft| ft.is_dir()) {
@@ -607,7 +610,8 @@ fn select_top_level_watch_dirs_capped(
}
if passes_custom_globs(path, custom_ignore, custom_include) {
if dirs.len() == max {
- return None; // one past the cap
+ // one past the cap
+ return None;
}
dirs.push(path.to_path_buf());
}
@@ -626,7 +630,8 @@ fn pruning_walker(
) -> ignore::Walk {
let mut walker = WalkBuilder::new(root);
walker
- .hidden(false) // Let gitignore, not the leading dot, decide.
+ // Let gitignore, not the leading dot, decide.
+ .hidden(false)
.git_ignore(true)
.git_global(true)
.git_exclude(true)
@@ -636,11 +641,13 @@ fn pruning_walker(
let custom_include = custom_include.clone();
walker.filter_entry(move |entry| {
if !entry.file_type().is_some_and(|ft| ft.is_dir()) {
- return true; // Files pass here; callers filter them separately.
+ // Files pass here; callers filter them separately.
+ return true;
}
let path = entry.path();
if dir_named(path, ".git") || dir_named(path, ".sl") {
- return false; // VCS metadata is watched separately (or not at all).
+ // VCS metadata is watched separately (or not at all).
+ return false;
}
passes_custom_globs(path, &custom_ignore, &custom_include)
});
@@ -892,7 +899,8 @@ fn prune_subtree_watches(
.cloned()
.collect();
for dir in stale {
- let _ = debouncer.unwatch(&dir); // Usually already gone; errors expected.
+ // Usually already gone; errors expected.
+ let _ = debouncer.unwatch(&dir);
watched.remove(&dir);
}
}
@@ -1121,7 +1129,8 @@ pub(crate) fn start_with_timeout(
let (head, tail): (Vec, Vec) = dirs
.into_iter()
.partition(|d| d.parent() == Some(watch_path.as_path()));
- pending_dirs = tail.into(); // Still shallow-first.
+ // Still shallow-first.
+ pending_dirs = tail.into();
head
} else {
dirs
@@ -1420,11 +1429,9 @@ mod tests {
assert_eq!(map_event_kind(&EventKind::Other), None);
}
- // ========================================================================
// Integration tests with real filesystem and debouncer
// These tests are serialized because macOS FSEvents has limited resources
// when many watchers are created simultaneously.
- // ========================================================================
mod integration {
use super::*;
@@ -1706,7 +1713,8 @@ mod tests {
let watch_path = dunce::canonicalize(temp_dir.path()).unwrap();
let config = FsNotifyConfig {
- debounce_ms: 50, // Slightly longer debounce to batch events
+ // Slightly longer debounce to batch events
+ debounce_ms: 50,
ignore_patterns: vec![],
};
@@ -1873,7 +1881,8 @@ mod tests {
};
let (mut rx, handle) = start_with_retry(watch_path.clone(), config).unwrap();
- let _ = collect_events(&mut rx); // drain startup stragglers
+ // drain startup stragglers
+ let _ = collect_events(&mut rx);
// Drop joins the watcher thread, which drops the debouncer and the
// event sender. Run it on a watchdog thread so a broken Shutdown
@@ -1896,7 +1905,8 @@ mod tests {
let disconnected = loop {
match rx.try_recv() {
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => break true,
- Ok(_) => {} // drain any straggler before disconnect
+ // drain any straggler before disconnect
+ Ok(_) => {}
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
if std::time::Instant::now() >= deadline {
break false;
@@ -2015,7 +2025,8 @@ mod tests {
#[test]
#[serial]
- #[ignore] // Flaky on macOS due to recursive watcher behavior
+ // Flaky on macOS due to recursive watcher behavior
+ #[ignore]
fn test_debouncer_git_directory_ignored() {
// .git directory contents should always be ignored
let temp_dir = TempDir::new().unwrap();
@@ -2373,8 +2384,8 @@ mod tests {
);
}
- // ── per-dir strategy (Linux default; forced here so it runs on any
- // platform without process-global env races) ──────────────────────
+ // per-dir strategy (Linux default; forced here so it runs on any
+ // platform without process-global env races)
/// Watch-count accounting: nested gitignored dirs cost zero watches
/// and `.git` costs a handful, not one per internal dir.
@@ -2545,9 +2556,7 @@ mod tests {
}
}
- // ========================================================================
// Unit tests for merge_events and build_globsets
- // ========================================================================
mod merge_events_tests {
use super::*;
@@ -3517,7 +3526,8 @@ mod tests {
let temp = TempDir::new().unwrap();
let new_dir = temp.path().join("new");
fs::create_dir(&new_dir).unwrap();
- let old_dir = temp.path().join("old"); // never created — "moved away"
+ // never created — "moved away"
+ let old_dir = temp.path().join("old");
let mut pruned = Vec::new();
let mut added = Vec::new();
@@ -3663,7 +3673,8 @@ mod tests {
// A `.git` SYMLINK to an external (non-git) dir must NOT be followed
// and watched: the cheap dir branch is gated on a real (non-symlink)
// dir, and git validation rejects the target.
- let external = TempDir::new().unwrap(); // stands in for ~/.ssh, /etc
+ // stands in for ~/.ssh, /etc
+ let external = TempDir::new().unwrap();
let proj = TempDir::new().unwrap();
std::os::unix::fs::symlink(external.path(), proj.path().join(".git")).unwrap();
diff --git a/crates/codegen/kigi-fsnotify/tests/integration.rs b/crates/codegen/kigi-fsnotify/tests/integration.rs
index 50cfb64..28a138f 100644
--- a/crates/codegen/kigi-fsnotify/tests/integration.rs
+++ b/crates/codegen/kigi-fsnotify/tests/integration.rs
@@ -1,9 +1,8 @@
//! Integration tests using the public API only. Each test exercises the
//! real OS watcher against a `tempfile`-rooted fake git repo.
//!
-//! These can be flaky on some CI runners where FS events aren't reliably
-//! delivered (matches the existing pattern in `watcher.rs` integration
-//! tests). Marked `#[ignore]` for now; run locally with
+//! The watcher-driven tests are `#[ignore]`d because some CI runners do not
+//! reliably deliver FS events. Run them locally with
//! `cargo test --test integration -- --ignored`.
use std::fs;
@@ -224,8 +223,8 @@ async fn source_emits_completed_with_head_change_on_sl_goto() {
.await
.unwrap();
- // Move the working-copy parent (p1) before releasing wlock, then release.
- // `read_head` reads the new p1 on demand when the wlock-removal is processed.
+ // `read_head` reads p1 on demand when the wlock removal is processed, so
+ // p1 must move before the release for the change to be observed.
fs::write(temp.path().join(".sl/dirstate"), sl_dirstate(0x22)).unwrap();
fs::remove_file(&wlock).unwrap();
@@ -251,9 +250,7 @@ async fn shared_dedupes_by_directory() {
let temp = TempDir::new().unwrap();
let path = temp.path().to_path_buf();
- // First call creates the watcher; subsequent calls for the same canonical
- // directory hand back clones of the *same* source rather than opening a
- // new OS watch. Skip gracefully where the OS denies watches (CI limits).
+ // Skip gracefully where the OS denies watches (CI descriptor limits).
let Ok(a) = kigi_fsnotify::shared(path.clone(), FsConfig::default()) else {
eprintln!("skipping: OS watcher unavailable (resource limit?)");
return;
@@ -267,7 +264,6 @@ async fn shared_dedupes_by_directory() {
"second shared() must clone the existing source, not create a new one"
);
- // The reuse must be counted as a cache hit (no new OS watcher created).
let after = kigi_fsnotify::stats();
assert_eq!(
after.reused_total - before.reused_total,
@@ -280,7 +276,6 @@ async fn shared_dedupes_by_directory() {
);
assert!(after.live_watchers >= 1, "the shared watcher must be live");
- // A different directory gets its own independent watcher (a real miss).
let other = TempDir::new().unwrap();
let c = kigi_fsnotify::shared(other.path().to_path_buf(), FsConfig::default()).unwrap();
assert!(!Arc::ptr_eq(&a, &c), "different dirs must not share");
@@ -290,8 +285,7 @@ async fn shared_dedupes_by_directory() {
"a new directory must create a new watcher"
);
- // Once the last sharer drops, the registry entry is reclaimed and a later
- // request rebuilds a fresh source (exercises the recreate-after-drop path).
+ // Dropping the last sharer must reclaim the registry entry.
drop(a);
drop(b);
let d = kigi_fsnotify::shared(path, FsConfig::default()).unwrap();
@@ -340,7 +334,6 @@ async fn shared_watcher_scaling_demo() {
" before sharing this needed {SESSIONS} OS watchers; after sharing it needs {created}."
);
- // One real OS watch for the whole fleet; the rest are cache hits.
assert_eq!(created, 1, "all sessions on one cwd share a single watcher");
assert_eq!(reused, (SESSIONS - 1) as u64);
assert!(after.live_watchers >= 1);
diff --git a/crates/codegen/kigi-gix-status/src/lib.rs b/crates/codegen/kigi-gix-status/src/lib.rs
index 3e52eb6..d2ceae3 100644
--- a/crates/codegen/kigi-gix-status/src/lib.rs
+++ b/crates/codegen/kigi-gix-status/src/lib.rs
@@ -13,8 +13,8 @@ pub(crate) const OUTER_RESERVE: usize = 8;
const ENV_THREADS: &str = "KIGI_GIX_STATUS_THREADS";
-/// Pure produce-worker budget. Always `n >= 1`. Caps at 8; shrinks under tight
-/// soft nproc headroom (`headroom < 2` → 1).
+/// Produce-worker budget from explicit inputs, ignoring the env override.
+/// Always `>= 1`, since `Some(0)` means unlimited in gix.
pub fn compute_gix_status_thread_limit_from(
cores: usize,
soft_nproc: Option,
@@ -35,8 +35,8 @@ pub fn compute_gix_status_thread_limit_from(
limit.max(1)
}
-/// Production budget (`n >= 1`). Honours `KIGI_GIX_STATUS_THREADS=N` for `N >= 1`
-/// (forced dial; bypasses nproc). Else cores + soft nproc + thread usage.
+/// Production budget (always `>= 1`). `KIGI_GIX_STATUS_THREADS=N` forces `N`,
+/// bypassing the nproc headroom calculation entirely.
pub fn compute_gix_status_thread_limit() -> usize {
if let Ok(raw) = std::env::var(ENV_THREADS)
&& let Some(n) = parse_env_thread_override(&raw)
@@ -49,12 +49,11 @@ pub fn compute_gix_status_thread_limit() -> usize {
compute_gix_status_thread_limit_from(cores, soft_nproc_limit(), threads_used())
}
-/// `N >= 1` only; reject `0` and garbage.
fn parse_env_thread_override(raw: &str) -> Option {
raw.parse::().ok().filter(|&n| n >= 1)
}
-/// Test helper: `None` = uncapped, `Some(n)` with `n >= 1`. Never `Some(0)`.
+/// `None` leaves gix at its own default worker count.
fn apply_thread_limit<'repo, P>(
platform: gix::status::Platform<'repo, P>,
limit: Option,
@@ -71,7 +70,6 @@ where
})
}
-/// Apply [`compute_gix_status_thread_limit`] as `Some(n)` on the status platform.
pub fn with_budgeted_thread_limit<'repo, P>(
platform: gix::status::Platform<'repo, P>,
) -> gix::status::Platform<'repo, P>
@@ -125,7 +123,6 @@ fn threads_used() -> usize {
}
}
-/// Test scan: `Ok(true)` iff a dirty entry ending in `want_suffix` is seen.
/// Errors carry the gix `Debug` form so a `SpawnThread` failure stays
/// distinguishable from a genuinely missed dirty file.
#[cfg(test)]
@@ -351,7 +348,6 @@ mod nproc_tests {
Ok(())
}
- /// Child entry: tighten nproc, run status, exit 0 if survived with dirty found.
fn run_child(mode: &str, repo: &Path) -> ! {
let cores = std::thread::available_parallelism()
.map(usize::from)
@@ -474,8 +470,7 @@ mod nproc_tests {
.expect("spawn child")
}
- /// Parent-side boilerplate: guard re-entry, require 2+ cores, build the
- /// repo, run the child, and turn child-side skips into parent-side skips.
+ /// A child-side skip becomes a parent-side skip, reported as `None`.
fn spawn_child_or_skip(mode: &str) -> Option {
if std::env::var_os(CHILD_ENV).is_some() {
// Never fork further children from inside a child.
diff --git a/crates/codegen/kigi-hooks-plugins-types/src/lib.rs b/crates/codegen/kigi-hooks-plugins-types/src/lib.rs
index c47678b..6bc9ac3 100644
--- a/crates/codegen/kigi-hooks-plugins-types/src/lib.rs
+++ b/crates/codegen/kigi-hooks-plugins-types/src/lib.rs
@@ -10,9 +10,7 @@
use serde::{Deserialize, Serialize};
-// ---------------------------------------------------------------------------
// Enums
-// ---------------------------------------------------------------------------
/// Plugin scope.
///
@@ -177,9 +175,7 @@ pub enum OutcomeStatus {
Unsupported,
}
-// ---------------------------------------------------------------------------
// Hook types
-// ---------------------------------------------------------------------------
/// A single hook's metadata for display in the pager.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -189,7 +185,6 @@ pub struct HookInfo {
pub name: String,
/// Event type this hook runs on.
pub event: HookEvent,
- /// Handler type.
pub handler_type: HookHandlerType,
/// Raw matcher pattern from config (for display). None = matches all tools.
/// Maps from `HookSpec.configured_matcher` (not the compiled regex).
@@ -219,9 +214,7 @@ pub struct HooksListResponse {
pub load_errors: Vec,
}
-// ---------------------------------------------------------------------------
// Plugin types
-// ---------------------------------------------------------------------------
/// A single plugin's metadata for display in the pager.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -281,9 +274,7 @@ pub struct PluginsListResponse {
pub plugins: Vec,
}
-// ---------------------------------------------------------------------------
// MCP server types
-// ---------------------------------------------------------------------------
/// Source of an MCP server configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -339,9 +330,7 @@ pub struct McpServersListResponse {
pub servers: Vec,
}
-// ---------------------------------------------------------------------------
// Plugin component inventory (from marketplace catalogs)
-// ---------------------------------------------------------------------------
const MAX_COMPONENT_NAME_CHARS: usize = 120;
const MAX_COMPONENT_DESC_CHARS: usize = 120;
@@ -509,9 +498,7 @@ impl PluginComponents {
}
}
-// ---------------------------------------------------------------------------
// Action types
-// ---------------------------------------------------------------------------
/// Request wrapper for `kigi/hooks/action`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@@ -607,9 +594,7 @@ pub struct ActionOutcome {
pub requires_restart: bool,
}
-// ---------------------------------------------------------------------------
// Tests
-// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
@@ -1093,9 +1078,7 @@ mod tests {
}
}
-// ---------------------------------------------------------------------------
// Marketplace types (wire format for kigi/marketplace/* ACP endpoints)
-// ---------------------------------------------------------------------------
/// Response for `kigi/marketplace/list`.
#[derive(Debug, Clone, Serialize, Deserialize)]
diff --git a/crates/codegen/kigi-hooks/src/config.rs b/crates/codegen/kigi-hooks/src/config.rs
index 3b5466e..77fa83b 100644
--- a/crates/codegen/kigi-hooks/src/config.rs
+++ b/crates/codegen/kigi-hooks/src/config.rs
@@ -277,7 +277,8 @@ pub fn parse_hook_file(content: &str, file_path: &Path) -> (Vec, Vec v.clone(),
- None => return (specs, errors), // No hooks key — not an error, just no hooks.
+ // No hooks key — not an error, just no hooks.
+ None => return (specs, errors),
};
let hooks_map: HooksMap = match HooksMap::from_value(hooks_value) {
@@ -517,7 +518,8 @@ mod tests {
assert_eq!(s.event, HookEventName::PreToolUse);
assert!(s.matcher.is_some());
assert!(s.enabled);
- assert_eq!(s.timeout_ms, 2000); // 2 seconds → 2000 ms
+ // 2 seconds → 2000 ms
+ assert_eq!(s.timeout_ms, 2000);
assert_eq!(s.command, Some(PathBuf::from("bin/check.sh")));
}
@@ -554,7 +556,8 @@ mod tests {
}"#;
let (specs, errors) = parse_hook_file(json, Path::new("/tmp/test.json"));
assert!(errors.is_empty());
- assert!(specs[0].matcher.is_none()); // empty string → None → match all
+ // empty string → None → match all
+ assert!(specs[0].matcher.is_none());
}
#[test]
@@ -1072,7 +1075,7 @@ mod tests {
);
}
- /// `matcher` is intentionally NOT env-expanded. A
+ /// `matcher` is deliberately NOT env-expanded. A
/// matcher with `$VAR` must store the literal `$VAR` (anchored as
/// part of the regex by `HookMatcher::new`). A future contributor
/// adding "completeness" here would break regex semantics.
diff --git a/crates/codegen/kigi-hooks/src/discovery.rs b/crates/codegen/kigi-hooks/src/discovery.rs
index 561d38d..7c668cc 100644
--- a/crates/codegen/kigi-hooks/src/discovery.rs
+++ b/crates/codegen/kigi-hooks/src/discovery.rs
@@ -248,7 +248,8 @@ fn load_hooks_from_settings_file(path: &Path) -> (Vec, Vec)
Ok(c) => c,
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
- return (Vec::new(), Vec::new()); // Missing file is fine.
+ // Missing file is fine.
+ return (Vec::new(), Vec::new());
}
return (
Vec::new(),
@@ -406,7 +407,8 @@ mod tests {
#[test]
fn load_nonexistent_dir() {
let (registry, errors) = load_hooks(Some(Path::new("/nonexistent/path/hooks")), None);
- assert!(errors.is_empty()); // NotFound is silent
+ // NotFound is silent
+ assert!(errors.is_empty());
assert!(registry.is_empty());
}
@@ -613,10 +615,11 @@ mod tests {
let toml = dir.path().join("hooks.toml");
std::fs::write(&toml, "").unwrap();
- assert!(!is_valid_hook_file(&toml)); // TOML no longer accepted
+ // TOML no longer accepted
+ assert!(!is_valid_hook_file(&toml));
}
- // ── Settings file discovery tests ────────────────────────────
+ // Settings file discovery tests
#[test]
fn load_from_settings_file() {
@@ -642,7 +645,8 @@ mod tests {
))],
&[],
);
- assert!(errors.is_empty()); // Missing file is fine, not an error.
+ // Missing file is fine, not an error.
+ assert!(errors.is_empty());
assert!(registry.is_empty());
}
diff --git a/crates/codegen/kigi-hooks/src/dispatcher.rs b/crates/codegen/kigi-hooks/src/dispatcher.rs
index d8b1e26..26cf68b 100644
--- a/crates/codegen/kigi-hooks/src/dispatcher.rs
+++ b/crates/codegen/kigi-hooks/src/dispatcher.rs
@@ -412,7 +412,7 @@ mod tests {
registry
}
- // ── extract_tool_name tests ──────────────────────────────────
+ // extract_tool_name tests
#[test]
fn extract_tool_name_from_pre_tool_use() {
@@ -453,7 +453,7 @@ mod tests {
);
}
- // ── dispatch_pre_tool_use tests ──────────────────────────────
+ // dispatch_pre_tool_use tests
#[tokio::test]
async fn empty_registry_allows() {
@@ -501,7 +501,8 @@ mod tests {
let spec = make_command_spec(
"disabled-deny",
None,
- false, // disabled!
+ // disabled!
+ false,
"echo '{\"decision\":\"deny\",\"reason\":\"should not run\"}'; exit 2",
);
let registry = registry_from_specs(vec![spec]);
@@ -726,7 +727,7 @@ mod tests {
assert_eq!(result.decision, HookDecision::Allow);
}
- // ── fail-open regression tests ───────────────────────────────
+ // fail-open regression tests
#[tokio::test]
async fn fail_open_records_error_in_run_results() {
@@ -757,7 +758,7 @@ mod tests {
}
}
- // ── dispatch_non_blocking tests ──────────────────────────────
+ // dispatch_non_blocking tests
#[tokio::test]
async fn non_blocking_empty_registry() {
@@ -827,7 +828,7 @@ mod tests {
assert!(matches!(results[1], HookRunResult::Success { .. }));
}
- // ── hub_hook_kind tests ──────────────────────────────────────
+ // hub_hook_kind tests
#[test]
fn hub_hook_kind_returns_none_for_pre_tool_use() {
@@ -878,7 +879,8 @@ mod tests {
}
};
assert_eq!(
- cases.len() + 1, // +1 for PreToolUse (blocking, tested separately)
+ // +1 for PreToolUse (blocking, tested separately)
+ cases.len() + 1,
total_variants(HookEventName::SessionStart),
"update hub_hook_kind test when new HookEventName variants are added"
);
diff --git a/crates/codegen/kigi-hooks/src/env_expand.rs b/crates/codegen/kigi-hooks/src/env_expand.rs
index a353303..f95ac5d 100644
--- a/crates/codegen/kigi-hooks/src/env_expand.rs
+++ b/crates/codegen/kigi-hooks/src/env_expand.rs
@@ -17,7 +17,7 @@
//!
//! * config-load-time expansion is idempotent (re-running it on an already
//! expanded string is a no-op),
-//! * vars that are intentionally deferred to runtime (set later by the
+//! * vars that are deliberately deferred to runtime (set later by the
//! shell, the dispatcher, or `extra_env`) survive the load-time pass and
//! are caught by the runtime pre-flight check in
//! [`crate::runner::command`] if they remain unset at execution, and
@@ -111,7 +111,7 @@ fn make_sentinel() -> String {
///
/// Unresolved references are preserved verbatim so this function is safe
/// to call repeatedly (idempotent on already-expanded strings) and so
-/// references that are intentionally resolved at runtime (e.g. by the
+/// references that are deliberately resolved at runtime (e.g. by the
/// dispatcher's always-set `KIGI_HOOK_*` vars) survive the load-time pass.
///
/// Parameter-expansion-modifier forms (`${VAR:-x}`, `${VAR%pat}`, etc.)
@@ -126,7 +126,7 @@ pub(crate) fn expand_env_vars_with_extra(input: &str, extra: &HashMap,
},
- // ── Subagent events ─────────────────────────────────────────
+ // Subagent events
/// Fires when a subagent is spawned.
SubagentStart {
#[serde(rename = "subagentId")]
@@ -308,7 +308,7 @@ pub enum HookPayload {
duration_ms: Option,
},
- // ── Compaction events ───────────────────────────────────────
+ // Compaction events
PreCompact {
/// "manual" or "auto".
source: String,
@@ -407,7 +407,8 @@ mod tests {
(HookEventName::PermissionDenied, "permission_denied"),
(HookEventName::SubagentStart, "subagent_start"),
(HookEventName::SubagentStop, "subagent_stop"),
- (HookEventName::SubagentEnd, "subagent_stop"), // alias collapses
+ // alias collapses
+ (HookEventName::SubagentEnd, "subagent_stop"),
(HookEventName::PreCompact, "pre_compact"),
(HookEventName::PostCompact, "post_compact"),
];
diff --git a/crates/codegen/kigi-hooks/src/lib.rs b/crates/codegen/kigi-hooks/src/lib.rs
index 0724ede..d42ca2d 100644
--- a/crates/codegen/kigi-hooks/src/lib.rs
+++ b/crates/codegen/kigi-hooks/src/lib.rs
@@ -1,13 +1,9 @@
-//! # kigi-hooks
-//!
//! Runtime hook system for Kigi — file-based discovery, command execution,
//! and policy enforcement.
//!
-//! ## Overview
-//!
-//! This crate provides a minimal hooks system for Kigi. Hooks are discovered
-//! from dedicated directories (`~/.kigi/hooks/` and `/.kigi/hooks/`),
-//! defined in JSON files (compatible settings format), and executed as child processes.
+//! Hooks are discovered from dedicated directories (`~/.kigi/hooks/` and
+//! `/.kigi/hooks/`), defined in JSON files (compatible
+//! settings format), and executed as child processes.
//!
//! ## v0 scope
//!
diff --git a/crates/codegen/kigi-hooks/src/matcher.rs b/crates/codegen/kigi-hooks/src/matcher.rs
index 0f6d221..b07d744 100644
--- a/crates/codegen/kigi-hooks/src/matcher.rs
+++ b/crates/codegen/kigi-hooks/src/matcher.rs
@@ -2,7 +2,7 @@ use kigi_tools::types::{claude_names_for, kigi_names_for};
use regex::Regex;
/// A compiled hook matcher for tool names. The pattern semantics are chosen so that
-/// `matcher` entries in hooks migrated from other agent CLIs keep firing unchanged:
+/// `matcher` entries in hooks migrated from other agent CLIs keep firing `unchanged`:
///
/// - an empty pattern or `"*"` matches every tool;
/// - a "simple" pattern (only `[A-Za-z0-9_|]`, i.e. a plain name or `|`-list) is an
@@ -117,7 +117,8 @@ mod tests {
// Contains regex metachars -> regex mode, unanchored.
let m = HookMatcher::new("run_.*").unwrap();
assert!(m.is_match("run_terminal_command"));
- assert!(m.is_match("xrun_yyy")); // unanchored: substring match
+ // unanchored: substring match
+ assert!(m.is_match("xrun_yyy"));
assert!(!m.is_match("read_file"));
}
@@ -152,13 +153,14 @@ mod tests {
assert!(!m.is_match("run_terminal_command"));
}
- // ── External tool-name aliases ────────────────────────────────
+ // External tool-name aliases
#[test]
fn claude_bash_matches_kigi_tool() {
let m = HookMatcher::new("Bash").unwrap();
- assert!(m.is_match("Bash")); // external alias name
- assert!(m.is_match("run_terminal_command")); // Kigi name
+ // external alias name
+ assert!(m.is_match("Bash"));
+ assert!(m.is_match("run_terminal_command"));
assert!(!m.is_match("read_file"));
// Bug-fix regression: exact, not prefix.
assert!(!m.is_match("run_terminal_command_v2"));
@@ -169,8 +171,10 @@ mod tests {
let m = HookMatcher::new("Edit|Write").unwrap();
assert!(m.is_match("Edit"));
assert!(m.is_match("Write"));
- assert!(m.is_match("search_replace")); // Kigi equivalent
- assert!(m.is_match("hashline_edit")); // second Kigi alias
+ // Kigi equivalent
+ assert!(m.is_match("search_replace"));
+ // second Kigi alias
+ assert!(m.is_match("hashline_edit"));
assert!(!m.is_match("read_file"));
// The old anchoring bug matched these; the exact-list mode must not.
assert!(!m.is_match("Editorial"));
diff --git a/crates/codegen/kigi-hooks/src/result.rs b/crates/codegen/kigi-hooks/src/result.rs
index 9932d19..c892fd3 100644
--- a/crates/codegen/kigi-hooks/src/result.rs
+++ b/crates/codegen/kigi-hooks/src/result.rs
@@ -11,9 +11,7 @@ pub enum HookDecision {
/// HTTP-specific execution details for scrollback enrichment.
///
-/// Populated only for `"http"` handler type hooks. Carries the target
-/// URL, HTTP status, and a short preview of the response body so that
-/// scrollback annotations can display them.
+/// Populated only for `"http"` handler type hooks.
#[derive(Debug, Clone)]
pub struct HttpInfo {
/// The URL that was POSTed to.
@@ -39,8 +37,7 @@ pub struct HttpInfo {
///
/// [`url`]: HttpInfo::url
pub raw_url: Option,
- /// HTTP status code (e.g. 200, 500). `None` if the request never
- /// completed (timeout, connection error).
+ /// `None` if the request never completed (timeout, connection error).
pub status: Option,
/// Short preview of the response body (truncated to ~200 chars).
/// `None` if no body was read (e.g. non-blocking hooks, timeouts).
@@ -50,11 +47,9 @@ pub struct HttpInfo {
/// The outcome of a single hook execution.
#[derive(Debug)]
pub enum HookRunResult {
- /// Hook executed successfully.
Success {
hook_name: String,
elapsed: Duration,
- /// HTTP details, populated only for `"http"` handler type hooks.
http_info: Option,
},
/// Hook was skipped because it is disabled.
@@ -64,7 +59,6 @@ pub enum HookRunResult {
hook_name: String,
error: String,
elapsed: Duration,
- /// HTTP details, populated only for `"http"` handler type hooks.
http_info: Option,
},
}
diff --git a/crates/codegen/kigi-hooks/src/runner/command.rs b/crates/codegen/kigi-hooks/src/runner/command.rs
index e8915ea..ff474b6 100644
--- a/crates/codegen/kigi-hooks/src/runner/command.rs
+++ b/crates/codegen/kigi-hooks/src/runner/command.rs
@@ -146,7 +146,7 @@ pub async fn run_command_hook(
// cannot open /dev/tty and corrupt the TUI display. Delegates to
// `kigi_tools::util::detach_command`: Unix uses the same setsid /
// EPERM→setpgid pre_exec path as before; Windows sets CREATE_NO_WINDOW only
- // (DETACHED_PROCESS is intentionally omitted — it breaks stdio inheritance).
+ // (DETACHED_PROCESS is deliberately omitted — it breaks stdio inheritance).
kigi_tools::util::detach_command(&mut cmd);
// Spawn the child process.
@@ -649,7 +649,8 @@ mod tests {
let large = vec![b'x'; MAX_OUTPUT_BYTES + 1000];
let result = truncate_output(&large);
assert!(result.ends_with(" [truncated]"));
- assert!(result.len() > MAX_OUTPUT_BYTES); // marker appended
+ // marker appended
+ assert!(result.len() > MAX_OUTPUT_BYTES);
}
#[test]
@@ -833,11 +834,12 @@ mod tests {
#[test]
fn shell_command_detection() {
// Commands with shell metacharacters should be detected.
- assert!("echo hello".contains(' ')); // space
- assert!("a || b".contains('|')); // pipe/or
- assert!("a && b".contains('&')); // and
- assert!("a; b".contains(';')); // semicolon
- assert!("a > out".contains('>')); // redirect
+ assert!("echo hello".contains(' '));
+ // pipe/or
+ assert!("a || b".contains('|'));
+ assert!("a && b".contains('&'));
+ assert!("a; b".contains(';'));
+ assert!("a > out".contains('>'));
// Env-var interpolation must also force the sh -c branch so that
// commands like `${CLAUDE_PLUGIN_ROOT}/hooks/foo.sh` get expanded
// by the shell rather than treated as a literal executable path.
@@ -860,7 +862,7 @@ mod tests {
/// Regression: a hook command that uses `${VAR}` interpolation
/// without any other shell metacharacters must still be invoked via
/// `sh -c` so that the env var supplied via `extra_env` is expanded.
- /// Previously the runner treated `${...}` as part of a literal path
+ /// earlier the runner treated `${...}` as part of a literal path
/// and `command_path.exists()` failed; the hook silently never ran.
/// Now the env-var pre-spawn check refuses with a clear reason when
/// the var is unset (and the dispatcher fail-opens, so the tool call
@@ -1104,7 +1106,7 @@ mod tests {
#[tokio::test]
async fn test_undefined_env_var_refuses_to_spawn() {
let mut extra_env = std::collections::HashMap::new();
- // Intentionally do NOT set NEVER_SET_GB1183 anywhere.
+ // Deliberately do NOT set NEVER_SET_GB1183 anywhere.
extra_env.insert("UNRELATED_GB1183".to_string(), "/tmp".to_string());
let spec = HookSpec {
@@ -1150,7 +1152,7 @@ mod tests {
/// Regression: a hook command starting with `~` must be
/// routed through `sh -c` so the shell expands `~` to `$HOME`.
- /// Previously `~/.claude/hook.sh` was treated as a relative path and
+ /// earlier `~/.claude/hook.sh` was treated as a relative path and
/// joined to `source_dir`, producing a broken path.
///
/// The test injects `HOME` via `extra_env` so it works in sandboxed
@@ -1245,7 +1247,7 @@ mod tests {
configured_matcher: None,
matcher: None,
enabled: true,
- // `MISSING_GB1183_DEFAULT` is intentionally unset; the `:-`
+ // `MISSING_GB1183_DEFAULT` is deliberately unset; the `:-`
// modifier supplies a fallback that points at the real script.
command: Some(std::path::PathBuf::from(format!(
"${{MISSING_GB1183_DEFAULT:-{}}}",
diff --git a/crates/codegen/kigi-hooks/src/runner/http.rs b/crates/codegen/kigi-hooks/src/runner/http.rs
index dbbc8cd..2aef2d3 100644
--- a/crates/codegen/kigi-hooks/src/runner/http.rs
+++ b/crates/codegen/kigi-hooks/src/runner/http.rs
@@ -35,44 +35,55 @@ fn is_blocked_ip(ip: &IpAddr) -> bool {
IpAddr::V4(v4) => {
let octets = v4.octets();
if octets[0] == 127 {
- return false; // loopback — allowed for local dev
+ // loopback — allowed for local dev
+ return false;
}
if octets[0] == 10 {
- return true; // RFC 1918: 10.0.0.0/8
+ // RFC 1918: 10.0.0.0/8
+ return true;
}
if octets[0] == 172 && (16..=31).contains(&octets[1]) {
- return true; // RFC 1918: 172.16.0.0/12
+ // RFC 1918: 172.16.0.0/12
+ return true;
}
if octets[0] == 192 && octets[1] == 168 {
- return true; // RFC 1918: 192.168.0.0/16
+ // RFC 1918: 192.168.0.0/16
+ return true;
}
if octets[0] == 169 && octets[1] == 254 {
- return true; // RFC 3927: 169.254.0.0/16 (link-local, cloud metadata)
+ // RFC 3927: 169.254.0.0/16 (link-local, cloud metadata)
+ return true;
}
if octets[0] == 100 && (64..=127).contains(&octets[1]) {
- return true; // RFC 6598: 100.64.0.0/10 (CGNAT)
+ // RFC 6598: 100.64.0.0/10 (CGNAT)
+ return true;
}
if v4.is_unspecified() {
- return true; // 0.0.0.0
+ // 0.0.0.0
+ return true;
}
false
}
IpAddr::V6(v6) => {
if v6.is_loopback() {
- return false; // ::1 — allowed for local dev
+ // ::1 — allowed for local dev
+ return false;
}
if v6.is_unspecified() {
- return true; // ::
+ // ::
+ return true;
}
if let Some(v4) = v6.to_ipv4_mapped() {
return is_blocked_ip(&IpAddr::V4(v4));
}
let segments = v6.segments();
if segments[0] & 0xffc0 == 0xfe80 {
- return true; // fe80::/10 — link-local
+ // fe80::/10 — link-local
+ return true;
}
if segments[0] & 0xfe00 == 0xfc00 {
- return true; // fc00::/7 — unique local (ULA)
+ // fc00::/7 — unique local (ULA)
+ return true;
}
false
}
@@ -398,7 +409,7 @@ mod tests {
use super::*;
use reqwest::StatusCode;
- // ── parse_http_blocking_result tests ──────────────────────────
+ // parse_http_blocking_result tests
#[test]
fn http_allow_json() {
@@ -560,7 +571,7 @@ mod tests {
}
}
- // ── SSRF protection: is_blocked_ip tests ──────────────
+ // SSRF protection: is_blocked_ip tests
#[test]
fn ssrf_blocks_rfc1918_10x() {
@@ -632,7 +643,7 @@ mod tests {
));
}
- // ── SSRF protection: validate_hook_url tests ──────────
+ // SSRF protection: validate_hook_url tests
#[tokio::test]
async fn ssrf_rejects_http_scheme() {
@@ -675,7 +686,7 @@ mod tests {
assert!(result.unwrap_err().contains("invalid URL"));
}
- // ── URL env-var expansion (extra_env precedence) ───────────
+ // URL env-var expansion (extra_env precedence)
use crate::config::HookSpec;
use crate::event::{HookEventEnvelope, HookEventName, HookPayload};
@@ -883,7 +894,7 @@ mod tests {
let (result, _, info) = run_http_hook(&spec, &envelope, &ctx, true).await;
// Either `Failed` (timeout / connection error) is fine; both
- // exercise paths that previously embedded the raw URL via
+ // exercise paths that earlier embedded the raw URL via
// `format!("...{e}")`. Pure timeouts use a different
// formatting branch (no URL involved), so prefer the
// connection-error case but tolerate either.
diff --git a/crates/codegen/kigi-hooks/src/runner/mod.rs b/crates/codegen/kigi-hooks/src/runner/mod.rs
index 306f2cb..dce2565 100644
--- a/crates/codegen/kigi-hooks/src/runner/mod.rs
+++ b/crates/codegen/kigi-hooks/src/runner/mod.rs
@@ -7,32 +7,23 @@ use crate::config::HookSpec;
use crate::event::HookEventEnvelope;
use crate::result::{HookDecision, HttpInfo};
-/// Context passed to any hook runner for environment setup.
pub struct RunContext<'a> {
pub session_id: &'a str,
pub workspace_root: &'a str,
}
-/// Result of running a single hook (any handler type).
#[derive(Debug)]
pub enum HookRunnerResult {
- /// Hook ran and produced a decision (for blocking hooks).
Decision(HookDecision),
- /// Hook ran successfully (for non-blocking hooks).
Success,
- /// Hook failed — caller should fail-open.
+ /// Callers must fail open on this variant: a broken hook never blocks the
+ /// session.
Failed(String),
}
-/// Bundle returned by each runner: the result, wall-clock duration, and
-/// optional HTTP metadata for enriched scrollback logging.
+/// Result, wall-clock duration, and HTTP metadata for scrollback enrichment.
pub type HookRunOutput = (HookRunnerResult, Duration, Option);
-/// Run a hook using the appropriate handler for its type.
-///
-/// Dispatches to `command::run_command_hook()` or `http::run_http_hook()`
-/// based on `spec.handler_type`. Returns the result, elapsed duration, and
-/// optional HTTP metadata for scrollback enrichment.
pub async fn run_hook(
spec: &HookSpec,
envelope: &HookEventEnvelope,
diff --git a/crates/codegen/kigi-hooks/src/test_support.rs b/crates/codegen/kigi-hooks/src/test_support.rs
index 172e535..13c63cd 100644
--- a/crates/codegen/kigi-hooks/src/test_support.rs
+++ b/crates/codegen/kigi-hooks/src/test_support.rs
@@ -1,32 +1,18 @@
-//! Test-only helpers shared across `kigi-hooks` unit + integration tests.
-//!
-//! This module is gated on `#[cfg(test)]` and is exported as `pub(crate)`
-//! so any in-crate `#[cfg(test)] mod tests` can use it. Integration tests
-//! under `tests/` cannot reach it; for those, copy or re-implement the
-//! handful of functions here that they need (the only one currently used
-//! by integration tests is unrelated).
+//! Test-only helpers for `kigi-hooks`. Gated on `#[cfg(test)]`, so integration
+//! tests under `tests/` cannot reach it — they must re-implement what they need.
use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
/// Run `f` with the env var `name` set to `value` (or unset if `value`
/// is `None`), restoring the previous value on return.
///
-/// Uses `catch_unwind` so a panic inside `f` does not leak the env var
-/// into the rest of the test process.
+/// The save -> set -> run -> restore lifecycle is panic-safe but not race-safe:
+/// `cargo test` runs tests in parallel and env vars are process-global, so
+/// callers must pick uniquely-named vars.
///
-/// `cargo test` runs tests in parallel by default. Process env vars are
-/// process-global, so callers should pick uniquely-named vars to avoid
-/// inter-test races. The lifecycle here (save -> set -> run -> restore)
-/// is panic-safe but not race-safe.
-///
-/// **FOLLOW-UP**: the helper does not
-/// enforce the unique-name discipline -- a future contributor passing
-/// a common name like `HOME` could trigger flaky tests. The standard
-/// fix is to add `serial_test` as a dev-dep and decorate every
-/// env-touching test with `#[serial(env_var)]` so the test runner
-/// serialises them. For now the unique-name
-/// convention plus `catch_unwind` restoration is sufficient for the
-/// tests that ship today.
+/// FIXME: nothing enforces the unique-name discipline; a caller passing `HOME`
+/// would produce flaky tests. The fix is a `serial_test` dev-dep plus
+/// `#[serial(env_var)]` on every env-touching test.
pub(crate) fn with_env_var(name: &str, value: Option<&str>, f: impl FnOnce() -> R) -> R {
let previous = std::env::var_os(name);
// SAFETY: env-var writes are not thread-safe. Callers use uniquely
@@ -74,7 +60,7 @@ mod tests {
#[test]
fn restores_previous_unset_state_on_normal_return() {
let key = "KIGI_HOOKS_TEST_SUPPORT_UNSET_RESTORE";
- // SAFETY: see module-level note.
+ // SAFETY: see the thread-safety note on `with_env_var`.
unsafe {
std::env::remove_var(key);
}
@@ -87,7 +73,7 @@ mod tests {
#[test]
fn restores_after_panic() {
let key = "KIGI_HOOKS_TEST_SUPPORT_PANIC_RESTORE";
- // SAFETY: see module-level note.
+ // SAFETY: see the thread-safety note on `with_env_var`.
unsafe {
std::env::remove_var(key);
}
@@ -106,7 +92,7 @@ mod tests {
#[test]
fn allows_explicit_unset() {
let key = "KIGI_HOOKS_TEST_SUPPORT_EXPLICIT_UNSET";
- // SAFETY: see module-level note.
+ // SAFETY: see the thread-safety note on `with_env_var`.
unsafe {
std::env::set_var(key, "before");
}
@@ -114,7 +100,7 @@ mod tests {
assert!(std::env::var(key).is_err());
});
assert_eq!(std::env::var(key).unwrap(), "before");
- // SAFETY: see module-level note.
+ // SAFETY: see the thread-safety note on `with_env_var`.
unsafe {
std::env::remove_var(key);
}
diff --git a/crates/codegen/kigi-hooks/src/trust.rs b/crates/codegen/kigi-hooks/src/trust.rs
index 3c999a6..87bf3be 100644
--- a/crates/codegen/kigi-hooks/src/trust.rs
+++ b/crates/codegen/kigi-hooks/src/trust.rs
@@ -34,7 +34,7 @@ pub fn list_trusted_projects_with_file(trust_file: &Path) -> std::io::Result Result<(), String> {
fn disable_hook_with_file(hook_name: &str, file: &Path) -> Result<(), String> {
if is_hook_disabled_with_file(hook_name, file) {
- return Ok(()); // Already disabled.
+ // Already disabled.
+ return Ok(());
}
if let Some(parent) = file.parent() {
let _ = std::fs::create_dir_all(parent);
diff --git a/crates/codegen/kigi-hooks/tests/integration.rs b/crates/codegen/kigi-hooks/tests/integration.rs
index 68acd42..19c7271 100644
--- a/crates/codegen/kigi-hooks/tests/integration.rs
+++ b/crates/codegen/kigi-hooks/tests/integration.rs
@@ -382,7 +382,8 @@ async fn hook_receives_env_vars() {
// Verify env vars were received.
let output = std::fs::read_to_string(&output_file).unwrap();
assert!(output.contains("EVENT=pre_tool_use"), "output: {output}");
- assert!(output.contains("NAME="), "output: {output}"); // auto-generated name
+ // auto-generated name
+ assert!(output.contains("NAME="), "output: {output}");
assert!(output.contains("SESSION=sess-456"), "output: {output}");
}
diff --git a/crates/codegen/kigi-http/src/lib.rs b/crates/codegen/kigi-http/src/lib.rs
index d782b81..6823284 100644
--- a/crates/codegen/kigi-http/src/lib.rs
+++ b/crates/codegen/kigi-http/src/lib.rs
@@ -56,7 +56,7 @@ static CLIENT_TYPE: OnceLock = OnceLock::new();
// `OriginClientInfo` is owned by `kigi-sampler` so `SamplerConfig` can use
// it without taking a circular dependency on `kigi-shell`. Re-exported
// under the same path (`crate::http::OriginClientInfo`) so existing call-sites
-// compile unchanged. The telemetry engine in `kigi-telemetry` consumes
+// compile `unchanged`. The telemetry engine in `kigi-telemetry` consumes
// the same type via `kigi_sampler::OriginClientInfo`. The shell-specific
// constructors that depended on `ClientType` (a shell-only type) are free
// functions below.
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/actions.rs b/crates/codegen/kigi-hunk-tracker/src/actor/actions.rs
index b95b685..c95f236 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/actions.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/actions.rs
@@ -84,7 +84,7 @@ impl HunkTrackerActor {
let accepted = matches!(action, HunkAction::Accept);
self.update_session_stats(&hunk.line_info, accepted);
- // Remove from turn_index
+ // Drop from turn_index
self.remove_from_turn_index(hunk_id, &hunk.source);
match action {
@@ -383,7 +383,7 @@ impl HunkTrackerActor {
let accepted = matches!(action, HunkAction::Accept);
self.update_session_stats(&hunk.line_info, accepted);
- // Remove from turn_index
+ // Drop from turn_index
self.remove_from_turn_index(&hunk.id, &hunk.source);
affected_hunk_ids.push(hunk.id.clone());
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/file_utils.rs b/crates/codegen/kigi-hunk-tracker/src/actor/file_utils.rs
index eac502a..eea433f 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/file_utils.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/file_utils.rs
@@ -190,7 +190,7 @@ mod tests {
assert!(!is_binary(empty));
}
- // === TooLarge / bounded read tests (SF-2) ===
+ // TooLarge / bounded read tests (SF-2)
#[test]
fn test_classify_bytes_too_large() {
@@ -237,7 +237,7 @@ mod tests {
);
}
- // === LFS pointer tests ===
+ // LFS pointer tests
#[test]
fn test_is_lfs_pointer_valid() {
@@ -310,7 +310,8 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("huge_binary.bin");
let mut data = vec![0xFFu8; MAX_TRACKED_TEXT_BYTES * 10];
- data[50] = 0; // null byte in prefix
+ // null byte in prefix
+ data[50] = 0;
std::fs::write(&path, &data).unwrap();
let state = read_file_bounded(&path).await;
// Size > limit means TooLarge (bounded read guarantee - no full allocation)
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/git.rs b/crates/codegen/kigi-hunk-tracker/src/actor/git.rs
index 6afb580..8c1365b 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/git.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/git.rs
@@ -415,7 +415,8 @@ impl HunkTrackerActor {
}
result.content
}
- Err(_) => missing_content(), // spawn_blocking was cancelled or panicked
+ // spawn_blocking was cancelled or panicked
+ Err(_) => missing_content(),
}
}
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/hunks.rs b/crates/codegen/kigi-hunk-tracker/src/actor/hunks.rs
index f5986ed..33f9e62 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/hunks.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/hunks.rs
@@ -97,7 +97,8 @@ impl HunkTrackerActor {
if let Some(best_match) = find_matching_old_hunk(new_hunk, &old_hunks) {
// Skip if this old hunk was already claimed by another new hunk
if claimed_old_ids.contains(&best_match.id) {
- continue; // new_hunk keeps its new ID
+ // new_hunk keeps its new ID
+ continue;
}
claimed_old_ids.insert(best_match.id.clone());
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/mod.rs b/crates/codegen/kigi-hunk-tracker/src/actor/mod.rs
index 7e2c63a..972091c 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/mod.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/mod.rs
@@ -567,7 +567,7 @@ impl HunkTrackerActor {
}
}
- /// Restore a previously snapshotted state, replacing all current file
+ /// Restore a earlier snapshotted state, replacing all current file
/// states, turn index, and session stats.
/// Preserves the full FileContentState (including Binary/TooLarge).
fn restore_snapshot(&mut self, snapshot: HunkTrackerSnapshot) {
@@ -589,7 +589,7 @@ impl HunkTrackerActor {
self.turn_index = snapshot.turn_index;
self.session_stats = snapshot.session_stats;
- // TODO: Re-emit HunkEvent::FileAdded / HunkEvent::HunkAdded for
+ // TODO: Re-emit HunkEvent::`FileAdded` / HunkEvent::`HunkAdded` for
// all restored files and hunks so that connected clients (TUI, VSCode
// extension) see the restored state without requiring a manual refresh.
// Alternative: emit a single HunkEvent::StateRestored { file_count }
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/mutations.rs b/crates/codegen/kigi-hunk-tracker/src/actor/mutations.rs
index 80a74f8..b07375d 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/mutations.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/mutations.rs
@@ -29,7 +29,7 @@ use super::state::{FileContentState, FileHunkState};
/// of truth for the string.
pub const REFRESH_SCAN_LOG_PREFIX: &str = "refresh_all_baselines: completed in";
-/// Log-line prefix for the unchanged-git-state skip path of
+/// Log-line prefix for the `unchanged`-git-state skip path of
/// [`HunkTrackerActor::refresh_all_baselines`] (no scan ran).
pub const REFRESH_SKIP_LOG_PREFIX: &str = "refresh_all_baselines: git state unchanged";
@@ -37,7 +37,7 @@ pub const REFRESH_SKIP_LOG_PREFIX: &str = "refresh_all_baselines: git state unch
///
/// Git-stored content typically has exactly one trailing newline appended.
/// We strip only one to avoid falsely treating files with meaningful trailing
-/// whitespace as clean. Bare `\r` (classic Mac) is intentionally out of scope.
+/// whitespace as clean. Bare `\r` (classic Mac) is deliberately out of scope.
fn strip_single_trailing_newline(content: &str) -> &str {
content
.strip_suffix("\r\n")
@@ -67,7 +67,8 @@ impl HunkTrackerActor {
// Classify current content into FileContentState (single classification, cloned for file_states)
let current_state = classify_string(content.clone());
- let current_state_for_hunks = current_state.clone(); // Used by recompute_hunks below
+ // Used by recompute_hunks below
+ let current_state_for_hunks = current_state.clone();
// Binary or TooLarge content: still track as an agent file (so
// `get_all_tracked_paths` reports it for worktree replication)
@@ -132,7 +133,7 @@ impl HunkTrackerActor {
},
);
- // Emit FileAdded event
+ // Emit `FileAdded` event
self.send_event(HunkEvent::FileAdded {
path: path.clone(),
is_agent_file: true,
@@ -277,7 +278,8 @@ impl HunkTrackerActor {
path.clone(),
FileHunkState {
baseline,
- current_content: missing_content(), // Will be set by recompute_hunks
+ // Will be set by recompute_hunks
+ current_content: missing_content(),
hunks: vec![],
is_agent_file: false,
baseline_accepted: false,
@@ -311,7 +313,7 @@ impl HunkTrackerActor {
(FileContentState::Symlink, FileContentState::Symlink) => true,
// Symlink on disk vs Full(target) in HEAD (or vice versa):
// git stores symlinks as plain text blobs, so the types
- // differ even when the file is unchanged. Consult dirty cache.
+ // differ even when the file is `unchanged`. Consult dirty cache.
(FileContentState::Symlink, FileContentState::Full(_))
| (FileContentState::Full(_), FileContentState::Symlink) => {
let rel = path.strip_prefix(&self.working_dir).unwrap_or(&path);
@@ -367,7 +369,8 @@ impl HunkTrackerActor {
// `rm foo.txt` on a committed file).
let baseline = self.read_baseline(&path).await;
if matches!(baseline, FileContentState::Missing) {
- return; // Not in HEAD either, nothing to track
+ // Not in HEAD either, nothing to track
+ return;
}
// Seed file_states with baseline and Missing current content.
@@ -427,7 +430,7 @@ impl HunkTrackerActor {
// Clear hunks since baseline == current
let old_hunks = std::mem::take(&mut state.hunks);
- // Remove from turn_index and emit removed events for all hunks
+ // Drop from turn_index and emit `Removed` events for all hunks
for hunk in old_hunks {
if let Some(prompt_index) = hunk.source.prompt_index()
&& let Some(set) = self.turn_index.get_mut(&prompt_index)
@@ -455,7 +458,7 @@ impl HunkTrackerActor {
/// - Re-read baseline from the new HEAD
/// - Re-read current content from disk
/// - Recompute hunks
- /// - Drop files that are now clean (baseline == current, not agent files)
+ /// - Drop files that are clean (baseline == current, not agent files)
pub(super) async fn refresh_all_baselines(&mut self) {
self.refresh_all_baselines_except(&HashSet::new()).await;
}
@@ -572,7 +575,7 @@ impl HunkTrackerActor {
state.current_content = new_current;
state.baseline_accepted = false;
- // Check if file is now clean (baseline == current).
+ // Check if file is clean (baseline == current).
// For Full states, compare text (ignoring trailing newline).
// For non-diffable states (Binary/TooLarge/LFS): consult the git
// dirty cache (refreshed above) — if git says the file is clean,
@@ -601,7 +604,7 @@ impl HunkTrackerActor {
}
// Symlink on disk vs Full(target) in HEAD (or vice versa):
// git stores symlinks as plain text blobs, so the types
- // differ even when the file is unchanged. Consult dirty cache.
+ // differ even when the file is `unchanged`. Consult dirty cache.
(FileContentState::Symlink, FileContentState::Full(_))
| (FileContentState::Full(_), FileContentState::Symlink) => {
let rel = path.strip_prefix(&self.working_dir).unwrap_or(&path);
@@ -698,7 +701,7 @@ impl HunkTrackerActor {
for path in non_agent_paths {
if let Some(state) = self.file_states.remove(&path) {
- // Remove from turn_index and emit removed events for all hunks
+ // Drop from turn_index and emit `Removed` events for all hunks
for hunk in state.hunks {
if let Some(prompt_index) = hunk.source.prompt_index()
&& let Some(set) = self.turn_index.get_mut(&prompt_index)
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/queries.rs b/crates/codegen/kigi-hunk-tracker/src/actor/queries.rs
index 413f4ce..c8457bf 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/queries.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/queries.rs
@@ -15,7 +15,6 @@ use crate::types::{
use super::HunkTrackerActor;
impl HunkTrackerActor {
- /// Get all hunks.
pub(super) fn get_all_hunks(&self) -> Vec> {
self.file_states
.values()
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/state.rs b/crates/codegen/kigi-hunk-tracker/src/actor/state.rs
index cb3cea1..ddddf19 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/state.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/state.rs
@@ -10,7 +10,7 @@ use crate::types::Hunk;
/// Maximum size (in bytes) of file text content to retain in memory.
/// Files larger than this are stored as TooLarge.
/// This is aligned with the diff limit to ensure consistent behavior.
-pub(crate) const MAX_TRACKED_TEXT_BYTES: usize = 1024 * 1024; // 1 MB
+pub(crate) const MAX_TRACKED_TEXT_BYTES: usize = 1024 * 1024;
/// Explicit state of file content storage.
/// Replaces Option for baseline/current_content to avoid unbounded memory.
diff --git a/crates/codegen/kigi-hunk-tracker/src/actor/tests.rs b/crates/codegen/kigi-hunk-tracker/src/actor/tests.rs
index 043e4e9..7bd42ea 100644
--- a/crates/codegen/kigi-hunk-tracker/src/actor/tests.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/actor/tests.rs
@@ -144,7 +144,6 @@ impl TestHarness {
self.working_dir.join(path)
}
- /// Get all hunks
async fn get_all_hunks(&self) -> Vec> {
self.handle.get_all_hunks().await
}
@@ -160,7 +159,6 @@ impl TestHarness {
self.handle.get_file_hunk_data(self.abs_path(path)).await
}
- /// Accept a hunk
async fn accept_hunk(&self, hunk_id: &crate::types::HunkId) -> bool {
self.handle
.hunk_action(hunk_id.clone(), HunkAction::Accept)
@@ -211,9 +209,7 @@ impl TestHarness {
}
}
-// =========================================================================
// Basic Hunk Tracking Tests
-// =========================================================================
#[tokio::test]
async fn test_new_file_creates_single_hunk() {
@@ -349,9 +345,7 @@ async fn test_revert_to_baseline_removes_hunk() {
);
}
-// =========================================================================
// Hunk Accept/Reject Tests
-// =========================================================================
#[tokio::test]
async fn test_accept_hunk_removes_it() {
@@ -395,9 +389,7 @@ async fn test_reject_hunk_reverts_file() {
assert!(hunks_after.is_empty(), "Rejected hunk should be removed");
}
-// =========================================================================
// Event Emission Tests
-// =========================================================================
#[tokio::test]
async fn test_hunk_added_event_emitted() {
@@ -426,7 +418,8 @@ async fn test_hunk_removed_event_on_revert() {
harness.write_baseline("foo.rs", "original\n");
harness.agent_write("foo.rs", "modified\n", 0);
harness.settle().await;
- harness.drain_events(); // Clear initial events
+ // Clear initial events
+ harness.drain_events();
// Revert
harness.agent_write("foo.rs", "original\n", 1);
@@ -439,9 +432,7 @@ async fn test_hunk_removed_event_on_revert() {
assert!(has_removed, "Should emit HunkRemoved event when reverting");
}
-// =========================================================================
// Prompt Index Attribution Tests
-// =========================================================================
#[tokio::test]
async fn test_hunks_have_prompt_index() {
@@ -558,7 +549,6 @@ line 3
);
harness.settle().await;
- // Now create an external edit on the agent file
harness.external_write(
"external_only.rs",
r#"line 1
@@ -787,9 +777,7 @@ line 5
assert_eq!(summary.files_with_pending, 0);
}
-// =========================================================================
// Source Attribution Preservation Tests
-// =========================================================================
#[tokio::test]
async fn test_external_edit_preserves_agent_hunk_source() {
@@ -909,9 +897,7 @@ line 10
}
}
-// =========================================================================
// Binary File Handling Tests
-// =========================================================================
#[tokio::test]
async fn test_binary_file_agent_write_ignored() {
@@ -969,9 +955,7 @@ async fn test_text_file_with_valid_utf8_tracked() {
assert_eq!(hunks[0].new_text, "Hello, 世界!\n");
}
-// =========================================================================
// Accept/Reject Per-Hunk Tests (Bug Demonstration)
-// =========================================================================
// These tests explicitly demonstrate the bug where accept/reject affects
// ALL hunks in a file instead of just the targeted hunk.
//
@@ -1048,11 +1032,9 @@ line 10
"Immediately after accept: 1 hunk in list (state.hunks.retain)"
);
- // ============================================================
// BUG: Now trigger a recompute by making a trivial external change
// This will diff baseline vs current, and since baseline == current
// (from the buggy accept), all hunks will disappear!
- // ============================================================
// Make a tiny change that doesn't affect the hunks
// This triggers recompute_hunks internally
@@ -1150,10 +1132,8 @@ line 10
assert!(success, "Reject should succeed");
harness.settle().await;
- // ============================================================
// BUG: After rejecting ONE hunk, the ENTIRE file is reverted!
// FIX: Now we only revert the specific hunk's lines
- // ============================================================
// Read file content from disk
let content = std::fs::read_to_string(harness.working_dir.join("bug_reject.rs")).unwrap();
@@ -1209,7 +1189,6 @@ original line 2
harness.accept_hunk(&hunks[0].id).await;
harness.settle().await;
- // Now make a NEW change to a DIFFERENT line
harness.agent_write(
"baseline_bug.rs",
r#"modified line 1
@@ -1221,9 +1200,7 @@ NEW CHANGE
let new_hunks = harness.get_all_hunks().await;
- // ============================================================
// This SHOULD work correctly - new change creates new hunk
- // ============================================================
assert_eq!(
new_hunks.len(),
1,
@@ -1244,9 +1221,7 @@ NEW CHANGE
);
}
-// =========================================================================
// Tests that will PASS after the fix is implemented
-// =========================================================================
/// EXPECTED BEHAVIOR: Accept one hunk, other hunks remain
///
@@ -1624,9 +1599,7 @@ line 12
);
}
-// =========================================================================
// Per-Turn Attribution Tests (Bug Demonstration)
-// =========================================================================
// These tests demonstrate the bug where agent-to-agent overlapping edits
// lose the latest prompt_index attribution.
@@ -1701,13 +1674,11 @@ line 5
"Hunk ID should be preserved for overlapping edit"
);
- // ============================================================
// BUG: The hunk should now be attributed to turn 1, but it's still turn 0
// FIX: Now agent-to-agent edits update the prompt_index
- // ============================================================
match &hunks_after_turn_1[0].source {
crate::types::HunkSource::AgentEdit { prompt_index } => {
- // FIX APPLIED: prompt_index is now 1 (the latest agent turn)
+ // FIX APPLIED: prompt_index is 1 (the latest agent turn)
assert_eq!(
*prompt_index, 1,
"FIXED: Hunk should be re-attributed to turn 1"
@@ -1775,9 +1746,7 @@ line 5
}
}
-// =========================================================================
// Integration Bug Test: record_agent_write vs handle_file_change
-// =========================================================================
// This test demonstrates the bug in the CLI shell where tool execution
// only triggers fs_notify (handle_file_change) but never calls record_agent_write.
// This means ALL hunks from agent tools are classified as External, not AgentEdit.
@@ -1811,7 +1780,7 @@ async fn test_bug_fs_notify_path_creates_external_hunks_not_agent_hunks() {
crate::types::HunkSource::External => {
// This is the CURRENT BROKEN BEHAVIOR
// Since forward_to_hunk_tracker only calls handle_file_change,
- // and the file wasn't previously tracked as an agent file,
+ // and the file wasn't already tracked as an agent file,
// the hunk is created as External.
}
crate::types::HunkSource::AgentEdit { prompt_index } => {
@@ -1840,7 +1809,8 @@ async fn test_record_agent_write_creates_agent_edit_hunks() {
harness.write_baseline("tool_correct.rs", "original content\n");
// Simulate what SHOULD happen: tool calls record_agent_write directly
- let prompt_index = 5; // Example prompt index
+ // Example prompt index
+ let prompt_index = 5;
harness.agent_write("tool_correct.rs", "modified by agent tool\n", prompt_index);
harness.settle().await;
@@ -2232,9 +2202,7 @@ async fn test_repro_turn_action_preserves_other_turns() {
);
}
-// =========================================================================
// Worktree Diff Bug: previous_content as fallback baseline
-// =========================================================================
// When a session runs in a worktree created from dirty state, files that
// exist on disk but are not committed to git should use previous_content
// as the baseline, not None. Otherwise the diff shows the entire file
@@ -2262,7 +2230,8 @@ async fn test_worktree_previous_content_used_as_baseline_when_not_in_git() {
"hi.txt",
"hello world\nanother line\n",
0,
- Some("hello world\n"), // previous_content from the tool
+ // previous_content from the tool
+ Some("hello world\n"),
);
harness.settle().await;
@@ -2297,7 +2266,8 @@ async fn test_new_file_without_previous_content_shows_all_lines() {
"brand_new.txt",
"line 1\nline 2\n",
0,
- None, // No previous content — truly new file
+ // No previous content — truly new file
+ None,
);
harness.settle().await;
@@ -2333,7 +2303,8 @@ async fn test_git_baseline_takes_precedence_over_previous_content() {
"committed.txt",
"original line 1\nmodified line 2\n",
0,
- Some("some other content\n"), // previous_content differs from git HEAD
+ // previous_content differs from git HEAD
+ Some("some other content\n"),
);
harness.settle().await;
@@ -2356,9 +2327,7 @@ async fn test_git_baseline_takes_precedence_over_previous_content() {
);
}
-// =========================================================================
// Baseline refresh after accept + git restore
-// =========================================================================
/// Reproduces the bug where accepting all hunks then running `git restore .`
/// leaves the hunk tracker with a stale baseline, producing a giant backwards
@@ -2504,12 +2473,10 @@ async fn test_binary_file_survives_baseline_refresh() {
);
}
-// =========================================================================
-// HunkContentChanged event tests
-// =========================================================================
+// `HunkContentChanged` event tests
/// When the agent edits the same region twice, the hunk tracker must emit
-/// HunkContentChanged (not just HunkAdded) so LOC tracking records the update.
+/// `HunkContentChanged` (not just `HunkAdded`) so LOC tracking records the update.
#[tokio::test]
async fn test_content_changed_emitted_on_overlapping_agent_edit() {
let mut harness = TestHarness::new();
@@ -2520,7 +2487,8 @@ async fn test_content_changed_emitted_on_overlapping_agent_edit() {
// Agent modifies lines 2-3 (prompt 0)
harness.agent_write("content.rs", "line1\nchanged2\nchanged3\nline4\nline5\n", 0);
harness.settle().await;
- harness.drain_events(); // consume initial events
+ // consume initial events
+ harness.drain_events();
// Agent edits the same region again, expanding it (prompt 1)
harness.agent_write(
@@ -2532,7 +2500,7 @@ async fn test_content_changed_emitted_on_overlapping_agent_edit() {
let events = harness.drain_events();
- // Must contain at least one HunkContentChanged event
+ // Must contain at least one `HunkContentChanged` event
let content_changed_events: Vec<_> = events
.iter()
.filter(|e| matches!(e, HunkEvent::HunkContentChanged { .. }))
@@ -2568,7 +2536,7 @@ async fn test_content_changed_emitted_on_overlapping_agent_edit() {
}
/// When a human externally edits a region that the agent already touched,
-/// HunkContentChanged must have trigger_source=ExternalEditOnAgentFile.
+/// `HunkContentChanged` must have trigger_source=ExternalEditOnAgentFile.
#[tokio::test]
async fn test_content_changed_external_edit_on_agent_hunk() {
let mut harness = TestHarness::new();
@@ -2625,12 +2593,12 @@ async fn test_content_changed_external_edit_on_agent_hunk() {
/// different locations). Then agent writes a new version that merges
/// both regions into one contiguous change. The diff engine produces
/// one merged hunk. `find_matching_old_hunk` matches one old hunk and
-/// claims its ID. The other old hunk is now "orphaned" — but the merged
+/// claims its ID. The other old hunk is "orphaned" — but the merged
/// new hunk still overlaps with it.
///
-/// For HunkContentChanged, the prev lookup should find the matched old
+/// For `HunkContentChanged`, the prev lookup should find the matched old
/// hunk by ID (primary path). We separately verify that non-ID-matched
-/// hunks that overlap still get a HunkRemoved event (the overlap fallback
+/// hunks that overlap still get a `HunkRemoved` event (the overlap fallback
/// for prev is only used when a NEW hunk gets a fresh ID but has overlap).
///
/// To test the actual fallback: we need a case where `find_matching_old_hunk`
@@ -2649,8 +2617,8 @@ async fn test_content_changed_prev_lookup_uses_overlap_fallback() {
// Agent writes: change line 3 and line 17 (two separate hunks far apart)
let mut v1: Vec = (1..=20).map(|i| format!("line{i}\n")).collect();
- v1[2] = "CHANGED3\n".to_string(); // line 3
- v1[16] = "CHANGED17\n".to_string(); // line 17
+ v1[2] = "CHANGED3\n".to_string();
+ v1[16] = "CHANGED17\n".to_string();
harness.agent_write("overlap.rs", &v1.join(""), 0);
harness.settle().await;
@@ -2662,20 +2630,23 @@ async fn test_content_changed_prev_lookup_uses_overlap_fallback() {
hunks_v1.iter().map(|h| &h.line_info).collect::>()
);
let old_hunk_ids: Vec<_> = hunks_v1.iter().map(|h| h.id.clone()).collect();
- harness.drain_events(); // consume v1 events
+ // consume v1 events
+ harness.drain_events();
// Agent writes again: change line 3 AND line 4 (expanding the first hunk
// so it's different content). Also change line 17 differently.
let mut v2: Vec = (1..=20).map(|i| format!("line{i}\n")).collect();
v2[2] = "CHANGED3_V2\n".to_string();
- v2[3] = "CHANGED4_V2\n".to_string(); // expand first hunk
- v2[16] = "CHANGED17_V2\n".to_string(); // change second hunk
+ // expand first hunk
+ v2[3] = "CHANGED4_V2\n".to_string();
+ // change second hunk
+ v2[16] = "CHANGED17_V2\n".to_string();
harness.agent_write("overlap.rs", &v2.join(""), 1);
harness.settle().await;
let events = harness.drain_events();
- // We should see HunkContentChanged events with prev_lines_added > 0.
+ // We should see `HunkContentChanged` events with `prev_lines_added` > 0.
// At least one of them should have come from the overlap fallback path
// (the old hunk whose ID was claimed by a different new hunk).
let content_changed: Vec<_> = events
@@ -2691,7 +2662,7 @@ async fn test_content_changed_prev_lookup_uses_overlap_fallback() {
})
.collect();
- // There should be at least one HunkContentChanged
+ // There should be at least one `HunkContentChanged`
assert!(
!content_changed.is_empty(),
"Should emit HunkContentChanged for overlapping edits. Events: {:?}",
@@ -2701,7 +2672,7 @@ async fn test_content_changed_prev_lookup_uses_overlap_fallback() {
.collect::>()
);
- // Every HunkContentChanged should have prev_lines_added > 0
+ // Every `HunkContentChanged` should have `prev_lines_added` > 0
// (they all overlap with an old hunk that had lines)
for (hunk_id, prev_added, _prev_removed) in &content_changed {
assert!(
@@ -2713,7 +2684,7 @@ async fn test_content_changed_prev_lookup_uses_overlap_fallback() {
);
}
- // Verify that at least one HunkContentChanged has a NEW hunk ID
+ // Verify that at least one `HunkContentChanged` has a NEW hunk ID
// (not matching any old hunk ID) — this proves the overlap fallback
// path was used (the hunk got a fresh ID because the old ID was
// already claimed by another new hunk).
@@ -2723,7 +2694,7 @@ async fn test_content_changed_prev_lookup_uses_overlap_fallback() {
// Note: this assertion may not always hold depending on diff engine
// behavior (both hunks might get matched by ID). If it fails, the
- // test still validates that prev_lines_added > 0 for all events,
+ // test still validates that `prev_lines_added` > 0 for all events,
// which is the core correctness property. Log rather than fail.
if !has_new_id {
eprintln!(
@@ -2734,9 +2705,7 @@ async fn test_content_changed_prev_lookup_uses_overlap_fallback() {
}
}
-// ============================================================================
// SF-1: State transition tests (Full <-> TooLarge, Full <-> Binary)
-// ============================================================================
/// SF-1: Test Full -> TooLarge transition via handle_file_change
/// When a tracked text file grows beyond MAX_TRACKED_TEXT_BYTES, it should
@@ -2761,7 +2730,6 @@ async fn test_transition_full_to_too_large_external_edit() {
let tracked = harness.handle.get_all_tracked_paths().await;
assert!(tracked.contains(&file_path), "File should be tracked");
- // Now grow the file beyond MAX_TRACKED_TEXT_BYTES
let large_content = "x".repeat(MAX_TRACKED_TEXT_BYTES + 100);
std::fs::write(&file_path, &large_content).unwrap();
@@ -2946,9 +2914,7 @@ async fn test_too_large_survives_baseline_refresh() {
);
}
-// =========================================================================
// get_file_hunk_data() Query API Tests
-// =========================================================================
// These tests verify the explicit FileContentStatus contract exposed by
// get_file_hunk_data(), ensuring Missing, Binary, TooLarge, and Full states
// are correctly propagated through the query/API surface.
@@ -3251,9 +3217,7 @@ async fn test_get_file_hunk_data_mixed_states() {
assert!(data.hunks.is_empty());
}
-// =========================================================================
// Action-Path Hardening Tests
-// =========================================================================
// These tests verify that accept/reject actions are safe under the explicit
// content-state model, and that transitions correctly clear/create hunks.
@@ -3595,9 +3559,7 @@ async fn test_file_creation_hunk_action() {
assert!(!exists, "File should be deleted after rejecting creation");
}
-// =========================================================================
// Validation + UI Messaging Smoke Tests
-// =========================================================================
// These tests verify that the API correctly exposes file content status
// for clients to display appropriate UI messages (e.g., "file too large").
@@ -3611,7 +3573,8 @@ async fn test_ui_messaging_too_large_file() {
// Create a large file
let file_path = harness.working_dir.join("huge_ui_test.txt");
- let large_size = MAX_TRACKED_TEXT_BYTES + 500_000; // ~1.5 MB
+ // ~1.5 MB
+ let large_size = MAX_TRACKED_TEXT_BYTES + 500_000;
let large_content = "x".repeat(large_size);
std::fs::write(&file_path, &large_content).unwrap();
@@ -3854,9 +3817,7 @@ async fn test_memory_bounded_multiple_large_files() {
}
}
-// =========================================================================
// Issue #2: Deleted committed files visible as deletion hunks
-// =========================================================================
/// Deleting a committed file that was never tracked by the hunk tracker
/// should produce a deletion hunk (in AllDirty mode).
@@ -3915,7 +3876,8 @@ async fn test_deleted_committed_file_produces_deletion_hunk() {
/// produce any hunks (we only track agent files).
#[tokio::test]
async fn test_deleted_committed_file_ignored_in_agent_only_mode() {
- let mut harness = TestHarness::new(); // AgentOnly mode
+ // AgentOnly mode
+ let mut harness = TestHarness::new();
// Commit a file (never tracked by hunk tracker)
harness.write_baseline("foo.txt", "content\n");
@@ -3983,14 +3945,13 @@ async fn test_deleted_file_cleaned_up_after_commit() {
let hunks = harness.get_all_hunks().await;
assert_eq!(hunks.len(), 1, "Should have deletion hunk");
- // Now commit the deletion
git(&harness.working_dir, &["add", "cleanup.txt"]);
git(
&harness.working_dir,
&["commit", "-m", "delete cleanup.txt"],
);
- // Refresh baselines (simulates git_head_changed)
+ // Refresh baselines (simulates `git_head_changed`)
harness.handle.refresh_all_baselines();
harness.settle().await;
@@ -4047,11 +4008,9 @@ async fn test_reject_deletion_of_committed_file_restores_it() {
);
}
-// =========================================================================
// Issue #1: Staged-only files visible after git reset --soft HEAD^
-// =========================================================================
-/// After `git reset --soft HEAD^`, a file that was added in the undone
+/// After `git reset --soft HEAD^`, a file added by the undone
/// commit should appear as a new file (staged in index, not in HEAD).
#[tokio::test]
async fn test_soft_reset_staged_new_file_visible() {
@@ -4066,7 +4025,7 @@ async fn test_soft_reset_staged_new_file_visible() {
// Soft reset: moves HEAD back but keeps index and worktree
git(&harness.working_dir, &["reset", "--soft", "HEAD^"]);
- // Trigger baseline refresh (simulates git_head_changed)
+ // Trigger baseline refresh (simulates `git_head_changed`)
harness.handle.refresh_all_baselines();
harness.settle().await;
@@ -4265,7 +4224,7 @@ async fn test_mixed_reset_modified_files_visible() {
);
}
-/// After `git reset HEAD~1` (mixed reset), newly added files in the undone
+/// After `git reset HEAD~1` (mixed reset), newly created files in the undone
/// commit should appear as untracked new files with creation hunks.
#[tokio::test]
async fn test_mixed_reset_new_file_visible() {
@@ -4305,9 +4264,7 @@ async fn test_mixed_reset_new_file_visible() {
);
}
-// =========================================================================
// GetAllFileContents Tests
-// =========================================================================
/// Empty actor returns no file contents.
#[tokio::test]
@@ -4550,9 +4507,7 @@ async fn test_get_all_file_contents_returns_absolute_paths() {
);
}
-// =========================================================================
// refresh_all_baselines: non-diffable file cleanup via git dirty cache
-// =========================================================================
/// A committed binary file that is NOT dirty in git status should be removed
/// from tracking after refresh_all_baselines (no longer a phantom).
@@ -4723,9 +4678,7 @@ async fn test_dirty_lfs_file_survives_refresh() {
);
}
-// =========================================================================
// Gitignored file filtering tests
-// =========================================================================
/// Gitignored files (e.g., cargo build artifacts in `target/`) should NOT
/// be tracked in AllDirty mode. The git dirty cache never contains ignored
@@ -4820,10 +4773,9 @@ async fn test_gitignored_file_cleaned_up_by_refresh_all_baselines() {
let tracked = harness.handle.get_all_tracked_paths().await;
assert!(tracked.contains(&real_file), "Dirty file should be tracked");
- // Now restore the file so it's clean
std::fs::write(&real_file, "original\n").unwrap();
- // Trigger refresh — the file is now clean (baseline == current), should be removed
+ // Trigger refresh — the file is clean (baseline == current), should be removed
harness.handle.refresh_all_baselines();
let _ = harness.handle.get_all_hunks().await;
@@ -4998,9 +4950,7 @@ async fn test_untracked_directory_tracks_child_files_not_directory() {
);
}
-// =========================================================================
// Command Coalescing Tests
-// =========================================================================
use crate::actor::{CoalescedBatch, CoalescedPathAction};
use crate::commands::HunkTrackerCommand;
@@ -5279,7 +5229,7 @@ async fn test_coalescing_delete_then_recreate() {
/// Integration test: refresh_all + file changes are correctly coalesced.
/// When refresh_all is in the batch, tracked-file changes should be skipped
-/// (refresh_all handles them), but new files should still be added.
+/// (refresh_all handles them), but new files should still be tracked.
#[tokio::test]
async fn test_coalescing_refresh_all_with_file_changes() {
let mut harness = TestHarness::with_mode(TrackingMode::AllDirty);
@@ -5290,7 +5240,6 @@ async fn test_coalescing_refresh_all_with_file_changes() {
harness.agent_write("existing.rs", "modified\n", 0);
harness.settle().await;
- // Now queue: file change on existing + refresh_all + new file change
std::fs::write(harness.working_dir.join("existing.rs"), "v2\n").unwrap();
harness
.handle
@@ -5396,9 +5345,7 @@ async fn test_snapshot_turn_delta_is_per_turn() {
assert!(empty.file_states.is_empty() && empty.hunk_ids.is_empty());
}
-// =========================================================================
// Baseline refresh during git rebases (scan counting + hunk preservation)
-// =========================================================================
/// Count of `BaselineUpdated` events in a drained batch. The real
/// `refresh_all_baselines` scan path emits one per still-tracked file, while
@@ -5412,9 +5359,7 @@ fn baseline_updates(events: &[HunkEvent]) -> usize {
.count()
}
-// =========================================================================
// Scoped (pathspec-limited) dirty-cache scans
-// =========================================================================
/// Construct an actor directly (not spawned, via the production constructor)
/// so tests can call `pub(super)` methods like `refresh_git_dirty_cache` and
diff --git a/crates/codegen/kigi-hunk-tracker/src/commands.rs b/crates/codegen/kigi-hunk-tracker/src/commands.rs
index 3b9a6a3..b17306f 100644
--- a/crates/codegen/kigi-hunk-tracker/src/commands.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/commands.rs
@@ -13,7 +13,7 @@ use crate::types::{
/// Commands sent to the HunkTrackerActor via mpsc channel.
#[derive(Debug)]
pub enum HunkTrackerCommand {
- // === Mutation Commands (fire-and-forget) ===
+ // Mutation Commands (fire-and-forget)
/// Agent tool wrote to a file - record it and compute hunks
RecordAgentWrite {
path: PathBuf,
@@ -40,7 +40,7 @@ pub enum HunkTrackerCommand {
/// Set tracking mode
SetMode { mode: TrackingMode },
- // === Action Commands (accept/reject hunks) ===
+ // Action Commands (accept/reject hunks)
/// Apply action (accept/reject) to a specific hunk
HunkAction {
hunk_id: HunkId,
@@ -68,7 +68,7 @@ pub enum HunkTrackerCommand {
reply: oneshot::Sender, HunkActionError>>,
},
- // === Query Commands (request-response via oneshot) ===
+ // Query Commands (request-response via oneshot)
/// Get all current hunks
GetAllHunks {
reply: oneshot::Sender>>,
@@ -121,7 +121,7 @@ pub enum HunkTrackerCommand {
reply: oneshot::Sender>,
},
- // === Session Summary Commands ===
+ // Session Summary Commands
/// Get complete session summary (stats + pending turns)
GetSessionSummary {
reply: oneshot::Sender,
@@ -140,7 +140,7 @@ pub enum HunkTrackerCommand {
/// content from disk. Used after a git HEAD/index change to reconcile stale state.
RefreshAllBaselines,
- // === Snapshot / Restore Commands (for cross-session sync-back) ===
+ // Snapshot / Restore Commands (for cross-session sync-back)
/// Take a snapshot of all hunk tracker state for preservation across
/// session kill/reload cycles.
SnapshotState {
@@ -153,7 +153,7 @@ pub enum HunkTrackerCommand {
reply: oneshot::Sender,
},
- /// Restore a previously snapshotted state. Replaces all current file
+ /// Restore a earlier snapshotted state. Replaces all current file
/// states, turn index, and session stats.
RestoreState(HunkTrackerSnapshot),
}
diff --git a/crates/codegen/kigi-hunk-tracker/src/diff.rs b/crates/codegen/kigi-hunk-tracker/src/diff.rs
index 2200ae3..355c91b 100644
--- a/crates/codegen/kigi-hunk-tracker/src/diff.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/diff.rs
@@ -17,7 +17,7 @@ const DIFF_TIMEOUT: Duration = Duration::from_secs(10);
/// Maximum file size (in bytes) to attempt diffing.
/// Files larger than this will be skipped to avoid pathological diff behavior.
-const MAX_DIFF_FILE_SIZE: usize = 1024 * 1024; // 1 MB
+const MAX_DIFF_FILE_SIZE: usize = 1024 * 1024;
/// Generate a unified diff patch string from baseline and current content.
/// This produces a patch that can be parsed by Pierre's `getSingularPatch`.
@@ -112,7 +112,8 @@ pub fn generate_hunk_patch(baseline: &str, current: &str, hunk: &Hunk) -> String
// Hunk header (1-indexed)
let header_old_start = context_before_start + 1;
- let header_new_start = context_before_start + 1; // Context is same in both
+ // Context is same in both
+ let header_new_start = context_before_start + 1;
let _ = writeln!(
output,
@@ -297,7 +298,8 @@ impl HunkBuilder {
source,
old_text,
new_text,
- patch: None, // Patch is generated later when requested
+ // Patch is generated later when requested
+ patch: None,
created_at: chrono::Utc::now(),
selected: false,
}
@@ -346,7 +348,8 @@ pub fn patch_lines(
insert_text: &str,
) -> String {
let lines: Vec<&str> = content.lines().collect();
- let start_idx = start_line.saturating_sub(1); // Convert to 0-indexed
+ // Convert to 0-indexed
+ let start_idx = start_line.saturating_sub(1);
let mut result = Vec::new();
@@ -608,7 +611,8 @@ mod tests {
line_info: HunkLineInfo {
old_start: 10,
old_count: 1,
- new_start: 12, // slightly shifted
+ // slightly shifted
+ new_start: 12,
new_count: 1,
},
source: agent_source(),
@@ -630,7 +634,8 @@ mod tests {
line_info: HunkLineInfo {
old_start: 100,
old_count: 1,
- new_start: 102, // slightly shifted
+ // slightly shifted
+ new_start: 102,
new_count: 1,
},
source: agent_source(),
@@ -665,7 +670,8 @@ mod tests {
old_start: 1,
old_count: 1,
new_start: 1,
- new_count: 2, // covers new lines 1-2
+ // covers new lines 1-2
+ new_count: 2,
},
source: agent_source(),
old_text: Some("old-small\n".to_string()),
@@ -682,7 +688,8 @@ mod tests {
old_start: 3,
old_count: 1,
new_start: 3,
- new_count: 4, // covers new lines 3-6
+ // covers new lines 3-6
+ new_count: 4,
},
source: agent_source(),
old_text: Some("old-large\n".to_string()),
@@ -692,7 +699,8 @@ mod tests {
selected: false,
});
- let old_hunks = vec![old_hunk_small.clone(), old_hunk_large.clone()]; // small first!
+ // small first!
+ let old_hunks = vec![old_hunk_small.clone(), old_hunk_large.clone()];
// New hunk overlaps both, but more with large:
// new lines 2-5 (end=6)
@@ -706,7 +714,8 @@ mod tests {
old_start: 2,
old_count: 4,
new_start: 2,
- new_count: 4, // lines 2-5
+ // lines 2-5
+ new_count: 4,
},
source: agent_source(),
old_text: Some("different-old\n".to_string()),
diff --git a/crates/codegen/kigi-hunk-tracker/src/events.rs b/crates/codegen/kigi-hunk-tracker/src/events.rs
index c23a2eb..98c0d48 100644
--- a/crates/codegen/kigi-hunk-tracker/src/events.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/events.rs
@@ -6,7 +6,7 @@ use std::sync::Arc;
use crate::types::{Hunk, HunkId, HunkLineInfo, HunkSource};
-/// Why a hunk was removed. Used by the LOC sink to decide whether to
+/// Why the tracker removed a hunk. Used by the LOC sink to decide whether to
/// negate the hunk's accumulated LOC contribution.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -30,7 +30,7 @@ pub enum HunkEvent {
/// A new hunk was created
HunkAdded { path: PathBuf, hunk: Arc },
- /// A hunk was removed.
+ /// A hunk stopped being tracked.
HunkRemoved {
path: PathBuf,
hunk_id: HunkId,
@@ -69,6 +69,6 @@ pub enum HunkEvent {
/// A file stopped being tracked (all hunks gone, not an agent file)
FileRemoved { path: PathBuf },
- /// Baseline was updated for a file (after accept or commit)
+ /// The tracker updated a file's baseline (after accept or commit)
BaselineUpdated { path: PathBuf },
}
diff --git a/crates/codegen/kigi-hunk-tracker/src/handle.rs b/crates/codegen/kigi-hunk-tracker/src/handle.rs
index 7e4f4fb..578ed7e 100644
--- a/crates/codegen/kigi-hunk-tracker/src/handle.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/handle.rs
@@ -68,7 +68,6 @@ impl HunkTrackerHandle {
.send(HunkTrackerCommand::HandleFileDeleted { path });
}
- /// Refresh git dirty cache.
pub fn refresh_git_dirty_cache(&self) {
let _ = self.cmd_tx.send(HunkTrackerCommand::RefreshGitDirtyCache);
}
@@ -140,7 +139,6 @@ impl HunkTrackerHandle {
reply_rx.await.unwrap_or_else(|_| Ok(vec![]))
}
- /// Get all hunks.
pub async fn get_all_hunks(&self) -> Vec> {
let (reply_tx, reply_rx) = oneshot::channel();
let _ = self
@@ -169,7 +167,6 @@ impl HunkTrackerHandle {
reply_rx.await.unwrap_or_default()
}
- /// Get hunks by source.
pub async fn get_hunks_by_source(&self, source: HunkSourceFilter) -> Vec> {
let (reply_tx, reply_rx) = oneshot::channel();
let _ = self.cmd_tx.send(HunkTrackerCommand::GetHunksBySource {
@@ -293,7 +290,7 @@ impl HunkTrackerHandle {
reply_rx.await.ok()
}
- /// Restore a previously snapshotted state. Replaces all current file
+ /// Restore a earlier snapshotted state. Replaces all current file
/// states, turn index, and session stats in the actor.
///
/// This is fire-and-forget — doesn't wait for processing.
diff --git a/crates/codegen/kigi-hunk-tracker/src/loc/mod.rs b/crates/codegen/kigi-hunk-tracker/src/loc/mod.rs
index c2a75f9..be992b0 100644
--- a/crates/codegen/kigi-hunk-tracker/src/loc/mod.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/loc/mod.rs
@@ -17,9 +17,7 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
-// ---------------------------------------------------------------------------
// Enums
-// ---------------------------------------------------------------------------
/// Who authored a change.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -65,7 +63,7 @@ pub enum EventType {
Added,
/// An existing hunk's content changed in place.
Updated,
- /// A hunk was removed. `lines_added` / `lines_removed` are negated
+ /// Removal of an existing hunk. `lines_added` / `lines_removed` are negated
/// so that `SUM` zeroes out the hunk's accumulated contribution.
Removed,
}
@@ -80,9 +78,7 @@ impl std::fmt::Display for EventType {
}
}
-// ---------------------------------------------------------------------------
// HunkRecord
-// ---------------------------------------------------------------------------
/// A single LOC attribution record derived from a [`Hunk`].
///
@@ -126,7 +122,7 @@ pub struct HunkRecord {
pub source_type: Option,
/// Whether this is a new hunk or an in-place update.
pub event_type: EventType,
- /// Why the hunk was removed. Only set for [`EventType::Removed`] records.
+ /// Reason for the removal. Only set for [`EventType::Removed`] records.
#[serde(skip_serializing_if = "Option::is_none")]
pub removal_reason: Option,
}
@@ -201,9 +197,7 @@ impl HunkRecord {
}
}
-// ---------------------------------------------------------------------------
// HunkRecordWriter
-// ---------------------------------------------------------------------------
/// Trait for persisting [`HunkRecord`]s.
///
@@ -279,9 +273,7 @@ impl HunkRecordWriter for JsonlHunkRecordWriter {
}
}
-// ---------------------------------------------------------------------------
// LocAggregate (channel-based bridge to signals)
-// ---------------------------------------------------------------------------
/// Lightweight aggregate update emitted by the LOC sink for consumption by
/// an external bridge (e.g., the signals system in `kigi-shell`).
@@ -290,7 +282,7 @@ impl HunkRecordWriter for JsonlHunkRecordWriter {
/// The bridge task translates them into `SignalEvent` variants.
#[derive(Debug, Clone)]
pub enum LocAggregate {
- /// Lines were added or changed (from HunkAdded or HunkContentChanged).
+ /// New or modified lines (from `HunkAdded` or `HunkContentChanged`).
LinesChanged {
author_type: AuthorType,
lines_added: i64,
@@ -305,9 +297,7 @@ pub enum LocAggregate {
},
}
-// ---------------------------------------------------------------------------
// Sink configuration
-// ---------------------------------------------------------------------------
/// Context passed to the LOC sink at spawn time.
pub struct LocSinkContext {
@@ -322,9 +312,7 @@ pub struct LocSinkContext {
pub aggregate_tx: Option>,
}
-// ---------------------------------------------------------------------------
// run_loc_sink
-// ---------------------------------------------------------------------------
/// Consume [`HunkEvent`]s and write LOC attribution records.
///
@@ -344,7 +332,7 @@ pub async fn run_loc_sink(
ctx: LocSinkContext,
cancellation_token: tokio_util::sync::CancellationToken,
) {
- // Accumulated (lines_added, lines_removed) per hunk_id.
+ // Accumulated (`lines_added`, `lines_removed`) per hunk_id.
// Used to emit negating records when hunks are rejected/superseded.
let mut acc: HashMap = HashMap::new();
@@ -383,7 +371,7 @@ async fn handle_event(
match event {
HunkEvent::HunkAdded { path: _, ref hunk } => {
// For new hunks, the hunk's own source is the correct attribution.
- // lines_added/lines_removed are the full counts (no prior state).
+ // `lines_added`/`lines_removed` are the full counts (no prior state).
let record = HunkRecord::from_hunk(
hunk,
&ctx.session_id,
diff --git a/crates/codegen/kigi-hunk-tracker/src/loc/tests.rs b/crates/codegen/kigi-hunk-tracker/src/loc/tests.rs
index 9add689..5af1818 100644
--- a/crates/codegen/kigi-hunk-tracker/src/loc/tests.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/loc/tests.rs
@@ -9,9 +9,7 @@ use crate::types::{Hunk, HunkId, HunkLineInfo, HunkSource};
use super::*;
-// ---------------------------------------------------------------------------
// Helpers
-// ---------------------------------------------------------------------------
fn sample_agent_hunk() -> Hunk {
Hunk {
@@ -129,9 +127,7 @@ fn make_ctx() -> LocSinkContext {
}
}
-// ---------------------------------------------------------------------------
// Unit tests: HunkRecord::from_hunk
-// ---------------------------------------------------------------------------
#[test]
fn from_hunk_agent_edit() {
@@ -148,7 +144,8 @@ fn from_hunk_agent_edit() {
assert_eq!(record.hunk_id, HunkId::from_string("test-hunk-001".into()));
assert_eq!(record.file_path, PathBuf::from("/tmp/foo.rs"));
assert_eq!(record.hunk_start, 10);
- assert_eq!(record.hunk_end, 14); // 10 + 5 - 1
+ // 10 + 5 - 1
+ assert_eq!(record.hunk_end, 14);
assert_eq!(record.lines_added, 5);
assert_eq!(record.lines_removed, 3);
assert_eq!(record.author_type, Some(AuthorType::Agent));
@@ -191,7 +188,8 @@ fn from_hunk_external() {
assert_eq!(record.prompt_index, None);
assert_eq!(record.source_type, Some(SourceType::External));
assert_eq!(record.hunk_start, 1);
- assert_eq!(record.hunk_end, 4); // 1 + 4 - 1
+ // 1 + 4 - 1
+ assert_eq!(record.hunk_end, 4);
}
#[test]
@@ -244,7 +242,8 @@ fn from_hunk_pure_deletion() {
// Pure deletion: new_count == 0, so uses old_start/old_count
assert_eq!(record.hunk_start, 5);
- assert_eq!(record.hunk_end, 7); // 5 + 3 - 1
+ // 5 + 3 - 1
+ assert_eq!(record.hunk_end, 7);
assert_eq!(record.lines_added, 0i64);
assert_eq!(record.lines_removed, 3i64);
}
@@ -252,7 +251,8 @@ fn from_hunk_pure_deletion() {
/// Verify that attribution_source overrides the hunk's preserved source.
#[test]
fn from_hunk_trigger_source_overrides_preserved_source() {
- let hunk = sample_agent_hunk(); // hunk.source = AgentEdit
+ // hunk.source = AgentEdit
+ let hunk = sample_agent_hunk();
let trigger = HunkSource::ExternalEditOnAgentFile;
let record = HunkRecord::from_hunk(
&hunk,
@@ -273,9 +273,7 @@ fn from_hunk_trigger_source_overrides_preserved_source() {
assert_eq!(record.event_type, EventType::Updated);
}
-// ---------------------------------------------------------------------------
// Sink tests
-// ---------------------------------------------------------------------------
#[tokio::test]
async fn sink_processes_added_and_content_changed() {
@@ -285,7 +283,8 @@ async fn sink_processes_added_and_content_changed() {
let hunk = sample_agent_hunk();
let mut updated_hunk = sample_agent_hunk();
- updated_hunk.line_info.new_count = 8; // grew from 5 to 8 lines
+ // grew from 5 to 8 lines
+ updated_hunk.line_info.new_count = 8;
// Send a mix of events — only HunkAdded and HunkContentChanged should produce records
tx.send(HunkEvent::FileAdded {
@@ -302,8 +301,10 @@ async fn sink_processes_added_and_content_changed() {
path: PathBuf::from("/tmp/foo.rs"),
hunk: Arc::new(updated_hunk),
trigger_source: HunkSource::AgentEdit { prompt_index: 2 },
- prev_lines_added: 5, // original hunk had 5 lines added
- prev_lines_removed: 3, // original hunk had 3 lines removed
+ // original hunk had 5 lines added
+ prev_lines_added: 5,
+ // original hunk had 3 lines removed
+ prev_lines_removed: 3,
})
.unwrap();
tx.send(HunkEvent::HunkMoved {
@@ -377,7 +378,8 @@ async fn sink_removed_hunk_zeroes_out_accumulated_total() {
let ctx = make_ctx();
let cancel = tokio_util::sync::CancellationToken::new();
- let hunk = sample_agent_hunk(); // lines_added=5, lines_removed=3
+ // lines_added=5, lines_removed=3
+ let hunk = sample_agent_hunk();
let hunk_id = hunk.id.clone();
let path = hunk.path.clone();
@@ -429,12 +431,14 @@ async fn sink_removed_hunk_after_updates_zeroes_correctly() {
let ctx = make_ctx();
let cancel = tokio_util::sync::CancellationToken::new();
- let hunk = sample_agent_hunk(); // lines_added=5, lines_removed=3
+ // lines_added=5, lines_removed=3
+ let hunk = sample_agent_hunk();
let hunk_id = hunk.id.clone();
let path = hunk.path.clone();
let mut updated = sample_agent_hunk();
- updated.line_info.new_count = 8; // grew from 5 → 8
+ // grew from 5 → 8
+ updated.line_info.new_count = 8;
// Add → update → remove
tx.send(HunkEvent::HunkAdded {
@@ -478,7 +482,8 @@ async fn sink_accepted_hunk_preserves_loc() {
let ctx = make_ctx();
let cancel = tokio_util::sync::CancellationToken::new();
- let hunk = sample_agent_hunk(); // lines_added=5, lines_removed=3
+ // lines_added=5, lines_removed=3
+ let hunk = sample_agent_hunk();
let hunk_id = hunk.id.clone();
let path = hunk.path.clone();
@@ -576,7 +581,8 @@ async fn sink_shrinking_hunk_produces_negative_delta() {
.sum();
assert_eq!(agent_total, 10);
assert_eq!(human_total, -3);
- assert_eq!(agent_total + human_total, 7); // net lines in file
+ // net lines in file
+ assert_eq!(agent_total + human_total, 7);
}
#[tokio::test]
@@ -615,9 +621,7 @@ async fn sink_drains_on_cancellation() {
assert!(w.flush_count > 0, "Writer should be flushed on shutdown");
}
-// ---------------------------------------------------------------------------
// JSONL round-trip test
-// ---------------------------------------------------------------------------
#[tokio::test]
async fn jsonl_round_trip() {
@@ -687,9 +691,7 @@ async fn jsonl_writer_appends() {
assert_eq!(lines.len(), 2);
}
-// ---------------------------------------------------------------------------
// Deserialization validation
-// ---------------------------------------------------------------------------
/// Invalid enum values must be rejected during deserialization.
/// This validates that the serde enum gate works — a typo like "foo"
@@ -728,9 +730,7 @@ fn deserialize_rejects_invalid_source_type() {
assert!(result.is_err(), "Should reject invalid source_type");
}
-// ---------------------------------------------------------------------------
// Writer failure resilience
-// ---------------------------------------------------------------------------
/// The sink must continue processing events even when the writer fails.
/// This validates the "log warning and drop the record" error policy.
diff --git a/crates/codegen/kigi-hunk-tracker/src/types.rs b/crates/codegen/kigi-hunk-tracker/src/types.rs
index 752fefa..dfee265 100644
--- a/crates/codegen/kigi-hunk-tracker/src/types.rs
+++ b/crates/codegen/kigi-hunk-tracker/src/types.rs
@@ -47,11 +47,11 @@ impl std::fmt::Display for HunkId {
pub struct HunkLineInfo {
/// 1-indexed start line in baseline (old) file
pub old_start: usize,
- /// Number of lines from baseline that were changed/deleted
+ /// Number of baseline lines this hunk changes or deletes
pub old_count: usize,
/// 1-indexed start line in current (new) file
pub new_start: usize,
- /// Number of lines in current that were added/modified
+ /// Number of current-file lines this hunk adds or modifies
pub new_count: usize,
}
@@ -81,7 +81,7 @@ pub enum HunkSource {
prompt_index: usize,
},
- /// External edit (by user) to a file the agent has previously touched.
+ /// External edit (by user) to a file the agent has already touched.
/// These are tracked separately so we know they're "part of agent session"
/// but weren't written by the agent itself.
ExternalEditOnAgentFile,
@@ -253,7 +253,7 @@ pub enum HunkAction {
pub enum HunkUpdate {
/// A new hunk was created
Added(Hunk),
- /// A hunk was removed (accepted, rejected, or reverted)
+ /// A hunk left the pending set (accepted, rejected, or reverted)
Removed { hunk_id: HunkId },
/// A hunk's position changed but content is the same
Moved {
@@ -299,9 +299,7 @@ pub enum TrackingMode {
AllDirty,
}
-// ============================================================================
// Session Stats & Summary
-// ============================================================================
/// Simple counters for session summary. Reset on baseline reset (commit).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -359,9 +357,7 @@ pub struct SessionSummary {
pub unattributed_pending: usize,
}
-// ============================================================================
// Content Status Types (for explicit API responses)
-// ============================================================================
/// Status of file content - explicit discrimination for API consumers.
/// This replaces the ambiguous `Option` where `None` could mean
@@ -494,13 +490,13 @@ pub struct FileHunkData {
/// Hunks for this file (each hunk includes its own patch fragment)
pub hunks: Vec>,
- // === Explicit content status (new fields) ===
+ // Explicit content status (new fields)
/// Baseline content with explicit status (git HEAD)
pub baseline: FileContentView,
/// Current content with explicit status (on disk)
pub current: FileContentView,
- // === Legacy fields for backward compatibility ===
+ // Legacy fields for backward compatibility
// These are populated from FileContentView for existing callers.
// Will be deprecated once all callers migrate to baseline/current views.
/// Baseline content (git HEAD) - legacy, use `baseline.content` instead
@@ -511,9 +507,7 @@ pub struct FileHunkData {
pub current_content: Option,
}
-// ============================================================================
// Snapshot / Restore (for cross-session sync-back)
-// ============================================================================
// FileContentState is crate-internal (actor::state is pub(crate));
// imported here for snapshot serialization.
@@ -782,7 +776,7 @@ mod snapshot_tests {
assert_eq!(snap.session_stats.accepted_hunks, 5);
}
- // === Snapshot preserves Binary/TooLarge (regression test) ===
+ // Snapshot preserves Binary/TooLarge (regression test)
#[test]
fn snapshot_preserves_binary_state() {
@@ -855,9 +849,7 @@ mod snapshot_tests {
}
}
-// ============================================================================
// FileContentView Tests (content status propagation)
-// ============================================================================
#[cfg(test)]
mod content_view_tests {
diff --git a/crates/codegen/kigi-log/src/appender.rs b/crates/codegen/kigi-log/src/appender.rs
index 3fe6611..af74a7e 100644
--- a/crates/codegen/kigi-log/src/appender.rs
+++ b/crates/codegen/kigi-log/src/appender.rs
@@ -41,6 +41,7 @@ pub(crate) fn flush_file_log_guards() {
if let Some(m) = FILE_LOG_GUARDS.get() {
// Recover from a poisoned mutex so exit-flush still drains the guards.
let mut guards = m.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
- guards.clear(); // dropping each WorkerGuard flushes + joins its writer thread
+ // dropping each WorkerGuard flushes + joins its writer thread
+ guards.clear();
}
}
diff --git a/crates/codegen/kigi-log/src/debug_log.rs b/crates/codegen/kigi-log/src/debug_log.rs
index 1960693..97a2536 100644
--- a/crates/codegen/kigi-log/src/debug_log.rs
+++ b/crates/codegen/kigi-log/src/debug_log.rs
@@ -105,7 +105,7 @@ where
Ok(fmt_layer)
}
-// ── Per-session routing layer ───────────────────────────────────────────────
+// Per-session routing layer
/// Filesystem-safe session key. Sanitized once at capture (`on_new_span`) and
/// stashed in the span's tracing extensions, so events fired anywhere under the
@@ -148,7 +148,7 @@ impl Visit for EventVisitor {
}
}
-// Format one compact, ANSI-free firehose line. Intentionally NOT byte-identical
+// Format one compact, ANSI-free firehose line. Deliberately NOT byte-identical
// to `fmt::Layer`: its `FormatEvent` can't be reused from another layer and a
// `MakeWriter` can't see span context, so we render here. Span context is
// omitted on purpose — the file name already carries the session id.
@@ -355,7 +355,7 @@ where
}
}
-// ── Install + lifecycle ──────────────────────────────────────────────────────
+// Install + lifecycle
/// Resolve the requested debug target and install the matching firehose layer on
/// `registry`, then init the subscriber.
diff --git a/crates/codegen/kigi-log/src/hooks_log.rs b/crates/codegen/kigi-log/src/hooks_log.rs
index 785937b..89ee2d6 100644
--- a/crates/codegen/kigi-log/src/hooks_log.rs
+++ b/crates/codegen/kigi-log/src/hooks_log.rs
@@ -112,7 +112,8 @@ fn resolve_log_path() -> Option {
let default_path = || kigi_home().join("logs").join("hooks.log");
let raw = match std::env::var(ENV_HOOKS_LOG) {
Ok(val) => val,
- Err(_) => return None, // opt-in only
+ // opt-in only
+ Err(_) => return None,
};
let raw = raw.trim();
match raw {
diff --git a/crates/codegen/kigi-log/src/instrumentation.rs b/crates/codegen/kigi-log/src/instrumentation.rs
index 2980064..9b04c15 100644
--- a/crates/codegen/kigi-log/src/instrumentation.rs
+++ b/crates/codegen/kigi-log/src/instrumentation.rs
@@ -251,7 +251,8 @@ where
// registration issue when the layer is boxed as Box>.
let fmt_layer = tracing_subscriber::fmt::layer()
.json()
- .with_current_span(false) // `spans` array already carries the full ancestor list
+ // `spans` array already carries the full ancestor list
+ .with_current_span(false)
.with_ansi(false)
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
.with_thread_ids(true)
diff --git a/crates/codegen/kigi-log/src/lib.rs b/crates/codegen/kigi-log/src/lib.rs
index 4157cf8..db1fc89 100644
--- a/crates/codegen/kigi-log/src/lib.rs
+++ b/crates/codegen/kigi-log/src/lib.rs
@@ -1,10 +1,7 @@
//! Local, zero-egress observability for Kigi sessions.
//!
-//! Every sink in this crate writes to the local filesystem (under the Kigi
-//! home directory) and nothing else: the unified session log, the `--debug`
-//! firehose, subsystem file logs (memory, hooks, sampling), and the
-//! env-gated performance instrumentation. No module here opens a network
-//! connection — that property is the crate's contract.
+//! Every sink writes under the Kigi home directory and nowhere else. No module
+//! here may open a network connection — that is the crate's contract.
mod appender;
pub mod debug_log;
diff --git a/crates/codegen/kigi-log/src/memory_log.rs b/crates/codegen/kigi-log/src/memory_log.rs
index fcbbddf..cce83af 100644
--- a/crates/codegen/kigi-log/src/memory_log.rs
+++ b/crates/codegen/kigi-log/src/memory_log.rs
@@ -1,15 +1,8 @@
//! Memory system tracing target and optional file-based logging layer.
//!
-//! Provides a dedicated tracing target (`xai_memory`) with an optional
-//! file logger that writes to `~/.kigi/logs/memory.log`.
-//!
-//! ## When to use
-//!
-//! Use `tracing::info!(target: memory_log::TARGET, ...)` at memory system
-//! lifecycle points — config resolution, storage init, flush, search, etc.
-//! These events are always emitted (zero cost when the layer is absent).
-//!
-//! ## Enabling (debug builds)
+//! Emit events with `tracing::info!(target: memory_log::TARGET, ...)` at memory
+//! system lifecycle points — config resolution, storage init, flush, search.
+//! They are always emitted, at zero cost when the layer is absent.
//!
//! ```bash
//! # build with memory logging enabled, then:
@@ -63,9 +56,7 @@ mod inner {
}
}
- /// Build the memory log layer.
- ///
- /// Writes to `~/.kigi/logs/memory.log`. Filters to `xai_memory=trace`.
+ /// Writes to `~/.kigi/logs/memory.log`, filtered to `xai_memory=trace`.
/// Set `KIGI_MEMORY_LOG=0` to disable, `KIGI_MEMORY_LOG=/path` to redirect.
pub fn layer() -> Option>
where
diff --git a/crates/codegen/kigi-log/src/sampling_log.rs b/crates/codegen/kigi-log/src/sampling_log.rs
index 903b3ae..b4d81b2 100644
--- a/crates/codegen/kigi-log/src/sampling_log.rs
+++ b/crates/codegen/kigi-log/src/sampling_log.rs
@@ -60,7 +60,8 @@ where
let fmt_layer = tracing_subscriber::fmt::layer()
.json()
- .with_current_span(false) // `spans` array already carries the full ancestor list
+ // `spans` array already carries the full ancestor list
+ .with_current_span(false)
.with_ansi(false)
.with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339())
.with_target(false)
diff --git a/crates/codegen/kigi-log/src/session_ctx.rs b/crates/codegen/kigi-log/src/session_ctx.rs
index b340add..00ca623 100644
--- a/crates/codegen/kigi-log/src/session_ctx.rs
+++ b/crates/codegen/kigi-log/src/session_ctx.rs
@@ -5,16 +5,13 @@
//! `session_id` field. [`with_session_ctx`] installs that span for the
//! duration of a session's work.
-/// The `session_id` field name the debug-log firehose router keys on:
-/// `debug_log::SessionIdVisitor` stashes a `SessionId` extension on any span
-/// carrying this field — the span *name* is not load-bearing for routing. Shared
-/// so the `info_span!` here and the router in `debug_log` can't silently drift; a
-/// rename trips `session_span_exposes_router_field` below.
+/// The field name the firehose router keys on: `debug_log::SessionIdVisitor`
+/// stashes a `SessionId` extension on any span carrying this field — the span
+/// *name* is not load-bearing for routing.
pub(crate) const SESSION_ID_FIELD: &str = "session_id";
-/// Build the per-session tracing span the firehose router routes by. The field
-/// name MUST be the literal `session_id` (tracing field names can't come from a
-/// const); the test below pins it against [`SESSION_ID_FIELD`].
+/// The field name MUST be the literal `session_id` (tracing field names can't
+/// come from a const); the test below pins it against [`SESSION_ID_FIELD`].
fn session_span(session_id: &str) -> tracing::Span {
tracing::info_span!("session", session_id = %session_id)
}
@@ -30,11 +27,8 @@ pub async fn with_session_ctx(session_id: &str, fut: F)
mod tests {
use super::*;
- /// The debug-log firehose router (`debug_log`) finds the session span by its
- /// `session_id` field (not by name). That field name is a literal in
- /// `session_span` (tracing field names can't be a const), so pin it against the
- /// shared const here — a rename of either breaks this test instead of silently
- /// degrading routing to the per-pid fallback.
+ /// Without this pin, a diverging field name silently degrades routing to the
+ /// per-pid fallback instead of failing.
#[test]
fn session_span_exposes_router_field() {
// A bare registry enables every callsite, so the span has live metadata.
diff --git a/crates/codegen/kigi-log/src/unified_log.rs b/crates/codegen/kigi-log/src/unified_log.rs
index 77b020a..96999df 100644
--- a/crates/codegen/kigi-log/src/unified_log.rs
+++ b/crates/codegen/kigi-log/src/unified_log.rs
@@ -26,14 +26,12 @@ pub fn set_version(ver: &str) {
pub const LOG_DIR: &str = "logs";
const LOG_FILE: &str = "unified.jsonl";
-pub const MAX_SIZE: u64 = 5 * 1024 * 1024; // 5 MB
+pub const MAX_SIZE: u64 = 5 * 1024 * 1024;
/// ACP method name for unified log notifications.
pub const LOG_METHOD: &str = "kigi/log";
-// ---------------------------------------------------------------------------
// Log entry types
-// ---------------------------------------------------------------------------
/// Log level for a unified log entry.
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, Serialize, Deserialize)]
@@ -126,9 +124,7 @@ pub struct ClientLogEntry {
pub ctx: Option,
}
-// ---------------------------------------------------------------------------
// Writer
-// ---------------------------------------------------------------------------
struct LogWriter {
file: File,
@@ -228,9 +224,7 @@ pub fn trim_file(path: &std::path::Path) {
}
}
-// ---------------------------------------------------------------------------
// Public API
-// ---------------------------------------------------------------------------
/// Return a new timestamp string in the unified log format.
fn now_ts() -> String {
@@ -315,7 +309,7 @@ pub fn snapshot_log() -> Option> {
{
let _ = w.file.flush();
}
- // Lock released intentionally — snapshot is approximate.
+ // Lock released deliberately — snapshot is approximate.
match fs::read(&path) {
Ok(data) if !data.is_empty() => Some(data),
_ => None,
diff --git a/crates/codegen/kigi-markdown-core/src/lib.rs b/crates/codegen/kigi-markdown-core/src/lib.rs
index 559eb90..849565c 100644
--- a/crates/codegen/kigi-markdown-core/src/lib.rs
+++ b/crates/codegen/kigi-markdown-core/src/lib.rs
@@ -1,6 +1,6 @@
//! Headless markdown analysis sharing Kigi's exact `pulldown-cmark` config.
//!
-//! This crate is intentionally lean -- it depends only on `pulldown-cmark` -- so it
+//! This crate is deliberately lean -- it depends only on `pulldown-cmark` -- so it
//! can be used without pulling in the terminal-rendering stack (syntect, ratatui,
//! two-face). [`parser_options`] is the single source of truth for the parser
//! feature set, shared with `kigi-markdown` so analysis matches what Kigi
@@ -966,7 +966,7 @@ mod tests {
0,
true,
),
- // GFM: body rows may have more/fewer cells; padded/truncated, still a table (ex. 204).
+ // GFM: body rows may have more/fewer cells; `padded`/truncated, still a table (ex. 204).
(
"ragged_body_rows_still_table",
"| abc | def |\n| --- | --- |\n| bar |\n| bar | baz | boo |\n",
diff --git a/crates/codegen/kigi-markdown/benches/bench.rs b/crates/codegen/kigi-markdown/benches/bench.rs
index 01c18e9..5a48c69 100644
--- a/crates/codegen/kigi-markdown/benches/bench.rs
+++ b/crates/codegen/kigi-markdown/benches/bench.rs
@@ -8,12 +8,10 @@ use kigi_markdown::{
MarkdownStyle, StreamingMarkdownRenderer, Syntect, render_markdown_ratatui_full,
};
-/// Default style for benchmarking.
fn default_style() -> MarkdownStyle {
MarkdownStyle::default()
}
-/// Create syntect highlighter for benchmarking.
fn create_syntect() -> Syntect {
Syntect::new(include_bytes!("../assets/tokyo-night.tmTheme"))
}
@@ -94,7 +92,6 @@ This is *some inline html block*.
});
}
-/// Generate a markdown document with multiple blocks for streaming simulation.
fn generate_streaming_content(num_blocks: usize) -> String {
let mut content = String::new();
for i in 0..num_blocks {
@@ -129,7 +126,7 @@ fn generate_streaming_content(num_blocks: usize) -> String {
content
}
-/// Benchmark streaming with full re-render on each token (O(N²) baseline).
+/// O(N²) baseline: a full re-render on every token.
fn bench_streaming_full_rerender(c: &mut Criterion) {
let mut group = c.benchmark_group("streaming");
let syntect = create_syntect();
@@ -164,11 +161,9 @@ fn bench_streaming_full_rerender(c: &mut Criterion) {
group.finish();
}
-/// Generate a hyperlink-heavy markdown document.
-///
-/// Each block contains 4 inline links and 1 autolink so the renderer's
-/// link-translation path is exercised on most rendered lines. Designed
-/// to surface O(lines * link_targets) costs in `translate_link_targets`.
+/// Each block carries 4 inline links and 1 autolink, so most rendered lines hit
+/// the link-translation path — surfacing O(lines * link_targets) costs in
+/// `translate_link_targets`.
fn generate_hyperlink_content(num_blocks: usize) -> String {
let mut content = String::new();
for i in 0..num_blocks {
@@ -189,11 +184,8 @@ fn generate_hyperlink_content(num_blocks: usize) -> String {
content
}
-/// Benchmark a single full render of a hyperlink-heavy document.
-///
-/// Surfaces the cost of the parse-time `link_targets` collection plus the
-/// post-render translation step. Pair with `bench_render_markdown` to see
-/// the link-translation overhead in isolation.
+/// Parse-time `link_targets` collection plus the post-render translation step.
+/// Pair with `bench_render_markdown` to isolate the link-translation overhead.
fn bench_render_markdown_hyperlinks(c: &mut Criterion) {
let syntect = create_syntect();
let mut group = c.benchmark_group("render_markdown_hyperlinks");
@@ -219,10 +211,8 @@ fn bench_render_markdown_hyperlinks(c: &mut Criterion) {
group.finish();
}
-/// Benchmark incremental streaming of a hyperlink-heavy document.
-///
-/// Exercises `rerender_tail` repeatedly, which calls the link-translation
-/// path on the unfrozen tail every push.
+/// `rerender_tail` runs the link-translation path over the unfrozen tail on
+/// every push.
fn bench_streaming_hyperlinks_incremental(c: &mut Criterion) {
let mut group = c.benchmark_group("streaming_hyperlinks");
let syntect = create_syntect();
@@ -251,7 +241,7 @@ fn bench_streaming_hyperlinks_incremental(c: &mut Criterion) {
group.finish();
}
-/// Benchmark streaming with incremental renderer (O(N) target).
+/// O(N) target against the `bench_streaming_full_rerender` baseline.
fn bench_streaming_incremental(c: &mut Criterion) {
let mut group = c.benchmark_group("streaming");
let syntect = create_syntect();
@@ -280,12 +270,10 @@ fn bench_streaming_incremental(c: &mut Criterion) {
group.finish();
}
-/// Generate a math-heavy markdown document.
-///
-/// Each block exercises all four delimiter forms (`$...$`, `$$...$$`,
-/// `\(...\)`, `\[...\]`) plus the expensive converter paths: scripts,
-/// fractions, roots, symbol lookups, alphabets, and multi-row environments
-/// (aligned / pmatrix / cases) that go through the MathBox 2D layout.
+/// Each block covers all four delimiter forms (`$...$`, `$$...$$`, `\(...\)`,
+/// `\[...\]`) plus the expensive converter paths: scripts, fractions, roots,
+/// symbol lookups, alphabets, and the multi-row environments (aligned / pmatrix
+/// / cases) that go through the MathBox 2D layout.
fn generate_math_content(num_blocks: usize) -> String {
let mut content = String::new();
for i in 0..num_blocks {
@@ -311,10 +299,8 @@ fn generate_math_content(num_blocks: usize) -> String {
content
}
-/// Benchmark a single full render of a math-heavy document.
-///
-/// Surfaces the cost of the LaTeX → Unicode converter plus the parse-time
-/// `\(...\)` / `\[...\]` source scans and block replacements.
+/// The LaTeX → Unicode converter plus the parse-time `\(...\)` / `\[...\]`
+/// source scans and block replacements.
fn bench_render_markdown_math(c: &mut Criterion) {
let syntect = create_syntect();
let mut group = c.benchmark_group("render_markdown_math");
@@ -340,11 +326,8 @@ fn bench_render_markdown_math(c: &mut Criterion) {
group.finish();
}
-/// Benchmark incremental streaming of a math-heavy document.
-///
-/// Exercises the streaming hot path the `MAX_MATH_SOURCE_LEN` guard
-/// protects: every push re-renders the unfrozen tail, re-running the math
-/// scans and conversions on it.
+/// The hot path the `MAX_MATH_SOURCE_LEN` guard protects: every push re-renders
+/// the unfrozen tail, re-running the math scans and conversions over it.
fn bench_streaming_math_incremental(c: &mut Criterion) {
let mut group = c.benchmark_group("streaming_math");
let syntect = create_syntect();
@@ -373,7 +356,7 @@ fn bench_streaming_math_incremental(c: &mut Criterion) {
group.finish();
}
-/// Generate a document with plain URLs in prose (no markdown link syntax).
+/// Bare URLs in prose, with no markdown link syntax to pick them up.
fn generate_plain_url_content(num_blocks: usize) -> String {
let mut content = String::new();
for i in 0..num_blocks {
@@ -384,12 +367,8 @@ fn generate_plain_url_content(num_blocks: usize) -> String {
content
}
-/// Benchmark streaming + finish() of a plain-URL-heavy document.
-///
-/// Exercises the `detect_plain_urls` scan, which after the multi-line-URL
-/// fix runs inside both `rerender_tail` (every `push_and_render`) and
-/// `finish()`. Bench numbers from this point forward are not comparable
-/// to historical runs that measured the prior `finish()`-only path.
+/// The `detect_plain_urls` scan runs inside both `rerender_tail` (on every
+/// `push_and_render`) and `finish()`, so this measures both.
fn bench_streaming_plain_urls_incremental(c: &mut Criterion) {
let mut group = c.benchmark_group("streaming_plain_urls");
let syntect = create_syntect();
@@ -416,11 +395,9 @@ fn bench_streaming_plain_urls_incremental(c: &mut Criterion) {
group.finish();
}
-/// Generate a realistic nested YAML document of roughly `num_lines` lines.
-///
-/// Produces nested keys, lists, and scalars (the kind of config an LLM streams
-/// into a single fenced block) so the syntect highlighter does real work per
-/// line rather than trivial whitespace.
+/// Nested keys, lists, and scalars — the kind of config an LLM streams into a
+/// single fenced block — so the syntect highlighter does real work per line
+/// rather than trivial whitespace.
fn generate_yaml_lines(num_lines: usize) -> Vec {
let mut lines = Vec::with_capacity(num_lines);
let mut i = 0usize;
@@ -450,13 +427,11 @@ fn generate_yaml_lines(num_lines: usize) -> Vec {
lines
}
-/// Benchmark streaming a SINGLE open ```yaml fenced block line-by-line WITHOUT
-/// ever closing the fence.
-///
+/// A single yaml fence that is opened and never closed, streamed line by line.
/// This reproduces the UI-freeze pathology: while the fence is open the block
/// never checkpoints, so every `push_and_render` re-highlights the whole tail.
-/// With the incremental open-code cache, per-line cost should stay roughly flat
-/// in block size instead of growing linearly (overall O(N) instead of O(N²)).
+/// The open-code cache should keep per-line cost roughly flat in block size
+/// instead of growing linearly (overall O(N) instead of O(N²)).
fn bench_streaming_open_yaml_incremental(c: &mut Criterion) {
let mut group = c.benchmark_group("streaming_open_yaml");
let syntect = create_syntect();
@@ -464,16 +439,12 @@ fn bench_streaming_open_yaml_incremental(c: &mut Criterion) {
for num_lines in [100, 500, 1081] {
let lines = generate_yaml_lines(num_lines);
- // Only the cache-on path ships; the parameter is just the line count
- // (no A/B baseline here — the no-cache baseline was measured ad hoc and
- // is not committed).
group.bench_with_input(
BenchmarkId::from_parameter(num_lines),
&lines,
|b, lines| {
b.iter(|| {
let mut renderer = StreamingMarkdownRenderer::new(default_style(), true);
- // Open the fence but never close it.
renderer.push_and_render("```yaml\n", Some(&syntect));
let mut total_lines = 0;
for line in lines.iter() {
@@ -497,7 +468,6 @@ fn bench_streaming_open_yaml_incremental(c: &mut Criterion) {
fn generate_fence_in_list_content(trailing_words: usize) -> String {
let mut s = String::new();
s.push_str("Here is where you're stuck:\n\n");
- // Two list items embedding closed scala fences (the pathological shape).
for i in 0..2 {
s.push_str(&format!(
"- **[File{i}.scala:{}](https://example.com/f{i})** (domain)\n \
@@ -513,7 +483,6 @@ fn generate_fence_in_list_content(trailing_words: usize) -> String {
100 + i,
));
}
- // Continued streaming within the same (never-closing) list context.
for w in 0..trailing_words {
if w % 12 == 0 {
s.push_str("\n- item: ");
diff --git a/crates/codegen/kigi-markdown/bin/md_table_test.rs b/crates/codegen/kigi-markdown/bin/md_table_test.rs
index 26f78f0..e8bb933 100644
--- a/crates/codegen/kigi-markdown/bin/md_table_test.rs
+++ b/crates/codegen/kigi-markdown/bin/md_table_test.rs
@@ -31,7 +31,7 @@ use kigi_markdown::{
};
use kigi_ratatui_textarea::{TextArea, TextAreaState};
-// ── Tokyo Night Storm palette (matches kigi-tui) ──────────────────────
+// Tokyo Night Storm palette (matches kigi-tui)
#[path = "playground_common.rs"]
mod playground_common;
@@ -39,7 +39,7 @@ use playground_common::{get_syntect, md_style};
const MD_STYLE: MarkdownStyle = md_style(anstyle::Style::new());
-// ── Compute minimum render width ─────────────────────────────────────────────
+// Compute minimum render width
/// Minimum width = 4k + 1, where k = number of table columns.
/// Columns = max(`|` count per line) - 1 (the outer pipes are borders).
@@ -58,7 +58,7 @@ fn min_render_width(source: &str) -> usize {
}
}
-// ── Render helpers ───────────────────────────────────────────────────────────
+// Render helpers
/// One-shot full render at a given width.
fn render_full(source: &str, width: usize) -> Vec> {
@@ -84,7 +84,7 @@ fn render_streaming(source: &str, width: usize) -> Vec> {
renderer.view().lines.to_vec()
}
-// ── App state ────────────────────────────────────────────────────────────────
+// App state
const DEFAULT_MARKDOWN: &str = "\
| A | B | C |
@@ -145,7 +145,7 @@ impl App {
}
}
-// ── Main ─────────────────────────────────────────────────────────────────────
+// Main
fn main() -> io::Result<()> {
// Terminal setup
@@ -164,14 +164,14 @@ fn main() -> io::Result<()> {
let ev = event::read()?;
match &ev {
- // ── Global: Ctrl-Q quits from anywhere ──
+ // Global: Ctrl-Q quits from anywhere
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::CONTROL,
..
}) => break,
- // ── Focused: defocus on Esc or Tab ──
+ // Focused: defocus on Esc or Tab
Event::Key(KeyEvent {
code: KeyCode::Esc | KeyCode::Tab,
..
@@ -179,7 +179,7 @@ fn main() -> io::Result<()> {
app.textarea_focused = false;
}
- // ── Unfocused: quit on q, Ctrl-C, Ctrl-D ──
+ // Unfocused: quit on q, Ctrl-C, Ctrl-D
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
..
@@ -190,7 +190,7 @@ fn main() -> io::Result<()> {
..
}) if !app.textarea_focused => break,
- // ── Unfocused: Space or Enter to re-focus ──
+ // Unfocused: Space or Enter to re-focus
Event::Key(KeyEvent {
code: KeyCode::Char(' ') | KeyCode::Enter,
..
@@ -198,7 +198,7 @@ fn main() -> io::Result<()> {
app.textarea_focused = true;
}
- // ── Unfocused: width controls ──
+ // Unfocused: width controls
Event::Key(KeyEvent {
code: KeyCode::Char('h') | KeyCode::Left,
..
@@ -224,13 +224,13 @@ fn main() -> io::Result<()> {
app.adjust_width(5);
}
- // ── Focused: forward keys to textarea, live re-render ──
+ // Focused: forward keys to textarea, live re-render
Event::Key(key) if app.textarea_focused => {
app.textarea.input(*key);
app.rerender();
}
- // ── Focused: forward mouse to textarea ──
+ // Focused: forward mouse to textarea
Event::Mouse(mouse) if app.textarea_focused => {
app.textarea
.handle_mouse(*mouse, app.textarea_area, app.textarea_state);
@@ -248,7 +248,7 @@ fn main() -> io::Result<()> {
Ok(())
}
-// ── Drawing ──────────────────────────────────────────────────────────────────
+// Drawing
fn draw(f: &mut ratatui::Frame, app: &mut App) {
let size = f.area();
@@ -259,7 +259,8 @@ fn draw(f: &mut ratatui::Frame, app: &mut App) {
// to the textarea.
let render_w = app.render_width as u16;
- let full_height = wrapped_line_count(&app.full_lines, render_w).max(1) + 2; // +2 for border
+ // +2 for border
+ let full_height = wrapped_line_count(&app.full_lines, render_w).max(1) + 2;
let stream_height = wrapped_line_count(&app.streaming_lines, render_w).max(1) + 2;
// Detect mismatches between full and streaming
@@ -281,7 +282,7 @@ fn draw(f: &mut ratatui::Frame, app: &mut App) {
])
.split(size);
- // ── Header ──
+ // Header
let focus_indicator = if app.textarea_focused {
Span::styled(
" EDITING ",
@@ -339,7 +340,7 @@ fn draw(f: &mut ratatui::Frame, app: &mut App) {
]);
f.render_widget(header, chunks[0]);
- // ── Textarea ──
+ // Textarea
let border_color = if app.textarea_focused {
Color::Green
} else {
@@ -367,11 +368,11 @@ fn draw(f: &mut ratatui::Frame, app: &mut App) {
f.set_cursor_position((cx, cy));
}
- // ── Full render panel ──
+ // Full render panel
let full_title = format!(" full: {} ", app.render_width);
render_panel(f, chunks[2], &full_title, &app.full_lines, render_w, false);
- // ── Streaming render panel ──
+ // Streaming render panel
let stream_title = format!(" stream: {} ", app.render_width);
render_panel(
f,
@@ -403,7 +404,8 @@ fn wrapped_line_count(lines: &[Line<'_>], width: u16) -> u16 {
if display_w == 0 {
1u16
} else {
- display_w.div_ceil(w) as u16 // ceil division
+ // ceil division
+ display_w.div_ceil(w) as u16
}
})
.sum()
diff --git a/crates/codegen/kigi-markdown/fuzz/fuzz_targets/render_all.rs b/crates/codegen/kigi-markdown/fuzz/fuzz_targets/render_all.rs
index ba3d5af..567c1c4 100644
--- a/crates/codegen/kigi-markdown/fuzz/fuzz_targets/render_all.rs
+++ b/crates/codegen/kigi-markdown/fuzz/fuzz_targets/render_all.rs
@@ -11,19 +11,16 @@ fuzz_target!(|data: &[u8]| {
return;
};
- // Full render: pretty / non-pretty
for pretty in [true, false] {
let _ = render_markdown_ratatui_full(s, STYLE, pretty, None);
}
- // Streaming with rotating chunk sizes: pretty / non-pretty
for pretty in [true, false] {
let mut r = StreamingMarkdownRenderer::new(STYLE, pretty);
let mut pos = 0;
let mut ci = 0;
while pos < s.len() {
let mut end = (pos + CHUNK_SIZES[ci]).min(s.len());
- // snap to char boundary
while end < s.len() && !s.is_char_boundary(end) {
end += 1;
}
diff --git a/crates/codegen/kigi-markdown/src/buffers.rs b/crates/codegen/kigi-markdown/src/buffers.rs
index 9acdc93..c396b3a 100644
--- a/crates/codegen/kigi-markdown/src/buffers.rs
+++ b/crates/codegen/kigi-markdown/src/buffers.rs
@@ -1,7 +1,4 @@
//! Reusable buffers and internal data types for markdown parsing and rendering.
-//!
-//! This module contains all the intermediate data structures used by
-//! MarkdownHighlighter during parsing and rendering.
use std::ops::Range;
@@ -9,7 +6,6 @@ use anstyle::Style as AnsiStyle;
use ratatui::text::{Line, Span};
use syntect::highlighting::Style as SyntectStyle;
-/// A range of text with optional styling.
#[derive(Debug, Clone)]
pub struct Highlight {
pub style: Option,
@@ -18,12 +14,11 @@ pub struct Highlight {
/// Syntax-highlighted code block replacement.
///
-/// Stores the raw highlighted spans per line (intermediate representation).
-/// This allows rendering to either ANSI strings or ratatui Lines on demand.
+/// Spans are kept in their intermediate form so the block can be rendered to
+/// either ANSI strings or ratatui Lines on demand.
#[derive(Debug, Clone)]
pub struct Replace {
- /// Raw highlighted spans per line: Vec<(style, text)>.
- /// Each inner Vec represents one line of the code block.
+ /// One inner Vec per line of the code block.
pub highlighted: Vec>,
/// Source byte range this replaces.
pub range: Range,
@@ -37,7 +32,6 @@ pub struct Replace {
pub struct LinkTarget {
/// Source byte range of the *link text* (not the full `[text](url)` span).
pub source_range: Range,
- /// Destination URL.
pub url: String,
/// Monotonically increasing identifier assigned during parsing.
pub id: u32,
@@ -118,12 +112,10 @@ impl StyledCell {
Self { spans: Vec::new() }
}
- /// Get plain text content (for width calculation).
pub fn plain_text(&self) -> String {
self.spans.iter().map(|s| s.text.as_str()).collect()
}
- /// Clear the cell content.
pub fn clear(&mut self) {
self.spans.clear();
}
@@ -134,15 +126,10 @@ impl StyledCell {
pub struct TableState {
/// Column alignments from the table header.
pub alignments: Vec,
- /// Header row cells.
pub header: Vec,
- /// Body rows (each row is a Vec of styled cells).
pub rows: Vec>,
- /// Current row being built.
pub current_row: Vec,
- /// Current cell content being accumulated.
pub current_cell: StyledCell,
- /// Current style state for the cell.
pub cell_bold: bool,
pub cell_italic: bool,
pub cell_code: bool,
@@ -151,7 +138,6 @@ pub struct TableState {
/// is set produce link-tagged `CellSpan`s so the table renderer can
/// apply link styling and emit `HyperlinkTarget`s.
pub cell_link: Option<(String, u32)>,
- /// Whether we're in the header section.
pub in_header: bool,
/// Source byte range of the entire table.
pub range: Range,
@@ -194,11 +180,9 @@ impl TableState {
/// `HyperlinkTarget`.
#[derive(Debug, Clone)]
pub struct TableHyperlink {
- /// Index within `TableReplace::styled_lines`.
pub line_offset: usize,
- /// Column range (display cells) on that line.
+ /// Column range in display cells, not bytes.
pub column_range: Range,
- /// Destination URL.
pub url: String,
/// Stable identifier shared with the paragraph link path.
pub id: u32,
@@ -245,7 +229,6 @@ pub struct MermaidReplace {
pub range: Range,
}
-/// Calculate the display width of a string (accounting for Unicode).
pub fn unicode_display_width(s: &str) -> usize {
use unicode_width::UnicodeWidthStr;
s.width()
@@ -287,8 +270,10 @@ pub enum RenderEventKind {
Mermaid = 3,
}
-/// Render event: marks where a highlight/replace/table starts or ends.
-/// Derives Ord for sorting by (pos, kind, index, is_end).
+/// Marks where a highlight/replace/table starts or ends.
+///
+/// Field order is load-bearing: the derived `Ord` sorts the event queue by
+/// (pos, kind, index, is_end).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct RenderEvent {
pub pos: usize,
@@ -301,22 +286,8 @@ pub struct RenderEvent {
///
/// All vectors are cleared (keeping capacity) between renders, eliminating
/// allocation overhead in the streaming hot path.
-///
-/// # Buffer Categories
-///
-/// **Parse output buffers** - populated during `run()`, read-only during `render()`:
-/// - `highlights`: Style ranges for inline formatting
-/// - `replaces`: Syntax-highlighted code blocks
-/// - `transforms`: Character substitutions (e.g., bullets)
-/// - `untagged_code_ranges`: Code blocks without language tags
-/// - `table_replaces`: Formatted table replacements
-///
-/// **Render scratch buffers** - temporary storage during `render()`:
-/// - `render_events`: Sorted event queue for the render loop
-/// - `current_spans`: Building current line's spans
-/// - `active_highlights`: Stack of active highlight indices
pub struct MarkdownBuffers {
- // Parse output buffers (written by run(), read by render())
+ // Parse output buffers: written by run(), read-only during render().
pub highlights: Vec,
pub replaces: Vec,
pub transforms: Vec,
@@ -327,7 +298,7 @@ pub struct MarkdownBuffers {
/// Closed fenced code blocks, in document order (see [`CodeBlockMeta`]).
pub code_blocks: Vec,
- // Render scratch buffers (used only during render())
+ // Render scratch buffers: used only during render().
pub render_events: Vec,
pub current_spans: Vec>,
pub active_highlights: Vec,
diff --git a/crates/codegen/kigi-markdown/src/checkpoint.rs b/crates/codegen/kigi-markdown/src/checkpoint.rs
index 1d73b90..59c34b9 100644
--- a/crates/codegen/kigi-markdown/src/checkpoint.rs
+++ b/crates/codegen/kigi-markdown/src/checkpoint.rs
@@ -1,8 +1,8 @@
//! Checkpoint types for incremental markdown rendering.
//!
-//! This module defines types for identifying stable boundaries in markdown text
-//! where rendered output can be "frozen" and cached. Content before a checkpoint
-//! will not change regardless of what text is appended after it.
+//! A checkpoint marks a stable boundary in markdown text where rendered output
+//! can be "frozen" and cached: content before it will not change regardless of
+//! what text is appended after it.
//!
//! # Design
//!
diff --git a/crates/codegen/kigi-markdown/src/colors.rs b/crates/codegen/kigi-markdown/src/colors.rs
index d8c8970..b33a443 100644
--- a/crates/codegen/kigi-markdown/src/colors.rs
+++ b/crates/codegen/kigi-markdown/src/colors.rs
@@ -165,7 +165,7 @@ pub fn set_color_level(level: ColorLevel) -> Result<(), ColorLevel> {
/// Convert an `anstyle::Color` to the appropriate level based on terminal support.
///
/// This will downgrade colors as needed:
-/// - TrueColor terminals: pass through unchanged
+/// - TrueColor terminals: pass through `unchanged`
/// - 256-color terminals: RGB colors are converted to closest ANSI 256 color
/// - Basic terminals: colors are converted to closest ANSI 16 color
/// - No color: returns None
@@ -234,22 +234,26 @@ mod tests {
// Medium gray
let result = rgb_to_ansi256(RgbColor(128, 128, 128));
- assert!(result.index() >= 232); // Should be in grayscale range
+ // Should be in grayscale range
+ assert!(result.index() >= 232);
}
#[test]
fn test_rgb_to_ansi256_colors() {
// Pure red
let result = rgb_to_ansi256(RgbColor(255, 0, 0));
- assert_eq!(result.index(), 196); // Bright red in the cube
+ // Bright red in the cube
+ assert_eq!(result.index(), 196);
// Pure green
let result = rgb_to_ansi256(RgbColor(0, 255, 0));
- assert_eq!(result.index(), 46); // Bright green in the cube
+ // Bright green in the cube
+ assert_eq!(result.index(), 46);
// Pure blue
let result = rgb_to_ansi256(RgbColor(0, 0, 255));
- assert_eq!(result.index(), 21); // Bright blue in the cube
+ // Bright blue in the cube
+ assert_eq!(result.index(), 21);
}
#[test]
diff --git a/crates/codegen/kigi-markdown/src/hyperlinks.rs b/crates/codegen/kigi-markdown/src/hyperlinks.rs
index 58fd40b..995645b 100644
--- a/crates/codegen/kigi-markdown/src/hyperlinks.rs
+++ b/crates/codegen/kigi-markdown/src/hyperlinks.rs
@@ -60,7 +60,7 @@ pub(crate) struct ChunkLinkRange {
/// transform's replacement string. Both endpoints (start/end) clamp the
/// same direction, so a link whose endpoint straddles a transform produces
/// a column range that excludes the straddling bytes. This is intentional
-/// rather than precise — a future transform that intentionally rewrites
+/// rather than precise — a future transform that deliberately rewrites
/// link text should add a typed mapping instead of relying on this clamp.
pub(crate) fn source_to_chunk_offset(
src_pos: usize,
@@ -231,7 +231,7 @@ mod hyperlink_tests {
/// one whose `column_range` slices to `expected_slice` in the
/// rendered output. Since `render_markdown_ratatui_full` now also
/// emits a url_scan target for the pretty-mode `(url)` suffix, tests
- /// that previously checked `hyperlinks.len() == 1` must explicitly
+ /// that earlier checked `hyperlinks.len() == 1` must explicitly
/// pick the parser-produced entry.
fn parser_link_text<'a>(
out: &'a crate::output::MarkdownRenderOutput,
@@ -448,7 +448,7 @@ mod hyperlink_tests {
let view = renderer.view();
// Compare on `(url, line_index, column_range)` — ids are
- // intentionally independent between the two code paths (full
+ // Deliberately independent between the two code paths (full
// re-render restarts id counters; streaming preserves continuity).
let extract = |hs: &[HyperlinkTarget]| -> Vec<(String, usize, std::ops::Range)> {
let mut v: Vec<_> = hs
@@ -623,7 +623,7 @@ mod hyperlink_tests {
}
/// Paragraph links must keep the `link_text` foreground color even when
- /// the `text` style sets its own foreground. Previously the parser
+ /// the `text` style sets its own foreground. earlier the parser
/// pushed `ms.text` as a highlight after the link_text highlight whenever
/// no `Heading`/`Emphasis`/`Strong`/`Strikethrough` ancestor was present
/// — and `merge_styles` lets the later fg color win, so `ms.text`'s color
diff --git a/crates/codegen/kigi-markdown/src/latex/commands.rs b/crates/codegen/kigi-markdown/src/latex/commands.rs
index d426f31..b6ce3a2 100644
--- a/crates/codegen/kigi-markdown/src/latex/commands.rs
+++ b/crates/codegen/kigi-markdown/src/latex/commands.rs
@@ -205,7 +205,7 @@ fn script_atom_is_wordlike(atom: &str, rendered: &str) -> bool {
fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode: Mode) {
let name = cursor.read_command_name();
match name {
- // ── Structure ────────────────────────────────────────────────────
+ // Structure
"" => out.push('\\'),
"\\" => out.push('\n'),
"begin" => render_environment(cursor, out, depth, mode),
@@ -232,7 +232,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
}
}
- // ── Fractions / binomials / roots ────────────────────────────────
+ // Fractions / binomials / roots
"frac" | "dfrac" | "tfrac" | "cfrac" => {
let num = take_brace_arg(cursor).map(|a| render_atom(a, depth, mode));
let den = take_brace_arg(cursor).map(|a| render_atom(a, depth, mode));
@@ -261,7 +261,8 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
cursor.bump();
}
let idx = &cursor.src[start..cursor.pos];
- cursor.bump(); // consume `]`
+ // consume `]`
+ cursor.bump();
Some(render_atom(idx, depth, mode))
} else {
None
@@ -290,7 +291,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
}
}
- // ── Boxes (frame dropped; content preserved) ─────────────────────
+ // Boxes (frame dropped; content preserved)
"boxed" => {
if let Some(arg) = take_brace_arg(cursor) {
out.push_str(&render_atom(arg, depth, mode));
@@ -302,7 +303,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
}
}
- // ── Text / alphabets ─────────────────────────────────────────────
+ // Text / alphabets
"text" | "textrm" | "textit" | "textbf" | "textsf" | "texttt" | "textnormal" | "mbox"
| "hbox" => {
if let Some(arg) = take_brace_arg(cursor) {
@@ -321,7 +322,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
render_mapped_alphabet(cursor, out, depth, mode, map_mathbf)
}
- // ── Accents (combining marks) ────────────────────────────────────
+ // Accents (combining marks)
"hat" | "widehat" => render_accent(cursor, out, depth, mode, '\u{0302}'),
"bar" | "overline" => render_accent(cursor, out, depth, mode, '\u{0304}'),
"tilde" | "widetilde" => render_accent(cursor, out, depth, mode, '\u{0303}'),
@@ -335,7 +336,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
"mathring" => render_accent(cursor, out, depth, mode, '\u{030A}'),
"underline" => render_accent(cursor, out, depth, mode, '\u{0332}'),
- // ── Negation ─────────────────────────────────────────────────────
+ // Negation
"not" => {
if let Some(atom) = cursor.read_atom() {
let rendered = render_atom(atom, depth, mode);
@@ -359,7 +360,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
}
}
- // ── Decorations rendered as base + script ────────────────────────
+ // Decorations rendered as base + script
"overset" | "stackrel" => {
let over = take_brace_arg(cursor).map(|a| render_atom(a, depth, mode));
let base = take_brace_arg(cursor).map(|a| render_atom(a, depth, mode));
@@ -385,7 +386,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
}
}
- // ── Modular arithmetic ───────────────────────────────────────────
+ // Modular arithmetic
"pmod" => {
if let Some(arg) = take_brace_arg(cursor) {
if !out.at_line_start() && !out.ends_with_space() {
@@ -401,7 +402,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
out.push_str("mod ");
}
- // ── Spacing ──────────────────────────────────────────────────────
+ // Spacing
"," | ";" | ":" | ">" | " " | "space" | "thinspace" | "medspace" | "thickspace"
| "enspace" => {
if !out.at_line_start() && !out.ends_with_space() {
@@ -412,7 +413,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
"qquad" => out.push_str(" "),
"!" | "negthinspace" | "negmedspace" | "negthickspace" => {}
- // ── No-ops (sizing/styling/structure hints) ──────────────────────
+ // No-ops (sizing/styling/structure hints)
"limits" | "nolimits" | "displaystyle" | "textstyle" | "scriptstyle"
| "scriptscriptstyle" | "big" | "Big" | "bigg" | "Bigg" | "bigl" | "Bigl" | "biggl"
| "Biggl" | "bigr" | "Bigr" | "biggr" | "Biggr" | "bigm" | "Bigm" | "biggm" | "Biggm"
@@ -425,7 +426,7 @@ fn render_command(cursor: &mut Cursor<'_>, out: &mut MathBox, depth: usize, mode
}
}
- // ── Symbol table ─────────────────────────────────────────────────
+ // Symbol table
_ => {
if let Some(sym) = symbol(name) {
out.push_str(sym);
diff --git a/crates/codegen/kigi-markdown/src/latex/cursor.rs b/crates/codegen/kigi-markdown/src/latex/cursor.rs
index b680b66..49d6cb8 100644
--- a/crates/codegen/kigi-markdown/src/latex/cursor.rs
+++ b/crates/codegen/kigi-markdown/src/latex/cursor.rs
@@ -1,6 +1,5 @@
//! Byte cursor over TeX source.
-/// Byte cursor over the TeX source.
pub(super) struct Cursor<'a> {
pub(super) src: &'a str,
pub(super) pos: usize,
@@ -24,7 +23,7 @@ impl<'a> Cursor<'a> {
/// Consume `\command` (alphabetic name) or `\`; the leading
/// backslash must already be consumed. Returns the command name.
///
- /// Unlike TeX we do NOT consume trailing whitespace: the caller's
+ /// Unlike TeX, trailing whitespace is NOT consumed: the caller's
/// whitespace collapsing keeps `\to 0` rendering as `→ 0`.
pub(super) fn read_command_name(&mut self) -> &'a str {
let start = self.pos;
@@ -43,7 +42,6 @@ impl<'a> Cursor<'a> {
}
}
- /// Skip whitespace (TeX collapses it; meaning comes from commands).
pub(super) fn skip_ws(&mut self) {
while matches!(self.peek(), Some(c) if c.is_whitespace()) {
self.bump();
@@ -59,7 +57,7 @@ impl<'a> Cursor<'a> {
while let Some(ch) = self.bump() {
match ch {
'\\' => {
- // Skip escaped char so `\{`/`\}` don't affect depth.
+ // Skip the escaped char so `\{`/`\}` don't affect depth.
self.bump();
}
'{' => depth += 1,
diff --git a/crates/codegen/kigi-markdown/src/latex/environments.rs b/crates/codegen/kigi-markdown/src/latex/environments.rs
index fa3c9a9..5f08af7 100644
--- a/crates/codegen/kigi-markdown/src/latex/environments.rs
+++ b/crates/codegen/kigi-markdown/src/latex/environments.rs
@@ -19,8 +19,8 @@ pub(super) fn render_environment(
};
let env_name = env_name.trim().trim_end_matches('*');
- // Capture body source until the matching `\end{name}`, tracking nesting
- // of same-named environments. Scans raw source from the cursor.
+ // Scan raw source for the matching `\end{name}`, tracking nesting of
+ // same-named environments.
let body_start = cursor.pos;
let mut body_end = cursor.src.len();
let mut resume = cursor.src.len();
@@ -38,8 +38,8 @@ pub(super) fn render_environment(
} else if command_at(after_bs, "end") {
"end".len()
} else {
- // Not begin/end: skip the backslash and the char after it (so
- // `\\` and `\{` never confuse the scan).
+ // Skip the backslash and the char after it so that `\\` and `\{`
+ // never confuse the scan.
let skip = after_bs.chars().next().map_or(0, char::len_utf8);
search = bs_pos + 1 + skip.max(1);
continue;
@@ -66,7 +66,7 @@ pub(super) fn render_environment(
cursor.pos = resume;
let mut body = &cursor.src[body_start..body_end.min(cursor.src.len())];
- // Optional column spec for array environments: `\begin{array}{ll}`.
+ // Discard the optional column spec: `\begin{array}{ll}`.
if env_name == "array" || env_name == "alignat" {
let mut probe = Cursor::new(body);
probe.skip_ws();
@@ -92,9 +92,9 @@ fn command_at(rest: &str, word: &str) -> bool {
/// Split an environment body into rows (`\\`) and cells (`&`) at brace and
/// environment depth 0, render each cell, then lay the rows out according to
-/// the environment. Returns one string per visual row; the caller attaches
-/// them as a box. In `flat` mode, matrix/cases environments render as a
-/// single row with `; ` between matrix rows.
+/// the environment. Returns one string per visual row. In `flat` mode,
+/// matrix/cases environments collapse to a single row with `; ` between
+/// matrix rows.
fn env_rows_to_strings(
body: &str,
env_name: &str,
@@ -130,7 +130,7 @@ fn env_rows_to_strings(
env_depth = env_depth.saturating_sub(1);
}
// Skip the backslash plus the char after it so escaped
- // delimiters (`\&`, `\{`, `\}`) never affect depth/splits.
+ // delimiters (`\&`, `\{`, `\}`) never affect depth or splits.
let skip = rest.chars().next().map_or(0, char::len_utf8);
i += 1 + skip.max(1);
continue;
@@ -148,7 +148,6 @@ fn env_rows_to_strings(
row.push(body[cell_start.min(bytes.len())..].to_string());
rows.push(row);
- // Render each cell, drop fully-empty rows.
let mut rendered_rows: Vec> = rows
.into_iter()
.map(|cells| {
@@ -177,15 +176,14 @@ fn env_rows_to_strings(
let n_rows = rendered_rows.len();
if is_matrix {
- // Flat (inline) mode: one row, single delimiter pair, rows joined
- // with `; ` — `(1 2; 3 4)`.
if flat {
let inner = rendered_rows
.iter()
.map(|cells| cells.join(" "))
.collect::>()
.join("; ");
- // Single-row delimiter pair; plain `matrix` has none (' ').
+ // Ask for the single-row form; plain `matrix` has no delimiter
+ // and reports ' '.
let (l, r) = matrix_delims(env_name, 0, 1);
let mut s = String::new();
if l != ' ' {
@@ -197,7 +195,6 @@ fn env_rows_to_strings(
}
return vec![s];
}
- // Pad columns to equal width so rows align.
let n_cols = rendered_rows.iter().map(Vec::len).max().unwrap_or(0);
let mut widths = vec![0usize; n_cols];
for cells in &rendered_rows {
@@ -243,9 +240,7 @@ fn env_rows_to_strings(
.collect()
} else {
// aligned/align/gather/split/equation/…: `&` is an invisible
- // alignment marker; rejoin cells with a single space. One string per
- // row; the caller's box attachment (or flat `; ` join) handles the
- // rest.
+ // alignment marker, so cells rejoin with a single space.
rendered_rows
.iter()
.map(|cells| {
@@ -255,7 +250,7 @@ fn env_rows_to_strings(
.cloned()
.collect::>()
.join(" ");
- // Collapse any double spaces introduced around markers.
+ // Empty alignment cells leave runs of spaces behind.
while s.contains(" ") {
s = s.replace(" ", " ");
}
diff --git a/crates/codegen/kigi-markdown/src/latex/math_box.rs b/crates/codegen/kigi-markdown/src/latex/math_box.rs
index 29a72cd..e5dfbb5 100644
--- a/crates/codegen/kigi-markdown/src/latex/math_box.rs
+++ b/crates/codegen/kigi-markdown/src/latex/math_box.rs
@@ -124,7 +124,7 @@ impl MathBox {
self.lines.push(String::new());
}
}
- // Place the box rows, left-padded to the attach column.
+ // Place the box rows, left-`padded` to the attach column.
for (i, row) in rows.iter().enumerate() {
let target = self.anchor - box_anchor + i;
let line = &mut self.lines[target];
diff --git a/crates/codegen/kigi-markdown/src/latex/mod.rs b/crates/codegen/kigi-markdown/src/latex/mod.rs
index 953db5b..99a5b62 100644
--- a/crates/codegen/kigi-markdown/src/latex/mod.rs
+++ b/crates/codegen/kigi-markdown/src/latex/mod.rs
@@ -79,7 +79,6 @@ pub(crate) fn latex_to_unicode_display(src: &str) -> Option> {
Some(lines)
}
-/// Run the converter and return the output lines.
fn convert(src: &str, flat: bool) -> Vec {
let mut cursor = Cursor::new(src);
let mut out = MathBox::new(flat);
diff --git a/crates/codegen/kigi-markdown/src/latex/symbols.rs b/crates/codegen/kigi-markdown/src/latex/symbols.rs
index 8494300..c31fb91 100644
--- a/crates/codegen/kigi-markdown/src/latex/symbols.rs
+++ b/crates/codegen/kigi-markdown/src/latex/symbols.rs
@@ -1,4 +1,10 @@
//! Character and symbol mapping tables.
+//!
+//! In the `map_math*` alphabets the explicit letter arms come first on
+//! purpose: those letters were encoded as Letterlike Symbols before the
+//! contiguous Mathematical Alphanumeric blocks existed, so their block slots
+//! are unassigned and the arithmetic arms below would yield a reserved
+//! codepoint.
pub(super) fn to_superscript(c: char) -> Option {
Some(match c {
diff --git a/crates/codegen/kigi-markdown/src/latex/tests.rs b/crates/codegen/kigi-markdown/src/latex/tests.rs
index 6afb853..5fd65a5 100644
--- a/crates/codegen/kigi-markdown/src/latex/tests.rs
+++ b/crates/codegen/kigi-markdown/src/latex/tests.rs
@@ -29,10 +29,10 @@ fn subscripts_map_to_unicode() {
#[test]
fn script_fallback_uses_parens() {
- // φ has no superscript form → fall back to ^(...)
+ // Greek letters have no superscript forms; a multi-char run falls back to
+ // ^(...) while a lone char keeps the bare marker.
assert_eq!(inline("x^{\\alpha\\beta}"), "x^(αβ)");
assert_eq!(inline("x^\\alpha"), "x^α");
- // Single unmappable subscript char.
assert_eq!(inline("a_q"), "a_q");
}
diff --git a/crates/codegen/kigi-markdown/src/latex_delimiters.rs b/crates/codegen/kigi-markdown/src/latex_delimiters.rs
index 3a72770..d6942f9 100644
--- a/crates/codegen/kigi-markdown/src/latex_delimiters.rs
+++ b/crates/codegen/kigi-markdown/src/latex_delimiters.rs
@@ -24,7 +24,7 @@
//! located and the ASCII whitespace immediately inside the delimiters is
//! trimmed, so the emitted `$…$` has no space right after the opening `$` or
//! before the closing `$`. pulldown-cmark's dollar-math flanking rule rejects
-//! `$ … $` (whitespace next to a delimiter) and would otherwise leave a padded
+//! `$ … $` (whitespace next to a delimiter) and would otherwise leave a `padded`
//! span as raw `$ … $` text. Interior newlines join to spaces (TeX treats them
//! as spaces) so a span wrapped across source lines cannot be re-parsed as
//! block structure.
@@ -199,7 +199,8 @@ impl LatexDelimiterNormalizer {
b'`' => {
let run = count_run(bytes, i, b'`');
if i + run == n && !final_flush {
- break; // run may extend; hold it back
+ // run may extend; hold it back
+ break;
}
out.push_str(&buf[i..i + run]);
i += run;
@@ -277,7 +278,8 @@ impl LatexDelimiterNormalizer {
b'$' => {
let run = count_run(bytes, i, b'$');
if run == 1 && i + 1 == n && !final_flush {
- break; // may become `$$`; hold it back
+ // may become `$$`; hold it back
+ break;
}
if run >= 2 {
// A display opener is exactly two `$`; any further
@@ -340,7 +342,8 @@ impl LatexDelimiterNormalizer {
let r = count_run(bytes, i, b'`');
if i + r == n && !final_flush {
out.push_str(&buf[start..i]);
- return (out, i); // hold back the trailing run
+ // hold back the trailing run
+ return (out, i);
}
if r == run {
i += r;
@@ -350,13 +353,15 @@ impl LatexDelimiterNormalizer {
handled = true;
break;
}
- i += r; // non-matching run is literal content
+ // non-matching run is literal content
+ i += r;
}
_ => i += 1,
}
}
if !handled {
- out.push_str(&buf[start..i]); // EOF inside code
+ // EOF inside code
+ out.push_str(&buf[start..i]);
}
}
State::Fenced { ch, len } => {
@@ -379,7 +384,8 @@ impl LatexDelimiterNormalizer {
i += 1;
}
if i < n {
- i += 1; // include the newline
+ // include the newline
+ i += 1;
self.at_line_start = true;
} else {
self.at_line_start = false;
@@ -430,13 +436,15 @@ fn scan_fence_open(bytes: &[u8], i: usize, final_flush: bool) -> FenceScan {
j += 1;
}
if spaces >= 4 {
- return FenceScan::No; // indented; not treated as a fence opener
+ // indented; not treated as a fence opener
+ return FenceScan::No;
}
if j == n {
return if final_flush {
FenceScan::No
} else {
- FenceScan::NeedMore // ≤3 spaces then EOF: a fence may still start
+ // ≤3 spaces then EOF: a fence may still start
+ FenceScan::NeedMore
};
}
let ch = bytes[j];
@@ -445,10 +453,12 @@ fn scan_fence_open(bytes: &[u8], i: usize, final_flush: bool) -> FenceScan {
}
let run = count_run(bytes, j, ch);
if j + run == n && !final_flush {
- return FenceScan::NeedMore; // run may extend
+ // run may extend
+ return FenceScan::NeedMore;
}
if run < 3 {
- return FenceScan::No; // inline code / stray tildes, not a fence
+ // inline code / stray tildes, not a fence
+ return FenceScan::No;
}
FenceScan::Match {
ch,
@@ -482,7 +492,8 @@ fn scan_fence_close(bytes: &[u8], i: usize, ch: u8, len: usize, final_flush: boo
}
let run = count_run(bytes, j, ch);
if j + run == n && !final_flush {
- return FenceScan::NeedMore; // run may still grow to >= len
+ // run may still grow to >= len
+ return FenceScan::NeedMore;
}
if run < len {
return FenceScan::No;
@@ -510,7 +521,8 @@ fn scan_fence_close(bytes: &[u8], i: usize, ch: u8, len: usize, final_flush: boo
end: j + run,
}
} else {
- FenceScan::No // non-whitespace after the run → info string → content
+ // non-whitespace after the run → info string → content
+ FenceScan::No
}
}
@@ -617,9 +629,11 @@ fn find_inline_close(bytes: &[u8], open: usize, final_flush: bool) -> InlineClos
}
if bytes[k] == b'\\' {
match bytes.get(k + 1) {
- None => break, // trailing `\`: need the next byte to classify
+ // trailing `\`: need the next byte to classify
+ None => break,
Some(b')') => return InlineClose::Found { close: k },
- Some(_) => k += 2, // `\\` pair or `\x` escape: skip both bytes
+ // `\\` pair or `\x` escape: skip both bytes
+ Some(_) => k += 2,
}
} else {
k += 1;
@@ -678,7 +692,8 @@ fn find_display_close(buf: &str, content_start: usize, final_flush: bool) -> Dis
}
match bytes[k] {
b'\\' => match bytes.get(k + 1) {
- None => break, // trailing `\`: need the next byte to classify
+ // trailing `\`: need the next byte to classify
+ None => break,
Some(b']') => {
return DisplayClose::Found {
close: k,
@@ -709,9 +724,11 @@ fn find_display_close(buf: &str, content_start: usize, final_flush: bool) -> Dis
if could_extend && !final_flush {
return DisplayClose::NeedMore;
}
- k += 2; // `\e…` of something else: span content
+ // `\e…` of something else: span content
+ k += 2;
}
- Some(_) => k += 2, // `\\` pair or `\x` escape: span content
+ // `\\` pair or `\x` escape: span content
+ Some(_) => k += 2,
},
b'$' => {
let run = count_run(bytes, k, b'$');
@@ -722,7 +739,8 @@ fn find_display_close(buf: &str, content_start: usize, final_flush: bool) -> Dis
};
}
if k + run == n && !final_flush {
- return DisplayClose::NeedMore; // lone `$` at EOB may extend
+ // lone `$` at EOB may extend
+ return DisplayClose::NeedMore;
}
k += run;
}
@@ -734,7 +752,8 @@ fn find_display_close(buf: &str, content_start: usize, final_flush: bool) -> Dis
j += 1;
}
if j == n {
- break; // need the next line's first byte to decide
+ // need the next line's first byte to decide
+ break;
}
if matches!(bytes[j], b'\n' | b'>') {
return DisplayClose::Unmatched;
@@ -757,7 +776,7 @@ fn find_display_close(buf: &str, content_start: usize, final_flush: bool) -> Dis
/// through byte-for-byte, keeping the pass idempotent). Multi-line interiors
/// have each line trimmed and joined with a single space so CommonMark block
/// parsing (setext underlines, list items, headings) cannot split the span;
-/// TeX treats the newlines as spaces, so rendering is unchanged.
+/// TeX treats the newlines as spaces, so rendering is `unchanged`.
fn emit_display_span(out: &mut String, interior: &str) {
out.push_str("$$");
push_joined_lines(out, interior);
@@ -825,7 +844,7 @@ mod tests {
normalize_latex_delimiters(s)
}
- // ── Basic conversions ────────────────────────────────────────────────
+ // Basic conversions
#[test]
fn inline_paren_converts() {
@@ -833,7 +852,7 @@ mod tests {
assert_eq!(norm("a \\(x\\) b"), "a $x$ b");
}
- // ── Inline `\( … \)` boundary-whitespace trimming (the regression) ────
+ // Inline `\( … \)` boundary-whitespace trimming (the regression)
#[test]
fn normalize_inline_paren_trims_boundary_ws() {
@@ -858,7 +877,7 @@ mod tests {
fn normalize_inline_paren_trim_leaves_escapes_and_dollars_alone() {
// Escaped `\\(`/`\\)` is a literal backslash + paren, not a math span.
assert_eq!(norm("\\\\( x \\\\)"), "\\\\( x \\\\)");
- // Only the backslash forms are ours: a space-padded bare `$ x $` is NOT
+ // Only the backslash forms are ours: a space-`padded` bare `$ x $` is NOT
// trimmed (currency untouched-ness is covered by `currency_not_misconverted`).
assert_eq!(norm("$ x $"), "$ x $");
}
@@ -880,7 +899,7 @@ mod tests {
assert_eq!(norm("a\n\\[x\\]\nb"), "a\n$$x$$\nb");
}
- // ── Multi-line display spans join onto one line ──────────────────────
+ // Multi-line display spans join onto one line
#[test]
fn multiline_display_with_setext_hazard_joins() {
@@ -971,7 +990,7 @@ mod tests {
assert_eq!(norm("$$\nprice \\$5\n=\nz\n$$"), "$$price \\$5 = z$$");
}
- // ── Inline `\(…\)` spans join interior newlines ──────────────────────
+ // Inline `\(…\)` spans join interior newlines
#[test]
fn multiline_inline_paren_joins() {
@@ -1016,7 +1035,7 @@ mod tests {
}
}
- // ── Escapes & currency ───────────────────────────────────────────────
+ // Escapes & currency
#[test]
fn escaped_backslash_paren_stays_literal() {
@@ -1037,7 +1056,7 @@ mod tests {
assert_eq!(norm("\\(a\\) costs $5"), "$a$ costs $5");
}
- // ── Code is left verbatim ────────────────────────────────────────────
+ // Code is left verbatim
#[test]
fn inline_code_latex_untouched() {
@@ -1072,7 +1091,7 @@ mod tests {
);
}
- // ── Math inside tables (the bug) ─────────────────────────────────────
+ // Math inside tables (the bug)
#[test]
fn table_cell_backslash_math_converts() {
@@ -1081,7 +1100,7 @@ mod tests {
assert_eq!(norm(input), expected);
}
- // ── Streaming equivalence (the key invariant) ────────────────────────
+ // Streaming equivalence (the key invariant)
const RICH_DOC: &str = concat!(
"Inline \\(a+b\\), dollar $c+d$, display \\[e=mc^2\\].\n\n",
@@ -1146,13 +1165,13 @@ mod tests {
" ",
"\\\\(escaped\\\\)",
"`unterminated \\(x\\)\nafter \\(y\\)",
- // Padded inline spans exercise the look-ahead + trim hold-back.
+ // `Padded` inline spans exercise the look-ahead + trim hold-back.
"\\( x \\)",
"a \\( x+y \\) b",
"\\( \\alpha + \\beta \\)",
"\\( \\{x\\} \\)",
"\\( \\) empty",
- // Unclosed padded open: held back until finish() flushes a lone `$`.
+ // Unclosed `padded` open: held back until finish() flushes a lone `$`.
"unclosed padded \\( x + y",
// Display spans exercise the close-scan hold-back and its aborts.
"$$\nx\n=\ny\n$$",
@@ -1172,7 +1191,7 @@ mod tests {
}
}
- // ── finish() flushes held-back partials literally ────────────────────
+ // finish() flushes held-back partials literally
#[test]
fn finish_flushes_partial_backslash() {
diff --git a/crates/codegen/kigi-markdown/src/lib.rs b/crates/codegen/kigi-markdown/src/lib.rs
index 2fecc19..ee47aa6 100644
--- a/crates/codegen/kigi-markdown/src/lib.rs
+++ b/crates/codegen/kigi-markdown/src/lib.rs
@@ -129,7 +129,7 @@ pub fn render_markdown_ratatui_with_buffers_width(
/// still-open fenced code block: only the streaming tail re-render passes
/// `Some(cache)`; `finish()` and non-streaming callers pass `None`. Everything
/// other than that one open block (closed code blocks, HTML, math, tables,
-/// inline) always goes through the unchanged batch highlighter, so output is
+/// inline) always goes through the `unchanged` batch highlighter, so output is
/// byte-for-byte identical to the cache-less path. See [`open_code_highlighter`].
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_markdown_ratatui_with_link_id(
diff --git a/crates/codegen/kigi-markdown/src/open_code_highlighter.rs b/crates/codegen/kigi-markdown/src/open_code_highlighter.rs
index ec14a47..bcec8a6 100644
--- a/crates/codegen/kigi-markdown/src/open_code_highlighter.rs
+++ b/crates/codegen/kigi-markdown/src/open_code_highlighter.rs
@@ -366,7 +366,7 @@ mod tests {
);
}
- // ── highlight_closed (closed-fence memo) ─────────────────────────
+ // highlight_closed (closed-fence memo)
#[test]
fn closed_memo_matches_batch_and_is_idempotent() {
diff --git a/crates/codegen/kigi-markdown/src/parse.rs b/crates/codegen/kigi-markdown/src/parse.rs
index 64f7fd8..d24a717 100644
--- a/crates/codegen/kigi-markdown/src/parse.rs
+++ b/crates/codegen/kigi-markdown/src/parse.rs
@@ -262,8 +262,10 @@ pub(crate) fn cell_word_separator<'a>(
{
let mut in_whitespace = false;
let mut after_break_char = false;
- let mut prev_is_digit = false; // was the *previous* char a digit?
- let mut digit_before_break = false; // was the char before the break char a digit?
+ // was the *previous* char a digit?
+ let mut prev_is_digit = false;
+ // was the char before the break char a digit?
+ let mut digit_before_break = false;
let mut last_break_ch: char = '\0';
let mut break_char_start: usize = 0;
for (idx, ch) in line.char_indices() {
@@ -740,7 +742,7 @@ impl<'a, 'b, 'syn, 'oc> MarkdownParser<'a, 'b, 'syn, 'oc> {
Event::Html(_) => {
// Render HTML block content as regular text (not code).
// pulldown-cmark treats XML-like tags (e.g. ) as HTML
- // blocks, which previously got code-block styling via Replace.
+ // blocks, which would otherwise get code-block styling via Replace.
self.push_highlight(Some(self.ms.text), &range);
}
Event::InlineHtml(html) => {
@@ -1115,7 +1117,7 @@ impl<'a, 'b, 'syn, 'oc> MarkdownParser<'a, 'b, 'syn, 'oc> {
}
}
- // We intentionally use allow_outside=true here (instead of the previous
+ // Use allow_outside=true here (instead of the alternate
// pointer-based allow_outside=false) and then do an rfind on the prefix
// before the (last) dest_url occurrence. This is required because dest_url
// may be a CowStr::Owned (after percent-decoding or HTML entity expansion)
@@ -1224,7 +1226,8 @@ impl<'a, 'b, 'syn, 'oc> MarkdownParser<'a, 'b, 'syn, 'oc> {
}
None
}
- TagEnd::Strikethrough => None, // No highlight pushed
+ // No highlight pushed
+ TagEnd::Strikethrough => None,
TagEnd::CodeBlock => {
// pulldown synthesizes a block end at end-of-input even for an
// unterminated fence, so the end event alone does not prove
@@ -1591,7 +1594,8 @@ impl<'a, 'b, 'syn, 'oc> MarkdownParser<'a, 'b, 'syn, 'oc> {
// = 1 + sum(col_width) + num_cols * 2 * padding + (num_cols - 1) + 1
// = num_cols * (2 * padding + 1) + sum(col_width) + 2 - 1
if let Some(max_width) = self.max_table_width {
- let overhead = num_cols * (2 * padding + 1) + 1; // borders + padding
+ // borders + padding
+ let overhead = num_cols * (2 * padding + 1) + 1;
let content_budget = max_width.saturating_sub(overhead);
let total_content: usize = col_widths.iter().sum();
@@ -1740,7 +1744,8 @@ impl<'a, 'b, 'syn, 'oc> MarkdownParser<'a, 'b, 'syn, 'oc> {
// Body rows
for (i, row) in state.rows.iter().enumerate() {
- let row_offset = separator_offset + 1 + i; // offset 2, 3, ...
+ // offset 2, 3, ...
+ let row_offset = separator_offset + 1 + i;
let (row_plains, row_styleds, row_links) = self.format_styled_content_lines(
row,
diff --git a/crates/codegen/kigi-markdown/src/render.rs b/crates/codegen/kigi-markdown/src/render.rs
index b61b95d..333cc54 100644
--- a/crates/codegen/kigi-markdown/src/render.rs
+++ b/crates/codegen/kigi-markdown/src/render.rs
@@ -1546,7 +1546,8 @@ mod tests {
true,
&mut buffers,
None,
- Some(30), // narrow enough to force wrapping in column B
+ // narrow enough to force wrapping in column B
+ Some(30),
);
// Find the lines that contain "abc" — they should have a styled span
@@ -1587,7 +1588,8 @@ mod tests {
true,
&mut buffers,
None,
- Some(20), // narrow enough to force wrapping around the em-dash
+ // narrow enough to force wrapping around the em-dash
+ Some(20),
);
let text = lines_to_text(&output.lines);
let all_text: String = text.join("");
@@ -1628,7 +1630,8 @@ mod tests {
let md = "| A | B |\n|---|---|\n| x | y |\n| w | z |\n\n";
let table_start_line = 0usize;
- let table_source_lines = 4usize; // header + separator + 2 rows
+ // header + separator + 2 rows
+ let table_source_lines = 4usize;
let (output, _) = render_markdown_ratatui_full(md, test_style::STYLE, true, None);
@@ -1694,7 +1697,8 @@ mod tests {
Some(30),
);
- let table_source_lines = 3; // header + separator + 1 row
+ // header + separator + 1 row
+ let table_source_lines = 3;
for (i, &src_line) in output.line_source_map.iter().enumerate() {
assert!(
src_line < table_source_lines,
@@ -2428,7 +2432,7 @@ mod math_tests {
#[test]
fn paren_inline_math_in_table_cell_renders_unicode() {
- // `\(…\)` inside a table cell must convert. Previously the
+ // `\(…\)` inside a table cell must convert. Historically the
// backslash-form scanner was disabled inside tables, leaving raw TeX.
// Normalization rewrites `\(…\)` → `$…$` before parsing, so the existing
// in-cell `$` path converts it.
diff --git a/crates/codegen/kigi-markdown/src/source_map.rs b/crates/codegen/kigi-markdown/src/source_map.rs
index ecb2e99..1c4d0a5 100644
--- a/crates/codegen/kigi-markdown/src/source_map.rs
+++ b/crates/codegen/kigi-markdown/src/source_map.rs
@@ -105,37 +105,6 @@ impl SourceMap {
}
}
-// ## Restoring Byte-Level Source Maps (if ever needed)
+// Ratatui path tracks line-level mapping only (`line_source_map`) for
+// copy/selection. Byte-level `SourceMap` is unused here (~6% faster).
//
-// The ratatui rendering path currently only tracks line-level source mapping
-// (`line_source_map`), which is sufficient for copy/selection operations.
-// Byte-level `SourceMap` was removed for simplicity and ~6% speedup.
-//
-// To restore byte-level source maps:
-//
-// 1. Add field to MarkdownRenderOutput and MarkdownRenderView:
-// ```
-// pub source_map: SourceMap,
-// ```
-//
-// 2. In render_ratatui(), add tracking variables:
-// ```
-// let mut source_map = SourceMap::new();
-// let mut rendered_offset: usize = 0;
-// ```
-//
-// 3. For each text segment emitted, record the mapping:
-// ```
-// source_map.add(rendered_offset, source_start..source_end);
-// rendered_offset += emitted_text.len();
-// ```
-//
-// 4. In streaming.rs, update FrozenState to track:
-// ```
-// source_map_len: usize,
-// rendered_bytes: usize,
-// ```
-//
-// 5. Use SourceMap::extend_with_offsets() to merge tail source maps.
-//
-// See git history for the removed implementation.
diff --git a/crates/codegen/kigi-markdown/src/streaming.rs b/crates/codegen/kigi-markdown/src/streaming.rs
index ec41f2e..1da76f8 100644
--- a/crates/codegen/kigi-markdown/src/streaming.rs
+++ b/crates/codegen/kigi-markdown/src/streaming.rs
@@ -1899,7 +1899,8 @@ The frozen lines are **never re-rendered**, making streaming O(N) instead of O(N
let first_splits: Vec = if first_half.len() > 1 {
vec![first_half.len() / 2]
} else {
- vec![first_half.len()] // No split, use whole thing
+ // No split, use whole thing
+ vec![first_half.len()]
};
// Split second half (if possible)
@@ -1927,7 +1928,8 @@ The frozen lines are **never re-rendered**, making streaming O(N) instead of O(N
.collect();
if chunks.len() < 2 {
- continue; // Need at least 2 chunks
+ // Need at least 2 chunks
+ continue;
}
tested += 1;
@@ -2482,9 +2484,7 @@ The frozen lines are **never re-rendered**, making streaming O(N) instead of O(N
assert_streaming_matches_full_both(text);
}
- // ----------------------------------------------------------------------
// Syntect-enabled streaming equivalence (incremental open-code highlighter)
- // ----------------------------------------------------------------------
/// Build a nested YAML body of at least `num_lines` lines (no fences).
fn yaml_body(num_lines: usize) -> String {
@@ -2866,7 +2866,8 @@ The frozen lines are **never re-rendered**, making streaming O(N) instead of O(N
#[test]
fn clone_preserves_held_back_pending() {
let mut r = StreamingMarkdownRenderer::new(test_style::STYLE, true);
- r.push_and_render("ab \\(\\alpha\\) cd \\", None); // trailing `\` held back
+ // trailing `\` held back
+ r.push_and_render("ab \\(\\alpha\\) cd \\", None);
let mut cloned = r.clone();
r.push_and_render("(\\beta\\) ef\n\n", None);
cloned.push_and_render("(\\beta\\) ef\n\n", None);
diff --git a/crates/codegen/kigi-markdown/src/style.rs b/crates/codegen/kigi-markdown/src/style.rs
index 9f63559..4c3c05f 100644
--- a/crates/codegen/kigi-markdown/src/style.rs
+++ b/crates/codegen/kigi-markdown/src/style.rs
@@ -1,13 +1,9 @@
//! Markdown styling types.
-//!
-//! This module provides the `MarkdownStyle` struct which defines colors and
-//! effects for all markdown elements.
use anstyle::{Effects, Style};
use crate::colors::adapt_style;
-/// Table border characters for rendering tables in pretty mode.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct TableBorders {
chars: [char; 11],
@@ -42,7 +38,8 @@ impl TableBorders {
Self { chars }
}
- // Short names (used in table formatting)
+ // Every character is reachable under two names: a terse one for dense
+ // table-formatting expressions and a spelled-out one for everything else.
pub const fn h(&self) -> char {
self.chars[Self::H]
}
@@ -77,7 +74,6 @@ impl TableBorders {
self.chars[Self::X]
}
- // Long names (for readability)
pub const fn horizontal(&self) -> char {
self.chars[Self::H]
}
@@ -121,9 +117,8 @@ impl Default for TableBorders {
/// Style configuration for markdown rendering.
///
-/// Each field controls the styling for a specific markdown element.
-/// The `_inner` variants are applied to the content, while `_outer` variants
-/// are applied to the syntax markers (which are hidden in pretty mode).
+/// `_inner` variants style an element's content; `_outer` variants style its
+/// syntax markers, which pretty mode hides.
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
pub struct MarkdownStyle {
pub heading_inner: [Style; 6],
@@ -161,9 +156,8 @@ pub struct MarkdownStyle {
}
impl MarkdownStyle {
- /// Adapt all styles for the terminal's color capabilities.
- ///
- /// This downgrades RGB colors to 256-color or 16-color as needed.
+ /// Downgrade every style's RGB colors to 256-color or 16-color to match
+ /// the terminal's capabilities.
pub fn adapt(self) -> Self {
Self {
heading_inner: [
@@ -210,8 +204,8 @@ impl MarkdownStyle {
}
}
-/// Check if ALL active styles have HIDDEN effect.
-/// Used in pretty mode to determine if text should be skipped.
+/// Pretty mode skips a span when every style covering it is HIDDEN. An empty
+/// style set is not hidden.
pub(crate) fn all_hidden(styles: impl IntoIterator- >) -> bool {
let mut has_any = false;
let mut all_are_hidden = true;
@@ -230,11 +224,15 @@ pub(crate) fn all_hidden(styles: impl IntoIterator
- >) -> boo
}
/// Merge multiple styles into one for rendering.
-/// Strips HIDDEN from final output - it's a semantic marker, not a visual style.
+///
+/// HIDDEN is a semantic marker rather than a visual effect, so it never
+/// reaches the output: a style that raised it contributes nothing at all, and
+/// a trailing HIDDEN is dropped from the result.
pub(crate) fn merge_styles(styles: impl IntoIterator
- >) -> Style {
let mut out = Style::new();
let mut prev = Style::new();
for style in styles {
+ // Rewind past a style that raised HIDDEN before folding in the next one.
if out.get_effects().contains(Effects::HIDDEN) {
out = prev;
} else {
@@ -265,13 +263,12 @@ pub(crate) fn merge_styles(styles: impl IntoIterator
- >) -> S
out.effects(out.get_effects().remove(Effects::HIDDEN))
}
-// Simple default style for testing (no colors, just effects)
#[cfg(any(test, fuzzing))]
pub mod test_style {
use super::MarkdownStyle;
use anstyle::Style;
- /// A minimal style for testing with no colors.
+ /// Effects only, no colors, so assertions compare stable escape sequences.
pub const STYLE: MarkdownStyle = MarkdownStyle {
heading_inner: [Style::new().bold(); 6],
heading_outer: [Style::new().dimmed().hidden(); 6],
diff --git a/crates/codegen/kigi-markdown/src/syntax.rs b/crates/codegen/kigi-markdown/src/syntax.rs
index 60f8ce9..726d5b8 100644
--- a/crates/codegen/kigi-markdown/src/syntax.rs
+++ b/crates/codegen/kigi-markdown/src/syntax.rs
@@ -1,7 +1,4 @@
//! Syntax highlighting support using syntect.
-//!
-//! This module provides the `Syntect` struct which holds the syntax definitions
-//! and theme for code block highlighting.
use std::io::Cursor;
use std::path::Path;
@@ -14,20 +11,16 @@ use syntect::{
/// Syntax highlighting configuration.
///
-/// Holds the theme and syntax definitions for code highlighting.
-/// Create one instance and pass it to the markdown renderer.
+/// Loading the syntax set is expensive; create one instance and pass it to
+/// the markdown renderer.
pub struct Syntect {
- /// The color theme for syntax highlighting.
pub theme: SyntectTheme,
- /// The syntax definitions (supports 250+ languages via two-face).
pub syntax_set: SyntaxSet,
}
impl Syntect {
- /// Create a new Syntect instance from theme bytes.
- ///
- /// The theme bytes should be a TextMate `.tmTheme` file.
- /// Uses two-face's extended syntax set with 250+ languages.
+ /// `theme_bytes` must be a TextMate `.tmTheme` file. The syntax set is
+ /// two-face's extended one, covering the 250+ languages bat ships.
///
/// # Example
///
@@ -37,12 +30,11 @@ impl Syntect {
pub fn new(theme_bytes: &[u8]) -> Self {
let mut cursor = Cursor::new(theme_bytes);
let theme = ThemeSet::load_from_reader(&mut cursor).expect("Failed to load theme");
- // Use two-face's extended syntax set which includes 250+ languages from bat
let syntax_set = two_face::syntax::extra_newlines();
Self { theme, syntax_set }
}
- /// Find a syntax definition by file path extension.
+ /// Resolves on the extension alone; the rest of the file name is ignored.
pub fn find_syntax_by_file_path(&self, file_path: &Path) -> Option<&SyntaxReference> {
let ext = file_path.extension()?.to_str()?;
self.syntax_set.find_syntax_by_extension(ext)
@@ -53,7 +45,6 @@ impl Syntect {
self.syntax_set.find_syntax_by_token(token)
}
- /// Create a highlighter for the given file path.
pub fn highlight_lines_by_file_path(&self, file_path: &Path) -> Option
> {
Some(HighlightLines::new(
self.find_syntax_by_file_path(file_path)?,
@@ -61,7 +52,6 @@ impl Syntect {
))
}
- /// Create a highlighter for the given language token.
pub fn highlight_lines_for_token(&self, token: &str) -> Option