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.
121 lines
4.2 KiB
Python
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()
|