SwiftUI Clinic — API

Paste the Swift, get a senior SwiftUI review and a full refined rewrite.

API tokens Open the app

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.

StatusMeaning
401Missing or expired token — create a new session.
402Not enough credits — top up at skillsafe.ai/account/credits.
403The token isn't allowed to do this (e.g. a guest reviewing a very large paste).
404Unknown job or record id.
5xxTransient 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

POST /guest

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

GET /me

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

POST /estimate

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 fieldTypeNotes
codestring, requiredThe 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.
targetstringapp | 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.
notesstring, optionalExtra 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_factsobject, optionalWhat 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_notestring, optionalOnly 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

POST /run
GET /jobs/{job_id}

/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.

FieldTypeMeaning
review_namestringA short name for the review, taken from the code's own domain naming — its view, model or file names.
verdict_levelstringsound (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).
verdictstringOne or two sentences: the overall state and the single most important change.
overviewstringOne or two paragraphs: what this UI code does, and the pattern behind what was found.
healtharray 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.
findingsarray{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.
checklistarray 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_checkarray{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.
rewriteobject{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_stepsstring[]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.
summarystring3–5 sentences a code reviewer could paste into a PR review.

The five health areas, in order, spelled exactly like this:

areaWhat its note covers
State & observationThe 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 & performanceForEach 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 & reuseBig 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 & lifecycleAsync 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 & structureNavigation 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:

itemWhat 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 frameworkModels 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 privateEvery @State is declared private; state a caller must influence is a @Binding or a parameter instead.
Shared dependencies ride the environmentCross-cutting dependencies are injected with .environment() and read with @Environment, rather than reached as singletons or threaded through every initializer.
Screens split into focused subviewsThe 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 pathsConditional and switching view code uses @ViewBuilder or Group; AnyView appears only where a documented trade-off demands it.
Navigation is a type-safe stack pathNavigationStack 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 identityRows 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 lazilyLong 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 workNo 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 viewLoading 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 pathNo !, 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

POST /run-stream

/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.

EventPayloadMeaning
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.