- 新增 docs/02_Architecture/AR09_Object_Naming_Standard.md(规范性文件,非设计稿): 十节结构 —— 原理 / 判据 / 对象术语表 / 五层命名规范 / 命名模式库 / 如何检查(人工五问 + 6 个机器守卫)/ 如何修复(迁移顺序 + 改名六步法)/ 现状问题登记(逐条带 file:line)/ 反例库 / 修订记录。 核心判断:名称即契约,改名前先查已靠名字建立的协议表 - 02_Architecture/README.md 登记 AR09,并说明其规范性定位(约束新增代码, 与 TOP_CODING_RULES.md 的 G03 配套),与 AR01–AR08 设计稿区别 - 收入本轮讨论与调研文档:对象命名标准化清单、对象标准化与解耦总则、 六层架构与三对象建设重点阶段性复盘、目录结构化迁移说明、 AionUi 对照分析两篇 Co-Authored-By: Claude Code <noreply@anthropic.com>
26 KiB
AR09 对象命名规范(Object Naming Standard)
版本:V1.0 日期:2026-09-17 性质:规范性文件(normative)。「必须 / 禁止」是硬约束,「应当」是默认做法,「建议」可依场景取舍。 与 TOP_CODING_RULES.md 的关系:G03(变量命名锚定)给的是四条底线;本文件是它的展开与可执行化。 冲突时以 TOP_CODING_RULES.md 为准,本文件不得放宽 G03。 适用范围:后端 Go、数据库、API、前端 Vue、注释与文档。 读者:写代码的人 + 执行命名清理的 AI。
一、原理:为什么命名值得单独立一份规范
1.1 命名是唯一零成本的文档
注释会过期,文档会失联,只有名字跟着每一次调用出现在读者眼前。 一个名字读错,读者付出的不是"多看一眼",而是先建立错误的心智模型,再用后续所有代码去纠正它。
1.2 名称即契约
大多数命名失误只是难读,但有一类会静默断链:当某个标识符是靠名字派生、靠名字配对、靠名字跨进程传递时,改名就等于改协议,而编译器与测试都不会拦。
本项目已经存在的这类协议:
| 靠名字建立的协议 | 位置 | 改名后果 |
|---|---|---|
action_key 由 action_title(中文)派生,前后端靠它配对 |
specialistFlow.js 的 findLatestRun |
改文案 → 配对静默失效,UI 永远显示"未执行" |
路由 query 参数 specialist/skill/prompt |
appCatalog.js → SmartAssistantPage.vue |
写读不同名 → 参数被静默丢弃(见 §8.1) |
专员 / 技能的 key |
specialist.key、skill_definition.key、ValidSkillKeys |
改 key → 任务挂载、技能绑定、种子数据全部对不上 |
| 种子数据的 key | seedSpecialistRuleFiles 等 |
拼错 → 启动时只能靠显式校验挡住,否则静默漏种 |
所以:改名前必须先问"这个名字有没有被别人当协议用"。 这是本文件第七节的由来。
1.3 认知债会复利
一个失真的名字不会自己消失。它会被复制(新代码照着写)、被引用(更多地方依赖这个错名)、被解释(文档里加一段"注意这里其实是指……")。 三个月后,改它的成本是今天的十倍。失真名字的唯一低成本修复窗口,是它刚被写下的那一刻。
1.4 命名错误逃得过编译器和测试
拼写错误的变量名编译不过,语义错误的名字编译得过。
const app = specialistCatalog.getByKey(key) 一切正常,只是每个读它的人都要在脑子里纠正一次。
这类错误没有任何自动化工具能替你发现——除非专门写一个(见第六节)。
1.5 所以要在"新增"那道口子上拦
追改历史代码性价比低。命名规范的绝大部分价值,发生在新代码被写下的那五秒钟:选名字时对照一次术语表。
二、判据:一个名字好不好,用这五条量
| 判据 | 含义 | 反例 |
|---|---|---|
| 准确性 | 名字指向的对象,和它实际装的东西是同一个 | businessApps 装的是专员 |
| 唯一性 | 一个概念全项目只有一个名字;一个名字只指一个概念 | contract-review 既是专员 key 又是技能 key |
| 一致性 | 同类东西的写法统一(ID 就永远 ID) |
UserID 与 mediaId 并存 |
| 可检索性 | 能被 grep 精确找到 | id、data、res、info |
| 稳定性 | 不会因为阶段变化而变成谎言 | newUser、tempData、userList2 |
冲突时的优先级
外部标准 > 一致性 > 准确性 > 书写简短
- 外部标准最高:第三方规范定义的字段名(金蝶的
FCustId、OOXML 的sldId、HTTP 头)必须按字符照抄,不许"美化"。美化等于自建一层翻译,且翻译层迟早漏项。 - 一致性高于准确性:
ID和Id哪个更好看无关紧要,全项目只能有一个。两种写法并存时,读代码的人每次都要停下确认"这是同一个东西吗"。 - 准确性高于简短:宁可名字长一点,也不要让人误判对象边界。
三、对象术语表
3.1 正式术语(必须使用)
| 对象 | 正式名 | 代码落点 | 说明 |
|---|---|---|---|
| 专员 | specialist |
model/specialist.go |
可挂载到任务的数字员工 |
| 技能 | skill |
model/skill_definition.go |
任务级能力定义 |
| 应用 | app |
model/app_definition.go |
长程任务运行壳 |
| 连接器 | connector |
internal/connector/* |
外部系统接入 |
| 动作 | action |
model/action_definition.go |
技能调用的底层执行单元 |
| 任务 | task |
model/worker_task.go |
工作实例容器 |
3.2 兼容术语(限制使用,禁止扩散)
| 历史词 | 现状 | 允许出现的位置 | 禁止 |
|---|---|---|---|
role |
旧心智残留 | 无。已无正式对象叫 role | 不得用于新变量、新文件名、新字段 |
assistant |
只指"默认通用助手"这一件事 | assistant.js(默认聊天接口)、smart-assistant(默认技能 key)、general-assistant(默认专员 key) |
不得用来指代专员、技能或对象类型 |
capability |
泛能力总称 | 仅限口语与文档泛指 | 不得充当对象级文件名或字段名 |
businessApps |
历史静态变量,装的是专员 | 现有代码 | 禁止新增引用;建议整块删除(见 §8) |
availableSkills |
静态 fallback 技能源 | 现有代码 | 禁止新增引用;建议改名 staticSkillCatalog |
worker |
任务执行子系统前缀 | worker_task / worker_run / worker_artifact / /api/worker/* / workerRuntime.js |
待拍板(见 §3.4) |
3.3 共名冲突登记
同一个字符串同时是两个对象的标识符 —— 这是既成事实,不强行改,但要靠注释和测试守住,并且不再新增:
| 字符串 | 身份一 | 身份二 |
|---|---|---|
contract-review |
专员 key | 技能 key |
report-generation |
专员 key | 技能 key |
处理要求:
model/skill_keys.go必须保留说明注释(已有)。- 任何"按 key 查对象"的代码,必须写明查的是哪一类,不允许出现
getByKey(key)这种不区分对象类型的调用。 skill_keys_test.go那种"前后端 key 集合双向对齐"的测试必须保留 —— 它是这两个身份不互相污染的唯一保障。
3.4 待拍板:worker 这个命名空间
现状:/api/worker/tasks、model/worker_task.go、worker_run.go、worker_artifact.go、api/worker.js、store/workerRuntime.js。
worker 不在正式术语表里,但已经形成一整套一致的前缀。两个选择:
- A. 收编为正式术语:定义为"任务执行子系统",写进 §3.1。成本为零,立刻合法。
- B. 重命名:把
worker_*收敛为task_*。语义更准,但要动表名、API 路径、前端模块,成本高。
建议 A。理由:它已经自洽,且不与任何人抢名字;真正会误导的是 §8 里那些"名实相反"的(businessApps 装专员、capability 装 skill),而不是一个自洽的子系统前缀。
四、分层规范
4.1 后端 Go
| 项 | 规范 | 禁止 |
|---|---|---|
| 类型名 | CamelCase,对象名 + 用途后缀:Specialist、SkillDefinition、WorkerTask |
Data、Info、Manager、Helper 这类空词 |
| 字段名 | CamelCase |
业务字段用 Id/Data/Res 这类模糊名 |
ID 写法 |
一律 ID:UserID、TaskID、RouteID、AIRouteID |
UserId、mediaId、sourceId |
| json tag | 一律 snake_case:json:"specialist_key" |
json:"skillKey"、json:"createdAt" |
| 外部标准字段 | 原样保留,并在字段注释里写明来源系统 | 美化、改大小写、加前缀 |
| 常量 | CamelCase,同族常量同前缀 |
裸数字、魔法字符串 |
| 接收器 | 与类型同义的单字母或短名(s、req) |
无意义的 this、obj |
允许的例外(白名单,只有这些):
- 第三方 API 字段:
FCustId、FUseOrgId、FStockId等 ERP 字段 - 文档格式规范属性:OOXML 的
sldId、styleId
例外必须在字段旁注明来源,否则下一个人会以为是漂移。
4.2 数据库
| 项 | 规范 |
|---|---|
| 表名 | 单数 snake_case:specialist、skill_definition、worker_task |
| 列名 | snake_case,与 Go 字段的 json tag 一致 |
| 外键 | <对象>_id:specialist_id、task_id |
| 时间 | <动词>_at:created_at、finished_at |
| JSON 列 | 后缀 _json,并在字段注释写明结构(如 allowed_skills 存的是字符串数组) |
| 布尔列 | 语义正向:enabled、pinned;禁止 not_xxx |
| 改名 | AutoMigrate 不会改列名,必须手写迁移(P06.1) |
4.3 API
| 项 | 规范 | 现状 |
|---|---|---|
| 路径 | 复数资源:/api/specialists、/api/skills、/api/apps |
✅ 已一致,保持不回退 |
| 路径参数 | snake_case:/api/knowledge/audit/{source_id} |
❌ 现在是 {sourceId}、{recordId} |
| query 参数 | snake_case,且跨层同名 | ❌ 见 §8.1 |
| 入口协议 | 对象入口 query 必须定义在一处常量,写读都引它 | ❌ 现在两边各写各的 |
入口协议强制要求:任何"从 A 页面带参数打开 B 页面"的协议,参数名必须是共享常量,不允许两边各写字符串字面量。这是 §8.1 事故的直接教训。
4.4 前端
| 项 | 规范 |
|---|---|
| 文件名 | store:<对象>Catalog.js(如 specialistCatalog.js / skillCatalog.js / appCatalog.js);API:<对象>.js(如 specialist.js / worker.js) |
| catalog store 模板 | normalize<Object> → hydrate → getByKey / getByPath(specialistCatalog.js 为标准范本) |
| computed | current<Object>Presentation,对象名必须准确 |
| 局部变量 | 必须与对象一致。const app = specialistCatalog.getByKey() 是禁止的 |
| 布尔 | is<X> / has<X> / can<X>,禁止否定式 |
| 容器 | 单个 item,集合 items,映射 <name>Map,列表 <name>List |
4.5 注释与文档
- 历史兼容字段必须在注释里写明"历史兼容 / 待迁移",否则会被当成正式命名复制。
- 文档里的文件引用用相对路径,禁止
file:///home/<某人的用户名>/...这类绝对路径 —— 别人 clone 下来全是死链(违反 G18)。
五、命名模式库(做法)
5.1 后缀表
| 后缀 | 含义 | 例 |
|---|---|---|
_id |
主键 | task_id |
_key |
业务标识(字符串,可读) | specialist_key、action_key |
_at |
时间点 | finished_at |
_count |
计数 | retry_count |
_json |
存 JSON 的文本列 | allowed_skills、input_json |
_ms / _bytes |
单位必须进名字 | timeout_ms、size_bytes |
单位不写进名字是隐性 bug 源:timeout 是秒还是毫秒?读的人只能翻实现。
5.2 布尔
is / has / can / should + 正向语义。禁止 flag、status(名词当布尔用)、notXxx(双重否定)。
5.3 容器
item(单) / items(集) / xxxMap(映射) / xxxList(有序)。让读者不点进定义就知道能不能 .map()。
5.4 函数动词表
同一件事只用一个动词,禁止同义混用:
| 动词 | 职责 | 禁止替代 |
|---|---|---|
normalize<X> |
把外部来的数据整理成内部统一形状 | format / convert / adapt |
hydrate |
把目录/缓存数据装进运行时 | load / init |
resolve<X> |
按规则算出一个结果(可能多来源) | get / find |
load<X> |
从存储读 | fetch / read |
build<X> |
组装(无 IO) | create / make |
ensure<X> |
没有就补上(幂等) | check / init |
apply<X> |
把某份数据/规则作用到目标上(幂等) | update / set |
parse<X> |
字符串 → 结构 | decode / read |
5.5 禁止词表
作为业务标识符禁止使用(框架/标准库要求时除外):
id、data、info、res、req(局部除外)、tmp、temp、obj、item(无上下文时)、list(无前缀时)、util、common、helper、manager、handler(无对象前缀时)、flag、value
理由:不是难看,是搜不到。grep 出几百条结果等于没有结果(G03 的最小长度约束就是这个意思)。
5.6 缩写表
只有下表内的缩写允许使用,其余一律写全:
| 允许 | 全称 |
|---|---|
ID |
identifier |
URL |
uniform resource locator |
API |
application programming interface |
JSON |
JavaScript object notation |
AI |
artificial intelligence |
OCR |
optical character recognition |
PPT / PDF |
文件格式 |
req / res |
仅限 HTTP 处理函数的局部变量 |
禁止:usr、mgr、svc、num、str、val。(HTTP 处理函数的局部 req / res、Go 的 ctx、局部配置 cfg 属公认短名,不算违规。)
六、如何检查
6.1 人工五问(写名字时问自己,review 时问作者)
- 这个名字指的是哪个对象?能一句话说出它是 specialist / skill / app / connector / action / task 中的哪一个吗?
- 换个人读,会不会理解成另一个对象?
- 全项目 grep 一下,是不是只有这一种写法?
- 改它会不会断掉靠名字派生的协议(§1.2 的表)?
- 它是外部标准吗?是 → 按字符照抄,一个字都不许动。
6.2 机器守卫(建议落成一个 Go test,与 skill_keys_test.go 同一位置)
已有先例:model/skill_keys_test.go 读 frontend/src/config/workbench.js,把前端技能 key 与后端 ValidSkillKeys(22 个)双向对齐。
命名守卫照这个模式写,每条都要带白名单,否则会被误报淹没:
| # | 查什么 | 正则 | 白名单 |
|---|---|---|---|
| A | Go 里 Id 写法 |
\b[A-Za-z]+Id\b |
ERP 字段(FCustId 等)、OOXML(sldId、styleId) |
| B | camelCase json tag | json:"[a-z]+[A-Z] |
无 |
| C | camelCase 路径参数 | \{[a-z]+[A-Z][A-Za-z]*\} |
无 |
| D | 禁用词当业务标识符 | 按 §5.5 词表 | 框架约定(req 局部) |
| E | 前端 skill key ↔ 后端 ValidSkillKeys |
已有的 skill_keys_test.go |
— |
| F | 入口协议写读对称 | 见下 | — |
守卫 F 的做法(最重要,也最难自动化):
入口协议不要靠扫描源码配对,而是把参数名提成共享常量(如 frontend/src/config/objectEntry.js 导出 ENTRY_PARAMS = { specialist: 'app_specialist', ... }),写端读端都引它。
这样 F 就从"看不见的约定"变成了"编译器能查的引用",守卫 F 也就不需要了 —— 能用结构消除的检查,不要用扫描去补。
6.3 跨层对齐检查
任何"同一份清单在前后端各写一遍"的东西,都必须有双向对齐测试:
- 技能 key:前端
availableSkills↔ 后端ValidSkillKeys✅ 已有 - 专员 key:前端静态目录 ↔ 后端种子数据 ⬜ 建议补
- 入口协议参数名:⬜ 建议补(提到共享常量后自然消解)
6.4 检查结果的严重性分级
| 级别 | 判据 | 处理 |
|---|---|---|
| P0 | 名字不一致导致功能已断(写读不同名、配对失效) | 当 bug 修,立即 |
| P1 | 跨层漂移,会被新代码复制(json tag、路径参数、失真局部变量) | 本迭代内 |
| P2 | 名字带旧词但语义尚可推断(RoleKind、currentRolePresentation) |
排期做,别急 |
| P3 | 死代码 / 无人引用的历史变量 | 先判生死,再决定改名还是删除 |
注意 P3 的陷阱:给一份没人读的静态数据改名,等于给它续命。 先确认引用数,是 0 就删(见 §8.9)。
七、如何修复
7.1 迁移顺序:由内到外,最后才碰数据库
① 局部变量
② 前端协议 / 入口参数
③ 文件名
④ 字段名 / 数据库列名
理由:
- ① 影响面最小、可读性收益最直接、无跨进程风险。
- ② 优先于 ③ —— 协议不统一会持续产生新的分叉,不先堵住,你改完还会被新代码重新搞乱。
- ④ 放最后:牵动数据库,而 SQLite 的列改名
AutoMigrate不管,必须手写迁移,风险最高、回滚最难(G05:大规模操作前先备份)。
7.2 改名六步法
- 定名:对照 §3 术语表 + §5 模式库,写下新名字,并回答 §6.1 的五问。
- 查引用:
grep -rn "<旧名>",范围必须包含- 源码 / 模板 / 样式
- 字符串字面量(路由、query key、事件名、localStorage key)
- JSON tag、SQL 语句、迁移脚本
- 配置、种子数据、测试、e2e、文档、注释
- 判协议:这个旧名是否出现在 §1.2 那张表里?是 → 这不是改名,是改协议,写下来通知所有相关方(含前端)。
- 一次改完:不留双写法。兼容层是唯一例外,且必须带删除条件("等 X 上线后删除"),否则兼容层会永久化。
- 验证(按 G04,不靠感觉):
go build ./...+go test ./...exit 0npm run buildexit 0- 真起 dev server,在浏览器里走一遍受影响路径 —— 编译通过不代表功能没断(§8.1 就是编译全绿但功能已断)
- 提交:一次提交只装一件事(G14);提交信息写为什么改名,不只写改了什么。
7.3 会静默断链的改名(动手前必读)
| 改名对象 | 为什么危险 | 必须同时检查 |
|---|---|---|
action_key / action_title |
key 由中文标题派生,前后端靠名字配对 | specialistFlow.js 的 findLatestRun;任何改文案的地方 |
| 路由 query 参数 | 写端读端是两处独立字面量,编译不报错 | 全链路 grep,或改成共享常量 |
专员 / 技能的 key |
任务挂载、技能绑定、审计日志全按 key 存 | worker_task.specialist_key、ai_call_log.specialist_key、种子数据 |
| 种子数据的 key | 拼错或改名 → 与库中现有记录对不上 | 启动时校验(applySpecialistRuleFiles 已有) |
| 外键列名 | 老库有数据,改列名要迁移 | 手写迁移 + 备份 + 副本验证(P06.1/P06.7) |
判断口诀:这个名字如果在两个不同的进程/两次运行之间传递过,它就是协议,不是名字。
7.4 各级的具体做法
① 局部变量 直接改,改完读一遍上下文确认没有同名遮蔽(shadowing)。
② 前端协议 / 入口参数 不要只是"改一致",而是提成共享常量:
// frontend/src/config/objectEntry.js
export const ENTRY_PARAMS = {
specialist: 'app_specialist',
skill: 'app_skill',
prompt: 'app_prompt',
}
写端 params.set(ENTRY_PARAMS.specialist, ...),读端 route.query[ENTRY_PARAMS.specialist]。
这样下一个参数不会再各写各的。
③ 文件名
- 只改名:
git mv,保持历史可追溯。 - 一文件两职责:先拆文件,再改名,同时改注册点(
router.go等)。只拆一半会留下一个名实仍不符的残文件,比不改更糟。
④ 字段名 / 列名
- 先备份(
data/backups/内置VACUUM INTO,D24)。 - 在库副本上验证新列/新列名能被正确读写(P06.1)。
- 写一次性迁移;老列在确认无引用前不删。
- 同步改 json tag、前端读取点、文档字段表。
- 改完立即跑一遍 P06.2 的做法:探测脚本用完即删,别留成断言"升级前状态"的测试。
7.5 回滚与提交
- 一次提交一件事:改名与功能改动不要混在一个提交里(否则回滚会带走功能)。
- 改名列必须写明"旧名 → 新名",方便
git log --follow与后来人搜索。 - 如果改名波及数据库,提交信息里写明迁移步骤与验证结果。
八、本项目现状(2026-09-17 实测)
以下每条都在代码里核实过,标了行号。级别按 §6.4。
8.1 【P0|功能已断】对象入口协议写读不对称
- 写端:appCatalog.js:140-142 拼
specialist/skill/prompt - 读端:
SmartAssistantPage.vue:603、604、619读app_specialist/app_skill/app_prompt - 全仓库核实:没有任何地方读不带前缀的,也没有任何地方写带前缀的
- 后果:从能力目录点应用进工作台,预置的专员 / 技能 / 提示词三个参数全部被静默丢弃,不报错。用户看到的是"点进去还是空的"
- 注意:这是 bug,不是风格问题。按 §7.4② 提成共享常量一起修
8.2 【P1】camelCase json tag
my_app_center.go:36-41 六处:installState、skillKey、createdAt、iconText、coverTone、isCustomApp,是全站 snake_case 体系里仅有的例外,且集中在同一个结构体。
→ 改 snake_case,前端同步。
8.3 【P1】URL 路径参数 camelCase
api/knowledge.go:97 的 {sourceId}、api/exam.go:975 的 {recordId}。
→ 改 snake_case({source_id}、{record_id}),同步前端调用处。
8.4 【P1】局部变量失真
SmartAssistantPage.vue:314:const app = specialistCatalog.getByKey(key) —— 拿到的是专员,却叫 app。
注:它所在的 computed 名字是对的(currentSpecialistPresentation,kind: 'specialist'),失真仅限这一个局部变量。
→ 改 specialist。P1 原因:它会被后续代码照抄。
8.5 【P2】role 心智残留
SmartAssistantPage.vue:354 的 currentRolePresentation,把 assistant / specialist / skill 三类收在一个 role 概念下。
→ 收敛为 currentObjectPresentation,或按 §3.1 拆成三份。
8.6 【P2】RoleKind 字段名带旧词
model/skill_definition.go:13:RoleKind,注释 assistant / specialist / skill。
- 澄清:这个字段本身不是垃圾,语义是"这条定义属于哪类对象"(
skill_definition表同时装 assistant / specialist / skill 三类记录)。问题只在名字里的role让人误读成"角色"。 - 改名方向:
object_kind(准确)。 - 成本提醒:改字段名要同时动 ① 数据库列
role_kind② json tag ③ 前端读取点。SQLite 改列名AutoMigrate不管,得手写迁移。 - 建议:默认方案是保留字段名,在注释与文档里标注语义;真要改,按 §7.4④ 走完整流程,不要顺手改。
8.7 【P2】capability_definition.go 名实不符
internal/api/capability_definition.go 实际装了两套东西:skillDefinitionReq 5 个 handler + actionDefinitionReq 5 个 handler(共 15 个函数)。
- 不能只改名为
skill_definition.go—— action 的 handler 还在里面,改完名实仍然不符。 - 两个正确方案:① 拆成
skill_definition.go+action_definition.go,同时改router.go注册;② 不拆,改名skill_and_action_definition.go。 - 要害:
capability不是正式对象,让它当文件名会持续制造一个不存在的一级概念。
8.8 【P1】worker 命名空间待拍板
/api/worker/tasks、worker_task.go、worker_run.go、worker_artifact.go、api/worker.js、store/workerRuntime.js。
→ 见 §3.4,建议收编为正式术语,成本为零。
8.9 【P3】businessApps 疑似死代码 —— 建议删,不建议改名
- 定义:
config/workbench.js:1434,内容是专员目录(第一条即contract-review合同审查专员,带tier/workerType/roleCard),名字与内容相反 - 引用情况:模块外零引用;仅
workbench.js内部 6 处自用(2232按 legacy 路由查、2236按 tier 筛、2240数量、2244可升级数、2252-2253dw/adw 统计) - 建议:先确认那几个统计入口是否还有页面在用 → 没有就整块删除;有就随使用者一起迁到
specialistCatalog - 不要改名成
staticSpecialistCatalog—— 那等于给一份没人读的静态副本续命
8.10 【P3】availableSkills 静态 fallback
config/workbench.js:105,22 条技能定义,被 4 处用于兜底:store/skillCatalog.js:4、components/chat/PlusMenu.vue:183、config/projectTemplates.js:19、components/chat/CurrentObjectChip.vue:55。
→ 确认 skillCatalog 覆盖完整后,改名 staticSkillCatalog(语义准确),或一并删除。
8.11 【已守住,勿回退】
- 后端对象 API 命名已清楚:
/api/specialists、/api/skills、/api/apps、/api/actions(router.go:43-50) - 前端 catalog store 命名已规范:
specialistCatalog.js(normalizeSpecialist/getByKey/getByPath)是标准范本 - 技能 key 前后端对齐测试已有(
model/skill_keys_test.go,22 个 key)
九、反例库(真实事故)
| 症状 | 根因 | 正确做法 |
|---|---|---|
| 从应用目录点进去,工作台是空的(专员/技能/提示词都没带上) | 写端读端各写一套 query 字面量,编译不报错 | 参数名提成共享常量(§7.4②) |
const app = specialistCatalog.getByKey() 读起来像在查应用 |
局部变量照抄了旧的"应用"心智 | 变量名必须与对象一致 |
businessApps 里装的全是专员 |
历史命名未随对象迁移 | 名实不符时优先删,其次改名 |
capability_definition.go 里装的是 skill 和 action |
中间态概念被固化成了文件名 | 让已经消失的概念从文件名里消失 |
RoleKind 字段名比模型名旧一代 |
模型改名了,字段没跟上 | 一套迁移要改到底,不能只改外层 |
| 改了任务步骤的中文文案,UI 就永远显示"未执行" | action_key 由中文标题派生,名字成了协议 |
能不派生就不派生;非派生不可就加配对检查 |
contract-review 查出来两条不同对象的记录 |
同一字符串被两个对象共用 | 共名要登记 + 用测试守住 |
差点把 ERP 的 FCustId "美化"成 FCustId→CustID |
误以为一致性高于一切 | 外部标准优先级最高,照抄 |
文档里写 file:///home/<用户名>/... 链接 |
本机可点,他人全断 | 文档引用一律相对路径 |
十、修订记录
| 版本 | 日期 | 变更 |
|---|---|---|
| V1.0 | 2026-09-17 | 首版。原理 / 判据 / 术语表 / 分层规范 / 模式库 / 检查 / 修复 / 现状 / 反例库 |
注:本文件是命名问题的最高依据,与 TOP_CODING_RULES.md 配套使用。术语表(§3)变更属于架构级决策,改前先更新 PROJECT_STATE.md 的已拍板决策表。