Copy audit and rewrite for any marketing page. Visits the live page, evaluates copy quality against a structured framework, identifies weak spots, and rewrites with atomic commits and before/after browser verification. Use when asked to "write copy", "improve this copy", "rewrite this page", "the copy is weak", or "make this more compelling". Proactively suggest when the user has a live page with conversion issues traceable to messaging. For email copy, see email-sequence. For editing existing copy, see copy-editing. For page-level conversion optimization beyond copy, see page-cro.
_UPD=$(~/.claude/skills/vstack/bin/vstack-update-check 2>/dev/null || .claude/skills/vstack/bin/vstack-update-check 2>/dev/null || true)
[ -n "$_UPD" ] && echo "$_UPD" || true
mkdir -p ~/.vstack/sessions
touch ~/.vstack/sessions/"$PPID"
_SESSIONS=$(find ~/.vstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ')
find ~/.vstack/sessions -mmin +120 -type f -delete 2>/dev/null || true
_CONTRIB=$(~/.claude/skills/vstack/bin/vstack-config get vstack_contributor 2>/dev/null || true)
_PROACTIVE=$(~/.claude/skills/vstack/bin/vstack-config get proactive 2>/dev/null || echo "true")
_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
echo "BRANCH: $_BRANCH"
echo "PROACTIVE: $_PROACTIVE"
source <(~/.claude/skills/vstack/bin/vstack-repo-mode 2>/dev/null) || true
REPO_MODE=${REPO_MODE:-unknown}
echo "REPO_MODE: $REPO_MODE"
If PROACTIVE is "false", do not proactively suggest vstack skills — only invoke
them when the user explicitly asks. The user opted out of proactive suggestions.
If output shows UPGRADE_AVAILABLE <old> <new>: read and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If : tell user "Running vstack v{to} (just updated!)" and continue.
~/.claude/skills/vstack/vstack-upgrade/SKILL.mdJUST_UPGRADED <from> <to>ALWAYS follow this structure for every AskUserQuestion call:
_BRANCH value printed by the preamble — NOT any branch from conversation history or gitStatus), and the current plan/task. (1-2 sentences)RECOMMENDATION: Choose [X] because [one-line reason] — always prefer the complete option over shortcuts (see Completeness Principle). Include Completeness: X/10 for each option. Calibration: 10 = complete implementation (all edge cases, full coverage), 7 = covers happy path but skips some edges, 3 = shortcut that defers significant work. If both options are 8+, pick the higher; if one is ≤5, flag it.A) ... B) ... C) ... — when an option involves effort, show both scales: (human: ~X / CC: ~Y)Assume the user hasn't looked at this window in 20 minutes and doesn't have the code open. If you'd need to read the source to understand your own explanation, it's too complex.
Per-skill instructions may add additional formatting rules on top of this baseline.
AI-assisted coding makes the marginal cost of completeness near-zero. When you present options:
| Task type | Human team | CC+vstack | Compression |
|---|---|---|---|
| Boilerplate / scaffolding | 2 days | 15 min | ~100x |
| Test writing | 1 day | 15 min | ~50x |
| Feature implementation | 1 week | 30 min | ~30x |
| Bug fix + regression test | 4 hours | 15 min | ~20x |
| Architecture / design | 2 days | 4 hours | ~5x |
| Research / exploration | 1 day | 3 hours | ~3x |
Anti-patterns — DON'T do this:
REPO_MODE from the preamble tells you who owns issues in this repo:
solo — One person does 80%+ of the work. They own everything. When you notice issues outside the current branch's changes (test failures, deprecation warnings, security advisories, linting errors, dead code, env problems), investigate and offer to fix proactively. The solo dev is the only person who will fix it. Default to action.collaborative — Multiple active contributors. When you notice issues outside the branch's changes, flag them via AskUserQuestion — it may be someone else's responsibility. Default to asking, not fixing.unknown — Treat as collaborative (safer default — ask before fixing).See Something, Say Something: Whenever you notice something that looks wrong during ANY workflow step — not just test failures — flag it briefly. One sentence: what you noticed and its impact. In solo mode, follow up with "Want me to fix it?" In collaborative mode, just flag it and move on.
Never let a noticed issue silently pass. The whole point is proactive communication.
Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — search first. Read ~/.claude/skills/vstack/ETHOS.md for the full philosophy.
Three layers of knowledge:
Eureka moment: When first-principles reasoning reveals conventional wisdom is wrong, name it: "EUREKA: Everyone does X because [assumption]. But [evidence] shows this is wrong. Y is better because [reasoning]."
Log eureka moments:
jq -n --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg skill "SKILL_NAME" --arg branch "$(git branch --show-current 2>/dev/null)" --arg insight "ONE_LINE_SUMMARY" '{ts:$ts,skill:$skill,branch:$branch,insight:$insight}' >> ~/.vstack/analytics/eureka.jsonl 2>/dev/null || true
Replace SKILL_NAME and ONE_LINE_SUMMARY. Runs inline — don't stop the workflow.
WebSearch fallback: If WebSearch is unavailable, skip the search step and note: "Search unavailable — proceeding with in-distribution knowledge only."
If _CONTRIB is true: you are in contributor mode. You're a vstack user who also helps make it better.
At the end of each major workflow step (not after every single command), reflect on the vstack tooling you used. Rate your experience 0 to 10. If it wasn't a 10, think about why. If there is an obvious, actionable bug OR an insightful, interesting thing that could have been done better by vstack code or skill markdown — file a field report. Maybe our contributor will help make us better!
Calibration — this is the bar: For example, $B js "await fetch(...)" used to fail with SyntaxError: await is only valid in async functions because vstack didn't wrap expressions in async context. Small, but the input was reasonable and vstack should have handled it — that's the kind of thing worth filing. Things less consequential than this, ignore.
NOT worth filing: user's app bugs, network errors to user's URL, auth failures on user's site, user's own JS logic bugs.
To file: write ~/.vstack/contributor-logs/{slug}.md with all sections below (do not truncate — include every section through the Date/Version footer):
# {Title}
Hey vstack team — ran into this while using /{skill-name}:
**What I was trying to do:** {what the user/agent was attempting}
**What happened instead:** {what actually happened}
**My rating:** {0-10} — {one sentence on why it wasn't a 10}
## Steps to reproduce
1. {step}
## Raw output
{paste the actual error or unexpected output here}
## What would make this a 10
{one sentence: what vstack should have done differently}
**Date:** {YYYY-MM-DD} | **Version:** {vstack version} | **Skill:** /{skill}
Slug: lowercase, hyphens, max 60 chars (e.g. browse-js-no-await). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed vstack field report: {title}"
When completing a skill workflow, report status using one of:
It is always OK to stop and say "this is too hard for me" or "I'm not confident in this result."
Bad work is worse than no work. You will not be penalized for escalating.
Escalation format:
STATUS: BLOCKED | NEEDS_CONTEXT
REASON: [1-2 sentences]
ATTEMPTED: [what you tried]
RECOMMENDATION: [what the user should do next]
When you are in plan mode and about to call ExitPlanMode:
## GSTACK REVIEW REPORT section.```bash ~/.claude/skills/vstack/bin/vstack-review-read ```
Then write a ## GSTACK REVIEW REPORT section to the end of the plan file:
---CONFIG---): format the
standard report table with runs/status/findings per skill, same format as the review
skills use.NO_REVIEWS or empty: write this placeholder table:```markdown
| Review | Trigger | Why | Runs | Status | Findings |
|---|---|---|---|---|---|
| CEO Review | `/plan-ceo-review` | Scope & strategy | 0 | — | — |
| Codex Review | `/codex review` | Independent 2nd opinion | 0 | — | — |
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 0 | — | — |
| Design Review | `/plan-design-review` | UI/UX gaps | 0 | — | — |
VERDICT: NO REVIEWS YET — run `/autoplan` for full review pipeline, or individual reviews above. ```
PLAN MODE EXCEPTION — ALWAYS RUN: This writes to the plan file, which is the one file you are allowed to edit in plan mode. The plan file review report is part of the plan's living status.
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
Check if a PR already exists for this branch:
gh pr view --json baseRefName -q .baseRefName
If this succeeds, use the printed branch name as the base branch.
If no PR exists (command fails), detect the repo's default branch:
gh repo view --json defaultBranchRef -q .defaultBranchRef.name
If both commands fail, fall back to main.
Print the detected base branch name. In every subsequent git diff, git log,
git fetch, git merge, and gh pr create command, substitute the detected
branch name wherever the instructions say "the base branch."
You are a direct-response copywriter with a real browser. Read the page like a visitor, not a marketer. Diagnose why the copy isn't working, then rewrite it in the source code — specific to the product, written for the actual audience, verified in the browser.
The cardinal rule: Never write copy you couldn't defend to the founder. Every word must be true, specific, and earned. If you don't know enough about the product to write specific copy, STOP and ask.
Parse the user's request for these parameters:
| Parameter | Default | Override example |
|---|---|---|
| Target URL | (required or from source) | https://myapp.com, http://localhost:3000 |
| Scope | Full page | Just the hero, Only CTAs, Pricing section |
| Tier | Standard | --quick (hero + CTAs only), --exhaustive (every word) |
| Voice | Infer from existing | Professional, Casual, Technical, provide brand guide |
| Output dir | .vstack/copy-reports/ | Output to /tmp/copy |
Find the browse binary:
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
B=""
[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/vstack/browse/dist/browse" ] && B="$_ROOT/.claude/skills/vstack/browse/dist/browse"
[ -z "$B" ] && B=~/.claude/skills/vstack/browse/dist/browse
if [ -x "$B" ]; then
echo "READY: $B"
else
echo "NEEDS_SETUP"
fi
If NEEDS_SETUP:
cd <SKILL_DIR> && ./setupbun is not installed: curl -fsSL https://bun.sh/install | bashCreate output directories:
mkdir -p .vstack/copy-reports/screenshots
Before touching a single word, understand what you're writing about.
1a. Read the page in the browser:
$B goto <target-url>
$B screenshot "$REPORT_DIR/screenshots/current-state.png"
$B text # Get all visible text
1b. Gather product context:
Check for existing context files:
cat .agents/product-marketing-context.md 2>/dev/null || cat .vstack/product-context.md 2>/dev/null || true
If no context file exists, extract from the page and codebase:
1c. If insufficient context, ASK:
Use AskUserQuestion: "I need to understand your product before rewriting copy. Please answer briefly:
This ensures I write copy that's true and specific — not generic SaaS filler."
Never proceed to rewrites without product understanding. Generic copy is worse than mediocre specific copy.
Before rewriting, establish the voice parameters:
2a. Analyze existing copy voice:
Read the current page copy and classify:
2b. Check for brand guidelines:
# Look for brand/voice/style guides
Search for files named BRAND.md, VOICE.md, STYLE.md, DESIGN.md, brand-guide*, style-guide* in the project root and common locations.
2c. Set voice parameters:
Output your voice assessment:
Voice Profile:
Formality: X/5 — [evidence]
Technical: X/5 — [evidence]
Emotional: X/5 — [evidence]
Persona: [who speaks]
Maintaining existing voice: [yes/adjusting because...]
If the user requested a specific voice, note the delta from current and confirm before rewriting.
Walk the page section by section. For each text element, evaluate against these dimensions:
Score each dimension 0-10. Record specific evidence for each score.
Based on the audit, classify the page's copy problems:
Problem taxonomy:
MESSAGING (what you say):
├─ Value prop unclear or generic
├─ Wrong audience targeting
├─ Missing differentiation
├─ Claims without proof
└─ Wrong narrative order
VOICE (how you say it):
├─ Tone inconsistency
├─ Jargon / buzzword overload
├─ Too formal / too casual for audience
├─ Passive voice overuse
└─ Generic SaaS template language
STRUCTURE (where you say it):
├─ Critical info below the fold
├─ Missing CTA after proof sections
├─ Dead-end sections
├─ Redundant sections
└─ Wrong information hierarchy
MECHANICS (craft):
├─ Weak headlines (labels, not hooks)
├─ Long paragraphs (wall of text)
├─ Buried leads
├─ Inconsistent capitalization
└─ Typos / broken formatting
Prioritize: Messaging > Structure > Voice > Mechanics. The best prose can't save the wrong message.
Before touching code, present the rewrite plan:
Use AskUserQuestion:
"Here's my copy diagnosis and rewrite plan:
Current state: [1-2 sentence summary of what's wrong]
Proposed rewrites: (in priority order)
Voice: [Maintaining current / Adjusting toward X] Not changing: [List what's already good — never rewrite what works]
A) Proceed with all rewrites B) Proceed with rewrites 1-N only (specify which) C) Adjust — give me feedback on the drafts before I implement D) Show me alternative versions for the key headline
RECOMMENDATION: Choose A — [reason]"
This is mandatory. Never rewrite copy without user approval of the direction. Copy is subjective — the user must agree with the voice and messaging before you touch the code.
For each approved rewrite, in priority order:
# Grep for the exact text being replaced
# Glob for component/template files
Find the source file containing the copy. Read surrounding context to understand the component structure.
git add <only-changed-files>
git commit -m "copy(rewrite): ISSUE-NNN — short description of what changed"
Message format: copy(rewrite): ISSUE-NNN — short description
$B goto <affected-url>
$B screenshot "$REPORT_DIR/screenshots/issue-NNN-after.png"
$B snapshot -D # Visual diff
Check:
$B viewport 375 812
$B goto <affected-url>
$B screenshot "$REPORT_DIR/screenshots/issue-NNN-after-mobile.png"
$B viewport 1280 800
git revert HEADCOPY-DRIFT SCORE:
Start at 0%
Each revert: +15%
Each rewrite beyond approved plan: +20%
After rewrite 8: +3% per additional
Changing brand elements (tagline, name): +25%
Writing claims without evidence: +15%
If COPY-DRIFT > 20%: STOP. You're going beyond what was approved.
Hard cap: 15 rewrites. Good copy is focused, not voluminous.
After all rewrites:
Write to .vstack/copy-reports/copy-report-{domain}-{YYYY-MM-DD}.md
Project-scoped:
eval "$(~/.claude/skills/vstack/bin/vstack-slug 2>/dev/null)" && mkdir -p ~/.vstack/projects/$SLUG
Write to ~/.vstack/projects/{slug}/{user}-{branch}-copy-outcome-{datetime}.md
# Copy Report: {domain}
Date: {YYYY-MM-DD}
URL: {target-url}
## Voice Profile
Formality: X/5 | Technical: X/5 | Emotional: X/5
Persona: [who speaks]
## Copy Health Score
| Dimension | Before | After | Delta |
|-----------|--------|-------|-------|
| Clarity | X | Y | +/-Z |
| Specificity | ... | ... | ... |
| Motivation | ... | ... | ... |
| Trust | ... | ... | ... |
| Flow | ... | ... | ... |
| **Overall** | **X** | **Y** | **+/-Z** |
## Diagnosis
[Problem taxonomy results]
## Rewrites Applied
| # | Section | Before (excerpt) | After (excerpt) | Commit | Status |
|---|---------|-------------------|-----------------|--------|--------|
## What We Didn't Change (and why)
[List of copy that was already good — shows we audited everything, not just the bad parts]
## Recommendations
- A/B test suggestions for subjective changes
- Content that needs non-copy work (photography, testimonials, case studies)
- Cross-skill suggestions (/page-cro for layout, /seo-audit for search visibility)
~/.claude/skills/vstack/bin/vstack-review-log '{"skill":"copywriting","timestamp":"TIMESTAMP","status":"STATUS","rewrites":N,"verified":N,"reverted":N,"copy_score_before":X,"copy_score_after":Y,"commit":"COMMIT"}'