---
name: create-gtm-skill
description: >-
  Author a GTM skill in getcargohq/gtm-skills (one-off CLI job or deployed
  pipeline). Use when creating, adding, scaffolding, or reviewing a new
  skill, SKILL.md, cookbook, or pipeline folder in gtm-skills.
---

# Create a GTM skill

This is an authoring skill for people writing this repo. It is not a
customer skill and does not install with `npx skills add`. The canonical
copy lives in `.agents/skills` so Codex, Claude Code, and Cursor all load
the same file (the last two via symlink).

Read `CONTRIBUTING.md` first. That file is the contract the validators
enforce; this file is the procedure.

One folder at the repo root is one customer skill. Install path:
`npx skills add getcargohq/gtm-skills/<name>`.

## 1. Pick the kind

| Kind     | Frontmatter                 | Job                                                                        |
| -------- | --------------------------- | -------------------------------------------------------------------------- |
| one-off  | `metadata.source: one-off`  | Runs once in a turn: exact `cargo-ai` command and a price                  |
| pipeline | `metadata.source: cookbook` | Deploys a standing play. Customer copy says **pipeline**, never "cookbook" |

`metadata.source` must be exactly one of those two strings. The validator
uses `cookbook` as the internal discriminator. Do not put that word in
README, plugin listings, skill body, or examples. Customers learn: some
skills run once; some deploy a pipeline that keeps running.

If the same job exists both ways, they compete for prompts. Pin the seam in
`evals/routing.jsonl`: one case that must hit the pipeline, one that must
hit the one-off sibling.

Name the folder after the job, not the vendor. `name` in frontmatter equals
the folder name. The slug is what an agent matches before it opens the file,
so the kind has to be readable from the name:

| Kind      | Shape                 | Examples                                               |
| --------- | --------------------- | ------------------------------------------------------ |
| one-off   | verb + object         | `enrich-company-data`, `score-leads`, `build-tam-list` |
| pipeline  | the standing practice | `tam-building`, `account-scoring`, `crm-enrichment`    |
| migration | `x-to-cargo`          | `clay-to-cargo`                                        |

A verb says "do this once." A standing noun says "keep this running." That
is how `build-tam-list` and `tam-building` stay apart, and why
`score-leads` pairs with `account-scoring` rather than `score-accounts`.
Do not give both kinds the same stem.

`enrich-crm` would read as a one-off next to `enrich-company-data`. A
pipeline that keeps CRM records filled is `crm-enrichment`. Contacts later
stay in that same slug. Accounts that are not in a CRM, if they land, are
`account-enrichment`. Planned pipelines stay nouns (`contact-sourcing`,
`signal-based-tam`, `rep-cockpit`). Do not rename existing skills to force
`-ing` vs `-ment`; the kind matters more than the suffix.

Inside a cookbook, resource slugs are snake_case: plays, workflows, tools,
agents, segments and folders (`score_accounts`, `enrich_crm_contact`,
`account_enrichment`, `tam_tier_analyst`, `tam_tier_a`,
`crm_enrichment_plays`), the same as the connectors and models the CDK
already requires it of. Workers and apps are the exception: the CDK requires
kebab-case for them. File names stay kebab-case (`score-accounts.ts`). A slug
is the resource's identity in `cargo.state.json`, so renaming one after a
deploy creates a new resource and orphans the old one: get it right before
the first deploy.

A model that extracts a system you hold (a CRM, a warehouse) pulls all the
data. Never filter or pick columns in its `config`: extract every record and
every column, and narrow in the SQL or the play filter that reads it. A
filter in the config is a second place the question is asked, invisible to
anyone reading the queries, and changing it means a redeploy and a
re-extraction instead of an edited query. A paid search source is the
exception, because there the search is the data source rather than a filter
on it: `aiArk.fetchCompanies` or a Sales Navigator search bills per returned
record, so its query in `config` is what decides what you buy.

## 2. Description (the only text before load)

Four parts, in order:

1. The job in one sentence
2. `Triggers:` literal quoted phrases a user would type
3. Proper nouns (connectors, CRM, CDK)
4. `Skip when:` the sibling skill that should win instead

Quote any YAML value that contains colon-space. Unquoted, the installer
skips the file with a warning nobody reads.

## 3. Write the kind

### One-off

Copy the structure of `enrich-company-data/SKILL.md`:

- Setup (CLI install, login, session stamp)
- Do the job (exact `cargo-ai orchestration action execute` / `execute-batch`)
- Example (see below)
- What it costs (table of `integration.action` → credits)
- Worth knowing / Going further / star ask

Slugs and prices are not yours to invent. `validate.ts` asserts every
`integrationSlug`/`actionSlug` and every credit number against
`getcargohq/cargo-skills` playbooks. Copy from a sibling, then run
`node scripts/validate.ts`.

Register: `skills.sh.json`, `hooks/skill-loads.sh`, root `README.md` table.

### Pipeline

Copy the section order from `tam-building/SKILL.md`. The validator requires
these headings:

1. Honest banner: `**State: to-be-approved.**`
2. `## The outcome`
3. `## Example` (see below)
4. `## Put it in your project` (copy the procedure from `tam-building`; each
   skill carries its own)
5. `## What you will be asked` — derive before ask; more than ~4 asked rows
   means you skipped lookups
6. `## What you can change` — offer unprompted; every variation has a cost
7. `## What should not change` — name the concrete symptom if violated
8. `## Done when`
9. `## What it costs`
10. `## Composes into`

Folder:

```
<name>/
  SKILL.md
  README.md
  infra/           # or models/, plays/, agents/ — whatever the outcome needs
  references/      # supporting agent docs; never a nested SKILL.md
  evals/acceptance.md
```

Isolation: every model and connector the resources import lives in this
folder. No relative import may leave it. Two pipeline skills may both
carry an `accounts` model; the placing agent rewires to the project's
existing one.

Resources must earn their deploy:

- Do not wrap a single connector action in a tool. That is a one-off.
- Do not declare a segment that restates the play's own filter.
- Do not add `examples/example.md` until a live install has sanitized
  audit findings, mappings, a plan summary, and lookup-backed credits.
- A folder of CDK with no `SKILL.md` does not belong at the root. Older
  examples live in history: `git checkout 305cd88 -- <name>`, then write
  the skill.

**Credits.** Pipeline markdown must not contain a numeric credit amount.
Fetch live prices at run time (`cargo-ai connection integration get <slug>`).
One-offs quote playbook numbers; pipelines do not.

**Write the system you write back to.** If the outcome updates a CRM
record, the play runs on that CRM extract and matches the CRM record id.
Do not sit a native `unifyAccounts` / `ids` map between the play and the
write — the run looks successful and nothing lands.

**One CRM shape in the file.** HubSpot as the checked example; Salesforce
and Attio adapt that file (connector, extractor, record-id, write action,
fill-blank guard). No parallel branches.

CDK workflow bodies must compile. These fail `cargo-cdk check` / `plan`:
`undefined`/`void`, comma / SequenceExpression, `Date#getTime()`.

Register: `skills.sh.json` ("Make it run forever"), `hooks/skill-loads.sh`,
root README pipeline table, `.github/data/approvals.json` with
`state: to-be-approved` and empty evidence.

### Both kinds: personas and the example

`validate.ts` requires these of every skill; the site renders them before
anyone installs.

- `metadata.personas`, right after `metadata.source`: 1–3 of
  `sales-development`, `account-executive`, `revops`, `sales-leadership`,
  `marketing`, `gtm-engineering`, primary first.
- `## Example`, in exactly this shape:

  ```
  ## Example

  > Find work emails for the 40 people in webinar-attendees.csv.

  Illustrative output, fictional records:

  <table or fenced block, about 20 lines at most>

  <one closing line with the result in numbers>
  ```

  Fictional companies on the `.example` TLD. A one-off's credits must add up
  from its own cost table; a pipeline quotes none and shows what the deployed
  play writes. Never a command in it, and never a table row that opens on a
  backticked `integration.action` cell — the validator reads that as a price.

## 4. Finish

```sh
node scripts/build-catalog.mjs
node scripts/generate-llms-txt.ts
npm run typecheck
npm run validate
npm run format:check
```

`catalog.json` and `llms.txt` are generated. Edit SKILL.md, then regenerate.

Do not claim an outcome for a pipeline skill until it is approved (demo
workspace + two implementations in `approvals.json`). The banner in
`SKILL.md` must match that file exactly.

## Anti-patterns

- A new root skill that is the same job as an existing slug with extra
  words (`crm-account-enrichment` vs `crm-enrichment`)
- A pipeline named like a one-off (`enrich-crm`, `score-accounts`,
  `build-tam`) or a one-off named like a pipeline (`tam-building` for a
  list today)
- Customer-facing "cookbook"
- Hardcoded credits in a pipeline
- Stub walkthroughs that restate `SKILL.md`
- `PLACEHOLDER` values that survive into a "done" template when the
  checked example can use real HubSpot (or whatever) property names
- Asking the operator for anything a connector or live schema can answer
