Review your SwiftUI code from your own scripts
Send Swift — one file, a screen with its model, several files with file-name
comment headers, or a whole grab-bag of views — and get back one JSON object: an
honest sound / refactor / rework verdict, a health check across five UI-code areas, findings
ranked by severity each with corrected Swift, a twelve-item checklist scored against the
paste, and a complete refined rewrite of what you pasted. Everything this app does goes
through the SkillSafe App API — plain JSON over HTTPS — so you can wire the
review into a CI gate, a pull-request bot, or a pre-merge check that refuses a diff
introducing a fresh default-initialized @ObservedObject or an index-keyed
ForEach.
Every code step below is shown in cURL, Python, JavaScript, Go, Java, Ruby, PHP and C#;
pick a language once and the whole page follows.
Basics
Base URL: https://api.skillsafe.ai/v1/app-api, app slug
swiftui-clinic. Every request sends
Authorization: Bearer <token> and JSON bodies with
Content-Type: application/json. Responses are wrapped in an envelope:
{"data": …} on success, {"error": {"code", "message"}} on failure.
The review itself is produced by the gpt-terra model. Estimates are free;
runs are metered against your credit balance. There is a single run task — one paste
in, one review out, no follow-up calls and no session state to carry.
| Status | Meaning |
|---|---|
401 | Missing or expired token — create a new session. |
402 | Not enough credits — top up at skillsafe.ai/account/credits. |
403 | The token isn't allowed to do this (e.g. a guest reviewing a very large paste). |
404 | Unknown job or record id. |
5xx | Transient platform error — retry with backoff. |
Browsers enforce CORS for this API, so run these examples from a server, script or terminal — not from another website's frontend.
Step 0 — A tiny client
Every task below is a single HTTP call, so start with a short helper that adds the auth
header, sends JSON and unwraps the data envelope. The later steps reuse it.
export API="https://api.skillsafe.ai/v1/app-api"
export TOKEN="YOUR_TOKEN" # see step 1
# every call looks like:
# curl -s "$API/..." -H "Authorization: Bearer $TOKEN" [-d '{json}']
# jq is used below to pull fields out of the {"data": ...} envelope
import json, requests
API = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # see step 1 — read it from your shell environment in real code
def api(method, path, body=None, **headers):
res = requests.request(method, API + path, json=body,
headers={"Authorization": f"Bearer {TOKEN}", **headers})
payload = res.json()
if not res.ok:
raise RuntimeError(payload.get("error", {}).get("message", res.reason))
return payload["data"]
// Node 18+ (built-in fetch)
const API = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN"; // see step 1 — read it from your shell environment in real code
async function api(method, path, body, extraHeaders = {}) {
const res = await fetch(API + path, {
method,
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", ...extraHeaders },
body: body === undefined ? undefined : JSON.stringify(body),
});
const json = await res.json();
if (!res.ok) throw new Error(json.error?.message ?? res.statusText);
return json.data;
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
const API = "https://api.skillsafe.ai/v1/app-api"
var token = os.Getenv("SKILLSAFE_TOKEN") // see step 1
func call(method, path string, body, out any) error {
var buf bytes.Buffer
if body != nil {
json.NewEncoder(&buf).Encode(body)
}
req, _ := http.NewRequest(method, API+path, &buf)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var env struct {
Data json.RawMessage `json:"data"`
Error *struct{ Message string `json:"message"` } `json:"error"`
}
json.NewDecoder(res.Body).Decode(&env)
if res.StatusCode >= 400 {
return fmt.Errorf("api %s %s: %s", method, path, env.Error.Message)
}
if out == nil {
return nil
}
return json.Unmarshal(env.Data, out)
}
// Java 17+, no dependencies. Pair with your JSON library (Jackson, Gson…)
// to read fields out of the returned envelope.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class SkillSafe {
static final String API = "https://api.skillsafe.ai/v1/app-api";
static final String TOKEN = System.getenv("SKILLSAFE_TOKEN"); // see step 1
static final HttpClient HTTP = HttpClient.newHttpClient();
static String api(String method, String path, String jsonBody) throws Exception {
var req = HttpRequest.newBuilder(URI.create(API + path))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method(method, jsonBody == null
? HttpRequest.BodyPublishers.noBody()
: HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
var res = HTTP.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() >= 400) throw new RuntimeException(res.body());
return res.body(); // envelope: {"data": …}
}
}
require "net/http"
require "json"
API = "https://api.skillsafe.ai/v1/app-api"
TOKEN = ENV.fetch("SKILLSAFE_TOKEN") # see step 1
def api(method, path, body = nil)
uri = URI(API + path)
req = Net::HTTP.const_get(method.capitalize).new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = body.to_json if body
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
payload = JSON.parse(res.body)
raise (payload.dig("error", "message") || res.message) unless res.is_a?(Net::HTTPSuccess)
payload["data"]
end
<?php
const API = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = getenv("SKILLSAFE_TOKEN"); // see step 1
function api(string $method, string $path, ?array $body = null): mixed {
global $TOKEN;
$ch = curl_init(API . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $TOKEN",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body),
]);
$payload = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) {
throw new Exception($payload["error"]["message"] ?? "HTTP $status");
}
return $payload["data"];
}
// .NET 8+
using System.Net.Http.Json;
using System.Text.Json;
static class SkillSafe
{
const string Api = "https://api.skillsafe.ai/v1/app-api";
static readonly HttpClient Http = new();
static SkillSafe() =>
Http.DefaultRequestHeaders.Authorization =
new("Bearer", Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN")); // see step 1
public static async Task<JsonElement> ApiAsync(HttpMethod method, string path, object? body = null)
{
var req = new HttpRequestMessage(method, Api + path);
if (body != null) req.Content = JsonContent.Create(body);
var res = await Http.SendAsync(req);
var json = await res.Content.ReadFromJsonAsync<JsonElement>();
if (!res.IsSuccessStatusCode)
throw new Exception(json.GetProperty("error").GetProperty("message").GetString());
return json.GetProperty("data");
}
}
Step 1 — Get a token
A guest token lets you check balances and estimate costs for free. For metered review runs
billed to your own account, use your personal token: open the
token page, sign in with SkillSafe, and press
Copy shell export — it puts export SKILLSAFE_TOKEN="…" on your
clipboard, which every example below reads. Treat the token like a password: it can spend
your credits. For fully headless scripts, POST /guest mints a guest token with
no browser involved.
curl -s -X POST "$API/guest" \
-H "Content-Type: application/json" \
-d '{"slug":"swiftui-clinic"}' | jq -r '.data.token'
token = api("POST", "/guest", {"slug": "swiftui-clinic"})["token"]
const { token } = await api("POST", "/guest", { slug: "swiftui-clinic" });
var guest struct{ Token string `json:"token"` }
err := call("POST", "/guest", map[string]string{"slug": "swiftui-clinic"}, &guest)
String envelope = api("POST", "/guest", """
{"slug":"swiftui-clinic"}""");
// token is at data.token in the returned JSON
token = api("POST", "/guest", { slug: "swiftui-clinic" })["token"]
$token = api("POST", "/guest", ["slug" => "swiftui-clinic"])["token"];
var guest = await SkillSafe.ApiAsync(HttpMethod.Post, "/guest",
new { slug = "swiftui-clinic" });
var token = guest.GetProperty("token").GetString();
The app stores this browser's token under the localStorage key
skillsafe_app_token:swiftui-clinic, on the app's own origin. The
token page reads and manages it for you — you never need
to open developer tools.
Step 2 — Check who you are and your balance
Returns subject_type ("user" or "guest"),
subject_id and your credits balance. Check this before reviewing
a large paste.
curl -s "$API/me" -H "Authorization: Bearer $TOKEN" | jq '.data'
me = api("GET", "/me")
print(me["subject_type"], me["credits"])
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
var me struct {
SubjectType string `json:"subject_type"`
Credits int64 `json:"credits"`
}
err := call("GET", "/me", nil, &me)
String envelope = api("GET", "/me", null);
// data.subject_type, data.credits
me = api("GET", "/me")
puts "#{me["subject_type"]}: #{me["credits"]} credits"
$me = api("GET", "/me");
echo "{$me['subject_type']}: {$me['credits']} credits\n";
var me = await SkillSafe.ApiAsync(HttpMethod.Get, "/me");
Console.WriteLine($"{me.GetProperty("subject_type")}: {me.GetProperty("credits")} credits");
Step 3 — Estimate the cost
Send exactly the input you would send to /run; the response's
hold_credits is the worst-case cost. Nothing is charged and no job is created,
so estimating is free — useful when you are feeding in a whole diff or a directory of
source files and want a ceiling before spending credits.
| Input field | Type | Notes |
|---|---|---|
code | string, required | The Swift source to review, up to 100000 characters: one file, a screen with its model, or several files concatenated with file-name comment headers such as // Feed/FeedScreen.swift. Very long pastes may be clipped middle-out, with a [... clipped ...] marker showing where. |
target | string | app | library | legacy | unknown — what the code is. The review is calibrated to it: app (an iOS/iPadOS/macOS app on current OS releases) makes the Observation framework the standard — @Observable models, @State ownership, @Environment injection, type-safe NavigationStack routing and .task-scoped async work are first-class concerns; library (a reusable component package or design system) makes stateless components with hoisted state, ViewModifier-based styling, stable public parameter types, no AnyView in public surfaces and #Preview coverage first-class concerns; legacy (a deployment target before iOS 17 / macOS 14) treats ObservableObject/@Published/@StateObject as the legitimate tools and reviews their correct use instead of filing them as defects. On unknown the review infers from the paste and says which it assumed. |
notes | string, optional | Extra context, up to 20000 characters: what the screen does, performance constraints, the minimum deployment target, what is intentionally unfinished, which public APIs cannot break. |
prescan_facts | object, optional | What the app's free client-side prescan mechanically detected in the code: {"antipatterns": [], "items": [], "signals": {}}. antipatterns and items hold {id, label, lines} entries — keyword-matched SwiftUI smells (ap:legacy-observation, ap:index-foreach) and the declarations found (i:view:FeedScreen, i:model:FeedModel), each with the line numbers it was seen on. signals is a counter object: {"views": 0, "models": 0, "state_props": 0, "effect_modifiers": 0, "foreach_blocks": 0, "lazy_containers": 0, "previews": 0, "tests": 0, "lines": 0}. Every id you send comes back in coverage_check. The web UI fills this from its own scan; API callers may omit the field or send {"antipatterns": [], "items": [], "signals": {}}. |
retry_note | string, optional | Only set by the app's automatic reformat retry when a first reply was not valid JSON. Leave it out. |
cat > CheckoutScreen.swift <<'SWIFT'
import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}
SWIFT
jq -n --rawfile c CheckoutScreen.swift \
'{code: $c, target: "android", notes: "",
prescan_facts: {antipatterns: [], items: [], signals: {}}}' > input.json
curl -s -X POST "$API/estimate" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d @input.json | jq '.data.hold_credits'
CODE = """import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}"""
payload = {
"code": CODE,
"target": "android",
"notes": "",
"prescan_facts": {"antipatterns": [], "items": [], "signals": {}},
}
est = api("POST", "/estimate", payload)
print("worst case:", est.get("hold_credits", est.get("credits")), "credits")
const code = `import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}`;
const payload = {
code,
target: "android",
notes: "",
prescan_facts: { antipatterns: [], items: [], signals: {} },
};
const est = await api("POST", "/estimate", payload);
console.log("worst case:", est.hold_credits ?? est.credits, "credits");
const code = `import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}`
payload := map[string]any{
"code": code,
"target": "android",
"notes": "",
"prescan_facts": map[string]any{
"antipatterns": []any{}, "items": []any{}, "signals": map[string]any{},
},
}
var est struct{ HoldCredits int64 `json:"hold_credits"` }
err := call("POST", "/estimate", payload, &est)
String code = """
import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}""";
String jsonPayload = """
{"code": %s, "target": "android",
"notes": "",
"prescan_facts": {"antipatterns": [], "items": [], "signals": {}}}
""".formatted(toJsonString(code));
String envelope = api("POST", "/estimate", jsonPayload);
// worst-case cost is at data.hold_credits
CODE_TEXT = <<~'SWIFT'
import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}
SWIFT
payload = { code: CODE_TEXT, target: "android",
notes: "",
prescan_facts: { antipatterns: [], items: [], signals: {} } }
est = api("POST", "/estimate", payload)
puts "worst case: #{est["hold_credits"] || est["credits"]} credits"
$code = <<<'SWIFT'
import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}
SWIFT;
$payload = [
"code" => $code,
"target" => "android",
"notes" => "",
"prescan_facts" => ["antipatterns" => [], "items" => [], "signals" => new stdClass()],
];
$est = api("POST", "/estimate", $payload);
echo "worst case: " . ($est["hold_credits"] ?? $est["credits"]) . " credits\n";
var code = """
import SwiftUI
struct CheckoutScreen: View {
let total: String
@State var promo = ""
var body: some View {
VStack {
Text(total)
Button("Apply SAVE10") { promo = "SAVE10" }
}
}
}
""";
var payload = new {
code,
target = "android",
notes = "",
prescan_facts = new {
antipatterns = Array.Empty<object>(), items = Array.Empty<object>(),
signals = new { },
},
};
var est = await SkillSafe.ApiAsync(HttpMethod.Post, "/estimate", payload);
Console.WriteLine($"worst case: {est.GetProperty("hold_credits")} credits");
prescan_facts is how you make the review answer for things you already know
about. Send {"antipatterns": [{"id": "ap:state-not-private", "label": "@State that is
not private", "lines": [5]}], "items": [{"id":
"i:view:CheckoutScreen", "label": "CheckoutScreen (view)", "lines": [3]}],
"signals": {"views": 1, "models": 0, "state_props": 1, "effect_modifiers": 0,
"foreach_blocks": 0, "lazy_containers": 0, "previews": 0, "tests": 0, "lines": 13}}
and every one of those ids comes back in coverage_check — addressed, or
explained away as a false positive (an ObservableObject in a codebase whose
deployment target predates iOS 17 is correct usage, and the review says so). Nothing you flag is
silently dropped.Nothing you flag is
silently dropped.
Step 4 — Run the review and wait for the result
/run takes the same input as /estimate, places a credit hold and
returns a job_id. Poll /jobs/{job_id} every 1–2 seconds
until status is succeeded or failed (a run typically
takes 30–90 s, since the refined rewrite is written out in full). Always send an
Idempotency-Key header so a network retry can't start a second,
double-charged run. The review is in output — usually nested as
output.output, and as a JSON string, so parse defensively. The samples
below print the review name and verdict, the five health areas and the findings, then write
rewrite.code to Refined.swift using
rewrite.filename.
JOB_ID=$(curl -s -X POST "$API/run" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: review-$(date +%s)" \
-d @input.json | jq -r '.data.job_id')
while :; do
JOB=$(curl -s "$API/jobs/$JOB_ID" -H "Authorization: Bearer $TOKEN")
STATUS=$(echo "$JOB" | jq -r '.data.status')
[ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ] && break
sleep 2
done
# unwrap the review once, then read it
echo "$JOB" | jq -r '.data.output.output' > review.json
jq -r '
"\(.review_name) [\(.verdict_level)]: \(.verdict)",
"",
"HEALTH",
(.health[] | " [\(.status)] \(.area) - \(.note)"),
"",
"FINDINGS",
(.findings[] | " (\(.severity)) \(.category): \(.title)"),
"",
"CHECKLIST",
(.checklist[] | " [\(.status)] \(.item) - \(.note)")' review.json
# and drop the refined code straight into the repo
jq -r '.rewrite.code' review.json > "$(jq -r '.rewrite.filename' review.json)" # Refined.swift
import time
job_id = api("POST", "/run", payload,
**{"Idempotency-Key": "review-001"})["job_id"]
while True:
job = api("GET", f"/jobs/{job_id}")
if job["status"] in ("succeeded", "failed"):
break
time.sleep(1.5)
if job["status"] == "failed":
raise RuntimeError(job.get("error", "run failed"))
raw = job["output"]
if isinstance(raw, dict) and "output" in raw:
raw = raw["output"]
review = json.loads(raw) if isinstance(raw, str) else raw
print(f'{review["review_name"]} [{review["verdict_level"]}]: {review["verdict"]}')
for area in review["health"]:
print(f' [{area["status"]:>4}] {area["area"]:<32} {area["note"]}')
for f in review["findings"]:
print(f' ({f["severity"]}) {f["category"]}: {f["title"]}')
if f["fix_code"]:
print(f' {f["fix_code"]}')
for item in review["checklist"]:
print(f' [{item["status"]:>4}] {item["item"]:<42} {item["note"]}')
for c in review["coverage_check"]:
print(f' {c["id"]}: {"ok" if c["addressed"] else "SET ASIDE"} - {c["note"]}')
with open(review["rewrite"]["filename"], "w", encoding="utf-8") as fh: # Refined.swift
fh.write(review["rewrite"]["code"])
import { writeFileSync } from "node:fs";
const { job_id } = await api("POST", "/run", payload,
{ "Idempotency-Key": crypto.randomUUID() });
let job;
do {
await new Promise((r) => setTimeout(r, 1500));
job = await api("GET", `/jobs/${job_id}`);
} while (job.status !== "succeeded" && job.status !== "failed");
if (job.status === "failed") throw new Error(job.error ?? "run failed");
const raw = job.output?.output ?? job.output;
const review = typeof raw === "string" ? JSON.parse(raw) : raw;
console.log(`${review.review_name} [${review.verdict_level}]: ${review.verdict}`);
for (const area of review.health) {
console.log(` [${area.status}] ${area.area}: ${area.note}`);
}
for (const f of review.findings) {
console.log(` (${f.severity}) ${f.category}: ${f.title}`);
if (f.fix_code) console.log(` ${f.fix_code}`);
}
for (const item of review.checklist) console.log(` [${item.status}] ${item.item}: ${item.note}`);
for (const c of review.coverage_check) {
console.log(` ${c.id}: ${c.addressed ? "ok" : "SET ASIDE"} - ${c.note}`);
}
writeFileSync(review.rewrite.filename, review.rewrite.code); // Refined.swift
var started struct{ JobID string `json:"job_id"` }
if err := call("POST", "/run", payload, &started); err != nil {
log.Fatal(err)
}
var job struct {
Status string `json:"status"`
Error string `json:"error"`
Output json.RawMessage `json:"output"`
}
for {
if err := call("GET", "/jobs/"+started.JobID, nil, &job); err != nil {
log.Fatal(err)
}
if job.Status == "succeeded" || job.Status == "failed" {
break
}
time.Sleep(1500 * time.Millisecond)
}
// job.Output is {"output": "<json string>"} — unwrap, unquote, then unmarshal:
type Review struct {
ReviewName string `json:"review_name"`
VerdictLevel string `json:"verdict_level"`
Verdict string `json:"verdict"`
Health []struct {
Area, Status, Note string
} `json:"health"`
Findings []struct {
Severity, Category, Title, Detail string
FixCode string `json:"fix_code"`
} `json:"findings"`
Checklist []struct {
Item, Status, Note string
} `json:"checklist"`
Rewrite struct {
Filename, Code string
} `json:"rewrite"`
}
var wrapper struct{ Output string `json:"output"` }
json.Unmarshal(job.Output, &wrapper)
var review Review
json.Unmarshal([]byte(wrapper.Output), &review)
fmt.Printf("%s [%s]: %s\n", review.ReviewName, review.VerdictLevel, review.Verdict)
for _, a := range review.Health {
fmt.Printf(" [%s] %s: %s\n", a.Status, a.Area, a.Note)
}
for _, f := range review.Findings {
fmt.Printf(" (%s) %s: %s\n", f.Severity, f.Category, f.Title)
}
for _, c := range review.Checklist {
fmt.Printf(" [%s] %s: %s\n", c.Status, c.Item, c.Note)
}
os.WriteFile(review.Rewrite.Filename, []byte(review.Rewrite.Code), 0o644) // Refined.swift
String envelope = api("POST", "/run", jsonPayload);
String jobId = /* data.job_id via your JSON library */;
while (true) {
String job = api("GET", "/jobs/" + jobId, null);
String status = /* data.status */;
if (status.equals("succeeded") || status.equals("failed")) break;
Thread.sleep(1500);
}
// The review is at data.output.output as a JSON string — parse it again, then read
// review_name, verdict_level, verdict, overview, health[] (five areas with area/status/note),
// findings[] (severity/category/title/detail/fix_code), checklist[] (item/status/note),
// coverage_check[] (id/addressed/note), rewrite{filename, code}, next_steps[] and summary.
// Finally write the refined code to disk:
// Files.writeString(Path.of(rewriteFilename), rewriteCode); // Refined.swift
started = api("POST", "/run", payload)
job = nil
loop do
job = api("GET", "/jobs/#{started["job_id"]}")
break if %w[succeeded failed].include?(job["status"])
sleep 1.5
end
raise (job["error"] || "run failed") if job["status"] == "failed"
raw = job["output"].is_a?(Hash) ? job["output"].fetch("output", job["output"]) : job["output"]
review = raw.is_a?(String) ? JSON.parse(raw) : raw
puts "#{review["review_name"]} [#{review["verdict_level"]}]: #{review["verdict"]}"
review["health"].each { |a| puts " [#{a["status"]}] #{a["area"]}: #{a["note"]}" }
review["findings"].each do |f|
puts " (#{f["severity"]}) #{f["category"]}: #{f["title"]}"
puts " #{f["fix_code"]}" unless f["fix_code"].to_s.empty?
end
review["checklist"].each { |c| puts " [#{c["status"]}] #{c["item"]}: #{c["note"]}" }
review["coverage_check"].each { |c| puts " #{c["id"]}: #{c["addressed"] ? "ok" : "SET ASIDE"}" }
File.write(review["rewrite"]["filename"], review["rewrite"]["code"]) # Refined.swift
$started = api("POST", "/run", $payload);
do {
sleep(2);
$job = api("GET", "/jobs/" . $started["job_id"]);
} while (!in_array($job["status"], ["succeeded", "failed"]));
if ($job["status"] === "failed") {
throw new Exception($job["error"] ?? "run failed");
}
$raw = is_array($job["output"]) ? ($job["output"]["output"] ?? $job["output"]) : $job["output"];
$review = is_string($raw) ? json_decode($raw, true) : $raw;
echo "{$review['review_name']} [{$review['verdict_level']}]: {$review['verdict']}\n";
foreach ($review["health"] as $a) {
echo " [{$a['status']}] {$a['area']}: {$a['note']}\n";
}
foreach ($review["findings"] as $f) {
echo " ({$f['severity']}) {$f['category']}: {$f['title']}\n";
if ($f["fix_code"] !== "") { echo " {$f['fix_code']}\n"; }
}
foreach ($review["checklist"] as $item) {
echo " [{$item['status']}] {$item['item']}: {$item['note']}\n";
}
foreach ($review["coverage_check"] as $c) {
echo " {$c['id']}: " . ($c["addressed"] ? "ok" : "SET ASIDE") . "\n";
}
file_put_contents($review["rewrite"]["filename"], $review["rewrite"]["code"]); // Refined.swift
var started = await SkillSafe.ApiAsync(HttpMethod.Post, "/run", payload);
var jobId = started.GetProperty("job_id").GetString();
JsonElement job;
while (true)
{
job = await SkillSafe.ApiAsync(HttpMethod.Get, $"/jobs/{jobId}");
var status = job.GetProperty("status").GetString();
if (status is "succeeded" or "failed") break;
await Task.Delay(1500);
}
var rawText = job.GetProperty("output").GetProperty("output").GetString();
using var doc = JsonDocument.Parse(rawText!);
var review = doc.RootElement;
Console.WriteLine($"{review.GetProperty("review_name")} " +
$"[{review.GetProperty("verdict_level")}]: {review.GetProperty("verdict")}");
foreach (var a in review.GetProperty("health").EnumerateArray())
{
Console.WriteLine($" [{a.GetProperty("status")}] {a.GetProperty("area")}: {a.GetProperty("note")}");
}
foreach (var f in review.GetProperty("findings").EnumerateArray())
{
Console.WriteLine($" ({f.GetProperty("severity")}) {f.GetProperty("category")}: " +
$"{f.GetProperty("title")}");
}
foreach (var c in review.GetProperty("checklist").EnumerateArray())
{
Console.WriteLine($" [{c.GetProperty("status")}] {c.GetProperty("item")}: {c.GetProperty("note")}");
}
var rewrite = review.GetProperty("rewrite");
await File.WriteAllTextAsync(rewrite.GetProperty("filename").GetString()!, // Refined.swift
rewrite.GetProperty("code").GetString()!);
The model is asked for one JSON object and nothing else, but a stray code fence or preamble
is always possible. Strip a leading ```json fence, take the text between the
first { and the last }, and only then parse — that is what
the app does before it falls back to a retry_note reformat run.
The review object — output schema
One JSON object, always the same shape. Every array is present (findings is
empty only if genuinely nothing applies); health always has exactly the five
areas, checklist always has exactly the twelve items, and
rewrite.code is never empty. If the paste was too thin to review responsibly,
you still get this object: what is there gets reviewed, the verdict says the
paste is thin, and what you would need to show lands in next_steps. If the
paste is not Swift/SwiftUI at all, you still get the object — one high-severity finding
explaining what arrived, every health area at risk, every checklist item at
na, and a rewrite.code block of // comments saying what
to paste instead. A paste spanning several files keeps its // ui/... file-name
comment headers, and each one is refined in place.
| Field | Type | Meaning |
|---|---|---|
review_name | string | A short name for the review, taken from the code's own domain naming — its view, model or file names. |
verdict_level | string | sound (nothing material found), refactor (findings exist but are medium/low or only bite at scale) or rework (a high finding means the UI is broken as pasted — model state lost on re-render, rows corrupting on mutation, navigation refiring, work leaking past the view's lifetime, a crash on the view path). |
verdict | string | One or two sentences: the overall state and the single most important change. |
overview | string | One or two paragraphs: what this UI code does, and the pattern behind what was found. |
health | array of 5 | {area, status, note} — the five areas listed below, each exactly once. status is good (nothing material), risk (works, with caveats) or bad (a high-severity finding lives here). Each note references something concrete in the pasted code; an area the paste does not exercise at all is good with a note saying so, unless its absence is itself the risk (a screen that loads data with no task or model in sight), which is risk with the reason. An area a high finding touches is never good. |
findings | array | {severity, category, title, detail, fix_code}. severity is high (a real defect in the code as pasted — an observed model recreated on every parent render, index-keyed rows over a mutable collection, a sticky NavigationLink(isActive:) that refires, blocking or throwing work executed in body, an onAppear task that outlives the view, a force unwrap on data the user controls, a Timer or observer never invalidated) | medium (works today but degrades or misleads — legacy observation on a non-legacy target, AnyView erasure on a hot path, a non-lazy VStack over a large ForEach in a ScrollView, a non-private @State, DispatchQueue.main.async where @MainActor belongs, hardcoded color literals, a reusable view owning a copy of hoistable state) | low (polish — naming drift, missing previews, a subview extraction that would help, minor structure); category is state, observation, rendering, composition, navigation, effects, performance, structure, naming or previews. detail quotes the view, model, property wrapper, type or expression it concerns; fix_code is corrected Swift in your own naming and style, or an empty string when the finding is a question or trade-off rather than a mechanical fix. |
checklist | array of 12 | {item, status, note} — the twelve items listed below, each exactly once and in order. status is pass (the paste shows it handled), fail (the paste shows it mishandled — a finding backs this) or na (the paste gives no evidence either way — no navigation, no async work in sight). The note says what was seen or what is missing. |
coverage_check | array | {id, addressed, note} — one entry per prescan_facts item you sent (ap:legacy-observation, i:view:FeedScreen, …), saying where the review covers it or why it was set aside (a keyword hit can be a false positive — ObservableObject on a legacy deployment target is correct usage, and a force unwrap inside test fixtures is inert; the note says so). Nothing you flagged is silently dropped. |
rewrite | object | {filename, code} — filename is normally Refined.swift (unless the paste's own file-name headers suggest a better name), and code is your own code refined: same screen, same intent, findings fixed — observation migrated to @Observable (or ownership corrected on a legacy target), @State made private, rows given stable identity, lazy containers introduced where lists are large, navigation made a typed NavigationStack path, work moved out of body into an @MainActor model method driven by .task, force unwraps replaced with guard let, hardcoded values replaced with semantic tokens, repeated styling folded into a ViewModifier. Your naming, domain vocabulary and comments are preserved, and it is a complete replacement for what you pasted, not a fragment. |
next_steps | string[] | Ordered and concrete: hold FeedModel in @State instead of a default-initialized @ObservedObject, key the ForEach on post.id, replace the sticky isActive link with a NavigationStack path, and so on. |
summary | string | 3–5 sentences a code reviewer could paste into a PR review. |
The five health areas, in order, spelled exactly like this:
| area | What its note covers |
|---|---|
State & observation | The simplest fitting wrapper is chosen (@State, @Binding, @Observable + @State, @Bindable, @Environment); models use the Observation framework on non-legacy targets so only views that read a changed property re-render; @State is private; an observed model is owned exactly once, never recreated by a parent re-render. |
Rendering & performance | ForEach rows carry stable identity, large collections sit in lazy containers, per-row work is computed in the model rather than in body, body stays cheap (no I/O, no decoding, no per-render formatter allocation, no UIScreen.main sizing), and expensive subviews skip re-renders via granular state or Equatable conformance. |
View composition & reuse | Big body blocks are split into small, focused subviews so a state change invalidates only the subview that reads it; repeated styling lives in a ViewModifier with a View extension; @ViewBuilder or Group is preferred over AnyView; reusable components hoist state instead of owning a copy of what the caller supplied. |
Effects & lifecycle | Async work rides .task {} (cancelled automatically when the view disappears) or an explicit model method, .task(id:) restarts honestly when its input changes, no onAppear-launched or detached Tasks outlive the view, no DispatchQueue hops where @MainActor belongs, and Timers or observers are always invalidated. |
Navigation & structure | Navigation is a NavigationStack with a typed Hashable destination enum and navigationDestination(for:) — not a deprecated NavigationView or a sticky isActive boolean; colors and fonts come from asset-catalog or semantic tokens rather than Color(red:green:blue:) literals; force unwraps stay off the view path; naming follows PascalCase views with clear model and destination types. |
The twelve checklist items, in order, spelled exactly like this:
| item | What its note covers |
|---|---|
State uses the simplest fitting wrapper | @State for view-local value types, @Binding for a two-way reference to a parent's state, @Observable held in @State for an owned model, a plain reference for read-only data, @Bindable for two-way bindings into an observable, @Environment for shared dependencies. |
Models use the Observation framework | Models are @Observable classes, not ObservableObject/@Published — property-level tracking means only views that read the changed property re-render. On a legacy target this item is na, not fail. |
View-local state is private | Every @State is declared private; state a caller must influence is a @Binding or a parameter instead. |
Shared dependencies ride the environment | Cross-cutting dependencies are injected with .environment() and read with @Environment, rather than reached as singletons or threaded through every initializer. |
Screens split into focused subviews | The screen's body composes small named subviews rather than one monolithic block, so invalidation stays narrow and each piece is previewable. |
No AnyView erasure on reusable paths | Conditional and switching view code uses @ViewBuilder or Group; AnyView appears only where a documented trade-off demands it. |
Navigation is a type-safe stack path | NavigationStack with a Hashable destination enum and navigationDestination(for:), path held in @State or an observable router — no NavigationView, no isActive booleans. |
ForEach rows carry stable identity | Rows iterate Identifiable elements or declare an explicit stable id: key path — never 0..<count or .indices over a collection that can change. |
Large collections render lazily | Long scrolling content sits in LazyVStack/LazyHStack/lazy grids (or List) so rows are created only when visible. |
body stays free of I/O and heavy work | No networking, decoding, file access or expensive computation executes during body evaluation; formatters and other costly objects are not allocated per render. |
Async work rides .task and cancels with the view | Loading runs in .task {} / .task(id:) on an @MainActor model method, so it cancels when the view disappears — not in onAppear-launched or detached Tasks. |
Force unwraps kept off the view path | No !, try! or as! on data the view renders — absence is handled with if let/guard let and honest empty states. |
A small, realistic result for the CheckoutScreen.swift paste above, trimmed for length:
{
"review_name": "CheckoutScreen - promo code entry",
"verdict_level": "refactor",
"verdict": "'CheckoutScreen' works, but its one piece of state is a non-private '@State' and the
screen mixes state ownership with layout; make the state private and split a
previewable content view before this grows.",
"overview": "One screen-level view that renders a total and a button applying a promo code. The
intent is clear and 'total' is already an immutable 'let', but '@State var promo'
is not private - a caller could initialize what the view owns - and the promo
string 'SAVE10' is an inline literal inside the click closure. There is no model,
no navigation and no async work in the paste, so the data-flow story stops at this
single flag.",
"health": [
{ "area": "State & observation", "status": "risk",
"note": "'@State var promo' is not private, so callers can set what the view owns; the
wrapper choice itself ('@State' for a view-local string) is right." },
{ "area": "Rendering & performance", "status": "good",
"note": "A three-view 'VStack' with no lists, no I/O in 'body' and nothing allocated per
render - nothing material at this size." },
{ "area": "View composition & reuse", "status": "risk",
"note": "'CheckoutScreen' owns the state and the whole layout in one 'body'; there is no
stateless content view to preview with a preset promo." },
{ "area": "Effects & lifecycle", "status": "good",
"note": "No '.task', 'onAppear' or Timer appears in the paste, so the area is not
exercised by this code." },
{ "area": "Navigation & structure", "status": "good",
"note": "No navigation in the paste; no hardcoded color literals; 'SAVE10' as an inline
literal is noted under findings." }
],
"findings": [
{ "severity": "medium", "category": "state",
"title": "CheckoutScreen's promo @State is not private",
"detail": "'@State var promo = \"\"' lets a caller write 'CheckoutScreen(total: t, promo:
...)' and seed state the view is supposed to own - the wrapper is right, the
access level is not.",
"fix_code": "@State private var promo = \"\"" },
{ "severity": "low", "category": "composition",
"title": "State ownership and layout live in one view",
"detail": "'CheckoutScreen' owns 'promo' and lays out the whole screen in the same 'body',
so the content cannot be previewed with a preset promo code.",
"fix_code": "struct CheckoutContent: View {\n let total: String\n let promo: String\n let onApply: () -> Void\n\n var body: some View {\n VStack {\n Text(total)\n Button(\"Apply SAVE10\") { onApply() }\n }\n }\n}" },
{ "severity": "low", "category": "structure",
"title": "The promo code is an inline literal in the click closure",
"detail": "'promo = \"SAVE10\"' hardcodes a business value inside the view; when the code
changes it will change in a view file.",
"fix_code": "" }
],
"checklist": [
{ "item": "State uses the simplest fitting wrapper", "status": "pass",
"note": "'@State' for a view-local string is the right wrapper." },
{ "item": "Models use the Observation framework", "status": "na",
"note": "No model class appears in the paste." },
{ "item": "View-local state is private", "status": "fail",
"note": "'@State var promo' is not declared private." },
{ "item": "Shared dependencies ride the environment", "status": "na",
"note": "No shared dependency appears in the paste." },
{ "item": "Screens split into focused subviews", "status": "fail",
"note": "One 'body' owns state and layout; no content subview." },
{ "item": "No AnyView erasure on reusable paths", "status": "pass",
"note": "No 'AnyView' in the paste." },
{ "item": "Navigation is a type-safe stack path", "status": "na",
"note": "No navigation in the paste." },
{ "item": "ForEach rows carry stable identity", "status": "na",
"note": "No 'ForEach' in the paste." },
{ "item": "Large collections render lazily", "status": "na",
"note": "No scrolling collection in the paste." },
{ "item": "body stays free of I/O and heavy work", "status": "pass",
"note": "'body' only reads 'total' and 'promo'." },
{ "item": "Async work rides .task and cancels with the view", "status": "na",
"note": "No async work in the paste." },
{ "item": "Force unwraps kept off the view path", "status": "pass",
"note": "No '!', 'try!' or 'as!' anywhere in the paste." }
],
"coverage_check": [
{ "id": "ap:state-not-private", "addressed": true,
"note": "Covered by the first finding - the flag becomes '@State private var'." },
{ "id": "i:view:CheckoutScreen", "addressed": true,
"note": "The view under review; refined in full in rewrite.code." }
],
"rewrite": { "filename": "Refined.swift",
"code": "import SwiftUI\n\nstruct CheckoutScreen: View {\n let total: String\n @State private var promo = \"\"\n … }" },
"next_steps": [
"Declare the promo flag '@State private var promo' so only the view can set it.",
"Split a stateless 'CheckoutContent(total:promo:onApply:)' out of the screen and add a #Preview for it.",
"Move the 'SAVE10' literal out of the view into the code that owns promo campaigns."
],
"summary": "The screen does one thing and does it nearly right: the wrapper choice is correct and
body stays cheap, but the '@State' is not private and the view mixes ownership with
layout. …"
}
The refined rewrite is a starting point, not a sign-off: it is written to be complete and self-consistent with the findings, but it is AI-generated and it only sees what you pasted. Read it, put it through the Swift compiler, SwiftLint and your snapshot or UI tests, and keep the human review in the loop before it goes anywhere near production — a change to a published view's parameters is a contract change.
Step 5 — Stream the review as it is written
/run-stream takes exactly the same body as /run but answers with
server-sent events, so you can show progress instead of a spinner — useful here
because the refined rewrite makes for a long reply. This app's own progress panel is this
endpoint. Events are separated by a blank line; each has an event: line and a
data: line carrying JSON.
| Event | Payload | Meaning |
|---|---|---|
job | {job_id, status} | Sent once, when the job is accepted — show "starting". |
delta | {text} | A chunk of the reply, in order. Append it; the accumulated length is your only progress signal (the total is not known in advance). |
done | {job_id, status, charged_credits, output} | The final, authoritative result — read the review from output.output rather than trusting concatenated deltas, and the settled price from charged_credits. |
error | {code, message} | Replaces done when the run fails. |
# -N disables buffering so events print as they arrive
curl -N -s -X POST "$API/run-stream" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: review-$(date +%s)" \
-d @input.json
# event: job
# data: {"job_id":"job_...","status":"running"}
#
# event: delta
# data: {"text":"{\"review_name\":\"CheckoutScreen"}
# ...
# event: done
# data: {"job_id":"job_...","status":"succeeded","charged_credits":612,"output":{"output":"{...}"}}
import json, requests
result = None
with requests.post(
API + "/run-stream",
headers={"Authorization": f"Bearer {TOKEN}",
"Idempotency-Key": "review-001"},
json=payload,
stream=True,
) as r:
r.raise_for_status()
event = None
for line in r.iter_lines(decode_unicode=True):
if not line:
continue
if line.startswith("event:"):
event = line[len("event:"):].strip()
elif line.startswith("data:"):
data = json.loads(line[len("data:"):].strip())
if event == "delta":
print(".", end="", flush=True) # live progress
elif event == "done":
result = data
elif event == "error":
raise RuntimeError(data.get("message", "run failed"))
review = json.loads(result["output"]["output"]) # authoritative
print("charged:", result["charged_credits"], "-", review["review_name"])
for area in review["health"]:
print(f' [{area["status"]}] {area["area"]}')
open(review["rewrite"]["filename"], "w", encoding="utf-8").write(review["rewrite"]["code"])
const res = await fetch(API + "/run-stream", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify(payload),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = "", done = null;
for (;;) {
const chunk = await reader.read();
if (chunk.done) break;
buf += decoder.decode(chunk.value, { stream: true });
const frames = buf.split("\n\n");
buf = frames.pop();
for (const frame of frames) {
const name = /^event:\s*(.+)$/m.exec(frame)?.[1];
const body = /^data:\s*(.+)$/m.exec(frame)?.[1];
if (!name || !body) continue;
const data = JSON.parse(body);
if (name === "delta") process.stdout.write("."); // live progress
if (name === "done") done = data;
if (name === "error") throw new Error(data.message ?? "run failed");
}
}
const review = JSON.parse(done.output.output);
console.log(`\n${done.charged_credits} credits - ${review.review_name}`);
for (const area of review.health) console.log(` [${area.status}] ${area.area}`);
writeFileSync(review.rewrite.filename, review.rewrite.code); // Refined.swift
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", API+"/run-stream", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "review-001")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var event string
var final map[string]any
sc := bufio.NewScanner(res.Body)
sc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)
for sc.Scan() {
line := sc.Text()
switch {
case strings.HasPrefix(line, "event:"):
event = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
case strings.HasPrefix(line, "data:"):
var data map[string]any
json.Unmarshal([]byte(strings.TrimPrefix(line, "data:")), &data)
switch event {
case "delta":
fmt.Print(".") // live progress
case "done":
final = data
case "error":
log.Fatal(data["message"])
}
}
}
// final["output"].(map[string]any)["output"].(string) is the review JSON —
// unmarshal it into the Review struct from step 4, then write review.Rewrite.Code to disk.
// Java 17+ — read the stream line by line instead of buffering the body.
var req = HttpRequest.newBuilder(URI.create(API + "/run-stream"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.header("Idempotency-Key", "review-001")
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
.build();
var res = HTTP.send(req, HttpResponse.BodyHandlers.ofLines());
String event = null, done = null;
for (String line : (Iterable<String>) res.body()::iterator) {
if (line.startsWith("event:")) {
event = line.substring(6).trim();
} else if (line.startsWith("data:")) {
String data = line.substring(5).trim();
if ("delta".equals(event)) System.out.print("."); // live progress
else if ("done".equals(event)) done = data;
else if ("error".equals(event)) throw new RuntimeException(data);
}
}
// parse `done`, then parse data.output.output again — it is a JSON string holding
// review_name, verdict_level, health[], findings[], checklist[], rewrite{filename, code} and the rest.
require "net/http"
require "json"
uri = URI(API + "/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = "review-001"
req.body = payload.to_json
event = nil
done = nil
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.request(req) do |res|
res.read_body do |chunk|
chunk.each_line do |line|
line = line.strip
if line.start_with?("event:")
event = line.delete_prefix("event:").strip
elsif line.start_with?("data:")
data = JSON.parse(line.delete_prefix("data:").strip)
case event
when "delta" then print "." # live progress
when "done" then done = data
when "error" then raise (data["message"] || "run failed")
end
end
end
end
end
end
review = JSON.parse(done["output"]["output"])
puts "\n#{done["charged_credits"]} credits - #{review["review_name"]}"
review["health"].each { |a| puts " [#{a["status"]}] #{a["area"]}" }
File.write(review["rewrite"]["filename"], review["rewrite"]["code"]) # Refined.swift
$event = null;
$done = null;
$ch = curl_init(API . "/run-stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $TOKEN",
"Content-Type: application/json",
"Idempotency-Key: review-001",
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$event, &$done) {
foreach (explode("\n", $chunk) as $line) {
$line = trim($line);
if (str_starts_with($line, "event:")) {
$event = trim(substr($line, 6));
} elseif (str_starts_with($line, "data:")) {
$data = json_decode(trim(substr($line, 5)), true);
if ($event === "delta") { echo "."; } // live progress
elseif ($event === "done") { $done = $data; }
elseif ($event === "error") { throw new Exception($data["message"] ?? "run failed"); }
}
}
return strlen($chunk);
},
]);
curl_exec($ch);
curl_close($ch);
$review = json_decode($done["output"]["output"], true);
echo "\n{$done['charged_credits']} credits - {$review['review_name']}\n";
foreach ($review["health"] as $a) { echo " [{$a['status']}] {$a['area']}\n"; }
file_put_contents($review["rewrite"]["filename"], $review["rewrite"]["code"]); // Refined.swift
var req = new HttpRequestMessage(HttpMethod.Post, Api + "/run-stream") {
Content = JsonContent.Create(payload),
};
req.Headers.Add("Idempotency-Key", "review-001");
using var res = await Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await res.Content.ReadAsStreamAsync());
string? evt = null, done = null;
while (await reader.ReadLineAsync() is { } line)
{
if (line.StartsWith("event:")) evt = line[6..].Trim();
else if (line.StartsWith("data:"))
{
var data = line[5..].Trim();
if (evt == "delta") Console.Write("."); // live progress
else if (evt == "done") done = data;
else if (evt == "error") throw new Exception(data);
}
}
using var final = JsonDocument.Parse(done!);
var text = final.RootElement.GetProperty("output").GetProperty("output").GetString();
using var reviewDoc = JsonDocument.Parse(text!);
var review = reviewDoc.RootElement;
Console.WriteLine(review.GetProperty("review_name"));
foreach (var a in review.GetProperty("health").EnumerateArray())
Console.WriteLine($" [{a.GetProperty("status")}] {a.GetProperty("area")}");
var rewrite = review.GetProperty("rewrite");
await File.WriteAllTextAsync(rewrite.GetProperty("filename").GetString()!, // Refined.swift
rewrite.GetProperty("code").GetString()!);
In a browser, the native EventSource only speaks GET, and this endpoint is a
POST — read the fetch response body incrementally, as the JavaScript
sample above does. On an idempotent replay the server may answer with a plain JSON
envelope instead of an event stream; check the Content-Type before you start
parsing frames.