优化skill,添加特征树、编辑窗口
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate submitted Studio source against the imported SimpleCADAPI runtime.
|
||||
|
||||
This is a server-side guard only. Agent-facing API documentation remains the
|
||||
original SimpleCADAPI skill and its API Markdown pages.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import importlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import simplecadapi as scad
|
||||
|
||||
|
||||
STDLIB_MODULES = ("gear", "bearing", "chain", "fastener")
|
||||
|
||||
# Make the package's public ``scad.std.<module>`` paths available before AST
|
||||
# resolution. Model source may use any of these documented standard factories.
|
||||
for _module_name in STDLIB_MODULES:
|
||||
importlib.import_module(f"simplecadapi.std.{_module_name}")
|
||||
|
||||
|
||||
def resolve_simplecad_path(parts: list[str]) -> Any:
|
||||
value: Any = scad
|
||||
for part in parts:
|
||||
value = getattr(value, part)
|
||||
return value
|
||||
|
||||
|
||||
class SourceValidator(ast.NodeVisitor):
|
||||
def __init__(self) -> None:
|
||||
self.package_aliases: set[str] = set()
|
||||
self.imported_names: dict[str, tuple[str, list[str]]] = {}
|
||||
self.errors: list[dict[str, object]] = []
|
||||
|
||||
def error(self, node: ast.AST, message: str) -> None:
|
||||
self.errors.append({
|
||||
"line": getattr(node, "lineno", None),
|
||||
"column": getattr(node, "col_offset", None),
|
||||
"message": message,
|
||||
})
|
||||
|
||||
def visit_Import(self, node: ast.Import) -> None:
|
||||
for alias in node.names:
|
||||
if alias.name == "simplecadapi":
|
||||
self.package_aliases.add(alias.asname or "simplecadapi")
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
||||
module = node.module or ""
|
||||
if module == "simplecadapi" or module.startswith("simplecadapi."):
|
||||
module_parts = module.split(".")[1:]
|
||||
for alias in node.names:
|
||||
if alias.name == "*":
|
||||
self.error(node, "Wildcard imports from SimpleCADAPI are not allowed in submitted source.")
|
||||
continue
|
||||
try:
|
||||
resolve_simplecad_path(module_parts + [alias.name])
|
||||
except AttributeError:
|
||||
self.error(alias, f"SimpleCADAPI import does not exist: {module}.{alias.name}")
|
||||
self.imported_names[alias.asname or alias.name] = (module, module_parts + [alias.name])
|
||||
self.generic_visit(node)
|
||||
|
||||
def attribute_path(self, node: ast.Attribute) -> tuple[str, list[str]] | None:
|
||||
parts: list[str] = [node.attr]
|
||||
current: ast.AST = node.value
|
||||
while isinstance(current, ast.Attribute):
|
||||
parts.append(current.attr)
|
||||
current = current.value
|
||||
if isinstance(current, ast.Name) and current.id in self.package_aliases:
|
||||
return current.id, list(reversed(parts))
|
||||
return None
|
||||
|
||||
def visit_Attribute(self, node: ast.Attribute) -> None:
|
||||
resolved = self.attribute_path(node)
|
||||
if resolved:
|
||||
_, parts = resolved
|
||||
try:
|
||||
resolve_simplecad_path(parts)
|
||||
except AttributeError:
|
||||
self.error(node, f"SimpleCADAPI attribute does not exist: scad.{'.'.join(parts)}")
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Call(self, node: ast.Call) -> None:
|
||||
if isinstance(node.func, ast.Name) and node.func.id in {"getattr", "setattr", "hasattr", "dir", "inspect"}:
|
||||
self.error(node, "Dynamic or introspective SDK access is not allowed in submitted SimpleCADAPI source.")
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
def validate_source(source_path: Path) -> dict[str, object]:
|
||||
try:
|
||||
source = source_path.read_text(encoding="utf-8")
|
||||
tree = ast.parse(source, filename=str(source_path))
|
||||
except (OSError, SyntaxError) as exc:
|
||||
return {"valid": False, "errors": [{"message": f"Cannot parse Python source: {exc}"}]}
|
||||
validator = SourceValidator()
|
||||
validator.visit(tree)
|
||||
if not validator.package_aliases:
|
||||
validator.errors.append({"message": "Submitted SimpleCADAPI source must import simplecadapi as scad."})
|
||||
return {"valid": not validator.errors, "errors": validator.errors}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--source", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(validate_source(args.source), ensure_ascii=True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user