diff --git a/Cargo.lock b/Cargo.lock index 2d33d6e..6d13893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,7 +432,7 @@ dependencies = [ [[package]] name = "codegraph-bench" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "clap", @@ -447,7 +447,7 @@ dependencies = [ [[package]] name = "codegraph-core" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "serde", @@ -463,7 +463,7 @@ dependencies = [ [[package]] name = "codegraph-daemon" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -483,7 +483,7 @@ dependencies = [ [[package]] name = "codegraph-extract" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "codegraph-graph" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -545,7 +545,7 @@ dependencies = [ [[package]] name = "codegraph-mcp" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "axum", @@ -568,7 +568,7 @@ dependencies = [ [[package]] name = "codegraph-resolve" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -582,7 +582,7 @@ dependencies = [ [[package]] name = "codegraph-rs" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "assert_cmd", @@ -618,7 +618,7 @@ dependencies = [ [[package]] name = "codegraph-store" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -630,7 +630,7 @@ dependencies = [ [[package]] name = "codegraph-watch" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", diff --git a/crates/codegraph-cli/src/installer/skill.rs b/crates/codegraph-cli/src/installer/skill.rs index d8f68c0..7c17aa2 100644 --- a/crates/codegraph-cli/src/installer/skill.rs +++ b/crates/codegraph-cli/src/installer/skill.rs @@ -396,6 +396,97 @@ mod tests { ); } + // --- Codex MAX_DESCRIPTION_LEN guard -------------------------------------- + + /// Codex's `core-skills/src/loader.rs` rejects any skill whose YAML + /// front-matter `description`, after `sanitize_single_line` (split on + /// whitespace and rejoin with single spaces), exceeds `MAX_DESCRIPTION_LEN` + /// (1024 chars). An over-limit description is an `InvalidField` parse + /// error, which routes the skill into `outcome.errors` (not + /// `outcome.skills`) with NO warning surfaced to the user — the skill just + /// silently disappears from the agent's skill list. This test fails + /// closed: it asserts the front-matter fence, the single folded + /// `description: >` block, and its extracted content are all present and + /// non-empty before checking the length, so a change in shape (e.g. losing + /// the fence or the scalar) fails the test rather than the assertion being + /// skipped. + #[test] + fn skill_description_within_codex_limit() { + let content = SKILL_MD; + assert!( + content.starts_with("---\n"), + "SKILL.md must open with a YAML front-matter fence" + ); + let after_open = &content[4..]; + let close_idx = after_open + .find("\n---") + .expect("SKILL.md must have a closing front-matter fence"); + let front_matter = &after_open[..close_idx]; + + let lines: Vec<&str> = front_matter.lines().collect(); + let desc_start = lines + .iter() + .position(|line| { + let trimmed = line.trim_start(); + trimmed.starts_with("description:") && trimmed["description:".len()..].trim() == ">" + }) + .expect("front-matter must have exactly one `description: >` folded scalar"); + assert!( + lines + .iter() + .filter(|line| line.trim_start().starts_with("description:")) + .count() + == 1, + "expected exactly one `description:` key in the front-matter" + ); + + let key_re_is_top_level = |line: &str| -> bool { + let mut chars = line.chars(); + match chars.next() { + Some(c) if c.is_ascii_alphanumeric() || c == '_' || c == '-' => {} + _ => return false, + } + line.contains(':') + && line + .split(':') + .next() + .unwrap() + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') + }; + + let mut block_lines = Vec::new(); + for line in &lines[desc_start + 1..] { + if key_re_is_top_level(line) { + break; + } + block_lines.push(*line); + } + assert!( + !block_lines.is_empty(), + "description block must not be empty (extraction failed closed)" + ); + + // Replicates Codex's `sanitize_single_line`: split on whitespace, join + // with single spaces. For a `description: >` folded scalar this is + // identical to what YAML parsing + sanitize would produce. + let joined = block_lines.join(" "); + let folded = joined.split_whitespace().collect::>().join(" "); + + let len = folded.chars().count(); + assert!( + len <= 1024, + "description folds to {len} chars, exceeding Codex's \ + MAX_DESCRIPTION_LEN=1024 — the skill would be silently dropped \ + from Codex's skill list" + ); + assert!( + len <= 950, + "description folds to {len} chars, exceeding the 950-char \ + maintenance margin kept below Codex's 1024 hard limit" + ); + } + // --- decide(): all six branches ------------------------------------------ #[test] diff --git a/skills/codegraph/SKILL.md b/skills/codegraph/SKILL.md index de2061a..906e050 100644 --- a/skills/codegraph/SKILL.md +++ b/skills/codegraph/SKILL.md @@ -2,20 +2,15 @@ name: codegraph description: > Use CodeGraph for ALL codebase navigation and code research on any indexed - project. Reach for it — even when the user doesn't say "codegraph" — on any - of these intents: "how does X work", "who calls X", "what breaks if I change - X", "where is X defined", tracing a call flow, onboarding an unfamiliar - repo, surveying an area, or any whole-project analysis: "analyze the - project", "analyze the code", "understand the codebase", "explain the - architecture", "give me an overview of this repo", "what does this project - do", "survey the code". Also trigger when the user asks to index or - initialize a codebase for an agent, or when .codegraph/ is present in the - repo root. Prefer the codegraph_* tools over grep, find, or the Read tool - whenever source files are involved — one codegraph call returns verbatim - source plus the structural graph and replaces dozens of grep+read - round-trips. Chinese equivalents that should also trigger this skill: - "分析项目代码", "分析项目结构", "分析这个仓库", "看看这个项目", "梳理代码", - "项目结构", "代码分析", "这个项目是做什么的", "介绍下这个项目". + project. Reach for it even when the user doesn't say "codegraph" — for + "how does X work", "who calls X", "what breaks if I change X", "where is X + defined", tracing a call flow, onboarding/surveying an area, or + whole-project analysis: "analyze the project", "explain the architecture", + "what does this project do". Also trigger when asked to index/initialize a + codebase, or when .codegraph/ is present. Prefer codegraph_* tools over + grep, find, or Read on source files — one call returns verbatim source plus + the structural graph, replacing dozens of grep+read round-trips. Chinese + triggers: "分析项目代码", "分析这个仓库", "代码分析", "这个项目是做什么的". --- # CodeGraph — Agent Skill