♻️ refactor(tsl-api-reference): split lookup workflows and maintenance tooling

This commit is contained in:
csh
2026-08-20 17:47:48 +08:00
parent 9b95bf2682
commit cd0405d64b
18 changed files with 707 additions and 374 deletions
@@ -1,15 +1,10 @@
#!/usr/bin/env python3
"""Resolve framework-class profiles without replacing ordinary API lookup."""
"""Library for validating and resolving evidence-backed framework profiles."""
import argparse
import json
import sys
from pathlib import Path
from lookup import (
DEFAULT_TSV,
codegen_root_for_tsv,
load_rows,
normalize,
search_exact,
slice_entry,
@@ -230,33 +225,6 @@ def validate_index(data, rows, codegen_root):
return errors
def find_framework(data, name):
requested = normalize(name)
return next(
(
framework
for framework in data.get("frameworks", [])
if isinstance(framework, dict)
and normalize(framework.get("qualified_name", "")) == requested
),
None,
)
def parse_config(values):
config = {}
errors = []
for value in values:
key, separator, raw_value = value.partition("=")
if not separator or not key.strip() or not raw_value.strip():
errors.append(
f"invalid --config {value!r}; expected a non-empty key=value pair"
)
continue
config[key.strip()] = raw_value.strip()
return config, errors
def scaffold_packet(framework, config):
configured_names = {
entry["api"].rsplit(".", 1)[-1]
@@ -320,135 +288,3 @@ def scaffold_packet(framework, config):
"diagnostics": diagnostics,
"evidence": framework.get("evidence", []),
}
def print_text(packet):
print(f"framework: {packet['framework']}")
print(f"scope/module: {packet['scope']}/{packet['module']}")
print(f"scaffold_status: {packet['scaffold_status']}")
print(f"contract_status: {packet['contract_status']}")
print("lifecycle: " + " -> ".join(item["phase"] for item in packet["lifecycle"]))
print("configuration: " + ", ".join(item["api"] for item in packet["configuration"]))
required_hooks = [item["api"] for item in packet["hooks"] if item.get("required")]
print("required_hooks: " + (", ".join(required_hooks) or "none"))
print("state_apis: " + ", ".join(item["api"] for item in packet["state_apis"]))
print("execution: " + ", ".join(item["api"] for item in packet["execution"]))
print("result_apis: " + ", ".join(item["api"] for item in packet["result_apis"]))
if packet["configured_values"]:
print("configured_values: " + ", ".join(
f"{key}={value}" for key, value in packet["configured_values"].items()
))
if packet["diagnostics"]:
print("diagnostics:")
for diagnostic in packet["diagnostics"]:
print(f"- {diagnostic['code']}: {diagnostic['message']}")
def main(argv=None):
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(
description="Resolve framework-class scaffolds for TSL API consumers.",
allow_abbrev=False,
)
action = parser.add_mutually_exclusive_group(required=True)
action.add_argument("--framework", help="exact framework qualified name")
action.add_argument(
"--check", action="store_true", help="validate all framework profile references"
)
parser.add_argument(
"--config",
action="append",
default=[],
metavar="KEY=VALUE",
help="raw framework configuration; may be repeated",
)
parser.add_argument("--format", choices=("text", "json"), default="text")
parser.add_argument("--index", metavar="PATH", help="framework_index.json path")
parser.add_argument("--tsv", metavar="PATH", help="function_index.tsv path")
args = parser.parse_args(argv)
index_path = Path(args.index) if args.index else DEFAULT_INDEX
tsv_path = Path(args.tsv) if args.tsv else DEFAULT_TSV
try:
data = load_index(index_path)
rows = load_rows(tsv_path)
except (OSError, UnicodeError, ValueError) as error:
print(f"ERROR: {error}", file=sys.stderr)
return 1
if not rows:
print(f"ERROR: {tsv_path} has no entries", file=sys.stderr)
return 1
codegen_root = codegen_root_for_tsv(tsv_path)
errors = validate_index(data, rows, codegen_root)
if args.check:
if errors:
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1
print(f"OK: {len(data['frameworks'])} framework profile(s) validated")
return 0
if errors:
packet = {
"status": "data_error",
"framework": args.framework,
"diagnostics": [
{
"code": "PROFILE_REFERENCE_ERROR",
"severity": "error",
"message": error,
"next_action": "Fix framework_index.json or rebuild the API index.",
}
for error in errors
],
}
if args.format == "json":
print(json.dumps(packet, ensure_ascii=False, indent=2))
else:
print(f"Framework profile data is invalid for {args.framework!r}.")
for diagnostic in packet["diagnostics"]:
print(f"- {diagnostic['code']}: {diagnostic['message']}")
return 1
framework = find_framework(data, args.framework)
if framework is None:
packet = {
"status": "not_found",
"framework": args.framework,
"diagnostics": [
{
"code": "FRAMEWORK_NOT_FOUND",
"severity": "error",
"message": f"No framework profile named {args.framework!r}.",
"next_action": "Use --check or add a framework profile.",
}
],
}
if args.format == "json":
print(json.dumps(packet, ensure_ascii=False, indent=2))
else:
print(f"No framework profile named {args.framework!r}.")
return 0
config, config_errors = parse_config(args.config)
packet = scaffold_packet(framework, config)
for error in config_errors:
packet["diagnostics"].append(
{
"code": "INVALID_CONFIGURATION",
"severity": "error",
"message": error,
"next_action": "Use KEY=VALUE syntax with non-empty values.",
}
)
if args.format == "json":
print(json.dumps(packet, ensure_ascii=False, indent=2))
else:
print_text(packet)
return 0
if __name__ == "__main__":
raise SystemExit(main())