Files
dingtalk-workspace-cli/scripts/dev/gen_param_decls.py
T
玉澜 e705012011 feat(schema): migrate parameter overlays into code declarations
Move 121/210 parameter-level Schema field overlays from
schema_hints/metadata JSON into in-code ParamDecl declarations on
DeclareLeafMetadata commands. The declared values are emitted as
dws.schema.* annotations at assembly time via ApplyParamDecls,
outranking tool_schema_hint (rank 620 > 500) so the hint overlay
becomes redundant once the declaration is in place.

Key mechanism changes:
- Add SchemaDecl.Parameters []ParamDecl with property/required/
  interface_type/description/required_when/enum fields
- Add cli.ParamDecl type carried inside ContractFinalPayload
- ApplyParamDecls emits dws.schema.* annotations from the payload
  at assembly time (no sync.Map, no tree-rebuild key issue)
- Add cli.AnnotateRuntimeFlagInterfaceType (41 overlays needed it)
- Add cli.AnnotateRuntimeFlagRequiredValue for explicit true/false
- Compatibility alias check tolerates dws.schema.* annotation
  differences between primary and alias commands
- Remove runtime source_hash recomputation (87ms/997k allocs saved;
  enforced by check-generated-drift.sh at build time instead)
- Add shortcut.Flag.RequiredWhen and wire through FromShortcut
- Add boolFlag OR semantics to fix confirmationBypass disagreement
- Add bindKey default kebab-to-camel for forgotten Bind
- Add schema consumption benchmarks (catalog decode 1.5s/817MB,
  shortcut load 1.9ms/3MB — three orders of magnitude apart)
- Add catalog codegen feasibility probe (34 tools: 0.06s compile,
  31ns access, 87KB linked — extrapolates to 2.1MB for 845 tools)

Migrated products (29 tools, 121 fields):
  aisearch(1), chat(12), contact(4), doc(3), drive(1),
  hrbrain(10), mail(2), report(1), sheet(3), todo(6)

Remaining 89 fields across 11 products blocked by:
  - RPCName not found as string literal (19 tools, shortcut/variable)
  - No matching DeclareLeafMetadata near callMCPTool (11 tools)
  - No single RPCName for multi-step commands (drive.upload etc.)

All tests green: corecmd, cli, helpers. Generation and drift clean.
2026-08-01 09:25:40 +08:00

121 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Generate ParamDecl Go snippets from schema_hints/metadata overlays.
For each product with parameter overlays, emits the Go code that should be
inserted into the corresponding DeclareLeafMetadata Schema block. Also reports
whether the overlaid flags are registered before or after the DeclareLeafMetadata
call, so the operator knows which need reordering.
Usage: python3 scripts/dev/gen_param_decls.py
"""
import json
import glob
import os
import re
import sys
HELPER_DIR = "internal/helpers"
# Map product hint filename to helper Go file(s)
PRODUCT_FILES = {
"aisearch": ["aisearch.go"],
"aitable": ["aitable.go"],
"attendance": ["attendance.go"],
"chat": ["chat.go"],
"contact": ["contact.go"],
"devdoc": ["devdoc.go"],
"doc": ["doc.go"],
"drive": ["drive.go"],
"hrbrain": ["hrbrain.go"],
"mail": ["mail.go"],
"markdown": ["markdown.go"],
"minutes": ["minutes.go"],
"report": ["report.go"],
"sheet": ["sheet.go"],
"todo": ["todo.go"],
}
def bool_ptr(v: bool) -> str:
return "boolPtr(true)" if v else "boolPtr(false)"
def render_param_decl(name: str, overlay: dict) -> str:
parts = [f'Name: "{name}"']
if "property" in overlay:
parts.append(f'Property: "{overlay["property"]}"')
if "required" in overlay:
parts.append(f"Required: {bool_ptr(overlay['required'])}")
if "interface_type" in overlay:
parts.append(f'InterfaceType: "{overlay["interface_type"]}"')
if "description" in overlay:
desc = overlay["description"].replace('"', '\\"')
parts.append(f'Description: "{desc}"')
if "required_when" in overlay:
parts.append(f'RequiredWhen: "{overlay["required_when"]}"')
if "enum" in overlay and overlay["enum"]:
vals = ", ".join(f'"{v}"' for v in overlay["enum"])
parts.append(f"Enum: []string{{{vals}}}")
return "{" + ", ".join(parts) + "}"
def find_declare_line(src: str, tool_name: str) -> int:
"""Find the line number of DeclareLeafMetadata for a tool whose RPCName matches."""
# Try to find by RPCName in the Schema block
pattern = re.compile(
r'DeclareLeafMetadata\(\w+,\s*LeafSpec\{.*?RPCName:\s*"' + re.escape(tool_name) + '"',
re.S,
)
m = pattern.search(src)
if m:
return src[: m.start()].count("\n") + 1
return -1
def find_flag_reg_line(src: str, flag_name: str) -> int:
"""Find the first line where a flag is registered."""
pattern = re.compile(r'\.Flags\(\)\.\w+\("' + re.escape(flag_name) + '"')
m = pattern.search(src)
if m:
return src[: m.start()].count("\n") + 1
return -1
def main():
for hint_file in sorted(glob.glob("internal/cli/schema_hints/metadata/*.json")):
product = os.path.basename(hint_file).replace(".json", "")
d = json.load(open(hint_file, encoding="utf-8"))
tools = d.get("tools") or {}
overlays = {t: (v or {}).get("parameters") or {} for t, v in tools.items()}
overlays = {t: p for t, p in overlays.items() if p}
if not overlays:
continue
helper_files = PRODUCT_FILES.get(product, [])
src = ""
for hf in helper_files:
path = os.path.join(HELPER_DIR, hf)
if os.path.exists(path):
src += open(path, encoding="utf-8").read() + "\n"
print(f"\n// === {product} ({len(overlays)} tools) ===")
for tool_name, params in sorted(overlays.items()):
decl_line = find_declare_line(src, tool_name)
print(f"\n// tool: {tool_name} (DeclareLeafMetadata at line {decl_line})")
print(f"Parameters: []corecmd.ParamDecl{{")
for pname in sorted(params):
overlay = params[pname]
reg_line = find_flag_reg_line(src, pname)
order = ""
if decl_line > 0 and reg_line > 0:
if reg_line > decl_line:
order = f" // ⚠ flag registered AFTER DeclareLeafMetadata (line {reg_line} > {decl_line}) — MOVE BEFORE"
else:
order = f" // ✓ flag registered before (line {reg_line})"
print(f"\t{render_param_decl(pname, overlay)},{order}")
print("},")
if __name__ == "__main__":
main()