feat(gtm): add /gtm command for cross-channel go-to-market planning#141
feat(gtm): add /gtm command for cross-channel go-to-market planning#141auroracapital merged 1 commit intomainfrom
Conversation
Introduces the ops-gtm skill as a strategy layer on top of /marketing. Generates a GTM plan across paid, unpaid, sales, and AI-automation avenues, then hands executable items off to /marketing via the Skill tool so credential resolution and API calls stay single-sourced. https://claude.ai/code/session_01RNfoAVC7WcLQwuhu24Q1EE
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 53 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
||
| ```bash | ||
| # Existing prefs | ||
| jq -r '{project: .gtm_default_project, audience: .gtm_default_audience, voice: .gtm_brand_voice, budget: .gtm_monthly_budget, stage: .gtm_stage}' "$PREFS_PATH" 2>/dev/null |
There was a problem hiding this comment.
Bug: The shell variable $PREFS_PATH is used in bash snippets without being defined, causing preference read and write operations to silently fail.
Severity: HIGH
Suggested Fix
Define the PREFS_PATH variable at the beginning of the skill file, similar to other skills. Add the line: PREFS_PATH="${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json" to ensure the correct path is used for preference operations.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: claude-ops/skills/ops-gtm/SKILL.md#L313
Potential issue: The `ops-gtm/SKILL.md` file uses the shell variable `$PREFS_PATH` in
multiple bash snippets without defining it. When these snippets are executed,
`$PREFS_PATH` expands to an empty string. Consequently, commands like `jq` that attempt
to read from or write to the preferences file will target an empty file path and
silently fail. This prevents the `/gtm setup` command from reading existing preferences
or persisting new user configurations, effectively breaking the skill's ability to
remember settings across sessions.
Did we get this right? 👍 / 👎 to inform future reviews.
| 4. **Monthly budget tier** — `[<$1k, $1k–5k, $5k–25k, $25k+]` | ||
| 5. **Brand voice** — `[Playful, Professional, Technical, Bold]` | ||
|
|
||
| Save to `$PREFS_PATH` as `gtm_default_project`, `gtm_project_type`, `gtm_stage`, `gtm_monthly_budget`, `gtm_brand_voice`. Never write API keys here — GTM does not own any credentials (`/marketing` does). |
There was a problem hiding this comment.
🔴 Preference key mismatch: setup saves gtm_project_type but Runtime Context reads gtm_default_audience
The setup section (line 329) saves the Project Type answer under the key gtm_project_type, but this key is never read anywhere. Meanwhile, the Runtime Context (line 32) and the setup auto-scan jq command (line 313) both read gtm_default_audience, which is never saved by any flow. This means:
- Running
/gtm setupand answering "Project type" writes to a dead key (gtm_project_type) — subsequent runs never pick it up. - The
gtm_default_audiencepreference is always null/missing, so the audience default never works despite the sub-command routing table (line 77) claiming setup configures "audience".
Key mismatch detail
- Read sites:
claude-ops/skills/ops-gtm/SKILL.md:32listsgtm_default_audienceas a readable pref;claude-ops/skills/ops-gtm/SKILL.md:313jq command reads.gtm_default_audience. - Write site:
claude-ops/skills/ops-gtm/SKILL.md:329savesgtm_project_type. - Neither key cross-references the other.
Prompt for agents
The preference keys are inconsistent between the setup save operation and the Runtime Context / auto-scan read operations. The setup section at line 329 saves gtm_project_type, but the Runtime Context at line 32 and the jq auto-scan at line 313 read gtm_default_audience. One of these must be corrected to match the other. Additionally, there is no actual audience question in the setup flow (lines 322-327), yet the routing table at line 77 claims setup configures audience. The fix likely involves either: (a) renaming gtm_project_type to gtm_default_audience everywhere and reframing the project type question as an audience question, or (b) adding a separate audience question to setup and updating the Runtime Context and jq to also read gtm_project_type, or (c) deciding which concept (project type vs audience) is the correct one and aligning all references.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
|
||
| Save to `$PREFS_PATH` as `gtm_default_project`, `gtm_project_type`, `gtm_stage`, `gtm_monthly_budget`, `gtm_brand_voice`. Never write API keys here — GTM does not own any credentials (`/marketing` does). | ||
|
|
||
| Finish with a smoke test: run `/gtm brief` in background and report `✓ setup complete — try /gtm plan` or `✗ [error]`. |
There was a problem hiding this comment.
Severity 7/10 — Smoke test is self-referential and background-incompatible
/gtm brief requires intake questions (AskUserQuestion calls for project name, ICP, etc.). Running it with run_in_background: true means:
- The sub-command will immediately fail or hang — no user can respond to interactive prompts in a background task.
- Even if it were fire-and-forget, the result (✓/✗) is reported before the background task completes, making the status always stale/incorrect.
Suggested fix — replace the self-invocation with a lightweight prefs-validation check:
jq -e '.gtm_default_project and .gtm_stage' "$PREFS_PATH" > /dev/null 2>&1 \
&& echo "✓ setup complete — try /gtm plan" \
|| echo "✗ preferences write failed — check $PREFS_PATH"This verifies the actual output of the setup flow without recursively invoking another sub-command.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is ON, but it could not run because the branch was deleted or merged before autofix could start.
Reviewed by Cursor Bugbot for commit 8726ac4. Configure here.
| 4. **Monthly budget tier** — `[<$1k, $1k–5k, $5k–25k, $25k+]` | ||
| 5. **Brand voice** — `[Playful, Professional, Technical, Bold]` | ||
|
|
||
| Save to `$PREFS_PATH` as `gtm_default_project`, `gtm_project_type`, `gtm_stage`, `gtm_monthly_budget`, `gtm_brand_voice`. Never write API keys here — GTM does not own any credentials (`/marketing` does). |
There was a problem hiding this comment.
Preference key mismatch between setup save and read
Medium Severity
The setup sub-command saves gtm_project_type to preferences, but the Runtime Context section and the setup auto-scan jq command both read gtm_default_audience instead — a key that is never written anywhere. This means "Project type" chosen during setup is silently discarded on subsequent runs, and the gtm_default_audience preference will always be empty, causing the intake flow to re-ask for information the user already provided.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 8726ac4. Configure here.
There was a problem hiding this comment.
Pull request overview
Adds a new ops-gtm skill that acts as a go-to-market strategy layer over ops-marketing, generating cross-channel GTM plans and delegating executable work to /marketing via the Skill tool.
Changes:
- Introduces
ops-gtmskill definition and operating instructions (intake, planning flow, channel catalog, handoff flow). - Adds sub-command structure for targeted GTM outputs (
plan,paid,unpaid,sales,automation,launch,brief,setup). - Defines integration points that hand execution off to
ops-marketingrather than duplicating credential/API logic.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| Save to `$PREFS_PATH` as `gtm_default_project`, `gtm_project_type`, `gtm_stage`, `gtm_monthly_budget`, `gtm_brand_voice`. Never write API keys here — GTM does not own any credentials (`/marketing` does). |
There was a problem hiding this comment.
Setup says it saves gtm_project_type, but earlier sections reference gtm_default_audience and instruct intake to read gtm_default_* keys. This looks inconsistent (audience is never collected/saved; project type key name doesn’t match the gtm_default_* pattern). Either add an audience question and save it, or remove the gtm_default_audience references; and standardize the preference key names so intake/setup agree.
| Save to `$PREFS_PATH` as `gtm_default_project`, `gtm_project_type`, `gtm_stage`, `gtm_monthly_budget`, `gtm_brand_voice`. Never write API keys here — GTM does not own any credentials (`/marketing` does). | |
| 6. **Default audience** — free-text, e.g. `SMB founders`, `Developers`, or `Consumers` | |
| Save to `$PREFS_PATH` as `gtm_default_project`, `gtm_default_project_type`, `gtm_default_stage`, `gtm_default_monthly_budget`, `gtm_default_brand_voice`, `gtm_default_audience`. Never write API keys here — GTM does not own any credentials (`/marketing` does). |
| |---|---|---|---| | ||
| | Programmatic SEO | Dev tools, marketplaces, comparison queries | High upfront, compounding | `/marketing seo` (tracking) + manual content ops | | ||
| | Topic-cluster SEO | Content-led SaaS, info-intent | Medium, 3–6mo to signal | `/marketing seo` | | ||
| | Lifecycle email (welcome, nurture, winback) | Any with email capture | Medium, high leverage | `/marketing email` (Klaviyo flows) | |
There was a problem hiding this comment.
The Unpaid catalog and later sections imply /marketing email can scaffold lifecycle flows (e.g., "Klaviyo flows"), but claude-ops/skills/ops-marketing/SKILL.md only documents email metrics/inspection (no flow creation/scaffolding endpoints or commands). As-is, the handoff to /marketing email won’t produce the promised artifacts. Either adjust the wording to match what /marketing email actually does today, or add a corresponding flow-scaffolding capability to /marketing and reference the correct sub-command here.
| | Lifecycle email (welcome, nurture, winback) | Any with email capture | Medium, high leverage | `/marketing email` (Klaviyo flows) | | |
| | Lifecycle email (welcome, nurture, winback) | Any with email capture | Medium, high leverage | `/marketing email` (audit/metrics) + manual Klaviyo flow build | |
| Agent(team_name="gtm-research-team", name="paid-research", prompt="Research paid acquisition channels that fit ${PROJECT_TYPE} at ${STAGE} with $${MONTHLY_BUDGET}/mo. Return a ranked list with fit signals, expected CAC, and which /marketing sub-command (if any) executes each channel.") | ||
| Agent(team_name="gtm-research-team", name="unpaid-research", prompt="Research organic channels (SEO, content, community, PR, partnerships, referrals, lifecycle email) for ${PROJECT_TYPE} at ${STAGE}. Return ranked list with effort estimate, time-to-signal, and /marketing sub-command mapping.") | ||
| Agent(team_name="gtm-research-team", name="sales-research", prompt="Design a sales motion for ${PROJECT_TYPE}: outbound vs inbound vs PLG vs channel. Propose a 30/60/90 plan with concrete activities and target metrics.") | ||
| Agent(team_name="gtm-research-team", name="automation-research", prompt="Propose AI-automation recipes: lead enrichment, personalized outreach, content ops, lifecycle copy, support deflection, lead scoring. For each, list trigger, model/tool stack, and where it plugs into /marketing.") |
There was a problem hiding this comment.
The Agent Team prompt examples include shell-style placeholders like ${PROJECT_TYPE}, ${STAGE}, and ${MONTHLY_BUDGET}, but these variables are never defined elsewhere in the skill. If passed through literally, the subagents will see the raw placeholders instead of the actual intake values. Consider either interpolating the real intake values before calling Agent(...), or switch the examples to neutral placeholders like [project_type], [stage], [monthly_budget] and explicitly instruct to substitute them.
| | Input | Action | | ||
| |---|---| | ||
| | (empty), plan | Full GTM plan across all four avenues | | ||
| | paid | Paid acquisition deep-dive (Meta, Google, LinkedIn, TikTok, affiliates, sponsorships) | | ||
| | unpaid | Organic deep-dive (SEO, content, community, PR, partnerships, referrals, lifecycle email) | |
There was a problem hiding this comment.
Markdown tables here start each row with || (double pipe), which renders an extra empty column in most Markdown renderers. Update the table syntax to use a single leading | (and make the same adjustment to the other tables in this file) so the routing table renders correctly.
| | Channel | Fits | Cost profile | Execution | | ||
| |---|---|---|---| | ||
| | Meta Ads (Facebook + Instagram) | B2C, marketplace, broad consumer | $5–50 CPA typical | `/marketing ads` · `/marketing meta create-campaign` | | ||
| | Google Ads — Search | High-intent buyers, existing demand | $1–30 CPC | `/marketing google-ads` | | ||
| | Google Ads — Performance Max | E-comm with catalog | Blended CPA | `/marketing google-ads` | | ||
| | YouTube Ads | Awareness at scale | $0.01–0.30 CPV | `/marketing google-ads` (video campaigns) | | ||
| | LinkedIn Ads | B2B, ACV > $10k | $8–15 CPC, $50+ CPL | manual — LinkedIn Campaign Manager | |
There was a problem hiding this comment.
The channel catalog tables also use || at the start of rows, which will create an unintended blank first column when rendered. Standardize these tables to normal Markdown | ... | formatting for readability (this applies to Paid/Unpaid/Sales/AI Automation tables).
| Before producing any plan section, make sure the following are known. Source them in this order — only ask the user for what's still missing. | ||
|
|
||
| 1. **Auto-scan the repo** (step 4 of Runtime Context). | ||
| 2. **Read prefs** for `gtm_default_*` keys. |
There was a problem hiding this comment.
Project intake says to read prefs for gtm_default_* keys, but the rest of the document uses non-default keys like gtm_stage / gtm_monthly_budget (and setup later saves gtm_project_type). As written, setup values may not be picked up during intake. Align the preference key naming (either read the specific keys you write in setup, or rename what setup writes to match the gtm_default_* convention).
| 2. **Read prefs** for `gtm_default_*` keys. | |
| 2. **Read prefs** for `gtm_project_type`, `gtm_stage`, `gtm_primary_goal`, and `gtm_monthly_budget` (fall back to legacy `gtm_default_*` keys if present). |
| - `[Launch via /marketing]` — hand top executable items to `/marketing` | ||
| - `[Save only]` — keep the plan file, take no action | ||
| - `[Refine plan]` — re-run with adjusted intake | ||
| - `[Schedule follow-up]` — revisit in 7/30 days (delegate to `/ops cron` if available) |
There was a problem hiding this comment.
The handoff option suggests delegating to /ops cron, but there does not appear to be a corresponding skill/command in this repo (no /ops:cron or ops-cron skill). This would fail at runtime when selected. Consider delegating to an existing mechanism (e.g., ops-daemon / daemon-services scheduling) or remove/replace this option with a supported command.
| - `[Schedule follow-up]` — revisit in 7/30 days (delegate to `/ops cron` if available) | |
| - `[Schedule follow-up]` — revisit in 7/30 days; if a supported scheduling mechanism is available, use it, otherwise save the reminder details in the plan and tell the user to schedule it with their preferred tool |
| ```bash | ||
| # Existing prefs | ||
| jq -r '{project: .gtm_default_project, audience: .gtm_default_audience, voice: .gtm_brand_voice, budget: .gtm_monthly_budget, stage: .gtm_stage}' "$PREFS_PATH" 2>/dev/null | ||
|
|
There was a problem hiding this comment.
The setup section’s shell snippet references $PREFS_PATH, but this variable isn’t defined anywhere in the document. Other skills typically define it as ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json before use. Define PREFS_PATH (or inline the full path consistently) to avoid ambiguous instructions and potential mis-writes.
Minor release bundling three feature PRs and multiple security/stability fixes: - PR #141: /gtm — cross-channel go-to-market planning skill - PR #139: /ops:projects portfolio dashboard + GSD registry sync - PR #140: ops-speedup v2 parity (GPU/ANE monitoring, power hogs, OS actions) - PR #138: ops-memory-extractor Claude Code OAuth support + wacli persistent --follow fix Bug fixes (see CHANGELOG.md [1.7.0] for full detail): - SEV-9: ops-speedup eval shell-injection (Seer) - SEV-9: ops-projects hardcoded /Users/ path breaking for all other users (Seer + blocksorg + cursor + devin + codex) - SEV-8: ops-speedup RETURN trap race + systemd mask allowlist - SEV-7: ops-speedup lsof +D probe wedge, daemon-services backing-script gap, ops-projects AskUserQuestion allowed-tools mismatch - wacli --follow torn down by immediate backfill (now INITIAL_BACKFILL_DELAY=30 + reentrant guard) Bumps claude-ops/package.json, claude-ops/.claude-plugin/plugin.json, and .claude-plugin/marketplace.json plugins[0].version 1.6.2 → 1.7.0.


Introduces the ops-gtm skill as a strategy layer on top of /marketing.
Generates a GTM plan across paid, unpaid, sales, and AI-automation
avenues, then hands executable items off to /marketing via the Skill
tool so credential resolution and API calls stay single-sourced.
https://claude.ai/code/session_01RNfoAVC7WcLQwuhu24Q1EE
Note
Medium Risk
Introduces a new high-level orchestration skill that writes plan artifacts and can trigger downstream
/marketingexecutions, so incorrect routing/prompting could lead to unintended campaign actions despite per-item confirmation gates.Overview
Adds a new
ops-gtmskill (skills/ops-gtm/SKILL.md) that guides users through GTM intake, generates full or channel-specific plans (paid/unpaid/sales/automation/launch/brief), and persists dated plan/brief files under${CLAUDE_PLUGIN_DATA_DIR}/gtm/without overwriting.Defines explicit execution handoff paths that map plan items to
/marketingsub-commands (ormanualsteps), including an approval-driven flow that delegates launchable items toSkill("ops-marketing", ...)while enforcing plugin rules around ≤4-option prompts and explicit confirmation for paid/outbound actions.Reviewed by Cursor Bugbot for commit 8726ac4. Bugbot is set up for automated code reviews on this repo. Configure here.