Swift Shift — API

Paste the Swift, get a Swift 6.2 concurrency migration review and a fully migrated rewrite.

API tokens Open the app

Review your Swift concurrency from your own scripts

Send Swift — one file, a type, several files with // File: comment headers, or a whole grab-bag of snippets — and get back one JSON object: an honest ready / adopt / rework verdict, a health check across five concurrency areas, findings ranked by severity each with corrected Swift, a ten-item checklist scored against the paste, a fully migrated Swift 6.2 rewrite of what you pasted, and an ordered migration plan that starts with the build settings. 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 @unchecked Sendable over shared mutable state. 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 swift-shift. 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.

The input is the Swift you want migrated plus a little context: code, the source_version it compiles under today, the target it builds into, free-form notes, and an optional prescan object of mechanically detected concurrency constructs — omit it and the review runs on the code alone. The output is one JSON object: a ready / adopt / rework verdict, a five-area health check, severity-ranked findings with corrected Swift, a ten-item checklist, a fully migrated rewrite, and a migration_plan that starts with build settings and ends with code changes.

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":"swift-shift"}' | jq -r '.data.token'
token = api("POST", "/guest", {"slug": "swift-shift"})["token"]
const { token } = await api("POST", "/guest", { slug: "swift-shift" });
var guest struct{ Token string `json:"token"` }
err := call("POST", "/guest", map[string]string{"slug": "swift-shift"}, &guest)
String envelope = api("POST", "/guest", """
    {"slug":"swift-shift"}""");
// token is at data.token in the returned JSON
token = api("POST", "/guest", { slug: "swift-shift" })["token"]
$token = api("POST", "/guest", ["slug" => "swift-shift"])["token"];
var guest = await SkillSafe.ApiAsync(HttpMethod.Post, "/guest",
    new { slug = "swift-shift" });
var token = guest.GetProperty("token").GetString();

The app stores this browser's token under the localStorage key skillsafe_app_token:swift-shift, 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, or several files concatenated with // File: Name.swift comment headers. Very long pastes are clipped middle-out, with a // [... clipped ...] marker showing where.
source_versionstringswift5 | swift60 | swift61 | unknown — the language mode the code compiles under today. It calibrates the migration plan: how far the paste already is from Swift 6.2, and which diagnostics it is currently seeing.
targetstringapp | library | executable | unknown — what the code is. app and executable targets are candidates for MainActor default inference (SE-0466); library targets must keep their public isolation explicit, since callers depend on it. On unknown the review infers from the paste and says which it assumed.
notesstring, optionalExtra context: what the code does, which APIs cannot break, what is intentionally unfinished, known hot paths.
prescanobject, optionalWhat the client-side prescanner mechanically detected: patterns (array of {id, label, lines} — concurrency constructs matched by regex, with ids like dq:dispatch-queue, lock:mutex, cb:completion-handler, task:unstructured, ma:mainactor, conc:concurrent, noniso:nonisolated, unchecked:sendable, send:sendable, global:mutable-static, async:async-func, await:await-expr, file:multi-file), types (array of {id, label, lines} — declared class/struct/actor/enum names, with ids like type:actor:JobStore), and signals (counts plus the derived booleans legacyGcd, callbackStyle and sharedMutableGlobals). Every patterns and types id you send must be reconciled by the review in coverage_check. The web UI fills this from its own scan; API callers may omit it entirely and the review runs on the code alone.
retry_notestring, optionalOnly set by the app's automatic reformat retry when a first reply was not valid JSON. Leave it out.
cat > ImageCache.swift <<'SWIFT'
// File: ImageCache.swift
import Foundation

final class ImageCache {
    static let shared = ImageCache()
    private var store: [String: Data] = [:]
    private let queue = DispatchQueue(label: "cache")

    func load(_ key: String, completion: @escaping (Data?) -> Void) {
        queue.async {
            completion(self.store[key])
        }
    }
}
SWIFT

jq -n --rawfile c ImageCache.swift \
  '{code: $c, source_version: "swift5", target: "app",
    notes: "Shared image cache used from view code; must stay a singleton."}' > 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 = """// File: ImageCache.swift
import Foundation

final class ImageCache {
    static let shared = ImageCache()
    private var store: [String: Data] = [:]
    private let queue = DispatchQueue(label: "cache")

    func load(_ key: String, completion: @escaping (Data?) -> Void) {
        queue.async {
            completion(self.store[key])
        }
    }
}"""

payload = {
    "code": CODE,
    "source_version": "swift5",
    "target": "app",
    "notes": "Shared image cache used from view code; must stay a singleton.",
}

est = api("POST", "/estimate", payload)
print("worst case:", est.get("hold_credits", est.get("credits")), "credits")
const code = `// File: ImageCache.swift
import Foundation

final class ImageCache {
    static let shared = ImageCache()
    private var store: [String: Data] = [:]
    private let queue = DispatchQueue(label: "cache")

    func load(_ key: String, completion: @escaping (Data?) -> Void) {
        queue.async {
            completion(self.store[key])
        }
    }
}`;

const payload = {
  code,
  source_version: "swift5",
  target: "app",
  notes: "Shared image cache used from view code; must stay a singleton.",
};

const est = await api("POST", "/estimate", payload);
console.log("worst case:", est.hold_credits ?? est.credits, "credits");
const code = `// File: ImageCache.swift
import Foundation

final class ImageCache {
    static let shared = ImageCache()
    private var store: [String: Data] = [:]
    private let queue = DispatchQueue(label: "cache")

    func load(_ key: String, completion: @escaping (Data?) -> Void) {
        queue.async {
            completion(self.store[key])
        }
    }
}`

payload := map[string]any{
	"code":           code,
	"source_version": "swift5",
	"target":         "app",
	"notes":          "Shared image cache used from view code; must stay a singleton.",
}

var est struct{ HoldCredits int64 `json:"hold_credits"` }
err := call("POST", "/estimate", payload, &est)
String code = """
    // File: ImageCache.swift
    import Foundation

    final class ImageCache {
        static let shared = ImageCache()
        private var store: [String: Data] = [:]
        private let queue = DispatchQueue(label: "cache")

        func load(_ key: String, completion: @escaping (Data?) -> Void) {
            queue.async {
                completion(self.store[key])
            }
        }
    }""";

String jsonPayload = """
    {"code": %s, "source_version": "swift5", "target": "app",
     "notes": "Shared image cache used from view code; must stay a singleton."}
    """.formatted(toJsonString(code));

String envelope = api("POST", "/estimate", jsonPayload);
// worst-case cost is at data.hold_credits
CODE_TEXT = <<~'SWIFT'
  // File: ImageCache.swift
  import Foundation

  final class ImageCache {
      static let shared = ImageCache()
      private var store: [String: Data] = [:]
      private let queue = DispatchQueue(label: "cache")

      func load(_ key: String, completion: @escaping (Data?) -> Void) {
          queue.async {
              completion(self.store[key])
          }
      }
  }
SWIFT

payload = { code: CODE_TEXT, source_version: "swift5", target: "app",
            notes: "Shared image cache used from view code; must stay a singleton." }

est = api("POST", "/estimate", payload)
puts "worst case: #{est["hold_credits"] || est["credits"]} credits"
$code = <<<'SWIFT'
// File: ImageCache.swift
import Foundation

final class ImageCache {
    static let shared = ImageCache()
    private var store: [String: Data] = [:]
    private let queue = DispatchQueue(label: "cache")

    func load(_ key: String, completion: @escaping (Data?) -> Void) {
        queue.async {
            completion(self.store[key])
        }
    }
}
SWIFT;

$payload = [
    "code"           => $code,
    "source_version" => "swift5",
    "target"         => "app",
    "notes"          => "Shared image cache used from view code; must stay a singleton.",
];

$est = api("POST", "/estimate", $payload);
echo "worst case: " . ($est["hold_credits"] ?? $est["credits"]) . " credits\n";
var code = """
    // File: ImageCache.swift
    import Foundation

    final class ImageCache {
        static let shared = ImageCache()
        private var store: [String: Data] = [:]
        private let queue = DispatchQueue(label: "cache")

        func load(_ key: String, completion: @escaping (Data?) -> Void) {
            queue.async {
                completion(self.store[key])
            }
        }
    }
    """;

var payload = new {
    code,
    source_version = "swift5",
    target = "app",
    notes = "Shared image cache used from view code; must stay a singleton.",
};

var est = await SkillSafe.ApiAsync(HttpMethod.Post, "/estimate", payload);
Console.WriteLine($"worst case: {est.GetProperty("hold_credits")} credits");

prescan is optional — omit it and the review runs on the code alone — but it is how you make the review answer for things you already know about. Send {"patterns": [{"id": "dq:dispatch-queue", "label": "DispatchQueue", "lines": [7, 10]}, {"id": "cb:completion-handler", "label": "completion handler", "lines": [9]}, {"id": "global:mutable-static", "label": "mutable static state", "lines": [5, 6]}], "types": [{"id": "type:class:ImageCache", "label": "class ImageCache", "lines": [4]}], "signals": {"legacyGcd": true, "callbackStyle": true, "sharedMutableGlobals": true}} and every one of those patterns and types ids comes back in coverage_check — addressed, or explained away as a false positive (a DispatchQueue used only as a serial label for logging is not a data race, and the review says so). 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 migrated 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, the findings and the migration plan, then write rewrite.code to Migrated.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)"),
  "",
  "MIGRATION PLAN",
  (.migration_plan[] | "  - \(.)")' review.json

# and drop the migrated code straight into the repo
jq -r '.rewrite.code' review.json > "$(jq -r '.rewrite.filename' review.json)"   # Migrated.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"]}')
for i, step in enumerate(review["migration_plan"], 1):
    print(f'  {i}. {step}')

with open(review["rewrite"]["filename"], "w", encoding="utf-8") as fh:   # Migrated.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}`);
}
review.migration_plan.forEach((step, i) => console.log(`  ${i + 1}. ${step}`));

writeFileSync(review.rewrite.filename, review.rewrite.code);   // Migrated.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"`
	MigrationPlan []string `json:"migration_plan"`
}
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)
}
for i, step := range review.MigrationPlan {
	fmt.Printf("  %d. %s\n", i+1, step)
}
os.WriteFile(review.Rewrite.Filename, []byte(review.Rewrite.Code), 0o644) // Migrated.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[] (ten items: item/status/note),
// coverage_check[] (id/addressed/note), rewrite{filename, code}, migration_plan[], next_steps[]
// and summary.
// Finally write the migrated code to disk:
//   Files.writeString(Path.of(rewriteFilename), rewriteCode);   // Migrated.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"}" }
review["migration_plan"].each_with_index { |s, i| puts "  #{i + 1}. #{s}" }

File.write(review["rewrite"]["filename"], review["rewrite"]["code"])   # Migrated.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";
}
foreach ($review["migration_plan"] as $i => $step) {
    echo "  " . ($i + 1) . ". $step\n";
}

file_put_contents($review["rewrite"]["filename"], $review["rewrite"]["code"]);   // Migrated.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")}");
}
foreach (var step in review.GetProperty("migration_plan").EnumerateArray())
{
    Console.WriteLine($"  - {step}");
}

var rewrite = review.GetProperty("rewrite");
await File.WriteAllTextAsync(rewrite.GetProperty("filename").GetString()!,   // Migrated.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 ten 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 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 // File: ... file-name comment headers, and each one is migrated in place.

FieldTypeMeaning
review_namestringA short name for the review, taken from the code's own domain naming — its type or file names.
verdict_levelstringready (already Swift 6.2-safe), adopt (migration needed, but mechanical) or rework (real data races or unsound concurrency as pasted).
verdictstringOne or two sentences: the overall state and the single most important change.
overviewstringOne or two paragraphs: what this code does, and the pattern behind what was found.
healtharray of 5{area, status, note} — the five areas listed below, each exactly once and in order. 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. An area a high finding touches is never good.
findingsarray{severity, category, title, detail, fix_code}. severity is high (a real data race, unsound isolation, @unchecked Sendable over genuinely shared mutable state, unprotected global mutable state) | medium (works today but will fight the 6.2 compiler or mislead — nonisolated used to silence errors, @concurrent on non-CPU-bound work, a legacy DispatchQueue where an actor belongs) | low (polish — completion handlers that could be async, a missing isolation rationale on an unstructured Task); category is isolation, data-race, offloading, globals, conformance, legacy-patterns, sendable or structure. detail quotes the pasted code verbatim; 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 10{item, status, note} — the ten 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 unstructured tasks, no global state). The note says what was seen or what is missing.
coverage_checkarray{id, addressed, note} — one entry per prescan patterns or types id you sent (dq:dispatch-queue, type:actor:JobStore, …), confirming where the review covers it or why it was set aside (a regex hit can be a false positive; the note says so). Nothing you flagged is silently dropped.
rewriteobject{filename, code} — the full migrated Swift 6.2 source. filename defaults to Migrated.swift (unless the paste's own // File: headers suggest a better name), and code is your own code migrated: same domain, same intent, findings fixed — shared mutable state moved into actors, completion handlers turned into async functions, legacy queues and locks retired, isolation made explicit and @concurrent reserved for CPU-heavy work. Your naming, domain vocabulary, // File: headers and comments are preserved, and it is a complete replacement for what you pasted, not a fragment.
migration_planstring[]Ordered, concrete steps for this paste: first the Xcode / SwiftPM build-setting changes (Approachable Concurrency, SE-0466 MainActor default isolation, SE-0461 NonisolatedNonsendingByDefault), then the code changes, in the order they should be applied.
next_stepsstring[]Ordered and concrete follow-up actions once the plan has been applied: what to test, what to measure, what to migrate next.
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
Actor isolationEvery type that holds mutable state has a stated isolation domain — an actor, @MainActor, or a documented nonisolated — and nonisolated is never used simply to make a diagnostic go away. Under SE-0466 an app or executable target can infer @MainActor by default; a library spells its public isolation out.
Data-race safetyNo mutable state is reachable from two isolation domains at once: no captured self mutated inside a detached closure, no dictionary or array shared across queues, and no @unchecked Sendable standing in for an argument nobody has made.
Async & offloadingAsync functions stay on the calling actor (SE-0461) unless there is a reason to leave it, @concurrent is reserved for genuinely CPU-heavy work, and unstructured Tasks carry a rationale for the isolation they run in.
Global & static stateGlobal and static var state is actor-protected, immutable let, or explicitly isolated — a mutable singleton reachable from any thread is the classic Swift 6 error, and the fix is usually to make the type an actor or pin it to @MainActor.
Protocol conformancesConformances that need main-actor state use isolated conformance syntax rather than nonisolated shims or assumeIsolated escapes, and Sendable conformances are earned by the type's contents rather than asserted.

The ten checklist items, in order, spelled exactly like this:

itemWhat its note covers
Async functions stay on the calling actorUnder SE-0461 a nonisolated async function runs on its caller's actor; the paste does not hop off it accidentally, and any deliberate hop is spelled out.
Global/static mutable state is actor-protectedEvery global or static var is an actor, isolated to @MainActor, or made an immutable let — nothing mutable is reachable unsynchronized.
@concurrent used only for CPU-heavy work@concurrent marks work that genuinely benefits from leaving the caller's actor; awaiting I/O does not qualify.
No nonisolated used to silence isolation errorsEach nonisolated exists because the member truly touches no isolated state, not because it made a diagnostic disappear.
No @unchecked Sendable without a safety argumentAny @unchecked Sendable is backed by a written argument for why the type is safe to share; otherwise the type becomes an actor or a value type.
Legacy DispatchQueue/lock patterns replaced by actorsSerial queues, NSLock and os_unfair_lock used as ad-hoc mutual exclusion give way to an actor that owns the state.
Completion handlers migrated to async/awaitCallback-style APIs are expressed as async functions, so errors and cancellation flow through the language instead of around it.
Unstructured Tasks carry an isolation rationaleEvery Task { } or Task.detached { } says which isolation it runs in and why, and its lifetime is owned by something.
MainActor conformances use isolated conformance syntaxA conformance needing main-actor state is declared as an isolated conformance rather than papered over with nonisolated shims or assumeIsolated.
Build settings plan covers SE-0466 / SE-0461The migration plan names the actual Xcode / SwiftPM settings to flip — Approachable Concurrency, MainActor default isolation, NonisolatedNonsendingByDefault — before any code change.

A small, realistic result for the ImageCache.swift paste above, trimmed for length:

{
  "review_name": "ImageCache - shared image store",
  "verdict_level": "rework",
  "verdict": "'ImageCache' is a mutable singleton whose dictionary is read and written from a
              private serial queue while 'shared' is reachable from any isolation domain; make
              the type an actor before anything else.",
  "overview": "A single final class caches Data blobs by String key. It keeps a static
               'shared' instance, a mutable dictionary, and a DispatchQueue used as an ad-hoc
               lock, then hands results back through a completion handler. The intent - one
               cache, serialized access - is exactly what an actor expresses natively, but as
               written the compiler cannot see the serialization: 'shared' is a mutable global
               reachable from any thread, the closure passed to 'queue.async' captures 'self'
               and mutates state the caller may also touch, and the completion handler runs on
               whichever queue the work happened to finish on. The target is 'app', so the type
               is also a candidate for MainActor default inference once SE-0466 is enabled.",
  "health": [
    { "area": "Actor isolation", "status": "bad",
      "note": "'final class ImageCache' declares no isolation at all, yet every member touches
               the shared 'store' dictionary." },
    { "area": "Data-race safety", "status": "bad",
      "note": "'queue.async { completion(self.store[key]) }' reads 'store' off the queue while
               nothing stops another caller writing it from a different thread." },
    { "area": "Async & offloading", "status": "risk",
      "note": "'load' is callback-based, so cancellation and errors cannot propagate, and the
               completion handler runs on the private queue rather than the caller's actor." },
    { "area": "Global & static state", "status": "bad",
      "note": "'static let shared = ImageCache()' exposes mutable instance state as a global;
               this is the canonical Swift 6 concurrency error." },
    { "area": "Protocol conformances", "status": "good",
      "note": "The paste declares no protocol conformances, so no isolated-conformance question
               arises yet." }
  ],
  "findings": [
    { "severity": "high", "category": "data-race",
      "title": "store is mutated and read without isolation",
      "detail": "'private var store: [String: Data] = [:]' is guarded only by convention:
                 'queue.async' serializes the read in 'load', but nothing in the type's
                 signature stops a caller writing 'store' from another domain.",
      "fix_code": "actor ImageCache {\n    static let shared = ImageCache()\n    private var store: [String: Data] = [:]\n\n    func load(_ key: String) -> Data? {\n        store[key]\n    }\n}" },
    { "severity": "high", "category": "globals",
      "title": "shared is a mutable singleton with no isolation domain",
      "detail": "'static let shared = ImageCache()' is a global handle onto mutable state; once
                 strict concurrency is on, every use of it is diagnosed.",
      "fix_code": "actor ImageCache {\n    static let shared = ImageCache()\n}\n// or, for view-facing caches on an app target:\n// @MainActor final class ImageCache { static let shared = ImageCache() }" },
    { "severity": "medium", "category": "legacy-patterns",
      "title": "DispatchQueue used as an ad-hoc lock",
      "detail": "'private let queue = DispatchQueue(label: \"cache\")' exists only to serialize
                 access to 'store' - which is precisely what actor isolation provides, checked
                 by the compiler rather than by discipline.",
      "fix_code": "// delete the queue entirely; the actor serializes access\nactor ImageCache {\n    private var store: [String: Data] = [:]\n}" },
    { "severity": "low", "category": "offloading",
      "title": "Completion handler where async would do",
      "detail": "'func load(_ key: String, completion: @escaping (Data?) -> Void)' forces every
                 caller into a closure and drops cancellation on the floor.",
      "fix_code": "func load(_ key: String) async -> Data? {\n    store[key]\n}" }
  ],
  "checklist": [
    { "item": "Async functions stay on the calling actor", "status": "na",
      "note": "No async functions in the paste; 'load' is callback-based." },
    { "item": "Global/static mutable state is actor-protected", "status": "fail",
      "note": "'static let shared' vends an unisolated instance holding a mutable dictionary." },
    { "item": "@concurrent used only for CPU-heavy work", "status": "na",
      "note": "No '@concurrent' attribute appears in the paste." },
    { "item": "No nonisolated used to silence isolation errors", "status": "na",
      "note": "No 'nonisolated' members; the type has no isolation to opt out of yet." },
    { "item": "No @unchecked Sendable without a safety argument", "status": "pass",
      "note": "The type asserts no Sendable conformance - it simply is not Sendable today." },
    { "item": "Legacy DispatchQueue/lock patterns replaced by actors", "status": "fail",
      "note": "'DispatchQueue(label: \"cache\")' is the only synchronization in the paste." },
    { "item": "Completion handlers migrated to async/await", "status": "fail",
      "note": "'load' takes an '@escaping (Data?) -> Void' completion instead of returning." },
    { "item": "Unstructured Tasks carry an isolation rationale", "status": "na",
      "note": "No 'Task { }' or 'Task.detached { }' in the paste." },
    { "item": "MainActor conformances use isolated conformance syntax", "status": "na",
      "note": "The type conforms to no protocols." },
    { "item": "Build settings plan covers SE-0466 / SE-0461", "status": "fail",
      "note": "No package or target settings were shown; the plan below supplies them." }
  ],
  "coverage_check": [
    { "id": "dq:dispatch-queue", "addressed": true,
      "note": "Third finding - the queue is removed and the actor serializes access." },
    { "id": "cb:completion-handler", "addressed": true,
      "note": "Fourth finding - 'load' becomes an async function returning 'Data?'." },
    { "id": "global:mutable-static", "addressed": true,
      "note": "Second finding - 'shared' becomes a handle onto an actor." },
    { "id": "type:class:ImageCache", "addressed": true,
      "note": "The type under review; migrated in full in rewrite.code." }
  ],
  "rewrite": { "filename": "Migrated.swift",
               "code": "// File: ImageCache.swift\nimport Foundation\n\nactor ImageCache {\n    static let shared = ImageCache()\n    private var store: [String: Data] = [:]\n\n    func load(_ key: String) -> Data? {\n        store[key]\n    }\n\n    func insert(_ data: Data, for key: String) {\n        store[key] = data\n    }\n}" },
  "migration_plan": [
    "Turn on Approachable Concurrency for the target (Xcode: Swift Compiler - Concurrency;
     SwiftPM: .enableUpcomingFeature settings on the target).",
    "Enable SE-0466 MainActor default isolation for this app target, so unannotated types
     default to the main actor rather than to nothing.",
    "Enable SE-0461 NonisolatedNonsendingByDefault so nonisolated async functions stay on the
     caller's actor.",
    "Change 'final class ImageCache' to 'actor ImageCache' and delete the DispatchQueue.",
    "Replace 'load(_:completion:)' with 'func load(_ key: String) -> Data?' and update the
     call sites to 'await ImageCache.shared.load(key)'.",
    "Add the missing write path ('insert(_:for:)') so callers never reach 'store' directly."
  ],
  "next_steps": [
    "Audit the view code that calls 'load' - each call site becomes an 'await' inside a Task
     owned by the view's lifetime.",
    "Decide whether the cache should be an actor or '@MainActor', based on whether it is read
     from background work at all.",
    "Build once with strict concurrency on and fix the remaining diagnostics in call sites."
  ],
  "summary": "'ImageCache' serializes access by convention rather than by isolation: a mutable
              static singleton, a dictionary reachable from any thread, and a DispatchQueue
              standing in for a lock. …"
}

The migrated 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, build it with strict concurrency enabled, run your test suite, and keep the human review in the loop before it goes anywhere near production — changing a public type's isolation is a source-breaking change for every caller.

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 migrated 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 relying on 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\":\"ImageCache"}
# ...
# 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);   // Migrated.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"])   # Migrated.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"]);   // Migrated.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()!,   // Migrated.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.