Everything the app does is one of five HTTPS calls against the SkillSafe App API. Paste a module in, get back a plain-text reply in a fixed contract, and pull the test file out of it. Every 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. There is no
/apps/<slug>/ path segment — the app is bound to the token you mint at
/guest, so every later call is just /me, /estimate,
/run, /run-stream. Every request carries
Authorization: Bearer <token> and, when it has a body,
Content-Type: application/json.
Responses use an envelope. Success is
{"ok":true,"data":{...},"meta":{...}}; failure is
{"ok":false,"error":{"code":"...","message":"..."}}. Read your payload out of
data, never off the top level.
| Error code | Status | What it means |
|---|---|---|
unauthorized | 401 | Missing, malformed or expired token. Mint a new one at /guest or sign in again. |
not_found | 404 | Unknown path or job id. Almost always a URL with an extra /apps/test-forge segment in it. |
payment_required | 402 | Not enough credits to place the hold. Top up, or call /estimate first. |
validation_error | 422 | The input object was rejected — usually an empty code or a framework that is neither Vitest nor Jest. |
rate_limited | 429 | Too many calls too fast. Back off and retry; honour Retry-After when present. |
internal_error | 5xx | Transient platform fault. Retry with backoff and the same Idempotency-Key. |
Browsers enforce CORS on this API, so run these examples from a terminal, a server or a CI job — not from another site's frontend.
Step 0 — A tiny client
Every later step is one HTTP call, so start with a small helper that sets the auth header,
sends JSON and unwraps the data envelope. The rest of the page reuses it.
export API="https://api.skillsafe.ai/v1/app-api"
export SKILLSAFE_TOKEN="YOUR_TOKEN" # see step 1
# every call below is one of these two shapes:
# curl -s "$API/<path>" -H "Authorization: Bearer $SKILLSAFE_TOKEN"
# curl -s -X POST "$API/<path>" -H "Authorization: Bearer $SKILLSAFE_TOKEN" \
# -H "Content-Type: application/json" -d '{...}'
# jq pulls fields out of the {"ok":true,"data":{...}} envelope
import json, requests
API = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # see step 1
def api(method, path, body=None, **extra_headers):
headers = {"Authorization": "Bearer " + TOKEN}
headers.update(extra_headers)
res = requests.request(method, API + path, json=body, headers=headers)
payload = res.json()
if not res.ok:
err = payload.get("error") or {}
raise RuntimeError("%s: %s" % (err.get("code", res.status_code),
err.get("message", res.reason)))
return payload["data"]
// Node 18+ (built-in fetch). Keep the token out of source control in real code.
const API = "https://api.skillsafe.ai/v1/app-api";
const TOKEN = "YOUR_TOKEN"; // see step 1
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 payload = await res.json();
if (!res.ok) {
const e = payload.error || {};
throw new Error(`${e.code || res.status}: ${e.message || res.statusText}`);
}
return payload.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
type apiError struct {
Code string `json:"code"`
Message string `json:"message"`
}
func call(method, path string, body, out any, extra map[string]string) 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")
for k, v := range extra {
req.Header.Set(k, v)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var env struct {
OK bool `json:"ok"`
Data json.RawMessage `json:"data"`
Error *apiError `json:"error"`
}
json.NewDecoder(res.Body).Decode(&env)
if res.StatusCode >= 400 {
if env.Error != nil {
return fmt.Errorf("%s: %s", env.Error.Code, env.Error.Message)
}
return fmt.Errorf("http %d", res.StatusCode)
}
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;
import java.util.Map;
public class TestForge {
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,
Map<String, String> extra) throws Exception {
var b = 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));
if (extra != null) extra.forEach(b::header);
var res = HTTP.send(b.build(), HttpResponse.BodyHandlers.ofString());
if (res.statusCode() >= 400) throw new RuntimeException(res.body());
return res.body(); // envelope: {"ok":true,"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, extra = {})
uri = URI(API + path)
req = Net::HTTP.const_get(method.capitalize).new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
extra.each { |k, v| req[k] = v }
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)
unless res.is_a?(Net::HTTPSuccess)
err = payload["error"] || {}
raise "#{err["code"] || res.code}: #{err["message"] || res.message}"
end
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, array $extra = []): mixed {
global $TOKEN;
$headers = [
"Authorization: Bearer $TOKEN",
"Content-Type: application/json",
];
foreach ($extra as $k => $v) { $headers[] = "$k: $v"; }
$ch = curl_init(API . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
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) {
$err = $payload["error"] ?? [];
throw new Exception(($err["code"] ?? $status) . ": " . ($err["message"] ?? "request failed"));
}
return $payload["data"];
}
// .NET 8+
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
static class TestForge
{
const string Api = "https://api.skillsafe.ai/v1/app-api";
static readonly HttpClient Http = new();
static TestForge() =>
Http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",
Environment.GetEnvironmentVariable("SKILLSAFE_TOKEN")); // see step 1
public static async Task<JsonElement> ApiAsync(
HttpMethod method, string path, object? body = null,
(string Name, string Value)? extraHeader = null)
{
var req = new HttpRequestMessage(method, Api + path);
if (body != null) req.Content = JsonContent.Create(body);
if (extraHeader is { } h) req.Headers.Add(h.Name, h.Value);
var res = await Http.SendAsync(req);
var payload = await res.Content.ReadFromJsonAsync<JsonElement>();
if (!res.IsSuccessStatusCode)
{
var err = payload.GetProperty("error");
throw new Exception(err.GetProperty("code").GetString() + ": " +
err.GetProperty("message").GetString());
}
return payload.GetProperty("data");
}
}
Step 1 — Get a token
The token carries the app identity: POST /guest with
{"slug":"test-forge"} binds it to this app, which is why no later URL mentions
the slug. The new token comes back at data.token. This call needs no auth header.
For a token tied to your SkillSafe account and credits, open the
token page, sign in, and press
Copy shell export — it puts
export SKILLSAFE_TOKEN="..." on your clipboard, which is exactly what these
examples read. Treat it like a password: it can spend your credits.
export SKILLSAFE_TOKEN=$(curl -s -X POST "$API/guest" \
-H "Content-Type: application/json" \
-d '{"slug":"test-forge"}' | jq -r '.data.token')
echo "${SKILLSAFE_TOKEN:0:8}..."
# /guest is the one call that needs no Authorization header.
res = requests.post(API + "/guest", json={"slug": "test-forge"})
res.raise_for_status()
TOKEN = res.json()["data"]["token"]
print(TOKEN[:8] + "...")
// /guest is the one call that needs no Authorization header.
const guestRes = await fetch(API + "/guest", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ slug: "test-forge" }),
});
const guest = (await guestRes.json()).data;
console.log(guest.token.slice(0, 8) + "...");
// assign it to TOKEN (or export it) before the calls below
// /guest is the one call that needs no Authorization header.
var guest struct {
Token string `json:"token"`
GuestID string `json:"guest_id"`
}
body, _ := json.Marshal(map[string]string{"slug": "test-forge"})
res, err := http.Post(API+"/guest", "application/json", bytes.NewReader(body))
if err != nil {
panic(err)
}
defer res.Body.Close()
var env struct {
Data json.RawMessage `json:"data"`
}
json.NewDecoder(res.Body).Decode(&env)
json.Unmarshal(env.Data, &guest)
token = guest.Token // the package-level token the helper reads
// /guest is the one call that needs no Authorization header.
var req = HttpRequest.newBuilder(URI.create(API + "/guest"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"slug\":\"test-forge\"}"))
.build();
var res = HTTP.send(req, HttpResponse.BodyHandlers.ofString());
// the token is at data.token in res.body(); read it with your JSON library
System.out.println(res.body());
# /guest is the one call that needs no Authorization header.
uri = URI(API + "/guest")
res = Net::HTTP.post(uri, { slug: "test-forge" }.to_json,
"Content-Type" => "application/json")
TOKEN = JSON.parse(res.body)["data"]["token"]
puts TOKEN[0, 8] + "..."
<?php
// /guest is the one call that needs no Authorization header.
$ch = curl_init(API . "/guest");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["slug" => "test-forge"]),
]);
$payload = json_decode(curl_exec($ch), true);
curl_close($ch);
$TOKEN = $payload["data"]["token"];
echo substr($TOKEN, 0, 8) . "...\n";
// /guest is the one call that needs no Authorization header.
using var anon = new HttpClient();
var guestRes = await anon.PostAsJsonAsync(
"https://api.skillsafe.ai/v1/app-api/guest", new { slug = "test-forge" });
var guest = (await guestRes.Content.ReadFromJsonAsync<JsonElement>())
.GetProperty("data");
var token = guest.GetProperty("token").GetString();
Console.WriteLine(token![..8] + "...");
A guest token is anonymous and lives on its own small wallet. Signing in on the token page gives you a personal token whose runs bill your account and whose history the app can show you again later.
Step 2 — Check the session and balance
Returns subject_type ("user" or "guest"),
subject_id and the credits balance this token can spend. It is also
the cheapest way to find out whether a token you stored last week is still good — a dead
one answers 401 unauthorized.
curl -s "$API/me" -H "Authorization: Bearer $SKILLSAFE_TOKEN" | jq '.data'
# {"subject_type":"guest","subject_id":"g_...","credits":1200}
me = api("GET", "/me")
print(me["subject_type"], me["subject_id"], me["credits"], "credits")
const me = await api("GET", "/me");
console.log(me.subject_type, me.subject_id, me.credits, "credits");
var me struct {
SubjectType string `json:"subject_type"`
SubjectID string `json:"subject_id"`
Credits int64 `json:"credits"`
}
if err := call("GET", "/me", nil, &me, nil); err != nil {
panic(err)
}
fmt.Println(me.SubjectType, me.SubjectID, me.Credits, "credits")
String envelope = api("GET", "/me", null, null);
// data.subject_type, data.subject_id, data.credits
System.out.println(envelope);
me = api("GET", "/me")
puts "#{me["subject_type"]} #{me["subject_id"]}: #{me["credits"]} credits"
$me = api("GET", "/me");
echo "{$me['subject_type']} {$me['subject_id']}: {$me['credits']} credits\n";
var me = await TestForge.ApiAsync(HttpMethod.Get, "/me");
Console.WriteLine($"{me.GetProperty("subject_type")}: {me.GetProperty("credits")} credits");
Step 3 — Estimate the cost (free)
Send the input object you are about to run. /estimate is free: it creates no job,
charges nothing, and returns the worst-case hold so you can decide before spending. The body
is the input object directly — there is no {"input": ...}
wrapper on either /estimate or /run.
| Input field | Type | Notes |
|---|---|---|
code | string, required | The source module exactly as it sits in your repo — TypeScript or JavaScript, ESM or CJS. |
notes | string, optional | What you care about: the bug that already shipped, the boundary you distrust. Each note becomes its own named test. |
framework | string | Exactly "Vitest" or "Jest". Empty defaults to Vitest. |
filename | string, optional | Source path like src/lib/duration.ts. Drives the test filename and the import path. |
retry_note | string, optional | Send only after a reply failed to parse: describe what was wrong with the shape. The app sends it on attempt 2 and never on attempt 1. |
| Estimate field | Meaning |
|---|---|
hold_credits | Worst-case credits reserved when you call /run. |
min_credits | Floor for a run that returns almost immediately. |
model / model_alias | The model that would serve the run, and the stable alias the app displays. |
markup_bps | Platform markup in basis points, already folded into the numbers above. |
sponsor_enabled | True when the app sponsors this run, so a guest token can run without signing in. |
# input.json holds the input object itself - no {"input": ...} wrapper:
# {"code":"export function toMs(...) {...}","notes":"rounding at .5 bit us once",
# "framework":"Vitest","filename":"src/lib/duration.ts"}
curl -s -X POST "$API/estimate" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-d @input.json \
| jq '.data | {hold_credits, min_credits, model, sponsor_enabled}'
source = open("src/lib/duration.ts").read()
payload = {
"code": source,
"notes": "rounding at .5 bit us once",
"framework": "Vitest",
"filename": "src/lib/duration.ts",
}
est = api("POST", "/estimate", payload) # free: no job, no charge
print(est["hold_credits"], "credits held at most on", est["model_alias"])
if est["sponsor_enabled"]:
print("sponsored - a guest token can run this")
import { readFileSync } from "node:fs";
const payload = {
code: readFileSync("src/lib/duration.ts", "utf8"),
notes: "rounding at .5 bit us once",
framework: "Vitest",
filename: "src/lib/duration.ts",
};
const est = await api("POST", "/estimate", payload); // free: no job, no charge
console.log(est.hold_credits, "credits at most on", est.model_alias);
source, _ := os.ReadFile("src/lib/duration.ts")
payload := map[string]string{
"code": string(source),
"notes": "rounding at .5 bit us once",
"framework": "Vitest",
"filename": "src/lib/duration.ts",
}
var est struct {
HoldCredits int64 `json:"hold_credits"`
MinCredits int64 `json:"min_credits"`
Model string `json:"model"`
ModelAlias string `json:"model_alias"`
MarkupBps int `json:"markup_bps"`
SponsorEnabled bool `json:"sponsor_enabled"`
}
if err := call("POST", "/estimate", payload, &est, nil); err != nil {
panic(err)
}
fmt.Println(est.HoldCredits, "credits at most on", est.ModelAlias)
// Build the input object with your JSON library; it is sent as the body itself,
// with no {"input": ...} wrapper.
String source = Files.readString(Path.of("src/lib/duration.ts"));
String body = mapper.writeValueAsString(Map.of(
"code", source,
"notes", "rounding at .5 bit us once",
"framework", "Vitest",
"filename", "src/lib/duration.ts"));
String envelope = api("POST", "/estimate", body, null); // free: no job, no charge
// data.hold_credits, data.min_credits, data.model, data.model_alias,
// data.markup_bps, data.sponsor_enabled
payload = {
code: File.read("src/lib/duration.ts"),
notes: "rounding at .5 bit us once",
framework: "Vitest",
filename: "src/lib/duration.ts"
}
est = api("POST", "/estimate", payload) # free: no job, no charge
puts "#{est["hold_credits"]} credits at most on #{est["model_alias"]}"
<?php
$payload = [
"code" => file_get_contents("src/lib/duration.ts"),
"notes" => "rounding at .5 bit us once",
"framework" => "Vitest",
"filename" => "src/lib/duration.ts",
];
$est = api("POST", "/estimate", $payload); // free: no job, no charge
echo "{$est['hold_credits']} credits at most on {$est['model_alias']}\n";
var payload = new {
code = File.ReadAllText("src/lib/duration.ts"),
notes = "rounding at .5 bit us once",
framework = "Vitest",
filename = "src/lib/duration.ts",
};
var est = await TestForge.ApiAsync(HttpMethod.Post, "/estimate", payload); // free
Console.WriteLine($"{est.GetProperty("hold_credits")} credits at most on " +
$"{est.GetProperty("model_alias")}");
The app clips modules longer than 60,000 characters before sending: it removes lines from the
middle and keeps both ends, marking the cut in-band with a
[code truncated] line that states how many lines went missing. A blind head slice
would hide the very export you wanted tested. If you send oversize input yourself, do the same
and keep the marker — the skill is told to treat it as a hole and list it under
Not covered: rather than invent what was removed.
Step 4 — Run the forge and poll the job
/run takes the same input object as /estimate, places the credit
hold and returns a job. Poll /jobs/{job_id} every second or two until
status is succeeded or failed; a forge usually takes
20–60 seconds. The reply text lands at output.output.
Always send an Idempotency-Key header. A network blip on the response leaves you
unsure whether the run started, and a naive retry bills twice; with the key, the platform
replays the first run instead of starting a second. The app derives its key from a hash of the
input plus an attempt number — test-forge-<hash(input)>-a1, then
-a2 for the one automatic reshape retry — so retrying the same input is free
but a deliberate second attempt is not accidentally deduplicated.
KEY="test-forge-$(shasum -a 256 input.json | cut -c1-16)-a1"
JOB_ID=$(curl -s -X POST "$API/run" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d @input.json | jq -r '.data.job_id')
while :; do
JOB=$(curl -s "$API/jobs/$JOB_ID" -H "Authorization: Bearer $SKILLSAFE_TOKEN")
STATUS=$(echo "$JOB" | jq -r '.data.status')
case "$STATUS" in succeeded|failed) break ;; esac
sleep 2
done
echo "$JOB" | jq -r '.data.output.output' > reply.txt
import hashlib, time
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
key = "test-forge-" + hashlib.sha256(canonical.encode()).hexdigest()[:16] + "-a1"
started = api("POST", "/run", payload, **{"Idempotency-Key": key})
job_id = started["job_id"]
while True:
job = api("GET", "/jobs/" + job_id)
if job["status"] in ("succeeded", "failed"):
break
time.sleep(1.5)
if job["status"] == "failed":
raise RuntimeError(job.get("error") or "run failed")
reply = job["output"]["output"] # the plain-text contract; see step 6
import { createHash } from "node:crypto";
const canonical = JSON.stringify(payload);
const key = "test-forge-" +
createHash("sha256").update(canonical).digest("hex").slice(0, 16) + "-a1";
const started = await api("POST", "/run", payload, { "Idempotency-Key": key });
let job;
do {
await new Promise((r) => setTimeout(r, 1500));
job = await api("GET", `/jobs/${started.job_id}`);
} while (job.status !== "succeeded" && job.status !== "failed");
if (job.status === "failed") throw new Error(job.error || "run failed");
const reply = job.output.output; // the plain-text contract; see step 6
canonical, _ := json.Marshal(payload)
sum := sha256.Sum256(canonical)
key := fmt.Sprintf("test-forge-%x-a1", sum[:8])
var started struct {
JobID string `json:"job_id"`
}
if err := call("POST", "/run", payload, &started,
map[string]string{"Idempotency-Key": key}); err != nil {
panic(err)
}
var job struct {
Status string `json:"status"`
Error string `json:"error"`
Output struct {
Output string `json:"output"`
} `json:"output"`
}
for {
if err := call("GET", "/jobs/"+started.JobID, nil, &job, nil); err != nil {
panic(err)
}
if job.Status == "succeeded" || job.Status == "failed" {
break
}
time.Sleep(1500 * time.Millisecond)
}
reply := job.Output.Output // the plain-text contract; see step 6
var digest = MessageDigest.getInstance("SHA-256").digest(body.getBytes(UTF_8));
String key = "test-forge-" + HexFormat.of().formatHex(digest).substring(0, 16) + "-a1";
String startEnvelope = api("POST", "/run", body, Map.of("Idempotency-Key", key));
String jobId = /* data.job_id, via your JSON library */;
String status;
String jobEnvelope;
do {
Thread.sleep(1500);
jobEnvelope = api("GET", "/jobs/" + jobId, null, null);
status = /* data.status */;
} while (!status.equals("succeeded") && !status.equals("failed"));
if (status.equals("failed")) throw new RuntimeException(jobEnvelope);
String reply = /* data.output.output - the plain-text contract; see step 6 */;
require "digest"
canonical = JSON.generate(payload)
key = "test-forge-#{Digest::SHA256.hexdigest(canonical)[0, 16]}-a1"
started = api("POST", "/run", payload, { "Idempotency-Key" => key })
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"
reply = job["output"]["output"] # the plain-text contract; see step 6
<?php
$canonical = json_encode($payload);
$key = "test-forge-" . substr(hash("sha256", $canonical), 0, 16) . "-a1";
$started = api("POST", "/run", $payload, ["Idempotency-Key" => $key]);
do {
sleep(2);
$job = api("GET", "/jobs/" . $started["job_id"]);
} while (!in_array($job["status"], ["succeeded", "failed"], true));
if ($job["status"] === "failed") {
throw new Exception($job["error"] ?? "run failed");
}
$reply = $job["output"]["output"]; // the plain-text contract; see step 6
using System.Security.Cryptography;
using System.Text;
var canonical = JsonSerializer.Serialize(payload);
var hash = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(canonical)));
var key = $"test-forge-{hash[..16].ToLowerInvariant()}-a1";
var started = await TestForge.ApiAsync(HttpMethod.Post, "/run", payload,
("Idempotency-Key", key));
var jobId = started.GetProperty("job_id").GetString();
JsonElement job;
string? status;
do
{
await Task.Delay(1500);
job = await TestForge.ApiAsync(HttpMethod.Get, $"/jobs/{jobId}");
status = job.GetProperty("status").GetString();
} while (status is not ("succeeded" or "failed"));
if (status == "failed") throw new Exception("run failed");
var reply = job.GetProperty("output").GetProperty("output").GetString();
Step 5 — Stream it instead (SSE)
Same body, same Idempotency-Key, but the response is
text/event-stream. Events are job (the run started),
delta with {"text":"..."} chunks, then done carrying the
final payload, or error. Treat the done payload as authoritative and
the concatenated deltas as a preview — a dropped chunk at the tail is possible, and the
app rebuilds from done.output.output for exactly that reason.
The section markers arriving in the stream are a free progress bar:
FILENAME:, then PLAN:, then TESTS:, then
NOTES:. On an idempotent replay the platform may answer with plain JSON instead of
a stream — check the Content-Type before parsing.
curl -N -s -X POST "$API/run-stream" \
-H "Authorization: Bearer $SKILLSAFE_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d @input.json \
| while IFS= read -r line; do
case "$line" in
"event: "*) EV="${line#event: }" ;;
"data: "*)
case "$EV" in
delta) printf '%s' "$(echo "${line#data: }" | jq -r '.text')" ;;
done) echo "${line#data: }" | jq -r '.output.output' > reply.txt ;;
error) echo "${line#data: }" >&2 ;;
esac ;;
esac
done
import requests
res = requests.post(API + "/run-stream", json=payload, stream=True, headers={
"Authorization": "Bearer " + TOKEN,
"Idempotency-Key": key,
})
event, reply, done = "message", "", None
for raw in res.iter_lines(decode_unicode=True):
if raw is None:
continue
if raw.startswith("event:"):
event = raw[6:].strip()
elif raw.startswith("data:"):
data = json.loads(raw[5:].strip())
if event == "delta":
reply += data.get("text", "")
elif event == "done":
done = data
elif event == "error":
raise RuntimeError(data.get("message", "run failed"))
# the done payload wins; deltas can drop the tail
reply = (done or {}).get("output", {}).get("output") or reply
const res = await fetch(API + "/run-stream", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(payload),
});
let reply = "", done = null, buffer = "";
const reader = res.body.getReader();
const decoder = new TextDecoder();
for (;;) {
const chunk = await reader.read();
if (chunk.done) break;
buffer += decoder.decode(chunk.value, { stream: true });
let idx;
while ((idx = buffer.indexOf("\n\n")) >= 0) {
const frame = buffer.slice(0, idx);
buffer = buffer.slice(idx + 2);
let event = "message", dataStr = "";
for (const line of frame.split("\n")) {
if (line.startsWith("event:")) event = line.slice(6).trim();
else if (line.startsWith("data:")) dataStr += line.slice(5).trim();
}
if (!dataStr) continue;
const data = JSON.parse(dataStr);
if (event === "delta") reply += data.text || "";
else if (event === "done") done = data;
else if (event === "error") throw new Error(data.message || "run failed");
}
}
reply = done?.output?.output || reply; // done wins; deltas can drop the tail
bodyBytes, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", API+"/run-stream", bytes.NewReader(bodyBytes))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", key)
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var reply strings.Builder
var doneText string
event := "message"
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(line[6:])
case strings.HasPrefix(line, "data:"):
var d struct {
Text string `json:"text"`
Message string `json:"message"`
Output struct {
Output string `json:"output"`
} `json:"output"`
}
json.Unmarshal([]byte(strings.TrimSpace(line[5:])), &d)
switch event {
case "delta":
reply.WriteString(d.Text)
case "done":
doneText = d.Output.Output
case "error":
panic(d.Message)
}
}
}
final := doneText // done wins; deltas can drop the tail
if final == "" {
final = reply.String()
}
var req = HttpRequest.newBuilder(URI.create(API + "/run-stream"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.header("Idempotency-Key", key)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var res = HTTP.send(req, HttpResponse.BodyHandlers.ofLines());
var reply = new StringBuilder();
String doneText = null;
String event = "message";
for (String line : (Iterable<String>) res.body()::iterator) {
if (line.startsWith("event:")) {
event = line.substring(6).trim();
} else if (line.startsWith("data:")) {
String json = line.substring(5).trim();
if (event.equals("delta")) {
reply.append(/* data.text, via your JSON library */ "");
} else if (event.equals("done")) {
doneText = /* data.output.output */ json;
} else if (event.equals("error")) {
throw new RuntimeException(json);
}
}
}
// the done payload wins; deltas can drop the tail
String finalReply = doneText != null ? doneText : reply.toString();
uri = URI(API + "/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = key
req.body = payload.to_json
reply = +""
done = nil
event = "message"
buffer = +""
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.request(req) do |res|
res.read_body do |chunk|
buffer << chunk
while (i = buffer.index("\n\n"))
frame = buffer.slice!(0, i + 2)
frame.each_line do |line|
line = line.chomp
if line.start_with?("event:")
event = line[6..].strip
elsif line.start_with?("data:")
data = JSON.parse(line[5..].strip)
case event
when "delta" then reply << data["text"].to_s
when "done" then done = data
when "error" then raise(data["message"] || "run failed")
end
end
end
end
end
end
end
reply = done&.dig("output", "output") || reply # done wins
<?php
$reply = "";
$doneText = null;
$event = "message";
$buffer = "";
$ch = curl_init(API . "/run-stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $TOKEN",
"Content-Type: application/json",
"Idempotency-Key: $key",
"Accept: text/event-stream",
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$reply, &$doneText, &$event, &$buffer) {
$buffer .= $chunk;
while (($i = strpos($buffer, "\n\n")) !== false) {
$frame = substr($buffer, 0, $i);
$buffer = substr($buffer, $i + 2);
foreach (explode("\n", $frame) as $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") { $reply .= $data["text"] ?? ""; }
elseif ($event === "done") { $doneText = $data["output"]["output"] ?? null; }
elseif ($event === "error") { throw new Exception($data["message"] ?? "run failed"); }
}
}
}
return strlen($chunk);
},
]);
curl_exec($ch);
curl_close($ch);
$reply = $doneText ?? $reply; // done wins; deltas can drop the tail
var req = new HttpRequestMessage(HttpMethod.Post,
"https://api.skillsafe.ai/v1/app-api/run-stream")
{
Content = JsonContent.Create(payload),
};
req.Headers.Add("Idempotency-Key", key);
using var res = await Http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await res.Content.ReadAsStreamAsync());
var reply = new StringBuilder();
string? doneText = null;
var evt = "message";
while (await reader.ReadLineAsync() is { } line)
{
if (line.StartsWith("event:")) evt = line[6..].Trim();
else if (line.StartsWith("data:"))
{
var data = JsonSerializer.Deserialize<JsonElement>(line[5..].Trim());
if (evt == "delta")
reply.Append(data.GetProperty("text").GetString());
else if (evt == "done")
doneText = data.GetProperty("output").GetProperty("output").GetString();
else if (evt == "error")
throw new Exception(data.GetProperty("message").GetString());
}
}
// the done payload wins; deltas can drop the tail
var finalReply = doneText ?? reply.ToString();
Step 6 — Parse the output contract
The reply is plain text, not JSON, in a fixed shape. Read it strictly: the app treats a reply
that does not match as a failed parse and retries once with a retry_note.
FILENAME: duration.test.ts
FRAMEWORK: Vitest
SUMMARY: 14 tests across formatDuration and parseDuration, including the .5 rounding boundary
PLAN:
- **formatDuration**
- formats whole minutes without a seconds part
- rounds half-open intervals up (edge)
- throws on a negative input (edge)
- **parseDuration**
- parses the "1h30m" form (from notes)
TESTS:
import { describe, it, expect } from "vitest";
import { formatDuration, parseDuration } from "./duration";
describe("formatDuration", () => {
it("formats whole minutes without a seconds part", () => {
expect(formatDuration(120000)).toBe("2m");
});
});
NOTES:
**Assumptions:** the .5 boundary rounds up, matching the <= in the source.
**Mocks and setup:** none.
**Not covered:** the module's exported `nowLabel` reads the clock through an
import that is not shown, so it is left untested.
Coverage confidence: 78%, the clock-reading export is untested.
| Marker | Rule |
|---|---|
FILENAME: | The first line. Everything after the prefix, trimmed, is the test filename (e.g. duration.test.ts). Required, non-empty. |
FRAMEWORK: | The first line after that starting with the prefix. Its value must be exactly Vitest or Jest (case-insensitively); anything else is a failed parse. |
SUMMARY: | The first following line with the prefix, one line, no markdown. Optional — treat a missing summary as an empty string, and stop looking once you hit PLAN: or TESTS:. |
PLAN: | A line reading exactly PLAN: and nothing else. Everything up to the TESTS: line is a markdown bullet list, grouped per describe block, with (edge) and (from notes) tags. May be empty. |
TESTS: | A line reading exactly TESTS:. Everything up to NOTES: (or the end) is the raw test file with no code fence. Required, non-empty. Strip a fence if one appears anyway. |
NOTES: | A line reading exactly NOTES:, after TESTS:. Everything after it is markdown: assumptions, mocks and setup, what is not covered. |
| Confidence | The last line of NOTES reads Coverage confidence: NN%, <clause>. Scan the notes bottom-up for it and strip any surrounding asterisks. |
Two defensive details worth copying from the app: strip a single markdown fence wrapped around
the whole reply before splitting into lines, and strip one wrapped around just the
TESTS block. Also note that no line inside the test code may start with NOTES: at
column zero — the contract reserves that, so an unindented comment containing it would
cut the file short.
# reply.txt holds the raw reply from step 4 or 5.
awk '/^TESTS:[ \t]*$/{f=1;next} /^NOTES:[ \t]*$/{f=0} f' reply.txt > duration.test.ts
FILENAME=$(sed -n 's/^FILENAME: *//p' reply.txt | head -1)
FRAMEWORK=$(sed -n 's/^FRAMEWORK: *//p' reply.txt | head -1)
CONF=$(grep -o 'Coverage confidence: [0-9]\{1,3\}%' reply.txt | tail -1)
mv duration.test.ts "src/lib/$FILENAME"
echo "$FRAMEWORK - $CONF"
import re
def parse_result(text):
text = text.strip()
fence = re.match(r"^```[^\n]*\n([\s\S]*?)\n?```$", text)
if fence:
text = fence.group(1).strip()
lines = text.split("\n")
def find(prefix, start=0):
for i in range(start, len(lines)):
if lines[i].startswith(prefix):
return i
return -1
fi = find("FILENAME:")
if fi == -1:
return None
wi = find("FRAMEWORK:", fi + 1)
if wi == -1:
return None
framework = lines[wi][len("FRAMEWORK:"):].strip().lower()
framework = {"vitest": "Vitest", "jest": "Jest"}.get(framework)
if not framework:
return None
summary = ""
for i in range(wi + 1, len(lines)):
if re.fullmatch(r"(PLAN|TESTS):[ \t]*", lines[i]):
break
if lines[i].startswith("SUMMARY:"):
summary = lines[i][len("SUMMARY:"):].strip()
break
pi = ti = ni = -1
for i in range(wi + 1, len(lines)):
if pi == -1 and re.fullmatch(r"PLAN:[ \t]*", lines[i]):
pi = i
elif ti == -1 and re.fullmatch(r"TESTS:[ \t]*", lines[i]):
ti = i
elif ti != -1 and re.fullmatch(r"NOTES:[ \t]*", lines[i]):
ni = i
break
if ti == -1:
return None
end = ni if ni != -1 else len(lines)
tests = "\n".join(lines[ti + 1:end]).strip()
filename = lines[fi][len("FILENAME:"):].strip()
if not filename or not tests:
return None
notes = "\n".join(lines[ni + 1:]).strip() if ni != -1 else ""
conf = None
for line in reversed(notes.split("\n")):
m = re.match(r"^Coverage confidence:\s*(\d{1,3})%", line.strip().strip("*").strip())
if m:
conf = int(m.group(1))
break
return {
"filename": filename,
"framework": framework,
"summary": summary,
"plan": "\n".join(lines[pi + 1:ti]).strip() if -1 < pi < ti else "",
"tests": tests,
"notes": notes,
"confidence": conf,
}
result = parse_result(reply)
open("src/lib/" + result["filename"], "w").write(result["tests"] + "\n")
print(result["framework"], result["confidence"], "% confidence")
function parseResult(text) {
let t = String(text ?? "").trim();
const fence = t.match(/^```[^\n]*\n([\s\S]*?)\n?```$/);
if (fence) t = fence[1].trim();
const lines = t.split(/\r?\n/);
const find = (p, from = 0) => lines.findIndex((l, i) => i >= from && l.startsWith(p));
const fi = find("FILENAME:");
if (fi === -1) return null;
const wi = find("FRAMEWORK:", fi + 1);
if (wi === -1) return null;
const fw = lines[wi].slice("FRAMEWORK:".length).trim().toLowerCase();
const framework = fw === "vitest" ? "Vitest" : fw === "jest" ? "Jest" : "";
if (!framework) return null;
let summary = "";
for (let i = wi + 1; i < lines.length; i++) {
if (/^(PLAN|TESTS):[ \t]*$/.test(lines[i])) break;
if (lines[i].startsWith("SUMMARY:")) {
summary = lines[i].slice("SUMMARY:".length).trim();
break;
}
}
let pi = -1, ti = -1, ni = -1;
for (let i = wi + 1; i < lines.length; i++) {
if (pi === -1 && /^PLAN:[ \t]*$/.test(lines[i])) { pi = i; continue; }
if (ti === -1 && /^TESTS:[ \t]*$/.test(lines[i])) { ti = i; continue; }
if (ti !== -1 && /^NOTES:[ \t]*$/.test(lines[i])) { ni = i; break; }
}
if (ti === -1) return null;
const filename = lines[fi].slice("FILENAME:".length).trim();
const tests = lines.slice(ti + 1, ni === -1 ? lines.length : ni).join("\n").trim();
if (!filename || !tests) return null;
const notes = ni === -1 ? "" : lines.slice(ni + 1).join("\n").trim();
let confidence = null;
for (const line of notes.split("\n").reverse()) {
const m = line.trim().replace(/^\*+|\*+$/g, "").trim()
.match(/^Coverage confidence:\s*(\d{1,3})%/);
if (m) { confidence = Math.min(100, parseInt(m[1], 10)); break; }
}
return {
filename, framework, summary,
plan: pi !== -1 && pi < ti ? lines.slice(pi + 1, ti).join("\n").trim() : "",
tests, notes, confidence,
};
}
const result = parseResult(reply);
writeFileSync("src/lib/" + result.filename, result.tests + "\n");
console.log(result.framework, result.confidence + "% confidence");
type Result struct {
Filename string
Framework string
Summary string
Plan string
Tests string
Notes string
Confidence int
}
var (
planRe = regexp.MustCompile(`^PLAN:[ \t]*$`)
testsRe = regexp.MustCompile(`^TESTS:[ \t]*$`)
notesRe = regexp.MustCompile(`^NOTES:[ \t]*$`)
confRe = regexp.MustCompile(`^Coverage confidence:\s*(\d{1,3})%`)
fenceRe = regexp.MustCompile("(?s)^```[^\n]*\n(.*?)\n?```$")
)
func parseResult(text string) *Result {
t := strings.TrimSpace(text)
if m := fenceRe.FindStringSubmatch(t); m != nil {
t = strings.TrimSpace(m[1])
}
lines := strings.Split(t, "\n")
fi, wi := -1, -1
for i, l := range lines {
if strings.HasPrefix(l, "FILENAME:") {
fi = i
break
}
}
if fi == -1 {
return nil
}
for i := fi + 1; i < len(lines); i++ {
if strings.HasPrefix(lines[i], "FRAMEWORK:") {
wi = i
break
}
}
if wi == -1 {
return nil
}
var framework string
switch strings.ToLower(strings.TrimSpace(lines[wi][len("FRAMEWORK:"):])) {
case "vitest":
framework = "Vitest"
case "jest":
framework = "Jest"
default:
return nil
}
summary := ""
for i := wi + 1; i < len(lines); i++ {
if planRe.MatchString(lines[i]) || testsRe.MatchString(lines[i]) {
break
}
if strings.HasPrefix(lines[i], "SUMMARY:") {
summary = strings.TrimSpace(lines[i][len("SUMMARY:"):])
break
}
}
pi, ti, ni := -1, -1, -1
for i := wi + 1; i < len(lines); i++ {
switch {
case pi == -1 && planRe.MatchString(lines[i]):
pi = i
case ti == -1 && testsRe.MatchString(lines[i]):
ti = i
case ti != -1 && notesRe.MatchString(lines[i]):
ni = i
}
if ni != -1 {
break
}
}
if ti == -1 {
return nil
}
end := len(lines)
if ni != -1 {
end = ni
}
res := &Result{
Filename: strings.TrimSpace(lines[fi][len("FILENAME:"):]),
Framework: framework,
Summary: summary,
Tests: strings.TrimSpace(strings.Join(lines[ti+1:end], "\n")),
}
if pi != -1 && pi < ti {
res.Plan = strings.TrimSpace(strings.Join(lines[pi+1:ti], "\n"))
}
if ni != -1 {
res.Notes = strings.TrimSpace(strings.Join(lines[ni+1:], "\n"))
}
if res.Filename == "" || res.Tests == "" {
return nil
}
noteLines := strings.Split(res.Notes, "\n")
for i := len(noteLines) - 1; i >= 0; i-- {
l := strings.Trim(strings.TrimSpace(noteLines[i]), "*")
if m := confRe.FindStringSubmatch(strings.TrimSpace(l)); m != nil {
res.Confidence, _ = strconv.Atoi(m[1])
break
}
}
return res
}
record Result(String filename, String framework, String summary,
String plan, String tests, String notes, int confidence) {}
static Result parseResult(String text) {
String t = text.strip();
var fence = Pattern.compile("^```[^\n]*\n(.*?)\n?```$", Pattern.DOTALL).matcher(t);
if (fence.matches()) t = fence.group(1).strip();
String[] lines = t.split("\r?\n");
int fi = -1, wi = -1, pi = -1, ti = -1, ni = -1;
for (int i = 0; i < lines.length; i++) {
if (lines[i].startsWith("FILENAME:")) { fi = i; break; }
}
if (fi < 0) return null;
for (int i = fi + 1; i < lines.length; i++) {
if (lines[i].startsWith("FRAMEWORK:")) { wi = i; break; }
}
if (wi < 0) return null;
String fw = lines[wi].substring("FRAMEWORK:".length()).strip();
String framework = fw.equalsIgnoreCase("Vitest") ? "Vitest"
: fw.equalsIgnoreCase("Jest") ? "Jest" : null;
if (framework == null) return null;
String summary = "";
for (int i = wi + 1; i < lines.length; i++) {
if (lines[i].matches("(PLAN|TESTS):[ \t]*")) break;
if (lines[i].startsWith("SUMMARY:")) {
summary = lines[i].substring("SUMMARY:".length()).strip();
break;
}
}
for (int i = wi + 1; i < lines.length; i++) {
if (pi < 0 && lines[i].matches("PLAN:[ \t]*")) { pi = i; continue; }
if (ti < 0 && lines[i].matches("TESTS:[ \t]*")) { ti = i; continue; }
if (ti >= 0 && lines[i].matches("NOTES:[ \t]*")) { ni = i; break; }
}
if (ti < 0) return null;
int end = ni < 0 ? lines.length : ni;
String tests = String.join("\n", Arrays.copyOfRange(lines, ti + 1, end)).strip();
String filename = lines[fi].substring("FILENAME:".length()).strip();
if (filename.isEmpty() || tests.isEmpty()) return null;
String plan = (pi >= 0 && pi < ti)
? String.join("\n", Arrays.copyOfRange(lines, pi + 1, ti)).strip() : "";
String notes = ni < 0 ? ""
: String.join("\n", Arrays.copyOfRange(lines, ni + 1, lines.length)).strip();
int confidence = -1;
var conf = Pattern.compile("^Coverage confidence:\\s*(\\d{1,3})%");
String[] noteLines = notes.split("\n");
for (int i = noteLines.length - 1; i >= 0; i--) {
var m = conf.matcher(noteLines[i].strip().replaceAll("^\\*+|\\*+$", "").strip());
if (m.find()) { confidence = Integer.parseInt(m.group(1)); break; }
}
return new Result(filename, framework, summary, plan, tests, notes, confidence);
}
FRAMEWORKS = { "vitest" => "Vitest", "jest" => "Jest" }.freeze
def parse_result(text)
t = text.to_s.strip
t = Regexp.last_match(1).strip if t =~ /\A```[^\n]*\n(.*?)\n?```\z/m
lines = t.split(/\r?\n/)
fi = lines.index { |l| l.start_with?("FILENAME:") }
return nil unless fi
wi = (fi + 1...lines.size).find { |i| lines[i].start_with?("FRAMEWORK:") }
return nil unless wi
framework = FRAMEWORKS[lines[wi].delete_prefix("FRAMEWORK:").strip.downcase]
return nil unless framework
summary = ""
(wi + 1...lines.size).each do |i|
break if lines[i].match?(/\A(PLAN|TESTS):[ \t]*\z/)
if lines[i].start_with?("SUMMARY:")
summary = lines[i].delete_prefix("SUMMARY:").strip
break
end
end
pi = ti = ni = nil
(wi + 1...lines.size).each do |i|
if pi.nil? && lines[i].match?(/\APLAN:[ \t]*\z/) then pi = i
elsif ti.nil? && lines[i].match?(/\ATESTS:[ \t]*\z/) then ti = i
elsif ti && lines[i].match?(/\ANOTES:[ \t]*\z/) then ni = i; break
end
end
return nil unless ti
filename = lines[fi].delete_prefix("FILENAME:").strip
tests = lines[(ti + 1)...(ni || lines.size)].join("\n").strip
return nil if filename.empty? || tests.empty?
notes = ni ? lines[(ni + 1)..].join("\n").strip : ""
confidence = notes.split("\n").reverse.each do |line|
m = line.strip.gsub(/\A\*+|\*+\z/, "").strip.match(/\ACoverage confidence:\s*(\d{1,3})%/)
break m[1].to_i if m
end
confidence = nil unless confidence.is_a?(Integer)
{ filename: filename, framework: framework, summary: summary,
plan: pi && pi < ti ? lines[(pi + 1)...ti].join("\n").strip : "",
tests: tests, notes: notes, confidence: confidence }
end
result = parse_result(reply)
File.write("src/lib/#{result[:filename]}", result[:tests] + "\n")
<?php
function parse_result(string $text): ?array {
$t = trim($text);
if (preg_match('/^```[^\n]*\n([\s\S]*?)\n?```$/', $t, $m)) {
$t = trim($m[1]);
}
$lines = preg_split('/\r?\n/', $t);
$fi = $wi = $pi = $ti = $ni = -1;
foreach ($lines as $i => $l) {
if (str_starts_with($l, "FILENAME:")) { $fi = $i; break; }
}
if ($fi < 0) return null;
for ($i = $fi + 1; $i < count($lines); $i++) {
if (str_starts_with($lines[$i], "FRAMEWORK:")) { $wi = $i; break; }
}
if ($wi < 0) return null;
$fw = strtolower(trim(substr($lines[$wi], strlen("FRAMEWORK:"))));
$framework = ["vitest" => "Vitest", "jest" => "Jest"][$fw] ?? null;
if ($framework === null) return null;
$summary = "";
for ($i = $wi + 1; $i < count($lines); $i++) {
if (preg_match('/^(PLAN|TESTS):[ \t]*$/', $lines[$i])) break;
if (str_starts_with($lines[$i], "SUMMARY:")) {
$summary = trim(substr($lines[$i], strlen("SUMMARY:")));
break;
}
}
for ($i = $wi + 1; $i < count($lines); $i++) {
if ($pi < 0 && preg_match('/^PLAN:[ \t]*$/', $lines[$i])) { $pi = $i; continue; }
if ($ti < 0 && preg_match('/^TESTS:[ \t]*$/', $lines[$i])) { $ti = $i; continue; }
if ($ti >= 0 && preg_match('/^NOTES:[ \t]*$/', $lines[$i])) { $ni = $i; break; }
}
if ($ti < 0) return null;
$end = $ni < 0 ? count($lines) : $ni;
$filename = trim(substr($lines[$fi], strlen("FILENAME:")));
$tests = trim(implode("\n", array_slice($lines, $ti + 1, $end - $ti - 1)));
if ($filename === "" || $tests === "") return null;
$notes = $ni < 0 ? "" : trim(implode("\n", array_slice($lines, $ni + 1)));
$confidence = null;
foreach (array_reverse(explode("\n", $notes)) as $line) {
$clean = trim(trim(trim($line), "*"));
if (preg_match('/^Coverage confidence:\s*(\d{1,3})%/', $clean, $m)) {
$confidence = (int) $m[1];
break;
}
}
return [
"filename" => $filename,
"framework" => $framework,
"summary" => $summary,
"plan" => ($pi >= 0 && $pi < $ti)
? trim(implode("\n", array_slice($lines, $pi + 1, $ti - $pi - 1))) : "",
"tests" => $tests,
"notes" => $notes,
"confidence" => $confidence,
];
}
$result = parse_result($reply);
file_put_contents("src/lib/" . $result["filename"], $result["tests"] . "\n");
using System.Text.RegularExpressions;
record ForgeResult(string Filename, string Framework, string Summary,
string Plan, string Tests, string Notes, int? Confidence);
static ForgeResult? ParseResult(string text)
{
var t = text.Trim();
var fence = Regex.Match(t, @"^```[^\n]*\n([\s\S]*?)\n?```$");
if (fence.Success) t = fence.Groups[1].Value.Trim();
var lines = t.Split('\n').Select(l => l.TrimEnd('\r')).ToArray();
int fi = Array.FindIndex(lines, l => l.StartsWith("FILENAME:"));
if (fi < 0) return null;
int wi = Array.FindIndex(lines, fi + 1, l => l.StartsWith("FRAMEWORK:"));
if (wi < 0) return null;
var fw = lines[wi]["FRAMEWORK:".Length..].Trim();
var framework = fw.Equals("Vitest", StringComparison.OrdinalIgnoreCase) ? "Vitest"
: fw.Equals("Jest", StringComparison.OrdinalIgnoreCase) ? "Jest" : null;
if (framework is null) return null;
var summary = "";
for (int i = wi + 1; i < lines.Length; i++)
{
if (Regex.IsMatch(lines[i], @"^(PLAN|TESTS):[ \t]*$")) break;
if (lines[i].StartsWith("SUMMARY:"))
{
summary = lines[i]["SUMMARY:".Length..].Trim();
break;
}
}
int pi = -1, ti = -1, ni = -1;
for (int i = wi + 1; i < lines.Length; i++)
{
if (pi < 0 && Regex.IsMatch(lines[i], @"^PLAN:[ \t]*$")) { pi = i; continue; }
if (ti < 0 && Regex.IsMatch(lines[i], @"^TESTS:[ \t]*$")) { ti = i; continue; }
if (ti >= 0 && Regex.IsMatch(lines[i], @"^NOTES:[ \t]*$")) { ni = i; break; }
}
if (ti < 0) return null;
int end = ni < 0 ? lines.Length : ni;
var filename = lines[fi]["FILENAME:".Length..].Trim();
var tests = string.Join("\n", lines[(ti + 1)..end]).Trim();
if (filename.Length == 0 || tests.Length == 0) return null;
var plan = pi >= 0 && pi < ti ? string.Join("\n", lines[(pi + 1)..ti]).Trim() : "";
var notes = ni < 0 ? "" : string.Join("\n", lines[(ni + 1)..]).Trim();
int? confidence = null;
foreach (var line in notes.Split('\n').Reverse())
{
var m = Regex.Match(line.Trim().Trim('*').Trim(),
@"^Coverage confidence:\s*(\d{1,3})%");
if (m.Success) { confidence = int.Parse(m.Groups[1].Value); break; }
}
return new ForgeResult(filename, framework, summary, plan, tests, notes, confidence);
}
Read the NOTES before you keep the tests. The Not covered: list and the coverage
confidence line are the honest part of the reply — a test file that admits what it cannot
pin down is worth more than one that claims everything. In CI, gating on the confidence number
is a reasonable habit.