Skip to content

Commit 3032b25

Browse files
authored
Merge pull request #23 from full-stack-plugins/feat/rust-hook-prompt-guidance
按需校验语法 worker 的固定 grammar 资产
2 parents d778d58 + c6d7a63 commit 3032b25

6 files changed

Lines changed: 143 additions & 111 deletions

File tree

‎crates/codeguard-adapters/src/grammar_asset_manifest.rs‎

Lines changed: 113 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,39 @@ pub struct GrammarAsset {
9393
/// 读取仓内固定清单,并检查来源、许可证及候选资产字节。
9494
pub fn bundled_grammar_candidates() -> Result<GrammarAssetManifest, String> {
9595
let manifest = parse_grammar_asset_manifest(include_bytes!("../../../grammars/manifest.json"))?;
96-
let codegraph_license = include_bytes!("../../../grammars/LICENSE.codegraph");
97-
if digest(codegraph_license) != CODEGRAPH_LICENSE_SHA256 {
96+
verify_codegraph_license()?;
97+
verify_dependency_license(&manifest)?;
98+
for asset in &manifest.assets {
99+
verify_bundled_asset(asset)?;
100+
}
101+
Ok(manifest)
102+
}
103+
104+
/// 只核对指定内置 grammar 的字节与许可,供按需加载的语法工作进程使用。
105+
/// 参数为固定语言 ID;返回清单身份和静态 WASM 字节,未知语言返回错误。
106+
pub fn bundled_grammar_candidate(language: &str) -> Result<(GrammarAsset, &'static [u8]), String> {
107+
let manifest = parse_grammar_asset_manifest(include_bytes!("../../../grammars/manifest.json"))?;
108+
let asset = manifest
109+
.assets
110+
.iter()
111+
.find(|asset| asset.language == language)
112+
.ok_or("不支持的 grammar 语种")?;
113+
verify_codegraph_license()?;
114+
if matches!(language, "objc" | "solidity") {
115+
verify_dependency_license(&manifest)?;
116+
}
117+
let wasm = verify_bundled_asset(asset)?;
118+
Ok((asset.clone(), wasm))
119+
}
120+
121+
fn verify_codegraph_license() -> Result<(), String> {
122+
if digest(include_bytes!("../../../grammars/LICENSE.codegraph")) != CODEGRAPH_LICENSE_SHA256 {
98123
return Err("CodeGraph 许可证字节与固定来源不符".into());
99124
}
125+
Ok(())
126+
}
127+
128+
fn verify_dependency_license(manifest: &GrammarAssetManifest) -> Result<(), String> {
100129
if digest(include_bytes!(
101130
"../../../grammars/LICENSE.tree-sitter-wasms"
102131
)) != manifest
@@ -106,90 +135,92 @@ pub fn bundled_grammar_candidates() -> Result<GrammarAssetManifest, String> {
106135
{
107136
return Err("依赖包许可证字节与固定来源不符".into());
108137
}
109-
for asset in &manifest.assets {
110-
let (wasm, license) = match asset.language.as_str() {
111-
"c" => (
112-
include_bytes!("../../../grammars/c/parser.wasm").as_slice(),
113-
include_bytes!("../../../grammars/c/LICENSE").as_slice(),
114-
),
115-
"cpp" => (
116-
include_bytes!("../../../grammars/cpp/parser.wasm").as_slice(),
117-
include_bytes!("../../../grammars/cpp/LICENSE").as_slice(),
118-
),
119-
"csharp" => (
120-
include_bytes!("../../../grammars/csharp/parser.wasm").as_slice(),
121-
include_bytes!("../../../grammars/csharp/LICENSE").as_slice(),
122-
),
123-
"go" => (
124-
include_bytes!("../../../grammars/go/parser.wasm").as_slice(),
125-
include_bytes!("../../../grammars/go/LICENSE").as_slice(),
126-
),
127-
"javascript" => (
128-
include_bytes!("../../../grammars/javascript/parser.wasm").as_slice(),
129-
include_bytes!("../../../grammars/javascript/LICENSE").as_slice(),
130-
),
131-
"lua" => (
132-
include_bytes!("../../../grammars/lua/parser.wasm").as_slice(),
133-
include_bytes!("../../../grammars/lua/LICENSE").as_slice(),
134-
),
135-
"luau" => (
136-
include_bytes!("../../../grammars/luau/parser.wasm").as_slice(),
137-
include_bytes!("../../../grammars/luau/LICENSE").as_slice(),
138-
),
139-
"java" => (
140-
include_bytes!("../../../grammars/java/parser.wasm").as_slice(),
141-
include_bytes!("../../../grammars/java/LICENSE").as_slice(),
142-
),
143-
"typescript" => (
144-
include_bytes!("../../../grammars/typescript/parser.wasm").as_slice(),
145-
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
146-
),
147-
"python" => (
148-
include_bytes!("../../../grammars/python/parser.wasm").as_slice(),
149-
include_bytes!("../../../grammars/python/LICENSE").as_slice(),
150-
),
151-
"tsx" => (
152-
include_bytes!("../../../grammars/tsx/parser.wasm").as_slice(),
153-
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
154-
),
155-
"zig" => (
156-
include_bytes!("../../../grammars/zig/parser.wasm").as_slice(),
157-
include_bytes!("../../../grammars/zig/LICENSE").as_slice(),
158-
),
159-
"objc" => (
160-
include_bytes!("../../../grammars/objc/parser.wasm").as_slice(),
161-
include_bytes!("../../../grammars/objc/LICENSE").as_slice(),
162-
),
163-
"solidity" => (
164-
include_bytes!("../../../grammars/solidity/parser.wasm").as_slice(),
165-
include_bytes!("../../../grammars/solidity/LICENSE").as_slice(),
166-
),
167-
"rust" => (
168-
include_bytes!("../../../grammars/rust/parser.wasm").as_slice(),
169-
include_bytes!("../../../grammars/rust/LICENSE").as_slice(),
170-
),
171-
_ => return Err("候选语言未知".into()),
138+
Ok(())
139+
}
140+
141+
fn verify_bundled_asset(asset: &GrammarAsset) -> Result<&'static [u8], String> {
142+
let (wasm, license) = match asset.language.as_str() {
143+
"c" => (
144+
include_bytes!("../../../grammars/c/parser.wasm").as_slice(),
145+
include_bytes!("../../../grammars/c/LICENSE").as_slice(),
146+
),
147+
"cpp" => (
148+
include_bytes!("../../../grammars/cpp/parser.wasm").as_slice(),
149+
include_bytes!("../../../grammars/cpp/LICENSE").as_slice(),
150+
),
151+
"csharp" => (
152+
include_bytes!("../../../grammars/csharp/parser.wasm").as_slice(),
153+
include_bytes!("../../../grammars/csharp/LICENSE").as_slice(),
154+
),
155+
"go" => (
156+
include_bytes!("../../../grammars/go/parser.wasm").as_slice(),
157+
include_bytes!("../../../grammars/go/LICENSE").as_slice(),
158+
),
159+
"javascript" => (
160+
include_bytes!("../../../grammars/javascript/parser.wasm").as_slice(),
161+
include_bytes!("../../../grammars/javascript/LICENSE").as_slice(),
162+
),
163+
"lua" => (
164+
include_bytes!("../../../grammars/lua/parser.wasm").as_slice(),
165+
include_bytes!("../../../grammars/lua/LICENSE").as_slice(),
166+
),
167+
"luau" => (
168+
include_bytes!("../../../grammars/luau/parser.wasm").as_slice(),
169+
include_bytes!("../../../grammars/luau/LICENSE").as_slice(),
170+
),
171+
"java" => (
172+
include_bytes!("../../../grammars/java/parser.wasm").as_slice(),
173+
include_bytes!("../../../grammars/java/LICENSE").as_slice(),
174+
),
175+
"typescript" => (
176+
include_bytes!("../../../grammars/typescript/parser.wasm").as_slice(),
177+
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
178+
),
179+
"python" => (
180+
include_bytes!("../../../grammars/python/parser.wasm").as_slice(),
181+
include_bytes!("../../../grammars/python/LICENSE").as_slice(),
182+
),
183+
"tsx" => (
184+
include_bytes!("../../../grammars/tsx/parser.wasm").as_slice(),
185+
include_bytes!("../../../grammars/typescript/LICENSE").as_slice(),
186+
),
187+
"zig" => (
188+
include_bytes!("../../../grammars/zig/parser.wasm").as_slice(),
189+
include_bytes!("../../../grammars/zig/LICENSE").as_slice(),
190+
),
191+
"objc" => (
192+
include_bytes!("../../../grammars/objc/parser.wasm").as_slice(),
193+
include_bytes!("../../../grammars/objc/LICENSE").as_slice(),
194+
),
195+
"solidity" => (
196+
include_bytes!("../../../grammars/solidity/parser.wasm").as_slice(),
197+
include_bytes!("../../../grammars/solidity/LICENSE").as_slice(),
198+
),
199+
"rust" => (
200+
include_bytes!("../../../grammars/rust/parser.wasm").as_slice(),
201+
include_bytes!("../../../grammars/rust/LICENSE").as_slice(),
202+
),
203+
_ => return Err("候选语言未知".into()),
204+
};
205+
verify_grammar_asset(asset, wasm, license)?;
206+
if asset.language == "zig"
207+
&& crate::adapt_zig_wasm(include_bytes!("../../../grammars/zig/source.wasm"))? != wasm
208+
{
209+
return Err("Zig grammar 来源适配关系不符".into());
210+
}
211+
if matches!(asset.language.as_str(), "objc" | "solidity") {
212+
let source = if asset.language == "objc" {
213+
include_bytes!("../../../grammars/objc/source.wasm").as_slice()
214+
} else {
215+
include_bytes!("../../../grammars/solidity/source.wasm").as_slice()
172216
};
173-
verify_grammar_asset(asset, wasm, license)?;
174-
if asset.language == "zig"
175-
&& crate::adapt_zig_wasm(include_bytes!("../../../grammars/zig/source.wasm"))? != wasm
217+
if asset.source_sha256.as_deref() != Some(digest(source).as_str())
218+
|| crate::adapt_legacy_dylink(&asset.language, source)? != wasm
176219
{
177-
return Err("Zig grammar 来源适配关系不符".into());
178-
}
179-
if matches!(asset.language.as_str(), "objc" | "solidity") {
180-
let source = if asset.language == "objc" {
181-
include_bytes!("../../../grammars/objc/source.wasm").as_slice()
182-
} else {
183-
include_bytes!("../../../grammars/solidity/source.wasm").as_slice()
184-
};
185-
if asset.source_sha256.as_deref() != Some(digest(source).as_str())
186-
|| crate::adapt_legacy_dylink(&asset.language, source)? != wasm
187-
{
188-
return Err("依赖 grammar 来源适配关系不符".into());
189-
}
220+
return Err("依赖 grammar 来源适配关系不符".into());
190221
}
191222
}
192-
Ok(manifest)
223+
Ok(wasm)
193224
}
194225

195226
/// 解析来源固定的候选清单;拒绝重复字段、漂移来源和伪造的已验收状态。

‎crates/codeguard-adapters/src/lib.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ mod grammar_asset_manifest;
6767
mod legacy_dylink_compat;
6868
mod zig_wasm_compat;
6969
pub use grammar_asset_manifest::{
70-
GrammarAsset, GrammarAssetManifest, bundled_grammar_candidates, parse_grammar_asset_manifest,
71-
verify_grammar_asset,
70+
GrammarAsset, GrammarAssetManifest, bundled_grammar_candidate, bundled_grammar_candidates,
71+
parse_grammar_asset_manifest, verify_grammar_asset,
7272
};
7373
pub use legacy_dylink_compat::adapt_legacy_dylink;
7474
pub use zig_wasm_compat::adapt_zig_wasm;

‎crates/codeguard-adapters/tests/grammar_asset_manifest.rs‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
use codeguard_adapters::{
2-
adapt_legacy_dylink, adapt_zig_wasm, bundled_grammar_candidates, parse_grammar_asset_manifest,
3-
verify_grammar_asset,
2+
adapt_legacy_dylink, adapt_zig_wasm, bundled_grammar_candidate, bundled_grammar_candidates,
3+
parse_grammar_asset_manifest, verify_grammar_asset,
44
};
5+
6+
#[test]
7+
fn selected_candidate_matches_full_manifest_without_promoting_unknown_language() {
8+
let full = bundled_grammar_candidates().expect("all bundled assets");
9+
for asset in &full.assets {
10+
let (selected, wasm) = bundled_grammar_candidate(&asset.language).expect("selected asset");
11+
assert_eq!(&selected, asset);
12+
assert_eq!(wasm.len(), asset.bytes);
13+
}
14+
assert!(bundled_grammar_candidate("unknown").is_err());
15+
}
516
use serde_json::Value;
617

718
#[test]

‎crates/codeguard-cli/src/syntax_worker_command.rs‎

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::syntax_worker_envelope::SyntaxWorkerEnvelope;
44
use crate::syntax_worker_recovery::SyntaxWorkerRecovery;
5-
use codeguard_adapters::bundled_grammar_candidates;
5+
use codeguard_adapters::bundled_grammar_candidate;
66
use codeguard_runtime::{WasmGrammar, scan_wasm_recoveries};
77
use sha2::{Digest, Sha256};
88
use std::io::Read;
@@ -54,33 +54,10 @@ pub fn run(args: &[String]) -> ExitCode {
5454
}
5555

5656
fn observe(language: &str, source: &[u8]) -> Result<SyntaxWorkerEnvelope, String> {
57-
let manifest = bundled_grammar_candidates()?;
58-
let asset = manifest
59-
.assets
60-
.iter()
61-
.find(|asset| asset.language == language)
62-
.ok_or("不支持的 grammar 语种")?;
57+
let (asset, wasm) = bundled_grammar_candidate(language)?;
6358
if asset.codeguard_runtime_validation != "rust_loader_smoke_passed" {
6459
return Err("grammar 与当前 Rust WASM 运行时不兼容".into());
6560
}
66-
let wasm = match language {
67-
"c" => include_bytes!("../../../grammars/c/parser.wasm").as_slice(),
68-
"cpp" => include_bytes!("../../../grammars/cpp/parser.wasm").as_slice(),
69-
"csharp" => include_bytes!("../../../grammars/csharp/parser.wasm").as_slice(),
70-
"go" => include_bytes!("../../../grammars/go/parser.wasm").as_slice(),
71-
"javascript" => include_bytes!("../../../grammars/javascript/parser.wasm").as_slice(),
72-
"lua" => include_bytes!("../../../grammars/lua/parser.wasm").as_slice(),
73-
"luau" => include_bytes!("../../../grammars/luau/parser.wasm").as_slice(),
74-
"java" => include_bytes!("../../../grammars/java/parser.wasm").as_slice(),
75-
"python" => include_bytes!("../../../grammars/python/parser.wasm").as_slice(),
76-
"typescript" => include_bytes!("../../../grammars/typescript/parser.wasm").as_slice(),
77-
"tsx" => include_bytes!("../../../grammars/tsx/parser.wasm").as_slice(),
78-
"zig" => include_bytes!("../../../grammars/zig/parser.wasm").as_slice(),
79-
"objc" => include_bytes!("../../../grammars/objc/parser.wasm").as_slice(),
80-
"solidity" => include_bytes!("../../../grammars/solidity/parser.wasm").as_slice(),
81-
"rust" => include_bytes!("../../../grammars/rust/parser.wasm").as_slice(),
82-
_ => return Err("不支持的 grammar 语种".into()),
83-
};
8461
let mut grammar = WasmGrammar::load(
8562
asset.loader_symbol.as_deref().unwrap_or(language),
8663
wasm,

‎openspec/changes/introduce-rust-codeguard-cli/tasks.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,3 +1047,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30
10471047
- [ ] 14.17 逐语言误报与性能评测。责任:评测/adapters。规格:SP06,SP12。依赖:14.3,14.4,14.11。验收:独立合法/非法 oracle、版本/方言语料、原生对照;分别报告误报、漏报、未知覆盖及冷暖启动/包体/内存;先实测再定预算。
10481048
- [ ] 14.18 打包兼容与分阶段发布验收。责任:发布/CLI。规格:SP04,SP11,SP12。依赖:14.12-14.17。验收:每个平台验证内置资产/许可证/散列和离线 npx;保持退出码、统一入口及旧报告读取;同步中英文示例;未验收语言/宿主仍标 gap。
10491049
- [ ] 14.19 CodeGraph 32 种 grammar 全量接入与 Zig 误报回归。责任:adapters/runtime/CLI/发布。规格:SP01,SP04,SP06,SP11,SP12。依赖:14.1-14.18。验收:逐语言固定可再分发来源、许可证、原始和发行字节、真实 ABI、Rust worker 离线加载、合法/非法版本语料、原生工具对照、原生优先统一命令、任务与对话反馈、资源预算及发行包实装;覆盖 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。当前十五份候选(C/C++/C#/Go/Java/JavaScript/Lua/Luau/Objective-C/Python/Rust/Solidity/TypeScript/TSX/Zig),已验收发行 0;COBOL 超出 8 MiB 上限,其他 17 种仍无资产清单和执行路由。C++/C#/Lua/Luau 的固定来源、许可证、真实 ABI、离线加载及基础正反例见[局部验收](../../../tests/acceptance/cpp-csharp-lua-luau-grammar-candidates.md)。C/Go/JavaScript/Rust 的固定来源、许可证、离线加载及基础正反例见[局部验收](../../../tests/acceptance/mainstream-grammar-candidates.md)。不得以 `grammar status` 库存、WASM 可加载或未验收候选替代原生 lint 或语言验收。
1050+
1051+
14.19 性能子项进展:候选 worker 已改为仅校验所选固定 grammar 及相关许可证,避免每次单文件初检遍历全部 WASM;全量库存命令保持完整校验。选择接口先 RED 后通过 15 语种身份一致性测试,单次调试构建启动观察见[局部验收](../../../tests/acceptance/syntax-worker-selected-asset.md)。这不是正式性能评测,也不改变 15/32 候选和 0 份发行验收的状态。
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# 语法 worker 按需资产校验局部验收
2+
3+
日期:2026-09-29。范围:已固定的 15 份候选 grammar,仍非语言能力或发行验收。
4+
5+
原实现的每次 `__syntax-worker <language>` 都先调用全量 `bundled_grammar_candidates()`,对所有已打包 WASM 和许可证重新计算摘要,再选择一份资产。随着语言数增加,单文件反馈会承担无关语言的校验开销。
6+
7+
现在 worker 通过 `bundled_grammar_candidate(language)` 仅选择固定清单中的目标语言,校验 CodeGraph 许可证、该语言的 WASM 与许可证,以及该语言适用的来源适配关系;Objective-C/Solidity 额外校验依赖包许可证。未知语言拒绝。全量 `grammar status` 仍校验全部资产。测试先因缺少选择接口而编译失败,随后证明 15 份选择结果与全量清单身份及字节长度一致,未知语言拒绝。
8+
9+
调试构建、同一主机上的一次前后观察:Lua worker 从约 2.14 秒到 1.63 秒,C# 从约 3.25 秒到 1.62 秒。此观察包含进程启动和 Rust WASM 解析开销,不能分离摘要计算贡献,也不能当作正式冷/暖启动性能基准。
10+
11+
未完成:32 份资产覆盖、逐语言语料与原生对照、公开原生优先路由、宿主实装及发行验收。候选仍不能代替原生 lint;结果仍为 `incomplete`。

0 commit comments

Comments
 (0)