|
|
|
|
@@ -177,6 +177,27 @@ let setup_env () =
|
|
|
|
|
| [f; List l] | [f; ListRef { contents = l }] ->
|
|
|
|
|
List (List.mapi (fun i x -> Sx_ref.cek_call f (List [Number (float_of_int i); x])) l)
|
|
|
|
|
| _ -> List []);
|
|
|
|
|
(* Native list-replace — bypasses CEK map-indexed callback chain for deep tree edits *)
|
|
|
|
|
bind "list-replace" (fun args -> match args with
|
|
|
|
|
| [List l; Number idx; v] ->
|
|
|
|
|
let i = int_of_float idx in
|
|
|
|
|
List (List.mapi (fun j x -> if j = i then v else x) l)
|
|
|
|
|
| [ListRef { contents = l }; Number idx; v] ->
|
|
|
|
|
let i = int_of_float idx in
|
|
|
|
|
List (List.mapi (fun j x -> if j = i then v else x) l)
|
|
|
|
|
| _ -> Nil);
|
|
|
|
|
(* Native navigate — bypasses CEK reduce callback chain for deep path reads *)
|
|
|
|
|
bind "navigate" (fun args -> match args with
|
|
|
|
|
| [tree; List path] | [tree; ListRef { contents = path }] ->
|
|
|
|
|
let nodes = match tree with List _ | ListRef _ -> tree | _ -> List [tree] in
|
|
|
|
|
List.fold_left (fun current idx ->
|
|
|
|
|
match current, idx with
|
|
|
|
|
| (List l | ListRef { contents = l }), Number n ->
|
|
|
|
|
let i = int_of_float n in
|
|
|
|
|
if i >= 0 && i < List.length l then List.nth l i else Nil
|
|
|
|
|
| _ -> Nil
|
|
|
|
|
) nodes path
|
|
|
|
|
| _ -> Nil);
|
|
|
|
|
bind "trim" (fun args -> match args with
|
|
|
|
|
| [String s] -> String (String.trim s) | _ -> String "");
|
|
|
|
|
bind "split" (fun args -> match args with
|
|
|
|
|
@@ -683,38 +704,93 @@ let rec handle_tool name args =
|
|
|
|
|
Filename.dirname spec_dir
|
|
|
|
|
in
|
|
|
|
|
let spec = args |> member "spec" |> to_string_option in
|
|
|
|
|
let spec_arg = match spec with Some s -> " " ^ s | None -> "" in
|
|
|
|
|
let cmd = Printf.sprintf "cd %s/tests/playwright && npx playwright test%s --reporter=line 2>&1" project_dir spec_arg in
|
|
|
|
|
let ic = Unix.open_process_in cmd in
|
|
|
|
|
let lines = ref [] in
|
|
|
|
|
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
|
|
|
|
|
ignore (Unix.close_process_in ic);
|
|
|
|
|
let all_lines = List.rev !lines in
|
|
|
|
|
let fails = List.filter (fun l -> let t = String.trim l in
|
|
|
|
|
String.length t > 1 && (t.[0] = '\xE2' (* ✘ *) || (String.length t > 4 && String.sub t 0 4 = "FAIL"))) all_lines in
|
|
|
|
|
let summary = List.find_opt (fun l ->
|
|
|
|
|
try let _ = Str.search_forward (Str.regexp "passed\\|failed") l 0 in true
|
|
|
|
|
with Not_found -> false) (List.rev all_lines) in
|
|
|
|
|
let result = match summary with
|
|
|
|
|
| Some s ->
|
|
|
|
|
if fails = [] then s
|
|
|
|
|
else s ^ "\n\nFailures:\n" ^ String.concat "\n" fails
|
|
|
|
|
| None ->
|
|
|
|
|
let last_n = List.filteri (fun i _ -> i >= List.length all_lines - 10) all_lines in
|
|
|
|
|
String.concat "\n" last_n
|
|
|
|
|
let mode = args |> member "mode" |> to_string_option in
|
|
|
|
|
let url = args |> member "url" |> to_string_option in
|
|
|
|
|
let selector = args |> member "selector" |> to_string_option in
|
|
|
|
|
let expr = args |> member "expr" |> to_string_option in
|
|
|
|
|
let actions = args |> member "actions" |> to_string_option in
|
|
|
|
|
(* Determine whether to run specs or the inspector *)
|
|
|
|
|
let use_inspector = match mode with
|
|
|
|
|
| Some m when m <> "run" -> true
|
|
|
|
|
| _ -> spec = None && mode <> None
|
|
|
|
|
in
|
|
|
|
|
text_result result
|
|
|
|
|
if not use_inspector then begin
|
|
|
|
|
(* Original spec runner *)
|
|
|
|
|
let spec_arg = match spec with Some s -> " " ^ s | None -> "" in
|
|
|
|
|
let cmd = Printf.sprintf "cd %s/tests/playwright && npx playwright test%s --reporter=line 2>&1" project_dir spec_arg in
|
|
|
|
|
let ic = Unix.open_process_in cmd in
|
|
|
|
|
let lines = ref [] in
|
|
|
|
|
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
|
|
|
|
|
ignore (Unix.close_process_in ic);
|
|
|
|
|
let all_lines = List.rev !lines in
|
|
|
|
|
let fails = List.filter (fun l -> let t = String.trim l in
|
|
|
|
|
String.length t > 1 && (t.[0] = '\xE2' (* ✘ *) || (String.length t > 4 && String.sub t 0 4 = "FAIL"))) all_lines in
|
|
|
|
|
let summary = List.find_opt (fun l ->
|
|
|
|
|
try let _ = Str.search_forward (Str.regexp "passed\\|failed") l 0 in true
|
|
|
|
|
with Not_found -> false) (List.rev all_lines) in
|
|
|
|
|
let result = match summary with
|
|
|
|
|
| Some s ->
|
|
|
|
|
if fails = [] then s
|
|
|
|
|
else s ^ "\n\nFailures:\n" ^ String.concat "\n" fails
|
|
|
|
|
| None ->
|
|
|
|
|
let last_n = List.filteri (fun i _ -> i >= List.length all_lines - 10) all_lines in
|
|
|
|
|
String.concat "\n" last_n
|
|
|
|
|
in
|
|
|
|
|
text_result result
|
|
|
|
|
end else begin
|
|
|
|
|
(* SX-aware inspector *)
|
|
|
|
|
let inspector_args = `Assoc (List.filter_map Fun.id [
|
|
|
|
|
(match mode with Some m -> Some ("mode", `String m) | None -> Some ("mode", `String "inspect"));
|
|
|
|
|
(match url with Some u -> Some ("url", `String u) | None -> None);
|
|
|
|
|
(match selector with Some s -> Some ("selector", `String s) | None -> None);
|
|
|
|
|
(match expr with Some e -> Some ("expr", `String e) | None -> None);
|
|
|
|
|
(match actions with Some a -> Some ("actions", `String a) | None -> None);
|
|
|
|
|
]) in
|
|
|
|
|
let args_json = Yojson.Safe.to_string (Yojson.Safe.from_string (Yojson.Basic.to_string inspector_args)) in
|
|
|
|
|
let cmd = Printf.sprintf "cd %s && node tests/playwright/sx-inspect.js '%s' 2>&1" project_dir (String.escaped args_json) in
|
|
|
|
|
let ic = Unix.open_process_in cmd in
|
|
|
|
|
let lines = ref [] in
|
|
|
|
|
(try while true do lines := input_line ic :: !lines done with End_of_file -> ());
|
|
|
|
|
ignore (Unix.close_process_in ic);
|
|
|
|
|
let raw = String.concat "\n" (List.rev !lines) in
|
|
|
|
|
(* Try to parse as JSON and format nicely *)
|
|
|
|
|
try
|
|
|
|
|
let json = Yojson.Basic.from_string raw in
|
|
|
|
|
let pretty = Yojson.Basic.pretty_to_string json in
|
|
|
|
|
text_result pretty
|
|
|
|
|
with _ ->
|
|
|
|
|
text_result raw
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
| "sx_harness_eval" ->
|
|
|
|
|
let expr_str = args |> member "expr" |> to_string in
|
|
|
|
|
let mock_str = args |> member "mock" |> to_string_option in
|
|
|
|
|
let file = args |> member "file" |> to_string_option in
|
|
|
|
|
let setup_str = args |> member "setup" |> to_string_option in
|
|
|
|
|
let files_json = try args |> member "files" with _ -> `Null in
|
|
|
|
|
let e = !env in
|
|
|
|
|
(* Optionally load a file's definitions *)
|
|
|
|
|
(match file with
|
|
|
|
|
| Some f ->
|
|
|
|
|
(try load_sx_file e f
|
|
|
|
|
with exn -> Printf.eprintf "[mcp] Warning: %s: %s\n%!" f (Printexc.to_string exn))
|
|
|
|
|
let warnings = ref [] in
|
|
|
|
|
(* Collect all files to load *)
|
|
|
|
|
let all_files = match files_json with
|
|
|
|
|
| `List items ->
|
|
|
|
|
List.map (fun j -> Yojson.Safe.Util.to_string j) items
|
|
|
|
|
| _ -> match file with Some f -> [f] | None -> []
|
|
|
|
|
in
|
|
|
|
|
(* Load each file *)
|
|
|
|
|
List.iter (fun f ->
|
|
|
|
|
try load_sx_file e f
|
|
|
|
|
with exn ->
|
|
|
|
|
warnings := Printf.sprintf "Warning: %s: %s" f (Printexc.to_string exn) :: !warnings
|
|
|
|
|
) all_files;
|
|
|
|
|
(* Run setup expression if provided *)
|
|
|
|
|
(match setup_str with
|
|
|
|
|
| Some s ->
|
|
|
|
|
let setup_exprs = Sx_parser.parse_all s in
|
|
|
|
|
List.iter (fun expr ->
|
|
|
|
|
try ignore (Sx_ref.eval_expr expr (Env e))
|
|
|
|
|
with exn ->
|
|
|
|
|
warnings := Printf.sprintf "Setup error: %s" (Printexc.to_string exn) :: !warnings
|
|
|
|
|
) setup_exprs
|
|
|
|
|
| None -> ());
|
|
|
|
|
(* Create harness with optional mock overrides *)
|
|
|
|
|
let mock_arg = match mock_str with
|
|
|
|
|
@@ -743,7 +819,10 @@ let rec handle_tool name args =
|
|
|
|
|
) items)
|
|
|
|
|
| _ -> "\n\n(no IO calls)"
|
|
|
|
|
in
|
|
|
|
|
text_result (Printf.sprintf "Result: %s%s" (Sx_types.inspect result) log_str)
|
|
|
|
|
let warn_str = if !warnings = [] then "" else
|
|
|
|
|
"\n\nWarnings:\n" ^ String.concat "\n" (List.rev !warnings)
|
|
|
|
|
in
|
|
|
|
|
text_result (Printf.sprintf "Result: %s%s%s" (Sx_types.inspect result) log_str warn_str)
|
|
|
|
|
|
|
|
|
|
| "sx_write_file" ->
|
|
|
|
|
let file = args |> member "file" |> to_string in
|
|
|
|
|
@@ -912,6 +991,219 @@ let rec handle_tool name args =
|
|
|
|
|
) Nil exprs in
|
|
|
|
|
text_result (Sx_runtime.value_to_str result)
|
|
|
|
|
|
|
|
|
|
| "sx_trace" ->
|
|
|
|
|
let expr_str = args |> member "expr" |> to_string in
|
|
|
|
|
let max_steps = (try args |> member "max_steps" |> to_int with _ -> 200) in
|
|
|
|
|
let file = try Some (args |> member "file" |> to_string) with _ -> None in
|
|
|
|
|
let e = !env in
|
|
|
|
|
(match file with
|
|
|
|
|
| Some f -> (try load_sx_file e f with _ -> ())
|
|
|
|
|
| None -> ());
|
|
|
|
|
let exprs = Sx_parser.parse_all expr_str in
|
|
|
|
|
let expr = match exprs with [e] -> e | _ -> List exprs in
|
|
|
|
|
let state = ref (Sx_ref.make_cek_state expr (Env e) (List [])) in
|
|
|
|
|
let steps = Buffer.create 2048 in
|
|
|
|
|
let step_count = ref 0 in
|
|
|
|
|
let truncate s n = if String.length s > n then String.sub s 0 n ^ "..." else s in
|
|
|
|
|
(try
|
|
|
|
|
while !step_count < max_steps do
|
|
|
|
|
let s = !state in
|
|
|
|
|
(match s with
|
|
|
|
|
| CekState cs ->
|
|
|
|
|
incr step_count;
|
|
|
|
|
let n = !step_count in
|
|
|
|
|
if cs.cs_phase = "eval" then begin
|
|
|
|
|
let ctrl = cs.cs_control in
|
|
|
|
|
(match ctrl with
|
|
|
|
|
| Symbol sym_name ->
|
|
|
|
|
let resolved = (try
|
|
|
|
|
let v = Sx_ref.eval_expr ctrl cs.cs_env in
|
|
|
|
|
truncate (Sx_runtime.value_to_str v) 60
|
|
|
|
|
with _ -> "???") in
|
|
|
|
|
Buffer.add_string steps
|
|
|
|
|
(Printf.sprintf "%3d LOOKUP %s → %s\n" n sym_name resolved)
|
|
|
|
|
| List (hd :: _) ->
|
|
|
|
|
let head_str = truncate (Sx_runtime.value_to_str hd) 30 in
|
|
|
|
|
let ctrl_str = truncate (Sx_runtime.value_to_str ctrl) 80 in
|
|
|
|
|
Buffer.add_string steps
|
|
|
|
|
(Printf.sprintf "%3d CALL %s\n" n ctrl_str);
|
|
|
|
|
ignore head_str
|
|
|
|
|
| _ ->
|
|
|
|
|
Buffer.add_string steps
|
|
|
|
|
(Printf.sprintf "%3d LITERAL %s\n" n
|
|
|
|
|
(truncate (Sx_runtime.value_to_str ctrl) 60)))
|
|
|
|
|
end else begin
|
|
|
|
|
(* continue phase *)
|
|
|
|
|
let val_str = truncate (Sx_runtime.value_to_str cs.cs_value) 60 in
|
|
|
|
|
let kont = cs.cs_kont in
|
|
|
|
|
let frame_type = match kont with
|
|
|
|
|
| List (Dict d :: _) ->
|
|
|
|
|
(match Hashtbl.find_opt d "type" with
|
|
|
|
|
| Some (String s) -> s | _ -> "?")
|
|
|
|
|
| List (CekState ks :: _) ->
|
|
|
|
|
(match ks.cs_control with
|
|
|
|
|
| Dict d ->
|
|
|
|
|
(match Hashtbl.find_opt d "type" with
|
|
|
|
|
| Some (String s) -> s | _ -> "?")
|
|
|
|
|
| _ -> "?")
|
|
|
|
|
| _ -> "done" in
|
|
|
|
|
Buffer.add_string steps
|
|
|
|
|
(Printf.sprintf "%3d RETURN %s → %s\n" n val_str frame_type)
|
|
|
|
|
end;
|
|
|
|
|
(match Sx_ref.cek_terminal_p s with
|
|
|
|
|
| Bool true -> raise Exit
|
|
|
|
|
| _ -> ());
|
|
|
|
|
state := Sx_ref.cek_step s
|
|
|
|
|
| _ -> raise Exit)
|
|
|
|
|
done
|
|
|
|
|
with
|
|
|
|
|
| Exit -> ()
|
|
|
|
|
| Eval_error msg ->
|
|
|
|
|
Buffer.add_string steps (Printf.sprintf "ERROR: %s\n" msg)
|
|
|
|
|
| exn ->
|
|
|
|
|
Buffer.add_string steps (Printf.sprintf "ERROR: %s\n" (Printexc.to_string exn)));
|
|
|
|
|
let final_val = (match !state with
|
|
|
|
|
| CekState cs -> Sx_runtime.value_to_str cs.cs_value
|
|
|
|
|
| v -> Sx_runtime.value_to_str v) in
|
|
|
|
|
text_result (Printf.sprintf "Result: %s\n\nTrace (%d steps):\n%s"
|
|
|
|
|
final_val !step_count (Buffer.contents steps))
|
|
|
|
|
|
|
|
|
|
| "sx_deps" ->
|
|
|
|
|
let file = args |> member "file" |> to_string in
|
|
|
|
|
let name = try Some (args |> member "name" |> to_string) with _ -> None in
|
|
|
|
|
let dir = try args |> member "dir" |> to_string with _ ->
|
|
|
|
|
try Sys.getenv "SX_PROJECT_DIR" with Not_found ->
|
|
|
|
|
try Sys.getenv "PWD" with Not_found -> "." in
|
|
|
|
|
let tree = parse_file file in
|
|
|
|
|
(* Find the target subtree *)
|
|
|
|
|
let target = match name with
|
|
|
|
|
| Some n ->
|
|
|
|
|
(* Find the named define/defcomp/defisland *)
|
|
|
|
|
let items = match tree with List l | ListRef { contents = l } -> l | _ -> [tree] in
|
|
|
|
|
let found = List.find_opt (fun item ->
|
|
|
|
|
match item with
|
|
|
|
|
| List (Symbol head :: Symbol def_name :: _)
|
|
|
|
|
| List (Symbol head :: List (Symbol def_name :: _) :: _)
|
|
|
|
|
when (head = "define" || head = "defcomp" || head = "defisland" ||
|
|
|
|
|
head = "defmacro" || head = "deftest") ->
|
|
|
|
|
def_name = n || ("~" ^ def_name) = n || def_name = String.sub n 1 (String.length n - 1)
|
|
|
|
|
| _ -> false
|
|
|
|
|
) items in
|
|
|
|
|
(match found with Some f -> f | None -> tree)
|
|
|
|
|
| None -> tree
|
|
|
|
|
in
|
|
|
|
|
let free_syms = call_sx "collect-free-symbols" [target] in
|
|
|
|
|
let sym_names = match free_syms with
|
|
|
|
|
| List items | ListRef { contents = items } ->
|
|
|
|
|
List.filter_map (fun v -> match v with String s -> Some s | _ -> None) items
|
|
|
|
|
| _ -> []
|
|
|
|
|
in
|
|
|
|
|
(* Resolve where each symbol is defined *)
|
|
|
|
|
let file_defines = Hashtbl.create 32 in
|
|
|
|
|
let same_file_items = match tree with List l | ListRef { contents = l } -> l | _ -> [] in
|
|
|
|
|
List.iter (fun item ->
|
|
|
|
|
match item with
|
|
|
|
|
| List (Symbol head :: Symbol def_name :: _)
|
|
|
|
|
when (head = "define" || head = "defcomp" || head = "defisland" || head = "defmacro") ->
|
|
|
|
|
Hashtbl.replace file_defines def_name true
|
|
|
|
|
| _ -> ()
|
|
|
|
|
) same_file_items;
|
|
|
|
|
(* Check primitives *)
|
|
|
|
|
let is_prim name = try ignore (Sx_primitives.get_primitive name); true with _ -> false in
|
|
|
|
|
(* Scan directory for definitions *)
|
|
|
|
|
let all_sx_files = glob_sx_files dir in
|
|
|
|
|
let ext_defs = Hashtbl.create 64 in
|
|
|
|
|
List.iter (fun path ->
|
|
|
|
|
if path <> file then
|
|
|
|
|
try
|
|
|
|
|
let t = parse_file path in
|
|
|
|
|
let items = match t with List l | ListRef { contents = l } -> l | _ -> [] in
|
|
|
|
|
List.iter (fun item ->
|
|
|
|
|
match item with
|
|
|
|
|
| List (Symbol head :: Symbol def_name :: _)
|
|
|
|
|
when (head = "define" || head = "defcomp" || head = "defisland" || head = "defmacro") ->
|
|
|
|
|
if not (Hashtbl.mem ext_defs def_name) then
|
|
|
|
|
Hashtbl.replace ext_defs def_name (relative_path ~base:dir path)
|
|
|
|
|
| _ -> ()
|
|
|
|
|
) items
|
|
|
|
|
with _ -> ()
|
|
|
|
|
) all_sx_files;
|
|
|
|
|
(* Format output *)
|
|
|
|
|
let lines = List.map (fun sym ->
|
|
|
|
|
if Hashtbl.mem file_defines sym then
|
|
|
|
|
Printf.sprintf " %-30s (same file)" sym
|
|
|
|
|
else if is_prim sym then
|
|
|
|
|
Printf.sprintf " %-30s [primitive]" sym
|
|
|
|
|
else match Hashtbl.find_opt ext_defs sym with
|
|
|
|
|
| Some path -> Printf.sprintf " %-30s %s" sym path
|
|
|
|
|
| None -> Printf.sprintf " %-30s ???" sym
|
|
|
|
|
) sym_names in
|
|
|
|
|
let header = match name with
|
|
|
|
|
| Some n -> Printf.sprintf "Dependencies of %s in %s" n file
|
|
|
|
|
| None -> Printf.sprintf "Dependencies of %s" file
|
|
|
|
|
in
|
|
|
|
|
text_result (Printf.sprintf "%s\n%d symbols referenced:\n%s"
|
|
|
|
|
header (List.length sym_names) (String.concat "\n" lines))
|
|
|
|
|
|
|
|
|
|
| "sx_build_manifest" ->
|
|
|
|
|
let target = (try args |> member "target" |> to_string with _ -> "js") in
|
|
|
|
|
(match target with
|
|
|
|
|
| "ocaml" ->
|
|
|
|
|
let e = !env in
|
|
|
|
|
(* Collect all bindings from the env *)
|
|
|
|
|
let bindings = ref [] in
|
|
|
|
|
(* Walk env chain collecting all bindings *)
|
|
|
|
|
let rec collect_bindings env acc =
|
|
|
|
|
Hashtbl.iter (fun k v ->
|
|
|
|
|
if not (Hashtbl.mem acc k) then Hashtbl.replace acc k v
|
|
|
|
|
) env.bindings;
|
|
|
|
|
match env.parent with Some p -> collect_bindings p acc | None -> ()
|
|
|
|
|
in
|
|
|
|
|
let all = Hashtbl.create 256 in
|
|
|
|
|
collect_bindings e all;
|
|
|
|
|
Hashtbl.iter (fun k v ->
|
|
|
|
|
let kind = match v with
|
|
|
|
|
| NativeFn _ -> "native"
|
|
|
|
|
| Lambda _ -> "lambda"
|
|
|
|
|
| Component _ -> "component"
|
|
|
|
|
| Island _ -> "island"
|
|
|
|
|
| Macro _ -> "macro"
|
|
|
|
|
| _ -> "value"
|
|
|
|
|
in
|
|
|
|
|
bindings := (k, kind) :: !bindings
|
|
|
|
|
) all;
|
|
|
|
|
let sorted = List.sort (fun (a,_) (b,_) -> String.compare a b) !bindings in
|
|
|
|
|
let by_kind = Hashtbl.create 8 in
|
|
|
|
|
List.iter (fun (name, kind) ->
|
|
|
|
|
let cur = try Hashtbl.find by_kind kind with Not_found -> [] in
|
|
|
|
|
Hashtbl.replace by_kind kind (name :: cur)
|
|
|
|
|
) sorted;
|
|
|
|
|
let sections = Buffer.create 2048 in
|
|
|
|
|
Buffer.add_string sections "OCaml Build Manifest\n====================\n\n";
|
|
|
|
|
Buffer.add_string sections (Printf.sprintf "Total bindings: %d\n\n" (List.length sorted));
|
|
|
|
|
Buffer.add_string sections "Loaded files: parser.sx, tree-tools.sx, harness.sx\n\n";
|
|
|
|
|
List.iter (fun kind ->
|
|
|
|
|
match Hashtbl.find_opt by_kind kind with
|
|
|
|
|
| Some names ->
|
|
|
|
|
let rev_names = List.rev names in
|
|
|
|
|
Buffer.add_string sections
|
|
|
|
|
(Printf.sprintf "%s (%d):\n %s\n\n" kind (List.length rev_names)
|
|
|
|
|
(String.concat ", " rev_names))
|
|
|
|
|
| None -> ()
|
|
|
|
|
) ["native"; "lambda"; "macro"; "component"; "island"; "value"];
|
|
|
|
|
text_result (Buffer.contents sections)
|
|
|
|
|
| _ ->
|
|
|
|
|
let project_dir = try Sys.getenv "SX_PROJECT_DIR" with Not_found ->
|
|
|
|
|
try Sys.getenv "PWD" with Not_found -> "." in
|
|
|
|
|
let cmd = Printf.sprintf "cd %s && python3 hosts/javascript/manifest.py 2>&1"
|
|
|
|
|
(Filename.quote project_dir) in
|
|
|
|
|
let ic = Unix.open_process_in cmd in
|
|
|
|
|
let buf = Buffer.create 4096 in
|
|
|
|
|
(try while true do Buffer.add_string buf (input_line ic ^ "\n") done
|
|
|
|
|
with End_of_file -> ());
|
|
|
|
|
ignore (Unix.close_process_in ic);
|
|
|
|
|
text_result (Buffer.contents buf))
|
|
|
|
|
|
|
|
|
|
| _ -> error_result ("Unknown tool: " ^ name)
|
|
|
|
|
|
|
|
|
|
and write_edit file result =
|
|
|
|
|
@@ -980,6 +1272,16 @@ let tool_definitions = `List [
|
|
|
|
|
[file_prop; path_prop; ("wrapper", `Assoc [("type", `String "string"); ("description", `String "Wrapper with _ placeholder")])] ["file"; "path"; "wrapper"];
|
|
|
|
|
tool "sx_eval" "Evaluate an SX expression. Environment has parser + tree-tools + primitives."
|
|
|
|
|
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to evaluate")])] ["expr"];
|
|
|
|
|
tool "sx_trace" "Step-through SX evaluation showing each CEK machine step (symbol lookups, function calls, returns). Useful for debugging."
|
|
|
|
|
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to trace")]);
|
|
|
|
|
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")]);
|
|
|
|
|
("max_steps", `Assoc [("type", `String "integer"); ("description", `String "Max CEK steps to show (default: 200)")])] ["expr"];
|
|
|
|
|
tool "sx_deps" "Dependency analysis for a component or file. Shows all referenced symbols and where they're defined."
|
|
|
|
|
[file_prop;
|
|
|
|
|
("name", `Assoc [("type", `String "string"); ("description", `String "Specific define/defcomp/defisland to analyze")]);
|
|
|
|
|
("dir", `Assoc [("type", `String "string"); ("description", `String "Directory to search for definitions (default: project root)")])] ["file"];
|
|
|
|
|
tool "sx_build_manifest" "Show build manifest: which modules, primitives, adapters, and exports are included in a JS or OCaml build."
|
|
|
|
|
[("target", `Assoc [("type", `String "string"); ("description", `String "Build target: \"js\" (default) or \"ocaml\"")])] [];
|
|
|
|
|
tool "sx_find_across" "Search for a pattern across all .sx files under a directory. Returns file paths, tree paths, and summaries."
|
|
|
|
|
[dir_prop; ("pattern", `Assoc [("type", `String "string"); ("description", `String "Search pattern")])] ["dir"; "pattern"];
|
|
|
|
|
tool "sx_comp_list" "List all definitions (defcomp, defisland, defmacro, defpage, define) across .sx files in a directory."
|
|
|
|
|
@@ -1042,13 +1344,20 @@ let tool_definitions = `List [
|
|
|
|
|
[file_prop; path_prop] ["file"];
|
|
|
|
|
tool "sx_doc_gen" "Generate component documentation from all defcomp/defisland/defmacro signatures in a directory."
|
|
|
|
|
[dir_prop] ["dir"];
|
|
|
|
|
tool "sx_harness_eval" "Evaluate SX in a test harness with mock IO. Returns result + IO trace. Use mock param to override default mock responses."
|
|
|
|
|
tool "sx_harness_eval" "Evaluate SX in a test harness with mock IO. Returns result + IO trace. Supports loading multiple files and setup expressions."
|
|
|
|
|
[("expr", `Assoc [("type", `String "string"); ("description", `String "SX expression to evaluate")]);
|
|
|
|
|
("mock", `Assoc [("type", `String "string"); ("description", `String "Optional mock platform overrides as SX dict, e.g. {:fetch (fn (url) {:status 200})}")]);
|
|
|
|
|
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")])]
|
|
|
|
|
("file", `Assoc [("type", `String "string"); ("description", `String "Optional .sx file to load for definitions")]);
|
|
|
|
|
("files", `Assoc [("type", `String "array"); ("items", `Assoc [("type", `String "string")]); ("description", `String "Multiple .sx files to load in order")]);
|
|
|
|
|
("setup", `Assoc [("type", `String "string"); ("description", `String "SX setup expression to run before main evaluation")])]
|
|
|
|
|
["expr"];
|
|
|
|
|
tool "sx_playwright" "Run Playwright browser tests for the SX docs site. Optionally specify a single spec file."
|
|
|
|
|
[("spec", `Assoc [("type", `String "string"); ("description", `String "Optional spec file name (e.g. demo-interactions.spec.js)")])]
|
|
|
|
|
tool "sx_playwright" "Run Playwright browser tests or inspect SX pages interactively. Modes: run (spec files), inspect (page report), diff (SSR vs hydrated), eval (JS expression), interact (action sequence), screenshot."
|
|
|
|
|
[("spec", `Assoc [("type", `String "string"); ("description", `String "Spec file to run (run mode). e.g. stepper.spec.js")]);
|
|
|
|
|
("mode", `Assoc [("type", `String "string"); ("description", `String "Mode: run, inspect, diff, eval, interact, screenshot")]);
|
|
|
|
|
("url", `Assoc [("type", `String "string"); ("description", `String "URL path to navigate to (default: /)")]);
|
|
|
|
|
("selector", `Assoc [("type", `String "string"); ("description", `String "CSS selector to focus on (screenshot mode)")]);
|
|
|
|
|
("expr", `Assoc [("type", `String "string"); ("description", `String "JS expression to evaluate (eval mode)")]);
|
|
|
|
|
("actions", `Assoc [("type", `String "string"); ("description", `String "Semicolon-separated action sequence (interact mode). Actions: click:sel, fill:sel:val, wait:ms, text:sel, html:sel, attrs:sel, screenshot, screenshot:sel, count:sel, visible:sel")])]
|
|
|
|
|
[];
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|