goagent-kernel
介绍
设计初衷
怎么用
能力
坑速查
  • 本站 llms.txt
  • oekit llms.txt
  • oagentkit llms.txt
  • 包清单与机器事实
  • 门禁体系
  • 决策记录(ADR)
  • 版本记录
GitHub
介绍
设计初衷
怎么用
能力
坑速查
  • 本站 llms.txt
  • oekit llms.txt
  • oagentkit llms.txt
  • 包清单与机器事实
  • 门禁体系
  • 决策记录(ADR)
  • 版本记录
GitHub
  • 决策记录

    • ADR-0001 · 领域知识红线
    • ADR-0002 · 门禁执行体可被宿主复用
    • ADR-0003 · 与上游矩阵的分层
    • ADR-0004 · 执行沙箱:入口唯一 + 档位登记
    • ADR-0005 · 门面包不可变
    • ADR-0006 · 通用化判据
项内容
状态已采纳(Accepted)
日期2026-10-19
背景门禁执行体此前锁在 package main 里,宿主仓够不着——每个宿主要么各写一遍判据(各判一套,漂移到没人发现为止),要么干脆不写(policy 里的依赖拓扑表变成死条文:躺在文件里、看起来有人维护过,实际一条都不判)。
决策把执行体下沉为可导入包 archcheck/(cmd/archcheck 退化为 CLI 装配),对外 API 三件套:Load / Run / WriteReport + Result.HasBlocking();判据与声明面分离(policy 是声明面,本包是执行面),宿主只写自己的声明面即可复用全部判定。
后果宿主不必复制判据实现;判据的量纲与前提对齐(god object 判据改比「实质方法数」);泛型实例化各自成条;policy 自校验强制「规则须有执行体」。
依据archcheck/ 包(facade.go / deps.go / godobject.go / selfcheck.go);判据推导与实测数据见 issue #13 / #14。

> 一句话结论:机制面若要跨仓复用,就不能锁在 package main 里——判据与执行体分离,宿主只写声明面。


> 本文件正文原样搬自 README「门禁执行体可被宿主复用(2026-10-19)」一节。

门禁执行体可被宿主复用(2026-10-19)

执行体此前锁在 package main 里,宿主仓够不着——于是每个宿主要么各写一遍判据 (各判一套,漂移到没人发现为止),要么干脆不写(policy 里的依赖拓扑表变成死条文: 躺在文件里、看起来有人维护过,实际一条都不判)。现在它是可导入的包:

import (
	"path/filepath"

	"github.com/yi-nology/goagent-kernel/archcheck"
)

// ① 定位仓根:从当前目录向上找 architecture-policy.yaml
//    (宿主仓把自己的 policy 放仓根,本仓同样如此——**不在 docs/ 下**)
root, err := archcheck.FindRoot(".")
if err != nil {
    return err
}

// ② 加载声明面。policy 解析失败 = 门禁给不出结论,必须响亮失败
pol, err := archcheck.Load(filepath.Join(root, "architecture-policy.yaml"))
if err != nil {
    return err
}

// ③ 跑判定
res, err := archcheck.Run(root, pol)
if err != nil {
    return err
}

// ④ 出报告 + ⑤ 判阻断
archcheck.WriteReport(os.Stderr, res, archcheck.ReportOptions{Full: false})
if res.HasBlocking() {
    os.Exit(1)
}

> archcheck.PolicyPath(root, pol) 不是加载路径——它返回的是报告里显示用的 > 相对路径(缺省即 "architecture-policy.yaml")。拿它去 Load 会读到错的地方。

宿主复用时要知道的六条:

  1. policy 的 modules 段要求每个 root 指向真实存在的包目录——陈旧的 root 会被 policy 自校验判红(这正是「机制包迁出当天即红」的落点)。

  2. 跨仓依赖用 ext:<import 路径前缀> 登记,且该前缀必须落在 dependency_direction.ext_prefixes 声明的受管包面之内。两层结构的理由见 archcheck/deps.go 包头:若「哪些外部包受管」也只由各模块自己声明,那么最后 一个声明它的模块撤掉声明时,整片包面随之消失,那条边就静默失管了。

  3. 未登记包判定按「包目录」粒度,不是顶层目录——宿主的包根通常是嵌套的 (internal/xxx),按顶层目录判会把整棵 internal/ 误报成一个未登记的包。

  4. 判据与实现分离:policy 是声明,本包是执行体。宿主可以只用自己的声明面 (只写 modules 段 + dependency_direction 段),复用本包的全部判定。

  5. 判据面只有两种(import 路径 / 源码文本),没有 forbid_call_regex / allow_paths 这类字段——常见诉求用现有字段表达:

    诉求表达方式
    某个第三方包只允许经某一个适配层引入(路径白名单)forbid_import_regex + scope_exclude
    某目录禁止裸 fetch(,但 api 客户端目录与两个 SSE 钩子除外forbid_regex(匹配调用形态)+ scope_exclude

    写了不存在的字段会被 YAML 静默丢弃(那条规则于是变成「声明还在、一条都判 不了」),policy 自校验会把它报成「规则无执行体」并给出可执行的下一步。

  6. max_methods_per_type 比的是「实质方法数」,不是方法总数(v0.6.5 起)。 类型退化成 façade(实现下沉到子包、自己只做转发)之后,方法数 ≈ 公共 API 表面规模,不是复杂度集中度——拿它判 god object 是「把正确的工作判成错的」, 逼维护者在「撤销一次正确的重构」与「调高阈值(等于放弃判据)」之间二选一。 故「转发壳」(方法体只有一条转发语句、且剥掉注释与空白后长度 ≤ forwarding_shell_max_chars,缺省 140)不计入阈值;报告里同时给出方法总数 与壳数,原始信号不丢。

    policy 写法含义
    不写 forwarding_shell_max_chars用缺省 140(老 policy 无需改动即可享受修正)
    forwarding_shell_max_chars: 0关闭该区分,所有方法都算实质方法,判定逐字退回旧行为
    forwarding_shell_max_chars: <N>自定义刻度(方法体有效长度 ≤ N 才算壳)
  7. 泛型实例化各自成条(v0.9.0 起)。按 Go 规范,按实例特化的方法 (func (m *Manager[int]) C())只属于 Manager[int],不是 Manager 的方法。 剥掉实参会把它并进基类型——两个实例化各挂 20 个特化方法就被判成 「Manager 有 40 个方法」,而它其实不胖(方向是虚高 ⇒ 假红)。 故聚合键保留实参:报告里出现 pkg.Manager[int] / pkg.Manager[string], 一眼看出是哪个实例化胖。 非泛型类型的键逐字不变(pkg.Report 仍是 pkg.Report)——这条必须成立, 否则改动会波及所有宿主,而不只是泛型用户(宿主仓实测无泛型接收者,零影响)。

    判据走 go/ast(不是逐行正则)——「一个函数体是不是只有一条转发语句」是结构性 问题,只有语法树能判。实现见 archcheck/godobject.go,判据的由来与实测数据见 issue #13。

Prev
ADR-0001 · 领域知识红线
Next
ADR-0003 · 与上游矩阵的分层