Repository navigation
refactor(layering): 首批下移工具适配层,并加装分层门禁与首个 CI #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a PR changes Useful? React with 👍 / 👎. |
||
|
|
||
| - name: Run test suite | ||
| run: cargo test --workspace --all-targets | ||
|
|
||
| - name: Explain remaining layering debt | ||
| if: always() | ||
| run: python3 scripts/check_layering.py --explain | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <name> # 登记一个既有模块(带理由) | ||
| """ | ||
| 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): | ||
|
Comment on lines
+68
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a conventional directory module such as Useful? React with 👍 / 👎. |
||
| 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()) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The repository declares Rust 1.85 as its minimum, but this job uses whichever newer stable toolchain happens to be preinstalled on
ubuntu-latest. A change using an API or language feature introduced after 1.85 will therefore pass CI while failing for users on the supported minimum compiler; install and select Rust 1.85 for at least one build/test job.Useful? React with 👍 / 👎.