Files
dingtalk-workspace-cli/scripts/dev/catalog_codegen_probe.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

103 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""One-shot feasibility probe: render one product's catalog shard as Go literals.
Answers whether the shortcut storage model (compiled-in struct literals, zero
runtime parse) is viable for the 845-tool Schema catalog, by measuring compile
time, binary size and access cost on a single product first.
Deliberately drops field_provenance: it is 50% of the bytes and no runtime
consumer reads it, so a codegen design would keep it in JSON for audit only.
Keeping it here would make the size numbers pessimistic and the design wrong.
"""
import json
import sys
def q(s: str) -> str:
return json.dumps(s if s is not None else "", ensure_ascii=False)
def strs(v) -> str:
if not v:
return "nil"
return "[]string{" + ", ".join(q(x) for x in v) + "}"
def render_param(name, p) -> str:
fields = [
f"Name: {q(p.get('name') or name)}",
f"Type: {q(p.get('type'))}",
f"Description: {q(p.get('description'))}",
f"Property: {q(p.get('property'))}",
f"Required: {str(bool(p.get('required'))).lower()}",
f"CLIRequired: {str(bool(p.get('cli_required'))).lower()}",
f"RequiredWhen: {q(p.get('required_when'))}",
f"Format: {q(p.get('format'))}",
f"Enum: {strs(p.get('enum'))}",
]
return "{" + ", ".join(fields) + "}"
def render_tool(canonical, t) -> str:
params = t.get("parameters") or {}
lines = [
f"\t{q(canonical)}: {{",
f"\t\tName: {q(t.get('name'))},",
f"\t\tCanonicalPath: {q(t.get('canonical_path'))},",
f"\t\tCLIPath: {q(t.get('cli_path'))},",
f"\t\tPrimaryCLIPath: {q(t.get('primary_cli_path'))},",
f"\t\tProductID: {q(t.get('product_id'))},",
f"\t\tGroup: {q(t.get('group'))},",
f"\t\tTitle: {q(t.get('title'))},",
f"\t\tDescription: {q(t.get('description'))},",
f"\t\tAliases: {strs(t.get('aliases'))},",
f"\t\tIsAlias: {str(bool(t.get('is_alias'))).lower()},",
f"\t\tEffect: {q(t.get('effect'))},",
f"\t\tRisk: {q(t.get('risk'))},",
f"\t\tConfirmation: {q(t.get('confirmation'))},",
f"\t\tIdempotency: {q(t.get('idempotency'))},",
f"\t\tInterfaceMode: {q(t.get('interface_mode'))},",
f"\t\tAvailability: {q(t.get('availability'))},",
f"\t\tAgentSummary: {q(t.get('agent_summary'))},",
f"\t\tUseWhen: {strs(t.get('use_when'))},",
f"\t\tAvoidWhen: {strs(t.get('avoid_when'))},",
f"\t\tExamples: {strs(t.get('examples'))},",
f"\t\tHasParameters: {str(bool(t.get('has_parameters'))).lower()},",
f"\t\tParameterCount: {int(t.get('parameter_count') or 0)},",
]
if params:
lines.append("\t\tParameters: map[string]Param{")
for pname in sorted(params):
lines.append(f"\t\t\t{q(pname)}: {render_param(pname, params[pname])},")
lines.append("\t\t},")
lines.append("\t},")
return "\n".join(lines)
def main() -> int:
src, product, out = sys.argv[1], sys.argv[2], sys.argv[3]
tools = json.load(open(src, encoding="utf-8"))["tools"]
body = "\n".join(render_tool(c, tools[c]) for c in sorted(tools))
with open(out, "w", encoding="utf-8") as f:
f.write(f"""// Copyright 2026 Alibaba Group
// Code generated by scripts/dev/catalog_codegen_probe.py; DO NOT EDIT.
//
// Feasibility probe for storing the Schema catalog as compiled-in Go literals
// instead of embedded JSON, mirroring how internal/shortcut stores its 376
// definitions. field_provenance is omitted: no runtime consumer reads it.
package catalogcodegen
// Tools holds the {product} product's leaf ToolSpecs as static data, so there is
// no decode step on the first lookup.
var Tools = map[string]Tool{{
{body}
}}
""")
print(f"generated {out}: {len(tools)} tools")
return 0
if __name__ == "__main__":
raise SystemExit(main())