Claude Code · Operator Setup

The .claude/ folder
for GTM.

Every file a GTM operator actually needs — so Claude walks into every session already knowing your ICP, stack, and campaign rules.

By Avinash Raju May 2026 11 min read

Most GTM operators are making a mess in Claude Code. Forget prompts — they never set up the folders right. Without a .claude/ folder, Claude forgets your ICP every single session. Forgets your conventions. Forgets your stack. You re-explain yourself every time.

That's not an AI problem. That's a setup problem. The folder fixes it once, then Claude walks into every session already knowing your stack, your ICP, your sequences, and your campaign rules. Research, score, enrich, launch — all from a single session, no re-prompting.

Below is every file a GTM operator actually needs in .claude/, what each one does, and when to reach for it.


Section 01The mental model

OperationKnow which file does what before you create any of them.

Claude Code reads four kinds of context from your repo, and they behave very differently. Mixing them up is the single biggest reason operator setups feel "smart for an hour, dumb after compaction."

  • AdvisoryCLAUDE.md. Loaded into every session as instructions. Claude will follow it most of the time, but it is suggestion, not law.
  • Deterministic.claude/hooks/*.sh. The harness fires them on specific events (session start, after a tool call, before context compaction). They always run. No model decision involved.
  • On-demand.claude/skills/*, .claude/agents/*, .claude/commands/*. The model loads these only when the user or context invokes them. They cost zero context until they're needed.
  • Path-scopedrules/*.md with glob matchers. Loaded into context only when the working file path matches the glob (e.g. outbound/**).

The rule of thumb is simple. Need it to always happen? Hook. Need Claude to decide when to use it? Skill or agent. Need to name a workflow you'll run again? Command. Need it loaded only in certain folders? Rule.

Pro tipThe moment you find yourself prompt-engineering inside a session ("remember our ICP is …"), stop and write a file instead. The session is the wrong layer.

Section 02Project root: the 4 files

OperationSet up the four files at the top of every GTM repo before you touch .claude/.

These four files live in the project root, not inside .claude/. They establish the project itself. Get them right and every session afterwards is configured the same way for you and any teammate who clones the repo.

Prerequisite setupA folder for your GTM project (e.g. ~/work/outbound). Open it in Claude Code. The four files below go directly in that folder.

CLAUDE.md — your project rules. ICP, stack, conventions, tone. Loaded into every session. This is the file everyone forgets to write.

# GTM Project — Operator Rules ## ICP Primary: B2B SaaS, Series A–B, 20–200 headcount, US-only. Buyer titles: VP Sales, Head of Growth, CRO, Founder (if <50 employees). Disqualify: agencies, consultancies, anyone <$1M ARR, anyone with a public outage in the last 30 days. ## Stack - Source: LinkedIn Sales Navigator + careers-page intent - Enrich: Findymail MCP (waterfall to BetterContact MCP if not_found) - CRM: HubSpot - Sequencer: Instantly (workspace ID stored in `.claude/CLAUDE.local.md`) - Tracking: RB2B (consent-gated) ## Conventions - Lead lists live in `outbound/lists/{campaign}/` as CSV with snake_case headers. - Sequence drafts live in `outbound/sequences/{campaign}.md`. - Never commit anything matching `*.local.*` or anything inside `secrets/`. - All Instantly campaigns get a dry-run + approval step before activation. ## Tone Operator-grade. Terse. No corporate hedging. No emoji. Reply rate > reply volume. Relevance > personalization tokens.

CLAUDE.local.md — personal overrides (API keys, workspace IDs, anything that varies per teammate). Gitignored. .gitignore needs to keep it out of the repo:

.mcp.json — your MCP server registry. Claude reads this on session start and connects each server. Must live at the project root. Keep API keys out — reference env vars or the local override file.

{ "mcpServers": { "findymail": { "command": "npx", "args": ["-y", "@findymail/mcp-server"], "env": { "FINDYMAIL_API_KEY": "${FINDYMAIL_API_KEY}" } }, "instantly": { "command": "npx", "args": ["-y", "@instantly/mcp-server"], "env": { "INSTANTLY_API_KEY": "${INSTANTLY_API_KEY}" } }, "hubspot": { "command": "npx", "args": ["-y", "@hubspot/mcp-server"], "env": { "HUBSPOT_TOKEN": "${HUBSPOT_TOKEN}" } } } }
Pro tipPut workspace IDs and personal preferences in CLAUDE.local.md — never in CLAUDE.md. Anything that would change if a teammate cloned the repo belongs in the .local. file.

Section 03hooks/ — deterministic execution

OperationWire bash scripts to harness events so the right thing happens automatically, every time.

Hooks live in .claude/hooks/. They're shell scripts named for the event that fires them: SessionStart.sh, PostToolUse.sh, PreCompact.sh, etc. The harness runs them — Claude does not decide. That's the entire point.

Three hooks belong in every GTM operator's repo:

Prerequisite setupCreate .claude/hooks/. Make each script executable: chmod +x .claude/hooks/*.sh. Register them in .claude/settings.json (see §7).

SessionStart.sh — load campaign context every time a new session opens. Writes a fresh CONTEXT.md Claude reads on its first tick.

#!/usr/bin/env bash # .claude/hooks/SessionStart.sh # Loads current campaign state into context at every session start. set -euo pipefail cd "$(dirname "$0")/../.." ACTIVE_CAMPAIGN=$(cat outbound/active-campaign 2>/dev/null || echo "none") LEAD_COUNT=$(find outbound/lists -name "*.csv" 2>/dev/null | wc -l | tr -d ' ') LAST_SEND=$(stat -f "%Sm" -t "%Y-%m-%d" outbound/sends.log 2>/dev/null || echo "never") cat > .claude/CONTEXT.md <<EOF # Session bootstrap — $(date +"%Y-%m-%d %H:%M") - Active campaign: ${ACTIVE_CAMPAIGN} - Lead lists on disk: ${LEAD_COUNT} - Last send: ${LAST_SEND} - Working tree clean: $(git status --porcelain | wc -l | tr -d ' ') uncommitted changes EOF

PostToolUse.sh — fires after every tool call. Use it to auto-enrich any new lead written to disk so you never end a session with a half-enriched list.

#!/usr/bin/env bash # .claude/hooks/PostToolUse.sh # Auto-enriches any new leads.csv written during the session. set -euo pipefail TOOL_NAME="${CLAUDE_TOOL_NAME:-}" FILE_PATH="${CLAUDE_FILE_PATH:-}" # Only act on Write/Edit of a leads CSV if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then exit 0; fi if [[ ! "$FILE_PATH" =~ outbound/lists/.*\.csv$ ]]; then exit 0; fi # Mark for enrichment — the next /prospect run will pick this up echo "$FILE_PATH" >> .claude/.enrich-queue echo "🪝 PostToolUse: queued $FILE_PATH for enrichment"

PreCompact.sh — fires before Claude compacts the context. Snapshot pipeline state to disk so the post-compact session knows what was happening.

#!/usr/bin/env bash # .claude/hooks/PreCompact.sh # Save pipeline state before context compaction wipes working memory. set -euo pipefail mkdir -p .claude/snapshots TS=$(date +"%Y%m%d-%H%M%S") cat > ".claude/snapshots/pre-compact-${TS}.md" <<EOF # Pre-compact snapshot — ${TS} $(git status --short) $(ls -1t outbound/lists 2>/dev/null | head -5) $(tail -10 outbound/sends.log 2>/dev/null || echo "no send log yet") EOF
Pro tipHooks are the only layer that can survive a model getting confused. If something must happen on every campaign launch — DNC scrub, deliverability check, list dedupe — write a hook, not a prompt. The hook always runs.

Section 04commands/ — slash commands

OperationName the workflows you run more than once a week so they become a single keystroke.

Slash commands live in .claude/commands/ as markdown files. The filename becomes the command (prospect.md/prospect). The body is the prompt that runs when the user invokes it. Variables interpolate from the args you pass.

The three commands every GTM operator should have:

--- description: Research, score, and enrich a list of accounts in one go argument-hint: "<path-to-domains.txt or paste domains>" --- You are running the /prospect workflow. Take the input list of company domains and produce a single enriched, scored output file at `outbound/lists/{today}-{auto-slug}.csv`. Steps (in order, do not skip): 1. Use the `researcher` subagent to pull firmographics + trigger events for each domain. 2. Use the `qualifier` subagent to score each account against the ICP rules in `CLAUDE.md`. Drop anything that fails a hard filter. 3. For surviving accounts, use the `enricher` skill to find verified contacts (Findymail → BetterContact waterfall). 4. Write the final CSV with columns: domain, company_name, employee_count, industry, trigger_event, fit_score, contact_first_name, contact_last_name, contact_title, contact_email, email_verification. 5. Print a one-line summary: input count → qualified → enriched, plus the output path. Input: $ARGUMENTS

Save that as .claude/commands/prospect.md. From any session in the repo, type /prospect domains.txt and the entire research-score-enrich loop runs end-to-end. Now do the same for the other two:

--- description: Write a 3-step cold email sequence and push to Instantly as a draft argument-hint: "<campaign-name> <path-to-enriched.csv>" --- You are running the /sequence workflow. 1. Read the enriched lead list at the path provided. 2. Group leads by industry + trigger_event. Pick the dominant pain pattern. 3. Use the `copywriter` skill to draft 3 sequence variants — each is initial + 2 follow-ups, matched to the dominant pain. Write per the tone rules in CLAUDE.md. 4. Save the chosen variant to `outbound/sequences/{campaign-name}.md`. 5. Use the Instantly MCP to push the sequence as a draft (NOT activated) under campaign name `{campaign-name}`. 6. Output the Instantly campaign ID and a checklist for me to verify before /launch. Input: $ARGUMENTS
--- description: Activate a verified Instantly draft campaign and confirm it is live argument-hint: "<instantly-campaign-id>" --- You are running the /launch workflow. This activates a real campaign — be deliberate. 1. Use the Instantly MCP `get_campaign` to pull the campaign by ID. Display: name, lead count, sequence steps, sender accounts, schedule. 2. Pause and ask me: "Activate this campaign now? (yes/no)" 3. If I respond "yes", call `activate_campaign`. Confirm the response. Append a log line to `outbound/sends.log`. 4. If I respond anything else, abort and tell me what's missing. Input: $ARGUMENTS
Pro tipThe rule for whether to make something a command: if you've typed roughly the same paragraph into Claude more than twice, it's a command. Stop re-typing.

Section 05skills/ — on-demand expertise

OperationBundle reusable expertise the model can decide to invoke when it's relevant.

Skills live in .claude/skills/{skill-name}/ with a SKILL.md file at the root. The model reads only the skill's name + short description until it decides the skill is relevant — then it loads the full body. Zero context cost when unused.

The four skills a GTM operator wants:

  • icp-builder/ — ask the user 6 sharp questions, output a tight ICP doc.
  • lead-scorer/ — score a row against the ICP rules; return HOT / WARM / COLD with reasoning.
  • copywriter/ — write pain-first sequences keyed to a specific angle.
  • enricher/ — Findymail → BetterContact waterfall with verification cutoffs.

Here's the enricher skill in full — copy this exactly to .claude/skills/enricher/SKILL.md:

--- name: enricher description: Find a verified email for a lead using a Findymail → BetterContact waterfall. Drops anything below 80% confidence. Use this whenever a lead row has a name + company_domain but no email. --- # Lead enrichment waterfall When invoked with a list of leads (each having `first_name`, `last_name`, `company_domain`): 1. **First pass — Findymail MCP.** For each lead, call the Findymail MCP. Keep results where `confidence_score >= 80` AND `verification_status == "verified"`. Drop everything else from this pass. 2. **Second pass — BetterContact MCP** (only on rows that failed pass 1). For each failed lead, call the BetterContact MCP. Keep results where the response is `verified` (BetterContact only returns verified or not_found by default). Append to the enriched output. 3. **Output.** Return a single CSV with columns: `first_name, last_name, company_domain, email, source` (source = `findymail` | `bettercontact` | `not_found`), plus a one-line summary: total in, total out, hit rate %. ## Hard rules - Never accept "risky" verification status from any provider — drop it. - Never write to a CSV outside `outbound/lists/`. - If both providers fail on a lead, mark it `not_found` and keep moving — don't ask the user.
Pro tipSkill descriptions are SEO for the model. Spend two minutes writing the description so the model invokes the skill at the right moment. "Find a verified email…" is invoked. "Email enrichment skill" is not.

Section 06agents/ — isolated context

OperationRun sub-tasks in a fresh context window so the main session stays clean.

Agents (subagents) live in .claude/agents/{agent-name}.md. Each one runs in an isolated context — its own prompt, its own tools, its own working memory — and returns a single result to the calling session. Use them when a task would otherwise blow up the main context with intermediate noise (firmographic dumps, raw tool output, multi-step research trees).

Three agents earn their keep in a GTM repo:

  • researcher.md — pulls firmographics + trigger events for a list of domains. Returns a tidy table.
  • qualifier.md — checks each row against ICP rules and returns a verdict.
  • reply-handler.md — categorises inbound replies and drafts the next outbound move.

The qualifier agent in full:

--- name: qualifier description: Score a list of accounts against the ICP rules in CLAUDE.md. Returns HOT/WARM/COLD with one-line reasoning per row. tools: Read --- You are the qualifier subagent. You receive a list of account rows. You return verdicts. You do not enrich, you do not write copy, you do not call MCPs. # Inputs A list of account rows. Each row has at minimum: `domain`, `employee_count`, `industry`, `country`, `trigger_event` (optional). # Steps 1. Read `CLAUDE.md` from the project root and extract the ICP block. If no ICP block exists, abort and say so. 2. For each input row, evaluate against: - hard filters (industry, country, employee band, disqualifiers) — if any fail, verdict = COLD - trigger event present + ICP fit = HOT - ICP fit, no trigger = WARM 3. Return a markdown table with columns: domain, verdict, one_line_reasoning. # Hard rules - Never modify any input file. - Never call any MCP. - If a row is missing a required field, mark the verdict as `INSUFFICIENT_DATA` and keep going.
Pro tipSubagents are how you run things in parallel. Three researcher calls on three list segments run simultaneously and merge — what would take 90 seconds sequentially completes in 30. Use it when list size hurts.

Section 07rules, output-styles, plugins, settings

OperationWire the remaining pieces — path-scoped rules, custom output formats, bundled plugins, and the operator-level settings file.

These are the four files that finish the setup. Most operators skip them. They're what turns a working folder into a polished, shareable system.

rules/outbound.md — path-scoped context. Only loads when the working file path matches a glob (e.g. outbound/**). Useful when you have multiple sub-domains in one repo and don't want them polluting each other's context.

--- glob: outbound/** --- # Outbound-only rules These rules only apply when working inside `outbound/`. - Lead lists are CSV with snake_case headers. No spaces, no Title Case. - Sequence files are markdown with one `## Step 1`, `## Step 2`, `## Step 3` block. - Every send must be logged to `outbound/sends.log` with timestamp + campaign name + count. - Never push a campaign to Instantly without the dry-run + approval step from the /launch command.

.claude/output-styles/terse.md — a custom response format. When you flip Claude into terse mode it returns data, not prose. Useful for piping output to other tools.

--- name: terse description: Data only, no prose. Use when output will be piped into another step. --- When in terse mode: - Return the answer as a CSV, JSON, or markdown table — whatever the user implied. - No introduction, no conclusion, no "let me know if…" closer. - No emoji. No headers unless the data is structured into named sections. - If the answer is one number, return one number.

.claude/plugins/gtm-pack/ — bundles hooks + skills + agents into a single shareable plugin. First-class in Claude Code from 2026 onwards. The pattern: same folder structure as .claude/ itself, just one level deeper. Drop a colleague's gtm-pack into their repo and they get all your hooks, skills, and agents at once. Don't build this until your .claude/ setup is stable for at least a quarter.

.claude/settings.json — the operator-level config. Permissions, default model, hook registry, statusline format. The settings.local.json sibling holds personal overrides and is gitignored.

{ "permissions": { "allow": [ "Bash(git:*)", "Bash(ls:*)", "Bash(cat:*)", "Bash(grep:*)", "Read(*)", "Edit(outbound/**)", "Write(outbound/**)", "Write(.claude/**)" ] }, "model": "claude-opus-4-7", "hooks": { "SessionStart": ".claude/hooks/SessionStart.sh", "PostToolUse": ".claude/hooks/PostToolUse.sh", "PreCompact": ".claude/hooks/PreCompact.sh" }, "statusLine": { "type": "command", "command": "echo \"$(cat .claude/CONTEXT.md 2>/dev/null | head -1)\"" } }
Pro tipThe permissions.allow list is the most underrated speedup in Claude Code. Every entry there is one fewer permission prompt blocking your session. Allowlist your common reads and your scoped writes; keep destructive operations out.

ClosingOpen Claude. Type /prospect. Done.

That's the whole point. The .claude/ folder is a one-time setup that turns every future session into a working operator. No more re-explaining the ICP. No more pasting in your stack. No more "here's our tone again." The folder fixes it once, and the model walks in already knowing.

If you want the full gtm-pack as a single drop-in folder — exactly what's described above, files included — comment GTM on the LinkedIn post or book a call below.

Want this mapped to your stack?

Book a free 30-minute call. We'll review your current outbound, design the .claude/ folder for your ICP, and ship the working version into your repo before the call ends. Same folder we run on AptAI's own outbound and on every PE / B2B SaaS / agency client.

Book A GTM Strategy Call →

← Back to all guides