Move AnnotateRuntime* into cli/runtimeannotate and the Cobra-keyed ContractFinal store into cli/contractfinal so corecmd depends on thin subpackages instead of the cli delivery root. Keep contract as DTO-only, document Description declare-vs-delivery, and house homology gates under cli/homology. Co-authored-by: Cursor <cursoragent@cursor.com>
210 lines
12 KiB
Markdown
210 lines
12 KiB
Markdown
# command 领域模型
|
||
|
||
本文档描述 `internal/corecmd` 包的领域模型——类型、概念及其关系。
|
||
|
||
## 核心模型图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ corecmd.Spec │
|
||
│ (一个叶子命令的完整契约) │
|
||
├─────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌─── CLI 表面 ───┐ ┌─── 参数声明 ───────────────────────────┐ │
|
||
│ │ Use │ │ FlagSpec[] │ │
|
||
│ │ Short │ │ ├─ Name / Kind / Default │ │
|
||
│ │ Long │ │ ├─ Required / MarkRequired │ │
|
||
│ │ Example │ │ ├─ Aliases[] / EnvVar (回退链) │ │
|
||
│ └────────────────┘ │ ├─ Bind / Transform / OmitEmpty │ │
|
||
│ │ └─ Enum / Format / SchemaDescription │ │
|
||
│ │ │ │
|
||
│ │ Constraint[] │ │
|
||
│ │ ├─ at_least_one │ │
|
||
│ │ ├─ exactly_one │ │
|
||
│ │ └─ mutually_exclusive │ │
|
||
│ │ │ │
|
||
│ │ ConstParams map[string]any │ │
|
||
│ └────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─── 安全模型 ──────────────────────────────────────────────────┐ │
|
||
│ │ Safety contract.SafetySpec │ │
|
||
│ │ ├─ Effect (read / write / destructive) │ │
|
||
│ │ ├─ Risk (low / medium / high) │ │
|
||
│ │ ├─ Confirmation (not_required / user_required) ──▶ 运行时门 │ │
|
||
│ │ └─ Idempotency (idempotent / retryable / …) │ │
|
||
│ │ │ │
|
||
│ │ 四字段彼此独立;同一值同时供运行时与 Schema 使用 │ │
|
||
│ │ ConfirmFirst: bool (只控制确认门顺序) │ │
|
||
│ └───────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─── Contract 声明 (Agent 可见的元数据) ────────────────────────┐ │
|
||
│ │ ContractDecl │ │
|
||
│ │ ├─ Title / Description │ │
|
||
│ │ ├─ contract.DryRunSpec {PreviewKind, RemoteReads} │ │
|
||
│ │ ├─ contract.InterfaceSpec {Mode, Availability, Reason, Ref}│ │
|
||
│ │ ├─ contract.SelectionSpec {AgentSummary, UseWhen, AvoidWhen│ │
|
||
│ │ │ Prerequisites, Tips, Examples} │ │
|
||
│ │ ├─ contract.ToolIdentitySpec {ProductID, CanonicalPath, …} │ │
|
||
│ │ └─ Positionals[] {Name, Type, Required, Variadic} │ │
|
||
│ └───────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─── 执行体 (恰好一个) ─────────────────────────────────────────┐ │
|
||
│ │ Invoke(Ctx, toolArgs) ← #830 过渡:单步派发(目标 mcpbind)│ │
|
||
│ │ Orchestrate(Ctx) ← #830 过渡:多步编排(目标 Handler)│ │
|
||
│ │ RunE(cmd, args) ← 逃生舱:完全自定义 │ │
|
||
│ └───────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌─── 钩子 ─────────────────────────────────────────────────────┐ │
|
||
│ │ Validate(cmd, args) ← 条件式业务校验(约束表达不了的) │ │
|
||
│ │ PostMount(cmd) ← 挂载收尾(设置 Args 等 cobra 属性) │ │
|
||
│ └───────────────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 构建与执行流
|
||
|
||
```
|
||
corecmd.Spec ──── corecmd.New() ────▶ cobra.Command ──── 用户执行 ────▶ Ctx
|
||
│ │ │
|
||
构建时检查: 注册产物: 执行上下文:
|
||
• validateDispatchDecl • Flags + Aliases • Str(name)
|
||
• validateSafetySpec • Annotations (Schema) • Int(name)
|
||
• validateContractDecl • Long (约束 help) • Bool(name)
|
||
• RegisterFlags • RunE (管线) • StrSlice(name)
|
||
• ValidateConstraintDecls • Changed(name)
|
||
• embedContractIntoSchema • DryRun() / Yes()
|
||
• AnnotateConstraints
|
||
• PostMount
|
||
```
|
||
|
||
## 领域概念
|
||
|
||
| 概念 | 类型 | 职责 |
|
||
|------|------|------|
|
||
| **corecmd.Spec** | struct | 一个命令的完整契约(声明 + 执行) |
|
||
| **FlagSpec** | struct | 一个参数的注册、回退链、绑定规则 |
|
||
| **Constraint** | struct | 参数间的关系约束 |
|
||
| **Safety** | contract.SafetySpec | 运行时与 Schema 共用的安全契约 |
|
||
| **ContractDecl** | struct | Agent 可见的完整工具规格声明 |
|
||
| **contract.SelectionSpec** | struct | Agent 选择该工具的语义指引 |
|
||
| **contract.InterfaceSpec** | struct | 工具的接口模式与可用性 |
|
||
| **contract.DryRunSpec** | struct | dry-run 能力声明 |
|
||
| **contract.ToolIdentitySpec** | struct | 工具在注册表中的身份标识 |
|
||
| **contract.RuntimeSchemaPositional** | struct | 有序位置参数声明 |
|
||
| **Ctx** | struct | 执行上下文(类型安全的 flag 读取) |
|
||
| **New** | func | 统一构建器(`corecmd.Spec` → `*cobra.Command`) |
|
||
|
||
## SafetySpec
|
||
|
||
`corecmd.Spec.Safety` 使用 `corecmd/contract.SafetySpec`(无 cli 类型别名),没有 command 自定义 Risk/Safety 枚举,也没有 `SafetyDecl` 覆盖层:
|
||
|
||
```go
|
||
Safety: contract.SafetySpec{
|
||
Effect: "write",
|
||
Risk: "high",
|
||
Confirmation: "user_required",
|
||
Idempotency: "unknown",
|
||
}
|
||
```
|
||
|
||
- `Confirmation == "user_required"` 时执行确认;`--yes` 和 `--dry-run` 可跳过交互。
|
||
- `Effect`、`Risk`、`Idempotency` 不参与确认决策,也不会改写 `Confirmation`。
|
||
- 任意一个字段非空时,四个字段必须全部显式声明;构建时拒绝部分声明。
|
||
- 完全空值仅作为历史只读默认,最终发布为 `read/low/not_required/idempotent`。
|
||
|
||
## FlagSpec 有效值回退链
|
||
|
||
框架统一的 flag 值解析顺序:
|
||
|
||
```
|
||
显式主 flag (Changed)
|
||
│ 空?
|
||
▼
|
||
隐藏别名 (Changed, 按声明序)
|
||
│ 空?
|
||
▼
|
||
环境变量 (EnvVar)
|
||
│ 空?
|
||
▼
|
||
注册默认值 (Default)
|
||
│ 空?
|
||
▼
|
||
ArgDefault (兜底)
|
||
```
|
||
|
||
各 Kind 的特殊行为:
|
||
|
||
| Kind | 入参条件 | 回退链 |
|
||
|------|----------|--------|
|
||
| KindString | 有效值非空(或 !OmitEmpty) | 完整参与 |
|
||
| KindInt | 值 ≠ 0(putInt 语义) | 完整参与 |
|
||
| KindBool | Changed 时入参(显式 false 也下发) | 不参与别名/env 回退 |
|
||
| KindStringSlice | 存在非空元素 | 仅 Changed 的主 flag/alias |
|
||
|
||
## Constraint 约束
|
||
|
||
声明式跨 flag 关系,构建时校验合法性,运行时统一执行:
|
||
|
||
| Kind | 语义 | 错误文案示例 |
|
||
|------|------|-------------|
|
||
| `at_least_one` | 至少提供一个 | "请至少指定 --a、--b 之一" |
|
||
| `exactly_one` | 恰好提供一个 | "请指定 --a、--b 之一" / "只能指定其一" |
|
||
| `mutually_exclusive` | 最多提供一个 | "参数 --a、--b 互斥,只能指定其一" |
|
||
|
||
"是否提供"的判定复用有效值回退链(显式主 flag → 别名 → env),注册默认值不算作已提供。
|
||
|
||
## ContractDecl 子结构
|
||
|
||
### contract.SelectionSpec(Agent 选择指引)
|
||
|
||
```go
|
||
contract.SelectionSpec{
|
||
AgentSummary: "一句话描述工具做什么",
|
||
UseWhen: []string{"在什么场景下应该选择这个工具"},
|
||
AvoidWhen: []string{"什么场景不应该用,应该用什么替代"},
|
||
Prerequisites: []string{"使用前提条件"},
|
||
Tips: []string{"使用技巧"},
|
||
Examples: []string{"dws dev app create --name Bot --dry-run"},
|
||
}
|
||
```
|
||
|
||
### contract.InterfaceSpec(接口模式)
|
||
|
||
```go
|
||
contract.InterfaceSpec{
|
||
Mode: "composite", // local / mcp / composite
|
||
Availability: "available", // available / unavailable
|
||
Reason: "...", // composite/unavailable 时的原因
|
||
Ref: &contract.InterfaceRefSpec{ // mcp 时的 ref
|
||
ProductID: "...",
|
||
RPCName: "...",
|
||
},
|
||
}
|
||
```
|
||
|
||
### contract.DryRunSpec(dry-run 能力)
|
||
|
||
```go
|
||
contract.DryRunSpec{
|
||
PreviewKind: "invocation", // invocation / request / plan
|
||
RemoteReads: false, // dry-run 时是否发起远端读
|
||
}
|
||
```
|
||
|
||
## 执行体三选一
|
||
|
||
| 执行体 | 适用场景 | 框架做了什么 |
|
||
|--------|----------|-------------|
|
||
| **Invoke** | #830 过渡单步派发(生产仍用;目标 mcpbind) | 框架完成 required→constraint→validate→buildArgs→confirm,传入装配好的 toolArgs |
|
||
| **Orchestrate** | #830 过渡多步编排(生产仍用;目标 Handler) | 框架完成 required→constraint→validate→confirm,传入 Ctx 自行组装调用 |
|
||
| **RunE** | 逃生舱 | 框架仍执行 Safety 确认,具体业务执行完全自定义 |
|
||
|
||
## 设计不变量
|
||
|
||
1. **一个 corecmd.Spec = 一个叶子命令的全部事实**
|
||
2. **声明面绝不调用后端**——command 是 dispatch-agnostic
|
||
3. **执行面绝不发明 CLI 表面**——业务 flag 必须在 Flags 声明
|
||
4. **构建时拦截 > 运行时报错**——声明错误 panic 在注册阶段
|
||
5. **SafetySpec 是单一事实源**——Confirmation 驱动运行时,其余字段原样进入 Schema
|
||
6. **声明即 review**——代码中的 Schema 经 code review 后直接投影,不依赖外部 hint 文件
|