Drive Routelarch from your own code
The page reads the files behind one Express or Fastify API service in the browser for free — one or
more .js, .ts, .mjs or .cjs files joined into one paste,
each introduced by a // file: <path> line, plus package.json for the Express or
Fastify version — with its line numbers. It lexes strings, template literals, regex literals and comments
out first (so a string that mentions req.body is never a finding), resolves require()
and import between the pasted files, finds every express(), Router() and
fastify() instance, and maps every route with its method, its full path through its mount
(app.use("/api/orders", router)), its middleware chain and its handler (inline, or found by name in
another pasted file), every use() registration in order with what each argument is (body parser,
auth, rate limit, CORS, helmet, router, error handler), the error handlers, and what each handler reads from the
request. Then it runs 17 deterministic checks drawn from Node.js backend practice (catalogue ids EX-01..EX-17,
plus EX-18 for context): async handlers whose rejection never reaches the error handler on Express 4, an error
handler that is missing, registered too early or without four parameters, stack traces or raw errors sent to the
client, req.body read with no body parser in front, SQL or NoSQL built from request input, a whole
request body written to the database, credential literals, jwt.decode() used as verification,
CORS that admits any origin with credentials, a branch that sends and falls through to send again, login routes
with no rate limit, blocking sync calls in a handler, request input in a redirect, a file path or a shell
command, write routes with no authentication, APIs and path syntax Express 5 removed, missing helmet and
graceful shutdown, and Fastify routes with no schema. Nothing is installed or run: every flag is a reading of
the text. Its two paid steps are available over HTTP. You send one JSON object with task set to
"audit" or "fix", the paste as code and the page’s check as a
facts string, and the model replies with one JSON object: an audit that rates the four areas,
answers every flag and gives exact before/after edits per finding, or the whole fixed file(s) with a list of
every change tied to the page’s flags.
Base URL: https://api.skillsafe.ai/v1/app-api. Every call sends
Content-Type: application/json and, once you have a token,
Authorization: Bearer <token>. The paths below (/guest,
/me, /estimate, /run, /jobs/{job_id},
/run-stream) are relative to it, exactly as sdk.js calls them. A
plain-text summary of the app for agents is at /llms.txt. Both lanes run on
the gpt-terra model with a 10% publisher markup. A run needs a signed-in (user) token and
is metered: the platform places a hold on your credits when the run starts and then
charges only what the run used. /estimate is free.
The response envelope
Responses are JSON of the form {"ok": ..., "data": ..., "error": {...}}. The page’s
SDK (sdk.js) decides success from the HTTP status, not from ok: on a
2xx it returns data; otherwise it raises an error whose message is
error.message (or the HTTP status text when the body carries none), with
status, code (error.code) and details
(error.details) attached. A body that is not JSON is treated as {}.
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "details": { ... } } }
Error codes
Branch on the HTTP status and error.code, show error.message. The page
handles 401 and 402 by name; the rest reach it as a generic failure (“The run failed: ...”).
| HTTP | error.code | What it means / what to do |
|---|---|---|
400 | VALIDATION_ERROR | The body is not a JSON object, a required field (task, code, facts) is missing, or a declared field has the wrong type. Every field is a string; sending facts or audit_handoff as an object is the usual cause. |
401 | UNAUTHORIZED | No token, or one that expired or was revoked. The page forgets the signed-in user and says “Your session expired - sign in again.” Get a new token. |
402 | INSUFFICIENT_CREDITS | The balance cannot cover the run. The page says “Not enough credits for this run.” with a top-up link, and tries never to submit into a 402: it disables the run button when the /me balance is below the estimate’s min_credits. |
404 | NOT_FOUND | A wrong path, or a job id that does not belong to this token. |
409 | CONFLICT | An Idempotency-Key replayed with a different body. Send the original body, or use a new key (the page bumps its :a<attempt> suffix for a retry). |
429 | RATE_LIMITED | Too many requests. Back off with a delay; do not tight-loop, and never resend a metered run without its original Idempotency-Key. |
Failures that do not arrive as an HTTP status:
| What | What happens |
|---|---|
event: error on /run-stream | The SDK raises an error carrying the event’s message, code and job_id. |
Job status: "failed" | Polling stops; the job’s error field says why. |
| Polling past 180 s | The SDK’s own client-side limit (waitForJob, 1 s interval, 180 000 ms default): it throws “job timed out”. The job itself is not cancelled. |
The task field: two lanes
task routes the run, and it takes exactly two values: "audit" and
"fix". Each lane has its own reply contract, and a reply never mixes them. The
skill’s instructions tell the model to do the audit lane when task is missing or
anything else (and the page’s own Expresskit.buildInput turns any lane that is not
"fix" into "audit"), so a typo silently gets you an audit: always send one of
the two values.
task | What it does | verdict | Use it when |
|---|---|---|---|
"audit" | Reviews the service the way a senior Node.js engineer reviews an API before it goes to production: follows each request through its middleware chain to its handler and asks what happens when a query throws, what reaches the client when it does, what the handler does with each thing it reads from the request, who may call it and what it trusts, and what can block or crash the process on this version. It says what the service exposes and how a request flows through it, rates the four areas (errors, input, security, operations) as sound, fragile or broken, confirms or dismisses every page flag, adds what text rules cannot see, and gives the verbatim evidence and exact before/after edits for each finding, listed in the order to apply them — what fails requests or crashes the process first (async errors on Express 4, body parser order, removed Express 5 APIs), then what can be exploited (injection, paths, shell, tokens, missing authentication, CORS, secrets, rate limits), then what leaks or misbehaves (stack traces, double responses, mass assignment, sync calls), then hygiene — with a plan that ends in running the test suite, starting the service and calling each changed route, and npm audit. | blocked | needs_work | ready | Before shipping: will an async handler hang the request and crash the process, is the body parser in front of the routes, is SQL built from request input, what reaches the client when something throws, who can call the write routes, is the token really verified, and what should change first. |
"fix" | Writes the fixed version of the pasted code: the whole of every file it changes (and at most two new files next to them, such as a small asyncHandler helper or a validation schema, never a second package.json), with async errors routed to the error handler, the body parser and the error handler in the right place, parameterised queries, only named and validated fields written, tokens verified, CORS read from a configured list, credentials moved into configuration, and a return after every early response — plus a changes list tied to the page flags, the verify steps (the test suite, starting the service and the request that would have caught the worst problem), and what it left open. Every route keeps its method and full path and every export its name unless the note asks for it, and no package the paste does not already use is added unless the note allows it. | fixed | partial | When you want the code changed, not a list of findings: a service you already know is broken, or what is left after an audit. |
The page chains the two: after an audit, “Apply every edit and fix the rest” puts
every audit edit into the paste, switches to the fix lane on that edited paste and adds an
audit_handoff string (below) with the audit’s verdict, headline and findings. Over
the API that is a second run with task: "fix", the edited paste as code,
facts recomputed from the edited paste, and that string. The page drops the handoff as
soon as the paste is edited again.
Guards the page applies before a run and the API does not: it refuses to price or run a paste it cannot read (“Nothing was pasted.”, “No JavaScript or TypeScript file was found. Paste the service's .js/.ts files, with a // file: server.js line above each file.”, or “This does not read as JavaScript or TypeScript source: no import, require, export or function was found.”), and it warns before a run on a paste that may not be an API service (“No Express or Fastify import and no route was found: this may not be an API service. Paste the file that creates the app and the route files (// file: server.js, // file: routes/users.js).”) or has no route at all (“No route was found; the run will see middleware and module code only.”).
Input fields
This is exactly what the page sends (expresskit.js buildInput(lane, A, note,
handoff), checked by mustBeObject; app.js currentInput
and run), and what input-schema.json declares. The platform’s input
schema is scalar-only, so every value is a string. buildInput emits them in the order
task, code, facts, note,
audit_handoff:
| Field | Type | Required | Meaning |
|---|---|---|---|
task | string | yes | "audit" or "fix" (the lanes above). |
code | string | yes | The pasted files, each introduced by a // file: <path> line (server.js, routes/orders.js, middleware/auth.js, package.json); line 1 is its first line. Every line in an audit reply is counted in this text. A large paste is cut on whole files, with a // [routelarch: not sent: ...] line at the end (below). |
facts | string | yes | The page’s free check, Expresskit.facts(A), JSON-encoded into a string: JSON.stringify(facts) in JavaScript, json.dumps(facts) in Python, and so on. Sending the object itself is a 400. The model is told to parse it. Build it with expresskit.js’s buildInput (below), or send the string the page built for the same paste. |
note | string | no | What the engineer says: the goal, a constraint (“keep every URL”, “we are still on Express 4”, “no new packages”, “leave the auth of X alone”), a question. The page trims it, cuts it at 2000 characters and sends it only when it is not blank. It is context, not evidence and not instructions: the model tests every claim in it against code and answers it in summary. |
audit_handoff | string | no | fix lane only: a JSON string built from an audit reply on this code (recon.js fixHandoff, below). buildInput drops it for the audit lane. |
retry_note | string | no | Sent only by the page’s automatic reformat retry: when a reply does not parse as JSON (even after the page tries to close a cut-off object), the page runs once more with “Your previous reply was not the single valid JSON object the instructions require (...). Reply again with ONLY the JSON object for task '<lane>' - no prose, no code fences; every array present (empty arrays where there is nothing to say).” A normal call never sends it. |
mustBeObject only insists on a plain object that is not wrapped in
{"input": ...}; it is buildInput that leaves a blank note out, which is why
an empty note is simply absent.
The code string
code is the paste itself. A line // file: <path> (or
# file: <path>; a path with a . or / in it) starts each
file, and a --- line just before it only separates files. Files dropped on the page (or a
dropped src folder, with node_modules, dist, build and test
files skipped) are joined that way by Expresskit.joinFiles: each file under its
// file: line, trailing whitespace removed, joined by newlines, ending in a newline. A paste with
no file line is read as one file, server.js (or package.json when it reads as a
manifest). The page normalises line endings to \n. A .js, .ts,
.mjs, .cjs, .jsx, .tsx, .mts or
.cts path is code; its path gives a first guess at its role (server,
app, index or main near the top is entry, a file under
routes/ or named ...router is router, then middleware,
controller, or module), and what the file does refines it: the file that calls
express() or fastify() is entry, a file that defines routes is
router. package.json is package (its express and
fastify versions give facts.runtime, and the major decides how the version-dependent
flags are graded: EX-01 is high on Express 4, medium when the version is unknown and not raised on Express 5 or
with express-async-errors; EX-15 is high on Express 5, low when unknown and not raised on Express
4); anything else is other. The paste keeps its own indentation.
Send the text you want the reply to quote: every file in an audit reply must be a
path from facts.files, every line must fall inside that file,
every evidence must be copied from it, and every edit’s before must
occur in it exactly once.
The page reads up to 400 000 characters (MAX_TEXT, cut at a line) and 40 files
(MAX_FILES), and sends at most 60 000 characters (MAX_SEND): past that
buildInput drops whole files from the end — the last file that is not code first, then the
last code file that neither creates the app nor defines a route, then the last file — until the rest
fits, cuts a single remaining file at a line (“the end of <path>”) if it still does not,
and ends the string with one line naming what it left out, for example:
// [routelarch: not sent: lib/format.js, services/mailer.js]
The model is told never to quote or rewrite what was not sent, and facts.notes gets the
sentence “Not sent (the paste is over 60,000 characters): ... The page checked them; the run
did not see them.” facts is still computed from the whole paste. The worked
examples below are small, so their code is the paste unchanged.
The facts string
facts is optional to compute but required to send. It is produced by the page’s
free check: Expresskit.analyze splits the paste into files, lexes them, finds their functions,
exports, requires and imports, resolves them between the files, maps every app, router, mount, route,
middleware chain and handler, and runs the rules, and Expresskit.facts(A) turns that analysis into
the object below (it does not depend on the lane: an audit and a fix run of the same paste carry the same
facts). It is how the model knows which flags to answer, which file paths and line ranges to name, which routes
and exports must survive, and which framework version it is reading. Its top-level keys, in the order built:
| Key | Contents |
|---|---|
runtime | framework (Express, Fastify, or a note that neither import was found), express and fastify (the version range in a pasted package.json, or null), express_major and fastify_major (4, 5, ... or null when unknown), node (engines.node), package_json_pasted, dependencies (the package names, up to 60), and code_files, routes (counts) and entry_pasted (whether the file that creates the app was pasted). |
files | Every file: path (use it verbatim), kind (code, package, other), line and end_line (the paste lines of the file’s content, without its // file: line), and for code a role (entry, router, middleware, controller, module). |
exports | Every export (up to 200): name (default for module.exports = x or a default export), file, line, kind (function, const, module.exports, export default). |
functions | Every named function (up to 200): name, file, line, async, params. |
routes | Every route (up to 150): method, path, full_path (with its mount prefix), label ("POST /api/orders"), file, line, kind (express or fastify), handler (inline, a name with the file it was found in, or <name> (not pasted)), async, wrapped (inside an asyncHandler-style call), middleware (the chain before the handler: {name, kind} with kind body_parser, auth, rate_limit, cors, helmet, router, middleware, error_handler ...), reads (body, params, query, headers, cookies), and for a Fastify route with an options object, options. |
middleware | Every use() in registration order (up to 120): file, line, on (the app or router variable), path (the mount path or null), args ({name, kind} as above). |
error_handlers | Where error-handling middleware (four parameters) or setErrorHandler is registered (up to 20): file, line, on. |
flags | Every flag the page raised: uid (F1, F2, ... in severity, file, then line order), id (EX-01..EX-18), sev (high | medium | low | info), file, line, text, detail, and lines (every line an aggregated flag covers, only when it covers more than one). An audit must answer every non-info flag by its uid; a fix must close every high and medium flag or name it in open. |
verdict_candidate | The audit verdict the flags point at (the page’s own reading): blocked with any high flag, needs_work with any medium, otherwise ready. |
notes | Plain sentences about what was not read or not sent (empty for a paste that fits). |
The simplest source is expresskit.js itself: the page loads it from
/expresskit.js, and it also runs in Node (it sets module.exports).
Its buildInput is the function the page calls, so this script prints exactly what the page would
send as code and facts (with the 60 000-character cut applied), with the facts decoded
so you can read them; the step 4 samples compact them back into the page’s string byte for byte. Save the
paste as orders-paste.txt and redirect the output into orders-check.json:
// build-check.js -- run: node build-check.js orders-paste.txt > orders-check.json (expresskit.js saved next to it)
const fs = require("fs");
const Expresskit = require("./expresskit.js");
const A = Expresskit.analyze(fs.readFileSync(process.argv[2], "utf8"));
if (!A.ok) { console.error(A.errors.join("\n")); process.exit(1); }
const inp = Expresskit.buildInput("audit", A, "", null); // code and facts do not depend on the lane
console.log(JSON.stringify({ code: inp.code, facts: JSON.parse(inp.facts) }, null, 2));
const real = A.flags.filter((f) => f.sev !== "info");
const codeFiles = A.files.filter((f) => f.kind === "code").length;
console.error(codeFiles + " code file(s), " + A.routes.length + " route(s), " + A.middleware.length + " use() registration(s); " +
real.length + " flags (" + A.counts.high + " high, " + A.counts.medium + " medium, " + A.counts.low + " low); verdict_candidate " + A.candidate);
For the orders example it reports 3 code file(s), 3 route(s), 4 use() registration(s); 10 flags (5 high, 3 medium, 2 low); verdict_candidate blocked;
for the tasks example, 3 code file(s), 3 route(s), 0 use() registration(s); 7 flags (2 high, 2 medium, 3 low); verdict_candidate blocked.
For both worked examples its code and facts are exactly what the page sends.
Without the page’s check. The platform only checks that facts
is a string, so a minimal object works — "{}", or a hand-built one such as
{"flags": [], "notes": ["No page check was run; read the code directly."]}
— and the model still reads code. But you lose what the check adds: with no
flags there is nothing to confirm, dismiss or close, with no files there are
no paths, line ranges or roles to name, with no routes and middleware the model has
to rebuild every mount and chain itself, with no runtime it does not know the Express major (and so
whether an async handler needs a wrapper), and with no exports nothing says what must survive a
fix. Build it with expresskit.js as above when you can.
The audit_handoff string
Only for task: "fix", and only for the code the audit was about with every audit edit
applied. The page builds both from the normalised audit reply res
(recon.js normalize, below) and the analysis A of the audited
paste, in recon.js fixHandoff:
const run = Recon.applyFindings(A, res.findings, Expresskit); // every finding's edits, in order, each on top of the last
JSON.stringify({ verdict: res.verdict, headline: res.headline,
findings: res.findings.map(f => ({ id: f.id, severity: f.severity, file: f.file, line: f.line, title: f.title, why: f.why, edits: f.edits.length })),
applied: /* edits that applied */, failed: /* edits that did not */ })
// run.A.text is the edited paste: it becomes code, and facts is recomputed from it
That is {"verdict", "headline", "findings": [{id, severity, file, line, title, why, edits}],
"applied", "failed"}, where each finding’s edits is the number of
edits it carried. applyFindings applies every finding’s edits with
Expresskit.applyEdits, in the order listed: each before must occur exactly once
in its file as the earlier edits left it (leading and trailing blank lines ignored), or that edit is
reported as failed and skipped — it is never guessed. The handoff is a previous reply, not
evidence: the model checks it against code and fixes what is left. This script does the
whole step — the edited paste, its recomputed facts and the handoff string, in the same shape as
build-check.js prints, so the samples read it the same way:
// handoff.js -- run: node handoff.js orders-paste.txt orders-reply.txt > orders-fix-check.json (expresskit.js, recon.js next to it)
const fs = require("fs");
const Expresskit = require("./expresskit.js");
const Recon = require("./recon.js");
const A = Expresskit.analyze(fs.readFileSync(process.argv[2], "utf8")); // the audited paste
const res = Recon.normalize(Recon.parseResult(fs.readFileSync(process.argv[3], "utf8")), "audit");
const ho = Recon.fixHandoff(A, res); // every edit, in order
const B = Expresskit.analyze(ho.text); // facts recomputed from the edited paste
const inp = Expresskit.buildInput("fix", B, "", ho.handoff);
console.log(JSON.stringify({ code: inp.code, facts: JSON.parse(inp.facts), audit_handoff: inp.audit_handoff }, null, 2));
console.error(ho.applied + " edit(s) applied, " + ho.failed + " did not apply");
Save the audit’s output.output as orders-reply.txt; for the orders
audit below it reports 8 edit(s) applied, 0 did not apply. The edited paste then reads
3 code file(s), 3 route(s), 4 use() registration(s); 3 flags (0 high, 2 medium, 1 low); verdict_candidate needs_work:
what is left is the CORS origin list (EX-09), authentication in front of POST /api/orders (EX-14)
and helmet (EX-16) — the three decisions the audit’s own questions ask about. The page sends that fix
run with the audit’s note and the key routelarch:fix:5b65685f-2c1f:a1.
The output contracts
The model is instructed (SKILL.md) to reply with one JSON object and
nothing else — no prose, no code fences — with every array of its lane
present ([] when empty). The run’s output text is that object. In both lanes it
must ground everything in code: a middleware, a helper (db, User,
requireAuth, a config module ...), a database schema, a proxy or the deployment that is not in the
paste is never claimed to do anything — it goes into questions, and a handler or middleware
that was not pasted is written about conditionally (“if requireAuth checks the token ...”). It must
never write a secret value: a line that holds a literal credential is never copied into the reply (not in
evidence, not in an edit, not in a file), and no credential, real hostname or endpoint is invented. A new setting
is read the way the paste already reads settings (through its config module). It must never change what the
service offers its clients: every route keeps its method and full path, every export keeps its name, and every
response keeps its shape and status on success, unless the note asks for it (adding a 400/401/403/404 answer
for a request that must be refused is allowed). It uses an API only when it is sure the API exists with that
name in that package and version, says which advice depends on the version when the version is unknown, and
never adds a package the paste does not already use unless the note allows it (a small asyncHandler
helper is preferred to a new package). Every page flag whose sev is not info is
answered exactly once (audit lane: in flag_answers), and an info flag is never reported as a
finding.
task: "audit"
task:"audit".verdict:blocked(any finding is high) |needs_work(the worst is medium) |ready(nothing above low; low findings and questions do not stop a release).headline: one sentence.service: one or two sentences — what the service exposes and how a request flows through it.areas[]: exactly four entries, in the order errors, input, security, operations:{area, status, note}withstatussound|fragile|broken(brokenwhen a high finding sits in that area,fragilefor a medium one; the findings decide the ratings).errors: async errors reach one error handler that is registered last with four parameters, no stack or raw error reaches the client, no response is sent twice;input: a body parser before every route that reads the body, validated input, parameterised queries, no whole body written, no request input in paths, shell commands or redirects;security: authentication in front of every write and ownership checks inside, no credential in the source, verified tokens, CORS from a list, rate limits on login and token routes;operations: nothing blocks the event loop, nothing the framework version removed, helmet, graceful shutdown, Fastify schemas.findings[]:{id, flags[], file, line, severity, area, title, evidence, why, edits[]}.idis a catalogue id (EX-01..EX-17) orother;flagslists the pageuids it confirms ([]for something the page did not flag — a handler that never checks the record belongs toreq.user, a role or price taken from the body, a list route with no limit, a 500 where a 404 belongs);fileis apathfromfacts.files, copied exactly, and it is the file the finding’s edits change (a body parser order fix for a route inroutes/orders.jsis a finding onserver.js, carrying the flag);lineis the line ofcodewith the offending text, inside that file’sline..end_line;severityishigh(a request fails, the process can crash, or anyone can exploit it: a rejected await with no path to the error handler on Express 4,req.bodyread with no parser in front, SQL built from request input, request input in a file path or a shell command,jwt.decode()used as verification or"none"accepted, an Express 5 removed API on Express 5) |medium(it works but leaks, trusts or skips a guard: a missing, misplaced or three-parameter error handler, a stack trace sent to the client, a whole request body written, a MongoDB filter taken from the request, a credential literal, CORS that reflects any origin with credentials, a response sent twice, a login route with no rate limit, a blocking sync call in a handler, an open redirect, a write route with no authentication, a trusted client-sent owner id, a missing ownership check) |low(bounded risk or hygiene:cors()with no options on a cookie-less API,jwt.verifywith no algorithms list, a raw error object sent, no helmet, no graceful shutdown, a Fastify route with no schema, a removed Express 5 API when the version is unknown);areaiserrors|input|security|operations;evidenceis one to three whole, consecutive lines copied verbatim fromcodeas pasted (indentation may be trimmed), never a line that holds a credential literal.edits[]:{before, after}on that one file. Eachbeforeis a contiguous, exact slice of the file — whole lines with their indentation, as pasted — that occurs exactly once in the file as it stands when the edit is applied;afteris its replacement, with the same indentation style (spaces, never tabs). Findings are applied in the order listed and their edits in order, each on top of the ones before, so a later finding that touches the same lines writes itsbeforeas the earlier one left it. A newrequireorimportgoes into the edit of the first finding that needs it, as an edit of an existing require/import line. A fix that spans two pasted files is one finding per file, each carrying the flag. An audit edit never creates a file. A credential literal (EX-07) cannot be edited without quoting it, so its finding hasedits: []and says inwhywhich setting replaces it; the fix lane rewrites the file without the value. Every line an edit adds follows the house rules itself, so the page’s re-check raises nothing new.editsmay be[]only for a low finding, a credential literal, a change outside the paste, a change that needs a new file or a new package, or a decision only the engineer can make (which origins to allow, which auth to use).flag_answers[]:{flag, status, reason}, exactly one per non-info page flag,statusconfirmed(a finding lists that uid) |dismissed(with the reason).plan[]: the findings in the order to apply them, ending with running the test suite, starting the service and calling each changed route (a bad request, an unauthenticated request, a request that makes the database throw), andnpm audit.questions[](only what the paste cannot answer) andsummary(two to four sentences, answeringnoteif one was sent).
task: "fix"
task:"fix".verdict:fixed(openis empty) |partial.headline: one sentence.files[]:{path, content}— every pasted file it changes, whole (every line, without the// file:line, indented as the file already is: spaces, never tabs), under itspathfromfacts.files. At most two new files next to the files they serve (a smallasyncHandlerhelper, a validation schema), never a secondpackage.json. Only the packages the paste already uses. Every export, route, require/import still used and comment survives; a credential literal is replaced with a configuration read, never copied.changes[]:{file, function, flags[], change, why}, one per change.functionis the route label asfacts.routeswrites it ("POST /api/orders") for a change inside a route handler, the function name asfacts.functionswrites it for a named function or middleware,(module)for a file-level change (a require, ause()line, a constant) or(new file)for a file it added;flagsare the page flags the change closes. Every high and medium flag appears in theflagsof a change or of anopenitem.verify[]: always the test suite and starting the service, plus the request that would have caught the worst problem (send a query that makes the database throw and expect a JSON 500 with no stack; send a body with an extrarolefield and expect it ignored; call the write route without a token and expect a 401; request../server.jsas a file name and expect a 400 or 404).open[]:{item, flags[], reason}for what it left unfixed — a decision for the engineer, code outside the paste, or something the note asked it to leave alone (a route whose only fix would move it, a package the note does not allow).questions[]andsummary, as in the audit lane; with anaudit_handoff, it fixes what the audit left.
How the page reads a reply (recon.js parseResult and
normalize): it strips a code fence and parses from the first { to the
last }. A reply is read as the lane that was asked for, unless it names the other lane
and carries that lane’s body (a files array for fix, a
findings array for audit). An unknown verdict shows as
needs_work or partial and counts as a disagreement; an unknown
severity becomes medium; ids like EX7 or ex 7
become EX-07; flags keeps only F<n> values; findings
with no file, evidence and title are dropped; service may also arrive as
route or api, file as path, a
change’s function as func and its change as
what, and an open item as path; questions is
capped at 10, plan at 30 and verify at 20. An audit reply carries 9 sections
(verdict, headline, service, areas, findings, flag_answers, plan, questions, summary) and a fix reply 8
(verdict, headline, files, changes, verify, open, questions, summary), which is what the page counts
when it recovers a partial reply.
Worked examples
Both are examples the page ships (the example buttons on the app page; the full
pastes are in /example.js, ids orders and
tasks, and the replies in /example-result.js), built exactly as the
page builds them: Expresskit.buildInput(lane, Expresskit.analyze(text), note, null), with the real
model reply the page replays for each. The request bodies cut the long code and facts
strings with a visible ..., and the replies are pretty-printed excerpts, with ...
where entries were left out. Every value shown is copied from the input or the reply. The services and their
values are invented; the tasks paste holds a (made-up) database password in a connection string, which is why
that line is not shown here and why the reply never repeats it.
Audit: an orders API on Express 4 whose async handlers never reach the error handler, whose body parser comes after the router, and which builds SQL from the query string
Four files: package.json (express ^4.19.2, pg and
cors), server.js, db.js and routes/orders.js: a small orders
service. server.js registers cors({ origin: true, credentials: true }), mounts the
orders router on /api/orders and only then registers express.json(), and its
error handler sends err.message and err.stack to the client. The three route handlers
are async with no try/catch and no wrapper, GET /api/orders interpolates
req.query.status into its SQL, and POST /api/orders has no authentication in front of
it. The page’s free check raises 10 flags (5 high, 3 medium, 2 low) (plus the EX-18 context flag); its own
reading of the verdict is blocked. The engineer’s note: “Since we moved the handlers to
async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch
on Monday?”
The code string, whole: 1,679 characters, 66 lines ending in a newline. Save it as
orders-paste.txt and run
node build-check.js orders-paste.txt > orders-check.json:
// file: package.json
{
"name": "pinvarra-orders-api",
"private": true,
"main": "server.js",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"pg": "^8.11.3"
}
}
// file: server.js
const express = require("express");
const cors = require("cors");
const ordersRouter = require("./routes/orders");
const app = express();
app.use(cors({ origin: true, credentials: true }));
app.use("/api/orders", ordersRouter);
app.use(express.json());
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: err.message, stack: err.stack });
});
app.listen(3000, () => {
console.log("orders api listening");
});
// file: db.js
const { Pool } = require("pg");
const pool = new Pool({ max: 10 });
module.exports = pool;
// file: routes/orders.js
const { Router } = require("express");
const db = require("../db");
const router = Router();
router.get("/", async (req, res) => {
const { status } = req.query;
const result = await db.query(`SELECT id, sku, qty, status FROM orders WHERE status = '${status}' ORDER BY id DESC`);
res.json(result.rows);
});
router.get("/:id", async (req, res) => {
const result = await db.query("SELECT id, sku, qty, status FROM orders WHERE id = $1", [req.params.id]);
if (!result.rows.length) {
return res.status(404).json({ error: "order not found" });
}
res.json(result.rows[0]);
});
router.post("/", async (req, res) => {
const { sku, qty } = req.body;
const result = await db.query(
"INSERT INTO orders (sku, qty, status) VALUES ($1, $2, 'open') RETURNING id",
[sku, qty]
);
res.status(201).json({ id: result.rows[0].id });
});
module.exports = router;
Its facts, decoded, as an excerpt: the runtime block, the files, the first route and
the first three use() registrations, the error handler, and every flag without its
detail sentence (the full object also lists the other two routes, the fourth registration —
the error handler — and the two exports, the module.exports of db.js and of
routes/orders.js; the request carries it as one compact JSON string):
{
"runtime": {
"framework": "Express",
"express": "^4.19.2",
"express_major": 4,
"fastify": null,
"fastify_major": null,
"node": null,
"package_json_pasted": true,
"dependencies": [
"cors",
"express",
"pg"
],
"code_files": 3,
"routes": 3,
"entry_pasted": true
},
"files": [
{"path":"package.json","kind":"package","line":2,"end_line":11},
{"path":"server.js","kind":"code","line":13,"end_line":30,"role":"entry"},
{"path":"db.js","kind":"code","line":32,"end_line":36,"role":"module"},
{"path":"routes/orders.js","kind":"code","line":38,"end_line":66,"role":"router"}
],
"routes": [
{
"method": "GET",
"path": "/",
"full_path": "/api/orders",
"label": "GET /api/orders",
"file": "routes/orders.js",
"line": 43,
"kind": "express",
"handler": "inline",
"async": true,
"wrapped": false,
"middleware": [],
"reads": [
"query"
]
},
...
],
"middleware": [
{"file":"server.js","line":19,"on":"app","path":null,"args":[{"name":"cors({ origin: true, credentials: tru...","kind":"cors"}]},
{"file":"server.js","line":20,"on":"app","path":"/api/orders","args":[{"name":"ordersRouter","kind":"router"}]},
{"file":"server.js","line":21,"on":"app","path":null,"args":[{"name":"express.json()","kind":"body_parser"}]},
...
],
"error_handlers": [
{
"file": "server.js",
"line": 23,
"on": "app"
}
],
"flags": [
{"uid":"F1","id":"EX-01","sev":"high","file":"routes/orders.js","line":45,"text":"async handler for GET /api/orders: a rejected await never reaches the error handler"},
{"uid":"F2","id":"EX-05","sev":"high","file":"routes/orders.js","line":45,"text":"SQL built from request input in GET /api/orders"},
{"uid":"F3","id":"EX-01","sev":"high","file":"routes/orders.js","line":50,"text":"async handler for GET /api/orders/:id: a rejected await never reaches the error handler"},
{"uid":"F4","id":"EX-04","sev":"high","file":"routes/orders.js","line":58,"text":"req.body is read by a route registered before the body parser (POST /api/orders)"},
{"uid":"F5","id":"EX-01","sev":"high","file":"routes/orders.js","line":59,"text":"async handler for POST /api/orders: a rejected await never reaches the error handler"},
{"uid":"F6","id":"EX-09","sev":"medium","file":"server.js","line":19,"text":"CORS reflects any origin and allows credentials"},
{"uid":"F7","id":"EX-03","sev":"medium","file":"server.js","line":25,"text":"the stack trace of err is sent to the client"},
{"uid":"F8","id":"EX-14","sev":"medium","file":"routes/orders.js","line":57,"text":"no authentication in front of POST /api/orders"},
{"uid":"F9","id":"EX-16","sev":"low","file":"server.js","line":17,"text":"no helmet() on app"},
{"uid":"F10","id":"EX-16","sev":"low","file":"server.js","line":28,"text":"no graceful shutdown: SIGTERM is never handled"},
{"uid":"F11","id":"EX-18","sev":"info","file":"server.js","line":13,"text":"context: Express, 3 code files, 3 routes, 4 use() registrations, express ^4.19.2"}
],
"verdict_candidate": "blocked",
"notes": []
}
Note what the chain says on its own: use() on line 20 mounts the router and line 21 registers the
body parser, so every route inside the router reads req.body before it is parsed (F4). The request
body (POST /estimate, then POST /run or /run-stream with
Idempotency-Key: routelarch:audit:e299aa89-260e:a1 as the page sends it):
{
"task": "audit",
"code": "// file: package.json\n{\n \"name\": \"pinvarra-orders-api\",\n \"private\": true,\n \"main\": \"server.js\",\n \"dependencies\": {\n \"cors\": \"^2.8.5\",\n \"express\": \"^4.19.2\",\n \"pg\": \"^8.11.3\"\n }\n}\n// file: server.js\nconst express = require(\"express\");\nconst cors = require(\"cors\");\n...",
"facts": "{\"runtime\":{\"framework\":\"Express\",\"express\":\"^4.19.2\",\"express_major\":4,\"fastify\":null,\"fastify_major\":null,\"node\":null,\"package_json_pasted\":true,\"dependencies\":[\"cors\",\"express\",\"pg\"],\"code_files\":3,\"routes\":3,\"entry_pasted\":true},\"files\":[{\"path\":\"package.json\",\"kind\":\"package\",\"line\":2,\"end_line\":11},{\"path\":\"server.js\",\"kin...",
"note": "Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?"
}
The reply (output.output), an excerpt: the verdict, the headline, the service, the four areas,
the finding that answers “why does it hang and crash” (the unwrapped async handler that also builds
SQL from the query string) with its edits, the body parser finding, the first two flag answers, the last two plan
steps, the first question and the summary:
{
"task": "audit",
"verdict": "blocked",
"headline": "Unparameterised SQL, unhandled async rejections on Express 4, and a body parser registered after the router can each hang or crash this orders API before Monday's launch.",
"service": "This Express 4 service exposes three JSON routes under /api/orders (list by status, fetch by id, create) backed directly by a pg Pool; a request passes through CORS, then the orders router, and only afterward a JSON body parser and a single catch-all error handler.",
"areas": [
{
"area": "errors",
"status": "broken",
"note": "All three route handlers are async with no try/catch, no asyncHandler wrapper and no express-async-errors, so a rejected query hangs the request and can crash the process on Node 15+; the error handler itself also sends err.stack and err.message to the client."
},
{
"area": "input",
"status": "broken",
"note": "express.json() is registered after the orders router, so req.body is undefined on every POST /api/orders; GET /api/orders also builds its SQL by interpolating req.query.status directly into the query text."
},
{
"area": "security",
"status": "fragile",
"note": "POST /api/orders has no authentication in its chain, and CORS reflects any Origin while allowing credentials, so a page on any other site could call it using a signed-in user's cookies."
},
{
"area": "operations",
"status": "sound",
"note": "No helmet() and no SIGTERM handling were the only gaps found here, both low severity; nothing blocks the event loop and no Express 5 API is used."
}
],
"findings": [
{
"id": "EX-01",
"flags": [
"F1",
"F2"
],
"file": "routes/orders.js",
"line": 45,
"severity": "high",
"area": "errors",
"title": "GET /api/orders builds SQL from request input and can hang the process on failure",
"evidence": " const { status } = req.query;\n const result = await db.query(`SELECT id, sku, qty, status FROM orders WHERE status = '${status}' ORDER BY id DESC`);",
"why": "req.query.status is spliced straight into the query text, so a value like ' OR '1'='1 changes the WHERE clause and a value with a quote can run a second statement (SQL injection). Separately this is an async handler with no try/catch and no wrapper: on Express 4 a rejected db.query() (a bad connection, a syntax error) never reaches the error handler, the request just hangs, and on Node 15+ the unhandled rejection can crash the whole process - matching the hangs and crashes reported before launch.",
"edits": [
{
"before": "const db = require(\"../db\");",
"after": "const db = require(\"../db\");\nconst asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);"
},
{
"before": "router.get(\"/\", async (req, res) => {\n const { status } = req.query;\n const result = await db.query(`SELECT id, sku, qty, status FROM orders WHERE status = '${status}' ORDER BY id DESC`);\n res.json(result.rows);\n});",
"after": "router.get(\"/\", asyncHandler(async (req, res) => {\n const { status } = req.query;\n const result = await db.query(\n \"SELECT id, sku, qty, status FROM orders WHERE status = $1 ORDER BY id DESC\",\n [status]\n );\n res.json(result.rows);\n}));"
}
]
},
{
"id": "EX-04",
"flags": [
"F4"
],
"file": "server.js",
"line": 20,
"severity": "high",
"area": "input",
"title": "The JSON body parser is registered after the orders router",
"evidence": "app.use(\"/api/orders\", ordersRouter);\napp.use(express.json());",
"why": "Express runs middleware in registration order: a request to /api/orders reaches the router before express.json() ever parses the body, so req.body is undefined for POST /api/orders. Destructuring `const { sku, qty } = req.body;` then throws inside an async handler, which becomes an unhandled rejection under EX-01 - every POST request fails this way today.",
"edits": [
{
"before": "app.use(\"/api/orders\", ordersRouter);\napp.use(express.json());",
"after": "app.use(express.json());\napp.use(\"/api/orders\", ordersRouter);"
}
]
},
...
],
"flag_answers": [
{
"flag": "F1",
"status": "confirmed",
"reason": "Async handler with no try/catch or wrapper at routes/orders.js line 45."
},
{
"flag": "F2",
"status": "confirmed",
"reason": "req.query.status is interpolated into the SQL text at routes/orders.js line 45."
},
...
],
"plan": [
...,
"Run the test suite, start the service, then call each route: GET /api/orders?status=' OR '1'='1 (expect a normal result, not an error), POST /api/orders with a normal body (expect 201, no crash), a request that makes the database throw (expect a JSON 500 with no stack), and POST /api/orders without a token once auth is added (expect 401).",
"Run npm audit."
],
"questions": [
"What authentication should protect POST /api/orders, and should the GET routes stay public? No auth code is pasted.",
...
],
"summary": "The hangs and crashes reported trace to two compounding Express 4 bugs: express.json() is registered after the orders router, so every POST /api/orders reads req.body as undefined and throws, and none of the three handlers catch a rejected promise, so that throw (or any database error) becomes an unhandled rejection that Express 4 can't route to the error handler and Node can turn into a process crash. GET /api/orders also builds its SQL by string interpolation, a SQL injection alongside the crash risk. The body-parser order, all three handlers and the parameterised query are fixed with edits here; POST /api/orders still needs authentication, CORS needs a real origin allow-list, and the error handler still needs to stop returning err.stack before Monday's launch."
}
The reply has 8 findings and confirms all 10 flags, with 8 edits in all. The first finding (EX-01, carrying F1
and F2) adds a one-line asyncHandler next to the db require and wraps
GET /api/orders in it with a $1 placeholder; the second (EX-01, F3 and F5) wraps
GET /api/orders/:id and POST /api/orders the same way; the EX-04 finding shown above
moves express.json() in front of the router (F4); the EX-03 finding stops sending
err.stack (F7); and the low EX-16 finding for F10 adds a SIGTERM handler that closes the server and
the pool. Three findings carry no edit because they need a decision the paste cannot make: authentication for
POST /api/orders (EX-14, F8), the CORS origin list (EX-09, F6) and helmet, which is not yet a
dependency (EX-16, F9) — which is why its questions ask about exactly those three, and why the re-check
below still raises them.
Fix: a Fastify task API that decodes tokens without verifying them, builds ORDER BY from the query string, keeps its database password in the source and reflects any origin with credentials
Four files: package.json (fastify ^4.28.1, @fastify/cors,
jsonwebtoken and mysql2), server.js, config.js and
routes/tasks.js. server.js creates the MySQL pool from a literal connection string that
carries a password, registers @fastify/cors with origin: true, credentials: true, and
authenticates every request in an onRequest hook that calls jwt.decode() and never
jwt.verify(); routes/tasks.js puts request.query.sort into
ORDER BY. The free check raises 7 flags (2 high, 2 medium, 3 low); its reading of the verdict is
blocked. This example has no audit before it, so it carries no audit_handoff. The
engineer’s note: “Pen test found that any made-up token is accepted. Fix every high and medium problem
the check found; keep every route URL, and do not add packages.”
The code string is 2,227 characters, 78 lines ending in a newline; the whole paste is the
tasks entry of /example.js. Save it as tasks-paste.txt and
run node build-check.js tasks-paste.txt > tasks-check.json. Two excerpts: the hook F1 points at,
and the routes file, whole:
// file: server.js (lines 26-33)
app.addHook("onRequest", async (request, reply) => {
const auth = request.headers.authorization || "";
const claims = jwt.decode(auth.replace(/^Bearer /, ""));
if (!claims) {
return reply.code(401).send({ error: "sign in first" });
}
request.user = claims;
});
// file: routes/tasks.js
module.exports = async function tasksRoutes(fastify) {
fastify.get("/", async (request) => {
const sort = request.query.sort || "created_at";
const [rows] = await fastify.db.query(
`SELECT id, title, done FROM tasks WHERE owner_id = ? ORDER BY ${sort}`,
[request.user.sub]
);
return rows;
});
fastify.post(
"/",
{
schema: {
body: {
type: "object",
required: ["title"],
properties: { title: { type: "string", minLength: 1, maxLength: 200 } },
},
},
},
async (request, reply) => {
const [result] = await fastify.db.execute("INSERT INTO tasks (owner_id, title) VALUES (?, ?)", [request.user.sub, request.body.title]);
reply.code(201);
return { id: result.insertId };
}
);
fastify.patch("/:id", async (request) => {
await fastify.db.execute("UPDATE tasks SET done = ? WHERE id = ? AND owner_id = ?", [request.body.done ? 1 : 0, request.params.id, request.user.sub]);
return { ok: true };
});
};
Its facts, decoded, cut to runtime, files, the flags (without their
detail) and verdict_candidate (the full object also carries the 3 exports —
port and jwtKey of config.js, the module.exports of
routes/tasks.js —, the function tasksRoutes and the three routes
GET /tasks, POST /tasks and PATCH /tasks/:id; Fastify has no
use() chain here, so middleware and error_handlers are empty):
{
"runtime": {
"framework": "Fastify",
"express": null,
"express_major": null,
"fastify": "^4.28.1",
"fastify_major": 4,
"node": null,
"package_json_pasted": true,
"dependencies": [
"@fastify/cors",
"fastify",
"jsonwebtoken",
"mysql2"
],
"code_files": 3,
"routes": 3,
"entry_pasted": true
},
"files": [
{"path":"package.json","kind":"package","line":2,"end_line":11},
{"path":"server.js","kind":"code","line":13,"end_line":37,"role":"entry"},
{"path":"config.js","kind":"code","line":39,"end_line":44,"role":"module"},
{"path":"routes/tasks.js","kind":"code","line":46,"end_line":78,"role":"router"}
],
"flags": [
{"uid":"F1","id":"EX-08","sev":"high","file":"server.js","line":28,"text":"jwt.decode() used without jwt.verify()"},
{"uid":"F2","id":"EX-05","sev":"high","file":"routes/tasks.js","line":49,"text":"SQL built from request input in GET /tasks"},
{"uid":"F3","id":"EX-07","sev":"medium","file":"server.js","line":20,"text":"credential literal in a connection string"},
{"uid":"F4","id":"EX-09","sev":"medium","file":"server.js","line":22,"text":"CORS reflects any origin and allows credentials"},
{"uid":"F5","id":"EX-16","sev":"low","file":"server.js","line":37,"text":"no graceful shutdown: SIGTERM is never handled"},
{"uid":"F6","id":"EX-17","sev":"low","file":"routes/tasks.js","line":47,"text":"GET /tasks reads request.query with no schema"},
{"uid":"F7","id":"EX-17","sev":"low","file":"routes/tasks.js","line":74,"text":"PATCH /tasks/:id reads request.body with no schema"},
{"uid":"F8","id":"EX-18","sev":"info","file":"server.js","line":13,"text":"context: Fastify, 3 code files, 3 routes, 0 use() registrations, fastify ^4.28.1"}
],
"verdict_candidate": "blocked"
}
The request body (Idempotency-Key: routelarch:fix:601c1dd2-1fbb:a1):
{
"task": "fix",
"code": "// file: package.json\n{\n \"name\": \"ferlowen-tasks\",\n \"private\": true,\n \"dependencies\": {\n \"@fastify/cors\": \"^9.0.1\",\n \"fastify\": \"^4.28.1\",\n \"jsonwebtoken\": \"^9.0.2\",\n \"mysql2\": \"^3.11.0\"\n }\n}\n// file: server.js\nconst Fastify = require(\"fastify\");\nconst cors = require(\"@fastify/cors\");\nconst jwt = require(\"jsonwebtoken\");\nconst mysql = require(\"mysql2/promise\");\nconst config = require(\"./config\");\n...",
"facts": "{\"runtime\":{\"framework\":\"Fastify\",\"express\":null,\"express_major\":null,\"fastify\":\"^4.28.1\",\"fastify_major\":4,\"node\":null,\"package_json_pasted\":true,\"dependencies\":[\"@fastify/cors\",\"fastify\",\"jsonwebtoken\",\"mysql2\"],\"code_files\":3,\"routes\":3,\"entry_pasted\":true},\"files\":[{\"path\":\"package.json\",\"kind\":...",
"note": "Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages."
}
The reply (output.output), an excerpt: the verdict, the headline, the first of the three
returned files (server.js, whole, with the connection string replaced by
config.databaseUrl), the change that closes the token hole and the one that closes the SQL
injection, the first three verification steps, the (empty) open list and the summary:
{
"task": "fix",
"verdict": "fixed",
"headline": "Tokens are now verified, the sort column is allow-listed, the DB credential moved to config, and CORS is restricted to a configured origin list.",
"files": [
{
"path": "server.js",
"content": "const Fastify = require(\"fastify\");\nconst cors = require(\"@fastify/cors\");\nconst jwt = require(\"jsonwebtoken\");\nconst mysql = require(\"mysql2/promise\");\nconst config = require(\"./config\");\n\nconst app = Fastify({ logger: true });\nconst db = mysql.createPool(config.databaseUrl);\n\napp.register(cors, { origin: config.corsOrigins, credentials: true });\n\napp.decorate(\"db\", db);\n\napp.addHook(\"onRequest\", async (request, reply) => {\n const auth = request.headers.authorization || \"\";\n const token = auth.replace(/^Bearer /, \"\");\n let claims;\n try {\n claims = jwt.verify(token, config.jwtKey, { algorithms: [\"HS256\"] });\n } catch (err) {\n return reply.code(401).send({ error: \"sign in first\" });\n }\n request.user = claims;\n});\n\napp.register(require(\"./routes/tasks\"), { prefix: \"/tasks\" });\n\napp.listen({ port: config.port, host: \"0.0.0.0\" });\n"
},
...
],
"changes": [
{
"file": "server.js",
"function": "(module)",
"flags": [
"F1"
],
"change": "Verify the bearer token with jwt.verify(token, config.jwtKey, { algorithms: [\"HS256\"] }) inside a try/catch instead of jwt.decode(), answering 401 when verification throws",
"why": "stops anyone from writing an unsigned or forged token and being treated as signed in"
},
{
"file": "routes/tasks.js",
"function": "GET /tasks",
"flags": [
"F2"
],
"change": "Check request.query.sort against an allow-list of column names (created_at, title, done, id), falling back to created_at when it does not match, before it reaches the ORDER BY clause",
"why": "prevents SQL injection through the sort query parameter"
},
...
],
"verify": [
"Run the test suite if one exists.",
"Set DATABASE_URL, JWT_KEY and CORS_ORIGINS and start the service (node server.js); confirm it now throws at startup when DATABASE_URL is unset instead of using a literal credential.",
"Call GET /tasks with an Authorization header holding a base64-encoded but unsigned or wrongly-signed token (the pen test's made-up token) and expect 401, not a 200 with data.",
...
],
"open": [],
"questions": [],
"summary": "Fixed both high findings: the onRequest hook now calls jwt.verify with the HS256 algorithm and config.jwtKey inside a try/catch, and GET /tasks checks its sort parameter against an allow-list before it reaches the SQL text. Fixed both medium findings by moving the database connection string and the CORS origin list into config.js, read from DATABASE_URL (failing fast if absent) and CORS_ORIGINS; server.js now uses config.databaseUrl and config.corsOrigins instead of a literal credential and origin: true. No package was added and every route keeps its method and path; the remaining low findings (no graceful shutdown, no Fastify schema on GET /tasks and PATCH /tasks/:id) were left alone since the note scoped this pass to high and medium problems. The rotated database password itself must still be changed out of band since it already appeared in the source."
}
The other two files are config.js, which now reads databaseUrl from a
DATABASE_URL setting (failing at startup when it is missing) and builds corsOrigins by
splitting a CORS_ORIGINS setting on commas, and routes/tasks.js, which checks
request.query.sort against a SORTABLE_COLUMNS allow-list. The other three changes
create the pool from config.databaseUrl (F3), register CORS with
origin: config.corsOrigins (F4), and add the two settings to config.js (F3, F4). The
verdict is fixed because every high and medium flag is closed and open is empty; the
three low flags were left alone, as the note scoped the pass, and package.json needed no change, so
it is not returned.
The page checks the reply against the code; the API does not
What comes back from the API is the model’s raw text in output.output. The page
holds it to account in the browser before showing it (recon.js
reconcile, with the facts, the code and the handoff it sent): it checks every quote against the
paste, applies every edit or returned file to the paste and re-runs its own checks on the result. None of
that happens in the API. A direct API caller gets the reply unchecked and must do the applying and
re-checking itself.
- Both lanes: the reply is the lane you asked for (one written for the other task is a disagreement), it carries a valid verdict for that lane, and no credential from the paste is repeated anywhere in it (every credential value the free check found in the paste).
- Audit: the areas table. Exactly four rows, covering errors, input, security and operations once each, each with a valid status.
- Audit: every quote is checked against the paste. A finding’s
idis in the catalogue (EX-01..EX-17, orother; EX-18 is context and never a finding); itsfileis a pasted file; itslineis inside that file; itsevidenceis found line by line in that file ofcode(indentation trimmed) and on or nearline; every citeduidis a non-info flag the page raised. A finding that carries a high flag at a lower severity, or a medium or high finding with no edit, is pointed out. - Audit: every flag is answered once, consistently. Every non-info flag answered exactly once and no other; a
confirmedanswer needs a finding carrying that uid; adismissedone must not be carried by a finding and should give a reason. The verdict must fit the worst severity (blockedwith a high finding,needs_workwith a medium, otherwiseready). - Audit: the edits are applied in order and re-checked. The findings’ edits are applied in the listed order, each on top of the ones before (
Expresskit.applyEdits): abeforethat is not found, is found more than once, or changes nothing is a disagreement. After each finding the free check runs again: the code must still read and no new high flag may appear. With every edit applied, every confirmed flag a finding with edits carries must be gone, every export of the paste must still exist, every route must still answer at its method and full path, and a new medium flag is pointed out. - Fix: the files. At least one file, at most two new ones and never a second
package.json; none empty, none with a// file:line, every path a JavaScript/TypeScript file orpackage.json; a file indented with tabs when the paste uses spaces is pointed out. - Fix: the fixed files are put back and re-checked. The files replace the pasted files of the same path (or are added) (
Expresskit.replaceFiles) and the free check runs again: the code must read, no high or medium flag of the paste may remain unless anopenitem names it, no new high or medium flag may appear, every export and every route must still be there, and a package added topackage.jsonis pointed out. - Fix: the changes list matches the code. Every change names a returned file, a function or route label in the fixed code (or
(module)or(new file)) and real page flags; a high or medium flag no change and no open item names is pointed out. The verdict must fit theopenlist, and an emptyverifyis pointed out.
recon.js also runs in Node, so you can hold a reply to exactly the page’s standard.
Save /expresskit.js and /recon.js next to this script,
pass the file you built the body from (build-check.js or handoff.js output),
the reply text and the lane; it prints every statement, the count, and the paste with every edit (or
file) put back:
// recheck.js -- run: node recheck.js orders-check.json orders-reply.txt audit > orders-edited.txt (expresskit.js, recon.js next to it)
const fs = require("fs");
const Recon = require("./recon.js"); // loads ./expresskit.js itself
const sent = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); // code, facts (and audit_handoff) you sent
const lane = process.argv[4] || "audit";
const res = Recon.normalize(Recon.parseResult(fs.readFileSync(process.argv[3], "utf8")), lane); // output.output
const r = Recon.reconcile(res, { facts: sent.facts, code: sent.code, handoff: sent.audit_handoff || null });
r.notes.forEach((n) => console.error(n.kind + ": " + n.text));
console.error(r.disagreements + " disagreement(s) in " + r.statements + " statements checked");
if (r.after) (r.after.flags || []).forEach((f) => console.error("still raised: " + f.id + " " + f.sev + " " + f.file + ":" + f.line + " " + f.text));
if (r.after && r.after.text) console.log(r.after.text.replace(/\n$/, "")); // the paste with every edit (or file) put back
For the orders audit reply it reports:
ok: The areas table rates errors, input, security and operations once each.
warn: Finding 4 (EX-14) is medium and gives no edit; the page cannot check a fix for it - apply it by hand, or run the fix lane.
warn: Finding 5 (EX-09) is medium and gives no edit; the page cannot check a fix for it - apply it by hand, or run the fix lane.
ok: Every one of the 8 findings names a pasted file, a line inside it and evidence copied from it.
ok: The verdict (blocked) fits the worst finding.
ok: Every one of the page's 10 flags is answered once.
ok: All 8 edits applied in order, each before text found exactly once.
ok: Every confirmed flag a finding carries is gone once all edits are applied.
ok: Every export of the paste is still there after the edits.
ok: Every one of the 3 routes still answers at its method and path after the edits.
ok: The reply repeats no credential from the paste.
0 disagreement(s) in 11 statements checked
still raised: EX-09 medium server.js:20 CORS reflects any origin and allows credentials
still raised: EX-14 medium routes/orders.js:70 no authentication in front of POST /api/orders
still raised: EX-16 low server.js:18 no helmet() on app
The two warnings are the page doing its job: the audit left the CORS origin list and the authentication of
POST /api/orders as decisions for the engineer, so the re-check still raises them (on their new
lines, after the edits) and the handoff to the fix lane carries them into the next run.
and for the tasks fix reply (node recheck.js tasks-check.json tasks-reply.txt fix):
ok: 3 files returned whole.
ok: With the fixed files in place the free check raises no high or medium flag (3 low).
ok: Every one of the 3 exports is still there.
ok: Every one of the 3 routes still answers at its method and path.
ok: Every one of the 5 changes names a returned file, a function in the fixed code and real page flags.
ok: The verdict (fixed) fits the open list.
ok: The reply repeats no credential from the paste (1 found in it).
0 disagreement(s) in 7 statements checked
still raised: EX-16 low server.js:40 no graceful shutdown: SIGTERM is never handled
still raised: EX-17 low routes/tasks.js:61 GET /tasks reads request.query with no schema
still raised: EX-17 low routes/tasks.js:88 PATCH /tasks/:id reads request.body with no schema
The page does not install, build or run the code. After that, run the service’s test suite, start it and
send the requests the reply’s plan or verify names (a bad request, an
unauthenticated request, a request that makes the database throw), run npm audit, and rotate any
credential that was ever in the source, before you ship anything.
Step 1 — a tiny client helper
One function per language that does what sdk.js does for every call: JSON in,
bearer token if you have one, data out on a 2xx, an error carrying
status, error.code and error.message otherwise. The later
steps assume this helper is in the same file (for Go, Java and C#, the later snippets go inside
main / the top-level statements; Java’s makeInput from step 4 goes
in the class).
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN" # step 2: paste your token here
# api METHOD PATH [extra curl args...] -- prints the whole envelope; pipe to jq
api() {
method="$1"; path="$2"; shift 2
curl -sS -X "$method" "$BASE$path" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" "$@"
}
# success: jq '.data' failure: jq '.error | {code, message}'
import hashlib, json, time, urllib.parse
import requests # pip install requests
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # step 2: paste your token here
class ApiError(Exception):
def __init__(self, status, code, message, details=None):
super().__init__(f"{status} {code}: {message}")
self.status, self.code, self.details = status, code, details
def api(method, path, body=None, headers=None):
h = {"Content-Type": "application/json"}
if TOKEN:
h["Authorization"] = "Bearer " + TOKEN
h.update(headers or {})
r = requests.request(method, BASE + path, headers=h,
data=None if body is None else json.dumps(body))
try:
env = r.json()
except ValueError:
env = {}
if not r.ok:
err = env.get("error") or {}
raise ApiError(r.status_code, err.get("code"), err.get("message") or r.reason, err.get("details"))
return env.get("data")
// routelarch.mjs -- Node 18+ (global fetch), run with: node routelarch.mjs
import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // step 2
async function api(method, path, body, headers = {}) {
const h = { "Content-Type": "application/json", ...headers };
if (TOKEN) h.Authorization = "Bearer " + TOKEN;
const res = await fetch(BASE + path, {
method,
headers: h,
body: body === undefined ? undefined : JSON.stringify(body),
});
const json = await res.json().catch(() => ({}));
if (!res.ok) {
const err = new Error((json.error && json.error.message) || res.statusText);
err.status = res.status;
err.code = json.error && json.error.code;
err.details = json.error && json.error.details;
throw err;
}
return json.data;
}
package main
import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"
)
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "YOUR_TOKEN" // step 2: paste your token here
type apiError struct {
Status int
Code string
Message string
}
func (e *apiError) Error() string { return fmt.Sprintf("%d %s: %s", e.Status, e.Code, e.Message) }
type envelope struct {
OK bool `json:"ok"`
Data json.RawMessage `json:"data"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func newRequest(method, path string, body any, headers map[string]string) (*http.Request, error) {
var buf []byte
if body != nil {
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
buf = b
}
req, err := http.NewRequest(method, base+path, bytes.NewReader(buf))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
for k, v := range headers {
req.Header.Set(k, v)
}
return req, nil
}
func errorFrom(res *http.Response, env envelope) error {
e := &apiError{Status: res.StatusCode, Message: http.StatusText(res.StatusCode)}
if env.Error != nil {
e.Code = env.Error.Code
if env.Error.Message != "" {
e.Message = env.Error.Message
}
}
return e
}
// api sends body as JSON and decodes the envelope's data into out (out may be nil).
func api(method, path string, body any, headers map[string]string, out any) error {
req, err := newRequest(method, path, body, headers)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var env envelope
_ = json.NewDecoder(res.Body).Decode(&env)
if res.StatusCode < 200 || res.StatusCode > 299 {
return errorFrom(res, env)
}
if out != nil && len(env.Data) > 0 {
return json.Unmarshal(env.Data, out)
}
return nil
}
func main() {
// steps 2-6 go here; remove any import a step you skip no longer uses
_, _, _, _, _ = bufio.NewScanner, sha256.Sum256, hex.EncodeToString, url.PathEscape, strings.Index
_, _ = time.Second, os.ReadFile
}
// Routelarch.java -- Java 17+, needs com.fasterxml.jackson.core:jackson-databind on the classpath
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Routelarch {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // step 2: paste your token here
static final HttpClient HTTP = HttpClient.newHttpClient();
static final ObjectMapper JSON = new ObjectMapper();
static class ApiException extends Exception {
final int status;
final String code;
ApiException(int status, String code, String message) {
super(status + " " + code + ": " + message);
this.status = status;
this.code = code;
}
}
static HttpRequest.Builder request(String method, String path, Object body, Map<String, String> headers) throws Exception {
HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(BASE + path))
.header("Content-Type", "application/json")
.method(method, body == null ? HttpRequest.BodyPublishers.noBody()
: HttpRequest.BodyPublishers.ofString(JSON.writeValueAsString(body)));
if (token != null && !token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
return b;
}
static ApiException errorFrom(int status, JsonNode env) {
JsonNode err = env.path("error");
return new ApiException(status, err.path("code").asText(null), err.path("message").asText("HTTP " + status));
}
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
HttpResponse<String> res = HTTP.send(request(method, path, body, headers).build(), HttpResponse.BodyHandlers.ofString());
JsonNode env;
try { env = JSON.readTree(res.body()); } catch (Exception e) { env = JSON.createObjectNode(); }
if (env == null) env = JSON.createObjectNode();
if (res.statusCode() / 100 != 2) throw errorFrom(res.statusCode(), env);
return env.path("data");
}
public static void main(String[] args) throws Exception {
// steps 2-6 go here (makeInput from step 4 goes next to api())
}
}
require "digest"
require "json"
require "net/http"
require "uri"
BASE = "https://api.skillsafe.ai/v1/app-api"
$token = "YOUR_TOKEN" # step 2: paste your token here
class ApiError < StandardError
attr_reader :status, :code
def initialize(status, code, message)
super("#{status} #{code}: #{message}")
@status = status
@code = code
end
end
def build_request(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri)
req["Content-Type"] = "application/json"
req["Authorization"] = "Bearer #{$token}" if $token && !$token.empty?
headers.each { |k, v| req[k] = v }
req.body = JSON.generate(body) unless body.nil?
[uri, req]
end
def api(method, path, body = nil, headers = {})
uri, req = build_request(method, path, body, headers)
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
envelope = (JSON.parse(res.body) rescue {})
envelope = {} unless envelope.is_a?(Hash)
unless res.is_a?(Net::HTTPSuccess)
err = envelope["error"] || {}
raise ApiError.new(res.code.to_i, err["code"], err["message"] || res.message)
end
envelope["data"]
end
<?php
// needs the curl extension
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // step 2: paste your token here
class ApiError extends Exception {
public $status;
public $apiCode;
public function __construct($status, $code, $message) {
parent::__construct("$status $code: $message");
$this->status = $status;
$this->apiCode = $code;
}
}
function headers_for(array $extra = []): array {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($extra as $k => $v) $h[] = "$k: $v";
return $h;
}
function api(string $method, string $path, $body = null, array $headers = []) {
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => headers_for($headers),
CURLOPT_RETURNTRANSFER => true,
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
$raw = curl_exec($ch);
if ($raw === false) throw new RuntimeException(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$env = json_decode($raw);
if (!is_object($env)) $env = new stdClass();
if ($status < 200 || $status > 299) {
$e = $env->error ?? new stdClass();
throw new ApiError($status, $e->code ?? null, $e->message ?? "HTTP $status");
}
return $env->data ?? null;
}
// Program.cs -- .NET 6+ console app (implicit usings on) with top-level statements; steps 2-6 (with the MakeInput local function) go ABOVE this class
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
static class Api
{
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "YOUR_TOKEN"; // step 2: paste your token here
public static readonly HttpClient Http = new HttpClient();
public class ApiException : Exception
{
public int Status;
public string? Code;
public ApiException(int status, string? code, string message) : base($"{status} {code}: {message}")
{
Status = status;
Code = code;
}
}
public static HttpRequestMessage Request(string method, string path, object? body = null, IDictionary<string, string>? headers = null)
{
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (body != null) req.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
if (!string.IsNullOrEmpty(Token)) req.Headers.TryAddWithoutValidation("Authorization", "Bearer " + Token);
if (headers != null) foreach (var kv in headers) req.Headers.TryAddWithoutValidation(kv.Key, kv.Value);
return req;
}
public static ApiException ErrorFrom(HttpResponseMessage res, JsonNode? env)
{
var err = env?["error"];
return new ApiException((int)res.StatusCode, err?["code"]?.GetValue<string>(),
err?["message"]?.GetValue<string>() ?? res.ReasonPhrase ?? "error");
}
public static async Task<JsonNode?> Call(string method, string path, object? body = null, IDictionary<string, string>? headers = null)
{
using var req = Request(method, path, body, headers);
using var res = await Http.SendAsync(req);
var text = await res.Content.ReadAsStringAsync();
JsonNode? env = null;
try { env = JsonNode.Parse(text); } catch (JsonException) { }
if (!res.IsSuccessStatusCode) throw ErrorFrom(res, env);
return env?["data"];
}
}
Step 2 — a token
Two kinds work:
-
Personal — needed for a run. Both lanes are metered in credits on
gpt-terra, so a run needs a signed-in (user) token (the page says “The audit run is metered, so it needs an account.”, or the same of the fix run) unless the estimate sayssponsor_enabled. Sign in and copy your token from /tokens.html, and paste it in place of the placeholderYOUR_TOKENin the step 1 helper (the samples never read it from anywhere else). -
Guest — enough for
/meand/estimate.POST /guestwith{"slug": "routelarch"}and noAuthorizationheader, exactly assdk.jsguest()does. The response’sdatacarriestokenandguest_id. Reuse a token you already hold rather than minting a new one each time.
# personal token (needed for a run): copy it from /tokens.html, then
# put it in TOKEN (in place of YOUR_TOKEN) and skip the rest of this block
# guest token (enough for /me and /estimate): no Authorization header on this one call
TOKEN=$(curl -sS -X POST "$BASE/guest" \
-H "Content-Type: application/json" \
-d '{"slug":"routelarch"}' | jq -r '.data.token')
echo "guest token: ${TOKEN:0:12}..."
# personal token (needed for a run): paste it from /tokens.html in place of YOUR_TOKEN and skip this
if TOKEN in ("", "YOUR_TOKEN"):
TOKEN = "" # no Authorization header on /guest
guest = api("POST", "/guest", {"slug": "routelarch"})
TOKEN = guest["token"]
print("guest", guest.get("guest_id"))
// personal token (needed for a run): paste it from /tokens.html into TOKEN and skip this
if (!TOKEN || TOKEN === "YOUR_TOKEN") {
TOKEN = ""; // no Authorization header on /guest
const guest = await api("POST", "/guest", { slug: "routelarch" });
TOKEN = guest.token;
console.log("guest", guest.guest_id);
}
// inside main -- personal token (needed for a run): paste it from /tokens.html in place of YOUR_TOKEN and skip this
if token == "" || token == "YOUR_TOKEN" {
token = "" // no Authorization header on /guest
var guest struct {
Token string `json:"token"`
GuestID string `json:"guest_id"`
}
if err := api("POST", "/guest", map[string]string{"slug": "routelarch"}, nil, &guest); err != nil {
panic(err)
}
token = guest.Token
fmt.Println("guest", guest.GuestID)
}
// inside main -- personal token (needed for a run): paste it from /tokens.html in place of YOUR_TOKEN and skip this
if (token.isEmpty() || token.equals("YOUR_TOKEN")) {
token = ""; // no Authorization header on /guest
JsonNode guest = api("POST", "/guest", Map.of("slug", "routelarch"), null);
token = guest.path("token").asText();
System.out.println("guest " + guest.path("guest_id").asText());
}
# personal token (needed for a run): paste it from /tokens.html in place of YOUR_TOKEN and skip this
if $token.empty? || $token == "YOUR_TOKEN"
$token = "" # no Authorization header on /guest
guest = api("POST", "/guest", { slug: "routelarch" })
$token = guest["token"]
puts "guest #{guest["guest_id"]}"
end
// personal token (needed for a run): paste it from /tokens.html in place of YOUR_TOKEN and skip this
if ($TOKEN === "" || $TOKEN === "YOUR_TOKEN") {
$TOKEN = ""; // no Authorization header on /guest
$guest = api("POST", "/guest", ["slug" => "routelarch"]);
$TOKEN = $guest->token;
echo "guest {$guest->guest_id}\n";
}
// top-level statements (above the Api class) -- personal token (needed for a run): paste it from /tokens.html in place of YOUR_TOKEN and skip this
if (Api.Token == "" || Api.Token == "YOUR_TOKEN")
{
Api.Token = ""; // no Authorization header on /guest
var guest = await Api.Call("POST", "/guest", new { slug = "routelarch" });
Api.Token = guest!["token"]!.GetValue<string>();
Console.WriteLine($"guest {guest["guest_id"]}");
}
Step 3 — GET /me
Who the token belongs to and what it can spend. sdk.js documents the shape as
{subject_type, subject_id, credits, profile?}. The page treats the caller as signed
in only when subject_type is "user", shows credits as the
balance (1 credit = $0.0001, so it divides by 10 000 for the dollar figure), and compares it
with the estimate in step 4 before it lets you run.
api GET /me | jq '.data // .error'
me = api("GET", "/me")
print(me["subject_type"], me.get("subject_id"), me.get("credits"))
signed_in = me["subject_type"] == "user"
const me = await api("GET", "/me");
console.log(me.subject_type, me.subject_id, me.credits);
const signedIn = me.subject_type === "user";
var me struct {
SubjectType string `json:"subject_type"`
SubjectID any `json:"subject_id"`
Credits any `json:"credits"`
}
if err := api("GET", "/me", nil, nil, &me); err != nil {
panic(err)
}
fmt.Println(me.SubjectType, me.SubjectID, me.Credits)
signedIn := me.SubjectType == "user"
_ = signedIn
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.path("subject_type").asText() + " " + me.path("subject_id").asText() + " " + me.path("credits"));
boolean signedIn = me.path("subject_type").asText().equals("user");
me = api("GET", "/me")
puts [me["subject_type"], me["subject_id"], me["credits"]].join(" ")
signed_in = me["subject_type"] == "user"
$me = api("GET", "/me");
echo $me->subject_type, " ", $me->subject_id ?? "", " ", $me->credits ?? "", "\n";
$signedIn = $me->subject_type === "user";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me?["subject_type"]} {me?["subject_id"]} {me?["credits"]}");
var signedIn = me?["subject_type"]?.GetValue<string>() == "user";
Step 4 — build the input, then POST /estimate
First a small makeInput that does what buildInput does: task,
then code as the paste the page would send, facts as a compact JSON string,
the note when it is not blank, and — for the fix lane only — the
audit_handoff string when the file carries one. It reads the file
build-check.js (or handoff.js) printed. The samples build the
audit input of the orders example from orders-check.json; step 6 reuses
makeInput for the tasks (fix) example. Then price it. The body of
/estimate is the same input you will send to /run; no job is created and
nothing is charged — /estimate is free. From the response the page reads:
| Field | Meaning |
|---|---|
hold_credits | A reservation, not a price: the worst case the platform holds while the run is in flight. The page shows it as “reserves N credits ($...) for the audit run” and adds that you are charged only for what the run actually uses, usually far less. What you pay is the job’s charged_credits after settlement (step 5). A balance below the hold is allowed, but the page warns that the reply may be cut short (truncated: true). |
min_credits | The smallest balance that can start a run. Below it the page disables the run button and names the shortfall, because the call would be a 402. |
sponsor_enabled | When true, a caller who is not signed in may run; otherwise the page asks them to sign in. |
warnings | Input warnings. They do not refuse the call; the page shows them next to the price (“input warnings: ...”), so check them before you run. |
The page re-prices whenever the paste, the note or the lane change. The amounts grow with the size
of code and facts, so read them from the call rather than hard-coding them.
# orders-check.json: printed by build-check.js (see "The facts string").
# (.facts | tojson) turns the facts into ONE compact string, as JSON.stringify does.
BODY=$(jq -c --arg note "Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?" \
'{task: "audit", code, facts: (.facts | tojson), note: $note}' orders-check.json)
# No page check at hand? facts: "{}" is accepted as a string, but the reply then has no page flags to answer.
# Fix lane after an audit: read handoff.js's output instead and add its string:
# '{task: "fix", code, facts: (.facts | tojson), note: $note} + (if .audit_handoff then {audit_handoff} else {} end)'
api POST /estimate -d "$BODY" \
| jq '.error // (.data | {hold_credits, min_credits, sponsor_enabled, warnings})'
def make_input(task, check_path, note=None):
"""The body the page sends: task, code, facts as a JSON STRING, an optional note, and
(fix lane only) the audit_handoff string -- every value a string."""
with open(check_path, encoding="utf-8") as f:
check = json.load(f) # build-check.js (or handoff.js) output
inp = {
"task": task,
"code": check["code"], # the paste as the page sends it
"facts": json.dumps(check["facts"], separators=(",", ":"), ensure_ascii=False), # a STRING, like JSON.stringify
}
n = (note or "").strip()
if n:
inp["note"] = n[:2000] # optional; the page caps it at 2000 chars
if task == "fix" and check.get("audit_handoff"):
inp["audit_handoff"] = check["audit_handoff"] # optional; already a JSON string
return inp
inp = make_input("audit", "orders-check.json",
"Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?")
est = api("POST", "/estimate", inp)
print("hold", est.get("hold_credits"), "min", est.get("min_credits"),
"sponsored", est.get("sponsor_enabled"), "warnings", est.get("warnings") or [])
// The body the page sends: task, code, facts as a JSON STRING, an optional note,
// and (fix lane only) the audit_handoff string -- every value a string.
function makeInput(task, checkPath, note) {
const check = JSON.parse(readFileSync(checkPath, "utf8")); // build-check.js (or handoff.js) output
const input = {
task,
code: check.code, // the paste as the page sends it
facts: JSON.stringify(check.facts), // a STRING: the page's free check
};
const n = (note || "").trim();
if (n) input.note = n.length > 2000 ? n.slice(0, 2000) : n; // optional; max 2000 chars
if (task === "fix" && check.audit_handoff) input.audit_handoff = check.audit_handoff; // optional
return input;
}
const input = makeInput("audit", "orders-check.json",
"Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?");
const est = await api("POST", "/estimate", input);
console.log("hold", est.hold_credits, "min", est.min_credits,
"sponsored", est.sponsor_enabled, "warnings", est.warnings || []);
// makeInput builds the body the page sends: task, code, facts as a JSON STRING,
// an optional note and (fix lane only) the audit_handoff string.
makeInput := func(task, checkPath, note string) map[string]any {
raw, err := os.ReadFile(checkPath) // build-check.js (or handoff.js) output
if err != nil {
panic(err)
}
var check struct {
Code string `json:"code"`
Facts json.RawMessage `json:"facts"`
AuditHandoff string `json:"audit_handoff"`
}
if err := json.Unmarshal(raw, &check); err != nil {
panic(err)
}
var facts bytes.Buffer
if err := json.Compact(&facts, check.Facts); err != nil { // keeps key order; travels as a STRING
panic(err)
}
in := map[string]any{"task": task, "code": check.Code, "facts": facts.String()}
if n := []rune(strings.TrimSpace(note)); len(n) > 0 {
if len(n) > 2000 {
n = n[:2000] // optional; the page caps it at 2000 chars
}
in["note"] = string(n)
}
if task == "fix" && check.AuditHandoff != "" {
in["audit_handoff"] = check.AuditHandoff // optional; already a JSON string
}
return in
}
input := makeInput("audit", "orders-check.json",
"Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?")
var est struct {
HoldCredits any `json:"hold_credits"`
MinCredits any `json:"min_credits"`
SponsorEnabled bool `json:"sponsor_enabled"`
Warnings []any `json:"warnings"`
}
if err := api("POST", "/estimate", input, nil, &est); err != nil {
panic(err)
}
fmt.Printf("hold %v min %v sponsored %v warnings %v\n", est.HoldCredits, est.MinCredits, est.SponsorEnabled, est.Warnings)
// ---- add to the Routelarch class, next to api(): the body the page sends
static Map<String, Object> makeInput(String task, String checkPath, String note) throws Exception {
JsonNode check = JSON.readTree(Files.readString(Path.of(checkPath))); // build-check.js (or handoff.js) output
Map<String, Object> in = new LinkedHashMap<>();
in.put("task", task);
in.put("code", check.path("code").asText()); // the paste as the page sends it
in.put("facts", JSON.writeValueAsString(check.path("facts"))); // a STRING, compact
String n = note == null ? "" : note.strip();
if (!n.isEmpty()) in.put("note", n.length() > 2000 ? n.substring(0, 2000) : n); // optional; max 2000 chars
if (task.equals("fix") && check.hasNonNull("audit_handoff")) in.put("audit_handoff", check.get("audit_handoff").asText());
return in;
}
// ---- inside main
Map<String, Object> input = makeInput("audit", "orders-check.json",
"Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?");
JsonNode est = api("POST", "/estimate", input, null);
System.out.println("hold " + est.path("hold_credits") + " min " + est.path("min_credits")
+ " sponsored " + est.path("sponsor_enabled").asBoolean(false) + " warnings " + est.path("warnings"));
# The body the page sends: task, code, facts as a JSON STRING, an optional note,
# and (fix lane only) the audit_handoff string -- every value a string.
def make_input(task, check_path, note = nil)
check = JSON.parse(File.read(check_path, encoding: "UTF-8")) # build-check.js (or handoff.js) output
input = {
task: task,
code: check["code"], # the paste as the page sends it
facts: JSON.generate(check["facts"]) # a STRING, compact
}
n = note.to_s.strip
input[:note] = n[0, 2000] unless n.empty? # optional; max 2000 chars
input[:audit_handoff] = check["audit_handoff"] if task == "fix" && !check["audit_handoff"].to_s.empty?
input
end
input = make_input("audit", "orders-check.json",
"Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?")
est = api("POST", "/estimate", input)
puts "hold #{est["hold_credits"]} min #{est["min_credits"]} " \
"sponsored #{est["sponsor_enabled"]} warnings #{(est["warnings"] || []).inspect}"
// The body the page sends: task, code, facts as a JSON STRING, an optional note,
// and (fix lane only) the audit_handoff string -- every value a string.
function make_input(string $task, string $checkPath, string $note = ""): array {
// decode to objects (not arrays) so an empty {} stays {} when re-encoded
$check = json_decode(file_get_contents($checkPath)); // build-check.js (or handoff.js) output
$input = [
"task" => $task,
"code" => $check->code, // the paste as the page sends it
"facts" => json_encode($check->facts, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), // a STRING
];
$n = trim($note);
if ($n !== "") $input["note"] = mb_substr($n, 0, 2000); // optional; max 2000 chars
if ($task === "fix" && !empty($check->audit_handoff)) $input["audit_handoff"] = $check->audit_handoff;
return $input;
}
$input = make_input("audit", "orders-check.json",
"Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?");
$est = api("POST", "/estimate", $input);
echo "hold ", $est->hold_credits ?? "-", " min ", $est->min_credits ?? "-",
" sponsored ", json_encode($est->sponsor_enabled ?? false),
" warnings ", json_encode($est->warnings ?? []), "\n";
// The body the page sends: task, code, facts as a JSON STRING, an optional note,
// and (fix lane only) the audit_handoff string -- every value a string.
static Dictionary<string, string> MakeInput(string task, string checkPath, string? note = null)
{
var compact = new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
var check = JsonNode.Parse(File.ReadAllText(checkPath))!; // build-check.js (or handoff.js) output
var input = new Dictionary<string, string>
{
["task"] = task,
["code"] = check["code"]!.GetValue<string>(), // the paste as the page sends it
["facts"] = check["facts"]!.ToJsonString(compact), // a STRING, compact
};
var n = (note ?? "").Trim();
if (n.Length > 0) input["note"] = n.Length > 2000 ? n[..2000] : n; // optional; max 2000 chars
var handoff = check["audit_handoff"]?.GetValue<string>();
if (task == "fix" && !string.IsNullOrEmpty(handoff)) input["audit_handoff"] = handoff;
return input;
}
var input = MakeInput("audit", "orders-check.json",
"Since we moved the handlers to async/await the API sometimes hangs, and twice the whole process died. What else should we fix before the launch on Monday?");
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"hold {est?["hold_credits"]} min {est?["min_credits"]} " +
$"sponsored {est?["sponsor_enabled"]} warnings {est?["warnings"]?.ToJsonString() ?? "[]"}");
Step 5 — POST /run, then poll the job (audit)
Send the same input to /run with an Idempotency-Key header, using a
personal token. Its data carries a job_id. Poll
GET /jobs/{job_id} the way sdk.js waitForJob does: every
second until status is succeeded or failed, giving up on
the client side after 180 seconds. On the finished job, read:
output.output— the model’s reply as a string: the JSON object from the output contract of the lane you asked for. Parse it yourself (from the first{to the last}). The samples print the verdict and headline, each area’s status, each finding’sid,file,line,severityand number of edits, then each flag answer. Keep the text:recheck.jsreads it.truncated—truewhen the reply was cut short because the balance ran out. The page then closes the cut-off JSON, shows what arrived as a partial result (“N of 9 sections recovered” for an audit, of 8 for a fix run), and says it was cut short by the available balance.charged_credits— the real cost of the run, as opposed to the estimate’shold_creditsreservation. The page re-reads/meafterwards for the new balance.error— on afailedjob.
Idempotency key. Resending a request with the same key must not start a second
paid run, so derive the key from the input, not from the clock — and include the lane, so an
audit and a fix run of the same paste never share a key. The page uses
routelarch:<lane>:<inputHash>:a<attempt> (app.js
run), where <lane> is audit or fix,
<inputHash> is a 32-bit djb2 hash of JSON.stringify(input) in hex, a
dash, and the string’s length in hex (app.js inputHash, over the
input without retry_note), and <attempt> is 1
for a normal run and 2 for the automatic reformat retry (which also adds
retry_note). For the two worked examples that is routelarch:audit:e299aa89-260e:a1 (orders audit) and routelarch:fix:601c1dd2-1fbb:a1
(tasks fix); the fix run of the orders audit handed on (above) is routelarch:fix:5b65685f-2c1f:a1. The format is the page’s
convention, not a requirement: any stable, unique string does. The samples below use the lane and a
SHA-256 prefix.
The page itself uses /run-stream (step 6), not /run; both take the
same body and header.
KEY="routelarch:audit:$(printf '%s' "$BODY" | shasum -a 256 | cut -c1-16):a1"
JOB=$(api POST /run -H "Idempotency-Key: $KEY" -d "$BODY" | jq -r '.data.job_id')
for i in $(seq 180); do # 1 s interval, 180 s client-side limit
J=$(api GET "/jobs/$JOB")
S=$(printf '%s' "$J" | jq -r '.data.status')
if [ "$S" = "succeeded" ] || [ "$S" = "failed" ]; then break; fi
sleep 1
done
printf '%s' "$J" | jq '.data | {status, truncated, charged_credits, error}'
printf '%s' "$J" | jq -r '.data.output.output' \
| jq '{verdict, headline, areas: [(.areas // [])[] | {area, status}],
findings: [(.findings // [])[] | {id, file, line, severity, edits: ((.edits // []) | length)}],
flag_answers: [(.flag_answers // [])[] | {flag, status}]}'
body = json.dumps(inp)
key = "routelarch:audit:" + hashlib.sha256(body.encode()).hexdigest()[:16] + ":a1"
job_id = api("POST", "/run", inp, {"Idempotency-Key": key})["job_id"]
deadline = time.time() + 180 # 1 s interval, 180 s client-side limit
while True:
job = api("GET", "/jobs/" + urllib.parse.quote(job_id, safe=""))
if job["status"] in ("succeeded", "failed"):
break
if time.time() > deadline:
raise TimeoutError("job timed out")
time.sleep(1)
if job["status"] == "failed":
raise SystemExit(f"run failed: {job.get('error')}")
print("truncated:", job.get("truncated") is True, "charged:", job.get("charged_credits"))
text = (job.get("output") or {}).get("output") or ""
reply = json.loads(text[text.index("{"): text.rindex("}") + 1])
print(reply["verdict"], "-", reply["headline"])
for s in reply.get("areas", []):
print("area", s["area"], s["status"])
for f in reply.get("findings", []):
print(f["id"], f["file"], f.get("line"), f["severity"], len(f.get("edits") or []), "edit(s)")
for a in reply.get("flag_answers", []):
print("answer", a["flag"], a["status"])
const key = "routelarch:audit:" +
createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16) + ":a1";
const { job_id } = await api("POST", "/run", input, { "Idempotency-Key": key });
let job;
const deadline = Date.now() + 180000; // 1 s interval, 180 s client-side limit
for (;;) {
job = await api("GET", "/jobs/" + encodeURIComponent(job_id));
if (job.status === "succeeded" || job.status === "failed") break;
if (Date.now() > deadline) throw new Error("job timed out");
await new Promise((r) => setTimeout(r, 1000));
}
if (job.status === "failed") throw new Error("run failed: " + JSON.stringify(job.error));
console.log("truncated:", job.truncated === true, "charged:", job.charged_credits);
const text = (job.output && job.output.output) || "";
const reply = JSON.parse(text.slice(text.indexOf("{"), text.lastIndexOf("}") + 1));
console.log(reply.verdict, "-", reply.headline);
for (const s of reply.areas || []) console.log("area", s.area, s.status);
for (const f of reply.findings || []) console.log(f.id, f.file, f.line, f.severity, (f.edits || []).length, "edit(s)");
for (const a of reply.flag_answers || []) console.log("answer", a.flag, a.status);
inputJSON, _ := json.Marshal(input)
sum := sha256.Sum256(inputJSON)
key := "routelarch:audit:" + hex.EncodeToString(sum[:8]) + ":a1"
var started struct {
JobID string `json:"job_id"`
}
if err := api("POST", "/run", input, map[string]string{"Idempotency-Key": key}, &started); err != nil {
panic(err)
}
type jobT struct {
Status string `json:"status"`
Output *struct {
Output string `json:"output"`
} `json:"output"`
Truncated bool `json:"truncated"`
ChargedCredits any `json:"charged_credits"`
Error any `json:"error"`
}
var job jobT
deadline := time.Now().Add(180 * time.Second) // 1 s interval, 180 s client-side limit
for {
job = jobT{}
if err := api("GET", "/jobs/"+url.PathEscape(started.JobID), nil, nil, &job); err != nil {
panic(err)
}
if job.Status == "succeeded" || job.Status == "failed" {
break
}
if time.Now().After(deadline) {
panic("job timed out")
}
time.Sleep(time.Second)
}
if job.Status == "failed" {
panic(fmt.Sprintf("run failed: %v", job.Error))
}
fmt.Println("truncated:", job.Truncated, "charged:", job.ChargedCredits)
text := ""
if job.Output != nil {
text = job.Output.Output
}
var reply map[string]any
if a, b := strings.Index(text, "{"), strings.LastIndex(text, "}"); a >= 0 && b > a {
if err := json.Unmarshal([]byte(text[a:b+1]), &reply); err != nil {
panic(err)
}
}
fmt.Println(reply["verdict"], "-", reply["headline"])
areas, _ := reply["areas"].([]any)
for _, e := range areas {
s, _ := e.(map[string]any)
fmt.Println("area", s["area"], s["status"])
}
findings, _ := reply["findings"].([]any)
for _, e := range findings {
f, _ := e.(map[string]any)
edits, _ := f["edits"].([]any)
fmt.Println(f["id"], f["file"], f["line"], f["severity"], len(edits), "edit(s)")
}
answers, _ := reply["flag_answers"].([]any)
for _, e := range answers {
a, _ := e.(map[string]any)
fmt.Println("answer", a["flag"], a["status"])
}
byte[] digest = MessageDigest.getInstance("SHA-256").digest(JSON.writeValueAsBytes(input));
String key = "routelarch:audit:" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
String jobId = api("POST", "/run", input, Map.of("Idempotency-Key", key)).path("job_id").asText();
JsonNode job;
long deadline = System.currentTimeMillis() + 180_000; // 1 s interval, 180 s client-side limit
while (true) {
job = api("GET", "/jobs/" + URLEncoder.encode(jobId, StandardCharsets.UTF_8), null, null);
String s = job.path("status").asText();
if (s.equals("succeeded") || s.equals("failed")) break;
if (System.currentTimeMillis() > deadline) throw new RuntimeException("job timed out");
Thread.sleep(1000);
}
if (job.path("status").asText().equals("failed")) throw new RuntimeException("run failed: " + job.path("error"));
System.out.println("truncated: " + job.path("truncated").asBoolean(false) + ", charged: " + job.path("charged_credits"));
String text = job.path("output").path("output").asText("");
JsonNode reply = JSON.readTree(text.substring(text.indexOf('{'), text.lastIndexOf('}') + 1));
System.out.println(reply.path("verdict").asText() + " - " + reply.path("headline").asText());
for (JsonNode s : reply.path("areas")) {
System.out.println("area " + s.path("area").asText() + " " + s.path("status").asText());
}
for (JsonNode f : reply.path("findings")) {
System.out.println(f.path("id").asText() + " " + f.path("file").asText() + " " + f.path("line") + " "
+ f.path("severity").asText() + " " + f.path("edits").size() + " edit(s)");
}
for (JsonNode a : reply.path("flag_answers")) {
System.out.println("answer " + a.path("flag").asText() + " " + a.path("status").asText());
}
key = "routelarch:audit:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job_id = api("POST", "/run", input, { "Idempotency-Key" => key })["job_id"]
deadline = Time.now + 180 # 1 s interval, 180 s client-side limit
job = nil
loop do
job = api("GET", "/jobs/#{URI.encode_www_form_component(job_id)}")
break if %w[succeeded failed].include?(job["status"])
raise "job timed out" if Time.now > deadline
sleep 1
end
raise "run failed: #{job["error"].inspect}" if job["status"] == "failed"
puts "truncated: #{job["truncated"] == true}, charged: #{job["charged_credits"]}"
text = (job["output"] || {})["output"].to_s
reply = JSON.parse(text[text.index("{")..text.rindex("}")])
puts "#{reply["verdict"]} - #{reply["headline"]}"
(reply["areas"] || []).each { |s| puts "area #{s["area"]} #{s["status"]}" }
(reply["findings"] || []).each { |f| puts "#{f["id"]} #{f["file"]} #{f["line"].inspect} #{f["severity"]} #{(f["edits"] || []).size} edit(s)" }
(reply["flag_answers"] || []).each { |a| puts "answer #{a["flag"]} #{a["status"]}" }
$key = "routelarch:audit:" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$jobId = api("POST", "/run", $input, ["Idempotency-Key" => $key])->job_id;
$deadline = time() + 180; // 1 s interval, 180 s client-side limit
while (true) {
$job = api("GET", "/jobs/" . rawurlencode($jobId));
if (in_array($job->status, ["succeeded", "failed"], true)) break;
if (time() > $deadline) throw new RuntimeException("job timed out");
sleep(1);
}
if ($job->status === "failed") throw new RuntimeException("run failed: " . json_encode($job->error ?? null));
echo "truncated: ", json_encode(($job->truncated ?? false) === true), ", charged: ", $job->charged_credits ?? "-", "\n";
$text = $job->output->output ?? "";
$reply = json_decode(substr($text, strpos($text, "{"), strrpos($text, "}") - strpos($text, "{") + 1));
echo $reply->verdict, " - ", $reply->headline, "\n";
foreach ($reply->areas ?? [] as $s) echo "area ", $s->area, " ", $s->status, "\n";
foreach ($reply->findings ?? [] as $f) echo $f->id, " ", $f->file, " ", json_encode($f->line ?? null), " ", $f->severity, " ", count($f->edits ?? []), " edit(s)\n";
foreach ($reply->flag_answers ?? [] as $a) echo "answer ", $a->flag, " ", $a->status, "\n";
var digest = System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(input)));
var key = "routelarch:audit:" + Convert.ToHexString(digest, 0, 8).ToLowerInvariant() + ":a1";
var started = await Api.Call("POST", "/run", input, new Dictionary<string, string> { ["Idempotency-Key"] = key });
var jobId = started!["job_id"]!.GetValue<string>();
JsonNode job;
var deadline = DateTime.UtcNow.AddSeconds(180); // 1 s interval, 180 s client-side limit
while (true)
{
job = (await Api.Call("GET", "/jobs/" + Uri.EscapeDataString(jobId)))!;
var s = job["status"]?.GetValue<string>();
if (s == "succeeded" || s == "failed") break;
if (DateTime.UtcNow > deadline) throw new TimeoutException("job timed out");
await Task.Delay(1000);
}
if (job["status"]?.GetValue<string>() == "failed") throw new Exception($"run failed: {job["error"]?.ToJsonString()}");
var truncated = job["truncated"] is JsonValue t && t.TryGetValue(out bool tb) && tb;
Console.WriteLine($"truncated: {truncated}, charged: {job["charged_credits"]}");
var text = job["output"]?["output"]?.GetValue<string>() ?? "";
var reply = JsonNode.Parse(text[text.IndexOf('{')..(text.LastIndexOf('}') + 1)])!;
Console.WriteLine($"{reply["verdict"]} - {reply["headline"]}");
foreach (var s in reply["areas"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"area {s?["area"]} {s?["status"]}");
foreach (var f in reply["findings"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"{f?["id"]} {f?["file"]} {f?["line"]?.ToJsonString() ?? "null"} {f?["severity"]} {f?["edits"]?.AsArray().Count ?? 0} edit(s)");
foreach (var a in reply["flag_answers"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"answer {a?["flag"]} {a?["status"]}");
Step 6 — POST /run-stream (Server-Sent Events, fix)
The alternative to step 5, and what the page uses: same kind of body, same
Idempotency-Key header, one request. Run one or the other for a given input, not
both. To show the second lane, the samples build the fix input of the
tasks example (with its own key) and stream it; to stream the audit input instead, pass the
step 4 input and key. The response is text/event-stream. sdk.js splits
it into frames on a blank line, takes the event name from an event: line (default
message) and concatenates the data: lines, skips frames with no data or
with data that is not JSON, and handles these events:
| Event | Data | What the SDK does |
|---|---|---|
delta | {text} | Passes text to onDelta. |
job | job info | Passes it to onJob (the page uses it to advance its progress display). |
done | the final payload: {job_id, status, charged_credits, output} | Keeps it as the result. |
pending | a payload | Treated exactly like done. If it carries a job_id and no output, poll that job as in step 5. |
error | {message, code, job_id} | After the stream ends, raises an error with those fields. |
If the response is not text/event-stream, it is an ordinary JSON envelope:
the SDK returns its data (an idempotent replay returns this way) or raises the
error on a non-2xx status. Handle both shapes.
From the final payload the page reads output.output (falling back to the
concatenated delta text), truncated and charged_credits.
Browsers receive progress ticks rather than deltas from this endpoint, so the page relies on the
final payload; if the stream fails part-way, it tries to recover a partial reply from whatever
delta text arrived. The samples print the verdict and headline, each returned file’s path and
size, each change (function, flags and change), each
verification step, each open item and each question. To put the files into the paste and re-check
them the way the page’s “Put every file into the paste” does, keep the reply text
and run recheck.js with the lane fix. To fix code you audited, read handoff.js’s output instead of
tasks-check.json: makeInput then carries its audit_handoff.
# the fix worked example: tasks-check.json from build-check.js
# (for audited code, read handoff.js's output instead; the + (...) adds its audit_handoff)
FBODY=$(jq -c --arg note "Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages." \
'{task: "fix", code, facts: (.facts | tojson), note: $note} + (if .audit_handoff then {audit_handoff} else {} end)' \
tasks-check.json)
FKEY="routelarch:fix:$(printf '%s' "$FBODY" | shasum -a 256 | cut -c1-16):a1"
# -N turns off buffering; the whole stream lands in $STREAM
STREAM=$(api POST /run-stream -N -H "Idempotency-Key: $FKEY" -d "$FBODY")
# frames look like:
# event: job
# data: {...}
#
# event: done
# data: {"job_id":"...","status":"...","charged_credits":...,"output":{"output":"{\"task\":\"fix\",...}"}}
# the final reply text, from the done (or pending) frame:
REPLY=$(printf '%s\n' "$STREAM" \
| awk '/^event:/{ev=$2} /^data:/ && (ev=="done" || ev=="pending"){sub(/^data: */,""); print}' \
| jq -r '.output.output')
# an idempotent replay answers with a plain JSON envelope instead: REPLY=$(printf '%s' "$STREAM" | jq -r '.data.output.output')
printf '%s' "$REPLY" | jq '{verdict, headline,
files: [(.files // [])[] | {path, characters: (.content | length)}],
changes: [(.changes // [])[] | {function, flags, change}], verify, open, questions}'
# the fix worked example: tasks-check.json from build-check.js
# (for audited code, pass handoff.js's output instead: make_input adds its audit_handoff)
fix_inp = make_input("fix", "tasks-check.json",
"Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages.")
fix_key = "routelarch:fix:" + hashlib.sha256(json.dumps(fix_inp).encode()).hexdigest()[:16] + ":a1"
h = {"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN, "Idempotency-Key": fix_key}
done, failure, streamed = None, None, ""
with requests.post(BASE + "/run-stream", headers=h, data=json.dumps(fix_inp), stream=True) as r:
if "text/event-stream" not in r.headers.get("content-type", ""):
try:
env = r.json()
except ValueError:
env = {}
if not r.ok:
err = env.get("error") or {}
raise ApiError(r.status_code, err.get("code"), err.get("message") or r.reason)
done = env.get("data")
else:
event, data = "message", ""
for line in r.iter_lines(decode_unicode=True):
if line == "": # a blank line ends a frame
if data:
try:
payload = json.loads(data)
except ValueError:
payload = None
if event == "delta" and payload is not None:
streamed += payload.get("text") or ""
elif event == "job":
print("job", payload)
elif event in ("done", "pending"):
done = payload
elif event == "error":
failure = payload
event, data = "message", ""
elif line.startswith("event:"):
event = line[6:].strip()
elif line.startswith("data:"):
data += line[5:].strip()
if failure:
raise SystemExit(f"{failure.get('code')}: {failure.get('message')} (job {failure.get('job_id')})")
done = done or {}
full = (done.get("output") or {}).get("output") or streamed
print("truncated:", done.get("truncated") is True, "charged:", done.get("charged_credits"))
result = json.loads(full[full.index("{"): full.rindex("}") + 1])
print(result["verdict"], "-", result["headline"])
for f in result.get("files", []):
print("file", f["path"], len(f.get("content") or ""), "characters")
for c in result.get("changes", []):
print("change", c.get("function"), ",".join(c.get("flags") or []) or "-", "-", c.get("change"))
for step in result.get("verify", []):
print("verify", step)
for o in result.get("open", []):
print("open", o["item"], "-", o.get("reason"))
for q in result.get("questions", []):
print("question", q)
// the fix worked example: tasks-check.json from build-check.js
// (for audited code, pass handoff.js's output instead: makeInput adds its audit_handoff)
const fixInput = makeInput("fix", "tasks-check.json",
"Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages.");
const fixKey = "routelarch:fix:" +
createHash("sha256").update(JSON.stringify(fixInput)).digest("hex").slice(0, 16) + ":a1";
const res = await fetch(BASE + "/run-stream", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + TOKEN, "Idempotency-Key": fixKey },
body: JSON.stringify(fixInput),
});
let done = null, failure = null, streamed = "";
if (!(res.headers.get("content-type") || "").includes("text/event-stream")) {
const json = await res.json().catch(() => ({}));
if (!res.ok) throw new Error((json.error && json.error.message) || res.statusText);
done = json.data;
} else {
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
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;
let data;
try { data = JSON.parse(dataStr); } catch { continue; }
if (event === "delta") streamed += data.text || "";
else if (event === "job") console.log("job", data);
else if (event === "done" || event === "pending") done = data;
else if (event === "error") failure = data;
}
}
}
if (failure) throw new Error(`${failure.code}: ${failure.message} (job ${failure.job_id})`);
const full = (done && done.output && done.output.output) || streamed;
console.log("truncated:", !!(done && done.truncated), "charged:", done && done.charged_credits);
const result = JSON.parse(full.slice(full.indexOf("{"), full.lastIndexOf("}") + 1));
console.log(result.verdict, "-", result.headline);
for (const f of result.files || []) console.log("file", f.path, (f.content || "").length, "characters");
for (const c of result.changes || []) console.log("change", c.function, (c.flags || []).join(",") || "-", "-", c.change);
for (const step of result.verify || []) console.log("verify", step);
for (const o of result.open || []) console.log("open", o.item, "-", o.reason);
for (const q of result.questions || []) console.log("question", q);
// the fix worked example: tasks-check.json from build-check.js
// (for audited code, pass handoff.js's output instead: makeInput adds its audit_handoff)
fixInput := makeInput("fix", "tasks-check.json",
"Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages.")
fixJSON, _ := json.Marshal(fixInput)
fixSum := sha256.Sum256(fixJSON)
fixKey := "routelarch:fix:" + hex.EncodeToString(fixSum[:8]) + ":a1"
sreq, err := newRequest("POST", "/run-stream", fixInput, map[string]string{"Idempotency-Key": fixKey})
if err != nil {
panic(err)
}
sres, err := http.DefaultClient.Do(sreq)
if err != nil {
panic(err)
}
defer sres.Body.Close()
var done, failure map[string]any
var streamed strings.Builder
if !strings.Contains(sres.Header.Get("Content-Type"), "text/event-stream") {
var env envelope
_ = json.NewDecoder(sres.Body).Decode(&env)
if sres.StatusCode < 200 || sres.StatusCode > 299 {
panic(errorFrom(sres, env))
}
_ = json.Unmarshal(env.Data, &done)
} else {
sc := bufio.NewScanner(sres.Body)
sc.Buffer(make([]byte, 64*1024), 16*1024*1024) // the done frame can be long
event, data := "message", ""
for sc.Scan() {
line := sc.Text()
switch {
case line == "": // a blank line ends a frame
if data != "" {
var p map[string]any
if json.Unmarshal([]byte(data), &p) == nil {
switch event {
case "delta":
if t, ok := p["text"].(string); ok {
streamed.WriteString(t)
}
case "job":
fmt.Println("job", p)
case "done", "pending":
done = p
case "error":
failure = p
}
}
}
event, data = "message", ""
case strings.HasPrefix(line, "event:"):
event = strings.TrimSpace(line[6:])
case strings.HasPrefix(line, "data:"):
data += strings.TrimSpace(line[5:])
}
}
}
if failure != nil {
panic(fmt.Sprintf("%v: %v (job %v)", failure["code"], failure["message"], failure["job_id"]))
}
full := streamed.String()
if out, ok := done["output"].(map[string]any); ok {
if s, ok := out["output"].(string); ok && s != "" {
full = s
}
}
fmt.Println("truncated:", done["truncated"] == true, "charged:", done["charged_credits"])
var result map[string]any
if a, b := strings.Index(full, "{"), strings.LastIndex(full, "}"); a >= 0 && b > a {
if err := json.Unmarshal([]byte(full[a:b+1]), &result); err != nil {
panic(err)
}
}
fmt.Println(result["verdict"], "-", result["headline"])
files, _ := result["files"].([]any)
for _, e := range files {
f, _ := e.(map[string]any)
c, _ := f["content"].(string)
fmt.Println("file", f["path"], len([]rune(c)), "characters")
}
changes, _ := result["changes"].([]any)
for _, e := range changes {
c, _ := e.(map[string]any)
var flags []string
if fs, ok := c["flags"].([]any); ok {
for _, fl := range fs {
flags = append(flags, fmt.Sprint(fl))
}
}
fl := strings.Join(flags, ",")
if fl == "" {
fl = "-"
}
fmt.Println("change", c["function"], fl, "-", c["change"])
}
verify, _ := result["verify"].([]any)
for _, st := range verify {
fmt.Println("verify", st)
}
open, _ := result["open"].([]any)
for _, e := range open {
o, _ := e.(map[string]any)
fmt.Println("open", o["item"], "-", o["reason"])
}
questions, _ := result["questions"].([]any)
for _, q := range questions {
fmt.Println("question", q)
}
// the fix worked example: tasks-check.json from build-check.js
// (for audited code, pass handoff.js's output instead: makeInput adds its audit_handoff)
Map<String, Object> fixInput = makeInput("fix", "tasks-check.json",
"Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages.");
byte[] fixDigest = MessageDigest.getInstance("SHA-256").digest(JSON.writeValueAsBytes(fixInput));
String fixKey = "routelarch:fix:" + HexFormat.of().formatHex(fixDigest, 0, 8) + ":a1";
HttpRequest sreq = request("POST", "/run-stream", fixInput, Map.of("Idempotency-Key", fixKey)).build();
HttpResponse<Stream<String>> sres = HTTP.send(sreq, HttpResponse.BodyHandlers.ofLines());
JsonNode done = null, failure = null;
StringBuilder streamed = new StringBuilder();
if (!sres.headers().firstValue("content-type").orElse("").contains("text/event-stream")) {
JsonNode env = JSON.readTree(sres.body().collect(Collectors.joining("\n")));
if (env == null) env = JSON.createObjectNode();
if (sres.statusCode() / 100 != 2) throw errorFrom(sres.statusCode(), env);
done = env.path("data");
} else {
String event = "message";
StringBuilder data = new StringBuilder();
Iterator<String> it = sres.body().iterator();
while (it.hasNext()) {
String line = it.next();
if (line.isEmpty()) { // a blank line ends a frame
if (data.length() > 0) {
JsonNode p = null;
try { p = JSON.readTree(data.toString()); } catch (Exception ignored) { }
if (p != null) {
switch (event) {
case "delta": streamed.append(p.path("text").asText("")); break;
case "job": System.out.println("job " + p); break;
case "done": case "pending": done = p; break;
case "error": failure = p; break;
default: break;
}
}
}
event = "message";
data.setLength(0);
} else if (line.startsWith("event:")) event = line.substring(6).trim();
else if (line.startsWith("data:")) data.append(line.substring(5).trim());
}
}
if (failure != null) {
throw new RuntimeException(failure.path("code").asText() + ": " + failure.path("message").asText()
+ " (job " + failure.path("job_id").asText() + ")");
}
String full = done == null ? "" : done.path("output").path("output").asText("");
if (full.isEmpty()) full = streamed.toString();
System.out.println("truncated: " + (done != null && done.path("truncated").asBoolean(false))
+ ", charged: " + (done == null ? "-" : done.path("charged_credits").toString()));
JsonNode result = JSON.readTree(full.substring(full.indexOf('{'), full.lastIndexOf('}') + 1));
System.out.println(result.path("verdict").asText() + " - " + result.path("headline").asText());
for (JsonNode f : result.path("files")) {
System.out.println("file " + f.path("path").asText() + " " + f.path("content").asText("").codePointCount(0, f.path("content").asText("").length()) + " characters");
}
for (JsonNode c : result.path("changes")) {
List<String> flags = new ArrayList<>();
c.path("flags").forEach(fl -> flags.add(fl.asText()));
System.out.println("change " + c.path("function").asText() + " " + (flags.isEmpty() ? "-" : String.join(",", flags))
+ " - " + c.path("change").asText());
}
for (JsonNode step : result.path("verify")) System.out.println("verify " + step.asText());
for (JsonNode o : result.path("open")) System.out.println("open " + o.path("item").asText() + " - " + o.path("reason").asText());
for (JsonNode q : result.path("questions")) System.out.println("question " + q.asText());
# the fix worked example: tasks-check.json from build-check.js
# (for audited code, pass handoff.js's output instead: make_input adds its audit_handoff)
fix_input = make_input("fix", "tasks-check.json",
"Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages.")
fix_key = "routelarch:fix:#{Digest::SHA256.hexdigest(JSON.generate(fix_input))[0, 16]}:a1"
uri, req = build_request("POST", "/run-stream", fix_input, { "Idempotency-Key" => fix_key })
done = failure = nil
streamed = +""
Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 300) do |http|
http.request(req) do |res|
unless res["content-type"].to_s.include?("text/event-stream")
envelope = (JSON.parse(res.body) rescue {})
unless res.is_a?(Net::HTTPSuccess)
raise ApiError.new(res.code.to_i, envelope.dig("error", "code"), envelope.dig("error", "message") || res.message)
end
done = envelope["data"]
next
end
buf = +""
res.read_body do |chunk|
buf << chunk
while (i = buf.index("\n\n"))
frame = buf.slice!(0, i + 2)
event = "message"
data = +""
frame.split("\n").each do |line|
if line.start_with?("event:") then event = line[6..].strip
elsif line.start_with?("data:") then data << line[5..].strip
end
end
next if data.empty?
payload = (JSON.parse(data) rescue nil)
next if payload.nil?
case event
when "delta" then streamed << payload["text"].to_s
when "job" then puts "job #{payload.inspect}"
when "done", "pending" then done = payload
when "error" then failure = payload
end
end
end
end
end
raise "#{failure["code"]}: #{failure["message"]} (job #{failure["job_id"]})" if failure
done ||= {}
full = (done["output"] || {})["output"].to_s
full = streamed if full.empty?
puts "truncated: #{done["truncated"] == true}, charged: #{done["charged_credits"]}"
result = JSON.parse(full[full.index("{")..full.rindex("}")])
puts "#{result["verdict"]} - #{result["headline"]}"
(result["files"] || []).each { |f| puts "file #{f["path"]} #{f["content"].to_s.length} characters" }
(result["changes"] || []).each do |c|
flags = (c["flags"] || []).join(",")
puts "change #{c["function"]} #{flags.empty? ? "-" : flags} - #{c["change"]}"
end
(result["verify"] || []).each { |step| puts "verify #{step}" }
(result["open"] || []).each { |o| puts "open #{o["item"]} - #{o["reason"]}" }
(result["questions"] || []).each { |q| puts "question #{q}" }
// the fix worked example: tasks-check.json from build-check.js
// (for audited code, pass handoff.js's output instead: make_input adds its audit_handoff)
$fixInput = make_input("fix", "tasks-check.json",
"Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages.");
$fixKey = "routelarch:fix:" . substr(hash("sha256", json_encode($fixInput)), 0, 16) . ":a1";
$done = null; $failure = null; $streamed = ""; $buf = ""; $ctype = "";
$handle = function (string $event, string $data) use (&$done, &$failure, &$streamed) {
$p = json_decode($data);
if ($p === null) return; // not JSON: skipped, as sdk.js does
switch ($event) {
case "delta": $streamed .= $p->text ?? ""; break;
case "job": echo "job ", json_encode($p), "\n"; break;
case "done": case "pending": $done = $p; break;
case "error": $failure = $p; break;
}
};
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => headers_for(["Idempotency-Key" => $fixKey]),
CURLOPT_POSTFIELDS => json_encode($fixInput, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$ctype) {
if (stripos($line, "content-type:") === 0) $ctype = strtolower(trim(substr($line, 13)));
return strlen($line);
},
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) use (&$buf, &$ctype, $handle) {
$buf .= $chunk; // collects the response in memory
if (strpos($ctype, "text/event-stream") === false) return strlen($chunk); // plain JSON: parse at the end
while (($i = strpos($buf, "\n\n")) !== false) {
$frame = substr($buf, 0, $i);
$buf = substr($buf, $i + 2);
$event = "message"; $data = "";
foreach (explode("\n", $frame) as $line) {
if (strpos($line, "event:") === 0) $event = trim(substr($line, 6));
elseif (strpos($line, "data:") === 0) $data .= trim(substr($line, 5));
}
if ($data !== "") $handle($event, $data);
}
return strlen($chunk);
},
]);
if (curl_exec($ch) === false) throw new RuntimeException(curl_error($ch));
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if (strpos($ctype, "text/event-stream") === false) {
$env = json_decode($buf) ?? new stdClass();
if ($status < 200 || $status > 299) {
throw new ApiError($status, $env->error->code ?? null, $env->error->message ?? "HTTP $status");
}
$done = $env->data ?? null;
}
if ($failure) throw new RuntimeException(($failure->code ?? "") . ": " . ($failure->message ?? "") . " (job " . ($failure->job_id ?? "") . ")");
$full = $done->output->output ?? "";
if ($full === "") $full = $streamed;
echo "truncated: ", json_encode(($done->truncated ?? false) === true), ", charged: ", $done->charged_credits ?? "-", "\n";
$result = json_decode(substr($full, strpos($full, "{"), strrpos($full, "}") - strpos($full, "{") + 1));
echo $result->verdict, " - ", $result->headline, "\n";
foreach ($result->files ?? [] as $f) echo "file ", $f->path, " ", mb_strlen($f->content ?? ""), " characters\n";
foreach ($result->changes ?? [] as $c) {
$flags = implode(",", $c->flags ?? []);
echo "change ", $c->function ?? "", " ", $flags === "" ? "-" : $flags, " - ", $c->change ?? "", "\n";
}
foreach ($result->verify ?? [] as $step) echo "verify ", $step, "\n";
foreach ($result->open ?? [] as $o) echo "open ", $o->item, " - ", $o->reason ?? "", "\n";
foreach ($result->questions ?? [] as $q) echo "question ", $q, "\n";
// the fix worked example: tasks-check.json from build-check.js
// (for audited code, pass handoff.js's output instead: MakeInput adds its audit_handoff)
var fixInput = MakeInput("fix", "tasks-check.json",
"Pen test found that any made-up token is accepted. Fix every high and medium problem the check found; keep every route URL, and do not add packages.");
var fixDigest = System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(fixInput)));
var fixKey = "routelarch:fix:" + Convert.ToHexString(fixDigest, 0, 8).ToLowerInvariant() + ":a1";
using var sreq = Api.Request("POST", "/run-stream", fixInput, new Dictionary<string, string> { ["Idempotency-Key"] = fixKey });
using var sres = await Api.Http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
JsonNode? done = null, failure = null;
var streamed = new StringBuilder();
if (sres.Content.Headers.ContentType?.MediaType != "text/event-stream")
{
JsonNode? env = null;
try { env = JsonNode.Parse(await sres.Content.ReadAsStringAsync()); } catch (JsonException) { }
if (!sres.IsSuccessStatusCode) throw Api.ErrorFrom(sres, env);
done = env?["data"];
}
else
{
using var reader = new StreamReader(await sres.Content.ReadAsStreamAsync());
var ev = "message";
var data = new StringBuilder();
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.Length == 0) // a blank line ends a frame
{
if (data.Length > 0)
{
JsonNode? p = null;
try { p = JsonNode.Parse(data.ToString()); } catch (JsonException) { }
if (p != null)
{
switch (ev)
{
case "delta": streamed.Append(p["text"]?.GetValue<string>() ?? ""); break;
case "job": Console.WriteLine($"job {p.ToJsonString()}"); break;
case "done": case "pending": done = p; break;
case "error": failure = p; break;
}
}
}
ev = "message";
data.Clear();
}
else if (line.StartsWith("event:")) ev = line[6..].Trim();
else if (line.StartsWith("data:")) data.Append(line[5..].Trim());
}
}
if (failure != null) throw new Exception($"{failure["code"]}: {failure["message"]} (job {failure["job_id"]})");
var full = done?["output"]?["output"]?.GetValue<string>();
if (string.IsNullOrEmpty(full)) full = streamed.ToString();
var cut = done?["truncated"] is JsonValue tv && tv.TryGetValue(out bool cutShort) && cutShort;
Console.WriteLine($"truncated: {cut}, charged: {done?["charged_credits"]}");
var result = JsonNode.Parse(full[full.IndexOf('{')..(full.LastIndexOf('}') + 1)])!;
Console.WriteLine($"{result["verdict"]} - {result["headline"]}");
foreach (var f in result["files"]?.AsArray() ?? new JsonArray())
Console.WriteLine($"file {f?["path"]} {(f?["content"]?.GetValue<string>() ?? "").Length} characters");
foreach (var c in result["changes"]?.AsArray() ?? new JsonArray())
{
var flags = string.Join(",", (c?["flags"]?.AsArray() ?? new JsonArray()).Select(fl => fl?.GetValue<string>()));
Console.WriteLine($"change {c?["function"]} {(flags == "" ? "-" : flags)} - {c?["change"]}");
}
foreach (var step in result["verify"]?.AsArray() ?? new JsonArray()) Console.WriteLine($"verify {step}");
foreach (var o in result["open"]?.AsArray() ?? new JsonArray()) Console.WriteLine($"open {o?["item"]} - {o?["reason"]}");
foreach (var q in result["questions"]?.AsArray() ?? new JsonArray()) Console.WriteLine($"question {q}");
What the page does that you now do
- Reads the paste in the browser and builds
codeandfactsfrom it (expresskit.js), joining dropped files under// file:lines and cutting a large paste on whole files with a// [routelarch: not sent: ...]line; you send the same two strings. - Prices every input with
/estimateand refuses to run belowmin_credits, or on a paste with no JavaScript or TypeScript it can read. - Sends each attempt with an input- and lane-derived
Idempotency-Key, and retries once withretry_notewhen a reply does not parse. - Closes a cut-off JSON reply and shows what arrived when
truncatedis true or the stream ends early. - Hands an audit to the fix lane as
audit_handoff, with every edit applied to the paste first, and only while that edited paste is unchanged. - Checks the reply against the code (
recon.js), in the browser: files, lines, quoted evidence, every edit applied in order and the free check re-run after each finding, flag answers and verdict; the fixed files put back, re-checked and matched against the changes list and the open items; exports and routes that must survive; and repeated credentials.recheck.jsabove runs the same checks for you.
Or skip all of this and use Routelarch in the browser: the check, the flags, every route with
its full path, middleware chain and handler, the use() registrations in order, putting an edit or
the fixed files back into the paste, and the Markdown and CSV exports are free.