From ccaa3f72fc6f79be19ad3c6c9d81606ff1b0c10a Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:42:34 +0800 Subject: [PATCH 01/31] =?UTF-8?q?test:=20=E9=AA=8C=E8=AF=81=20npm=20?= =?UTF-8?q?=E5=8C=85=E5=86=85=2032=20=E4=BB=BD=E8=AF=AD=E6=B3=95=E5=80=99?= =?UTF-8?q?=E9=80=89=E5=AE=9E=E9=99=85=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/Codeguard-Technical-Design.md | 2 +- docs/Codeguard-Technical-Design.zh_CN.md | 2 +- .../introduce-rust-codeguard-cli/tasks.md | 2 +- tests/acceptance/npm-wasm-local-package.md | 14 ++-- tests/npm_pack_wasm.test.mjs | 71 ++++++++++++++++++- 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 00dc6ee8..d3b43647 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -578,7 +578,7 @@ The Node path supports the published macOS arm64 package and local packaging. Fo The `--public` packer mode produced `@partme.ai/codeguard@0.1.2` for `darwin-arm64`; its npm `os`/`cpu` constraints reject other hosts. Registry publication, a fresh-cache registry-hosted `npx --yes @partme.ai/codeguard@0.1.2 --version --format json` run, and registry/local tarball and binary SHA-256 comparisons passed on Apple Silicon macOS. Its clean-checkout build identity reports the candidate source commit but does not prove a reproducible build, signed provenance, or S13 release completion; see [acceptance evidence](../tests/acceptance/npm-0.1.2-candidate.md). For broader distribution, adapt CodeGraph's thin entry/platform bundle pattern only after S13 release evidence is ready: publish a version-matched package for each certified target, then a public command package with exact optional platform dependencies. Missing platform support must fail clearly; network recovery must follow Codeguard's signed selection and explicit host permission contract before any download. -For a later WASM candidate package, `--require-wasm` verifies the binary's 32 reported identities against the pinned manifest, rejects a default build without the worker, and executes a real Zig grammar probe before packing. `--public` implies this guard and retains its clean-checkout/build-identity checks. A local private tarball then runs offline through `npm exec`; Zig and Dart probes demonstrate the launcher reaches the bundled Rust worker. This is [local packaging evidence](../tests/acceptance/npm-wasm-local-package.md), not a registry release or per-language syntax qualification. The published `0.1.2` package predates the guard and has no WASM fallback; a future public release needs a new version, platform and license verification, and the remaining S14 acceptance. +For a later WASM candidate package, `--require-wasm` verifies the binary's 32 reported identities against the pinned manifest, rejects a default build without the worker, and executes a real Zig grammar probe before packing. `--public` implies this guard and retains its clean-checkout/build-identity checks. A local private tarball then runs offline through `npm exec`; Zig and Dart probes demonstrate the launcher reaches the bundled Rust worker, and four bounded installed-package `check all` runs invoke all 32 candidates. This is [local packaging evidence](../tests/acceptance/npm-wasm-local-package.md), not a registry release or per-language syntax qualification. The published `0.1.2` package predates the guard and has no WASM fallback; a future public release needs a new version, platform and license verification, and the remaining S14 acceptance. ## 12. Test and evaluation plan diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 02fec1ee..95bb40ba 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -578,7 +578,7 @@ Node 路径支持已发布的 macOS arm64 包和本地打包。本地打包流 `--public` 打包模式已产出仅适用于 `darwin-arm64` 的 `@partme.ai/codeguard@0.1.2`;npm `os`/`cpu` 限制会拒绝其它宿主。npm 仓库发布、Apple Silicon macOS 上全新缓存运行 `npx --yes @partme.ai/codeguard@0.1.2 --version --format json`、注册表/本地 tarball 及二进制 SHA-256 对比均通过。干净 checkout 构建身份回报候选源码提交,但不证明可复现构建或签名来源,也不代表 S13 发行证据已完成,见[验收记录](../tests/acceptance/npm-0.1.2-candidate.md)。扩大分发范围可参考 CodeGraph 的精简入口加平台包模式,但须先满足 S13 发行证据:为每个通过验收的平台发布版本一致的包,再提供含精确可选平台依赖的公开命令包。缺对应平台包时应给出清晰错误;如需网络补包,下载前必须遵循 Codeguard 的签名选择与独立宿主授权。 -后续 WASM 候选包使用 `--require-wasm`,打包前将二进制报告的 32 份身份逐一与固定清单核对,拒绝无 worker 的默认构建,并实际执行 Zig grammar 探针。`--public` 自动启用该门禁,同时保留干净源码和构建身份核对。本地 private tarball 再经离线 `npm exec` 运行;Zig、Dart 探针证明 Node 入口能够调用内置 Rust worker。见[本地打包验收](../tests/acceptance/npm-wasm-local-package.md)。这不是注册表发行或逐语言语法验收。已发布的 `0.1.2` 早于该门禁,不含 WASM 兜底;未来公开版本须升级版本号,补齐平台、许可证和 S14 剩余验收。 +后续 WASM 候选包使用 `--require-wasm`,打包前将二进制报告的 32 份身份逐一与固定清单核对,拒绝无 worker 的默认构建,并实际执行 Zig grammar 探针。`--public` 自动启用该门禁,同时保留干净源码和构建身份核对。本地 private tarball 再经离线 `npm exec` 运行;Zig、Dart 探针证明 Node 入口能够调用内置 Rust worker,四个有界项目的包内 `check all` 实际调用全部 32 份候选。见[本地打包验收](../tests/acceptance/npm-wasm-local-package.md)。这不是注册表发行或逐语言语法验收。已发布的 `0.1.2` 早于该门禁,不含 WASM 兜底;未来公开版本须升级版本号,补齐平台、许可证和 S14 剩余验收。 ## 12. 测试与评估方案 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index ab01fb70..cd66eb44 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1074,4 +1074,4 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.6 / 14.7 逐文件原生优先局部增量:源码可选构建中的 `check all` 现只在 Ruff 本轮完整检查同一路径、工具/配置摘要存在且源码 SHA-256 与候选阶段重新读取的字节一致时,对该 Python 文件跳过重复 WASM;`findings` 原生诊断仍原样保留,混合项目的 Zig 文件继续跑固定 worker,整体仍为退出码 3 和 `incomplete`。封闭协议升至 0.33.0,0.32.0 单独归档,新增 `native_preferred_count`;未完成扫描、路径错配或源码变化不得跳过。见[Python 原生优先局部验收](../../../tests/acceptance/check-all-native-preferred-python.md)。尚未覆盖其它 31 种 grammar 的逐模块原生能力判定、独立语法 oracle、任务/宿主反馈及发行验收;14.6/14.7/14.19 保持未完成。 -14.18 / 14.19 本地 npm 打包增量:打包器现可用 `--require-wasm` 核对二进制内 32 份候选身份和固定 SHA-256,实跑 Zig worker;`--public` 自动启用该门禁。无 WASM 特性的默认二进制在出包前被拒。本机 private tarball 经离线 `npm exec` 读到 32 份清单并实际运行 Zig、Dart 候选 worker,仍返回退出码 3、未验收且交付未评估;见[本地 npm 包局部验收](../../../tests/acceptance/npm-wasm-local-package.md)。Linux CI 的同一测试待本次提交运行;已发布 `0.1.2` 不含此能力,尚无新版本公开发行、所有 32 份包内逐一正反例、逐语言原生对照或宿主反馈。14.18、14.19 不勾选。 +14.18 / 14.19 本地 npm 打包增量:打包器现可用 `--require-wasm` 核对二进制内 32 份候选身份和固定 SHA-256,实跑 Zig worker;`--public` 自动启用该门禁。无 WASM 特性的默认二进制在出包前被拒。本机 private tarball 经离线 `npm exec` 读到 32 份清单并实际运行 Zig、Dart 候选 worker;安装后 `check all` 分四个有界项目调用全部 32 份,候选并集与清单一致,每组跳过 0、退出码 3、交付未完成。原有两项 Linux CI [36610681267](https://github.com/full-stack-plugins/codeguard/actions/runs/36610681267) 通过;新增全量用例在本机通过,Linux 待本次提交运行。见[本地 npm 包局部验收](../../../tests/acceptance/npm-wasm-local-package.md)。已发布 `0.1.2` 不含此能力,尚无新版本公开发行、所有 32 份包内逐一正反例、逐语言原生对照或宿主反馈。14.18、14.19 不勾选。 diff --git a/tests/acceptance/npm-wasm-local-package.md b/tests/acceptance/npm-wasm-local-package.md index be5397fa..fd004cbd 100644 --- a/tests/acceptance/npm-wasm-local-package.md +++ b/tests/acceptance/npm-wasm-local-package.md @@ -9,14 +9,18 @@ 本机 Apple Silicon macOS 使用以下指令验证: ```bash -cargo build --locked --offline -p codeguard-cli --features wasm-precheck -CODEGUARD_NO_WASM_BIN=/tmp/codeguard-default-for-wasm-pack-af20dd8 \ +cargo build --locked -p codeguard-cli +cp target/debug/codeguard target/debug/codeguard-no-wasm +cargo build --locked -p codeguard-cli --features wasm-precheck +CODEGUARD_NO_WASM_BIN="$PWD/target/debug/codeguard-no-wasm" \ CODEGUARD_WASM_BIN="$PWD/target/debug/codeguard" \ node --test tests/npm_pack_wasm.test.mjs ``` -测试先要求一个单独保存的默认构建作为拒绝反例;上述 `/tmp` 路径只是本次本机临时证据,不是仓库或 CI 的固定路径。CI 先构建默认二进制并复制到 `$RUNNER_TEMP`,再构建 WASM 特性二进制,给同一测试传入两条实际路径。 +测试先要求一个单独保存的默认构建作为拒绝反例;示例把副本保存在被 Git 忽略的 `target/`,不是发行资产。CI 先构建默认二进制并复制到 `$RUNNER_TEMP`,再构建 WASM 特性二进制,给同一测试传入两条实际路径。 -本机结果:2/2 项通过。负例确认无 WASM worker 的二进制在打包前被拒。正例产出 private 本地 tarball,用独立缓存执行 `npm exec --offline --package -- codeguard grammar status --format=json`,得到 32 份候选、0 份已验收发行;随后同一离线安装的 Rust 程序分别解析 Zig 和 Dart 源码,返回退出码 3、固定 grammar 摘要、`grammar_qualified=false`、`native.status=not_run`、`delivery_decision=not_evaluated`。测试中的临时源码采用物理路径,避免 macOS `/var` 别名路径触发源码路径安全检查。首次按别名路径测试失败后已修正,并再次通过。 +本机结果:3/3 项通过。负例确认无 WASM worker 的二进制在打包前被拒。正例产出 private 本地 tarball,用独立缓存执行 `npm exec --offline --package -- codeguard grammar status --format=json`,得到 32 份候选、0 份已验收发行;随后同一离线安装的 Rust 程序分别解析 Zig 和 Dart 源码,返回退出码 3、固定 grammar 摘要、`grammar_qualified=false`、`native.status=not_run`、`delivery_decision=not_evaluated`。新增测试在安装后的统一 `check all` 中将 31 个源文件分为四个有界项目,报告里 `candidate_observed` 的语言并集与固定清单的 32 种完全相同;每组 `skipped_count=0`、`delivery_decision=incomplete`、退出码 3。本次三项测试合计约 101 秒,其中包内全量路由约 71 秒。测试中的临时源码采用物理路径,避免 macOS `/var` 别名路径触发源码路径安全检查。首次按别名路径测试失败后已修正,并再次通过。 -这是**本地单平台包可运行性**,不是 32 种语言全部通过 npm 包逐一解析、独立语法 oracle、误报/漏报、原生优先完整路由、Linux 包验收或公开 npm 发布。已发布的 `@partme.ai/codeguard@0.1.2` 不含本次 WASM 特性;同一版本不能再次发布为新能力。待 CI 实际结果和新版本发行证据到位后,再决定是否更新对应发布任务。S14.18、S14.19 不勾选。 +Linux CI [36610681267](https://github.com/full-stack-plugins/codeguard/actions/runs/36610681267) 的原有 2 项打包测试和完整工作区测试均通过。新增包内全量路由已在本机通过;Linux CI 仍待本次提交运行,不能把原有 CI 结果冒充为新增用例的证明。 + +这仍只是**本地单平台包可运行性**,不是独立语法 oracle、误报/漏报、原生优先完整路由、所有平台包验收或公开 npm 发布。已发布的 `@partme.ai/codeguard@0.1.2` 不含本次 WASM 特性;同一版本不能再次发布为新能力。S14.18、S14.19 不勾选。 diff --git a/tests/npm_pack_wasm.test.mjs b/tests/npm_pack_wasm.test.mjs index 114b9cb7..447343d8 100644 --- a/tests/npm_pack_wasm.test.mjs +++ b/tests/npm_pack_wasm.test.mjs @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; -import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; +import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; @@ -63,3 +63,72 @@ test('本地离线 npm 包实际运行固定 Zig 与 Dart WASM', () => { rmSync(scratch, { recursive: true, force: true }); } }); + +test('本地离线 npm 包通过统一 check all 调用全部 32 份候选', () => { + assert.ok(wasmBinary, 'CODEGUARD_WASM_BIN is required'); + const packed = spawnSync(process.execPath, [packer, '--require-wasm', wasmBinary], { + cwd: root, encoding: 'utf8', timeout: 120_000, + }); + assert.equal(packed.status, 0, packed.stderr); + const tarball = packed.stdout.trim(); + const scratch = realpathSync(mkdtempSync(path.join(os.tmpdir(), 'codeguard-npm-wasm-all-'))); + const samples = [ + ['a.ets', "@Component struct C { build() { Text('hi') } }"], + ['a.c', 'int main(void) { return 0; }'], + ['a.cfm', 'SELECT #x# FROM users'], + ['a.cfs', 'component { function f() { return 1; } }'], + ['a.cbl', ' IDENTIFICATION DIVISION.\n PROGRAM-ID. HELLO.\n'], + ['a.cpp', 'int main() { return 0; }'], + ['a.cs', 'class C { static int F() { return 1; } }'], + ['a.dart', "String greet(String name) => 'Hello $name';"], + ['a.erl', '-module(hello).\nhello() -> ok.\n'], + ['a.go', 'package main\nfunc main() {}\n'], + ['a.java', 'class A {}'], + ['a.js', 'const value = 1;'], + ['a.kt', 'fun main() { val x = 1 }'], + ['a.lua', 'local x = 1\n'], + ['a.luau', 'local x: number = 1\n'], + ['a.nix', 'let x = 1; in x'], + ['a.m', '@interface Foo : NSObject\n@end\n'], + ['a.pas', "program Hello;\nbegin\n writeln('Hi');\nend.\n"], + ['a.php', ' Int { return 1 }'], + ['a.tf', 'resource "x" "y" { foo = "bar" }'], + ['a.tsx', 'const C = () =>
;'], + ['a.ts', 'const value: number = 1;'], + ['a.vb', 'Public Class C\n Public Function F() As Integer\n Return 1\n End Function\nEnd Class\n'], + ['a.zig', 'const Empty = struct {};\n'], + ]; + try { + const observed = new Set(); + samples.forEach(([filename, source], index) => { + const group = path.join(scratch, `group-${Math.floor(index / 8)}`); + mkdirSync(group, { recursive: true }); + writeFileSync(path.join(group, filename), source); + }); + for (let groupIndex = 0; groupIndex < 4; groupIndex++) { + const checked = spawnSync('npm', [ + 'exec', '--offline', '--yes', '--cache', path.join(scratch, 'cache'), + '--package', tarball, '--', 'codeguard', 'check', 'all', path.join(scratch, `group-${groupIndex}`), + '--format=json', '--timeout', '120s', + ], { cwd: root, encoding: 'utf8', timeout: 150_000, maxBuffer: 4 * 1024 * 1024 }); + assert.equal(checked.status, 3, `group ${groupIndex}: ${checked.stderr}`); + const report = JSON.parse(checked.stdout); + assert.equal(report.delivery_decision, 'incomplete'); + assert.equal(report.syntax_candidates.skipped_count, 0, `group ${groupIndex}`); + for (const item of report.syntax_candidates.observations) { + if (item.status === 'candidate_observed') observed.add(item.language); + } + } + const manifest = JSON.parse(readFileSync(path.join(root, 'grammars', 'manifest.json'))); + assert.deepEqual([...observed].sort(), manifest.assets.map(asset => asset.language).sort()); + } finally { + rmSync(scratch, { recursive: true, force: true }); + } +}); From f4b6a1986c1e11ac5d33745221e4ab932e83cc07 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 11:56:42 +0800 Subject: [PATCH 02/31] =?UTF-8?q?docs:=20=E8=AE=B0=E5=BD=95=20Linux=20?= =?UTF-8?q?=E5=8C=85=E5=86=85=E5=85=A8=E9=87=8F=E8=AF=AD=E6=B3=95=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openspec/changes/introduce-rust-codeguard-cli/tasks.md | 2 +- tests/acceptance/npm-wasm-local-package.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index cd66eb44..f090f6a5 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1074,4 +1074,4 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.6 / 14.7 逐文件原生优先局部增量:源码可选构建中的 `check all` 现只在 Ruff 本轮完整检查同一路径、工具/配置摘要存在且源码 SHA-256 与候选阶段重新读取的字节一致时,对该 Python 文件跳过重复 WASM;`findings` 原生诊断仍原样保留,混合项目的 Zig 文件继续跑固定 worker,整体仍为退出码 3 和 `incomplete`。封闭协议升至 0.33.0,0.32.0 单独归档,新增 `native_preferred_count`;未完成扫描、路径错配或源码变化不得跳过。见[Python 原生优先局部验收](../../../tests/acceptance/check-all-native-preferred-python.md)。尚未覆盖其它 31 种 grammar 的逐模块原生能力判定、独立语法 oracle、任务/宿主反馈及发行验收;14.6/14.7/14.19 保持未完成。 -14.18 / 14.19 本地 npm 打包增量:打包器现可用 `--require-wasm` 核对二进制内 32 份候选身份和固定 SHA-256,实跑 Zig worker;`--public` 自动启用该门禁。无 WASM 特性的默认二进制在出包前被拒。本机 private tarball 经离线 `npm exec` 读到 32 份清单并实际运行 Zig、Dart 候选 worker;安装后 `check all` 分四个有界项目调用全部 32 份,候选并集与清单一致,每组跳过 0、退出码 3、交付未完成。原有两项 Linux CI [36610681267](https://github.com/full-stack-plugins/codeguard/actions/runs/36610681267) 通过;新增全量用例在本机通过,Linux 待本次提交运行。见[本地 npm 包局部验收](../../../tests/acceptance/npm-wasm-local-package.md)。已发布 `0.1.2` 不含此能力,尚无新版本公开发行、所有 32 份包内逐一正反例、逐语言原生对照或宿主反馈。14.18、14.19 不勾选。 +14.18 / 14.19 本地 npm 打包增量:打包器现可用 `--require-wasm` 核对二进制内 32 份候选身份和固定 SHA-256,实跑 Zig worker;`--public` 自动启用该门禁。无 WASM 特性的默认二进制在出包前被拒。本机 private tarball 经离线 `npm exec` 读到 32 份清单并实际运行 Zig、Dart 候选 worker;安装后 `check all` 分四个有界项目调用全部 32 份,候选并集与清单一致,每组跳过 0、退出码 3、交付未完成。Linux CI [37094165607](https://github.com/full-stack-plugins/codeguard/actions/runs/37094165607) 的新增全量包内测试与完整工作区测试通过;见[本地 npm 包局部验收](../../../tests/acceptance/npm-wasm-local-package.md)。已发布 `0.1.2` 不含此能力,尚无新版本公开发行、所有 32 份包内逐一正反例、逐语言原生对照或宿主反馈。14.18、14.19 不勾选。 diff --git a/tests/acceptance/npm-wasm-local-package.md b/tests/acceptance/npm-wasm-local-package.md index fd004cbd..44e3996e 100644 --- a/tests/acceptance/npm-wasm-local-package.md +++ b/tests/acceptance/npm-wasm-local-package.md @@ -21,6 +21,6 @@ node --test tests/npm_pack_wasm.test.mjs 本机结果:3/3 项通过。负例确认无 WASM worker 的二进制在打包前被拒。正例产出 private 本地 tarball,用独立缓存执行 `npm exec --offline --package -- codeguard grammar status --format=json`,得到 32 份候选、0 份已验收发行;随后同一离线安装的 Rust 程序分别解析 Zig 和 Dart 源码,返回退出码 3、固定 grammar 摘要、`grammar_qualified=false`、`native.status=not_run`、`delivery_decision=not_evaluated`。新增测试在安装后的统一 `check all` 中将 31 个源文件分为四个有界项目,报告里 `candidate_observed` 的语言并集与固定清单的 32 种完全相同;每组 `skipped_count=0`、`delivery_decision=incomplete`、退出码 3。本次三项测试合计约 101 秒,其中包内全量路由约 71 秒。测试中的临时源码采用物理路径,避免 macOS `/var` 别名路径触发源码路径安全检查。首次按别名路径测试失败后已修正,并再次通过。 -Linux CI [36610681267](https://github.com/full-stack-plugins/codeguard/actions/runs/36610681267) 的原有 2 项打包测试和完整工作区测试均通过。新增包内全量路由已在本机通过;Linux CI 仍待本次提交运行,不能把原有 CI 结果冒充为新增用例的证明。 +Linux CI [37094165607](https://github.com/full-stack-plugins/codeguard/actions/runs/37094165607) 在提交 `ccaa3f7` 上完成:包含新增全 32 份包内路由的 npm 步骤、既有 WASM worker 测试与完整工作区测试均成功。原有 [36610681267](https://github.com/full-stack-plugins/codeguard/actions/runs/36610681267) 只覆盖早先的两项打包测试,不用于证明新增用例。 这仍只是**本地单平台包可运行性**,不是独立语法 oracle、误报/漏报、原生优先完整路由、所有平台包验收或公开 npm 发布。已发布的 `@partme.ai/codeguard@0.1.2` 不含本次 WASM 特性;同一版本不能再次发布为新能力。S14.18、S14.19 不勾选。 From 422e1913f10a0e6dab488861a036a352c4acddf7 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:03:20 +0800 Subject: [PATCH 03/31] =?UTF-8?q?test:=20=E5=AF=B9=E7=85=A7=20Zig=20?= =?UTF-8?q?=E5=8E=9F=E7=94=9F=20AST=20=E9=AA=8C=E8=AF=81=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E5=80=99=E9=80=89=E7=B2=BE=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/zig_native_differential.rs | 80 +++++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + .../introduce-rust-codeguard-cli/tasks.md | 2 + tests/acceptance/zig-native-differential.md | 17 ++++ 5 files changed, 103 insertions(+) create mode 100644 crates/codeguard-cli/tests/zig_native_differential.rs create mode 100644 tests/acceptance/zig-native-differential.md diff --git a/crates/codeguard-cli/tests/zig_native_differential.rs b/crates/codeguard-cli/tests/zig_native_differential.rs new file mode 100644 index 00000000..fdb6efcf --- /dev/null +++ b/crates/codeguard-cli/tests/zig_native_differential.rs @@ -0,0 +1,80 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::io::Write; +use std::process::{Command, Stdio}; + +#[test] +#[ignore = "requires explicit existing Zig 0.16.0 via CODEGUARD_ZIG_BIN; independent native oracle"] +fn pinned_zig_worker_matches_native_ast_check_on_syntax_corpus() { + let zig = std::env::var("CODEGUARD_ZIG_BIN").expect("provide an existing Zig executable"); + let version = Command::new(&zig).arg("version").output().unwrap(); + assert!(version.status.success()); + assert_eq!(String::from_utf8_lossy(&version.stdout).trim(), "0.16.0"); + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-zig-differential-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + let mut compared = 0; + for (name, source) in [ + ("empty_struct", "const S = struct {};\n"), + ("empty_enum", "const E = enum {};\n"), + ("empty_union", "const U = union(enum) {};\n"), + ("empty_opaque", "const O = opaque {};\n"), + ("function", "fn add(a: i32, b: i32) i32 { return a + b; }\n"), + ("unicode_string", "const label = \"你好 Zig\";\n"), + ( + "nested_container", + "const S = struct { const Inner = struct {}; };\n", + ), + ("missing_brace", "const S = struct {\n"), + ( + "missing_paren", + "fn add(a: i32, b: i32 i32 { return a + b; }\n", + ), + ("unterminated_string", "const label = \"hello;\n"), + ("missing_initializer", "const value = ;\n"), + ] { + let mut native = Command::new(&zig) + .arg("ast-check") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + native + .stdin + .take() + .unwrap() + .write_all(source.as_bytes()) + .unwrap(); + let native = native.wait_with_output().unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1)), + "{name}: {native:?}" + ); + let file = root.join(format!("{name}.zig")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "zig"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + let recoveries = report["recoveries"].as_array().unwrap(); + assert_eq!( + recoveries.is_empty(), + native.status.success(), + "{name}: native={} worker={report}", + String::from_utf8_lossy(&native.stderr) + ); + compared += 1; + } + fs::remove_dir_all(root).unwrap(); + assert_eq!(compared, 11); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index d3b43647..4347f193 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -20,6 +20,8 @@ A source build with `--features wasm-precheck` now exposes `codeguard grammar pr A first narrow native-first route is now available for Zig: `lint zig FILE --zig-tool ABS_PATH --format=json` runs a Zig 0.16.0 `ast-check` executable with stable bytes on the selected source bytes before considering WASM. Native AST positions remain visible without source snippets; when no explicit tool is supplied, the pinned Zig grammar remains an unqualified fallback. Both outcomes stay incomplete because AST checking is narrower than full lint, build and tests. See the [0.1.0 feedback schema](../schemas/zig-lint-feedback-v0.1.schema.json). +The pinned Zig worker now also has an explicit, real Zig 0.16.0 differential test: seven valid and four malformed sources are sent independently to native `ast-check` and the public isolated `grammar probe` path. All eleven classifications matched on the tested host; this is a narrow [precision record](../tests/acceptance/zig-native-differential.md), not an error-rate estimate or language qualification. + ## 2. Technology choices and trade-offs | Concern | Selected technology / current evidence | Consequence and alternative | diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 95bb40ba..57fa65bd 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -20,6 +20,8 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选特性下的 首个局部原生优先入口为 Zig:`lint zig FILE --zig-tool ABS_PATH --format=json` 对选定源码字节先运行版本报告为 Zig 0.16.0 且字节保持一致的 `ast-check`;原生错误只暴露行列位置,不回显源码。未提供显式工具时,固定 Zig grammar 作为未验收候选兜底。两种结果都保持未完成,因为 AST 检查范围小于完整 lint、构建和测试;见 [0.1.0 报告 Schema](../schemas/zig-lint-feedback-v0.1.schema.json)。 +固定 Zig worker 现还有真实 Zig 0.16.0 差分测试:7 份合法、4 份破损源码分别送入原生 `ast-check` 和公开隔离 `grammar probe` 路径。本机 11 份分类全部一致;这只是窄范围[精度记录](../tests/acceptance/zig-native-differential.md),不是误报率估计或语种验收。 + ## 2. 技术选型与权衡 | 关注点 | 选型 / 当前证据 | 影响与替代方案 | diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index f090f6a5..8fd31896 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1075,3 +1075,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.6 / 14.7 逐文件原生优先局部增量:源码可选构建中的 `check all` 现只在 Ruff 本轮完整检查同一路径、工具/配置摘要存在且源码 SHA-256 与候选阶段重新读取的字节一致时,对该 Python 文件跳过重复 WASM;`findings` 原生诊断仍原样保留,混合项目的 Zig 文件继续跑固定 worker,整体仍为退出码 3 和 `incomplete`。封闭协议升至 0.33.0,0.32.0 单独归档,新增 `native_preferred_count`;未完成扫描、路径错配或源码变化不得跳过。见[Python 原生优先局部验收](../../../tests/acceptance/check-all-native-preferred-python.md)。尚未覆盖其它 31 种 grammar 的逐模块原生能力判定、独立语法 oracle、任务/宿主反馈及发行验收;14.6/14.7/14.19 保持未完成。 14.18 / 14.19 本地 npm 打包增量:打包器现可用 `--require-wasm` 核对二进制内 32 份候选身份和固定 SHA-256,实跑 Zig worker;`--public` 自动启用该门禁。无 WASM 特性的默认二进制在出包前被拒。本机 private tarball 经离线 `npm exec` 读到 32 份清单并实际运行 Zig、Dart 候选 worker;安装后 `check all` 分四个有界项目调用全部 32 份,候选并集与清单一致,每组跳过 0、退出码 3、交付未完成。Linux CI [37094165607](https://github.com/full-stack-plugins/codeguard/actions/runs/37094165607) 的新增全量包内测试与完整工作区测试通过;见[本地 npm 包局部验收](../../../tests/acceptance/npm-wasm-local-package.md)。已发布 `0.1.2` 不含此能力,尚无新版本公开发行、所有 32 份包内逐一正反例、逐语言原生对照或宿主反馈。14.18、14.19 不勾选。 + +14.4 / 14.17 / 14.19 Zig 原生差分局部增量:固定本机 Zig 0.16.0 `ast-check` 作为独立 oracle,公开隔离 worker 对同一 11 份合法/破损源码的恢复分类逐例一致,真实原生显式测试 1/1 通过;候选仍未验收且交付未评估。仅覆盖此版本的 7 个正例、4 个反例,不代表完整误报率、其他语种、版本/方言或发行资格;见[Zig 原生差分验收](../../../tests/acceptance/zig-native-differential.md)。14.4、14.17、14.19 不勾选。 diff --git a/tests/acceptance/zig-native-differential.md b/tests/acceptance/zig-native-differential.md new file mode 100644 index 00000000..ed26d3fe --- /dev/null +++ b/tests/acceptance/zig-native-differential.md @@ -0,0 +1,17 @@ +# Zig 0.16.0 原生 AST 与内置 WASM 的局部差分验收 + +日期:2026-10-03。对应既有 OpenSpec 14.4、14.17、14.19 的局部精度证据,任务均未完成。 + +新增被显式忽略的真实原生测试 `zig_native_differential`。运行时须提供现存 `CODEGUARD_ZIG_BIN`;测试先核对 `zig version` 为 0.16.0,再将完全相同的 UTF-8 源码分别送入 `zig ast-check` 的 stdin 和 CodeGuard 公开 `grammar probe zig` 的隔离 WASM worker。独立原生结果退出码 0/1 与候选恢复节点的有无逐例比较。测试还核对候选始终 `grammar_qualified=false`、`delivery_decision=not_evaluated`,避免差分一致被误当成交付通过。 + +本机所用 Zig 为 Homebrew 0.16.0,程序 SHA-256 `71cc3995a7586753ebf82c66dfb8bef43df446517550678781834586a960f8c9`。执行: + +```bash +CODEGUARD_ZIG_BIN=/opt/homebrew/bin/zig \ +cargo test --locked -p codeguard-cli --features wasm-precheck \ + --test zig_native_differential -- --ignored --nocapture +``` + +结果:1 项真实原生差分测试通过,11 份样例分类一致,其中 7 份合法样例覆盖空 struct/enum/union/opaque、函数、中文字符串和嵌套容器;4 份破损样例覆盖缺失大括号/右括号/初始化表达式和未闭合字符串。实际 worker 参与对照,而非只测试裸 parser 或资产清单。 + +这只能证明上述 11 个 Zig 0.16.0 样例的一致性。`ast-check` 不进行完整类型检查、构建或 lint;没有其他 Zig 版本、项目配置、宏/嵌入方言、系统误报率/漏报率、冷暖性能或公开发行验收。CFQuery 与 VB.NET 的既知精度缺口也不因 Zig 通过而改变。完整 S14.17、14.19 仍不勾选。 From 87f64e3d67151c003fdb6cc02d7d8ed741a225f7 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:05:39 +0800 Subject: [PATCH 04/31] =?UTF-8?q?test:=20=E9=94=81=E5=AE=9A=20Zig=20?= =?UTF-8?q?=E8=AF=AD=E6=B3=95=E5=B7=AE=E5=88=86=E8=AF=AD=E6=96=99=E7=9A=84?= =?UTF-8?q?=E5=8E=9F=E7=94=9F=E9=A2=84=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/zig_native_differential.rs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/crates/codeguard-cli/tests/zig_native_differential.rs b/crates/codeguard-cli/tests/zig_native_differential.rs index fdb6efcf..a5c39492 100644 --- a/crates/codeguard-cli/tests/zig_native_differential.rs +++ b/crates/codeguard-cli/tests/zig_native_differential.rs @@ -17,24 +17,30 @@ fn pinned_zig_worker_matches_native_ast_check_on_syntax_corpus() { .join(format!("codeguard-zig-differential-{}", std::process::id())); fs::create_dir_all(&root).unwrap(); let mut compared = 0; - for (name, source) in [ - ("empty_struct", "const S = struct {};\n"), - ("empty_enum", "const E = enum {};\n"), - ("empty_union", "const U = union(enum) {};\n"), - ("empty_opaque", "const O = opaque {};\n"), - ("function", "fn add(a: i32, b: i32) i32 { return a + b; }\n"), - ("unicode_string", "const label = \"你好 Zig\";\n"), + for (name, source, expected_valid) in [ + ("empty_struct", "const S = struct {};\n", true), + ("empty_enum", "const E = enum {};\n", true), + ("empty_union", "const U = union(enum) {};\n", true), + ("empty_opaque", "const O = opaque {};\n", true), + ( + "function", + "fn add(a: i32, b: i32) i32 { return a + b; }\n", + true, + ), + ("unicode_string", "const label = \"你好 Zig\";\n", true), ( "nested_container", "const S = struct { const Inner = struct {}; };\n", + true, ), - ("missing_brace", "const S = struct {\n"), + ("missing_brace", "const S = struct {\n", false), ( "missing_paren", "fn add(a: i32, b: i32 i32 { return a + b; }\n", + false, ), - ("unterminated_string", "const label = \"hello;\n"), - ("missing_initializer", "const value = ;\n"), + ("unterminated_string", "const label = \"hello;\n", false), + ("missing_initializer", "const value = ;\n", false), ] { let mut native = Command::new(&zig) .arg("ast-check") @@ -54,6 +60,12 @@ fn pinned_zig_worker_matches_native_ast_check_on_syntax_corpus() { matches!(native.status.code(), Some(0 | 1)), "{name}: {native:?}" ); + assert_eq!( + native.status.success(), + expected_valid, + "{name}: native oracle disagrees with the pinned corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); let file = root.join(format!("{name}.zig")); fs::write(&file, source).unwrap(); let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) From 65a1ed40e7bfe37b9358ad29674d946053ed9517 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:35:49 +0800 Subject: [PATCH 05/31] =?UTF-8?q?fix(cfquery):=20=E6=8C=89=20CFML=20?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=B8=8E=E6=A0=87=E7=AD=BE=E8=BE=B9=E7=95=8C?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=80=99=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/codeguard-cli/src/grammar_route.rs | 98 +++++++++++++++++-- .../tests/check_all_grammar_candidates.rs | 10 +- crates/codeguard-cli/tests/grammar_route.rs | 41 ++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + .../specs/syntax-precheck/spec.md | 8 ++ .../introduce-rust-codeguard-cli/tasks.md | 2 + tests/acceptance/cfquery-markup-boundary.md | 11 +++ 8 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 tests/acceptance/cfquery-markup-boundary.md diff --git a/crates/codeguard-cli/src/grammar_route.rs b/crates/codeguard-cli/src/grammar_route.rs index d7de92d3..28286041 100644 --- a/crates/codeguard-cli/src/grammar_route.rs +++ b/crates/codeguard-cli/src/grammar_route.rs @@ -63,8 +63,7 @@ pub fn route_source<'a>(relative_path: &str, source: &'a [u8]) -> Vec(relative_path: &str, source: &'a [u8]) -> Vec') - else { + let Some(tag_end) = find_tag_end(&lowercase, after_name) else { break; }; - let body_start = after_name + tag_end_relative + 1; + let body_start = tag_end + 1; let Some(close_relative) = find_bytes(&lowercase[body_start..], b"") else { break; }; @@ -103,3 +99,91 @@ fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option { .windows(needle.len()) .position(|window| window == needle) } + +// 只跳过由服务器移除的 CFML 注释;普通 HTML 注释中的 CFML 标签仍会执行。 +fn next_cfquery_open(source: &[u8], mut cursor: usize) -> Option { + while cursor < source.len() { + let open = cursor + find_bytes(&source[cursor..], b"<")?; + if source[open..].starts_with(b"")?; + if let Some(nested) = nested.filter(|nested| *nested < close) { + depth += 1; + cursor = nested + b"".len(); + if depth == 0 { + return Some(cursor); + } + } + } +} + +fn find_tag_end(source: &[u8], start: usize) -> Option { + let mut quote = None; + let mut index = start; + while index < source.len() { + let byte = source[index]; + if quote == Some(byte) { + quote = None; + } else if quote.is_none() { + if source[index..].starts_with(b"\n\n ---> name=\"q\">SELECT #x# FROM users\n", ) .unwrap(); let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) @@ -103,6 +103,14 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { "{language}: {observations:?}" ); } + assert_eq!( + observations + .iter() + .filter(|item| item["language"] == "cfquery") + .count(), + 2, + "HTML-comment CFML executes; only CFML-comment query is excluded" + ); assert!( observations .iter() diff --git a/crates/codeguard-cli/tests/grammar_route.rs b/crates/codeguard-cli/tests/grammar_route.rs index f0e56f76..163adf8d 100644 --- a/crates/codeguard-cli/tests/grammar_route.rs +++ b/crates/codeguard-cli/tests/grammar_route.rs @@ -69,3 +69,44 @@ fn ambiguous_and_embedded_sources_do_not_get_speculative_routes() { assert_eq!(routes.len(), 1); assert_eq!(routes[0].language, "tsx"); } + +#[test] +fn cfquery_route_keeps_html_comment_tags_but_skips_nested_cfml_comments() { + let source = b"\n SELECT #broken --->\nSELECT #id# FROM users"; + let routes = route_source("page.cfm", source); + let query_routes: Vec<_> = routes + .iter() + .filter(|route| route.language == "cfquery") + .collect(); + assert_eq!(query_routes.len(), 2); + assert_eq!(query_routes[0].source, b"SELECT #html# FROM users"); + assert_eq!(query_routes[1].source, b"SELECT #id# FROM users"); + assert_eq!( + &source[query_routes[1].byte_offset + ..query_routes[1].byte_offset + query_routes[1].source.len()], + query_routes[1].source + ); +} + +#[test] +fn cfquery_route_uses_quote_aware_tag_boundary_and_skips_non_markup_contexts() { + let source = b"SELECT #bad\">\nvar text = \"SELECT #bad\";\nb\">SELECT #id# FROM users"; + let routes = route_source("page.cfm", source); + let queries: Vec<_> = routes + .iter() + .filter(|route| route.language == "cfquery") + .collect(); + assert_eq!(queries.len(), 1); + assert_eq!(queries[0].source, b"SELECT #id# FROM users"); +} + +#[test] +fn cfquery_route_ignores_nested_cfml_comment_inside_open_tag() { + let source = b" more ---> name=\"q\">SELECT #id# FROM users"; + let routes = route_source("page.cfm", source); + let query = routes + .iter() + .find(|route| route.language == "cfquery") + .unwrap(); + assert_eq!(query.source, b"SELECT #id# FROM users"); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 4347f193..920a36ac 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -224,6 +224,8 @@ Use `codeguard grammar status --format=json` to query the separate source covera R, Ruby, PHP and Kotlin now also have pinned CodeGraph bytes, licenses, measured Rust ABI and narrow positive/negative worker samples; the PHP fixture covers mixed HTML/PHP. They remain unqualified candidates. The original CodeGraph Dart WASM remains incompatible with Rust, but CodeGuard now has a byte-reproducible Zig rebuild that compiles the real external scanner and uses a pinned WASI import adaptation. Rust loader and isolated-worker fixtures pass; Dart remains unqualified for public lint and release. See the [Dart rebuild record](../tests/acceptance/dart-grammar-rebuild-candidate.md). Erlang also has pinned CodeGraph bytes, upstream 0.19 license and ABI 14 with narrow Rust and worker samples. It has no qualified native comparison or public lint route; see the [Erlang candidate record](../tests/acceptance/erlang-grammar-candidate.md). Pascal now has pinned CodeGraph bytes and its original Isopod dependency commit and license; ABI 14 and narrow worker tests pass, but native comparison and public lint remain open. See the [Pascal candidate record](../tests/acceptance/pascal-grammar-candidate.md). The remaining CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET assets also load in the Rust worker, bringing the candidate inventory to 32/32 with zero released syntax capabilities. CFQuery misses `SELECT FROM`, VB.NET misparses a valid unindented method, and COBOL has a high load cost; see the [seven-asset acceptance record](../tests/acceptance/final-seven-grammar-candidates.md). +The CFQuery source router now skips tags inside nested CFML comments and ordinary tag attributes or explicit CFScript bodies; it preserves tags inside HTML comments because [Adobe's CFML comment semantics](https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/elements-of-cfml/comments.html) do not make HTML comments server-side suppression. Quoted `>` characters and nested CFML comments inside a real opening tag no longer truncate its query body. This addresses a concrete false route without hiding a CFML tag that can execute; the CFQuery grammar's SQL false negative remains. See the [markup-boundary regression](../tests/acceptance/cfquery-markup-boundary.md). + 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. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 57fa65bd..48fe5126 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -224,6 +224,8 @@ flowchart LR R、Ruby、PHP、Kotlin 也已固定 CodeGraph 字节、许可证和 Rust 实测 ABI,并通过隔离 worker 的窄范围正反例;PHP 样例覆盖 HTML/PHP 混合内容,仍仅为未验收候选。CodeGraph 原 Dart WASM 仍不能由 Rust 直接加载;CodeGuard 现以固定上游 C 源码和真实外部 scanner 经 Zig 可重复构建,再做固定 WASI 导入适配。Rust 加载与隔离 worker 的窄范围测试通过,但公开 lint 与发行仍未验收;见[Dart 重建局部验收](../tests/acceptance/dart-grammar-rebuild-candidate.md)。Erlang 也已固定 CodeGraph 字节、上游 0.19 许可证及 ABI 14,Rust 与隔离 worker 的窄范围样例通过;原生对照和公开 lint 路由尚缺,见[Erlang 候选局部验收](../tests/acceptance/erlang-grammar-candidate.md)。Pascal 已固定 CodeGraph 字节、原始 Isopod 依赖提交和许可证,ABI 14 及窄范围 worker 样例通过;原生对照和公开 lint 尚缺,见[Pascal 候选局部验收](../tests/acceptance/pascal-grammar-candidate.md)。其余七份 CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET 来源 WASM 也已固定并由 Rust worker 加载,累计 32/32 份候选、0 项已发行。CFQuery 漏掉 `SELECT FROM`,VB.NET 对合法未缩进方法体误报,COBOL 成本高;都不能发布为 lint,见[七份局部验收](../tests/acceptance/final-seven-grammar-candidates.md)。 +CFQuery 源码路由现跳过嵌套 CFML 注释、普通标签属性与明确 CFScript 体中的假标签;HTML 注释中的 CFML 标签仍保留,因为 [Adobe 的 CFML 注释语义](https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/elements-of-cfml/comments.html)不把普通 HTML 注释当作服务端屏蔽。真实开标签属性中的 `>` 和开标签内的嵌套 CFML 注释也不再截断查询体。此修复避免一类误路由,同时不隐藏可能执行的 CFML 标签;CFQuery grammar 对 SQL 的漏检仍存在,见[标记边界回归](../tests/acceptance/cfquery-markup-boundary.md)。 + 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 可选构建特性下,Rust CLI 现在有私有[候选工作进程](../crates/codeguard-cli/src/syntax_worker_command.rs)与[父进程核验器](../crates/codeguard-cli/src/syntax_worker_runner.rs)。Unix 路径复用现有进程组执行内核,源码输入上限 1 MiB、输出上限 64 KiB,沿用调用方截止时间和取消,并重新核对源码/grammar 摘要与恢复位置。这只是已测试的局部契约:经目标平台实测的内存上限、跨平台强制约束、正式 lint/check 路由与发行打包仍缺;候选 grammar 即使无恢复节点也保持 `incomplete`。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md index 9fa28b8d..1884da72 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md @@ -147,6 +147,14 @@ Rust runtime MUST 按需加载 grammar,在受控解析工作进程中限制输 - **WHEN** 只支持文件中一部分语言区域 - **THEN** 通过位置映射返回已检/未覆盖区域,不报告整个文件 clean +#### Scenario: A CFQuery tag appears inside different comment forms +- **WHEN** CFML 文件的可嵌套 `` 注释、普通 HTML 注释和标记正文中各含完整的 `...` 标签 +- **THEN** 跳过被 ColdFusion 移除的 CFML 注释内标签,保留仍由 ColdFusion 处理的 HTML 注释内标签及正文标签;候选整体仍不获得原生 lint 或交付权威 + +#### Scenario: A CFML comment appears inside a query opening tag +- **WHEN** 合法 `` 开标签的属性之间含可嵌套 CFML 注释,其文本带有 `>` +- **THEN** 跳过注释后定位真正的开标签结束位置,查询体范围与原文件字节偏移保持一致 + #### Scenario: A newer dialect is outside the validated range - **WHEN** 文件使用未验收语言版本或方言 - **THEN** 保留兼容性限制和原生确认动作,不直接断言合法源码违规 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 8fd31896..cf81300d 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1077,3 +1077,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.18 / 14.19 本地 npm 打包增量:打包器现可用 `--require-wasm` 核对二进制内 32 份候选身份和固定 SHA-256,实跑 Zig worker;`--public` 自动启用该门禁。无 WASM 特性的默认二进制在出包前被拒。本机 private tarball 经离线 `npm exec` 读到 32 份清单并实际运行 Zig、Dart 候选 worker;安装后 `check all` 分四个有界项目调用全部 32 份,候选并集与清单一致,每组跳过 0、退出码 3、交付未完成。Linux CI [37094165607](https://github.com/full-stack-plugins/codeguard/actions/runs/37094165607) 的新增全量包内测试与完整工作区测试通过;见[本地 npm 包局部验收](../../../tests/acceptance/npm-wasm-local-package.md)。已发布 `0.1.2` 不含此能力,尚无新版本公开发行、所有 32 份包内逐一正反例、逐语言原生对照或宿主反馈。14.18、14.19 不勾选。 14.4 / 14.17 / 14.19 Zig 原生差分局部增量:固定本机 Zig 0.16.0 `ast-check` 作为独立 oracle,公开隔离 worker 对同一 11 份合法/破损源码的恢复分类逐例一致,真实原生显式测试 1/1 通过;候选仍未验收且交付未评估。仅覆盖此版本的 7 个正例、4 个反例,不代表完整误报率、其他语种、版本/方言或发行资格;见[Zig 原生差分验收](../../../tests/acceptance/zig-native-differential.md)。14.4、14.17、14.19 不勾选。 + +14.4 / 14.17 / 14.19 CFQuery 标记边界纠错:RED 证实 CFML 注释中的示例查询被错误送入 worker,以及属性值或开标签内注释中的 `>` 截断查询体。核对 Adobe 官方语义后修正早期假设:HTML 注释中的 CFML 标签仍需保留。现跳过可嵌套 CFML 注释、普通标签属性与明确 CFScript 体,保留 HTML 注释及正文中的查询;5 项路由测试、4 项真实 `check all` 回归及全 32 份分批调用均通过,见[局部验收](../../../tests/acceptance/cfquery-markup-boundary.md)。CFQuery 自身的 `SELECT FROM` 漏检、VB.NET 误报和逐语言原生对照仍在,三项任务不勾选。 diff --git a/tests/acceptance/cfquery-markup-boundary.md b/tests/acceptance/cfquery-markup-boundary.md new file mode 100644 index 00000000..734c1b48 --- /dev/null +++ b/tests/acceptance/cfquery-markup-boundary.md @@ -0,0 +1,11 @@ +# CFQuery 候选路由的标记边界纠错 + +日期:2026-10-03。对应既有 OpenSpec 14.4、14.17、14.19 的局部误报治理,完整任务仍未完成。 + +旧路由直接在 `.cfm/.cfc` 源码字节中搜索 ``,所以被服务器移除的 CFML 注释内示例标签也会变成候选;真实 `check all` 可能把其中破损插值回显为源码疑似问题。RED 用例中 HTML 注释、CFML 注释与正文各一段查询产生 3 条路由,正确预期是保留 HTML 注释与正文中的 2 条。另一个 RED 用例表明 `` 的属性值会让旧逻辑在引号内的 `>` 截断标签,污染查询体字节与位置。 + +[Adobe 官方注释说明](https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/elements-of-cfml/comments.html)明确 CFML 注释使用三条短横线、在服务端移除、允许嵌套,也允许置于开标签内;普通 HTML 注释不会提供同等的 CFML 注释语义。因此路由现仅跳过闭合且可嵌套的 CFML 注释,并保留 HTML 注释中的 CFML 标签。普通标签属性和明确的 CFScript 体里的假标签不单独路由;开标签的 `>` 按引号及嵌套 CFML 注释边界定位。未闭合 CFML 注释或不明确的标记尾部不猜测后续 CFQuery。真实标签的原始字节切片与偏移不变,仍调用固定 CFQuery worker 且保持未验收状态。 + +验收:`grammar_route` 5/5 通过,覆盖嵌套 CFML 注释被跳过、HTML 注释中的查询被保留、CFScript 字符串和属性值中的假标签、开标签内嵌套注释,以及真实标签的字节偏移;`check_all_grammar_candidates` 4/4 通过,包括实际 worker 路径中 HTML 注释加正文报告 2 条 CFQuery 候选、CFML 注释中的查询不生成观察,以及四个有界项目仍调用全部 32 份候选。受影响测试的特性 Clippy `-D warnings`、格式、OpenSpec strict 与空白检查通过。 + +这不是完整 CFML 词法或 SQL 语义解析。CFQuery grammar 对 `SELECT FROM` 的已知漏检仍在,VB.NET 的已知误报也未改变;没有原生 ColdFusion/SQL 对照和误报率统计,因此 S14.4、14.17、14.19 保持未完成。 From c2b5d4f7e0ded7bf920693ad6043065b4d9d6435 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 12:57:13 +0800 Subject: [PATCH 06/31] =?UTF-8?q?test(go):=20=E5=A2=9E=E5=8A=A0=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E8=AF=AD=E6=B3=95=E4=B8=8E=20WASM=20=E5=B7=AE?= =?UTF-8?q?=E5=88=86=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/go_native_differential.rs | 146 ++++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + .../specs/syntax-precheck/spec.md | 4 + .../introduce-rust-codeguard-cli/tasks.md | 2 + tests/acceptance/go-native-differential.md | 17 ++ 6 files changed, 173 insertions(+) create mode 100644 crates/codeguard-cli/tests/go_native_differential.rs create mode 100644 tests/acceptance/go-native-differential.md diff --git a/crates/codeguard-cli/tests/go_native_differential.rs b/crates/codeguard-cli/tests/go_native_differential.rs new file mode 100644 index 00000000..648f001a --- /dev/null +++ b/crates/codeguard-cli/tests/go_native_differential.rs @@ -0,0 +1,146 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::io::Write; +use std::path::Path; +use std::process::{Command, Stdio}; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("empty", "package main\nfunc main() {}\n", true), + ( + "generics", + "package p\nfunc Id[T any](x T) T { return x }\n", + true, + ), + ( + "range_int", + "package p\nfunc f() { for i := range 3 { _ = i } }\n", + true, + ), + ("unicode", "package p\nvar 世界 = \"ok\"\n", true), + ("raw_string", "package p\nconst s = `hello\nworld`\n", true), + ( + "composite", + "package p\ntype P struct { X int }\nvar p = P{X: 1}\n", + true, + ), + ( + "interface", + "package p\ntype I interface { F() error }\n", + true, + ), + ( + "switch", + "package p\nfunc f(x any) { switch x.(type) { case int: return; default: return } }\n", + true, + ), + ("missing_brace", "package p\nfunc f() {\n", false), + ("missing_paren", "package p\nfunc f( { }\n", false), + ( + "unterminated_string", + "package p\nconst s = \"oops\n", + false, + ), + ("missing_init", "package p\nvar x =\n", false), + ("bad_if", "package p\nfunc f() { if { } }\n", false), + ] +} + +fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool { + let file = root.join(format!("{name}.go")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "go"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report["recoveries"].as_array().unwrap().is_empty() +} + +#[test] +fn go_worker_preserves_native_labeled_syntax_corpus() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-go-corpus-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + assert_eq!( + candidate_is_valid(&root, name, source), + expected_valid, + "{name}: pinned Go 1.23.4 syntax classification changed" + ); + } + fs::remove_dir_all(root).unwrap(); +} + +#[test] +#[ignore = "requires explicit existing Go 1.23.4 via CODEGUARD_GO_BIN and CODEGUARD_GOFMT_BIN"] +fn pinned_go_worker_matches_native_gofmt_on_syntax_corpus() { + let go = std::env::var("CODEGUARD_GO_BIN").expect("provide an existing Go executable"); + let gofmt = std::env::var("CODEGUARD_GOFMT_BIN").expect("provide matching gofmt executable"); + let version = Command::new(&go).arg("version").output().unwrap(); + assert!(version.status.success()); + assert!( + String::from_utf8_lossy(&version.stdout).starts_with("go version go1.23.4 "), + "unexpected Go version: {}", + String::from_utf8_lossy(&version.stdout) + ); + assert_eq!( + fs::canonicalize(&go).unwrap().parent(), + fs::canonicalize(&gofmt).unwrap().parent(), + "Go and gofmt must come from the same toolchain directory" + ); + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-go-differential-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + let mut compared = 0; + for (name, source, expected_valid) in corpus() { + let mut native = Command::new(&gofmt) + .arg("-e") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + native + .stdin + .take() + .unwrap() + .write_all(source.as_bytes()) + .unwrap(); + let native = native.wait_with_output().unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 2)), + "{name}: {native:?}" + ); + assert_eq!( + native.status.success(), + expected_valid, + "{name}: gofmt disagrees with the pinned corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + assert_eq!( + candidate_is_valid(&root, name, source), + native.status.success(), + "{name}: gofmt={} worker classification differs", + String::from_utf8_lossy(&native.stderr) + ); + compared += 1; + } + fs::remove_dir_all(root).unwrap(); + assert_eq!(compared, 13); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 920a36ac..12444aa6 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -226,6 +226,8 @@ R, Ruby, PHP and Kotlin now also have pinned CodeGraph bytes, licenses, measured The CFQuery source router now skips tags inside nested CFML comments and ordinary tag attributes or explicit CFScript bodies; it preserves tags inside HTML comments because [Adobe's CFML comment semantics](https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/elements-of-cfml/comments.html) do not make HTML comments server-side suppression. Quoted `>` characters and nested CFML comments inside a real opening tag no longer truncate its query body. This addresses a concrete false route without hiding a CFML tag that can execute; the CFQuery grammar's SQL false negative remains. See the [markup-boundary regression](../tests/acceptance/cfquery-markup-boundary.md). +The Go 1.23.4 native `gofmt -e` differential corpus now compares eight valid and five malformed sources with the isolated Go WASM worker, with all 13 syntax classifications matching locally. This is a narrow syntax oracle, not type checking, `go vet`, a measured population error rate, or grammar qualification; see the [Go differential record](../tests/acceptance/go-native-differential.md). + 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. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 48fe5126..642862b5 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -226,6 +226,8 @@ R、Ruby、PHP、Kotlin 也已固定 CodeGraph 字节、许可证和 Rust 实测 CFQuery 源码路由现跳过嵌套 CFML 注释、普通标签属性与明确 CFScript 体中的假标签;HTML 注释中的 CFML 标签仍保留,因为 [Adobe 的 CFML 注释语义](https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/elements-of-cfml/comments.html)不把普通 HTML 注释当作服务端屏蔽。真实开标签属性中的 `>` 和开标签内的嵌套 CFML 注释也不再截断查询体。此修复避免一类误路由,同时不隐藏可能执行的 CFML 标签;CFQuery grammar 对 SQL 的漏检仍存在,见[标记边界回归](../tests/acceptance/cfquery-markup-boundary.md)。 +Go 1.23.4 原生 `gofmt -e` 差分语料现将 8 份合法、5 份破损源码与隔离 Go WASM worker 逐例比较,本机 13 份语法分类一致。这只是窄范围语法 oracle,不代表类型检查、`go vet`、总体误报率测量或 grammar 已验收;见[Go 差分验收](../tests/acceptance/go-native-differential.md)。 + 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 可选构建特性下,Rust CLI 现在有私有[候选工作进程](../crates/codeguard-cli/src/syntax_worker_command.rs)与[父进程核验器](../crates/codeguard-cli/src/syntax_worker_runner.rs)。Unix 路径复用现有进程组执行内核,源码输入上限 1 MiB、输出上限 64 KiB,沿用调用方截止时间和取消,并重新核对源码/grammar 摘要与恢复位置。这只是已测试的局部契约:经目标平台实测的内存上限、跨平台强制约束、正式 lint/check 路由与发行打包仍缺;候选 grammar 即使无恢复节点也保持 `incomplete`。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md index 1884da72..97a4ef8a 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md @@ -159,6 +159,10 @@ Rust runtime MUST 按需加载 grammar,在受控解析工作进程中限制输 - **WHEN** 文件使用未验收语言版本或方言 - **THEN** 保留兼容性限制和原生确认动作,不直接断言合法源码违规 +#### Scenario: A candidate grammar matches a narrow native syntax corpus +- **WHEN** 同一组合法与破损源码分别经过固定版本的语言原生语法工具和隔离 WASM worker +- **THEN** 逐例记录语法分类一致性、工具与语料身份;仅扩大该版本的局部精度证据,不将候选提升为完整 lint、其它版本或交付通过 + ### Requirement: Precheck briefs SHALL reach agent conversations with concrete next actions human/结构化报告及宿主渲染 MUST 按结论、方式/范围、原生状态、依据、下一步和实际任务引用组织信息。必须/推荐动作 MUST 明确。疑似异常、正常、未完成和原生诊断四类模板 MUST 独立验证;CVE 等非语法覆盖不能套用语法通过结论。插件 MUST 通过真实宿主的工具结果/上下文 API 交付,文件或 stdout 存在不等于交付。初次反馈后只发送有意义的变化;原始工具文本 MUST 作为数据,不执行其中指令。AGENTS MUST 仅保留长期指引,不追加每轮扫描日志。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index cf81300d..a1887d97 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1079,3 +1079,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 Zig 原生差分局部增量:固定本机 Zig 0.16.0 `ast-check` 作为独立 oracle,公开隔离 worker 对同一 11 份合法/破损源码的恢复分类逐例一致,真实原生显式测试 1/1 通过;候选仍未验收且交付未评估。仅覆盖此版本的 7 个正例、4 个反例,不代表完整误报率、其他语种、版本/方言或发行资格;见[Zig 原生差分验收](../../../tests/acceptance/zig-native-differential.md)。14.4、14.17、14.19 不勾选。 14.4 / 14.17 / 14.19 CFQuery 标记边界纠错:RED 证实 CFML 注释中的示例查询被错误送入 worker,以及属性值或开标签内注释中的 `>` 截断查询体。核对 Adobe 官方语义后修正早期假设:HTML 注释中的 CFML 标签仍需保留。现跳过可嵌套 CFML 注释、普通标签属性与明确 CFScript 体,保留 HTML 注释及正文中的查询;5 项路由测试、4 项真实 `check all` 回归及全 32 份分批调用均通过,见[局部验收](../../../tests/acceptance/cfquery-markup-boundary.md)。CFQuery 自身的 `SELECT FROM` 漏检、VB.NET 误报和逐语言原生对照仍在,三项任务不勾选。 + +14.4 / 14.17 / 14.19 Go 原生差分局部增量:固定本机 Go 1.23.4 `gofmt -e` 作为独立语法 oracle,同一 8 份合法和 5 份破损源码经公开隔离 Go worker 逐例恢复分类一致,显式原生测试 1/1 与可由 CI 常规运行的 WASM 语料回归 1/1 通过;候选仍未验收且交付未评估。此对照不覆盖类型/语义、`go vet`、其他版本和构建标签或系统误报漏报率;见[Go 原生差分验收](../../../tests/acceptance/go-native-differential.md)。14.4、14.17、14.19 不勾选。 diff --git a/tests/acceptance/go-native-differential.md b/tests/acceptance/go-native-differential.md new file mode 100644 index 00000000..183e9a01 --- /dev/null +++ b/tests/acceptance/go-native-differential.md @@ -0,0 +1,17 @@ +# Go 1.23.4 原生 gofmt 与内置 WASM 的局部差分验收 + +日期:2026-10-03。对应既有 OpenSpec 14.4、14.17、14.19 的局部精度证据,三项任务仍未完成。 + +新增显式忽略的真实原生测试 `go_native_differential`。它要求现存 `CODEGUARD_GO_BIN` 与同目录的 `CODEGUARD_GOFMT_BIN`,先核对 `go version` 为 Go 1.23.4,再将同一 UTF-8 源码送入 `gofmt -e` 的 stdin 与公开 `grammar probe go` 隔离 worker。测试要求原生退出码为 0 或 2、候选实际解析且没有输入路径错误,并逐例比较原生成功与 WASM 恢复节点的有无。候选仍必须返回 `grammar_qualified=false`、`delivery_decision=not_evaluated` 与退出码 3。 + +本机工具为 Go 1.23.4 darwin/arm64,`go` SHA-256 `5900a8e0942f88b7bacf2b44b397b4950c8dc9f850a16ae1249e1636860dd6ef`,`gofmt` SHA-256 `f0e9b77df6dcfeec18d33bd9305ff034c28aa2142d3a4fa3aa44e62a44c89e3b`。执行: + +```bash +CODEGUARD_GO_BIN=/usr/local/go/bin/go CODEGUARD_GOFMT_BIN=/usr/local/go/bin/gofmt \ +cargo test --locked -p codeguard-cli --features wasm-precheck \ + --test go_native_differential -- --ignored --nocapture +``` + +结果:真实原生差分测试 1/1 通过,常规 CI 可运行的 WASM 固定语料回归 1/1 通过,13 份样例分类一致。8 份合法样例覆盖空函数、泛型、整数 range、Unicode 标识符、原始字符串、复合字面量、接口和 type switch;5 份破损样例覆盖缺少大括号/圆括号、未闭合字符串、缺失初始化表达式和破损 if。测试规范化临时目录路径,避免 macOS `/var` 别名被 CLI 的符号链接防护拒绝;并断言报告无输入错误,防止“未执行”被误算为一致。 + +`gofmt` 只提供语法解析对照,不执行类型检查、`go vet`、测试或完整构建。此结果不证明 Go 其它版本、构建标签、生成代码、项目配置、规模化误报漏报率或公开发行资格。其余 31 种 grammar 的精度不受该局部证据覆盖。 From 0b6be18f4bcd211124636267b305a927c3b3f0b3 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 13:01:38 +0800 Subject: [PATCH 07/31] =?UTF-8?q?test(javascript):=20=E5=A2=9E=E5=8A=A0=20?= =?UTF-8?q?Node=20=E8=AF=AD=E6=B3=95=E4=B8=8E=20WASM=20=E5=B7=AE=E5=88=86?= =?UTF-8?q?=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/javascript_native_differential.rs | 124 ++++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + .../introduce-rust-codeguard-cli/tasks.md | 2 + .../javascript-native-differential.md | 17 +++ 5 files changed, 147 insertions(+) create mode 100644 crates/codeguard-cli/tests/javascript_native_differential.rs create mode 100644 tests/acceptance/javascript-native-differential.md diff --git a/crates/codeguard-cli/tests/javascript_native_differential.rs b/crates/codeguard-cli/tests/javascript_native_differential.rs new file mode 100644 index 00000000..5b128169 --- /dev/null +++ b/crates/codeguard-cli/tests/javascript_native_differential.rs @@ -0,0 +1,124 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::io::Write; +use std::path::Path; +use std::process::{Command, Stdio}; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("plain", "const x = 1;\n", true), + ("arrow", "const f = (x) => x + 1;\n", true), + ("optional", "const x = value?.child ?? 0;\n", true), + ( + "class", + "class Box { #value = 1; get value() { return this.#value; } }\n", + true, + ), + ( + "import", + "import { readFile } from \"node:fs/promises\";\n", + true, + ), + ("await", "await Promise.resolve(1);\n", true), + ( + "async", + "async function f() { return await Promise.resolve(1); }\n", + true, + ), + ("unicode", "const 名称 = \"你好\";\n", true), + ("missing_brace", "function f() {\n", false), + ("missing_paren", "function f( { return 1; }\n", false), + ("unterminated", "const s = \"oops;\n", false), + ("missing_init", "const x = ;\n", false), + ("bad_optional", "const x = value?.;\n", false), + ] +} + +fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool { + let file = root.join(format!("{name}.js")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "javascript"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report["recoveries"].as_array().unwrap().is_empty() +} + +#[test] +fn javascript_worker_preserves_native_labeled_syntax_corpus() { + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-javascript-corpus-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + assert_eq!( + candidate_is_valid(&root, name, source), + expected_valid, + "{name}: pinned Node 24.18.0 syntax classification changed" + ); + } + fs::remove_dir_all(root).unwrap(); +} + +#[test] +#[ignore = "requires explicit existing Node 24.18.0 via CODEGUARD_NODE_BIN"] +fn pinned_javascript_worker_matches_native_node_check_on_syntax_corpus() { + let node = std::env::var("CODEGUARD_NODE_BIN").expect("provide an existing Node executable"); + let version = Command::new(&node).arg("--version").output().unwrap(); + assert!(version.status.success()); + assert_eq!(String::from_utf8_lossy(&version.stdout).trim(), "v24.18.0"); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-javascript-differential-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + let mut compared = 0; + for (name, source, expected_valid) in corpus() { + let mut native = Command::new(&node) + .args(["--check", "--input-type=module"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + native + .stdin + .take() + .unwrap() + .write_all(source.as_bytes()) + .unwrap(); + let native = native.wait_with_output().unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1)), + "{name}: {native:?}" + ); + assert_eq!( + native.status.success(), + expected_valid, + "{name}: Node disagrees with the pinned corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + assert_eq!( + candidate_is_valid(&root, name, source), + native.status.success(), + "{name}: Node={} worker classification differs", + String::from_utf8_lossy(&native.stderr) + ); + compared += 1; + } + fs::remove_dir_all(root).unwrap(); + assert_eq!(compared, 13); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 12444aa6..7362c172 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -228,6 +228,8 @@ The CFQuery source router now skips tags inside nested CFML comments and ordinar The Go 1.23.4 native `gofmt -e` differential corpus now compares eight valid and five malformed sources with the isolated Go WASM worker, with all 13 syntax classifications matching locally. This is a narrow syntax oracle, not type checking, `go vet`, a measured population error rate, or grammar qualification; see the [Go differential record](../tests/acceptance/go-native-differential.md). +The JavaScript module corpus similarly compares Node 24.18.0 `--check --input-type=module` against the isolated JavaScript worker on eight valid and five malformed sources, with all 13 classifications matching locally. The pinned worker corpus runs in ordinary CI; native comparison is explicit because Node versions differ by host. This does not qualify JSX, TypeScript, ESLint rules, or the whole JavaScript ecosystem; see the [JavaScript differential record](../tests/acceptance/javascript-native-differential.md). + 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. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 642862b5..fe24c74b 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -228,6 +228,8 @@ CFQuery 源码路由现跳过嵌套 CFML 注释、普通标签属性与明确 CF Go 1.23.4 原生 `gofmt -e` 差分语料现将 8 份合法、5 份破损源码与隔离 Go WASM worker 逐例比较,本机 13 份语法分类一致。这只是窄范围语法 oracle,不代表类型检查、`go vet`、总体误报率测量或 grammar 已验收;见[Go 差分验收](../tests/acceptance/go-native-differential.md)。 +JavaScript 模块语料也用 Node 24.18.0 `--check --input-type=module` 与隔离 JavaScript worker 对照 8 份合法、5 份破损源码,本机 13 份分类一致。固定 worker 语料可进入常规 CI;原生对照因宿主 Node 版本不同而需显式运行。这不验收 JSX、TypeScript、ESLint 规则或整个 JavaScript 生态;见[JavaScript 差分验收](../tests/acceptance/javascript-native-differential.md)。 + 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 可选构建特性下,Rust CLI 现在有私有[候选工作进程](../crates/codeguard-cli/src/syntax_worker_command.rs)与[父进程核验器](../crates/codeguard-cli/src/syntax_worker_runner.rs)。Unix 路径复用现有进程组执行内核,源码输入上限 1 MiB、输出上限 64 KiB,沿用调用方截止时间和取消,并重新核对源码/grammar 摘要与恢复位置。这只是已测试的局部契约:经目标平台实测的内存上限、跨平台强制约束、正式 lint/check 路由与发行打包仍缺;候选 grammar 即使无恢复节点也保持 `incomplete`。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index a1887d97..9cde009f 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1081,3 +1081,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 CFQuery 标记边界纠错:RED 证实 CFML 注释中的示例查询被错误送入 worker,以及属性值或开标签内注释中的 `>` 截断查询体。核对 Adobe 官方语义后修正早期假设:HTML 注释中的 CFML 标签仍需保留。现跳过可嵌套 CFML 注释、普通标签属性与明确 CFScript 体,保留 HTML 注释及正文中的查询;5 项路由测试、4 项真实 `check all` 回归及全 32 份分批调用均通过,见[局部验收](../../../tests/acceptance/cfquery-markup-boundary.md)。CFQuery 自身的 `SELECT FROM` 漏检、VB.NET 误报和逐语言原生对照仍在,三项任务不勾选。 14.4 / 14.17 / 14.19 Go 原生差分局部增量:固定本机 Go 1.23.4 `gofmt -e` 作为独立语法 oracle,同一 8 份合法和 5 份破损源码经公开隔离 Go worker 逐例恢复分类一致,显式原生测试 1/1 与可由 CI 常规运行的 WASM 语料回归 1/1 通过;候选仍未验收且交付未评估。此对照不覆盖类型/语义、`go vet`、其他版本和构建标签或系统误报漏报率;见[Go 原生差分验收](../../../tests/acceptance/go-native-differential.md)。14.4、14.17、14.19 不勾选。 + +14.4 / 14.17 / 14.19 JavaScript 原生差分局部增量:固定本机 Node 24.18.0 模块语法检查作为独立 oracle,同一 8 份合法和 5 份破损源码经公开隔离 JavaScript worker 逐例恢复分类一致,显式原生测试 1/1 与常规 CI 语料回归 1/1 通过。此对照不覆盖 ESLint、TypeScript/JSX、其它方言或系统误报漏报率;见[JavaScript 原生差分验收](../../../tests/acceptance/javascript-native-differential.md)。三项任务仍不勾选。 diff --git a/tests/acceptance/javascript-native-differential.md b/tests/acceptance/javascript-native-differential.md new file mode 100644 index 00000000..372ebc22 --- /dev/null +++ b/tests/acceptance/javascript-native-differential.md @@ -0,0 +1,17 @@ +# JavaScript / Node 24.18.0 原生语法与内置 WASM 的局部差分验收 + +日期:2026-10-03。对应既有 OpenSpec 14.4、14.17、14.19 的局部精度证据,三项任务仍未完成。 + +固定 13 份独立标注的 `.js` 模块源码,用现存 Node 24.18.0 的 `node --check --input-type=module` 通过 stdin 做原生语法对照,并调用公开 `grammar probe javascript` 的隔离 WASM worker 检查同一字节。测试核对原生退出码 0/1、候选实际解析无输入错误、恢复节点分类逐例一致;候选仍须返回 `grammar_qualified=false`、`delivery_decision=not_evaluated` 和退出码 3。原生对照测试只在显式提供 `CODEGUARD_NODE_BIN` 时运行;同一语料的 worker 回归常规 CI 可运行。 + +本机 Node 为 v24.18.0,程序 SHA-256 `ee6fb0e015284d83a91e8ec5213f43a157f8a392b58555301682892ba928c04a`。显式原生测试命令: + +```bash +CODEGUARD_NODE_BIN=<现存 Node 24.18.0 绝对路径> \ +cargo test --locked -p codeguard-cli --features wasm-precheck \ + --test javascript_native_differential -- --ignored --nocapture +``` + +本机结果:常规 worker 回归 1/1、显式原生差分 1/1 通过,13 份样例分类一致。8 份合法样例覆盖箭头函数、可选链/空值合并、私有类字段、模块 import、顶层 await、async 和 Unicode 标识符;5 份破损样例覆盖缺少大括号/圆括号、未闭合字符串、缺失初始化表达式及不完整可选链。 + +Node 的模块解析语法对照不等于 ESLint、类型检查、项目配置或浏览器方言验收。此结果不证明其它 Node/ECMAScript 版本、JSX、TypeScript、CommonJS 特有语义、规模化误报漏报率或公开发行资格;其余 31 种 grammar 不受该局部证据覆盖。 From 9305402b727744f435470e46931fab3b60adbfc1 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 13:25:12 +0800 Subject: [PATCH 08/31] =?UTF-8?q?feat(go):=20=E6=8C=89=E5=8E=9F=E7=94=9F?= =?UTF-8?q?=E5=8C=85=E8=8C=83=E5=9B=B4=E4=BC=98=E5=85=88=E8=B7=B3=E8=BF=87?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E8=AF=AD=E6=B3=95=E5=80=99=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/codeguard-cli/src/check_command.rs | 6 +- .../src/check_syntax_candidates.rs | 35 ++++- crates/codeguard-cli/src/go_lint_command.rs | 118 +++++++++++++++++ .../tests/check_all_native_preferred_go.rs | 123 ++++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + .../specs/syntax-precheck/spec.md | 4 + .../introduce-rust-codeguard-cli/tasks.md | 2 + .../check-all-native-preferred-go.md | 17 +++ 9 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 crates/codeguard-cli/tests/check_all_native_preferred_go.rs create mode 100644 tests/acceptance/check-all-native-preferred-go.md diff --git a/crates/codeguard-cli/src/check_command.rs b/crates/codeguard-cli/src/check_command.rs index dfceacaf..037b76d3 100644 --- a/crates/codeguard-cli/src/check_command.rs +++ b/crates/codeguard-cli/src/check_command.rs @@ -1455,7 +1455,11 @@ pub fn run(args: &[String]) -> ExitCode { let syntax_candidates = crate::check_syntax_candidates::observe( &root, &discovery, - &python_lint, + crate::check_syntax_candidates::NativeCoverage { + python_lint: &python_lint, + go_lint: &go_lint, + go_tool: parsed.go_tool.as_deref(), + }, parsed.selection == Selection::Java, deadline, if request_cancelled { diff --git a/crates/codeguard-cli/src/check_syntax_candidates.rs b/crates/codeguard-cli/src/check_syntax_candidates.rs index c48e4747..55d6d63e 100644 --- a/crates/codeguard-cli/src/check_syntax_candidates.rs +++ b/crates/codeguard-cli/src/check_syntax_candidates.rs @@ -1,12 +1,13 @@ //! 全项目原生检查之后的有界候选语法观察;永不产生已确认违规或通过。 use crate::discovery::DiscoveryReport; +use crate::go_lint_command::selected_sources_for_candidate; use crate::grammar_probe_command::read_plain_source; use crate::grammar_route::route_source; use crate::syntax_worker_runner::run_syntax_worker_candidate; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::path::Path; use std::sync::atomic::AtomicBool; use std::time::{Duration, Instant}; @@ -15,12 +16,19 @@ const MAX_FILES: usize = 64; const MAX_FRAGMENTS: usize = 64; const MAX_VISIBLE_RECOVERIES: usize = 8; +/// 本轮已执行的原生语法相关结果及显式工具;仅用于判断同一源码是否可避免重复解析。 +pub struct NativeCoverage<'a> { + pub python_lint: &'a Value, + pub go_lint: &'a Value, + pub go_tool: Option<&'a Path>, +} + /// 对已发现源码按方言选择固定资产,受文件、片段和剩余时间限制。 /// 参数为项目根、静态发现、语言选择、共同截止时间及范围稳定状态;返回不具门禁权威的报告。 pub fn observe( root: &Path, discovery: &DiscoveryReport, - python_lint: &Value, + native: NativeCoverage<'_>, java_only: bool, deadline: Instant, skip_reason: Option<&str>, @@ -63,6 +71,18 @@ pub fn observe( }; let candidate_deadline = deadline.min(Instant::now() + Duration::from_secs(90)); let cancelled = AtomicBool::new(false); + let native_go_files = if java_only || !discovery.languages.contains_key("go") { + BTreeMap::new() + } else { + selected_sources_for_candidate( + root, + native.go_tool, + native.go_lint, + candidate_deadline, + &cancelled, + ) + .unwrap_or_default() + }; for (index, relative) in paths.into_iter().enumerate() { if index >= MAX_FILES || observations.len() >= MAX_FRAGMENTS @@ -83,7 +103,9 @@ pub fn observe( continue; } }; - if native_python_covers(python_lint, relative, &source) { + if native_python_covers(native.python_lint, relative, &source) + || native_go_covers(&native_go_files, relative, &source) + { native_preferred_count += 1; continue; } @@ -171,6 +193,13 @@ pub fn observe( ) } +fn native_go_covers(files: &BTreeMap, relative: &str, source: &[u8]) -> bool { + relative.ends_with(".go") + && files + .get(relative) + .is_some_and(|digest| *digest == format!("{:x}", Sha256::digest(source))) +} + // 仅本轮 Ruff 已完整扫描同一份 Python 字节时跳过重复解析;配置发现不算执行。 fn native_python_covers(python_lint: &Value, relative: &str, source: &[u8]) -> bool { if !relative.ends_with(".py") { diff --git a/crates/codeguard-cli/src/go_lint_command.rs b/crates/codeguard-cli/src/go_lint_command.rs index cd7887cc..729a4617 100644 --- a/crates/codeguard-cli/src/go_lint_command.rs +++ b/crates/codeguard-cli/src/go_lint_command.rs @@ -465,6 +465,124 @@ pub(crate) fn current_source_snapshot_sha256(root: &Path) -> Option { source_snapshot(root).map(|snapshot| snapshot_sha256(&snapshot)) } +/// 返回本轮原生 vet 与默认包清单共同确认的源码摘要;任一身份或范围不完整则不跳过 WASM。 +#[cfg(feature = "wasm-precheck")] +pub(crate) fn selected_sources_for_candidate( + root: &Path, + tool: Option<&Path>, + scan: &Value, + deadline: Instant, + cancelled: &AtomicBool, +) -> Option> { + let tool = tool?.canonicalize().ok()?; + let before = source_snapshot(root)?; + let tool_sha = file_sha256(&tool, 128 * 1024 * 1024)?; + let identities = before + .modules + .iter() + .map(|(module, identity)| { + ( + module.clone(), + json!({"manifest_sha256":identity.manifest_sha256,"go_sum_sha256":identity.sum_sha256}), + ) + }) + .collect::>(); + if scan["root"] != root.to_string_lossy().as_ref() + || scan["native_tool_version"] != "go1.23.4" + || !matches!( + scan["native_status"].as_str(), + Some("clean_observed_unverified" | "findings_observed_unverified") + ) + || scan["tool_sha256"] != tool_sha + || scan["source_snapshot_sha256"] != snapshot_sha256(&before) + || scan["source_file_count"] != before.hashes.len() + || scan["module_count"] != before.modules.len() + || scan["modules_completed"] != before.modules.len() + || scan["module_identities"] != json!(identities) + || scan["module_results"].as_array().is_none_or(|results| { + let roots = results + .iter() + .filter(|result| { + matches!( + result["status"].as_str(), + Some("clean_observed_unverified" | "findings_observed_unverified") + ) + }) + .filter_map(|result| result["root"].as_str()) + .collect::>(); + results.len() != before.modules.len() + || roots + != before + .modules + .keys() + .map(String::as_str) + .collect::>() + }) + { + return None; + } + let scratch = private_scratch()?; + let environment = go_environment(&scratch, &tool); + let mut selected = BTreeMap::new(); + for module in before.modules.keys() { + let directory = if module == "." { + root.to_path_buf() + } else { + root.join(module) + }; + let outcome = run_process( + &ProcessSpec { + executable: tool.clone(), + args: ["list", "-json", "./..."] + .into_iter() + .map(OsString::from) + .collect(), + cwd: directory.clone(), + env: environment.clone(), + stdin: None, + deadline, + output_limit_bytes: 8 * 1024 * 1024, + }, + cancelled, + ); + if outcome.termination != Termination::Exited(0) || !outcome.stderr.is_empty() { + return None; + } + let listed = codeguard_adapters::parse_go_list_scope(&directory, &outcome.stdout).ok()?; + for path in listed.active_files.iter().chain(&listed.ignored_files) { + let path = if module == "." { + path.clone() + } else { + format!("{module}/{path}") + }; + if !before.hashes.contains_key(&path) + || owner_module(&path, &before.modules) != Some(module.as_str()) + { + return None; + } + } + for path in listed.active_files { + let path = if module == "." { + path + } else { + format!("{module}/{path}") + }; + if selected + .insert(path.clone(), before.hashes[&path].clone()) + .is_some() + { + return None; + } + } + } + if source_snapshot(root).as_ref() != Some(&before) + || file_sha256(&tool, 128 * 1024 * 1024).as_deref() != Some(tool_sha.as_str()) + { + return None; + } + Some(selected) +} + fn snapshot_sha256(snapshot: &GoSourceSnapshot) -> String { format!( "{:x}", diff --git a/crates/codeguard-cli/tests/check_all_native_preferred_go.rs b/crates/codeguard-cli/tests/check_all_native_preferred_go.rs new file mode 100644 index 00000000..b67c1671 --- /dev/null +++ b/crates/codeguard-cli/tests/check_all_native_preferred_go.rs @@ -0,0 +1,123 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use serde_json::Value; +use std::fs; +use std::os::unix::fs::PermissionsExt; +use std::process::Command; + +#[test] +fn malformed_go_package_scope_cannot_preempt_candidate_worker() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-go-scope-reject-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + fs::write( + root.join("go.mod"), + "module example.com/scope-reject\n\ngo 1.23.4\n", + ) + .unwrap(); + fs::write(root.join("main.go"), "package main\nfunc main() {}\n").unwrap(); + let tool = root.join("go-fake"); + fs::write( + &tool, + "#!/bin/sh\ncase \"$1\" in\nversion) echo 'go version go1.23.4 darwin/arm64' ;;\nvet) printf '# example.com/scope-reject\\n{}\\n' >&2 ;;\nlist) printf '%s\\n' '{\"Dir\":\"/outside\",\"ImportPath\":\"example.com/scope-reject\",\"Name\":\"main\",\"GoFiles\":[\"main.go\"]}' ;;\nesac\n", + ) + .unwrap(); + fs::set_permissions(&tool, fs::Permissions::from_mode(0o700)).unwrap(); + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["check", "all"]) + .arg(&root) + .args([ + "--go-tool", + tool.to_str().unwrap(), + "--format=json", + "--timeout", + "40s", + ]) + .output() + .unwrap(); + fs::remove_dir_all(&root).unwrap(); + assert_eq!(output.status.code(), Some(3)); + let report: Value = serde_json::from_slice(&output.stdout).unwrap(); + assert_eq!( + report["native_results"]["go_lint"]["native_status"], "clean_observed_unverified", + "{report}" + ); + assert_eq!(report["syntax_candidates"]["native_preferred_count"], 0); + assert!( + report["syntax_candidates"]["observations"] + .as_array() + .unwrap() + .iter() + .any(|item| item["path"] == "main.go" && item["language"] == "go") + ); +} + +#[test] +#[ignore = "requires explicit existing Go 1.23.4 via CODEGUARD_GO_BIN"] +fn completed_go_vet_preempts_only_a_selected_source_file() { + let go = std::env::var("CODEGUARD_GO_BIN").expect("provide an existing Go executable"); + let version = Command::new(&go).arg("version").output().unwrap(); + assert!(version.status.success()); + assert!(String::from_utf8_lossy(&version.stdout).starts_with("go version go1.23.4 ")); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-go-native-preferred-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + fs::write( + root.join("go.mod"), + "module example.com/native-preferred\n\ngo 1.23.4\n", + ) + .unwrap(); + fs::write( + root.join("main.go"), + "package main\nimport \"fmt\"\nfunc main() { fmt.Printf(\"%d\", \"bad\") }\n", + ) + .unwrap(); + fs::write( + root.join("excluded.go"), + "//go:build never\n\npackage main\nfunc broken( { }\n", + ) + .unwrap(); + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["check", "all"]) + .arg(&root) + .args(["--go-tool", &go, "--format=json", "--timeout", "120s"]) + .output() + .unwrap(); + fs::remove_dir_all(&root).unwrap(); + assert_eq!( + output.status.code(), + Some(3), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + let report: Value = serde_json::from_slice(&output.stdout).unwrap(); + assert!( + matches!( + report["native_results"]["go_lint"]["native_status"].as_str(), + Some("clean_observed_unverified" | "findings_observed_unverified") + ), + "{report}" + ); + assert_eq!( + report["native_results"]["go_lint"]["findings"][0]["rule_id"], "printf", + "{report}" + ); + assert_eq!(report["syntax_candidates"]["native_preferred_count"], 1); + let observations = report["syntax_candidates"]["observations"] + .as_array() + .unwrap(); + assert!(!observations.iter().any(|item| item["path"] == "main.go")); + assert!(observations.iter().any(|item| { + item["path"] == "excluded.go" + && item["language"] == "go" + && item["status"] == "candidate_observed" + && item["recovery_count"] + .as_u64() + .is_some_and(|count| count > 0) + })); + assert_eq!(report["delivery_decision"], "incomplete"); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 7362c172..d731d80a 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -230,6 +230,8 @@ The Go 1.23.4 native `gofmt -e` differential corpus now compares eight valid and The JavaScript module corpus similarly compares Node 24.18.0 `--check --input-type=module` against the isolated JavaScript worker on eight valid and five malformed sources, with all 13 classifications matching locally. The pinned worker corpus runs in ordinary CI; native comparison is explicit because Node versions differ by host. This does not qualify JSX, TypeScript, ESLint rules, or the whole JavaScript ecosystem; see the [JavaScript differential record](../tests/acceptance/javascript-native-differential.md). +For Go under `check all`, a completed Go 1.23.4 `go vet` now preempts duplicate WASM only for files selected by a bounded `go list -json ./...` under the same default build conditions and matching source, module and tool identities. Build-tag-excluded files still reach the candidate worker; malformed package scope or changed inputs cannot preempt it. A real Go integration and a forged-scope regression cover this boundary, while the overall delivery remains incomplete; see the [Go native-first record](../tests/acceptance/check-all-native-preferred-go.md). + 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. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index fe24c74b..01f6c93c 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -230,6 +230,8 @@ Go 1.23.4 原生 `gofmt -e` 差分语料现将 8 份合法、5 份破损源码 JavaScript 模块语料也用 Node 24.18.0 `--check --input-type=module` 与隔离 JavaScript worker 对照 8 份合法、5 份破损源码,本机 13 份分类一致。固定 worker 语料可进入常规 CI;原生对照因宿主 Node 版本不同而需显式运行。这不验收 JSX、TypeScript、ESLint 规则或整个 JavaScript 生态;见[JavaScript 差分验收](../tests/acceptance/javascript-native-differential.md)。 +`check all` 的 Go 路径现仅在 Go 1.23.4 `go vet` 完成、受控 `go list -json ./...` 证明文件进入同一默认构建范围,且源码、模块与工具身份均匹配时,跳过该文件的重复 WASM。构建标签排除的文件仍进入候选 worker;包清单越界或输入变化不能触发跳过。真实 Go 集成与伪造范围回归已覆盖边界,但整体交付仍未完成;见[Go 原生优先验收](../tests/acceptance/check-all-native-preferred-go.md)。 + 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 可选构建特性下,Rust CLI 现在有私有[候选工作进程](../crates/codeguard-cli/src/syntax_worker_command.rs)与[父进程核验器](../crates/codeguard-cli/src/syntax_worker_runner.rs)。Unix 路径复用现有进程组执行内核,源码输入上限 1 MiB、输出上限 64 KiB,沿用调用方截止时间和取消,并重新核对源码/grammar 摘要与恢复位置。这只是已测试的局部契约:经目标平台实测的内存上限、跨平台强制约束、正式 lint/check 路由与发行打包仍缺;候选 grammar 即使无恢复节点也保持 `incomplete`。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md index 97a4ef8a..019d1cba 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md @@ -18,6 +18,10 @@ - **WHEN** `lint zig` 收到可核对的 Zig 0.16.0 工具与普通 `.zig` 文件 - **THEN** 优先以受控进程运行原生 `zig ast-check`;原生诊断必须保留,不能因 WASM 观察覆盖。只有该原生工具未提供或不可运行时,才可返回未验收 WASM 候选观察,并始终保持整体未完成;`ast-check` 不等于全部 Zig lint、测试或构建。 +#### Scenario: Go vet excludes a file under build constraints +- **WHEN** 本轮 Go 1.23.4 `go vet` 完成,受控 `go list` 证明部分源码进入默认构建,而另一份 `.go` 源码被构建标签排除 +- **THEN** 仅对同一源码字节且进入原生包清单的文件跳过重复 WASM;被排除文件继续候选初检,并保持平台/构建标签覆盖未完成 + #### Scenario: A local checker is absent from PATH - **WHEN** 项目声明的本地原生工具已可按受支持方式定位 - **THEN** 核对其配置和版本后优先运行,不创建错误的安装任务 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 9cde009f..5f25b906 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1083,3 +1083,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 Go 原生差分局部增量:固定本机 Go 1.23.4 `gofmt -e` 作为独立语法 oracle,同一 8 份合法和 5 份破损源码经公开隔离 Go worker 逐例恢复分类一致,显式原生测试 1/1 与可由 CI 常规运行的 WASM 语料回归 1/1 通过;候选仍未验收且交付未评估。此对照不覆盖类型/语义、`go vet`、其他版本和构建标签或系统误报漏报率;见[Go 原生差分验收](../../../tests/acceptance/go-native-differential.md)。14.4、14.17、14.19 不勾选。 14.4 / 14.17 / 14.19 JavaScript 原生差分局部增量:固定本机 Node 24.18.0 模块语法检查作为独立 oracle,同一 8 份合法和 5 份破损源码经公开隔离 JavaScript worker 逐例恢复分类一致,显式原生测试 1/1 与常规 CI 语料回归 1/1 通过。此对照不覆盖 ESLint、TypeScript/JSX、其它方言或系统误报漏报率;见[JavaScript 原生差分验收](../../../tests/acceptance/javascript-native-differential.md)。三项任务仍不勾选。 + +14.6 / 14.7 / 14.19 Go 原生优先局部增量:`check all` 现在按模块一次运行受控 `go list`,仅在本轮 Go 1.23.4 vet 已完成、工具/模块/源码摘要与默认包清单均匹配时跳过被选中 Go 文件的重复 WASM;构建标签排除的破损源码仍进入隔离候选,原生 `printf` 发现保留。真实 Go 测试先 RED 后 1/1 通过,伪造越界包清单常规回归 1/1 通过,全 32 份候选分批路由 4/4 保持通过,见[Go 原生优先局部验收](../../../tests/acceptance/check-all-native-preferred-go.md)。平台/标签覆盖、其它 31 种 grammar、任务/宿主反馈和发行仍缺,父任务不勾选。 diff --git a/tests/acceptance/check-all-native-preferred-go.md b/tests/acceptance/check-all-native-preferred-go.md new file mode 100644 index 00000000..39908e9d --- /dev/null +++ b/tests/acceptance/check-all-native-preferred-go.md @@ -0,0 +1,17 @@ +# `check all` 的 Go 原生包范围优先局部验收 + +日期:2026-10-03。对应 OpenSpec 14.6、14.7、14.19 的局部增量,父任务仍未完成。 + +原 `check all` 即使本轮 Go 1.23.4 `go vet` 完成,也对所有 `.go` 文件重复运行候选 WASM。直接按整体 vet 状态跳过又会遗漏构建标签排除的文件。RED 的真实 Go 项目包含普通 `main.go` 和 `//go:build never` 下的破损 `excluded.go`,期望仅前者由原生检查覆盖;旧实现的 `native_preferred_count` 为 0。 + +现在候选阶段在原生 vet 之后,复核同一工具 SHA-256、Go 版本、全部模块身份及源码快照,再用受控 `go list -json ./...` 按模块取得默认构建中的普通/测试源码与排除源码。只对清单中选中的、源码字节仍匹配的文件跳过重复 WASM;排除或未列出的 Go 文件仍运行候选 worker。任一包清单越界、工具/源码变化、模块状态不完整、超时或清单解析失败,都不授予跳过。清单按模块一次运行,不对每个文件重复启动 `go list`。原生发现保留,整体仍 `incomplete`、退出码 3;默认宿主平台和构建标签之外的覆盖不宣称完成。 + +验收:本机 Go 1.23.4 的显式真实集成测试 1/1 通过:`main.go` 的原生 `printf` 发现完整保留并被原生优先计数,带构建标签且有恢复节点的 `excluded.go` 仍出现在候选观察;伪造越界包路径的常规集成回归 1/1 通过,vet 局部状态虽完整也不能跳过。相关 `check_all_grammar_candidates` 4/4 通过,包括四组项目实际调用全部 32 份候选。默认和 WASM 特性构建的 Clippy 全目标 `-D warnings`、格式及 OpenSpec strict 均通过。显式命令: + +```bash +CODEGUARD_GO_BIN=/usr/local/go/bin/go \ +cargo test --locked -p codeguard-cli --features wasm-precheck \ + --test check_all_native_preferred_go -- --ignored --nocapture +``` + +Go 的原生 vet/默认包清单不是全平台、全部构建标签或全部 lint 规则证明;工具制品和策略仍未获发行资格。其它语言的逐文件原生优先、任务/宿主反馈及正式语法能力验收仍缺,因此 14.6、14.7、14.19 不勾选。 From 874973c128674ae3375daf92e0cb68200ccaf87d Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 13:48:50 +0800 Subject: [PATCH 09/31] =?UTF-8?q?=E8=A1=A5=E5=85=85=20Ruby=20=E4=B8=8E=20S?= =?UTF-8?q?wift=20WASM=20=E5=8E=9F=E7=94=9F=E5=B7=AE=E5=88=86=E8=AF=81?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- README.zh-CN.md | 2 +- .../tests/ruby_native_differential.rs | 120 ++++++++++++++++++ .../tests/swift_native_differential.rs | 106 ++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + grammars/manifest.json | 4 +- grammars/swift/README.md | 2 +- .../specs/syntax-precheck/spec.md | 4 + .../introduce-rust-codeguard-cli/tasks.md | 2 + tests/acceptance/ruby-native-differential.md | 17 +++ tests/acceptance/swift-native-differential.md | 21 +++ 12 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 crates/codeguard-cli/tests/ruby_native_differential.rs create mode 100644 crates/codeguard-cli/tests/swift_native_differential.rs create mode 100644 tests/acceptance/ruby-native-differential.md create mode 100644 tests/acceptance/swift-native-differential.md diff --git a/README.md b/README.md index 678b8ad0..e5aeb614 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ The current source-built CLI's `codeguard grammar status --format=json` reports In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` can explicitly run any of the 32 pinned candidates in an isolated worker. It always exits 3 with `status=incomplete`, `native.status=not_run`, and `delivery_decision=not_evaluated`; a completed parse also has `precheck.status=incomplete`. Input or worker failures use the same [closed JSON schema](schemas/grammar-probe-v0.1.schema.json). Recovery anchors are suspected observations only. This diagnostic command does not yet connect all grammars to native-first `lint/check` or to the published npm package. -The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback or part of published npm `0.1.2`. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). +The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback or part of published npm `0.1.2`. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). A [Ruby 2.6.10 differential](tests/acceptance/ruby-native-differential.md) agrees on 13 narrow syntax cases; a [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. The source-built Unix CLI also has a narrow Zig route: `codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`. An explicitly supplied tool reporting Zig 0.16.0 and retaining the same byte digest runs native `ast-check` first; its source positions are retained without exposing source snippets. Without an explicit tool, the pinned Zig WASM provides an unqualified observation. Both paths remain incomplete because `ast-check` covers only local AST errors, not full lint, build or tests. See the [Zig feedback schema](schemas/zig-lint-feedback-v0.1.schema.json). diff --git a/README.zh-CN.md b/README.zh-CN.md index 4277a98a..8b920fee 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -102,7 +102,7 @@ cargo build --locked -p codeguard-cli 源码以 `--features wasm-precheck` 构建后,可显式运行 `codeguard grammar probe --format=json`,由隔离 worker 调用 32 份固定候选中的任意一份。命令始终以退出码 3 返回,并标记 `status=incomplete`、`native.status=not_run`、`delivery_decision=not_evaluated`;解析完成时还标记 `precheck.status=incomplete`。输入或 worker 失败也遵守同一份[封闭 JSON Schema](schemas/grammar-probe-v0.1.schema.json)。恢复锚点只是疑似观察。此诊断入口尚未把全部语种接入原生优先的 `lint/check`,也尚未进入已发布 npm 包。 -同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底,也未进入已发布 npm `0.1.2`;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。 +同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底,也未进入已发布 npm `0.1.2`;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)在 13 个窄范围语法样例上一致;[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 源码构建的 Unix CLI 还提供局部 Zig 入口:`codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`。显式提供且报告 Zig 0.16.0、执行前后字节摘要一致的工具优先运行原生 `ast-check`,仅输出诊断位置,不回显源码;未显式提供工具时,固定 Zig WASM 给出未验收候选观察。两条路径都保持未完成,因为 `ast-check` 仅覆盖局部 AST 错误,不等于完整 lint、构建或测试。见 [Zig 报告 Schema](schemas/zig-lint-feedback-v0.1.schema.json)。 diff --git a/crates/codeguard-cli/tests/ruby_native_differential.rs b/crates/codeguard-cli/tests/ruby_native_differential.rs new file mode 100644 index 00000000..427ed4e7 --- /dev/null +++ b/crates/codeguard-cli/tests/ruby_native_differential.rs @@ -0,0 +1,120 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::io::Write; +use std::path::Path; +use std::process::{Command, Stdio}; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("plain", "x = 1\n", true), + ("class", "class Box\n def value\n 1\n end\nend\n", true), + ("block", "[1, 2].map { |x| x + 1 }\n", true), + ("hash", "h = { name: \"a\", count: 2 }\n", true), + ( + "interpolation", + "name = \"world\"\ns = \"hello #{name}\"\n", + true, + ), + ("heredoc", "s = <<~TEXT\n hello\nTEXT\n", true), + ("modifier", "puts \"ok\" if true\n", true), + ("module", "module M\n CONST = 1\nend\n", true), + ( + "missing_end", + "class Box\n def value\n 1\n end\n", + false, + ), + ("unclosed_string", "s = \"oops\n", false), + ("bad_def", "def f(\n 1\nend\n", false), + ("missing_expr", "x =\n", false), + ("bad_call", "foo(1, 2\n", false), + ] +} + +fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool { + let file = root.join(format!("{name}.rb")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "ruby"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report["recoveries"].as_array().unwrap().is_empty() +} + +#[test] +fn ruby_worker_preserves_native_labeled_syntax_corpus() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-ruby-corpus-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + assert_eq!( + candidate_is_valid(&root, name, source), + expected_valid, + "{name}: pinned Ruby 2.6.10 syntax classification changed" + ); + } + fs::remove_dir_all(root).unwrap(); +} + +#[test] +#[ignore = "requires explicit existing Ruby 2.6.10 via CODEGUARD_RUBY_BIN"] +fn pinned_ruby_worker_matches_native_ruby_check_on_syntax_corpus() { + let ruby = std::env::var("CODEGUARD_RUBY_BIN").expect("provide an existing Ruby executable"); + let version = Command::new(&ruby).arg("--version").output().unwrap(); + assert!(version.status.success()); + assert!(String::from_utf8_lossy(&version.stdout).starts_with("ruby 2.6.10p210 ")); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-ruby-differential-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + let mut compared = 0; + for (name, source, expected_valid) in corpus() { + let mut native = Command::new(&ruby) + .args(["-c", "-"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + native + .stdin + .take() + .unwrap() + .write_all(source.as_bytes()) + .unwrap(); + let native = native.wait_with_output().unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1)), + "{name}: {native:?}" + ); + assert_eq!( + native.status.success(), + expected_valid, + "{name}: Ruby disagrees with the pinned corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + assert_eq!( + candidate_is_valid(&root, name, source), + native.status.success(), + "{name}: Ruby={} worker classification differs", + String::from_utf8_lossy(&native.stderr) + ); + compared += 1; + } + fs::remove_dir_all(root).unwrap(); + assert_eq!(compared, 13); +} diff --git a/crates/codeguard-cli/tests/swift_native_differential.rs b/crates/codeguard-cli/tests/swift_native_differential.rs new file mode 100644 index 00000000..21e1f2c1 --- /dev/null +++ b/crates/codeguard-cli/tests/swift_native_differential.rs @@ -0,0 +1,106 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::path::Path; +use std::process::Command; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("plain", "let value = 1\n", true), + ( + "function", + "func add(_ a: Int, _ b: Int) -> Int { a + b }\n", + true, + ), + ("struct", "struct Point { let x: Int; let y: Int }\n", true), + ("enum", "enum Status { case ready, failed(String) }\n", true), + ("optional", "let name: String? = nil\n", true), + ("closure", "let double = { (x: Int) in x * 2 }\n", true), + ("interpolation", "let text = \"hello \\(1 + 2)\"\n", true), + ( + "async", + "func fetch() async throws -> Int { return 1 }\n", + true, + ), + ("missing_brace", "func f() {\n", false), + ("unclosed_string", "let s = \"oops\n", false), + ("missing_expr", "let x =\n", false), + ("bad_param", "func f(_ x: ) {}\n", false), + ("bad_call", "print(1, 2\n", false), + ] +} + +fn candidate_report(root: &Path, name: &str, source: &str) -> serde_json::Value { + let file = root.join(format!("{name}.swift")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "swift"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report +} + +#[test] +fn known_swift_missing_parameter_type_gap_cannot_claim_qualification() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-swift-gap-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + let report = candidate_report(&root, "bad_param", "func f(_ x: ) {}\n"); + fs::remove_dir_all(root).unwrap(); + assert!(report["recoveries"].as_array().unwrap().is_empty()); + assert_eq!(report["status"], "incomplete"); +} + +#[test] +#[ignore = "requires explicit existing Apple Swift 6.4 via CODEGUARD_SWIFTC_BIN"] +fn swift_native_parse_exposes_one_known_candidate_false_negative() { + let swiftc = std::env::var("CODEGUARD_SWIFTC_BIN") + .expect("provide an existing Apple Swift compiler executable"); + let version = Command::new(&swiftc).arg("--version").output().unwrap(); + assert!(version.status.success()); + assert!(String::from_utf8_lossy(&version.stdout).contains("Apple Swift version 6.4")); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-swift-differential-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + let mut mismatches = Vec::new(); + for (name, source, expected_valid) in corpus() { + let file = root.join(format!("{name}.swift")); + fs::write(&file, source).unwrap(); + let native = Command::new(&swiftc) + .args(["-frontend", "-parse"]) + .arg(&file) + .output() + .unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1)), + "{name}: {native:?}" + ); + assert_eq!( + native.status.success(), + expected_valid, + "{name}: Swift disagrees with the pinned corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + let report = candidate_report(&root, name, source); + if report["recoveries"].as_array().unwrap().is_empty() != native.status.success() { + mismatches.push(name); + } + } + fs::remove_dir_all(root).unwrap(); + assert_eq!(mismatches, ["bad_param"]); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index d731d80a..9d5d0a05 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -232,6 +232,8 @@ The JavaScript module corpus similarly compares Node 24.18.0 `--check --input-ty For Go under `check all`, a completed Go 1.23.4 `go vet` now preempts duplicate WASM only for files selected by a bounded `go list -json ./...` under the same default build conditions and matching source, module and tool identities. Build-tag-excluded files still reach the candidate worker; malformed package scope or changed inputs cannot preempt it. A real Go integration and a forged-scope regression cover this boundary, while the overall delivery remains incomplete; see the [Go native-first record](../tests/acceptance/check-all-native-preferred-go.md). +Ruby 2.6.10 `ruby -c` agrees with the isolated Ruby worker on a narrow 13-case corpus, with the fixed worker corpus running in ordinary CI; this does not qualify Ruby lint or other versions. By contrast, Apple Swift 6.4 `swiftc -frontend -parse` rejects `func f(_ x: ) {}` as a missing parameter type while the fixed Swift WASM emits no recovery. The 13-case Swift differential has 12 agreements and this one false negative. The candidate remains unqualified and cannot claim a clean syntax result; see the [Ruby](../tests/acceptance/ruby-native-differential.md) and [Swift](../tests/acceptance/swift-native-differential.md) records. + 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. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 01f6c93c..2cb8dad3 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -232,6 +232,8 @@ JavaScript 模块语料也用 Node 24.18.0 `--check --input-type=module` 与隔 `check all` 的 Go 路径现仅在 Go 1.23.4 `go vet` 完成、受控 `go list -json ./...` 证明文件进入同一默认构建范围,且源码、模块与工具身份均匹配时,跳过该文件的重复 WASM。构建标签排除的文件仍进入候选 worker;包清单越界或输入变化不能触发跳过。真实 Go 集成与伪造范围回归已覆盖边界,但整体交付仍未完成;见[Go 原生优先验收](../tests/acceptance/check-all-native-preferred-go.md)。 +Ruby 2.6.10 `ruby -c` 与隔离 Ruby worker 在 13 例窄范围语料上分类一致,固定 worker 语料进入常规 CI;这不等于 Ruby lint 或其它版本验收。相反,Apple Swift 6.4 `swiftc -frontend -parse` 把 `func f(_ x: ) {}` 判为缺少参数类型,固定 Swift WASM 却没有恢复节点。Swift 13 例差分中 12 例一致、1 例漏检,候选仍不可签发语法通过;见[Ruby](../tests/acceptance/ruby-native-differential.md)与[Swift](../tests/acceptance/swift-native-differential.md)局部记录。 + 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 可选构建特性下,Rust CLI 现在有私有[候选工作进程](../crates/codeguard-cli/src/syntax_worker_command.rs)与[父进程核验器](../crates/codeguard-cli/src/syntax_worker_runner.rs)。Unix 路径复用现有进程组执行内核,源码输入上限 1 MiB、输出上限 64 KiB,沿用调用方截止时间和取消,并重新核对源码/grammar 摘要与恢复位置。这只是已测试的局部契约:经目标平台实测的内存上限、跨平台强制约束、正式 lint/check 路由与发行打包仍缺;候选 grammar 即使无恢复节点也保持 `incomplete`。 diff --git a/grammars/manifest.json b/grammars/manifest.json index 80d3835e..5c9a2f96 100644 --- a/grammars/manifest.json +++ b/grammars/manifest.json @@ -529,7 +529,7 @@ "codegraph_runtime": "web-tree-sitter 0.25.3", "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], - "known_limitations": ["Pinned CodeGraph asset; only narrow syntax fixtures; native differential, language and dialect ranges, public lint route, resource budget and release remain unqualified"], + "known_limitations": ["Swift 6.4 native parse rejects missing parameter type in `func f(_ x: ) {}` but this WASM emits no recovery; 12/13 narrow differential cases agree; language and dialect ranges, public native-first lint route, resource budget and release remain unqualified"], "release_status": "candidate_unvalidated" }, { @@ -583,7 +583,7 @@ "codegraph_runtime": "web-tree-sitter 0.25.3", "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], - "known_limitations": ["Rust loader and narrow fixtures passed; Ruby language versions, dialect corpus, native differential, isolation and release remain unqualified"], + "known_limitations": ["Ruby 2.6.10 native syntax differential agrees on 13 narrow cases; other versions, dialect corpus, systematic precision, native-first lint route and release remain unqualified"], "release_status": "candidate_unvalidated" }, { diff --git a/grammars/swift/README.md b/grammars/swift/README.md index 5bb26211..5fe390ad 100644 --- a/grammars/swift/README.md +++ b/grammars/swift/README.md @@ -2,4 +2,4 @@ 固定 CodeGraph 提交中的 `tree-sitter-swift.wasm`,SHA-256 `cc77a63b8487956270e2f385e29a03ba0773ba532a3c8a8844a26b4c98793843`。CodeGraph [Swift 来源记录](https://github.com/colbymchenry/codegraph/blob/main/docs/design/swift-kernel-port-checklist.md) 指出实际生成源是 crates.io `tree-sitter-swift` **0.7.3** crate(CodeGraph Cargo.lock 校验和 `fe36052155b9dd69ca82b3b8f1b4ccfb2d867125ac1a4db1dd7331829242668c`),其 `src/parser.c` SHA-256 `d3edff6effe31b9a507f496577407987343b101b23eb7bee7a9b050e8ab5d27a`。清单中的 `31d17fe7e818a2048c808b5c6fdc2dc792f4f5b5` 是对应语法规约的上游标签提交;**该标签生成的 C 文件与 crate 发行物不同,不能当作 WASM 的字节来源**。本目录 `LICENSE` 取自固定 crate,SHA-256 `3533cec129bb4bba015c0d61d86dd7c3b7e82110e4d2ff7837a01eff5bad5ccc`。 -Rust 离线加载实测 ABI 15,基础正反例及隔离 worker 通过。Swift 版本语料、编译器对照、公开 lint 路由和发行包尚未验收。 +Rust 离线加载实测 ABI 15,基础正反例及隔离 worker 通过。Swift 6.4 原生 `swiftc -frontend -parse` 与同一 worker 的 13 例独立差分中,12 例分类一致;`func f(_ x: ) {}` 被原生编译器拒绝为缺少参数类型,而固定 WASM 无恢复节点,是已确认漏检。见[局部差分验收](../../tests/acceptance/swift-native-differential.md)。因此此候选不得签发语法通过;Swift 版本/方言语料、修复后的编译器对照、公开原生优先 lint 路由和发行包仍未验收。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md index 019d1cba..a91146be 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md @@ -167,6 +167,10 @@ Rust runtime MUST 按需加载 grammar,在受控解析工作进程中限制输 - **WHEN** 同一组合法与破损源码分别经过固定版本的语言原生语法工具和隔离 WASM worker - **THEN** 逐例记录语法分类一致性、工具与语料身份;仅扩大该版本的局部精度证据,不将候选提升为完整 lint、其它版本或交付通过 +#### Scenario: Native syntax rejects a source that the WASM candidate accepts +- **WHEN** 固定 Swift 6.4 编译器的语法阶段拒绝缺少参数类型的源码,而固定 Swift WASM 没有恢复节点 +- **THEN** 记录带原生证据的漏检及待修复 grammar 身份,保持候选未验收和完整检查义务;不能因零恢复节点签发 clean、放入白名单或关闭任务 + ### Requirement: Precheck briefs SHALL reach agent conversations with concrete next actions human/结构化报告及宿主渲染 MUST 按结论、方式/范围、原生状态、依据、下一步和实际任务引用组织信息。必须/推荐动作 MUST 明确。疑似异常、正常、未完成和原生诊断四类模板 MUST 独立验证;CVE 等非语法覆盖不能套用语法通过结论。插件 MUST 通过真实宿主的工具结果/上下文 API 交付,文件或 stdout 存在不等于交付。初次反馈后只发送有意义的变化;原始工具文本 MUST 作为数据,不执行其中指令。AGENTS MUST 仅保留长期指引,不追加每轮扫描日志。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 5f25b906..964022d8 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1085,3 +1085,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 JavaScript 原生差分局部增量:固定本机 Node 24.18.0 模块语法检查作为独立 oracle,同一 8 份合法和 5 份破损源码经公开隔离 JavaScript worker 逐例恢复分类一致,显式原生测试 1/1 与常规 CI 语料回归 1/1 通过。此对照不覆盖 ESLint、TypeScript/JSX、其它方言或系统误报漏报率;见[JavaScript 原生差分验收](../../../tests/acceptance/javascript-native-differential.md)。三项任务仍不勾选。 14.6 / 14.7 / 14.19 Go 原生优先局部增量:`check all` 现在按模块一次运行受控 `go list`,仅在本轮 Go 1.23.4 vet 已完成、工具/模块/源码摘要与默认包清单均匹配时跳过被选中 Go 文件的重复 WASM;构建标签排除的破损源码仍进入隔离候选,原生 `printf` 发现保留。真实 Go 测试先 RED 后 1/1 通过,伪造越界包清单常规回归 1/1 通过,全 32 份候选分批路由 4/4 保持通过,见[Go 原生优先局部验收](../../../tests/acceptance/check-all-native-preferred-go.md)。平台/标签覆盖、其它 31 种 grammar、任务/宿主反馈和发行仍缺,父任务不勾选。 + +14.4 / 14.17 / 14.19 Ruby 与 Swift 原生差分增量:Ruby 2.6.10 `ruby -c` 和隔离 Ruby worker 的 13 例分类一致,显式原生测试与常规 worker 语料均 1/1 通过;见[Ruby 局部验收](../../../tests/acceptance/ruby-native-differential.md)。Swift 6.4 `swiftc -frontend -parse` 对 13 例中 12 例与 WASM 一致,但缺参数类型的 `func f(_ x: ) {}` 被原生拒绝而 WASM 零恢复,确认为漏检;显式测试 1/1 通过仅表示成功捕获这一差异,不表示语法验收,见[Swift 精度阻塞](../../../tests/acceptance/swift-native-differential.md)。须修 grammar、固定重建产物并以原生对照复验;三项父任务不勾选。 diff --git a/tests/acceptance/ruby-native-differential.md b/tests/acceptance/ruby-native-differential.md new file mode 100644 index 00000000..6b9e2c93 --- /dev/null +++ b/tests/acceptance/ruby-native-differential.md @@ -0,0 +1,17 @@ +# Ruby 2.6.10 原生语法与内置 WASM 的局部差分验收 + +日期:2026-10-03。对应 OpenSpec 14.4、14.17、14.19 的局部精度证据,父任务仍未完成。 + +固定 13 份 Ruby 2.6 语法样例,用现存 `ruby -c -` 通过 stdin 做独立语法对照,并调用公开 `grammar probe ruby` 的隔离 WASM worker 检查同一字节。测试核对原生版本、退出码、候选实际解析、恢复节点分类与未验收状态。原生对照需显式提供 `CODEGUARD_RUBY_BIN`;同一固定语料的 worker 回归进入常规 CI。 + +本机 Ruby 为 `2.6.10p210`,`/usr/bin/ruby` SHA-256 `8404c9d5fd2200e82c2520c8d9c1badf2a164c0da7c350b54e86cb0025ad9b50`。运行: + +```bash +CODEGUARD_RUBY_BIN=/usr/bin/ruby \ +cargo test --locked -p codeguard-cli --features wasm-precheck \ + --test ruby_native_differential -- --ignored --nocapture +``` + +结果:常规 worker 回归 1/1、显式原生差分 1/1 通过;8 份合法与 5 份破损样例分类一致。语料覆盖类和模块、块、哈希、插值、heredoc、后置条件,以及缺少 `end`、未闭合字符串、破损定义/调用和缺失表达式。报告始终 `grammar_qualified=false`、`delivery_decision=not_evaluated`,退出码 3。 + +这仅是 Ruby 2.6.10 的窄范围语法分类证据;不覆盖其它 Ruby 版本、项目 DSL/模板、语义、规则 lint、系统误报漏报率或公开发行。 diff --git a/tests/acceptance/swift-native-differential.md b/tests/acceptance/swift-native-differential.md new file mode 100644 index 00000000..aa6d8c2e --- /dev/null +++ b/tests/acceptance/swift-native-differential.md @@ -0,0 +1,21 @@ +# Swift 6.4 原生语法差分发现固定 WASM 漏检 + +日期:2026-10-03。对应 OpenSpec 14.4、14.17、14.19 的精度阻塞,父任务仍未完成。 + +同一组 13 份 `.swift` 源码分别交给现存 Apple Swift 6.4 的 `swiftc -frontend -parse` 与公开 `grammar probe swift` 隔离 WASM worker。8 份合法、5 份破损;原生标注均由编译器确认。12 份分类一致,1 份破损源码不一致: + +```swift +func f(_ x: ) {} +``` + +编译器在第 1 行第 13 列报告 `expected parameter type following ':'` 并退出 1;固定 WASM 返回零恢复节点。常规 CI 单例回归确认这一已知漏检不会获得 `grammar_qualified=true` 或交付通过,显式原生测试锁定 13 例中只存在这个已知不一致项,以便 grammar 修复时主动重新评估。它是当前候选的**漏报反例**,不是合法源码误报,也不能靠删除测试或加白名单放过。 + +本机 `swiftc --version` 为 Apple Swift 6.4,`/usr/bin/swiftc` SHA-256 `34129c71a01a74f7f3b2443521519b2e5447553fa187f5fcafaaf8c42cc192e2`。运行: + +```bash +CODEGUARD_SWIFTC_BIN=/usr/bin/swiftc \ +cargo test --locked -p codeguard-cli --features wasm-precheck \ + --test swift_native_differential -- --ignored --nocapture +``` + +本机显式差分 1/1 通过,用时 68.56 秒;其通过表示**成功识别并保留漏检**,不表示 Swift 语法验收通过。修复需针对固定来源 grammar 及版本/方言语料,重新构建可追溯 WASM,再重复原生差分、资源及发行包验证。当前不得将 Swift WASM 作为 lint 或语法 clean 的依据。 From 207e958ff769f68666c88ce87a702da429e8d457 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 14:11:13 +0800 Subject: [PATCH 10/31] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E5=85=B1=E4=BA=AB?= =?UTF-8?q?=E5=90=8E=E7=BC=80=E7=9A=84=20WASM=20=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/codeguard-cli/src/grammar_route.rs | 29 +++++++++++++++++-- .../tests/check_all_grammar_candidates.rs | 9 ++++++ crates/codeguard-cli/tests/grammar_route.rs | 24 ++++++++++++++- docs/Codeguard-Architecture.md | 2 +- docs/Codeguard-Architecture.zh_CN.md | 2 +- docs/Codeguard-Technical-Design.md | 2 +- docs/Codeguard-Technical-Design.zh_CN.md | 2 +- .../specs/syntax-precheck/spec.md | 6 ++++ .../introduce-rust-codeguard-cli/tasks.md | 2 ++ .../ambiguous-grammar-extensions.md | 9 ++++++ 10 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 tests/acceptance/ambiguous-grammar-extensions.md diff --git a/crates/codeguard-cli/src/grammar_route.rs b/crates/codeguard-cli/src/grammar_route.rs index 28286041..a1f1f1a0 100644 --- a/crates/codeguard-cli/src/grammar_route.rs +++ b/crates/codeguard-cli/src/grammar_route.rs @@ -36,14 +36,15 @@ pub fn route_source<'a>(relative_path: &str, source: &'a [u8]) -> Vec "lua", Some("luau") => "luau", Some("nix") => "nix", - Some("m" | "mm") => "objc", + Some("m") if has_objc_marker(source) => "objc", + Some("mm") => "objc", Some("pas" | "dpr" | "dpk" | "lpr") => "pascal", Some("php") => "php", Some("py") => "python", Some("r") => "r", Some("rb") => "ruby", Some("rs") => "rust", - Some("scala" | "sc") => "scala", + Some("scala") => "scala", Some("sol") => "solidity", Some("swift") => "swift", Some("tf" | "tfvars" | "tofu") => "terraform", @@ -94,6 +95,30 @@ pub fn route_source<'a>(relative_path: &str, source: &'a [u8]) -> Vec bool { + let markers: &[&[u8]] = &[ + b"@interface".as_slice(), + b"@implementation", + b"@protocol", + b"@class", + b"@property", + b"@synthesize", + b"@autoreleasepool", + b"#import", + ]; + source.split(|byte| *byte == b'\n').any(|line| { + let line = line.trim_ascii_start(); + markers.iter().any(|marker| { + line.starts_with(marker) + && line.get(marker.len()).is_some_and(|next| { + next.is_ascii_whitespace() || matches!(next, b'(' | b'{' | b'<' | b'"') + }) + }) + }) +} + fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option { haystack .windows(needle.len()) diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index e1dd3841..1d3d99ee 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -68,6 +68,12 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { fs::create_dir_all(&root).unwrap(); fs::write(root.join("component.tsx"), "const C = () =>
;\n").unwrap(); fs::write(root.join("script.js"), "const x = 1;\n").unwrap(); + fs::write( + root.join("plot.m"), + "title('@interface Foo');\nplot(1:3);\n", + ) + .unwrap(); + fs::write(root.join("synth.sc"), "{ SinOsc.ar(440) }.play;\n").unwrap(); fs::write( root.join("component.cfs"), "component { function f() { return 1; } }\n", @@ -103,6 +109,9 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { "{language}: {observations:?}" ); } + assert!(!observations.iter().any(|item| item["path"] == "plot.m")); + assert!(!observations.iter().any(|item| item["path"] == "synth.sc")); + assert_eq!(report["syntax_candidates"]["unrouted_count"], 2); assert_eq!( observations .iter() diff --git a/crates/codeguard-cli/tests/grammar_route.rs b/crates/codeguard-cli/tests/grammar_route.rs index 163adf8d..13ce7a4b 100644 --- a/crates/codeguard-cli/tests/grammar_route.rs +++ b/crates/codeguard-cli/tests/grammar_route.rs @@ -39,7 +39,12 @@ fn routes_every_pinned_grammar_without_merging_dialects() { ]; let mut observed = BTreeSet::new(); for (language, path) in cases { - let routes = route_source(path, b"source"); + let source: &[u8] = if path == "a.m" { + b"@interface Foo : NSObject\n@end\n" + } else { + b"source" + }; + let routes = route_source(path, source); assert_eq!(routes.len(), 1, "{path}"); assert_eq!(routes[0].language, language, "{path}"); observed.insert(language); @@ -60,6 +65,23 @@ fn routes_every_pinned_grammar_without_merging_dialects() { fn ambiguous_and_embedded_sources_do_not_get_speculative_routes() { assert!(route_source("a.h", b"int x;").is_empty()); assert!(route_source("a.sql", b"SELECT 1").is_empty()); + assert!(route_source("plot.m", b"x = [1, 2, 3];\nplot(x);\n").is_empty()); + assert!( + route_source( + "plot.m", + b"title('@interface Foo');\n% #import is only an example\n" + ) + .is_empty() + ); + assert!(route_source("synth.sc", b"{ SinOsc.ar(440) }.play;\n").is_empty()); + assert_eq!( + route_source("model.m", b"@implementation Model\n@end\n")[0].language, + "objc" + ); + assert_eq!( + route_source("script.scala", b"object App {}\n")[0].language, + "scala" + ); assert!( route_source("a.cfm", b"SELECT 1") .iter() diff --git a/docs/Codeguard-Architecture.md b/docs/Codeguard-Architecture.md index 471b5ab1..8b311572 100644 --- a/docs/Codeguard-Architecture.md +++ b/docs/Codeguard-Architecture.md @@ -32,7 +32,7 @@ WASM requirements and 19 implementation tasks are recorded in the existing chang A source build with `--features wasm-precheck` now exposes `codeguard grammar probe --format=json` for all 32 pinned candidates. It uses the isolated Rust worker and reports unqualified observations with exit 3, not a lint or delivery verdict. Successful observations and input failures for a valid language share the [closed 0.1.0 JSON schema](../schemas/grammar-probe-v0.1.schema.json) and remain incomplete. Native-first `lint/check`, language/dialect acceptance, task/host feedback and distribution remain separate open work; current discovery also groups JavaScript/TSX under TypeScript and has no separate CFQuery/CFScript source mapping. -The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.33.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback, task/host integration or released npm capability. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). +The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.33.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback, task/host integration or released npm capability. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). ```mermaid flowchart LR diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index 9c8a489d..bd2db307 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -32,7 +32,7 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选 Rust worker 源码以 `--features wasm-precheck` 构建后,可通过 `codeguard grammar probe --format=json` 显式调用全部 32 份固定候选资产。命令使用隔离 Rust worker,以退出码 3 报告未经语言验收的观察,不能作为 lint 或交付结论。成功观察与有效语种的输入失败都遵守[0.1.0 封闭 JSON Schema](../schemas/grammar-probe-v0.1.schema.json),保持未完成。原生优先 `lint/check`、语言/方言验收、任务/宿主反馈及发行仍需独立完成;现有发现清单还把 JavaScript/TSX 归入 TypeScript,并未为 CFQuery/CFScript 建立独立源码映射。 -源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.33.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底、任务/宿主接线或已发布 npm 能力;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 +源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.33.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底、任务/宿主接线或已发布 npm 能力;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 ```mermaid flowchart LR diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 9d5d0a05..7ec64afb 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -212,7 +212,7 @@ The source-built `check all` now adds a narrower actual project report: native a } ``` -The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. +The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix; the static language inventory and per-file unresolved diagnostics still need improvement. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 2cb8dad3..d73c886e 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -212,7 +212,7 @@ flowchart LR } ``` -实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 +实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析;静态语言库存及逐文件未解析诊断仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md index a91146be..deee02ec 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md @@ -138,6 +138,12 @@ Rust runtime MUST 按需加载 grammar,在受控解析工作进程中限制输 同一语言族有独立 grammar 的方言 MUST 按实际文件类型选择和报告;尤其 `.tsx` MUST 使用 TSX grammar,不能拿普通 TypeScript grammar 的恢复节点当作 JSX 源码异常。候选报告读者 MUST 核对 grammar 方言与源文件扩展名一致;不匹配视为报告无效,不生成源码 finding。 +多语言共用后缀 MUST 保留歧义:`.m` 可能属于 Objective-C 或 MATLAB,`.sc` 可能属于 Scala 或 SuperCollider。没有可核对的语言证据时,自动检查 MUST 不调用猜测的 grammar、不把恢复节点记为源码问题,并保持范围未完成;用户显式指定 `grammar probe` 的语种仍可作候选诊断。 + +#### Scenario: Shared extension would produce a speculative syntax finding +- **WHEN** 项目包含无 Objective-C 专属标记的 MATLAB `.m` 源码,或 SuperCollider `.sc` 源码 +- **THEN** 自动候选路由不把它们交给 Objective-C/Scala WASM;歧义范围保持未完成,不能用资产数量或其它文件的检查结果代替该范围 + #### Scenario: Valid JSX is parsed with a TypeScript-only grammar - **WHEN** `.tsx` 文件含合法 JSX,而普通 TypeScript grammar 会产生恢复节点 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 964022d8..73eeeea3 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1087,3 +1087,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.6 / 14.7 / 14.19 Go 原生优先局部增量:`check all` 现在按模块一次运行受控 `go list`,仅在本轮 Go 1.23.4 vet 已完成、工具/模块/源码摘要与默认包清单均匹配时跳过被选中 Go 文件的重复 WASM;构建标签排除的破损源码仍进入隔离候选,原生 `printf` 发现保留。真实 Go 测试先 RED 后 1/1 通过,伪造越界包清单常规回归 1/1 通过,全 32 份候选分批路由 4/4 保持通过,见[Go 原生优先局部验收](../../../tests/acceptance/check-all-native-preferred-go.md)。平台/标签覆盖、其它 31 种 grammar、任务/宿主反馈和发行仍缺,父任务不勾选。 14.4 / 14.17 / 14.19 Ruby 与 Swift 原生差分增量:Ruby 2.6.10 `ruby -c` 和隔离 Ruby worker 的 13 例分类一致,显式原生测试与常规 worker 语料均 1/1 通过;见[Ruby 局部验收](../../../tests/acceptance/ruby-native-differential.md)。Swift 6.4 `swiftc -frontend -parse` 对 13 例中 12 例与 WASM 一致,但缺参数类型的 `func f(_ x: ) {}` 被原生拒绝而 WASM 零恢复,确认为漏检;显式测试 1/1 通过仅表示成功捕获这一差异,不表示语法验收,见[Swift 精度阻塞](../../../tests/acceptance/swift-native-differential.md)。须修 grammar、固定重建产物并以原生对照复验;三项父任务不勾选。 + +14.4 / 14.6 / 14.19 共享后缀误报收敛:自动路由不再把任意 `.m` 当 Objective-C,也不再把 `.sc` 当 Scala;`.m` 需行首 Objective-C 专属标记,`.scala` 与 `.mm` 路由保留。MATLAB 字符串/注释中的标记及 SuperCollider 正例不进入错误 grammar;32 份候选的明确样例仍需完整回归。现有静态发现仍可能将共享后缀归入旧语言目录,缺少项目级方言裁决和逐文件未路由解释,父任务保持未完成;见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。 diff --git a/tests/acceptance/ambiguous-grammar-extensions.md b/tests/acceptance/ambiguous-grammar-extensions.md new file mode 100644 index 00000000..2a02e595 --- /dev/null +++ b/tests/acceptance/ambiguous-grammar-extensions.md @@ -0,0 +1,9 @@ +# 共享后缀的语法候选路由局部验收 + +日期:2026-10-03。对应 OpenSpec 14.4、14.6、14.19 的低误报局部修复,父任务仍未完成。 + +`.m` 可表示 Objective-C 或 MATLAB,`.sc` 可表示 Scala 脚本或 SuperCollider。先写路由反例并确认失败:普通 MATLAB `plot.m` 被送入 Objective-C 候选;带 `@interface` 字符串与 `% #import` 注释的 MATLAB 文件也被宽松的字节匹配误识别。现只在 `.m` 源码行首出现 Objective-C 专属声明或 `#import` 时自动路由,`.mm` 与 `.scala` 保持明确路由;`.sc` 没有项目级证据时不猜测。显式 `grammar probe objc FILE` 仍可由用户指定语种运行。 + +`grammar_route` 的共享后缀、32 份资产可路由集合及 CFQuery 边界回归 5/5 通过;真实 `check all` 混合项目测试确认 MATLAB/SuperCollider 文件未产生错误候选观察,`unrouted_count=2` 且报告仍是未完成。四个有界项目的 32 份 worker 全量回归 1/1 通过,用时 54.74 秒,候选语种并集仍与固定清单一致。 + +这只减少自动候选误路由;静态发现仍可能按旧语言注册表把 `.m/.sc` 归入 Objective-C/Scala,自动报告也仅给未路由总数。项目级语言证据、未路由文件逐项诊断、`.m` 中不带标记的合法 Objective-C 范围及原生优先/发行验收仍缺,不能宣布这两个方言已完整支持。 From ebc55381177789d45dbfcb54a6763a0791f727f8 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 14:33:18 +0800 Subject: [PATCH 11/31] =?UTF-8?q?=E8=AE=A9=E9=9D=99=E6=80=81=E5=8F=91?= =?UTF-8?q?=E7=8E=B0=E4=BF=9D=E7=95=99=E5=85=B1=E4=BA=AB=E5=90=8E=E7=BC=80?= =?UTF-8?q?=E7=9A=84=E8=AF=AD=E8=A8=80=E6=AD=A7=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/codeguard-cli/src/check_command.rs | 2 +- .../src/check_syntax_candidates.rs | 3 ++ crates/codeguard-cli/src/discovery.rs | 28 +++++++++++++ crates/codeguard-cli/src/grammar_route.rs | 26 +----------- crates/codeguard-cli/src/lib.rs | 1 + .../codeguard-cli/src/source_language_hint.rs | 25 +++++++++++ .../tests/check_all_grammar_candidates.rs | 42 +++++++++++++++++++ crates/codeguard-cli/tests/detect_cli.rs | 38 +++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 +- docs/Codeguard-Technical-Design.zh_CN.md | 2 +- .../specs/project-initialization/spec.md | 6 +++ .../introduce-rust-codeguard-cli/tasks.md | 2 + .../ambiguous-grammar-extensions.md | 4 +- 13 files changed, 153 insertions(+), 28 deletions(-) create mode 100644 crates/codeguard-cli/src/source_language_hint.rs diff --git a/crates/codeguard-cli/src/check_command.rs b/crates/codeguard-cli/src/check_command.rs index 037b76d3..b9c7f271 100644 --- a/crates/codeguard-cli/src/check_command.rs +++ b/crates/codeguard-cli/src/check_command.rs @@ -1474,7 +1474,7 @@ pub fn run(args: &[String]) -> ExitCode { let syntax_candidates = json!({ "status":"not_run","reason":"binary_without_wasm_precheck","execution_phase":"after_native", "authority":"candidate_unqualified","delivery_decision":"incomplete", - "source_file_count":discovery.languages.values().map(|item| item.source_files.len()).sum::(), + "source_file_count":discovery.languages.values().map(|item| item.source_files.len()).sum::() + discovery.ambiguous_source_files.len(), "skipped_count":0,"unrouted_count":0,"native_preferred_count":0,"observations":[], "next_action":"使用包含固定语法资产的发行包运行候选初检,并完成适用原生检查" }); diff --git a/crates/codeguard-cli/src/check_syntax_candidates.rs b/crates/codeguard-cli/src/check_syntax_candidates.rs index 55d6d63e..bc486764 100644 --- a/crates/codeguard-cli/src/check_syntax_candidates.rs +++ b/crates/codeguard-cli/src/check_syntax_candidates.rs @@ -39,6 +39,9 @@ pub fn observe( paths.extend(evidence.source_files.iter()); } } + if !java_only { + paths.extend(discovery.ambiguous_source_files.iter()); + } let source_file_count = paths.len(); let mut observations = Vec::new(); let mut skipped_count = 0; diff --git a/crates/codeguard-cli/src/discovery.rs b/crates/codeguard-cli/src/discovery.rs index 85a6741b..4dfcbd2d 100644 --- a/crates/codeguard-cli/src/discovery.rs +++ b/crates/codeguard-cli/src/discovery.rs @@ -44,6 +44,8 @@ pub struct DiscoveryReport { pub root: String, pub observation_complete: bool, pub languages: BTreeMap, + /// 后缀不足以判定语言的源码;仍须进入候选范围的未路由计数。 + pub(crate) ambiguous_source_files: BTreeSet, pub build_roots: BTreeMap>, pub declared_versions: BTreeMap, /// 与同次清单字节摘要绑定的 Maven 直接模块声明。 @@ -147,6 +149,7 @@ pub fn discover( root: root.to_string_lossy().into_owned(), observation_complete: true, languages: BTreeMap::new(), + ambiguous_source_files: BTreeSet::new(), build_roots: BTreeMap::new(), declared_versions: BTreeMap::new(), maven_module_models: BTreeMap::new(), @@ -406,6 +409,31 @@ fn observe_file( } } } + if name.ends_with(".sc") { + report.ambiguous_source_files.insert(relative.into()); + report + .unknown_conditions + .push(format!("ambiguous_language_suffix:{relative}")); + return; + } + if name.ends_with(".m") { + match observation.read_bounded(path, 1024 * 1024) { + Ok(source) if crate::source_language_hint::has_objc_marker(&source) => {} + Ok(_) => { + report.ambiguous_source_files.insert(relative.into()); + report + .unknown_conditions + .push(format!("ambiguous_language_suffix:{relative}")); + return; + } + Err(_) => { + report.observation_complete = false; + report.blocked_paths.push(relative.into()); + report.ambiguous_source_files.insert(relative.into()); + return; + } + } + } for language in ®istry.languages { if language .extensions diff --git a/crates/codeguard-cli/src/grammar_route.rs b/crates/codeguard-cli/src/grammar_route.rs index a1f1f1a0..6e87c1c2 100644 --- a/crates/codeguard-cli/src/grammar_route.rs +++ b/crates/codeguard-cli/src/grammar_route.rs @@ -1,5 +1,7 @@ //! 按源码后缀和明确的嵌入边界选择候选 grammar;不推断项目语言版本。 +use crate::source_language_hint::has_objc_marker; + /// 一段可独立解析的候选源码;嵌入片段的位置相对于原文件的字节起点。 #[derive(Clone, Debug)] pub struct GrammarRoute<'a> { @@ -95,30 +97,6 @@ pub fn route_source<'a>(relative_path: &str, source: &'a [u8]) -> Vec bool { - let markers: &[&[u8]] = &[ - b"@interface".as_slice(), - b"@implementation", - b"@protocol", - b"@class", - b"@property", - b"@synthesize", - b"@autoreleasepool", - b"#import", - ]; - source.split(|byte| *byte == b'\n').any(|line| { - let line = line.trim_ascii_start(); - markers.iter().any(|marker| { - line.starts_with(marker) - && line.get(marker.len()).is_some_and(|next| { - next.is_ascii_whitespace() || matches!(next, b'(' | b'{' | b'<' | b'"') - }) - }) - }) -} - fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option { haystack .windows(needle.len()) diff --git a/crates/codeguard-cli/src/lib.rs b/crates/codeguard-cli/src/lib.rs index 088488f2..22150569 100644 --- a/crates/codeguard-cli/src/lib.rs +++ b/crates/codeguard-cli/src/lib.rs @@ -178,6 +178,7 @@ pub mod rustdoc_repair_brief; #[cfg(unix)] pub(crate) mod rustdoc_task_recheck; pub mod sarif_feedback; +mod source_language_hint; #[cfg(feature = "wasm-precheck")] mod syntax_worker_candidate_observation; #[cfg(feature = "wasm-precheck")] diff --git a/crates/codeguard-cli/src/source_language_hint.rs b/crates/codeguard-cli/src/source_language_hint.rs new file mode 100644 index 00000000..399ebb4e --- /dev/null +++ b/crates/codeguard-cli/src/source_language_hint.rs @@ -0,0 +1,25 @@ +//! 只读语言候选证据;供默认发现与可选 WASM 路由共用。 + +/// 识别 `.m` 源码行首的 Objective-C 专属词法标记。 +/// 参数为有界原始源码;返回值仅是语言候选证据,不证明语法或项目配置。 +pub(crate) fn has_objc_marker(source: &[u8]) -> bool { + let markers: &[&[u8]] = &[ + b"@interface".as_slice(), + b"@implementation", + b"@protocol", + b"@class", + b"@property", + b"@synthesize", + b"@autoreleasepool", + b"#import", + ]; + source.split(|byte| *byte == b'\n').any(|line| { + let line = line.trim_ascii_start(); + markers.iter().any(|marker| { + line.starts_with(marker) + && line.get(marker.len()).is_some_and(|next| { + next.is_ascii_whitespace() || matches!(next, b'(' | b'{' | b'<' | b'"') + }) + }) + }) +} diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index 1d3d99ee..e412ff48 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -112,6 +112,17 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { assert!(!observations.iter().any(|item| item["path"] == "plot.m")); assert!(!observations.iter().any(|item| item["path"] == "synth.sc")); assert_eq!(report["syntax_candidates"]["unrouted_count"], 2); + let unknown = report["discovery"]["unknown_conditions"] + .as_array() + .unwrap(); + for path in ["plot.m", "synth.sc"] { + assert!( + unknown + .iter() + .any(|item| item == &format!("ambiguous_language_suffix:{path}")), + "{path}: {unknown:?}" + ); + } assert_eq!( observations .iter() @@ -132,6 +143,37 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { ); } +#[test] +fn ambiguous_only_project_remains_an_explicit_unrouted_scope() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-ambiguous-only-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + fs::write(root.join("plot.m"), "plot(1:3);\n").unwrap(); + fs::write(root.join("synth.sc"), "{ SinOsc.ar(440) }.play;\n").unwrap(); + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["check", "all"]) + .arg(&root) + .args(["--format=json", "--timeout", "30s"]) + .output() + .unwrap(); + fs::remove_dir_all(&root).unwrap(); + assert_eq!(output.status.code(), Some(3)); + let report: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + assert_eq!(report["syntax_candidates"]["source_file_count"], 2); + assert_eq!(report["syntax_candidates"]["unrouted_count"], 2); + assert_eq!(report["syntax_candidates"]["status"], "not_run"); + assert_eq!(report["syntax_candidates"]["reason"], "unrouted_source"); + assert_eq!(report["delivery_decision"], "incomplete"); + assert!( + report["syntax_candidates"]["observations"] + .as_array() + .unwrap() + .is_empty() + ); +} + #[test] fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { let root = std::env::temp_dir().canonicalize().unwrap().join(format!( diff --git a/crates/codeguard-cli/tests/detect_cli.rs b/crates/codeguard-cli/tests/detect_cli.rs index 6647397c..3ab15582 100644 --- a/crates/codeguard-cli/tests/detect_cli.rs +++ b/crates/codeguard-cli/tests/detect_cli.rs @@ -7,6 +7,44 @@ use std::sync::atomic::{AtomicU64, Ordering}; static NEXT: AtomicU64 = AtomicU64::new(0); +#[test] +fn detect_keeps_shared_m_and_sc_suffixes_ambiguous_without_false_languages() { + let project = TempProject::new(); + fs::write( + project.0.join("plot.m"), + "title('@interface Foo');\nplot(1:3);\n", + ) + .unwrap(); + fs::write(project.0.join("synth.sc"), "{ SinOsc.ar(440) }.play;\n").unwrap(); + fs::write( + project.0.join("model.m"), + "@interface Model : NSObject\n@end\n", + ) + .unwrap(); + fs::write(project.0.join("main.scala"), "object Main {}\n").unwrap(); + let report = detect_json(&project); + let languages = report["languages"].as_array().unwrap(); + let objc = languages.iter().find(|item| item["id"] == "objc").unwrap(); + let scala = languages.iter().find(|item| item["id"] == "scala").unwrap(); + assert_eq!(objc["source_files"], serde_json::json!(["model.m"])); + assert_eq!(scala["source_files"], serde_json::json!(["main.scala"])); + assert!(report["observation_complete"].as_bool().unwrap()); + assert!( + report["unknown_conditions"] + .as_array() + .unwrap() + .iter() + .any(|item| item == "ambiguous_language_suffix:plot.m") + ); + assert!( + report["unknown_conditions"] + .as_array() + .unwrap() + .iter() + .any(|item| item == "ambiguous_language_suffix:synth.sc") + ); +} + struct TempProject(PathBuf); impl TempProject { diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 7ec64afb..30ea6e44 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -212,7 +212,7 @@ The source-built `check all` now adds a narrower actual project report: native a } ``` -The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix; the static language inventory and per-file unresolved diagnostics still need improvement. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. +The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix. Read-only discovery now uses the same bounded `.m` evidence and records unresolved shared-suffix paths in `unknown_conditions`; `check all` retains them in its unrouted count. Project-level dialect evidence and structured per-file ambiguity reporting still need work. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index d73c886e..e4c55eba 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -212,7 +212,7 @@ flowchart LR } ``` -实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析;静态语言库存及逐文件未解析诊断仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 +实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析。只读发现现在使用相同的有界 `.m` 证据,把共享后缀的未判定路径记入 `unknown_conditions`;`check all` 仍将其计入未路由数量。项目级方言依据及逐文件结构化歧义报告仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/project-initialization/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/project-initialization/spec.md index 48bd583f..af1531a6 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/project-initialization/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/project-initialization/spec.md @@ -8,6 +8,12 @@ init MUST 识别指定项目边界、各构建根、语言/方言、项目版本、语言目标、构建器/包管理器、框架、源集、现有质量配置及检查前置条件。声明、锁定/解析、本机安装版本 MUST 分开;每项附来源和内容身份,未观察到的事实 MUST 标 unknown,不从主语言或本机版本猜测其它模块。读取范围 MUST 遵循批准扫描与配置发现政策。 +共享源码后缀的语言归类 MUST 使用可核对的文件或项目证据;仅靠 `.m` 或 `.sc` 后缀不得宣称 Objective-C 或 Scala。证据不足的普通文件 MUST 保留为明确的未判定范围,不得从后续检查范围中静默消失;只读发现不能为消除歧义执行项目脚本或 WASM。 + +#### Scenario: Source extension belongs to multiple languages +- **WHEN** `detect` 遇到无 Objective-C 专属行首标记的 MATLAB `.m` 或 SuperCollider `.sc` +- **THEN** 语言清单不把它们记为 Objective-C/Scala,报告保留相对路径与语言未判定条件;明确的 Objective-C `.m` 和 `.scala` 仍按证据列示 + #### Scenario: Multiple build roots use different languages - **WHEN** 一个项目含 Java、TypeScript 和 Python 构建根 - **THEN** 逐根保留语言、版本及构建配置,不以主语言覆盖其它根 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 73eeeea3..d0dbd69f 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1089,3 +1089,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 Ruby 与 Swift 原生差分增量:Ruby 2.6.10 `ruby -c` 和隔离 Ruby worker 的 13 例分类一致,显式原生测试与常规 worker 语料均 1/1 通过;见[Ruby 局部验收](../../../tests/acceptance/ruby-native-differential.md)。Swift 6.4 `swiftc -frontend -parse` 对 13 例中 12 例与 WASM 一致,但缺参数类型的 `func f(_ x: ) {}` 被原生拒绝而 WASM 零恢复,确认为漏检;显式测试 1/1 通过仅表示成功捕获这一差异,不表示语法验收,见[Swift 精度阻塞](../../../tests/acceptance/swift-native-differential.md)。须修 grammar、固定重建产物并以原生对照复验;三项父任务不勾选。 14.4 / 14.6 / 14.19 共享后缀误报收敛:自动路由不再把任意 `.m` 当 Objective-C,也不再把 `.sc` 当 Scala;`.m` 需行首 Objective-C 专属标记,`.scala` 与 `.mm` 路由保留。MATLAB 字符串/注释中的标记及 SuperCollider 正例不进入错误 grammar;32 份候选的明确样例仍需完整回归。现有静态发现仍可能将共享后缀归入旧语言目录,缺少项目级方言裁决和逐文件未路由解释,父任务保持未完成;见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。 + +14.4 / 14.6 / 14.19 后续发现层补齐:默认构建的只读 `detect` 与可选 WASM 路由共用受限 Objective-C 标记判断;MATLAB `.m`、SuperCollider `.sc` 不再被列成 Objective-C/Scala,`unknown_conditions` 保留歧义相对路径,`check all` 的候选范围仍计入并报告未路由数量。真实 `detect` 与 `check all` 回归通过;项目级 `.sc` 方言裁决、无标记 Objective-C `.m`、更强的词法证据、逐文件结构化歧义协议及完整语言验收仍缺,父任务不勾选。见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。 diff --git a/tests/acceptance/ambiguous-grammar-extensions.md b/tests/acceptance/ambiguous-grammar-extensions.md index 2a02e595..4067c597 100644 --- a/tests/acceptance/ambiguous-grammar-extensions.md +++ b/tests/acceptance/ambiguous-grammar-extensions.md @@ -6,4 +6,6 @@ `grammar_route` 的共享后缀、32 份资产可路由集合及 CFQuery 边界回归 5/5 通过;真实 `check all` 混合项目测试确认 MATLAB/SuperCollider 文件未产生错误候选观察,`unrouted_count=2` 且报告仍是未完成。四个有界项目的 32 份 worker 全量回归 1/1 通过,用时 54.74 秒,候选语种并集仍与固定清单一致。 -这只减少自动候选误路由;静态发现仍可能按旧语言注册表把 `.m/.sc` 归入 Objective-C/Scala,自动报告也仅给未路由总数。项目级语言证据、未路由文件逐项诊断、`.m` 中不带标记的合法 Objective-C 范围及原生优先/发行验收仍缺,不能宣布这两个方言已完整支持。 +后续补齐默认构建的静态发现:只读 `detect` 对 `.m` 最多读取 1 MiB 普通源码,与路由共用行首标记判别;无法读取时记录阻塞,不推断语言。无标记 `.m` 与 `.sc` 不进入错误语言清单,`unknown_conditions` 保留具体相对路径。`check all` 仍将这些路径计入候选源范围,报告 `unrouted_count=2`,且总交付未完成。真实 `detect` 回归、混合项目 `check all` 回归及仅含两份歧义源码的项目回归分别通过;后者证实 `source_file_count=2`、`status=not_run`、`reason=unrouted_source` 和 `delivery_decision=incomplete`,不会把空候选结果当成 clean。 + +这只减少错误语言归类和自动候选误路由;项目级 `.sc` 方言证据、无专属标记的合法 Objective-C `.m`、更强的词法判别、逐文件结构化歧义协议及原生优先/发行验收仍缺,不能宣布这两个方言已完整支持。 From 7900a1a8ccd5b4f5b54de8b5db3ccba16a356d3b Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:01:28 +0800 Subject: [PATCH 12/31] =?UTF-8?q?=E6=89=93=E5=8C=85=2032=20=E4=BB=BD=20WAS?= =?UTF-8?q?M=20=E5=80=99=E9=80=89=E5=8F=8A=E4=B8=8A=E6=B8=B8=E8=AE=B8?= =?UTF-8?q?=E5=8F=AF=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 8 ++--- Cargo.toml | 2 +- NOTICE | 5 ++++ README.md | 2 +- README.zh-CN.md | 2 +- docs/Codeguard-Architecture.md | 2 +- docs/Codeguard-Architecture.zh_CN.md | 2 +- docs/Codeguard-Technical-Design.md | 2 +- docs/Codeguard-Technical-Design.zh_CN.md | 2 +- npm/README.md | 12 ++++++-- scripts/pack-npm-local.mjs | 38 +++++++++++++++++++++++- tests/npm_pack_wasm.test.mjs | 11 ++++++- 12 files changed, 73 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40566c5b..9dcaedb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,7 +145,7 @@ dependencies = [ [[package]] name = "codeguard-adapters" -version = "0.1.2" +version = "0.1.3" dependencies = [ "codeguard-core", "roxmltree", @@ -158,7 +158,7 @@ dependencies = [ [[package]] name = "codeguard-cli" -version = "0.1.2" +version = "0.1.3" dependencies = [ "base64", "codeguard-adapters", @@ -175,7 +175,7 @@ dependencies = [ [[package]] name = "codeguard-core" -version = "0.1.2" +version = "0.1.3" dependencies = [ "base64", "serde", @@ -183,7 +183,7 @@ dependencies = [ [[package]] name = "codeguard-runtime" -version = "0.1.2" +version = "0.1.3" dependencies = [ "codeguard-core", "flate2", diff --git a/Cargo.toml b/Cargo.toml index f46a464a..0c779734 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.1.2" +version = "0.1.3" edition = "2024" rust-version = "1.85" license = "Apache-2.0" diff --git a/NOTICE b/NOTICE index c333b987..b9f061a7 100644 --- a/NOTICE +++ b/NOTICE @@ -2,3 +2,8 @@ Codeguard Copyright 2026 Full Stack Skills / PartMe.AI Licensed under the Apache License, Version 2.0. See LICENSE. + +The optional Tree-sitter grammar candidates retain their upstream licenses. +For npm distributions, see grammar-licenses/ in the package; for source builds, +see grammars/*/LICENSE and grammars/LICENSE.codegraph. The pinned source and +license digests are recorded in grammars/manifest.json. diff --git a/README.md b/README.md index e5aeb614..325c2908 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Codeguard helps developers and coding agents discover existing quality configuration, run selected native checkers, and turn results into durable repair tasks. Rust owns orchestration and interpretation; Maven, P3C, Checkstyle, Javadoc, Ruff, Cargo, ESLint, and other native tools remain responsible for their checks. -> **Status:** early development, source `0.1.2`. Several native checks and local repair workflows work within explicitly bounded scopes. Full delivery gates, complete language coverage, automatic task closure, and host-plugin integration remain incomplete. +> **Status:** early development, source `0.1.3`. Several native checks and local repair workflows work within explicitly bounded scopes. Full delivery gates, complete language coverage, automatic task closure, and host-plugin integration remain incomplete. > > **Baseline:** The current source and [implementation evidence](openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md) define callable behavior. Declared Rust minimum `1.85`, edition `2024`, Cargo resolver `2`. `@partme.ai/codeguard@0.1.2` is published for Apple Silicon macOS; no multi-platform binary release or crates.io availability is claimed. diff --git a/README.zh-CN.md b/README.zh-CN.md index 8b920fee..20845d0e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,7 +6,7 @@ Codeguard 面向开发者与编程智能体,识别项目已有质量配置,调用选定的原生检查器,并将结果转成持久修复任务。Rust 负责调度和结果解释;Maven、P3C、Checkstyle、Javadoc、Ruff、Cargo、ESLint 等原生工具继续负责具体检查。 -> **当前状态:**早期开发阶段,源码版本 `0.1.2`。部分原生检查和本地修复流程已在明确范围内实现。完整交付门禁、全部语言覆盖、任务自动关闭和宿主插件接入仍未完成。 +> **当前状态:**早期开发阶段,源码版本 `0.1.3`。部分原生检查和本地修复流程已在明确范围内实现。完整交付门禁、全部语言覆盖、任务自动关闭和宿主插件接入仍未完成。 > > **基线:**可调用行为以当前源码和[实施证据](openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md)为准。声明 Rust 最低版本 `1.85`,edition `2024`,Cargo resolver `2`。`@partme.ai/codeguard@0.1.2` 已发布,当前仅支持 Apple Silicon macOS;不声称已有多平台二进制发行版或 crates.io 发布。 diff --git a/docs/Codeguard-Architecture.md b/docs/Codeguard-Architecture.md index 8b311572..816930f5 100644 --- a/docs/Codeguard-Architecture.md +++ b/docs/Codeguard-Architecture.md @@ -2,7 +2,7 @@ > **Purpose:** explain system ownership, component contracts, repair flow, and the gap between current implementation and target behavior. > -> **Document version:** 1.2.2 · **Updated:** 2026-09-29 · **Source baseline:** current checkout and the [implementation evidence](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md); software version `0.1.2`. +> **Document version:** 1.2.3 · **Updated:** 2026-10-03 · **Source baseline:** current checkout and the [implementation evidence](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md); software version `0.1.3`. [简体中文](Codeguard-Architecture.zh_CN.md) · [README](../README.md) · [Technical design](Codeguard-Technical-Design.md) diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index bd2db307..25e957ec 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -2,7 +2,7 @@ > **文档说明:**说明系统职责、组件契约、修复流程,以及当前实现与目标行为之间的差距。 > -> **文档版本:**1.2.2 · **最后更新:**2026-09-29 · **源码基线:**当前 checkout 与[实施证据](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md);软件版本 `0.1.2`。 +> **文档版本:**1.2.3 · **最后更新:**2026-10-03 · **源码基线:**当前 checkout 与[实施证据](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md);软件版本 `0.1.3`。 [English](Codeguard-Architecture.md) · [README](../README.zh-CN.md) · [技术方案](Codeguard-Technical-Design.zh_CN.md) diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 30ea6e44..edd2c484 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -2,7 +2,7 @@ > **Purpose:** turn the architecture into concrete implementation contracts, command responsibilities, extension steps, and observable acceptance criteria. > -> **Document version:** 1.2.2 · **Updated:** 2026-09-29 · **Source baseline:** current checkout and the [implementation evidence](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md); software version `0.1.2`. +> **Document version:** 1.2.3 · **Updated:** 2026-10-03 · **Source baseline:** current checkout and the [implementation evidence](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md); software version `0.1.3`. [简体中文](Codeguard-Technical-Design.zh_CN.md) · [Architecture](Codeguard-Architecture.md) · [README](../README.md) diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index e4c55eba..d03924c0 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -2,7 +2,7 @@ > **文档说明:**将架构落实为实现契约、命令职责、扩展步骤和可观察的验收标准。 > -> **文档版本:**1.2.2 · **最后更新:**2026-09-29 · **源码基线:**当前检出版本与[实施证据](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md);软件版本 `0.1.2`。 +> **文档版本:**1.2.3 · **最后更新:**2026-10-03 · **源码基线:**当前检出版本与[实施证据](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md);软件版本 `0.1.3`。 [English](Codeguard-Technical-Design.md) · [架构设计](Codeguard-Architecture.zh_CN.md) · [README](../README.zh-CN.md) diff --git a/npm/README.md b/npm/README.md index 6c285564..3d9a0357 100644 --- a/npm/README.md +++ b/npm/README.md @@ -10,7 +10,15 @@ npx @partme.ai/codeguard init . --apply This initial npm package contains an **Apple Silicon macOS** executable only (`darwin-arm64`). npm rejects installation on other platforms. The CLI is an early implementation: individual checkers and end-to-end quality gates are incomplete. A successful command or an empty task list does not certify a project as clean. -The published `0.1.2` package does not include WASM syntax prechecks. Future packages that advertise the 32 pinned grammar candidates must pass a WASM worker and asset probe before packing. Candidate observations remain incomplete and do not replace native lint or certify a clean project. +Version `0.1.3` bundles 32 pinned Tree-sitter WASM grammar candidates. The Rust CLI can run them through `grammar probe` and bounded `check all` after native checks. Every candidate remains unqualified: observations return `incomplete` and do not replace native lint or certify a clean project. The package includes upstream grammar notices in `grammar-licenses/`. + +```bash +npx @partme.ai/codeguard@0.1.3 grammar status --format=json +npx @partme.ai/codeguard@0.1.3 grammar probe zig ./sample.zig --format=json +npx @partme.ai/codeguard@0.1.3 check all . --format=json +``` + +Candidate commands return exit code `3` because the result is incomplete. `grammar status` reports `candidate_count: 32` and `released_count: 0` until language qualification is complete. The project workspace created by `init` is `.codeguard/`; existing user source under `codeguard/` stays in scope. See the [repository README](https://github.com/full-stack-plugins/codeguard#readme) for supported commands, current coverage, and limitations. @@ -18,4 +26,4 @@ The project workspace created by `init` is `.codeguard/`; existing user source u 此 npm 包只提供精简 Node 入口和 Rust 原生程序,不在安装时构建、下载或运行检测工具。首个 npm 版本仅支持 Apple Silicon macOS。`init` 创建的项目数据目录是 `.codeguard/`;部分检测器和完整质量门禁仍在开发中。具体能力和限制见[项目说明](https://github.com/full-stack-plugins/codeguard/blob/main/README.zh-CN.md)。 -已发布的 `0.1.2` 不含 WASM 语法初检。未来声称包含 32 份固定 grammar 候选的包,须在打包前验证 worker 与资产;候选观察始终不代替原生 lint,也不证明项目质量通过。 +`0.1.3` 包含 32 份固定 Tree-sitter WASM grammar 候选,可通过 `grammar probe` 和原生检查后的有界 `check all` 调用。候选观察仍返回 `incomplete`,不能代替原生 lint 或证明项目质量通过;上游许可证随包存放于 `grammar-licenses/`。 diff --git a/scripts/pack-npm-local.mjs b/scripts/pack-npm-local.mjs index 1749be57..a01af402 100644 --- a/scripts/pack-npm-local.mjs +++ b/scripts/pack-npm-local.mjs @@ -36,6 +36,7 @@ for (const argument of process.argv.slice(2)) { } const publishable = options.has('--public'); const requireWasm = publishable || options.has('--require-wasm'); +const grammarLicenses = new Map(); if (publishable && host !== 'darwin-arm64') { throw new Error(`The first @partme.ai/codeguard release is certified only for darwin-arm64, not ${host}`); } @@ -77,6 +78,25 @@ if (requireWasm) { if (expected.size !== 32 || manifest.assets.length !== 32) { throw new Error('WASM capability probe failed: local manifest must contain 32 distinct assets'); } + const licenseEntries = [ + [manifest.codegraph_license, manifest.codegraph_license_sha256], + ...manifest.assets.map(asset => [asset.license, asset.license_sha256]), + ]; + for (const [relativePath, expectedSha256] of licenseEntries) { + if (typeof relativePath !== 'string' || + !/^(?:LICENSE\.codegraph|[a-z0-9_-]+\/LICENSE)$/.test(relativePath) || + typeof expectedSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(expectedSha256)) { + throw new Error('WASM license gate failed: invalid manifest license identity'); + } + if (grammarLicenses.has(relativePath) && grammarLicenses.get(relativePath) !== expectedSha256) { + throw new Error(`WASM license gate failed: conflicting hash for ${relativePath}`); + } + const source = path.join(root, 'grammars', relativePath); + if (!existsSync(source) || createHash('sha256').update(readFileSync(source)).digest('hex') !== expectedSha256) { + throw new Error(`WASM license gate failed: missing or changed ${relativePath}`); + } + grammarLicenses.set(relativePath, expectedSha256); + } const inventory = spawnSync(binary, ['grammar', 'status', '--format=json'], { cwd: root, encoding: 'utf8', timeout: 30_000, maxBuffer: 1024 * 1024, shell: false, }); @@ -122,6 +142,13 @@ try { copyFileSync(path.join(root, 'LICENSE'), path.join(stage, 'LICENSE')); copyFileSync(path.join(root, 'NOTICE'), path.join(stage, 'NOTICE')); } + if (requireWasm) { + for (const relativePath of grammarLicenses.keys()) { + const destination = path.join(stage, 'grammar-licenses', relativePath); + mkdirSync(path.dirname(destination), { recursive: true }); + copyFileSync(path.join(root, 'grammars', relativePath), destination); + } + } const nativeName = process.platform === 'win32' ? 'codeguard.exe' : 'codeguard'; const packagedBinary = path.join(stage, 'native', nativeName); copyFileSync(binary, packagedBinary); @@ -137,7 +164,11 @@ try { cpu: [process.arch], engines: { node: '>=18' }, bin: { codeguard: './codeguard.cjs' }, - files: publishable ? ['codeguard.cjs', `native/${nativeName}`, 'README.md', 'LICENSE', 'NOTICE'] : ['codeguard.cjs', `native/${nativeName}`], + files: [ + 'codeguard.cjs', `native/${nativeName}`, + ...(requireWasm ? ['grammar-licenses/**'] : []), + ...(publishable ? ['README.md', 'LICENSE', 'NOTICE'] : []), + ], }, null, 2) + '\n'); chmodSync(path.join(stage, 'codeguard.cjs'), 0o755); mkdirSync(outDir, { recursive: true }); @@ -155,6 +186,11 @@ try { if (!files.some(file => file.path === `native/${nativeName}`)) { throw new Error('npm tarball omitted the Rust executable'); } + for (const relativePath of grammarLicenses.keys()) { + if (!files.some(file => file.path === `grammar-licenses/${relativePath}`)) { + throw new Error(`npm tarball omitted grammar license ${relativePath}`); + } + } const finalName = `codeguard-${publishable ? 'public-' : ''}${identity.cli_version}-${host}.tgz`; const finalPath = path.join(outDir, finalName); if (existsSync(finalPath)) rmSync(finalPath); diff --git a/tests/npm_pack_wasm.test.mjs b/tests/npm_pack_wasm.test.mjs index 447343d8..9cb80250 100644 --- a/tests/npm_pack_wasm.test.mjs +++ b/tests/npm_pack_wasm.test.mjs @@ -1,5 +1,6 @@ import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; @@ -30,6 +31,15 @@ test('本地离线 npm 包实际运行固定 Zig 与 Dart WASM', () => { assert.ok(tarball.endsWith('.tgz')); const scratch = realpathSync(mkdtempSync(path.join(os.tmpdir(), 'codeguard-npm-wasm-'))); try { + const extracted = spawnSync('tar', ['-xzf', tarball, '-C', scratch], { encoding: 'utf8', timeout: 30_000 }); + assert.equal(extracted.status, 0, extracted.stderr); + const manifest = JSON.parse(readFileSync(path.join(root, 'grammars', 'manifest.json'))); + const codegraphLicense = readFileSync(path.join(scratch, 'package', 'grammar-licenses', manifest.codegraph_license)); + assert.equal(createHash('sha256').update(codegraphLicense).digest('hex'), manifest.codegraph_license_sha256); + for (const asset of manifest.assets) { + const license = readFileSync(path.join(scratch, 'package', 'grammar-licenses', asset.license)); + assert.equal(createHash('sha256').update(license).digest('hex'), asset.license_sha256, asset.language); + } const inventory = spawnSync('npm', [ 'exec', '--offline', '--yes', '--cache', path.join(scratch, 'cache'), '--package', tarball, '--', 'codeguard', 'grammar', 'status', '--format=json', @@ -56,7 +66,6 @@ test('本地离线 npm 包实际运行固定 Zig 与 Dart WASM', () => { assert.equal(report.grammar_qualified, false); assert.equal(report.native.status, 'not_run'); assert.equal(report.delivery_decision, 'not_evaluated'); - const manifest = JSON.parse(readFileSync(path.join(root, 'grammars', 'manifest.json'))); assert.equal(report.grammar_sha256, manifest.assets.find(asset => asset.language === language).sha256); } } finally { From 904df54ee3b1cd5868482f737926e7a75c737660 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:23:27 +0800 Subject: [PATCH 13/31] =?UTF-8?q?=E8=AE=B0=E5=BD=95=2032=20=E4=BB=BD=20WAS?= =?UTF-8?q?M=20npm=20=E5=80=99=E9=80=89=E5=8F=91=E8=A1=8C=E9=AA=8C?= =?UTF-8?q?=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 ++++++++++-------- README.zh-CN.md | 22 ++++++++++-------- docs/Codeguard-Architecture.md | 17 +++++++------- docs/Codeguard-Architecture.zh_CN.md | 15 ++++++------ docs/Codeguard-Technical-Design.md | 8 +++---- docs/Codeguard-Technical-Design.zh_CN.md | 6 ++--- .../introduce-rust-codeguard-cli/tasks.md | 2 ++ tests/acceptance/npm-0.1.3-wasm-candidate.md | 23 +++++++++++++++++++ 8 files changed, 73 insertions(+), 42 deletions(-) create mode 100644 tests/acceptance/npm-0.1.3-wasm-candidate.md diff --git a/README.md b/README.md index 325c2908..34dc25c8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Codeguard helps developers and coding agents discover existing quality configura > **Status:** early development, source `0.1.3`. Several native checks and local repair workflows work within explicitly bounded scopes. Full delivery gates, complete language coverage, automatic task closure, and host-plugin integration remain incomplete. > -> **Baseline:** The current source and [implementation evidence](openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md) define callable behavior. Declared Rust minimum `1.85`, edition `2024`, Cargo resolver `2`. `@partme.ai/codeguard@0.1.2` is published for Apple Silicon macOS; no multi-platform binary release or crates.io availability is claimed. +> **Baseline:** The current source and [implementation evidence](openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md) define callable behavior. Declared Rust minimum `1.85`, edition `2024`, Cargo resolver `2`. `@partme.ai/codeguard@0.1.3` is published for Apple Silicon macOS; no multi-platform binary release or crates.io availability is claimed. ```text Project files + existing checker configuration @@ -94,22 +94,24 @@ cargo build --locked -p codeguard-cli Version output includes `cli_version`, `target`, and protocol information. Discovery and init preview return observations and unknowns. Dry-run creates neither `AGENTS.md` nor a project data directory. Add Cargo `--offline` only when dependencies are cached. -The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with thirty-two pinned grammar candidates: ArkTS, C, C++, C#, Go, Java, JavaScript, Lua, Luau, Objective-C, Python, Rust, Solidity, TypeScript, TSX, Zig, Nix, Terraform, R, Ruby, PHP, Kotlin, Dart, Erlang, Pascal, CFML, CFQuery, CFScript, COBOL, Scala, Swift and VB.NET. Java and TypeScript/TSX have narrow single-file fallback paths; `lint python` can provide bounded suspected syntax positions when Ruff is unavailable or no project Ruff configuration is declared. Native Ruff results take priority. 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 workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bounded Rust worker with thirty-two pinned grammar candidates: ArkTS, C, C++, C#, Go, Java, JavaScript, Lua, Luau, Objective-C, Python, Rust, Solidity, TypeScript, TSX, Zig, Nix, Terraform, R, Ruby, PHP, Kotlin, Dart, Erlang, Pascal, CFML, CFQuery, CFScript, COBOL, Scala, Swift and VB.NET. Java and TypeScript/TSX have narrow single-file fallback paths; `lint python` can provide bounded suspected syntax positions when Ruff is unavailable or no project Ruff configuration is declared. Native Ruff results take priority. The published npm `0.1.3` package includes the candidate feature; `0.1.2` did not. 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 thirty-two unqualified asset candidates (ArkTS, C, C++, C#, Go, Java, JavaScript, Lua, Luau, Objective-C, Python, Rust, Solidity, TypeScript, TSX, Zig, Nix, Terraform, R, Ruby, PHP, Kotlin, Dart, Erlang, Pascal, CFML, CFQuery, CFScript, COBOL, Scala, Swift, VB.NET) and zero released syntax capabilities. C, C++, C#, Go, JavaScript, Lua, Luau and Rust are byte-pinned CodeGraph assets that load in the Rust worker but have no language-qualified standalone lint route or release qualification. ArkTS, Nix and Terraform are also pinned Rust-loadable candidates with narrow valid/invalid fixtures and no language-qualified standalone lint route or release qualification; see the [limited acceptance record](tests/acceptance/arkts-nix-terraform-grammar-candidates.md). The two dependency grammars retain their source bytes and use a byte-pinned legacy `dylink` metadata conversion for Rust loading; they have no language-qualified standalone lint route or release qualification. Zig uses a byte-pinned import adaptation so the corrected CodeGraph grammar can load in Rust; it has a narrow `lint zig` native-first candidate route but no release qualification. [Zig's limited acceptance record](tests/acceptance/zig-grammar-candidate.md) distinguishes this from released syntax support. Python has a source-build-only, unqualified CLI fallback path; its report remains incomplete and cannot approve delivery. R, Ruby, PHP and Kotlin have pinned CodeGraph bytes, licenses, measured Rust ABI and narrow worker samples; PHP covers mixed HTML/PHP, and all four remain unqualified. The original CodeGraph Dart WASM cannot be instantiated by Rust, but CodeGuard now pins a reproducible Zig rebuild with the real scanner and a byte-pinned WASI import adaptation. Its narrow Rust and worker fixtures pass, while public lint and release remain unqualified; see the [Dart rebuild record](tests/acceptance/dart-grammar-rebuild-candidate.md). Erlang now has pinned CodeGraph bytes, the upstream 0.19 license, ABI 14 and narrow Rust worker tests; it remains unqualified without native comparison or public lint. See the [Erlang candidate record](tests/acceptance/erlang-grammar-candidate.md). Pascal now has pinned CodeGraph bytes and its original Isopod dependency commit and license; ABI 14 and narrow worker tests pass, but native comparison and public lint remain open. See the [Pascal candidate record](tests/acceptance/pascal-grammar-candidate.md). The remaining CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET assets also load in the Rust worker, bringing the candidate inventory to 32/32 with zero released syntax capabilities. CFQuery misses `SELECT FROM`, VB.NET misparses a valid unindented method, and COBOL has a high load cost; see the [seven-asset acceptance record](tests/acceptance/final-seven-grammar-candidates.md). The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB asset now fits the bounded 20 MiB loader input, but its cold-load and memory budgets remain unqualified. +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 thirty-two unqualified asset candidates (ArkTS, C, C++, C#, Go, Java, JavaScript, Lua, Luau, Objective-C, Python, Rust, Solidity, TypeScript, TSX, Zig, Nix, Terraform, R, Ruby, PHP, Kotlin, Dart, Erlang, Pascal, CFML, CFQuery, CFScript, COBOL, Scala, Swift, VB.NET) and zero released syntax capabilities. C, C++, C#, Go, JavaScript, Lua, Luau and Rust are byte-pinned CodeGraph assets that load in the Rust worker but have no language-qualified standalone lint route or release qualification. ArkTS, Nix and Terraform are also pinned Rust-loadable candidates with narrow valid/invalid fixtures and no language-qualified standalone lint route or release qualification; see the [limited acceptance record](tests/acceptance/arkts-nix-terraform-grammar-candidates.md). The two dependency grammars retain their source bytes and use a byte-pinned legacy `dylink` metadata conversion for Rust loading; they have no language-qualified standalone lint route or release qualification. Zig uses a byte-pinned import adaptation so the corrected CodeGraph grammar can load in Rust; it has a narrow `lint zig` native-first candidate route but no release qualification. [Zig's limited acceptance record](tests/acceptance/zig-grammar-candidate.md) distinguishes this from released syntax support. Python has an unqualified CLI fallback path; its report remains incomplete and cannot approve delivery. R, Ruby, PHP and Kotlin have pinned CodeGraph bytes, licenses, measured Rust ABI and narrow worker samples; PHP covers mixed HTML/PHP, and all four remain unqualified. The original CodeGraph Dart WASM cannot be instantiated by Rust, but CodeGuard now pins a reproducible Zig rebuild with the real scanner and a byte-pinned WASI import adaptation. Its narrow Rust and worker fixtures pass, while public lint and release remain unqualified; see the [Dart rebuild record](tests/acceptance/dart-grammar-rebuild-candidate.md). Erlang now has pinned CodeGraph bytes, the upstream 0.19 license, ABI 14 and narrow Rust worker tests; it remains unqualified without native comparison or public lint. See the [Erlang candidate record](tests/acceptance/erlang-grammar-candidate.md). Pascal now has pinned CodeGraph bytes and its original Isopod dependency commit and license; ABI 14 and narrow worker tests pass, but native comparison and public lint remain open. See the [Pascal candidate record](tests/acceptance/pascal-grammar-candidate.md). The remaining CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET assets also load in the Rust worker, bringing the candidate inventory to 32/32 with zero released syntax capabilities. CFQuery misses `SELECT FROM`, VB.NET misparses a valid unindented method, and COBOL has a high load cost; see the [seven-asset acceptance record](tests/acceptance/final-seven-grammar-candidates.md). The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB asset now fits the bounded 20 MiB loader input, but its cold-load and memory budgets remain unqualified. ### npm installation and one-off use -In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` can explicitly run any of the 32 pinned candidates in an isolated worker. It always exits 3 with `status=incomplete`, `native.status=not_run`, and `delivery_decision=not_evaluated`; a completed parse also has `precheck.status=incomplete`. Input or worker failures use the same [closed JSON schema](schemas/grammar-probe-v0.1.schema.json). Recovery anchors are suspected observations only. This diagnostic command does not yet connect all grammars to native-first `lint/check` or to the published npm package. +In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` can explicitly run any of the 32 pinned candidates in an isolated worker. It always exits 3 with `status=incomplete`, `native.status=not_run`, and `delivery_decision=not_evaluated`; a completed parse also has `precheck.status=incomplete`. Input or worker failures use the same [closed JSON schema](schemas/grammar-probe-v0.1.schema.json). Recovery anchors are suspected observations only. This diagnostic command does not yet connect all grammars to language-qualified native-first `lint/check`; the published npm package includes it as an unqualified candidate. -The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback or part of published npm `0.1.2`. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). A [Ruby 2.6.10 differential](tests/acceptance/ruby-native-differential.md) agrees on 13 narrow syntax cases; a [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. +The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback; published npm `0.1.3` includes this candidate route. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). A [Ruby 2.6.10 differential](tests/acceptance/ruby-native-differential.md) agrees on 13 narrow syntax cases; a [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. The source-built Unix CLI also has a narrow Zig route: `codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`. An explicitly supplied tool reporting Zig 0.16.0 and retaining the same byte digest runs native `ast-check` first; its source positions are retained without exposing source snippets. Without an explicit tool, the pinned Zig WASM provides an unqualified observation. Both paths remain incomplete because `ast-check` covers only local AST errors, not full lint, build or tests. See the [Zig feedback schema](schemas/zig-lint-feedback-v0.1.schema.json). -On Apple Silicon macOS, the published `0.1.2` package was verified with a fresh npm cache: +On Apple Silicon macOS, the published `0.1.3` candidate package was verified with a fresh npm cache: ```bash -npx --yes @partme.ai/codeguard@0.1.2 --version --format json +npx --yes @partme.ai/codeguard@0.1.3 --version --format json +npx --yes @partme.ai/codeguard@0.1.3 grammar status --format=json +npx --yes @partme.ai/codeguard@0.1.3 check all . --format=json ``` For a project check, replace the arguments after the package name with the desired Codeguard command. The package is currently restricted to macOS arm64. @@ -129,9 +131,9 @@ For a one-off Node invocation without a global installation: npm exec --yes --package "$CODEGUARD_TARBALL" -- codeguard detect . --format json ``` -The packer currently supports macOS and Linux on x64/arm64 when a matching native binary is available. It requires Node 18+, npm, and an already built Rust binary for the current host. It checks the binary's reported target and version before packaging, and writes a platform-tagged tarball under ignored `release/npm/`. The default package is private and local-only. `node scripts/pack-npm-local.mjs --public` prepares the public `@partme.ai/codeguard` package with a README, full license, and host restrictions. Version `0.1.2` is published; its registry-hosted `npx` entry and artifact hashes were verified on Apple Silicon macOS. The binary reports the candidate source commit, which is not a signed or reproducible-build attestation. Other platforms need their own verified binary packages. See the [distribution design](docs/Codeguard-Technical-Design.md) and [0.1.2 acceptance record](tests/acceptance/npm-0.1.2-candidate.md). +The packer currently supports macOS and Linux on x64/arm64 when a matching native binary is available. It requires Node 18+, npm, and an already built Rust binary for the current host. It checks the binary's reported target and version before packaging, and writes a platform-tagged tarball under ignored `release/npm/`. The default package is private and local-only. `node scripts/pack-npm-local.mjs --public` prepares the public `@partme.ai/codeguard` package with a README, full license, and host restrictions. Version `0.1.3` is published with all 32 WASM candidates; its registry-hosted `npx` entry and artifact hashes were verified on Apple Silicon macOS. The binary reports the candidate source commit, which is not a signed or reproducible-build attestation. Other platforms need their own verified binary packages. See the [distribution design](docs/Codeguard-Technical-Design.md) and [0.1.3 acceptance record](tests/acceptance/npm-0.1.3-wasm-candidate.md). -To prepare a **local** package with candidate WASM support, build with `cargo build --locked -p codeguard-cli --features wasm-precheck` and run `node scripts/pack-npm-local.mjs --require-wasm target/debug/codeguard`. The packer rejects a binary without the worker, checks all 32 pinned asset identities, and executes a bounded Zig candidate probe before writing the tarball. `--public` now requires the same check; this does not change the already published `0.1.2`, which contains no WASM. Local offline npm execution of Zig and Dart remains candidate-only and does not qualify a language or certify a release. +To prepare a **local** package with candidate WASM support, build with `cargo build --locked -p codeguard-cli --features wasm-precheck` and run `node scripts/pack-npm-local.mjs --require-wasm target/debug/codeguard`. The packer rejects a binary without the worker, checks all 32 pinned asset identities, and executes a bounded Zig candidate probe before writing the tarball. `--public` requires the same check and verified upstream licenses. The published `0.1.3` package carries all 32 candidates; local and registry installation do not qualify a language or certify a clean project. ## 5. Repair workflow @@ -187,7 +189,7 @@ Generic `dependencies`, generic `security`, arbitrary category/language combinat ### Native-first entry points and syntax fallback — design target -**The published `0.1.2` does not yet implement bundled WASM fallback or automatic conversation delivery.** The existing commands remain the unified entry points; native adapter-specific prerequisites still apply today: +**The published `0.1.3` includes bounded WASM candidate routing but not qualified fallback or automatic conversation delivery.** The existing commands remain the unified entry points; native adapter-specific prerequisites still apply today: An opt-in source build with `--features codeguard-cli/wasm-precheck` has narrow TypeScript and Java single-file candidate paths. For TypeScript, a visible local ESLint 10 entry and one flat config take priority: when an executable Node is resolved from `PATH`, the CLI runs its existing bounded native probe; without Node it reports the setup gap. Only when the project-local ESLint package is not observed can the bundled TypeScript/TSX candidate provide suspected syntax positions; an untrusted entry, package identity or config returns a specific setup blocker. The Java candidate likewise remains incomplete. Python `lint` first preserves Ruff results, then can attach suspected positions for missing Ruff or undeclared project Ruff configuration; a broken Ruff configuration stays a setup blocker. For one checked file in an initialized workspace, the source-only path syncs one stable native-confirmation task; repeat candidate scans do not close it. No bundled grammar has an accepted language/dialect range. Native observations and prechecks never approve delivery. Default and published binaries keep their previous native-context behavior. [TypeScript acceptance](tests/acceptance/typescript-syntax-fallback-candidate.md) · [native-first acceptance](tests/acceptance/native-first-eslint-candidate.md) · [Java acceptance](tests/acceptance/java-syntax-fallback-candidate.md). diff --git a/README.zh-CN.md b/README.zh-CN.md index 20845d0e..56942ba3 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -8,7 +8,7 @@ Codeguard 面向开发者与编程智能体,识别项目已有质量配置, > **当前状态:**早期开发阶段,源码版本 `0.1.3`。部分原生检查和本地修复流程已在明确范围内实现。完整交付门禁、全部语言覆盖、任务自动关闭和宿主插件接入仍未完成。 > -> **基线:**可调用行为以当前源码和[实施证据](openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md)为准。声明 Rust 最低版本 `1.85`,edition `2024`,Cargo resolver `2`。`@partme.ai/codeguard@0.1.2` 已发布,当前仅支持 Apple Silicon macOS;不声称已有多平台二进制发行版或 crates.io 发布。 +> **基线:**可调用行为以当前源码和[实施证据](openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md)为准。声明 Rust 最低版本 `1.85`,edition `2024`,Cargo resolver `2`。`@partme.ai/codeguard@0.1.3` 已发布,当前仅支持 Apple Silicon macOS;不声称已有多平台二进制发行版或 crates.io 发布。 ```text 项目文件 + 已有检查器配置 @@ -94,22 +94,24 @@ cargo build --locked -p codeguard-cli 版本输出包含 `cli_version`、`target` 和协议信息。发现与 init 预览返回观察及未知项。Dry-run 不创建 `AGENTS.md` 或项目数据目录。仅在依赖已缓存时为 Cargo 添加 `--offline`。 -工作区要求 `std`。可选 `wasm-precheck` 构建特性已有三十二份固定 grammar 候选资产与有界 Rust worker。Java 和 TypeScript/TSX 有局部单文件兜底;`lint python` 在 Ruff 不可用或项目未声明 Ruff 配置时可报告有界疑似语法位置,原生 Ruff 结果优先。公开 npm `0.1.2` 不含该路径。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。 +工作区要求 `std`。可选 `wasm-precheck` 构建特性已有三十二份固定 grammar 候选资产与有界 Rust worker。Java 和 TypeScript/TSX 有局部单文件兜底;`lint python` 在 Ruff 不可用或项目未声明 Ruff 配置时可报告有界疑似语法位置,原生 Ruff 结果优先。公开 npm `0.1.3` 包含这一候选路径;`0.1.2` 不含。不声称 `no_std`、已完成项目级 WASM 兜底、零 unsafe 或性能最快。运行层系统调用需要安全审查,当前不宣称已完成整体安全审计。 -当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有三十二份未验收资产候选、零项已发行语法能力。C、C++、C#、Go、JavaScript、Lua、Luau、Rust、ArkTS、Nix、Terraform、R、Ruby、PHP、Kotlin、Erlang、Pascal、CFML、CFQuery、CFScript、COBOL、Scala、Swift、VB.NET 等已固定字节并由 Rust worker 做窄范围验证,但没有逐语言验收的独立 lint 路由或发行验收。Objective-C 与 Solidity 的依赖包原始字节、许可证和固定散列的 `dylink` 元数据转换已入库;Zig 的新版语法也有固定导入适配。CodeGraph 原 Dart WASM 无法由 Rust 直接加载;CodeGuard 现以固定源码和真实外部 scanner 经 Zig 重建,并固定适配前后字节,150 例上游语料错误分类与预期一致,但仍未做 Dart 原生工具对照或发布验收,见[Dart 重建局部验收](tests/acceptance/dart-grammar-rebuild-candidate.md)。Erlang 已固定 CodeGraph 字节、上游 0.19 许可证和 ABI 14,Rust/隔离 worker 窄范围样例通过,但原生对照与公开 lint 尚缺,见[Erlang 候选局部验收](tests/acceptance/erlang-grammar-candidate.md)。Pascal 已固定 CodeGraph 字节、原始 Isopod 依赖提交和许可证,ABI 14 及窄范围 worker 样例通过;原生对照和公开 lint 尚缺,见[Pascal 候选局部验收](tests/acceptance/pascal-grammar-candidate.md)。其余七份 CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET 来源 WASM 也已固定并由 Rust worker 加载,累计 32/32 份候选、0 项已发行。CFQuery 漏掉 `SELECT FROM`,VB.NET 对合法未缩进方法体误报,COBOL 成本高;都不能发布为 lint,见[七份局部验收](tests/acceptance/final-seven-grammar-candidates.md)。Python 已有仅限源码可选构建的 CLI 候选兜底,结果保持不完整,不能批准交付。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 的 16.4 MB 固定资产现可在 20 MiB 输入限额下加载,但冷启动和内存预算尚未验收。 +当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有三十二份未验收资产候选、零项已发行语法能力。C、C++、C#、Go、JavaScript、Lua、Luau、Rust、ArkTS、Nix、Terraform、R、Ruby、PHP、Kotlin、Erlang、Pascal、CFML、CFQuery、CFScript、COBOL、Scala、Swift、VB.NET 等已固定字节并由 Rust worker 做窄范围验证,但没有逐语言验收的独立 lint 路由或发行验收。Objective-C 与 Solidity 的依赖包原始字节、许可证和固定散列的 `dylink` 元数据转换已入库;Zig 的新版语法也有固定导入适配。CodeGraph 原 Dart WASM 无法由 Rust 直接加载;CodeGuard 现以固定源码和真实外部 scanner 经 Zig 重建,并固定适配前后字节,150 例上游语料错误分类与预期一致,但仍未做 Dart 原生工具对照或发布验收,见[Dart 重建局部验收](tests/acceptance/dart-grammar-rebuild-candidate.md)。Erlang 已固定 CodeGraph 字节、上游 0.19 许可证和 ABI 14,Rust/隔离 worker 窄范围样例通过,但原生对照与公开 lint 尚缺,见[Erlang 候选局部验收](tests/acceptance/erlang-grammar-candidate.md)。Pascal 已固定 CodeGraph 字节、原始 Isopod 依赖提交和许可证,ABI 14 及窄范围 worker 样例通过;原生对照和公开 lint 尚缺,见[Pascal 候选局部验收](tests/acceptance/pascal-grammar-candidate.md)。其余七份 CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET 来源 WASM 也已固定并由 Rust worker 加载,累计 32/32 份候选、0 项已发行。CFQuery 漏掉 `SELECT FROM`,VB.NET 对合法未缩进方法体误报,COBOL 成本高;都不能发布为 lint,见[七份局部验收](tests/acceptance/final-seven-grammar-candidates.md)。Python 已有未验收的 CLI 候选兜底,结果保持不完整,不能批准交付。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 的 16.4 MB 固定资产现可在 20 MiB 输入限额下加载,但冷启动和内存预算尚未验收。 ### npm 安装与一次性调用 -源码以 `--features wasm-precheck` 构建后,可显式运行 `codeguard grammar probe --format=json`,由隔离 worker 调用 32 份固定候选中的任意一份。命令始终以退出码 3 返回,并标记 `status=incomplete`、`native.status=not_run`、`delivery_decision=not_evaluated`;解析完成时还标记 `precheck.status=incomplete`。输入或 worker 失败也遵守同一份[封闭 JSON Schema](schemas/grammar-probe-v0.1.schema.json)。恢复锚点只是疑似观察。此诊断入口尚未把全部语种接入原生优先的 `lint/check`,也尚未进入已发布 npm 包。 +源码以 `--features wasm-precheck` 构建后,可显式运行 `codeguard grammar probe --format=json`,由隔离 worker 调用 32 份固定候选中的任意一份。命令始终以退出码 3 返回,并标记 `status=incomplete`、`native.status=not_run`、`delivery_decision=not_evaluated`;解析完成时还标记 `precheck.status=incomplete`。输入或 worker 失败也遵守同一份[封闭 JSON Schema](schemas/grammar-probe-v0.1.schema.json)。恢复锚点只是疑似观察。此诊断入口尚未把全部语种接入已验收的原生优先 `lint/check`;已发布 npm 包仅把它作为未验收候选提供。 -同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底,也未进入已发布 npm `0.1.2`;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)在 13 个窄范围语法样例上一致;[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 +同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底;已发布 npm `0.1.3` 包含该候选路径;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)在 13 个窄范围语法样例上一致;[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 源码构建的 Unix CLI 还提供局部 Zig 入口:`codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`。显式提供且报告 Zig 0.16.0、执行前后字节摘要一致的工具优先运行原生 `ast-check`,仅输出诊断位置,不回显源码;未显式提供工具时,固定 Zig WASM 给出未验收候选观察。两条路径都保持未完成,因为 `ast-check` 仅覆盖局部 AST 错误,不等于完整 lint、构建或测试。见 [Zig 报告 Schema](schemas/zig-lint-feedback-v0.1.schema.json)。 -已在 Apple Silicon macOS 上通过全新 npm 缓存验证公开 `0.1.2` 包: +已在 Apple Silicon macOS 上通过全新 npm 缓存验证公开 `0.1.3` 候选包: ```bash -npx --yes @partme.ai/codeguard@0.1.2 --version --format json +npx --yes @partme.ai/codeguard@0.1.3 --version --format json +npx --yes @partme.ai/codeguard@0.1.3 grammar status --format=json +npx --yes @partme.ai/codeguard@0.1.3 check all . --format=json ``` 检查项目时,可将包名后的参数换成需要执行的 Codeguard 指令。当前公开包仅支持 macOS arm64。 @@ -129,9 +131,9 @@ codeguard --version --format json npm exec --yes --package "$CODEGUARD_TARBALL" -- codeguard detect . --format json ``` -打包器在已有匹配原生程序时支持 macOS 与 Linux 的 x64/arm64 平台。打包需要 Node 18+、npm 和预先构建的本机 Rust 二进制;打包前核对二进制报告的平台和版本,在已忽略的 `release/npm/` 写入带平台标识的 tarball。默认包标记为 private,仅用于本地分发。`node scripts/pack-npm-local.mjs --public` 准备带说明、完整许可证与平台限制的公开 `@partme.ai/codeguard` 包。`0.1.2` 已发布;其公开包在 Apple Silicon macOS 上通过全新缓存 `npx` 执行和制品摘要核对。二进制回报的候选源码提交不等于签名或可复现构建证明。其它平台仍需各自验收的二进制包。见[发行技术方案](docs/Codeguard-Technical-Design.zh_CN.md)及[0.1.2 验收记录](tests/acceptance/npm-0.1.2-candidate.md)。 +打包器在已有匹配原生程序时支持 macOS 与 Linux 的 x64/arm64 平台。打包需要 Node 18+、npm 和预先构建的本机 Rust 二进制;打包前核对二进制报告的平台和版本,在已忽略的 `release/npm/` 写入带平台标识的 tarball。默认包标记为 private,仅用于本地分发。`node scripts/pack-npm-local.mjs --public` 准备带说明、完整许可证与平台限制的公开 `@partme.ai/codeguard` 包。`0.1.3` 已发布,包含全部 32 份 WASM 候选;其公开包在 Apple Silicon macOS 上通过全新缓存 `npx` 执行和制品摘要核对。二进制回报的候选源码提交不等于签名或可复现构建证明。其它平台仍需各自验收的二进制包。见[发行技术方案](docs/Codeguard-Technical-Design.zh_CN.md)及[0.1.3 验收记录](tests/acceptance/npm-0.1.3-wasm-candidate.md)。 -若要准备**本地**含 WASM 候选的包,先执行 `cargo build --locked -p codeguard-cli --features wasm-precheck`,再运行 `node scripts/pack-npm-local.mjs --require-wasm target/debug/codeguard`。打包器会拒绝缺少 worker 的二进制,核对 32 份固定资产身份,并在写包前实际运行有界 Zig 候选探针。`--public` 现在也要求同样检查;这不改变已发布且不含 WASM 的 `0.1.2`。本地离线 npm 包能运行 Zig、Dart 候选,并不等于语种精度或公开发行验收通过。 +若要准备**本地**含 WASM 候选的包,先执行 `cargo build --locked -p codeguard-cli --features wasm-precheck`,再运行 `node scripts/pack-npm-local.mjs --require-wasm target/debug/codeguard`。打包器会拒绝缺少 worker 的二进制,核对 32 份固定资产身份,并在写包前实际运行有界 Zig 候选探针。`--public` 同样要求这些检查及上游许可证核验。已发布的 `0.1.3` 包含全部 32 份候选;本地及注册表实装不等于语种精度验收或项目质量通过。 ## 5. 修复流程 @@ -186,7 +188,7 @@ Python 编辑快反馈可执行 `codeguard lint python . --file src/changed.py - ### 原生优先的统一入口与语法兜底——设计目标 -**公开 `0.1.2` 尚未实现内置 WASM 兜底或自动对话交付。** 保留已有命令作为统一入口;当前仍须满足各原生适配器的参数和前置条件: +**公开 `0.1.3` 已包含有界 WASM 候选路由,但尚无已验收兜底或自动对话交付。** 保留已有命令作为统一入口;当前仍须满足各原生适配器的参数和前置条件: 可选源码构建已有 Java/TypeScript 单文件候选路径。TypeScript 项目若有可见的本地 ESLint 10 入口和唯一 flat config,会先尝试从 `PATH` 解析 Node 并运行既有有界原生探针;缺 Node 时报告准备缺口;仅本地 ESLint 包未观察到时才提供 TypeScript/TSX 疑似语法初检。入口、包身份或配置不可信时保留具体环境阻塞。Java 候选仍须原生确认。Python `lint` 先保留 Ruff 结果,在 Ruff 缺失或项目未声明配置时补充疑似位置;损坏的 Ruff 配置仍是环境阻塞。已初始化工作区的单文件初检可同步一张稳定的原生确认任务;重复候选扫描不会关闭它。所有内置 grammar 的版本/方言尚未验收,原生局部观察和初检都不能批准交付。默认及公开二进制沿用既有原生上下文路径,见 [Java 局部验收](tests/acceptance/java-syntax-fallback-candidate.md)、[TypeScript 局部验收](tests/acceptance/typescript-syntax-fallback-candidate.md)与[原生优先局部验收](tests/acceptance/native-first-eslint-candidate.md)。 diff --git a/docs/Codeguard-Architecture.md b/docs/Codeguard-Architecture.md index 816930f5..7f74fc58 100644 --- a/docs/Codeguard-Architecture.md +++ b/docs/Codeguard-Architecture.md @@ -32,7 +32,7 @@ WASM requirements and 19 implementation tasks are recorded in the existing chang A source build with `--features wasm-precheck` now exposes `codeguard grammar probe --format=json` for all 32 pinned candidates. It uses the isolated Rust worker and reports unqualified observations with exit 3, not a lint or delivery verdict. Successful observations and input failures for a valid language share the [closed 0.1.0 JSON schema](../schemas/grammar-probe-v0.1.schema.json) and remain incomplete. Native-first `lint/check`, language/dialect acceptance, task/host feedback and distribution remain separate open work; current discovery also groups JavaScript/TSX under TypeScript and has no separate CFQuery/CFScript source mapping. -The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.33.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback, task/host integration or released npm capability. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). +The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.33.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback or task/host integration; npm 0.1.3 distributes it as an unqualified candidate. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). ```mermaid flowchart LR @@ -93,7 +93,7 @@ The CLI does not own a model provider, chat transport, IDE UI, RAG store, distri | Repair | Stable tasks, local attempts, selected native rechecks | Reliable verified closure, recurrence, and full event reconciliation | | False-positive exceptions | Candidate inspection/proposals, exact matching and signature-related building blocks | Trusted approval source, lifecycle integration, and usable activation | | Quality gates | Core algebra; public scans remain partial; index path safety | End-to-end checks against actual worktree/index/ref/CI content | -| Distribution | Source build and npm 0.1.2 for Apple Silicon macOS; artifact verification/download/install primitives | Supported binary releases, platform tests and host runtime bindings | +| Distribution | Source build and npm 0.1.3 for Apple Silicon macOS; artifact verification/download/install primitives | Supported binary releases, platform tests and host runtime bindings | Current `check all` includes Rust build scheduling and persistence; [build recheck](../tests/acceptance/rust-build-task-verification.md) is also present. Older notes saying these integrations are wholly absent must not be used as the current status. Complete build combinations and formal closure remain missing. @@ -214,7 +214,7 @@ The final two nodes are target behavior. Current candidate-absent observations d ### 8.1 Native-first routing with bundled WASM precheck — target -**Design addition, not implemented in the published `0.1.2` package.** Keep a single entry point; callers select a language or project, not a parser backend: +**Target contract, partly implemented in the published `0.1.3` candidate package.** Keep a single entry point; callers select a language or project, not a parser backend: ```bash codeguard lint java . @@ -259,7 +259,7 @@ 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 projects a [pinned CodeGraph coverage inventory](../grammars/codegraph-coverage.json): 30 vendored WASM files and two dependency-provided grammars. CodeGuard currently pins 32 Rust-loadable candidates, including ArkTS, Nix and Terraform; none is qualified for release. ArkTS, Nix and Terraform provenance and worker evidence are recorded in the [candidate acceptance](../tests/acceptance/arkts-nix-terraform-grammar-candidates.md). ArkTS, Nix and Terraform have pinned bytes, licenses and narrow Rust worker evidence, but no language-qualified standalone lint route or release qualification; see the [limited acceptance record](../tests/acceptance/arkts-nix-terraform-grammar-candidates.md). The corrected Zig source WASM requires a pinned import adaptation to load in Rust; public `lint zig` and language qualification remain open. Python has a narrow source-build-only fallback for unavailable or undeclared Ruff, with native results taking priority and no delivery approval. An initialized single-file scope now syncs one stable native-confirmation task; candidate zero recoveries do not close it. This inventory is deliberately separate from the pinned [candidate asset manifest](../grammars/manifest.json). COBOL now loads under a bounded 20 MiB input limit, but its cold-start and memory budget remain unqualified. Inventory rows never trigger loading or count as supported parser capability. +The read-only `codeguard grammar status` command projects a [pinned CodeGraph coverage inventory](../grammars/codegraph-coverage.json): 30 vendored WASM files and two dependency-provided grammars. CodeGuard currently pins 32 Rust-loadable candidates, including ArkTS, Nix and Terraform; none is qualified for release. ArkTS, Nix and Terraform provenance and worker evidence are recorded in the [candidate acceptance](../tests/acceptance/arkts-nix-terraform-grammar-candidates.md). ArkTS, Nix and Terraform have pinned bytes, licenses and narrow Rust worker evidence, but no language-qualified standalone lint route or release qualification; see the [limited acceptance record](../tests/acceptance/arkts-nix-terraform-grammar-candidates.md). The corrected Zig source WASM requires a pinned import adaptation to load in Rust; language-qualified `lint zig` remains open. Python has a narrow unqualified fallback for unavailable or undeclared Ruff, with native results taking priority and no delivery approval. An initialized single-file scope now syncs one stable native-confirmation task; candidate zero recoveries do not close it. This inventory is deliberately separate from the pinned [candidate asset manifest](../grammars/manifest.json). COBOL now loads under a bounded 20 MiB input limit, but its cold-start and memory budget remain unqualified. Inventory rows never trigger loading or count as supported parser capability. R, Ruby, PHP and Kotlin now also have pinned CodeGraph bytes, licenses, measured Rust ABI and narrow positive/negative worker samples; the PHP fixture covers mixed HTML/PHP. They remain unqualified candidates. The original CodeGraph Dart WASM remains incompatible with Rust, but CodeGuard now has a byte-reproducible Zig rebuild that compiles the real external scanner and uses a pinned WASI import adaptation. Rust loader and isolated-worker fixtures pass; Dart remains unqualified for public lint and release. See the [Dart rebuild record](../tests/acceptance/dart-grammar-rebuild-candidate.md). Erlang also has pinned CodeGraph bytes, upstream 0.19 license and ABI 14 with narrow Rust and worker samples. It has no qualified native comparison or public lint route; see the [Erlang candidate record](../tests/acceptance/erlang-grammar-candidate.md). Pascal now has pinned CodeGraph bytes and its original Isopod dependency commit and license; ABI 14 and narrow worker tests pass, but native comparison and public lint remain open. See the [Pascal candidate record](../tests/acceptance/pascal-grammar-candidate.md). The remaining CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET assets also load in the Rust worker, bringing the candidate inventory to 32/32 with zero released syntax capabilities. CFQuery misses `SELECT FROM`, VB.NET misparses a valid unindented method, and COBOL has a high load cost; see the [seven-asset acceptance record](../tests/acceptance/final-seven-grammar-candidates.md). @@ -474,7 +474,7 @@ There is no daemon uptime SLO, multi-region RPO/RTO, production metrics backend ## 15. Deployment, upgrades, and integration -Current deployment is a locally built binary plus independently prepared native toolchains. The `@partme.ai/codeguard@0.1.2` npm package bundles an Apple Silicon macOS binary behind a Node command entry; the Node layer only forwards arguments, cwd, environment, streams and exit status. Publication, a fresh-cache `npx` version check, and registry/local artifact byte comparison passed on that host. The binary reports a candidate source commit, but this does not establish signed provenance, a reproducible build, a multi-platform binary release, or completed quality gates. `tools install` public apply is blocked while internal package verification/download/install primitives have tests. +Current deployment is a locally built binary plus independently prepared native toolchains. The `@partme.ai/codeguard@0.1.3` npm package bundles an Apple Silicon macOS binary behind a Node command entry; the Node layer only forwards arguments, cwd, environment, streams and exit status. Publication, a fresh-cache `npx` version check, and registry/local artifact byte comparison passed on that host. The binary reports a candidate source commit, but this does not establish signed provenance, a reproducible build, a multi-platform binary release, or completed quality gates. `tools install` public apply is blocked while internal package verification/download/install primitives have tests. ```mermaid flowchart LR @@ -482,15 +482,16 @@ flowchart LR Verify --> Wasm{WASM package requested?} Wasm -->|yes| Probe[Match 32 pinned identities and run Zig worker] Wasm -->|no, local private only| Pack[Local platform-tagged npm tarball] - Probe --> Pack + Probe --> Licenses[Verify pinned upstream licenses] + Licenses --> Pack Pack --> Node[Node bin: codeguard] Node --> Rust[Bundled Rust executable] Rust --> Native[Selected native analyzers] ``` -The default local package is marked private and contains no npm install hook. Its `bin` entry is [npm/codeguard.cjs](../npm/codeguard.cjs); [scripts/pack-npm-local.mjs](../scripts/pack-npm-local.mjs) builds it from an existing binary. The `--public` packaging mode produced the published `@partme.ai/codeguard@0.1.2`, restricted to Apple Silicon macOS. Fresh-cache registry execution and matching package/binary hashes are recorded in [npm 0.1.2 acceptance evidence](../tests/acceptance/npm-0.1.2-candidate.md). Broad registry distribution still needs approved platform coverage, a trusted artifact manifest, matching versions and hashes, and a release process. CodeGraph's Node entry and platform package layout informed this design; its optional network fallback is not part of Codeguard's current installation path. +The default local package is marked private and contains no npm install hook. Its `bin` entry is [npm/codeguard.cjs](../npm/codeguard.cjs); [scripts/pack-npm-local.mjs](../scripts/pack-npm-local.mjs) builds it from an existing binary. The `--public` packaging mode produced the published `@partme.ai/codeguard@0.1.3`, restricted to Apple Silicon macOS. Fresh-cache registry execution and matching package/binary hashes are recorded in [npm 0.1.3 acceptance evidence](../tests/acceptance/npm-0.1.3-wasm-candidate.md). Broad registry distribution still needs approved platform coverage, a trusted artifact manifest, matching versions and hashes, and a release process. CodeGraph's Node entry and platform package layout informed this design; its optional network fallback is not part of Codeguard's current installation path. -For a new candidate-WASM package, `--require-wasm` and `--public` both reject a binary lacking the worker. The packer compares its inventory against the 32 pinned manifest identities and executes a Zig worker probe before writing a tarball; [local offline npm acceptance](../tests/acceptance/npm-wasm-local-package.md) additionally runs Zig and Dart from the package. This does not retroactively add WASM to published `0.1.2`, nor does it qualify all languages for lint or certify a public release. +For the published 0.1.3 candidate-WASM package, `--require-wasm` and `--public` both reject a binary lacking the worker. The packer compares its inventory against the 32 pinned manifest identities and executes a Zig worker probe before writing a tarball; [local offline npm acceptance](../tests/acceptance/npm-wasm-local-package.md) additionally runs Zig and Dart from the package. The packer also verifies and includes all pinned upstream licenses; the public 0.1.3 tarball matches the registry bytes. This does not retroactively add WASM to 0.1.2 or qualify any language for lint; see [public candidate acceptance](../tests/acceptance/npm-0.1.3-wasm-candidate.md). For upgrades: record binary/schema versions, preserve workbench data, preview managed changes, rerun a bounded check, and validate report consumption before adopting the new binary. Downgrade must respect supported schemas; never rewrite historical files into an older shape merely to make parsing pass. diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index 25e957ec..a435d065 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -32,7 +32,7 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选 Rust worker 源码以 `--features wasm-precheck` 构建后,可通过 `codeguard grammar probe --format=json` 显式调用全部 32 份固定候选资产。命令使用隔离 Rust worker,以退出码 3 报告未经语言验收的观察,不能作为 lint 或交付结论。成功观察与有效语种的输入失败都遵守[0.1.0 封闭 JSON Schema](../schemas/grammar-probe-v0.1.schema.json),保持未完成。原生优先 `lint/check`、语言/方言验收、任务/宿主反馈及发行仍需独立完成;现有发现清单还把 JavaScript/TSX 归入 TypeScript,并未为 CFQuery/CFScript 建立独立源码映射。 -源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.33.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底、任务/宿主接线或已发布 npm 能力;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 +源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.33.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底或任务/宿主接线;npm 0.1.3 仅将其作为未验收候选分发;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 ```mermaid flowchart LR @@ -93,7 +93,7 @@ CLI 不承担模型供应商、聊天传输、IDE UI、RAG 存储、分布式调 | 修复 | 稳定任务、本地尝试、部分原工具复检 | 可靠的验证关闭、复发处理、完整事件协调 | | 误报例外 | 候选查询/提案、精确匹配和签名相关基础能力 | 可信批准来源、生命周期接线和可用生效流程 | | 质量门禁 | 核心判定逻辑;公开扫描仍局部;index 路径安全 | 针对实际工作树/index/ref/CI 内容的端到端检查 | -| 分发 | 源码构建、Apple Silicon macOS npm 0.1.2;制品核验、下载和安装基础能力 | 可支持的二进制发行、平台测试和宿主运行时绑定 | +| 分发 | 源码构建、Apple Silicon macOS npm 0.1.3;制品核验、下载和安装基础能力 | 可支持的二进制发行、平台测试和宿主运行时绑定 | 当前 `check all` 已包含 Rust 构建调度和持久化,[构建复检](../tests/acceptance/rust-build-task-verification.md)也已存在。旧记录中“完全没有这些接线”的描述不能用作当前状态;完整构建组合和正式关闭仍待完成。 @@ -214,7 +214,7 @@ flowchart LR ### 8.1 原生优先与内置 WASM 初检路径——目标 -**本节是新增设计,公开的 `0.1.2` 包尚未实现。** 保留统一入口,调用者选择语言或项目,不选择解析器后端: +**本节目标契约仅在公开 `0.1.3` 候选包中局部实现。** 保留统一入口,调用者选择语言或项目,不选择解析器后端: ```bash codeguard lint java . @@ -474,7 +474,7 @@ CLI 返回命令专属的版本化 JSON、可读投影及部分 SARIF。[Schema ## 15. 部署、升级与集成 -当前部署形态是本地构建二进制加独立准备的原生工具链。`@partme.ai/codeguard@0.1.2` npm 包通过 Node 命令入口携带 Apple Silicon macOS 二进制;Node 层只转发参数、工作目录、环境、输出与退出状态。该平台的发布、全新缓存 `npx` 版本检查及注册表/本地制品字节核对已通过。二进制回报候选源码提交,但这不证明签名来源、可复现构建、多平台发行或完整质量门禁。公开 `tools install` apply 仍受阻,内部包核验、下载和安装基础能力已有测试。 +当前部署形态是本地构建二进制加独立准备的原生工具链。`@partme.ai/codeguard@0.1.3` npm 包通过 Node 命令入口携带 Apple Silicon macOS 二进制;Node 层只转发参数、工作目录、环境、输出与退出状态。该平台的发布、全新缓存 `npx` 版本检查及注册表/本地制品字节核对已通过。二进制回报候选源码提交,但这不证明签名来源、可复现构建、多平台发行或完整质量门禁。公开 `tools install` apply 仍受阻,内部包核验、下载和安装基础能力已有测试。 ```mermaid flowchart LR @@ -482,15 +482,16 @@ flowchart LR Verify --> Wasm{要求包含 WASM?} Wasm -->|是| Probe[核对 32 份固定身份并运行 Zig worker] Wasm -->|否,仅本地私有包| Pack[本地带平台标识的 npm 包] - Probe --> Pack + Probe --> Licenses[核对固定上游许可证] + Licenses --> Pack Pack --> Node[Node bin: codeguard] Node --> Rust[包内 Rust 可执行文件] Rust --> Native[选定的原生检查器] ``` -默认本地包标记为 private,不含 npm 安装钩子。`bin` 入口见 [npm/codeguard.cjs](../npm/codeguard.cjs),[scripts/pack-npm-local.mjs](../scripts/pack-npm-local.mjs)使用已构建二进制打包。`--public` 模式已产出公开的 `@partme.ai/codeguard@0.1.2`,仅覆盖 Apple Silicon macOS;全新缓存运行和包/二进制摘要核对见[npm 0.1.2 验收记录](../tests/acceptance/npm-0.1.2-candidate.md)。扩大分发范围还需平台覆盖、可信制品清单、版本与摘要绑定及发行流程。CodeGraph 的 Node 入口和平台包布局为此设计提供参考;其可选网络回退不属于 Codeguard 当前安装路径。 +默认本地包标记为 private,不含 npm 安装钩子。`bin` 入口见 [npm/codeguard.cjs](../npm/codeguard.cjs),[scripts/pack-npm-local.mjs](../scripts/pack-npm-local.mjs)使用已构建二进制打包。`--public` 模式已产出公开的 `@partme.ai/codeguard@0.1.3`,仅覆盖 Apple Silicon macOS;全新缓存运行和包/二进制摘要核对见[npm 0.1.3 验收记录](../tests/acceptance/npm-0.1.3-wasm-candidate.md)。扩大分发范围还需平台覆盖、可信制品清单、版本与摘要绑定及发行流程。CodeGraph 的 Node 入口和平台包布局为此设计提供参考;其可选网络回退不属于 Codeguard 当前安装路径。 -新的 WASM 候选包使用 `--require-wasm` 或 `--public` 时,打包器会拒绝没有 worker 的二进制,核对固定清单的 32 份资产身份,并在写包前执行 Zig worker;[本地离线 npm 验收](../tests/acceptance/npm-wasm-local-package.md)另从包内运行 Zig、Dart。该检查不会让已发布 `0.1.2` 自动获得 WASM,也不证明全部语言的 lint 精度或公开发行已验收。 +已发布的 0.1.3 WASM 候选包使用 `--require-wasm` 或 `--public` 时,打包器会拒绝没有 worker 的二进制,核对固定清单的 32 份资产身份,并在写包前执行 Zig worker;[本地离线 npm 验收](../tests/acceptance/npm-wasm-local-package.md)另从包内运行 Zig、Dart。打包器还核对并分发全部固定上游许可证;公开 0.1.3 包与注册表字节一致。这不会让旧版 0.1.2 自动获得 WASM,也不证明任何语言的 lint 精度;见[公开候选验收](../tests/acceptance/npm-0.1.3-wasm-candidate.md)。 升级时记录二进制/schema 版本,保留工作区,预览受管变更,重跑有限检查,再核对报告消费。降级必须服从已有 schema 读取范围,不能为了通过解析而改写历史记录成旧格式。 diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index edd2c484..a0f149a7 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -151,7 +151,7 @@ Add a new adapter by defining these contracts, implementing observation first, a ### 5.3 Native-first selection and syntax fallback — target, not shipped -The proposed routing extends existing `codeguard lint java .`, `codeguard lint typescript .` and `codeguard check all .` entries. It adds no requirement for callers to choose WASM. Current adapter options/prerequisites still apply until implementation and acceptance; the published `0.1.2` does not contain this fallback. The architecture document's **8.1–8.3** owns component boundaries and execution diagrams; this section defines the algorithm. +The proposed routing extends existing `codeguard lint java .`, `codeguard lint typescript .` and `codeguard check all .` entries. It adds no requirement for callers to choose WASM. Current adapter options/prerequisites still apply until implementation and acceptance; the published `0.1.3` includes only the bounded, unqualified candidate route; `0.1.2` did not contain it. The architecture document's **8.1–8.3** owns component boundaries and execution diagrams; this section defines the algorithm. ```mermaid flowchart LR @@ -220,7 +220,7 @@ The opt-in CLI build first checks for a local ESLint 10 package and a single ord 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, thirty-two CodeGuard asset candidates (ArkTS, C, C++, C#, Go, Java, JavaScript, Lua, Luau, Objective-C, Python, Rust, Solidity, TypeScript, TSX, Zig, Nix, Terraform, R, Ruby, PHP, Kotlin, Dart, Erlang, Pascal, CFML, CFQuery, CFScript, COBOL, Scala, Swift and VB.NET), and zero released capabilities without running a parser. Python has a narrow source-build-only `lint` fallback for unavailable or undeclared Ruff, never a clean or delivery result. A bound, single-file result uses [feedback 0.15.0](../schemas/python-lint-feedback-v0.15.schema.json) and persists a [0.1.0 confirmation observation](../schemas/python-syntax-confirmation-observation-v0.1.schema.json) through the existing work sync. The task identity binds workspace and source scope; current-source SHA-256 and pinned grammar are checked before import. Native capability-matched closure remains open. C, C++, C#, Go, JavaScript, Lua, Luau and Rust have pinned vendored bytes and narrow Rust worker smoke tests, but no language-qualified standalone lint route or language qualification. ArkTS, Nix and Terraform have pinned bytes, licenses and narrow Rust worker evidence, but no language-qualified standalone lint route or release qualification; see the [limited acceptance record](../tests/acceptance/arkts-nix-terraform-grammar-candidates.md). The two dependency-provided assets now have pinned original and adapted bytes plus license references, but are still unqualified candidates without language-qualified lint routes. COBOL now loads under a bounded 20 MiB input limit, but cold-start and memory budgets remain unqualified. +Use `codeguard grammar status --format=json` to query the separate source coverage inventory. It reports 32 distinct CodeGraph grammar assets, thirty-two CodeGuard asset candidates (ArkTS, C, C++, C#, Go, Java, JavaScript, Lua, Luau, Objective-C, Python, Rust, Solidity, TypeScript, TSX, Zig, Nix, Terraform, R, Ruby, PHP, Kotlin, Dart, Erlang, Pascal, CFML, CFQuery, CFScript, COBOL, Scala, Swift and VB.NET), and zero released capabilities without running a parser. Python has a narrow unqualified `lint` fallback for unavailable or undeclared Ruff, never a clean or delivery result. A bound, single-file result uses [feedback 0.15.0](../schemas/python-lint-feedback-v0.15.schema.json) and persists a [0.1.0 confirmation observation](../schemas/python-syntax-confirmation-observation-v0.1.schema.json) through the existing work sync. The task identity binds workspace and source scope; current-source SHA-256 and pinned grammar are checked before import. Native capability-matched closure remains open. C, C++, C#, Go, JavaScript, Lua, Luau and Rust have pinned vendored bytes and narrow Rust worker smoke tests, but no language-qualified standalone lint route or language qualification. ArkTS, Nix and Terraform have pinned bytes, licenses and narrow Rust worker evidence, but no language-qualified standalone lint route or release qualification; see the [limited acceptance record](../tests/acceptance/arkts-nix-terraform-grammar-candidates.md). The two dependency-provided assets now have pinned original and adapted bytes plus license references, but are still unqualified candidates without language-qualified lint routes. COBOL now loads under a bounded 20 MiB input limit, but cold-start and memory budgets remain unqualified. R, Ruby, PHP and Kotlin now also have pinned CodeGraph bytes, licenses, measured Rust ABI and narrow positive/negative worker samples; the PHP fixture covers mixed HTML/PHP. They remain unqualified candidates. The original CodeGraph Dart WASM remains incompatible with Rust, but CodeGuard now has a byte-reproducible Zig rebuild that compiles the real external scanner and uses a pinned WASI import adaptation. Rust loader and isolated-worker fixtures pass; Dart remains unqualified for public lint and release. See the [Dart rebuild record](../tests/acceptance/dart-grammar-rebuild-candidate.md). Erlang also has pinned CodeGraph bytes, upstream 0.19 license and ABI 14 with narrow Rust and worker samples. It has no qualified native comparison or public lint route; see the [Erlang candidate record](../tests/acceptance/erlang-grammar-candidate.md). Pascal now has pinned CodeGraph bytes and its original Isopod dependency commit and license; ABI 14 and narrow worker tests pass, but native comparison and public lint remain open. See the [Pascal candidate record](../tests/acceptance/pascal-grammar-candidate.md). The remaining CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET assets also load in the Rust worker, bringing the candidate inventory to 32/32 with zero released syntax capabilities. CFQuery misses `SELECT FROM`, VB.NET misparses a valid unindented method, and COBOL has a high load cost; see the [seven-asset acceptance record](../tests/acceptance/final-seven-grammar-candidates.md). @@ -588,9 +588,9 @@ Artifact signature, package digest, layout, archive extraction and publication p The Node path supports the published macOS arm64 package and local packaging. For local packaging: explicitly build the Rust binary, validate its `--version --format json` target and version, create a host-tagged tarball, and install or execute it through npm. The package contains `codeguard.cjs` and the native binary, declares npm `os`/`cpu`, and has no install lifecycle script. Local packages are private by default; `--public` packages are public. The launcher does not implement checks or silently fetch tooling. [README](../README.md) contains tested commands. This is separate from Codeguard's `tools install`, which provisions external analyzers under a different trust contract. -The `--public` packer mode produced `@partme.ai/codeguard@0.1.2` for `darwin-arm64`; its npm `os`/`cpu` constraints reject other hosts. Registry publication, a fresh-cache registry-hosted `npx --yes @partme.ai/codeguard@0.1.2 --version --format json` run, and registry/local tarball and binary SHA-256 comparisons passed on Apple Silicon macOS. Its clean-checkout build identity reports the candidate source commit but does not prove a reproducible build, signed provenance, or S13 release completion; see [acceptance evidence](../tests/acceptance/npm-0.1.2-candidate.md). For broader distribution, adapt CodeGraph's thin entry/platform bundle pattern only after S13 release evidence is ready: publish a version-matched package for each certified target, then a public command package with exact optional platform dependencies. Missing platform support must fail clearly; network recovery must follow Codeguard's signed selection and explicit host permission contract before any download. +The `--public` packer mode produced `@partme.ai/codeguard@0.1.3` for `darwin-arm64`; its npm `os`/`cpu` constraints reject other hosts. Registry publication, a fresh-cache registry-hosted `npx --yes @partme.ai/codeguard@0.1.3 --version --format json` run, and registry/local tarball and binary SHA-256 comparisons passed on Apple Silicon macOS. Its clean-checkout build identity reports the candidate source commit but does not prove a reproducible build, signed provenance, or S13 release completion; see [acceptance evidence](../tests/acceptance/npm-0.1.3-wasm-candidate.md). For broader distribution, adapt CodeGraph's thin entry/platform bundle pattern only after S13 release evidence is ready: publish a version-matched package for each certified target, then a public command package with exact optional platform dependencies. Missing platform support must fail clearly; network recovery must follow Codeguard's signed selection and explicit host permission contract before any download. -For a later WASM candidate package, `--require-wasm` verifies the binary's 32 reported identities against the pinned manifest, rejects a default build without the worker, and executes a real Zig grammar probe before packing. `--public` implies this guard and retains its clean-checkout/build-identity checks. A local private tarball then runs offline through `npm exec`; Zig and Dart probes demonstrate the launcher reaches the bundled Rust worker, and four bounded installed-package `check all` runs invoke all 32 candidates. This is [local packaging evidence](../tests/acceptance/npm-wasm-local-package.md), not a registry release or per-language syntax qualification. The published `0.1.2` package predates the guard and has no WASM fallback; a future public release needs a new version, platform and license verification, and the remaining S14 acceptance. +For the published 0.1.3 WASM candidate package, `--require-wasm` verifies the binary's 32 reported identities against the pinned manifest, rejects a default build without the worker, and executes a real Zig grammar probe before packing. `--public` implies this guard and retains its clean-checkout/build-identity checks. A local private tarball then runs offline through `npm exec`; Zig and Dart probes demonstrate the launcher reaches the bundled Rust worker, and four bounded installed-package `check all` runs invoke all 32 candidates. This [local packaging evidence](../tests/acceptance/npm-wasm-local-package.md) is supplemented by [registry installation and byte-identity evidence](../tests/acceptance/npm-0.1.3-wasm-candidate.md). The packer verifies and includes pinned upstream licenses. The published candidate still does not establish per-language syntax qualification; broader platforms and the remaining S14 acceptance are open. ## 12. Test and evaluation plan diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index d03924c0..e8a48110 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -151,7 +151,7 @@ CachePort.lookup(validated_identity) -> VerifiedCacheEntry | Miss ### 5.3 原生优先与语法兜底选择——目标,尚未发行 -拟议路径扩展已有的 `codeguard lint java .`、`codeguard lint typescript .` 和 `codeguard check all .`,不要求调用者选择 WASM。在实现和验收前,仍须使用当前适配器参数/前置条件;公开 `0.1.2` 不含此兜底。架构文档 **8.1–8.3** 负责组件边界与执行图,本节定义算法。 +拟议路径扩展已有的 `codeguard lint java .`、`codeguard lint typescript .` 和 `codeguard check all .`,不要求调用者选择 WASM。在实现和验收前,仍须使用当前适配器参数/前置条件;公开 `0.1.3` 仅包含有界、未验收的候选路径;`0.1.2` 不含。架构文档 **8.1–8.3** 负责组件边界与执行图,本节定义算法。 ```mermaid flowchart LR @@ -588,9 +588,9 @@ CLI/runtime 部分模块已实现制品签名、包摘要、布局、解压和 Node 路径支持已发布的 macOS arm64 包和本地打包。本地打包流程为:显式构建 Rust 二进制,核对 `--version --format json` 报告的平台与版本,生成带本机标识的 tarball,通过 npm 安装或一次性执行。包包含 `codeguard.cjs` 和原生程序,声明 npm `os`/`cpu`,不含安装生命周期脚本。本地包默认标记为 private;`--public` 包为公开包。Node 入口不实现检测,也不静默下载工具。[README](../README.zh-CN.md)给出已实测的指令。这条路径与按另一套信任契约准备外部检查器的 `tools install` 分开。 -`--public` 打包模式已产出仅适用于 `darwin-arm64` 的 `@partme.ai/codeguard@0.1.2`;npm `os`/`cpu` 限制会拒绝其它宿主。npm 仓库发布、Apple Silicon macOS 上全新缓存运行 `npx --yes @partme.ai/codeguard@0.1.2 --version --format json`、注册表/本地 tarball 及二进制 SHA-256 对比均通过。干净 checkout 构建身份回报候选源码提交,但不证明可复现构建或签名来源,也不代表 S13 发行证据已完成,见[验收记录](../tests/acceptance/npm-0.1.2-candidate.md)。扩大分发范围可参考 CodeGraph 的精简入口加平台包模式,但须先满足 S13 发行证据:为每个通过验收的平台发布版本一致的包,再提供含精确可选平台依赖的公开命令包。缺对应平台包时应给出清晰错误;如需网络补包,下载前必须遵循 Codeguard 的签名选择与独立宿主授权。 +`--public` 打包模式已产出仅适用于 `darwin-arm64` 的 `@partme.ai/codeguard@0.1.3`;npm `os`/`cpu` 限制会拒绝其它宿主。npm 仓库发布、Apple Silicon macOS 上全新缓存运行 `npx --yes @partme.ai/codeguard@0.1.3 --version --format json`、注册表/本地 tarball 及二进制 SHA-256 对比均通过。干净 checkout 构建身份回报候选源码提交,但不证明可复现构建或签名来源,也不代表 S13 发行证据已完成,见[验收记录](../tests/acceptance/npm-0.1.3-wasm-candidate.md)。扩大分发范围可参考 CodeGraph 的精简入口加平台包模式,但须先满足 S13 发行证据:为每个通过验收的平台发布版本一致的包,再提供含精确可选平台依赖的公开命令包。缺对应平台包时应给出清晰错误;如需网络补包,下载前必须遵循 Codeguard 的签名选择与独立宿主授权。 -后续 WASM 候选包使用 `--require-wasm`,打包前将二进制报告的 32 份身份逐一与固定清单核对,拒绝无 worker 的默认构建,并实际执行 Zig grammar 探针。`--public` 自动启用该门禁,同时保留干净源码和构建身份核对。本地 private tarball 再经离线 `npm exec` 运行;Zig、Dart 探针证明 Node 入口能够调用内置 Rust worker,四个有界项目的包内 `check all` 实际调用全部 32 份候选。见[本地打包验收](../tests/acceptance/npm-wasm-local-package.md)。这不是注册表发行或逐语言语法验收。已发布的 `0.1.2` 早于该门禁,不含 WASM 兜底;未来公开版本须升级版本号,补齐平台、许可证和 S14 剩余验收。 +已发布的 0.1.3 WASM 候选包使用 `--require-wasm`,打包前将二进制报告的 32 份身份逐一与固定清单核对,拒绝无 worker 的默认构建,并实际执行 Zig grammar 探针。`--public` 自动启用该门禁,同时保留干净源码和构建身份核对。本地 private tarball 再经离线 `npm exec` 运行;Zig、Dart 探针证明 Node 入口能够调用内置 Rust worker,四个有界项目的包内 `check all` 实际调用全部 32 份候选。这份[本地打包证据](../tests/acceptance/npm-wasm-local-package.md)已由[注册表实装和字节一致性证据](../tests/acceptance/npm-0.1.3-wasm-candidate.md)补充。打包器核对并包含固定上游许可证;公开候选仍不构成逐语言语法验收,其它平台与 S14 剩余验收仍待完成。 ## 12. 测试与评估方案 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index d0dbd69f..e5866181 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1091,3 +1091,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.6 / 14.19 共享后缀误报收敛:自动路由不再把任意 `.m` 当 Objective-C,也不再把 `.sc` 当 Scala;`.m` 需行首 Objective-C 专属标记,`.scala` 与 `.mm` 路由保留。MATLAB 字符串/注释中的标记及 SuperCollider 正例不进入错误 grammar;32 份候选的明确样例仍需完整回归。现有静态发现仍可能将共享后缀归入旧语言目录,缺少项目级方言裁决和逐文件未路由解释,父任务保持未完成;见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。 14.4 / 14.6 / 14.19 后续发现层补齐:默认构建的只读 `detect` 与可选 WASM 路由共用受限 Objective-C 标记判断;MATLAB `.m`、SuperCollider `.sc` 不再被列成 Objective-C/Scala,`unknown_conditions` 保留歧义相对路径,`check all` 的候选范围仍计入并报告未路由数量。真实 `detect` 与 `check all` 回归通过;项目级 `.sc` 方言裁决、无标记 Objective-C `.m`、更强的词法证据、逐文件结构化歧义协议及完整语言验收仍缺,父任务不勾选。见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。 + +13.4 / 14.18 / 14.19 公开候选发行增量:`@partme.ai/codeguard@0.1.3` 在 `darwin-arm64` 从干净提交 `7900a1a` 构建并发布,32 份固定 grammar 候选均可经包内 Rust worker 和有界 `check all` 调用。打包器校验并分发全部上游许可证,Linux CI、本地离线 npm 全量路由、注册表/本地包与二进制摘要及新缓存 `npx` 均通过;见[公开包局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。插件显式候选运行时仍锁 0.1.2,并用旧包成员及大小上限校验,不能直接接收 0.1.3;默认 Hook 仍为 Python。候选保持不完整,`released_count=0`;语言精度/版本、完整原生优先、任务及宿主反馈、多平台和可信来源未验收,三个父任务继续保持未完成。 diff --git a/tests/acceptance/npm-0.1.3-wasm-candidate.md b/tests/acceptance/npm-0.1.3-wasm-candidate.md new file mode 100644 index 00000000..d9e7dfc0 --- /dev/null +++ b/tests/acceptance/npm-0.1.3-wasm-candidate.md @@ -0,0 +1,23 @@ +# npm 0.1.3:32 份 WASM 候选的公开包局部验收 + +日期:2026-10-03。对应 OpenSpec `introduce-rust-codeguard-cli` 的 13.4、14.18、14.19 局部增量。这是 **候选资产可从公开 npm 包调用** 的证据,不是 32 种语言的 lint 精度验收。 + +## 源码与打包 + +- 干净源码提交:`7900a1a8ccd5b4f5b54de8b5db3ccba16a356d3b`。用 `CODEGUARD_BUILD_SHA=<该提交> CARGO_INCREMENTAL=0 cargo build --release --locked -p codeguard-cli --features wasm-precheck` 构建 Apple Silicon macOS 程序,再运行 `node scripts/pack-npm-local.mjs --public target/release/codeguard`。 +- 打包器拒绝无 WASM worker 的默认程序,核对 32 个固定 grammar 身份并执行真实 Zig 探针。每份上游许可证和 CodeGraph 许可证先按 `grammars/manifest.json` 的 SHA-256 核对,再随包写入 `grammar-licenses/`;缺失、漂移、清单冲突或 tarball 遗漏均拒绝打包。 +- 本机 Node 测试先因包内缺许可证失败,修复后 `tests/npm_pack_wasm.test.mjs` 为 3/3 通过:离线安装包中 Zig、Dart 探针返回候选状态;四组 `check all` 的 `candidate_observed` 并集恰好覆盖全部 32 种,`skipped_count=0`,组报告保持 `incomplete` 与退出码 3。 +- 工作区测试 `cargo test --workspace --all-targets --locked` 成功;WASM 运行层 12 个 grammar 加载测试和 Dart 上游语料测试、CLI 全 32 路由 5 个测试及公开探针 2 个测试成功。相同源码提交的 [Linux CI 37104976461](https://github.com/full-stack-plugins/codeguard/actions/runs/37104976461) 通过 worker 边界、包内 npm 测试及工作区测试。 + +## 注册表与实装 + +- `npm publish ./release/npm/codeguard-public-0.1.3-darwin-arm64.tgz --access public --tag latest --ignore-scripts` 成功;`@partme.ai/codeguard@0.1.3` 为当前 `latest`,仅支持 `darwin-arm64`。 +- 注册表地址:`https://registry.npmjs.org/@partme.ai/codeguard/-/codeguard-0.1.3.tgz`。SHA-512 integrity:`sha512-RdhW/Tvf6a+7ZhRQFzSCJL87olzKmCjzQfyiqPZ3djTOodbdP7YfchT9VOFq/d7i+g95AME1mQC4HilHdb6WXQ==`。 +- 注册表下载包与本机 tarball 的 SHA-256 同为 `37509cdd108fee631eebb9ebcd794a6e8d63652801b3522625d0909c3da2ae12`;包内二进制与本机构建的 SHA-256 同为 `04bdea8c489693a8e97b451df8d0c2b7a89ba82372db5a4d1fed277ba19b0f4d`。包共 38 个文件,其中 `grammar-licenses/` 有 32 个许可证文件(TypeScript 与 TSX 共用一份上游许可证,另含 CodeGraph 许可证)。 +- 全新 npm 缓存从注册表安装并运行 `codeguard --version --format json`,返回 `cli_version=0.1.3`、`target=macos_arm64`、上述源码提交。相同安装下 `codeguard grammar status --format=json` 返回 `candidate_count=32`、`released_count=0`、`delivery_decision=not_evaluated`、退出码 3。 + +## 未完成边界 + +公开包证明 32 份固定资产可安装、可由 Rust worker 调用,并能经有界 `check all` 进入候选观察。它不证明所有语言/版本/方言的误报与漏报率、完整原生优先路由、每种语言的独立 lint、任务自动复检关闭、宿主对话反馈、其它平台、签名来源或可复现构建。已知 Swift 漏检、CFQuery 漏检、VB.NET 误报和 COBOL 成本问题仍保留;所有 grammar 的 `grammar_qualified=false`,语法能力 `released_count=0`。13.4、14.18、14.19 均不据此勾选。 + +`codeguard-plugin` 的显式候选运行时仍锁定 npm 0.1.2,且当前校验器只接受旧包的 6 个成员并限定 tarball 小于 8 MiB;0.1.3 包有 38 个成员、约 12.1 MB,不能直接替换锁文件。默认 Hook 仍使用旧 Python 入口。插件升级须单独调整包成员/大小/许可证校验、锁定摘要和候选事件测试,再遵循插件仓版本与市场发布流程;本次发行不声称插件宿主已使用 WASM。 From 086197b120b969f3464fd14e8c9fa433ffc2e030 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:25:12 +0800 Subject: [PATCH 14/31] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=E6=8E=A5=E6=94=B6=E6=96=B0=E5=8C=85?= =?UTF-8?q?=E7=9A=84=E5=AE=B9=E9=87=8F=E7=BC=BA=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openspec/changes/introduce-rust-codeguard-cli/tasks.md | 2 +- tests/acceptance/npm-0.1.3-wasm-candidate.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index e5866181..0c75c2b7 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1092,4 +1092,4 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.6 / 14.19 后续发现层补齐:默认构建的只读 `detect` 与可选 WASM 路由共用受限 Objective-C 标记判断;MATLAB `.m`、SuperCollider `.sc` 不再被列成 Objective-C/Scala,`unknown_conditions` 保留歧义相对路径,`check all` 的候选范围仍计入并报告未路由数量。真实 `detect` 与 `check all` 回归通过;项目级 `.sc` 方言裁决、无标记 Objective-C `.m`、更强的词法证据、逐文件结构化歧义协议及完整语言验收仍缺,父任务不勾选。见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。 -13.4 / 14.18 / 14.19 公开候选发行增量:`@partme.ai/codeguard@0.1.3` 在 `darwin-arm64` 从干净提交 `7900a1a` 构建并发布,32 份固定 grammar 候选均可经包内 Rust worker 和有界 `check all` 调用。打包器校验并分发全部上游许可证,Linux CI、本地离线 npm 全量路由、注册表/本地包与二进制摘要及新缓存 `npx` 均通过;见[公开包局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。插件显式候选运行时仍锁 0.1.2,并用旧包成员及大小上限校验,不能直接接收 0.1.3;默认 Hook 仍为 Python。候选保持不完整,`released_count=0`;语言精度/版本、完整原生优先、任务及宿主反馈、多平台和可信来源未验收,三个父任务继续保持未完成。 +13.4 / 14.18 / 14.19 公开候选发行增量:`@partme.ai/codeguard@0.1.3` 在 `darwin-arm64` 从干净提交 `7900a1a` 构建并发布,32 份固定 grammar 候选均可经包内 Rust worker 和有界 `check all` 调用。打包器校验并分发全部上游许可证,Linux CI、本地离线 npm 全量路由、注册表/本地包与二进制摘要及新缓存 `npx` 均通过;见[公开包局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。插件显式候选运行时仍锁 0.1.2,并用旧包成员、tarball/下载流 8 MiB 与二进制 32 MiB 上限校验,不能直接接收 0.1.3;默认 Hook 仍为 Python。候选保持不完整,`released_count=0`;语言精度/版本、完整原生优先、任务及宿主反馈、多平台和可信来源未验收,三个父任务继续保持未完成。 diff --git a/tests/acceptance/npm-0.1.3-wasm-candidate.md b/tests/acceptance/npm-0.1.3-wasm-candidate.md index d9e7dfc0..b95dd896 100644 --- a/tests/acceptance/npm-0.1.3-wasm-candidate.md +++ b/tests/acceptance/npm-0.1.3-wasm-candidate.md @@ -20,4 +20,4 @@ 公开包证明 32 份固定资产可安装、可由 Rust worker 调用,并能经有界 `check all` 进入候选观察。它不证明所有语言/版本/方言的误报与漏报率、完整原生优先路由、每种语言的独立 lint、任务自动复检关闭、宿主对话反馈、其它平台、签名来源或可复现构建。已知 Swift 漏检、CFQuery 漏检、VB.NET 误报和 COBOL 成本问题仍保留;所有 grammar 的 `grammar_qualified=false`,语法能力 `released_count=0`。13.4、14.18、14.19 均不据此勾选。 -`codeguard-plugin` 的显式候选运行时仍锁定 npm 0.1.2,且当前校验器只接受旧包的 6 个成员并限定 tarball 小于 8 MiB;0.1.3 包有 38 个成员、约 12.1 MB,不能直接替换锁文件。默认 Hook 仍使用旧 Python 入口。插件升级须单独调整包成员/大小/许可证校验、锁定摘要和候选事件测试,再遵循插件仓版本与市场发布流程;本次发行不声称插件宿主已使用 WASM。 +`codeguard-plugin` 的显式候选运行时仍锁定 npm 0.1.2,且当前校验器只接受旧包的 6 个成员、把 tarball 和下载流限制在 8 MiB、把解压后的二进制限制在 32 MiB;0.1.3 包有 38 个成员、约 12.1 MB,二进制约 100.1 MB,不能直接替换锁文件。默认 Hook 仍使用旧 Python 入口。插件升级须单独调整包成员/大小/许可证校验、锁定摘要和候选事件测试,再遵循插件仓版本与市场发布流程;本次发行不声称插件宿主已使用 WASM。 From eb83f67226b3aa395b28ae428850ac73586a8701 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 15:59:30 +0800 Subject: [PATCH 15/31] docs: update 32 WASM plugin candidate evidence --- openspec/changes/introduce-rust-codeguard-cli/tasks.md | 2 ++ tests/acceptance/npm-0.1.3-wasm-candidate.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 0c75c2b7..7a8224f4 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1093,3 +1093,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.6 / 14.19 后续发现层补齐:默认构建的只读 `detect` 与可选 WASM 路由共用受限 Objective-C 标记判断;MATLAB `.m`、SuperCollider `.sc` 不再被列成 Objective-C/Scala,`unknown_conditions` 保留歧义相对路径,`check all` 的候选范围仍计入并报告未路由数量。真实 `detect` 与 `check all` 回归通过;项目级 `.sc` 方言裁决、无标记 Objective-C `.m`、更强的词法证据、逐文件结构化歧义协议及完整语言验收仍缺,父任务不勾选。见[局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。 13.4 / 14.18 / 14.19 公开候选发行增量:`@partme.ai/codeguard@0.1.3` 在 `darwin-arm64` 从干净提交 `7900a1a` 构建并发布,32 份固定 grammar 候选均可经包内 Rust worker 和有界 `check all` 调用。打包器校验并分发全部上游许可证,Linux CI、本地离线 npm 全量路由、注册表/本地包与二进制摘要及新缓存 `npx` 均通过;见[公开包局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。插件显式候选运行时仍锁 0.1.2,并用旧包成员、tarball/下载流 8 MiB 与二进制 32 MiB 上限校验,不能直接接收 0.1.3;默认 Hook 仍为 Python。候选保持不完整,`released_count=0`;语言精度/版本、完整原生优先、任务及宿主反馈、多平台和可信来源未验收,三个父任务继续保持未完成。 + +2026-10-03 后续增量:上段插件 0.1.2 状态是 CLI 0.1.3 初发时的快照。插件仓本地提交 `7bfb07a` 已升级显式候选锁和安装器,支持 0.1.3 的 38 个成员及 32 份许可证,增加 `exec check all ABS_PROJECT`;插件候选测试 5/5,本仓离线 npm 包 32 语种统一检查回归 1/1 通过。插件仓仍需 PR、受保护 CI、tag、市场同步;默认 Python Hook、逐语言精度、任务自动复检和真实宿主反馈仍缺,13.4、14.18、14.19 不勾选。详见[更新的发行局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。 diff --git a/tests/acceptance/npm-0.1.3-wasm-candidate.md b/tests/acceptance/npm-0.1.3-wasm-candidate.md index b95dd896..86a48e60 100644 --- a/tests/acceptance/npm-0.1.3-wasm-candidate.md +++ b/tests/acceptance/npm-0.1.3-wasm-candidate.md @@ -21,3 +21,5 @@ 公开包证明 32 份固定资产可安装、可由 Rust worker 调用,并能经有界 `check all` 进入候选观察。它不证明所有语言/版本/方言的误报与漏报率、完整原生优先路由、每种语言的独立 lint、任务自动复检关闭、宿主对话反馈、其它平台、签名来源或可复现构建。已知 Swift 漏检、CFQuery 漏检、VB.NET 误报和 COBOL 成本问题仍保留;所有 grammar 的 `grammar_qualified=false`,语法能力 `released_count=0`。13.4、14.18、14.19 均不据此勾选。 `codeguard-plugin` 的显式候选运行时仍锁定 npm 0.1.2,且当前校验器只接受旧包的 6 个成员、把 tarball 和下载流限制在 8 MiB、把解压后的二进制限制在 32 MiB;0.1.3 包有 38 个成员、约 12.1 MB,二进制约 100.1 MB,不能直接替换锁文件。默认 Hook 仍使用旧 Python 入口。插件升级须单独调整包成员/大小/许可证校验、锁定摘要和候选事件测试,再遵循插件仓版本与市场发布流程;本次发行不声称插件宿主已使用 WASM。 + +2026-10-03 插件后续进展:`codeguard-plugin` 本地提交 `7bfb07a` 已把显式候选运行时锁定到 0.1.3,并验证 38 个精确成员、32 份许可证、注册表与离线安装。其 `exec check all ABS_PROJECT` 可调用 CLI 的原生优先统一检查;插件候选测试 5/5 通过。本仓再次用离线 npm 包执行 32 语种 `check all` 路由测试 1/1 通过。插件 `main` 受 GitHub PR 规则保护,本地提交尚未发布为插件 tag/市场版本;默认 Hook 仍是 Python,自动对话反馈与逐语言精度验收未完成。原段记录的是 CLI 0.1.3 初发时的插件状态,不是此后插件的当前状态。 From 1c3904a33bc893f8095a7d55e4a2ead2be362aab Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:12:52 +0800 Subject: [PATCH 16/31] docs: record VB.NET grammar false positive evidence --- grammars/manifest.json | 2 +- grammars/vbnet/README.md | 2 ++ .../introduce-rust-codeguard-cli/tasks.md | 2 ++ .../vbnet-unindented-method-false-positive.md | 17 +++++++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/acceptance/vbnet-unindented-method-false-positive.md diff --git a/grammars/manifest.json b/grammars/manifest.json index 5c9a2f96..7c8edf44 100644 --- a/grammars/manifest.json +++ b/grammars/manifest.json @@ -547,7 +547,7 @@ "codegraph_runtime": "web-tree-sitter 0.25.3", "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], - "known_limitations": ["Pinned CodeGraph asset; only narrow syntax fixtures; native differential, language and dialect ranges, public lint route, resource budget and release remain unqualified"], + "known_limitations": ["Pinned CodeGraph asset reports MISSING ':' at row 1 column 30 for a valid unindented Public Function declaration inside Public Class; the same sample with indentation has zero recoveries. This is a known grammar false positive, not a confirmed source violation; native compiler comparison and grammar repair remain required", "Only narrow syntax fixtures; language and dialect ranges, public lint route, resource budget and release remain unqualified"], "release_status": "candidate_unvalidated" }, { diff --git a/grammars/vbnet/README.md b/grammars/vbnet/README.md index a3552e3e..3624a0ce 100644 --- a/grammars/vbnet/README.md +++ b/grammars/vbnet/README.md @@ -3,3 +3,5 @@ 固定 CodeGraph 提交中的 `tree-sitter-vbnet.wasm`,SHA-256 `e38a09e1826c7ec06340c0531a98bbb5ee79bdd710bba3d3349b79a19721e844`。CodeGraph 的[来源和补丁说明](https://github.com/colbymchenry/codegraph/blob/main/docs/grammars/tree-sitter-vbnet.md)把上游固定为 `govindbanura/tree-sitter-vbnet@538b7087bf80e86004531b392fe1186379c0a2b5`,并应用本目录 `source.patch`(SHA-256 `33963ebe3ba60929fb58d90efd54f206f98a54ea13ae325d784732c828b7b93a`)。`LICENSE` 与上游提交相同,SHA-256 `fa7ab39bcf92c216d429e8aa8be8a5a40db85bfbe4ff14a7bb0ecbbcca937454`。 Rust 离线加载实测 ABI 15,缩进后的类方法和破损声明样例及隔离 worker 通过。**已观察到误报:合法的未缩进类方法体被解析为 `MISSING ":"`**。该候选不得用于公开源码违规判定;需先修复 grammar 并与原生 VB.NET 编译器对照。版本语料、公开路由与发行包也未验收。 + +2026-10-03 复现:已发布的 0.1.3 二进制对 `Public Class C`、未缩进的 `Public Function F() As Integer`、`Return 1`、`End Function`、`End Class` 五行执行 `grammar probe vbnet FILE --format=json`,在第 2 行末字节 45 报出一处 `MISSING ":"`;仅增加方法体缩进的同义样例返回零恢复节点。两例均退出 3、`grammar_qualified=false`,不产生已确认源码违规。详见[误报局部复现](../../tests/acceptance/vbnet-unindented-method-false-positive.md);本机没有 .NET 编译器,因此仍需独立原生语法对照。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 7a8224f4..5299d4ff 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1095,3 +1095,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 13.4 / 14.18 / 14.19 公开候选发行增量:`@partme.ai/codeguard@0.1.3` 在 `darwin-arm64` 从干净提交 `7900a1a` 构建并发布,32 份固定 grammar 候选均可经包内 Rust worker 和有界 `check all` 调用。打包器校验并分发全部上游许可证,Linux CI、本地离线 npm 全量路由、注册表/本地包与二进制摘要及新缓存 `npx` 均通过;见[公开包局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。插件显式候选运行时仍锁 0.1.2,并用旧包成员、tarball/下载流 8 MiB 与二进制 32 MiB 上限校验,不能直接接收 0.1.3;默认 Hook 仍为 Python。候选保持不完整,`released_count=0`;语言精度/版本、完整原生优先、任务及宿主反馈、多平台和可信来源未验收,三个父任务继续保持未完成。 2026-10-03 后续增量:上段插件 0.1.2 状态是 CLI 0.1.3 初发时的快照。插件仓本地提交 `7bfb07a` 已升级显式候选锁和安装器,支持 0.1.3 的 38 个成员及 32 份许可证,增加 `exec check all ABS_PROJECT`;插件候选测试 5/5,本仓离线 npm 包 32 语种统一检查回归 1/1 通过。插件仓仍需 PR、受保护 CI、tag、市场同步;默认 Python Hook、逐语言精度、任务自动复检和真实宿主反馈仍缺,13.4、14.18、14.19 不勾选。详见[更新的发行局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。 + +14.4 / 14.17 / 14.19 VB.NET 精度复现:0.1.3 固定 WASM 对未缩进类方法返回一处 `MISSING ":"`,仅缩进变化的样例为零恢复;两者保持候选未验收和交付未评估。复现输入、坐标与资产身份见[局部证据](../../../tests/acceptance/vbnet-unindented-method-false-positive.md)。本机无 .NET 原生编译器,grammar 修复、原生差分与误报统计未完成,父任务不勾选。 diff --git a/tests/acceptance/vbnet-unindented-method-false-positive.md b/tests/acceptance/vbnet-unindented-method-false-positive.md new file mode 100644 index 00000000..21504c02 --- /dev/null +++ b/tests/acceptance/vbnet-unindented-method-false-positive.md @@ -0,0 +1,17 @@ +# VB.NET 未缩进方法候选误报复现 + +日期:2026-10-03。对应 OpenSpec 14.4、14.17、14.19 的精度阻塞,不是验收完成。 + +使用已发布的 0.1.3 macOS arm64 WASM 二进制,对以下两份源码分别执行 `codeguard grammar probe vbnet <物理绝对路径> --format=json`: + +```vbnet +Public Class C +Public Function F() As Integer +Return 1 +End Function +End Class +``` + +未缩进版本返回一处 `MISSING ":"`,`start_row=1`、`start_column_byte=30`、`start_byte=45`。给方法声明和主体增加四、八个空格后,同一固定 grammar 返回零恢复节点。两份报告均为 `status=incomplete`、`grammar_qualified=false`、`delivery_decision=not_evaluated`、退出码 3;WASM SHA-256 为 `e38a09e1826c7ec06340c0531a98bbb5ee79bdd710bba3d3349b79a19721e844`。 + +该差异符合已记录的 grammar 误报风险,但本机未安装 .NET 编译器,尚无原生语法判定证据。修复必须在固定来源 grammar 上重建、核验发行字节与许可证,使用合法/非法样例及原生编译器差分复测;不能通过忽略所有 `MISSING ":"`、给源码补缩进或把候选升级为源码违规来规避。 From b9dd6d5a62fc784d0b3ed874ee24f82035fd9ecd Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:21:29 +0800 Subject: [PATCH 17/31] feat: expose bounded grammar limitations in status --- README.md | 2 ++ README.zh-CN.md | 2 ++ .../src/grammar_asset_manifest.rs | 7 ++++++- .../tests/grammar_asset_manifest.rs | 21 +++++++++++++++++++ .../src/grammar_status_command.rs | 3 ++- .../codeguard-cli/tests/grammar_status_cli.rs | 12 +++++++++++ docs/Codeguard-Technical-Design.md | 2 ++ docs/Codeguard-Technical-Design.zh_CN.md | 2 ++ .../specs/syntax-precheck/spec.md | 7 +++++++ .../introduce-rust-codeguard-cli/tasks.md | 2 ++ schemas/grammar-asset-manifest.schema.json | 2 +- .../grammar-known-limitations-projection.md | 9 ++++++++ 12 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 tests/acceptance/grammar-known-limitations-projection.md diff --git a/README.md b/README.md index 34dc25c8..283ffbcd 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ The workspace requires `std`. The opt-in `wasm-precheck` build feature uses a bo 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 thirty-two unqualified asset candidates (ArkTS, C, C++, C#, Go, Java, JavaScript, Lua, Luau, Objective-C, Python, Rust, Solidity, TypeScript, TSX, Zig, Nix, Terraform, R, Ruby, PHP, Kotlin, Dart, Erlang, Pascal, CFML, CFQuery, CFScript, COBOL, Scala, Swift, VB.NET) and zero released syntax capabilities. C, C++, C#, Go, JavaScript, Lua, Luau and Rust are byte-pinned CodeGraph assets that load in the Rust worker but have no language-qualified standalone lint route or release qualification. ArkTS, Nix and Terraform are also pinned Rust-loadable candidates with narrow valid/invalid fixtures and no language-qualified standalone lint route or release qualification; see the [limited acceptance record](tests/acceptance/arkts-nix-terraform-grammar-candidates.md). The two dependency grammars retain their source bytes and use a byte-pinned legacy `dylink` metadata conversion for Rust loading; they have no language-qualified standalone lint route or release qualification. Zig uses a byte-pinned import adaptation so the corrected CodeGraph grammar can load in Rust; it has a narrow `lint zig` native-first candidate route but no release qualification. [Zig's limited acceptance record](tests/acceptance/zig-grammar-candidate.md) distinguishes this from released syntax support. Python has an unqualified CLI fallback path; its report remains incomplete and cannot approve delivery. R, Ruby, PHP and Kotlin have pinned CodeGraph bytes, licenses, measured Rust ABI and narrow worker samples; PHP covers mixed HTML/PHP, and all four remain unqualified. The original CodeGraph Dart WASM cannot be instantiated by Rust, but CodeGuard now pins a reproducible Zig rebuild with the real scanner and a byte-pinned WASI import adaptation. Its narrow Rust and worker fixtures pass, while public lint and release remain unqualified; see the [Dart rebuild record](tests/acceptance/dart-grammar-rebuild-candidate.md). Erlang now has pinned CodeGraph bytes, the upstream 0.19 license, ABI 14 and narrow Rust worker tests; it remains unqualified without native comparison or public lint. See the [Erlang candidate record](tests/acceptance/erlang-grammar-candidate.md). Pascal now has pinned CodeGraph bytes and its original Isopod dependency commit and license; ABI 14 and narrow worker tests pass, but native comparison and public lint remain open. See the [Pascal candidate record](tests/acceptance/pascal-grammar-candidate.md). The remaining CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET assets also load in the Rust worker, bringing the candidate inventory to 32/32 with zero released syntax capabilities. CFQuery misses `SELECT FROM`, VB.NET misparses a valid unindented method, and COBOL has a high load cost; see the [seven-asset acceptance record](tests/acceptance/final-seven-grammar-candidates.md). The separate [coverage inventory](grammars/codegraph-coverage.json) is not an approved asset manifest. COBOL's 16.4 MB asset now fits the bounded 20 MiB loader input, but its cold-load and memory budgets remain unqualified. +The source-built `grammar status` now includes each candidate's bounded `known_limitations`, including the [VB.NET false-positive reproduction](tests/acceptance/vbnet-unindented-method-false-positive.md). This is risk context for the agent, not a confirmed violation or a native compiler result. + ### npm installation and one-off use In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` can explicitly run any of the 32 pinned candidates in an isolated worker. It always exits 3 with `status=incomplete`, `native.status=not_run`, and `delivery_decision=not_evaluated`; a completed parse also has `precheck.status=incomplete`. Input or worker failures use the same [closed JSON schema](schemas/grammar-probe-v0.1.schema.json). Recovery anchors are suspected observations only. This diagnostic command does not yet connect all grammars to language-qualified native-first `lint/check`; the published npm package includes it as an unqualified candidate. diff --git a/README.zh-CN.md b/README.zh-CN.md index 56942ba3..7cc86a62 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -98,6 +98,8 @@ cargo build --locked -p codeguard-cli 当前源码构建的 CLI 可用 `codeguard grammar status --format=json` 只读展示来源覆盖缺口,不加载 grammar 或运行 lint:固定 CodeGraph 来源含 30 份随仓 WASM,另有两种独立 grammar 从其依赖取得;CodeGuard 当前有三十二份未验收资产候选、零项已发行语法能力。C、C++、C#、Go、JavaScript、Lua、Luau、Rust、ArkTS、Nix、Terraform、R、Ruby、PHP、Kotlin、Erlang、Pascal、CFML、CFQuery、CFScript、COBOL、Scala、Swift、VB.NET 等已固定字节并由 Rust worker 做窄范围验证,但没有逐语言验收的独立 lint 路由或发行验收。Objective-C 与 Solidity 的依赖包原始字节、许可证和固定散列的 `dylink` 元数据转换已入库;Zig 的新版语法也有固定导入适配。CodeGraph 原 Dart WASM 无法由 Rust 直接加载;CodeGuard 现以固定源码和真实外部 scanner 经 Zig 重建,并固定适配前后字节,150 例上游语料错误分类与预期一致,但仍未做 Dart 原生工具对照或发布验收,见[Dart 重建局部验收](tests/acceptance/dart-grammar-rebuild-candidate.md)。Erlang 已固定 CodeGraph 字节、上游 0.19 许可证和 ABI 14,Rust/隔离 worker 窄范围样例通过,但原生对照与公开 lint 尚缺,见[Erlang 候选局部验收](tests/acceptance/erlang-grammar-candidate.md)。Pascal 已固定 CodeGraph 字节、原始 Isopod 依赖提交和许可证,ABI 14 及窄范围 worker 样例通过;原生对照和公开 lint 尚缺,见[Pascal 候选局部验收](tests/acceptance/pascal-grammar-candidate.md)。其余七份 CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET 来源 WASM 也已固定并由 Rust worker 加载,累计 32/32 份候选、0 项已发行。CFQuery 漏掉 `SELECT FROM`,VB.NET 对合法未缩进方法体误报,COBOL 成本高;都不能发布为 lint,见[七份局部验收](tests/acceptance/final-seven-grammar-candidates.md)。Python 已有未验收的 CLI 候选兜底,结果保持不完整,不能批准交付。[覆盖库存](grammars/codegraph-coverage.json)不是已批准资产清单。COBOL 的 16.4 MB 固定资产现可在 20 MiB 输入限额下加载,但冷启动和内存预算尚未验收。 +当前源码构建的 `grammar status` 现逐候选返回有界的 `known_limitations`,包括[VB.NET 误报复现](tests/acceptance/vbnet-unindented-method-false-positive.md)。这是提供给智能体的风险背景,不是已确认违规或原生编译器结果。 + ### npm 安装与一次性调用 源码以 `--features wasm-precheck` 构建后,可显式运行 `codeguard grammar probe --format=json`,由隔离 worker 调用 32 份固定候选中的任意一份。命令始终以退出码 3 返回,并标记 `status=incomplete`、`native.status=not_run`、`delivery_decision=not_evaluated`;解析完成时还标记 `precheck.status=incomplete`。输入或 worker 失败也遵守同一份[封闭 JSON Schema](schemas/grammar-probe-v0.1.schema.json)。恢复锚点只是疑似观察。此诊断入口尚未把全部语种接入已验收的原生优先 `lint/check`;已发布 npm 包仅把它作为未验收候选提供。 diff --git a/crates/codeguard-adapters/src/grammar_asset_manifest.rs b/crates/codeguard-adapters/src/grammar_asset_manifest.rs index ceb4cca7..66e292bb 100644 --- a/crates/codeguard-adapters/src/grammar_asset_manifest.rs +++ b/crates/codeguard-adapters/src/grammar_asset_manifest.rs @@ -798,7 +798,12 @@ pub fn parse_grammar_asset_manifest(raw: &[u8]) -> Result 8 + || asset.known_limitations.iter().any(|limitation| { + limitation.is_empty() + || limitation.len() > 1024 + || limitation.chars().any(char::is_control) + }) || asset.release_status != "candidate_unvalidated" { return Err(format!("{} grammar 来源或验收状态不符", asset.language)); diff --git a/crates/codeguard-adapters/tests/grammar_asset_manifest.rs b/crates/codeguard-adapters/tests/grammar_asset_manifest.rs index 7bcda84c..1b90c2f3 100644 --- a/crates/codeguard-adapters/tests/grammar_asset_manifest.rs +++ b/crates/codeguard-adapters/tests/grammar_asset_manifest.rs @@ -298,6 +298,27 @@ fn missing_provenance_duplicate_fields_and_unknown_languages_are_rejected() { assert!(parse_grammar_asset_manifest(wrong_npm_integrity.as_bytes()).is_err()); } +#[test] +fn candidate_limitations_are_bounded_before_dialog_projection() { + let original: serde_json::Value = + serde_json::from_str(include_str!("../../../grammars/manifest.json")).unwrap(); + for limitations in [ + serde_json::json!(["x".repeat(1025)]), + serde_json::json!(vec!["x"; 9]), + serde_json::json!(["line one\nline two"]), + ] { + let mut manifest = original.clone(); + let vbnet = manifest["assets"] + .as_array_mut() + .unwrap() + .iter_mut() + .find(|asset| asset["language"] == "vbnet") + .unwrap(); + vbnet["known_limitations"] = limitations; + assert!(parse_grammar_asset_manifest(manifest.to_string().as_bytes()).is_err()); + } +} + #[test] fn changed_wasm_license_or_abi_never_qualifies_a_candidate() { let manifest = bundled_grammar_candidates().unwrap(); diff --git a/crates/codeguard-cli/src/grammar_status_command.rs b/crates/codeguard-cli/src/grammar_status_command.rs index 0f32e838..55c542b0 100644 --- a/crates/codeguard-cli/src/grammar_status_command.rs +++ b/crates/codeguard-cli/src/grammar_status_command.rs @@ -135,6 +135,7 @@ fn coverage_report() -> Result { "candidate_input_sha256":candidate.and_then(|item| item.source_sha256.as_deref()), "integration_status":candidate.map_or("not_integrated", |item| item.release_status.as_str()), "runtime_observation":candidate.map(|item| item.codeguard_runtime_validation.as_str()), + "known_limitations":candidate.map(|item| item.known_limitations.as_slice()).unwrap_or(&[]), "released":false, "gap":if candidate.is_some_and(|item| item.codeguard_runtime_validation == "rust_loader_incompatible") {"rust_loader_incompatible"} else if asset.bytes.is_some_and(|bytes| bytes > 20 * 1024 * 1024) {"current_loader_size_limit"} @@ -144,7 +145,7 @@ fn coverage_report() -> Result { }) .collect::>(); Ok(json!({ - "schema_version":"1.0.0", + "schema_version":"1.1.0", "report_type":"grammar_coverage_inventory", "source_repository":inventory.source_repository, "source_commit":inventory.source_commit, diff --git a/crates/codeguard-cli/tests/grammar_status_cli.rs b/crates/codeguard-cli/tests/grammar_status_cli.rs index 09c2c039..18a95f5e 100644 --- a/crates/codeguard-cli/tests/grammar_status_cli.rs +++ b/crates/codeguard-cli/tests/grammar_status_cli.rs @@ -10,6 +10,7 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() { 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["schema_version"], "1.1.0"); assert_eq!(report["codegraph_grammar_count"], 32); assert_eq!(report["codegraph_vendored_count"], 30); assert_eq!(report["candidate_count"], 32); @@ -31,6 +32,7 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() { for asset in assets { assert_eq!(asset["released"], false); assert!(!asset["gap"].as_str().unwrap().is_empty()); + assert!(!asset["known_limitations"].as_array().unwrap().is_empty()); } for language in [ "arkts", @@ -96,6 +98,16 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() { ); let zig = assets.iter().find(|row| row["language"] == "zig").unwrap(); assert_eq!(zig["gap"], "language_qualification_and_release_pending"); + let vbnet = assets + .iter() + .find(|row| row["language"] == "vbnet") + .unwrap(); + assert!( + vbnet["known_limitations"][0] + .as_str() + .unwrap() + .contains("known grammar false positive") + ); for language in ["objc", "solidity"] { let asset = assets .iter() diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index a0f149a7..bc1bf2a0 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -224,6 +224,8 @@ Use `codeguard grammar status --format=json` to query the separate source covera R, Ruby, PHP and Kotlin now also have pinned CodeGraph bytes, licenses, measured Rust ABI and narrow positive/negative worker samples; the PHP fixture covers mixed HTML/PHP. They remain unqualified candidates. The original CodeGraph Dart WASM remains incompatible with Rust, but CodeGuard now has a byte-reproducible Zig rebuild that compiles the real external scanner and uses a pinned WASI import adaptation. Rust loader and isolated-worker fixtures pass; Dart remains unqualified for public lint and release. See the [Dart rebuild record](../tests/acceptance/dart-grammar-rebuild-candidate.md). Erlang also has pinned CodeGraph bytes, upstream 0.19 license and ABI 14 with narrow Rust and worker samples. It has no qualified native comparison or public lint route; see the [Erlang candidate record](../tests/acceptance/erlang-grammar-candidate.md). Pascal now has pinned CodeGraph bytes and its original Isopod dependency commit and license; ABI 14 and narrow worker tests pass, but native comparison and public lint remain open. See the [Pascal candidate record](../tests/acceptance/pascal-grammar-candidate.md). The remaining CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET assets also load in the Rust worker, bringing the candidate inventory to 32/32 with zero released syntax capabilities. CFQuery misses `SELECT FROM`, VB.NET misparses a valid unindented method, and COBOL has a high load cost; see the [seven-asset acceptance record](../tests/acceptance/final-seven-grammar-candidates.md). +The source-built `grammar status` report now uses protocol 1.1.0 and projects each pinned candidate's bounded `known_limitations`, including the [VB.NET false-positive reproduction](../tests/acceptance/vbnet-unindented-method-false-positive.md). Manifest validation rejects overlong, excessive or control-character-bearing entries before they reach agent-facing output. This is a read-only risk signal; it cannot upgrade a recovery to a violation or close a native confirmation task. See the [projection acceptance](../tests/acceptance/grammar-known-limitations-projection.md). + The CFQuery source router now skips tags inside nested CFML comments and ordinary tag attributes or explicit CFScript bodies; it preserves tags inside HTML comments because [Adobe's CFML comment semantics](https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/elements-of-cfml/comments.html) do not make HTML comments server-side suppression. Quoted `>` characters and nested CFML comments inside a real opening tag no longer truncate its query body. This addresses a concrete false route without hiding a CFML tag that can execute; the CFQuery grammar's SQL false negative remains. See the [markup-boundary regression](../tests/acceptance/cfquery-markup-boundary.md). The Go 1.23.4 native `gofmt -e` differential corpus now compares eight valid and five malformed sources with the isolated Go WASM worker, with all 13 syntax classifications matching locally. This is a narrow syntax oracle, not type checking, `go vet`, a measured population error rate, or grammar qualification; see the [Go differential record](../tests/acceptance/go-native-differential.md). diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index e8a48110..63a0f435 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -224,6 +224,8 @@ flowchart LR R、Ruby、PHP、Kotlin 也已固定 CodeGraph 字节、许可证和 Rust 实测 ABI,并通过隔离 worker 的窄范围正反例;PHP 样例覆盖 HTML/PHP 混合内容,仍仅为未验收候选。CodeGraph 原 Dart WASM 仍不能由 Rust 直接加载;CodeGuard 现以固定上游 C 源码和真实外部 scanner 经 Zig 可重复构建,再做固定 WASI 导入适配。Rust 加载与隔离 worker 的窄范围测试通过,但公开 lint 与发行仍未验收;见[Dart 重建局部验收](../tests/acceptance/dart-grammar-rebuild-candidate.md)。Erlang 也已固定 CodeGraph 字节、上游 0.19 许可证及 ABI 14,Rust 与隔离 worker 的窄范围样例通过;原生对照和公开 lint 路由尚缺,见[Erlang 候选局部验收](../tests/acceptance/erlang-grammar-candidate.md)。Pascal 已固定 CodeGraph 字节、原始 Isopod 依赖提交和许可证,ABI 14 及窄范围 worker 样例通过;原生对照和公开 lint 尚缺,见[Pascal 候选局部验收](../tests/acceptance/pascal-grammar-candidate.md)。其余七份 CFML/CFQuery/CFScript/COBOL/Scala/Swift/VB.NET 来源 WASM 也已固定并由 Rust worker 加载,累计 32/32 份候选、0 项已发行。CFQuery 漏掉 `SELECT FROM`,VB.NET 对合法未缩进方法体误报,COBOL 成本高;都不能发布为 lint,见[七份局部验收](../tests/acceptance/final-seven-grammar-candidates.md)。 +当前源码构建的 `grammar status` 报告协议升级为 1.1.0,逐候选投影固定清单中有界的 `known_limitations`,包括[VB.NET 误报复现](../tests/acceptance/vbnet-unindented-method-false-positive.md)。清单校验会在文本进入智能体反馈前拒绝过长、过多或包含控制字符的条目。这仍是只读风险提示,不能把恢复节点升级为源码违规或关闭原生确认任务。见[投影局部验收](../tests/acceptance/grammar-known-limitations-projection.md)。 + CFQuery 源码路由现跳过嵌套 CFML 注释、普通标签属性与明确 CFScript 体中的假标签;HTML 注释中的 CFML 标签仍保留,因为 [Adobe 的 CFML 注释语义](https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/elements-of-cfml/comments.html)不把普通 HTML 注释当作服务端屏蔽。真实开标签属性中的 `>` 和开标签内的嵌套 CFML 注释也不再截断查询体。此修复避免一类误路由,同时不隐藏可能执行的 CFML 标签;CFQuery grammar 对 SQL 的漏检仍存在,见[标记边界回归](../tests/acceptance/cfquery-markup-boundary.md)。 Go 1.23.4 原生 `gofmt -e` 差分语料现将 8 份合法、5 份破损源码与隔离 Go WASM worker 逐例比较,本机 13 份语法分类一致。这只是窄范围语法 oracle,不代表类型检查、`go vet`、总体误报率测量或 grammar 已验收;见[Go 差分验收](../tests/acceptance/go-native-differential.md)。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md index deee02ec..6f7e52c5 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md @@ -76,6 +76,8 @@ CodeGuard MUST 提供只读的来源覆盖清单,区分 CodeGraph 随仓 WASM、由依赖包提供但尚未固定字节的 grammar、CodeGuard 已复制候选及已验收发行能力。覆盖清单或 `grammar status` 的存在 MUST NOT 自动改变原生检查义务、加载未经批准的字节,或把来源资产数量说成已支持语言数量。来源资产超过当前加载预算时 MUST 明示预算缺口,不能静默跳过。 +`grammar status` 的逐语言候选行 MUST 投影固定清单中的已知限制,且只能来自受控、非空、长度有界的文本;已知误报不得被泛化的“待验收”状态掩盖。该投影仍为只读风险提示,不是原生对照、违规确认或白名单决定。 + 目标范围 MUST 包含当前固定 CodeGraph 来源的 32 份独立 grammar,并包含 Zig;`jsx` 与 `javascript` 共用同一份 grammar,不重复计数。每份 grammar 都须分别完成合法再分发来源、许可、字节与 ABI、Rust 离线加载、版本/方言正反语料、原生工具对照、统一 `lint/check` 的原生优先路由、任务/对话反馈及发行包实装验收。部分候选或来源库存不能充当全部接入。若上游 WASM 与 Rust 运行时导入不兼容,适配 MUST 固定原始和派生字节、限定变换范围并以正反解析样本复验;不能回退到已知产生误报的旧 grammar 来制造可加载状态。 在正式 `lint/check` 路由验收之前,CLI MAY 暴露显式的单文件 `grammar probe` 候选观察。该命令 MUST 通过与正式路径相同的固定资产核验及隔离 worker,报告源码与 grammar 摘要、恢复锚点、未验收状态及原生确认需求;无论有无恢复节点,MUST 返回未完成,不能产生已确认违规、任务关闭或交付通过。有效语种的输入/worker 失败 MUST 使用同一版本化封闭 JSON 协议报告具体未完成原因,未知语种属于参数错误。它不能自动代替已配置的原生工具执行。 @@ -104,6 +106,11 @@ CodeGuard MUST 提供只读的来源覆盖清单,区分 CodeGraph 随仓 WASM - **WHEN** 查询固定 CodeGraph 来源中的 grammar 覆盖状态 - **THEN** 列出其来源身份、CodeGuard 集成状态及下一项缺口;不运行解析器、不签发质量或交付通过 +#### Scenario: A candidate grammar has a known false positive + +- **WHEN** 固定资产清单记录 VB.NET 等语种的具体已知误报 +- **THEN** `grammar status` 的该语种行显示有界的已知限制,并继续报告未验收、零发行和原生确认需求 + #### Scenario: CodeGraph can load a copied grammar but Rust cannot - **WHEN** Rust 运行时拒绝 ABI 或外部扫描器组合 - **THEN** 该 grammar 保持不可用并显示兼容原因,不宣称语言初检已支持 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 5299d4ff..cea3a7ac 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1097,3 +1097,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 2026-10-03 后续增量:上段插件 0.1.2 状态是 CLI 0.1.3 初发时的快照。插件仓本地提交 `7bfb07a` 已升级显式候选锁和安装器,支持 0.1.3 的 38 个成员及 32 份许可证,增加 `exec check all ABS_PROJECT`;插件候选测试 5/5,本仓离线 npm 包 32 语种统一检查回归 1/1 通过。插件仓仍需 PR、受保护 CI、tag、市场同步;默认 Python Hook、逐语言精度、任务自动复检和真实宿主反馈仍缺,13.4、14.18、14.19 不勾选。详见[更新的发行局部验收](../../../tests/acceptance/npm-0.1.3-wasm-candidate.md)。 14.4 / 14.17 / 14.19 VB.NET 精度复现:0.1.3 固定 WASM 对未缩进类方法返回一处 `MISSING ":"`,仅缩进变化的样例为零恢复;两者保持候选未验收和交付未评估。复现输入、坐标与资产身份见[局部证据](../../../tests/acceptance/vbnet-unindented-method-false-positive.md)。本机无 .NET 原生编译器,grammar 修复、原生差分与误报统计未完成,父任务不勾选。 + +14.1 / 14.7 / 14.17 / 14.19 已知限制投影:`grammar status` 逐候选返回来自固定清单的有界 `known_limitations`,VB.NET 的具体误报不再被通用待验收状态遮盖;清单解析拒绝超长、控制字符和过多条目。两项目标测试先 RED 后 GREEN,见[局部验收](../../../tests/acceptance/grammar-known-limitations-projection.md)。这不修 grammar,不证明原生对照或逐语言精度,父任务不勾选。 diff --git a/schemas/grammar-asset-manifest.schema.json b/schemas/grammar-asset-manifest.schema.json index b8d86f56..46b843d6 100644 --- a/schemas/grammar-asset-manifest.schema.json +++ b/schemas/grammar-asset-manifest.schema.json @@ -43,7 +43,7 @@ "codegraph_runtime": {"const": "web-tree-sitter 0.25.3"}, "codeguard_runtime_validation": {"const": "rust_loader_smoke_passed"}, "language_versions": {"type": "array", "maxItems": 0}, - "known_limitations": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}}, + "known_limitations": {"type": "array", "minItems": 1, "maxItems": 8, "items": {"type": "string", "minLength": 1, "maxLength": 1024}}, "release_status": {"const": "candidate_unvalidated"} } } diff --git a/tests/acceptance/grammar-known-limitations-projection.md b/tests/acceptance/grammar-known-limitations-projection.md new file mode 100644 index 00000000..3c0b59f6 --- /dev/null +++ b/tests/acceptance/grammar-known-limitations-projection.md @@ -0,0 +1,9 @@ +# 已知 grammar 限制的只读报告 + +日期:2026-10-03。对应 OpenSpec 14.1、14.7、14.17、14.19 的局部增量。 + +先写 RED 测试:`grammar status --format=json` 的逐语种行没有 `known_limitations`,而可变清单接受超过 1024 字节的限制说明。随后以固定资产清单作为只读来源,逐行投影限制并将只读报告协议升为 `1.1.0`;清单解析拒绝空限制、超过 8 条、单条超过 1024 字节或控制字符。`grammar status` 不加载 WASM,不运行原生工具,保持退出码 3、`released_count=0`、`delivery_decision=not_evaluated`。 + +目标测试:`cargo test --locked -p codeguard-adapters --test grammar_asset_manifest candidate_limitations_are_bounded_before_dialog_projection -- --exact` 和 `cargo test --locked -p codeguard-cli --test grammar_status_cli codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion -- --exact`。本轮在修改前分别失败于超长文本被接受、报告缺字段;修改后两项通过。VB.NET 行明确包含已知未缩进方法的误报提示,详见[原始复现](vbnet-unindented-method-false-positive.md)。该报告没有完成原生对照或修复 grammar,不改变候选和门禁状态。 + +扩展反例还覆盖 9 条限制与换行控制字符。完整 manifest 测试 11/11、`grammar_status_cli` 2/2、受影响 crate 的 all-targets Clippy `-D warnings`、`cargo fmt --all --check`、OpenSpec strict 和 JSON Schema 对当前清单的校验均通过。已发布的 npm 0.1.3 由旧源码构建,不包含本次报告字段;此处只证明当前源码候选,不冒称已发布行为。 From 97baffcb5fa5dba63038f3baa85b7ddf5b2ad713 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:26:56 +0800 Subject: [PATCH 18/31] fix: ignore MATLAB block comments in Objective-C routing --- crates/codeguard-cli/src/source_language_hint.rs | 11 +++++++++++ .../tests/check_all_grammar_candidates.rs | 2 +- crates/codeguard-cli/tests/detect_cli.rs | 2 +- crates/codeguard-cli/tests/grammar_route.rs | 16 ++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 +- docs/Codeguard-Technical-Design.zh_CN.md | 2 +- .../specs/syntax-precheck/spec.md | 7 +++++++ .../introduce-rust-codeguard-cli/tasks.md | 2 ++ tests/acceptance/ambiguous-grammar-extensions.md | 2 ++ 9 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/codeguard-cli/src/source_language_hint.rs b/crates/codeguard-cli/src/source_language_hint.rs index 399ebb4e..af5af15f 100644 --- a/crates/codeguard-cli/src/source_language_hint.rs +++ b/crates/codeguard-cli/src/source_language_hint.rs @@ -13,8 +13,19 @@ pub(crate) fn has_objc_marker(source: &[u8]) -> bool { b"@autoreleasepool", b"#import", ]; + let mut matlab_block_comment = false; source.split(|byte| *byte == b'\n').any(|line| { let line = line.trim_ascii_start(); + if matlab_block_comment { + if line.starts_with(b"%}") { + matlab_block_comment = false; + } + return false; + } + if line.starts_with(b"%{") { + matlab_block_comment = true; + return false; + } markers.iter().any(|marker| { line.starts_with(marker) && line.get(marker.len()).is_some_and(|next| { diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index e412ff48..54ef7047 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -70,7 +70,7 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { fs::write(root.join("script.js"), "const x = 1;\n").unwrap(); fs::write( root.join("plot.m"), - "title('@interface Foo');\nplot(1:3);\n", + "title('@interface Foo');\n%{\n#import \n@interface Fake\n%}\nplot(1:3);\n", ) .unwrap(); fs::write(root.join("synth.sc"), "{ SinOsc.ar(440) }.play;\n").unwrap(); diff --git a/crates/codeguard-cli/tests/detect_cli.rs b/crates/codeguard-cli/tests/detect_cli.rs index 3ab15582..b8637009 100644 --- a/crates/codeguard-cli/tests/detect_cli.rs +++ b/crates/codeguard-cli/tests/detect_cli.rs @@ -12,7 +12,7 @@ fn detect_keeps_shared_m_and_sc_suffixes_ambiguous_without_false_languages() { let project = TempProject::new(); fs::write( project.0.join("plot.m"), - "title('@interface Foo');\nplot(1:3);\n", + "title('@interface Foo');\n%{\n#import \n@interface Fake\n%}\nplot(1:3);\n", ) .unwrap(); fs::write(project.0.join("synth.sc"), "{ SinOsc.ar(440) }.play;\n").unwrap(); diff --git a/crates/codeguard-cli/tests/grammar_route.rs b/crates/codeguard-cli/tests/grammar_route.rs index 13ce7a4b..b1193874 100644 --- a/crates/codeguard-cli/tests/grammar_route.rs +++ b/crates/codeguard-cli/tests/grammar_route.rs @@ -73,6 +73,22 @@ fn ambiguous_and_embedded_sources_do_not_get_speculative_routes() { ) .is_empty() ); + assert!( + route_source( + "plot.m", + b"%{\n#import \n@interface Fake\n%}\nplot(1:3);\n" + ) + .is_empty() + ); + assert!(route_source("plot.m", b"%{\n#import \n").is_empty()); + assert_eq!( + route_source( + "model.m", + b"%{\n#import \n%}\n#import \n" + )[0] + .language, + "objc" + ); assert!(route_source("synth.sc", b"{ SinOsc.ar(440) }.play;\n").is_empty()); assert_eq!( route_source("model.m", b"@implementation Model\n@end\n")[0].language, diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index bc1bf2a0..5f8b6e17 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -212,7 +212,7 @@ The source-built `check all` now adds a narrower actual project report: native a } ``` -The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix. Read-only discovery now uses the same bounded `.m` evidence and records unresolved shared-suffix paths in `unknown_conditions`; `check all` retains them in its unrouted count. Project-level dialect evidence and structured per-file ambiguity reporting still need work. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. +The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix. Read-only discovery now uses the same bounded `.m` evidence and records unresolved shared-suffix paths in `unknown_conditions`; `check all` retains them in its unrouted count. The shared detector ignores Objective-C-looking lines inside MATLAB `%{ ... %}` block comments, including an unclosed block, while retaining a real marker outside the block. This reduces a concrete false route; it is not a complete MATLAB lexer. Project-level dialect evidence and structured per-file ambiguity reporting still need work. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 63a0f435..39d3b3c9 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -212,7 +212,7 @@ flowchart LR } ``` -实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析。只读发现现在使用相同的有界 `.m` 证据,把共享后缀的未判定路径记入 `unknown_conditions`;`check all` 仍将其计入未路由数量。项目级方言依据及逐文件结构化歧义报告仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 +实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析。只读发现现在使用相同的有界 `.m` 证据,把共享后缀的未判定路径记入 `unknown_conditions`;`check all` 仍将其计入未路由数量。共享判别器现跳过 MATLAB `%{ ... %}` 块注释内看似 Objective-C 的行首标记,未闭合块也不猜测,同时保留块外真实标记。这消除一类误路由,但不是完整 MATLAB 词法器。项目级方言依据及逐文件结构化歧义报告仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md index 6f7e52c5..c3a99f97 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md +++ b/openspec/changes/introduce-rust-codeguard-cli/specs/syntax-precheck/spec.md @@ -147,10 +147,17 @@ Rust runtime MUST 按需加载 grammar,在受控解析工作进程中限制输 多语言共用后缀 MUST 保留歧义:`.m` 可能属于 Objective-C 或 MATLAB,`.sc` 可能属于 Scala 或 SuperCollider。没有可核对的语言证据时,自动检查 MUST 不调用猜测的 grammar、不把恢复节点记为源码问题,并保持范围未完成;用户显式指定 `grammar probe` 的语种仍可作候选诊断。 +`.m` 文件中 MATLAB 块注释 `%{ ... %}` 内的行首 `#import` 或 `@interface` 只是注释数据,MUST NOT 作为 Objective-C 证据;块注释外的明确 Objective-C 专属标记仍可用于候选路由。发现与检查 MUST 使用同一判别,避免一个入口报告未知而另一个入口执行错误 grammar。 + #### Scenario: Shared extension would produce a speculative syntax finding - **WHEN** 项目包含无 Objective-C 专属标记的 MATLAB `.m` 源码,或 SuperCollider `.sc` 源码 - **THEN** 自动候选路由不把它们交给 Objective-C/Scala WASM;歧义范围保持未完成,不能用资产数量或其它文件的检查结果代替该范围 +#### Scenario: An Objective-C marker appears only in a MATLAB block comment + +- **WHEN** `.m` 文件的 `%{ ... %}` 块注释内含行首 `#import`,块注释外只有 MATLAB 语句 +- **THEN** `detect` 与 `check all` 均保持该文件为未解析的歧义范围,不调用 Objective-C grammar;块注释外的真实 Objective-C 标记仍可路由 + #### Scenario: Valid JSX is parsed with a TypeScript-only grammar - **WHEN** `.tsx` 文件含合法 JSX,而普通 TypeScript grammar 会产生恢复节点 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index cea3a7ac..ebfc7964 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1099,3 +1099,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 VB.NET 精度复现:0.1.3 固定 WASM 对未缩进类方法返回一处 `MISSING ":"`,仅缩进变化的样例为零恢复;两者保持候选未验收和交付未评估。复现输入、坐标与资产身份见[局部证据](../../../tests/acceptance/vbnet-unindented-method-false-positive.md)。本机无 .NET 原生编译器,grammar 修复、原生差分与误报统计未完成,父任务不勾选。 14.1 / 14.7 / 14.17 / 14.19 已知限制投影:`grammar status` 逐候选返回来自固定清单的有界 `known_limitations`,VB.NET 的具体误报不再被通用待验收状态遮盖;清单解析拒绝超长、控制字符和过多条目。两项目标测试先 RED 后 GREEN,见[局部验收](../../../tests/acceptance/grammar-known-limitations-projection.md)。这不修 grammar,不证明原生对照或逐语言精度,父任务不勾选。 + +14.4 / 14.6 / 14.19 MATLAB 块注释误路由局部修复:`.m` 内 `%{ ... %}` 的行首 `#import` / `@interface` 不再作为 Objective-C 证据;块外真实标记保留。先 RED 后修复的候选路由、默认发现和真实 `check all` 回归通过,歧义范围继续报告未完成;见[共享后缀局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。完整 MATLAB 词法和逐项目方言选择尚缺,父任务不勾选。 diff --git a/tests/acceptance/ambiguous-grammar-extensions.md b/tests/acceptance/ambiguous-grammar-extensions.md index 4067c597..2f8b846b 100644 --- a/tests/acceptance/ambiguous-grammar-extensions.md +++ b/tests/acceptance/ambiguous-grammar-extensions.md @@ -9,3 +9,5 @@ 后续补齐默认构建的静态发现:只读 `detect` 对 `.m` 最多读取 1 MiB 普通源码,与路由共用行首标记判别;无法读取时记录阻塞,不推断语言。无标记 `.m` 与 `.sc` 不进入错误语言清单,`unknown_conditions` 保留具体相对路径。`check all` 仍将这些路径计入候选源范围,报告 `unrouted_count=2`,且总交付未完成。真实 `detect` 回归、混合项目 `check all` 回归及仅含两份歧义源码的项目回归分别通过;后者证实 `source_file_count=2`、`status=not_run`、`reason=unrouted_source` 和 `delivery_decision=incomplete`,不会把空候选结果当成 clean。 这只减少错误语言归类和自动候选误路由;项目级 `.sc` 方言证据、无专属标记的合法 Objective-C `.m`、更强的词法判别、逐文件结构化歧义协议及原生优先/发行验收仍缺,不能宣布这两个方言已完整支持。 + +2026-10-03 增量:MATLAB `%{ ... %}` 块注释内可以出现行首 `#import`、`@interface` 示例。先将真实块注释样例加入共享后缀路由测试,旧判别错误把它交给 Objective-C,目标测试 RED;现在只在块注释外寻找行首 Objective-C 专属标记,块注释未闭合时也不猜测。块注释外真实 `#import` 仍可路由。`grammar_route`、只读 `detect` 和真实 `check all` 三条目标回归分别通过;混合项目继续对该 MATLAB 文件保留未解析范围,不产生 Objective-C 候选观察。这是对同一只读判别器的局部修复,尚不覆盖 MATLAB 完整词法、块注释边界与跨项目方言证据,14.4/14.6/14.19 不勾选。 From bb2e09f0a4b396561b469b32dc4f5450f291143a Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:33:55 +0800 Subject: [PATCH 19/31] test: add Rust grammar native differential evidence --- README.md | 2 +- README.zh-CN.md | 2 +- .../tests/rust_native_differential.rs | 130 ++++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + .../introduce-rust-codeguard-cli/tasks.md | 2 + tests/acceptance/rust-native-differential.md | 11 ++ 7 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 crates/codeguard-cli/tests/rust_native_differential.rs create mode 100644 tests/acceptance/rust-native-differential.md diff --git a/README.md b/README.md index 283ffbcd..73b562d2 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ The source-built `grammar status` now includes each candidate's bounded `known_l In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` can explicitly run any of the 32 pinned candidates in an isolated worker. It always exits 3 with `status=incomplete`, `native.status=not_run`, and `delivery_decision=not_evaluated`; a completed parse also has `precheck.status=incomplete`. Input or worker failures use the same [closed JSON schema](schemas/grammar-probe-v0.1.schema.json). Recovery anchors are suspected observations only. This diagnostic command does not yet connect all grammars to language-qualified native-first `lint/check`; the published npm package includes it as an unqualified candidate. -The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback; published npm `0.1.3` includes this candidate route. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). A [Ruby 2.6.10 differential](tests/acceptance/ruby-native-differential.md) agrees on 13 narrow syntax cases; a [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. +The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback; published npm `0.1.3` includes this candidate route. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). A [Rust 2021 differential against rustfmt 1.9.0](tests/acceptance/rust-native-differential.md) agrees on 13 narrow syntax cases; this is not Rust lint qualification. A [Ruby 2.6.10 differential](tests/acceptance/ruby-native-differential.md) agrees on 13 narrow syntax cases; a [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. The source-built Unix CLI also has a narrow Zig route: `codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`. An explicitly supplied tool reporting Zig 0.16.0 and retaining the same byte digest runs native `ast-check` first; its source positions are retained without exposing source snippets. Without an explicit tool, the pinned Zig WASM provides an unqualified observation. Both paths remain incomplete because `ast-check` covers only local AST errors, not full lint, build or tests. See the [Zig feedback schema](schemas/zig-lint-feedback-v0.1.schema.json). diff --git a/README.zh-CN.md b/README.zh-CN.md index 7cc86a62..f7a28a63 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -104,7 +104,7 @@ cargo build --locked -p codeguard-cli 源码以 `--features wasm-precheck` 构建后,可显式运行 `codeguard grammar probe --format=json`,由隔离 worker 调用 32 份固定候选中的任意一份。命令始终以退出码 3 返回,并标记 `status=incomplete`、`native.status=not_run`、`delivery_decision=not_evaluated`;解析完成时还标记 `precheck.status=incomplete`。输入或 worker 失败也遵守同一份[封闭 JSON Schema](schemas/grammar-probe-v0.1.schema.json)。恢复锚点只是疑似观察。此诊断入口尚未把全部语种接入已验收的原生优先 `lint/check`;已发布 npm 包仅把它作为未验收候选提供。 -同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底;已发布 npm `0.1.3` 包含该候选路径;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)在 13 个窄范围语法样例上一致;[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 +同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底;已发布 npm `0.1.3` 包含该候选路径;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[Rust 2021 与 rustfmt 1.9.0 差分](tests/acceptance/rust-native-differential.md)在 13 个窄范围样例上一致,但不代表 Rust lint 已验收。[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)在 13 个窄范围语法样例上一致;[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 源码构建的 Unix CLI 还提供局部 Zig 入口:`codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`。显式提供且报告 Zig 0.16.0、执行前后字节摘要一致的工具优先运行原生 `ast-check`,仅输出诊断位置,不回显源码;未显式提供工具时,固定 Zig WASM 给出未验收候选观察。两条路径都保持未完成,因为 `ast-check` 仅覆盖局部 AST 错误,不等于完整 lint、构建或测试。见 [Zig 报告 Schema](schemas/zig-lint-feedback-v0.1.schema.json)。 diff --git a/crates/codeguard-cli/tests/rust_native_differential.rs b/crates/codeguard-cli/tests/rust_native_differential.rs new file mode 100644 index 00000000..f160ef6e --- /dev/null +++ b/crates/codeguard-cli/tests/rust_native_differential.rs @@ -0,0 +1,130 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::path::Path; +use std::process::Command; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("function", "fn main() {}\n", true), + ("generic", "fn id(x: T) -> T { x }\n", true), + ("async", "async fn fetch() -> u8 { 1 }\n", true), + ("macro", "macro_rules! one { () => { 1 }; }\n", true), + ("raw_string", "const S: &str = r#\"hello\"#;\n", true), + ( + "lifetime", + "fn same<'a>(x: &'a str) -> &'a str { x }\n", + true, + ), + ( + "match_guard", + "fn f(x: i32) -> i32 { match x { n if n > 0 => n, _ => 0 } }\n", + true, + ), + ( + "const_generic", + "struct Buf([u8; N]);\n", + true, + ), + ("missing_brace", "fn main() {\n", false), + ("missing_paren", "fn f( { }\n", false), + ("unclosed_string", "const S: &str = \"oops;\n", false), + ("missing_expr", "fn f() { let x = ; }\n", false), + ("bad_match", "fn f(x: i32) { match x { => 1 } }\n", false), + ] +} + +fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool { + let file = root.join(format!("{name}.rs")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "rust"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report["recoveries"].as_array().unwrap().is_empty() +} + +#[test] +fn rust_worker_preserves_native_labeled_syntax_corpus() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-rust-corpus-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + assert_eq!( + candidate_is_valid(&root, name, source), + expected_valid, + "{name}" + ); + } + fs::remove_dir_all(root).unwrap(); +} + +#[test] +#[ignore = "requires explicit existing rustfmt 1.9.0-stable via CODEGUARD_RUSTFMT_BIN"] +fn pinned_rust_worker_matches_native_rustfmt_on_syntax_corpus() { + let rustfmt = + std::env::var("CODEGUARD_RUSTFMT_BIN").expect("provide an existing rustfmt executable"); + let version = Command::new(&rustfmt).arg("--version").output().unwrap(); + assert!(version.status.success()); + assert!( + String::from_utf8_lossy(&version.stdout).starts_with("rustfmt 1.9.0-stable (48a229ceae "), + "unexpected rustfmt version: {}", + String::from_utf8_lossy(&version.stdout) + ); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-rust-differential-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + let file = root.join(format!("{name}.rs")); + fs::write(&file, source).unwrap(); + let native = Command::new(&rustfmt) + .args(["--emit", "stdout", "--edition", "2021"]) + .arg(&file) + .output() + .unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1 | 101)), + "{name}: {native:?}" + ); + if !native.status.success() { + let diagnostic = String::from_utf8_lossy(&native.stderr); + assert!( + diagnostic.contains("error") && !diagnostic.contains("panicked"), + "{name}: {native:?}" + ); + } + assert_eq!( + native.status.success(), + expected_valid, + "{name}: rustfmt disagrees with corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + assert_eq!( + fs::read_to_string(&file).unwrap(), + source, + "{name}: native mutated source" + ); + assert_eq!( + candidate_is_valid(&root, name, source), + native.status.success(), + "{name}: rustfmt={} worker classification differs", + String::from_utf8_lossy(&native.stderr) + ); + } + fs::remove_dir_all(root).unwrap(); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 5f8b6e17..97147a00 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -234,6 +234,8 @@ The JavaScript module corpus similarly compares Node 24.18.0 `--check --input-ty For Go under `check all`, a completed Go 1.23.4 `go vet` now preempts duplicate WASM only for files selected by a bounded `go list -json ./...` under the same default build conditions and matching source, module and tool identities. Build-tag-excluded files still reach the candidate worker; malformed package scope or changed inputs cannot preempt it. A real Go integration and a forged-scope regression cover this boundary, while the overall delivery remains incomplete; see the [Go native-first record](../tests/acceptance/check-all-native-preferred-go.md). +Rust 2021 `rustfmt 1.9.0 --emit stdout` and the isolated Rust worker classify the same eight valid and five broken syntax samples consistently; the bounded [Rust differential record](../tests/acceptance/rust-native-differential.md) does not qualify Clippy, compilation or other editions. + Ruby 2.6.10 `ruby -c` agrees with the isolated Ruby worker on a narrow 13-case corpus, with the fixed worker corpus running in ordinary CI; this does not qualify Ruby lint or other versions. By contrast, Apple Swift 6.4 `swiftc -frontend -parse` rejects `func f(_ x: ) {}` as a missing parameter type while the fixed Swift WASM emits no recovery. The 13-case Swift differential has 12 agreements and this one false negative. The candidate remains unqualified and cannot claim a clean syntax result; see the [Ruby](../tests/acceptance/ruby-native-differential.md) and [Swift](../tests/acceptance/swift-native-differential.md) records. 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. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 39d3b3c9..f0f18195 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -234,6 +234,8 @@ JavaScript 模块语料也用 Node 24.18.0 `--check --input-type=module` 与隔 `check all` 的 Go 路径现仅在 Go 1.23.4 `go vet` 完成、受控 `go list -json ./...` 证明文件进入同一默认构建范围,且源码、模块与工具身份均匹配时,跳过该文件的重复 WASM。构建标签排除的文件仍进入候选 worker;包清单越界或输入变化不能触发跳过。真实 Go 集成与伪造范围回归已覆盖边界,但整体交付仍未完成;见[Go 原生优先验收](../tests/acceptance/check-all-native-preferred-go.md)。 +Rust 2021 的 `rustfmt 1.9.0 --emit stdout` 与隔离 Rust worker 对 8 份合法和 5 份破损语法样例分类一致;这份有界的[Rust 差分记录](../tests/acceptance/rust-native-differential.md)不等于 Clippy、完整编译或其它 edition 已验收。 + Ruby 2.6.10 `ruby -c` 与隔离 Ruby worker 在 13 例窄范围语料上分类一致,固定 worker 语料进入常规 CI;这不等于 Ruby lint 或其它版本验收。相反,Apple Swift 6.4 `swiftc -frontend -parse` 把 `func f(_ x: ) {}` 判为缺少参数类型,固定 Swift WASM 却没有恢复节点。Swift 13 例差分中 12 例一致、1 例漏检,候选仍不可签发语法通过;见[Ruby](../tests/acceptance/ruby-native-differential.md)与[Swift](../tests/acceptance/swift-native-differential.md)局部记录。 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index ebfc7964..b00373ef 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1101,3 +1101,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.1 / 14.7 / 14.17 / 14.19 已知限制投影:`grammar status` 逐候选返回来自固定清单的有界 `known_limitations`,VB.NET 的具体误报不再被通用待验收状态遮盖;清单解析拒绝超长、控制字符和过多条目。两项目标测试先 RED 后 GREEN,见[局部验收](../../../tests/acceptance/grammar-known-limitations-projection.md)。这不修 grammar,不证明原生对照或逐语言精度,父任务不勾选。 14.4 / 14.6 / 14.19 MATLAB 块注释误路由局部修复:`.m` 内 `%{ ... %}` 的行首 `#import` / `@interface` 不再作为 Objective-C 证据;块外真实标记保留。先 RED 后修复的候选路由、默认发现和真实 `check all` 回归通过,歧义范围继续报告未完成;见[共享后缀局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。完整 MATLAB 词法和逐项目方言选择尚缺,父任务不勾选。 + +14.4 / 14.17 / 14.19 Rust 原生差分局部增量:固定本机 rustfmt 1.9.0 与 Rust 2021 的 8 份合法、5 份破损语料,经公开隔离 Rust worker 对照,13/13 分类一致;普通语料测试和显式原生差分测试均 1/1 通过。原生未闭合字符串的退出 101 须伴随语法错误诊断,不将崩溃冒充拒绝。仅证明窄范围局部一致,完整编译/Clippy、其它 edition、系统误报率与发行/宿主验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/rust-native-differential.md)。 diff --git a/tests/acceptance/rust-native-differential.md b/tests/acceptance/rust-native-differential.md new file mode 100644 index 00000000..76677b56 --- /dev/null +++ b/tests/acceptance/rust-native-differential.md @@ -0,0 +1,11 @@ +# Rust grammar 与原生 rustfmt 的局部语法差分 + +日期:2026-10-03。对应 OpenSpec 14.4、14.17、14.19 的局部增量,不是 Rust lint 验收。 + +固定本机 `rustfmt 1.9.0-stable (48a229ceae 2026-09-01)`,实际程序位于 Rustup stable toolchain,SHA-256 `98e8da71078a8b5710f1818c98da25d92de162a122bd9a6db222ca929e545468`。用 `rustfmt --emit stdout --edition 2021 FILE` 只读解析每份样例;CodeGuard 对同一普通源码调用公开 `grammar probe rust FILE --format=json`,并要求真实 worker 返回候选观察、零授权和退出码 3。 + +共 13 例:8 份合法样例覆盖普通函数、泛型、async、macro_rules、raw string、生命周期、match guard 与 const generic;5 份破损样例覆盖缺失大括号、圆括号、未闭合字符串、缺失表达式与破损 match。原生与候选的合法/破损分类全部一致。普通固定语料测试 1/1 通过;显式设置 `CODEGUARD_RUSTFMT_BIN=/opt/homebrew/opt/rustup/bin/rustfmt` 运行忽略的真实原生差分测试 1/1 通过。测试还核对 rustfmt 不修改源文件。 + +初次原生差分曾错误地只接受 rustfmt 退出 0/1;未闭合字符串实际返回 101 并给出 `error[E0765]`。测试现仅在非零状态伴随错误诊断、且没有 panic 文本时把它认作源码拒绝,防止工具崩溃被计为语法命中。 + +此对照只覆盖本机 rustfmt 版本及 Rust 2021 的 13 份小样例。`rustfmt` 的解析接受与完整 `rustc` 类型/构建检查、Clippy 规则和其它 edition 不等价;当前 grammar 仍 `grammar_qualified=false`,零恢复节点仍不表示原生 lint 或交付通过。尚无独立 holdout、系统误报/漏报率、跨平台版本、完整原生优先路由和宿主反馈,父任务不勾选。 From 0cbf08b23eb6ac25d99d78e7ed678f041bcf68b3 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:45:22 +0800 Subject: [PATCH 20/31] test: require clean recovery on all grammar smoke cases --- crates/codeguard-cli/tests/check_all_grammar_candidates.rs | 6 ++++++ openspec/changes/introduce-rust-codeguard-cli/tasks.md | 2 ++ tests/acceptance/check-all-32-grammar-candidates.md | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index 54ef7047..f96daeb5 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -256,6 +256,12 @@ fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { let observations = report["syntax_candidates"]["observations"] .as_array() .unwrap(); + for observation in observations { + assert_eq!( + observation["recovery_count"], 0, + "group {group_index}: unexpected recovery in valid sample: {observation}" + ); + } for language in observations .iter() .filter(|item| item["status"] == "candidate_observed") diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index b00373ef..3b7d5d2c 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1103,3 +1103,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.6 / 14.19 MATLAB 块注释误路由局部修复:`.m` 内 `%{ ... %}` 的行首 `#import` / `@interface` 不再作为 Objective-C 证据;块外真实标记保留。先 RED 后修复的候选路由、默认发现和真实 `check all` 回归通过,歧义范围继续报告未完成;见[共享后缀局部验收](../../../tests/acceptance/ambiguous-grammar-extensions.md)。完整 MATLAB 词法和逐项目方言选择尚缺,父任务不勾选。 14.4 / 14.17 / 14.19 Rust 原生差分局部增量:固定本机 rustfmt 1.9.0 与 Rust 2021 的 8 份合法、5 份破损语料,经公开隔离 Rust worker 对照,13/13 分类一致;普通语料测试和显式原生差分测试均 1/1 通过。原生未闭合字符串的退出 101 须伴随语法错误诊断,不将崩溃冒充拒绝。仅证明窄范围局部一致,完整编译/Clippy、其它 edition、系统误报率与发行/宿主验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/rust-native-differential.md)。 + +14.4 / 14.17 / 14.19 全量最小正例收紧:分批 `check all` 运行 32 份固定 grammar 的既有样例,现在逐观察要求 `recovery_count=0`;本机目标测试 1/1 通过,确保路由虽成功却对合法样例产生恢复节点时不能假装验收成功。该语料规模很小,不等于逐语言原生 oracle、版本/方言覆盖或系统误报率,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 diff --git a/tests/acceptance/check-all-32-grammar-candidates.md b/tests/acceptance/check-all-32-grammar-candidates.md index 86ff4213..65e6055f 100644 --- a/tests/acceptance/check-all-32-grammar-candidates.md +++ b/tests/acceptance/check-all-32-grammar-candidates.md @@ -2,7 +2,7 @@ 日期:2026-09-29。对应 OpenSpec S14.6、14.7、14.17、14.19 的局部进展,父任务均未完成。 -源码以 `--features wasm-precheck` 构建后,`check all` 先运行现有原生节点,再对发现的普通源码执行有界候选阶段。新增路由按文件后缀选择 30 份直接 grammar,并对 `.cfs` 选 CFScript、完整 `...` 标签体选 CFQuery;`.tsx` 使用 TSX、`.js` 使用 JavaScript。`.h` 与普通 `.sql` 不凭后缀猜测。跨平台测试把 31 个源码文件分为四个有界项目,四次真实 `check all` 的报告合计出现 32 个 `candidate_observed`,语言并集与固定资产清单完全相同。每次保持退出码 3、`delivery_decision=incomplete`、`grammar_qualified=false`、`skipped_count=0`。 +源码以 `--features wasm-precheck` 构建后,`check all` 先运行现有原生节点,再对发现的普通源码执行有界候选阶段。新增路由按文件后缀选择 30 份直接 grammar,并对 `.cfs` 选 CFScript、完整 `...` 标签体选 CFQuery;`.tsx` 使用 TSX、`.js` 使用 JavaScript。`.h` 与普通 `.sql` 不凭后缀猜测。跨平台测试把 31 个源码文件分为四个有界项目,四次真实 `check all` 的报告合计出现 32 个 `candidate_observed`,语言并集与固定资产清单完全相同。2026-10-03 增加逐观察断言:这批最小合法样例的 `recovery_count` 全部为 0;目标测试 1/1 通过。每次保持退出码 3、`delivery_decision=incomplete`、`grammar_qualified=false`、`skipped_count=0`。这只证明固定最小样例没有恢复节点,不证明实际项目、语言版本或方言的误报率。 最初 macOS 单项目 31 文件样例在 45 秒预算内只完成 26 种,Swift 超时,后续 5 文件被跳过;预算调至 90 秒后,同机 54.09 秒完成 32 种。Linux CI 对同一单项目样例在 90 秒内仍只完成 26/32,故正式跨平台测试改为四个有界项目;本机四次调用合计 54.48 秒。该时间包括既有原生节点、每份资产的隔离进程及高成本 COBOL;不是冷暖启动统计、性能承诺或所有平台的内存验收。候选阶段限制 64 个文件、64 个片段,每个 worker 限 1 MiB 源码和 64 KiB 输出;未观察范围与原生阻塞保持未完成。 From 7e2b4ebb851752a8934057e13f9f9fa3454d94a8 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:50:24 +0800 Subject: [PATCH 21/31] test: compare C grammar with native Clang syntax --- README.md | 2 +- README.zh-CN.md | 2 +- .../tests/c_native_differential.rs | 137 ++++++++++++++++++ docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + .../introduce-rust-codeguard-cli/tasks.md | 2 + tests/acceptance/c-native-differential.md | 11 ++ 7 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 crates/codeguard-cli/tests/c_native_differential.rs create mode 100644 tests/acceptance/c-native-differential.md diff --git a/README.md b/README.md index 73b562d2..b0a3f332 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ The source-built `grammar status` now includes each candidate's bounded `known_l In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` can explicitly run any of the 32 pinned candidates in an isolated worker. It always exits 3 with `status=incomplete`, `native.status=not_run`, and `delivery_decision=not_evaluated`; a completed parse also has `precheck.status=incomplete`. Input or worker failures use the same [closed JSON schema](schemas/grammar-probe-v0.1.schema.json). Recovery anchors are suspected observations only. This diagnostic command does not yet connect all grammars to language-qualified native-first `lint/check`; the published npm package includes it as an unqualified candidate. -The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback; published npm `0.1.3` includes this candidate route. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). A [Rust 2021 differential against rustfmt 1.9.0](tests/acceptance/rust-native-differential.md) agrees on 13 narrow syntax cases; this is not Rust lint qualification. A [Ruby 2.6.10 differential](tests/acceptance/ruby-native-differential.md) agrees on 13 narrow syntax cases; a [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. +The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback; published npm `0.1.3` includes this candidate route. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). Narrow 13-case syntax differentials agree for [C11 against Apple Clang 21](tests/acceptance/c-native-differential.md), [Rust 2021 against rustfmt 1.9.0](tests/acceptance/rust-native-differential.md), and [Ruby 2.6.10](tests/acceptance/ruby-native-differential.md); none qualifies native lint. A [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. The source-built Unix CLI also has a narrow Zig route: `codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`. An explicitly supplied tool reporting Zig 0.16.0 and retaining the same byte digest runs native `ast-check` first; its source positions are retained without exposing source snippets. Without an explicit tool, the pinned Zig WASM provides an unqualified observation. Both paths remain incomplete because `ast-check` covers only local AST errors, not full lint, build or tests. See the [Zig feedback schema](schemas/zig-lint-feedback-v0.1.schema.json). diff --git a/README.zh-CN.md b/README.zh-CN.md index f7a28a63..2a8ca760 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -104,7 +104,7 @@ cargo build --locked -p codeguard-cli 源码以 `--features wasm-precheck` 构建后,可显式运行 `codeguard grammar probe --format=json`,由隔离 worker 调用 32 份固定候选中的任意一份。命令始终以退出码 3 返回,并标记 `status=incomplete`、`native.status=not_run`、`delivery_decision=not_evaluated`;解析完成时还标记 `precheck.status=incomplete`。输入或 worker 失败也遵守同一份[封闭 JSON Schema](schemas/grammar-probe-v0.1.schema.json)。恢复锚点只是疑似观察。此诊断入口尚未把全部语种接入已验收的原生优先 `lint/check`;已发布 npm 包仅把它作为未验收候选提供。 -同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底;已发布 npm `0.1.3` 包含该候选路径;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[Rust 2021 与 rustfmt 1.9.0 差分](tests/acceptance/rust-native-differential.md)在 13 个窄范围样例上一致,但不代表 Rust lint 已验收。[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)在 13 个窄范围语法样例上一致;[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 +同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底;已发布 npm `0.1.3` 包含该候选路径;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[C11 与 Apple Clang 21 差分](tests/acceptance/c-native-differential.md)、[Rust 2021 与 rustfmt 1.9.0 差分](tests/acceptance/rust-native-differential.md)和[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)各在 13 个窄范围语法样例上一致,但不代表对应语言的原生 lint 已验收。[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 源码构建的 Unix CLI 还提供局部 Zig 入口:`codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`。显式提供且报告 Zig 0.16.0、执行前后字节摘要一致的工具优先运行原生 `ast-check`,仅输出诊断位置,不回显源码;未显式提供工具时,固定 Zig WASM 给出未验收候选观察。两条路径都保持未完成,因为 `ast-check` 仅覆盖局部 AST 错误,不等于完整 lint、构建或测试。见 [Zig 报告 Schema](schemas/zig-lint-feedback-v0.1.schema.json)。 diff --git a/crates/codeguard-cli/tests/c_native_differential.rs b/crates/codeguard-cli/tests/c_native_differential.rs new file mode 100644 index 00000000..619746cd --- /dev/null +++ b/crates/codeguard-cli/tests/c_native_differential.rs @@ -0,0 +1,137 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::path::Path; +use std::process::Command; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("function", "int main(void) { return 0; }\n", true), + ("pointer", "int f(const int *x) { return *x; }\n", true), + ("structure", "struct Point { int x; int y; };\n", true), + ( + "typedef", + "typedef unsigned long Count; Count n = 0;\n", + true, + ), + ("enumeration", "enum Color { RED, BLUE };\n", true), + ( + "compound_literal", + "struct P { int x; }; struct P p = (struct P){1};\n", + true, + ), + ( + "designated", + "struct P { int x; }; struct P p = {.x = 1};\n", + true, + ), + ( + "preprocessor", + "#define VALUE 1\nint f(void) { return VALUE; }\n", + true, + ), + ("missing_brace", "int main(void) { return 0;\n", false), + ("missing_semicolon", "int x = 1\n", false), + ( + "missing_expr", + "int f(void) { int x = ; return x; }\n", + false, + ), + ("unclosed_string", "char *s = \"oops;\n", false), + ("bad_declarator", "int f( { return 1; }\n", false), + ] +} + +fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool { + let file = root.join(format!("{name}.c")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "c"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report["recoveries"].as_array().unwrap().is_empty() +} + +#[test] +fn c_worker_preserves_native_labeled_syntax_corpus() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-c-corpus-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + assert_eq!( + candidate_is_valid(&root, name, source), + expected_valid, + "{name}" + ); + } + fs::remove_dir_all(root).unwrap(); +} + +#[test] +#[ignore = "requires explicit existing Apple clang 21 via CODEGUARD_CLANG_BIN"] +fn pinned_c_worker_matches_native_clang_on_syntax_corpus() { + let clang = std::env::var("CODEGUARD_CLANG_BIN").expect("provide an existing clang executable"); + let version = Command::new(&clang).arg("--version").output().unwrap(); + assert!(version.status.success()); + assert!( + String::from_utf8_lossy(&version.stdout) + .starts_with("Apple clang version 21.0.0 (clang-2100.3.34.2)"), + "unexpected clang version: {}", + String::from_utf8_lossy(&version.stdout) + ); + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-c-differential-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + let file = root.join(format!("{name}.c")); + fs::write(&file, source).unwrap(); + let native = Command::new(&clang) + .args(["-fsyntax-only", "-std=c11", "-Wno-everything", "-x", "c"]) + .arg(&file) + .output() + .unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1)), + "{name}: {native:?}" + ); + if !native.status.success() { + assert!( + String::from_utf8_lossy(&native.stderr).contains("error:"), + "{name}: {native:?}" + ); + } + assert_eq!( + native.status.success(), + expected_valid, + "{name}: clang disagrees with corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + assert_eq!( + fs::read_to_string(&file).unwrap(), + source, + "{name}: native mutated source" + ); + assert_eq!( + candidate_is_valid(&root, name, source), + native.status.success(), + "{name}: clang={} worker classification differs", + String::from_utf8_lossy(&native.stderr) + ); + } + fs::remove_dir_all(root).unwrap(); +} diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 97147a00..eda0ddd4 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -236,6 +236,8 @@ For Go under `check all`, a completed Go 1.23.4 `go vet` now preempts duplicate Rust 2021 `rustfmt 1.9.0 --emit stdout` and the isolated Rust worker classify the same eight valid and five broken syntax samples consistently; the bounded [Rust differential record](../tests/acceptance/rust-native-differential.md) does not qualify Clippy, compilation or other editions. +C11 `Apple clang 21 -fsyntax-only` and the isolated C worker classify eight valid and five syntax-broken samples consistently. The initial corpus mistakenly treated a return-type error as a syntax error, so it was replaced with a missing initializer expression; the bounded [C differential record](../tests/acceptance/c-native-differential.md) does not qualify semantic diagnostics, C lint or other language versions. + Ruby 2.6.10 `ruby -c` agrees with the isolated Ruby worker on a narrow 13-case corpus, with the fixed worker corpus running in ordinary CI; this does not qualify Ruby lint or other versions. By contrast, Apple Swift 6.4 `swiftc -frontend -parse` rejects `func f(_ x: ) {}` as a missing parameter type while the fixed Swift WASM emits no recovery. The 13-case Swift differential has 12 agreements and this one false negative. The candidate remains unqualified and cannot claim a clean syntax result; see the [Ruby](../tests/acceptance/ruby-native-differential.md) and [Swift](../tests/acceptance/swift-native-differential.md) records. 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. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index f0f18195..ad55a44e 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -236,6 +236,8 @@ JavaScript 模块语料也用 Node 24.18.0 `--check --input-type=module` 与隔 Rust 2021 的 `rustfmt 1.9.0 --emit stdout` 与隔离 Rust worker 对 8 份合法和 5 份破损语法样例分类一致;这份有界的[Rust 差分记录](../tests/acceptance/rust-native-differential.md)不等于 Clippy、完整编译或其它 edition 已验收。 +C11 的 `Apple clang 21 -fsyntax-only` 与隔离 C worker 对 8 份合法和 5 份语法破损样例分类一致。初版语料曾误把返回类型错误当成语法错误,现已改成缺失初始化表达式;这份有界的[C 差分记录](../tests/acceptance/c-native-differential.md)不等于语义诊断、C lint 或其它语言版本已验收。 + Ruby 2.6.10 `ruby -c` 与隔离 Ruby worker 在 13 例窄范围语料上分类一致,固定 worker 语料进入常规 CI;这不等于 Ruby lint 或其它版本验收。相反,Apple Swift 6.4 `swiftc -frontend -parse` 把 `func f(_ x: ) {}` 判为缺少参数类型,固定 Swift WASM 却没有恢复节点。Swift 13 例差分中 12 例一致、1 例漏检,候选仍不可签发语法通过;见[Ruby](../tests/acceptance/ruby-native-differential.md)与[Swift](../tests/acceptance/swift-native-differential.md)局部记录。 由父进程控制解析工作进程,设置总截止时间、单文件输入上限、内存/进程限制及诊断数量上限。按需加载 grammar,不提供通用网络/文件系统导入;终止卡住的进程时保留其它模块的原生结果。宿主约束执行和 Rust MSRV 兼容性须测试,生产数值预算须经测量后确定,不编造性能承诺。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 3b7d5d2c..a7c31286 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1105,3 +1105,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 Rust 原生差分局部增量:固定本机 rustfmt 1.9.0 与 Rust 2021 的 8 份合法、5 份破损语料,经公开隔离 Rust worker 对照,13/13 分类一致;普通语料测试和显式原生差分测试均 1/1 通过。原生未闭合字符串的退出 101 须伴随语法错误诊断,不将崩溃冒充拒绝。仅证明窄范围局部一致,完整编译/Clippy、其它 edition、系统误报率与发行/宿主验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/rust-native-differential.md)。 14.4 / 14.17 / 14.19 全量最小正例收紧:分批 `check all` 运行 32 份固定 grammar 的既有样例,现在逐观察要求 `recovery_count=0`;本机目标测试 1/1 通过,确保路由虽成功却对合法样例产生恢复节点时不能假装验收成功。该语料规模很小,不等于逐语言原生 oracle、版本/方言覆盖或系统误报率,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 + +14.4 / 14.17 / 14.19 C11 原生差分局部增量:Apple Clang 21 `-fsyntax-only` 与隔离 C worker 在 8 份合法、5 份语法破损样例上 13/13 分类一致;普通语料和显式原生差分测试各 1/1 通过。初版语料中的返回类型错误是语义诊断,已改为纯语法缺失初始化表达式,不把语义拒绝当成 grammar 漏检。其它 C 版本、系统误报率、完整原生 lint/路由、发行与宿主验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/c-native-differential.md)。 diff --git a/tests/acceptance/c-native-differential.md b/tests/acceptance/c-native-differential.md new file mode 100644 index 00000000..8b37002b --- /dev/null +++ b/tests/acceptance/c-native-differential.md @@ -0,0 +1,11 @@ +# C grammar 与原生 Clang 的局部语法差分 + +日期:2026-10-03。对应 OpenSpec 14.4、14.17、14.19 的局部增量,不是 C lint 验收。 + +固定本机 Apple clang `21.0.0 (clang-2100.3.34.2)`,实际程序 `/Library/Developer/CommandLineTools/usr/bin/clang`,SHA-256 `1590ac950a3d627817d09ade5cb60b2115f17a72182a3141e010b4bcc482a0c9`。用 `clang -fsyntax-only -std=c11 -Wno-everything -x c FILE` 只读检查每份样例;CodeGuard 对同一源码调用公开 `grammar probe c FILE --format=json`,要求隔离 worker 返回候选观察、零授权和退出码 3。 + +共 13 例:8 份合法样例覆盖普通函数、指针、结构体、typedef、枚举、复合字面量、指定初始化与预处理宏;5 份破损样例覆盖缺失大括号、分号、初始化表达式、未闭合字符串与错误声明符。Clang 与候选的合法/破损分类全部一致。普通固定语料测试 1/1 通过;显式设置 `CODEGUARD_CLANG_BIN=/Library/Developer/CommandLineTools/usr/bin/clang` 运行忽略的真实原生差分测试 1/1 通过。测试核对 Clang 不修改源文件。 + +首次语料把 `int f(void) { return ; + 1; }` 误标为语法错误:Clang 因返回类型拒绝,但 Tree-sitter 对该语法不产生恢复节点。这是语义差异,不作为 grammar 漏检。改成 `int x = ;` 后两者同为语法拒绝。测试避免把编译器的类型/语义检查与语法恢复等同。 + +此对照只覆盖本机 Clang 版本及 C11 的 13 份小样例。`-fsyntax-only` 还会执行语义检查;语料标签已经人工核对为语法范围,但不代表 Clang 诊断均是 grammar 可检的内容。当前 grammar 仍 `grammar_qualified=false`,零恢复节点不表示原生 lint 或交付通过。尚无独立 holdout、系统误报/漏报率、跨平台版本、完整原生优先路由和宿主反馈,父任务不勾选。 From 12ed33078099e30d8376d24147ae7e2f669d564d Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:53:10 +0800 Subject: [PATCH 22/31] docs: align grammar limitations with differential evidence --- .../codeguard-cli/tests/grammar_status_cli.rs | 21 +++++++++++++++++++ grammars/manifest.json | 14 +++++++------ .../introduce-rust-codeguard-cli/tasks.md | 2 ++ .../grammar-known-limitations-projection.md | 2 ++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/crates/codeguard-cli/tests/grammar_status_cli.rs b/crates/codeguard-cli/tests/grammar_status_cli.rs index 18a95f5e..c3f100a6 100644 --- a/crates/codeguard-cli/tests/grammar_status_cli.rs +++ b/crates/codeguard-cli/tests/grammar_status_cli.rs @@ -108,6 +108,27 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() { .unwrap() .contains("known grammar false positive") ); + for (language, evidence) in [ + ("c", "C11 Apple clang 21 differential"), + ("go", "Go 1.23.4 gofmt differential"), + ("javascript", "Node 24.18.0 module syntax differential"), + ("rust", "Rust 2021 rustfmt 1.9.0 differential"), + ("zig", "Zig 0.16.0 ast-check differential"), + ("cfquery", "does not validate full SQL semantics"), + ] { + let asset = assets + .iter() + .find(|row| row["language"] == language) + .unwrap(); + assert!( + asset["known_limitations"][0] + .as_str() + .unwrap() + .contains(evidence), + "{language}: {asset}" + ); + assert_eq!(asset["released"], false); + } for language in ["objc", "solidity"] { let asset = assets .iter() diff --git a/grammars/manifest.json b/grammars/manifest.json index 7c8edf44..169c3763 100644 --- a/grammars/manifest.json +++ b/grammars/manifest.json @@ -22,7 +22,7 @@ "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], "known_limitations": [ - "Rust loader smoke and narrow fixtures passed; language versions, dialect corpus, native differential, resource isolation and release remain unqualified" + "C11 Apple clang 21 differential agrees on 13 narrow syntax cases; other C versions, systematic precision, native-first lint route, resource budgets and release remain unqualified" ], "release_status": "candidate_unvalidated" }, @@ -83,7 +83,7 @@ "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], "known_limitations": [ - "Rust loader smoke and narrow fixtures passed; language versions, dialect corpus, native differential, resource isolation and release remain unqualified" + "Go 1.23.4 gofmt differential agrees on 13 narrow syntax cases; build tags, other versions, systematic precision, complete native-first lint coverage and release remain unqualified" ], "release_status": "candidate_unvalidated" }, @@ -123,7 +123,7 @@ "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], "known_limitations": [ - "Rust loader smoke and narrow fixtures passed; language versions, dialect corpus, native differential, resource isolation and release remain unqualified" + "Node 24.18.0 module syntax differential agrees on 13 narrow cases; script mode, other versions, systematic precision, complete native-first lint route and release remain unqualified" ], "release_status": "candidate_unvalidated" }, @@ -225,7 +225,7 @@ "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], "known_limitations": [ - "Rust loader smoke and narrow fixtures passed; language versions, dialect corpus, native differential, resource isolation and release remain unqualified" + "Rust 2021 rustfmt 1.9.0 differential agrees on 13 narrow syntax cases; other editions, systematic precision, complete native-first Clippy coverage and release remain unqualified" ], "release_status": "candidate_unvalidated" }, @@ -307,7 +307,7 @@ "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], "known_limitations": [ - "Rust loader smoke passed with byte-pinned import adaptation; Zig versions, corpus differential, resource isolation and release are not yet qualified" + "Zig 0.16.0 ast-check differential agrees on 11 narrow syntax cases; other versions, systematic precision, complete native-first lint route and release remain unqualified" ], "release_status": "candidate_unvalidated" }, @@ -456,7 +456,9 @@ "codegraph_runtime": "web-tree-sitter 0.25.3", "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], - "known_limitations": ["Pinned CodeGraph asset; only narrow syntax fixtures; native differential, language and dialect ranges, public lint route, resource budget and release remain unqualified"], + "known_limitations": [ + "SELECT FROM inside a CFQuery fragment yields zero recovery despite missing SQL select list; this grammar does not validate full SQL semantics. Native dialect comparison, systematic precision, public lint route, resource budget and release remain unqualified" + ], "release_status": "candidate_unvalidated" }, { diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index a7c31286..5387b684 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1107,3 +1107,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 全量最小正例收紧:分批 `check all` 运行 32 份固定 grammar 的既有样例,现在逐观察要求 `recovery_count=0`;本机目标测试 1/1 通过,确保路由虽成功却对合法样例产生恢复节点时不能假装验收成功。该语料规模很小,不等于逐语言原生 oracle、版本/方言覆盖或系统误报率,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 14.4 / 14.17 / 14.19 C11 原生差分局部增量:Apple Clang 21 `-fsyntax-only` 与隔离 C worker 在 8 份合法、5 份语法破损样例上 13/13 分类一致;普通语料和显式原生差分测试各 1/1 通过。初版语料中的返回类型错误是语义诊断,已改为纯语法缺失初始化表达式,不把语义拒绝当成 grammar 漏检。其它 C 版本、系统误报率、完整原生 lint/路由、发行与宿主验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/c-native-differential.md)。 + +14.1 / 14.7 / 14.17 / 14.19 状态证据校正:固定清单的 C、Go、JavaScript、Rust、Zig 已知限制同步窄范围原生差分事实,CFQuery 明确提示 SQL 片段漏检;只读 `grammar status` 测试逐项断言说明且仍 `released=false`。这只是源码状态投影,已发布包未更新,也未消除系统精度和原生 lint 缺口;见[局部验收](../../../tests/acceptance/grammar-known-limitations-projection.md)。 diff --git a/tests/acceptance/grammar-known-limitations-projection.md b/tests/acceptance/grammar-known-limitations-projection.md index 3c0b59f6..99d9dce6 100644 --- a/tests/acceptance/grammar-known-limitations-projection.md +++ b/tests/acceptance/grammar-known-limitations-projection.md @@ -7,3 +7,5 @@ 目标测试:`cargo test --locked -p codeguard-adapters --test grammar_asset_manifest candidate_limitations_are_bounded_before_dialog_projection -- --exact` 和 `cargo test --locked -p codeguard-cli --test grammar_status_cli codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion -- --exact`。本轮在修改前分别失败于超长文本被接受、报告缺字段;修改后两项通过。VB.NET 行明确包含已知未缩进方法的误报提示,详见[原始复现](vbnet-unindented-method-false-positive.md)。该报告没有完成原生对照或修复 grammar,不改变候选和门禁状态。 扩展反例还覆盖 9 条限制与换行控制字符。完整 manifest 测试 11/11、`grammar_status_cli` 2/2、受影响 crate 的 all-targets Clippy `-D warnings`、`cargo fmt --all --check`、OpenSpec strict 和 JSON Schema 对当前清单的校验均通过。已发布的 npm 0.1.3 由旧源码构建,不包含本次报告字段;此处只证明当前源码候选,不冒称已发布行为。 + +2026-10-03 后续校正:C、Go、JavaScript、Rust、Zig 已有窄范围原生差分,清单不再错误声称这些语言完全缺原生对照;CFQuery 明确提示 `SELECT FROM` 无恢复节点且该 grammar 不验证完整 SQL 语义。`grammar_status_cli` 逐语种断言证据文字与 `released=false`,完整语言验收仍未完成。已发布 npm 0.1.3 的嵌入清单不会因源码文字修改而自动更新。 From a513073d7fb4606ef0596ea300e5ca9c7f02f5c0 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:07:55 +0800 Subject: [PATCH 23/31] feat: surface grammar limitations in project feedback --- README.md | 2 +- README.zh-CN.md | 2 +- .../src/grammar_asset_manifest.rs | 5 + crates/codeguard-adapters/src/lib.rs | 2 +- crates/codeguard-cli/src/check_command.rs | 2 +- .../src/check_syntax_candidates.rs | 33 +- crates/codeguard-cli/src/main.rs | 2 +- .../tests/check_all_grammar_candidates.rs | 51 +- .../codeguard-cli/tests/check_all_java_p3c.rs | 2 +- crates/codeguard-cli/tests/check_all_npm.rs | 2 +- .../tests/check_all_partial_contract.rs | 20 +- crates/codeguard-cli/tests/python_cve_cli.rs | 2 +- docs/Codeguard-Architecture.md | 2 +- docs/Codeguard-Architecture.zh_CN.md | 2 +- docs/Codeguard-Command-Reference.md | 4 +- docs/Codeguard-Command-Reference.zh_CN.md | 2 +- docs/Codeguard-Technical-Design.md | 10 +- docs/Codeguard-Technical-Design.zh_CN.md | 10 +- .../introduce-rust-codeguard-cli/tasks.md | 2 + schemas/check-feedback-v0.33.schema.json | 5604 +++++++++++++++++ schemas/check-feedback.schema.json | 9 +- .../check-all-known-grammar-limitations.md | 11 + 22 files changed, 5746 insertions(+), 35 deletions(-) create mode 100644 schemas/check-feedback-v0.33.schema.json create mode 100644 tests/acceptance/check-all-known-grammar-limitations.md diff --git a/README.md b/README.md index b0a3f332..820cca50 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ The source-built `grammar status` now includes each candidate's bounded `known_l In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` can explicitly run any of the 32 pinned candidates in an isolated worker. It always exits 3 with `status=incomplete`, `native.status=not_run`, and `delivery_decision=not_evaluated`; a completed parse also has `precheck.status=incomplete`. Input or worker failures use the same [closed JSON schema](schemas/grammar-probe-v0.1.schema.json). Recovery anchors are suspected observations only. This diagnostic command does not yet connect all grammars to language-qualified native-first `lint/check`; the published npm package includes it as an unqualified candidate. -The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.33.0](schemas/check-feedback.schema.json) distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback; published npm `0.1.3` includes this candidate route. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). Narrow 13-case syntax differentials agree for [C11 against Apple Clang 21](tests/acceptance/c-native-differential.md), [Rust 2021 against rustfmt 1.9.0](tests/acceptance/rust-native-differential.md), and [Ruby 2.6.10](tests/acceptance/ruby-native-differential.md); none qualifies native lint. A [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. +The same source build now runs a bounded candidate pass from `codeguard check all . --format=json` after existing native checks. Its `syntax_candidates` field in [check feedback 0.34.0](schemas/check-feedback.schema.json) carries bounded known grammar limitations and distinguishes TSX, JavaScript, CFScript and explicitly embedded CFQuery, preserving native blockers and skipped work. Python files covered by a completed Ruff scan of identical source bytes skip duplicate WASM and count under `native_preferred_count`; files without matching native syntax evidence retain candidate prechecks. Four bounded integration fixtures collectively invoked all 32 pinned grammars through this command. Every observation remains unqualified and the command exits 3; 64 files/64 fragments/90 seconds bound the candidate pass. This is not yet a language-qualified native fallback; published npm `0.1.3` includes this candidate route, while the 0.34.0 known-limitation projection is still source-only. See [acceptance evidence](tests/acceptance/check-all-32-grammar-candidates.md). Narrow 13-case syntax differentials agree for [C11 against Apple Clang 21](tests/acceptance/c-native-differential.md), [Rust 2021 against rustfmt 1.9.0](tests/acceptance/rust-native-differential.md), and [Ruby 2.6.10](tests/acceptance/ruby-native-differential.md); none qualifies native lint. A [Swift 6.4 differential](tests/acceptance/swift-native-differential.md) exposes one native-rejected source that the Swift WASM misses, so Swift remains unqualified. The source-built Unix CLI also has a narrow Zig route: `codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`. An explicitly supplied tool reporting Zig 0.16.0 and retaining the same byte digest runs native `ast-check` first; its source positions are retained without exposing source snippets. Without an explicit tool, the pinned Zig WASM provides an unqualified observation. Both paths remain incomplete because `ast-check` covers only local AST errors, not full lint, build or tests. See the [Zig feedback schema](schemas/zig-lint-feedback-v0.1.schema.json). diff --git a/README.zh-CN.md b/README.zh-CN.md index 2a8ca760..b0c935ab 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -104,7 +104,7 @@ cargo build --locked -p codeguard-cli 源码以 `--features wasm-precheck` 构建后,可显式运行 `codeguard grammar probe --format=json`,由隔离 worker 调用 32 份固定候选中的任意一份。命令始终以退出码 3 返回,并标记 `status=incomplete`、`native.status=not_run`、`delivery_decision=not_evaluated`;解析完成时还标记 `precheck.status=incomplete`。输入或 worker 失败也遵守同一份[封闭 JSON Schema](schemas/grammar-probe-v0.1.schema.json)。恢复锚点只是疑似观察。此诊断入口尚未把全部语种接入已验收的原生优先 `lint/check`;已发布 npm 包仅把它作为未验收候选提供。 -同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.33.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底;已发布 npm `0.1.3` 包含该候选路径;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[C11 与 Apple Clang 21 差分](tests/acceptance/c-native-differential.md)、[Rust 2021 与 rustfmt 1.9.0 差分](tests/acceptance/rust-native-differential.md)和[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)各在 13 个窄范围语法样例上一致,但不代表对应语言的原生 lint 已验收。[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 +同一源码构建现在会在既有原生检查之后,从 `codeguard check all . --format=json` 执行有界候选初检。[检查反馈 0.34.0](schemas/check-feedback.schema.json)中的 `syntax_candidates` 附带有界的已知 grammar 限制,并区分 TSX、JavaScript、CFScript 和明确嵌入的 CFQuery,同时保留原生阻塞与未执行范围。Ruff 已对相同源码完成原生扫描的 Python 文件跳过重复 WASM,并计入 `native_preferred_count`;其余未确认语法能力的文件仍运行候选初检。四个有界集成样例经此入口合计调用了 32 种固定 grammar。观察仍未验收,命令保持退出码 3;候选阶段限 64 文件、64 片段、90 秒。这尚非逐语言验收的原生兜底;已发布 npm `0.1.3` 包含该候选路径,但 0.34.0 的已知限制投影目前只存在于源码构建;见[验收记录](tests/acceptance/check-all-32-grammar-candidates.md)。[C11 与 Apple Clang 21 差分](tests/acceptance/c-native-differential.md)、[Rust 2021 与 rustfmt 1.9.0 差分](tests/acceptance/rust-native-differential.md)和[Ruby 2.6.10 差分](tests/acceptance/ruby-native-differential.md)各在 13 个窄范围语法样例上一致,但不代表对应语言的原生 lint 已验收。[Swift 6.4 差分](tests/acceptance/swift-native-differential.md)发现一处原生拒绝而 Swift WASM 漏检的源码,因此 Swift 仍未验收。 源码构建的 Unix CLI 还提供局部 Zig 入口:`codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`。显式提供且报告 Zig 0.16.0、执行前后字节摘要一致的工具优先运行原生 `ast-check`,仅输出诊断位置,不回显源码;未显式提供工具时,固定 Zig WASM 给出未验收候选观察。两条路径都保持未完成,因为 `ast-check` 仅覆盖局部 AST 错误,不等于完整 lint、构建或测试。见 [Zig 报告 Schema](schemas/zig-lint-feedback-v0.1.schema.json)。 diff --git a/crates/codeguard-adapters/src/grammar_asset_manifest.rs b/crates/codeguard-adapters/src/grammar_asset_manifest.rs index 66e292bb..bd333690 100644 --- a/crates/codeguard-adapters/src/grammar_asset_manifest.rs +++ b/crates/codeguard-adapters/src/grammar_asset_manifest.rs @@ -118,6 +118,11 @@ pub fn bundled_grammar_candidates() -> Result { Ok(manifest) } +/// 只读取并校验内置 grammar 清单元数据,供报告投影已知限制;不代替选中资产的字节验证。 +pub fn bundled_grammar_metadata() -> Result { + parse_grammar_asset_manifest(include_bytes!("../../../grammars/manifest.json")) +} + /// 只核对指定内置 grammar 的字节与许可,供按需加载的语法工作进程使用。 /// 参数为固定语言 ID;返回清单身份和静态 WASM 字节,未知语言返回错误。 pub fn bundled_grammar_candidate(language: &str) -> Result<(GrammarAsset, &'static [u8]), String> { diff --git a/crates/codeguard-adapters/src/lib.rs b/crates/codeguard-adapters/src/lib.rs index bf66f11b..25a12db2 100644 --- a/crates/codeguard-adapters/src/lib.rs +++ b/crates/codeguard-adapters/src/lib.rs @@ -70,7 +70,7 @@ mod zig_wasm_compat; pub use dart_wasm_compat::adapt_dart_wasm; pub use grammar_asset_manifest::{ GrammarAsset, GrammarAssetManifest, bundled_grammar_candidate, bundled_grammar_candidates, - parse_grammar_asset_manifest, verify_grammar_asset, + bundled_grammar_metadata, parse_grammar_asset_manifest, verify_grammar_asset, }; pub use legacy_dylink_compat::adapt_legacy_dylink; pub use zig_wasm_compat::adapt_zig_wasm; diff --git a/crates/codeguard-cli/src/check_command.rs b/crates/codeguard-cli/src/check_command.rs index b9c7f271..47c687f7 100644 --- a/crates/codeguard-cli/src/check_command.rs +++ b/crates/codeguard-cli/src/check_command.rs @@ -1479,7 +1479,7 @@ pub fn run(args: &[String]) -> ExitCode { "next_action":"使用包含固定语法资产的发行包运行候选初检,并完成适用原生检查" }); let report = json!({ - "schema_version":"0.33.0", "report_type":"check_feedback", + "schema_version":"0.34.0", "report_type":"check_feedback", "operation":"check", "selection":parsed.selection.as_str(), "command_status":if request_cancelled { "cancelled" } else { "incomplete" }, "exit_code":if request_cancelled { 130 } else { 3 }, "delivery_decision":if parsed.selection == Selection::All { "incomplete" } else { "not_evaluated" }, "authority":"local_unverified", "reason":if request_cancelled { "request_cancelled" } else if parsed.selection == Selection::All { "full_project_obligations_and_trusted_policy_unavailable" } else { "java_selection_obligations_and_trusted_policy_unavailable" }, diff --git a/crates/codeguard-cli/src/check_syntax_candidates.rs b/crates/codeguard-cli/src/check_syntax_candidates.rs index bc486764..8a2ceccc 100644 --- a/crates/codeguard-cli/src/check_syntax_candidates.rs +++ b/crates/codeguard-cli/src/check_syntax_candidates.rs @@ -5,6 +5,7 @@ use crate::go_lint_command::selected_sources_for_candidate; use crate::grammar_probe_command::read_plain_source; use crate::grammar_route::route_source; use crate::syntax_worker_runner::run_syntax_worker_candidate; +use codeguard_adapters::bundled_grammar_metadata; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use std::collections::{BTreeMap, BTreeSet}; @@ -58,6 +59,20 @@ pub fn observe( observations, ); } + let manifest = match bundled_grammar_metadata() { + Ok(manifest) => manifest, + Err(_) => { + return report( + "not_run", + "grammar_manifest_invalid", + source_file_count, + source_file_count, + 0, + 0, + observations, + ); + } + }; let executable = match std::env::current_exe() { Ok(executable) => executable, Err(_) => { @@ -101,7 +116,8 @@ pub fn observe( observations.push(json!({ "path":relative,"language":null,"scope":"whole_file","byte_offset":0, "status":"candidate_unavailable","reason":reason,"grammar_qualified":false, - "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[] + "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], + "known_limitations":[] })); continue; } @@ -117,6 +133,12 @@ pub fn observe( unrouted_count += 1; } for route in routes { + let known_limitations = manifest + .assets + .iter() + .find(|asset| asset.language == route.language) + .map(|asset| asset.known_limitations.as_slice()) + .unwrap_or(&[]); if observations.len() >= MAX_FRAGMENTS || Instant::now() >= candidate_deadline { skipped_count += 1; continue; @@ -129,7 +151,8 @@ pub fn observe( observations.push(json!({ "path":relative,"language":route.language,"scope":route.scope,"byte_offset":route.byte_offset, "status":"candidate_unavailable","reason":"source_changed_during_precheck","grammar_qualified":false, - "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[] + "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], + "known_limitations":known_limitations })); continue; } @@ -153,13 +176,15 @@ pub fn observe( "path":relative,"language":route.language,"scope":route.scope,"byte_offset":route.byte_offset, "status":"candidate_observed","reason":null,"grammar_qualified":false, "source_sha256":observation.source_sha256,"grammar_sha256":observation.grammar_sha256, - "recovery_count":recovery_count,"recoveries":recoveries + "recovery_count":recovery_count,"recoveries":recoveries, + "known_limitations":known_limitations })); } Err(reason) => observations.push(json!({ "path":relative,"language":route.language,"scope":route.scope,"byte_offset":route.byte_offset, "status":"candidate_unavailable","reason":reason,"grammar_qualified":false, - "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[] + "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], + "known_limitations":known_limitations })), } } diff --git a/crates/codeguard-cli/src/main.rs b/crates/codeguard-cli/src/main.rs index ae178c87..761b3321 100644 --- a/crates/codeguard-cli/src/main.rs +++ b/crates/codeguard-cli/src/main.rs @@ -72,7 +72,7 @@ fn main() -> ExitCode { ); #[cfg(all(feature = "wasm-precheck", unix))] println!( - "check all 会在原生节点之后按源码方言有界执行 32 份固定 WASM 的候选初检;check_feedback 0.33.0 的 syntax_candidates 保留疑似位置、原生优先计数和未执行范围,始终不能代替原生义务或放行交付。" + "check all 会在原生节点之后按源码方言有界执行 32 份固定 WASM 的候选初检;check_feedback 0.34.0 的 syntax_candidates 保留疑似位置、已知限制、原生优先计数和未执行范围,始终不能代替原生义务或放行交付。" ); #[cfg(all(feature = "wasm-precheck", unix))] println!( diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index f96daeb5..fbd0c9b7 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -98,7 +98,7 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { String::from_utf8_lossy(&output.stderr) ); let report: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); - assert_eq!(report["schema_version"], "0.33.0"); + assert_eq!(report["schema_version"], "0.34.0"); assert_eq!(report["delivery_decision"], "incomplete"); let observations = report["syntax_candidates"]["observations"] .as_array() @@ -174,6 +174,55 @@ fn ambiguous_only_project_remains_an_explicit_unrouted_scope() { ); } +#[test] +fn known_grammar_precision_limits_reach_project_feedback() { + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-known-grammar-limits-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + fs::write( + root.join("class.vb"), + "Public Class C\nPublic Function F() As Integer\nReturn 1\nEnd Function\nEnd Class\n", + ) + .unwrap(); + fs::write( + root.join("query.cfm"), + "SELECT FROM users\n", + ) + .unwrap(); + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["check", "all"]) + .arg(&root) + .args(["--format=json", "--timeout", "45s"]) + .output() + .unwrap(); + fs::remove_dir_all(&root).unwrap(); + assert_eq!(output.status.code(), Some(3)); + let report: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + let observations = report["syntax_candidates"]["observations"] + .as_array() + .unwrap(); + for (language, expected) in [ + ("vbnet", "known grammar false positive"), + ("cfquery", "does not validate full SQL semantics"), + ] { + let observation = observations + .iter() + .find(|item| item["language"] == language) + .unwrap(); + assert!( + observation["known_limitations"][0] + .as_str() + .unwrap() + .contains(expected), + "{language}: {observation}" + ); + assert_eq!(observation["grammar_qualified"], false); + } + assert_eq!(report["delivery_decision"], "incomplete"); +} + #[test] fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { let root = std::env::temp_dir().canonicalize().unwrap().join(format!( diff --git a/crates/codeguard-cli/tests/check_all_java_p3c.rs b/crates/codeguard-cli/tests/check_all_java_p3c.rs index b1b06c02..ed827bf7 100644 --- a/crates/codeguard-cli/tests/check_all_java_p3c.rs +++ b/crates/codeguard-cli/tests/check_all_java_p3c.rs @@ -219,7 +219,7 @@ fn missing_p3c_configuration_is_reported_without_running_maven() { fs::set_permissions(&tool, fs::Permissions::from_mode(0o700)).unwrap(); let report = project.check(&["--maven-tool", tool.to_str().unwrap()]); assert!(!marker.exists()); - assert_eq!(report["schema_version"], "0.33.0"); + assert_eq!(report["schema_version"], "0.34.0"); assert_eq!(report["execution_tasks"][0]["id"], "java.p3c"); assert_eq!(report["execution_tasks"][0]["status"], "native_incomplete"); let java = &report["native_results"]["java_p3c"]; diff --git a/crates/codeguard-cli/tests/check_all_npm.rs b/crates/codeguard-cli/tests/check_all_npm.rs index e5f728c7..3759c6d1 100644 --- a/crates/codeguard-cli/tests/check_all_npm.rs +++ b/crates/codeguard-cli/tests/check_all_npm.rs @@ -411,7 +411,7 @@ fn real_npm_audit_enters_check_all_workbench() { String::from_utf8_lossy(&out.stderr) ); let report: Value = serde_json::from_slice(&out.stdout).unwrap(); - assert_eq!(report["schema_version"], "0.33.0"); + assert_eq!(report["schema_version"], "0.34.0"); assert_eq!( report["native_results"]["npm_cve"][0]["feedback"]["local_coherent"], true, "{report}" diff --git a/crates/codeguard-cli/tests/check_all_partial_contract.rs b/crates/codeguard-cli/tests/check_all_partial_contract.rs index 8cccf2cb..9a0883ae 100644 --- a/crates/codeguard-cli/tests/check_all_partial_contract.rs +++ b/crates/codeguard-cli/tests/check_all_partial_contract.rs @@ -59,12 +59,20 @@ fn check_feedback_schema_cannot_encode_allow() { .unwrap(); assert_eq!(previous["properties"]["schema_version"]["const"], "0.30.0"); assert_eq!(schema["additionalProperties"], false); - assert_eq!(schema["properties"]["schema_version"]["const"], "0.33.0"); + assert_eq!(schema["properties"]["schema_version"]["const"], "0.34.0"); let archived: Value = serde_json::from_str(include_str!( "../../../schemas/check-feedback-v0.32.schema.json" )) .unwrap(); assert_eq!(archived["properties"]["schema_version"]["const"], "0.32.0"); + let archived_previous: Value = serde_json::from_str(include_str!( + "../../../schemas/check-feedback-v0.33.schema.json" + )) + .unwrap(); + assert_eq!( + archived_previous["properties"]["schema_version"]["const"], + "0.33.0" + ); assert_eq!( schema["properties"]["syntax_candidates"]["properties"]["native_preferred_count"]["type"], "integer" @@ -74,6 +82,12 @@ fn check_feedback_schema_cannot_encode_allow() { ["grammar_qualified"]["const"], false ); + assert!(schema["properties"]["syntax_candidates"]["properties"]["observations"]["items"] + ["required"] + .as_array() + .unwrap() + .iter() + .any(|field| field == "known_limitations")); assert_eq!( schema["properties"]["syntax_candidates"]["properties"]["delivery_decision"]["const"], "incomplete" @@ -230,7 +244,7 @@ fn check_all_keeps_local_checker_candidate_separate_from_execution() { let (exit, report) = project.check(&[]); assert_eq!(exit, 3); - assert_eq!(report["schema_version"], "0.33.0"); + assert_eq!(report["schema_version"], "0.34.0"); assert_eq!(report["discovery"]["schema_version"], "0.4.0"); let candidates = report["discovery"]["native_tool_candidates"] .as_array() @@ -546,7 +560,7 @@ fn rust_only_project_keeps_categories_as_candidates_without_inventing_policy_obl let (exit, report) = project.check(&[]); assert_eq!(exit, 3); assert_eq!(report["report_type"], "check_feedback"); - assert_eq!(report["schema_version"], "0.33.0"); + assert_eq!(report["schema_version"], "0.34.0"); assert_eq!(report["execution_budget"]["timeout_ms"], 1_800_000); assert_eq!(report["execution_budget"]["source"], "builtin_default"); assert_eq!( diff --git a/crates/codeguard-cli/tests/python_cve_cli.rs b/crates/codeguard-cli/tests/python_cve_cli.rs index e210850d..37f7bb4f 100644 --- a/crates/codeguard-cli/tests/python_cve_cli.rs +++ b/crates/codeguard-cli/tests/python_cve_cli.rs @@ -477,7 +477,7 @@ fn check_all_observes_each_python_build_root_without_allowing_delivery() { let tool = project.native_tool(); let (exit, report) = project.check_all(Some(&tool)); assert_eq!(exit, 3); - assert_eq!(report["schema_version"], "0.33.0"); + assert_eq!(report["schema_version"], "0.34.0"); assert_eq!(report["delivery_decision"], "incomplete"); let scans = report["native_results"]["python_cve"].as_array().unwrap(); assert_eq!(scans.len(), 2); diff --git a/docs/Codeguard-Architecture.md b/docs/Codeguard-Architecture.md index 7f74fc58..4c51d6b5 100644 --- a/docs/Codeguard-Architecture.md +++ b/docs/Codeguard-Architecture.md @@ -32,7 +32,7 @@ WASM requirements and 19 implementation tasks are recorded in the existing chang A source build with `--features wasm-precheck` now exposes `codeguard grammar probe --format=json` for all 32 pinned candidates. It uses the isolated Rust worker and reports unqualified observations with exit 3, not a lint or delivery verdict. Successful observations and input failures for a valid language share the [closed 0.1.0 JSON schema](../schemas/grammar-probe-v0.1.schema.json) and remain incomplete. Native-first `lint/check`, language/dialect acceptance, task/host feedback and distribution remain separate open work; current discovery also groups JavaScript/TSX under TypeScript and has no separate CFQuery/CFScript source mapping. -The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.33.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback or task/host integration; npm 0.1.3 distributes it as an unqualified candidate. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). +The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.34.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, bounded known limitations, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback or task/host integration; npm 0.1.3 distributes it as an unqualified candidate. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). ```mermaid flowchart LR diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index a435d065..f66bf87b 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -32,7 +32,7 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选 Rust worker 源码以 `--features wasm-precheck` 构建后,可通过 `codeguard grammar probe --format=json` 显式调用全部 32 份固定候选资产。命令使用隔离 Rust worker,以退出码 3 报告未经语言验收的观察,不能作为 lint 或交付结论。成功观察与有效语种的输入失败都遵守[0.1.0 封闭 JSON Schema](../schemas/grammar-probe-v0.1.schema.json),保持未完成。原生优先 `lint/check`、语言/方言验收、任务/宿主反馈及发行仍需独立完成;现有发现清单还把 JavaScript/TSX 归入 TypeScript,并未为 CFQuery/CFScript 建立独立源码映射。 -源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.33.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底或任务/宿主接线;npm 0.1.3 仅将其作为未验收候选分发;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 +源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.34.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、有界已知限制、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底或任务/宿主接线;npm 0.1.3 仅将其作为未验收候选分发;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 ```mermaid flowchart LR diff --git a/docs/Codeguard-Command-Reference.md b/docs/Codeguard-Command-Reference.md index c5df332a..0a8a858d 100644 --- a/docs/Codeguard-Command-Reference.md +++ b/docs/Codeguard-Command-Reference.md @@ -12,7 +12,7 @@ Current slices include static discovery/init, selected native checks, partial ag The opt-in `codeguard-cli/wasm-precheck` build adds narrow `lint typescript ` and `lint java ` paths when no explicit native execution context is provided. TypeScript first recognizes an ordinary project-local ESLint 10 entry and one flat config, then uses an executable Node from `PATH` to run the existing bounded native probe; when Node is unresolved, it reports setup instead of starting WASM. Only when no project-local ESLint package is observed does it return [TypeScript feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) or TSX 0.4.0 candidate feedback. Untrusted local paths, package identity or config selection remain setup blockers. Java returns [Java feedback 0.1.0](../schemas/java-syntax-precheck-feedback-v0.1.schema.json). These paths retain exit 3 and never turn an unvalidated grammar or local native observation into delivery approval. Default/published binaries retain the existing native-context behavior. See [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). -The same source build also adds a bounded `syntax_candidates` pass to `check all` after its native nodes. [Feedback 0.33.0](../schemas/check-feedback.schema.json) keeps all 32 pinned grammars callable through bounded uses of the unified command while preserving native blockers, skipped scope and an incomplete verdict; source-matched Python files completed by Ruff count under `native_preferred_count` and skip duplicate WASM; `.tsx`, JavaScript, `.cfs` and explicit CFQuery tag bodies use distinct routes. This is candidate observation, not language-qualified native fallback or published package support. See [32-grammar acceptance](../tests/acceptance/check-all-32-grammar-candidates.md). +The same source build also adds a bounded `syntax_candidates` pass to `check all` after its native nodes. [Feedback 0.34.0](../schemas/check-feedback.schema.json) keeps all 32 pinned grammars callable through bounded uses of the unified command while preserving native blockers, bounded known grammar limitations, skipped scope and an incomplete verdict; source-matched Python files completed by Ruff count under `native_preferred_count` and skip duplicate WASM; `.tsx`, JavaScript, `.cfs` and explicit CFQuery tag bodies use distinct routes. This is candidate observation, not language-qualified native fallback or automatic host support. See [32-grammar acceptance](../tests/acceptance/check-all-32-grammar-candidates.md). With `--workspace` pointing to an initialized, canonical workspace, the TypeScript/TSX fallback emits [feedback 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json). It saves one source-scoped ESLint native-confirmation blocker and returns its real `setup.task_id` only after successful sync. Repeated scans and a later WASM result with no recovery nodes keep that task open; a versioned local report now links bounded suspected positions to that task through its report digest, while capability-matched native verification remains unfinished. See the [workbench acceptance record](../tests/acceptance/typescript-syntax-confirmation-task.md). @@ -22,7 +22,7 @@ A separate Python execution slice now accepts `codeguard lint python . --file sr The current partial execution entry is `codeguard hook execute PATH --timeout DURATION --format=json [--ruff-tool ABS_PATH] [--git-tool ABS_PATH]`, reading the same 1.0.0 `hook_trigger_request` from stdin. The explicit timeout is capped at 120 seconds. Session start only discovers; Stop reads at most 64 findings and 64 reports with an 8 MiB report-byte budget and returns `hook_next_guidance` without running a checker. An oversized backlog returns `not_run/guidance_scope_exceeded`; use `codeguard next PATH --format=json` explicitly. An empty task list recommends a fresh full check, never a pass. `repair_ready` first bounds one task history to 128 events and 1 MiB, then verifies an existing stable task through the same `task verify` implementation in a bounded child process; a missing task, oversized history or failed child remains incomplete. Only task/checker identity, observation, saved-event state and a bounded reason are projected back, while the full native report stays in the existing local verification path. Explicit tool options supported by `task verify` may be passed to `hook execute`; they are rejected on unrelated events. A confirmed Python-only edit runs local Ruff. With an explicit Git binary, `pre_commit` reads the current staged index, including an alternate `GIT_INDEX_FILE`; the Git observation itself is bounded by the lesser of the supplied timeout and 15 seconds. Its `hook_git_index_summary` reports index identity, total violation count, at most 32 paths of at most 512 bytes each, truncation and object status while `source_check=not_run`; it cannot prove full quality coverage. Missing Git or failed index observation is `not_run`. Failed or uncertain writes, mixed languages, push and CI also remain `not_run`. The outer `hook_execution_feedback` is now 0.5; historical 0.1–0.4 schemas are preserved. `prompt_submitted` returns constant `read_only_intent_guidance` with no source check or delivery verdict. `host_blocking_verified=false` and `delivery_decision=not_evaluated` remain fixed even if the host claims blocking. No plugin Hook invokes this entry automatically yet; it is not a strict gate. -Current `detect` 0.4.0 reports local ESLint and Maven Wrapper candidates separately from checker configuration. It skips `node_modules` during ordinary source discovery while observing fixed tool paths without following links. A candidate never means the native tool was executed or approved; `check_feedback` carries this discovery under version 0.33.0. +Current `detect` 0.4.0 reports local ESLint and Maven Wrapper candidates separately from checker configuration. It skips `node_modules` during ordinary source discovery while observing fixed tool paths without following links. A candidate never means the native tool was executed or approved; `check_feedback` carries this discovery under version 0.34.0. The candidate Claude Code soft adapter is `codeguard hook claude PATH --timeout DURATION --format=json [--ruff-tool ABS_PATH]`. It reads at most 1 MiB of host JSON on stdin and validates the event name and working directory. SessionStart does read-only discovery; UserPromptSubmit returns constant non-blocking timing guidance without inspecting prompt text or running a checker; successful Write/Edit/MultiEdit accepts only a regular file under the chosen project root and calls the same Rust local executor; PostToolUseFailure runs the no-check route without echoing the tool error. Stop reads bounded local facts, never a checker or editable task Markdown. A stable task on an initial Stop yields one `hookSpecificOutput.additionalContext` continuation; an already active Stop or no task returns `systemMessage` only, avoiding repeated wakeups. Context is bounded to 1200 characters for local edit feedback, and host exit 0 does not mean a check passed. Duplicate keys, oversized input, missing or escaping paths and mismatched events yield explicit unrun feedback. The separate plugin does not invoke this candidate yet. See the [candidate acceptance record](../tests/acceptance/claude-post-tool-hook-candidate.md). diff --git a/docs/Codeguard-Command-Reference.zh_CN.md b/docs/Codeguard-Command-Reference.zh_CN.md index 7470c177..694725c8 100644 --- a/docs/Codeguard-Command-Reference.zh_CN.md +++ b/docs/Codeguard-Command-Reference.zh_CN.md @@ -22,7 +22,7 @@ [已验收实现切片](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md)跟踪当前局部能力;[tasks](../openspec/changes/introduce-rust-codeguard-cli/tasks.md)独占完成状态。WASM 的完整统一入口仍为 S14 待办。可选 `codeguard-cli/wasm-precheck` 构建处理 `lint typescript <显式单文件>` 时,若观察到本地 ESLint 10 入口与唯一 flat config,会优先用 `PATH` 中可执行 Node 调用既有有界原生探针;缺 Node 则返回准备缺口。仅未观察到本地 ESLint 包时才输出 [TypeScript 0.3.0 候选反馈](../schemas/eslint-local-feedback-v0.3.schema.json)或 TSX 0.4.0。本地入口、包身份或配置不可信时保留具体环境阻塞,不启动 WASM。`lint java <显式单文件>` 仍输出 [Java 0.1.0 候选反馈](../schemas/java-syntax-precheck-feedback-v0.1.schema.json)。这些局部路径均不批准交付并保持退出 3;部分显式参数、Javadoc/Checkstyle 和符号链接沿用原路径,默认及已发布二进制也不含这项自动调度。见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)与技术方案 5.3–5.4;后文未标当前的语法、退出0/1与闭环承诺均为目标契约。 -同一源码构建现在也会在 `check all` 的原生节点之后输出有界 `syntax_candidates`。[反馈 0.33.0](../schemas/check-feedback.schema.json)让 32 份固定 grammar 均能经统一入口分批被选用,同时保留原生阻塞、未执行范围和未完成结论;Ruff 已完整覆盖的同摘要 Python 文件计入 `native_preferred_count`,不重复运行 WASM;`.tsx`、JavaScript、`.cfs` 与明确的 CFQuery 标签体分别路由。这仍是候选观察,不等于逐语言验收的原生兜底或已发布包能力,见[32 份验收](../tests/acceptance/check-all-32-grammar-candidates.md)。 +同一源码构建现在也会在 `check all` 的原生节点之后输出有界 `syntax_candidates`。[反馈 0.34.0](../schemas/check-feedback.schema.json)让 32 份固定 grammar 均能经统一入口分批被选用,同时保留原生阻塞、有界已知 grammar 限制、未执行范围和未完成结论;Ruff 已完整覆盖的同摘要 Python 文件计入 `native_preferred_count`,不重复运行 WASM;`.tsx`、JavaScript、`.cfs` 与明确的 CFQuery 标签体分别路由。这仍是候选观察,不等于逐语言验收的原生兜底或自动宿主反馈,见[32 份验收](../tests/acceptance/check-all-32-grammar-candidates.md)。 对已初始化且使用规范绝对路径的 `--workspace`,TypeScript/TSX 候选回退改用[反馈 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json):按源码范围保存同一张 ESLint 原生确认阻塞任务,仅同步成功时返回真实 `setup.task_id`。重复扫描和后续 WASM 无恢复节点不关闭任务。版本化本地报告已通过报告摘要关联有界疑似位置;能力匹配原生复检仍未接通,见[局部验收](../tests/acceptance/typescript-syntax-confirmation-task.md)。 diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index eda0ddd4..41873fb3 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -191,11 +191,11 @@ When some files have suspected issues and others are unresolved, use overall `in These are the target semantic fields, not a shipped project-wide lint/check report. A narrow [candidate precheck schema](../schemas/syntax-precheck-candidate.schema.json) and [strict Rust reader](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs) bind source SHA-256, pinned grammar identity and file dialect, recalculate status, and reject unknown versions or forged `clean`. Optional CLI single-file [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json), [TSX](../schemas/eslint-local-feedback-v0.4.schema.json) and [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) producers include recovery positions and setup guidance. With an initialized workspace, TypeScript/TSX now uses [feedback 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json) and attaches one stable native-confirmation task to the source scope; the [0.2.0 preparation report](../schemas/eslint-preparation-observation-v0.2.schema.json) stores bounded suspected positions with source and grammar identities and links them by digest to the task. Host rendering and capability-matched native closure remain absent. The bundled grammars remain unvalidated, so these reports cannot return `clean`. Missing required native execution remains incomplete (`3`); a suspected parser issue alone is not a confirmed violation (`1`). If a dedicated syntax-only operation is later introduced, its success must be explicitly scoped to syntax; no new `syntax` command is claimed here. -The source-built `check all` now adds a narrower actual project report: native adapters run first, then a [bounded source router](../crates/codeguard-cli/src/check_syntax_candidates.rs) selects pinned worker candidates. The closed [0.33.0 schema](../schemas/check-feedback.schema.json) counts files preempted by native checks; [0.32.0](../schemas/check-feedback-v0.32.schema.json) remains archived for older readers. Only a completed Ruff scan of matching Python source bytes skips duplicate WASM. A style checker such as P3C is not syntax confirmation. The following is an excerpt, not a complete `check_feedback` instance: +The source-built `check all` now adds a narrower actual project report: native adapters run first, then a [bounded source router](../crates/codeguard-cli/src/check_syntax_candidates.rs) selects pinned worker candidates. The closed [0.34.0 schema](../schemas/check-feedback.schema.json) counts files preempted by native checks and projects each selected grammar's bounded known limitations; [0.33.0](../schemas/check-feedback-v0.33.schema.json) remains archived for older readers. Only a completed Ruff scan of matching Python source bytes skips duplicate WASM. A style checker such as P3C is not syntax confirmation. The following is an excerpt, not a complete `check_feedback` instance: ```json { - "schema_version": "0.33.0", + "schema_version": "0.34.0", "command_status": "incomplete", "delivery_decision": "incomplete", "syntax_candidates": { @@ -205,7 +205,7 @@ The source-built `check all` now adds a narrower actual project report: native a "delivery_decision": "incomplete", "native_preferred_count": 1, "observations": [ - {"path": "src/view.tsx", "language": "tsx", "status": "candidate_observed", "grammar_qualified": false, "recovery_count": 0} + {"path": "src/view.tsx", "language": "tsx", "status": "candidate_observed", "grammar_qualified": false, "recovery_count": 0, "known_limitations": ["Rust loader smoke passed; TSX language versions, JSX dialects, isolation and syntax corpus are not yet validated"]} ], "next_action": "Run the applicable native checker before delivery" } @@ -291,7 +291,7 @@ Do not infer pass/fail from generic words such as `error`, `warning`, or `BUILD | Next action | Repair source, recommend/require native setup and confirmation, restore configuration, rescan, or obtain a concrete decision? | | Delivery | Is this only a local observation or a fully evaluated delivery result? | -[RunReport parsing](../crates/codeguard-cli/src/run_report.rs) supports a common structured contract; [check orchestration](../crates/codeguard-cli/src/check_command.rs) currently emits `check_feedback` `0.33.0`. Adapters also emit their own versioned local observations. Consumers must dispatch by protocol identity/version rather than assume a universal JSON shape. Human output is currently primarily Chinese; English documentation does not imply localized runtime messages. +[RunReport parsing](../crates/codeguard-cli/src/run_report.rs) supports a common structured contract; [check orchestration](../crates/codeguard-cli/src/check_command.rs) currently emits `check_feedback` `0.34.0`. Adapters also emit their own versioned local observations. Consumers must dispatch by protocol identity/version rather than assume a universal JSON shape. Human output is currently primarily Chinese; English documentation does not imply localized runtime messages. ### 7.3 Conversation report examples — target presentation @@ -463,7 +463,7 @@ Report review rules apply across all maintained documents: distinguish installed ### 7.5 Protocol versions and identity closure -The current common `RunReport` is `1.4`, aggregate `check_feedback` is `0.33.0`, and `check_aborted` is `0.11.0`; adapter-local observations have separate versions. Protocol versions must not be rewritten to match software `0.1.0`. The conversation/JSON briefs above are target examples, not complete instances of these three protocols. +The current common `RunReport` is `1.4`, aggregate `check_feedback` is `0.34.0`, and `check_aborted` is `0.11.0`; adapter-local observations have separate versions. Protocol versions must not be rewritten to match software `0.1.0`. The conversation/JSON briefs above are target examples, not complete instances of these three protocols. The target evidence chain correlates workspace/request/run/obligation/finding/task/attempt. Source locations use reversible paths and content identities; dependency locations use component, resolved version, graph and advisory identities. Lossy rendering of non-UTF-8 paths cannot participate in matching. Digests bind bytes, not approval authority. Upgrades preserve version semantics and never turn old empty findings into complete passes. diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index ad55a44e..b8ea701f 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -191,11 +191,11 @@ flowchart LR 这些是目标语义字段,尚非已发行的项目级 lint/check 报告。局部的[候选初检 schema](../schemas/syntax-precheck-candidate.schema.json) 与 [Rust 严格读者](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs)已绑定源码 SHA-256、固定 grammar 身份和文件方言,重新计算状态,并拒绝未知版本及伪造的 `clean`。可选 CLI 的 [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json)、[TSX](../schemas/eslint-local-feedback-v0.4.schema.json) 与 [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) 单文件生产端已提供恢复位置和准备指引。TypeScript/TSX 在已初始化工作区改用[反馈 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json),按源码范围关联一张稳定的原生确认任务;[0.2.0 准备报告](../schemas/eslint-preparation-observation-v0.2.schema.json)以源码和 grammar 身份保存有界疑似位置,并通过报告摘要关联到任务;宿主渲染和能力匹配原生关闭仍未接通。内置 grammar 仍是未验收候选,因此不能返回 `clean`。保持既有退出语义:必需原生执行缺失仍为未完成(`3`);解析器疑似问题本身不是已确认违规(`1`)。若未来增加独立语法操作,其成功必须限定为语法初检;本文不宣称已有 `syntax` 命令。 -源码构建的 `check all` 现另有较窄的真实项目报告:先执行原生适配器,再由[有界源码路由](../crates/codeguard-cli/src/check_syntax_candidates.rs)选择固定 worker 候选。[0.33.0 封闭 Schema](../schemas/check-feedback.schema.json)增加逐文件原生优先计数;[0.32.0](../schemas/check-feedback-v0.32.schema.json) 保留为历史协议。只有 Ruff 对同一 Python 源码摘要返回完整扫描状态时才跳过该文件的重复 WASM;P3C 等规范检查不能冒充语法确认。下面仅摘录字段,不是完整 `check_feedback` 实例: +源码构建的 `check all` 现另有较窄的真实项目报告:先执行原生适配器,再由[有界源码路由](../crates/codeguard-cli/src/check_syntax_candidates.rs)选择固定 worker 候选。[0.34.0 封闭 Schema](../schemas/check-feedback.schema.json)包含逐文件原生优先计数和所选 grammar 的有界已知限制;[0.33.0](../schemas/check-feedback-v0.33.schema.json) 保留为历史协议。只有 Ruff 对同一 Python 源码摘要返回完整扫描状态时才跳过该文件的重复 WASM;P3C 等规范检查不能冒充语法确认。下面仅摘录字段,不是完整 `check_feedback` 实例: ```json { - "schema_version": "0.33.0", + "schema_version": "0.34.0", "command_status": "incomplete", "delivery_decision": "incomplete", "syntax_candidates": { @@ -205,7 +205,7 @@ flowchart LR "delivery_decision": "incomplete", "native_preferred_count": 1, "observations": [ - {"path": "src/view.tsx", "language": "tsx", "status": "candidate_observed", "grammar_qualified": false, "recovery_count": 0} + {"path": "src/view.tsx", "language": "tsx", "status": "candidate_observed", "grammar_qualified": false, "recovery_count": 0, "known_limitations": ["Rust loader smoke passed; TSX language versions, JSX dialects, isolation and syntax corpus are not yet validated"]} ], "next_action": "交付前运行适用的原生检查器" } @@ -291,7 +291,7 @@ flowchart TD | 下一步 | 修源码、推荐/要求准备原生工具并确认、恢复配置、重扫,还是提出具体决策? | | 交付 | 只是局部观察,还是完整评估的交付结果? | -[RunReport 解析](../crates/codeguard-cli/src/run_report.rs)支持通用结构化契约,[检查编排](../crates/codeguard-cli/src/check_command.rs)当前输出 `check_feedback` `0.33.0`,适配器另有专属版本化局部观察。消费者必须按协议身份与版本分派,不能假定统一 JSON 形状。当前 human 输出以中文为主,英文文档不代表运行时消息已有英文国际化。 +[RunReport 解析](../crates/codeguard-cli/src/run_report.rs)支持通用结构化契约,[检查编排](../crates/codeguard-cli/src/check_command.rs)当前输出 `check_feedback` `0.34.0`,适配器另有专属版本化局部观察。消费者必须按协议身份与版本分派,不能假定统一 JSON 形状。当前 human 输出以中文为主,英文文档不代表运行时消息已有英文国际化。 ### 7.3 对话报告示例——目标呈现 @@ -463,7 +463,7 @@ Codeguard:1 个暂存路径需要处理 ### 7.5 协议版本与身份闭包 -当前通用 `RunReport` 为 `1.4`,聚合 `check_feedback` 为 `0.33.0`,`check_aborted` 为 `0.11.0`;适配器局部观察另有版本。版本属于具体协议,不能因为软件是 `0.1.0` 而统一改写。上面的对话/JSON 简报是目标示例,不是这三个协议的完整实例。 +当前通用 `RunReport` 为 `1.4`,聚合 `check_feedback` 为 `0.34.0`,`check_aborted` 为 `0.11.0`;适配器局部观察另有版本。版本属于具体协议,不能因为软件是 `0.1.0` 而统一改写。上面的对话/JSON 简报是目标示例,不是这三个协议的完整实例。 目标证据链关联 workspace/request/run/obligation/finding/task/attempt;源码定位使用可逆路径表示与内容身份,依赖定位使用组件、解析版本、图和 advisory。非 UTF-8 路径不能经有损显示字符串参与匹配。摘要只能绑定字节,不能证明字节来源已批准。报告升级保留旧字段的版本语义,不将旧空 findings 升格为完整通过。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 5387b684..a9e2a742 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1109,3 +1109,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.17 / 14.19 C11 原生差分局部增量:Apple Clang 21 `-fsyntax-only` 与隔离 C worker 在 8 份合法、5 份语法破损样例上 13/13 分类一致;普通语料和显式原生差分测试各 1/1 通过。初版语料中的返回类型错误是语义诊断,已改为纯语法缺失初始化表达式,不把语义拒绝当成 grammar 漏检。其它 C 版本、系统误报率、完整原生 lint/路由、发行与宿主验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/c-native-differential.md)。 14.1 / 14.7 / 14.17 / 14.19 状态证据校正:固定清单的 C、Go、JavaScript、Rust、Zig 已知限制同步窄范围原生差分事实,CFQuery 明确提示 SQL 片段漏检;只读 `grammar status` 测试逐项断言说明且仍 `released=false`。这只是源码状态投影,已发布包未更新,也未消除系统精度和原生 lint 缺口;见[局部验收](../../../tests/acceptance/grammar-known-limitations-projection.md)。 + +14.7 / 14.17 / 14.19 项目报告限制投影:`check all` 的 0.34.0 封闭协议逐候选附带固定清单的有界 `known_limitations`,VB.NET 已知误报与 CFQuery SQL 覆盖缺口不再只存在于独立库存命令;0.33.0 Schema 归档,清单解析失败时保持未完成。真实双文件目标测试先 RED 后 GREEN,六项候选回归与 18 项聚合契约通过,真实 JSON 通过 Draft 2020-12、旧 Schema/伪造放行被拒。默认宿主自动对话渲染、原生对照、系统精度和发行仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 diff --git a/schemas/check-feedback-v0.33.schema.json b/schemas/check-feedback-v0.33.schema.json new file mode 100644 index 00000000..64a288a5 --- /dev/null +++ b/schemas/check-feedback-v0.33.schema.json @@ -0,0 +1,5604 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:codeguard:schema:check-feedback:0.33.0", + "title": "CodeGuard partial project or language check feedback", + "$defs": { + "rust_cve": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "operation", + "language", + "checker_id", + "command_status", + "exit_code", + "delivery_decision", + "authority", + "coverage_proven", + "database_freshness", + "local_scan_complete", + "reason", + "tool_sha256", + "lock_sha256", + "manifest_sha256", + "dependency_count", + "database_advisory_count", + "database_last_commit", + "database_last_updated", + "native_warnings_present", + "findings", + "next_actions", + "execution_budget" + ], + "properties": { + "schema_version": { + "const": "0.1.0" + }, + "report_type": { + "const": "rust_cve_local_observation" + }, + "operation": { + "const": "cve" + }, + "language": { + "const": "rust" + }, + "checker_id": { + "const": "rust.cargo_audit" + }, + "command_status": { + "enum": [ + "incomplete", + "cancelled" + ] + }, + "exit_code": { + "enum": [ + 3, + 130 + ] + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "authority": { + "const": "local_unverified" + }, + "coverage_proven": { + "const": false + }, + "database_freshness": { + "const": "unverified" + }, + "local_scan_complete": { + "type": "boolean" + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "tool_sha256": { + "$ref": "#/$defs/rust_cve/$defs/nullable_digest" + }, + "lock_sha256": { + "$ref": "#/$defs/rust_cve/$defs/nullable_digest" + }, + "manifest_sha256": { + "$ref": "#/$defs/rust_cve/$defs/nullable_digest" + }, + "dependency_count": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "database_advisory_count": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "database_last_commit": { + "type": [ + "string", + "null" + ] + }, + "database_last_updated": { + "type": [ + "string", + "null" + ] + }, + "native_warnings_present": { + "type": "boolean" + }, + "findings": { + "type": "array", + "maxItems": 100000, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "advisory_id", + "package_name", + "package_version", + "package_source_sha256", + "package_checksum", + "cve_aliases", + "cvss_vector", + "severity" + ], + "properties": { + "advisory_id": { + "type": "string", + "pattern": "^RUSTSEC-" + }, + "package_name": { + "type": "string", + "minLength": 1 + }, + "package_version": { + "type": "string", + "minLength": 1 + }, + "package_source_sha256": { + "$ref": "#/$defs/rust_cve/$defs/digest" + }, + "package_checksum": { + "type": [ + "string", + "null" + ] + }, + "cve_aliases": { + "type": "array", + "items": { + "type": "string", + "pattern": "^CVE-" + } + }, + "cvss_vector": { + "type": [ + "string", + "null" + ] + }, + "severity": { + "const": "unverified" + } + } + } + }, + "next_actions": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "execution_budget": { + "type": "object", + "additionalProperties": false, + "required": [ + "timeout_ms", + "source", + "enforcement" + ], + "properties": { + "timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "source": { + "type": "string" + }, + "enforcement": { + "const": "native_execution_only" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "command_status": { + "const": "cancelled" + } + }, + "required": [ + "command_status" + ] + }, + "then": { + "properties": { + "exit_code": { + "const": 130 + } + } + }, + "else": { + "properties": { + "exit_code": { + "const": 3 + } + } + } + } + ], + "$defs": { + "digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "nullable_digest": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/rust_cve/$defs/digest" + } + ] + } + } + }, + "nullableSha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "goModuleResult": { + "type": "object", + "additionalProperties": false, + "required": [ + "root", + "status", + "diagnostic_count" + ], + "properties": { + "root": { + "type": "string", + "minLength": 1 + }, + "status": { + "enum": [ + "incomplete", + "findings_observed_unverified", + "clean_observed_unverified" + ] + }, + "diagnostic_count": { + "type": "integer", + "minimum": 0 + }, + "reason": { + "type": "string", + "minLength": 1 + } + } + }, + "goFinding": { + "type": "object", + "additionalProperties": false, + "required": [ + "finding_id", + "finding_fingerprint", + "source_sha256", + "module_root", + "rule_id", + "path", + "line", + "column", + "native_message_sha256", + "message_status" + ], + "properties": { + "finding_id": { + "type": "string", + "pattern": "^CG-[0-9a-f]{32}$" + }, + "finding_fingerprint": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "module_root": { + "type": "string", + "minLength": 1 + }, + "rule_id": { + "type": "string", + "minLength": 1 + }, + "path": { + "type": "string", + "minLength": 1 + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 1 + }, + "native_message_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "message_status": { + "const": "native_text_private_untrusted" + } + } + }, + "npmCheckResult": { + "type": "object", + "additionalProperties": false, + "required": [ + "build_root", + "feedback", + "observation", + "backlog_status" + ], + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "feedback": { + "oneOf": [ + { + "type": "null" + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm局部CVE反馈0.1/0.2/0.3;持久任务不授权门禁", + "oneOf": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm局部CVE反馈,不能自证库或门禁权威", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "status", + "reason", + "local_coherent", + "configuration", + "dependency_total", + "findings", + "advisory_coverage", + "tool_approval", + "delivery_decision", + "workbench_status", + "next_action" + ], + "properties": { + "schema_version": { + "const": "0.1.0" + }, + "report_type": { + "const": "npm_cve_feedback" + }, + "status": { + "enum": [ + "incomplete", + "local_observation" + ] + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "local_coherent": { + "type": "boolean" + }, + "configuration": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "build_root", + "checker_id", + "category", + "configuration", + "configuration_ref", + "reason", + "next_action" + ], + "additionalProperties": false, + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "checker_id": { + "type": "string", + "minLength": 1 + }, + "category": { + "enum": [ + "lint", + "comments", + "dependencies", + "cve", + "security", + "build" + ] + }, + "configuration": { + "enum": [ + "configured", + "missing", + "invalid", + "unknown" + ] + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "next_action": { + "type": "string", + "minLength": 1 + } + } + } + ] + }, + "dependency_total": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "component_ref", + "severity", + "advisory_sources", + "nodes" + ], + "properties": { + "component_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "severity": { + "enum": [ + "info", + "low", + "moderate", + "high", + "critical" + ] + }, + "advisory_sources": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "integer", + "minimum": 1 + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "node_ref", + "resolved_version" + ], + "properties": { + "node_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "resolved_version": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + } + } + } + } + } + }, + "advisory_coverage": { + "const": "not_evaluated" + }, + "tool_approval": { + "const": "unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "workbench_status": { + "const": "not_connected" + }, + "next_action": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "local_coherent": { + "const": true + } + } + }, + "then": { + "properties": { + "status": { + "const": "local_observation" + }, + "dependency_total": { + "type": "integer" + }, + "configuration": { + "type": "object", + "required": [ + "build_root", + "checker_id", + "category", + "configuration", + "configuration_ref", + "reason", + "next_action" + ], + "additionalProperties": false, + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "checker_id": { + "type": "string", + "minLength": 1 + }, + "category": { + "enum": [ + "lint", + "comments", + "dependencies", + "cve", + "security", + "build" + ] + }, + "configuration": { + "enum": [ + "configured", + "missing", + "invalid", + "unknown" + ] + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "next_action": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "else": { + "properties": { + "status": { + "const": "incomplete" + }, + "findings": { + "maxItems": 0 + }, + "dependency_total": { + "type": "null" + } + } + } + } + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm局部CVE反馈,不能自证库或门禁权威", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "status", + "reason", + "local_coherent", + "configuration", + "dependency_total", + "findings", + "advisory_coverage", + "tool_approval", + "delivery_decision", + "workbench_status", + "next_action", + "execution_budget" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "npm_cve_feedback" + }, + "status": { + "enum": [ + "incomplete", + "local_observation" + ] + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "local_coherent": { + "type": "boolean" + }, + "configuration": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "build_root", + "checker_id", + "category", + "configuration", + "configuration_ref", + "reason", + "next_action" + ], + "additionalProperties": false, + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "checker_id": { + "type": "string", + "minLength": 1 + }, + "category": { + "enum": [ + "lint", + "comments", + "dependencies", + "cve", + "security", + "build" + ] + }, + "configuration": { + "enum": [ + "configured", + "missing", + "invalid", + "unknown" + ] + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "next_action": { + "type": "string", + "minLength": 1 + } + } + } + ] + }, + "dependency_total": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "component_ref", + "severity", + "advisory_sources", + "nodes" + ], + "properties": { + "component_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "severity": { + "enum": [ + "info", + "low", + "moderate", + "high", + "critical" + ] + }, + "advisory_sources": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "integer", + "minimum": 1 + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "node_ref", + "resolved_version" + ], + "properties": { + "node_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "resolved_version": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + } + } + } + } + } + }, + "advisory_coverage": { + "const": "not_evaluated" + }, + "tool_approval": { + "const": "unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "workbench_status": { + "const": "not_connected" + }, + "next_action": { + "type": "string", + "minLength": 1 + }, + "execution_budget": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "timeout_ms", + "source", + "enforcement" + ], + "properties": { + "timeout_ms": { + "type": "integer", + "minimum": 1, + "maximum": 86400000 + }, + "source": { + "enum": [ + "cli", + "registered_environment", + "project_default", + "builtin_default" + ] + }, + "enforcement": { + "const": "native_execution_only" + } + } + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "local_coherent": { + "const": true + } + } + }, + "then": { + "properties": { + "status": { + "const": "local_observation" + }, + "dependency_total": { + "type": "integer" + }, + "configuration": { + "type": "object", + "required": [ + "build_root", + "checker_id", + "category", + "configuration", + "configuration_ref", + "reason", + "next_action" + ], + "additionalProperties": false, + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "checker_id": { + "type": "string", + "minLength": 1 + }, + "category": { + "enum": [ + "lint", + "comments", + "dependencies", + "cve", + "security", + "build" + ] + }, + "configuration": { + "enum": [ + "configured", + "missing", + "invalid", + "unknown" + ] + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "next_action": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "else": { + "properties": { + "status": { + "const": "incomplete" + }, + "findings": { + "maxItems": 0 + }, + "dependency_total": { + "type": "null" + } + } + } + } + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm局部CVE反馈,不能自证库或门禁权威", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "status", + "reason", + "local_coherent", + "configuration", + "dependency_total", + "findings", + "advisory_coverage", + "tool_approval", + "delivery_decision", + "workbench_status", + "next_action", + "execution_budget", + "workbench" + ], + "properties": { + "schema_version": { + "const": "0.3.0" + }, + "report_type": { + "const": "npm_cve_feedback" + }, + "status": { + "enum": [ + "incomplete", + "local_observation" + ] + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "local_coherent": { + "type": "boolean" + }, + "configuration": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "build_root", + "checker_id", + "category", + "configuration", + "configuration_ref", + "reason", + "next_action" + ], + "additionalProperties": false, + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "checker_id": { + "type": "string", + "minLength": 1 + }, + "category": { + "enum": [ + "lint", + "comments", + "dependencies", + "cve", + "security", + "build" + ] + }, + "configuration": { + "enum": [ + "configured", + "missing", + "invalid", + "unknown" + ] + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "next_action": { + "type": "string", + "minLength": 1 + } + } + } + ] + }, + "dependency_total": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "component_ref", + "severity", + "advisory_sources", + "nodes" + ], + "properties": { + "component_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "severity": { + "enum": [ + "info", + "low", + "moderate", + "high", + "critical" + ] + }, + "advisory_sources": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "integer", + "minimum": 1 + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "node_ref", + "resolved_version" + ], + "properties": { + "node_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "resolved_version": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + } + } + } + } + } + }, + "advisory_coverage": { + "const": "not_evaluated" + }, + "tool_approval": { + "const": "unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "workbench_status": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]*$" + }, + "next_action": { + "type": "string", + "minLength": 1 + }, + "execution_budget": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "timeout_ms", + "source", + "enforcement" + ], + "properties": { + "timeout_ms": { + "type": "integer", + "minimum": 1, + "maximum": 86400000 + }, + "source": { + "enum": [ + "cli", + "registered_environment", + "project_default", + "builtin_default" + ] + }, + "enforcement": { + "const": "native_execution_only" + } + } + } + ] + }, + "workbench": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]*$" + }, + "new_blockers": { + "type": "integer", + "minimum": 0 + }, + "failed_reports": { + "type": "integer", + "minimum": 0 + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "enum": [ + "synced_partial", + "sync_incomplete" + ] + } + } + }, + "then": { + "required": [ + "new_blockers", + "failed_reports" + ] + } + } + ] + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "local_coherent": { + "const": true + } + } + }, + "then": { + "properties": { + "status": { + "const": "local_observation" + }, + "dependency_total": { + "type": "integer" + }, + "configuration": { + "type": "object", + "required": [ + "build_root", + "checker_id", + "category", + "configuration", + "configuration_ref", + "reason", + "next_action" + ], + "additionalProperties": false, + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "checker_id": { + "type": "string", + "minLength": 1 + }, + "category": { + "enum": [ + "lint", + "comments", + "dependencies", + "cve", + "security", + "build" + ] + }, + "configuration": { + "enum": [ + "configured", + "missing", + "invalid", + "unknown" + ] + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "next_action": { + "type": "string", + "minLength": 1 + } + } + } + } + }, + "else": { + "properties": { + "status": { + "const": "incomplete" + }, + "findings": { + "maxItems": 0 + }, + "dependency_total": { + "type": "null" + } + } + } + } + ] + } + ] + } + ] + }, + "observation": { + "oneOf": [ + { + "type": "null" + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm工作台观察0.1/0.2与输入状态准备观察0.3;不授予覆盖", + "oneOf": [ + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm局部CVE工作台观察,不能自证漏洞或关闭", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "workspace_binding", + "workspace_id", + "run_id", + "authority", + "coverage_proven", + "delivery_decision", + "checker_id", + "build_root", + "manifest_sha256", + "lock_sha256", + "diagnostic_reason", + "component_count", + "advisories" + ], + "properties": { + "schema_version": { + "const": "0.1.0" + }, + "report_type": { + "const": "npm_cve_workbench_observation" + }, + "workspace_binding": { + "const": "bound" + }, + "workspace_id": { + "type": "string", + "minLength": 1 + }, + "run_id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{1,120}$" + }, + "authority": { + "const": "local_unverified" + }, + "coverage_proven": { + "const": false + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "checker_id": { + "const": "node.npm.audit" + }, + "build_root": { + "type": "string", + "minLength": 1 + }, + "manifest_sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "lock_sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "diagnostic_reason": { + "type": "string", + "pattern": "^[a-z0-9_]{1,80}$" + }, + "component_count": { + "type": "integer", + "minimum": 0, + "maximum": 10000 + }, + "advisories": { + "type": "array", + "maxItems": 10000, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "component_ref", + "severity", + "advisory_sources", + "nodes" + ], + "properties": { + "component_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "severity": { + "enum": [ + "info", + "low", + "moderate", + "high", + "critical" + ] + }, + "advisory_sources": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "integer", + "minimum": 1 + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "node_ref", + "resolved_version" + ], + "properties": { + "node_ref": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "resolved_version": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } + } + } + } + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "diagnostic_reason": { + "enum": [ + "npm_execution_context_missing", + "npm_manifest_invalid", + "npm_scripts_invalid" + ] + } + }, + "required": [ + "diagnostic_reason" + ] + }, + "then": { + "properties": { + "component_count": { + "const": 0 + }, + "advisories": { + "maxItems": 0 + } + } + } + }, + { + "not": { + "properties": { + "diagnostic_reason": { + "const": "npm_lock_missing" + } + }, + "required": [ + "diagnostic_reason" + ] + } + } + ] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm缺锁准备观察;未启动工具且没有漏洞结果", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "workspace_binding", + "workspace_id", + "run_id", + "authority", + "coverage_proven", + "delivery_decision", + "checker_id", + "build_root", + "manifest_sha256", + "lock_sha256", + "diagnostic_reason", + "component_count", + "advisories", + "lock_state" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "npm_cve_workbench_observation" + }, + "workspace_binding": { + "const": "bound" + }, + "workspace_id": { + "type": "string", + "minLength": 1 + }, + "run_id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{1,120}$" + }, + "authority": { + "const": "local_unverified" + }, + "coverage_proven": { + "const": false + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "checker_id": { + "const": "node.npm.audit" + }, + "build_root": { + "type": "string", + "minLength": 1 + }, + "manifest_sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + }, + "lock_sha256": { + "type": "null" + }, + "diagnostic_reason": { + "const": "npm_lock_missing" + }, + "component_count": { + "const": 0 + }, + "advisories": { + "type": "array", + "maxItems": 0 + }, + "lock_state": { + "const": "missing" + } + } + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "npm输入不可用的准备观察0.3;无原生漏洞结果", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "workspace_binding", + "workspace_id", + "run_id", + "authority", + "coverage_proven", + "delivery_decision", + "checker_id", + "build_root", + "manifest_sha256", + "lock_sha256", + "diagnostic_reason", + "component_count", + "advisories", + "manifest_state", + "lock_state" + ], + "properties": { + "schema_version": { + "const": "0.3.0" + }, + "report_type": { + "const": "npm_cve_workbench_observation" + }, + "workspace_binding": { + "const": "bound" + }, + "workspace_id": { + "type": "string", + "minLength": 1 + }, + "run_id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]{1,120}$" + }, + "authority": { + "const": "local_unverified" + }, + "coverage_proven": { + "const": false + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "checker_id": { + "const": "node.npm.audit" + }, + "build_root": { + "type": "string", + "minLength": 1 + }, + "manifest_sha256": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + } + ] + }, + "lock_sha256": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^[a-f0-9]{64}$" + } + ] + }, + "diagnostic_reason": { + "enum": [ + "npm_manifest_missing", + "npm_manifest_not_regular", + "npm_manifest_path_alias", + "npm_manifest_unavailable", + "npm_lock_missing", + "npm_lock_not_regular", + "npm_lock_path_alias", + "npm_lock_unavailable" + ] + }, + "component_count": { + "const": 0 + }, + "advisories": { + "type": "array", + "maxItems": 0 + }, + "manifest_state": { + "enum": [ + "present", + "missing", + "not_regular", + "path_alias", + "unavailable" + ] + }, + "lock_state": { + "enum": [ + "present", + "missing", + "not_regular", + "path_alias", + "unavailable" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "manifest_state": { + "const": "present" + } + } + }, + "then": { + "properties": { + "manifest_sha256": { + "type": "string" + } + } + }, + "else": { + "properties": { + "manifest_sha256": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "lock_state": { + "const": "present" + } + } + }, + "then": { + "properties": { + "lock_sha256": { + "type": "string" + } + } + }, + "else": { + "properties": { + "lock_sha256": { + "type": "null" + } + } + } + }, + { + "not": { + "properties": { + "manifest_state": { + "const": "present" + }, + "lock_state": { + "const": "present" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "missing" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_manifest_missing" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "present" + }, + "lock_state": { + "const": "missing" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_lock_missing" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "not_regular" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_manifest_not_regular" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "present" + }, + "lock_state": { + "const": "not_regular" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_lock_not_regular" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "path_alias" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_manifest_path_alias" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "present" + }, + "lock_state": { + "const": "path_alias" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_lock_path_alias" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "unavailable" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_manifest_unavailable" + } + } + } + }, + { + "if": { + "properties": { + "manifest_state": { + "const": "present" + }, + "lock_state": { + "const": "unavailable" + } + } + }, + "then": { + "properties": { + "diagnostic_reason": { + "const": "npm_lock_unavailable" + } + } + } + } + ] + } + ] + } + ] + }, + "backlog_status": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,127}$" + } + } + }, + "rust_comments": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Unverified selected Cargo rustdoc library observation", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "operation", + "language", + "command_status", + "exit_code", + "delivery_decision", + "authority", + "checker_id", + "scope", + "rule_selection", + "local_scan_complete", + "coverage_proven", + "reason", + "tool_sha256", + "manifest_sha256", + "lock_sha256", + "findings", + "backlog_status", + "execution_budget", + "next_actions", + "recheck_argv", + "workspace_binding", + "workspace_id", + "run_id", + "backlog_sync" + ], + "properties": { + "schema_version": { + "const": "0.4.0" + }, + "report_type": { + "const": "rustdoc_local_observation" + }, + "operation": { + "const": "comments" + }, + "language": { + "const": "rust" + }, + "command_status": { + "enum": [ + "incomplete", + "cancelled" + ] + }, + "exit_code": { + "enum": [ + 3, + 130 + ] + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "authority": { + "const": "local_unverified" + }, + "checker_id": { + "const": "rust.cargo_rustdoc" + }, + "scope": { + "const": "selected_root_library_default_features" + }, + "rule_selection": { + "const": "explicit_probe_not_approved_project_policy" + }, + "local_scan_complete": { + "type": "boolean" + }, + "coverage_proven": { + "const": false + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "tool_sha256": { + "$ref": "#/$defs/rust_comments/$defs/hash" + }, + "manifest_sha256": { + "$ref": "#/$defs/rust_comments/$defs/hash" + }, + "lock_sha256": { + "$ref": "#/$defs/rust_comments/$defs/hash" + }, + "backlog_status": { + "enum": [ + "not_integrated", + "queued", + "synced", + "sync_incomplete", + "failed" + ] + }, + "execution_budget": { + "type": "object", + "additionalProperties": false, + "required": [ + "timeout_ms", + "source", + "enforcement" + ], + "properties": { + "timeout_ms": { + "type": "integer", + "minimum": 1, + "maximum": 86400000 + }, + "source": { + "enum": [ + "cli", + "registered_environment", + "project_default", + "builtin_default" + ] + }, + "enforcement": { + "const": "native_execution_only" + } + } + }, + "next_actions": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "recheck_argv": { + "const": [ + "cargo", + "rustdoc", + "--lib", + "--locked", + "--offline", + "--message-format=json", + "--", + "--warn", + "missing_docs", + "--warn", + "rustdoc::broken_intra_doc_links" + ] + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "finding_id", + "finding_fingerprint", + "source_sha256", + "rule_id", + "level", + "path", + "line", + "column", + "classification", + "byte_start", + "byte_end", + "native_range_sha256", + "identity_status", + "repair_brief", + "target_path" + ], + "properties": { + "finding_id": { + "type": [ + "string", + "null" + ], + "pattern": "^CG-[0-9a-f]{32}$" + }, + "finding_fingerprint": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "rule_id": { + "enum": [ + "missing_docs", + "rustdoc::broken_intra_doc_links" + ] + }, + "level": { + "enum": [ + "warning", + "error" + ] + }, + "path": { + "type": "string", + "minLength": 1 + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 1 + }, + "classification": { + "const": "observed_unverified" + }, + "byte_start": { + "type": "integer", + "minimum": 0 + }, + "byte_end": { + "type": "integer", + "minimum": 0 + }, + "native_range_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "identity_status": { + "enum": [ + "unique_candidate", + "ambiguous" + ] + }, + "repair_brief": { + "$ref": "#/$defs/rust_comments/$defs/repair_brief" + }, + "target_path": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "identity_status": { + "const": "ambiguous" + } + } + }, + "then": { + "properties": { + "finding_id": { + "type": "null" + }, + "repair_brief": { + "properties": { + "status": { + "const": "investigation_required" + } + } + } + } + }, + "else": { + "properties": { + "finding_id": { + "type": "string" + } + } + } + } + ] + } + }, + "workspace_binding": { + "enum": [ + "bound", + "uninitialized", + "invalid", + "legacy_unbound" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + }, + "run_id": { + "type": "string", + "pattern": "^rustdoc-[0-9]+-[0-9]+$" + }, + "backlog_sync": { + "type": [ + "object", + "null" + ] + } + }, + "$defs": { + "hash": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "repair_brief": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "evidence", + "rule_basis", + "allowed_paths", + "steps", + "recheck_argv", + "working_directory", + "tool_requirement", + "attempt_history", + "history_status", + "closure_conditions" + ], + "properties": { + "status": { + "enum": [ + "repair_guidance", + "investigation_required" + ] + }, + "evidence": { + "type": "object", + "additionalProperties": false, + "required": [ + "finding_id", + "finding_fingerprint", + "path", + "source_sha256", + "byte_start", + "byte_end", + "rule_id", + "observation_reason" + ], + "properties": { + "finding_id": { + "type": [ + "string", + "null" + ], + "pattern": "^CG-[0-9a-f]{32}$" + }, + "finding_fingerprint": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "path": { + "type": "string", + "minLength": 1 + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "byte_start": { + "type": "integer", + "minimum": 0 + }, + "byte_end": { + "type": "integer", + "minimum": 0 + }, + "rule_id": { + "enum": [ + "missing_docs", + "rustdoc::broken_intra_doc_links" + ] + }, + "observation_reason": { + "type": "string", + "minLength": 1 + } + } + }, + "rule_basis": { + "type": "string", + "minLength": 1 + }, + "allowed_paths": { + "type": "array", + "maxItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "recheck_argv": { + "const": [ + "cargo", + "rustdoc", + "--lib", + "--locked", + "--offline", + "--message-format=json", + "--", + "--warn", + "missing_docs", + "--warn", + "rustdoc::broken_intra_doc_links" + ] + }, + "working_directory": { + "const": "selected_project_root" + }, + "tool_requirement": { + "type": "string", + "minLength": 1 + }, + "attempt_history": { + "const": [] + }, + "history_status": { + "const": "not_integrated" + }, + "closure_conditions": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "investigation_required" + } + } + }, + "then": { + "properties": { + "allowed_paths": { + "maxItems": 0 + } + } + }, + "else": { + "properties": { + "allowed_paths": { + "minItems": 1 + } + } + } + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "command_status": { + "const": "cancelled" + } + } + }, + "then": { + "properties": { + "exit_code": { + "const": 130 + }, + "reason": { + "const": "request_cancelled" + }, + "local_scan_complete": { + "const": false + } + } + }, + "else": { + "properties": { + "exit_code": { + "const": 3 + } + } + } + }, + { + "if": { + "properties": { + "local_scan_complete": { + "const": true + } + } + }, + "then": { + "properties": { + "reason": { + "const": "native_observed_unverified" + }, + "tool_sha256": { + "type": "string" + }, + "manifest_sha256": { + "type": "string" + }, + "lock_sha256": { + "type": "string" + }, + "findings": { + "items": { + "properties": { + "identity_status": { + "const": "unique_candidate" + } + } + } + } + } + } + }, + { + "if": { + "properties": { + "local_scan_complete": { + "const": false + } + } + }, + "then": { + "properties": { + "findings": { + "items": { + "properties": { + "repair_brief": { + "properties": { + "status": { + "const": "investigation_required" + } + } + } + } + } + } + } + } + }, + { + "if": { + "properties": { + "workspace_binding": { + "const": "bound" + } + } + }, + "then": { + "properties": { + "workspace_id": { + "type": "string", + "minLength": 1 + } + } + }, + "else": { + "properties": { + "workspace_id": { + "type": "null" + }, + "backlog_status": { + "const": "not_integrated" + }, + "backlog_sync": { + "type": "null" + } + } + } + } + ] + }, + "rust_build": { + "title": "Unverified selected Cargo type check observation", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "operation", + "language", + "command_status", + "exit_code", + "delivery_decision", + "authority", + "checker_id", + "scope", + "rule_selection", + "local_scan_complete", + "coverage_proven", + "reason", + "tool_sha256", + "manifest_sha256", + "lock_sha256", + "findings", + "backlog_status", + "execution_budget", + "next_actions", + "recheck_argv", + "workspace_binding", + "workspace_id", + "run_id", + "backlog_sync", + "build_level", + "test_execution", + "build_success" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "rust_build_local_observation" + }, + "operation": { + "const": "build" + }, + "language": { + "const": "rust" + }, + "command_status": { + "enum": [ + "incomplete", + "cancelled" + ] + }, + "exit_code": { + "enum": [ + 3, + 130 + ] + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "authority": { + "const": "local_unverified" + }, + "checker_id": { + "const": "rust.cargo_check" + }, + "scope": { + "const": "selected_root_all_targets_default_features" + }, + "rule_selection": { + "const": "explicit_probe_not_approved_project_policy" + }, + "local_scan_complete": { + "type": "boolean" + }, + "coverage_proven": { + "const": false + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "tool_sha256": { + "$ref": "#/$defs/rust_build/$defs/hash" + }, + "manifest_sha256": { + "$ref": "#/$defs/rust_build/$defs/hash" + }, + "lock_sha256": { + "$ref": "#/$defs/rust_build/$defs/hash" + }, + "backlog_status": { + "enum": [ + "not_integrated", + "queued", + "synced", + "sync_incomplete", + "failed" + ] + }, + "execution_budget": { + "type": "object", + "additionalProperties": false, + "required": [ + "timeout_ms", + "source", + "enforcement" + ], + "properties": { + "timeout_ms": { + "type": "integer", + "minimum": 1, + "maximum": 86400000 + }, + "source": { + "enum": [ + "cli", + "registered_environment", + "project_default", + "builtin_default" + ] + }, + "enforcement": { + "const": "native_execution_only" + } + } + }, + "next_actions": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "recheck_argv": { + "const": [ + "cargo", + "check", + "--locked", + "--offline", + "--all-targets", + "--message-format=json" + ] + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "finding_id", + "finding_fingerprint", + "source_sha256", + "rule_id", + "level", + "path", + "line", + "column", + "classification", + "byte_start", + "byte_end", + "native_range_sha256", + "identity_status", + "repair_brief", + "target_path", + "package_id_sha256", + "target_kinds", + "target_sha256" + ], + "properties": { + "finding_id": { + "type": [ + "string", + "null" + ], + "pattern": "^CG-[0-9a-f]{32}$" + }, + "finding_fingerprint": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "rule_id": { + "type": "string", + "pattern": "^E[0-9]{4}$" + }, + "level": { + "const": "error" + }, + "path": { + "type": "string", + "minLength": 1 + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 1 + }, + "classification": { + "const": "observed_unverified" + }, + "byte_start": { + "type": "integer", + "minimum": 0 + }, + "byte_end": { + "type": "integer", + "minimum": 0 + }, + "native_range_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "identity_status": { + "enum": [ + "unique_candidate", + "ambiguous" + ] + }, + "repair_brief": { + "$ref": "#/$defs/rust_build/$defs/repair_brief" + }, + "target_path": { + "type": "string", + "minLength": 1 + }, + "package_id_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "target_kinds": { + "type": "array", + "minItems": 1, + "maxItems": 16, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 8192 + } + }, + "target_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + }, + "allOf": [ + { + "if": { + "properties": { + "identity_status": { + "const": "ambiguous" + } + } + }, + "then": { + "properties": { + "finding_id": { + "type": "null" + }, + "repair_brief": { + "properties": { + "status": { + "const": "investigation_required" + } + } + } + } + }, + "else": { + "properties": { + "finding_id": { + "type": "string" + } + } + } + } + ] + } + }, + "workspace_binding": { + "enum": [ + "bound", + "uninitialized", + "invalid", + "legacy_unbound" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + }, + "run_id": { + "type": "string", + "pattern": "^cargo-build-[0-9]+-[0-9]+$" + }, + "backlog_sync": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "new_findings", + "new_blockers", + "failed_reports" + ], + "properties": { + "new_findings": { + "type": "integer", + "minimum": 0 + }, + "new_blockers": { + "type": "integer", + "minimum": 0 + }, + "failed_reports": { + "type": "integer", + "minimum": 0 + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "reason" + ], + "properties": { + "reason": { + "type": "string", + "minLength": 1 + } + } + } + ] + }, + "build_level": { + "const": "type_check" + }, + "test_execution": { + "const": false + }, + "build_success": { + "type": [ + "boolean", + "null" + ] + } + }, + "$defs": { + "hash": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "repair_brief": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "evidence", + "rule_basis", + "allowed_paths", + "steps", + "recheck_argv", + "working_directory", + "tool_requirement", + "attempt_history", + "history_status", + "closure_conditions" + ], + "properties": { + "status": { + "enum": [ + "repair_guidance", + "investigation_required" + ] + }, + "evidence": { + "type": "object", + "additionalProperties": false, + "required": [ + "finding_id", + "finding_fingerprint", + "path", + "source_sha256", + "byte_start", + "byte_end", + "rule_id", + "observation_reason", + "native_range_sha256", + "line", + "column" + ], + "properties": { + "finding_id": { + "type": [ + "string", + "null" + ], + "pattern": "^CG-[0-9a-f]{32}$" + }, + "finding_fingerprint": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "path": { + "type": "string", + "minLength": 1 + }, + "source_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "byte_start": { + "type": "integer", + "minimum": 0 + }, + "byte_end": { + "type": "integer", + "minimum": 0 + }, + "rule_id": { + "type": "string", + "pattern": "^E[0-9]{4}$" + }, + "observation_reason": { + "type": "string", + "minLength": 1 + }, + "native_range_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 1 + } + } + }, + "rule_basis": { + "type": "string", + "minLength": 1 + }, + "allowed_paths": { + "type": "array", + "maxItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "steps": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "recheck_argv": { + "const": [ + "cargo", + "check", + "--locked", + "--offline", + "--all-targets", + "--message-format=json" + ] + }, + "working_directory": { + "const": "selected_project_root" + }, + "tool_requirement": { + "type": "string", + "minLength": 1 + }, + "attempt_history": { + "const": [] + }, + "history_status": { + "const": "not_integrated" + }, + "closure_conditions": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "investigation_required" + } + } + }, + "then": { + "properties": { + "allowed_paths": { + "maxItems": 0 + } + } + }, + "else": { + "properties": { + "allowed_paths": { + "minItems": 1 + } + } + } + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "command_status": { + "const": "cancelled" + } + } + }, + "then": { + "properties": { + "exit_code": { + "const": 130 + }, + "reason": { + "const": "request_cancelled" + }, + "local_scan_complete": { + "const": false + } + } + }, + "else": { + "properties": { + "exit_code": { + "const": 3 + } + } + } + }, + { + "if": { + "properties": { + "local_scan_complete": { + "const": true + } + } + }, + "then": { + "properties": { + "reason": { + "const": "native_observed_unverified" + }, + "tool_sha256": { + "type": "string" + }, + "manifest_sha256": { + "type": "string" + }, + "lock_sha256": { + "type": "string" + }, + "findings": { + "items": { + "properties": { + "identity_status": { + "const": "unique_candidate" + } + } + } + }, + "build_success": { + "type": "boolean" + } + } + } + }, + { + "if": { + "properties": { + "local_scan_complete": { + "const": false + } + } + }, + "then": { + "properties": { + "findings": { + "items": { + "properties": { + "repair_brief": { + "properties": { + "status": { + "const": "investigation_required" + } + } + } + } + } + } + } + } + }, + { + "if": { + "properties": { + "workspace_binding": { + "const": "bound" + } + } + }, + "then": { + "properties": { + "workspace_id": { + "type": "string", + "minLength": 1 + } + } + }, + "else": { + "properties": { + "workspace_id": { + "type": "null" + }, + "backlog_status": { + "const": "not_integrated" + }, + "backlog_sync": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "local_scan_complete": { + "const": true + }, + "build_success": { + "const": true + } + } + }, + "then": { + "properties": { + "findings": { + "maxItems": 0 + } + } + } + }, + { + "if": { + "properties": { + "local_scan_complete": { + "const": true + }, + "build_success": { + "const": false + } + } + }, + "then": { + "properties": { + "findings": { + "minItems": 1 + } + } + } + } + ] + } + }, + "type": "object", + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "selection": { + "const": "all" + } + }, + "required": [ + "selection" + ] + }, + "then": { + "properties": { + "delivery_decision": { + "const": "incomplete" + } + } + }, + "else": { + "properties": { + "delivery_decision": { + "const": "not_evaluated" + }, + "native_results": { + "properties": { + "python_lint": { + "type": "null" + }, + "python_cve": { + "type": "array", + "maxItems": 0 + }, + "rust_lint": { + "type": "null" + }, + "rust_cve": { + "type": "null" + }, + "go_lint": { + "type": "null" + }, + "npm_cve": { + "type": "array", + "maxItems": 0 + } + } + }, + "execution_tasks": { + "items": { + "properties": { + "id": { + "enum": [ + "java.p3c", + "java.javadoc", + "java.dependencies", + "java.cve" + ] + } + } + } + }, + "category_candidates": { + "items": { + "properties": { + "language": { + "const": "java" + } + } + } + } + } + } + }, + { + "if": { + "properties": { + "command_status": { + "const": "cancelled" + } + }, + "required": [ + "command_status" + ] + }, + "then": { + "properties": { + "exit_code": { + "const": 130 + }, + "reason": { + "const": "request_cancelled" + } + } + }, + "else": { + "properties": { + "exit_code": { + "const": 3 + }, + "reason": { + "enum": [ + "full_project_obligations_and_trusted_policy_unavailable", + "java_selection_obligations_and_trusted_policy_unavailable" + ] + } + } + } + }, + { + "if": { + "properties": { + "selection": { + "const": "all" + }, + "command_status": { + "const": "incomplete" + } + }, + "required": [ + "selection", + "command_status" + ] + }, + "then": { + "properties": { + "reason": { + "const": "full_project_obligations_and_trusted_policy_unavailable" + } + } + } + }, + { + "if": { + "properties": { + "selection": { + "const": "java" + }, + "command_status": { + "const": "incomplete" + } + }, + "required": [ + "selection", + "command_status" + ] + }, + "then": { + "properties": { + "reason": { + "const": "java_selection_obligations_and_trusted_policy_unavailable" + } + } + } + } + ], + "required": [ + "schema_version", + "report_type", + "operation", + "selection", + "command_status", + "exit_code", + "delivery_decision", + "authority", + "reason", + "discovery", + "native_results", + "execution_tasks", + "obligation_status", + "required_obligations", + "category_candidates", + "syntax_candidates", + "unresolved_conditions", + "execution_budget", + "next", + "export" + ], + "properties": { + "schema_version": { + "const": "0.33.0" + }, + "export": { + "type": "object", + "additionalProperties": false, + "required": [ + "status", + "reason_code" + ], + "properties": { + "status": { + "enum": [ + "not_requested", + "saved", + "failed" + ] + }, + "reason_code": { + "type": [ + "string", + "null" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "failed" + } + }, + "required": [ + "status" + ] + }, + "then": { + "properties": { + "reason_code": { + "enum": [ + "output_parent_unavailable", + "output_not_regular_file", + "output_existing_file_is_not_codeguard_report", + "output_identity_unavailable", + "output_temp_create_failed", + "output_temp_write_failed", + "output_replace_failed", + "output_existing_read_failed", + "output_existing_too_large" + ] + } + } + }, + "else": { + "properties": { + "reason_code": { + "type": "null" + } + } + } + } + ] + }, + "execution_tasks": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "status" + ], + "properties": { + "id": { + "oneOf": [ + { + "enum": [ + "python.lint", + "rust.lint", + "go.lint", + "java.p3c", + "java.javadoc", + "java.dependencies", + "java.cve", + "rust.comments", + "rust.build", + "rust.cve" + ] + }, + { + "type": "string", + "pattern": "^npm\\.cve\\.[0-9]+$" + }, + { + "type": "string", + "pattern": "^python\\.cve\\.[0-9]+$" + } + ] + }, + "status": { + "enum": [ + "native_observed_unverified", + "native_incomplete", + "cancelled", + "deadline_exceeded", + "dependency_failed", + "cancelled_before_start", + "deadline_before_start", + "internal_failure" + ] + } + } + } + }, + "execution_budget": { + "type": "object", + "additionalProperties": false, + "required": [ + "timeout_ms", + "source", + "enforcement", + "jobs_limit", + "jobs_source", + "native_task_count", + "started_native_task_count" + ], + "properties": { + "timeout_ms": { + "type": "integer", + "minimum": 1, + "maximum": 86400000 + }, + "source": { + "enum": [ + "cli", + "registered_environment", + "project_default", + "builtin_default" + ] + }, + "enforcement": { + "const": "native_execution_only" + }, + "jobs_limit": { + "type": "integer", + "minimum": 1, + "maximum": 64 + }, + "jobs_source": { + "enum": [ + "cli", + "registered_environment", + "project_default", + "builtin_default" + ] + }, + "native_task_count": { + "type": "integer", + "minimum": 0 + }, + "started_native_task_count": { + "type": "integer", + "minimum": 0 + } + } + }, + "report_type": { + "const": "check_feedback" + }, + "operation": { + "const": "check" + }, + "selection": { + "enum": [ + "all", + "java" + ] + }, + "command_status": { + "enum": [ + "incomplete", + "cancelled" + ] + }, + "exit_code": { + "enum": [ + 3, + 130 + ] + }, + "delivery_decision": { + "enum": [ + "incomplete", + "not_evaluated" + ] + }, + "authority": { + "const": "local_unverified" + }, + "reason": { + "enum": [ + "full_project_obligations_and_trusted_policy_unavailable", + "java_selection_obligations_and_trusted_policy_unavailable", + "request_cancelled" + ] + }, + "discovery": { + "type": "object", + "required": [ + "schema_version", + "report_type", + "observation_complete", + "languages", + "native_tool_candidates" + ], + "properties": { + "schema_version": { + "const": "0.4.0" + }, + "report_type": { + "const": "discovery" + }, + "observation_complete": { + "type": "boolean" + }, + "languages": { + "type": "array" + }, + "native_tool_candidates": { + "type": "array" + } + } + }, + "native_results": { + "type": "object", + "additionalProperties": false, + "required": [ + "python_lint", + "python_cve", + "rust_lint", + "go_lint", + "java_p3c", + "java_javadoc", + "java_dependencies", + "java_cve", + "npm_cve", + "rust_comments", + "rust_build", + "rust_cve" + ], + "properties": { + "python_cve": { + "type": "array", + "maxItems": 1000, + "items": { + "type": "object", + "required": ["build_root", "reason", "native_report_valid", "findings"], + "properties": { + "build_root": {"type": "string"}, + "reason": {"type": "string"}, + "native_report_valid": {"type": "boolean"}, + "findings": {"type": "array"}, + "delivery_decision": {"const": "not_evaluated"}, + "coverage_proven": {"const": false} + } + } + }, + "go_lint": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "operation", + "language", + "checker_id", + "command_status", + "exit_code", + "delivery_decision", + "coverage_proven", + "authority", + "tool_approval", + "rulepack_approval", + "native_status", + "reason", + "native_tool_version", + "tool_sha256", + "manifest_sha256", + "go_sum_sha256", + "source_file_count", + "module_count", + "modules_completed", + "module_results", + "native_diagnostic_count", + "findings", + "native_recheck_argv", + "next_actions", + "workspace_binding", + "workspace_id", + "run_id", + "module_identities", + "backlog_status", + "backlog_sync", + "source_snapshot_sha256" + ], + "properties": { + "schema_version": { + "const": "0.6.0" + }, + "report_type": { + "const": "go_lint_local_feedback" + }, + "operation": { + "const": "lint" + }, + "language": { + "const": "go" + }, + "checker_id": { + "const": "go.vet" + }, + "command_status": { + "const": "incomplete" + }, + "exit_code": { + "const": 3 + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "tool_approval": { + "const": "unverified" + }, + "rulepack_approval": { + "const": "unverified" + }, + "native_status": { + "enum": [ + "incomplete", + "findings_observed_unverified", + "clean_observed_unverified" + ] + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "root": { + "type": "string" + }, + "native_tool_version": { + "type": [ + "string", + "null" + ] + }, + "tool_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "manifest_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "go_sum_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "source_file_count": { + "type": "integer", + "minimum": 0 + }, + "module_count": { + "type": "integer", + "minimum": 0 + }, + "modules_completed": { + "type": "integer", + "minimum": 0 + }, + "module_results": { + "type": "array", + "items": { + "$ref": "#/$defs/goModuleResult" + } + }, + "native_diagnostic_count": { + "type": "integer", + "minimum": 0 + }, + "findings": { + "type": "array", + "items": { + "$ref": "#/$defs/goFinding" + } + }, + "native_recheck_argv": { + "const": [ + "go", + "vet", + "-json", + "./..." + ] + }, + "next_actions": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "workspace_binding": { + "enum": [ + "bound", + "legacy_unbound", + "uninitialized", + "invalid" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + }, + "run_id": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$" + }, + "module_identities": { + "type": "object", + "maxProperties": 64, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": [ + "manifest_sha256", + "go_sum_sha256" + ], + "properties": { + "manifest_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "go_sum_sha256": { + "$ref": "#/$defs/nullableSha256" + } + } + } + }, + "backlog_status": { + "enum": [ + "not_attempted", + "synced_partial", + "not_initialized", + "backlog_update_failed" + ] + }, + "backlog_sync": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "new_findings", + "new_blockers", + "imported_reports", + "historical_findings", + "failed_reports" + ], + "properties": { + "new_findings": { + "type": "integer", + "minimum": 0 + }, + "new_blockers": { + "type": "integer", + "minimum": 0 + }, + "imported_reports": { + "type": "integer", + "minimum": 0 + }, + "historical_findings": { + "type": "integer", + "minimum": 0 + }, + "failed_reports": { + "type": "integer", + "minimum": 0 + } + } + } + ] + }, + "source_snapshot_sha256": { + "$ref": "#/$defs/nullableSha256" + } + } + } + ] + }, + "rust_lint": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "checker_id", + "configuration", + "configuration_ref", + "local_scan_complete", + "reason", + "tool_sha256", + "findings", + "scope", + "coverage_proven", + "authority", + "recheck_command", + "operation", + "language", + "command_status", + "delivery_decision", + "exit_code", + "workspace_binding", + "workspace_id", + "run_id", + "tool_approval", + "rulepack_approval", + "manifest_sha256", + "backlog_status", + "backlog_sync", + "next" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "rust_clippy_local_observation" + }, + "checker_id": { + "const": "rust.cargo_clippy" + }, + "configuration": { + "enum": [ + "unknown", + "configured" + ] + }, + "configuration_ref": { + "type": [ + "string", + "null" + ] + }, + "local_scan_complete": { + "type": "boolean" + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "tool_sha256": { + "type": [ + "string", + "null" + ] + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "rule_id", + "path", + "line", + "column", + "level", + "finding_id", + "finding_fingerprint", + "source_sha256" + ], + "properties": { + "rule_id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 1 + }, + "level": { + "enum": [ + "warning", + "error" + ] + }, + "finding_id": { + "type": "string" + }, + "finding_fingerprint": { + "type": "string" + }, + "source_sha256": { + "type": "string" + } + } + } + }, + "scope": { + "const": "default_features_all_targets" + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "recheck_command": { + "type": "string" + }, + "operation": { + "const": "lint" + }, + "language": { + "const": "rust" + }, + "command_status": { + "const": "incomplete" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "exit_code": { + "const": 3 + }, + "workspace_binding": { + "enum": [ + "bound", + "uninitialized", + "legacy_unbound", + "invalid" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + }, + "run_id": { + "type": "string" + }, + "tool_approval": { + "const": "unverified" + }, + "rulepack_approval": { + "const": "unverified" + }, + "manifest_sha256": { + "type": [ + "string", + "null" + ] + }, + "backlog_status": { + "enum": [ + "synced_partial", + "backlog_update_failed", + "not_initialized" + ] + }, + "backlog_sync": { + "type": [ + "object", + "null" + ] + }, + "next": { + "type": [ + "object", + "null" + ] + } + } + } + ] + }, + "java_p3c": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "operation", + "command_status", + "exit_code", + "workspace_binding", + "workspace_id", + "run_id", + "tool_approval", + "rulepack_approval", + "checker_id", + "language", + "category", + "source_file_count", + "observed_file_count", + "finding_count", + "findings", + "local_observation_complete", + "coverage_proven", + "authority", + "delivery_decision", + "files", + "backlog_status", + "backlog_sync", + "next" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "java_p3c_project_observation" + }, + "operation": { + "const": "lint" + }, + "command_status": { + "const": "incomplete" + }, + "exit_code": { + "const": 3 + }, + "workspace_binding": { + "enum": [ + "bound", + "uninitialized", + "legacy_unbound", + "invalid" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + }, + "run_id": { + "type": "string" + }, + "tool_approval": { + "const": "unverified" + }, + "rulepack_approval": { + "const": "unverified" + }, + "checker_id": { + "const": "java.maven.p3c" + }, + "language": { + "const": "java" + }, + "category": { + "const": "lint" + }, + "source_file_count": { + "type": "integer", + "minimum": 1 + }, + "observed_file_count": { + "type": "integer", + "minimum": 0 + }, + "finding_count": { + "type": "integer", + "minimum": 0 + }, + "findings": { + "type": "array" + }, + "local_observation_complete": { + "type": "boolean" + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "backlog_status": { + "enum": [ + "synced_partial", + "not_initialized", + "backlog_update_failed" + ] + }, + "backlog_sync": { + "type": [ + "object", + "null" + ] + }, + "next": { + "type": [ + "object", + "null" + ] + }, + "files": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "build_root", + "configuration", + "configuration_ref", + "reason", + "observation" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "build_root": { + "type": "string", + "minLength": 1 + }, + "configuration": { + "enum": [ + "configured", + "missing", + "unknown", + "invalid" + ] + }, + "configuration_ref": { + "type": [ + "string", + "null" + ] + }, + "configuration_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "reason": { + "enum": [ + "native_probe_returned", + "finding_projection_incomplete", + "p3c_configuration_not_confirmed", + "p3c_configuration_changed_before_scan", + "p3c_configuration_changed_during_scan", + "p3c_ruleset_selection_unresolved", + "request_cancelled", + "request_deadline_exceeded" + ] + }, + "observation": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "schema_version", + "report_type", + "local_status", + "reason", + "declared_rulesets", + "native_plan_sha256", + "findings", + "coverage_proven", + "delivery_decision" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "java_p3c_local_feedback" + }, + "local_status": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "declared_rulesets": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "uniqueItems": true + }, + "native_plan_sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "findings": { + "type": "array" + }, + "coverage_proven": { + "const": false + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + ] + } + } + } + } + } + } + ] + }, + "java_dependencies": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "checker_id", + "language", + "category", + "source_file_count", + "configured_build_root_count", + "observed_graph_count", + "local_probe_complete", + "project_checker_attribution", + "coverage_proven", + "authority", + "delivery_decision", + "probes" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "java_dependency_project_probe" + }, + "checker_id": { + "const": "java.maven.dependency" + }, + "language": { + "const": "java" + }, + "category": { + "const": "dependencies" + }, + "source_file_count": { + "type": "integer", + "minimum": 0 + }, + "configured_build_root_count": { + "type": "integer", + "minimum": 0 + }, + "observed_graph_count": { + "type": "integer", + "minimum": 0 + }, + "local_probe_complete": { + "type": "boolean" + }, + "project_checker_attribution": { + "const": "unverified" + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "probes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "build_root", + "configuration_ref", + "observation" + ], + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "observation": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "checker_id", + "category", + "native_status", + "reason", + "native_plan_sha256", + "maven_tool_sha256", + "java_runtime_sha256", + "dependency_closure_sha256", + "graph_sha256", + "nodes", + "edges", + "coverage_proven", + "authority", + "delivery_decision" + ], + "properties": { + "schema_version": { + "const": "0.2.0" + }, + "report_type": { + "const": "maven_dependency_tree_probe" + }, + "checker_id": { + "const": "java.maven.dependency" + }, + "category": { + "const": "dependencies" + }, + "native_status": { + "enum": [ + "incomplete", + "graph_observed_untrusted" + ] + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "native_plan_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "maven_tool_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "java_runtime_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "dependency_closure_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "graph_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "nodes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "group_id", + "artifact_id", + "version", + "type", + "scope", + "classifier", + "optional", + "artifact_sha256" + ], + "properties": { + "group_id": { + "type": "string" + }, + "artifact_id": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "classifier": { + "type": "string" + }, + "optional": { + "type": "boolean" + }, + "artifact_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + } + } + } + }, + "edges": { + "type": "array", + "items": { + "type": "array", + "prefixItems": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "integer", + "minimum": 0 + } + ], + "items": false + } + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + } + } + } + } + } + ] + }, + "java_cve": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "operation", + "command_status", + "exit_code", + "workspace_binding", + "workspace_id", + "run_id", + "tool_approval", + "rulepack_approval", + "checker_id", + "language", + "category", + "configured_build_root_count", + "observed_report_count", + "local_probe_complete", + "database_freshness", + "coverage_proven", + "authority", + "delivery_decision", + "attribution_probes", + "probes", + "backlog_status", + "backlog_sync", + "next" + ], + "properties": { + "schema_version": { + "const": "0.3.0" + }, + "report_type": { + "const": "java_cve_project_probe" + }, + "operation": { + "const": "check" + }, + "command_status": { + "const": "incomplete" + }, + "exit_code": { + "const": 3 + }, + "workspace_binding": { + "enum": [ + "bound", + "uninitialized", + "legacy_unbound", + "invalid" + ] + }, + "workspace_id": { + "type": [ + "string", + "null" + ] + }, + "run_id": { + "type": "string", + "minLength": 1 + }, + "tool_approval": { + "const": "unverified" + }, + "rulepack_approval": { + "const": "unverified" + }, + "checker_id": { + "const": "java.maven.dependency_check" + }, + "language": { + "const": "java" + }, + "category": { + "const": "cve" + }, + "configured_build_root_count": { + "type": "integer", + "minimum": 0 + }, + "observed_report_count": { + "type": "integer", + "minimum": 0 + }, + "local_probe_complete": { + "type": "boolean" + }, + "database_freshness": { + "const": "unverified" + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "backlog_status": { + "enum": [ + "synced_partial", + "not_initialized", + "backlog_update_failed" + ] + }, + "backlog_sync": { + "type": [ + "object", + "null" + ] + }, + "next": { + "type": [ + "object", + "null" + ] + }, + "attribution_probes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "build_root", + "status", + "bindings" + ], + "properties": { + "build_root": { + "type": "string" + }, + "status": { + "enum": [ + "dependency_graph_unavailable", + "dependency_graph_ambiguous", + "native_reports_unavailable", + "native_identity_unavailable", + "native_identity_mismatch", + "native_report_invalid", + "candidate_evaluated_untrusted" + ] + }, + "bindings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "observation_index", + "coordinate_state", + "graph_node_index", + "artifact_state" + ], + "properties": { + "observation_index": { + "type": "integer", + "minimum": 0 + }, + "coordinate_state": { + "enum": [ + "exact_coordinate_candidate", + "missing_maven_purl", + "unsupported_purl", + "conflicting_purls", + "not_in_graph", + "ambiguous_graph_coordinate" + ] + }, + "graph_node_index": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "artifact_state": { + "enum": [ + "attribution_unavailable", + "owasp_digest_missing", + "maven_digest_missing", + "digest_evidence_invalid", + "digest_mismatch", + "exact_digest_candidate" + ] + } + } + } + } + } + } + }, + "probes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "build_root", + "configuration_ref", + "observation" + ], + "properties": { + "build_root": { + "type": "string" + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "observation": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "checker_id", + "category", + "native_status", + "reason", + "plugin_version", + "native_plan_sha256", + "maven_tool_sha256", + "java_runtime_sha256", + "dependency_closure_sha256", + "database_tree_sha256", + "native_report_sha256", + "database_freshness", + "dependency_count", + "database_sources", + "advisories", + "coverage_proven", + "authority", + "delivery_decision" + ], + "properties": { + "schema_version": { + "const": "0.1.0" + }, + "report_type": { + "const": "owasp_maven_probe" + }, + "checker_id": { + "const": "java.maven.dependency_check" + }, + "category": { + "const": "cve" + }, + "native_status": { + "enum": [ + "incomplete", + "findings_observed_untrusted", + "empty_report_unverified" + ] + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "plugin_version": { + "type": [ + "string", + "null" + ] + }, + "native_plan_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "maven_tool_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "java_runtime_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "dependency_closure_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "database_tree_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "native_report_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "database_freshness": { + "const": "unverified" + }, + "dependency_count": { + "type": "integer", + "minimum": 0 + }, + "database_sources": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "timestamp" + ], + "properties": { + "name": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + } + } + }, + "advisories": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "source", + "advisory_id", + "score", + "package_ids", + "dependency_sha256", + "suppressed_by_native_tool" + ], + "properties": { + "source": { + "type": "string" + }, + "advisory_id": { + "type": "string" + }, + "score": { + "type": [ + "number", + "null" + ], + "minimum": 0, + "maximum": 10 + }, + "package_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "dependency_sha256": { + "$ref": "#/$defs/nullableSha256" + }, + "suppressed_by_native_tool": { + "type": "boolean" + } + } + } + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + } + } + } + } + } + ] + }, + "java_javadoc": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "checker_id", + "project_checker_attribution", + "language", + "category", + "source_file_count", + "observed_file_count", + "local_probe_complete", + "coverage_proven", + "authority", + "delivery_decision", + "files", + "maven_multifile_probes", + "probe_mode" + ], + "properties": { + "schema_version": { + "const": "0.3.0" + }, + "report_type": { + "const": "java_javadoc_project_probe" + }, + "checker_id": { + "enum": [ + "java.jdk.javadoc", + "java.maven.javadoc" + ] + }, + "project_checker_attribution": { + "const": "unverified" + }, + "language": { + "const": "java" + }, + "category": { + "const": "comments" + }, + "source_file_count": { + "type": "integer", + "minimum": 1 + }, + "observed_file_count": { + "type": "integer", + "minimum": 0 + }, + "local_probe_complete": { + "type": "boolean" + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + }, + "maven_multifile_probes": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "build_root", + "configuration_ref", + "observation" + ], + "properties": { + "build_root": { + "type": "string", + "minLength": 1 + }, + "configuration_ref": { + "type": "string", + "minLength": 1 + }, + "observation": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "report_type", + "checker_id", + "native_status", + "reason", + "source_count", + "observed_source_count", + "findings", + "native_plan_sha256", + "pom_mode", + "maven_tool_sha256", + "java_runtime_sha256", + "dependency_closure_sha256", + "project_checker_attribution", + "coverage_proven", + "authority", + "delivery_decision" + ], + "properties": { + "schema_version": { + "const": "0.1.0" + }, + "report_type": { + "const": "maven_javadoc_multifile_probe" + }, + "checker_id": { + "const": "java.maven.javadoc" + }, + "native_status": { + "enum": [ + "incomplete", + "findings_observed_untrusted", + "clean_log_unverified" + ] + }, + "reason": { + "type": "string", + "minLength": 1 + }, + "source_count": { + "type": "integer", + "minimum": 1 + }, + "observed_source_count": { + "type": "integer", + "minimum": 0 + }, + "findings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "line", + "column", + "rule_id" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 1 + }, + "rule_id": { + "type": "string", + "minLength": 1 + } + } + } + }, + "native_plan_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "pom_mode": { + "enum": [ + "unverified", + "direct_pom_replay" + ] + }, + "maven_tool_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "java_runtime_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "dependency_closure_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "project_checker_attribution": { + "const": "unverified" + }, + "coverage_proven": { + "const": false + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + } + } + }, + "files": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "build_root", + "configuration", + "configuration_ref", + "configuration_sha256", + "reason", + "observation" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "build_root": { + "type": "string", + "minLength": 1 + }, + "configuration": { + "enum": [ + "configured", + "missing", + "unknown", + "invalid" + ] + }, + "configuration_ref": { + "type": [ + "string", + "null" + ] + }, + "configuration_sha256": { + "type": [ + "string", + "null" + ], + "pattern": "^[0-9a-f]{64}$" + }, + "reason": { + "enum": [ + "request_cancelled", + "request_deadline_exceeded", + "javadoc_configuration_not_confirmed", + "javadoc_configuration_changed_before_scan", + "javadoc_configuration_changed_during_scan", + "javadoc_source_scope_unverified", + "native_probe_returned", + "maven_multifile_probe_selected" + ] + }, + "observation": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "report_type", + "local_status", + "findings", + "coverage_proven", + "delivery_decision" + ], + "properties": { + "report_type": { + "const": "java_javadoc_local_feedback" + }, + "local_status": { + "enum": [ + "incomplete", + "clean_scope_unproven", + "findings_observed_untrusted" + ] + }, + "findings": { + "type": "array" + }, + "coverage_proven": { + "const": false + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "reason": { + "const": "maven_multifile_probe_selected" + } + } + }, + "then": { + "properties": { + "observation": { + "type": "null" + } + } + } + } + ] + } + }, + "probe_mode": { + "enum": [ + "jdk_single_file", + "maven_multifile" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "probe_mode": { + "const": "maven_multifile" + } + } + }, + "then": { + "properties": { + "checker_id": { + "const": "java.maven.javadoc" + }, + "files": { + "items": { + "properties": { + "observation": { + "type": "null" + } + } + } + } + } + }, + "else": { + "properties": { + "checker_id": { + "const": "java.jdk.javadoc" + }, + "maven_multifile_probes": { + "maxItems": 0 + } + } + } + } + ] + } + ] + }, + "python_lint": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "schema_version", + "report_type", + "files", + "command_status", + "exit_code", + "delivery_decision" + ], + "properties": { + "schema_version": { + "const": "0.12.0" + }, + "report_type": { + "const": "python_lint_feedback" + }, + "files": { + "type": "array" + }, + "command_status": { + "const": "incomplete" + }, + "exit_code": { + "const": 3 + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + ] + }, + "npm_cve": { + "type": "array", + "items": { + "$ref": "#/$defs/npmCheckResult" + } + }, + "rust_comments": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/rust_comments" + } + ] + }, + "rust_build": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/rust_build" + } + ] + }, + "rust_cve": { + "oneOf": [ + {"type": "null"}, + {"$ref": "#/$defs/rust_cve"} + ] + } + } + }, + "obligation_status": { + "const": "unresolved" + }, + "required_obligations": { + "type": "null" + }, + "category_candidates": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "language", + "legacy_status", + "capability_status", + "category", + "checker_id", + "next_action", + "status", + "reason" + ], + "properties": { + "language": { + "type": "string", + "minLength": 1 + }, + "legacy_status": { + "enum": [ + "stable", + "planned", + "unknown" + ] + }, + "capability_status": { + "enum": [ + "gap", + null + ] + }, + "category": { + "enum": [ + "lint", + "comments", + "dependencies", + "cve", + "security", + "build" + ] + }, + "checker_id": { + "type": [ + "string", + "null" + ], + "minLength": 1 + }, + "next_action": { + "type": [ + "string", + "null" + ], + "minLength": 1 + }, + "status": { + "enum": [ + "observed_unverified", + "native_incomplete", + "not_configured", + "configuration_unresolved", + "not_integrated" + ] + }, + "reason": { + "enum": [ + "trusted_policy_and_coverage_unavailable", + "native_scan_incomplete", + "p3c_declared_rulesets_unverified_coverage", + "javadoc_single_file_probe_unverified_coverage", + "javadoc_checker_not_configured", + "javadoc_configuration_unresolved", + "javadoc_multifile_probe_unverified_project_coverage", + "dependency_graph_observed_unverified_coverage", + "cve_report_observed_database_unverified", + "configured_checker_not_integrated", + "recognized_checker_not_configured", + "checker_configuration_not_observed", + "checker_configuration_mixed_across_sources", + "checker_configuration_unresolved", + "checker_build_systems_mixed", + "gradle_model_not_resolved", + "native_adapter_not_integrated", + "planned_language_adapter_gap", + "npm_advisory_coverage_and_freshness_unverified", + "rust_cve_database_freshness_unverified", + "rust_cve_native_incomplete", + "python_cve_native_incomplete", + "python_cve_advisory_coverage_and_freshness_unverified" + ] + } + } + } + }, + "syntax_candidates": { + "type": "object", + "additionalProperties": false, + "required": ["status", "reason", "execution_phase", "authority", "delivery_decision", "source_file_count", "skipped_count", "unrouted_count", "native_preferred_count", "observations", "next_action"], + "properties": { + "status": {"enum": ["not_run", "attempted_incomplete", "observed_partial"]}, + "reason": {"enum": ["source_or_scope_changed", "request_cancelled", "worker_executable_unavailable", "bounded_scope", "unrouted_source", "candidate_unavailable", "candidate_unqualified", "native_preferred", "binary_without_wasm_precheck"]}, + "execution_phase": {"const": "after_native"}, + "authority": {"const": "candidate_unqualified"}, + "delivery_decision": {"const": "incomplete"}, + "source_file_count": {"type": "integer", "minimum": 0}, + "skipped_count": {"type": "integer", "minimum": 0}, + "unrouted_count": {"type": "integer", "minimum": 0}, + "native_preferred_count": {"type": "integer", "minimum": 0}, + "next_action": {"type": "string", "minLength": 1}, + "observations": { + "type": "array", "maxItems": 64, + "items": { + "type": "object", "additionalProperties": false, + "required": ["path", "language", "scope", "byte_offset", "status", "reason", "grammar_qualified", "source_sha256", "grammar_sha256", "recovery_count", "recoveries"], + "properties": { + "path": {"type": "string", "minLength": 1}, + "language": {"type": ["string", "null"]}, + "scope": {"enum": ["whole_file", "cfquery_body"]}, + "byte_offset": {"type": "integer", "minimum": 0}, + "status": {"enum": ["candidate_observed", "candidate_unavailable"]}, + "reason": {"type": ["string", "null"]}, + "grammar_qualified": {"const": false}, + "source_sha256": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"}, + "grammar_sha256": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"}, + "recovery_count": {"type": "integer", "minimum": 0}, + "recoveries": { + "type": "array", "maxItems": 8, + "items": { + "type": "object", "additionalProperties": false, + "required": ["kind", "syntax_kind", "start_byte", "end_byte", "start_row", "start_column_byte", "end_row", "end_column_byte"], + "properties": { + "kind": {"enum": ["ERROR", "MISSING"]}, + "syntax_kind": {"type": "string", "minLength": 1}, + "start_byte": {"type": "integer", "minimum": 0}, + "end_byte": {"type": "integer", "minimum": 0}, + "start_row": {"type": "integer", "minimum": 0}, + "start_column_byte": {"type": "integer", "minimum": 0}, + "end_row": {"type": "integer", "minimum": 0}, + "end_column_byte": {"type": "integer", "minimum": 0} + } + } + } + } + } + } + } + }, + "unresolved_conditions": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "next": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "schema_version", + "report_type", + "authority", + "delivery_decision" + ], + "properties": { + "schema_version": { + "const": "0.1.0" + }, + "report_type": { + "const": "repair_brief_preview" + }, + "authority": { + "const": "local_unverified" + }, + "delivery_decision": { + "const": "not_evaluated" + } + } + } + ] + } + } +} diff --git a/schemas/check-feedback.schema.json b/schemas/check-feedback.schema.json index 64a288a5..d7317767 100644 --- a/schemas/check-feedback.schema.json +++ b/schemas/check-feedback.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "urn:codeguard:schema:check-feedback:0.33.0", + "$id": "urn:codeguard:schema:check-feedback:0.34.0", "title": "CodeGuard partial project or language check feedback", "$defs": { "rust_cve": { @@ -3370,7 +3370,7 @@ ], "properties": { "schema_version": { - "const": "0.33.0" + "const": "0.34.0" }, "export": { "type": "object", @@ -5515,7 +5515,7 @@ "required": ["status", "reason", "execution_phase", "authority", "delivery_decision", "source_file_count", "skipped_count", "unrouted_count", "native_preferred_count", "observations", "next_action"], "properties": { "status": {"enum": ["not_run", "attempted_incomplete", "observed_partial"]}, - "reason": {"enum": ["source_or_scope_changed", "request_cancelled", "worker_executable_unavailable", "bounded_scope", "unrouted_source", "candidate_unavailable", "candidate_unqualified", "native_preferred", "binary_without_wasm_precheck"]}, + "reason": {"enum": ["source_or_scope_changed", "request_cancelled", "worker_executable_unavailable", "grammar_manifest_invalid", "bounded_scope", "unrouted_source", "candidate_unavailable", "candidate_unqualified", "native_preferred", "binary_without_wasm_precheck"]}, "execution_phase": {"const": "after_native"}, "authority": {"const": "candidate_unqualified"}, "delivery_decision": {"const": "incomplete"}, @@ -5528,7 +5528,7 @@ "type": "array", "maxItems": 64, "items": { "type": "object", "additionalProperties": false, - "required": ["path", "language", "scope", "byte_offset", "status", "reason", "grammar_qualified", "source_sha256", "grammar_sha256", "recovery_count", "recoveries"], + "required": ["path", "language", "scope", "byte_offset", "status", "reason", "grammar_qualified", "source_sha256", "grammar_sha256", "recovery_count", "recoveries", "known_limitations"], "properties": { "path": {"type": "string", "minLength": 1}, "language": {"type": ["string", "null"]}, @@ -5540,6 +5540,7 @@ "source_sha256": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"}, "grammar_sha256": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"}, "recovery_count": {"type": "integer", "minimum": 0}, + "known_limitations": {"type": "array", "maxItems": 8, "items": {"type": "string", "minLength": 1, "maxLength": 1024}}, "recoveries": { "type": "array", "maxItems": 8, "items": { diff --git a/tests/acceptance/check-all-known-grammar-limitations.md b/tests/acceptance/check-all-known-grammar-limitations.md new file mode 100644 index 00000000..e9c9a71f --- /dev/null +++ b/tests/acceptance/check-all-known-grammar-limitations.md @@ -0,0 +1,11 @@ +# `check all` 投影已知 grammar 限制的局部验收 + +日期:2026-10-03。对应 OpenSpec 14.7、14.17、14.19 的局部增量。 + +初始 RED 测试创建有效但未缩进的 VB.NET 方法和含 `SELECT FROM` 的 CFQuery 标签。统一 `check all --format=json` 虽返回候选观察,却没有给智能体提供清单中已知的 VB.NET 误报和 CFQuery SQL 覆盖缺口。随后在原生检查之后的候选阶段,只读取并校验内置固定清单元数据,把所选 grammar 的有界 `known_limitations` 附到每条观察;源码读取失败且尚无语言路由时使用空数组。真实 worker 仍自行核对选中资产字节,清单文字不能使恢复节点升级为确认违规。 + +聚合 `check_feedback` 从 0.33.0 升至封闭的 0.34.0;旧 Schema 独立归档。新字段要求数组最多 8 项、每项 1–1024 字符,仍要求 `grammar_qualified=false`、`delivery_decision=incomplete`。清单元数据无效时候选阶段报告 `grammar_manifest_invalid`,不继续解析或签发通过。真实双文件报告有 VB.NET、CFML、CFQuery 三条候选记录,VB.NET 和 CFQuery 都携带具体限制;无论某条 worker 观察是否完成,都不把恢复节点或零恢复当作原生判定。 + +目标回归先因缺字段失败,修复后 1/1 通过;六项候选路由测试 6/6、聚合契约测试 18/18(另 3 项按原标记忽略)。本机已有 `/opt/anaconda3/bin/python3` 的 Draft 2020-12 校验器接受真实 0.34.0 报告,拒绝旧 0.33.0 Schema、超长限制和伪造 `delivery_decision=allow`。Python 只用于独立协议验收,不属于 Rust 产品运行路径。 + +这一增量只改善报告中的可见限制。已发布 npm 0.1.3 仍嵌入旧协议;插件自动读取、对话渲染、原生对照、系统误报率与逐语言发行验收仍缺,父任务不勾选。 From 66a4d0eb88535fc08291b6303dbc42a868516f38 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:11:33 +0800 Subject: [PATCH 24/31] test: require limitation projection for every grammar --- crates/codeguard-cli/tests/check_all_grammar_candidates.rs | 7 +++++++ openspec/changes/introduce-rust-codeguard-cli/tasks.md | 2 +- tests/acceptance/check-all-known-grammar-limitations.md | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index fbd0c9b7..f7cd3e04 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -310,6 +310,13 @@ fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { observation["recovery_count"], 0, "group {group_index}: unexpected recovery in valid sample: {observation}" ); + assert!( + !observation["known_limitations"] + .as_array() + .unwrap() + .is_empty(), + "group {group_index}: candidate lacks its fixed known limits: {observation}" + ); } for language in observations .iter() diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index a9e2a742..7ff1567e 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1110,4 +1110,4 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.1 / 14.7 / 14.17 / 14.19 状态证据校正:固定清单的 C、Go、JavaScript、Rust、Zig 已知限制同步窄范围原生差分事实,CFQuery 明确提示 SQL 片段漏检;只读 `grammar status` 测试逐项断言说明且仍 `released=false`。这只是源码状态投影,已发布包未更新,也未消除系统精度和原生 lint 缺口;见[局部验收](../../../tests/acceptance/grammar-known-limitations-projection.md)。 -14.7 / 14.17 / 14.19 项目报告限制投影:`check all` 的 0.34.0 封闭协议逐候选附带固定清单的有界 `known_limitations`,VB.NET 已知误报与 CFQuery SQL 覆盖缺口不再只存在于独立库存命令;0.33.0 Schema 归档,清单解析失败时保持未完成。真实双文件目标测试先 RED 后 GREEN,六项候选回归与 18 项聚合契约通过,真实 JSON 通过 Draft 2020-12、旧 Schema/伪造放行被拒。默认宿主自动对话渲染、原生对照、系统精度和发行仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 +14.7 / 14.17 / 14.19 项目报告限制投影:`check all` 的 0.34.0 封闭协议逐候选附带固定清单的有界 `known_limitations`,VB.NET 已知误报与 CFQuery SQL 覆盖缺口不再只存在于独立库存命令;0.33.0 Schema 归档,清单解析失败时保持未完成。真实双文件目标测试先 RED 后 GREEN,六项候选回归与 18 项聚合契约通过,分批 32 份真实候选测试逐观察确认非空限制,真实 JSON 通过 Draft 2020-12、旧 Schema/伪造放行被拒。默认宿主自动对话渲染、原生对照、系统精度和发行仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 diff --git a/tests/acceptance/check-all-known-grammar-limitations.md b/tests/acceptance/check-all-known-grammar-limitations.md index e9c9a71f..58b4d905 100644 --- a/tests/acceptance/check-all-known-grammar-limitations.md +++ b/tests/acceptance/check-all-known-grammar-limitations.md @@ -6,6 +6,6 @@ 聚合 `check_feedback` 从 0.33.0 升至封闭的 0.34.0;旧 Schema 独立归档。新字段要求数组最多 8 项、每项 1–1024 字符,仍要求 `grammar_qualified=false`、`delivery_decision=incomplete`。清单元数据无效时候选阶段报告 `grammar_manifest_invalid`,不继续解析或签发通过。真实双文件报告有 VB.NET、CFML、CFQuery 三条候选记录,VB.NET 和 CFQuery 都携带具体限制;无论某条 worker 观察是否完成,都不把恢复节点或零恢复当作原生判定。 -目标回归先因缺字段失败,修复后 1/1 通过;六项候选路由测试 6/6、聚合契约测试 18/18(另 3 项按原标记忽略)。本机已有 `/opt/anaconda3/bin/python3` 的 Draft 2020-12 校验器接受真实 0.34.0 报告,拒绝旧 0.33.0 Schema、超长限制和伪造 `delivery_decision=allow`。Python 只用于独立协议验收,不属于 Rust 产品运行路径。 +目标回归先因缺字段失败,修复后 1/1 通过;六项候选路由测试 6/6、聚合契约测试 18/18(另 3 项按原标记忽略)。分批调用全部 32 份候选的真实 CLI 测试还逐观察核对非空已知限制,目标用例 1/1 通过。本机已有 `/opt/anaconda3/bin/python3` 的 Draft 2020-12 校验器接受真实 0.34.0 报告,拒绝旧 0.33.0 Schema、超长限制和伪造 `delivery_decision=allow`。Python 只用于独立协议验收,不属于 Rust 产品运行路径。 这一增量只改善报告中的可见限制。已发布 npm 0.1.3 仍嵌入旧协议;插件自动读取、对话渲染、原生对照、系统误报率与逐语言发行验收仍缺,父任务不勾选。 From a5bf96dd3de5ad84f1d8117813dd6cc9143a186b Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:16:08 +0800 Subject: [PATCH 25/31] feat: show known grammar limits in terminal feedback --- crates/codeguard-cli/src/check_command.rs | 14 ++++++++++ .../tests/check_all_grammar_candidates.rs | 26 +++++++++++++++++++ docs/Codeguard-Command-Reference.md | 2 +- docs/Codeguard-Command-Reference.zh_CN.md | 2 +- docs/Codeguard-Technical-Design.md | 2 +- docs/Codeguard-Technical-Design.zh_CN.md | 2 +- .../introduce-rust-codeguard-cli/tasks.md | 2 ++ .../check-all-known-grammar-limitations.md | 2 ++ 8 files changed, 48 insertions(+), 4 deletions(-) diff --git a/crates/codeguard-cli/src/check_command.rs b/crates/codeguard-cli/src/check_command.rs index 47c687f7..9c406d89 100644 --- a/crates/codeguard-cli/src/check_command.rs +++ b/crates/codeguard-cli/src/check_command.rs @@ -1903,6 +1903,20 @@ pub fn run(args: &[String]) -> ExitCode { item["path"], item["language"], item["recovery_count"] ); } + for item in observations + .iter() + .filter(|item| item["known_limitations"][0].is_string()) + .take(8) + { + if let Some(limitation) = item["known_limitations"][0].as_str() { + println!( + " {} [{}] 已知 grammar 限制:{};仍需适用原生工具确认。", + item["path"].as_str().unwrap_or("?"), + item["language"].as_str().unwrap_or("unknown"), + limitation + ); + } + } } } if report["unresolved_conditions"] diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index f7cd3e04..494dfad1 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -223,6 +223,32 @@ fn known_grammar_precision_limits_reach_project_feedback() { assert_eq!(report["delivery_decision"], "incomplete"); } +#[test] +fn human_feedback_names_known_candidate_false_positive() { + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-known-limit-human-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + fs::write( + root.join("class.vb"), + "Public Class C\nPublic Function F() As Integer\nReturn 1\nEnd Function\nEnd Class\n", + ) + .unwrap(); + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["check", "all"]) + .arg(&root) + .args(["--timeout", "45s"]) + .output() + .unwrap(); + fs::remove_dir_all(&root).unwrap(); + assert_eq!(output.status.code(), Some(3)); + let text = String::from_utf8(output.stdout).unwrap(); + assert!(text.contains("已知 grammar 限制"), "{text}"); + assert!(text.contains("known grammar false positive"), "{text}"); + assert!(!text.contains("Return 1"), "{text}"); +} + #[test] fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { let root = std::env::temp_dir().canonicalize().unwrap().join(format!( diff --git a/docs/Codeguard-Command-Reference.md b/docs/Codeguard-Command-Reference.md index 0a8a858d..0e20a900 100644 --- a/docs/Codeguard-Command-Reference.md +++ b/docs/Codeguard-Command-Reference.md @@ -12,7 +12,7 @@ Current slices include static discovery/init, selected native checks, partial ag The opt-in `codeguard-cli/wasm-precheck` build adds narrow `lint typescript ` and `lint java ` paths when no explicit native execution context is provided. TypeScript first recognizes an ordinary project-local ESLint 10 entry and one flat config, then uses an executable Node from `PATH` to run the existing bounded native probe; when Node is unresolved, it reports setup instead of starting WASM. Only when no project-local ESLint package is observed does it return [TypeScript feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) or TSX 0.4.0 candidate feedback. Untrusted local paths, package identity or config selection remain setup blockers. Java returns [Java feedback 0.1.0](../schemas/java-syntax-precheck-feedback-v0.1.schema.json). These paths retain exit 3 and never turn an unvalidated grammar or local native observation into delivery approval. Default/published binaries retain the existing native-context behavior. See [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). -The same source build also adds a bounded `syntax_candidates` pass to `check all` after its native nodes. [Feedback 0.34.0](../schemas/check-feedback.schema.json) keeps all 32 pinned grammars callable through bounded uses of the unified command while preserving native blockers, bounded known grammar limitations, skipped scope and an incomplete verdict; source-matched Python files completed by Ruff count under `native_preferred_count` and skip duplicate WASM; `.tsx`, JavaScript, `.cfs` and explicit CFQuery tag bodies use distinct routes. This is candidate observation, not language-qualified native fallback or automatic host support. See [32-grammar acceptance](../tests/acceptance/check-all-32-grammar-candidates.md). +The same source build also adds a bounded `syntax_candidates` pass to `check all` after its native nodes. [Feedback 0.34.0](../schemas/check-feedback.schema.json) keeps all 32 pinned grammars callable through bounded uses of the unified command while preserving native blockers, bounded known grammar limitations (also capped in terminal output), skipped scope and an incomplete verdict; source-matched Python files completed by Ruff count under `native_preferred_count` and skip duplicate WASM; `.tsx`, JavaScript, `.cfs` and explicit CFQuery tag bodies use distinct routes. This is candidate observation, not language-qualified native fallback or automatic host support. See [32-grammar acceptance](../tests/acceptance/check-all-32-grammar-candidates.md). With `--workspace` pointing to an initialized, canonical workspace, the TypeScript/TSX fallback emits [feedback 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json). It saves one source-scoped ESLint native-confirmation blocker and returns its real `setup.task_id` only after successful sync. Repeated scans and a later WASM result with no recovery nodes keep that task open; a versioned local report now links bounded suspected positions to that task through its report digest, while capability-matched native verification remains unfinished. See the [workbench acceptance record](../tests/acceptance/typescript-syntax-confirmation-task.md). diff --git a/docs/Codeguard-Command-Reference.zh_CN.md b/docs/Codeguard-Command-Reference.zh_CN.md index 694725c8..c9f88e9c 100644 --- a/docs/Codeguard-Command-Reference.zh_CN.md +++ b/docs/Codeguard-Command-Reference.zh_CN.md @@ -22,7 +22,7 @@ [已验收实现切片](../openspec/changes/introduce-rust-codeguard-cli/implementation-baseline.md)跟踪当前局部能力;[tasks](../openspec/changes/introduce-rust-codeguard-cli/tasks.md)独占完成状态。WASM 的完整统一入口仍为 S14 待办。可选 `codeguard-cli/wasm-precheck` 构建处理 `lint typescript <显式单文件>` 时,若观察到本地 ESLint 10 入口与唯一 flat config,会优先用 `PATH` 中可执行 Node 调用既有有界原生探针;缺 Node 则返回准备缺口。仅未观察到本地 ESLint 包时才输出 [TypeScript 0.3.0 候选反馈](../schemas/eslint-local-feedback-v0.3.schema.json)或 TSX 0.4.0。本地入口、包身份或配置不可信时保留具体环境阻塞,不启动 WASM。`lint java <显式单文件>` 仍输出 [Java 0.1.0 候选反馈](../schemas/java-syntax-precheck-feedback-v0.1.schema.json)。这些局部路径均不批准交付并保持退出 3;部分显式参数、Javadoc/Checkstyle 和符号链接沿用原路径,默认及已发布二进制也不含这项自动调度。见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)与技术方案 5.3–5.4;后文未标当前的语法、退出0/1与闭环承诺均为目标契约。 -同一源码构建现在也会在 `check all` 的原生节点之后输出有界 `syntax_candidates`。[反馈 0.34.0](../schemas/check-feedback.schema.json)让 32 份固定 grammar 均能经统一入口分批被选用,同时保留原生阻塞、有界已知 grammar 限制、未执行范围和未完成结论;Ruff 已完整覆盖的同摘要 Python 文件计入 `native_preferred_count`,不重复运行 WASM;`.tsx`、JavaScript、`.cfs` 与明确的 CFQuery 标签体分别路由。这仍是候选观察,不等于逐语言验收的原生兜底或自动宿主反馈,见[32 份验收](../tests/acceptance/check-all-32-grammar-candidates.md)。 +同一源码构建现在也会在 `check all` 的原生节点之后输出有界 `syntax_candidates`。[反馈 0.34.0](../schemas/check-feedback.schema.json)让 32 份固定 grammar 均能经统一入口分批被选用,同时保留原生阻塞、有界已知 grammar 限制(终端输出也限制条数)、未执行范围和未完成结论;Ruff 已完整覆盖的同摘要 Python 文件计入 `native_preferred_count`,不重复运行 WASM;`.tsx`、JavaScript、`.cfs` 与明确的 CFQuery 标签体分别路由。这仍是候选观察,不等于逐语言验收的原生兜底或自动宿主反馈,见[32 份验收](../tests/acceptance/check-all-32-grammar-candidates.md)。 对已初始化且使用规范绝对路径的 `--workspace`,TypeScript/TSX 候选回退改用[反馈 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json):按源码范围保存同一张 ESLint 原生确认阻塞任务,仅同步成功时返回真实 `setup.task_id`。重复扫描和后续 WASM 无恢复节点不关闭任务。版本化本地报告已通过报告摘要关联有界疑似位置;能力匹配原生复检仍未接通,见[局部验收](../tests/acceptance/typescript-syntax-confirmation-task.md)。 diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 41873fb3..06634289 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -191,7 +191,7 @@ When some files have suspected issues and others are unresolved, use overall `in These are the target semantic fields, not a shipped project-wide lint/check report. A narrow [candidate precheck schema](../schemas/syntax-precheck-candidate.schema.json) and [strict Rust reader](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs) bind source SHA-256, pinned grammar identity and file dialect, recalculate status, and reject unknown versions or forged `clean`. Optional CLI single-file [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json), [TSX](../schemas/eslint-local-feedback-v0.4.schema.json) and [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) producers include recovery positions and setup guidance. With an initialized workspace, TypeScript/TSX now uses [feedback 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json) and attaches one stable native-confirmation task to the source scope; the [0.2.0 preparation report](../schemas/eslint-preparation-observation-v0.2.schema.json) stores bounded suspected positions with source and grammar identities and links them by digest to the task. Host rendering and capability-matched native closure remain absent. The bundled grammars remain unvalidated, so these reports cannot return `clean`. Missing required native execution remains incomplete (`3`); a suspected parser issue alone is not a confirmed violation (`1`). If a dedicated syntax-only operation is later introduced, its success must be explicitly scoped to syntax; no new `syntax` command is claimed here. -The source-built `check all` now adds a narrower actual project report: native adapters run first, then a [bounded source router](../crates/codeguard-cli/src/check_syntax_candidates.rs) selects pinned worker candidates. The closed [0.34.0 schema](../schemas/check-feedback.schema.json) counts files preempted by native checks and projects each selected grammar's bounded known limitations; [0.33.0](../schemas/check-feedback-v0.33.schema.json) remains archived for older readers. Only a completed Ruff scan of matching Python source bytes skips duplicate WASM. A style checker such as P3C is not syntax confirmation. The following is an excerpt, not a complete `check_feedback` instance: +The source-built `check all` now adds a narrower actual project report: native adapters run first, then a [bounded source router](../crates/codeguard-cli/src/check_syntax_candidates.rs) selects pinned worker candidates. The closed [0.34.0 schema](../schemas/check-feedback.schema.json) counts files preempted by native checks and projects each selected grammar's bounded known limitations; [0.33.0](../schemas/check-feedback-v0.33.schema.json) remains archived for older readers. Only a completed Ruff scan of matching Python source bytes skips duplicate WASM. A style checker such as P3C is not syntax confirmation. Default terminal output also shows up to eight fixed limitations from candidate observations; automatic host delivery remains pending. The following is an excerpt, not a complete `check_feedback` instance: ```json { diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index b8ea701f..4d2f07b7 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -191,7 +191,7 @@ flowchart LR 这些是目标语义字段,尚非已发行的项目级 lint/check 报告。局部的[候选初检 schema](../schemas/syntax-precheck-candidate.schema.json) 与 [Rust 严格读者](../crates/codeguard-adapters/src/syntax_precheck_candidate_report.rs)已绑定源码 SHA-256、固定 grammar 身份和文件方言,重新计算状态,并拒绝未知版本及伪造的 `clean`。可选 CLI 的 [TypeScript](../schemas/eslint-local-feedback-v0.3.schema.json)、[TSX](../schemas/eslint-local-feedback-v0.4.schema.json) 与 [Java](../schemas/java-syntax-precheck-feedback-v0.1.schema.json) 单文件生产端已提供恢复位置和准备指引。TypeScript/TSX 在已初始化工作区改用[反馈 0.5.0](../schemas/eslint-local-feedback-v0.5.schema.json),按源码范围关联一张稳定的原生确认任务;[0.2.0 准备报告](../schemas/eslint-preparation-observation-v0.2.schema.json)以源码和 grammar 身份保存有界疑似位置,并通过报告摘要关联到任务;宿主渲染和能力匹配原生关闭仍未接通。内置 grammar 仍是未验收候选,因此不能返回 `clean`。保持既有退出语义:必需原生执行缺失仍为未完成(`3`);解析器疑似问题本身不是已确认违规(`1`)。若未来增加独立语法操作,其成功必须限定为语法初检;本文不宣称已有 `syntax` 命令。 -源码构建的 `check all` 现另有较窄的真实项目报告:先执行原生适配器,再由[有界源码路由](../crates/codeguard-cli/src/check_syntax_candidates.rs)选择固定 worker 候选。[0.34.0 封闭 Schema](../schemas/check-feedback.schema.json)包含逐文件原生优先计数和所选 grammar 的有界已知限制;[0.33.0](../schemas/check-feedback-v0.33.schema.json) 保留为历史协议。只有 Ruff 对同一 Python 源码摘要返回完整扫描状态时才跳过该文件的重复 WASM;P3C 等规范检查不能冒充语法确认。下面仅摘录字段,不是完整 `check_feedback` 实例: +源码构建的 `check all` 现另有较窄的真实项目报告:先执行原生适配器,再由[有界源码路由](../crates/codeguard-cli/src/check_syntax_candidates.rs)选择固定 worker 候选。[0.34.0 封闭 Schema](../schemas/check-feedback.schema.json)包含逐文件原生优先计数和所选 grammar 的有界已知限制;[0.33.0](../schemas/check-feedback-v0.33.schema.json) 保留为历史协议。只有 Ruff 对同一 Python 源码摘要返回完整扫描状态时才跳过该文件的重复 WASM;P3C 等规范检查不能冒充语法确认。默认终端输出也最多显示 8 条候选的固定已知限制;宿主自动交付仍待完成。下面仅摘录字段,不是完整 `check_feedback` 实例: ```json { diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 7ff1567e..265a436f 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1111,3 +1111,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.1 / 14.7 / 14.17 / 14.19 状态证据校正:固定清单的 C、Go、JavaScript、Rust、Zig 已知限制同步窄范围原生差分事实,CFQuery 明确提示 SQL 片段漏检;只读 `grammar status` 测试逐项断言说明且仍 `released=false`。这只是源码状态投影,已发布包未更新,也未消除系统精度和原生 lint 缺口;见[局部验收](../../../tests/acceptance/grammar-known-limitations-projection.md)。 14.7 / 14.17 / 14.19 项目报告限制投影:`check all` 的 0.34.0 封闭协议逐候选附带固定清单的有界 `known_limitations`,VB.NET 已知误报与 CFQuery SQL 覆盖缺口不再只存在于独立库存命令;0.33.0 Schema 归档,清单解析失败时保持未完成。真实双文件目标测试先 RED 后 GREEN,六项候选回归与 18 项聚合契约通过,分批 32 份真实候选测试逐观察确认非空限制,真实 JSON 通过 Draft 2020-12、旧 Schema/伪造放行被拒。默认宿主自动对话渲染、原生对照、系统精度和发行仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 + +14.7 / 14.19 文本反馈局部增量:`check all` 默认终端输出现在有界显示候选的固定已知限制,VB.NET 误报样例先 RED 后 GREEN,目标测试 1/1 通过,源码行未回显。显式 CLI 输出可由调用方转发,插件默认 Hook 的自动触发与宿主对话渲染仍未验收,父任务不勾选;见[同一局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 diff --git a/tests/acceptance/check-all-known-grammar-limitations.md b/tests/acceptance/check-all-known-grammar-limitations.md index 58b4d905..3b5fa2ae 100644 --- a/tests/acceptance/check-all-known-grammar-limitations.md +++ b/tests/acceptance/check-all-known-grammar-limitations.md @@ -9,3 +9,5 @@ 目标回归先因缺字段失败,修复后 1/1 通过;六项候选路由测试 6/6、聚合契约测试 18/18(另 3 项按原标记忽略)。分批调用全部 32 份候选的真实 CLI 测试还逐观察核对非空已知限制,目标用例 1/1 通过。本机已有 `/opt/anaconda3/bin/python3` 的 Draft 2020-12 校验器接受真实 0.34.0 报告,拒绝旧 0.33.0 Schema、超长限制和伪造 `delivery_decision=allow`。Python 只用于独立协议验收,不属于 Rust 产品运行路径。 这一增量只改善报告中的可见限制。已发布 npm 0.1.3 仍嵌入旧协议;插件自动读取、对话渲染、原生对照、系统误报率与逐语言发行验收仍缺,父任务不勾选。 + +终端文本入口也经过 RED/GREEN:只含有效未缩进 VB.NET 方法的项目,旧输出仅提示一处候选恢复节点;现在至多展示 8 条所选 grammar 的固定已知限制,明确写出 `known grammar false positive` 并要求原生确认。目标测试 1/1 通过,未回显该样例的 `Return 1` 源码行。此处只证明 CLI 文本可见,尚未验收插件默认 Hook 的自动对话交付。 From 65ee909dfd878de1cc5a0be4418e9323f3d4cf12 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:19:12 +0800 Subject: [PATCH 26/31] test: distinguish native spawn pressure from timeout --- .../tests/native_version_observation.rs | 32 +++++++++++++------ .../introduce-rust-codeguard-cli/tasks.md | 2 ++ .../acceptance/native-version-diagnostics.md | 2 ++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/crates/codeguard-cli/tests/native_version_observation.rs b/crates/codeguard-cli/tests/native_version_observation.rs index 573a98f6..8d66ba66 100644 --- a/crates/codeguard-cli/tests/native_version_observation.rs +++ b/crates/codeguard-cli/tests/native_version_observation.rs @@ -79,15 +79,29 @@ fn unsupported_version_stderr_and_nonzero_exit_are_distinct() { #[test] fn timeout_cancellation_and_exhausted_budget_keep_native_failure_kind() { for variant in 0..3 { - let fixture = Fixture::new(); - let mut request = fixture.request("exec /bin/sleep 1"); - let cancelled = AtomicBool::new(variant == 1); - request.process.deadline = if variant == 2 { - Instant::now() - } else { - Instant::now() + Duration::from_millis(30) - }; - let result = observe_native_version(&request, &cancelled); + let mut result = None; + for attempt in 0..3 { + let fixture = Fixture::new(); + let mut request = fixture.request("exec /bin/sleep 2"); + let cancelled = AtomicBool::new(variant == 1); + request.process.deadline = if variant == 2 { + Instant::now() + } else { + Instant::now() + Duration::from_millis(500) + }; + let observed = observe_native_version(&request, &cancelled); + // 并行 CI 偶发进程配额耗尽时,重新创建私有样例;只有真实超时才满足本用例。 + if variant == 0 + && observed.termination == Some(Termination::SpawnFailure) + && attempt < 2 + { + std::thread::sleep(Duration::from_millis(20)); + continue; + } + result = Some(observed); + break; + } + let result = result.unwrap(); let (termination, reason) = match variant { 0 => (Termination::TimedOut, "version_timed_out"), 1 => (Termination::Cancelled, "request_cancelled"), diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 265a436f..11310248 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1113,3 +1113,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.7 / 14.17 / 14.19 项目报告限制投影:`check all` 的 0.34.0 封闭协议逐候选附带固定清单的有界 `known_limitations`,VB.NET 已知误报与 CFQuery SQL 覆盖缺口不再只存在于独立库存命令;0.33.0 Schema 归档,清单解析失败时保持未完成。真实双文件目标测试先 RED 后 GREEN,六项候选回归与 18 项聚合契约通过,分批 32 份真实候选测试逐观察确认非空限制,真实 JSON 通过 Draft 2020-12、旧 Schema/伪造放行被拒。默认宿主自动对话渲染、原生对照、系统精度和发行仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 14.7 / 14.19 文本反馈局部增量:`check all` 默认终端输出现在有界显示候选的固定已知限制,VB.NET 误报样例先 RED 后 GREEN,目标测试 1/1 通过,源码行未回显。显式 CLI 输出可由调用方转发,插件默认 Hook 的自动触发与宿主对话渲染仍未验收,父任务不勾选;见[同一局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 + +5.2 / 5.6 Linux CI 版本探测测试稳定性:旧 30ms 超时夹具在并行负载下实际返回 `SpawnFailure`,不能误报为运行时超时。现在使用 500ms 预算与 2 秒睡眠,仅在瞬时启动失败时创建新夹具重试,最终仍严格要求 `TimedOut`;本机定向 1/1、完整文件 7/7 通过。远端复验仍待本次 PR CI,父任务保持未完成;见[原生版本诊断验收](../../../tests/acceptance/native-version-diagnostics.md)。 diff --git a/tests/acceptance/native-version-diagnostics.md b/tests/acceptance/native-version-diagnostics.md index b5ce3a32..3e463ea0 100644 --- a/tests/acceptance/native-version-diagnostics.md +++ b/tests/acceptance/native-version-diagnostics.md @@ -19,3 +19,5 @@ CODEGUARD_RUFF_BIN=/opt/anaconda3/bin/ruff cargo test -p codeguard-cli --test ru 7 项探测中的真实 Ruff 用例仅证明版本诊断。默认目标及 runtime process/private log/边界共 43 项通过;另外明确设置 CODEGUARD_RUFF_BIN,补跑两套各 5 项 ignored 真实扫描回归,10 项全部通过。这些扫描验证现有局部 Ruff 配置/范围/报告,不证明完整门禁。all-target Clippy、格式、OpenSpec strict 与 diff 检查通过。 完整 doctor、可信工具锁、原生兼容与运行环境、其它适配器复用、准备报告持久化/同步仍缺;5.2/5.6 不勾选,完整计划保持进行中。 + +2026-10-03 的 [CI 37112015676](https://github.com/full-stack-plugins/codeguard/actions/runs/37112015676) 在 30ms 超时样例中得到 `SpawnFailure` 而非 `TimedOut`。运行时正确保留了独立的启动失败类别;失败点是测试在并行 Linux 负载下未能真正启动睡眠进程。测试现给启动过程 500ms,令受测进程睡眠 2 秒,并仅对瞬时 `SpawnFailure` 使用新的私有夹具重试最多两次;最终仍必须观察到真正的 `TimedOut`,不能把启动失败当成超时通过。本机定向测试 1/1、该文件全部 7/7 通过,远端重新验收待最新 PR CI 完成。 From ff6018459602dac5b09221843dfd788fe9a7c9c0 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:40:09 +0800 Subject: [PATCH 27/31] =?UTF-8?q?=E5=B9=B6=E8=A1=8C=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E6=9C=89=E7=95=8C=E8=AF=AD=E6=B3=95=E5=80=99=E9=80=89=E4=BB=A5?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E5=8D=95=E9=A1=B9=E7=9B=AE=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E8=B5=84=E4=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 +- crates/codeguard-cli/src/check_command.rs | 1 + .../src/check_syntax_candidates.rs | 223 +++++++++++++----- .../tests/check_all_grammar_candidates.rs | 83 ++++++- .../introduce-rust-codeguard-cli/tasks.md | 2 + .../check-all-32-grammar-candidates.md | 2 + 6 files changed, 244 insertions(+), 71 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 821087e1..7b34330f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,9 @@ jobs: run: | cargo test -p codeguard-runtime --features wasm-precheck --test process_contract cargo test -p codeguard-runtime --features wasm-precheck --test wasm_grammar_load --test dart_upstream_corpus - cargo test -p codeguard-cli --features wasm-precheck --test syntax_worker_candidate --test grammar_probe_cli --test grammar_route --test check_all_grammar_candidates --test zig_lint_cli --test typescript_syntax_fallback_candidate --test java_syntax_fallback_candidate + cargo test -p codeguard-cli --features wasm-precheck --test syntax_worker_candidate --test grammar_probe_cli --test grammar_route --test zig_lint_cli --test typescript_syntax_fallback_candidate --test java_syntax_fallback_candidate + # 32 份 WASM 的批次和单项目回归各自独占候选阶段预算,避免测试进程互相争抢内存与 CPU。 + cargo test -p codeguard-cli --features wasm-precheck --test check_all_grammar_candidates -- --test-threads=1 - name: Verify the local npm package contains a running WASM worker run: | diff --git a/crates/codeguard-cli/src/check_command.rs b/crates/codeguard-cli/src/check_command.rs index 9c406d89..a3eb37fa 100644 --- a/crates/codeguard-cli/src/check_command.rs +++ b/crates/codeguard-cli/src/check_command.rs @@ -1461,6 +1461,7 @@ pub fn run(args: &[String]) -> ExitCode { go_tool: parsed.go_tool.as_deref(), }, parsed.selection == Selection::Java, + parsed.jobs_limit, deadline, if request_cancelled { Some("request_cancelled") diff --git a/crates/codeguard-cli/src/check_syntax_candidates.rs b/crates/codeguard-cli/src/check_syntax_candidates.rs index 8a2ceccc..a0c35a3e 100644 --- a/crates/codeguard-cli/src/check_syntax_candidates.rs +++ b/crates/codeguard-cli/src/check_syntax_candidates.rs @@ -4,18 +4,21 @@ use crate::discovery::DiscoveryReport; use crate::go_lint_command::selected_sources_for_candidate; use crate::grammar_probe_command::read_plain_source; use crate::grammar_route::route_source; +use crate::syntax_worker_candidate_observation::SyntaxWorkerCandidateObservation; use crate::syntax_worker_runner::run_syntax_worker_candidate; use codeguard_adapters::bundled_grammar_metadata; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use std::collections::{BTreeMap, BTreeSet}; use std::path::Path; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::time::{Duration, Instant}; const MAX_FILES: usize = 64; const MAX_FRAGMENTS: usize = 64; const MAX_VISIBLE_RECOVERIES: usize = 8; +const MAX_CONCURRENT_WORKERS: usize = 2; /// 本轮已执行的原生语法相关结果及显式工具;仅用于判断同一源码是否可避免重复解析。 pub struct NativeCoverage<'a> { @@ -24,13 +27,29 @@ pub struct NativeCoverage<'a> { pub go_tool: Option<&'a Path>, } +struct CandidateJob { + relative: String, + source: Arc>, + language: &'static str, + scope: &'static str, + byte_offset: usize, + end_byte: usize, + known_limitations: Vec, +} + +enum PlannedObservation { + Immediate(Value), + Worker(CandidateJob), +} + /// 对已发现源码按方言选择固定资产,受文件、片段和剩余时间限制。 -/// 参数为项目根、静态发现、语言选择、共同截止时间及范围稳定状态;返回不具门禁权威的报告。 +/// 参数为项目根、静态发现、语言选择、全局并发上限、共同截止时间及范围稳定状态;返回不具门禁权威的报告。 pub fn observe( root: &Path, discovery: &DiscoveryReport, native: NativeCoverage<'_>, java_only: bool, + jobs_limit: usize, deadline: Instant, skip_reason: Option<&str>, ) -> Value { @@ -44,7 +63,7 @@ pub fn observe( paths.extend(discovery.ambiguous_source_files.iter()); } let source_file_count = paths.len(); - let mut observations = Vec::new(); + let observations = Vec::new(); let mut skipped_count = 0; let mut unrouted_count = 0; let mut native_preferred_count = 0; @@ -101,9 +120,10 @@ pub fn observe( ) .unwrap_or_default() }; + let mut planned = Vec::new(); for (index, relative) in paths.into_iter().enumerate() { if index >= MAX_FILES - || observations.len() >= MAX_FRAGMENTS + || planned.len() >= MAX_FRAGMENTS || Instant::now() >= candidate_deadline || codeguard_runtime::sigint_cancellation_requested() { @@ -113,12 +133,12 @@ pub fn observe( let source = match read_plain_source(&root.join(relative)) { Ok(source) => source, Err(reason) => { - observations.push(json!({ + planned.push(PlannedObservation::Immediate(json!({ "path":relative,"language":null,"scope":"whole_file","byte_offset":0, "status":"candidate_unavailable","reason":reason,"grammar_qualified":false, "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], "known_limitations":[] - })); + }))); continue; } }; @@ -128,67 +148,99 @@ pub fn observe( native_preferred_count += 1; continue; } - let routes = route_source(relative, &source); + let routes = route_source(relative, &source) + .into_iter() + .map(|route| { + ( + route.language, + route.scope, + route.byte_offset, + route.byte_offset + route.source.len(), + ) + }) + .collect::>(); if routes.is_empty() { unrouted_count += 1; } - for route in routes { - let known_limitations = manifest - .assets - .iter() - .find(|asset| asset.language == route.language) - .map(|asset| asset.known_limitations.as_slice()) - .unwrap_or(&[]); - if observations.len() >= MAX_FRAGMENTS || Instant::now() >= candidate_deadline { + let source = Arc::new(source); + for (language, scope, byte_offset, end_byte) in routes { + if planned.len() >= MAX_FRAGMENTS || Instant::now() >= candidate_deadline { skipped_count += 1; continue; } - match run_syntax_worker_candidate( - &executable, route.language, relative, route.source, candidate_deadline, &cancelled, - ) { - Ok(observation) => { - if read_plain_source(&root.join(relative)).as_deref() != Ok(source.as_slice()) { - observations.push(json!({ - "path":relative,"language":route.language,"scope":route.scope,"byte_offset":route.byte_offset, - "status":"candidate_unavailable","reason":"source_changed_during_precheck","grammar_qualified":false, - "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], - "known_limitations":known_limitations - })); - continue; - } - // 嵌入片段的 worker 坐标以片段为起点,公开报告还原为文件坐标。 - let base_row = source[..route.byte_offset].iter().filter(|byte| **byte == b'\n').count(); - let base_column = source[..route.byte_offset].rsplit(|byte| *byte == b'\n').next().map_or(0, <[u8]>::len); - let recovery_count = observation.recoveries.len(); - let recoveries: Vec = observation.recoveries.iter().take(MAX_VISIBLE_RECOVERIES).map(|recovery| { - json!({ - "kind":recovery.kind, - "syntax_kind":recovery.syntax_kind, - "start_byte":route.byte_offset + recovery.start_byte, - "end_byte":route.byte_offset + recovery.end_byte, - "start_row":base_row + recovery.start_row, - "start_column_byte":if recovery.start_row == 0 { base_column + recovery.start_column_byte } else { recovery.start_column_byte }, - "end_row":base_row + recovery.end_row, - "end_column_byte":if recovery.end_row == 0 { base_column + recovery.end_column_byte } else { recovery.end_column_byte }, - }) - }).collect(); - observations.push(json!({ - "path":relative,"language":route.language,"scope":route.scope,"byte_offset":route.byte_offset, - "status":"candidate_observed","reason":null,"grammar_qualified":false, - "source_sha256":observation.source_sha256,"grammar_sha256":observation.grammar_sha256, - "recovery_count":recovery_count,"recoveries":recoveries, - "known_limitations":known_limitations - })); - } - Err(reason) => observations.push(json!({ - "path":relative,"language":route.language,"scope":route.scope,"byte_offset":route.byte_offset, - "status":"candidate_unavailable","reason":reason,"grammar_qualified":false, - "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], - "known_limitations":known_limitations - })), - } + let known_limitations = manifest + .assets + .iter() + .find(|asset| asset.language == language) + .map(|asset| asset.known_limitations.clone()) + .unwrap_or_default(); + planned.push(PlannedObservation::Worker(CandidateJob { + relative: relative.clone(), + source: Arc::clone(&source), + language, + scope, + byte_offset, + end_byte, + known_limitations, + })); + } + } + let mut completed: Vec> = vec![None; planned.len()]; + let worker_indices = planned + .iter() + .enumerate() + .filter_map(|(index, item)| matches!(item, PlannedObservation::Worker(_)).then_some(index)) + .collect::>(); + for batch in worker_indices.chunks(jobs_limit.clamp(1, MAX_CONCURRENT_WORKERS)) { + if Instant::now() >= candidate_deadline + || codeguard_runtime::sigint_cancellation_requested() + { + skipped_count += batch.len(); + continue; + } + let results = std::thread::scope(|scope| { + let handles = batch + .iter() + .map(|index| { + let PlannedObservation::Worker(job) = &planned[*index] else { + unreachable!("worker index must refer to a prepared job") + }; + scope.spawn(|| { + run_syntax_worker_candidate( + &executable, + job.language, + &job.relative, + &job.source[job.byte_offset..job.end_byte], + candidate_deadline, + &cancelled, + ) + }) + }) + .collect::>(); + handles + .into_iter() + .map(|handle| { + handle + .join() + .unwrap_or_else(|_| Err("syntax_worker_parent_panic".into())) + }) + .collect::>() + }); + for (index, result) in batch.iter().copied().zip(results) { + let PlannedObservation::Worker(job) = &planned[index] else { + unreachable!("worker result must match a prepared job") + }; + completed[index] = Some(candidate_result(root, job, result)); } } + let observations = planned + .into_iter() + .zip(completed) + .filter_map(|(item, result)| match item { + PlannedObservation::Immediate(value) => Some(value), + PlannedObservation::Worker(_) => result, + }) + .collect::>(); let observed = observations .iter() .any(|item| item["status"] == "candidate_observed"); @@ -221,6 +273,61 @@ pub fn observe( ) } +fn candidate_result( + root: &Path, + job: &CandidateJob, + result: Result, +) -> Value { + match result { + Ok(observation) => { + if read_plain_source(&root.join(&job.relative)).as_deref() != Ok(job.source.as_slice()) + { + return json!({ + "path":job.relative,"language":job.language,"scope":job.scope,"byte_offset":job.byte_offset, + "status":"candidate_unavailable","reason":"source_changed_during_precheck","grammar_qualified":false, + "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], + "known_limitations":job.known_limitations + }); + } + // 嵌入片段的 worker 坐标以片段为起点,公开报告还原为文件坐标。 + let base_row = job.source[..job.byte_offset] + .iter() + .filter(|byte| **byte == b'\n') + .count(); + let base_column = job.source[..job.byte_offset] + .rsplit(|byte| *byte == b'\n') + .next() + .map_or(0, <[u8]>::len); + let recovery_count = observation.recoveries.len(); + let recoveries: Vec = observation.recoveries.iter().take(MAX_VISIBLE_RECOVERIES).map(|recovery| { + json!({ + "kind":recovery.kind, + "syntax_kind":recovery.syntax_kind, + "start_byte":job.byte_offset + recovery.start_byte, + "end_byte":job.byte_offset + recovery.end_byte, + "start_row":base_row + recovery.start_row, + "start_column_byte":if recovery.start_row == 0 { base_column + recovery.start_column_byte } else { recovery.start_column_byte }, + "end_row":base_row + recovery.end_row, + "end_column_byte":if recovery.end_row == 0 { base_column + recovery.end_column_byte } else { recovery.end_column_byte }, + }) + }).collect(); + json!({ + "path":job.relative,"language":job.language,"scope":job.scope,"byte_offset":job.byte_offset, + "status":"candidate_observed","reason":null,"grammar_qualified":false, + "source_sha256":observation.source_sha256,"grammar_sha256":observation.grammar_sha256, + "recovery_count":recovery_count,"recoveries":recoveries, + "known_limitations":job.known_limitations + }) + } + Err(reason) => json!({ + "path":job.relative,"language":job.language,"scope":job.scope,"byte_offset":job.byte_offset, + "status":"candidate_unavailable","reason":reason,"grammar_qualified":false, + "source_sha256":null,"grammar_sha256":null,"recovery_count":0,"recoveries":[], + "known_limitations":job.known_limitations + }), + } +} + fn native_go_covers(files: &BTreeMap, relative: &str, source: &[u8]) -> bool { relative.ends_with(".go") && files diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index 494dfad1..ef9cdcc4 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -87,7 +87,7 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) .args(["check", "all"]) .arg(&root) - .args(["--format=json", "--timeout", "90s"]) + .args(["--format=json", "--timeout", "90s", "--jobs=1"]) .output() .unwrap(); fs::remove_dir_all(&root).unwrap(); @@ -99,6 +99,7 @@ fn check_all_routes_distinct_dialects_after_native_without_claiming_clean() { ); let report: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); assert_eq!(report["schema_version"], "0.34.0"); + assert_eq!(report["execution_budget"]["jobs_limit"], 1); assert_eq!(report["delivery_decision"], "incomplete"); let observations = report["syntax_candidates"]["observations"] .as_array() @@ -249,14 +250,8 @@ fn human_feedback_names_known_candidate_false_positive() { assert!(!text.contains("Return 1"), "{text}"); } -#[test] -fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { - let root = std::env::temp_dir().canonicalize().unwrap().join(format!( - "codeguard-check-all-grammars-{}", - std::process::id() - )); - fs::create_dir_all(&root).unwrap(); - for (index, (name, source)) in [ +fn grammar_samples() -> [(&'static str, &'static str); 31] { + [ ("a.ets", "@Component struct C { build() { Text('hi') } }"), ("a.c", "int main(void) { return 0; }"), ( @@ -298,9 +293,16 @@ fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { ), ("a.zig", "const Empty = struct {};\n"), ] - .into_iter() - .enumerate() - { +} + +#[test] +fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-check-all-grammars-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + for (index, (name, source)) in grammar_samples().into_iter().enumerate() { let group = root.join(format!("group-{}", index / 8)); fs::create_dir_all(&group).unwrap(); fs::write(group.join(name), source).unwrap(); @@ -364,6 +366,63 @@ fn check_all_invokes_all_32_pinned_candidates_across_bounded_projects() { assert_eq!(languages, expected); } +#[test] +fn one_mixed_project_observes_all_32_candidates_within_the_existing_budget() { + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-single-project-grammars-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + for (name, source) in grammar_samples() { + fs::write(root.join(name), source).unwrap(); + } + let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["check", "all"]) + .arg(&root) + .args(["--format=json", "--timeout", "120s"]) + .output() + .unwrap(); + fs::remove_dir_all(&root).unwrap(); + assert_eq!( + output.status.code(), + Some(3), + "{}", + String::from_utf8_lossy(&output.stderr) + ); + let report: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + assert_eq!(report["syntax_candidates"]["skipped_count"], 0); + assert_eq!(report["syntax_candidates"]["source_file_count"], 31); + assert_eq!(report["delivery_decision"], "incomplete"); + let observations = report["syntax_candidates"]["observations"] + .as_array() + .unwrap(); + assert_eq!(observations.len(), 32); + assert!( + observations + .windows(2) + .all(|pair| { pair[0]["path"].as_str().unwrap() <= pair[1]["path"].as_str().unwrap() }) + ); + let observed = observations + .iter() + .filter(|item| item["status"] == "candidate_observed") + .filter_map(|item| item["language"].as_str()) + .collect::>(); + let manifest: serde_json::Value = + serde_json::from_str(include_str!("../../../grammars/manifest.json")).unwrap(); + let expected = manifest["assets"] + .as_array() + .unwrap() + .iter() + .map(|asset| asset["language"].as_str().unwrap()) + .collect::>(); + assert_eq!(observed, expected, "{observations:?}"); + assert!( + observations + .iter() + .all(|item| item["grammar_qualified"] == false) + ); +} + #[test] fn oversized_source_is_an_explicit_candidate_gap() { let root = std::env::temp_dir().canonicalize().unwrap().join(format!( diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 11310248..98b6d667 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1115,3 +1115,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.7 / 14.19 文本反馈局部增量:`check all` 默认终端输出现在有界显示候选的固定已知限制,VB.NET 误报样例先 RED 后 GREEN,目标测试 1/1 通过,源码行未回显。显式 CLI 输出可由调用方转发,插件默认 Hook 的自动触发与宿主对话渲染仍未验收,父任务不勾选;见[同一局部验收](../../../tests/acceptance/check-all-known-grammar-limitations.md)。 5.2 / 5.6 Linux CI 版本探测测试稳定性:旧 30ms 超时夹具在并行负载下实际返回 `SpawnFailure`,不能误报为运行时超时。现在使用 500ms 预算与 2 秒睡眠,仅在瞬时启动失败时创建新夹具重试,最终仍严格要求 `TimedOut`;本机定向 1/1、完整文件 7/7 通过。远端复验仍待本次 PR CI,父任务保持未完成;见[原生版本诊断验收](../../../tests/acceptance/native-version-diagnostics.md)。 + +14.4 / 14.6 / 14.19 单项目预算局部增量:候选阶段按固定顺序准备有界源码片段,使用 `min(--jobs, 2)` 个隔离 worker 批量并行,报告仍按路径顺序;成功结果复读源码,所有任务共享原有 90 秒候选截止时间。本机 31 文件单项目可观察全部 32 种候选,约 41.54 秒、跳过 0,仍退出 3 且不作语言验收或交付许可。Linux CI 单项目回归、长期资源与冷暖启动验收尚待确认;逐语言精度、原生优先、任务及宿主反馈也未完成,父任务不勾选。见[32 份路由局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 diff --git a/tests/acceptance/check-all-32-grammar-candidates.md b/tests/acceptance/check-all-32-grammar-candidates.md index 65e6055f..7fa4fa87 100644 --- a/tests/acceptance/check-all-32-grammar-candidates.md +++ b/tests/acceptance/check-all-32-grammar-candidates.md @@ -13,3 +13,5 @@ 另有 1 MiB 超限源码反例:本轮未启动对应 worker,不生成 grammar 摘要或疑似源码违规,聚合记录 `attempted_incomplete/candidate_unavailable`。该反例及 Clippy 在最后一次协议补充后已单独重跑。 仍缺逐语言版本/方言语料、原生语法能力的差分对照、误报/漏报量化、为 32 种语言分别选择适用原生工具与配置、候选任务同步、宿主对话反馈、无 WASM 构建与各发行平台实装。已知 CFQuery 漏检和 VB.NET 误报依旧存在,因此本次路由不能升级为已验收 lint 或交付许可;S14.6、14.7、14.17、14.19 不勾选。 + +2026-10-03 单项目预算改进:候选阶段先按稳定路径准备最多 64 个片段,再按请求的 `--jobs` 上限、且最多 2 个隔离 worker 并行执行。结果仍按准备顺序输出;每份成功观察在发布前复读完整源码,变更则降为 `source_changed_during_precheck`。90 秒候选总截止时间、64 文件/片段及各 worker 的输入、输出、内存边界不变。本机以一项目 31 个源码文件调用 32 份候选,首次定向测试约 41.54 秒完成,退出码 3、跳过 0、32 个 `candidate_observed`,交付仍为 `incomplete`。这只是单次本机样例,不是 Linux 或真实项目的性能保证;Linux 单项目验收以本次 CI 为准。 From d370f2326860d0dec177c66856e0c5104af55ea7 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:51:09 +0800 Subject: [PATCH 28/31] =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=8D=95=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E4=B8=89=E5=8D=81=E4=BA=8C=E7=A7=8D=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E5=80=99=E9=80=89=E7=9A=84=E8=B7=A8=E5=B9=B3=E5=8F=B0=E9=AA=8C?= =?UTF-8?q?=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ README.zh-CN.md | 2 ++ docs/Codeguard-Architecture.md | 4 +++- docs/Codeguard-Architecture.zh_CN.md | 4 +++- docs/Codeguard-Technical-Design.md | 4 +++- docs/Codeguard-Technical-Design.zh_CN.md | 4 +++- openspec/changes/introduce-rust-codeguard-cli/tasks.md | 2 +- tests/acceptance/check-all-32-grammar-candidates.md | 2 +- 8 files changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 820cca50..cda8aa79 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,8 @@ In a source build with `--features wasm-precheck`, `codeguard grammar probe --format=json` for all 32 pinned candidates. It uses the isolated Rust worker and reports unqualified observations with exit 3, not a lint or delivery verdict. Successful observations and input failures for a valid language share the [closed 0.1.0 JSON schema](../schemas/grammar-probe-v0.1.schema.json) and remain incomplete. Native-first `lint/check`, language/dialect acceptance, task/host feedback and distribution remain separate open work; current discovery also groups JavaScript/TSX under TypeScript and has no separate CFQuery/CFScript source mapping. -The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.34.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, bounded known limitations, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; a single 31-file Linux run observed 26/32 before its 90-second cap. It is a partial candidate route, not a qualified native fallback or task/host integration; npm 0.1.3 distributes it as an unqualified candidate. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). +The source-built `check all` now runs a bounded candidate pass after its existing native checks. A separate [grammar router](../crates/codeguard-cli/src/grammar_route.rs) distinguishes `.tsx`, JavaScript, and `.cfs`; complete `...` bodies are parsed with CFQuery, while ordinary `.sql` and ambiguous `.h` are not guessed. Shared `.m` requires an Objective-C line marker, and `.sc` is not guessed as Scala without project evidence; see the [ambiguity regression](../tests/acceptance/ambiguous-grammar-extensions.md). Four bounded real CLI fixtures collectively invoked all 32 pinned workers. [Check feedback 0.34.0](../schemas/check-feedback.schema.json) records selected dialect, source/grammar digests, file-relative recovery positions, bounded known limitations, skipped work and incomplete status; Python files with completed, source-matched Ruff scans skip duplicate WASM; style-only P3C does not confirm syntax. Candidate observations never become confirmed violations or delivery permission. The pass has a 64-file/64-fragment/90-second cap and was about 54 seconds for four bounded fixtures on this Mac; the prior sequential implementation observed 26/32 in one 31-file Linux run before its 90-second cap. It is a partial candidate route, not a qualified native fallback or task/host integration; npm 0.1.3 distributes it as an unqualified candidate. See [acceptance evidence](../tests/acceptance/check-all-32-grammar-candidates.md). + +The source-built candidate scheduler now prepares bounded fragments in stable path order and runs up to `min(--jobs, 2)` isolated workers concurrently. It emits observations in that same order and rereads each source before accepting a worker result. A local 31-file, one-project fixture observed all 32 candidates within the unchanged 90-second pass; the Linux WASM integration step for source commit `ff60184` passed the same one-project test; the complete CI run passed. This changes candidate throughput, not its authority. ```mermaid flowchart LR diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index f66bf87b..63593134 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -32,7 +32,9 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选 Rust worker 源码以 `--features wasm-precheck` 构建后,可通过 `codeguard grammar probe --format=json` 显式调用全部 32 份固定候选资产。命令使用隔离 Rust worker,以退出码 3 报告未经语言验收的观察,不能作为 lint 或交付结论。成功观察与有效语种的输入失败都遵守[0.1.0 封闭 JSON Schema](../schemas/grammar-probe-v0.1.schema.json),保持未完成。原生优先 `lint/check`、语言/方言验收、任务/宿主反馈及发行仍需独立完成;现有发现清单还把 JavaScript/TSX 归入 TypeScript,并未为 CFQuery/CFScript 建立独立源码映射。 -源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.34.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、有界已知限制、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底或任务/宿主接线;npm 0.1.3 仅将其作为未验收候选分发;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 +源码构建的 `check all` 现会在既有原生检查之后执行有界候选初检。[语法路由](../crates/codeguard-cli/src/grammar_route.rs)区分 `.tsx`、JavaScript 和 `.cfs`,只对完整的 `...` 标签体选用 CFQuery;普通 `.sql`、有歧义的 `.h` 不猜测语种。共用后缀的 `.m` 要求 Objective-C 行首标记,`.sc` 在缺项目证据时不猜成 Scala;见[歧义回归](../tests/acceptance/ambiguous-grammar-extensions.md)。四个有界真实 CLI 样例合计逐份调用了 32 种固定 worker。[检查反馈 0.34.0](../schemas/check-feedback.schema.json)记录方言、源码及 grammar 摘要、原文件位置、有界已知限制、未执行范围和未完成状态;Ruff 本轮完整检查且摘要匹配的 Python 文件跳过重复 WASM;P3C 等规范检查不视为语法确认。候选观察不生成已确认违规或交付许可。候选阶段限 64 文件、64 片段、90 秒,本机四批 32 语种样例约 54 秒;此前串行实现的 Linux 单次 31 文件检查在 90 秒上限内完成 26/32。当前只是候选路由,尚非逐语言验收的原生兜底或任务/宿主接线;npm 0.1.3 仅将其作为未验收候选分发;见[验收记录](../tests/acceptance/check-all-32-grammar-candidates.md)。 + +源码构建的候选调度现在按稳定路径顺序准备有界片段,以 `min(--jobs, 2)` 个隔离 worker 并发执行;报告保持同序,接受结果前逐文件复读源码。本机 31 文件单项目样例在原 90 秒预算内观察到全部 32 种候选;源码提交 `ff60184` 的 Linux WASM 集成步骤已通过同一单项目测试;完整 CI 已通过。吞吐改进不提升候选观察的门禁权威。 ```mermaid flowchart LR diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 06634289..b311911b 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -212,7 +212,9 @@ The source-built `check all` now adds a narrower actual project report: native a } ``` -The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix. Read-only discovery now uses the same bounded `.m` evidence and records unresolved shared-suffix paths in `unknown_conditions`; `check all` retains them in its unrouted count. The shared detector ignores Objective-C-looking lines inside MATLAB `%{ ... %}` block comments, including an unclosed block, while retaining a real marker outside the block. This reduces a concrete false route; it is not a complete MATLAB lexer. Project-level dialect evidence and structured per-file ambiguity reporting still need work. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; a single 31-file Linux run completed 26/32 within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. +The actual report also includes native results, source/grammar digests, fragment offsets, bounded file-relative recovery anchors, skipped counts and unresolved obligations. A complete `` tag body is an embedded candidate; ordinary SQL or ambiguous C/C++ headers are not guessed. Automatic `.m` routing requires an Objective-C line marker, while `.sc` is left unresolved because SuperCollider shares that suffix. Read-only discovery now uses the same bounded `.m` evidence and records unresolved shared-suffix paths in `unknown_conditions`; `check all` retains them in its unrouted count. The shared detector ignores Objective-C-looking lines inside MATLAB `%{ ... %}` block comments, including an unclosed block, while retaining a real marker outside the block. This reduces a concrete false route; it is not a complete MATLAB lexer. Project-level dialect evidence and structured per-file ambiguity reporting still need work. Four bounded 32-grammar CLI fixtures took about 54 seconds on this Mac; the prior sequential implementation completed 26/32 in a single 31-file Linux run within 90 seconds; language-version corpora, native differential checks, task synchronization, host delivery and package release are still open. + +For one mixed-language project, the candidate pass plans at most 64 fragments, then runs isolated workers in ordered batches capped by both `--jobs` and two workers. All workers share the existing 90-second candidate deadline, and successful results are invalidated when the source changes before reporting. The local 31-file fixture observed all 32 pinned candidates without skipped fragments; the Linux WASM integration step for source commit `ff60184` passed the same one-project test; the complete CI run passed, while resource characterization remains pending. The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index 4d2f07b7..eb1b6360 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -212,7 +212,9 @@ flowchart LR } ``` -实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析。只读发现现在使用相同的有界 `.m` 证据,把共享后缀的未判定路径记入 `unknown_conditions`;`check all` 仍将其计入未路由数量。共享判别器现跳过 MATLAB `%{ ... %}` 块注释内看似 Objective-C 的行首标记,未闭合块也不猜测,同时保留块外真实标记。这消除一类误路由,但不是完整 MATLAB 词法器。项目级方言依据及逐文件结构化歧义报告仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 +实际报告还包含原生结果、源码与 grammar 摘要、片段偏移、有界原文件恢复坐标、未执行数量和未解决义务。完整 `` 标签体可作嵌入候选;普通 SQL 和有歧义的 C/C++ 头文件不猜测。自动 `.m` 路由要求 Objective-C 行首标记,`.sc` 与 SuperCollider 共用后缀,缺少项目证据时保持未解析。只读发现现在使用相同的有界 `.m` 证据,把共享后缀的未判定路径记入 `unknown_conditions`;`check all` 仍将其计入未路由数量。共享判别器现跳过 MATLAB `%{ ... %}` 块注释内看似 Objective-C 的行首标记,未闭合块也不猜测,同时保留块外真实标记。这消除一类误路由,但不是完整 MATLAB 词法器。项目级方言依据及逐文件结构化歧义报告仍待完善。本机四批 32 份 grammar 的 CLI 样例约 54 秒;此前串行实现的 Linux 单次 31 文件检查在 90 秒内完成 26/32;语言版本语料、原生差分对照、任务同步、宿主交付和发行包验收仍待完成。 + +对于单个混合语言项目,候选阶段最多规划 64 个片段,再按不超过 `--jobs` 且最多两个隔离 worker 的批次执行。所有 worker 共用原 90 秒候选截止时间,成功结果若在出报告前发现源码变化就作废。本机 31 文件样例观察到全部 32 种固定候选且未跳过片段;源码提交 `ff60184` 的 Linux WASM 集成步骤已通过同一单项目测试;完整 CI 已通过,资源画像仍待验收。 可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。 diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 98b6d667..915b6f3d 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1116,4 +1116,4 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 5.2 / 5.6 Linux CI 版本探测测试稳定性:旧 30ms 超时夹具在并行负载下实际返回 `SpawnFailure`,不能误报为运行时超时。现在使用 500ms 预算与 2 秒睡眠,仅在瞬时启动失败时创建新夹具重试,最终仍严格要求 `TimedOut`;本机定向 1/1、完整文件 7/7 通过。远端复验仍待本次 PR CI,父任务保持未完成;见[原生版本诊断验收](../../../tests/acceptance/native-version-diagnostics.md)。 -14.4 / 14.6 / 14.19 单项目预算局部增量:候选阶段按固定顺序准备有界源码片段,使用 `min(--jobs, 2)` 个隔离 worker 批量并行,报告仍按路径顺序;成功结果复读源码,所有任务共享原有 90 秒候选截止时间。本机 31 文件单项目可观察全部 32 种候选,约 41.54 秒、跳过 0,仍退出 3 且不作语言验收或交付许可。Linux CI 单项目回归、长期资源与冷暖启动验收尚待确认;逐语言精度、原生优先、任务及宿主反馈也未完成,父任务不勾选。见[32 份路由局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 +14.4 / 14.6 / 14.19 单项目预算局部增量:候选阶段按固定顺序准备有界源码片段,使用 `min(--jobs, 2)` 个隔离 worker 批量并行,报告仍按路径顺序;成功结果复读源码,所有任务共享原有 90 秒候选截止时间。本机 31 文件单项目可观察全部 32 种候选,约 41.54 秒、跳过 0,仍退出 3 且不作语言验收或交付许可。源码提交 `ff60184` 的 Linux CI WASM 集成步骤已通过单项目回归;完整 CI 已通过,长期资源与冷暖启动验收尚待确认;逐语言精度、原生优先、任务及宿主反馈也未完成,父任务不勾选。见[32 份路由局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 diff --git a/tests/acceptance/check-all-32-grammar-candidates.md b/tests/acceptance/check-all-32-grammar-candidates.md index 7fa4fa87..b37eeaad 100644 --- a/tests/acceptance/check-all-32-grammar-candidates.md +++ b/tests/acceptance/check-all-32-grammar-candidates.md @@ -14,4 +14,4 @@ 仍缺逐语言版本/方言语料、原生语法能力的差分对照、误报/漏报量化、为 32 种语言分别选择适用原生工具与配置、候选任务同步、宿主对话反馈、无 WASM 构建与各发行平台实装。已知 CFQuery 漏检和 VB.NET 误报依旧存在,因此本次路由不能升级为已验收 lint 或交付许可;S14.6、14.7、14.17、14.19 不勾选。 -2026-10-03 单项目预算改进:候选阶段先按稳定路径准备最多 64 个片段,再按请求的 `--jobs` 上限、且最多 2 个隔离 worker 并行执行。结果仍按准备顺序输出;每份成功观察在发布前复读完整源码,变更则降为 `source_changed_during_precheck`。90 秒候选总截止时间、64 文件/片段及各 worker 的输入、输出、内存边界不变。本机以一项目 31 个源码文件调用 32 份候选,首次定向测试约 41.54 秒完成,退出码 3、跳过 0、32 个 `candidate_observed`,交付仍为 `incomplete`。这只是单次本机样例,不是 Linux 或真实项目的性能保证;Linux 单项目验收以本次 CI 为准。 +2026-10-03 单项目预算改进:候选阶段先按稳定路径准备最多 64 个片段,再按请求的 `--jobs` 上限、且最多 2 个隔离 worker 并行执行。结果仍按准备顺序输出;每份成功观察在发布前复读完整源码,变更则降为 `source_changed_during_precheck`。90 秒候选总截止时间、64 文件/片段及各 worker 的输入、输出、内存边界不变。本机以一项目 31 个源码文件调用 32 份候选,首次定向测试约 41.54 秒完成,退出码 3、跳过 0、32 个 `candidate_observed`,交付仍为 `incomplete`。源码提交 `ff60184` 的 [Linux CI](https://github.com/full-stack-plugins/codeguard/actions/runs/37113831313) 中 WASM 集成步骤的候选文件 8/8 通过,用时约 106 秒,其中包含同一单项目 32/32 测试;完整流水线亦通过。这是固定最小样例的跨平台验收,不是真实项目的吞吐保证;长期资源画像仍缺。 From 729978dba77897aff3cf4b99580ed8ed1758b929 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:01:04 +0800 Subject: [PATCH 29/31] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Java=20=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E5=80=99=E9=80=89=E4=B8=8E=20javac=20=E7=9A=84?= =?UTF-8?q?=E7=AA=84=E8=8C=83=E5=9B=B4=E5=B7=AE=E5=88=86=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- README.md | 2 + README.zh-CN.md | 2 + .../codeguard-cli/tests/grammar_status_cli.rs | 1 + .../tests/java_native_differential.rs | 129 ++++++++++++++++++ docs/Codeguard-Architecture.md | 2 + docs/Codeguard-Architecture.zh_CN.md | 2 + docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + grammars/manifest.json | 3 +- .../introduce-rust-codeguard-cli/tasks.md | 2 + tests/acceptance/java-native-differential.md | 9 ++ 12 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 crates/codeguard-cli/tests/java_native_differential.rs create mode 100644 tests/acceptance/java-native-differential.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b34330f..6c3e6ac0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: | cargo test -p codeguard-runtime --features wasm-precheck --test process_contract cargo test -p codeguard-runtime --features wasm-precheck --test wasm_grammar_load --test dart_upstream_corpus - cargo test -p codeguard-cli --features wasm-precheck --test syntax_worker_candidate --test grammar_probe_cli --test grammar_route --test zig_lint_cli --test typescript_syntax_fallback_candidate --test java_syntax_fallback_candidate + cargo test -p codeguard-cli --features wasm-precheck --test syntax_worker_candidate --test grammar_probe_cli --test grammar_route --test zig_lint_cli --test typescript_syntax_fallback_candidate --test java_syntax_fallback_candidate --test java_native_differential # 32 份 WASM 的批次和单项目回归各自独占候选阶段预算,避免测试进程互相争抢内存与 CPU。 cargo test -p codeguard-cli --features wasm-precheck --test check_all_grammar_candidates -- --test-threads=1 diff --git a/README.md b/README.md index cda8aa79..fcedb886 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,8 @@ The same source build now runs a bounded candidate pass from `codeguard check al The source-build acceptance test also runs the 31-file mixed-language fixture as one project: at most two isolated candidate workers run concurrently, subject to `--jobs`, while report order, source rechecks, and the 90-second candidate deadline remain fixed. A local run observed all 32 candidates with no skipped fragments; the Linux WASM integration step for source commit `ff60184` passed the same one-project test; the complete CI run passed. This is still candidate coverage, not qualified lint. +Java now also has a narrow [javac 21 / Java 17 differential](tests/acceptance/java-native-differential.md): 8 valid and 5 invalid syntax samples agree with the pinned WASM. This does not qualify Java lint, other language versions, or project-wide native-first routing. + The source-built Unix CLI also has a narrow Zig route: `codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`. An explicitly supplied tool reporting Zig 0.16.0 and retaining the same byte digest runs native `ast-check` first; its source positions are retained without exposing source snippets. Without an explicit tool, the pinned Zig WASM provides an unqualified observation. Both paths remain incomplete because `ast-check` covers only local AST errors, not full lint, build or tests. See the [Zig feedback schema](schemas/zig-lint-feedback-v0.1.schema.json). On Apple Silicon macOS, the published `0.1.3` candidate package was verified with a fresh npm cache: diff --git a/README.zh-CN.md b/README.zh-CN.md index 626f57ef..4cce0de9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -108,6 +108,8 @@ cargo build --locked -p codeguard-cli 源码构建的验收测试还把 31 个源码文件作为一个混合语言项目:候选阶段最多同时运行两个隔离 worker,并服从 `--jobs`;报告顺序、源码复读与 90 秒总截止时间不变。本机单次运行观察到全部 32 种候选且无跳过片段;源码提交 `ff60184` 的 Linux WASM 集成步骤已通过同一单项目测试;完整 CI 已通过。这仍是候选覆盖,不是已验收的 lint。 +Java 另有一组窄范围的 [javac 21 / Java 17 差分](tests/acceptance/java-native-differential.md):8 份合法、5 份非法语法样例与固定 WASM 一致。这不代表 Java lint、其它语言版本或项目级原生优先路由已验收。 + 源码构建的 Unix CLI 还提供局部 Zig 入口:`codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`。显式提供且报告 Zig 0.16.0、执行前后字节摘要一致的工具优先运行原生 `ast-check`,仅输出诊断位置,不回显源码;未显式提供工具时,固定 Zig WASM 给出未验收候选观察。两条路径都保持未完成,因为 `ast-check` 仅覆盖局部 AST 错误,不等于完整 lint、构建或测试。见 [Zig 报告 Schema](schemas/zig-lint-feedback-v0.1.schema.json)。 已在 Apple Silicon macOS 上通过全新 npm 缓存验证公开 `0.1.3` 候选包: diff --git a/crates/codeguard-cli/tests/grammar_status_cli.rs b/crates/codeguard-cli/tests/grammar_status_cli.rs index c3f100a6..084204b1 100644 --- a/crates/codeguard-cli/tests/grammar_status_cli.rs +++ b/crates/codeguard-cli/tests/grammar_status_cli.rs @@ -111,6 +111,7 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() { for (language, evidence) in [ ("c", "C11 Apple clang 21 differential"), ("go", "Go 1.23.4 gofmt differential"), + ("java", "Java 17 syntax corpus of 8 valid and 5 invalid"), ("javascript", "Node 24.18.0 module syntax differential"), ("rust", "Rust 2021 rustfmt 1.9.0 differential"), ("zig", "Zig 0.16.0 ast-check differential"), diff --git a/crates/codeguard-cli/tests/java_native_differential.rs b/crates/codeguard-cli/tests/java_native_differential.rs new file mode 100644 index 00000000..e81564be --- /dev/null +++ b/crates/codeguard-cli/tests/java_native_differential.rs @@ -0,0 +1,129 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::path::Path; +use std::process::Command; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("empty_class", "class C {}\n", true), + ("field", "class C { int value = 1; }\n", true), + ( + "method", + "class C { int f(int x) { return x + 1; } }\n", + true, + ), + ( + "generic", + "class C { T value; C(T value) { this.value = value; } }\n", + true, + ), + ("interface", "interface C { void run(); }\n", true), + ("enumeration", "enum C { FIRST, SECOND }\n", true), + ("record", "record C(int value) {}\n", true), + ( + "lambda", + "class C { Runnable r = () -> { int value = 1; }; }\n", + true, + ), + ("missing_brace", "class C { void f() {}\n", false), + ("missing_semicolon", "class C { int value = 1 }\n", false), + ( + "missing_parameter_type", + "class C { void f(value) {} }\n", + false, + ), + ("missing_expression", "class C { int value = ; }\n", false), + ( + "unclosed_string", + "class C { String value = \"oops; }\n", + false, + ), + ] +} + +fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool { + let file = root.join(format!("{name}.java")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "java"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report["recoveries"].as_array().unwrap().is_empty() +} + +#[test] +fn java_worker_preserves_native_labeled_syntax_corpus() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-java-corpus-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + assert_eq!( + candidate_is_valid(&root, name, source), + expected_valid, + "{name}" + ); + } + fs::remove_dir_all(root).unwrap(); +} + +#[test] +#[ignore = "requires explicit existing javac 21 via CODEGUARD_JAVAC_BIN"] +fn pinned_java_worker_matches_native_javac_on_syntax_corpus() { + let javac = std::env::var("CODEGUARD_JAVAC_BIN").expect("provide an existing javac executable"); + let version = Command::new(&javac).arg("-version").output().unwrap(); + assert!(version.status.success()); + assert!( + String::from_utf8_lossy(&version.stdout).starts_with("javac 21."), + "unexpected javac version: {}", + String::from_utf8_lossy(&version.stdout) + ); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-java-differential-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + for (name, source, expected_valid) in corpus() { + let file = root.join(format!("{name}.java")); + fs::write(&file, source).unwrap(); + let output_dir = root.join(format!("out-{name}")); + fs::create_dir_all(&output_dir).unwrap(); + let native = Command::new(&javac) + .args(["--release", "17", "-proc:none", "-Xlint:none", "-d"]) + .arg(&output_dir) + .arg(&file) + .output() + .unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1)), + "{name}: {native:?}" + ); + assert_eq!( + native.status.success(), + expected_valid, + "{name}: javac disagrees with corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + assert_eq!(fs::read_to_string(&file).unwrap(), source); + assert_eq!( + candidate_is_valid(&root, name, source), + native.status.success(), + "{name}: javac={} worker classification differs", + String::from_utf8_lossy(&native.stderr) + ); + } + fs::remove_dir_all(root).unwrap(); +} diff --git a/docs/Codeguard-Architecture.md b/docs/Codeguard-Architecture.md index 55639222..ff47f1e4 100644 --- a/docs/Codeguard-Architecture.md +++ b/docs/Codeguard-Architecture.md @@ -36,6 +36,8 @@ The source-built `check all` now runs a bounded candidate pass after its existin The source-built candidate scheduler now prepares bounded fragments in stable path order and runs up to `min(--jobs, 2)` isolated workers concurrently. It emits observations in that same order and rereads each source before accepting a worker result. A local 31-file, one-project fixture observed all 32 candidates within the unchanged 90-second pass; the Linux WASM integration step for source commit `ff60184` passed the same one-project test; the complete CI run passed. This changes candidate throughput, not its authority. +The [Java 17 native differential](../tests/acceptance/java-native-differential.md) adds a 13-case javac 21 oracle for one grammar. It is local precision evidence only; Java remains an unqualified candidate. + ```mermaid flowchart LR A[check all discovery] --> B[Native adapters and blockers] diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index 63593134..cc8a46a0 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -36,6 +36,8 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选 Rust worker 源码构建的候选调度现在按稳定路径顺序准备有界片段,以 `min(--jobs, 2)` 个隔离 worker 并发执行;报告保持同序,接受结果前逐文件复读源码。本机 31 文件单项目样例在原 90 秒预算内观察到全部 32 种候选;源码提交 `ff60184` 的 Linux WASM 集成步骤已通过同一单项目测试;完整 CI 已通过。吞吐改进不提升候选观察的门禁权威。 +[Java 17 原生差分](../tests/acceptance/java-native-differential.md)给 Java grammar 增加了 13 例 javac 21 独立 oracle。它只是局部精度证据,Java 仍是未验收候选。 + ```mermaid flowchart LR A[check all 静态发现] --> B[原生适配器及环境阻塞] diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index b311911b..0fcb7115 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -216,6 +216,8 @@ The actual report also includes native results, source/grammar digests, fragment For one mixed-language project, the candidate pass plans at most 64 fragments, then runs isolated workers in ordered batches capped by both `--jobs` and two workers. All workers share the existing 90-second candidate deadline, and successful results are invalidated when the source changes before reporting. The local 31-file fixture observed all 32 pinned candidates without skipped fragments; the Linux WASM integration step for source commit `ff60184` passed the same one-project test; the complete CI run passed, while resource characterization remains pending. +For Java, [javac 21 with `--release 17`](../tests/acceptance/java-native-differential.md) accepted eight valid and rejected five malformed syntax fixtures, matching the isolated WASM worker. The test pins only that narrow classification; project-level native routing, version coverage and false-positive rates remain open. + The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). ### 5.4 Grammar import and runtime lifecycle — target diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index eb1b6360..d197aaf2 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -216,6 +216,8 @@ flowchart LR 对于单个混合语言项目,候选阶段最多规划 64 个片段,再按不超过 `--jobs` 且最多两个隔离 worker 的批次执行。所有 worker 共用原 90 秒候选截止时间,成功结果若在出报告前发现源码变化就作废。本机 31 文件样例观察到全部 32 种固定候选且未跳过片段;源码提交 `ff60184` 的 Linux WASM 集成步骤已通过同一单项目测试;完整 CI 已通过,资源画像仍待验收。 +Java 的 [javac 21 `--release 17` 对照](../tests/acceptance/java-native-differential.md)接受 8 例合法、拒绝 5 例破损语法源码,与隔离 WASM worker 分类一致。该测试仅固定窄范围分类;项目级原生路由、版本覆盖和误报率仍待验收。 + 可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。 ### 5.4 Grammar 引入与运行生命周期——目标 diff --git a/grammars/manifest.json b/grammars/manifest.json index 169c3763..dcd0ad96 100644 --- a/grammars/manifest.json +++ b/grammars/manifest.json @@ -103,7 +103,8 @@ "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], "known_limitations": [ - "Rust loader smoke passed; MSRV build, dialect versions, isolation and syntax corpus are not yet validated" + "A narrow Java 17 syntax corpus of 8 valid and 5 invalid samples matched javac 21 --release 17; other language versions, dialects and systematic false-positive rates remain unqualified", + "Project-native javac/P3C/Javadoc selection, MSRV and release qualification remain open" ], "release_status": "candidate_unvalidated" }, diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 915b6f3d..4032f18b 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1117,3 +1117,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 5.2 / 5.6 Linux CI 版本探测测试稳定性:旧 30ms 超时夹具在并行负载下实际返回 `SpawnFailure`,不能误报为运行时超时。现在使用 500ms 预算与 2 秒睡眠,仅在瞬时启动失败时创建新夹具重试,最终仍严格要求 `TimedOut`;本机定向 1/1、完整文件 7/7 通过。远端复验仍待本次 PR CI,父任务保持未完成;见[原生版本诊断验收](../../../tests/acceptance/native-version-diagnostics.md)。 14.4 / 14.6 / 14.19 单项目预算局部增量:候选阶段按固定顺序准备有界源码片段,使用 `min(--jobs, 2)` 个隔离 worker 批量并行,报告仍按路径顺序;成功结果复读源码,所有任务共享原有 90 秒候选截止时间。本机 31 文件单项目可观察全部 32 种候选,约 41.54 秒、跳过 0,仍退出 3 且不作语言验收或交付许可。源码提交 `ff60184` 的 Linux CI WASM 集成步骤已通过单项目回归;完整 CI 已通过,长期资源与冷暖启动验收尚待确认;逐语言精度、原生优先、任务及宿主反馈也未完成,父任务不勾选。见[32 份路由局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 + +14.4 / 14.17 / 14.19 Java 17 原生差分局部增量:本机现有 javac 21.0.12.1 以 `--release 17` 接受 8 份合法、拒绝 5 份纯语法破损样例,固定 Java WASM 隔离 worker 对 13 例的恢复分类与之全部一致。常规语料及显式原生差分目标测试各 1/1 通过,清单已把原来笼统的“语料未验收”更正为窄范围事实。其它 Java 版本/方言、系统误报率、统一命令原生优先与完整发行验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/java-native-differential.md)。 diff --git a/tests/acceptance/java-native-differential.md b/tests/acceptance/java-native-differential.md new file mode 100644 index 00000000..d5a23d05 --- /dev/null +++ b/tests/acceptance/java-native-differential.md @@ -0,0 +1,9 @@ +# Java 17 与 javac 21 的窄范围语法差分验收 + +日期:2026-10-03。对应 OpenSpec 14.4、14.17、14.19 的局部证据,不代表 Java 语法能力已验收或发行。 + +固定 CodeGuard 清单中的 Java WASM(SHA-256 `181a6fbc34d7864a551d91c13882fc33007e923b3c81a4bdbe7fa47492090077`)由公开 `grammar probe java` 隔离 worker 解析同一组 13 份源码:8 份合法类、接口、枚举、泛型、record 和 lambda,5 份缺括号、分号、参数类型、表达式或未闭合字符串的破损源码。普通语料测试逐例要求恢复节点的有无与预期一致,仍要求退出码 3、`grammar_qualified=false`、`delivery_decision=not_evaluated`。 + +独立原生 oracle 使用本机现有 `/usr/bin/javac` 21.0.12.1,对每例运行 `--release 17 -proc:none -Xlint:none`,编译产物写入各自隔离目录;13/13 的原生接受/拒绝与候选 worker 分类一致,且源文件未被修改。常规候选测试 1/1、显式原生差分测试 1/1 通过。Linux CI 执行无需 javac 的普通语料回归;原生差分用例默认忽略,必须显式提供 `CODEGUARD_JAVAC_BIN` 且版本为 21.x 才运行。 + +此证据只覆盖 Java 17 的 13 个窄范围样例。其它语言版本、语义诊断、真实项目原生检查范围、系统误报/漏报率、`check all` 的 Java 原生优先路由、任务与宿主反馈、MSRV 及发行包精度均未验收;不能把一次差分一致说成完整 lint 或批准交付。 From 1623cf957d92a4ce5d77365448fe41f79ba03cf5 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:10:26 +0800 Subject: [PATCH 30/31] =?UTF-8?q?=E5=9B=BA=E5=AE=9A=20Kotlin=20=E8=AF=AD?= =?UTF-8?q?=E6=B3=95=E5=80=99=E9=80=89=E7=9A=84=E5=8E=9F=E7=94=9F=E5=B7=AE?= =?UTF-8?q?=E5=88=86=E6=BC=8F=E6=A3=80=E8=AF=81=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- README.md | 2 + README.zh-CN.md | 2 + .../tests/check_all_grammar_candidates.rs | 5 + .../codeguard-cli/tests/grammar_status_cli.rs | 4 + .../tests/kotlin_native_differential.rs | 121 ++++++++++++++++++ docs/Codeguard-Architecture.md | 2 + docs/Codeguard-Architecture.zh_CN.md | 2 + docs/Codeguard-Technical-Design.md | 2 + docs/Codeguard-Technical-Design.zh_CN.md | 2 + grammars/manifest.json | 2 +- .../introduce-rust-codeguard-cli/tasks.md | 2 + .../acceptance/kotlin-native-differential.md | 9 ++ 13 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 crates/codeguard-cli/tests/kotlin_native_differential.rs create mode 100644 tests/acceptance/kotlin-native-differential.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c3e6ac0..fbb14190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: | cargo test -p codeguard-runtime --features wasm-precheck --test process_contract cargo test -p codeguard-runtime --features wasm-precheck --test wasm_grammar_load --test dart_upstream_corpus - cargo test -p codeguard-cli --features wasm-precheck --test syntax_worker_candidate --test grammar_probe_cli --test grammar_route --test zig_lint_cli --test typescript_syntax_fallback_candidate --test java_syntax_fallback_candidate --test java_native_differential + cargo test -p codeguard-cli --features wasm-precheck --test syntax_worker_candidate --test grammar_probe_cli --test grammar_route --test zig_lint_cli --test typescript_syntax_fallback_candidate --test java_syntax_fallback_candidate --test java_native_differential --test kotlin_native_differential # 32 份 WASM 的批次和单项目回归各自独占候选阶段预算,避免测试进程互相争抢内存与 CPU。 cargo test -p codeguard-cli --features wasm-precheck --test check_all_grammar_candidates -- --test-threads=1 diff --git a/README.md b/README.md index fcedb886..30ed835a 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,8 @@ The source-build acceptance test also runs the 31-file mixed-language fixture as Java now also has a narrow [javac 21 / Java 17 differential](tests/acceptance/java-native-differential.md): 8 valid and 5 invalid syntax samples agree with the pinned WASM. This does not qualify Java lint, other language versions, or project-wide native-first routing. +A [Kotlin 2.4.10 differential](tests/acceptance/kotlin-native-differential.md) found a concrete false negative: `kotlinc` rejects `fun f(x: ) = x` while the pinned WASM emits no recovery. The candidate and delivery remain incomplete; install or run the applicable native checker for confirmation. + The source-built Unix CLI also has a narrow Zig route: `codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`. An explicitly supplied tool reporting Zig 0.16.0 and retaining the same byte digest runs native `ast-check` first; its source positions are retained without exposing source snippets. Without an explicit tool, the pinned Zig WASM provides an unqualified observation. Both paths remain incomplete because `ast-check` covers only local AST errors, not full lint, build or tests. See the [Zig feedback schema](schemas/zig-lint-feedback-v0.1.schema.json). On Apple Silicon macOS, the published `0.1.3` candidate package was verified with a fresh npm cache: diff --git a/README.zh-CN.md b/README.zh-CN.md index 4cce0de9..bff0487b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -110,6 +110,8 @@ cargo build --locked -p codeguard-cli Java 另有一组窄范围的 [javac 21 / Java 17 差分](tests/acceptance/java-native-differential.md):8 份合法、5 份非法语法样例与固定 WASM 一致。这不代表 Java lint、其它语言版本或项目级原生优先路由已验收。 +[Kotlin 2.4.10 差分](tests/acceptance/kotlin-native-differential.md)发现具体漏检:`kotlinc` 拒绝 `fun f(x: ) = x`,固定 WASM 却没有恢复节点。候选与交付仍保持未完成,必须由适用原生检查器确认。 + 源码构建的 Unix CLI 还提供局部 Zig 入口:`codeguard lint zig FILE --zig-tool /absolute/path/to/zig --format=json`。显式提供且报告 Zig 0.16.0、执行前后字节摘要一致的工具优先运行原生 `ast-check`,仅输出诊断位置,不回显源码;未显式提供工具时,固定 Zig WASM 给出未验收候选观察。两条路径都保持未完成,因为 `ast-check` 仅覆盖局部 AST 错误,不等于完整 lint、构建或测试。见 [Zig 报告 Schema](schemas/zig-lint-feedback-v0.1.schema.json)。 已在 Apple Silicon macOS 上通过全新 npm 缓存验证公开 `0.1.3` 候选包: diff --git a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs index ef9cdcc4..ce1dc8ae 100644 --- a/crates/codeguard-cli/tests/check_all_grammar_candidates.rs +++ b/crates/codeguard-cli/tests/check_all_grammar_candidates.rs @@ -192,6 +192,7 @@ fn known_grammar_precision_limits_reach_project_feedback() { "SELECT FROM users\n", ) .unwrap(); + fs::write(root.join("broken.kt"), "fun f(x: ) = x\n").unwrap(); let output = Command::new(env!("CARGO_BIN_EXE_codeguard")) .args(["check", "all"]) .arg(&root) @@ -207,6 +208,7 @@ fn known_grammar_precision_limits_reach_project_feedback() { for (language, expected) in [ ("vbnet", "known grammar false positive"), ("cfquery", "does not validate full SQL semantics"), + ("kotlin", "native compiler rejects missing parameter type"), ] { let observation = observations .iter() @@ -220,6 +222,9 @@ fn known_grammar_precision_limits_reach_project_feedback() { "{language}: {observation}" ); assert_eq!(observation["grammar_qualified"], false); + if language == "kotlin" { + assert_eq!(observation["recovery_count"], 0); + } } assert_eq!(report["delivery_decision"], "incomplete"); } diff --git a/crates/codeguard-cli/tests/grammar_status_cli.rs b/crates/codeguard-cli/tests/grammar_status_cli.rs index 084204b1..e680d731 100644 --- a/crates/codeguard-cli/tests/grammar_status_cli.rs +++ b/crates/codeguard-cli/tests/grammar_status_cli.rs @@ -113,6 +113,10 @@ fn codegraph_coverage_is_explicit_and_never_claims_parser_or_gate_completion() { ("go", "Go 1.23.4 gofmt differential"), ("java", "Java 17 syntax corpus of 8 valid and 5 invalid"), ("javascript", "Node 24.18.0 module syntax differential"), + ( + "kotlin", + "Kotlin 2.4.10 native compiler rejects missing parameter type", + ), ("rust", "Rust 2021 rustfmt 1.9.0 differential"), ("zig", "Zig 0.16.0 ast-check differential"), ("cfquery", "does not validate full SQL semantics"), diff --git a/crates/codeguard-cli/tests/kotlin_native_differential.rs b/crates/codeguard-cli/tests/kotlin_native_differential.rs new file mode 100644 index 00000000..f442a680 --- /dev/null +++ b/crates/codeguard-cli/tests/kotlin_native_differential.rs @@ -0,0 +1,121 @@ +#![cfg(all(feature = "wasm-precheck", unix))] + +use std::fs; +use std::path::Path; +use std::process::Command; + +fn corpus() -> [(&'static str, &'static str, bool); 13] { + [ + ("empty_class", "class C {}\n", true), + ("primary_constructor", "class C(val value: Int)\n", true), + ("expression_function", "fun f(x: Int): Int = x + 1\n", true), + ("data_class", "data class C(val name: String)\n", true), + ("enumeration", "enum class C { A, B }\n", true), + ("object", "object C { val value = 1 }\n", true), + ("nullable", "fun f(): Int? = null\n", true), + ( + "lambda", + "fun f() { val g: (Int) -> Int = { it + 1 } }\n", + true, + ), + ("missing_class_brace", "class C {\n", false), + ( + "missing_function_brace", + "fun f(x: Int) { val a = 1\n", + false, + ), + ("missing_parameter_type", "fun f(x: ) = x\n", false), + ("missing_expression", "val x =\n", false), + ("unclosed_string", "val x = \"oops\n", false), + ] +} + +fn candidate_is_valid(root: &Path, name: &str, source: &str) -> bool { + let file = root.join(format!("{name}.kt")); + fs::write(&file, source).unwrap(); + let candidate = Command::new(env!("CARGO_BIN_EXE_codeguard")) + .args(["grammar", "probe", "kotlin"]) + .arg(&file) + .arg("--format=json") + .output() + .unwrap(); + assert_eq!(candidate.status.code(), Some(3), "{name}: {candidate:?}"); + let report: serde_json::Value = serde_json::from_slice(&candidate.stdout).unwrap(); + assert_eq!(report["grammar_qualified"], false); + assert_eq!(report["delivery_decision"], "not_evaluated"); + assert_eq!( + report["reason"], + serde_json::Value::Null, + "{name}: {report}" + ); + report["recoveries"].as_array().unwrap().is_empty() +} + +#[test] +fn kotlin_worker_preserves_native_labeled_syntax_corpus() { + let root = std::env::temp_dir() + .canonicalize() + .unwrap() + .join(format!("codeguard-kotlin-corpus-{}", std::process::id())); + fs::create_dir_all(&root).unwrap(); + let mut mismatches = Vec::new(); + for (name, source, expected_valid) in corpus() { + if candidate_is_valid(&root, name, source) != expected_valid { + mismatches.push(name); + } + } + fs::remove_dir_all(root).unwrap(); + assert_eq!(mismatches, ["missing_parameter_type"]); +} + +#[test] +#[ignore = "requires explicit existing kotlinc 2.4.10 via CODEGUARD_KOTLINC_BIN"] +fn pinned_kotlin_worker_matches_native_kotlinc_on_syntax_corpus() { + let kotlinc = + std::env::var("CODEGUARD_KOTLINC_BIN").expect("provide an existing kotlinc executable"); + let version = Command::new(&kotlinc).arg("-version").output().unwrap(); + assert!(version.status.success()); + let version_text = format!( + "{}{}", + String::from_utf8_lossy(&version.stdout), + String::from_utf8_lossy(&version.stderr) + ); + assert!( + version_text.contains("kotlinc-jvm 2.4.10"), + "unexpected kotlinc version: {version_text}" + ); + let root = std::env::temp_dir().canonicalize().unwrap().join(format!( + "codeguard-kotlin-differential-{}", + std::process::id() + )); + fs::create_dir_all(&root).unwrap(); + let mut mismatches = Vec::new(); + for (name, source, expected_valid) in corpus() { + let file = root.join(format!("{name}.kt")); + fs::write(&file, source).unwrap(); + let output_dir = root.join(format!("out-{name}")); + fs::create_dir_all(&output_dir).unwrap(); + let native = Command::new(&kotlinc) + .arg(&file) + .args(["-nowarn", "-d"]) + .arg(&output_dir) + .output() + .unwrap(); + assert!( + matches!(native.status.code(), Some(0 | 1)), + "{name}: {native:?}" + ); + assert_eq!( + native.status.success(), + expected_valid, + "{name}: kotlinc disagrees with corpus label: {}", + String::from_utf8_lossy(&native.stderr) + ); + assert_eq!(fs::read_to_string(&file).unwrap(), source); + if candidate_is_valid(&root, name, source) != native.status.success() { + mismatches.push(name); + } + } + fs::remove_dir_all(root).unwrap(); + assert_eq!(mismatches, ["missing_parameter_type"]); +} diff --git a/docs/Codeguard-Architecture.md b/docs/Codeguard-Architecture.md index ff47f1e4..ec76ce06 100644 --- a/docs/Codeguard-Architecture.md +++ b/docs/Codeguard-Architecture.md @@ -38,6 +38,8 @@ The source-built candidate scheduler now prepares bounded fragments in stable pa The [Java 17 native differential](../tests/acceptance/java-native-differential.md) adds a 13-case javac 21 oracle for one grammar. It is local precision evidence only; Java remains an unqualified candidate. +A separate [Kotlin native differential](../tests/acceptance/kotlin-native-differential.md) found one false negative in 13 samples: a missing parameter type is rejected by `kotlinc` but invisible to the pinned WASM. The route keeps this grammar unqualified and projects the limitation to status and project feedback. + ```mermaid flowchart LR A[check all discovery] --> B[Native adapters and blockers] diff --git a/docs/Codeguard-Architecture.zh_CN.md b/docs/Codeguard-Architecture.zh_CN.md index cc8a46a0..bceafe59 100644 --- a/docs/Codeguard-Architecture.zh_CN.md +++ b/docs/Codeguard-Architecture.zh_CN.md @@ -38,6 +38,8 @@ WASM 的规范与 19 项实施任务已纳入既有 change;可选 Rust worker [Java 17 原生差分](../tests/acceptance/java-native-differential.md)给 Java grammar 增加了 13 例 javac 21 独立 oracle。它只是局部精度证据,Java 仍是未验收候选。 +[Kotlin 原生差分](../tests/acceptance/kotlin-native-differential.md)在 13 例中发现一处漏检:参数类型缺失被 `kotlinc` 拒绝,却没有触发固定 WASM 的恢复节点。该语法仍未验收,具体限制投影到状态和项目反馈。 + ```mermaid flowchart LR A[check all 静态发现] --> B[原生适配器及环境阻塞] diff --git a/docs/Codeguard-Technical-Design.md b/docs/Codeguard-Technical-Design.md index 0fcb7115..d76b769b 100644 --- a/docs/Codeguard-Technical-Design.md +++ b/docs/Codeguard-Technical-Design.md @@ -218,6 +218,8 @@ For one mixed-language project, the candidate pass plans at most 64 fragments, t For Java, [javac 21 with `--release 17`](../tests/acceptance/java-native-differential.md) accepted eight valid and rejected five malformed syntax fixtures, matching the isolated WASM worker. The test pins only that narrow classification; project-level native routing, version coverage and false-positive rates remain open. +The [Kotlin 2.4.10 differential](../tests/acceptance/kotlin-native-differential.md) records a 12/13 classification agreement and pins the disagreement: `fun f(x: ) = x` is native-rejected but has zero WASM recoveries. `grammar status` and `check all` expose this as a bounded known limitation; zero recoveries cannot close a native confirmation obligation. + The opt-in CLI build first checks for a local ESLint 10 package and a single ordinary flat config on single-file TypeScript requests without explicit native context. With an executable Node resolved from `PATH`, it invokes the existing bounded native version/report probe; without Node it reports the preparation gap rather than running WASM first. Only when no project-local ESLint package is observed does the CLI emit [ESLint feedback 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json) for `.ts/.mts/.cts` or [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json) for `.tsx`. TSX uses its own pinned grammar. Candidate prechecks preserve `native=not_run` and `delivery=not_evaluated`, and remain incomplete even with zero recoveries. An ambiguous config, untrusted local path or invalid package identity remains a setup blocker and does not trigger WASM; a partial explicit context also retains its existing path. This is a single-file slice, not project script equivalence, per-module orchestration or automatic host feedback; see the [native-first acceptance](../tests/acceptance/native-first-eslint-candidate.md). ### 5.4 Grammar import and runtime lifecycle — target diff --git a/docs/Codeguard-Technical-Design.zh_CN.md b/docs/Codeguard-Technical-Design.zh_CN.md index d197aaf2..47ffc398 100644 --- a/docs/Codeguard-Technical-Design.zh_CN.md +++ b/docs/Codeguard-Technical-Design.zh_CN.md @@ -218,6 +218,8 @@ flowchart LR Java 的 [javac 21 `--release 17` 对照](../tests/acceptance/java-native-differential.md)接受 8 例合法、拒绝 5 例破损语法源码,与隔离 WASM worker 分类一致。该测试仅固定窄范围分类;项目级原生路由、版本覆盖和误报率仍待验收。 +[Kotlin 2.4.10 差分](../tests/acceptance/kotlin-native-differential.md)记录 12/13 分类一致,并固定反例:`fun f(x: ) = x` 被原生拒绝而 WASM 零恢复。`grammar status` 和 `check all` 将其作为有界已知限制展示;零恢复不能关闭原生确认义务。 + 可选特性构建的 CLI 在无显式原生上下文的 TypeScript 单文件请求中,先核对本地 ESLint 10 包及唯一普通 flat config。若从 `PATH` 解析到可执行 Node,就调用既有有界原生版本与报告探针;缺 Node 时报告准备缺口,不让 WASM 抢跑。仅未观察到本地 ESLint 包时,`.ts/.mts/.cts` 输出 [ESLint 反馈 0.3.0](../schemas/eslint-local-feedback-v0.3.schema.json),`.tsx` 用独立 grammar 输出 [0.4.0](../schemas/eslint-local-feedback-v0.4.schema.json)。候选初检保留 `native=not_run`、`delivery=not_evaluated`,即使零恢复节点也保持未完成。配置选择歧义、本地路径不可信或包身份损坏时保留环境阻塞而不启动 WASM;部分显式上下文也沿用原路径。该增量不证明项目脚本参数等价、逐模块调度或宿主对话交付,见[原生优先局部验收](../tests/acceptance/native-first-eslint-candidate.md)。 ### 5.4 Grammar 引入与运行生命周期——目标 diff --git a/grammars/manifest.json b/grammars/manifest.json index dcd0ad96..f7e64e64 100644 --- a/grammars/manifest.json +++ b/grammars/manifest.json @@ -622,7 +622,7 @@ "codegraph_runtime": "web-tree-sitter 0.25.3", "codeguard_runtime_validation": "rust_loader_smoke_passed", "language_versions": [], - "known_limitations": ["Rust loader and narrow fixtures passed; Kotlin language versions, dialect corpus, native differential, isolation and release remain unqualified"], + "known_limitations": ["Kotlin 2.4.10 native compiler rejects missing parameter type in `fun f(x: ) = x` but this WASM emits no recovery; 12/13 narrow differential cases agree. Zero recoveries cannot establish valid Kotlin syntax", "Other Kotlin versions, dialects, public native-first lint route, resource budget and release remain unqualified"], "release_status": "candidate_unvalidated" } ], diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 4032f18b..83ae4d52 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1119,3 +1119,5 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.6 / 14.19 单项目预算局部增量:候选阶段按固定顺序准备有界源码片段,使用 `min(--jobs, 2)` 个隔离 worker 批量并行,报告仍按路径顺序;成功结果复读源码,所有任务共享原有 90 秒候选截止时间。本机 31 文件单项目可观察全部 32 种候选,约 41.54 秒、跳过 0,仍退出 3 且不作语言验收或交付许可。源码提交 `ff60184` 的 Linux CI WASM 集成步骤已通过单项目回归;完整 CI 已通过,长期资源与冷暖启动验收尚待确认;逐语言精度、原生优先、任务及宿主反馈也未完成,父任务不勾选。见[32 份路由局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 14.4 / 14.17 / 14.19 Java 17 原生差分局部增量:本机现有 javac 21.0.12.1 以 `--release 17` 接受 8 份合法、拒绝 5 份纯语法破损样例,固定 Java WASM 隔离 worker 对 13 例的恢复分类与之全部一致。常规语料及显式原生差分目标测试各 1/1 通过,清单已把原来笼统的“语料未验收”更正为窄范围事实。其它 Java 版本/方言、系统误报率、统一命令原生优先与完整发行验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/java-native-differential.md)。 + +14.4 / 14.17 / 14.19 Kotlin 精度阻塞:本机 `kotlinc-jvm 2.4.10` 与固定 Kotlin WASM 对 13 份语法样例仅 12/13 分类一致。`fun f(x: ) = x` 被原生以缺类型拒绝,WASM 却零恢复,确认漏检。普通语料与显式原生差分测试各 1/1 通过并固定此唯一分歧;清单、`grammar status` 和 `check all` 逐候选已知限制显示反例,项目报告仍不完整。需修 grammar 并补充版本语料、原生优先路由与发行验收;三项父任务不勾选。见[局部证据](../../../tests/acceptance/kotlin-native-differential.md)。 diff --git a/tests/acceptance/kotlin-native-differential.md b/tests/acceptance/kotlin-native-differential.md new file mode 100644 index 00000000..3ec8e351 --- /dev/null +++ b/tests/acceptance/kotlin-native-differential.md @@ -0,0 +1,9 @@ +# Kotlin 原生差分发现固定 grammar 漏检 + +日期:2026-10-03。对应 OpenSpec 14.4、14.17、14.19 的精度阻塞,Kotlin 仍为未验收候选。 + +固定 CodeGuard Kotlin WASM(SHA-256 `c80c88867a589a1a0959bcea89de84b7e9684b3693b2cdb2944812458e62ff48`)经隔离 `grammar probe kotlin` 解析 13 份源码:8 份合法、5 份故意破损。本机现有 `kotlinc-jvm 2.4.10` 分别编译同一输入,8 份合法通过、5 份非法拒绝;WASM 与原生仅 12/13 一致。 + +分歧样例是 `fun f(x: ) = x`。原生编译器在参数冒号后报告 `Type expected` 与 `Incomplete code`;固定 WASM 返回零恢复节点。这是**漏检**,不能因候选零恢复而宣称语法有效或建议跳过原生工具。常规语料测试明确要求仅该样例保留分歧,显式原生测试再次确认 12/13 和源码未变;两项均各 1/1 通过。`grammar status` 与 `check all` 的 `known_limitations` 现在显示具体漏检,项目样例还断言该候选零恢复、整体交付仍为 `incomplete`。Linux CI 运行无需 `kotlinc` 的固定回归;原生对照默认忽略,需显式提供 `CODEGUARD_KOTLINC_BIN` 且版本为 2.4.10。 + +后续需要修复或更新固定 grammar,保留原始与派生字节、许可证和来源,再以同一反例及更多语言版本/方言语料复测;还须完成统一原生优先路由、误报/漏报率、资源预算及发行包验收。只更新已知限制不能消除漏检。 From f9af1e91635ac4967f2effe9f44e831ca7fa65c9 Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:24:58 +0800 Subject: [PATCH 31/31] =?UTF-8?q?=E8=A1=A5=E5=85=A8=20Java=20=E4=B8=8E=20K?= =?UTF-8?q?otlin=20=E5=B7=AE=E5=88=86=E7=9A=84=20Linux=20=E9=AA=8C?= =?UTF-8?q?=E6=94=B6=E8=AF=81=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openspec/changes/introduce-rust-codeguard-cli/tasks.md | 4 ++-- tests/acceptance/java-native-differential.md | 2 ++ tests/acceptance/kotlin-native-differential.md | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openspec/changes/introduce-rust-codeguard-cli/tasks.md b/openspec/changes/introduce-rust-codeguard-cli/tasks.md index 83ae4d52..c92f178d 100644 --- a/openspec/changes/introduce-rust-codeguard-cli/tasks.md +++ b/openspec/changes/introduce-rust-codeguard-cli/tasks.md @@ -1118,6 +1118,6 @@ TDD缺自动同步先RED。相关普通:build9、work sync15、next6,共30 14.4 / 14.6 / 14.19 单项目预算局部增量:候选阶段按固定顺序准备有界源码片段,使用 `min(--jobs, 2)` 个隔离 worker 批量并行,报告仍按路径顺序;成功结果复读源码,所有任务共享原有 90 秒候选截止时间。本机 31 文件单项目可观察全部 32 种候选,约 41.54 秒、跳过 0,仍退出 3 且不作语言验收或交付许可。源码提交 `ff60184` 的 Linux CI WASM 集成步骤已通过单项目回归;完整 CI 已通过,长期资源与冷暖启动验收尚待确认;逐语言精度、原生优先、任务及宿主反馈也未完成,父任务不勾选。见[32 份路由局部验收](../../../tests/acceptance/check-all-32-grammar-candidates.md)。 -14.4 / 14.17 / 14.19 Java 17 原生差分局部增量:本机现有 javac 21.0.12.1 以 `--release 17` 接受 8 份合法、拒绝 5 份纯语法破损样例,固定 Java WASM 隔离 worker 对 13 例的恢复分类与之全部一致。常规语料及显式原生差分目标测试各 1/1 通过,清单已把原来笼统的“语料未验收”更正为窄范围事实。其它 Java 版本/方言、系统误报率、统一命令原生优先与完整发行验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/java-native-differential.md)。 +14.4 / 14.17 / 14.19 Java 17 原生差分局部增量:本机现有 javac 21.0.12.1 以 `--release 17` 接受 8 份合法、拒绝 5 份纯语法破损样例,固定 Java WASM 隔离 worker 对 13 例的恢复分类与之全部一致。常规语料及显式原生差分目标测试各 1/1 通过,提交 `729978d` 的 Linux CI 普通语料与全套测试通过;清单已把原来笼统的“语料未验收”更正为窄范围事实。其它 Java 版本/方言、系统误报率、统一命令原生优先与完整发行验收仍缺,父任务不勾选;见[局部验收](../../../tests/acceptance/java-native-differential.md)。 -14.4 / 14.17 / 14.19 Kotlin 精度阻塞:本机 `kotlinc-jvm 2.4.10` 与固定 Kotlin WASM 对 13 份语法样例仅 12/13 分类一致。`fun f(x: ) = x` 被原生以缺类型拒绝,WASM 却零恢复,确认漏检。普通语料与显式原生差分测试各 1/1 通过并固定此唯一分歧;清单、`grammar status` 和 `check all` 逐候选已知限制显示反例,项目报告仍不完整。需修 grammar 并补充版本语料、原生优先路由与发行验收;三项父任务不勾选。见[局部证据](../../../tests/acceptance/kotlin-native-differential.md)。 +14.4 / 14.17 / 14.19 Kotlin 精度阻塞:本机 `kotlinc-jvm 2.4.10` 与固定 Kotlin WASM 对 13 份语法样例仅 12/13 分类一致。`fun f(x: ) = x` 被原生以缺类型拒绝,WASM 却零恢复,确认漏检。普通语料与显式原生差分测试各 1/1 通过并固定此唯一分歧;提交 `1623cf9` 的 Linux CI 普通回归、单项目 32/32、npm 与完整测试亦通过。清单、`grammar status` 和 `check all` 逐候选已知限制显示反例,项目报告仍不完整。需修 grammar 并补充版本语料、原生优先路由与发行验收;三项父任务不勾选。见[局部证据](../../../tests/acceptance/kotlin-native-differential.md)。 diff --git a/tests/acceptance/java-native-differential.md b/tests/acceptance/java-native-differential.md index d5a23d05..6a74fb47 100644 --- a/tests/acceptance/java-native-differential.md +++ b/tests/acceptance/java-native-differential.md @@ -7,3 +7,5 @@ 独立原生 oracle 使用本机现有 `/usr/bin/javac` 21.0.12.1,对每例运行 `--release 17 -proc:none -Xlint:none`,编译产物写入各自隔离目录;13/13 的原生接受/拒绝与候选 worker 分类一致,且源文件未被修改。常规候选测试 1/1、显式原生差分测试 1/1 通过。Linux CI 执行无需 javac 的普通语料回归;原生差分用例默认忽略,必须显式提供 `CODEGUARD_JAVAC_BIN` 且版本为 21.x 才运行。 此证据只覆盖 Java 17 的 13 个窄范围样例。其它语言版本、语义诊断、真实项目原生检查范围、系统误报/漏报率、`check all` 的 Java 原生优先路由、任务与宿主反馈、MSRV 及发行包精度均未验收;不能把一次差分一致说成完整 lint 或批准交付。 + +[Linux CI 运行](https://github.com/full-stack-plugins/codeguard/actions/runs/37114988502) 对提交 `729978d` 的普通 Java WASM 语料和完整工作区测试均通过;原生 javac 对照仍只在上述本机显式执行,不能以 CI 绿色替代跨平台 javac 差分。 diff --git a/tests/acceptance/kotlin-native-differential.md b/tests/acceptance/kotlin-native-differential.md index 3ec8e351..6d61b721 100644 --- a/tests/acceptance/kotlin-native-differential.md +++ b/tests/acceptance/kotlin-native-differential.md @@ -7,3 +7,5 @@ 分歧样例是 `fun f(x: ) = x`。原生编译器在参数冒号后报告 `Type expected` 与 `Incomplete code`;固定 WASM 返回零恢复节点。这是**漏检**,不能因候选零恢复而宣称语法有效或建议跳过原生工具。常规语料测试明确要求仅该样例保留分歧,显式原生测试再次确认 12/13 和源码未变;两项均各 1/1 通过。`grammar status` 与 `check all` 的 `known_limitations` 现在显示具体漏检,项目样例还断言该候选零恢复、整体交付仍为 `incomplete`。Linux CI 运行无需 `kotlinc` 的固定回归;原生对照默认忽略,需显式提供 `CODEGUARD_KOTLINC_BIN` 且版本为 2.4.10。 后续需要修复或更新固定 grammar,保留原始与派生字节、许可证和来源,再以同一反例及更多语言版本/方言语料复测;还须完成统一原生优先路由、误报/漏报率、资源预算及发行包验收。只更新已知限制不能消除漏检。 + +[Linux CI 运行](https://github.com/full-stack-plugins/codeguard/actions/runs/37115529650) 对提交 `1623cf9` 的 Kotlin 固定回归、项目已知限制、单项目 32/32、npm 包与完整工作区测试均通过。WASM 集成步骤中的候选测试文件 8/8 通过,用时约 174 秒;这不是单个 Kotlin 文件的性能测量。原生 kotlinc 对照仍只在上述本机显式执行。