- 将 wechat_official_account 重命名为 weixin_public_account,符合中文命名规范 - 新增 DOCX 文档生成技能、聊天历史、请求 ID 中间件 - 增强工作流、热点服务、文章服务等模块功能 - 前端同步重命名组件和 API - 新增架构文档 AR13/AR14、专员文档更新 - 补充测试用例(seed_specialists_test, db_migration_test) Co-Authored-AI: yes
767 lines
49 KiB
Markdown
767 lines
49 KiB
Markdown
# AR09 对象命名规范(Object Naming Standard)
|
||
|
||
> **版本**:V1.1
|
||
> **日期**:2026-09-18
|
||
> **性质**:规范性文件(normative)。「必须 / 禁止」是硬约束,「应当」是默认做法,「建议」可依场景取舍。
|
||
> **与 TOP_CODING_RULES.md 的关系**:G03(命名锚定,10 条)给的是硬约束底线;本文件是它的展开与可执行化。
|
||
> 其中 **§5.7 前缀规范 与 G03 第 5-10 条互为详略** —— 准则记硬约束,本文件记判据、检查与修复流程。
|
||
> **冲突时以 TOP_CODING_RULES.md 为准**,本文件不得放宽 G03。
|
||
> **适用范围**:后端 Go、数据库、API、前端 Vue、注释与文档。
|
||
> **读者**:写代码的人 + 执行命名清理的 AI。
|
||
|
||
---
|
||
|
||
## 一、原理:为什么命名值得单独立一份规范
|
||
|
||
### 1.1 命名是唯一零成本的文档
|
||
|
||
注释会过期,文档会失联,只有名字跟着每一次调用出现在读者眼前。
|
||
一个名字读错,读者就会**先建立错误的心智模型,再用后续所有代码去纠正它**。
|
||
|
||
### 1.2 名称即契约
|
||
|
||
大多数命名失误只是难读,但有一类会**静默断链**:当某个标识符是靠名字派生、靠名字配对、靠名字跨进程传递时,改名就等于改协议,而编译器与测试都不会拦。
|
||
|
||
本项目已经存在的这类协议:
|
||
|
||
| 靠名字建立的协议 | 位置 | 改名后果 |
|
||
|---|---|---|
|
||
| `action_key` 由 `action_title`(中文)派生,前后端靠它配对 | `specialistFlow.js` 的 `findLatestRun` | 改文案 → 配对静默失效,UI 永远显示"未执行" |
|
||
| 路由 query 参数 `xapp_specialist` / `xapp_skill` / `xapp_prompt` | `xappCatalog.js` → `SmartAssistantPage.vue` | 写读不同名 → 参数被静默丢弃(2026-09-17 真实发生过,见 §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` |
|
||
|
||
### 冲突时的优先级
|
||
|
||
```text
|
||
外部标准 > 一致性 > 准确性 > 书写简短
|
||
```
|
||
|
||
- **外部标准最高**:第三方规范定义的字段名(金蝶的 `FCustId`、OOXML 的 `sldId`、HTTP 头)**必须按字符照抄**,不许"美化"。美化等于自建一层翻译,且翻译层迟早漏项。
|
||
- **一致性高于准确性**:`ID` 和 `Id` 哪个更好看无关紧要,全项目只能有一个。两种写法并存时,读代码的人每次都要停下确认"这是同一个东西吗"。
|
||
- **准确性高于简短**:宁可名字长一点,也不要让人误判对象边界。
|
||
|
||
---
|
||
|
||
## 三、对象术语表
|
||
|
||
### 3.1 正式术语(必须使用)
|
||
|
||
| 对象 | 正式名 | 代码落点 | 说明 |
|
||
|---|---|---|---|
|
||
| 专员 | `specialist` | `model/specialist.go` | 可挂载到任务的数字员工 |
|
||
| 技能 | `skill` | `model/skill_definition.go` | 任务级能力定义 |
|
||
| 应用 | `xapp` | `model/xapp_definition.go` | 长程任务运行壳 |
|
||
| 连接器 | `connector` | `internal/connector/*` | 外部系统接入 |
|
||
| 动作 | `action` | `model/action_definition.go` | 技能调用的底层执行单元 |
|
||
| 任务 | `task` | `model/task_record.go` | 工作实例容器 |
|
||
| 数据访问层 | `dal` / `DAO` | `internal/dal/*.go` | 统一的数据访问层:包名 `dal`,每个实体的访问对象叫 `XxxDAO`(`TaskRecordDAO`、`SpecialistDAO`…)。所有 handler 必须经它访问数据,禁止直接调用 `store.DB`。2026-09-19 由 `internal/repository` + `XxxRepo` 更名而来 |
|
||
|
||
### 3.2 兼容术语(限制使用,禁止扩散)
|
||
|
||
| 历史词 | 现状 | 允许出现的位置 | 禁止 |
|
||
|---|---|---|---|
|
||
| `role` | 旧心智残留 | 无。已无正式对象叫 role | 不得用于新变量、新文件名、新字段 |
|
||
| `assistant` | 只指"默认通用助手"这一件事 | 响应消息角色 `role: "assistant"`、`smart-assistant`(默认技能 key)、`general-assistant`(默认专员 key) | 不得用来指代专员、技能或对象类型。工作台主对话现走 `POST /api/chat/message` 与 `sendChatMessage`,不得再回退到旧 `assistant` 对话命名 |
|
||
| `capability` | 泛能力总称 | 仅限口语与文档泛指 | 不得充当对象级文件名或字段名。**已执行**:`capability_definition.go` → `skill_action_definition.go` |
|
||
| `businessApps` | **已删除**(2026-09-18,`d0d7b35`) | 无 | 不许复活 |
|
||
| `availableSkills` | **已更名 `staticSkillCatalog`**(2026-09-18) | 作为**来源前缀**使用,规则见 §5.7 | 不得再用旧名;不得跨模块引用(§5.7.5) |
|
||
| `worker` | 历史运行时术语 | 仅允许出现在迁移逻辑、迁移测试、历史说明中 | 不得回流到业务表名、API 路径、模块名或运行时对象名 |
|
||
|
||
### 3.3 共名冲突登记
|
||
|
||
同一个字符串同时是两个对象的标识符 —— 这是既成事实,不强行改,但要靠注释和测试守住,**并且不再新增**:
|
||
|
||
| 字符串 | 身份一 | 身份二 |
|
||
|---|---|---|
|
||
| `contract-review` | 专员 key | 技能 key |
|
||
| `report-generation` | 专员 key | 技能 key |
|
||
|
||
处理要求:
|
||
1. `model/skill_keys.go` 必须保留说明注释(已有)。
|
||
2. 任何"按 key 查对象"的代码,必须写明查的是哪一类,不允许出现 `getByKey(key)` 这种不区分对象类型的调用。
|
||
3. `skill_keys_test.go` 那种"前后端 key 集合双向对齐"的测试必须保留 —— 它是这两个身份不互相污染的唯一保障。
|
||
4. **跨对象提及同一字符串时,必须带类型前缀**:`specialist:contract-review` / `skill:contract-review`。
|
||
|
||
第 4 条已经写在 `skill_keys.go:24-25` 的注释里("日志里请带类型前缀")——
|
||
**这是 §5.7 前缀思想在本项目最早的一次自觉使用**:同一个字符串在两个对象间共名时,用前缀消歧。
|
||
它当时只写在注释里、只覆盖日志一处;§5.7 把它推广成通则。保留这条注释,别删。
|
||
|
||
### 3.4 `worker` 命名空间已退出业务主命名
|
||
|
||
当前运行时业务命名已经完成收口:
|
||
|
||
- 表 / 模型:`task_record`、`task_run`、`task_artifact`
|
||
- API 路径:`/api/tasks`、`/api/my/tasks`、`/api/artifacts/*`
|
||
- 前端模块:`api/taskRuntime.js`、`store/taskRuntime.js`
|
||
|
||
`worker` 仅保留在**迁移逻辑、迁移测试、历史说明**里,用来识别旧库与旧代码路径;它不再是正式术语,也不进入新白名单。
|
||
|
||
---
|
||
|
||
## 四、分层规范
|
||
|
||
### 4.1 后端 Go
|
||
|
||
| 项 | 规范 | 禁止 |
|
||
|---|---|---|
|
||
| 类型名 | `CamelCase`,对象名 + 用途后缀:`Specialist`、`SkillDefinition`、`TaskRecord` | `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`、`task_record` |
|
||
| **表名前缀** | 一级对象用**裸名**(`specialist` / `project` / `product`);归属或复合概念**必须带前缀**(`task_record` / `knowledge_chunk` / `position_exam_blueprint`)。详见 §5.7.3 |
|
||
| 列名 | 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/xapps` | ✅ 已一致,保持不回退 |
|
||
| **路径前缀** | 一级对象裸复数(`/api/specialists`);子系统带前缀(`/api/tasks`、`/api/knowledge/*`)。与表名规律同构 | ✅ 已一致,保持不回退(§5.7.3) |
|
||
| 路径参数 | snake_case:`/api/knowledge/audit/{source_id}` | ✅ 已收口为 snake_case |
|
||
| query 参数 | snake_case,且**跨层同名** | ✅ 持续守住 |
|
||
| 入口协议 | 对象入口 query 必须**定义在一处常量**,写读都引它 | ✅ 已抽到共享常量 |
|
||
|
||
**入口协议强制要求**:任何"从 A 页面带参数打开 B 页面"的协议,参数名必须是**共享常量**,不允许两边各写字符串字面量。这是 §8.1 事故的直接教训。
|
||
|
||
### 4.4 前端
|
||
|
||
| 项 | 规范 |
|
||
|---|---|
|
||
| 文件名 | store:`<对象>Catalog.js`(如 `specialistCatalog.js` / `skillCatalog.js` / `xappCatalog.js`);API:`<对象>.js`(如 `specialist.js` / `taskRuntime.js`) |
|
||
| store 文件名后缀 | **不带 `Store`**(Pinia 的 `useXxxStore` 已在导出名上表达)。现仅 `projectStore.js` 一个孤例带后缀 → 改为 `project.js`(§8.12-4) |
|
||
| 组件文件名 | 与对象相关的组件**必须带对象前缀**:`SpecialistChip.vue` / `SkillStrip.vue`;工作台布局件用 `surface` / `workspace` 语义(如 `SurfaceChatRail.vue`);纯布局件可用通用名(`ChatInputBar.vue` / `PlusMenu.vue`) |
|
||
| 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` | 文件格式 |
|
||
| `DAO` | data access object(数据访问对象,见 §3.1) |
|
||
| `dal` | data access layer(数据访问层包名,Go 包名一律小写) |
|
||
| `req` / `res` | 仅限 HTTP 处理函数的局部变量 |
|
||
|
||
禁止:`usr`、`mgr`、`svc`、`num`、`str`、`val`。(HTTP 处理函数的局部 `req` / `res`、Go 的 `ctx`、局部配置 `cfg` 属公认短名,不算违规。)
|
||
|
||
---
|
||
|
||
### 5.7 前缀规范(对象 / 来源 / 作用域)
|
||
|
||
> 前缀是本项目用得最重、也最容易失控的命名手段:34 张表、18 组 API 路径、全部 store 与组件都在用。
|
||
> 本节回答四件事:**什么时候必须加、加哪一类、什么时候必须去掉、怎么检查。**
|
||
>
|
||
> **与 TOP_CODING_RULES.md G03 的关系**:G03 第 5-10 条是这一节的**硬约束摘要**(准则层,必读);
|
||
> 本节是它的**完整展开** —— 判据表、三类前缀的对照、机器守卫(§6.2 守卫 G–J)、改名的额外要求(§7.6)、现状登记(§8.12)。
|
||
> 两者冲突时以 G03 为准。
|
||
|
||
#### 5.7.1 原理:前缀只在"平铺且无类型"的命名空间里才必要
|
||
|
||
前缀有成本:让名字变长、会随事实过期、被复制后极难收回。
|
||
**所以加前缀的唯一正当理由,是去掉它之后同一个命名空间里会出现两个可能同名的东西。**
|
||
|
||
判据落在命名空间的**形状**上,而不是被命名对象的重要性上:
|
||
|
||
| 命名空间 | 形状 | 前缀 |
|
||
|---|---|---|
|
||
| SQLite 表名 | 一个库内全平铺,无层级 | **必要**:`task_record` 不能叫 `task` |
|
||
| 表内列名 | 一张表内全平铺 | **必要**:`object_entry_route` 的 `object_` |
|
||
| URL query | 无 schema 的字符串袋 | **必要**:`xapp_specialist`(§8.1 事故的根源就在这里) |
|
||
| JSON key | 跨语言,接收端不做类型检查 | **必要**:全站 snake_case |
|
||
| 目录内文件名 | 平铺 | **必要**:`SkillStrip.vue` |
|
||
| shell 变量 | 一个 shell 内共享(`start_dev` 同时跑两个服务) | **必要**:`BACKEND_PORT` / `FRONTEND_PORT` |
|
||
| Go 包内标识符 | 有包作用域 | **冗余但可接受**(跨包引用时才真正需要) |
|
||
| 结构体字段 | 有 struct 作用域 | **不需要**:`SkillDefinition.Key` 不必叫 `skill_key` |
|
||
|
||
**一句话**:编译器或运行时能替你消歧的地方,前缀是噪音;不能的地方,前缀是唯一的消歧手段。
|
||
|
||
**本项目一个现成的正面例子**:`deploy/eai_agentplatform.env` 里是裸名 `PORT`、`DB_PATH`(一个 systemd unit 独占一份进程环境,不会冲突),而 `start_dev_10231_10232.sh:56-57` 里是 `BACKEND_PORT` / `FRONTEND_PORT`(同一个 shell 里跑两个服务,必须区分)。
|
||
同一个"端口"概念,在两层用了两种写法 —— **这不是不一致,这是对命名空间边界的正确判断**,应当保持。
|
||
|
||
#### 5.7.2 三类前缀,各有各的生命周期
|
||
|
||
| 类别 | 标记什么 | 本项目实例 | 生命周期 |
|
||
|---|---|---|---|
|
||
| **对象前缀** | 属于哪个一级对象 / 子系统 | `skill_definition`、`worker_task`、`knowledge_space` | **永久**(对象在,前缀在) |
|
||
| **来源前缀** | 这份数据的来路 | `staticSkillCatalog`、`normalizeCustomApp`、`normalizeRemoteApp`、(已退役)`legacy_*` | **必须写退出条件**(§5.7.6) |
|
||
| **作用域前缀** | 从哪个入口来 / 属于谁 | query 的 `app_`、API 的 `my_` | 随接口一起设计,不单独退役 |
|
||
|
||
**对象前缀的白名单 = §3.1 术语表 + §5.7.3 的子系统清单。** 不在这两张表里的前缀不许发明 —— 这与 §5.6 缩写表是同一个思路:**能用的集合必须封闭,否则每个人都会造自己的。**
|
||
|
||
#### 5.7.3 已收敛的两条规律(DB 层与 API 层各自独立长成了同一条)
|
||
|
||
34 张表的前缀乍看是随手加的,实际有规律:
|
||
|
||
```text
|
||
一级对象 → 裸名 specialist / project / product / position / course / user
|
||
归属或复合 → 带前缀 worker_task / knowledge_chunk / position_exam_blueprint / ai_call_log
|
||
```
|
||
|
||
API 路径**独立地**收敛到了同一条:
|
||
|
||
```text
|
||
一级对象 → 裸复数 /api/specialists /api/skills /api/xapps /api/actions /api/products
|
||
子系统 → 带前缀 /api/tasks /api/knowledge/* /api/system/* /api/exam/* /api/ai/*
|
||
```
|
||
|
||
两层没有互相参照却选了一样的分法,说明这条规则是自然的,不是硬塞的。**写进规范,守住不回退。**
|
||
|
||
子系统前缀白名单(现有;新增须先登记到本表):
|
||
|
||
`task` · `knowledge` · `exam` · `weixin_public_account` · `ai` · `media` · `system` · `user` · `position`
|
||
|
||
**已知的一处不同构(登记,不强制回改)**:`specialist` 是裸名,而它的同位对象是 `skill_definition` / `xapp_definition` / `action_definition`。
|
||
`_definition` 后缀标记的是"对象定义表",按此语义 `specialist` 应属同一族。但改表名牵动迁移与所有引用,收益不抵成本 —— 按 §6.4 的 P3 纪律**先登记,不顺手改**。
|
||
|
||
#### 5.7.4 硬约束
|
||
|
||
1. **一个标识符最多带一个类型前缀。** `task_record` ✅ / `xapp_specialist_skill_key` ❌。需要两层信息时用组合词,不要叠加前缀。
|
||
2. **次序固定:前缀 + 核心词 + 后缀。** `skill_definition` ✅ / `definition_skill` ❌。
|
||
3. **前缀必须写全,禁止缩写。** `specialist_` ✅ / `sp_`、`sk_`、`wr_` ❌(§5.6)。
|
||
4. **禁止拼音前缀。** 前缀取自 §3.1 的英文术语表;中文是对外展示层的事,不进标识符。
|
||
5. **过渡前缀不得嵌套。** `legacy_` 之上不许再叠一层,理由见 §5.7.6。
|
||
6. **来源前缀不得跨模块引用**(§5.7.5)。
|
||
7. **作用域前缀不得进入模型、表、字段名。** `my_` 只能出现在 API 路径与 query。
|
||
现状是对的:API 是 `api/my_xapp_center.go`,模型是 `model/user_xapp_center.go` —— **保持这个分层**。
|
||
附带提醒:`my-` 命名的是**视图**不是资源(`/api/my/tasks` 与 `/api/tasks` 并存)。当出现第三种视图(管理员看某个用户的)时它会没有名字 —— 届时改用 `?owner=` 参数,**不要**再加 `their-tasks` 这种前缀。
|
||
|
||
#### 5.7.5 来源前缀不得泄漏到调用点
|
||
|
||
**这是本节最实用的一条,也是本项目正在发生的一个问题。**
|
||
|
||
`staticSkillCatalog` 的**定义位置是对的**(`config/workbench.js`),但它现在有 6 处引用,其中 **5 处是同一种兜底写法**:
|
||
|
||
```js
|
||
skillCatalog.getByKey(key) || staticSkillCatalog.find((item) => item.key === key)
|
||
```
|
||
|
||
分布在 `SmartAssistantPage.vue`(3 处)、`PlusMenu.vue`、`CurrentObjectChip.vue`。
|
||
|
||
问题在于**前缀泄漏到了调用点**:调用方本来不该知道"这份数据可能是静态兜底的"。
|
||
每多一个调用点,就多一处将来要同步修改的地方;而且**哪一处漏了不会有任何提示**。
|
||
|
||
→ **做法**:把兜底收进 `skillCatalog`,调用点只写一个名字:
|
||
|
||
```js
|
||
// store/skillCatalog.js —— 前缀留在声明处,不出模块
|
||
function resolve(key) {
|
||
return getByKey(key) || staticSkills.find((item) => item.key === key) || null
|
||
}
|
||
```
|
||
|
||
(`skillCatalog.js` 目前只有 `hydrate` / `getByKey`,**尚无 `resolve`** —— 这是待办,见 §8.12-2。)
|
||
|
||
**推广成规则**:任何带来源前缀的符号(`static*` / `legacy*` / `custom*` / `remote*`),被声明模块之外的文件直接引用,即为泄漏。
|
||
判断不需要读实现 —— **看 import 就够**。
|
||
|
||
#### 5.7.6 来源前缀必须带退出条件
|
||
|
||
`static*` / `legacy*` / `tmp*` / `old*` / `deprecated*` 描述的是**过程状态**,而过程会结束。
|
||
没有退出条件,它们就会永久化,代码变成考古现场。
|
||
|
||
本项目在同一张表上同时有一个反例和一个正例。
|
||
|
||
**反例 —— `skill_definition` 的五代列名:**
|
||
|
||
```text
|
||
entry_route → route → legacy_entry_route / legacy_route
|
||
→ legacy_object_entry_route → (已删除)
|
||
```
|
||
|
||
`legacy_object_entry_route` 的字面意思是"遗留的 · 对象入口路由" —— `legacy_` 叠在**已经 legacy 的概念**上。
|
||
**过渡前缀出现第二层,就是上一次迁移没有退出条件的证据。**
|
||
|
||
**正例 —— 同一个字段的收尾:**
|
||
上面那批 legacy 列的 drop 逻辑(`db.go:148-158`)与 `RoleKind → ObjectKind` 的迁移写在**同一笔提交**(`d0d7b35`)里,而不是"先留着以后再说"。这是正确做法,值得照抄。
|
||
|
||
→ **要求**:写下来源前缀的同一行注释或相邻 TODO,必须写清"什么时候可以去掉"。
|
||
现存待办示例:`staticSkillCatalog` 的退出条件 = `workbench.js` 的静态兜底被确认可由 `skillCatalog` 完全覆盖后删除。
|
||
|
||
#### 5.7.7 前缀与分隔符
|
||
|
||
分隔符由**所在层**决定,不由个人喜好:
|
||
|
||
| 层 | 分隔符 | 例 |
|
||
|---|---|---|
|
||
| Go 标识符 | CamelCase 连写,前缀是完整单词、不加分隔符 | `staticSkillCatalog`、`normalizeRemoteApp` |
|
||
| 表名 / 列名 / JSON key | snake_case | `task_record`、`xapp_specialist` |
|
||
| URL 路径 / query | snake_case | `/api/tasks`、`xapp_skill` |
|
||
| 文件名 | 组件 `CamelCase.vue`、模块 `camelCase.js` | `SpecialistChip.vue`、`skillCatalog.js` |
|
||
|
||
同一层内不得混用。模型层当前 **237 : 0** 全是 snake_case,是一条干净基线,**加守卫防回退**(§6.2 守卫 I)。
|
||
|
||
---
|
||
|
||
## 六、如何检查
|
||
|
||
### 6.1 人工五问(写名字时问自己,review 时问作者)
|
||
|
||
1. 这个名字指的是**哪个对象**?能一句话说出它是 specialist / skill / app / connector / action / task 中的哪一个吗?
|
||
2. 换个人读,会不会理解成**另一个对象**?
|
||
3. 全项目 grep 一下,**是不是只有这一种写法**?
|
||
4. 改它会不会断掉**靠名字派生的协议**(§1.2 的表)?
|
||
5. 它是**外部标准**吗?是 → 按字符照抄,一个字都不许动。
|
||
|
||
### 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 | 入口协议写读对称 | 见下 | — |
|
||
| G | **前缀白名单 diff**:从表名、API 路径、store/组件文件名、query 参数中抽取前缀,与 §3.1 术语表 + §5.7.3 子系统清单求差集 | 见下 | 两张表本身就是白名单 |
|
||
| H | **过渡前缀缺退出条件**:`legacy_` / `legacy[A-Z]` / `static[A-Z]` / `tmp_` / `old[A-Z]` / `deprecated` 每一处都必须有说明移除条件的注释或 TODO | `(legacy_|static[A-Z]|tmp_|old[A-Z]|deprecated)` | 迁移脚本内部的一次性变量 |
|
||
| I | **分隔符不混用**:模型层 JSON tag 必须 100% snake_case(基线 237:0) | `json:"[a-z0-9_]*[A-Z]` | 无(`my_app_center.go` 已修) |
|
||
| J | **来源前缀跨模块引用**:带来源前缀的符号不得被声明它的模块之外的文件引用 | 见下 | 声明模块自身 |
|
||
|
||
**守卫 G 的做法**:表名与路径前缀可以直接从 `model/*.go` 的 `TableName()` 和 `router.go` 的注册里抽;
|
||
抽出的前缀集合与白名单求差集,多出来的就是"某人新造的前缀",需要先登记再放行。
|
||
**这条守卫的价值在于把"前缀"从个人习惯变成受控词汇表** —— 与 §5.6 缩写表同一个机制。
|
||
|
||
**守卫 J 的做法**:`static*` / `legacy*` / `custom*` / `remote*` 开头的导出符号,grep 其被 import 的位置,
|
||
凡出现在声明模块之外即为违规。当前 `staticSkillCatalog` 有 **5 处**这类引用(§5.7.5),是这条守卫的首批命中项。
|
||
不需要读实现,**看 import 就够** —— 这是一条几乎零成本、却能挡住"兜底逻辑被抄 N 遍"的守卫。
|
||
|
||
**守卫 F 的做法**(最重要,也最难自动化):
|
||
入口协议不要靠扫描源码配对,而是**把参数名提成共享常量**(如 `frontend/src/config/objectEntry.js` 导出 `XAPP_ENTRY_QUERY_KEYS = { specialist: 'xapp_specialist', ... }`),写端读端都引它。
|
||
这样 F 就从"看不见的约定"变成了"编译器能查的引用",守卫 F 也就不需要了 —— **能用结构消除的检查,不要用扫描去补**。
|
||
|
||
### 6.3 跨层对齐检查
|
||
|
||
任何"同一份清单在前后端各写一遍"的东西,都必须有双向对齐测试:
|
||
|
||
- 技能 key:前端 `staticSkillCatalog` ↔ 后端 `ValidSkillKeys` ✅ 已有(`skill_keys_test.go`,22 个 key 双向对齐)
|
||
- 专员 key:前端静态目录 ↔ 后端种子数据 ⬜ 建议补
|
||
- 入口协议参数名:✅ **已做**。写端与读端都改为引用 `frontend/src/config/objectEntry.js` 的共享常量,不再各写各的字面量。
|
||
|
||
### 6.4 检查结果的严重性分级
|
||
|
||
| 级别 | 判据 | 处理 |
|
||
|---|---|---|
|
||
| **P0** | 名字不一致导致**功能已断**(写读不同名、配对失效) | 当 bug 修,立即 |
|
||
| **P1** | 跨层漂移,会被新代码复制(json tag、路径参数、失真局部变量) | 本迭代内 |
|
||
| **P2** | 名字带旧词但语义尚可推断(`RoleKind`、`currentRolePresentation`) | 排期做,别急 |
|
||
| **P3** | 死代码 / 无人引用的历史变量 | **先判生死,再决定改名还是删除** |
|
||
|
||
**注意 P3 的陷阱**:给一份没人读的静态数据改名,等于给它续命。
|
||
先确认引用数,是 0 就删(见 §8.9,`businessApps` 已按此原则删除)。
|
||
|
||
**P3 同样适用于前缀**:给死代码补一个语义正确的前缀,只是让它死得更体面(§5.7)。
|
||
|
||
---
|
||
|
||
## 七、如何修复
|
||
|
||
### 7.1 迁移顺序:由内到外,最后才碰数据库
|
||
|
||
```text
|
||
① 局部变量
|
||
② 前端协议 / 入口参数
|
||
③ 文件名
|
||
④ 字段名 / 数据库列名
|
||
```
|
||
|
||
理由:
|
||
- ① 影响面最小、可读性收益最直接、无跨进程风险。
|
||
- ② 优先于 ③ —— 协议不统一会**持续产生新的分叉**,不先堵住,你改完还会被新代码重新搞乱。
|
||
- ④ 放最后:牵动数据库,而 SQLite 的列改名 `AutoMigrate` 不管,必须手写迁移,风险最高、回滚最难(G05:大规模操作前先备份)。
|
||
|
||
### 7.2 改名六步法
|
||
|
||
1. **定名**:对照 §3 术语表 + §5 模式库,写下新名字,并回答 §6.1 的五问。
|
||
2. **查引用**:`grep -rn "<旧名>"`,范围必须包含
|
||
- 源码 / 模板 / 样式
|
||
- **字符串字面量**(路由、query key、事件名、localStorage key)
|
||
- **JSON tag、SQL 语句、迁移脚本**
|
||
- 配置、种子数据、测试、e2e、文档、注释
|
||
3. **判协议**:这个旧名是否出现在 §1.2 那张表里?是 → 这不是改名,是改协议,写下来通知所有相关方(含前端)。
|
||
4. **一次改完**:不留双写法。兼容层是唯一例外,且**必须带删除条件**("等 X 上线后删除"),否则兼容层会永久化。
|
||
5. **验证**(按 G04,不靠感觉):
|
||
- `go build ./...` + `go test ./...` exit 0
|
||
- `npm run build` exit 0
|
||
- **真起 dev server,在浏览器里走一遍受影响路径** —— 编译通过不代表功能没断(§8.1 就是编译全绿但功能已断)
|
||
6. **提交**:一次提交只装一件事(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)。
|
||
|
||
**② 前端协议 / 入口参数**
|
||
不要只是"改一致",而是**提成共享常量**:
|
||
```js
|
||
// frontend/src/config/objectEntry.js
|
||
export const XAPP_ENTRY_QUERY_KEYS = {
|
||
specialist: 'xapp_specialist',
|
||
skill: 'xapp_skill',
|
||
prompt: 'xapp_prompt',
|
||
}
|
||
```
|
||
写端 `params.set(ENTRY_PARAMS.specialist, ...)`,读端 `route.query[ENTRY_PARAMS.specialist]`。
|
||
这样下一个参数不会再各写各的。
|
||
|
||
**③ 文件名**
|
||
- 只改名:`git mv`,保持历史可追溯。
|
||
- 一文件两职责:先拆文件,再改名,**同时改注册点**(`router.go` 等)。只拆一半会留下一个名实仍不符的残文件,比不改更糟。
|
||
|
||
**④ 字段名 / 列名**
|
||
1. 先备份(`data/backups/` 内置 `VACUUM INTO`,D24)。
|
||
2. **在库副本上验证**新列/新列名能被正确读写(P06.1)。
|
||
3. 写一次性迁移;老列在确认无引用前不删。
|
||
4. 同步改 json tag、前端读取点、文档字段表。
|
||
5. 改完立即跑一遍 P06.2 的做法:**探测脚本用完即删**,别留成断言"升级前状态"的测试。
|
||
|
||
### 7.5 回滚与提交
|
||
|
||
- 一次提交一件事:改名与功能改动**不要混在一个提交里**(否则回滚会带走功能)。
|
||
- 改名列必须写明"旧名 → 新名",方便 `git log --follow` 与后来人搜索。
|
||
- 如果改名波及数据库,提交信息里写明迁移步骤与验证结果。
|
||
|
||
### 7.6 前缀改名的额外要求
|
||
|
||
前缀改名与普通改名有一个根本区别:**前缀几乎总是活在字符串里**(表名、列名、JSON tag、URL 参数、env 变量名),
|
||
而字符串改名**编译器一个都管不着**。在 §7.2 六步法之上,额外三条:
|
||
|
||
**① 必须按字符串 grep,不是按符号 grep。**
|
||
查 `LegacyObjectEntryRoute` 是不够的,必须查 `legacy_object_entry_route`,并且覆盖:
|
||
JSON tag、SQL 语句与迁移脚本、前端字面量、query 参数、文档。范围与 §7.2 第 2 步相同,但**起点是字符串**。
|
||
|
||
**② 禁止子串替换、禁止正则通配,必须逐标识符精确锚定。**
|
||
|
||
本项目有一个现成的雷:`role_kind`(正确新名 `object_kind`)与 `role_card_json`(正确新名 `interaction_card_json`)
|
||
**都以 `role_` 开头,但去向完全不同**。
|
||
一句 `sed 's/role_/object_/g'` 会把 `role_card_json` 误伤成 `object_card_json` —— 而它本该叫 `interaction_card_json`。
|
||
|
||
这不是假设:[db.go:171](../../eai_agentplatform/backend-go/internal/store/db.go#L171) 与 [:184](../../eai_agentplatform/backend-go/internal/store/db.go#L184)
|
||
两条迁移的目标名确实不同,**证明了批量替换必然出错**。
|
||
结论:前缀改名**只能一个标识符一个标识符地改**,宁可慢。
|
||
|
||
**③ 本项目不需要兼容窗口,但持久化的名字除外。**
|
||
|
||
前后端同版本发布(单二进制 + 内网部署,无外部消费者),所以 URL query、JSON key 这类字符串边界
|
||
**一次改完即可**,不需要新旧并存的过渡期 —— 这是本项目相对一般工程的一个有利条件,可以放心用。
|
||
|
||
**唯一例外**:任何被**持久化**的名字。浏览器 localStorage 里的 key、已发出的书签 URL、磁盘上的配置文件 —— 这些在改名后仍然存在,读端需要容旧或做一次性迁移。
|
||
|
||
---
|
||
|
||
## 八、本项目现状
|
||
|
||
> **2026-09-17 实测**:以下每条都在代码里核实过,标了行号,级别按 §6.4。
|
||
> **2026-09-18 更新**:§8.1–8.10 已在提交 `d0d7b35` 执行完毕。原文保留是为了留下**判断依据**(§9 反例库直接依赖它);
|
||
> **每条的『现状』行是当下事实,冲突时以它为准。** 新增 §8.12(前缀维度)、§8.13(未根治项)。
|
||
|
||
### 8.1 【曾为 P0|09-18 已修,但未根治】对象入口协议写读不对称
|
||
|
||
- **写端**:[xappCatalog.js:143-146](../../eai_agentplatform/frontend/src/store/xappCatalog.js#L143-L146) 拼 `xapp_specialist` / `xapp_skill` / `xapp_prompt`
|
||
- **读端**:`SmartAssistantPage.vue:604`、`605`、`606` 读 `xapp_specialist` / `xapp_skill` / `xapp_prompt`
|
||
- **全仓库核实**:没有任何地方读不带前缀的,也没有任何地方写带前缀的
|
||
- **后果**:从能力目录点应用进工作台,预置的专员 / 技能 / 提示词**三个参数全部被静默丢弃**,不报错。用户看到的是"点进去还是空的"
|
||
- **注意**:这是 bug,不是风格问题。按 §7.4② 提成共享常量一起修
|
||
- **现状(09-18)**:✅ **功能与根因都已修** —— 两侧统一为 `xapp_specialist` / `xapp_skill` / `xapp_prompt`,并提成 `frontend/src/config/objectEntry.js` 共享常量;读端另有 3 个 `watch`(`SmartAssistantPage.vue:700/707/714`),使二次打开另一个应用时也能切换。
|
||
|
||
### 8.2 【P1】camelCase json tag
|
||
|
||
`my_app_center.go:36-41` 六处:`installState`、`skillKey`、`createdAt`、`iconText`、`coverTone`、`isCustomApp`,是全站 snake_case 体系里仅有的例外,且集中在同一个结构体。
|
||
→ 改 snake_case,前端同步。
|
||
**现状(09-18)**:⬜ 未动。模型层基线仍为 237 : 0 全 snake_case,违规仍集中在这一处。
|
||
|
||
### 8.3 【P1】URL 路径参数 camelCase
|
||
|
||
`api/knowledge.go:97` 的 `{sourceId}`、`api/exam.go:975` 的 `{recordId}`。
|
||
→ 改 snake_case(`{source_id}`、`{record_id}`),同步前端调用处。
|
||
**现状(09-18)**:⬜ 未动(`knowledge.go:94/160`、`exam.go:972` 路径仍为 camelCase)。
|
||
|
||
### 8.4 【P1】局部变量失真
|
||
|
||
`SmartAssistantPage.vue:314`:`const app = specialistCatalog.getByKey(key)` —— 拿到的是专员,却叫 `app`。
|
||
注:它所在的 computed 名字是对的(`currentSpecialistPresentation`,`kind: 'specialist'`),失真仅限这一个局部变量。
|
||
→ 改 `specialist`。**P1 原因:它会被后续代码照抄。**
|
||
**现状(09-18)**:✅ 已修(`const app = ` 在 `SmartAssistantPage.vue` 中已无匹配)。
|
||
|
||
### 8.5 【P2】`role` 心智残留
|
||
|
||
`SmartAssistantPage.vue:354` 的 `currentRolePresentation`,把 assistant / specialist / skill 三类收在一个 role 概念下。
|
||
→ 收敛为 `currentObjectPresentation`,或按 §3.1 拆成三份。
|
||
**现状(09-18)**:✅ 已修(全仓已无 `currentRolePresentation`)。
|
||
|
||
### 8.6 【P2】`RoleKind` 字段名带旧词
|
||
|
||
`model/skill_definition.go:13`:旧 `RoleKind`,早期注释曾写 `assistant / specialist / skill`。
|
||
|
||
- **澄清**:这个字段表达的是"这条定义属于哪类对象"。当前正式值已收口为 `specialist / skill`,不再把 `assistant` 作为对象分类值保留。
|
||
- **改名方向**:`object_kind`(准确)。
|
||
- **成本提醒**:改字段名要同时动 ① 数据库列 `role_kind` ② json tag ③ 前端读取点。SQLite 改列名 `AutoMigrate` 不管,得手写迁移。
|
||
- **建议**:**默认方案是保留字段名,在注释与文档里标注语义**;真要改,按 §7.4④ 走完整流程,不要顺手改。
|
||
- **现状(09-18)**:✅ **已按完整流程改名** `ObjectKind`(列 `object_kind`),迁移写在 `migrateSkillObjectKindColumn`(先拷数据再 drop 旧列,未踩 AutoMigrate 只加不删的坑)。
|
||
印证 §5.7.6:旧列与新列的迁移逻辑**同批删除**,未留考古层。
|
||
⚠️ 全仓仅剩的 `role_kind` 在 `db_migration_test.go:186-201` —— **这是正确用法**:测试必须重建升级前的旧库才能验证迁移。
|
||
**不要把这类旧名当成待清理项。**
|
||
|
||
### 8.7 【P2|09-18 已改】`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` 不是正式对象,让它当文件名会持续制造一个不存在的一级概念。
|
||
- **现状(09-18)**:✅ 已改为 `api/skill_action_definition.go`(采用方案②,未拆分),`router.go` 注册已同步,构建通过。
|
||
⚠️ 遗留观察:该文件名与 §5.7.4 第 1 条不冲突,但它是**双对象文件**;若将来 action 的 handler 变多,仍应拆成两文件(拆分时必须同时改 `router.go` 注册)。
|
||
|
||
### 8.8 【P1|09-18 已完成】`worker` 命名空间已退出业务路径
|
||
|
||
已完成的收口:
|
||
|
||
- `/api/worker/tasks` → `/api/tasks`
|
||
- `worker_task.go` / `worker_run.go` / `worker_artifact.go` → `task_record.go` / `task_run.go` / `task_artifact.go`
|
||
- `api/worker.js` / `store/workerRuntime.js` → `api/taskRuntime.js` / `store/taskRuntime.js`
|
||
|
||
**现状(09-18)**:✅ 业务代码已完成去兼容;`worker_*` 仅保留在迁移逻辑、迁移测试与历史说明中。
|
||
|
||
### 8.9 【P3|09-18 已删除】`businessApps` 死代码
|
||
|
||
- **定义**:`config/workbench.js:1434`,内容是**专员目录**(第一条即 `contract-review` 合同审查专员,带 `tier` / `workerType` / `roleCard`),名字与内容相反
|
||
- **引用情况**:模块外**零引用**;仅 `workbench.js` 内部 6 处自用(`2232` 按 legacy 路由查、`2236` 按 tier 筛、`2240` 数量、`2244` 可升级数、`2252-2253` dw/adw 统计)
|
||
- **建议**:先确认那几个统计入口是否还有页面在用 → 没有就**整块删除**;有就随使用者一起迁到 `specialistCatalog`
|
||
- **不要**改名成 `staticSpecialistCatalog` —— 那等于给一份没人读的静态副本续命
|
||
- **现状(09-18)**:✅ **已整块删除**,按建议路径处理。
|
||
**留下一条经验**:判断"改还是删"要看**引用数**,不是看名字难不难看。
|
||
这一条最初被外部清单列为"建议改名为 `staticSpecialistCatalog`",实际核实后是模块外零引用的死变量 —— 按原名改就给它续了命。
|
||
|
||
### 8.10 【P3|09-18 已更名,但只解决一半】`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`(语义准确),或一并删除。
|
||
**现状(09-18)**:✅ 已更名 `staticSkillCatalog`,未删除(`skillCatalog` 尚未完全覆盖,兜底仍有实际作用)。
|
||
⚠️ 但**改名只解决了一半** —— 兜底逻辑现在被抄到了 5 个调用点,见 §5.7.5 与 §8.12-2。
|
||
|
||
### 8.11 【已守住,勿回退】
|
||
|
||
- 后端对象 API 命名已清楚:`/api/specialists`、`/api/skills`、`/api/xapps`、`/api/actions`(`router.go:43-50`)
|
||
- 前端 catalog store 命名已规范:`specialistCatalog.js`(`normalizeSpecialist` / `getByKey` / `getByPath`)是标准范本
|
||
- 技能 key 前后端对齐测试已有(`model/skill_keys_test.go`,22 个 key)
|
||
|
||
### 8.12 【2026-09-18 新增】前缀维度现状
|
||
|
||
#### 8.12-1 【已成立,守住】表名与 API 路径的前缀规律
|
||
|
||
34 张表与 18 组 API 路径**各自独立**收敛到了同一条分法(一级对象裸名、归属复合带前缀,详见 §5.7.3)。
|
||
两层没有互相参照却选了一样的写法 —— 这是自然规律,**写进规范是为了守住,不是为了改造**。
|
||
|
||
#### 8.12-2 【P1】`staticSkillCatalog` 的前缀泄漏到 5 个调用点
|
||
|
||
- **定义**:`config/workbench.js`(原名 `availableSkills`,09-18 更名)
|
||
- **引用**:共 6 处,其中 **5 处是同一种兜底写法** `skillCatalog.getByKey(k) || staticSkillCatalog.find(...)`
|
||
—— `SmartAssistantPage.vue:299/340/408`、`PlusMenu.vue:183`、`CurrentObjectChip.vue:55`
|
||
- **根因**:`store/skillCatalog.js` 只有 `hydrate` / `getByKey`,**没有 `resolve`**,所以每个调用方都得自己拼兜底
|
||
- **修法**:加 `resolve(key)` 把兜底收进模块(§5.7.5),调用点只写 `skillCatalog.resolve(key)`
|
||
- **为什么是 P1 而不是 P2**:这 5 处是**将来删除静态目录时必然踩到的地雷** —— 漏掉任何一处不会有任何提示
|
||
|
||
#### 8.12-3 【登记,不强制回改】`specialist` 是裸名,同位对象却带 `_definition`
|
||
|
||
`specialist` 与 `skill_definition` / `xapp_definition` / `action_definition` 是同级对象,命名却不同族。
|
||
按 §6.4 的 P3 纪律:**先登记,不顺手改**(改表名牵动迁移与全部引用,收益不抵成本)。
|
||
|
||
#### 8.12-4 【P3】`projectStore.js` 是 12 个 store 里唯一带 `Store` 后缀的
|
||
|
||
`frontend/src/store/` 下 12 个文件,只有 `projectStore.js` 带后缀(Pinia 的 `useProjectStore` 已经在导出名上表达了这层意思)。
|
||
→ 改名 `project.js`,与 `xappCatalog.js` / `skillCatalog.js` 等同构。
|
||
|
||
#### 8.12-5 【P3】`docs/10-eaiintro/` 与其余 7 个目录不同构,且混入 Office 锁文件
|
||
|
||
- **目录名**:`10-eaiintro` 用连字符且无文档前缀,而其余是 `01_System_Overall/`(SY)、`02_Architecture/`(AR)…… `09_Research/`(RS)
|
||
- **内容**:27 个跟踪文件里混着 pptx / jpg 等**二进制素材**,性质上属于"素材目录"
|
||
- **顺带发现**:`~$梅奥心磁_….pptx` 等 **3 个 Office 锁文件被 git 跟踪了**(`~$` 是 Office 打开文件时产生的临时锁文件,本不该入库)
|
||
- **建议**:先决定它是"文档目录"还是"素材目录" —— 前者按 `11_<Name>/` 纳入编号体系并配文档前缀,后者移出 `docs/`;锁文件从版本库删除并加进 `.gitignore`
|
||
|
||
#### 8.12-6 【09-18 已收口】工作台主对话接口已统一为 `chat/message`
|
||
|
||
当前前端接口文件是 `api/chatMessage.js`,主入口是 `sendChatMessage`,后端对应 `POST /api/chat/message`。
|
||
|
||
- 默认助手语义仍只存在于 `smart-assistant` / `general-assistant` 这组默认对象身份里
|
||
- 工作台主对话入口则使用中性命名 `chat/message`
|
||
- 因此,`assistant` 现在是**对象身份语义**,不是**主对话 API 前缀**
|
||
|
||
后续若再新增聊天接口,也应守住这条分层:对象身份不要回流到总入口路径名。
|
||
|
||
#### 8.12-7 【已合规,保持】`my_` 停留在 API 层
|
||
|
||
- API:`api/my_xapp_center.go`、`/api/my/xapp-center`、`/api/my/tasks`
|
||
- 模型:`model/user_xapp_center.go`(不是 `my_`)
|
||
|
||
这正是 §5.7.4 第 7 条要求的分层,**现状正确,不要"统一"成 `my_`**。
|
||
|
||
### 8.13 【P1|09-18 已修】入口协议已抽成共享常量
|
||
|
||
§8.1 的功能和根因都已修:`xappCatalog.js` 与 `SmartAssistantPage.vue` 现通过 `frontend/src/config/objectEntry.js` 共享 `XAPP_ENTRY_QUERY_KEYS`,不再各写各的 `'xapp_specialist'` / `'xapp_skill'` / `'xapp_prompt'` 字面量。
|
||
|
||
- **收益**:入口协议改名只需要动一处
|
||
- **守则**:能用结构消除的检查,不要靠扫描补漏
|
||
|
||
---
|
||
|
||
## 九、反例库(真实事故)
|
||
|
||
| 症状 | 根因 | 正确做法 |
|
||
|---|---|---|
|
||
| 从应用目录点进去,工作台是空的(专员/技能/提示词都没带上) | 写端读端各写一套 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/<用户名>/...` 链接 | 本机可点,他人全断 | 文档引用一律相对路径 |
|
||
| `skill_definition` 一个字段先后有 **5 代列名**,最后一代叫 `legacy_object_entry_route` | 上一次迁移没有退出条件,只好再叠一层 `legacy_` | 过渡前缀必须写退出条件,且**禁止嵌套**(§5.7.6) |
|
||
| 同一个兜底判断在 5 个调用点被抄了 5 遍 | `static` 前缀泄漏到调用点,调用方被迫知道数据来路 | 来源前缀不出模块,收进 `resolve()`(§5.7.5) |
|
||
| `sed 's/role_/object_/g'` 会把 `role_card_json` 误伤成 `object_card_json` | 前缀改名用了子串替换 | 逐标识符精确锚定,**禁止通配**(§7.6②) |
|
||
| 3 个 `~$*.pptx` Office 锁文件进了版本库 | 素材与文档混放,无 `.gitignore` 守卫 | 目录按性质分离;临时前缀文件不入库(§8.12-5) |
|
||
| 差点把 `businessApps` "改名"成 `staticSpecialistCatalog` | 只看了名字难看,没数引用数 | 改还是删,**看引用数**(§6.4 / §8.9) |
|
||
|
||
---
|
||
|
||
## 十、修订记录
|
||
|
||
| 版本 | 日期 | 变更 |
|
||
|---|---|---|
|
||
| V1.0 | 2026-09-17 | 首版。原理 / 判据 / 术语表 / 分层规范 / 模式库 / 检查 / 修复 / 现状 / 反例库 |
|
||
| V1.1 | 2026-09-18 | ① 新增 **§5.7 前缀规范**(原理 / 三类前缀 / 两条已收敛规律 / 硬约束 / 泄漏 / 退出条件 / 分隔符);② §6.2 新增守卫 **G–J**;③ 新增 **§7.6 前缀改名的额外要求**(字符串 grep、禁止子串替换、无兼容窗口的例外);④ **§8 全面刷新** —— §8.1–8.10 已在 `d0d7b35` 执行完毕,逐条标注『现状』,新增 §8.12(前缀维度)与 §8.13(入口协议未根治);⑤ §1.2 / §3.2 / §4.2 / §4.3 / §4.4 / §6.3 / §6.4 同步当下事实;⑥ §9 反例库补 5 条前缀类事故;⑦ 同步 `TOP_CODING_RULES.md` V1.2 —— 该文件 G03 新增第 5-10 条承载**同样的硬约束**,本文件 §5.7 是其完整展开,两者互为详略 |
|
||
|
||
---
|
||
|
||
*注:本文件是命名问题的最高依据,与 `TOP_CODING_RULES.md` 配套使用。术语表(§3)变更属于架构级决策,改前先更新 `PROJECT_STATE.md` 的已拍板决策表。*
|