Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ Version output includes `cli_version`, `target`, and protocol information. Disco

The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with pinned Java/TypeScript grammar candidates. It currently reaches only single-file `lint java` and `lint typescript` requests without explicit native context; it is absent from the published npm `0.1.2` package. No `no_std`, completed project-wide WASM fallback, zero-unsafe, or fastest-runtime claim is made. Runtime OS calls require safety review; a comprehensive security audit is not claimed.

The current source-built CLI's `codeguard grammar status --format=json` reports the coverage gap without loading grammars or running lint: the pinned CodeGraph source has 30 vendored WASM files plus two distinct grammars resolved through its dependency; CodeGuard has two unqualified candidates and zero released syntax capabilities. The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB source asset also exceeds the current Rust loader's 8 MiB limit.

### npm installation and one-off use

On Apple Silicon macOS, the published `0.1.2` package was verified with a fresh npm cache:
Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ cargo build --locked -p codeguard-cli

工作区要求 `std`。可选 `wasm-precheck` 构建特性已用固定的 Java/TypeScript grammar 候选资产与有界 Rust worker,为缺少显式原生上下文的 `lint java`、`lint typescript` 单文件请求提供疑似语法观察;公开 npm `0.1.2` 不含该路径。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。

当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前只有两份未验收候选,已发行语法能力为零。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 来源资产约 16.4 MB,还超过当前 Rust 加载器的 8 MiB 限额。

### npm 安装与一次性调用

已在 Apple Silicon macOS 上通过全新 npm 缓存验证公开 `0.1.2` 包:
Expand Down
218 changes: 218 additions & 0 deletions crates/codeguard-cli/src/grammar_status_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
//! CodeGraph grammar 来源覆盖清单的只读投影;不加载 WASM,也不签发检查结果。

use codeguard_adapters::bundled_grammar_candidates;
use serde::Deserialize;
use serde_json::{Value, json};
use std::collections::BTreeSet;
use std::process::ExitCode;

const SOURCE_COMMIT: &str = "40f112453583a2304c4b605a3a9d6545919662bd";
const LANGUAGES: [&str; 32] = [
"arkts",
"c",
"cfml",
"cfquery",
"cfscript",
"cobol",
"cpp",
"csharp",
"dart",
"erlang",
"go",
"java",
"javascript",
"kotlin",
"lua",
"luau",
"nix",
"objc",
"pascal",
"php",
"python",
"r",
"ruby",
"rust",
"scala",
"solidity",
"swift",
"terraform",
"tsx",
"typescript",
"vbnet",
"zig",
];

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct CoverageInventory {
schema_version: String,
source_repository: String,
source_commit: String,
scope: String,
note: String,
assets: Vec<CoverageAsset>,
}

#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct CoverageAsset {
language: String,
provider: String,
source_path: Option<String>,
sha256: Option<String>,
bytes: Option<usize>,
}

/// 展示 CodeGraph 固定来源与 CodeGuard 已验证候选之间的覆盖差距。
/// 参数仅接受 `--format human|json`;返回 3 表示仍有未接入或未验收的 grammar。
pub fn run(args: &[String]) -> ExitCode {
let format = match args {
[] => "human",
[option, value] if option == "--format" && matches!(value.as_str(), "human" | "json") => {
value.as_str()
}
[option] if option == "--format=human" => "human",
[option] if option == "--format=json" => "json",
_ => {
eprintln!("grammar status 仅支持 --format human|json");
return ExitCode::from(2);
}
};
let report = match coverage_report() {
Ok(report) => report,
Err(reason) => {
eprintln!("grammar 来源清单未完成:{reason}");
return ExitCode::from(4);
}
};
if format == "json" {
println!("{report}");
} else {
println!(
"CodeGraph grammar 32 种独立资产:随仓固定 30;CodeGuard 候选 {};已验收发行 0。",
report["candidate_count"]
);
println!("全部语法初检覆盖未完成;原生 lint 义务不变。使用 --format=json 查看逐语言缺口。");
}
ExitCode::from(3)
}

fn coverage_report() -> Result<Value, String> {
let raw = include_bytes!("../../../grammars/codegraph-coverage.json");
let strict = codeguard_adapters::parse_unique_json(raw).map_err(str::to_owned)?;
let inventory: CoverageInventory =
serde_json::from_value(strict).map_err(|error| error.to_string())?;
validate_inventory(&inventory)?;
let candidates = bundled_grammar_candidates()?;
let candidate_languages = candidates
.assets
.iter()
.map(|asset| asset.language.as_str())
.collect::<BTreeSet<_>>();
let inventory_languages = inventory
.assets
.iter()
.map(|asset| asset.language.as_str())
.collect::<BTreeSet<_>>();
if !candidate_languages.is_subset(&inventory_languages) {
return Err("CodeGuard 候选不在 CodeGraph 来源清单中".into());
}
let assets = inventory
.assets
.iter()
.map(|asset| {
let candidate = candidates
.assets
.iter()
.find(|candidate| candidate.language == asset.language);
json!({
"language":asset.language,
"provider":asset.provider,
"source_path":asset.source_path,
"source_sha256":asset.sha256,
"source_bytes":asset.bytes,
"integration_status":candidate.map_or("not_integrated", |item| item.release_status.as_str()),
"runtime_observation":candidate.map(|item| item.codeguard_runtime_validation.as_str()),
"released":false,
"gap":if asset.provider == "tree_sitter_wasms_dependency" {"dependency_bytes_not_pinned"}
else if asset.bytes.is_some_and(|bytes| bytes > 8 * 1024 * 1024) {"current_loader_size_limit"}
else if candidate.is_some() {"language_qualification_and_release_pending"}
else {"asset_provenance_license_and_loader_validation_pending"},
})
})
.collect::<Vec<_>>();
Ok(json!({
"schema_version":"1.0.0",
"report_type":"grammar_coverage_inventory",
"source_repository":inventory.source_repository,
"source_commit":inventory.source_commit,
"codegraph_grammar_count":inventory.assets.len(),
"codegraph_vendored_count":inventory.assets.iter().filter(|asset| asset.provider == "codegraph_vendored").count(),
"candidate_count":candidates.assets.len(),
"released_count":0,
"authority":"source_inventory_only",
"parser_capability":"unverified",
"gate_effect":"none",
"execution":"not_run",
"delivery_decision":"not_evaluated",
"next_action":"固定各缺口资产的上游来源与许可证,逐语言验证 Rust 加载、版本语料及原生对照;不得凭此库存跳过原生 lint。",
"assets":assets,
}))
}

fn validate_inventory(inventory: &CoverageInventory) -> Result<(), String> {
if inventory.schema_version != "1.0.0"
|| inventory.source_repository != "https://github.com/partme-ai/codegraph"
|| inventory.source_commit != SOURCE_COMMIT
|| inventory.scope != "CodeGraph grammar file map excluding the jsx alias of javascript"
|| inventory.note.is_empty()
|| inventory.assets.len() != LANGUAGES.len()
{
return Err("CodeGraph 来源清单版本、来源或范围不符".into());
}
let mut seen = BTreeSet::new();
let mut vendored = 0;
for asset in &inventory.assets {
if !seen.insert(asset.language.as_str()) {
return Err("CodeGraph grammar 重复".into());
}
match asset.provider.as_str() {
"codegraph_vendored" => {
vendored += 1;
let filename = if asset.language == "csharp" {
"c_sharp"
} else {
&asset.language
};
if asset.source_path.as_deref()
!= Some(format!("src/extraction/wasm/tree-sitter-{filename}.wasm").as_str())
|| !asset.sha256.as_deref().is_some_and(valid_sha256)
|| asset.bytes.is_none_or(|bytes| bytes < 8)
{
return Err("CodeGraph 随仓资产身份无效".into());
}
}
"tree_sitter_wasms_dependency" => {
if !matches!(asset.language.as_str(), "objc" | "solidity")
|| asset.source_path.is_some()
|| asset.sha256.is_some()
|| asset.bytes.is_some()
{
return Err("CodeGraph 依赖资产身份无效".into());
}
}
_ => return Err("CodeGraph 资产来源未知".into()),
}
}
if vendored != 30 || seen != LANGUAGES.into_iter().collect() {
return Err("CodeGraph grammar 覆盖集合不符".into());
}
Ok(())
}

fn valid_sha256(value: &str) -> bool {
value.len() == 64
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}
1 change: 1 addition & 0 deletions crates/codeguard-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub mod git_index_safety;
pub mod git_index_safety_command;
#[cfg(unix)]
pub mod go_lint_command;
pub mod grammar_status_command;
#[cfg(unix)]
pub mod hook_execute_command;
pub mod hook_plan_command;
Expand Down
6 changes: 6 additions & 0 deletions crates/codeguard-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ fn main() -> ExitCode {
println!(
"规则目录:rules list <language|all> [path] [--format human|json];只读配置声明、候选规则来源及目录缺口,不执行检查或批准白名单。"
);
println!(
"语法资产覆盖:grammar status [--format human|json];只读列出固定 CodeGraph 来源的 32 种独立 grammar、CodeGuard 候选与未接入项;不加载 WASM、不运行 lint。"
);
println!(
"检查预算:check all 支持 --jobs 1–64 和 CODEGUARD_JOBS;--timeout 优先于 CODEGUARD_TIMEOUT,默认 30m。项目默认值见 .codeguard/runtime.json 1.1;当前仅原生执行受截止时间约束。"
);
Expand All @@ -76,6 +79,9 @@ fn main() -> ExitCode {
[command, rest @ ..] if command == "init" => codeguard_cli::init_command::run(rest),
[command, rest @ ..] if command == "config" => codeguard_cli::config_command::run(rest),
[command, rest @ ..] if command == "tools" => codeguard_cli::tools_command::run(rest),
[command, operation, rest @ ..] if command == "grammar" && operation == "status" => {
codeguard_cli::grammar_status_command::run(rest)
}
#[cfg(unix)]
[command, rest @ ..] if command == "doctor" => codeguard_cli::doctor_command::run(rest),
[command, rest @ ..] if command == "plan" => codeguard_cli::plan_command::run(rest),
Expand Down
60 changes: 60 additions & 0 deletions crates/codeguard-cli/tests/grammar_status_cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use serde_json::Value;
use std::process::Command;

#[test]
fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() {
let output = Command::new(env!("CARGO_BIN_EXE_codeguard"))
.args(["grammar", "status", "--format=json"])
.output()
.expect("grammar status");
assert_eq!(output.status.code(), Some(3));
let report: Value = serde_json::from_slice(&output.stdout).expect("JSON report");
assert_eq!(report["report_type"], "grammar_coverage_inventory");
assert_eq!(report["codegraph_grammar_count"], 32);
assert_eq!(report["codegraph_vendored_count"], 30);
assert_eq!(report["candidate_count"], 2);
assert_eq!(report["released_count"], 0);
assert_eq!(report["authority"], "source_inventory_only");
assert_eq!(report["parser_capability"], "unverified");
assert_eq!(report["gate_effect"], "none");
assert_eq!(report["execution"], "not_run");
assert_eq!(report["delivery_decision"], "not_evaluated");
let assets = report["assets"].as_array().unwrap();
assert_eq!(assets.len(), 32);
for asset in assets {
assert_eq!(asset["released"], false);
assert!(!asset["gap"].as_str().unwrap().is_empty());
}
for language in ["java", "typescript"] {
let asset = assets
.iter()
.find(|row| row["language"] == language)
.unwrap();
assert_eq!(asset["integration_status"], "candidate_unvalidated");
assert_eq!(asset["runtime_observation"], "rust_loader_smoke_passed");
}
let cobol = assets
.iter()
.find(|row| row["language"] == "cobol")
.unwrap();
assert_eq!(cobol["gap"], "current_loader_size_limit");
assert!(cobol["source_bytes"].as_u64().unwrap() > 8 * 1024 * 1024);
for language in ["objc", "solidity"] {
let asset = assets
.iter()
.find(|row| row["language"] == language)
.unwrap();
assert_eq!(asset["gap"], "dependency_bytes_not_pinned");
assert!(asset["source_sha256"].is_null());
}
}

#[test]
fn grammar_status_rejects_unexpected_arguments_without_scanning() {
let output = Command::new(env!("CARGO_BIN_EXE_codeguard"))
.args(["grammar", "status", "--check"])
.output()
.expect("invalid grammar command");
assert_eq!(output.status.code(), Some(2));
assert!(output.stdout.is_empty());
}
2 changes: 2 additions & 0 deletions docs/Codeguard-Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ A completed native violation never selects fallback to obtain a clean result. A

Reuse pinned CodeGraph grammar WASM artifacts and applicable upstream licenses, source references, patches and corpus cases. Do not copy CodeGraph's graph extraction or source-blanking heuristics into a syntax verdict. CodeGraph's `src/extraction/grammars.ts` loads language artifacts through `web-tree-sitter`; successful loading there does not prove compatibility with Codeguard's Rust runtime. The local source review on 2026-09-28 found modified grammar artifacts and about 65 MB of grammar files, so a live directory is not a release input.

The read-only `codeguard grammar status` command now projects a [pinned CodeGraph coverage inventory](../grammars/codegraph-coverage.json): 30 vendored WASM files and two dependency-provided grammars, with Java and TypeScript as CodeGuard candidates and none qualified for release. This inventory is deliberately separate from the pinned [candidate asset manifest](../grammars/manifest.json). In particular, the COBOL source file exceeds the current loader's 8 MiB input limit. Inventory rows never trigger loading or count as supported parser capability.

```text
grammars/ # proposed release assets, not project state
├── manifest.json # immutable source, digest, ABI and capability map
Expand Down
2 changes: 2 additions & 0 deletions docs/Codeguard-Architecture.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ flowchart TD

复用固定版本的 CodeGraph grammar WASM,以及适用的上游许可证、源码引用、补丁和语料。不能把 CodeGraph 的图提取或源码遮盖启发式直接用于语法判定。CodeGraph 的 `src/extraction/grammars.ts` 通过 `web-tree-sitter` 加载语言资产,在那里可加载不证明兼容 Codeguard 的 Rust 运行时。发行时必须固定不可变的来源与资产清单,不能把正在修改的本地目录直接当作发行输入。

只读 `codeguard grammar status` 现投影[固定来源覆盖库存](../grammars/codegraph-coverage.json):CodeGraph 随仓 30 份 WASM,依赖提供另两种独立 grammar;CodeGuard 只有 Java、TypeScript 两份候选,尚无已验收发行能力。覆盖库存与[候选资产清单](../grammars/manifest.json)分开,不能凭库存行自动加载或宣称支持。尤其 COBOL 来源资产超过当前加载器 8 MiB 限额。

```text
grammars/ # 规划中的发行资产,不是项目状态
├── manifest.json # 固定来源、摘要、ABI 及能力映射
Expand Down
2 changes: 2 additions & 0 deletions docs/Codeguard-Technical-Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ The opt-in CLI build now also emits [ESLint feedback 0.3.0](../schemas/eslint-lo

Import only pinned WASM bytes with upstream license, commit/patch provenance, SHA-256, ABI, tested runtime and language/dialect ranges, corpus reference and known gaps. Verify each copied CodeGraph artifact in the selected Rust Tree-sitter runtime; do not copy TypeScript extraction logic or assume CodeGraph support equals syntax acceptance. A release manifest belongs to distribution assets, not writable `.codeguard/` project state.

Use `codeguard grammar status --format=json` to query the separate source coverage inventory. It reports 32 distinct CodeGraph grammar assets, two CodeGuard candidates, and zero released capabilities without running a parser. The two dependency-provided assets lack pinned bytes, and COBOL exceeds the current 8 MiB loader limit; these remain explicit gaps before any per-language qualification.

Use a parent-controlled parser worker with a total deadline, per-file input bound, memory/process limit and capped diagnostics. Lazy-load selected grammars, parse without general network/filesystem imports, and terminate a stuck worker without losing native results from other modules. Host-specific enforcement and Rust MSRV compatibility must be tested; numeric production budgets remain measurement-driven rather than invented promises.

The opt-in Rust CLI now has a private [candidate worker](../crates/codeguard-cli/src/syntax_worker_command.rs) and [parent verifier](../crates/codeguard-cli/src/syntax_worker_runner.rs). On Unix they use the existing process-group runner with a 1 MiB source limit, 64 KiB output limit, caller deadline and cancellation, then recheck source/grammar digests and recovery positions. This is a measured contract slice, not the finished isolation design: a verified memory bound, cross-platform enforcement, formal lint/check routing and release packaging remain open. Candidate grammar observations remain `incomplete` even when parsing finds no recovery nodes.
Expand Down
Loading
Loading