From 286a84edcd26c6d652f76e6478c419e5c8da17a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=89=E6=BE=9C?= Date: Sun, 2 Aug 2026 00:10:03 +0800 Subject: [PATCH] refactor(cmdcore): move annotate/ContractFinal store under corecmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Break the remaining corecmd→cli reverse dependency by owning runtimeannotate and contractfinal on the framework side, with cli keeping thin re-exports. Document the three authoring tiers and that Shortcut may use DeclareLeafMetadata. Co-authored-by: Cursor --- AGENTS.md | 12 ++- CHANGELOG.md | 2 +- docs/command-framework-architecture.md | 16 +++- docs/rfc-command-framework-convergence.md | 26 ++++--- internal/cli/contractfinal/doc.go | 14 ++-- internal/cli/contractfinal/reexport.go | 28 +++++++ internal/cli/runtime_schema_seam.go | 7 +- internal/cli/runtimeannotate/doc.go | 16 ++-- internal/cli/runtimeannotate/reexport.go | 73 +++++++++++++++++++ internal/corecmd/contract/doc.go | 8 +- internal/corecmd/contract/final.go | 6 +- internal/corecmd/contract_decl_test.go | 2 +- .../corecmd/contract_register_seam_test.go | 50 ++++++++++++- internal/corecmd/contractfinal/doc.go | 24 ++++++ .../contractfinal/final_test.go | 8 +- .../contractfinal/homology.go | 2 +- .../contractfinal/homology_test.go | 4 +- .../contractfinal/param_decls.go | 2 +- .../{cli => corecmd}/contractfinal/store.go | 5 +- internal/corecmd/corecmd.go | 8 +- internal/corecmd/corecmd_test.go | 38 +++++++++- .../runtimeannotate/annotate.go | 0 .../runtimeannotate/annotations.go | 0 .../runtimeannotate/constraints.go | 0 internal/corecmd/runtimeannotate/doc.go | 28 +++++++ .../runtimeannotate/embed_test.go | 0 26 files changed, 318 insertions(+), 61 deletions(-) create mode 100644 internal/cli/contractfinal/reexport.go create mode 100644 internal/cli/runtimeannotate/reexport.go create mode 100644 internal/corecmd/contractfinal/doc.go rename internal/{cli => corecmd}/contractfinal/final_test.go (92%) rename internal/{cli => corecmd}/contractfinal/homology.go (93%) rename internal/{cli => corecmd}/contractfinal/homology_test.go (90%) rename internal/{cli => corecmd}/contractfinal/param_decls.go (96%) rename internal/{cli => corecmd}/contractfinal/store.go (91%) rename internal/{cli => corecmd}/runtimeannotate/annotate.go (100%) rename internal/{cli => corecmd}/runtimeannotate/annotations.go (100%) rename internal/{cli => corecmd}/runtimeannotate/constraints.go (100%) create mode 100644 internal/corecmd/runtimeannotate/doc.go rename internal/{cli => corecmd}/runtimeannotate/embed_test.go (100%) diff --git a/AGENTS.md b/AGENTS.md index 59f6bdca..8a202780 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,10 +33,16 @@ changes must not rewrite it mechanically. - `Safety` uses `contract.SafetySpec` (`internal/corecmd/contract` only — no `cli.*` type alias). Its `confirmation` drives the runtime gate; `effect` / `risk` / `idempotency` are published unchanged. When `Contract` is set, convert once via `contractfinal.RegisterRuntimeContractFinal` (framework) or `cli.RegisterRuntimeContractFinal` (product re-export); assembly **pass-throughs** Final. - Package seam: - types / ProductDecl → `corecmd/contract` (DTO only; **no** Cobra-keyed ContractFinal store) - - AnnotateRuntime* writers → `internal/cli/runtimeannotate` (`corecmd` may import; must **not** import `internal/cli` root) - - ContractFinal cobra store + Register → `internal/cli/contractfinal` + - AnnotateRuntime* writers → `internal/corecmd/runtimeannotate` (framework-owned) + - ContractFinal cobra store + Register → `internal/corecmd/contractfinal` (framework-owned) - homology gates → `internal/cli/homology` - - Catalog / `ResolveMeta` / go:embed → `internal/cli` root (thin re-exports of annotate/store APIs) + - Catalog / `ResolveMeta` / go:embed → `internal/cli` root (thin re-exports of annotate/store APIs; `cli/runtimeannotate` + `cli/contractfinal` are thin re-exports only) + - **Hard rule**: `internal/corecmd` (and its subpackages) must **not** import any `internal/cli` package +- Authoring tiers (current, not aspirational): + - **Tier1** — `corecmd.New` / `NewLeafCommand` (fully managed declare + execute) + - **Tier2** — `DeclareLeafMetadata` (helpers migration; **Shortcut may also use this path — acceptable**) + - **Tier3** — bare Cobra (should shrink over time; reviewed exclusions where needed) + - Long-term outlook only: broader mcpbind / fewer hand-written `Execute` bodies. **Not** a current hard requirement to delete `Shortcut.Execute` or force mcpbind. - Description declare vs delivery: construction requires `ContractDecl.Description` (evidence). Catalog delivery prefers Cobra Long → provenance `cobra_help`; without Long, declared text → `contract_final`. Title: declared first, then Short, then MCP. Do **not** read this as "declare = wire final" or dual authority. - **Execute** = hooks (`Validate` / `Call` / `RunE` / `PostMount`) — not a second surface authority - Declaration path has **no reviewed parallel fields**; migration-only `runtime_gate` annotate until `Safety` is declared diff --git a/CHANGELOG.md b/CHANGELOG.md index f467f322..b6820ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/) and th ### Changed - **Hints retired; ContractDecl is the leaf Schema source** (#830) — `schema_hints/`, Manual/Schema hint overlays, and `schema_agent_metadata/` delivery are removed. Selection, safety, parameters, and interface facts declare on ProductDecl / leaf `Contract` (`corecmd.ContractDecl` + `contract.ParamDecl` / `Safety`). Authoring renamed `SchemaDecl` → `ContractDecl`; nested fields reuse `contract.*` directly. -- **Contract package seam** (#830) — types / ProductDecl live under `internal/corecmd/contract` (DTO only). Annotate writers live in `internal/cli/runtimeannotate`; Cobra-keyed ContractFinal store + Register live in `internal/cli/contractfinal`; homology gates in `internal/cli/homology`. `cli` root keeps Catalog/`ResolveMeta` delivery and thin re-exports. `corecmd` no longer imports the `cli` root package (it may still depend on `cli` subpackages such as `contractfinal` / `runtimeannotate`). +- **Contract package seam** (#830) — types / ProductDecl live under `internal/corecmd/contract` (DTO only). Annotate writers live in `internal/corecmd/runtimeannotate`; Cobra-keyed ContractFinal store + Register live in `internal/corecmd/contractfinal`; homology gates in `internal/cli/homology`. `cli` root (and `cli/runtimeannotate` / `cli/contractfinal`) keep thin re-exports for product code; Catalog/`ResolveMeta` stay on the `cli` delivery root. `internal/corecmd` must not import any `internal/cli` package. - **CommandMeta index for ResolveMeta** (#830) — generation publishes `schema_meta_index.json` alongside Catalog ToolSpec wire under `schema_catalog/`. Runtime `ResolveMeta` / `SafetyForCLIPath` read the meta-index only and do not decode the full embedded Catalog. ### Fixed diff --git a/docs/command-framework-architecture.md b/docs/command-framework-architecture.md index f007c7da..e222240c 100644 --- a/docs/command-framework-architecture.md +++ b/docs/command-framework-architecture.md @@ -220,6 +220,16 @@ DeclareLeafMetadata(baseListCmd, LeafSpec{ `DeclareLeafMetadata` 调用 `corecmd.AttachContract` 挂 Safety+Contract;不注册 flag、不接管参数投影。可选 `Validate` 与 `ConfirmSafety` 同挂在 **RunE 包装器**内(不是 PreRunE)。当 `Safety.Confirmation=user_required` 时,用**同一份** SafetySpec 包一层 `ConfirmSafety`,保证执行门禁与 Catalog 同源;无 Validate 时确认推迟到 gated `CallTool`,成功返回却未确认则 fail-closed。迁移态入口;新命令仍应走 `NewLeafCommand`。 +### 三档路径(当前可接受) + +| 档 | 入口 | 说明 | +|---|---|---| +| **Tier1** | `corecmd.New` / `NewLeafCommand` | 完全托管:声明 + 执行都归框架 | +| **Tier2** | `DeclareLeafMetadata` | helpers 迁移态;**Shortcut 也可采用,可接受** | +| **Tier3** | 裸 Cobra | 应逐步收;新增裸叶需补声明或精确排除 | + +长期展望(非当前硬要求):更多 Shortcut 可收敛到 mcpbind / 减少仅为参数装配的 `Execute`。**不要**把「Shortcut 必须去掉 Execute / 必须 mcpbind」当作当前门禁;也不要否定 Shortcut + `DeclareLeafMetadata`。 + ### Shortcut(智能快捷方式,已接入 live mount) ```go @@ -239,6 +249,8 @@ spec := FromShortcut(Shortcut{ Shortcut 当前仍保留自身的 `Risk`,adapter 只在边界将它展开成完整 `contract.SafetySpec`;command/Leaf 不再保留该枚举。Shortcut 的 Cobra type/default/usage provenance 保持不变,command 统一补充 Required、Enum 和关系约束投影。 +需要补 Agent Schema 且执行体暂不迁入时,Shortcut 也可走 Tier2 +`DeclareLeafMetadata`(与 helpers 同一路径)。 ## 文件结构 @@ -247,8 +259,8 @@ type/default/usage provenance 保持不变,command 统一补充 Required、Enu | `internal/corecmd/corecmd.go` | 核心类型 + `New` 构建器 + 运行时管线 | | `internal/corecmd/contract_decl.go` | ContractDecl 载荷类型 + 声明完整性守卫 | | `internal/corecmd/contract/` | 契约 DTO(`SafetySpec` / `ParamDecl` / `ProductDecl` / `ContractFinalPayload`);**无** Cobra-keyed Final store | -| `internal/cli/runtimeannotate/` | `AnnotateRuntime*` 写注解;`corecmd` 可导入,**不得**反向依赖 `internal/cli` 根 | -| `internal/cli/contractfinal/` | ContractFinal Cobra store + `RegisterRuntimeContractFinal` | +| `internal/corecmd/runtimeannotate/` | `AnnotateRuntime*` 写注解(框架侧;`cli` 薄 re-export) | +| `internal/corecmd/contractfinal/` | ContractFinal Cobra store + `RegisterRuntimeContractFinal`(框架侧;`cli` 薄 re-export) | | `internal/cli/homology/` | flag/help/schema 同源门禁(`HOM-*`) | | `internal/helpers/leaf.go` | LeafSpec 门面:`NewLeafCommand`(完全托管)+ `DeclareLeafMetadata`(声明元数据) | | `internal/shortcut/adapter.go` | FromShortcut 完整映射与 Risk 兼容边界 | diff --git a/docs/rfc-command-framework-convergence.md b/docs/rfc-command-framework-convergence.md index fbb8b936..edb49dbd 100644 --- a/docs/rfc-command-framework-convergence.md +++ b/docs/rfc-command-framework-convergence.md @@ -274,11 +274,13 @@ Definition(仅声明;不可编译) | 层 | 含义 | 今日落点 | |---|---|---| | **声明(declare)** | `corecmd.Spec` / `LeafSpec` / `ContractDecl` **数据字段**(声明证据;交付见下) | `Flags`/`Constraints`/`Risk`/`ConstParams`/`Contract`;类型真身在 `corecmd/contract`(DTO:`SafetySpec`/`ParamDecl`/`ProductDecl`/`ContractFinalPayload`;**无** Cobra store) | -| **框架转换** | 类型转换并注册(**禁止** JSON 注解桥) | `embedContractDecl` → `contractfinal.RegisterRuntimeContractFinal`(annotate + store;产品代码经 `cli.RegisterRuntimeContractFinal` 薄 re-export) | -| **注解 seam** | Cobra `dws.schema.*` 写入 | `internal/cli/runtimeannotate.AnnotateRuntime*`(`corecmd` 可依赖;**不** import `cli` 根;cli 根薄 re-export) | -| **Schema 透传** / 交付 | 组装读取注册表,原样投影为 `ToolSpec`;Catalog embed / `ResolveMeta` | `internal/cli` 根(交付边界);ContractFinal store 在 `cli/contractfinal` | +| **框架转换** | 类型转换并注册(**禁止** JSON 注解桥) | `embedContractDecl` → `corecmd/contractfinal.RegisterRuntimeContractFinal`(annotate + store;产品代码经 `cli.RegisterRuntimeContractFinal` 薄 re-export) | +| **注解 seam** | Cobra `dws.schema.*` 写入 | `internal/corecmd/runtimeannotate.AnnotateRuntime*`(框架侧;`cli` / `cli/runtimeannotate` 仅薄 re-export) | +| **Schema 透传** / 交付 | 组装读取注册表,原样投影为 `ToolSpec`;Catalog embed / `ResolveMeta` | `internal/cli` 根(交付边界);ContractFinal store 在 `corecmd/contractfinal`(`cli/contractfinal` 薄 re-export) | | **执行(execute)** | 钩子不发明表面 | `Validate` / `Call` / `RunE` / `PostMount` | +依赖方向硬规则:`internal/corecmd`(含子包)**不得** import 任何 `internal/cli` 包;annotate 与 ContractFinal store 归属框架侧。 + 硬规则: 1. 声明体系**不含评审并行字段**;hooks 不算声明。声明载荷携带 reviewed 字段(如 `Selection.Reviewed`)组装直接报错——`Reviewed` 是旧路径(hints/registry)专用标记。 @@ -308,21 +310,23 @@ Definition(仅声明;不可编译) 3. 写副作用:新 Leaf 声明完整 `SafetySpec`(框架 `ConfirmSafety` + Schema Final);未迁移旧路径显式标注 `runtime_gate`;二者皆无则不合格; 4. Schema `ToolSpec` 全字段组均落在 §5.0.4 表中某一权威格,禁止无主字段。 -#### 5.0.2a 两种声明模式(完全托管 / 声明元数据) +#### 5.0.2a 三档声明路径(Tier1 / Tier2 / Tier3) -`LeafSpec` 词汇对应两种正式构建入口,产出同一 `ContractFinal`: +当前生产允许的三档路径(同一 `ContractFinal` 语义;不是互相否定): -| 模式 | 入口 | 声明面 | 执行面 | 适用 | +| 档 | 入口 | 声明面 | 执行面 | 适用 | |---|---|---|---|---| -| **完全托管** | `NewLeafCommand(spec)` | Flags/Constraints/Safety/Contract 全进 command | 框架接管 flag 注册、参数投影、`ConfirmSafety`、派发 | 新命令;可自由设计执行面 | -| **声明元数据** | `DeclareLeafMetadata(cmd, spec)` | 仅 `Safety` + `Contract`(经 `AttachContract`) | **不**注册 flag、**不**接管参数投影;可选 `Validate` 与 `ConfirmSafety` **同挂 RunE 包装器**(Validate 在前);无 Validate 时确认推迟到首次 `CallTool` | 既有命令补声明且执行体必须冻结 | +| **Tier1 完全托管** | `corecmd.New` / `NewLeafCommand(spec)` | Flags/Constraints/Safety/Contract 全进 command | 框架接管 flag 注册、参数投影、`ConfirmSafety`、派发 | 新命令;可自由设计执行面 | +| **Tier2 声明元数据** | `DeclareLeafMetadata(cmd, spec)` | 仅 `Safety` + `Contract`(经 `AttachContract`) | **不**注册 flag、**不**接管参数投影;可选 `Validate` 与 `ConfirmSafety` **同挂 RunE 包装器**(Validate 在前);无 Validate 时确认推迟到首次 `CallTool` | helpers 迁移态;**Shortcut 也可采用此路径,可接受** | +| **Tier3 裸 Cobra** | 手写 `*cobra.Command` | 无框架声明(或仅 annotate / 评审排除) | 调用方自管 | 应逐步收;新增裸叶需迁移元数据或精确排除 | 选用规则: -1. 新命令默认走完全托管模式。 -2. 既有命令要补 Agent Schema、但执行面暂时不能迁入 LeafSpec → 声明元数据模式;声明必须写在命令字面量旁(与完全托管同一作者点),禁止旁路 generated map。 -3. 声明元数据模式是**迁移态**:具备条件后应升级为完全托管;不得用它绕开「业务 flag 必须声明」的框架纪律。 +1. 新命令默认走 Tier1 完全托管。 +2. 既有 helpers / Shortcut 要补 Agent Schema、但执行面暂时不能迁入 LeafSpec → Tier2;声明必须写在命令字面量旁,禁止旁路 generated map。**Shortcut + `DeclareLeafMetadata` 是合法当前路径**,不得在文档或评审里否定。 +3. Tier2 是**迁移态**:具备条件后应升级为 Tier1;不得用它绕开「业务 flag 必须声明」的框架纪律。大规模 Tier2→Tier1 与 Shortcut→mcpbind 属于后续里程碑,**不是**本阶段硬门槛。 4. `DeclareLeafMetadata` 传入 Flags/Call/RunE/PostMount 等执行面字段直接 panic,防止半接管;**唯一允许的执行钩子是 `Validate`**,与 `ConfirmSafety` 同在 RunE 层(禁止只挂 PreRunE——直接调 `RunE` / `proxySubCmd` 会跳过 PreRunE)。无独立 Caller 的本地/PAT 命令必须提供 `Validate`,否则会回退成确认抢先。 +5. **长期展望**(非当前硬要求):更多 Shortcut 可收敛到 mcpbind 形态 1/2,并减少仅为参数装配而存在的 `Execute` 函数体。不得把「必须删除 `Shortcut.Execute` / 必须 mcpbind」写成当前门禁。 #### 5.0.3 与目标 `Contract` 的对应 diff --git a/internal/cli/contractfinal/doc.go b/internal/cli/contractfinal/doc.go index f32ccc8d..f72d78c5 100644 --- a/internal/cli/contractfinal/doc.go +++ b/internal/cli/contractfinal/doc.go @@ -11,13 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package contractfinal owns the Cobra-keyed ContractFinal runtime store and -// the annotate+store registration seam. +// Package contractfinal is a thin delivery-facing re-export of +// internal/corecmd/contractfinal. // -// Production product / helper / shortcut code should call -// cli.RegisterRuntimeContractFinal (thin re-export). Framework code -// (corecmd.AttachContract) calls RegisterRuntimeContractFinal here directly -// so internal/corecmd never imports the cli root delivery package. -// -// Types remain in internal/corecmd/contract (DTO only — no cobra store). +// Ownership of the Cobra-keyed ContractFinal store and Register seam lives +// under the command framework. Production product code should prefer +// cli.RegisterRuntimeContractFinal; framework code calls +// corecmd/contractfinal.RegisterRuntimeContractFinal directly. package contractfinal diff --git a/internal/cli/contractfinal/reexport.go b/internal/cli/contractfinal/reexport.go new file mode 100644 index 00000000..dbe7d997 --- /dev/null +++ b/internal/cli/contractfinal/reexport.go @@ -0,0 +1,28 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package contractfinal + +import ( + corecf "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal" +) + +var ( + RegisterRuntimeContractFinal = corecf.RegisterRuntimeContractFinal + RuntimeContractFinal = corecf.RuntimeContractFinal + HasRuntimeContractFinal = corecf.HasRuntimeContractFinal + ClearRuntimeContractFinalForTest = corecf.ClearRuntimeContractFinalForTest + StoreRuntimeContractFinalRawForTest = corecf.StoreRuntimeContractFinalRawForTest + ApplyParamDecls = corecf.ApplyParamDecls + HasDeclaredOrAnnotatedConfirmation = corecf.HasDeclaredOrAnnotatedConfirmation +) diff --git a/internal/cli/runtime_schema_seam.go b/internal/cli/runtime_schema_seam.go index 301cbac3..804c0ce9 100644 --- a/internal/cli/runtime_schema_seam.go +++ b/internal/cli/runtime_schema_seam.go @@ -23,8 +23,9 @@ import ( ) // Thin re-exports so helpers / app / shortcut keep the cli. prefix while the -// implementations live in focused subpackages. corecmd imports those -// subpackages directly and must not import this delivery root. +// implementations live under internal/corecmd/{runtimeannotate,contractfinal}. +// Framework code imports those packages directly; this delivery root and the +// cli/* thin re-export subpackages only consume them. type RuntimeSchemaConstraints = runtimeannotate.RuntimeSchemaConstraints @@ -59,7 +60,7 @@ var ( // RegisterRuntimeContractFinal is the sole production registration entry for // product / helper / shortcut code (annotate + store). Framework code calls -// contractfinal.RegisterRuntimeContractFinal directly. +// corecmd/contractfinal.RegisterRuntimeContractFinal directly. func RegisterRuntimeContractFinal(cmd *cobra.Command, payload contract.ContractFinalPayload) { contractfinal.RegisterRuntimeContractFinal(cmd, payload) } diff --git a/internal/cli/runtimeannotate/doc.go b/internal/cli/runtimeannotate/doc.go index 11c4b203..32b296e8 100644 --- a/internal/cli/runtimeannotate/doc.go +++ b/internal/cli/runtimeannotate/doc.go @@ -11,16 +11,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package runtimeannotate owns Cobra dws.schema.* annotation writers and the -// RuntimeSchemaConstraints helpers used by the command framework. +// Package runtimeannotate is a thin delivery-facing re-export of +// internal/corecmd/runtimeannotate. // -// Package boundary: -// -// - Types / DTO / ProductDecl → internal/corecmd/contract -// - AnnotateRuntime* writers (this package) — no Catalog / go:embed -// - ContractFinal cobra store + Register seam → internal/cli/contractfinal -// - Catalog assembly / ResolveMeta / go:embed → internal/cli (root) -// -// Dependency direction: corecmd and cli/contractfinal import this package; -// this package must never import internal/cli (root) or contractfinal. +// Ownership of AnnotateRuntime* writers lives under the command framework. +// Prefer cli root re-exports for product code; this subpackage exists so +// existing import paths keep resolving to the same single implementation. package runtimeannotate diff --git a/internal/cli/runtimeannotate/reexport.go b/internal/cli/runtimeannotate/reexport.go new file mode 100644 index 00000000..32a60cae --- /dev/null +++ b/internal/cli/runtimeannotate/reexport.go @@ -0,0 +1,73 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runtimeannotate + +import ( + coreann "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate" +) + +const ( + AnnotationProduct = coreann.AnnotationProduct + AnnotationTool = coreann.AnnotationTool + AnnotationSource = coreann.AnnotationSource + AnnotationTitle = coreann.AnnotationTitle + AnnotationDescription = coreann.AnnotationDescription + AnnotationMetaSource = coreann.AnnotationMetaSource + AnnotationExclude = coreann.AnnotationExclude + AnnotationConstraints = coreann.AnnotationConstraints + AnnotationPositionals = coreann.AnnotationPositionals + AnnotationContract = coreann.AnnotationContract + AnnotationRisk = coreann.AnnotationRisk + AnnotationRuntimeGate = coreann.AnnotationRuntimeGate + AnnotationFlagProperty = coreann.AnnotationFlagProperty + AnnotationFlagType = coreann.AnnotationFlagType + AnnotationFlagRequired = coreann.AnnotationFlagRequired + AnnotationFlagReqWhen = coreann.AnnotationFlagReqWhen + AnnotationFlagExample = coreann.AnnotationFlagExample + AnnotationFlagFormat = coreann.AnnotationFlagFormat + AnnotationFlagEnum = coreann.AnnotationFlagEnum +) + +type RuntimeSchemaConstraints = coreann.RuntimeSchemaConstraints + +var ( + AttachRuntimeSchema = coreann.AttachRuntimeSchema + AnnotateRuntimeToolMetadata = coreann.AnnotateRuntimeToolMetadata + AnnotateRuntimeFlag = coreann.AnnotateRuntimeFlag + AnnotateRuntimeFlagProperty = coreann.AnnotateRuntimeFlagProperty + AnnotateRuntimeRequiredFlags = coreann.AnnotateRuntimeRequiredFlags + AnnotateRuntimeFlagRequiredValue = coreann.AnnotateRuntimeFlagRequiredValue + AnnotateRuntimeFlagDescription = coreann.AnnotateRuntimeFlagDescription + AnnotateRuntimeFlagRequiredWhen = coreann.AnnotateRuntimeFlagRequiredWhen + AnnotateRuntimeFlagFormat = coreann.AnnotateRuntimeFlagFormat + AnnotateRuntimeFlagInterfaceType = coreann.AnnotateRuntimeFlagInterfaceType + AnnotateRuntimeFlagEnum = coreann.AnnotateRuntimeFlagEnum + AnnotateRuntimeFlagExample = coreann.AnnotateRuntimeFlagExample + AnnotateRuntimeContract = coreann.AnnotateRuntimeContract + AnnotateRuntimeRisk = coreann.AnnotateRuntimeRisk + AnnotateRuntimeGate = coreann.AnnotateRuntimeGate + RuntimeContractRisk = coreann.RuntimeContractRisk + RuntimeContractGate = coreann.RuntimeContractGate + AnnotateRuntimeConstraints = coreann.AnnotateRuntimeConstraints + CommandConstraints = coreann.CommandConstraints + NormalizeConstraints = coreann.NormalizeConstraints + ConstraintsEmpty = coreann.ConstraintsEmpty + AnnotateRuntimePositionals = coreann.AnnotateRuntimePositionals + CommandPositionals = coreann.CommandPositionals + ExcludeFromRuntimeSchema = coreann.ExcludeFromRuntimeSchema + CommandFlag = coreann.CommandFlag + SetCommandAnnotation = coreann.SetCommandAnnotation + SetFlagAnnotation = coreann.SetFlagAnnotation + SetFlagAnnotationValues = coreann.SetFlagAnnotationValues +) diff --git a/internal/corecmd/contract/doc.go b/internal/corecmd/contract/doc.go index bcf743ea..21f32da9 100644 --- a/internal/corecmd/contract/doc.go +++ b/internal/corecmd/contract/doc.go @@ -16,7 +16,7 @@ // stores and Catalog / go:embed delivery code. // // This package is the sole definition point for: -// - ContractFinalPayload (DTO only — store lives in cli/contractfinal) +// - ContractFinalPayload (DTO only — store lives in corecmd/contractfinal) // - ProductDecl (+ string-keyed registry; not Cobra-keyed) // - SafetySpec / SelectionSpec / InterfaceSpec / DryRunSpec / identity / // positionals / ParamDecl @@ -28,9 +28,9 @@ // - Authoring wrapper → corecmd.ContractDecl (leaf-facing; nested fields are // these contract types). Name is ContractDecl, not SchemaDecl: "Schema" in // this repo means Catalog / ToolSpec delivery, not the author declaration. -// - AnnotateRuntime* writers → internal/cli/runtimeannotate -// (corecmd may import; must not import cli root). -// - ContractFinal cobra store + Register seam → internal/cli/contractfinal +// - AnnotateRuntime* writers → internal/corecmd/runtimeannotate +// (framework-owned; cli may thin re-export; corecmd must not import cli). +// - ContractFinal cobra store + Register seam → internal/corecmd/contractfinal // (framework calls RegisterRuntimeContractFinal here; product code uses // cli.RegisterRuntimeContractFinal re-export). // - Catalog assembly / ResolveMeta / go:embed → internal/cli (delivery root). diff --git a/internal/corecmd/contract/final.go b/internal/corecmd/contract/final.go index 523b67f7..b86ab01e 100644 --- a/internal/corecmd/contract/final.go +++ b/internal/corecmd/contract/final.go @@ -14,13 +14,13 @@ package contract // ContractFinalPayload is the Contract-authored final Schema leaf overlay. -// Registered in-process by the framework via the cli/contractfinal seam; +// Registered in-process by the framework via the corecmd/contractfinal seam; // Schema assembly reads it as pass-through. No JSON bridge. Treat as // read-only after Register. // // The Cobra-keyed runtime store and Register live in -// internal/cli/contractfinal (not this DTO package). AnnotateRuntime* -// writers live in internal/cli/runtimeannotate. Production product code +// internal/corecmd/contractfinal (not this DTO package). AnnotateRuntime* +// writers live in internal/corecmd/runtimeannotate. Production product code // registers through cli.RegisterRuntimeContractFinal (thin re-export); // framework code calls contractfinal.RegisterRuntimeContractFinal directly. type ContractFinalPayload struct { diff --git a/internal/corecmd/contract_decl_test.go b/internal/corecmd/contract_decl_test.go index 0978f303..03459dc2 100644 --- a/internal/corecmd/contract_decl_test.go +++ b/internal/corecmd/contract_decl_test.go @@ -17,7 +17,7 @@ import ( "strings" "testing" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/contractfinal" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" ) diff --git a/internal/corecmd/contract_register_seam_test.go b/internal/corecmd/contract_register_seam_test.go index 22faf95a..a19c9dd8 100644 --- a/internal/corecmd/contract_register_seam_test.go +++ b/internal/corecmd/contract_register_seam_test.go @@ -22,7 +22,8 @@ import ( ) // Production framework code must register ContractFinal through the -// contractfinal annotate+store seam, not by importing the cli delivery root. +// corecmd/contractfinal annotate+store seam, and must never import any +// internal/cli package (root or subpackage). func TestAttachContractUsesContractFinalRegisterSeam(t *testing.T) { _, thisFile, _, ok := runtime.Caller(0) if !ok { @@ -37,10 +38,51 @@ func TestAttachContractUsesContractFinalRegisterSeam(t *testing.T) { if !strings.Contains(body, "contractfinal.RegisterRuntimeContractFinal(") { t.Fatal("AttachContract/New must call contractfinal.RegisterRuntimeContractFinal") } - if strings.Contains(body, `"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"`) { - t.Fatal("corecmd must not import internal/cli delivery root") - } if strings.Contains(body, "cli.RegisterRuntimeContractFinal(") { t.Fatal("corecmd must not call cli.RegisterRuntimeContractFinal; use contractfinal seam") } + // Build the forbidden import prefix without embedding it as a contiguous + // literal in this test file (the walker below must not self-match). + forbidden := strings.Join([]string{ + `"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/`, + `internal/cli`, + }, "") + if strings.Contains(body, forbidden) { + t.Fatal("corecmd must not import any internal/cli package") + } +} + +func TestCorecmdPackageImportsForbidCLI(t *testing.T) { + _, thisFile, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller failed") + } + root := filepath.Dir(thisFile) + forbidden := strings.Join([]string{ + `"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/`, + `internal/cli`, + }, "") + err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(d.Name(), ".go") { + return nil + } + raw, readErr := os.ReadFile(path) + if readErr != nil { + return readErr + } + if strings.Contains(string(raw), forbidden) { + rel, _ := filepath.Rel(root, path) + t.Errorf("%s must not import internal/cli", rel) + } + return nil + }) + if err != nil { + t.Fatalf("walk corecmd: %v", err) + } } diff --git a/internal/corecmd/contractfinal/doc.go b/internal/corecmd/contractfinal/doc.go new file mode 100644 index 00000000..9389774e --- /dev/null +++ b/internal/corecmd/contractfinal/doc.go @@ -0,0 +1,24 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package contractfinal owns the Cobra-keyed ContractFinal runtime store and +// the annotate+store registration seam. +// +// Production product / helper / shortcut code should call +// cli.RegisterRuntimeContractFinal (thin re-export). Framework code +// (corecmd.AttachContract) calls RegisterRuntimeContractFinal here directly +// so internal/corecmd never imports any internal/cli package. +// +// Types remain in internal/corecmd/contract (DTO only — no cobra store). +// Annotate writers live in internal/corecmd/runtimeannotate. +package contractfinal diff --git a/internal/cli/contractfinal/final_test.go b/internal/corecmd/contractfinal/final_test.go similarity index 92% rename from internal/cli/contractfinal/final_test.go rename to internal/corecmd/contractfinal/final_test.go index bf072d1b..31397ad1 100644 --- a/internal/cli/contractfinal/final_test.go +++ b/internal/corecmd/contractfinal/final_test.go @@ -56,6 +56,12 @@ func TestCrossPlatformCoverageContractFinalNilCommandGuards(t *testing.T) { if HasRuntimeContractFinal(nil) { t.Fatal("HasRuntimeContractFinal(nil) must be false") } + registered := &cobra.Command{Use: "registered"} + t.Cleanup(func() { ClearRuntimeContractFinalForTest(registered) }) + RegisterRuntimeContractFinal(registered, contract.ContractFinalPayload{Title: "T"}) + if !HasRuntimeContractFinal(registered) { + t.Fatal("HasRuntimeContractFinal must be true after registration") + } ClearRuntimeContractFinalForTest(nil) } @@ -91,7 +97,7 @@ func TestCrossPlatformCoverageApplyParamDeclsSkipsBlankAndAnnotatesEnum(t *testi } } -func TestApplyParamDeclsRejectsUnknownFlag(t *testing.T) { +func TestCrossPlatformCoverageApplyParamDeclsRejectsUnknownFlag(t *testing.T) { cmd := &cobra.Command{Use: "apply-params"} cmd.Flags().String("mode", "", "mode") err := ApplyParamDecls(cmd, []contract.ParamDecl{ diff --git a/internal/cli/contractfinal/homology.go b/internal/corecmd/contractfinal/homology.go similarity index 93% rename from internal/cli/contractfinal/homology.go rename to internal/corecmd/contractfinal/homology.go index 11bc339c..725ca370 100644 --- a/internal/cli/contractfinal/homology.go +++ b/internal/corecmd/contractfinal/homology.go @@ -18,7 +18,7 @@ import ( "github.com/spf13/cobra" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/runtimeannotate" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate" ) // HasDeclaredOrAnnotatedConfirmation reports whether confirmation semantics are diff --git a/internal/cli/contractfinal/homology_test.go b/internal/corecmd/contractfinal/homology_test.go similarity index 90% rename from internal/cli/contractfinal/homology_test.go rename to internal/corecmd/contractfinal/homology_test.go index 29041065..c7b69095 100644 --- a/internal/cli/contractfinal/homology_test.go +++ b/internal/corecmd/contractfinal/homology_test.go @@ -18,11 +18,11 @@ import ( "github.com/spf13/cobra" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/runtimeannotate" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate" ) -func TestHasDeclaredOrAnnotatedConfirmationBranches(t *testing.T) { +func TestCrossPlatformCoverageHasDeclaredOrAnnotatedConfirmationBranches(t *testing.T) { bare := &cobra.Command{Use: "bare"} if HasDeclaredOrAnnotatedConfirmation(bare) { t.Fatal("bare command must not claim confirmation coverage") diff --git a/internal/cli/contractfinal/param_decls.go b/internal/corecmd/contractfinal/param_decls.go similarity index 96% rename from internal/cli/contractfinal/param_decls.go rename to internal/corecmd/contractfinal/param_decls.go index 3ef3c509..e7306473 100644 --- a/internal/cli/contractfinal/param_decls.go +++ b/internal/corecmd/contractfinal/param_decls.go @@ -19,8 +19,8 @@ import ( "github.com/spf13/cobra" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/runtimeannotate" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate" ) // ApplyParamDecls emits parameter declarations as dws.schema.* annotations on the diff --git a/internal/cli/contractfinal/store.go b/internal/corecmd/contractfinal/store.go similarity index 91% rename from internal/cli/contractfinal/store.go rename to internal/corecmd/contractfinal/store.go index 5ca32df1..ded230e3 100644 --- a/internal/cli/contractfinal/store.go +++ b/internal/corecmd/contractfinal/store.go @@ -18,8 +18,8 @@ import ( "github.com/spf13/cobra" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/runtimeannotate" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate" ) var contractFinalByCommand sync.Map // *cobra.Command → *contract.ContractFinalPayload @@ -27,7 +27,8 @@ var contractFinalByCommand sync.Map // *cobra.Command → *contract.ContractFina // RegisterRuntimeContractFinal annotates dws.schema.contract then stores the // typed final Schema overlay. This is the atomic annotate+store implementation. // -// cli.RegisterRuntimeContractFinal is the delivery-facing name for product code; +// Ownership lives under the command framework (this package). Product code +// should call cli.RegisterRuntimeContractFinal (thin re-export); // corecmd.AttachContract calls this function directly. func RegisterRuntimeContractFinal(cmd *cobra.Command, payload contract.ContractFinalPayload) { if cmd == nil { diff --git a/internal/corecmd/corecmd.go b/internal/corecmd/corecmd.go index c910baa0..6ecfd646 100644 --- a/internal/corecmd/corecmd.go +++ b/internal/corecmd/corecmd.go @@ -59,9 +59,9 @@ import ( "github.com/mattn/go-isatty" "github.com/spf13/cobra" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/contractfinal" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/runtimeannotate" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate" apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/cmdutil" ) @@ -1263,6 +1263,10 @@ func AttachContract(cmd *cobra.Command, safety contract.SafetySpec, decl Contrac len(sel.Tips) > 0 || len(sel.WorkflowRefs) > 0 { copied := sel copied.AgentSummary = strings.TrimSpace(sel.AgentSummary) + copied.AgentSummarySource = "corecmd.ContractDecl" + copied.SourceRefs = []string{"corecmd.ContractDecl"} + copied.MetadataSource = "corecmd.contract" + copied.Reviewed = nil payload.Selection = &copied } if id := decl.Identity; strings.TrimSpace(id.ProductID) != "" || strings.TrimSpace(id.Name) != "" { diff --git a/internal/corecmd/corecmd_test.go b/internal/corecmd/corecmd_test.go index 7d5b70e9..e239d5dc 100644 --- a/internal/corecmd/corecmd_test.go +++ b/internal/corecmd/corecmd_test.go @@ -20,7 +20,7 @@ import ( "strings" "testing" - "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli/contractfinal" + "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract" apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors" "github.com/spf13/cobra" @@ -1576,6 +1576,33 @@ func TestCrossPlatformCoverageAttachContractNilAndEmptyGuards(t *testing.T) { } } +func TestCrossPlatformCoverageAttachContractOverwritesLegacySelectionSources(t *testing.T) { + cmd := newTestCommand() + AttachContract(cmd, testWriteSafety(), ContractDecl{ + Description: "Declared description for attach coverage", + Interface: &contract.InterfaceSpec{Mode: "mcp", Availability: "available", Ref: &contract.InterfaceRefSpec{ProductID: "dev", RPCName: "op"}}, + Selection: contract.SelectionSpec{ + AgentSummary: "Declared summary", + UseWhen: []string{"use declared"}, + AvoidWhen: []string{"avoid declared"}, + Examples: []string{"dws t"}, + AgentSummarySource: "schema_hints/legacy", + SourceRefs: []string{"schema_hints/selection/sample.json"}, + MetadataSource: "schema_hints", + }, + }, "short", "long") + payload, ok := contractfinal.RuntimeContractFinal(cmd) + if !ok || payload.Selection == nil { + t.Fatal("expected ContractFinal selection payload") + } + sel := payload.Selection + if sel.AgentSummarySource != "corecmd.ContractDecl" || + len(sel.SourceRefs) != 1 || sel.SourceRefs[0] != "corecmd.ContractDecl" || + sel.MetadataSource != "corecmd.contract" || sel.Reviewed != nil { + t.Fatalf("selection sources = %#v", sel) + } +} + func TestCrossPlatformCoverageEffectiveSafetySpecZeroValueDefaultsToRead(t *testing.T) { got := effectiveSafetySpec(contract.SafetySpec{}) want := contract.SafetySpec{Effect: "read", Risk: "low", Confirmation: "not_required", Idempotency: "idempotent"} @@ -1610,3 +1637,12 @@ func TestCrossPlatformCoverageEmbedContractSkipsBlankAndHiddenFlags(t *testing.T } } } + +func TestCrossPlatformCoverageFirstNonEmpty(t *testing.T) { + if got := firstNonEmpty("", " ", "value", "later"); got != "value" { + t.Fatalf("firstNonEmpty = %q", got) + } + if got := firstNonEmpty("", " "); got != "" { + t.Fatalf("firstNonEmpty empty = %q", got) + } +} diff --git a/internal/cli/runtimeannotate/annotate.go b/internal/corecmd/runtimeannotate/annotate.go similarity index 100% rename from internal/cli/runtimeannotate/annotate.go rename to internal/corecmd/runtimeannotate/annotate.go diff --git a/internal/cli/runtimeannotate/annotations.go b/internal/corecmd/runtimeannotate/annotations.go similarity index 100% rename from internal/cli/runtimeannotate/annotations.go rename to internal/corecmd/runtimeannotate/annotations.go diff --git a/internal/cli/runtimeannotate/constraints.go b/internal/corecmd/runtimeannotate/constraints.go similarity index 100% rename from internal/cli/runtimeannotate/constraints.go rename to internal/corecmd/runtimeannotate/constraints.go diff --git a/internal/corecmd/runtimeannotate/doc.go b/internal/corecmd/runtimeannotate/doc.go new file mode 100644 index 00000000..2d17fd3e --- /dev/null +++ b/internal/corecmd/runtimeannotate/doc.go @@ -0,0 +1,28 @@ +// Copyright 2026 Alibaba Group +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package runtimeannotate owns Cobra dws.schema.* annotation writers and the +// RuntimeSchemaConstraints helpers used by the command framework. +// +// Package boundary: +// +// - Types / DTO / ProductDecl → internal/corecmd/contract +// - AnnotateRuntime* writers (this package) — no Catalog / go:embed +// - ContractFinal cobra store + Register seam → internal/corecmd/contractfinal +// - Catalog assembly / ResolveMeta / go:embed → internal/cli (root) +// +// Dependency direction: corecmd and corecmd/contractfinal import this package. +// This package must never import internal/cli (root or subpackages). +// Product / delivery code may consume via cli root thin re-exports or +// internal/cli/runtimeannotate (thin re-export of this package). +package runtimeannotate diff --git a/internal/cli/runtimeannotate/embed_test.go b/internal/corecmd/runtimeannotate/embed_test.go similarity index 100% rename from internal/cli/runtimeannotate/embed_test.go rename to internal/corecmd/runtimeannotate/embed_test.go