Claude Skill

competitor-watch

Use when an already-named set of rivals is watched on a cadence — pricing, features, positioning and changelog diffed into a maintained tracker plus an append-only, classified change log. NOT sizing the market or choosing who the rivals are (that is `market-research`), NOT one-of

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download ericrisco-rsc-harness-skills_competitor-watch-953fef5.zip · 17 KB
Part of ericrisco/rsc-harness — 46 skills

Install

skills CLI npx skills add https://github.com/ericrisco/rsc-harness/tree/main/skills/competitor-watch
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install ericrisco-rsc-harness@llmmart
Git git clone https://github.com/ericrisco/rsc-harness.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole ericrisco/rsc-harness collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

competitor-watch

You run a standing watch, not a one-shot study. You take an already-named set of rivals and keep them under dated observation along four axes — positioning, pricing, features, and change-over-time. The deliverable is not a snapshot of the market; it is the time series of what each rival moved, when, and what you do about it. Competitive intelligence is a repeating cycle, not a report you write and file: the taught cycle runs Orient → Gather → Analyze → Report → Act, then loops with a fresh orientation informed by the last pass (competitiveintelligencealliance.io, accessed 2026-06-02).

Two near-misses decide the routing (the rest are in Handoffs, below). ../market-research/SKILL.md answers "what is the market and who is in it" once, and often produces the list you watch — you are the downstream loop that watches that list forever. ../data-scraper/SKILL.md owns the generic mechanics of pulling data off a page on demand; you use change detection as a means, but your identity is the maintained tracker + classified change log + cadence. "Extract this one table once" → data-scraper. "Keep watching these five companies" → you.

Ethics gate — runs FIRST, before any capture

Legitimate CI is legal + ethical collection from public, observable sources with your identity disclosed. SCIP's Code of Ethics is the industry line (scip.org, accessed 2026-06-02). The practical gate is the front-page test: would you be comfortable if your collection method were reported on the front page of the news? If not, don't do it.

  • Public/observable sources only — their own site, public filings, trade-show material, published reviews (G2/Capterra), public social. Why: anything else is not CI, it's a legal risk.
  • Never pose as a customer to extract non-public info (no fake demo requests, no false pretenses, no misrepresenting who you are). Why: it's a Code-of-Ethics violation and it taints the data.
  • Never bypass access controls, paywalls, or rate limits / "no-scrape" terms. Why: circumventing access is the bright line between intelligence and intrusion.

If a request needs any of those, refuse and reframe to the legal equivalent: instead of "get their internal pricing," watch their public pricing page on a cadence and log the moves.

Ground & scope before you watch anything

  1. Get the competitor list. If there is none, or it's unvalidated guesswork → STOP and route to ../market-research/SKILL.md. Why: watching the wrong rivals forever is worse than not watching. You are not the one who decides who the competitors are.
  2. Pick the vital few, then the watch-axes. The 3–7 rivals that move your roadmap, and only the surfaces (below) that change your decisions. Why: watching 30 companies on every axis produces noise nobody reads; depth on the few beats breadth on the many.
  3. Persist the tracker of record under 02-DOCS/wiki/competitors/ (one profile per rival
    • a shared change log); keep raw captures under 02-DOCS/raw/competitors/. Why: the tracker is a maintained artifact, not a chat answer — it has to live somewhere re-runnable.
  4. Every price/feature cell carries a source_url + date, or it stays blank. Why: this is the single highest-value guard against inventing a rival's number. If you didn't see it on a dated public page, you don't know it — leave the cell empty and say so.
Bad:  Acme Pro tier — $79/mo                          (no source, no date — invented)
Good: Acme Pro tier — $79/mo  [acme.com/pricing, 2026-05-28]   (seen, sourced, dated)

Watch-list → config: map each surface

This is the real branch — different surfaces want different cadences and different selector types, so the table earns its place. Cadence is tiered by how fast the surface moves: time- sensitive surfaces want 5–15 min checks, general competitor surfaces hourly–daily, slow/compliance surfaces daily (visualping.io / scrapx.io cadence guidance, accessed 2026-06-02). Over-frequent on a slow page is just cost and noise; under-frequent on pricing is a missed move.

Axis (surface) What you watch Cadence Selector type
Pricing / packaging the price node, tier names, promo banner 5–15 min CSS on the price element, or JSONPath if pricing comes from an API
Features / changelog release-notes / "what's new" list daily CSS on the release list (first N items)
Positioning / homepage hero headline + subhead copy daily–weekly CSS on the hero text block
Launches / partnerships press / blog index daily CSS on the post list
Careers / hiring open-roles count + titles weekly CSS on the jobs list (signals strategy)
Customer reviews G2 / Capterra recent reviews weekly CSS on the review feed
Social public profile / posts daily–weekly platform-dependent; public only

That mapping — URL + selector + cadence per surface — is the monitoring config. Write it down as config, don't hold it in your head.

The change loop

Each pass on a watched URL: capture → diff vs last → classify → score → log → flag.

  1. Capture the watched node (not the whole page — the selector keeps the diff signal-clean).
  2. Diff against the last stored capture for that URL.
  3. Classify the change into exactly one axis: pricing | feature | positioning | messaging | team | other.
  4. Score materiality: high (changes our roadmap or pricing), medium (worth knowing), low (cosmetic / noise). Why: a diff with no classification and no materiality is noise — it tells you something moved but not whether to care.
  5. Append a dated change-log row. Never overwrite; the log is the time series.
  6. Flag the high rows for action and route them — a price move to whoever owns our pricing decision, a feature ship to product. You log and flag; you don't make those calls.
Bad:  "They changed their website."
      (no date, no axis, no old/new, no materiality, no action — unactionable)

Good: 2026-05-20 · pricing · Acme · acme.com/pricing
      Pro tier $49→$59/mo · high · revisit our mid-tier vs theirs
      (dated, classified, old→new, scored, with a next step)

The tracker artifacts

Three structured files. Required fields named here; full schema + a filled end-to-end example competitor live in references/tracker-schema.md. The profile is a .md page under the 02-DOCS/wiki/ OKF v0.1 bundle, so its YAML frontmatter carries a non-empty type: competitor (plus the OKF-recommended title/description/tags/timestamp) alongside the domain keys; its body uses standard markdown links, never wikilinks. The two CSVs are data files, not OKF documents.

  • Competitor profile (one per rival): OKF frontmatter (type: competitor, …) + the domain keys name, positioning_line, segment, pricing tiers (each with amount, currency, source_url, date), feature-matrix rows, watched URLs.
  • Feature matrix (CSV): rows = features, columns = competitors, each cell sourced + dated.
  • Change log (CSV, append-only): date,competitor,axis,url,old_value,new_value,materiality,action.
date,competitor,axis,url,old_value,new_value,materiality,action
2026-05-20,Acme,pricing,https://acme.com/pricing,$49/mo,$59/mo,high,revisit our mid-tier
2026-05-22,Acme,feature,https://acme.com/changelog,,SSO on Team plan,medium,note for product

Tooling — what you can actually run

The runnable default is changedetection.io (open-source, self-hosted): it does text/ visual / XPath/CSS-selector and JSON-API (JSONPath / jq) change detection, checks as often as ~1 minute, notifies via Slack/Discord/Telegram/email/API, and ships AI change summaries like "Price dropped from $89.99 to $67.00" (github.com/dgtlmoon/changedetection.io, accessed 2026-06-02). Prefer this when you must produce a config you can run rather than recommend a SaaS. Full docker-compose + per-axis watch recipe is in references/monitoring-config.md.

  • The Wayback Machine is an archive, not a monitor. It captures some snapshots (a pricing page may be archived once in months, or never) and does not tell you when something changed; its "Changes" diff (added=blue, deleted=yellow) only compares two existing captures (archive.org "Compare two versions", accessed 2026-06-02). Use it to reconstruct historical positioning, never as the live alerting layer.
  • The paid CI-suite tier exists and sets the feature bar — quote real numbers, don't over-prescribe: Crayon median ≈$28.7K/yr, Klue ~$16K–$42.7K/yr (priced by seats), Kompyte ~$20K avg ARR (entry from ~$300/yr), lightweight page-monitors Visualping from ~$10/mo, ChangeTower from ~$9/mo (vendr.com, autobound.ai, kompyte.com, accessed 2026-06-02). These auto-update battlecards — but the battlecard is a sales artifact owned by ../sales-pipeline/SKILL.md, not you. A self-host config covers most teams; the suite is overkill until you're tracking many rivals across social + filings 24/7 with a dedicated CI owner.
  • The recurring run (cron / webhook scheduling) is wiring, not watching → ../automation-flows/SKILL.md.

Handoffs

Request Route to
Size the market, produce/validate the competitor list, TAM/SAM/SOM, buyer/JTBD ../market-research/SKILL.md
Set OUR price / packaging / tiers (a decision, not an observation) ../pricing/SKILL.md
Build the sales battlecard / objection handling / "why we win" ../sales-pipeline/SKILL.md
Define OUR positioning / value prop / messaging ../brand-voice/SKILL.md
One-off "extract this page/table once" with no cadence or tracker ../data-scraper/SKILL.md
Wire the recurring run as a cron/webhook automation ../automation-flows/SKILL.md
Track OUR own SEO / AI-search visibility vs rivals on one page ../seo-geo/SKILL.md

Anti-patterns

Anti-pattern Why it's wrong Do instead
Treating it as a one-shot study The value is the delta over time; a snapshot is stale on arrival Set a cadence, append to a change log every pass
Inventing a rival's price/feature Unsourced "facts" pollute the tracker and mislead decisions Every cell gets source_url + date, or stays blank
Posing as a customer / bypassing a paywall Fails the front-page test; not CI, it's a legal risk Public observable sources only; refuse and reframe
Using the Wayback Machine as the live monitor It doesn't tell you when something changed and may never capture the page Wayback for historical reconstruction only; changedetection.io for live alerts
5-min cadence on a careers page / weekly on pricing Over-frequent = noise + cost; under-frequent = a missed move Tier the cadence by axis volatility (see the table)
Logging a raw diff with no axis/materiality "Something changed" is unactionable Classify the axis, score materiality, add a next step
Watching 30 competitors Breadth produces noise nobody reads Watch the vital 3–7 that move your roadmap
Building the battlecard inside this skill That's sales enablement, a different owner Hand off to ../sales-pipeline/SKILL.md; you supply the tracker

Verify

After you emit a tracker / change log / config, run scripts/verify.sh against your project docs. Read-only lint: required tracker columns present; every pricing/feature row has a non-empty source_url and date (the anti-invention guard); change-log axis and materiality in the allowed sets, with a url + date on every row; and a warning when a monitoring-config entry pairs a slow axis with a sub-15-min cadence, or pricing with a slower-than-daily one. It exits 0 on an empty/clean target — no false failures.

Files (rsc-harness)
  • evals
    • cases.yaml 4 KB
      skill: competitor-watch
      
      # Prompts that MUST load `competitor-watch`. The skill owns the STANDING WATCH over a
      # named set of rivals: a maintained tracker (pricing/features/positioning), a classified
      # change log over time, and the monitoring config (URL + selector + cadence). It does NOT
      # size the market or build the list (market-research), set our price (pricing), build the
      # sales battlecard (sales-pipeline), or do a one-off extraction (data-scraper).
      should_trigger:
        - prompt: "Set up monitoring on our top 3 competitors' pricing and feature pages."
          why: "Core watch: named set, watched surfaces, ongoing cadence — the skill's whole job."
      
        - prompt: "Did Competitor X change their pricing or ship anything new this month?"
          why: "Change-loop ask — capture, diff, classify the move, report what changed over a period."
      
        - prompt: "Build a feature comparison matrix versus our rivals and keep it updated."
          why: "Non-obvious: reads like a one-off table, but 'keep it updated' makes it the maintained matrix + change log, not a snapshot."
      
        - prompt: "monitoritza els competidors i avisa'm quan canviïn de preu."
          why: "Catalan: monitor competitors and alert me on price changes — the watch loop with notification, phrased without the skill name."
      
        - prompt: "Track how their positioning has shifted over the last 6 months."
          why: "Non-obvious time-series framing — no 'monitor/competitor' keyword in the obvious slot, but it's the dated change-over-time the skill produces (Wayback reconstruction + change log)."
      
      should_not_trigger:
        - prompt: "How big is this market and who are the main players we should worry about?"
          route_to: market-research
          why: "Sizing the market and producing the landscape/list is upstream; competitor-watch watches an already-named set, it doesn't build the list."
      
        - prompt: "What price should we charge for our new mid-tier plan?"
          route_to: pricing
          why: "Setting OUR price is a decision, not an observation of a rival; pricing owns it."
      
        - prompt: "Write the sales battlecard for beating Competitor X on calls."
          route_to: sales-pipeline
          why: "The sales-enablement battlecard / objection handling is a sales artifact; competitor-watch only supplies the tracker behind it."
      
        - prompt: "Scrape this one page and give me the table once."
          route_to: data-scraper
          why: "A one-off extraction with no cadence, tracker, or change log — that's the generic scraping primitive, not the standing watch."
      
        - prompt: "Schedule the competitor monitor to run every morning and post to Slack via webhook."
          route_to: automation-flows
          why: "Cron/webhook scheduling and reliable delivery is automation wiring; competitor-watch defines the watches, automation-flows makes them run."
      
      capability:
        - scenario: "We have a validated list of 3 competitors. Set up a competitor watch: the tracker of record, the watch config, and a repeatable change-loop process."
          must_include:
            - "Applies the ethics gate first: public/observable sources only, transparent identity, the front-page test; never pose as a customer or bypass access controls/paywalls."
            - "Persists the tracker under 02-DOCS/ (wiki/competitors + raw/competitors) as a maintained artifact, not a chat answer — or STOPs and routes to market-research if the list were not validated."
            - "Requires a source_url + date on every price/feature cell and refuses to invent a rival's number (blank cell when unconfirmed)."
            - "Maps each watched surface to URL + selector + tiered cadence: pricing 5-15 min, features/changelog daily, positioning/careers daily-weekly."
            - "Defines the change-log schema with axis classification (pricing|feature|positioning|messaging|team|other) and materiality (high|medium|low), append-only over time."
            - "Recommends changedetection.io (CSS/JSONPath selectors, ~1-min capable, notify) as the runnable default and flags the Wayback Machine as archive-only, not a live monitor."
            - "Routes the recurring run to automation-flows and the battlecard to sales-pipeline; does not build either inside this skill."
      
    • README.md 1.5 KB
      # Evals — competitor-watch
      
      `cases.yaml` has three blocks. **should_trigger** and **should_not_trigger** are routing
      checks: feed each `prompt` to the router and confirm it selects `competitor-watch` for the
      triggers (including the non-obvious "keep the matrix updated" and "how their positioning
      shifted over 6 months" framings and the Catalan phrasing) and the named real sibling for each
      near-miss — `market-research` (list/sizing), `pricing` (our price), `sales-pipeline` (the
      battlecard), `data-scraper` (one-off extraction), `automation-flows` (scheduling). A near-miss
      passes only when the router prefers the named sibling over `competitor-watch`. The
      **capability** block is an LLM- or human-graded rubric: run the scenario with the skill loaded
      and check the produced plan hits every `must_include` line — ethics gate first, tracker
      persisted under `02-DOCS/` with a `source_url`+`date` on every price/feature cell (no invented
      numbers), each surface mapped to URL+selector+tiered cadence, the change-log axis+materiality
      schema, `changedetection.io` as the runnable default with Wayback flagged as archive-only, and
      the handoffs to automation-flows and sales-pipeline. There is no automated runner and no live
      network call — the agent produces the tracker, watch config, and change-loop process as
      artifacts; grade by reading the output against the list, or wire it into your eval harness of
      choice. `scripts/verify.sh` separately lints an emitted tracker/change-log for sourcing and
      schema (see its header).
      
  • references
    • monitoring-config.md 5.1 KB
      # Monitoring config: the runnable stack
      
      The default you can actually run is **`changedetection.io`** — open-source, self-hosted, no
      vendor lock-in. It does text/visual, **XPath/CSS-selector**, and **JSON-API (JSONPath / jq)**
      change detection; checks as often as ~1 minute; notifies via Slack/Discord/Telegram/email/API;
      and ships AI change summaries like "Price dropped from \$89.99 to \$67.00"
      (github.com/dgtlmoon/changedetection.io, accessed 2026-06-02).
      
      ## Self-host: docker-compose
      
      ```yaml
      # docker-compose.yml — changedetection.io + a Playwright fetcher for JS-heavy pages
      services:
        changedetection:
          image: ghcr.io/dgtlmoon/changedetection.io
          container_name: changedetection
          ports:
            - "5000:5000"
          volumes:
            - ./datastore:/datastore
          environment:
            - PLAYWRIGHT_DRIVER_URL=ws://playwright-chrome:3000
            - BASE_URL=https://watch.internal.example.com
          restart: unless-stopped
        playwright-chrome:
          image: dgtlmoon/sockpuppetbrowser:latest
          container_name: playwright-chrome
          restart: unless-stopped
      ```
      
      Bring it up with `docker compose up -d`, open `http://localhost:5000`, and add one watch per
      watched URL from the competitor profile.
      
      ## Per-axis watch definitions
      
      Each watch = URL + selector + cadence. Map straight from the watch-list table in `SKILL.md`.
      
      ```text
      # Pricing (time-sensitive → 5–15 min)
      URL:       https://acme.com/pricing
      Filter:    css:.price-amount          # CSS selector for the price node only
      Cadence:   00:10:00                   # 10 minutes
      Notify:    tgram://<token>/<chat_id>  # Telegram on change
      
      # Features / changelog (daily)
      URL:       https://acme.com/changelog
      Filter:    css:.changelog li:nth-child(-n+5)   # first 5 release items
      Cadence:   1 day
      
      # Positioning / homepage (weekly)
      URL:       https://acme.com
      Filter:    css:h1.hero-title, css:.hero-subtitle
      Cadence:   7 days
      ```
      
      ### JSON-API pricing (when price comes from an endpoint, not HTML)
      
      If the pricing page hydrates from an API, watch the API and pin the exact field with JSONPath
      — far more stable than scraping rendered HTML.
      
      ```text
      URL:       https://acme.com/api/v2/plans
      Filter:    json:$.plans[?(@.id=='pro')].monthly_price
      Cadence:   00:10:00
      ```
      
      ### AI change summary (optional)
      
      changedetection.io can attach an LLM that turns a raw diff into one line ("Pro monthly went
      from \$49 to \$59"). Enable it per-watch when the page is noisy and you want the classification
      pre-chewed — but you still own the axis + materiality call; the summary is an input, not the log.
      
      ## Notification wiring
      
      Point the notify URL at where your team already lives. Common targets:
      
      ```text
      tgram://<bot_token>/<chat_id>          # Telegram
      slack://<token_a>/<token_b>/<token_c>  # Slack
      discord://<webhook_id>/<webhook_token> # Discord
      post://watch.internal.example.com/hook # your own webhook → automation-flows
      ```
      
      The *scheduling and downstream routing* of those webhooks (cron, retries, fan-out into a
      tracker write) is automation wiring → `../automation-flows/SKILL.md`. This skill defines the
      watches; that skill makes them run reliably on a schedule.
      
      ## Wayback Machine — historical reconstruction ONLY
      
      The Wayback Machine is a **passive archive, not a monitor**. It captures *some* snapshots (a
      pricing page may be archived once in months, or never) and **does not tell you when something
      changed**. Use it to reconstruct what a rival's page said in the past — never as the live
      alerting layer.
      
      Its "Changes" diff compares two existing captures (added = blue, deleted = yellow):
      
      ```text
      # Compare two captured timestamps of the same URL
      https://web.archive.org/web/diff/<TS1>/<TS2>/https://acme.com/pricing
      # e.g. TS = 20260101000000 (YYYYMMDDhhmmss)
      https://web.archive.org/web/diff/20260101000000/20260501000000/https://acme.com/pricing
      ```
      
      (archive.org "Compare two versions", accessed 2026-06-02.) Good for "how did their positioning
      read 6 months ago"; useless for "alert me when it changes."
      
      ## Self-host vs paid SaaS — when each is the right call
      
      | Option | Price (accessed 2026-06-02) | Right call when |
      |---|---|---|
      | **changedetection.io** (self-host) | free / infra cost only | Default. You want a config you run, full selector control, no vendor. |
      | Visualping | from ~\$10/mo | A few pages, want zero ops, visual diffs. |
      | ChangeTower | from ~\$9/mo | Same — lightweight page monitor, hosted. |
      | Kompyte | ~\$20K avg ARR (entry ~\$300/yr, 1–2wk setup) | You need site+social+filings 24/7 and a dedicated CI owner. |
      | Crayon | median ≈\$28.7K/yr (~\$12K–\$47K, 7–8wk setup) | Many rivals, auto-updated battlecards, priced by # competitors. |
      | Klue | ~\$16K–\$42.7K/yr (Basic/Standard/Premium, by seats) | Large CI team splitting "curators" vs "consumers." |
      
      (vendr.com/marketplace/crayon, autobound.ai "Top 15 CI tools 2026", kompyte.com comparison,
      visualping.io — accessed 2026-06-02.)
      
      The paid suites also auto-update **battlecards** — but the battlecard itself is a sales
      artifact owned by `../sales-pipeline/SKILL.md`, not this skill. A self-host config covers most
      teams; reach for a suite only when breadth (many rivals, many surfaces, 24/7) outgrows what one
      config + one owner can maintain.
      
    • tracker-schema.md 5.2 KB
      # Tracker schema + a filled example
      
      The tracker of record is three structured files under `02-DOCS/wiki/competitors/`. Raw
      captures (the diffed HTML/JSON snapshots) go under `02-DOCS/raw/competitors/<rival>/`. The
      rule that overrides everything: **a price or feature cell without a `source_url` + `date` is
      not knowledge — leave it blank and say so.**
      
      `02-DOCS/wiki/` is an OKF v0.1 bundle, so the one `.md` file here (the competitor profile)
      carries YAML frontmatter with a non-empty `type:`; the two CSVs are data files, not OKF
      documents. Any cross-references in the profile body use **standard markdown links** (e.g.
      `[Beta](./beta.md)`), never wikilinks. See `../../harness/references/wiki-protocol.md`
      "## Conventions".
      
      ## 1. Competitor profile (one file per rival)
      
      Markdown front-matter + body. One per rival, named `02-DOCS/wiki/competitors/<rival>.md`.
      The frontmatter is OKF v0.1 conformant: `type:` is the only required field (non-empty);
      `title`/`description`/`tags`/`timestamp` are the recommended standard surface. All the
      domain keys below (`name`, `positioning_source.date`, every `pricing_tiers[].date`, …) are
      kept verbatim — OKF allows any extra key and `verify.sh` reads them, so adding the OKF
      fields is purely additive.
      
      ```yaml
      ---
      type: competitor
      title: Acme
      description: "Tracker of record for Acme — positioning, pricing tiers, and watched surfaces."
      tags: [competitor, pricing-watch, feature-watch]
      timestamp: 2026-05-28T00:00:00Z
      name: Acme
      positioning_line: "The all-in-one workspace for remote-first teams"
      positioning_source: { url: "https://acme.com", date: "2026-05-28" }
      segment: "SMB / mid-market SaaS"
      watched_urls:
        - { axis: pricing,       url: "https://acme.com/pricing",   selector: ".price-amount",        cadence: "10m" }
        - { axis: feature,       url: "https://acme.com/changelog", selector: ".changelog li:first-child", cadence: "daily" }
        - { axis: positioning,   url: "https://acme.com",           selector: "h1.hero-title",        cadence: "weekly" }
        - { axis: team,          url: "https://acme.com/careers",   selector: ".jobs-count",          cadence: "weekly" }
      pricing_tiers:
        - { tier: "Starter", amount: 0,  currency: USD, period: mo, source_url: "https://acme.com/pricing", date: "2026-05-28" }
        - { tier: "Pro",     amount: 59, currency: USD, period: mo, source_url: "https://acme.com/pricing", date: "2026-05-28" }
        - { tier: "Team",    amount: 99, currency: USD, period: mo, source_url: "https://acme.com/pricing", date: "2026-05-28" }
      ---
      
      ## Notes
      Repositioned from "project tool" to "workspace" in Q2 2026 — see change log 2026-05-12.
      
      ## Related
      - [Beta](./beta.md) — closest competing positioning; cross-watch their pricing moves.
      ```
      
      Every `pricing_tiers` row and any feature claim must carry `source_url` + `date`. If you only
      have the tier name but not a confirmed price, omit `amount` rather than guess.
      
      ## 2. Feature matrix (CSV)
      
      Rows = features, columns = competitors, plus a sourcing column pair per competitor so
      `verify.sh` can confirm each asserted cell is dated. Keep it at `02-DOCS/wiki/competitors/feature-matrix.csv`.
      
      ```csv
      feature,acme_value,acme_source_url,acme_date,beta_value,beta_source_url,beta_date
      SSO (SAML),Team plan,https://acme.com/pricing,2026-05-28,Enterprise only,https://beta.io/security,2026-05-29
      API rate limit,1000 req/min,https://acme.com/docs/limits,2026-05-28,,,
      Audit log,yes,https://acme.com/security,2026-05-28,yes,https://beta.io/security,2026-05-29
      ```
      
      A blank `*_value` with blank source is honest ("we haven't confirmed it"). A non-blank value
      with a blank source is a violation — that's an invented fact and `verify.sh` fails it.
      
      ## 3. Change log (CSV, append-only)
      
      The time series. Never overwrite a row; each pass appends. Keep it at
      `02-DOCS/wiki/competitors/change-log.csv`.
      
      ```csv
      date,competitor,axis,url,old_value,new_value,materiality,action
      2026-05-12,Acme,positioning,https://acme.com,"project tool","remote-first workspace",high,"flag to brand-voice — they moved into our lane"
      2026-05-20,Acme,pricing,https://acme.com/pricing,$49/mo,$59/mo,high,"revisit our mid-tier vs theirs (route to pricing)"
      2026-05-22,Acme,feature,https://acme.com/changelog,,"SSO on Team plan",medium,"note for product"
      2026-05-29,Beta,team,https://beta.io/careers,12 roles,19 roles,low,"hiring sales — watch for go-to-market push"
      ```
      
      Field rules (these are what `verify.sh` enforces):
      
      - `axis` ∈ `{pricing, feature, positioning, messaging, team, other}` — exactly one.
      - `materiality` ∈ `{high, medium, low}`.
      - `url` and `date` are required on every row (the change is unverifiable without them).
      - `old_value` may be blank for a *new* surface (first capture); `new_value` should not be blank.
      - `action` is the next step or the route — empty action on a `high` row is a smell.
      
      ## End-to-end: one rival, one pass
      
      1. Orient: Acme is in our vital-few list. Watch pricing (10m), changelog (daily), hero (weekly), careers (weekly).
      2. Gather: capture `.price-amount` → reads `$59/mo` on the Pro tier.
      3. Analyze: diff vs last stored capture (`$49/mo`) → changed.
      4. Classify: `pricing`. Score: `high` (it's our competing tier).
      5. Log: append the `2026-05-20` pricing row above.
      6. Flag + route: high → route the pricing implication to `../pricing/SKILL.md`; you don't set our price.
      
  • scripts
    • verify.sh 10.3 KB
      #!/usr/bin/env bash
      #
      # verify.sh — competitor-tracker / change-log / monitoring-config linter for the
      # `competitor-watch` skill.
      #
      # WHAT IT DOES (read-only; never edits a file)
      #   Lints the structured artifacts this skill emits against the rules in SKILL.md.
      #   It keys off filenames and CSV headers, so any unrelated file is simply skipped
      #   and an empty/clean target never false-fails.
      #
      #   Checks:
      #     1. CHANGE LOG — any CSV whose header contains both `axis` and `materiality`
      #        (e.g. change-log.csv). Per data row:
      #          - `axis`        must be one of pricing|feature|positioning|messaging|team|other  (FAIL)
      #          - `materiality` must be one of high|medium|low                                   (FAIL)
      #          - `url`  must be non-empty                                                       (FAIL)
      #          - `date` must be non-empty                                                       (FAIL)
      #     2. FEATURE MATRIX — any CSV with paired `<x>_value` + `<x>_source_url` +
      #        `<x>_date` columns. A non-empty value with an empty source_url OR empty
      #        date is an invented fact                                                            (FAIL)
      #     3. MONITORING CONFIG — cadence sanity (WARN, never fail). In any *.md/*.yaml/
      #        *.yml/*.txt file, a line that names an axis and a cadence where:
      #          - a slow axis (positioning|careers|team|reviews|social) is paired with a
      #            sub-15-minute cadence  -> noise/cost                                            (WARN)
      #          - the pricing axis is paired with a slower-than-daily cadence (weekly/
      #            monthly)                -> a missed move                                        (WARN)
      #
      # HOW TO RUN (inside YOUR project, not the skills repo)
      #   ./verify.sh                 # scan ./ for tracker / change-log / config files
      #   ./verify.sh --path 02-DOCS  # scan a subdirectory
      #   ./verify.sh --strict        # treat any warning as a failure (exit 1)
      #
      # EXIT CODES
      #   0  clean, or warnings only without --strict (also: nothing to check)
      #   1  a structural violation (bad axis/materiality, missing url/date, unsourced
      #      value), or --strict with a warning
      #   2  bad usage
      #
      # Runs on stock macOS bash 3.2 — no mapfile, no associative arrays.
      
      set -euo pipefail
      
      if [ -t 1 ]; then
        RED=$'\033[31m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; NC=$'\033[0m'
      else
        RED=''; GREEN=''; YELLOW=''; NC=''
      fi
      
      ok_count=0; skip_count=0; warn_count=0; fail_count=0
      ok()   { printf '%s[ ok ]%s %s\n' "$GREEN"  "$NC" "$*"; ok_count=$((ok_count + 1)); }
      skip() { printf '%s[skip]%s %s\n' "$YELLOW" "$NC" "$*"; skip_count=$((skip_count + 1)); }
      warn() { printf '%s[warn]%s %s\n' "$YELLOW" "$NC" "$*"; warn_count=$((warn_count + 1)); }
      fail() { printf '%s[fail]%s %s\n' "$RED"    "$NC" "$*"; fail_count=$((fail_count + 1)); }
      
      usage() { sed -n '2,46p' "$0" | sed 's/^# \{0,1\}//'; }
      
      SCAN_PATH="."
      STRICT=0
      while [ $# -gt 0 ]; do
        case "$1" in
          --path)    SCAN_PATH="${2:?--path needs a value}"; shift 2 ;;
          --strict)  STRICT=1; shift ;;
          -h|--help) usage; exit 0 ;;
          *) printf '%sUnknown argument: %s%s\n\n' "$RED" "$1" "$NC"; usage; exit 2 ;;
        esac
      done
      
      if [ ! -e "$SCAN_PATH" ]; then
        printf '%sPath not found: %s%s\n' "$RED" "$SCAN_PATH" "$NC"; exit 2
      fi
      
      TMPDIR_V="$(mktemp -d 2>/dev/null || printf '/tmp/cw-verify.%s' "$$")"
      mkdir -p "$TMPDIR_V" 2>/dev/null || true
      cleanup() { rm -rf "$TMPDIR_V" 2>/dev/null || true; }
      trap cleanup EXIT
      
      CSV_FILES="$TMPDIR_V/csv"
      CFG_FILES="$TMPDIR_V/cfg"
      find "$SCAN_PATH" -type f -name '*.csv' 2>/dev/null > "$CSV_FILES" || true
      find "$SCAN_PATH" -type f \
        \( -name '*.md' -o -name '*.yaml' -o -name '*.yml' -o -name '*.txt' \) \
        2>/dev/null > "$CFG_FILES" || true
      
      # Allowed sets (whitespace-padded for safe substring match).
      AXES=" pricing feature positioning messaging team other "
      MATS=" high medium low "
      
      # Find the 0-based index of a column name in a comma-separated header line.
      # Echoes -1 if absent.
      col_index() {
        header="$1"; want="$2"
        idx=-1; i=0
        oldifs="$IFS"; IFS=','
        for c in $header; do
          # trim spaces and a trailing CR
          c="$(printf '%s' "$c" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
          if [ "$c" = "$want" ]; then idx=$i; fi
          i=$((i + 1))
        done
        IFS="$oldifs"
        printf '%s' "$idx"
      }
      
      # Echo the Nth (0-based) field of a CSV row. Simple split on comma — values in this
      # schema do not contain commas; quoted commas would need a real parser, which is out
      # of scope for a lint (we only read low-structure tracker cells).
      field_at() {
        row="$1"; n="$2"
        i=0
        oldifs="$IFS"; IFS=','
        for c in $row; do
          if [ "$i" -eq "$n" ]; then
            printf '%s' "$c" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
            IFS="$oldifs"; return 0
          fi
          i=$((i + 1))
        done
        IFS="$oldifs"
        printf ''
      }
      
      # ---------------------------------------------------------------------------
      # 1 + 2. CSV checks
      # ---------------------------------------------------------------------------
      csv_change_log_seen=0
      csv_matrix_seen=0
      while IFS= read -r f; do
        [ -z "$f" ] && continue
        header="$(head -n 1 "$f" 2>/dev/null | tr -d '\r')"
        [ -z "$header" ] && continue
      
        # --- change log: header has both axis and materiality ---
        ai="$(col_index "$header" axis)"
        mi="$(col_index "$header" materiality)"
        ui="$(col_index "$header" url)"
        di="$(col_index "$header" date)"
        if [ "$ai" -ge 0 ] && [ "$mi" -ge 0 ]; then
          csv_change_log_seen=1
          rownum=0; bad=0
          while IFS= read -r row; do
            rownum=$((rownum + 1))
            [ "$rownum" -le 1 ] && continue   # skip header
            # skip blank lines
            [ -z "$(printf '%s' "$row" | tr -d ', \r')" ] && continue
      
            axv="$(field_at "$row" "$ai")"
            mav="$(field_at "$row" "$mi")"
            case "$AXES" in *" $axv "*) : ;; *) fail "$f row $rownum: axis '$axv' not in {pricing,feature,positioning,messaging,team,other}"; bad=1 ;; esac
            case "$MATS" in *" $mav "*) : ;; *) fail "$f row $rownum: materiality '$mav' not in {high,medium,low}"; bad=1 ;; esac
            if [ "$ui" -ge 0 ]; then
              urv="$(field_at "$row" "$ui")"
              [ -z "$urv" ] && { fail "$f row $rownum: empty url"; bad=1; }
            fi
            if [ "$di" -ge 0 ]; then
              dtv="$(field_at "$row" "$di")"
              [ -z "$dtv" ] && { fail "$f row $rownum: empty date"; bad=1; }
            fi
          done < "$f"
          [ "$bad" -eq 0 ] && ok "change log clean: $f"
        fi
      
        # --- feature matrix: any <x>_value with a PAIRED <x>_source_url column ---
        # discover candidate prefixes that have a *_value column, then keep only those
        # that also declare a *_source_url column (so change-log old_value/new_value,
        # which have no paired source column, are not mistaken for matrix cells).
        cand="$(printf '%s' "$header" | tr ',' '\n' | sed 's/\r//' | sed -n 's/^[[:space:]]*\([A-Za-z0-9_]*\)_value[[:space:]]*$/\1/p')"
        prefixes=""
        for pre in $cand; do
          if [ "$(col_index "$header" "${pre}_source_url")" -ge 0 ]; then
            prefixes="$prefixes $pre"
          fi
        done
        if [ -n "$(printf '%s' "$prefixes" | tr -d ' ')" ]; then
          csv_matrix_seen=1
          bad=0
          for pre in $prefixes; do
            vi="$(col_index "$header" "${pre}_value")"
            si="$(col_index "$header" "${pre}_source_url")"
            pi="$(col_index "$header" "${pre}_date")"
            rownum=0
            while IFS= read -r row; do
              rownum=$((rownum + 1))
              [ "$rownum" -le 1 ] && continue   # skip header
              [ -z "$(printf '%s' "$row" | tr -d ', \r')" ] && continue
              val="$(field_at "$row" "$vi")"
              [ -z "$val" ] && continue   # blank value is honest; nothing to source
              src=""; dat=""
              [ "$si" -ge 0 ] && src="$(field_at "$row" "$si")"
              [ "$pi" -ge 0 ] && dat="$(field_at "$row" "$pi")"
              if [ -z "$src" ] || [ -z "$dat" ]; then
                fail "$f row $rownum: '${pre}' value '$val' has no source_url+date (invented fact)"
                bad=1
              fi
            done < "$f"
          done
          [ "$bad" -eq 0 ] && ok "feature-matrix cells all sourced+dated: $f"
        fi
      done < "$CSV_FILES"
      
      [ "$csv_change_log_seen" -eq 0 ] && skip "no change-log CSV (header with axis+materiality) found"
      [ "$csv_matrix_seen" -eq 0 ] && skip "no feature-matrix CSV (<x>_value/_source_url/_date) found"
      
      # ---------------------------------------------------------------------------
      # 3. Monitoring-config cadence sanity (WARN only)
      # ---------------------------------------------------------------------------
      SLOW_AXES="positioning careers team reviews review social"
      cfg_seen=0
      while IFS= read -r f; do
        [ -z "$f" ] && continue
        # only consider files that look like config (mention a cadence keyword)
        grep -iqE 'cadence|every|min|hour|daily|weekly|monthly' "$f" 2>/dev/null || continue
        while IFS= read -r ln; do
          low="$(printf '%s' "$ln" | tr 'A-Z' 'a-z')"
          case "$low" in *axis*|*cadence*|*pricing*|*positioning*|*careers*) : ;; *) continue ;; esac
      
          # sub-15-minute cadence present on the line?
          sub15=0
          # patterns like 5m, 10 min, 00:10:00, "5 minutes", "every 10 min"
          if printf '%s' "$low" | grep -Eq '(^|[^0-9])(0?[0-9]|1[0-4])[[:space:]]*m(in)?([^a-z]|$)'; then sub15=1; fi
          if printf '%s' "$low" | grep -Eq '00:0[0-9]:00|00:1[0-4]:00'; then sub15=1; fi
          if printf '%s' "$low" | grep -Eq '(0?[0-9]|1[0-4])[[:space:]]*minute'; then sub15=1; fi
      
          slower_than_daily=0
          if printf '%s' "$low" | grep -Eq 'weekly|monthly|[0-9]+[[:space:]]*(day|days)'; then
            # >1 day or weekly/monthly
            if printf '%s' "$low" | grep -Eq 'weekly|monthly|([2-9]|[1-9][0-9]+)[[:space:]]*day'; then slower_than_daily=1; fi
          fi
      
          # pricing axis with a slower-than-daily cadence -> missed move
          if printf '%s' "$low" | grep -q 'pricing' && [ "$slower_than_daily" -eq 1 ]; then
            cfg_seen=1
            warn "$f: pricing axis on a slower-than-daily cadence — risks a missed move :: $ln"
          fi
      
          # slow axis with a sub-15-min cadence -> noise/cost
          if [ "$sub15" -eq 1 ]; then
            for sa in $SLOW_AXES; do
              if printf '%s' "$low" | grep -q "$sa"; then
                cfg_seen=1
                warn "$f: '$sa' axis on a sub-15-min cadence — noise and cost :: $ln"
                break
              fi
            done
          fi
        done < "$f"
      done < "$CFG_FILES"
      [ "$cfg_seen" -eq 0 ] && skip "no monitoring-config cadence mismatches found"
      
      printf '\nok=%d skip=%d warn=%d fail=%d\n' "$ok_count" "$skip_count" "$warn_count" "$fail_count"
      
      if [ "$fail_count" -gt 0 ]; then exit 1; fi
      if [ "$STRICT" -eq 1 ] && [ "$warn_count" -gt 0 ]; then exit 1; fi
      exit 0
      
  • SKILL.md 12.3 KB
    ---
    name: competitor-watch
    description: "Use when an already-named set of rivals is watched on a cadence — pricing, features, positioning and changelog diffed into a maintained tracker plus an append-only, classified change log. NOT sizing the market or choosing who the rivals are (that is `market-research`), NOT one-off page extraction (that is `data-scraper`)."
    tags: [competitive-intelligence, monitoring, pricing-tracking, feature-tracking, marketing-ops]
    recommends: [market-research, pricing, sales-pipeline, brand-voice, data-scraper, automation-flows, seo-geo]
    origin: risco
    ---
    
    # competitor-watch
    
    You run a **standing watch**, not a one-shot study. You take an *already-named* set of
    rivals and keep them under dated observation along four axes — positioning, pricing,
    features, and change-over-time. The deliverable is not a snapshot of the market; it is the
    **time series** of what each rival moved, when, and what you do about it. Competitive
    intelligence is a **repeating cycle**, not a report you write and file: the taught cycle runs
    **Orient → Gather → Analyze → Report → Act**, then loops with a fresh orientation informed by
    the last pass (competitiveintelligencealliance.io, accessed 2026-06-02).
    
    Two near-misses decide the routing (the rest are in **Handoffs**, below). `../market-research/SKILL.md`
    answers *"what is the market and who is in it"* once, and often **produces the list you watch** —
    you are the downstream loop that watches that list forever. `../data-scraper/SKILL.md` owns the
    generic mechanics of pulling data off a page on demand; you *use* change detection as a means, but
    your identity is the maintained tracker + classified change log + cadence. "Extract this one table
    once" → data-scraper. "Keep watching these five companies" → you.
    
    ## Ethics gate — runs FIRST, before any capture
    
    Legitimate CI is **legal + ethical collection from public, observable sources** with your
    identity disclosed. SCIP's Code of Ethics is the industry line (scip.org, accessed 2026-06-02).
    The practical gate is the **front-page test**: would you be comfortable if your collection
    method were reported on the front page of the news? If not, don't do it.
    
    - **Public/observable sources only** — their own site, public filings, trade-show material,
      published reviews (G2/Capterra), public social. *Why:* anything else is not CI, it's a legal risk.
    - **Never pose as a customer** to extract non-public info (no fake demo requests, no false
      pretenses, no misrepresenting who you are). *Why:* it's a Code-of-Ethics violation and it taints the data.
    - **Never bypass access controls, paywalls, or rate limits / "no-scrape" terms.** *Why:*
      circumventing access is the bright line between intelligence and intrusion.
    
    If a request needs any of those, **refuse and reframe to the legal equivalent**: instead of
    "get their internal pricing," watch their *public* pricing page on a cadence and log the moves.
    
    ## Ground & scope before you watch anything
    
    1. **Get the competitor list.** If there is none, or it's unvalidated guesswork → STOP and
       route to `../market-research/SKILL.md`. *Why:* watching the wrong rivals forever is worse
       than not watching. You are not the one who decides who the competitors are.
    2. **Pick the vital few, then the watch-axes.** The 3–7 rivals that move your roadmap, and only
       the surfaces (below) that change your decisions. *Why:* watching 30 companies on every axis
       produces noise nobody reads; depth on the few beats breadth on the many.
    3. **Persist the tracker of record** under `02-DOCS/wiki/competitors/` (one profile per rival
       + a shared change log); keep raw captures under `02-DOCS/raw/competitors/`. *Why:* the
       tracker is a maintained artifact, not a chat answer — it has to live somewhere re-runnable.
    4. **Every price/feature cell carries a `source_url` + `date`, or it stays blank.** *Why:*
       this is the single highest-value guard against inventing a rival's number. If you didn't
       see it on a dated public page, you don't know it — leave the cell empty and say so.
    
    ```text
    Bad:  Acme Pro tier — $79/mo                          (no source, no date — invented)
    Good: Acme Pro tier — $79/mo  [acme.com/pricing, 2026-05-28]   (seen, sourced, dated)
    ```
    
    ## Watch-list → config: map each surface
    
    This is the real branch — different surfaces want different cadences and different selector
    types, so the table earns its place. Cadence is tiered by how fast the surface moves: time-
    sensitive surfaces want **5–15 min** checks, general competitor surfaces **hourly–daily**,
    slow/compliance surfaces **daily** (visualping.io / scrapx.io cadence guidance, accessed
    2026-06-02). Over-frequent on a slow page is just cost and noise; under-frequent on pricing is a missed move.
    
    | Axis (surface) | What you watch | Cadence | Selector type |
    |---|---|---|---|
    | Pricing / packaging | the price node, tier names, promo banner | **5–15 min** | CSS on the price element, or JSONPath if pricing comes from an API |
    | Features / changelog | release-notes / "what's new" list | daily | CSS on the release list (first N items) |
    | Positioning / homepage | hero headline + subhead copy | daily–weekly | CSS on the hero text block |
    | Launches / partnerships | press / blog index | daily | CSS on the post list |
    | Careers / hiring | open-roles count + titles | weekly | CSS on the jobs list (signals strategy) |
    | Customer reviews | G2 / Capterra recent reviews | weekly | CSS on the review feed |
    | Social | public profile / posts | daily–weekly | platform-dependent; public only |
    
    That mapping — **URL + selector + cadence per surface** — *is* the monitoring config. Write
    it down as config, don't hold it in your head.
    
    ## The change loop
    
    Each pass on a watched URL: **capture → diff vs last → classify → score → log → flag.**
    
    1. **Capture** the watched node (not the whole page — the selector keeps the diff signal-clean).
    2. **Diff** against the last stored capture for that URL.
    3. **Classify** the change into exactly one axis: `pricing | feature | positioning | messaging | team | other`.
    4. **Score materiality**: `high` (changes our roadmap or pricing), `medium` (worth knowing),
       `low` (cosmetic / noise). *Why:* a diff with no classification and no materiality is noise — it
       tells you something moved but not whether to care.
    5. **Append a dated change-log row.** Never overwrite; the log is the time series.
    6. **Flag the `high` rows** for action and route them — a price move to whoever owns *our*
       pricing decision, a feature ship to product. You log and flag; you don't make those calls.
    
    ```text
    Bad:  "They changed their website."
          (no date, no axis, no old/new, no materiality, no action — unactionable)
    
    Good: 2026-05-20 · pricing · Acme · acme.com/pricing
          Pro tier $49→$59/mo · high · revisit our mid-tier vs theirs
          (dated, classified, old→new, scored, with a next step)
    ```
    
    ## The tracker artifacts
    
    Three structured files. Required fields named here; full schema + a filled end-to-end example
    competitor live in `references/tracker-schema.md`. The profile is a `.md` page under the
    `02-DOCS/wiki/` OKF v0.1 bundle, so its YAML frontmatter carries a non-empty `type: competitor`
    (plus the OKF-recommended `title`/`description`/`tags`/`timestamp`) alongside the domain keys;
    its body uses standard markdown links, never wikilinks. The two CSVs are data files, not OKF
    documents.
    
    - **Competitor profile** (one per rival): OKF frontmatter (`type: competitor`, …) + the domain
      keys `name`, `positioning_line`, `segment`, pricing tiers (each with `amount`, `currency`,
      `source_url`, `date`), feature-matrix rows, watched URLs.
    - **Feature matrix** (CSV): rows = features, columns = competitors, each cell sourced + dated.
    - **Change log** (CSV, append-only): `date,competitor,axis,url,old_value,new_value,materiality,action`.
    
    ```csv
    date,competitor,axis,url,old_value,new_value,materiality,action
    2026-05-20,Acme,pricing,https://acme.com/pricing,$49/mo,$59/mo,high,revisit our mid-tier
    2026-05-22,Acme,feature,https://acme.com/changelog,,SSO on Team plan,medium,note for product
    ```
    
    ## Tooling — what you can actually run
    
    The runnable default is **`changedetection.io`** (open-source, self-hosted): it does text/
    visual / **XPath/CSS-selector** and **JSON-API (JSONPath / jq)** change detection, checks as
    often as ~1 minute, notifies via Slack/Discord/Telegram/email/API, and ships AI change
    summaries like "Price dropped from \$89.99 to \$67.00" (github.com/dgtlmoon/changedetection.io,
    accessed 2026-06-02). Prefer this when you must *produce a config you can run* rather than
    recommend a SaaS. Full docker-compose + per-axis watch recipe is in `references/monitoring-config.md`.
    
    - **The Wayback Machine is an archive, not a monitor.** It captures *some* snapshots (a
      pricing page may be archived once in months, or never) and **does not tell you when something
      changed**; its "Changes" diff (added=blue, deleted=yellow) only compares two existing
      captures (archive.org "Compare two versions", accessed 2026-06-02). Use it to **reconstruct
      historical positioning**, never as the live alerting layer.
    - **The paid CI-suite tier** exists and sets the feature bar — quote real numbers, don't
      over-prescribe: Crayon median ≈\$28.7K/yr, Klue ~\$16K–\$42.7K/yr (priced by seats), Kompyte
      ~\$20K avg ARR (entry from ~\$300/yr), lightweight page-monitors Visualping from ~\$10/mo,
      ChangeTower from ~\$9/mo (vendr.com, autobound.ai, kompyte.com, accessed 2026-06-02). These
      auto-update battlecards — but the **battlecard is a sales artifact owned by `../sales-pipeline/SKILL.md`**,
      not you. A self-host config covers most teams; the suite is overkill until you're tracking
      many rivals across social + filings 24/7 with a dedicated CI owner.
    - **The recurring run** (cron / webhook scheduling) is wiring, not watching → `../automation-flows/SKILL.md`.
    
    ## Handoffs
    
    | Request | Route to |
    |---|---|
    | Size the market, produce/validate the competitor list, TAM/SAM/SOM, buyer/JTBD | `../market-research/SKILL.md` |
    | Set OUR price / packaging / tiers (a decision, not an observation) | `../pricing/SKILL.md` |
    | Build the sales battlecard / objection handling / "why we win" | `../sales-pipeline/SKILL.md` |
    | Define OUR positioning / value prop / messaging | `../brand-voice/SKILL.md` |
    | One-off "extract this page/table once" with no cadence or tracker | `../data-scraper/SKILL.md` |
    | Wire the recurring run as a cron/webhook automation | `../automation-flows/SKILL.md` |
    | Track OUR own SEO / AI-search visibility vs rivals on one page | `../seo-geo/SKILL.md` |
    
    ## Anti-patterns
    
    | Anti-pattern | Why it's wrong | Do instead |
    |---|---|---|
    | Treating it as a one-shot study | The value is the delta over time; a snapshot is stale on arrival | Set a cadence, append to a change log every pass |
    | Inventing a rival's price/feature | Unsourced "facts" pollute the tracker and mislead decisions | Every cell gets `source_url` + `date`, or stays blank |
    | Posing as a customer / bypassing a paywall | Fails the front-page test; not CI, it's a legal risk | Public observable sources only; refuse and reframe |
    | Using the Wayback Machine as the live monitor | It doesn't tell you *when* something changed and may never capture the page | Wayback for historical reconstruction only; changedetection.io for live alerts |
    | 5-min cadence on a careers page / weekly on pricing | Over-frequent = noise + cost; under-frequent = a missed move | Tier the cadence by axis volatility (see the table) |
    | Logging a raw diff with no axis/materiality | "Something changed" is unactionable | Classify the axis, score materiality, add a next step |
    | Watching 30 competitors | Breadth produces noise nobody reads | Watch the vital 3–7 that move your roadmap |
    | Building the battlecard inside this skill | That's sales enablement, a different owner | Hand off to `../sales-pipeline/SKILL.md`; you supply the tracker |
    
    ## Verify
    
    After you emit a tracker / change log / config, run `scripts/verify.sh` against your project
    docs. Read-only lint: required tracker columns present; **every pricing/feature row has a
    non-empty `source_url` and `date`** (the anti-invention guard); change-log `axis` and
    `materiality` in the allowed sets, with a `url` + `date` on every row; and a **warning** when a
    monitoring-config entry pairs a slow axis with a sub-15-min cadence, or pricing with a
    slower-than-daily one. It exits 0 on an empty/clean target — no false failures.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related