Skip to content

feat(helpers): declarative LeafSpec command framework + devapp migration#676

Open
typefield wants to merge 4 commits into
DingTalk-Real-AI:mainfrom
typefield:feat/command-surface-naming
Open

feat(helpers): declarative LeafSpec command framework + devapp migration#676
typefield wants to merge 4 commits into
DingTalk-Real-AI:mainfrom
typefield:feat/command-surface-naming

Conversation

@typefield

Copy link
Copy Markdown
Contributor

概述

引入 LeafSpec 声明式命令框架(internal/helpers/leaf.go),并把 devapp(dingtalk-dev)产品的 28/31 个命令迁移到该框架,统一此前各命令手写的 flag 注册、required 校验、别名/env 回退、值转换、toolArgs 装配与派发。命令名/flag/行为零变化(catalog drift 全程零漂移)。

框架(LeafSpec)

  • NewLeafCommand(LeafSpec{...}):一条声明 = 一个命令。
  • 字段:Flags(LeafString/LeafInt64/LeafInt;Required/MarkRequired/Aliases/EnvVar/ArgDefault/Bind/Transform/OmitEmpty/Trim)、Validate(跨 flag 钩子)、RunE(逃生舱)、PostMount(挂载后钩子)、Call(可插拔派发)、Server
  • 横切收敛:required 校验、主 flag→别名→env 回退、toolArgs 装配、callMCPTool 默认派发,各一份。
  • 附带:internal/cli/schema_catalog_structure.go catalog 封闭结构校验 + check-command-surface.sh 硬门禁。

devapp 迁移(28/31)

devapp 走 executor.Runner 而非 callMCPTool,通过 Call 注入 runDevAppTool 复用框架。迁移:credentials/lifecycle/member/version/webapp/robot/permission/event/security/app 系列(见各 commit)。
保留手写 4 个(app delete 多步二次确认、robot submit/result 多步编排、robot config 自定义构造)——非声明式范畴,已加注释标注原因。

优化 + 测试

  • devAppCall/devAppCallCursor/devAppMeta 工厂,折叠 33 处重复闭包。
  • 删死代码 buildDevAppGetParamsrunDevAppMemberMutation
  • fakeDevAppRunner(fake executor.Runner)捕获派发的 toolArgs,补齐运行时 toolArgs 等价断言(drift 只覆盖 flag 层)——含表驱动覆盖所有迁移命令 + OmitEmpty 缺席断言。

验证

  • check-generated-drift.sh 全程 ok(registry_hash 不变)——flag 层零漂移。
  • go test ./internal/helpers 全量绿;make policy 全链路绿(rebase 到 upstream/main e69a108 后复核)。

不做(边界)

  • 不给 LeafSpec 加 LeafBool/Changed/嵌套信封(为 4 个复杂命令不值,框架膨胀)。
  • 不改任何命令名/flag/行为。

@typefield
typefield force-pushed the feat/command-surface-naming branch 4 times, most recently from 5645fe1 to 8d29f48 Compare July 18, 2026 14:38
…umption separation

== LeafSpec command framework (internal/helpers/leaf.go) ==
Declarative command construction: LeafSpec/LeafFlag/NewLeafCommand with
Call (pluggable dispatch), LeafInt, PostMount, Trim, Validate. Collapses
per-command hand-written required validation, alias/env fallback, value
transform, and toolArgs assembly into one declarative path.

== devapp migration (28/31 commands) ==
All MCP-direct devapp leaf commands migrated to LeafSpec. Factories
(devAppCall/devAppCallCursor/devAppMeta) fold 33 repeated closures.
fakeDevAppRunner asserts toolArgs for every migrated command. 4 complex
commands (delete/robot submit/result/config) kept hand-written.

== Schema generation/consumption separation ==
- gen.go: isolated //go:generate pragmas from business code.
- command_meta.go: ResolveMeta(cliPath) -> CommandMeta{Identity,Safety,Selection}.
- command_safety.go: SafetyForCLIPath + RenderSafetyAnnotation; safety metadata
  flows from embedded catalog into --help output.
- calendar.go HelpFunc fix: delegates to root HelpFunc at help-time.
- schema_catalog_structure.go: closed catalog structure validation gate.

== Registry + catalog per-product sharding ==
schema_command_registry and schema_catalog split into per-product shards,
eliminating concurrent-PR merge conflicts on these files.

== MCP metadata refresh tool ==
cmd/fetch_mcp_metadata: iterates 26 MCP server endpoints, merges with previous
data for cross-server interface_ref. make fetch-mcp-metadata target.

== AGENTS.md ==
Documents the generation/consumption split.

Verified: make policy exit 0, drift zero, all tests pass.
@typefield

Copy link
Copy Markdown
Contributor Author

已基于合并最新 main 后的提交 c76c30a0 完成 review。LeafSpec/devapp 迁移的等价性测试、make policy 和全量 go test ./... 均通过,但有以下问题建议合并前修正:

  1. [P1] MCP 元数据刷新读取了已删除的 Registry 文件
    cmd/fetch_mcp_metadata/main.go:185 仍读取 internal/cli/schema_command_registry.json,而本 PR 已将其迁为 schema_command_registry/registry.json + products/*.json。该函数会返回空映射,随后所有 live tool 都因 hasRef == false 被丢弃,make fetch-mcp-metadata 无法纳入任何新工具。建议复用分片 Registry 的统一加载/合并 API,并补测试。

  2. [P1] 已有 MCP 元数据永远不会被 live 数据刷新
    cmd/fetch_mcp_metadata/main.go:103allTools 已先由 prevTools 填满,已有 key 会直接 continue,因此 title/description/parameters 永远保留旧值。这与“用 live MCP 覆盖旧数据”的设计相反。成功拉取时应覆盖旧 entry,只在失败时保留旧值。

  3. [P2] 部分服务失败仍被标记为全覆盖
    cmd/fetch_mcp_metadata/main.go:152:请求失败会 [skip],但 snapshot_services 仍等于全部服务数,missing_services 固定为空。这样会生成一份错误宣称完整的快照,policy 无法发现缺口。应按成功/失败服务据实写 coverage,或失败即拒绝写文件。

  4. [P2] ResolveMeta 丢失并忽略 aliases
    internal/cli/command_meta.go:74CommandIdentity.Aliases 从未赋值,查找表也只登记 primary cli_path。Catalog 中已有 report list 等兼容路径,因此这些路径调用 ResolveMeta 会返回 false。建议复制 aliases,并将每个 alias 指向同一份 metadata,补真实 alias 用例。

  5. [P2] Required + Aliases 不符合 LeafSpec 的回退语义
    internal/helpers/leaf.go:186:普通 Required 只用主 flag 做 validateRequiredFlags,仅 EnvVar/RequiredHint 分支才走 leafEffectiveValue。只传兼容别名时仍会报缺少主 flag,与声明的“主 flag → 别名 → env”不一致。建议普通 required 也按 effective value 校验并补组合测试。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant