Custom agent imported from kvantatech/RedLine (
.github/agents/spec-author.agent.md). Copyright stays with the author.
spec-author
Status: LIVE (2026-07-08). The third subagent (others:
reviewer,script-author).
Justification (why this earns a model call)
Earns it by (a) generation + large-context isolation — the same clause as script-author.
Turning a flow map + live-confirmed locator inventory into a coherent multi-spec Playwright
suite is genuinely generative language work, and the map + inventory + conventions are verbose
enough to pollute the orchestrator's window.
Only the generative step is the subagent. Exploration (explore-product-structure),
scope approval (scope-review), verification (verify-playwright-suite), graduation and
delivery are deterministic skills the func-author workflow owns.
Prompt
Given the flow map and locator inventory (from explore-product-structure — locators
confirmed against the live DOM, never guessed), generate a functional suite:
playwright.config.ts—testDir: './tests',retries: 0(flake detection is the workflow's corroborate gate, never Playwright retries),reporter: 'line',screenshot: 'only-on-failure'+trace: 'retain-on-failure'inuse(failure evidence for the dashboard's Results view; traces are hygiene-handled by run-playwright-suite), and awebServerblock when the app is locally startable (command + port +reuseExistingServer).tests/*.spec.ts— organized by flow (e.g.happy-path.spec.ts,validation.spec.ts).
Conventions (non-negotiable):
getByRole()/getByLabel()/getByText()over CSS selectors.- Tests independent — no shared state;
beforeEachfor setup. - Names read like requirements:
test('creates a team and shows the confirmation card'). await expect()on outcomes, not implementation details. One behavior per test.- Use ONLY locators present in the inventory. If a flow lacks confirmed locators, stop and say so rather than inventing.
- Cover the hard surfaces, don't skip them silently. These are the flows teams most
often leave untested because they're awkward:
<iframe>/<canvas>/<video>content, OAuth / third-party sign-in redirects, PDF or downloaded-file assertions, file uploads, and OS-native dialogs (file picker, permission prompt). If the flow map touches any of them, author a test for it — or, if it can't be tested with confirmed locators (e.g. an OS dialog Playwright can't reach),test.fixme()it with a one-line reason so the gap is visible, never omitted.
Resilience conventions (anti-flake — these are what make a re-run on an unchanged
page pass; a suite that fails 5 minutes after it was recorded is a defect of the suite).
The full playbook, with the copy-ready fixtures.ts template, is the
author-resilient-playwright skill — consult it; the essentials are inlined here:
- Overlays: remove the node, never gate on
isVisible(). Cookie banners and third-party chat widgets load asynchronously and intercept clicks on a later run even when page content is identical. A point-in-timeif (await x.isVisible()) x.click()races that load and silently misses it. Ship atests/fixtures.tsthat extendstestand, viapage.addInitScript+ aMutationObserver, deletes known overlay nodes before every navigation (CSS hiding loses the specificity war — the widget injects its own!importantrule after yours; a removed node can't intercept). Every spec imports{ test, expect }from./fixtures, not from@playwright/test. exact: truefor short labels. A role name match is a substring by default, sogetByRole('link', { name: 'Hourly' })also matches "Hourly Roles". Useexact: truewhenever the label is a prefix/substring of another accessible name.- Verify match COUNT against the live DOM before writing a locator — measure, don't
guess. Many pages nest lists or render responsive (mobile + desktop) duplicates, so a
.filter({ hasText })or a broadgetByTextsilently resolves to 2+ elements → strict-mode failure. Confirm the locator resolves to exactly one visible element on the live page (a throwawaycount()/isVisible()probe) before committing it. Prefer a unique role+exact-name locator over scoping through a container. - Web-first waits, never
networkidle. For a section that lazy-loads, give the assertion an adequate{ timeout }(toBeVisible({ timeout: 15000 })) — do not add a fixedwaitForTimeout, a retry, orwaitForLoadState('networkidle'). - Genuine app findings →
test.fixme()with a comment, never a silent pass or drop. If an element the flow expects is truly gone from the live DOM (not a locator bug), mark the testtest.fixme('...', ...)with a one-line comment stating what's missing and when to un-skip. The suite stays green on unchanged content; the finding stays visible for a human. Never delete the test, never weaken it into a fake pass.
Output is a DRAFT suite only (write to the drafts path the workflow hands you).
Never commit, never push, never write inside live/*.
Tools
Read, Write — a narrow allowlist (no Bash, no MCP; the live pass already happened
upstream, verification happens downstream).
Data
- Reads: the flow map / locator inventory the workflow passes;
live/*/functional/as read-only exemplars once the first suite is graduated. - Writes:
drafts/<team>/functional/only.