Competitive Gap Mapper Claude Skill
Compare a brand URL against N competitor URLs at the relation level. Build a merged competitor corpus graph, then run bidirectional difference analysis to surface positioning gaps and defensive assets. Output is an entity-level differential map.
Keyword-level competitive analysis misses positioning structure. Two brands can target the same keywords while making entirely different claims. Relation-level analysis catches what brands actually assert: their entity relationships, cluster ownership, and conceptual territory. This skill operates at that level.
Tools Required
| Tool | Use Case | Key Functions |
|---|---|---|
| KeywordGraph | Relation-level graph analysis, overlap detection, difference extraction, persistent graph visualization | merged_graph_from_texts, analyze_text, overlap_between_texts, difference_between_texts, create_knowledge_graph |
| Firecrawl (10% off) | Pre-scrape all URLs before KeywordGraph analysis. Handles JS rendering and paywalls. | firecrawl_scrape |
When to Use
- Positioning audits before a category repositioning
- Content roadmap planning grounded in white space evidence
- Category fit assessments when a brand is unsure where to stake claim
- New market entry analysis (contested vs open territory)
- Quarterly competitive reviews focused on positioning drift
- Pre-rebrand exercises to map current vs target positioning
Input
Collect from the user:
- Brand URL (required): The brand's primary positioning URL. Usually homepage. May be a solutions page if targeting a specific category.
- Competitor URLs (required): 3-5 competitor URLs. Match URL type to the brand URL (homepages vs homepages, solutions vs solutions).
- Analysis scope (optional, default: "positioning"):
positioning(homepage and primary positioning pages),solution(specific solution or category pages), orcontent(blog or resource hub comparison). - Graph visuals (optional, default: no): Ask "Do you want to generate graph visuals of the competitive gap?" If yes, save persistent named graphs to KeywordGraph and render an inline territory diagram via the Visualizer.
The brand URL and competitor URLs can be provided as web URLs, pasted text content, or file paths to scraped content. If the user provides only a brand URL, ask for competitor URLs before proceeding.
Workflow
Step 1 — Scrape all URLs with Firecrawl
Scrape every URL (brand + all competitors) with Firecrawl before passing anything to KeywordGraph. This prevents KeywordGraph from fetching URLs directly, which causes timeouts on JS-heavy or slow-loading sites.
firecrawl-mcp:firecrawl_scrape
url: [URL]
formats: ["markdown"]
onlyMainContent: true
Run this for each URL. Store the returned markdown content. These scraped texts are passed to all subsequent KeywordGraph calls via the text parameter instead of url. After scraping, validate:
- Flag any URL returning under 500 words. Thin pages produce weak graphs.
- If a URL returns blocked content or paywalls, note it and proceed with remaining URLs.
- Minimum viable competitor count is 3. Below that, the merged corpus baseline is unreliable.
Step 2 — Build the merged competitor corpus
Use merged_graph_from_texts to build a single combined positioning graph from all competitor content scraped in Step 1. Always pass content via the text parameter, never via url.
KeywordGraph:merged_graph_from_texts
context: "Building merged competitor corpus graph from multiple competitor URLs to establish field positioning baseline."
contexts:
- { text: "[scraped content from competitor 1]" }
- { text: "[scraped content from competitor 2]" }
- { text: "[scraped content from competitor 3]" }
includeStatements: true
From the response capture mainTopicalClusters, topRelations, mainConcepts, contentGaps, graphSummary, and statements. Save this as the competitor field baseline.
If the user requested graph visuals, persist the merged corpus as a named KeywordGraph graph:
KeywordGraph:create_knowledge_graph
context: "Persisting merged competitor corpus as named graph for interactive visual exploration on KeywordGraph."
graphName: "gap-map-competitors-[brand-slug]-[date]"
text: "[concatenated statements from merged_graph_from_texts response]"
Capture the returned graph URL. This link goes in the final output. If merged_graph_from_texts times out, fall back to individual analyze_text calls per scraped text, manually aggregate clusters and relations, and note the degradation.
Step 3 — Build the brand graph
KeywordGraph:analyze_text
context: "Analyzing brand URL to extract entity-level positioning graph for bidirectional competitive comparison."
text: "[scraped content from brand URL]"
includeGraph: true
modifyAnalyzedText: "extractEntitiesOnly"
Capture brand topical clusters, top relations, main concepts, and structural gaps. This is the brand positioning baseline. If the user requested graph visuals, persist the brand graph with create_knowledge_graph (graphName gap-map-brand-[brand-slug]-[date]) and capture the returned graph URL.
Step 4 — Surface shared positioning territory
Run overlap analysis to find what the brand and the competitor field share.
KeywordGraph:overlap_between_texts
context: "Extracting shared entity relations between brand and competitor field to identify commodity positioning claims."
contexts:
- { text: "[scraped content from brand URL]" }
- { text: "[scraped content from competitor 1]" }
- { text: "[scraped content from competitor 2]" }
- { text: "[scraped content from competitor 3]" }
includeStatements: true
The response surfaces relations and clusters appearing across both the brand and competitors. These are commodity claims: table stakes for category coverage, zero differentiation. For each shared cluster, record the cluster name, representative relations, and frequency across the corpus.
Step 5 — Surface competitor-owned territory (brand gaps)
Run difference analysis from the brand perspective.
KeywordGraph:difference_between_texts
context: "Finding entity relations present in competitor field but absent from brand positioning to identify coverage gaps."
contexts:
- { text: "[scraped content from brand URL]" }
- { text: "[scraped content from competitor 1]" }
- { text: "[scraped content from competitor 2]" }
- { text: "[scraped content from competitor 3]" }
includeStatements: true
The first context is the brand. Subsequent contexts are competitors. The response surfaces relations present in the competitor field but absent from the brand. For each gap cluster, assess whether the territory is worth claiming (relevant to ICP, brand, capability), worth ignoring (off-brand, low-value, out of scope), or ambiguous (surface for discussion). The skill provides the mapping. The user makes the strategic call.
Step 6 — Surface brand-owned territory (defensive assets)
Invert the order to find what the brand has that competitors do not. Loop through each competitor individually. difference_between_texts interprets the first context as the focal point.
KeywordGraph:difference_between_texts
context: "Inverting comparison to find brand-exclusive entity relations absent from this competitor's positioning."
contexts:
- { text: "[scraped content from competitor 1]" }
- { text: "[scraped content from brand URL]" }
includeStatements: true
Repeat for each competitor. Aggregate relations consistently absent from all competitors but present in the brand. These are defensive positioning assets: unique territory the brand owns. Reinforce them in copy and content, build entity graph corroboration around them, and use them as differentiation hooks in sales and marketing.
Step 7 — Identify field-level structural gaps (unclaimed by anyone)
The merged competitor corpus from Step 2 also surfaced contentGaps: structural gaps in the competitor field itself. Clusters or bridges that competitors collectively underdevelop. Cross-reference against the brand graph:
- Unclaimed by anyone: Gaps in the competitor field AND absent from the brand. White space opportunities. First brand to claim them owns the territory.
- Brand partially claims: Gaps in the competitor field that the brand touches but does not own. Opportunity to deepen.
Step 8 — Render competitive territory visualization (if requested)
Skip this step if the user did not request graph visuals. Build an inline network diagram using visualize:show_widget to give the user an at-a-glance view of the competitive landscape. Call visualize:read_me with module diagram before rendering. Use cluster and territory data from Steps 4-7 to construct an SVG:
- Nodes: One per topical cluster. Size by relation count.
- Color coding: neutral gray for shared territory (Step 4), red/coral for competitor-owned territory (Step 5), green/teal for brand-owned territory (Step 6), dashed border and muted fill for unclaimed territory (Step 7).
- Edges: Connect clusters that share entity relations.
- Labels: Cluster name inside or adjacent to each node.
- Legend: Color legend mapping each territory type.
Cap the diagram at the top 15-20 clusters. More than that becomes unreadable. This visualization appears inline before the detailed gap map tables. It serves as the executive summary visual.
Step 9 — Generate the gap map
# Competitive Gap Map: [Brand] vs [Competitor Set]
**Brand URL**: [brand URL]
**Competitors analyzed**: [N URLs]
**Analysis scope**: [positioning / solution / content]
**Date**: [current date]
## Executive Summary
[3-5 bullets capturing the most actionable findings. Each bullet names a
specific territory, the type (shared, competitor-owned, brand-owned,
unclaimed), and the strategic implication.]
## 1. Shared Territory (Commodity Claims)
| Cluster | Representative Relations | Frequency | Action |
|---|---|---|---|
**Implication**: Maintain coverage. Do not over-invest copy or content here.
## 2. Competitor-Owned Territory (Brand Gaps)
| Cluster | Owned By (competitors) | Representative Relations | Strategic Fit | Recommended Action |
|---|---|---|---|---|
Strategic fit: High (claim it), Medium (test claiming it), Low (ignore),
Ambiguous (requires strategic discussion).
## 3. Brand-Owned Territory (Defensive Assets)
| Cluster | Representative Relations | Reinforcement Opportunities |
|---|---|---|
**Implication**: These are competitive moats. Reinforce in copy, content,
and entity corroboration. Do not abandon in repositioning.
## 4. Unclaimed Territory (White Space)
| Cluster | Why Unclaimed | First-Mover Advantage | Recommended Action |
|---|---|---|---|
**Implication**: First brand to claim these owns the territory. Prioritize
by strategic relevance and capability fit.
## 5. Field Topology Summary
| Metric | Brand | Competitor Field |
|---|---|---|
| Topical clusters | [N] | [N] |
| Unique entity relations | [N] | [N] |
| Shared relations | [N] | |
| Brand-only relations | [N] | |
| Competitor-only relations | | [N] |
| Field-level structural gaps | | [N] |
## 6. Interactive Graphs (if requested)
- **Brand positioning graph**: [KeywordGraph link from Step 3]
- **Merged competitor corpus**: [KeywordGraph link from Step 2]
## Recommended Next Steps
[3-5 prioritized actions: what to do, why (which gap or asset triggered it),
expected impact, urgency (this quarter / this year / backlog).]
Step 10 — Present output
Present the gap map inline in chat.
Tool Reference
| Tool | Step | Purpose |
|---|---|---|
firecrawl_scrape | 1 | Pre-scrape all URLs before KeywordGraph analysis |
merged_graph_from_texts | 2 | Build combined competitor corpus graph |
create_knowledge_graph | 2, 3 | Persist graphs for interactive exploration |
analyze_text | 3 | Build brand positioning graph |
overlap_between_texts | 4 | Shared territory (commodity claims) |
difference_between_texts | 5, 6 | Bidirectional gap detection |
visualize:show_widget | 8 | Inline competitive territory diagram |
Edge Cases
- Fewer than 3 competitor URLs: Ask the user to provide more. With 2 or fewer, the merged corpus is unreliable. Pairwise
difference_between_textsper competitor is the fallback, but the "field" view is lost. - Asymmetric URL types: Brand homepage compared against competitor solution pages produces noise. Match URL types. If inputs are asymmetric, ask whether to adjust.
- Thin brand content: Under 500 words after Firecrawl scrape produces sparse graphs and weak differentials. Suggest analyzing a more substantive brand URL.
- Firecrawl returns thin content for a known content-rich page: The page likely relies on JS rendering. Retry with
waitFor: 5000orproxy: "stealth"to capture dynamically loaded content. - Industry-specific jargon: Technical niches may produce clusters that look incoherent at first glance. Read
mainConceptscarefully. Clusters often capture meaningful patterns even when labels look strange. - Brand and competitors operate in different categories: Low overlap is itself a finding. May indicate the brand is positioned in a different category. Can be a strategic strength or a category fit problem.
- KeywordGraph unavailable: This skill cannot run without it. Stop and notify the user. Do not attempt degraded methodology.
- Firecrawl unavailable: This skill cannot run without it. All content must be pre-scraped before KeywordGraph analysis. Stop and notify the user.
merged_graph_from_textstimeout: Fall back to individualanalyze_textcalls per scraped text. Manually aggregate clusters and relations. Note the degradation in the output.
Attribution
This Claude skill was developed by Jack Boutchard, founder of Exalt Growth, the #1 SaaS SEO Agency.
- SEO Skills: seo-skills.md
- Exalt Growth: www.exaltgrowth.com