Conversation
…t set with user The gate has known blind spots that silently drop real competitors: - JS-heavy homepages (Tavily, Firecrawl) — bb fetch returns near-empty hero text, keyword matcher has nothing to match on - Cloudflare challenge pages (Perplexity) — title becomes "Just a moment...", no category signal - Semantic variants — "search foundation" (Jina AI), "retrieval backbone", etc. don't lexically match a list centered on "search API" - Apex-vs-product domain confusion — brave.com (browser) vs api-dashboard.search.brave.com (actual API) Auto-promoting the PASS list to enrichment is the wrong default because enrichment is expensive (5 competitors × 5 lane-subagents = 25 subagents, ~10-15 min wall time, ~300 bb calls). Running that on a partly wrong set wastes all of it. Insert a mandatory Step 4.5 between Gate and Deep Enrichment: 1. Main agent groups /tmp/competitor_gated.jsonl into three buckets — PASS, UNKNOWN (fetch failed — surfaced separately, these are the silent misses), and rejected-brand-matches (top ~10 REJECTs whose title contains a seed token or shows up in Wave C "X vs Y" graph). 2. AskUserQuestion with a checkbox list + free-text "add more" field. 3. Write the confirmed set to /tmp/competitor_enrichment_set.txt — this is the input for Step 5, not /tmp/competitor_passed.txt. Surfaced while testing the skill on Exa (exa.ai): the gate passed 22/101 candidates but silently rejected Tavily, Jina AI, Firecrawl, and Perplexity — all real direct competitors. Step 4.5 catches them. SKILL.md pipeline overview is now 8 steps (was 7). Step 5 input path is updated. workflow.md gets a User-confirm phase section with the three buckets and the list of known gate blind spots.
Lane subagents don't consistently emit the canonical Mentions bullet format specified in workflow.md — they drift into variants per lane: - Discussion lane: `- **HN** — [Title](url) — snippet` - News lane: `- **2025-08-06** — [News] Outlet — "title" — url` - Technical lane: `- **[Benchmark]** ...` (canonical) compile_report.mjs' parseMentions only matches the canonical `- **[SourceType]** Title | Snippet (source: URL, YYYY-MM-DD)` shape, so non-canonical variants silently dropped from the mentions feed. On the Exa end-to-end test, merge reported 404 total mentions across 5 competitors but the rendered feed showed 0. Rather than fight prompt drift across 25 subagents, normalize at merge time. New normalizeMentionBullet() rewrites the three observed variants into canonical form before dedup, so downstream (CSV, per-competitor pages, mentions.html feed) stays clean. After fix on the Exa run: 294 mentions render, 81% with dates, distribution: 42 LinkedIn / 34 HN / 25 Blog / 24 YouTube / 20 Reddit / 12 Comparison / 11 DevTo / 8 News / 4 Substack.
Pricing-page screenshots were adding ~300KB per competitor (Browsaur's
was 580KB) and doubling the per-run browse-CLI cost, but the per-tier
text already lives in the frontmatter (pricing_tiers, pricing_model)
and renders in the Pricing section of the per-competitor page. The
visual didn't add signal over the structured data — it was redundant.
Homepage hero stays. That one is worth keeping: the tagline, visual
brand identity, and positioning screenshot-vs-text diff surface things
the fields can't (logo treatment, animation cues, hero copy voice).
Changes:
- capture_screenshots.mjs: drop pricingCandidates() + pricing capture loop,
simplify result shape to {slug, hero, errors}, halve per-competitor
wall time (~10-20s vs ~15-20s, no pricing fallback chain).
- compile_report.mjs: remove 2-column .shots grid + .shot-pricing CSS,
render single .shot-hero card per page.
- SKILL.md Step 6 + references/workflow.md: doc sync. Also clarify that
`browse` is a separate package from `bb` (@browserbasehq/browse-cli
vs @browserbasehq/cli) — came up as a user question during test runs.
Existing runs re-rendered without pricing shots; ~1.5MB of PNGs removed
from the two test output dirs on Desktop.
…onomy
The old matrix view was broken on real runs. Subagents write key_features
and integrations as prose (comma-separated or full sentences), not as
pipe-separated atomic labels the matrix expected. Pipe-splitting gave one
unique blob per competitor, so the matrix trivially rendered a diagonal —
zero actual comparison across competitors.
Fix is to synthesize a shared taxonomy after enrichment and render the
matrix from that. New flow:
- After merge, the main agent reads all per-competitor .md files, distills
a canonical list of 12-20 atomic features and 10-20 integrations that
apply across the category, and writes {OUTPUT_DIR}/matrix.json with a
per-competitor yes/no mapping.
- compile_report.mjs auto-detects matrix.json and renders the Features +
Integrations axes from it. Falls back to the old pipe-split behavior
when matrix.json is missing.
Verified on the Exa test: before fix, Features axis was 5 one-off blobs
with a diagonal of ●s. After: 19 atomic feature rows × 5 competitors
with 36 ● cells showing real overlap (Web Search API, MCP server, Free
tier, Structured JSON are universal; only Jina has Reranker+Embeddings;
only Tavily has Site crawler; SerpAPI alone has CAPTCHA solving and
hourly throughput SLA; etc.).
SKILL.md Step 5 gets a new "Synthesize the comparison matrix" substep
with the matrix.json schema and the rule "do not skip — without this the
matrix view is trivially diagonal".
Rotated competitor-name headers were misaligned with their data columns and the rightmost labels (Tavily, You.com in the Exa test) got cut off. Root cause: `transform-origin: left top` combined with a fixed `width: 160px` on the label made each label's horizontal extent run ~131px to the right of the column, so labels visually floated several columns to the right of their target and the last N labels overflowed off-screen. Fix: anchor the rotated label to the BOTTOM-RIGHT of its column cell (position:absolute right:4px bottom:8px, transform-origin:right bottom) and steepen rotation from -35° to -55° so horizontal extent is reduced. Drop the fixed width — label is now only as wide as its text, which shrinks short names (Jina AI, Serper) and tightens layout. Cell width 44→52px and header height 130→150px give rotated labels room to live inside the cell rather than overflowing. Result on the Exa run: all 5 competitor names visible, each label's bottom-right sits at the top-right of its column, leaning up-left toward the column — the shingled "hanging label" pattern.
Rotated/diagonal competitor names (35° then 55°) kept producing awkward alignment: the rotation anchor vs the column's visual center never quite matched, and long names (You.com, LlamaIndex) overflowed off the right. Simpler fix: just make the headers horizontal. With 5 competitors × 110px each = 550px, plus the 240px feature column, the table is 790px wide — fits inside the 1200px container without scrolling. For >10 competitors the .mx-scroll wrapper already provides horizontal scroll. Drops the .mx-comp-h-inner rotation wrapper, bumps cell width from 52→110px and data font from 0.9→0.95rem for readability. Feature column grows 220→240px to fit longer taxonomy labels like "Hourly throughput SLA".
…rview
The overview page showed a list of competitors but no explicit view of
the user's own strategic position. Hard to answer at a glance: what do
I uniquely have, what are the table-stakes features I'm missing?
Extend matrix.json with a `userCompany` entry (same shape as each
competitor — features + integrations yes/no flags), and compute two
buckets on the overview page:
- Winning: features the user has where 0–1 competitors also have
them. Ordered by rarity (unique features first).
- Losing: features the user lacks where 3+ competitors have them.
Ordered by gap size (most common features first).
Each item shows who else has it ("only you" / "Tavily, SerpAPI" /
"4 competitors"), so users can assess the strategic weight at a glance.
Rendered as two cards (green-bordered "win", brand-red-bordered "loss")
between the summary stats and the results table on index.html. Cards
gracefully degrade to nothing if matrix.json lacks userCompany — a
skill run that skipped Step 5b's matrix synthesis gets an overview
without the strategic summary rather than an error.
On the Exa test: 4 wins (Site crawler · Embeddings · 3+ SDK languages
· CrewAI integration) and 3 losses (Image/visual search · Dedicated
news endpoint · Hourly throughput SLA). Clear strategic picture in
one screen.
SKILL.md Step 5b "Synthesize the comparison matrix" now documents
userCompany as a required field with the explicit note that without it
the strategic summary doesn't render.
Missed in the strategic-summary commit (652a9a4) — the SKILL.md block that defines the matrix.json schema was on an older revision of the file that didn't get re-staged. Re-add the userCompany field and flag it as required, with the explicit note that skipping it means the "Where you're winning / losing" cards don't render.
Step 5b (matrix synthesis) produces LLM inference from heterogeneous subagent prose. On the Browserbase run 2026-04-23 that inference confidently marked SOC 2 as a Browserbase moat — except Hyperbrowser, Kernel, AND Anchor Browser all have SOC 2 Type II (verified via their own trust portals and compliance blog posts). Shipping that to a GTM team would have made the whole report un-trustable. Add Step 5c: a mandatory fact-check subagent that runs after the taxonomy synthesis and before compile. For every true/false cell in matrix.json, it: - If true: finds a concrete source URL (docs, trust portal, changelog, GitHub license) or flips to false. - If false: runs one targeted bb search to guard against misses. - Outputs a verified matrix.json with a per-cell `sources` field plus a matrix_fact_check.md delta log of every flip. The "Where you're winning / losing" cards are strategic claims. Without verification they hallucinate moats. The SKILL now labels this step MANDATORY with the Browserbase-SOC 2 example as proof of what skipping it costs.
Bulleted lists of winning/losing features read like a spreadsheet, not the analyst-briefing the overview page is supposed to be. Extend matrix.json's userCompany with optional winningSummary / losingSummary prose fields (2-4 sentences each) and render them as paragraphs when present. Falls back to the existing bulleted list when absent so a partial run still shows the boolean comparison. SKILL.md flags these as strongly preferred and tells the main agent to write them AFTER the fact-check step so the prose is grounded in verified cells — otherwise the paragraph will state fluent but false moats. Updated the Exa example in the schema block to include the two summary fields. On the Browserbase run: two paragraphs replace the previous 12 bullets. Winning reads as enterprise moats (SLA, Stagehand, EU/APAC, Selenium, OpenAI Agents + n8n integrations). Losing reads as transparency + openness gaps — concrete competitor names cited (Anchor's Halluminate win, Steel's leaderboard, Browsaur MIT + Kernel + Steel AGPL).
…ompetitors
Step 1 was doing light self-research on the user's company while Step 5
did deep 5-lane enrichment on every competitor. That asymmetry meant the
userCompany row in matrix.json was filled from the main agent's memory
rather than from verified partials, and the strategic summary printed
fabricated moats about the user's OWN product.
Concrete examples from the Browserbase run 2026-04-23, caught only when
the user pushed back:
- Claimed a "published uptime SLA" — no numeric SLA exists on
browserbase.com, only a status page.
- Marked open-source as false — Stagehand is MIT-licensed at
github.com/browserbase/stagehand, plus Browserbase ships 10+ other OSS
repos (sdk-node, sdk-python, create-browser-app, Arena, open-operator,
mcp-server-browserbase, etc). The correct framing is "OSS at the SDK
layer, cloud-only at the infra layer" — a split the skill wasn't
capturing.
Systemic fix:
- Step 1 now mandates the same 5-lane partial enrichment on the user's
company that Step 5 runs on competitors. Partials go to
partials/{user-slug}.{lane}.md. merge_partials.mjs consolidates to
{OUTPUT_DIR}/{user-slug}.md.
- Step 5b (matrix synthesis) now explicitly reads {user-slug}.md as
the source for userCompany flags. Every flag must be traceable to
a Research Findings bullet with a cited URL — the rule applies
identically to the user's company and every competitor.
- Added the Browserbase-SLA + Browserbase-Stagehand errors to SKILL.md
as the cautionary tale for why this parity matters.
Adds sales-enablement output grounded in the fact-checked matrix.
Closes the single biggest gap surfaced by the v0.2 framework research:
the skill was a Competitor Profiling Matrix but not a Battle Card tool
— Klue/Crayon's most-requested artifact had no equivalent in our pipe.
Design: synthesis-only lane (no new bb calls), runs AFTER Step 5c
fact-check so battle cards are grounded in verified cells, not fresh
inference. Eliminates the failure mode where the skill's sales output
would contradict the matrix it publishes.
Changes:
- scripts/merge_partials.mjs: add 'battle' to LANES; union the
`## Battle Card` section into the merged {slug}.md between the
Comparison and Mentions sections.
- scripts/compile_report.mjs: parse the Battle Card section from
c.sections, render as a brand-accented `.research.battle` card on
the per-competitor HTML page (left border in brand orange,
uppercase small-caps subheadings for Landmines / Objection Handlers
/ Talk Tracks).
- references/battle-card.md (new): format spec — three sections,
citation rules, adversarial self-check checklist.
- references/battle-card-subagent.md (new): standalone prompt template
with placeholder list. Main agent substitutes per competitor and
launches one subagent per competitor in parallel.
- references/example-research.md: add a worked Battle Card section to
the Rival Co example.
- SKILL.md: new Step 5d (Battle synthesis) with explicit dependency
on Step 5c fact-check; Pipeline Overview updated to mention the
6th lane in deep/deeper modes.
Scope deliberately tight — this is Phase E of the approved v0.2 plan
(/Users/jay/.claude/plans/you-can-figure-out-jaunty-pelican.md).
Phases A/B/C/D/F/G deferred. Existing Browserbase + Exa compile runs
verified unchanged (no battle partials → battle card card omitted).
First end-to-end run of the battle lane on Browserbase data: 4 of 5
subagents emitted their Battle Card content with format drift that
parseSections() couldn't resolve — some led with `# Battle Card: X vs Y`
(h1, not h2 and so invisible to the `## `-only section splitter), some
skipped the wrapper heading entirely and led with `## 1. Landmines`.
Only 1 of 5 battle cards made it into the merged {slug}.md.
Same root cause as the earlier mention-bullet-format fix (commit 953f078):
subagents will drift from any prompt-level format spec.
Treat the entire battle partial body as the Battle Card content
regardless of heading style. Strip any leading `# Battle Card …` h1 or
`## Battle Card` h2 wrapper so we don't double-wrap, then emit the rest
under our canonical `## Battle Card` heading in the merged file.
After the fix: 5 of 5 battle cards rendered in per-competitor HTML on the
Browserbase run, each with 5-6 cited landmines, 5 objection handlers with
source links, and 2-3 talk tracks. Content quality spot-checked on Anchor
(counters the Halluminate stealth benchmark loss with the Advanced Stealth
update link) and Steel (flags US-only regions + self-benchmark bias).
Second end-to-end run on Browserbase (2026-04-24-1955) exposed two small-but-real bugs not caught on the 2026-04-23 run: 1) merge_partials.mjs — the Battle Card heading-stripper's regex required the first line to be exactly `## Battle Card\s*\n` or `# Battle Card[^\n]*\n`, so an h2-with-suffix line like `## Battle Card — Hyperbrowser` slipped through. The merged hyperbrowser.md got a duplicate `## Battle Card` heading and the HTML rendered the section twice. Generalize to strip any leading heading line (h1-h3) mentioning "Battle Card" with any suffix. One regex handles all observed drift patterns from the 5 subagents. 2) capture_screenshots.mjs — the --help template literal contained unescaped backticks around `website`, breaking the enclosing `\`...\`` literal and yielding a SyntaxError at load time. Never caught before because prior runs skipped --help. Replaced the inner backticks with double quotes. Verified on the fresh Browserbase run: all 5 battle cards merge with exactly one `## Battle Card` header each; 5/5 hero screenshots captured (anchor / browserbase / hyperbrowser / kernel / steel).
…cate cells
Three concrete bugs on the refreshed Browserbase run:
1) The user's own company leaked into the competitor table as the
first row with '—' pricing. Filter it out by matching
competitor_name AND slug (case-insensitive) against
matrix.json userCompany.name (falling back to --user-company).
Also rebuild metaLine + {{TOTAL}} + mentions-header count off the
filtered list so "N competitors" is accurate.
2) Overview table cells rendered full 650-char pricing_tiers strings
when subagents drifted into prose instead of pipe-separated tiers.
Add truncate() helper (~140-160ch with word-boundary ellipsis) on
tagline, pricing, and strategic_diff cells.
3) featurePills dropped all pills when key_features had no pipes —
because splitPipes returned a single giant blob. Fall back to
splitting on semicolons/commas, and cap each pill to 40 chars
with a word-boundary ellipsis. Prevents wall-of-text pills.
Also lifted the curatedMatrix load above the first use site to avoid
a temporal dead zone (the filter needs userCompany.name; the matrix
was previously loaded farther down for the renderer functions).
After fix: 5-row table instead of 6, pricing/tagline/diff cells fit
the intended max-widths, feature pills show as short capsules.
…ze pills Two bugs on the fresh Browserbase run that both traced to subagent format drift: 1) Browsaur missing entirely from mentions feed chips Root cause: browsaur.marketing.md wrote `competitor: Browsaur` instead of canonical `competitor_name: Browsaur`. merge_partials' CANONICAL_FIELDS whitelist dropped the field silently, leaving Browsaur's merged .md with an empty competitor_name. The overview table still rendered (by slug) but the mentions feed keys on competitor name for the chip label — blank chips filtered out. Fix: FIELD_ALIASES map in merge_partials — `competitor` and `name` and `company` all map to `competitor_name`; `homepage` and `url` to `website`; `price_tiers` and `pricing` to `pricing_tiers`. canonicalValue(fm, key) walks the alias table when the canonical key is absent. Silent fallback: subagents can drift on field names without us losing data. 2) Unstyled mention pills with invented source types Subagents emitted `[VendorBlog]`, `[HackerNews]`, `[GitHubIssue]`, `[CompetitorBlog]` — none matching the CSS classes. Rendered as unstyled spans. Fix: normalizeSourceType() in parseMentions. Canonical set (Benchmark/Comparison/News/Reddit/HN/LinkedIn/YouTube/Review/ Podcast/X/DevTo/Hashnode/Substack/Blog) stays. Aliases map HackerNews→HN, Twitter→X, VendorBlog/CompetitorBlog/GitHubIssue/ Medium/Docs→Blog. Unknown types keyword-scan for a canonical token; else fall back to Blog. Guarantees every pill gets styled. Also filter competitorRows (not deduped) when building allMentions, so the user's own company doesn't leak into the feed even if it has mentions. Fallback chip label is c.slug if competitor_name is blank. After fix on the Browserbase run: 5 competitor chips (Anchor 21, Browsaur 12, Hyperbrowser 22, Kernel 28, Steel 23), all source pills mapped to canonical palette.
70817ed to
d8702df
Compare
…or nits
A real 10-competitor run on Browserbase clocked 40 minutes and never
reached fact-check or screenshots before interrupt. Trace attribution:
Step 5 enrichment alone burned 25 minutes by self-throttling to 10
agents per message (5 sequential rounds of 10), when the Agent tool
happily runs 50+ in parallel. Wall clock collapses to the slowest
single agent (~5 min) once we stop batching.
Three classes of fix in this commit:
1. Parallelism guidance — workflow.md + SKILL.md
- Drop the "up to ~6 per message" cap. Replaced with the explicit
rule: launch ALL subagents needed for a phase in ONE Agent
message. For 10 × 5 lanes = 50 parallel agents in one message.
- Document the measured cost: splitting cost 20 minutes vs
unsplit on the Apr 2026 Browserbase run.
- Update Step 5 + Wave Management + the lane-fan-out section to
match. No remaining contradictions in the docs.
2. Discovery is parallel Bash, not subagents
Discovery is 6-12 `bb search` calls. Wrapping each wave in an
Agent subagent costs more in cold-start + tool-reasoning overhead
than the work itself (~1-2 min wasted). New "Discovery — parallel
Bash, not subagents" section in workflow.md gives the exact
3-Bash-call recipe (Wave A/B/C). SKILL.md Step 3 points at it.
3. Skill-creator audit nits (rules from the skill-creator skill)
- Add Tables of Contents to all 4 reference docs >100 lines
(workflow.md, research-patterns.md, example-research.md,
battle-card-subagent.md). battle-card.md is 91 lines so
skipped per the rule.
- Bump version "0.1.0" → "0.2.0". The skill picked up battle
cards (df62374), fact-check (8502f71), prose summaries
(9fd482f), user-company parity (845422d), matrix taxonomy
(c74d229), Step 4.5 user-confirm (ae58982), and 7 more fixes
since 0.1.0 — well past a minor bump.
- Kept `allowed-tools` frontmatter field. Not in skill-creator's
spec but harness-consumed in some Claude Code setups; harmless
if ignored, useful if respected.
Estimated next-run impact: 40 min → ~12-15 min through compile,
dominated by per-subagent ceiling (3-5 min) + matrix synthesis
(4 min) + fact-check (5-10 min if you want it).
…budget The Apr 25 Browserbase run got stuck at 111+ bb tool calls in fact-check before user interrupt. Root cause: the previous Step 5c mandated verifying EVERY cell of matrix.json — for a 7-company × 33-axis matrix that's 231 cells. Most of those cells are universal table-stakes (Playwright, Puppeteer, CDP, Python SDK) where any cloud browser has them; verifying all of those is redundant work that blocks the pipeline from reaching battle cards / screenshots / compile. The original problem fact-check was solving (the SOC 2 hallucination on the Apr 23 run) was about a HANDFUL of high-stakes cells: the ones that drive the "Where you're winning" summary, plus compliance + license + pricing. Rest doesn't need verification. Switch the default to spot-check with a hard 25-call budget. Priority order is explicit and ranked: 1. Every cell that appears in userCompany.winningSummary/losingSummary 2. Compliance cells (SOC 2, HIPAA, ISO 27001) across all competitors 3. Open-source license cells (Steel was wrong as AGPL — actually Apache 2.0) 4. Pricing tiers + funding numbers cited in summaries Explicit skip list: - Universal cells (Playwright, Puppeteer, CDP, Python SDK, etc.) - `false` cells with no claim - Integration cells unless cited in summaries The subagent counts its own bb calls and STOPS at 25 — partial fact-check beats blocking the pipeline. Full-sweep mode (~80 calls, verifies every non-universal cell) is opt-in for board-deck-level deliverables. Estimated impact on next run: fact-check phase 15+ min → 3-5 min, no more pipeline stalls before battle cards. The summary stays trustworthy because we verify the cells that actually feed it.
…discovery results
User reported research phase still takes ~25min before fact-check even after
prior parallelism fixes. Trace showed 2 lanes hitting 29-30 bb calls each
against an 8-call advisory budget, dragging the 30-agent fan-out from 5→12min.
- references/workflow.md: replace soft "BUDGETS (respect strictly)" with
HARD CAP + per-call self-counter ("# bb call N/8") and explicit "stop and
write what you have" instruction. Cite Apr 25 incident.
- references/workflow.md: drop discovery searches from 25→12 results per
query. Gate already filters most noise; 25 just inflated the candidate
list and downstream gate calls.
- profiles/example.json: drop redundant template (browserbase.json is the
reference profile).
1. matrix.html leaked user company as a column with all-false features. Move competitorRows definition above aggregates and replace `deduped` with `competitorRows` in matrix headers/cells, axis counts, pricing table, strategic-summary inner loop, per-competitor page generation, and CSV. Now a single filter applies consistently across all views. 2. report-template.html referenced undefined --high / --low CSS vars on strategic win/loss card border-lefts (and the loss badge text color), so the colored borders silently didn't render. Define both in :root (high=#5a8a1a green, low=#F03603 brand) so they match the existing palette tokens. 3. gate_candidates.mjs used spawnSync inside async gateOne, blocking the event loop and reducing the documented --concurrency 6 to N=1 in practice. Switch to promisified execFile so the worker pool actually parallelizes. 4. extract_vs_names.mjs used bidirectional startsWith for domain resolution, which mapped "steel" -> steelhead.com and "browse" -> browserbase.com. Restrict prefix matches to known branding suffixes (browser/ai/io/app/ labs/etc.), break ties by shortest suffix, and exclude seeds from the host map so the user's own domain can't shadow shorter extracted names. 5. capture_screenshots.mjs (also flagged): the underlying `browse` CLI shares a single session, so true async parallelism would race on the same tab. Clamp --concurrency to 1 with a stderr note rather than silently corrupting output.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 37087d2. Configure here.
|
|
||
| if (shouldOpen) { | ||
| const { execSync } = await import('child_process'); | ||
| try { execSync(`open "${join(dir, 'index.html')}"`); } catch {} |
There was a problem hiding this comment.
Shell injection via string interpolation in execSync
Medium Severity
The execSync call interpolates the user-provided directory path (dir from args[0]) into a shell command string. If the output directory path contains shell metacharacters like ", $, or backticks, this allows arbitrary command execution. Using execFileSync('open', [join(dir, 'index.html')]) would avoid shell interpretation entirely.
Reviewed by Cursor Bugbot for commit 37087d2. Configure here.
| } | ||
| } | ||
| return fields; | ||
| } |
There was a problem hiding this comment.
Duplicated parseFrontmatter and parseSections across three scripts
Low Severity
parseFrontmatter is independently implemented in compile_report.mjs, capture_screenshots.mjs, and merge_partials.mjs. parseSections is duplicated between compile_report.mjs and merge_partials.mjs. These are near-identical implementations in the same scripts/ directory. A shared utility module would reduce maintenance burden and ensure consistent parsing behavior across all scripts.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 37087d2. Configure here.
| else if (incEarly.length > 0 && excEarly.length === 0) { status = 'PASS'; reason = `hero200→include(${incEarly[0]})`; } | ||
| else if (excEarly.length > 0) { status = 'REJECT'; reason = `hero200→exclude(${excEarly[0]})`; } | ||
| else if (incHero.length > 0 && excHero.length === 0) { status = 'PASS'; reason = `hero→include(${incHero[0]})`; } | ||
| else { status = 'REJECT'; reason = 'no category signal'; } |
There was a problem hiding this comment.
Gate silently rejects candidates with conflicting hero signals
Medium Severity
When both include and exclude keywords appear in the full hero text (chars 200–800) but not in the title or early hero, classify falls through to REJECT with reason "no category signal". These candidates have conflicting signal, not no signal. Returning REJECT instead of UNKNOWN means they won't appear in the UNKNOWN bucket during user confirmation (Step 4.5), causing potentially valid competitors to be silently dropped.
Reviewed by Cursor Bugbot for commit 37087d2. Configure here.


Adds competitor-analysis — a skill that researches a company's competitors and surfaces the output as a browsable HTML report with screenshot
Note
Medium Risk
Adds a brand-new skill with multiple Node scripts that orchestrate Browserbase CLI calls, parse/merge research outputs, and generate HTML/CSV artifacts; while mostly additive, the gating/merging/report compilation logic is non-trivial and could misclassify competitors or produce incorrect reports if edge cases aren’t handled.
Overview
Introduces a new
competitor-analysisskill that discovers competitor domains viabb search, gates candidates by category-fit, runs multi-lane enrichment (including an optional post-fact-check Battle Card synthesis lane), and compiles results into an offline report.Adds Node-based tooling to support the pipeline: URL dedup + “X vs Y” name extraction, a concurrency-capable homepage gate, partial merging/normalization, optional screenshot capture via
browse, andcompile_report.mjsto generateindex.html, per-competitor pages, a matrix view (optionally driven bymatrix.jsonwith win/loss summaries), a mentions feed, andresults.csv.Reviewed by Cursor Bugbot for commit 37087d2. Bugbot is set up for automated code reviews on this repo. Configure here.