Run Test Lens from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page scans the Dart - every call matched to its closing bracket, its named arguments read at their own depth - - reads every test, testWidgets, group and setUp in order, and checks awaits and pumps, assertions, app context, isolation from real dependencies and flaky waits, then hands it all to the run as facts. The run writes the review or the rewrite; the page then holds it to its own facts and re-scans a rewritten test file with the same scanner (testreply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"test-lens"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, suite, code, facts) is missing. The page always sends all five of task, suite, code, carry, facts; carry is an empty string for review. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes, chosen by task, in the order the work runs - inspect, then produce:
review- each of the page's findings confirmed (with the issue, the fix and a short code fix) or cleared (with why), up to five issues a scan cannot see on real lines, and the fixes in the order to make them, each naming its ids.rewrite- the whole rewritten test file - every call awaited, a pump before every expect, an assertion in every test, fakes for real dependencies, no real waits - with what changed and what was kept, every test name unchanged.
A missing or unknown task is answered as review. Both lanes send the same five fields. The facts are the page's scan, one finding per line with its id, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the review's decisions into carry for the rewrite lane. The review sends at most 24,000 characters of code (the middle of a longer file is cut on whole lines and marked; every finding is still in the facts); the rewrite lane takes at most 16,000.
The input
| Field | Required | What |
|---|---|---|
task | yes | review or rewrite. If it is missing or neither, do review and set "task": "review". |
suite | yes | the test file's path, where it runs, and how many unit, widget and integration tests it holds. |
code | yes | the test file. A line starting "// [... N lines cut by the page" marks code the page scanned but did not send. |
carry | send it (empty for review) | rewrite task only: the review's decisions, one per line as id | confirmed or cleared or extra | where | fix or reason; may be empty. |
facts | yes | the page's scan: one finding per line as id | area | severity | line | where | evidence | source, and every test with its line, kind, name and counts of expects, actions and pumps. A line starting "must:" applies to your reply. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
From the source skill, @flutter/flutter-testing-apps - the only standard the run applies:
- A unit test checks one function, method or class; it mocks every external dependency and does no disk I/O, rendering or outside user action. Run it with the test or flutter_test package.
- A widget test checks one widget's look and interaction: pump it with WidgetTester inside the lifecycle context it needs (a MaterialApp with home: the widget), find it with Finder classes (find.text, find.byType, find.byKey) and check it with Matcher constants (findsOneWidget, findsNothing).
- An interaction is a Future: await tester.tap / enterText / drag, then await tester.pump() (or pumpAndSettle()) to trigger the frame, then expect. An integration test does the same against the whole app.
- An integration test imports integration_test and calls IntegrationTestWidgetsFlutterBinding.ensureInitialized() first in main().
- Views get faked or mocked view models and repositories; prefer a Fake implementation (FakeUserRepository) with well-defined inputs and outputs over a mocking library.
- Every test asserts: a test with no expect proves nothing.
House rules
- Use only the input. Never invent a class, a method, a key, a text or a value the code does not hold. When a fix needs a fake that the file does not define (FakeCartRepository), name it from the real class and say in a caveat that the team writes it; when an expect needs a value the code does not show, say so instead of guessing.
- The page's ids and lines are measured facts: never renumber an id.
- No web links (http or https) in prose.
- Write in English. Keep the JSON keys and the ids exactly as given.
- Never use backticks. Name calls plainly (await tester.pump(), findsOneWidget). Words from the rules above are free to use; what you never do is paste a whole sentence from this prompt.
- Never write a gendered pronoun for a user; write they, the user or the person.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
review
Confirm or clear each of the page's findings, add what a scan cannot see, and put the fixes in order.
summary- one sentence, at most 40 words: what these tests really prove today (often less than they claim), and the first fix.findings- one object per page finding you confirm, in id order:id(the page's id),widget(the test, e.g. "testWidgets - adding an item updates the badge"),issue(one sentence: what the test fails to prove, or how it misleads),fix(one sentence: the change),code(the corrected Dart for that piece only, at most 6 lines; "" when fix says it all).cleared- one object per page finding that is not a problem in this code, withidandwhy(one sentence tied to the code). Every page id appears exactly once, in findings or in cleared.extra- at most 5 gaps the scan cannot see, each tied to a line of the code:id(X1, X2, ...),area(async, assertions, setup, isolation or flaky),line(a number),widget,issue,fix. Examples: an expect that checks the wrong thing (findsWidgets where one is meant), a finder that matches more than the widget under test, a missing edge case (empty, error, loading), state shared between tests through a top-level variable, a layer the skill wants covered and the file leaves out.[]when there are none.plan- 2 to 10 short strings, the fixes in the order to make them (what makes the file not compile or not run first, then awaits and pumps, then assertions, then isolation and flakiness), each naming the ids it covers (e.g. "Await the tap and pump before the expect (C3, C4)"). Every confirmed id appears in a step.[]only when nothing is confirmed.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "review",
"summary": "one sentence",
"findings": [ { "id": "C1", "widget": "testWidgets - adding an item updates the badge", "issue": "one sentence", "fix": "one sentence", "code": "await tester.tap(find.byIcon(Icons.add));\nawait tester.pump();" } ],
"cleared": [ { "id": "C4", "why": "one sentence" } ],
"extra": [ { "id": "X1", "area": "assertions", "line": 10, "widget": "testWidgets - adding an item updates the badge", "issue": "one sentence", "fix": "one sentence" } ],
"plan": [ "Await the tap and pump before the expect (C3, C4)" ],
"caveats": [ ]
}
The page holds it to its own facts (check() in testreply.js; any warning makes the reply flagged): the reply answers the task it was sent; every page finding id appears exactly once across findings and cleared, and no id the page never found; a cleared id has a reason; a confirmed id has an issue and a fix; at most five extra issues, each on a line inside the paste; a fix order of 2 to 10 steps that names every confirmed id; a summary; no web link in the prose.
rewrite
Rewrite the test file with the fixes applied.
summary- one sentence, at most 40 words: what the rewrite changes and what it leaves for the team.code- the complete rewritten test file. Keep every import, group, setUp and test, with every test's name word for word; change only what the findings name, plus the imports a change needs. Never shorten the file with "..." or "unchanged" - write every line. If the code holds a line starting "// [... N lines cut by the page", keep that line exactly where it is. A tester call not awaited - await it. An async callback missing - (tester) async { ... }. An action with no pump before the expect - await tester.pump(); after it (pumpAndSettle() when an animation must finish). A test with no expect - add the expect its name promises (a bare find.text(...) line becomes expect(find.text(...), findsOneWidget)). A widget pumped with no app - MaterialApp(home: the widget). A real repository or service - a Fake of it (FakeCartRepository()), declared as used; do not write the fake's body when the file does not hold the real class - say so in a caveat. Real I/O in a unit test - the fake or mock the code under test accepts. A real wait - await tester.pump(the same Duration) in a widget test; in a unit test, remove it when the awaited call already completes, or say what the team must inject. pumpAndSettle over a spinner - pump() instead. No integration binding - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); first in main().changes- one object per page finding you fixed, in id order:id,change(one sentence: what changed, naming the test). Extra ids (X1, ...) from the carried review may appear too.kept- one object per page finding you did not change:id,why(one sentence - the review cleared it, or it needs a decision the code cannot make). Every page id appears exactly once, in changes or in kept; an id the carried review cleared belongs in kept.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "rewrite",
"summary": "one sentence",
"code": "the complete rewritten test file",
"changes": [ { "id": "C1", "change": "one sentence" } ],
"kept": [ { "id": "C4", "why": "one sentence" } ],
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; every page finding id appears exactly once across changes and kept; every widget class of the paste survives; the file is not shortened (at least 70% of its lines) and elides nothing; and the rewritten code is scanned again - a rule the changes claim fixed that still fires, or a rule the paste never had, is flagged. A test the rewrite drops or renames is flagged.
Worked examples
Three runs from the page's free samples (invented apps): the review of a widget test that passes without testing anything, the rewrite of the same file carrying that review, and the review of an integration test with no binding. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the code, the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.
review - A widget test that passes without testing anything
The page read it as: Tests are broken - 1 blocker, 6 major, 0 minor; findings C1 no-app, C2 real-dependency, C3 not-awaited, C4 no-pump, C5 no-expect, C6 real-dependency, C7 real-wait.
Input (facts is 2402 characters; the first 1398 are shown):
{
"task": "review",
"suite": "File: test/cart_screen_test.dart\nRuns on: a device or emulator\nKinds: 0 unit, 2 widget, 0 integration",
"code": "import 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:shop/cart/cart_screen.dart';\nimport 'package:shop/cart/cart_repository.dart';\n\nvoid main() {\n testWidgets('adding an item updates the badge', (tester) async {\n await tester.pumpWidget(CartScreen(repository: CartRepository()));\n tester.tap(find.byIcon(Icons.add));\n expect(find.text('1'), findsOneWidget);\n });\n\n testWidgets('shows the empty state', (tester) async {\n await tester.pumpWidget(MaterialApp(home: CartScreen(repository: CartRepository())));\n await Future.delayed(const Duration(seconds: 1));\n find.text('Your cart is empty');\n });\n}\n",
"carry": "",
"facts": "The page's scan of 19 lines - 2 tests (0 unit, 2 widget, 0 integration), 0 groups, 0 setUp/tearDowns; verdict: Tests are broken - 1 blocker, 6 major, 0 minor.\nFindings (id | area | severity | line | where | evidence | source):\n- C1 | setup | major | line 8 | testWidgets 'adding an item updates the badge' | pumpWidget is given CartScreen(repository: CartRepository()) with no MaterialApp around it, so Text, Scaffold and navigation fail for want of Directionality, MediaQuery or a Navigator. | source: await tester.pumpWidget(CartScreen(repository: CartRepository()));\n- C2 | isolation | major | line 8 | testWidgets 'adding an item updates the badge' | The test builds the real CartRepository, so it depends on whatever CartRepository talks to. | source: await tester.pumpWidget(CartScreen(repository: CartRepository()));\n- C3 | async | blocker | line 9 | testWidgets 'adding an item updates the badge' | tester.tap(...) is not awaited, so the next line runs before it finishes and the test can pass without testing anything. | source: tester.tap(find.byIcon(Icons.add));\n- C4 | async | major | line 10 | testWidgets 'adding an item updates the badge' | expect runs right after tester.tap (line 9) with no pump between, so it checks the frame before the tap took effect. | source: expect(find.text('1'), findsOneWidget);\n- C5 | assertions | major | line 13 | testWidgets 'shows the empty state' |\n\n[... 1004 more characters cut from this display ...]"
}
Reply:
{
"task": "review",
"summary": "Today neither test proves anything: the badge test checks a frame before an unawaited tap lands, and the empty-state test has no expect; first await the tap and pump before the badge expect.",
"findings": [
{
"id": "C1",
"widget": "testWidgets - adding an item updates the badge",
"issue": "CartScreen is pumped bare, so without the Directionality, MediaQuery and Navigator a MaterialApp supplies the screen can throw on build instead of showing the badge.",
"fix": "Wrap CartScreen in MaterialApp(home: ...) as the second test already does.",
"code": "await tester.pumpWidget(MaterialApp(\n home: CartScreen(repository: FakeCartRepository()),\n));"
},
{
"id": "C2",
"widget": "testWidgets - adding an item updates the badge",
"issue": "The badge test builds the real CartRepository, so its result depends on whatever storage or network that class reaches rather than on CartScreen alone.",
"fix": "Pass a FakeCartRepository that starts with no items, so one tap on the add icon has a known outcome.",
"code": "CartScreen(repository: FakeCartRepository())"
},
{
"id": "C3",
"widget": "testWidgets - adding an item updates the badge",
"issue": "tester.tap(find.byIcon(Icons.add)) is not awaited, so the expect on line 10 runs before the tap is delivered and the test can pass or fail without exercising the add button.",
"fix": "Await the tap.",
"code": "await tester.tap(find.byIcon(Icons.add));"
},
{
"id": "C4",
"widget": "testWidgets - adding an item updates the badge",
"issue": "No frame is pumped between the tap and expect(find.text('1'), findsOneWidget), so the expect reads the tree from before the badge could rebuild.",
"fix": "Call await tester.pump() after the awaited tap and before the expect.",
"code": "await tester.tap(find.byIcon(Icons.add));\nawait tester.pump();\nexpect(find.text('1'), findsOneWidget);"
},
{
"id": "C5",
"widget": "testWidgets - shows the empty state",
"issue": "Line 16 builds find.text('Your cart is empty') but never passes it to expect, so the test passes whether or not the empty state appears.",
"fix": "Turn the bare finder into an expect with findsOneWidget.",
"code": "expect(find.text('Your cart is empty'), findsOneWidget);"
},
{
"id": "C6",
"widget": "testWidgets - shows the empty state",
"issue": "The empty-state test uses the real CartRepository, so the cart is only empty if whatever that class reads happens to hold no items.",
"fix": "Pass a FakeCartRepository that returns no items, so the empty state is the defined input of the test.",
"code": "await tester.pumpWidget(MaterialApp(\n home: CartScreen(repository: FakeCartRepository()),\n));"
},
{
"id": "C7",
"widget": "testWidgets - shows the empty state",
"issue": "await Future.delayed(const Duration(seconds: 1)) waits on the fake clock inside testWidgets, which no pump advances, so the test can hang or time out instead of letting the screen load.",
"fix": "Replace the real wait with await tester.pump(const Duration(seconds: 1)) to advance the fake clock by the same amount.",
"code": "await tester.pump(const Duration(seconds: 1));"
}
],
"cleared": [],
"extra": [
{
"id": "X1",
"area": "assertions",
"line": 10,
"widget": "testWidgets - adding an item updates the badge",
"issue": "find.text('1') matches any Text reading 1 on the whole screen, such as a quantity or price, so it does not prove the badge itself shows the count.",
"fix": "Scope the finder to the badge, for example with find.descendant under the badge widget, once the team names which widget in CartScreen is the badge."
},
{
"id": "X2",
"area": "assertions",
"line": 10,
"widget": "testWidgets - adding an item updates the badge",
"issue": "The test never checks the badge before the tap, so a badge that already read 1 would pass as well.",
"fix": "Expect find.text('1') to be findsNothing right after pumpWidget and before the tap."
},
{
"id": "X3",
"area": "setup",
"line": 4,
"widget": "CartRepository",
"issue": "The file imports CartRepository but nothing covers it with a unit test, and the skill wants every repository unit tested with its own dependencies mocked.",
"fix": "Add a unit test file for CartRepository that fakes whatever it talks to and checks adding an item and reading an empty cart."
}
],
"plan": [
"Wrap the badge test's CartScreen in MaterialApp(home: ...) so it can build (C1)",
"Await the tap on the add icon and call await tester.pump() before the badge expect (C3, C4)",
"Replace Future.delayed with await tester.pump(const Duration(seconds: 1)) in the empty-state test (C7)",
"Turn the bare find.text('Your cart is empty') into an expect with findsOneWidget (C5)",
"Check the badge reads nothing before the tap and scope the '1' finder to the badge (X1, X2)",
"Swap the real CartRepository for a FakeCartRepository in both tests (C2, C6)",
"Add a unit test for CartRepository with its dependencies faked (X3)"
],
"caveats": [
"FakeCartRepository is not in this file: the team writes it from the real CartRepository, which the page did not send.",
"The widget that holds the badge in CartScreen is not shown, so the scoped finder for X1 needs its real type or key."
]
}
What step 5 prints for it: C1: Wrap CartScreen in MaterialApp(home: ...) as the second test already does. | C2: Pass a FakeCartRepository that starts with no items, so one tap on the add icon has a known outcome. | C3: Await the tap. | C4: Call await tester.pump() after the awaited tap and before the expect. | C5: Turn the bare finder into an expect with findsOneWidget. | C6: Pass a FakeCartRepository that returns no items, so the empty state is the defined input of the test. | C7: Replace the real wait with await tester.pump(const Duration(seconds: 1)) to advance the fake clock by the same amount.
The page's check: kept - nothing to flag
rewrite - A widget test that passes without testing anything
The input carries the saved review (R.laneForm fills carry from the review reply), as the page's handoff does. The page re-scanned the rewritten code: 7 findings before, 0 after.
Input (facts is 2402 characters; the first 1398 are shown; carry is 1508 characters; the first 1399 are shown):
{
"task": "rewrite",
"suite": "File: test/cart_screen_test.dart\nRuns on: a device or emulator\nKinds: 0 unit, 2 widget, 0 integration",
"code": "import 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:shop/cart/cart_screen.dart';\nimport 'package:shop/cart/cart_repository.dart';\n\nvoid main() {\n testWidgets('adding an item updates the badge', (tester) async {\n await tester.pumpWidget(CartScreen(repository: CartRepository()));\n tester.tap(find.byIcon(Icons.add));\n expect(find.text('1'), findsOneWidget);\n });\n\n testWidgets('shows the empty state', (tester) async {\n await tester.pumpWidget(MaterialApp(home: CartScreen(repository: CartRepository())));\n await Future.delayed(const Duration(seconds: 1));\n find.text('Your cart is empty');\n });\n}\n",
"carry": "C1 | confirmed | testWidgets - adding an item updates the badge | Wrap CartScreen in MaterialApp(home: ...) as the second test already does.\nC2 | confirmed | testWidgets - adding an item updates the badge | Pass a FakeCartRepository that starts with no items, so one tap on the add icon has a known outcome.\nC3 | confirmed | testWidgets - adding an item updates the badge | Await the tap.\nC4 | confirmed | testWidgets - adding an item updates the badge | Call await tester.pump() after the awaited tap and before the expect.\nC5 | confirmed | testWidgets - shows the empty state | Turn the bare finder into an expect with findsOneWidget.\nC6 | confirmed | testWidgets - shows the empty state | Pass a FakeCartRepository that returns no items, so the empty state is the defined input of the test.\nC7 | confirmed | testWidgets - shows the empty state | Replace the real wait with await tester.pump(const Duration(seconds: 1)) to advance the fake clock by the same amount.\nX1 | extra | testWidgets - adding an item updates the badge (line 10) | Scope the finder to the badge, for example with find.descendant under the badge widget, once the team names which widget in CartScreen is the badge.\nX2 | extra | testWidgets - adding an item updates the badge (line 10) | Expect find.text('1') to be findsNothing right after pumpWidget and before the tap.\nX3 | extra | CartRepository (line 4) | Add a unit test\n\n[... 109 more characters cut from this display ...]",
"facts": "The page's scan of 19 lines - 2 tests (0 unit, 2 widget, 0 integration), 0 groups, 0 setUp/tearDowns; verdict: Tests are broken - 1 blocker, 6 major, 0 minor.\nFindings (id | area | severity | line | where | evidence | source):\n- C1 | setup | major | line 8 | testWidgets 'adding an item updates the badge' | pumpWidget is given CartScreen(repository: CartRepository()) with no MaterialApp around it, so Text, Scaffold and navigation fail for want of Directionality, MediaQuery or a Navigator. | source: await tester.pumpWidget(CartScreen(repository: CartRepository()));\n- C2 | isolation | major | line 8 | testWidgets 'adding an item updates the badge' | The test builds the real CartRepository, so it depends on whatever CartRepository talks to. | source: await tester.pumpWidget(CartScreen(repository: CartRepository()));\n- C3 | async | blocker | line 9 | testWidgets 'adding an item updates the badge' | tester.tap(...) is not awaited, so the next line runs before it finishes and the test can pass without testing anything. | source: tester.tap(find.byIcon(Icons.add));\n- C4 | async | major | line 10 | testWidgets 'adding an item updates the badge' | expect runs right after tester.tap (line 9) with no pump between, so it checks the frame before the tap took effect. | source: expect(find.text('1'), findsOneWidget);\n- C5 | assertions | major | line 13 | testWidgets 'shows the empty state' |\n\n[... 1004 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "rewrite",
"summary": "Both tests now run inside MaterialApp on a FakeCartRepository, await the tap and pump before each expect, and the empty-state test asserts its text; the team still writes the fake and names the badge widget.",
"code": "import 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:shop/cart/cart_screen.dart';\nimport 'package:shop/cart/cart_repository.dart';\n\nvoid main() {\n testWidgets('adding an item updates the badge', (tester) async {\n await tester.pumpWidget(MaterialApp(home: [... 483 more characters cut from this display]",
"changes": [
{
"id": "C1",
"change": "In 'adding an item updates the badge', CartScreen is now pumped as MaterialApp(home: CartScreen(...)), as the second test already did."
},
{
"id": "C2",
"change": "In 'adding an item updates the badge', the real CartRepository() is replaced by FakeCartRepository(), which should start with no items."
},
{
"id": "C3",
"change": "In 'adding an item updates the badge', the tap on the add icon is now awaited."
},
{
"id": "C4",
"change": "In 'adding an item updates the badge', await tester.pump() now runs after the tap and before the expect on find.text('1')."
},
{
"id": "C5",
"change": "In 'shows the empty state', the bare finder became expect(find.text('Your cart is empty'), findsOneWidget)."
},
{
"id": "C6",
"change": "In 'shows the empty state', the real CartRepository() is replaced by FakeCartRepository(), which should return no items."
},
{
"id": "C7",
"change": "In 'shows the empty state', the real one-second Future.delayed became await tester.pump(const Duration(seconds: 1)) on the fake clock."
},
{
"id": "X2",
"change": "In 'adding an item updates the badge', expect(find.text('1'), findsNothing) now runs after pumpWidget and before the tap, so the test proves the tap made the change."
}
],
"kept": [],
"caveats": [
"FakeCartRepository is not defined in this file and its body is not written here; the team writes it from the real CartRepository, empty by default, and imports it where it lives.",
"X1 is left open: find.text('1') matches any '1' on the screen, so scope it with find.descendant once the team names the badge widget in CartScreen.",
"X3 is left for a new file: a unit test for CartRepository that fakes whatever it talks to and checks adding an item and reading an empty cart."
]
}
Cut from this display only (the saved reply and the check use the full text): code (793 characters, 310 shown).
What step 5 prints for it: C1: In 'adding an item updates the badge', CartScreen is now pumped as MaterialApp(home: CartScreen(...)), as the second test already did. | C2: In 'adding an item updates the badge', the real CartRepository() is replaced by FakeCartRepository(), which should start with no items. | C3: In 'adding an item updates the badge', the tap on the add icon is now awaited. | C4: In 'adding an item updates the badge', await tester.pump() now runs after the tap and before the expect on find.text('1'). | C5: In 'shows the empty state', the bare finder became expect(find.text('Your cart is empty'), findsOneWidget). | C6: In 'shows the empty state', the real CartRepository() is replaced by FakeCartRepository(), which should return no items. | C7: In 'shows the empty state', the real one-second Future.delayed became await tester.pump(const Duration(seconds: 1)) on the fake clock. | X2: In 'adding an item updates the badge', expect(find.text('1'), findsNothing) now runs after pumpWidget and before the tap, so the test proves the tap made the change.
The page's check: kept - nothing to flag
review - An integration test with no binding
The page read it as: Tests are broken - 1 blocker, 1 major, 0 minor; findings C1 no-binding, C2 no-pump.
Input:
{
"task": "review",
"suite": "File: integration_test/notes_test.dart\nRuns on: a Linux CI machine\nKinds: 0 unit, 0 widget, 1 integration",
"code": "import 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:integration_test/integration_test.dart';\nimport 'package:notes/main.dart';\n\nvoid main() {\n group('end-to-end', () {\n testWidgets('create a note', (tester) async {\n await tester.pumpWidget(const NotesApp());\n await tester.tap(find.byKey(const ValueKey('new-note')));\n await tester.pumpAndSettle();\n await tester.enterText(find.byType(TextField), 'Buy milk');\n await tester.tap(find.text('Save'));\n expect(find.text('Buy milk'), findsOneWidget);\n });\n });\n}\n",
"carry": "",
"facts": "The page's scan of 18 lines - 1 test (0 unit, 0 widget, 1 integration), 1 group, 0 setUp/tearDowns; verdict: Tests are broken - 1 blocker, 1 major, 0 minor.\nFindings (id | area | severity | line | where | evidence | source):\n- C1 | setup | blocker | line 6 | main | The file imports integration_test but never calls IntegrationTestWidgetsFlutterBinding.ensureInitialized(), so the tests do not run on the device's binding. | source: void main() {\n- C2 | async | major | line 14 | testWidgets 'create a note' | expect runs right after tester.tap (line 13) with no pump between, so it checks the frame before the tap took effect. | source: expect(find.text('Buy milk'), findsOneWidget);\nThe tests (line | kind | name | expects | actions | pumps):\n- line 8 | integration | create a note | 1 | 3 | 2\nmust: account for every finding id above exactly once - in findings (confirmed) or in cleared (with the reason)."
}
Reply:
{
"task": "review",
"summary": "Today the test proves only that 'Buy milk' can be typed into a field, since the expect also matches the TextField itself; first call IntegrationTestWidgetsFlutterBinding.ensureInitialized() at the top of main().",
"findings": [
{
"id": "C1",
"widget": "main",
"issue": "The file imports integration_test but main() never initialises its binding, so the test runs on the default test binding rather than the device's and its results are not reported back to the driver.",
"fix": "Call IntegrationTestWidgetsFlutterBinding.ensureInitialized() as the first statement of main(), before the group.",
"code": "void main() {\n IntegrationTestWidgetsFlutterBinding.ensureInitialized();\n group('end-to-end', () {"
},
{
"id": "C2",
"widget": "testWidgets - create a note",
"issue": "The expect on line 14 runs straight after the Save tap with no frame pumped, so it inspects the editor as it was before Save took effect.",
"fix": "Add await tester.pumpAndSettle() after the Save tap so the save and any route change finish before the expect.",
"code": "await tester.tap(find.text('Save'));\nawait tester.pumpAndSettle();\nexpect(find.text('Buy milk'), findsOneWidget);"
}
],
"cleared": [],
"extra": [
{
"id": "X1",
"area": "assertions",
"line": 14,
"widget": "testWidgets - create a note",
"issue": "find.text('Buy milk') also matches the text inside the TextField filled on line 12, so the expect passes even if Save does nothing and the note never reaches the list.",
"fix": "After the pump, first assert expect(find.byType(TextField), findsNothing) to prove the editor closed, then check find.text('Buy milk') with findsOneWidget."
},
{
"id": "X2",
"area": "assertions",
"line": 13,
"widget": "testWidgets - create a note",
"issue": "Only the happy path is covered; nothing checks what tapping Save does with an empty TextField, a critical-flow edge case the file leaves out.",
"fix": "Add a second testWidgets that taps Save without entering text and expects whatever the app shows for an empty note, with the expected text taken from the app code."
}
],
"plan": [
"Call IntegrationTestWidgetsFlutterBinding.ensureInitialized() first in main() (C1)",
"Pump with await tester.pumpAndSettle() after the Save tap, before the expect (C2)",
"Assert the editor closed with findsNothing on the TextField before checking the note text (X1)",
"Add an empty-note test that taps Save with no text entered (X2)"
],
"caveats": [
"The code does not show what NotesApp displays for an empty note, so the X2 expect needs the team to supply that value.",
"If NotesApp saves notes to real storage, an earlier run's 'Buy milk' note may still be listed; clear that storage before the test."
]
}
What step 5 prints for it: C1: Call IntegrationTestWidgetsFlutterBinding.ensureInitialized() as the first statement of main(), before the group. | C2: Add await tester.pumpAndSettle() after the Save tap so the save and any route change finish before the expect.
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./testkit.js"); require("./testreply.js")), then:
const K = TestKit, R = TestReply;
const f = K.form({ name: "test/counter_test.dart", targets: "mobile", carry: "",
code: "void main() {\n testWidgets('adds', (tester) async {\n await tester.pumpWidget(const Counter());\n tester.tap(find.byIcon(Icons.add));\n });\n}" });
// f.findings, f.scan.inventory (every test), f.checklist, f.commands (how to run the file)
// K.findingsCsv(f), K.testsCsv(f), K.checklistMd(f) - the free exports
const b = K.buildInput(f, "review"); // or "rewrite" (R.laneForm(form, "rewrite", review) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "review");
// R.check(reply, b) // {status: kept or flags, notes}
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="YOUR_TOKEN" # from https://test-lens.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://test-lens.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// test-lens.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://test-lens.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "YOUR_TOKEN" // from https://test-lens.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
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) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// Test Lens.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class Test Lens {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://test-lens.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://test-lens.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // from https://test-lens.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; 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"; // from https://test-lens.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"test-lens"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "test-lens"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "test-lens" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "test-lens"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "test-lens"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "test-lens" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "test-lens"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "test-lens" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "review", "suite": "File: test/cart_screen_test.dart
Runs on: a device or emulator
Kinds: 0 unit, 2 widget, 0 integration", "code": "import 'package:flutter_test/flutter_test.dart';
...", "carry": "", "facts": "The page's scan of 19 lines - 2 tests ...
Findings (id | area | severity | line | where | evidence | source):
- C1 | setup | major | line 8 | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "review", "suite": "File: test/cart_screen_test.dart
Runs on: a device or emulator
Kinds: 0 unit, 2 widget, 0 integration", "code": "import 'package:flutter_test/flutter_test.dart';
...", "carry": "", "facts": "The page's scan of 19 lines - 2 tests ...
Findings (id | area | severity | line | where | evidence | source):
- C1 | setup | major | line 8 | ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "review" or "rewrite", suite, code, carry, facts} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses test-lens:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A review reply carries summary, findings, cleared, extra, plan and caveats; a rewrite reply carries summary, code, changes, kept and caveats. The code below prints the gist of either: each confirmed finding and its fix, or each change.
KEY="test-lens:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r 'if .task == "rewrite" then (.changes[] | "\(.id): \(.change)")
else (.findings[] | "\(.id): \(.fix)") end' reply.json
import hashlib, time
key = "test-lens:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply["task"])
if reply["task"] == "rewrite":
for x in reply["changes"]:
print(x["id"], x["change"])
else:
for x in reply["findings"]:
print(x["id"], x["fix"])
import { createHash } from "node:crypto";
const key = `test-lens:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.task);
if (reply.task === "rewrite") { for (const x of reply.changes) console.log(x.id, x.change); }
else { for (const x of reply.findings) console.log(x.id, x.fix); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "test-lens:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
fmt.Println(j["charged_credits"], j["truncated"], reply["task"])
key, b := "findings", "fix"
if reply["task"] == "rewrite" {
key, b = "changes", "change"
}
xs, _ := reply[key].([]any)
for _, x := range xs {
xx := x.(map[string]any)
fmt.Println(xx["id"], xx[b])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "test-lens:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.path("task").asText());
if (reply.path("task").asText().equals("rewrite")) {
for (JsonNode x : reply.path("changes")) System.out.println(x.path("id").asText() + " " + x.path("change").asText());
} else {
for (JsonNode x : reply.path("findings")) System.out.println(x.path("id").asText() + " " + x.path("fix").asText());
}
require "digest"
key = "test-lens:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["task"]].inspect
if reply["task"] == "rewrite"
reply["changes"].each { |x| puts "#{x["id"]} #{x["change"]}" }
else
reply["findings"].each { |x| puts "#{x["id"]} #{x["fix"]}" }
end
$key = "test-lens:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", $reply["task"], "\n";
if ($reply["task"] === "rewrite") { foreach ($reply["changes"] as $x) echo $x["id"], " ", $x["change"], "\n"; }
else { foreach ($reply["findings"] as $x) echo $x["id"], " ", $x["fix"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"test-lens:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>())!;
var task = (string?)reply["task"];
Console.WriteLine($"{j["charged_credits"]} {task}");
if (task == "rewrite") { foreach (var x in reply["changes"]!.AsArray()) Console.WriteLine($"{x!["id"]} {x["change"]}"); }
else { foreach (var x in reply["findings"]!.AsArray()) Console.WriteLine($"{x!["id"]} {x["fix"]}"); }
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: test-lens:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "review", "suite": "File: test/cart_screen_test.dart
Runs on: a device or emulator
Kinds: 0 unit, 2 widget, 0 integration", "code": "import 'package:flutter_test/flutter_test.dart';
...", "carry": "", "facts": "The page's scan of 19 lines - 2 tests ...
Findings (id | area | severity | line | where | evidence | source):
- C1 | setup | major | line 8 | ..."}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "test-lens:" + rev["task"] + ":" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `test-lens:${rev.task}:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "test-lens:"+input["task"].(string)+":"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "test-lens:" + input.get("task") + ":" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "test-lens:#{JSON.parse(rev)["task"]}:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: test-lens:" . json_decode($rev, true)["task"] . ":" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"test-lens:{input["task"]}:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);