
- Claude Code
- Codex
- Copilot
- +1
API Contract Reviewer
Catches breaking REST changes in a PR before merge: diff vs spec, severity table, migration notes.
15 results

Catches breaking REST changes in a PR before merge: diff vs spec, severity table, migration notes.
Use case: Product teaser (template 1) | Element | Value | |---|---| | Subject | A matte black ceramic pour-over coffee dripper with a thin polished copper rim, sitting on a clear glass carafe | | Action | 1) A thin stream of dark coffee trickles from the dripper into the carafe while soft steam curls up from the rim; 2) the dripper and carafe turn a slow quarter turn; 3) the final second holds still on the dripper's front, copper rim catching the light | | Scene | A walnut kitchen counter by a window, early morning just after sunrise, a faint haze of steam in the air, uncluttered background | | Angle / Move | Vertical close-up at counter height, slightly above eye level / slow dolly in | | Lens | 85mm-style telephoto feel, shallow depth of field, kitchen background in soft bokeh | | Light / Palette / Mood | Soft low-angle morning sunlight from a window on the left with gentle fill and a warm rim on the copper / matte black, warm copper, honey-brown walnut, soft cream highlights / calm, quiet, unhurried | | Style / Time | Premium commercial look, photorealistic / real-time pace, steady trickle throughout, still hold in the last second | | Audio | Close, clear coffee trickling into glass, a faint soft drip, quiet low morning room tone, no music | | Negative | text, logos, letters, labels, hands, people, clutter, reflections of people, plastic look | Final prompt (paste into Veo / Gemini / Flow): Premium commercial look, photorealistic, vertical close-up at counter height of a matte black ceramic pour-over coffee dripper with a thin polished copper rim, sitting on a clear glass carafe on a walnut kitchen counter by a window in the early morning just after sunrise. A thin stream of dark coffee trickles from the dripper into the carafe while soft steam curls up from the rim, and the dripper and carafe turn a slow quarter turn. Slow dolly in, 85mm-style telephoto feel, shallow depth of field with the kitchen background in soft bokeh. Soft low-angle morning sunlight from a window on the left with gentle fill and a warm rim light catching the copper, palette of matte black, warm copper, honey-brown walnut and soft cream highlights, calm and quiet mood. Real-time pace; the final second holds still on the dripper's front. Audio: close, clear sound of coffee trickling into a glass carafe, a faint soft drip, quiet low morning room tone, no music. Negative prompt: text, logos, letters, labels, hands, people, clutter, reflections of people, plastic look Settings: 9:16, 8 seconds, audio on.
Use it to turn a rough video idea into a structured Veo prompt with a filled anatomy table, camera, lens, light, audio and a negative prompt.
Beat sheet | Beat | Purpose | What the viewer must get | Seconds | |---|---|---|---| | 1 | Hook | A reporter in the rain, recording a source who is trusting him with something | 4 | | 2 | Problem / stakes | The recording is precious and private; the small device in his wet hand is what holds it | 4 | | 3 | Reveal | Later that night, at home, he sets Fieldnote down and starts it working | 4 | | 4 | Proof | The words appear on its own e-ink screen: transcribed on the device, no laptop, no cloud | 8 | | 5 | Payoff + CTA | He has every word, privately; music enters; end line | 4 | Storyboard table (shots 1–3 of 6) | # | Sec (gen → use) | Framing | Camera move | Subject action | Dialogue / on-screen text | Audio / ambience | Continuity notes | |---|---|---|---|---|---|---|---| | 1 | 4 → 4 | Medium shot, eye level, over the source's shoulder (her back soft in frame-left foreground); he stands under the awning facing screen left | Slow handheld push-in | Holds Fieldnote out toward her at chest height; listens; gives one small nod | Source (on camera, from behind): "They said it would stay between us." | Location A bed; her voice close; rain on the awning | Location A. Faces screen left. Recorder in his right hand, red light on. Jacket zipped halfway, wet shoulders. | | 2 | 4 → 4 | Extreme close-up on his right hand holding Fieldnote, the source's coat a soft blur beyond | Static | Rain beads on the graphite body; the red light glows steadily; his thumb shifts once on the edge | VO: "What they tell me stays with me." | Location A bed, close rain ticks on the device | Location A. Recorder points screen left. Screen pale grey and blank. Ends with recorder centred, for a match cut. | | 3 | 4 → 4 | Medium close-up on the kitchen table under the pendant lamp | Static | His right hand enters from frame right and sets Fieldnote down, centred; his thumb presses the round button; hand withdraws | none | Location B bed; soft tap of the recorder on wood; one button click | Location and lighting change (B). Match cut from shot 2 on recorder position. Device dry now, red light off. | [The full answer continues with shots 4–6 and a coverage alternate, the ten-line continuity block pasted word for word into all seven shot prompts, each prompt's first and last frame, and the stitch plan with voice-over script, caption placement and export settings.]
Use it to turn a script or one-paragraph brief into a beat sheet, storyboard table, matching per-shot Sora 2 prompts and a stitch plan.

Use it to score a pricing page or in-app paywall on eleven criteria and get three ranked fixes with rewritten copy and one experiment to run.
Assumption: no n8n-MCP server is connected in this session, so I took the skill's fallback path: I wrote the workflow JSON directly from the bundled rules and checked it by review (a rule-check script plus a dry run of every expression). The real validator (`validate_workflow`) was not run, and nothing was deployed.
How the skill routed this
| Step | Sub-skill used | What it decided |
|---|---|---|
| Shape | `n8n-workflow-patterns` | Webhook processing pattern: Webhook, then Validate, then Action, then Respond |
| Node parameters | `n8n-node-configuration` | Slack post needs a channel and text. `responseMode: "responseNode"`. `options.responseCode` set on every Respond node. UUID node ids, no placeholder credentials |
| Expressions | `n8n-expression-syntax` | Webhook data lives under `.body`. Use `$('Webhook').item.json.body.*` once past the IF node. `=` prefix on every expression field |
| Error paths | `n8n-error-handling` | Slack gets `onError: "continueErrorOutput"` and its `main[1]` is wired to the 500 responder. `retryOnFail` is on. Bad input is caught upstream and gets a 400 |
| Validation | `n8n-validation-expert` | Runtime-profile checks done by review. Security warnings get fixed (header auth on the webhook) |
```
Webhook ──> Validate Input ──true──> Post to #signups ──success (main[0])──> Respond 200 OK
└──false──> Respond 400 Invalid Input
Post to #signups ──error (main[1])────> Respond 500 Slack Failed
```
Validation result (excerpt)
```
PASS Webhook responseMode is responseNode (Respond nodes control the reply)
PASS Every branch (success and each error) ends at a Respond to Webhook node: no hanging branch
PASS 'Post to #signups': onError=continueErrorOutput AND main[1] wired
PASS 'Post to #signups': retryOnFail with maxTries <= 5 and waitBetweenTries <= 5000
PASS Every {{ }} field starts with = (no 'Expression requires = prefix' error)
PASS Webhook fields read from .body, never $json.name / $json.email / $json.plan
…
43 passed, 0 failed
```
Expression dry run against sample webhook items:
```
valid IF -> true (Slack) Slack text: New signup: Ana Lima (ana@example.com) on pro
missing_plan IF -> false (400)
bad_email IF -> false (400)
no_body IF -> false (400)
500 body: {"error":"upstream_error","message":"The signup was received but could not be posted to Slack. Please retry later."}
```
Not verified without the server: the node typeVersions (Webhook 2.2, IF 2.3, Slack 2.3, Respond 1.5) and the Slack v2 parameter names; confirm by opening the nodes after import.
[The full answer also holds the six-node list, the importable workflow JSON and the expressions table with the reason behind each one.]Use it to have your coding agent build, configure and validate n8n workflows without the usual expression and node-config mistakes.
Phase 1: Root cause investigation
Full suite: FAIL test_expired_coupon_rejected_twice (tests/test_checkout.py:16) AssertionError: Coupon(code='OLD50', …expires_at=2020-01-01…) is not None.
In isolation the same test fails at a different line (17): AssertionError: 50.0 != 100.0. So the first lookup in a process is rejected and every later lookup is accepted; in the full suite the first lookup was done by test_expired_coupon_rejected. Cross-test pollution through module-level state.
Cache probe at the boundary: cache before [] → 1st None → cache after ['OLD50'] → 2nd Coupon(OLD50). Docstring says the cache was added in 3f2a1c9 "to cut DB reads": that is the regression point.
Trace (root-cause-tracing.md): apply_coupon → get_coupon → `if code in _CACHE: return _CACHE[code]`. get_coupon writes the coupon to _CACHE before the expiry check, and the cache-hit branch has no expiry check at all.
Phase 2: Pattern analysis
Working path: cache miss → _load → expiry check. Broken path: cache hit. Expiry depends on `now`, passed per call, so it cannot be a cached property; "don't cache expired coupons" alone would still serve a coupon cached while valid after it expires.
Phase 3: Hypothesis
"get_coupon returns cached coupons without re-checking expires_at against the caller's now; the cache was added around the lookup but the validity check stayed only on the miss path." Confirmed by the probe above.
Phase 4: Implementation
Failing test first: test_cached_coupon_rechecked_for_expiry (look up while valid, then after expiry) plus a setUp that clears the module cache. Before the fix: 5 tests, 2 failures, both for the expected reason.
Fix at the source: the cache stores the raw record; the expiry check runs on every lookup, cached or not.
c = _CACHE.get(code)
if c is None:
c = _load(code)
if c is None:
return None
_CACHE[code] = c
if c.expires_at < now:
return None
return c
Verification: python3 -m unittest -v → Ran 5 tests … OK. The originally failing test in isolation, 3 runs: OK, OK, OK. Fix attempts: 1.Stops your coding agent from guessing: root cause first, one hypothesis at a time, failing test before the fix.

Idea to approved design to test-first plan to working code, with an approval gate between each stage.
Scope: app/dashboard/page.tsx and the modules it imports (components/charts, UsageCard, ProjectList, RichTextEditor, lib/api.ts).
| # | Rule | File:line | Impact | Finding |
|---|---|---|---|---|
| 1 | async-dependencies | app/dashboard/page.tsx:8-10 | CRITICAL | getProjects(user.id) and getUsage(user.id) depend only on `user` but are awaited one after the other: 3 sequential round trips before any HTML is sent, where 2 would do. Promise.all them. |
| 2 | bundle-barrel-imports | components/ProjectList.tsx:3 | CRITICAL | `import _ from 'lodash'` in a 'use client' file ships all of lodash for one sortBy. Use lodash/sortBy, or toSorted / localeCompare. |
| 3 | async-suspense-boundaries | app/dashboard/page.tsx:7-19 | HIGH | The whole page, including the static shell and RichTextEditor, waits for all three fetches. Move usage and projects into async children in <Suspense> so the shell streams first. |
| 4 | server-serialization | app/dashboard/page.tsx:16 | HIGH | The raw projects array goes to client ProjectList, which reads only id, name and status. Map to {id, name, status} on the server. |
| 5 | rerender-no-inline-components | components/ProjectList.tsx:7 | HIGH | `Row` is defined inside ProjectList, so every keystroke in the filter remounts every <li>. Interaction cost, not first load. Hoist Row. |
| 6 | rerender-derived-state-no-effect | components/UsageCard.tsx:4-5 | MEDIUM | percent is kept in state and set in useEffect: server HTML renders "0% of plan used" and the real value appears only after hydration. Compute it during render. |
Score: 62 / 100 (100 − 2×10 CRITICAL − 3×5 HIGH − 1×3 MEDIUM). This counts rule findings; it is not a measured load time.
Checked and not scored: the local charts barrel (five tiny stubs, tree-shakeable); RichTextEditor is a <textarea> today, so no dynamic import yet. Not a performance rule but it blocks the build: components/charts/index.ts re-exports names the chart files do not export, and page.tsx reads `params.id` synchronously though params is a Promise in Next 15.
What makes the first load slow, in order:
1. The server waterfall (rows 1 and 3). Parallelizing projects and usage removes one full round trip; Suspense lets the header paint after only getUser.
2. Client JS weight (row 2). A one-line import change.
3. Payload and first-paint correctness (rows 4 and 6).
Row 5 is an interaction cost; fix it after the others. Fixes were not requested, so no files were changed.70 prioritized performance rules for React and Next.js that your agent applies while coding and scores in review.

Audit a live page that isn't converting: a 7-point scorecard, quick wins, test ideas and before/after copy.

24 named design moves for your coding agent: bolder, quieter, typeset, distill, polish, plus a heuristic critique and a technical audit.

Your agent interviews you about a plan until nothing is assumed, then writes the spec and tracer-bullet tickets.

One-line brief in, full design system out: page pattern, style, colour tokens, font pairing and anti-patterns from a local database.

Strips AI tells from a draft without inventing a new voice: quoted flags, a clean rewrite, a second-pass check.
Short answer: ChatGPT can't recommend Ledgerly, because nothing it can retrieve says Ledgerly exists. Even after launch, a single landing page is not enough. A "best X for Y" shortlist comes from web-wide consensus (reviews, roundups, forums, press), not from your own site.
Visibility ladder (retrieved → cited → mentioned → recommended)
| Rung | Ledgerly today | What governs it |
|---|---|---|
| 1. Retrieved | No: not live, not in Bing's index (ChatGPT search draws from a Bing-based index) | Being indexed and crawlable |
| 2. Cited | No | Extractable, factual, fresh pages |
| 3. Mentioned | No | Entity recognition; the web talking about you |
| 4. Recommended | No | Aggregate third-party consensus, largely independent of your own content |
Source pool (one web search for the exact query, 2026-11-07; not a ChatGPT answer): only third-party roundups and affiliate lists (NerdWallet, business.org, Zapier, Fueler, Techjockey, Aspire). The same five apps recur: Wave, Zoho Books, FreshBooks, QuickBooks, Xero. I did not run ChatGPT, so I'm not reporting what ChatGPT says; run the query list yourself.
Extractability check on site/index.html: clear definition in the first paragraph, partial (the H1 is a slogan); self-contained answer blocks, fail; statistics with sources, fail; comparison table for "vs" queries, fail; FAQ, partial (good answers hidden in collapsed <details>, none answers "Is Ledgerly good for freelancers?"); schema markup, fail; author or founder, fail; freshness date, fail; query-shaped headings, fail.
Top fixes, by impact against effort
1. Launch, then get indexed in Bing and Google. robots.txt with a wildcard allow plus explicit allows for OAI-SearchBot, PerplexityBot, Claude-SearchBot and Bingbot; decide training crawlers (GPTBot, ClaudeBot) separately.
2. Seed third-party consensus: G2 and Capterra listings with the first 20–30 real reviews; pitch the roundups that already rank for the query with a specific angle ("$12/month, no per-transaction fees, automatic invoice chasing"). Test: if a model ignored your own domain, would the web still put you on the list? Today, no.
3. Rewrite the hero into an extractable definition (below).
4. Add /vs/freshbooks, then QuickBooks and Wave, in the "fair source" pattern. Do not publish a self-ranked "best bookkeeping apps" listicle: in the skill's data 69% of such citations recommended competitors.
5. Publish /pricing.md and /llms.txt; fix the Studio button ("Talk to us" on a plan with a public price reads as a sales wall to an agent).
6. JSON-LD: Organization, SoftwareApplication with Offer per plan, FAQPage.
7. Buyer-intent FAQs rendered open in the HTML. 8. Named founder, "Last updated", one dated sourced number. 9. Measure monthly: 8 queries, 3–5 runs per engine, log rung and framing.
Hero answer block (57 words, every fact from the existing page):
> Ledgerly is a bookkeeping app for freelancers and two-person studios. It connects to your bank through Plaid or TrueLayer (US, UK, EU), auto-categorises each transaction by category and client, chases unpaid invoices on day 3, 7 and 14, and exports tax-ready P&L, VAT and quarterly estimates. Free plan available; Pro is $12/month with no per-transaction fees. […]Get cited in AI Overviews, ChatGPT and Perplexity: visibility audit, crawler check, ranked fixes, answer-block rewrites.

Turns the commits since your last tag into user-facing release notes, grouped and in your voice.