26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
131 lines
5.4 KiB
Rust
131 lines
5.4 KiB
Rust
//! Entrypoint smoke gate (#902): every advertised entrypoint must be reachable.
|
|
//!
|
|
//! Background: the v3.4.7 release shipped tools/commands whose *implementation*
|
|
//! existed but whose *entrypoint* was not wired — `lean-ctx pack` / `index` fell
|
|
//! through to the global help, and some tools were missing from the manifest.
|
|
//! "Implemented" must imply "invokable". Two independent surfaces are guarded:
|
|
//!
|
|
//! 1. MCP — key entrypoint tools are present in the manifest SSOT.
|
|
//! 2. CLI — top-level subcommands route to their handler instead of falling
|
|
//! through to the global help (the exact `pack`/`index` regression).
|
|
//!
|
|
//! Dispatch on the MCP side is registry-driven (`registry.get_arc(name)`), so the
|
|
//! manifest↔registry drift is already covered by `mcp_manifest_up_to_date.rs` and
|
|
//! `granular_defs_match_registry`. This gate adds the *advertised entrypoint* and
|
|
//! *CLI wiring* dimensions those tests do not cover.
|
|
|
|
use std::process::Command;
|
|
|
|
use serde_json::Value;
|
|
|
|
/// Collect every `"name"` string field anywhere in the manifest JSON. Tool names
|
|
/// (`ctx_*`) only ever appear as tool entry names, so membership is unambiguous.
|
|
fn collect_names(value: &Value, out: &mut std::collections::BTreeSet<String>) {
|
|
match value {
|
|
Value::Object(map) => {
|
|
if let Some(Value::String(n)) = map.get("name") {
|
|
out.insert(n.clone());
|
|
}
|
|
for v in map.values() {
|
|
collect_names(v, out);
|
|
}
|
|
}
|
|
Value::Array(items) => {
|
|
for v in items {
|
|
collect_names(v, out);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn mcp_entrypoint_tools_are_advertised() {
|
|
let manifest = lean_ctx::core::mcp_manifest::manifest_value();
|
|
let mut names = std::collections::BTreeSet::new();
|
|
collect_names(&manifest, &mut names);
|
|
|
|
assert!(
|
|
!names.is_empty(),
|
|
"manifest advertises no tools — manifest_value() produced an empty surface"
|
|
);
|
|
|
|
// The entrypoint tools that back the regressed CLI commands plus the core
|
|
// read/search surface. If a handler exists but is not advertised here, the
|
|
// tool is unreachable for MCP clients (the v3.4.7 "missing manifest entry").
|
|
let required = [
|
|
"ctx_read",
|
|
"ctx_shell",
|
|
"ctx_search",
|
|
"ctx_pack",
|
|
"ctx_index",
|
|
"ctx_proof",
|
|
"ctx_verify",
|
|
"ctx_explore",
|
|
];
|
|
for tool in required {
|
|
assert!(
|
|
names.contains(tool),
|
|
"entrypoint tool '{tool}' is not advertised in the manifest.\n\
|
|
Register it in the tool registry and regenerate:\n \
|
|
cargo run --example gen_mcp_manifest --features dev-tools"
|
|
);
|
|
}
|
|
}
|
|
|
|
fn lean_ctx() -> Command {
|
|
let mut cmd = Command::new(env!("CARGO_BIN_EXE_lean-ctx"));
|
|
// Mirror cli_characterization.rs: sandbox the binary so subcommands are
|
|
// side-effect-free (no daemon, no real HOME writes).
|
|
cmd.env("LEAN_CTX_ACTIVE", "1");
|
|
cmd.env("HOME", "/tmp/lean-ctx-entrypoint-test");
|
|
cmd.env("LEAN_CTX_DISABLED", "1");
|
|
cmd
|
|
}
|
|
|
|
/// Run the sandboxed binary; return trimmed stdout, trimmed stderr and the exit
|
|
/// code. Stderr matters because the unknown-command handler reports on stderr
|
|
/// (#1046 premium UX), so the wiring detector keys off it.
|
|
fn run(args: &[&str]) -> (String, String, i32) {
|
|
let out = lean_ctx()
|
|
.args(args)
|
|
.output()
|
|
.expect("failed to spawn lean-ctx binary");
|
|
let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
|
let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
|
(stdout, stderr, out.status.code().unwrap_or(-1))
|
|
}
|
|
|
|
#[test]
|
|
fn cli_subcommands_are_wired() {
|
|
// The dispatch fallthrough for an unknown command (#1046 premium UX) reports on
|
|
// stderr — `lean-ctx: unknown command '<cmd>'` plus a "did you mean?" hint —
|
|
// with empty stdout and exit 1. That stderr marker is the *signature* of "not
|
|
// wired" — the exact v3.4.7 `pack`/`index` regression. Derive it from a token
|
|
// that can never be a real command, so the detector validates itself.
|
|
let (stdout, stderr, code) = run(&["__leanctx_not_a_command__"]);
|
|
assert!(
|
|
stdout.is_empty() && stderr.contains("unknown command") && code == 1,
|
|
"unknown-command fallthrough changed (stdout={stdout:?}, stderr={stderr:?}, \
|
|
exit={code}); the wiring detector below keys off the stderr marker"
|
|
);
|
|
|
|
// Probe each command with a *garbage subcommand* — never `--help`, since e.g.
|
|
// `pack --help` skips dashed args and runs the default PR packer (a side effect).
|
|
// A wired command routes to its own handler and never emits the top-level
|
|
// `unknown command '<cmd>'` marker; an un-wired one hits the fallthrough and
|
|
// does. Set = the regressed entrypoints (`pack`, `index`) plus representative
|
|
// read / compile / observability commands, each verified side-effect-free on an
|
|
// unknown subcommand. The *full* tool surface is guarded MCP-side by
|
|
// `mcp_entrypoint_tools_are_advertised`.
|
|
let probe = "__leanctx_wiring_probe__";
|
|
for cmd in ["pack", "index", "instructions", "verify"] {
|
|
let (_stdout, stderr, _code) = run(&[cmd, probe]);
|
|
assert!(
|
|
!stderr.contains(&format!("unknown command '{cmd}'")),
|
|
"`lean-ctx {cmd}` falls through to the unknown-command handler — it is not \
|
|
wired in cli/dispatch (the v3.4.7 entrypoint regression)."
|
|
);
|
|
}
|
|
}
|