feat: add hook repair functionality for JSON components- Introduce HOOK_REPAIR_TEMPLATE to generate custom hook files.- Implement _build_hook_repair_agent to create agents for hook generation.- Add _extract_hook_names and _hook_name_to_file_name functions for hook processing.- Enhance _post_process_outputs to handle missing hooks and generate them if necessary.

This commit is contained in:
2026-01-19 19:02:00 +00:00
parent bf0bc04ad5
commit fecb84f4af

View File

@@ -329,6 +329,32 @@ Rules:
- Update or keep "lastUpdated" as a string if present in the reference header.
"""
HOOK_REPAIR_TEMPLATE = """\
Generate the custom hook file needed for this JSON component conversion.
Return ONLY valid JSON with this shape:
{{
"hook": {{
"name": "{hook_name}",
"filePath": "{hook_file_path}",
"source": "...typescript..."
}} | null
}}
Rules:
- Use the provided hook name and file path if a hook is needed.
- Extract stateful/side-effect logic from the TSX into the hook.
- If the component is stateless and doesn't need a hook, return {{"hook": null}}.
- Do NOT include Markdown or code fences.
Component name: {component_name}
Category: {category}
Expected hook name: {hook_name}
Expected hook path: {hook_file_path}
TSX source:
{tsx}
"""
def list_components(roots: Iterable[Path]) -> List[ComponentTarget]:
targets: List[ComponentTarget] = []
@@ -439,6 +465,14 @@ def _build_schema_repair_agent() -> Agent:
)
def _build_hook_repair_agent() -> Agent:
return Agent(
name="HookRepair",
instructions="Generate hook JSON only. Return ONLY valid JSON, no Markdown.",
model=DEFAULT_MODEL,
)
def _is_rate_limited(exc: Exception) -> bool:
status = getattr(exc, "status_code", None)
if status == 429:
@@ -811,6 +845,16 @@ def _extract_definition_var(snippet: str) -> Optional[str]:
return match.group(1)
def _extract_hook_names(snippet: str) -> List[str]:
return re.findall(r"hookName:\s*['\"]([^'\"]+)['\"]", snippet)
def _hook_name_to_file_name(hook_name: str) -> str:
if hook_name.startswith("use") and len(hook_name) > 3:
return f"use-{_to_kebab_case(hook_name[3:])}.ts"
return f"{_to_kebab_case(hook_name)}.ts"
def _build_json_components_file(
out_dir: Path, components: List[Dict[str, Any]]
) -> None:
@@ -979,11 +1023,24 @@ def _post_process_outputs(
out_dir: Path, processed: List[Tuple[ComponentTarget, Dict[str, Any]]]
) -> None:
components: List[Dict[str, Any]] = []
missing_hooks: List[Tuple[str, str, ComponentTarget]] = []
hooks_dir = out_dir / "src" / "hooks"
for target, data in processed:
component_name = data.get("componentName") or target.name
if not component_name:
continue
snippet = (data.get("jsonComponentExport") or {}).get("source", "")
for hook_name in _extract_hook_names(snippet):
hook_file_name = _hook_name_to_file_name(hook_name)
hook_path = hooks_dir / hook_file_name
if hook_path.exists():
continue
hook_data = data.get("hook") or {}
if hook_data.get("name") == hook_name and hook_data.get("source"):
hook_file_path = hook_data.get("filePath") or f"src/hooks/{hook_file_name}"
_write_if_content(out_dir / hook_file_path, hook_data["source"])
continue
missing_hooks.append((hook_name, hook_file_name, target))
components.append(
{
"name": component_name,
@@ -991,6 +1048,44 @@ def _post_process_outputs(
"snippet": snippet,
}
)
for hook_name, hook_file_name, target in missing_hooks:
hook_path = hooks_dir / hook_file_name
if hook_path.exists():
continue
component_name = target.name
tsx = target.path.read_text(encoding="utf-8")
hook_file_path = f"src/hooks/{hook_file_name}"
prompt = HOOK_REPAIR_TEMPLATE.format(
hook_name=hook_name,
hook_file_path=hook_file_path,
component_name=component_name,
category=target.category,
tsx=tsx,
)
result = _run_with_retries(
_build_hook_repair_agent(),
prompt,
f"hook-repair:{component_name}:{hook_name}",
)
output = getattr(result, "final_output", None)
if output is None:
output = str(result)
output = _extract_json_payload(str(output))
repaired = _parse_json_output(
output, f"hook-repair:{component_name}:{hook_name}", True
)
hook = repaired.get("hook")
if hook and hook.get("source"):
hook_path.parent.mkdir(parents=True, exist_ok=True)
hook_path.write_text(hook["source"], encoding="utf-8")
else:
print(
(
f"[warn] hook repair did not return hook source for "
f"{component_name} ({hook_name})"
),
file=sys.stderr,
)
_build_json_components_file(out_dir, components)
_ensure_interfaces_index(out_dir)
_ensure_hooks_index(out_dir)