@veleads/gtm 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +13 -2
- package/dist/cli.js +1250 -271
- package/kit/AGENTS.md +48 -22
- package/kit/guides/github.md +60 -0
- package/kit/schemas/account.schema.json +0 -5
- package/kit/schemas/campaign-config.schema.json +30 -0
- package/kit/schemas/contact.schema.json +0 -5
- package/kit/schemas/icp.schema.json +97 -8
- package/kit/schemas/lead.schema.json +0 -5
- package/kit/schemas/project.schema.json +24 -3
- package/kit/schemas/signal.schema.json +0 -5
- package/kit/skills/00-intake/SKILL.md +31 -15
- package/kit/skills/01-icp/SKILL.md +88 -33
- package/kit/skills/02-tam/SKILL.md +13 -8
- package/kit/skills/05-copy/SKILL.md +9 -3
- package/kit/skills/07-launch/SKILL.md +1 -1
- package/kit/skills/research/SKILL.md +62 -0
- package/kit/stack/docker-compose.yml +66 -0
- package/kit/stack/init/01-hatchet-db.sql +2 -0
- package/kit/templates/campaign/config.yaml +3 -0
- package/kit/templates/project/icp.md +44 -8
- package/kit/templates/project/icp.yaml +13 -4
- package/kit/templates/project/intake.md +26 -11
- package/kit/templates/project/project.yaml +8 -4
- package/kit/templates/project/research.md +45 -0
- package/kit/templates/repo/README.md +5 -0
- package/kit/templates/repo/env.example +3 -3
- package/kit/templates/repo/github/workflows/gtm.yml +5 -2
- package/package.json +4 -2
|
@@ -1,56 +1,111 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: 01-icp
|
|
3
|
-
description: Stage 1 of the gtm-brain process. Turns intake and deal evidence into
|
|
3
|
+
description: Stage 1 of the gtm-brain process. Turns research, intake and deal evidence into a layered ICP: a core profile every segment shares, 1–3 segments with what differs, personas, signals and exclusions. Writes icp.md (reasoning and hypotheses), icp.yaml (filters jobs use), signals.yaml, plus personas, offer, proof and voice. Ends at a hard stop for ICP sign-off by the project approver.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# 01 · ICP
|
|
6
|
+
# 01 · ICP
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
9
|
-
|
|
9
|
+
Decide who we target and why, in a form people (`icp.md`) and jobs (`icp.yaml`, `signals.yaml`) both
|
|
10
|
+
use. **Stage 1 ends with a decision, not certainty.** From stage 2 on, gtm-brain assumes an approved
|
|
11
|
+
ICP. If you're unsure between candidates, approve the best one to three as segments and let results
|
|
12
|
+
decide; revising the ICP later is normal (change it, re-approve, continue).
|
|
10
13
|
|
|
11
14
|
## Inputs
|
|
12
|
-
- `context/
|
|
13
|
-
- Won/lost
|
|
15
|
+
- `context/research.md` (public customers, competitors, triggers) and the confirmed `context/intake.md`
|
|
16
|
+
- Won/lost deals from the stakeholders (company names, outcome, reason). No contacts.
|
|
14
17
|
- Schemas: `.gtm/schemas/icp.schema.json`, `.gtm/schemas/signals.schema.json`
|
|
15
18
|
|
|
19
|
+
## The five layers
|
|
20
|
+
|
|
21
|
+
| Layer | Question | Lives in |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| 1. **Core profile** | What kind of company fits *at all*? | `icp.yaml` → `core.firmographics` |
|
|
24
|
+
| 2. **Segments** | Which distinct groups do we treat differently? | `icp.yaml` → `segments` (only what differs from core) |
|
|
25
|
+
| 3. **Personas** | Who inside them do we contact? | `icp.yaml` → `personas`; pains and language in `personas.md` |
|
|
26
|
+
| 4. **Signals** | Who is likely to buy *now*? | `signals.yaml` |
|
|
27
|
+
| 5. **Exclusions** | Who must we never target? | `icp.yaml` → `exclusions` |
|
|
28
|
+
|
|
29
|
+
The *why* (pains, value proposition, proof, hypotheses) is human text in `icp.md`; the YAML holds only
|
|
30
|
+
what jobs filter on.
|
|
31
|
+
|
|
32
|
+
## How core and segments combine
|
|
33
|
+
- A segment's final filters = **core, with every field the segment sets replacing the same core
|
|
34
|
+
field.** Lists are not combined: if core says `technologies_any: [A]` and a segment says `[B]`, the
|
|
35
|
+
segment uses only `[B]`.
|
|
36
|
+
- A segment can restrict `personas: [<ids>]`; otherwise it uses all personas.
|
|
37
|
+
- Exclusions apply to every segment.
|
|
38
|
+
- Check the result with **`npx gtm icp`**: it prints each segment's final filters and marks which fields
|
|
39
|
+
the segment set. Review that output, not the raw YAML.
|
|
40
|
+
|
|
41
|
+
## Deciding core vs. segment
|
|
42
|
+
- **Core** = what every good-fit company has in common: usually industry family, size band, business
|
|
43
|
+
model, required tech. If removing it from one segment would feel wrong, it belongs in core.
|
|
44
|
+
- **A new segment** is justified only when something real differs:
|
|
45
|
+
- **filters** (region, sub-industry, size band, tech), and
|
|
46
|
+
- usually **treatment**: a different pain/angle, buyer, proof, language, compliance or send timezone.
|
|
47
|
+
A difference that changes neither filters nor treatment is not a segment.
|
|
48
|
+
- **Regions:** make a region its own segment when language, compliance, timezone or proof differ; it
|
|
49
|
+
will usually also be its own campaign (see `02-tam`).
|
|
50
|
+
- **One to three segments.** More dilutes volume per segment and makes results unreadable.
|
|
51
|
+
- **Ids** are short and say who: `uk-recruitment-agencies`, `de-recruitment-agencies`.
|
|
52
|
+
|
|
16
53
|
## Steps
|
|
17
|
-
1. From
|
|
18
|
-
|
|
19
|
-
2.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
1. From `research.md` and intake, list what the **best customers share** (evidence, not guesses:
|
|
55
|
+
"7 of 10 best customers use Bullhorn"). These are core candidates.
|
|
56
|
+
2. Group the remaining differences into **segment candidates**. For each, write in `icp.md`:
|
|
57
|
+
- **Hypothesis:** who, why they'd buy now, what proves it. _"UK agencies with 10+ open roles book
|
|
58
|
+
calls because hiring capacity is their bottleneck."_
|
|
59
|
+
- **Evidence:** wins, research, interviews.
|
|
60
|
+
- **Pain → value proposition → proof** for this segment.
|
|
61
|
+
- **Success criteria:** the number that would confirm it (e.g. "≥ 1.5 meetings per 100 contacts").
|
|
62
|
+
3. Score candidates on paper (pain and urgency, ability to pay, reachability, market size, proof,
|
|
63
|
+
right to win). Drop clear losers; keep **one to three**.
|
|
64
|
+
4. Write `icp.yaml`: `core.firmographics`, then `segments` with only their differences, then
|
|
65
|
+
`personas` (role, `titles_any`, **`titles_none`** to block junk, seniorities, departments) and
|
|
66
|
+
`exclusions` (competitor domains from research, excluded industries/countries). Any criterion that
|
|
67
|
+
can't be expressed as a filter stays in `icp.md` marked "context only".
|
|
68
|
+
5. Write `signals.yaml`: for each signal `level`, `source_hint` (kind of source, never a vendor),
|
|
69
|
+
`weight` (0–100), `freshness_days`, and a `messaging_angle` a copywriter can use in one sentence.
|
|
70
|
+
Fewer and stronger is better.
|
|
71
|
+
6. Run **`npx gtm icp`** and check every segment's final filters read as intended.
|
|
72
|
+
7. Estimate the **expected TAM range per segment** (rough, from intake or research). It sets up the
|
|
73
|
+
stage-2 sanity check.
|
|
74
|
+
8. Write `offer.md` (low-friction CTA) and finish `proof.md` / `voice.md` with the stakeholders.
|
|
75
|
+
9. `npx gtm validate` must pass.
|
|
76
|
+
10. **HARD STOP.** Present for sign-off: the `npx gtm icp` output (core + every segment's final
|
|
77
|
+
filters + personas), each segment's hypothesis and success criteria, exclusions, signals with
|
|
78
|
+
weights, expected TAM range. Record the approval:
|
|
79
|
+
- **GitHub approver** (`owners.approver` is `@handle`): commit the files on a branch, open a PR
|
|
80
|
+
(`gh pr create`), run `npx gtm track gate-open icp --pr <number>` and **stop**. After the
|
|
81
|
+
approver approves the PR on GitHub, run `npx gtm approve icp --pr <number>` (it verifies
|
|
82
|
+
the review on GitHub), commit the recorded approval and push it to the same PR branch, merge
|
|
83
|
+
once CI passes, then `npx gtm track gate-done icp`.
|
|
84
|
+
- **Named approver** (not on GitHub): present in the session; only after their explicit approval,
|
|
85
|
+
`npx gtm approve icp --by "<Name (role)>" --notes "<what was approved>"`.
|
|
86
|
+
Either way, quote the approval in the run log.
|
|
36
87
|
|
|
37
88
|
## Outputs
|
|
38
|
-
`context/icp.md
|
|
89
|
+
`context/icp.md` (layers, hypotheses, evidence), `icp.yaml` (core + segments + personas +
|
|
90
|
+
exclusions), `signals.yaml`, `personas.md`, `offer.md`, `proof.md`, `voice.md`.
|
|
39
91
|
|
|
40
92
|
## Checklist
|
|
41
|
-
- [ ]
|
|
42
|
-
- [ ]
|
|
93
|
+
- [ ] Core holds what every segment shares; segments hold only real differences
|
|
94
|
+
- [ ] Every segment has a hypothesis, evidence and success criteria in `icp.md`
|
|
95
|
+
- [ ] One to three segments; each differs in filters and (usually) treatment
|
|
96
|
+
- [ ] `npx gtm icp` output reviewed: every segment's final filters read as intended
|
|
97
|
+
- [ ] Each persona has `titles_none` to block obvious junk (assistant, intern, interim...)
|
|
43
98
|
- [ ] Each signal has a messaging angle a copywriter could use in one sentence
|
|
44
|
-
- [ ]
|
|
45
|
-
- [ ]
|
|
46
|
-
- [ ] icp.md and icp.yaml agree
|
|
99
|
+
- [ ] Competitors (from research) and excluded industries listed
|
|
100
|
+
- [ ] `icp.md` and `icp.yaml` agree; non-filterable criteria marked "context only"
|
|
47
101
|
- [ ] Approval recorded with `gtm approve icp`, not by hand
|
|
48
102
|
|
|
49
103
|
## Gate
|
|
50
104
|
**ICP sign-off (hard stop).** Recorded with `npx gtm approve icp`, which pins a hash of the
|
|
51
|
-
approved content
|
|
52
|
-
|
|
53
|
-
|
|
105
|
+
approved content (core, segments, personas, exclusions). Any later change makes `npx gtm validate`
|
|
106
|
+
fail until the ICP is re-presented and approved again (or the change is reverted). Comments and
|
|
107
|
+
formatting do not count as changes. Changing how volume is split between approved segments is a
|
|
108
|
+
campaign decision and needs no re-approval; adding or removing a segment does.
|
|
54
109
|
|
|
55
110
|
## Next
|
|
56
111
|
Create a campaign (`npx gtm new-campaign <slug>`) and go to `.gtm/skills/02-tam/SKILL.md`.
|
|
@@ -17,22 +17,27 @@ Know how big the market is, and whether the ICP filters are right, before spendi
|
|
|
17
17
|
1. Confirm the ICP gate is approved. If not, stop and go back to `01-icp`.
|
|
18
18
|
2. Set `tam_id` in `config.yaml` (`<project>-tam-<YYYYqN>` or similar). Reuse an existing `tam_id`
|
|
19
19
|
if the same project already has a TAM built from the same approved ICP.
|
|
20
|
-
3.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
3. Set `config.yaml` → `tam.source` (the account source adapter; `mock-accounts` generates fictional
|
|
21
|
+
accounts to prove the pipeline, never for a real campaign) and `tam.max_accounts_per_segment`.
|
|
22
|
+
4. Trigger the job: `npx gtm job tam <campaign>`. It validates, sends segment filters and exclusions
|
|
23
|
+
from `icp.yaml` to the workers, which page accounts through the adapter, upsert them into
|
|
24
|
+
`accounts` (re-runs update, never duplicate) and print counts per segment plus a run-log row.
|
|
25
|
+
**You do not page through accounts yourself.** If it says there is no Hatchet connection, run
|
|
26
|
+
`npx gtm stack up` once (needs Docker). If that fails, or the job keeps waiting for a worker
|
|
27
|
+
(`npx gtm stack status`), stop and tell the human (AGENTS.md section 4).
|
|
28
|
+
5. Funnel counts per segment (printed by the job):
|
|
24
29
|
- **TAM**: accounts matching filters.
|
|
25
30
|
- **Reachable**: accounts where at least one target persona can be found (from the job's
|
|
26
31
|
persona count, or a sample-based estimate marked as an estimate).
|
|
27
32
|
- **Active segment**: reachable minus exclusions and suppression.
|
|
28
|
-
|
|
33
|
+
6. Sanity check: compare against the expected range. Spot-check 20 random accounts in the DB console
|
|
29
34
|
(name, industry, size); count how many are clearly ICP. Do not paste the rows anywhere.
|
|
30
|
-
|
|
35
|
+
7. If the spot check finds a pattern of bad matches, propose an `icp.yaml` filter change. That
|
|
31
36
|
change resets the ICP gate, so present it to the human first.
|
|
32
|
-
|
|
37
|
+
8. Write `tam/<tam_id>.md` from `.gtm/templates/outputs/tam-summary.md` and the run log.
|
|
33
38
|
|
|
34
39
|
## Outputs
|
|
35
|
-
- Rows in `accounts` (Postgres), tagged `
|
|
40
|
+
- Rows in `accounts` (this project's Postgres), tagged `tam_id`, `segment_id`, `run_id`.
|
|
36
41
|
- `tam/<tam_id>.md` with funnel table and sanity check. Run log.
|
|
37
42
|
|
|
38
43
|
## Checklist
|
|
@@ -28,9 +28,15 @@ the approver signed off.
|
|
|
28
28
|
5. Self-review against the checklist below, then render each step once with a fictional example
|
|
29
29
|
lead (fictional name and company) for the human.
|
|
30
30
|
6. **HARD STOP.** Present all variants for sign-off. Edits requested → change, re-present,
|
|
31
|
-
still pending.
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
still pending. Record the approval:
|
|
32
|
+
- **GitHub approver** (`owners.approver` is `@handle`): commit the files on a branch, open a PR
|
|
33
|
+
(`gh pr create`), run `npx gtm track gate-open copy <campaign> --pr <number>` and **stop**. After the
|
|
34
|
+
approver approves the PR on GitHub, run `npx gtm approve copy <campaign> --pr <number>` (it verifies
|
|
35
|
+
the review on GitHub), commit the recorded approval and push it to the same PR branch, merge
|
|
36
|
+
once CI passes, then `npx gtm track gate-done copy <campaign>`.
|
|
37
|
+
- **Named approver** (not on GitHub): present in the session; only after their explicit approval,
|
|
38
|
+
`npx gtm approve copy <campaign> --by "<Name (role)>" --notes "<what was approved>"`.
|
|
39
|
+
Either way, quote the approval in the run log.
|
|
34
40
|
|
|
35
41
|
## Outputs
|
|
36
42
|
`copy.md` (status line at top reflects the gate). Run log.
|
|
@@ -17,7 +17,7 @@ Get the approved leads and copy into the sender safely, and start sending only o
|
|
|
17
17
|
sequence from `copy.md`, pushes leads in send tiers, sets the schedule from `sending`. Write the
|
|
18
18
|
returned reference into `config.yaml` → `sending.sender_campaign_ref`.
|
|
19
19
|
3. Read the push counts: accepted vs. rejected (with reasons). Rejections > 2% → investigate first.
|
|
20
|
-
4. **HARD STOP: go/no-go.** Present: leads pushed by tier, daily volume and ramp, first send
|
|
20
|
+
4. Run `npx gtm track gate-open launch <campaign>`. **HARD STOP: go/no-go.** Present: leads pushed by tier, daily volume and ramp, first send
|
|
21
21
|
date/time, spend so far vs. cap, QA summary, sender campaign ref (paused). Ask the campaign owner
|
|
22
22
|
or GTM engineer for an explicit go. Record in `config.yaml` → `gates.launch` per AGENTS.md section 4.
|
|
23
23
|
5. Only after approval: `npx gtm job activate <campaign>`. Set `stage: results`.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: research
|
|
3
|
+
description: Researches the business this project runs outbound for (product, pricing, positioning, public customers, competitors, buying triggers) from public sources before anyone is asked questions. Writes context/research.md with a source URL for every claim. Used first in stage 00-intake and re-run before new campaigns or when the market changes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Research: the business, its customers and competitors
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
Do the homework so stakeholders are only asked what research can't answer. Every fact is sourced,
|
|
10
|
+
so nothing invented reaches the ICP or the copy.
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
- The business name and website (from `project.yaml` → `name`, or ask for the URL; that is the only
|
|
14
|
+
question allowed before researching).
|
|
15
|
+
- Existing `context/research.md` when re-running (update it; keep the history section).
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
1. **Source every claim** with a URL in the same line. No source → it doesn't go in.
|
|
19
|
+
2. **Public information only.** No paywalled content, no logging in, no scraping behind forms.
|
|
20
|
+
3. **Companies, not people.** Record company names and domains. Do not collect names, emails or
|
|
21
|
+
profiles of individuals (prospect data belongs in Postgres via jobs, never in git).
|
|
22
|
+
4. **Don't guess.** Unknown is a valid answer; mark it `unknown` and list it under open questions.
|
|
23
|
+
5. **Mark confidence:** `confirmed` (the business's own site says it), `likely` (credible third
|
|
24
|
+
party: reviews, press), `inferred` (your reading of several sources; say why).
|
|
25
|
+
6. **No web access?** Ask the human for the website URL and to paste the text of the home, product,
|
|
26
|
+
pricing and customers pages, then work from that and say so in the file.
|
|
27
|
+
|
|
28
|
+
## Steps
|
|
29
|
+
1. **The business:** home, product/solutions, pricing, customers/case studies, about, careers,
|
|
30
|
+
blog/news. Capture: what they sell (one sentence), who it is for, pricing model and visible price
|
|
31
|
+
points, proof points with numbers, positioning words they repeat.
|
|
32
|
+
2. **Public customers:** logos and case studies on their site, plus credible mentions (press,
|
|
33
|
+
reviews). For each: company name, domain, industry/size/region/tech if visible, source. These are
|
|
34
|
+
**suppression candidates** (don't cold-email customers) and the main input for the ICP layers:
|
|
35
|
+
- **Core candidates:** traits most customers share (industry family, size band, required tech,
|
|
36
|
+
business model). Say how many share it ("7 of 10 use Bullhorn").
|
|
37
|
+
- **Segment candidates:** clusters that differ in a filter *and* in treatment (region/language,
|
|
38
|
+
sub-industry, size band, buyer). For each: the cluster, its members, and why it might need a
|
|
39
|
+
different angle.
|
|
40
|
+
3. **Competitors (3–5):** from "alternatives to <business>" searches, review-site categories and
|
|
41
|
+
comparison pages. For each: domain, one-line positioning, pricing if public, how the business
|
|
42
|
+
differs. These are **exclusion candidates** for `icp.yaml`.
|
|
43
|
+
4. **Market and triggers:** what makes a company buy now (hiring for X, new leader, funding,
|
|
44
|
+
migration, regulation), with sources. These seed `signals.yaml`.
|
|
45
|
+
5. **Write `context/research.md`** from `.gtm/templates/project/research.md`.
|
|
46
|
+
6. **Summarise for the human** in 5 lines: what you found, the strongest ICP hint, top competitors,
|
|
47
|
+
and the open questions only they can answer.
|
|
48
|
+
|
|
49
|
+
## Outputs
|
|
50
|
+
`context/research.md`: sourced facts, customers (suppression candidates), **core candidates** and
|
|
51
|
+
**segment candidates** for the ICP, competitors (exclusion candidates), triggers (signal candidates),
|
|
52
|
+
open questions. Candidates are suggestions until a
|
|
53
|
+
stakeholder confirms them in intake or ICP.
|
|
54
|
+
|
|
55
|
+
## Checklist
|
|
56
|
+
- [ ] Every claim has a URL and a confidence mark
|
|
57
|
+
- [ ] No individuals' personal data
|
|
58
|
+
- [ ] Customers and competitors listed with domains
|
|
59
|
+
- [ ] Open questions listed (only what research can't answer)
|
|
60
|
+
|
|
61
|
+
## Next
|
|
62
|
+
Back to `.gtm/skills/00-intake/SKILL.md` (draft intake from research), or the stage that asked for a refresh.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# One project's stack: Postgres (this project's data + Hatchet's state), hatchet-lite (job engine) and
|
|
2
|
+
# the gtm worker at the core version this project repo pins. Driven by `npx gtm stack up|down|status`,
|
|
3
|
+
# which sets the compose project name (gtm-<project_id>), the ports and the worker image.
|
|
4
|
+
# Data stays in this project's Docker volumes; nothing here is written into the repo.
|
|
5
|
+
services:
|
|
6
|
+
postgres:
|
|
7
|
+
image: postgres:16.10
|
|
8
|
+
command: postgres -c max_connections=200
|
|
9
|
+
environment:
|
|
10
|
+
POSTGRES_USER: gtm
|
|
11
|
+
POSTGRES_PASSWORD: gtm
|
|
12
|
+
POSTGRES_DB: gtm
|
|
13
|
+
# Local only: reachable from this machine (QA samples in a DB console), not from the network.
|
|
14
|
+
ports: ["127.0.0.1:${GTM_PG_PORT:-5432}:5432"]
|
|
15
|
+
volumes:
|
|
16
|
+
- pgdata:/var/lib/postgresql/data
|
|
17
|
+
- ./init:/docker-entrypoint-initdb.d:ro
|
|
18
|
+
healthcheck:
|
|
19
|
+
test: ["CMD-SHELL", "pg_isready -U gtm -d gtm"]
|
|
20
|
+
interval: 2s
|
|
21
|
+
timeout: 5s
|
|
22
|
+
retries: 30
|
|
23
|
+
|
|
24
|
+
hatchet-lite:
|
|
25
|
+
image: ghcr.io/hatchet-dev/hatchet/hatchet-lite:v0.110.5
|
|
26
|
+
depends_on:
|
|
27
|
+
postgres:
|
|
28
|
+
condition: service_healthy
|
|
29
|
+
ports:
|
|
30
|
+
- "127.0.0.1:${GTM_HATCHET_UI_PORT:-8888}:8888"
|
|
31
|
+
- "127.0.0.1:${GTM_HATCHET_GRPC_PORT:-7077}:7077"
|
|
32
|
+
environment:
|
|
33
|
+
DATABASE_URL: postgresql://gtm:gtm@postgres:5432/hatchet?sslmode=disable
|
|
34
|
+
SERVER_AUTH_COOKIE_DOMAIN: localhost
|
|
35
|
+
SERVER_AUTH_COOKIE_INSECURE: "t"
|
|
36
|
+
SERVER_GRPC_BIND_ADDRESS: 0.0.0.0
|
|
37
|
+
SERVER_GRPC_INSECURE: "t"
|
|
38
|
+
SERVER_GRPC_BROADCAST_ADDRESS: localhost:${GTM_HATCHET_GRPC_PORT:-7077}
|
|
39
|
+
SERVER_GRPC_PORT: "7077"
|
|
40
|
+
SERVER_URL: http://localhost:${GTM_HATCHET_UI_PORT:-8888}
|
|
41
|
+
SERVER_AUTH_SET_EMAIL_VERIFIED: "t"
|
|
42
|
+
SERVER_INTERNAL_CLIENT_INTERNAL_GRPC_BROADCAST_ADDRESS: localhost:7077
|
|
43
|
+
volumes:
|
|
44
|
+
- hatchet_config:/config
|
|
45
|
+
|
|
46
|
+
worker:
|
|
47
|
+
image: ${GTM_WORKER_IMAGE:-ghcr.io/veleads/gtm-worker:latest}
|
|
48
|
+
depends_on:
|
|
49
|
+
postgres:
|
|
50
|
+
condition: service_healthy
|
|
51
|
+
hatchet-lite:
|
|
52
|
+
condition: service_started
|
|
53
|
+
restart: unless-stopped
|
|
54
|
+
environment:
|
|
55
|
+
DATABASE_URL: postgresql://gtm:gtm@postgres:5432/gtm
|
|
56
|
+
HATCHET_CLIENT_TOKEN: ${HATCHET_CLIENT_TOKEN:-}
|
|
57
|
+
HATCHET_CLIENT_HOST_PORT: hatchet-lite:7077
|
|
58
|
+
HATCHET_CLIENT_TLS_STRATEGY: none
|
|
59
|
+
# Provider API keys: ~/.gtm/stacks/<project_id>.secrets.env, outside the repo. Optional.
|
|
60
|
+
env_file:
|
|
61
|
+
- path: ${GTM_SECRETS_FILE:-/dev/null}
|
|
62
|
+
required: false
|
|
63
|
+
|
|
64
|
+
volumes:
|
|
65
|
+
pgdata:
|
|
66
|
+
hatchet_config:
|
|
@@ -5,6 +5,9 @@ campaign_id: __CAMPAIGN_ID__
|
|
|
5
5
|
owner: __OWNER__
|
|
6
6
|
stage: icp
|
|
7
7
|
tam_id: __PROJECT_ID__-tam-TODO
|
|
8
|
+
tam:
|
|
9
|
+
source: mock-accounts # mock-accounts proves the pipeline locally; replace with a real account source
|
|
10
|
+
max_accounts_per_segment: 500
|
|
8
11
|
segments: [segment-one]
|
|
9
12
|
personas: [persona-one]
|
|
10
13
|
|
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
# ICP: __PROJECT_NAME__
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Written in stage `01-icp` from `research.md`, intake and won/lost deals. This file holds the *why*;
|
|
4
|
+
> `icp.yaml` holds the filters jobs use. Keep them in sync, and review the final filters with
|
|
5
|
+
> `npx gtm icp`. Mark anything that can't be a filter as **(context only)**.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Summary
|
|
8
|
+
One paragraph: who we sell to, the problem we solve for them, and why now.
|
|
6
9
|
|
|
7
|
-
##
|
|
8
|
-
|
|
10
|
+
## 1. Core profile (every segment)
|
|
11
|
+
What every good-fit company has in common, with evidence.
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
| Criterion | Value | Evidence |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Industry family | | _e.g. 8 of 10 best customers are staffing agencies_ |
|
|
16
|
+
| Size band | | |
|
|
17
|
+
| Business model | | (context only, if not filterable) |
|
|
18
|
+
| Required tech | | |
|
|
12
19
|
|
|
13
|
-
##
|
|
20
|
+
## 2. Segments
|
|
21
|
+
One to three. Each differs from core in filters **and** (usually) treatment.
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
### <segment-id>
|
|
24
|
+
- **Who (filters beyond core):** _e.g. HQ in GB_
|
|
25
|
+
- **Hypothesis:** who, why they'd buy now, what proves it.
|
|
26
|
+
_"UK agencies with 10+ open roles book calls because hiring capacity is their bottleneck."_
|
|
27
|
+
- **Evidence:** wins, research, interviews.
|
|
28
|
+
- **Pain → value proposition → proof:**
|
|
29
|
+
- **Buyer / personas:** all, or only <persona-ids>
|
|
30
|
+
- **Treatment:** language, compliance basis, send timezone, offer differences (if any)
|
|
31
|
+
- **Expected TAM range:** _e.g. 2,000–4,000 companies_
|
|
32
|
+
- **Success criteria:** _e.g. ≥ 1.5 meetings per 100 contacts after 300 contacts_
|
|
33
|
+
|
|
34
|
+
## 3. Personas
|
|
35
|
+
- **<persona-id> (decision_maker / champion / influencer / user):** titles, what they care about.
|
|
36
|
+
Details and language in `personas.md`.
|
|
37
|
+
|
|
38
|
+
## 4. Signals (why now)
|
|
39
|
+
The triggers in `signals.yaml`, in plain words, with the angle each gives the copy.
|
|
40
|
+
|
|
41
|
+
## 5. Exclusions
|
|
42
|
+
- Competitors (from `research.md`):
|
|
43
|
+
- Excluded industries / countries:
|
|
44
|
+
- Existing customers and open deals: loaded as suppression lists (never written here).
|
|
45
|
+
|
|
46
|
+
## Candidates considered and dropped
|
|
47
|
+
| Candidate | Why dropped |
|
|
48
|
+
|---|---|
|
|
49
|
+
|
|
50
|
+
## Decision log
|
|
51
|
+
- <YYYY-MM-DD>: first ICP drafted.
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
# yaml-language-server: $schema=../.gtm/schemas/icp.schema.json
|
|
2
|
+
# Layers: core (every segment) → segments (only what differs) → personas → exclusions. Signals live in
|
|
3
|
+
# signals.yaml. A segment field REPLACES the same core field. Review the result with `npx gtm icp`.
|
|
2
4
|
version: 2
|
|
3
5
|
project_id: __PROJECT_ID__
|
|
4
6
|
approval:
|
|
5
7
|
status: pending # only a named human approves (AGENTS.md hard stop)
|
|
6
8
|
approved_by: null
|
|
7
9
|
approved_at: null
|
|
10
|
+
core:
|
|
11
|
+
firmographics:
|
|
12
|
+
industries: [TODO] # what every good-fit company shares
|
|
13
|
+
employee_count: {min: 20, max: 500}
|
|
8
14
|
segments:
|
|
9
15
|
- id: segment-one
|
|
10
|
-
description: TODO
|
|
16
|
+
description: TODO # who, in a few words
|
|
11
17
|
firmographics:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
hq_countries: [] # only what differs from core, e.g. [GB]
|
|
19
|
+
# - id: segment-two
|
|
20
|
+
# description: ...
|
|
21
|
+
# firmographics: {hq_countries: [DE, AT, CH], employee_count: {min: 50, max: 1000}}
|
|
22
|
+
# personas: [persona-one] # optional: restrict personas for this segment
|
|
15
23
|
personas:
|
|
16
24
|
- id: persona-one
|
|
17
25
|
role: decision_maker
|
|
18
26
|
titles_any: [TODO]
|
|
27
|
+
titles_none: [Assistant, Intern]
|
|
19
28
|
seniorities: []
|
|
20
29
|
departments: []
|
|
21
30
|
exclusions:
|
|
@@ -1,29 +1,44 @@
|
|
|
1
1
|
# Intake: __PROJECT_NAME__
|
|
2
2
|
|
|
3
|
-
> Stage 0 output.
|
|
3
|
+
> Stage 0 output. Drafted from `context/research.md` first; stakeholders only answer what research
|
|
4
|
+
> couldn't. Mark each field `(researched: <source>; confirm)`, `(confirmed by <who>)` or `(needs you)`.
|
|
5
|
+
> Write "unknown" rather than leaving blanks.
|
|
4
6
|
|
|
5
7
|
## Company
|
|
6
8
|
- **What they sell (one sentence):**
|
|
9
|
+
_e.g. "Outbound lead generation for B2B SaaS, delivered as a managed service."_
|
|
7
10
|
- **Website:**
|
|
8
|
-
- **
|
|
11
|
+
- **Deal size, sales cycle and motion:** average first-year value, days from first call to signature,
|
|
12
|
+
and how deals close.
|
|
13
|
+
_e.g. "~$15k/year, 30–45 days, founder-led demo then proposal."_
|
|
9
14
|
- **Current customers (count, typical size, industries, regions):**
|
|
10
15
|
- **Why outbound now:**
|
|
11
16
|
|
|
12
17
|
## Goals
|
|
13
|
-
- **Target
|
|
14
|
-
|
|
18
|
+
- **Target and timeframe:** a number you can check later.
|
|
19
|
+
_e.g. "20 qualified meetings in 60 days."_
|
|
20
|
+
- **What counts as a meeting:** who must attend and what makes it qualified.
|
|
21
|
+
_e.g. "Head of Sales or founder at a 20–200 person SaaS company books a 30-min call."_
|
|
15
22
|
|
|
16
|
-
##
|
|
17
|
-
|
|
23
|
+
## Owners and regions
|
|
24
|
+
(Recorded in `project.yaml`, explained here.)
|
|
25
|
+
- **GTM engineer:** runs the agent and the jobs. _e.g. "@abdullah"_
|
|
26
|
+
- **SDRs:** handle replies and booked meetings. _e.g. "@sara", or "none yet"_
|
|
27
|
+
- **Approver:** says yes to the ICP and the copy before anything is sent. Must not be the person who
|
|
28
|
+
opens the PRs (GitHub doesn't allow approving your own PR). _e.g. "@maria (co-founder)"_
|
|
29
|
+
- **Regions:** where prospects are; drives privacy rules (GDPR for UK/EU) and TAM filters.
|
|
30
|
+
_e.g. "UK and US"_
|
|
31
|
+
|
|
32
|
+
## Questionnaire (research pre-fills what it can)
|
|
33
|
+
1. Best customers (up to 10, company names only). Why did each buy?
|
|
18
34
|
2. Last 5 lost deals. Why did each lose?
|
|
19
|
-
3. Who signs, who champions, who blocks?
|
|
35
|
+
3. Who signs, who champions, who blocks? (roles, not names)
|
|
20
36
|
4. Top 3 competitors and how you win against each.
|
|
21
37
|
5. Triggers that make a prospect buy now (hiring, funding, new leader, migration...).
|
|
22
38
|
6. Proof you can use in outbound (case studies, numbers, logos) and what is off-limits.
|
|
23
39
|
7. Tone: words you love, words you hate.
|
|
24
|
-
8.
|
|
25
|
-
9.
|
|
26
|
-
10. Anyone or anything we must never contact.
|
|
40
|
+
8. Previous outbound: what was sent, results, which lists.
|
|
41
|
+
9. Anyone or anything we must never contact.
|
|
27
42
|
|
|
28
43
|
## Constraints
|
|
29
44
|
-
|
|
@@ -40,4 +55,4 @@
|
|
|
40
55
|
| Calendar / booking link | stakeholder | |
|
|
41
56
|
| Lawful basis documented (LIA for EU/UK legitimate interest) | stakeholder + GTM team | |
|
|
42
57
|
| Privacy notice URL | stakeholder | |
|
|
43
|
-
|
|
|
58
|
+
| GitHub: owners added to the repo; `npx gtm github setup --apply` run | GTM team | |
|
|
@@ -4,9 +4,13 @@ project_id: __PROJECT_ID__
|
|
|
4
4
|
name: __PROJECT_NAME__
|
|
5
5
|
status: onboarding
|
|
6
6
|
started_at: __TODAY__
|
|
7
|
+
# Owners: use GitHub handles (@username). An approver handle makes ICP/copy sign-off a PR review that
|
|
8
|
+
# GitHub enforces (CODEOWNERS). Use a plain name only for an approver who isn't on GitHub.
|
|
7
9
|
owners:
|
|
8
|
-
gtm_engineer: TODO
|
|
9
|
-
sdrs: []
|
|
10
|
-
approver: TODO #
|
|
11
|
-
regions: [TODO]
|
|
10
|
+
gtm_engineer: TODO # e.g. "@abdullah": runs the agent and jobs
|
|
11
|
+
sdrs: [] # e.g. ["@sara"]: handle replies and meetings
|
|
12
|
+
approver: TODO # e.g. "@maria": signs off ICP and copy
|
|
13
|
+
regions: [TODO] # where prospects are, e.g. [UK, US]
|
|
12
14
|
features: {} # opt-in core features (see .gtm/ CHANGELOG / FEATURES)
|
|
15
|
+
tracker:
|
|
16
|
+
kind: github # mirror stages and gates as GitHub issues (`npx gtm track`); "none" to switch off
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Research: __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
> Written by the `research` skill. Every claim has a source URL and a confidence mark
|
|
4
|
+
> (confirmed / likely / inferred). Candidates are suggestions until a stakeholder confirms them.
|
|
5
|
+
> Companies only: no individuals' personal data.
|
|
6
|
+
|
|
7
|
+
- **Researched on:** <YYYY-MM-DD> by <agent / person>
|
|
8
|
+
- **Website:**
|
|
9
|
+
- **Web access:** yes / no (worked from pasted page text)
|
|
10
|
+
|
|
11
|
+
## The business
|
|
12
|
+
| Fact | Value | Source | Confidence |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| What they sell (one sentence) | | | |
|
|
15
|
+
| Who it's for | | | |
|
|
16
|
+
| Pricing model / price points | | | |
|
|
17
|
+
| Proof points (numbers) | | | |
|
|
18
|
+
| Positioning words they repeat | | | |
|
|
19
|
+
|
|
20
|
+
## Public customers (suppression candidates)
|
|
21
|
+
| Company | Domain | Industry / size | Source |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
|
|
24
|
+
### ICP hints
|
|
25
|
+
**Core candidates** (traits most customers share):
|
|
26
|
+
| Trait | How many customers share it | Source |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
|
|
29
|
+
**Segment candidates** (clusters that differ in filters and treatment):
|
|
30
|
+
| Cluster | Differs by | Members | Why it may need its own angle |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
|
|
33
|
+
## Competitors (exclusion candidates)
|
|
34
|
+
| Competitor | Domain | Positioning | Pricing | How we differ | Source |
|
|
35
|
+
|---|---|---|---|---|---|
|
|
36
|
+
|
|
37
|
+
## Buying triggers (signal candidates)
|
|
38
|
+
| Trigger | Why it matters | Source |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
|
|
41
|
+
## Open questions (only stakeholders can answer)
|
|
42
|
+
-
|
|
43
|
+
|
|
44
|
+
## History
|
|
45
|
+
- <YYYY-MM-DD>: first research
|
|
@@ -10,6 +10,7 @@ npm install # or: pnpm install / yarn install
|
|
|
10
10
|
cp .env.example .env
|
|
11
11
|
npx gtm status
|
|
12
12
|
npx gtm validate
|
|
13
|
+
npx gtm stack up # this project's own stack (needs Docker); jobs run on it
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
## What lives where
|
|
@@ -22,3 +23,7 @@ npx gtm validate
|
|
|
22
23
|
## Core upgrades
|
|
23
24
|
A weekly workflow opens a PR when a new core is released (`.github/workflows/gtm-upgrade.yml`).
|
|
24
25
|
Manually: `npx gtm upgrade` (refuses while a campaign is between QA and launch).
|
|
26
|
+
|
|
27
|
+
## GitHub
|
|
28
|
+
Approvals and tracking run through GitHub: see `.gtm/guides/github.md` (owners as `@handles`,
|
|
29
|
+
`npx gtm github setup --apply`, PR-based sign-off, the org-wide board).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# Copy to .env (never commit). Provider API keys do NOT belong here: they
|
|
1
|
+
# Copy to .env (never commit). Provider API keys do NOT belong here: they go to this project's worker
|
|
2
|
+
# (local stack: ~/.gtm/stacks/<project_id>.secrets.env). `npx gtm stack up` writes the connection
|
|
3
|
+
# settings to ~/.gtm/stacks/<project_id>.env; set HATCHET_CLIENT_TOKEN here only for a hosted stack.
|
|
2
4
|
GTM_USER=jdoe
|
|
3
|
-
# Phase 2: trigger jobs on the shared Hatchet, read-only DB access for QA samples
|
|
4
5
|
HATCHET_CLIENT_TOKEN=
|
|
5
|
-
DATABASE_URL_READONLY=
|
|
@@ -6,6 +6,7 @@ on:
|
|
|
6
6
|
|
|
7
7
|
permissions:
|
|
8
8
|
contents: read
|
|
9
|
+
pull-requests: read # gtm validate --verify-github reads PR reviews
|
|
9
10
|
|
|
10
11
|
jobs:
|
|
11
12
|
check:
|
|
@@ -25,5 +26,7 @@ jobs:
|
|
|
25
26
|
fi
|
|
26
27
|
- name: Core files untouched and in sync
|
|
27
28
|
run: npx gtm sync --check
|
|
28
|
-
- name: Validate project repo
|
|
29
|
-
run: npx gtm validate
|
|
29
|
+
- name: Validate project repo (and verify GitHub approvals)
|
|
30
|
+
run: npx gtm validate --verify-github
|
|
31
|
+
env:
|
|
32
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|