From 8808478d9725d23d52ef3741812b11da8b6bd36d Mon Sep 17 00:00:00 2001 From: loong10k <20489781+loong10k@users.noreply.github.com> Date: Tue, 29 Sep 2026 01:25:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor(layering):=20=E9=A6=96=E6=89=B9?= =?UTF-8?q?=E4=B8=8B=E7=A7=BB=E5=B7=A5=E5=85=B7=E9=80=82=E9=85=8D=E5=B1=82?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E5=8A=A0=E8=A3=85=E5=88=86=E5=B1=82=E9=97=A8?= =?UTF-8?q?=E7=A6=81=E4=B8=8E=E9=A6=96=E4=B8=AA=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 四 crate 的设计意图是 core(契约)← runtime(执行)← adapters(工具适配) ← cli(组装),但实际倒挂:adapters 已持有 ESLint/Checkstyle/Cargo/Go 的诊断 类型,其身份映射逻辑却住在二进制 crate,使 cli 顶层模块达 145 个、远超 core。 本 PR 不做一次性大搬迁(diff 无法审阅),而是先搬最干净的一族并装上门禁: - 4 个 *_finding_identity(eslint/cargo_build/go/rustdoc,共 332 行)从 codeguard-cli 迁至 codeguard-adapters。四个模块本就直接依赖 adapters 的诊断 类型,跨 crate 后改为 crate 根 re-export;eslint 的 validate_records 由 pub(crate) 提升为 pub。cli 顶层模块 145 → 141,adapters 61 → 65。 - scripts/check_layering.py + layer-baseline.json:把倒挂变成显式债务。 cli 中剩余的 13 个领域形态模块(9 个 *_probe、2 个 *_module_graph、 npm_input_state、tool_identity)逐条登记归属与理由,额度只减不增—— 新增未登记的领域模块会失败,模块下移后基线未同步收紧也会失败。 两个方向都做了负向验证。 - .github/workflows/ci.yml:本仓此前没有任何 CI,995 个测试无自动门禁。 门禁跑 layering check + build + test。 关于固定语料的一个坑:corpus_integrity 会按 sha256 校验来自 codeguard-plugin@9e4adb1 的真实文件,只建空目录不够(实测摘要不匹配), CI 必须把该 commit 检出到兄弟目录。 验证:995 通过 / 0 失败(重构前后一致,无测试增减)。 --- .github/workflows/ci.yml | 37 ++++ .../src/cargo_build_finding_identity.rs | 2 +- .../src/eslint_finding_identity.rs | 4 +- .../src/go_finding_identity.rs | 2 +- crates/codeguard-adapters/src/lib.rs | 8 + .../src/rustdoc_finding_identity.rs | 2 +- crates/codeguard-cli/src/eslint_workbench.rs | 2 +- crates/codeguard-cli/src/go_lint_command.rs | 2 +- crates/codeguard-cli/src/lib.rs | 4 - .../codeguard-cli/src/rust_build_command.rs | 2 +- .../src/rust_comments_command.rs | 2 +- .../src/work_sync/eslint_report.rs | 2 +- .../src/work_sync/rust_build_report.rs | 2 +- .../src/work_sync/rustdoc_report.rs | 2 +- .../tests/eslint_finding_identity.rs | 2 +- .../tests/go_finding_identity.rs | 2 +- crates/codeguard-cli/tests/go_work_sync.rs | 2 +- .../tests/rustdoc_identity_contract.rs | 2 +- layer-baseline.json | 19 +++ scripts/check_layering.py | 158 ++++++++++++++++++ 20 files changed, 238 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/ci.yml rename crates/{codeguard-cli => codeguard-adapters}/src/cargo_build_finding_identity.rs (98%) rename crates/{codeguard-cli => codeguard-adapters}/src/eslint_finding_identity.rs (98%) rename crates/{codeguard-cli => codeguard-adapters}/src/go_finding_identity.rs (98%) rename crates/{codeguard-cli => codeguard-adapters}/src/rustdoc_finding_identity.rs (98%) create mode 100644 layer-baseline.json create mode 100644 scripts/check_layering.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a3c6b331 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: codeguard + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + gate: + # ubuntu-latest 预装稳定版 Rust;Cargo.toml 声明 rust-version = 1.85 / edition = 2024。 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Check out corpus evidence source + # 固定语料按 sha256 校验来自插件仓的真实文件(provenance.source_ref = + # codeguard-plugin@9e4adb1),因此必须把该 commit 检出到兄弟目录; + # 只建空目录不够,会因摘要不匹配而失败。 + run: | + git clone --quiet https://github.com/full-stack-plugins/codeguard-plugin.git ../codeguard-plugin + git -C ../codeguard-plugin checkout --quiet 9e4adb1 + + - name: Verify crate layering debt does not grow + run: python3 scripts/check_layering.py + + - name: Build workspace + run: cargo build --workspace --all-targets + + - name: Run test suite + run: cargo test --workspace --all-targets + + - name: Explain remaining layering debt + if: always() + run: python3 scripts/check_layering.py --explain diff --git a/crates/codeguard-cli/src/cargo_build_finding_identity.rs b/crates/codeguard-adapters/src/cargo_build_finding_identity.rs similarity index 98% rename from crates/codeguard-cli/src/cargo_build_finding_identity.rs rename to crates/codeguard-adapters/src/cargo_build_finding_identity.rs index 6c4c3802..6da60bff 100644 --- a/crates/codeguard-cli/src/cargo_build_finding_identity.rs +++ b/crates/codeguard-adapters/src/cargo_build_finding_identity.rs @@ -1,6 +1,6 @@ //! 编译发现身份绑定原生源码范围;不靠诊断顺序给其它问题续用身份。 -use codeguard_adapters::CargoBuildDiagnostic; +use crate::CargoBuildDiagnostic; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; diff --git a/crates/codeguard-cli/src/eslint_finding_identity.rs b/crates/codeguard-adapters/src/eslint_finding_identity.rs similarity index 98% rename from crates/codeguard-cli/src/eslint_finding_identity.rs rename to crates/codeguard-adapters/src/eslint_finding_identity.rs index 4051871b..67d5a9ae 100644 --- a/crates/codeguard-cli/src/eslint_finding_identity.rs +++ b/crates/codeguard-adapters/src/eslint_finding_identity.rs @@ -1,5 +1,5 @@ //! 原生 ESLint 发现到脱敏任务输入的稳定投影;不赋予策略或报告来源权威。 -use codeguard_adapters::EslintDiagnostic; +use crate::EslintDiagnostic; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use std::{collections::BTreeMap, path::Path}; @@ -103,7 +103,7 @@ pub fn project_eslint_findings( } /// 复核脱敏任务输入的稳定投影;消息摘要只验证形状,不自证原生来源。 -pub(crate) fn validate_records( +pub fn validate_records( relative: &str, absolute: &str, source: &[u8], diff --git a/crates/codeguard-cli/src/go_finding_identity.rs b/crates/codeguard-adapters/src/go_finding_identity.rs similarity index 98% rename from crates/codeguard-cli/src/go_finding_identity.rs rename to crates/codeguard-adapters/src/go_finding_identity.rs index 46b43670..626cf7b3 100644 --- a/crates/codeguard-cli/src/go_finding_identity.rs +++ b/crates/codeguard-adapters/src/go_finding_identity.rs @@ -1,6 +1,6 @@ //! Go 原生诊断的局部稳定身份;行号用于定位,源码字节摘要用于本轮归属复核。 -use codeguard_adapters::GoVetFinding; +use crate::GoVetFinding; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; diff --git a/crates/codeguard-adapters/src/lib.rs b/crates/codeguard-adapters/src/lib.rs index 3a06297c..4409d21d 100644 --- a/crates/codeguard-adapters/src/lib.rs +++ b/crates/codeguard-adapters/src/lib.rs @@ -351,3 +351,11 @@ mod tests { assert!(parse_legacy_registry(r#"{"languages":[]}"#).is_err()); } } +mod cargo_build_finding_identity; +pub use cargo_build_finding_identity::cargo_build_finding_record; +mod eslint_finding_identity; +pub use eslint_finding_identity::{project_eslint_findings, validate_records}; +mod go_finding_identity; +pub use go_finding_identity::go_finding_record; +mod rustdoc_finding_identity; +pub use rustdoc_finding_identity::rustdoc_finding_record; diff --git a/crates/codeguard-cli/src/rustdoc_finding_identity.rs b/crates/codeguard-adapters/src/rustdoc_finding_identity.rs similarity index 98% rename from crates/codeguard-cli/src/rustdoc_finding_identity.rs rename to crates/codeguard-adapters/src/rustdoc_finding_identity.rs index 23c80727..4c876d33 100644 --- a/crates/codeguard-cli/src/rustdoc_finding_identity.rs +++ b/crates/codeguard-adapters/src/rustdoc_finding_identity.rs @@ -1,6 +1,6 @@ //! 文档发现身份绑定原生源码范围;不靠诊断顺序给其它问题续用身份。 -use codeguard_adapters::RustdocFinding; +use crate::RustdocFinding; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; diff --git a/crates/codeguard-cli/src/eslint_workbench.rs b/crates/codeguard-cli/src/eslint_workbench.rs index 8d938039..91803764 100644 --- a/crates/codeguard-cli/src/eslint_workbench.rs +++ b/crates/codeguard-cli/src/eslint_workbench.rs @@ -1,6 +1,6 @@ //! ESLint 局部原生观察到既有修复队列的连接;没有审批或关闭权威。 +use codeguard_adapters::project_eslint_findings; use crate::{ - eslint_finding_identity::project_eslint_findings, eslint_lint_arguments::EslintLintArguments, eslint_probe_request::EslintProbeRequest, eslint_probe_result::EslintProbeResult, diff --git a/crates/codeguard-cli/src/go_lint_command.rs b/crates/codeguard-cli/src/go_lint_command.rs index 5f784a70..6a6d8316 100644 --- a/crates/codeguard-cli/src/go_lint_command.rs +++ b/crates/codeguard-cli/src/go_lint_command.rs @@ -2,7 +2,7 @@ use crate::check_budget::parse_check_timeout; use crate::discovery::discover; -use crate::go_finding_identity::go_finding_record; +use codeguard_adapters::go_finding_record; use crate::work_sync::{save_local_report, sync_local_workspace}; use crate::workspace_refresh::read_workspace_baseline; use codeguard_adapters::{GoVetParseState, legacy_registry, parse_go_vet_json}; diff --git a/crates/codeguard-cli/src/lib.rs b/crates/codeguard-cli/src/lib.rs index 607c5f77..6e0f4ba8 100644 --- a/crates/codeguard-cli/src/lib.rs +++ b/crates/codeguard-cli/src/lib.rs @@ -14,7 +14,6 @@ mod eslint_directory; mod eslint_discovery; #[cfg(unix)] mod eslint_effective_settings; -pub mod eslint_finding_identity; #[cfg(unix)] mod eslint_lint_arguments; #[cfg(unix)] @@ -71,7 +70,6 @@ pub use signed_approval_verifier::verify_signed_approval; pub use signed_prior_approval_input::SignedPriorApprovalInput; pub use verified_approval_snapshot::VerifiedApprovalSnapshot; pub mod cargo_audit_command; -pub mod cargo_build_finding_identity; pub mod cargo_build_repair_brief; #[cfg(unix)] pub mod check_budget; @@ -95,7 +93,6 @@ pub mod false_positive_decision; pub mod git_index_safety; #[cfg(unix)] pub mod git_index_safety_command; -pub mod go_finding_identity; #[cfg(unix)] pub mod go_lint_command; pub mod init_command; @@ -154,7 +151,6 @@ pub mod rust_comments_command; pub(crate) mod rust_cve_task_recheck; #[cfg(unix)] pub mod rust_lint_scan; -pub mod rustdoc_finding_identity; pub mod rustdoc_repair_brief; #[cfg(unix)] pub(crate) mod rustdoc_task_recheck; diff --git a/crates/codeguard-cli/src/rust_build_command.rs b/crates/codeguard-cli/src/rust_build_command.rs index e948fe6e..b58552d5 100644 --- a/crates/codeguard-cli/src/rust_build_command.rs +++ b/crates/codeguard-cli/src/rust_build_command.rs @@ -1,6 +1,6 @@ //! 原生 Cargo/check 类型检查观察;完整项目策略与修复任务仍独立核验。 -use crate::cargo_build_finding_identity::cargo_build_finding_record; +use codeguard_adapters::cargo_build_finding_record; use crate::cargo_build_repair_brief::cargo_build_repair_brief; use crate::check_budget::{ budget_record, parse_check_timeout, resolve_project_default, select_check_timeout, diff --git a/crates/codeguard-cli/src/rust_comments_command.rs b/crates/codeguard-cli/src/rust_comments_command.rs index a598ead6..c03c7086 100644 --- a/crates/codeguard-cli/src/rust_comments_command.rs +++ b/crates/codeguard-cli/src/rust_comments_command.rs @@ -5,7 +5,7 @@ use crate::check_budget::{ }; use crate::discovery::discover; use crate::doctor_scratch::DoctorScratch; -use crate::rustdoc_finding_identity::rustdoc_finding_record; +use codeguard_adapters::rustdoc_finding_record; use crate::rustdoc_repair_brief::rustdoc_repair_brief; use crate::work_sync::{save_local_report, sync_local_workspace}; use crate::workspace_refresh::read_workspace_baseline; diff --git a/crates/codeguard-cli/src/work_sync/eslint_report.rs b/crates/codeguard-cli/src/work_sync/eslint_report.rs index a6f4eaef..e363099d 100644 --- a/crates/codeguard-cli/src/work_sync/eslint_report.rs +++ b/crates/codeguard-cli/src/work_sync/eslint_report.rs @@ -115,7 +115,7 @@ pub(super) fn parse( { return Err("eslint_completion_invalid"); } - if !crate::eslint_finding_identity::validate_records( + if !codeguard_adapters::validate_records( relative, absolute, &bytes["source"], diff --git a/crates/codeguard-cli/src/work_sync/rust_build_report.rs b/crates/codeguard-cli/src/work_sync/rust_build_report.rs index 8147ff0b..5ed40299 100644 --- a/crates/codeguard-cli/src/work_sync/rust_build_report.rs +++ b/crates/codeguard-cli/src/work_sync/rust_build_report.rs @@ -3,7 +3,7 @@ use super::{ BlockerInput, FindingInput, MAX_REPORT_BYTES, ReportInput, read_bounded_file, safe_reason, safe_relative_path, safe_run_id, valid_sha256, }; -use crate::cargo_build_finding_identity::cargo_build_finding_record; +use codeguard_adapters::cargo_build_finding_record; use crate::cargo_build_repair_brief::cargo_build_repair_brief; use codeguard_adapters::CargoBuildDiagnostic; use codeguard_runtime::SourceSnapshot; diff --git a/crates/codeguard-cli/src/work_sync/rustdoc_report.rs b/crates/codeguard-cli/src/work_sync/rustdoc_report.rs index 98f81a2e..28e2c1fc 100644 --- a/crates/codeguard-cli/src/work_sync/rustdoc_report.rs +++ b/crates/codeguard-cli/src/work_sync/rustdoc_report.rs @@ -3,7 +3,7 @@ use super::{ BlockerInput, FindingInput, MAX_REPORT_BYTES, ReportInput, read_bounded_file, safe_reason, safe_relative_path, safe_run_id, valid_sha256, }; -use crate::rustdoc_finding_identity::rustdoc_finding_record; +use codeguard_adapters::rustdoc_finding_record; use crate::rustdoc_repair_brief::rustdoc_repair_brief; use codeguard_adapters::RustdocFinding; use serde_json::{Value, json}; diff --git a/crates/codeguard-cli/tests/eslint_finding_identity.rs b/crates/codeguard-cli/tests/eslint_finding_identity.rs index e315dd66..ede4843b 100644 --- a/crates/codeguard-cli/tests/eslint_finding_identity.rs +++ b/crates/codeguard-cli/tests/eslint_finding_identity.rs @@ -1,5 +1,5 @@ use codeguard_adapters::EslintDiagnostic; -use codeguard_cli::eslint_finding_identity::project_eslint_findings; +use codeguard_adapters::project_eslint_findings; fn finding(line: u32, column: u32) -> EslintDiagnostic { EslintDiagnostic { path: "/fixture/app.js".into(), diff --git a/crates/codeguard-cli/tests/go_finding_identity.rs b/crates/codeguard-cli/tests/go_finding_identity.rs index a7304b1a..f60c0165 100644 --- a/crates/codeguard-cli/tests/go_finding_identity.rs +++ b/crates/codeguard-cli/tests/go_finding_identity.rs @@ -1,5 +1,5 @@ use codeguard_adapters::GoVetFinding; -use codeguard_cli::go_finding_identity::go_finding_record; +use codeguard_adapters::go_finding_record; fn finding(line: u32) -> GoVetFinding { GoVetFinding { diff --git a/crates/codeguard-cli/tests/go_work_sync.rs b/crates/codeguard-cli/tests/go_work_sync.rs index 08384b46..15fd23a6 100644 --- a/crates/codeguard-cli/tests/go_work_sync.rs +++ b/crates/codeguard-cli/tests/go_work_sync.rs @@ -121,7 +121,7 @@ fn real_go_findings_sync_without_duplicate_tasks_or_false_closure() { fn synthetic_report(p: &Project) -> Value { use codeguard_adapters::GoVetFinding; - use codeguard_cli::go_finding_identity::go_finding_record; + use codeguard_adapters::go_finding_record; use sha2::{Digest, Sha256}; let mut report = p.lint(None); report["run_id"] = serde_json::json!("synthetic-go"); diff --git a/crates/codeguard-cli/tests/rustdoc_identity_contract.rs b/crates/codeguard-cli/tests/rustdoc_identity_contract.rs index ef76fa61..cc6634e0 100644 --- a/crates/codeguard-cli/tests/rustdoc_identity_contract.rs +++ b/crates/codeguard-cli/tests/rustdoc_identity_contract.rs @@ -1,5 +1,5 @@ use codeguard_adapters::parse_cargo_rustdoc_json; -use codeguard_cli::rustdoc_finding_identity::rustdoc_finding_record; +use codeguard_adapters::rustdoc_finding_record; use serde_json::json; fn record(source: &[u8], start: u64, end: u64) -> serde_json::Value { diff --git a/layer-baseline.json b/layer-baseline.json new file mode 100644 index 00000000..2e220be0 --- /dev/null +++ b/layer-baseline.json @@ -0,0 +1,19 @@ +{ + "accepted": { + "cargo_module_graph.rs": "Cargo 模块图领域模型;目标 core", + "checkstyle_probe.rs": "包裹 checkstyle 原生调用;目标 adapters,随 checkstyle 适配迁移", + "eslint_probe.rs": "包裹 eslint 原生调用与配置解析;目标 adapters,随 eslint 适配迁移", + "maven_dependency_probe.rs": "包裹 OWASP dependency-check;目标 adapters", + "maven_javadoc_probe.rs": "包裹 Maven Javadoc 生命周期;目标 adapters", + "maven_probe.rs": "包裹 Maven 原生调用;目标 adapters,随 Maven 适配迁移", + "module_graph.rs": "模块图领域模型;目标 core,与 core 既有 task_graph 同类", + "npm_audit_probe.rs": "包裹 npm audit 原生调用;目标 adapters", + "npm_input_state.rs": "npm 输入状态领域模型;目标 core", + "owasp_maven_probe.rs": "包裹 OWASP Maven 报告解析;目标 adapters", + "pmd6_probe.rs": "包裹 PMD6 原生调用;目标 adapters,随 PMD 适配迁移", + "ruff_probe.rs": "包裹 ruff 原生调用与配置观察;目标 adapters,随 ruff 适配迁移", + "tool_identity.rs": "工具身份与版本绑定;目标 adapters,core 已有 native_checker_binding 可对齐" + }, + "note": "codeguard-cli 中的已知分层债务。只减不增:模块下移后必须同步移除本文件登记。", + "version": 1 +} diff --git a/scripts/check_layering.py b/scripts/check_layering.py new file mode 100644 index 00000000..d342dd56 --- /dev/null +++ b/scripts/check_layering.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""分层门禁:阻止领域逻辑继续堆积在 codeguard-cli 这个二进制 crate。 + +背景:codeguard 把执行内核切成四个 crate,设计意图是 + core(契约与领域类型)← runtime(执行)← adapters(工具适配)← cli(组装) +但实际上一度出现倒挂——adapters 已有的工具诊断类型,其身份映射逻辑却住在 cli, +让 cli 膨胀到远大于 core,且无法单独测试领域规则。 + +本门禁不试图一次性搬完(那会让 diff 无法审阅),而是把「倒挂」变成显式债务: +cli 中仍然存在的领域形态模块被登记在 layer-baseline.json 里,数量只减不增。 +新增领域模块而不同步下移,会让本检查失败并指出应该去哪个 crate。 + +用法: + python3 scripts/check_layering.py # 检查 + python3 scripts/check_layering.py --explain # 打印每个模块的归属理由 + python3 scripts/check_layering.py --accept # 登记一个既有模块(带理由) +""" +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +CLI_SRC = ROOT / "crates" / "codeguard-cli" / "src" +BASELINE = ROOT / "layer-baseline.json" + +# 领域形态:这些后缀的模块在 cli 中几乎都属于"工具适配 / 报告构造 / 领域类型", +# 而不是命令行管道。cli 只应保留参数解析、终端输出与编排。 +DOMAIN_SHAPED = ( + "_probe", + "_identity", + "_observation", + "_diagnostic", + "_parsed", + "_config", + "_graph", + "_state", + "_model", +) + +# 每个后缀应归属的 crate,用于给出可执行的迁移建议。 +HOME_FOR = { + "_probe": "codeguard-adapters", + "_identity": "codeguard-adapters", + "_diagnostic": "codeguard-adapters", + "_parsed": "codeguard-adapters", + "_config": "codeguard-adapters", + "_observation": "codeguard-adapters", + "_graph": "codeguard-core", + "_state": "codeguard-core", + "_model": "codeguard-core", +} + +# 命令入口与管道是 cli 的正当职责,不计入债务。 +CLI_ALLOWED = ("_command", "_arguments", "_preview", "_brief", "_feedback", "main.rs", "lib.rs") + + +def domain_modules() -> dict[str, str]: + """返回 {模块名: 应归属 crate},只含 cli 中本应下移的领域模块。""" + found: dict[str, str] = {} + for path in sorted(CLI_SRC.rglob("*.rs")): + rel = path.relative_to(CLI_SRC).as_posix() + if rel in CLI_ALLOWED or any(rel.endswith(a) for a in CLI_ALLOWED): + continue + stem = rel[:-3] + if stem.endswith(CLI_ALLOWED[:-1]) or stem in ("main", "lib"): + continue + for suffix, home in HOME_FOR.items(): + if stem.endswith(suffix): + found[rel] = home + break + return found + + +def load_baseline() -> dict: + if not BASELINE.is_file(): + return {"version": 1, "accepted": {}} + return json.loads(BASELINE.read_text(encoding="utf-8")) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description="codeguard crate 分层门禁") + parser.add_argument("--explain", action="store_true", help="打印每个模块的归属理由") + parser.add_argument("--accept", metavar="NAME", help="把一个既有模块登记为已知债务") + parser.add_argument("--reason", metavar="TEXT", help="配合 --accept 记录理由") + args = parser.parse_args(argv) + + current = domain_modules() + baseline = load_baseline() + accepted: dict = baseline.get("accepted", {}) + + if args.accept: + if args.accept not in current: + print(f"{args.accept} 不在当前 cli 领域模块清单中,无需登记", file=sys.stderr) + return 2 + accepted[args.accept] = args.reason or "未记录理由" + baseline["accepted"] = accepted + BASELINE.write_text( + json.dumps(baseline, ensure_ascii=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + print(f"已登记 {args.accept} → {HOME_FOR.get(suffix_of(args.accept), 'codeguard-core')}") + return 0 + + if args.explain: + for name in sorted(current): + state = "已知债务" if name in accepted else "未登记(会被本检查拒绝)" + print(f"{name:52} → {current[name]:20} [{state}]") + if name in accepted: + print(f"{'':52} 理由:{accepted[name]}") + return 0 + + # 未登记的新领域模块 = 倒挂在扩大 + undeclared = sorted(set(current) - set(accepted)) + + if undeclared: + print("分层门禁失败:以下领域形态模块新增在 codeguard-cli 中。", file=sys.stderr) + print("它们应随工具适配或领域契约下移,而不是留在二进制 crate:\n", file=sys.stderr) + for name in undeclared: + print(f" {name} → 应归属 {current[name]}", file=sys.stderr) + print( + "\n处理方式:把模块移到对应 crate 并更新调用方;" + "若确有理由暂留,用 --accept 显式登记为已知债务。", + file=sys.stderr, + ) + return 1 + + # 已登记模块被移走了:基线应收紧,不留空壳额度 + removed = sorted(set(accepted) - set(current)) + stale = [n for n in removed if n in accepted] + + if stale: + print("分层门禁失败:以下已登记模块已不存在,请从 layer-baseline.json 移除,", file=sys.stderr) + print("以便额度随搬移自动收紧:\n", file=sys.stderr) + for name in stale: + print(f" {name}", file=sys.stderr) + return 1 + + print( + f"Layering OK: codeguard-cli 中已知领域债务 {len(current)} 个" + f"(全部已登记,无新增);core/adapters 未被 CLI 逻辑反向污染。" + ) + return 0 + + +def suffix_of(name: str) -> str: + stem = name[:-3] if name.endswith(".rs") else name + for suffix in HOME_FOR: + if stem.endswith(suffix): + return suffix + return "" + + +if __name__ == "__main__": + raise SystemExit(main())