Break your dev loop down to skills
Stop re-explaining your workflow to every coding agent.
- Tickets
tg:new-ticketNote in, proper GitHub issue out.tg:pick-ticketPicks the next ticket and ships it as a PR.- Testing
tg:uatOpens the browser and checks the thing works.- Code
tg:uiBuilds with the tokens and components you already have.tg:cleanupFixes comments that lie about the code.tg:improve-architectureFinds the messy modules and proposes better ones.- Words and numbers
tg:copyBlog and marketing copy, English and Greek.tg:greek-reviewMakes Greek UI text sound Greek.tg:growthAnalytics, Search Console and Ads in one go.
File a ticket worth picking up
Most tickets are a symptom and a shrug. This one does the digging first.
needs git · gh (logged in) · GitHub labels
---name: new-ticketdescription: >- Turn a raw note, bug report, or half-formed idea into a properly shaped GitHub issue: investigated, evidenced, and labelled to the repo's taxonomy. Use whenever the user hands over something to file rather than discuss: "open a ticket for this", "file this", "log a bug", "add this to the backlog", or pastes a note and asks where it goes. Investigates the cause before writing, assigns type/priority/area/size labels, and assigns a milestone only when an active delivery phase covers it. Creates issues and labels via `gh`, so this is not read-only.---
# File a ticket
Specs live in GitHub issues, not in the repo. The issue body **is** the spec, sothe investigation happens here, at filing time, not later when someone picks itup.
Read `references/conventions.md` before writing anything. It defines the labelaxes, the label-vs-milestone rule, and the required body shape.
## Step 0: Load the repo's taxonomy
Read `.claude/tg.json` for `tickets`:
```jsonc{ "tickets": { "repo": "acme/app", "areaPrefix": "project:", "topicPrefix": "topic:", "requireSize": true, "activeMilestones": ["Checkout v2 · Payments", "Search · POC"] }}```
If the key is missing, derive it: `gh label list` for the taxonomy, `gh apirepos/:owner/:repo/milestones` for phases, `git remote` for the repo. Show whatyou found, ask the user to confirm, and offer to write `.claude/tg.json` so thisstep never runs again.
**If the repo has no taxonomy at all** (default GitHub labels only), stop andoffer to bootstrap it: the five axes from `references/conventions.md`, createdwith `gh label create`. Don't file a well-formed issue into a repo that can'troute it.
## Step 1: Investigate before writing
This is the step that makes the ticket worth having.
For a **bug**: reproduce the claim against the code. Find the mechanism, not thesymptom. Name the commit that introduced it (`git log -S`, `git blame`) and theexact line that is wrong or missing. If the code says the bug can't happen, sayso and ask rather than filing a fiction.
For an **enhancement**: read the code that would change, so the acceptancecriteria describe something buildable.
If investigation shows the thing is already fixed, or is a duplicate of an openissue (`gh issue list --search`), say so and stop. Don't file it.
## Step 2: Decide where it belongs
Three outcomes, same as any triage:
1. **Fits an existing area**: file it, labelled. No permission needed.2. **Needs a new `topic:` tag**: cross-cutting work that isn't an area. Propose the tag and what else would carry it; wait for a yes.3. **Needs a new `project:` area or milestone**: a product decision. Stop and ask explicitly before creating anything.
Apply the label-vs-milestone rule from `references/conventions.md`: assign amilestone **only** if one of the `activeMilestones` genuinely contains this workand can close with it. An epic never gets a milestone. Most issues get none.
## Step 3: Write the body
Symptom -> Cause (with evidence) -> Fix -> Why it wasn't caught. Enhancements:Symptom -> Acceptance criteria as a checklist. Full format and a worked examplein `references/conventions.md`.
Title format: `[area/feature] what is wrong, in plain words`. Match theexisting titles in the repo rather than inventing a scheme.
## Step 4: File it
gh issue create --title "..." --body-file <tmp> \ --label "bug,priority:p1,project:x,size:m"
Show the user the rendered body and the label set **before** creating it. Aftercreating, print the URL.
If you judged it `size:xl`, don't file one issue; propose the split intoseveral, each independently shippable, and file them only once the user agrees.
## Things that will bite you
- **Filing the symptom as the cause.** "Button does nothing" is a title, not a cause. If you can't name the mechanism, say the investigation was inconclusive rather than dressing up a guess.- **Two area labels.** An issue belongs to one area. Spanning work is two issues or one issue plus a `topic:`.- **Inventing a milestone** because the work feels big. Big is `size:l`, not a new phase.What it does. You give it a note like “invite page is broken on Safari”. Before it writes anything, it goes and finds the actual bug: the file, the line, and the commit that broke it, using git blame and git log -S. Then it files the issue in a fixed format: symptom, cause, fix, and why the tests didn’t catch it. It adds the labels too, and shows you the issue before creating it.
Why. “Button does nothing” isn’t a ticket. Whoever picks it up, me or an agent, starts from zero. If the digging happens when the ticket is filed, the fix starts from evidence.
From ticket to PR
Ask “what’s next?” and get a pull request back.
needs git · gh (logged in) · a browser tool · worktree, dev and ship scripts · a dev login route
---name: pick-ticketdescription: >- Recommend what to work on next from GitHub issues, or work named issues through to a merged PR inside a parallel worktree. Use when the user says "what should I work on", "what's next", "start working on #N", "work on #N and #M", "pick up <issue>", or opens a conversation in a worktree and names tickets. Implements the fix, adds a regression test, UATs against the instance's own URL and database, then ships and watches CI.---
# Pick and work a ticket
Two modes. **No issues named -> recommend.** **Issues named -> work them.**
Almost every step of the working mode is a script. **Run the script; do notreimplement it.** The scripts own what has an exact right answer: which slot isfree, which release branch is current, what CI said. You own the judgement: thefix, the test, and reading a UAT result.
## Step 0: Load config
Read `.claude/tg.json`:
```jsonc{ "tickets": { "repo": "acme/app", "areaPrefix": "project:" }, "parallel": { "new": "npm run wt:new", // claims a slot, creates worktree + db, assigns + labels the issues "done": "npm run wt:done", // drops db, frees slot, removes worktree and branch "dev": "npm run dev:instance", // serves on this instance's own URL and database "ship": "npm run ship", // checks, rebase, push, PR with Closes #N, watch CI "reset": "npm run db:reset", "instanceFile": ".instance", "login": "/dev-login" }}```
**If `parallel` is missing or incomplete, stop before starting work** and offerto set the repo up so several worktrees can run at once without colliding:
- a deterministic port and hostname derived from the worktree name- a database per instance, created and dropped with the worktree- isolated caches, uploads and any other shared resource (Redis namespace, storage bucket, mail catcher)- a written record in `.claude/tg.json` so this never has to be asked again
Get the user's approval before changing the repo. Without isolation, twoworktrees share a database and quietly corrupt each other's UAT.
---
# Mode A: Recommend
The backlog is GitHub issues. Rank open issues and propose **3-4 independent**tasks, then stop. This is a report: don't write code or edit anything.
gh issue list --state open --limit 100 \ --json number,title,labels,milestone,assignees
Exclude: `blocked`, `in-progress`, anything already assigned, `ideation`,`manual-only`, and `size:xl` (propose splitting those instead).
Rank by, in order: `priority:p0` before `p1` before `p2`; issues in an activemilestone before ones without; bodies that already carry real evidence(file:line, a commit SHA) before thin ones. Those are ready to start, theothers need investigation first.
Prefer a set that touches **different areas**, so the tasks can run in parallelworktrees without conflicting. Say for each: what it is, why now, rough size,and whether it's ready or needs investigation first.
---
# Mode B: Work the tickets
## Step 1: Find your instance
cat .instance
That file is the whole context: `INSTANCE`, `ISSUES`, `BRANCH`, `BASE`, `URL`,`DB`.
**If there is no `.instance` file**, you are not in a prepared worktree. Go tothe primary checkout, run the `parallel.new` command with the issue numbers, andstart again in the directory it prints. Do not `git worktree add` by hand: youwould skip the env files, the database and the slot claim.
`parallel.new` has already assigned the issues to the current GitHub user andlabelled them `in-progress`. Don't do it again.
## Step 2: Read the tickets and the code
gh issue view <N>
for each issue in `ISSUES`. Then read the actual code before planning. Issuebodies here carry real file:line evidence; follow it rather than searching fromscratch.
If the issues need incompatible changes, or one is already fixed on `BASE`, sayso and stop rather than inventing scope.
## Step 3: Prove the bug first
For a bug, write the failing test **before** the fix, and run it to confirm itfails for the reason the ticket describes. A regression test that passes beforeyour change proves nothing.
For an enhancement, write the test alongside the change.
## Step 4: Implement
Match the surrounding code. If several issues share this worktree, make onecommit per issue so the history stays readable.
## Step 5: UAT against your own instance
Run `parallel.dev`. It serves at the `URL` from `.instance` (e.g.`http://wt2.localhost`) with its own database. **Never UAT against`http://localhost`**: that is the primary checkout, someone else's code.
Drive it with the Browser tools. Sign in through the `parallel.login` path, notthe real OAuth provider: real OAuth usually only accepts `http://localhost` as aredirect URI.
Need clean data? `parallel.reset`. Inside a worktree it resets only thisinstance's database.
Check the issue's acceptance criteria actually hold in the browser. If theydon't, fix and repeat. A passing unit test is not a UAT.
When a UAT passes, label the issue `uat-passed` and comment with the commit SHAyou verified. The label means nothing without the commit named.
## Step 6: Ship
Run `parallel.ship`. It runs format/lint/check-types/test, rebases onto thecurrent release branch, pushes, opens a PR with `Closes #N` for every issue, andblocks watching CI.
Read its exit code:
- **0**: green. Tell the user the PR is ready and stop. **Do not merge it.**- **2**: a check or CI failed; the output is already printed. Fix, commit, ship again. After three failed attempts on the same failure, stop and report.- **1**: something needs a decision (rebase conflict, uncommitted changes). Handle it, then re-run.
## Step 7: Stop
Merging is the user's call. When they say it's merged, run `parallel.done`,which drops the instance database, frees the slot, removes the worktree anddeletes the branch. Merging the PR closes the issues by itself; the PR mergingto a release branch is what earns the `in-release` label.
## Things that will bite you
- **The release branch moves.** Other sessions push to it constantly. Ship rebases for you; don't hand-merge it.- **Never run a root build** while a dev server is up: it clobbers the running build output.- **Don't touch files outside your tickets' scope.** Parallel worktrees all land on the same release branch; unrelated edits become someone else's conflict.- **Don't set `in-progress` or `in-release` by hand**: the scripts and the merge own those. `uat-passed` is yours, and only with a commit named.What it does. No issue number? It reads the backlog and suggests three or four tickets that don’t touch the same code. Give it a number and it gets its own git worktree, with its own branch, URL and database. It writes a failing test first, fixes the bug and checks it in the browser. Then a script runs the checks, rebases, opens the PR and waits on CI.
Why. I run several agents at once. If they share a database, they wreck each other’s tests. And it stops at green CI, because merging is my job.
Actually open the browser
Green tests don’t mean it works.
needs your app running locally · a browser tool · curl and python3 for the helper script
---name: uatdescription: >- Get the app into a specific state for manual UAT, and verify a feature's acceptance criteria live in the browser. Use when you need to "see X in the browser", reproduce a failing e2e test locally, check a variant, verify a ticket's acceptance criteria actually hold, or reach a state that takes many UI steps (a guest who already replied, a poll with votes, an order halfway through checkout). Drives the app's control API where one exists, otherwise Playwright.---
# UAT scenario setup
Clicking through the UI to reach a specific state is slow and error-prone. A controlAPI can build the same state in seconds, then you open the resulting URL in a browserand look at the thing you actually care about.
## Step 0: Config and mode
Read `.claude/tg.json`:
```jsonc{ "uat": { "controlApi": "http://localhost:3000/__control", "baseUrl": "http://localhost:3000", "login": "/dev-login" }, "parallel": { "dev": "yarn dev:instance", "instanceFile": ".instance" }}```
**Two modes.** If the repo exposes a control API, drive that: it reaches astate in one call that takes a dozen UI steps. If it doesn't, drive the UI withPlaywright and verify against the issue's acceptance criteria; see`references/verify-acs.md`.
**In a worktree, UAT the worktree.** If `.instance` exists, use its `URL` andits own database, never `http://localhost`, which is the primary checkout andsomeone else's code. If `parallel` is missing from the config and you are in aworktree, stop and offer to set the repo up for parallel instances beforetesting anything.
## What a control API for UAT should offer
You don't need a dedicated service. Most apps already have the pieces; you needenough of them to script a scenario end to end:
- **Log in as a seeded user** without a password or OAuth screen: a dev-only login route that takes a user id or email and sets the session cookie.- **Create the entities a scenario needs** (the parent object, its children, the settings or variant under test) through the same endpoints the UI uses.- **Act as a second user or a guest**: log in as another seeded user, or use whatever token the app issues to anonymous or invited participants. Many scenarios are only interesting once two people have touched them.- **Print the URLs to open**, so the last step of every scenario is "open this page" rather than "now navigate to…".
## Finding it in the repo
1. Look for a dev login: grep the backend routes for `demo`, `dev-login`, `impersonate`, `loginAs`. Note the env flag that enables it and anything else it depends on (a redirect URL, a dev-mode switch that returns verification codes in the response instead of emailing them). A missing flag usually shows up as a 404 or a 500 *after* the cookie would have been set.2. Find the seeded users in the fixture or seed scripts, and their roles. A long-lived dev database drifts from its fixtures: users get renamed, deleted, or have their role changed by hand. If dev login fails, check the user still exists in the database before assuming the endpoint is broken.3. Read the e2e suite's support commands. Whatever helper it uses to log in and create data is the control API, already proven to work.4. Read the route definitions for the entities your scenario needs: required fields, response envelope, which id the next step needs.
Record what you found under `uat` in `.claude/tg.json` so the next session startsthere.
## Check the auth middleware before scripting requests
Roles can resolve the acting user differently. An admin token might take thetarget user from a query parameter while a normal user token rejects that sameparameter, or a guest endpoint might accept either a session cookie or a headertoken. The same request then succeeds for one role and fails for another with anerror that doesn't explain why. Read the auth middleware once before writing anyrequests and encode the rule in the script, so callers never have to think aboutit. Doing this by hand with curl is where most of the wasted time goes.
## The helper script
`scripts/uat.sh` is a starting point: an `api METHOD PATH` wrapper with a cookiejar that fails loudly on HTTP errors, a `jget` JSON helper, and a `login`subcommand. Copy it into the repo and add one subcommand per scenario step.
Each subcommand prints the id or token the next one needs, so steps compose:
```bashU=scripts/uat.sh
$U login <seededUserId> # stores the session cookiePARENT=$($U create "Rooftop Dinner") # -> id$U urls "$PARENT" # pages to open```
Then add a `scenario-<name>` subcommand that chains the steps for a state youreach often, so the whole setup is one command.
Point it at your stack with `UAT_API` and `UAT_WEB` (defaults are localhost).In a worktree, set them from `.instance`.
## Browsing the result
To browse as the logged-in user, hit the dev login in the same browser you openthe printed URLs in, so the session cookie lands on the frontend origin. To browseas an anonymous visitor, use a private window or clear cookies for the frontendorigin.
## Cleaning up
Scenario data is ordinary data. Delete it by id through the API when a devdatabase gets noisy, or reset the instance's database if it has its own.
## Reproducing a failing e2e test
The e2e suite usually drives the same API through its support commands. When aspec fails, read the spec, build its end state with the script, and open the pageto inspect it, rather than re-running the whole suite. Note that this reaches thestate through the API, so it will *not* reproduce UI-timing failures (hydrationraces, click-before-hydrate); it is for inspecting state and rendering, and forconfirming the backend behaves as the test expects.What it does. It gets the app into the exact state you need (logged in, event created, a guest who already said yes) with a single API call. Then it opens the page and checks the ticket’s acceptance criteria one by one. If they pass, it tags the issue uat-passed with the commit it tested.
Why. Unit tests check what you thought to test. Someone still has to open the page and look. Now the agent does that part.
Use the design system you already have
So the app keeps looking like one app.
needs a design system (tokens + components) · a browser tool (optional)
---name: uidescription: >- Design and build UI inside an existing app, following that app's own design system, OR apply that design to a part of the app that already works but looks rough, keeping its functionality, with freedom over UI/UX. Use when the user asks to design/build a screen, section, component, flow, empty state, dashboard, or modal, AND when they ask to "make this look good", "apply the design", "restyle/redesign this page", "polish the UI", or hand you working-but-unstyled code to design later. Discovers the repo's authoritative design system (tokens, component library, conventions) and produces app-native code that reuses it, working in every theme the app supports. Preserves behavior on redesigns and asks when something is ambiguous or would have to change.---
# Design for the app, in the app's design system
You are a senior product designer building real interfaces inside an existingcodebase. Your medium is **that repo's framework + that repo's design system**,not standalone HTML, not ad-hoc CSS, not your own aesthetic. The output must looklike it already shipped in this product.
Whatever design system the repo has is **authoritative: read it, don't reinventit.** Never invent colors, spacing, or components that the system alreadyprovides.
## Step 0a: Check for a recorded design system
Read `.claude/tg.json` first:
```jsonc{ "design": { "system": "packages/ui", "tokens": "packages/ui/src/styles/theme.css", "components": "packages/ui/src/components/base", "themes": ["light", "dark"] }}```
If it's there, read those paths directly and skip the discovery below. If it'smissing, do the discovery, then **offer to write `.claude/tg.json`** with whatyou found so the next session starts here.
## Two modes
1. **Design new**: a screen/section/component that doesn't exist yet. Follow the full workflow at the bottom.2. **Apply the design to existing code**: the user built something that *works* but wasn't styled (or was styled roughly) and wants the design applied now. This is a **presentation-only redesign**, delivered in two phases: first a throwaway **mockup** to nail the look fast, then **apply** the approved look to the real code, reworking layout, hierarchy, and component choices freely, but keeping the feature's behavior exactly as before. This mode is for "I built it without caring how it looks: make it look right." See "Redesign mode" below; the mockup-first deliverable, preservation contract, and ask-first rule are not optional.
The design rules, token usage, component reuse, and anti-slop rules below apply to**both** modes equally.
## Step 0: Find the design system (do this before designing anything)
Never assume the stack. Spend a few minutes establishing the ground truth, then**state what you found** before writing code.
**Framework & conventions**- Read the root `package.json` (and any workspace `package.json`s) for the framework, styling libs, component libs, form libs, i18n libs, and icon libs.- Detect the repo shape: monorepo (`packages/`, `apps/`, `turbo.json`, `pnpm- workspace.yaml`, `nx.json`) vs. single app. In a monorepo, the design system is usually its own package; find it and note its import alias.- Read any `CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md`, `README`, or `docs/` that describes UI conventions. Those instructions outrank this skill's defaults.
**Tokens / theme**: look, in order, for:- CSS custom properties (`:root { --… }` plus theme blocks like `.dark`, `[data-theme]`) in a global stylesheet or a `styles/` folder.- A Tailwind config or `@theme` block (Tailwind v4), a `theme.ts`/`tokens.ts`, a styled-components/emotion/vanilla-extract theme, a Chakra/MUI theme, or design tokens JSON (Style Dictionary).Whatever you find is the palette. Learn the **semantic** names (background,foreground, card, muted, primary, destructive, border, ring…) rather than the rawvalues, so you never write mode-specific colors.
**Components**: find the primitive layer:- A design-system package (`packages/ui`, `packages/design-system`), a local `components/ui/**` (shadcn convention), or a third-party kit (MUI, Chakra, Mantine, Radix, Ant, Vuetify, …).- **Read the actual component you intend to use** for its variant/prop API; don't guess it. Enumerate the real variants and sizes.
**Icons, forms, i18n, data**: identify the icon component or set (and whetherthere's a name registry), the form stack (react-hook-form + a schema lib, Formik,native), the i18n mechanism and where message keys live, and how screens fetchdata (server components, a query lib, loaders).
**Copy the real usage.** When unsure whether a component, variant, or prop exists,grep the app for an existing usage and copy that pattern verbatim:```bashgrep -rn "from '<ui-import-alias>/button'" <app-src> | head -20```
**If the repo has no design system**, say so explicitly, then pick the smallestcoherent basis from what's already there (existing utility classes, an existingtheme object, the most-repeated local patterns), and propose (don't silentlyintroduce) any new dependency.
### Record what you found
Before writing any UI, state a short inventory. Fill in the blanks for this repo:
| Thing | This repo ||---|---|| Framework / router | || Styling approach | || Token file(s) + theme modes | || Component library + import alias | || Icons | || Forms | || i18n | || Where app screens live / file conventions | |
Then name the specific components and tokens you'll use for this piece of work("brand primary CTA, `Card` + `CardHeader`, heading typography component, icon forthe row leads").
## Use the system's semantics, not raw values
- Prefer **semantic** color names (`background`/`foreground`, `card`, `muted`, `primary`, `destructive`, status colors) over literal values or palette ramps, so every theme the app supports works for free.- Use the system's **spacing and radius scale**; don't introduce off-scale values.- Use the system's **typography components or classes** for headings and copy rather than hand-stacked font utilities, if it has them.- Match the **density and rhythm** of neighboring screens: open 1–2 existing screens in the same app area and mirror their container widths, gaps, and section structure.
## Compose existing components, don't restyle primitives
- Every interactive element should be an existing component with a real variant. Never hand-roll a styled `<button>` when the system exposes a button variant that does it.- Pass the props the component already has (a card header that lays out icon + title + actions takes props; don't re-implement its internals).- Use the system's icon mechanism. **Never** use emoji or paste raw `<svg>` for iconography when an icon component/set exists.- Reuse badges, inputs, selects, tabs, dialogs, sheets, tooltips, avatars, separators, skeletons, tables, and form wrappers the same way.- If a needed component genuinely doesn't exist, build it **out of** the system's primitives and tokens, in the place the repo puts local components, and say that you did.
## App-native rules
- **Match the framework's boundaries.** In React Server Component apps, default to server components and add `'use client'` only when the piece needs state, effects, or handlers. In other stacks, follow that stack's equivalent split (islands, loaders/actions, container vs. presentational).- **Match file conventions** already in the app: route folder shape, where local components live, naming, index/barrel usage, test co-location.- **i18n.** If the app is localized, pull copy through the same mechanism as neighboring screens and add keys where they belong. Don't hardcode user-facing strings if the surrounding code doesn't.- **All themes for free.** Because you only use semantic tokens, light/dark (and any other mode) work automatically. Never write theme overrides with literal colors, and never hardcode a value that would break one mode.- **Responsive.** Match the app's breakpoints and its primary device. For consumer apps assume mobile-first; keep hit targets ≥ 44px.- **Real content.** Use the product's real domain nouns and plausible real data. No lorem ipsum, no invented stats, no placeholder sections.
## Anti-slop
- ❌ Raw hex / arbitrary color utilities when a semantic token exists.- ❌ Re-styled primitives when a system component exists.- ❌ Emoji or inline SVG as icons when the app has an icon component.- ❌ A rounded card with a colored left border; over-shadowed floating boxes; gratuitous gradients the system doesn't sanction.- ❌ Three competing accents. The brand accent is the accent; use it with restraint, status colors only for status.- ❌ Generic dashboard filler: fake sparklines, "Total Users 12,847", a settings section nobody asked for.- ✅ Add one decisive, product-appropriate detail per screen (a smart empty state, a meaningful count, a well-chosen icon) that shows someone used the app.
## Redesign mode: apply the design, keep the functionality
The premise: the user shipped working logic and deferred the looks. Your job is tomake it look like it belongs in the product **without changing what it does**. Youhave real freedom on UI/UX (layout, hierarchy, spacing, which components expressit, grouping, empty/loading/error states, copy polish), but behavior is frozen.
### The deliverable: mockup first, then apply
Redesign is delivered in **two phases** so the look is agreed before any real codechanges:
**Phase 1: throwaway mockup.** Build a standalone `index.html` in a scratchlocation that never ships: a gitignored folder like `.design/<feature>/`, or yourscratchpad directory. (If you create `.design/`, confirm it's gitignored or addit.) It exists purely to iterate on the look fast, with none of the app's wiring inthe way. Make it read like the real app:- **Inline the app's real tokens**: copy the theme variable blocks (`:root`, dark block, etc.) from the app's token file into a `<style>` block and drive all colors/radii from them (`var(--primary)`, `var(--muted-foreground)`, …); never fresh hex. Add a toggle for each theme mode the app supports. If the tokens aren't CSS variables (a JS theme object, Tailwind config), transcribe the resolved values into CSS variables once, at the top, and use only those.- **Load the app's real fonts** (one web-font link is fine).- **Approximate the system's components** in plain HTML/CSS: a button styled like the real button, a card like the real card. It's a visual stand-in, not real code: fidelity of *look*, not of implementation.- Fill it with the feature's real content and every state (empty, loading, error, dense, long-text).- Iterate here until the user approves the direction. This is the fast feedback loop; nothing in the app has changed yet.
**Phase 2: apply to the real code.** Once the look is approved, port it into theactual app: restyle the real components with the system's components + semantictokens to match the approved mockup, under the preservation contract below. Themockup is the visual spec; the shipping code is the deliverable. Leave the mockupas a reference or delete it: the user's call.
The **shipping deliverable is the edited real files** (a reviewable diff whereevery changed line is presentation). The mockup is a disposable design aid, not theproduct.
### Preservation contract (do NOT change these)
- **Data & logic:** API/query/mutation calls, hooks, state, effects, event handlers, validation, computed values, sorting/filtering, side effects.- **Contracts:** component props and their types, exported names, function signatures, context/provider wiring, route paths and params, URL/search state.- **Forms:** field names, form-library registration, schema/resolver, submit handlers: reskin the fields (swap to the system's form components) but keep every field wired to the same name and the same submit path.- **i18n:** existing translation keys and message wiring. Restructure layout, not the translation contract.- **Semantics & a11y:** keep (or improve) roles, labels, `aria-*`, focus order, and keyboard behavior. Never regress accessibility for looks.- **Tests:** selectors the tests rely on (`data-testid`, roles, accessible names). If a rework would break a test hook, keep the hook, or flag it.
You MAY: restructure markup for layout, replace raw/unstyled elements with systemcomponents, move presentational markup into local component files, adjust classesto semantic tokens, add skeletons/empty states, and refine microcopy, as long asevery input, action, and output stays wired to the exact same logic.
### How to work a redesign
1. **Read the whole target first** and inventory what it does: every interactive element and the handler/state behind it, every data source, every form field, every route/prop, every UI state. Note the behavior you must preserve out loud.2. **Phase 1: mockup.** Build the throwaway mockup with the app's tokens and fonts inlined, reproducing the feature's screens and states in the design system. Iterate on the look and **get the user's approval** before touching app code.3. **Map old → new** at the element level against the approved mockup: this `<button onClick={x}>` becomes `<Button variant onClick={x}>`; this raw input becomes the bound form field with the same name. The wiring column never changes.4. **Phase 2: apply.** Rebuild the real presentation to match the mockup, keeping handlers/props/data attached to the same elements.5. **Diff-check behavior:** confirm no handler, prop, key, name, route, test hook, or data path was dropped or renamed. If you had to touch logic to make a layout work, stop: that's a signal to ask (below), not to proceed.
### Ask first, don't guess (applies especially in redesign mode)
Pause and ask the user when:- Applying the design would **require changing behavior, structure, or a contract** (prop shape, route, form field name, data flow) to look right.- The **existing behavior is unclear or looks buggy**: surface it, don't silently "fix" it as part of restyling.- The design system **has no component** for what the UI needs, or two reasonable design directions exist and the choice affects UX materially.- **Scope is ambiguous:** a light reskin vs. a full re-layout, or how far to take the freedom on a given screen.- Required **data/props/copy are missing** to render the intended design.- A change would need a **new dependency** or a change to the shared design system.
Ask concise, specific questions with a recommended default. Don't block on triviayou can decide from the surrounding code; reserve questions for real forks.
## Workflow
1. **Understand + confirm.** For a new or ambiguous ask, confirm in one or two lines: what surface (page / section / component / flow), which app area, the audience, and any constraint. Skip for small in-place tweaks.2. **Survey the system.** Do Step 0 and state the inventory + the components and tokens you'll use. Open 1–2 existing screens in the same app area to match layout density, spacing, and conventions.3. **Plan.** For anything beyond one component, lay out the section/screen list and the components each will reuse before writing files.4. **Build.** Write real files in the app, composing the system's components. Show structure early. Keep components focused; extract local pieces where the repo puts them.5. **Self-check (all must pass):** - [ ] Zero hardcoded colors; only the system's semantic tokens. Reads correctly in every theme mode the app supports. - [ ] Every interactive element is an existing system component with a real variant; icons use the app's icon mechanism; headings/copy use the app's typography layer. - [ ] Framework boundaries are right (client/server, islands, loaders). - [ ] Responsive at the app's real breakpoints; hit targets ≥ 44px. - [ ] Real copy, localized the way neighboring screens are; density matches them. - [ ] Keyboard + screen-reader sane: labels, focus states, focus order. - [ ] **Redesign mode only:** behavior parity. Every handler, prop, key, form field name, route, test hook, and data path is unchanged; a11y kept or improved; nothing in the preservation contract was touched.6. **Verify.** Run the repo's own checks on the files you touched (typecheck, lint, relevant tests; use the scripts in `package.json`, don't invent commands) and fix anything you introduced. If a dev server is available, load the screen and eyeball every theme mode.7. **Summarize briefly:** what you built, which system components/tokens it reuses, and where the files live.What it does. Before writing any UI, it finds your tokens, components and icons, and tells you what it found. Then it builds with those and checks light mode, dark mode and mobile. If you’re restyling something that already works, it mocks it up first and leaves the logic alone.
Why. Left alone, agents hardcode hex colours and rebuild your Button from scratch. It also keeps a list of AI-looking UI it won’t do: gradient cards, a coloured stripe down the side, three accent colours fighting each other.
Keep comments honest
For comments that argue with code that isn’t there anymore.
needs your typecheck and test scripts
---name: cleanupdescription: >- Check whether existing code comments still describe the code, or only the road that led to it. Use when the user asks to "check comment health", "review the comments", asks whether a comment "belongs here" or "describes this file", says a comment reads oddly or defensively, or after a refactor when comments written during the work are still in place. Catches comments that argue with deleted alternatives, state rules belonging on the type they describe, or have gone stale against the code around them.---
# Comment health
Comments rot differently from code. Code that no longer works fails a test;a comment that no longer describes anything just sits there being believed.This skill is for reading comments the way a stranger would: someone whonever saw the version being argued with.
## The one test
**Does this comment describe the code, or the road to the code?**
A comment written during a fix tends to argue with the thing being fixed.That is right in a commit message, where the before-state is in the diffbeside it. It is misleading in a file, where it is not: the reader sees adefence of a decision against an alternative they have never encountered,and has to reconstruct a history they do not have in order to parse asentence about the present.
The commit message is where the road goes. The file gets the destination.
## Symptoms, in rough order of how often they show up
**Ghost arguments.** The comment defines the code by what it is not:"rather than X", "not a Y sitting on top", "X was never required here","and not merely to avoid Z". If X is not visible from the file, the readeris being argued at about nothing. Ask: would this sentence survive someonewho has never seen X? If not, rewrite it to state what the code does, andkeep the alternative ONLY if a reader is likely to reach for it themselves(then it is a trap warning, which is forward-looking and earns its place).
The distinction is worth holding precisely:- *"`background-blend-mode`, not `mix-blend-mode`, because we had a wrapper element before"*: ghost. The wrapper is gone.- *"`background-blend-mode` blends only a box's own layers, where `mix-blend-mode` would reach past the element"*: trap warning. `mix-blend-mode` is what someone would plausibly switch to.
**History voice.** "used to", "once", "any more", "was the mistake", "beforethis". Reliable tells. Some of these are load-bearing (a migration note ona schema boundary genuinely needs to say what the old shape was), but mostare the author talking to their past self.
**Stacked blocks.** Two comment blocks in a row with no code between themalmost always means two moments in time, neither reconciled with the other.Read them as a pair and ask what one thing they are trying to say.
**Misplaced rules.** A comment at a call site that states a rule about thewhole system. Rules belong on the thing they constrain (the type, thefield, the schema), where they are found by anyone who touches it and wherethey cannot silently disagree with a second copy. What stays at the callsite is only the part that call site decides.
**Staleness.** The nastiest ones reference an escape hatch or a field thatwas removed, often in the very commit that wrote the comment. Grep anyidentifier a comment names. If it does not exist, the comment is lying.
**Density.** Count the lines that carry information someone would need tochange the code safely, against the total. Thirteen lines carrying four is arewrite, not a trim.
## How to run one
1. **Read the file whole.** Comments are judged in context; a paragraph that reads fine alone can be redundant with the one above it.2. **For each comment, ask in order:** - Is it TRUE of the code as it stands right now? - Does it belong at this line, or on the type/field it describes? - Would it survive a reader who has never seen what it argues against? - What fraction of it is load-bearing?3. **Verify before deciding.** Grep identifiers it mentions. Check whether the rule is already documented on the type. Do not trust the comment's own account of the code; read the code.4. **Rewrite, do not just delete.** Most bad comments have a real fact buried in them. Lead with what the code does; keep the *why* only where it stops someone breaking it.5. **Run typecheck and tests anyway.** Comment edits are usually safe, but these passes tend to travel with small code changes, and it is cheap.
## What good looks like
Lead with the fact. Follow with the constraint that is not visible from thecode. Stop.
```/* * The grain is one of the surface's own background layers, multiplied over * its fill. Two CSS facts hold this up: a background clips to the box's * `border-radius` for free, so a rounded card keeps its paper inside the * curve with nothing extra; and `background-blend-mode` blends only a box's * own layers, where `mix-blend-mode` would reach past the element and * composite against the whole page behind it. */```
Nothing there describes a previous attempt. The `mix-blend-mode` clausesurvives because it is the property a reader might reasonably switch to,not because it is what the code used to use.
## Watch yourself
The failure mode of this review is performing it and reproducing it: therewrite comes out shorter and still argues with the ghost. After rewriting,read your own version against the same test before moving on.What it does. It reads your comments like someone new to the codebase. It flags the ones that mention deleted code, defend a choice against an alternative nobody can see, or put a rule in the wrong place. Then it rewrites them to describe what the code does now.
Why. Broken code fails a test. A wrong comment just sits there and gets believed. Agents are really good at leaving those behind after a refactor.
Fix the code that’s painful to change
Find where it hurts, then fix the shape of it.
needs gh (logged in) · Claude Code sub-agents
---name: improve-architecturedescription: Explore a codebase to find opportunities for architectural improvement, focusing on making the codebase more testable by deepening shallow modules. Use when user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more AI-navigable.---
# Improve Codebase Architecture
Explore a codebase like an AI would, surface architectural friction, discover opportunities for improving testability, and propose module-deepening refactors as GitHub issue RFCs.
A **deep module** (John Ousterhout, "A Philosophy of Software Design") has a small interface hiding a large implementation. Deep modules are more testable, more AI-navigable, and let you test at the boundary instead of inside.
## Process
### 1. Explore the codebase
Use the Agent tool with subagent_type=Explore to navigate the codebase naturally. Do NOT follow rigid heuristics. Explore organically and note where you experience friction:
- Where does understanding one concept require bouncing between many small files?- Where are modules so shallow that the interface is nearly as complex as the implementation?- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called?- Where do tightly-coupled modules create integration risk in the seams between them?- Which parts of the codebase are untested, or hard to test?
The friction you encounter IS the signal.
### 2. Present candidates
Present a numbered list of deepening opportunities. For each candidate, show:
- **Cluster**: Which modules/concepts are involved- **Why they're coupled**: Shared types, call patterns, co-ownership of a concept- **Dependency category**: See [REFERENCE.md](REFERENCE.md) for the four categories- **Test impact**: What existing tests would be replaced by boundary tests
Do NOT propose interfaces yet. Ask the user: "Which of these would you like to explore?"
### 3. User picks a candidate
### 4. Frame the problem space
Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate:
- The constraints any new interface would need to satisfy- The dependencies it would need to rely on- A rough illustrative code sketch to make the constraints concrete. This is not a proposal, just a way to ground the constraints
Show this to the user, then immediately proceed to Step 5. The user reads and thinks about the problem while the sub-agents work in parallel.
### 5. Design multiple interfaces
Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module.
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category, what's being hidden). This brief is independent of the user-facing explanation in Step 4. Give each agent a different design constraint:
- Agent 1: "Minimize the interface: aim for 1-3 entry points max"- Agent 2: "Maximize flexibility: support many use cases and extension"- Agent 3: "Optimize for the most common caller: make the default case trivial"- Agent 4 (if applicable): "Design around the ports & adapters pattern for cross-boundary dependencies"
Each sub-agent outputs:
1. Interface signature (types, methods, params)2. Usage example showing how callers use it3. What complexity it hides internally4. Dependency strategy (how deps are handled, see [REFERENCE.md](REFERENCE.md))5. Trade-offs
Present designs sequentially, then compare them in prose.
After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated: the user wants a strong read, not just a menu.
### 6. User picks an interface (or accepts recommendation)
### 7. Create GitHub issue
Create a refactor RFC as a GitHub issue using `gh issue create`. Use the template in [REFERENCE.md](REFERENCE.md). Do NOT ask the user to review before creating. Just create it and share the URL.What it does. It walks the codebase and notes where it hurts: one idea spread over ten files, modules whose interface is as big as what they do, seams with no tests. You pick one. Three or more sub-agents each design a different interface for it, it recommends the best one and files it as a GitHub issue.
Why. Deep modules, as John Ousterhout calls them, hide a lot behind a small interface. They’re easier to test, and much easier for an agent to find its way around.
Copy that doesn’t sound like a bot
Blog posts and marketing pages, in English and Greek.
needs git · python3 · a native speaker for Greek
---name: copydescription: >- Write, rewrite, or translate blog posts, landing pages and marketing copy, in English and Greek. Use when the user asks to "write a blog post", "add a blog entry", "write landing page copy", "write marketing copy", or to punch up existing copy. Detects whether the site ships Greek and produces that version too, written natively rather than translated. For reviewing Greek copy that already exists, use greek-review instead.---
# Blog & marketing copy
You are writing product marketing copy, not running a general SEO content mill.A product page sells one thing (a plan, a feature, a use case) to one personmaking a real decision. Every piece of copy should read like it was written bysomeone who has actually lived the problem the product solves, not like theoutput of a content calendar.
Read these on demand, not all up front:
- [references/voice.md](references/voice.md): the house voice. Read this first, every time. It is the single most important file in this skill.- [references/structure.md](references/structure.md): title, meta description, heading, paragraph, and CTA structure.- [references/editorial-checklist.md](references/editorial-checklist.md): the pass before you call a draft done. AI-slop patterns, banned words, the no-em-dash rule, a short quality checklist sized for a small product blog (not a 100-point SEO scoring machine).- [references/translation.md](references/translation.md): EN<->EL translation rules and the Greek cultural-adaptation profile.- [references/greek.md](references/greek.md): the rules for Greek copy that doesn't read as translated, and the pass for fixing Greek that does.- [references/greek-learnings.md](references/greek-learnings.md): a running log of real Greek corrections, not a predicted rulebook. Skim it before writing Greek copy, append to it after every human correction. This is the actual mechanism that improves Greek quality over time; the written rules in `translation.md` only get partway there.
## Step 0: Languages and content locations
Read `.claude/tg.json`:
```jsonc{ "copy": { "locales": ["en", "el"], "contentDir": "path/to/messages" } }```
If it's missing, detect it: an i18n config, a `locales/` or `messages/`directory, framework i18n settings, or existing translated content. Find whereblog posts live (a content directory, an MDX folder, a TypeScript data file)and where marketing-page strings live (usually one message file per localeunder `contentDir`). Confirm what you found before writing, and offer to recordit in `.claude/tg.json`.
**If the site ships Greek, write the Greek too.** Not as a translation passafterwards, but as its own piece, following `references/greek.md`. A Greek pagethat reads as translated English is worse than no Greek page.
## Where content lives
- **Blog posts**: wherever the repo keeps them (see Step 0). If a post has a locale-independent structure (slug, dates, related posts) with per-locale copy objects, **never let the locale objects drift in structure**: same number of sections, same presence or absence of callouts and lists, different words only.- **Marketing page copy** (pricing, feature and use-case landing pages, footer, etc.): the per-locale message files under `copy.contentDir`. Keep every locale's keys in lockstep: every key added to one gets added to the others, same nesting, same interpolation placeholders (`{limit}`, `{name}`, etc.).- Check the repo's own house rules (CLAUDE.md, contributing docs, existing lint rules for copy) before writing, and don't contradict them.
## Workflow
1. **Read 2-3 existing posts or the page you're extending first.** Don't write from the schema alone; the voice only comes through in examples. If the content lives on another branch, `git show <branch>:<path>` works without checking it out.2. **Draft one locale first, but never "translate" the second one sentence by sentence.** For any scene-setting copy (intros, quoted messages, anything with a concrete image), read the first draft once for the beat it hits, then close it and write the other language's version from scratch, imagining the scene natively rather than rendering the sentences. A real shipped Greek intro read as calque ("buried under dog photos" translated literally, English clause order, formal "πρόκειται να" register in casual copy) precisely because it was produced by translation instead of native drafting. See [references/translation.md](references/translation.md)'s calque case study before writing any Greek scene-setting copy.3. **Run the editorial checklist** in [references/editorial-checklist.md](references/editorial-checklist.md) before presenting the draft.4. **Ask before inventing product facts.** Pricing, limits, plan names, and feature availability must come from the actual code (the pricing page, the payments catalog, the feature itself) or from the user, never guessed. A blog post that states a wrong price is worse than no blog post.5. If adding a new blog post and the blog has related-post links, wire the new post into 1-2 existing posts too. Posts don't get "Read next" links for free.6. **Greek requires a human pass, and not a same-breath one.** The model's own sense of natural Greek register is not reliable enough to self-certify. That's precisely the failure mode documented in `translation.md`'s calque case study: grammatically correct Greek that no native speaker would actually write, and the model didn't catch it, twice. Present new or changed Greek copy to the user (or another native speaker) before treating it as done. If reviewing your own Greek draft without the user in the loop yet, do it as a genuinely separate pass (see `greek-learnings.md`'s "fresh eyes" note), not immediately after writing it in the same turn. Self-review right after drafting is demonstrably unreliable here.
## What this skill deliberately does not do
No keyword-density targets, no 100-point SEO scoring rubric, no citation-tierbibliography, no schema/JSON-LD generation, no fabricated statistics withfake sources. This is for a small product blog run by a small team, not anenterprise content operation; match the effort to the venue. If a claim needsa statistic, use one you can actually verify or cut it.
## Attribution
The structural guidance in `references/structure.md` and`references/editorial-checklist.md` was adapted (not copied verbatim) fromthe MIT-licensed [`AgriciDaniel/claude-blog`](https://github.com/AgriciDaniel/claude-blog)skill suite, trimmed to what fits a small product blog, with the scoringapparatus and enterprise SEO machinery removed. The Greek locale profile in`references/translation.md` is original; the source repo had no Greekprofile.What it does. It reads your existing posts first to get the voice. It bans the usual suspects (“seamless”, “unlock”, “dive into”) and em dashes, wants one concrete detail per paragraph, and won’t make up prices. Greek gets written from scratch, in Greek.
Why. Default LLM copy all sounds the same. You can spot it in one line.
Greek that doesn’t sound translated
For Greek UI that reads like Google Translate.
needs python3 · translation JSON files · your test runner
---name: greek-reviewdescription: >- Review and fix existing Greek UI copy so it reads like something a Greek person would actually say: accented capitals, Title Case, formal-plural verbs, translationese error strings, inconsistent terminology. Use whenever the user says the Greek "sounds silly", "sounds translated", "sounds like a Greek platform", or asks to review/fix the Greek on a screen, a section, or the whole app. For writing new Greek copy from scratch, use copy instead.---
# Greek copy pass
Greek locales are usually written by translating the English one string at atime. That produces text that is grammatical and still wrong: nobody says it outloud. This skill is the correction pass. It is a **copywriting** job with amechanical safety net, not a find-and-replace.
The test for every string: **would I type this to a colleague?** If not, rewrite it.
## Step 0: Find the locale files
Read `.claude/tg.json`:
```jsonc{ "copy": { "locales": ["en", "el"], "contentDir": "messages" } }```
If it's missing, detect it: an i18n config, a `locales/` or `messages/`directory, or existing translated content. Confirm what you found, and offer torecord it in `.claude/tg.json`. Below, "the Greek messages file" means`<contentDir>/el.json` (or whatever format the repo uses).
## The eight rules
Apply all of them; most bad strings break two or three at once.
### 1. Capitals carry no tonos
Greek drops the tonos when a word is set in capitals: `ΑΜΟΙΒΗ`, never `ΑΜΟΙΒΉ`.The diaeresis stays (`ΠΡΟΪΟΝ`).
CSS `text-transform: uppercase` only applies that rule where the browserimplements Greek case mapping, so accented capitals can leak through. For textthat is displayed in capitals, use a Greek-aware uppercase helper that strips thetonos and keeps the diaeresis. If the repo has one, find where it is already wiredin (an eyebrow or label component often is) so you don't double-wrap. For ahand-rolled `uppercase` class, keep the class (it is the Latin path) and wrap thestring. The helper should return non-Greek text untouched, casing included, soEnglish labels and tests are unaffected. If the repo has no such helper, say soand propose adding one rather than fixing capitals string by string.
### 2. Sentence case, not Title Case
`Ειδικές Υπηρεσίες` is English typography wearing Greek words. Write`Ειδικές υπηρεσίες`. Proper nouns and document names keep their capitals(`Όροι Χρήσης`, `Πολιτική Απορρήτου`, the product's own name, `Google`, `Stripe`,plan names like `Pro` and `Team`).
### 3. One voice: second person singular
The app talks to one person as `εσύ`. Kill every formal plural (`Δοκιμάστε`,`Επιλέξτε`, `Διαχειριστείτε`, `Εισαγάγετε`, `τη στρατηγική σας`), including inthe admin area, which is where it hides longest.
### 4. Errors name what happened
Never `Δεν ήταν δυνατή η <ουσιαστικό>`, never `Αποτυχία <γενική>`. Name the thingand what it did: `Το αρχείο δεν ανέβηκε.`, `Η πρόσκληση δεν στάλθηκε.`,`Ο βοηθός δεν απάντησε.` Keep `Δοκίμασε ξανά.` as its own short sentence whenretrying is the actual next step.
### 5. Success messages state the fact
`Η τοποθεσία προστέθηκε`, not `Η τοποθεσία προστέθηκε με επιτυχία`. Nobody adds"with success" when telling you something worked.
### 6. Verbs for actions, nouns for things
A control the reader operates gets a verb: `Καθάρισε τα φίλτρα` over`Καθαρισμός όλων των φίλτρων`, `Κάλεσε μέλος` over `Πρόσκληση μέλους`,`Ψάξε αρχεία` over `Αναζήτηση αρχείων`. A column header or a section name stays anoun.
### 7. One word per concept, everywhere
Pick the word a Greek would use and never alternate. Keep a table like this forthe product's own nouns:
| concept | use | not ||---|---|---|| consultation / booking | `ραντεβού` | `συμβουλευτική` || upload | `ανέβασμα`, `Ανεβαίνει…` | `μεταφόρτωση` || loading | `Φορτώνει…` | `Φόρτωση...` || ellipsis | `…` | `...` |
### 8. The sentence has to hold together as Greek grammar
Greek is inflected and uses articles with proper names, so a line that is fineEnglish can be broken Greek even when every word is right. Read the wholesentence, including the words that sit on another line or come from a slot.
- **A proper name in running text takes its article.** `Η Δανάη και ο Άρης παντρεύονται`, never `Δανάη & Άρης παντρεύονται`. Bare names are only right when they stand alone, as a heading or a signature.- **A slot the user fills (a name, a venue) cannot be declined or given an article.** Nobody types `της Δανάης`. So never write copy whose grammar depends on the slot: no third-person verb with the slot as subject (`{names} σας προσκαλούν`), no genitive of it (`στον γάμο της {name}`), no sentence that runs into it (`…τον γάμο των` + names). Write around it: first person with the names as a signature (`Παντρευόμαστε!`, `Σας προσκαλούμε στον γάμο μας`), or a line that stands on its own.- **Agreement survives line breaks.** A verb on one line agrees with a subject on another; `μας`/`τους` must match who is speaking (`Μαζί με τις οικογένειές μας` once the card speaks in the first person).- **Do not carry over an English fragment that leans on what follows.** `to celebrate the marriage of` + names has no Greek equivalent that keeps the names in the nominative; rewrite the line so it is complete by itself.
**Invitations are the exception to rule 3.** Copy that the user sends to their ownguests (an invitation card, say) keeps the formal plural (`Σας προσκαλούμε`,`Παρακαλούμε απαντήστε`): that is the register of printed stationery, not app UI.
## Also cut
- **Filler that says nothing.** `Προσφέρουμε εξειδικευμένες λύσεις για κάθε ανάγκη` over two concrete cards: say what the two cards are.- **Copy that repeats its own heading.** A caption under "Ειδοποιήσεις Email" reading "Ρύθμισε τις προτιμήσεις ειδοποιήσεων email σου" is a wasted line; say when the thing actually fires.- **Legal hedging in product UI.** `Αυτή η ενέργεια δεν μπορεί να αναιρεθεί` → `Δεν γυρίζει πίσω.` (Actual legal pages keep their text; see Scope.)- **Length.** If a sentence wraps to three lines in the UI, it is two sentences or half as long. Check it rendered, not just in JSON.
## Watch out
- **No apostrophes inside a string if you can avoid it.** `Ναι, σβήσ' τον` is fine Greek but breaks a single-quoted assertion in a test file. Prefer `Ναι, διάγραψέ τον`.- **Preserve ICU placeholders and plural forms exactly**: `{count, plural, one {...} other {...}}`, `{firstName}`, `{amount}`. Rewrite the words around them.- **Gendered words.** The reader may be any gender: prefer `Καλώς ήρθες` over `Έτοιμος να ξεκινήσεις;`. Where the existing copy uses `ο/η`, keep it.- **SEO metadata is read by people too.** Titles and descriptions get the same pass, not crawler-speak.
## Scope
In scope: the Greek messages file, Greek hardcoded in frontend components, andGreek user-facing strings sent from the backend (verification codes, emails).
Leave alone: the bodies of legal pages such as privacy and terms (legal text; onlytheir heading casing), seeded/demo data, and anything the user has flagged as aseparate known issue.
## How to run it
1. **Map the surface.** Read the relevant sections out of the Greek messages file: `python3 -c "import json; d=json.load(open('<contentDir>/el.json')); print(json.dumps(d['<section>'], ensure_ascii=False, indent=1))"`. Also `grep -rl "[α-ωάέήίόύώ]" <frontend src>` for Greek hardcoded in components.2. **Rewrite a whole area at a time**, not scattered keys: a page reads as a unit and its terminology has to agree with itself. Edit the JSON with a small python script that loads with `object_pairs_hook=collections.OrderedDict`, `.update()`s the keys, and writes back with `json.dumps(..., ensure_ascii=False, indent=2)` plus a trailing newline, so key order and the rest of the file stay untouched.3. **Run the tests that assert on copy**, using the repo's own test script. Tests assert on Greek strings, so failures are expected and are your inventory of what the change touched. Update each assertion to the new copy; a failure that is *not* about your strings is a real break, so stop and look.4. **Look at it rendered.** Open the pages you touched in the running dev app (in a browser tool: navigate, then read the page text). This is where you catch sentences that are too long, capitals that kept their tonos, and English strings nobody ever localized.5. **Check parity when keys change.** Adding or deleting a key means doing the same in the English file; verify both files have identical key sets before committing.6. **Sweep for regressions across the whole locale** before you finish:
```bash python3 - <<'PY' import json, re d = json.load(open('<contentDir>/el.json')) def walk(o, p=''): if isinstance(o, dict): for k, v in o.items(): yield from walk(v, p + '.' + k) else: yield p, o pats = { 'formal plural': r'\b(Δοκιμάστε|Επιλέξτε|Προσθέστε|Συμπληρώστε|Πατήστε|Επικοινωνήστε|Ελέγξτε|Δείτε|Κάντε|Γράψτε|Ανεβάστε|Στείλτε|Διαχειριστείτε|Επισκεφθείτε|Εισαγάγετε|Επισημάνετε|σας)\b', 'translationese': r'Δεν ήταν δυνατ|Αποτυχία|με επιτυχία', 'dot ellipsis': r'\.\.\.', 'old terms': r'συμβουλευτικ|Μεταφόρτωση|Φόρτωση', } for name, pat in pats.items(): hits = [(k, v) for k, v in walk(d) if isinstance(v, str) and re.search(pat, v)] print(f'{name}: {len(hits)}') for k, v in hits[:20]: print(' ', k, '=', v[:100]) PY ```
Every count should be zero when the pass is done (invitation copy excepted, per rule 3). Adjust `old terms` to match your terminology table.7. **Commit per area**, not one commit for everything (profile, home, settings, admin…). The commit message says what was wrong with the old Greek and quotes one or two before/after pairs: that is what makes the next pass possible.
## Where the work goes
Follow the repo's branch convention. If the checkout is shared, another sessionmay be editing the same files, the Greek messages file in particular. Stage**your own paths explicitly** (never `git add -A` or a broad directory) andre-read the messages file from disk immediately before writing it.
---
**Scope:** this skill reviews and fixes Greek copy that already exists. To writenew Greek copy (a blog post, a landing page, marketing text), use `copy`, whichshares these rules.What it does. It goes through every Greek string: no accents on capitals, sentence case, the informal “you” everywhere, and error messages that say what actually broke. Anything else gets one test: would you type this to a colleague?
Why. Translate the English one string at a time and you get Greek that’s correct and that nobody actually says.
How’s the site doing?
One question covers all three dashboards.
needs gcloud · a python3 venv · GA4 service account · Search Console access · google-ads.yaml
---name: growthdescription: >- Query Google Analytics 4, Search Console, and Google Ads for a configured site, or run the full growth review that combines all three. Use when the user asks about traffic, sessions, funnels, key events, indexing, coverage, crawl errors, search queries, impressions, campaign or keyword performance, conversions, ad spend, or asks a broad question like "how are we doing", "what's working", "why did traffic drop". Read-only reporting.---
# Growth
Four data sources behind one entry point. **Ask which one before runninganything** unless the request already names it.
> Which do you want?> 1. **Analytics** (GA4): sessions, funnels, key events, custom dimensions> 2. **Search Console**: indexing, coverage, queries, crawl errors> 3. **Ads**: campaigns, keywords, conversions, spend> 4. **Full growth review**: all three, read together. What's working, what's> broken, what to do next
A request that clearly names its source ("did the sitemap get indexed") skipsthe question. A vague one ("how's the site doing") does not. Ask, because thefull review is much slower than a single lookup and often isn't what's wanted.
## Step 0: Load config
Read `.claude/tg.json`:
```jsonc{ "growth": { "site": "example.com", "gcpProject": "my-gcp-project", "ga4Properties": { "production": "000000000", "staging": "", "localhost": "" }, "gscSiteUrl": "sc-domain:example.com", "gscSiteUrlEncoded": "sc-domain%3Aexample.com", "adsManagerCustomerId": "0000000000", "adsCustomerId": "0000000000", "credentials": { "ga4Key": "/path/to/ga4-service-account.json", "adsYaml": "/path/to/google-ads.yaml" } }}```
If it's missing, try to derive it: the site from the git remote or the app'sown config, the property and customer IDs from whatever credentials are alreadyon the machine. Show what you found and confirm before querying. If you can'tderive it, ask, and offer to write `.claude/tg.json`.
**Never guess a property or customer ID.** Querying the wrong property returnsconfident, wrong numbers.
The reference files use `{{growth.*}}` placeholders throughout. Resolve every onefrom config before running a command; if a key is absent, ask rather thansubstituting a plausible value.
**Credentials stay in files.** The config names *paths* to the GA4 serviceaccount key and the Ads `google-ads.yaml`; those files hold live tokens with realspend behind them. Never print their contents or any individual field into chator logs, and keep them out of the repo.
## Then read the source guide
- Analytics -> `references/analytics.md`- Search Console -> `references/search-console.md`- Ads -> `references/ads.md`- Full review -> `references/review.md` (which uses the other three)
Each carries the auth setup, the query shapes, and the traps specific to thatAPI.
## Reporting
Report what the data says, including when it says nothing useful. A flat week isa finding. Sampling, thresholded rows, and `(not set)` buckets are real limits:name them rather than reporting around them. Never present a modelled orextrapolated figure as measured.What it does. It queries Google Analytics, Search Console or Google Ads, or all three at once for a “what’s working, what’s broken, what next” review. It never changes anything. It asks which one you want first, because the full review is slow.
Why. Each dashboard shows part of the picture. And it never guesses an account ID, because the wrong ID gives you confident, wrong numbers.
One config file per repo
The skills don’t know anything about your repo. That lives in .claude/tg.json: where your design system is, what your labels are called, which scripts create a worktree. If something’s missing, the skill works it out from the code and asks before saving it.
{ "design": { "tokens": "packages/ui/tokens.css", "themes": ["light", "dark"] }, "tickets": { "repo": "you/your-app", "areaPrefix": "project:" }}Want your team’s agents working like this? Let’s talk.