chore: normalize formatting and local commit policy

This commit is contained in:
kuddev committed 2026-08-30 11:49:13 +08:00
1 parent 4332799ea4
commit 7119bd451a
18 files changed
+174 -222

No files matched your search

+2 -2
View File
@@ -30,8 +30,8 @@ tocodex-docs/
.codex/
*.internal.md
# Local commit hooks are machine-specific policy and must never be published.
/.local-hooks/
# Local commit policy is machine-specific and must never be published.
/.pre-commit-config.yaml
# `docs/` is private by default. Public documentation must be reviewed and
# explicitly added to this file one path at a time; never allowlist another subtree.
+8 -3
View File
@@ -727,7 +727,8 @@ fn parse_envelope(bytes: &[u8]) -> Option<AiHookEvent> {
message: message.clone(),
selection: context_string(&payload, &["selection", "selected_text", "selectedText"]),
raw_context: sanitized_raw_context(&payload),
}); Some(AiHookEvent {
});
Some(AiHookEvent {
pane,
source,
kind,
@@ -1250,11 +1251,15 @@ mod remote_tests {
};
let mut gate = AiHookEventGate::default();
let done = event("{\"kind\":\"done\",\"session_id\":\"s\",\"bridge_sequence\":3,\"event_id\":\"s:3\"}");
let done = event(
"{\"kind\":\"done\",\"session_id\":\"s\",\"bridge_sequence\":3,\"event_id\":\"s:3\"}",
);
assert_eq!(gate.verdict(&done, 3), GateVerdict::Accepted);
assert_eq!(gate.verdict(&done, 3), GateVerdict::DuplicateEventId);
let older = event("{\"kind\":\"prompt\",\"session_id\":\"s\",\"bridge_sequence\":2,\"event_id\":\"s:2\"}");
let older = event(
"{\"kind\":\"prompt\",\"session_id\":\"s\",\"bridge_sequence\":2,\"event_id\":\"s:2\"}",
);
assert_eq!(gate.verdict(&older, 3), GateVerdict::StaleSequence);
// 无序号但带 provider 时间戳:走时间判据。
+1 -4
View File
@@ -392,10 +392,7 @@ mod tests {
let template_iterations = 200_000u128;
let template_started = Instant::now();
for _ in 0..template_iterations {
black_box(UiLanguage::EnUs.tr_args(
"provider.test.success",
&[("status", "200")],
));
black_box(UiLanguage::EnUs.tr_args("provider.test.success", &[("status", "200")]));
}
let template_ns = template_started.elapsed().as_nanos() / template_iterations;
+1 -2
View File
@@ -235,8 +235,7 @@ pub(crate) fn suggest_update(
// 远端目录是异步拉回来的,那一刻 cwd 与行都没变——少了这个代际,拉到的
// 条目要等用户再多打一个字符才会显形。
let remote_generation = crate::remote_dirs::generation();
let key =
format!("{}\u{0}{line}\u{0}{command_generation}\u{0}{remote_generation}", state.cwd);
let key = format!("{}\u{0}{line}\u{0}{command_generation}\u{0}{remote_generation}", state.cwd);
if state.completion_suppressed_line.as_deref() == Some(line.as_str()) {
state.suggestion_key = key;
state.suggestion.clear();
+3 -4
View File
@@ -2947,10 +2947,9 @@ mod tests {
}
// 同一批内容的行内形态被硬换行拆开时保持原文,不再跨行拼接。
assert!(
scan_grid(&TextGrid::from_rows(&[
r"• $\lim_{x\to0}\frac{\sin",
r"x}{x}=1$,$PV=nRT$",
]))
scan_grid(&TextGrid::from_rows(
&[r"• $\lim_{x\to0}\frac{\sin", r"x}{x}=1$,$PV=nRT$",]
))
.iter()
.all(|overlay| !overlay.source.contains("lim")),
"行内公式不得跨真实换行拼接"
+1 -1
View File
@@ -92,8 +92,8 @@ mod ssh_session;
mod ssh_sftp;
mod string;
mod svn_status;
mod taskbar;
mod sync;
mod taskbar;
mod terminal_profiles;
mod tray;
mod update_check;
+2 -3
View File
@@ -25,9 +25,8 @@ pub fn attach_handler() {
// MessageBox 的模态消息泵不能运行在发生 panic 的 GPUI 线程上,否则
// 会重入 AppCell 并把原始 panic 升级成无诊断信息的 double panic。
// 等待专用线程结束可保证主线程 unwind 退出前用户仍能看到并复制错误。
if let Ok(dialog_thread) = thread::Builder::new()
.name("panic-dialog".into())
.spawn(move || unsafe {
if let Ok(dialog_thread) =
thread::Builder::new().name("panic-dialog".into()).spawn(move || unsafe {
MessageBoxW(
ptr::null_mut(),
dialog_message.as_ptr(),
+5 -4
View File
@@ -114,7 +114,10 @@ fn snapshot() -> Result<std::collections::HashMap<u32, (u32, String)>, String> {
let handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if handle == INVALID_HANDLE_VALUE {
return Err(format!("CreateToolhelp32Snapshot failed: {}", std::io::Error::last_os_error()));
return Err(format!(
"CreateToolhelp32Snapshot failed: {}",
std::io::Error::last_os_error()
));
}
let mut processes: HashMap<u32, (u32, String)> = HashMap::new();
@@ -381,9 +384,7 @@ mod tests {
/// 带自定义可执行名的进程表,用来构造「helper 上面隔着一层 shell」的链。
fn named_table(rows: &[(u32, u32, &str)]) -> HashMap<u32, (u32, String)> {
rows.iter()
.map(|&(pid, parent, exe)| (pid, (parent, exe.to_owned())))
.collect()
rows.iter().map(|&(pid, parent, exe)| (pid, (parent, exe.to_owned()))).collect()
}
/// hook helper 的父进程往往不是 agent 本身:Windows 上 claude 通过
+5 -4
View File
@@ -280,10 +280,11 @@ mod tests {
assert!(!begin_fetch(&env, "/tmp/a"), "已经在拉了");
assert!(begin_fetch(&env, "/tmp/b"), "另一个目录不受影响");
finish_fetch(&env, "/tmp/a", Some(vec![RemoteEntry {
name: "x".to_owned(),
is_dir: false,
}]));
finish_fetch(
&env,
"/tmp/a",
Some(vec![RemoteEntry { name: "x".to_owned(), is_dir: false }]),
);
assert!(lookup(&env, "/tmp/a").is_some(), "落了缓存");
assert!(begin_fetch(&env, "/tmp/a"), "拉完就解锁");
}
+2 -6
View File
@@ -38,10 +38,7 @@ pub(super) fn request_once(
Ok(serde_json::from_str(&line)?)
}
pub(super) fn print_response(
response: &ApiResponse,
pretty: bool,
) -> Result<(), Box<dyn Error>> {
pub(super) fn print_response(response: &ApiResponse, pretty: bool) -> Result<(), Box<dyn Error>> {
if pretty {
println!("{}", serde_json::to_string_pretty(response)?);
} else {
@@ -225,8 +222,7 @@ fn run_cli_inner(options: ControlOptions) -> Result<(), Box<dyn Error>> {
print_response(&response, options.pretty)
},
CliCommand::CloseWindow { window } => {
let response =
request_once("window.close", json!({ "window_id": window }), timeout)?;
let response = request_once("window.close", json!({ "window_id": window }), timeout)?;
print_response(&response, options.pretty)
},
CliCommand::Focus { window, pane } => {
+4 -7
View File
@@ -238,10 +238,7 @@ impl RuntimeCommand {
},
"tab.close" => {
let params: TabTargetParams = parse_params(&request.params)?;
Ok(Self::CloseTab {
window_id: params.window_id,
tab_index: params.tab_index,
})
Ok(Self::CloseTab { window_id: params.window_id, tab_index: params.tab_index })
},
"tab.rename" => {
let params: RenameTabParams = parse_params(&request.params)?;
@@ -555,9 +552,9 @@ fn validate_multiline_text(text: &str, label: &str) -> Result<(), ApiError> {
)));
}
if text.chars().any(|ch| ch.is_control() && !matches!(ch, '\n' | '\r' | '\t')) {
return Err(ApiError::invalid_params(
format!("{label} contains unsupported terminal control characters"),
));
return Err(ApiError::invalid_params(format!(
"{label} contains unsupported terminal control characters"
)));
}
Ok(())
}
+15 -28
View File
@@ -34,9 +34,10 @@ use super::*;
use crate::cli::{
AgentCommand, AgentOptions, AgentPasteOptions, AgentReadOptions, AgentSendOptions,
AgentWaitOptions, EnvOptions, ListOptions, PaneCloseOptions, PaneCommand, PaneExecOptions,
PaneOptions, PanePasteOptions, PaneReadOptions, PaneResizeOptions, PaneSendOptions, PaneWaitOptions,
PaneZoomOptions, PasteSourceOptions, TabCloseOptions, TabCommand, TabMoveOptions,
TabRenameOptions, TabResourceOptions, WindowCloseOptions, WindowCommand, WindowResourceOptions,
PaneOptions, PanePasteOptions, PaneReadOptions, PaneResizeOptions, PaneSendOptions,
PaneWaitOptions, PaneZoomOptions, PasteSourceOptions, TabCloseOptions, TabCommand,
TabMoveOptions, TabRenameOptions, TabResourceOptions, WindowCloseOptions, WindowCommand,
WindowResourceOptions,
};
use std::fs::File;
use std::io::Read as _;
@@ -153,11 +154,7 @@ pub fn agent(options: AgentOptions) -> Result<(), Box<dyn Error>> {
fn window_close(options: WindowCloseOptions) -> Result<(), Box<dyn Error>> {
let timeout = validated_timeout(options.timeout_ms)?;
let response = request_once(
"window.close",
json!({ "window_id": options.window }),
timeout,
)?;
let response = request_once("window.close", json!({ "window_id": options.window }), timeout)?;
print_response(&response, options.output.pretty)
}
@@ -776,19 +773,15 @@ fn read_paste_source(source: PasteSourceOptions) -> Result<String, Box<dyn Error
} else {
joined(&text)
};
validate_paste_text(&value)
.map_err(|error| CliError::new("invalid_params", error.message))?;
validate_paste_text(&value).map_err(|error| CliError::new("invalid_params", error.message))?;
Ok(value)
}
fn read_paste_utf8(reader: impl Read, label: &str) -> Result<String, Box<dyn Error>> {
let mut bytes = Vec::new();
reader
.take((MAX_PROMPT_BYTES + 1) as u64)
.read_to_end(&mut bytes)
.map_err(|error| {
CliError::new("input_read_error", format!("could not read paste input {label}: {error}"))
})?;
reader.take((MAX_PROMPT_BYTES + 1) as u64).read_to_end(&mut bytes).map_err(|error| {
CliError::new("input_read_error", format!("could not read paste input {label}: {error}"))
})?;
if bytes.len() > MAX_PROMPT_BYTES {
return Err(CliError::new(
"invalid_params",
@@ -797,11 +790,8 @@ fn read_paste_utf8(reader: impl Read, label: &str) -> Result<String, Box<dyn Err
.into());
}
String::from_utf8(bytes).map_err(|error| {
CliError::new(
"invalid_utf8",
format!("paste input {label} is not valid UTF-8: {error}"),
)
.into()
CliError::new("invalid_utf8", format!("paste input {label} is not valid UTF-8: {error}"))
.into()
})
}
@@ -907,16 +897,13 @@ mod tests {
#[test]
fn paste_reader_is_strict_utf8_and_bounded() {
let text = read_paste_utf8(std::io::Cursor::new("first\nsecond"), "memory")
.expect("valid UTF-8");
let text =
read_paste_utf8(std::io::Cursor::new("first\nsecond"), "memory").expect("valid UTF-8");
assert_eq!(text, "first\nsecond");
assert!(read_paste_utf8(std::io::Cursor::new([0xff]), "memory").is_err());
assert!(
read_paste_utf8(
std::io::Cursor::new(vec![b'x'; MAX_PROMPT_BYTES + 1]),
"memory",
)
.is_err()
read_paste_utf8(std::io::Cursor::new(vec![b'x'; MAX_PROMPT_BYTES + 1]), "memory",)
.is_err()
);
}
+2 -10
View File
@@ -310,11 +310,7 @@ impl WindowContext {
Ok(self.close_pane(pane_id))
}
pub(crate) fn runtime_set_zoom(
&mut self,
pane_id: u64,
zoomed: bool,
) -> Result<(), ApiError> {
pub(crate) fn runtime_set_zoom(&mut self, pane_id: u64, zoomed: bool) -> Result<(), ApiError> {
let Some(tab_index) = self.tabs.iter().position(|tab| {
let mut panes = Vec::new();
tab.layout.leaves(&mut panes);
@@ -344,11 +340,7 @@ impl WindowContext {
Ok(())
}
pub(crate) fn runtime_resize_pane(
&mut self,
pane_id: u64,
ratio: f32,
) -> Result<(), ApiError> {
pub(crate) fn runtime_resize_pane(&mut self, pane_id: u64, ratio: f32) -> Result<(), ApiError> {
let Some(tab_index) = self.tabs.iter().position(|tab| {
let mut panes = Vec::new();
tab.layout.leaves(&mut panes);
+1 -9
View File
@@ -133,15 +133,7 @@ impl Layout {
/// 设置目标 pane 在最近父 Split 中占用的比例。内部 ratio 描述 first,
/// 因此 second 侧目标要换算成 `1 - target_ratio`。
pub(super) fn set_leaf_parent_ratio(&mut self, target: PaneId, target_ratio: f32) -> bool {
let Layout::Split {
ratio,
preview_ratio,
dragging,
first,
second,
..
} = self
else {
let Layout::Split { ratio, preview_ratio, dragging, first, second, .. } = self else {
return false;
};
+118 -118
View File
@@ -1,118 +1,118 @@
//! 文件行数预算守护(docs/project-constraints.md 第 1 条)。
//!
//! 单个 `.rs` 文件硬上限 2000 行;存量超标文件在 [`GRANDFATHERED`] 登记,
//! 登记值只允许下调。测试失败的正确动作是拆文件,不是上调预算。
use std::fs;
use std::path::{Path, PathBuf};
/// 新代码红线:超过它就必须拆(docs/project-constraints.md)。
const MAX_LINES: usize = 2000;
/// 存量豁免清单(相对仓库根,正斜杠路径 → 行数上限)。
///
/// 登记值 = 收录当天的行数加不足百行的呼吸空间:小修小补不触线,成规模
/// 新增必须伴随拆分。拆小后同步下调或删除对应条目。
const GRANDFATHERED: &[(&str, usize)] = &[
("nebula_app/src/display/mod.rs", 12000),
("nebula_app/src/display/settings.rs", 8650),
// 2026-08-17 上调(4850 → 5150):git 树 VS Code 化 + 源码 tab 接线;
// 下一步应把 VCS 面板拆成独立文件后下调。
("nebula_app/src/gpui_shell/workspace.rs", 5150),
// 2026-08-17 下调(3600 → 3550):网络区拆到 network_settings.rs。
("nebula_app/src/gpui_shell/settings_pane.rs", 3550),
("nebula_app/src/display/command_palette.rs", 3980),
("nebula_app/src/window_context.rs", 3800),
("nebula_app/src/event.rs", 3780),
("nebula_terminal/src/term/mod.rs", 3650),
// 2026-08-17 上调(2900 → 2970):draw_overlays 几何抽成两壳共享的 plan。
("nebula_app/src/display/terminal_math.rs", 2970),
// 2026-08-17 上调(2680 → 2760):SVN wc.db 回退 + 单文件 stage/discard。
("nebula_app/src/display/side_panel.rs", 2760),
("nebula_app/src/gpui_shell/terminal/view.rs", 2540),
("nebula_app/src/display/chrome.rs", 2550),
("nebula_app/src/display/markdown_view.rs", 2150),
];
/// 这些目录不受预算约束:构建产物、vendored 第三方代码、版本库内部。
fn excluded(component: &str) -> bool {
component == ".git"
|| component == "third_party"
|| component == "target"
|| component.starts_with(".target")
}
fn collect_rust_files(root: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(root) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
let name = entry.file_name();
let name = name.to_string_lossy();
// 隐藏项(.git、.tmp_* 草稿等)不是源码,不受预算约束。
if name.starts_with('.') {
continue;
}
if path.is_dir() {
if !excluded(&name) {
collect_rust_files(&path, out);
}
} else if path.extension().is_some_and(|extension| extension == "rs") {
out.push(path);
}
}
}
#[test]
fn every_source_file_respects_its_line_budget() {
let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("nebula_app sits directly under the repo root")
.to_path_buf();
let mut files = Vec::new();
collect_rust_files(&repo_root, &mut files);
assert!(
files.len() > 100,
"行数守护扫到的文件数异常({}),排除规则可能吞掉了源码目录",
files.len()
);
let mut violations = Vec::new();
let mut stale_grants = Vec::new();
for path in files {
let relative = path
.strip_prefix(&repo_root)
.expect("collected files live under the repo root")
.to_string_lossy()
.replace('\\', "/");
let Ok(content) = fs::read(&path) else {
panic!("无法读取 {relative}");
};
// 按换行字节计数:与编码无关,空文件为 0 行。
let lines = content.iter().filter(|byte| **byte == b'\n').count()
+ usize::from(!content.is_empty() && content.last() != Some(&b'\n'));
let grant =
GRANDFATHERED.iter().find(|(name, _)| *name == relative).map(|(_, budget)| *budget);
let budget = grant.unwrap_or(MAX_LINES);
if lines > budget {
violations.push(format!("{relative}: {lines} 行 > 预算 {budget}"));
}
// 豁免文件瘦身到红线以内后,条目必须删除,防止预算复胖。
if let Some(budget) = grant
&& lines <= MAX_LINES
{
stale_grants.push(format!(
"{relative}: 已降到 {lines} 行(≤{MAX_LINES}),豁免条目({budget})应删除"
));
}
}
assert!(
violations.is_empty(),
"以下文件超出行数预算(docs/project-constraints.md 第 1 条,正确动作是拆文件):\n{}",
violations.join("\n")
);
assert!(stale_grants.is_empty(), "以下豁免条目已过期:\n{}", stale_grants.join("\n"));
}
//! 文件行数预算守护(docs/project-constraints.md 第 1 条)。
//!
//! 单个 `.rs` 文件硬上限 2000 行;存量超标文件在 [`GRANDFATHERED`] 登记,
//! 登记值只允许下调。测试失败的正确动作是拆文件,不是上调预算。
use std::fs;
use std::path::{Path, PathBuf};
/// 新代码红线:超过它就必须拆(docs/project-constraints.md)。
const MAX_LINES: usize = 2000;
/// 存量豁免清单(相对仓库根,正斜杠路径 → 行数上限)。
///
/// 登记值 = 收录当天的行数加不足百行的呼吸空间:小修小补不触线,成规模
/// 新增必须伴随拆分。拆小后同步下调或删除对应条目。
const GRANDFATHERED: &[(&str, usize)] = &[
("nebula_app/src/display/mod.rs", 12000),
("nebula_app/src/display/settings.rs", 8650),
// 2026-08-17 上调(4850 → 5150):git 树 VS Code 化 + 源码 tab 接线;
// 下一步应把 VCS 面板拆成独立文件后下调。
("nebula_app/src/gpui_shell/workspace.rs", 5150),
// 2026-08-17 下调(3600 → 3550):网络区拆到 network_settings.rs。
("nebula_app/src/gpui_shell/settings_pane.rs", 3550),
("nebula_app/src/display/command_palette.rs", 3980),
("nebula_app/src/window_context.rs", 3800),
("nebula_app/src/event.rs", 3780),
("nebula_terminal/src/term/mod.rs", 3650),
// 2026-08-17 上调(2900 → 2970):draw_overlays 几何抽成两壳共享的 plan。
("nebula_app/src/display/terminal_math.rs", 2970),
// 2026-08-17 上调(2680 → 2760):SVN wc.db 回退 + 单文件 stage/discard。
("nebula_app/src/display/side_panel.rs", 2760),
("nebula_app/src/gpui_shell/terminal/view.rs", 2540),
("nebula_app/src/display/chrome.rs", 2550),
("nebula_app/src/display/markdown_view.rs", 2150),
];
/// 这些目录不受预算约束:构建产物、vendored 第三方代码、版本库内部。
fn excluded(component: &str) -> bool {
component == ".git"
|| component == "third_party"
|| component == "target"
|| component.starts_with(".target")
}
fn collect_rust_files(root: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(root) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
let name = entry.file_name();
let name = name.to_string_lossy();
// 隐藏项(.git、.tmp_* 草稿等)不是源码,不受预算约束。
if name.starts_with('.') {
continue;
}
if path.is_dir() {
if !excluded(&name) {
collect_rust_files(&path, out);
}
} else if path.extension().is_some_and(|extension| extension == "rs") {
out.push(path);
}
}
}
#[test]
fn every_source_file_respects_its_line_budget() {
let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("nebula_app sits directly under the repo root")
.to_path_buf();
let mut files = Vec::new();
collect_rust_files(&repo_root, &mut files);
assert!(
files.len() > 100,
"行数守护扫到的文件数异常({}),排除规则可能吞掉了源码目录",
files.len()
);
let mut violations = Vec::new();
let mut stale_grants = Vec::new();
for path in files {
let relative = path
.strip_prefix(&repo_root)
.expect("collected files live under the repo root")
.to_string_lossy()
.replace('\\', "/");
let Ok(content) = fs::read(&path) else {
panic!("无法读取 {relative}");
};
// 按换行字节计数:与编码无关,空文件为 0 行。
let lines = content.iter().filter(|byte| **byte == b'\n').count()
+ usize::from(!content.is_empty() && content.last() != Some(&b'\n'));
let grant =
GRANDFATHERED.iter().find(|(name, _)| *name == relative).map(|(_, budget)| *budget);
let budget = grant.unwrap_or(MAX_LINES);
if lines > budget {
violations.push(format!("{relative}: {lines} 行 > 预算 {budget}"));
}
// 豁免文件瘦身到红线以内后,条目必须删除,防止预算复胖。
if let Some(budget) = grant
&& lines <= MAX_LINES
{
stale_grants.push(format!(
"{relative}: 已降到 {lines} 行(≤{MAX_LINES}),豁免条目({budget})应删除"
));
}
}
assert!(
violations.is_empty(),
"以下文件超出行数预算(docs/project-constraints.md 第 1 条,正确动作是拆文件):\n{}",
violations.join("\n")
);
assert!(stale_grants.is_empty(), "以下豁免条目已过期:\n{}", stale_grants.join("\n"));
}
+1 -3
View File
@@ -244,9 +244,7 @@ fn base64_encode(input: &[u8]) -> String {
#[cfg(test)]
mod tests {
use super::{
FOREIGN_HOOK_RUNNERS, Outcome, base64_encode, foreign_hook_runner, outcome_line,
};
use super::{FOREIGN_HOOK_RUNNERS, Outcome, base64_encode, foreign_hook_runner, outcome_line};
#[test]
fn encodes_remote_hook_payload() {
+2 -5
View File
@@ -443,11 +443,8 @@ impl ThemeName {
/// 两块深色面板会糊成一片,竖线是这种形态下唯一的结构分界。
pub fn card_geometry(self) -> ThemeCardGeometry {
match self {
Self::Nord => ThemeCardGeometry {
radius: 0.0,
gutter: 0.0,
shadow: false,
divider: 1.0,
Self::Nord => {
ThemeCardGeometry { radius: 0.0, gutter: 0.0, shadow: false, divider: 1.0 }
},
_ => ThemeCardGeometry {
radius: DEFAULT_PANE_CARD_RADIUS,
+1 -9
View File
@@ -231,15 +231,7 @@ impl<T: Copy + Eq> SplitTree<T> {
/// pane;目标在第二侧时必须写成 `1 - target_ratio`。递归到最近父节点再
/// 修改,避免嵌套 pane 的 resize 错改祖先 Split。
pub fn set_leaf_parent_ratio(&mut self, target: T, target_ratio: f32) -> bool {
let SplitTree::Split {
ratio,
preview_ratio,
dragging,
first,
second,
..
} = self
else {
let SplitTree::Split { ratio, preview_ratio, dragging, first, second, .. } = self else {
return false;
};