
Oma Market
- 18 installs
- 41 repo stars
- Updated August 4, 2026
- gracefullight/stock-checker
Extract pain points, trends, and competitor positioning from community sources like Reddit, HN, and GitHub Issues into a market brief.
About
Classifies intent into pain/trend/competitor/discovery, fans out to community sources via oma-search, and clusters findings with SWOT/Porter/PESTEL frameworks. A developer uses it for voice-of-customer pain analysis, trend detection, and competitor sentiment research.
- Community signal fan-out across Reddit, HN, Bluesky, Mastodon, GitHub
- Auto-applied SWOT, Porter's 5F, and PESTEL frameworks into one brief
Oma Market by the numbers
- 18 all-time installs (skills.sh)
- Ranked #664 of 853 Sales & Marketing skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gracefullight/stock-checker --skill oma-marketAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 41 |
| Last updated | August 4, 2026 |
| Repository | gracefullight/stock-checker ↗ |
What it does
Extract pain points, trends, and competitor positioning from community sources like Reddit, HN, and GitHub Issues into a market brief.
Files
Market Research Agent - Community Signal Intelligence
Scheduling
Goal
Classify user intent into pain / trend / competitor / discovery, fan-out to community sources via oma search fetch, score and cluster findings with deterministic CLI compute, auto-apply strategic frameworks, and emit a single LAW-compliant markdown brief.
Intent signature
- User asks about pain points, user complaints, or voice-of-customer signals for a product or category.
- User asks what is trending, growing, or declining in a space this week or month.
- User asks how one product compares to another in community sentiment or positioning.
- User asks for discovery or exploratory market research on a topic.
When to use
- Extracting real user pain points from community posts (Reddit, HN, GitHub Issues, Bluesky, Mastodon)
- Detecting trends in a product category over a time window (7d / 30d / 90d / 180d)
- Competitor sentiment analysis and SWOT positioning
- Open-ended discovery research across multiple sources
When NOT to use
- General web research without market framing -> use oma-search directly
- Single-source queries only -> use
oma search fetchstandalone - Delta tracking or trend velocity over time (v2 feature) -> defer
- Live dashboards or scheduled monitoring -> out of scope (v1 one-shot only)
Expected inputs
- Topic string and optional
--intent pain|trend|competitor|discovery - Optional
--window 7d|30d|90d|180d(default:30d) - Optional
--sources <list>to override defaults - Optional
--vs <entity>for competitor COMPARISON mode - Optional
--frameworks auto|none|swot,5f,pestel
Expected outputs
- Single markdown brief at
.agents/results/market/{topic-slug}-{YYYYMMDD}.md - Badge first-line,
What we learned:body opener (or COMPARISON title), engine footer - No raw evidence dump; no Sources block; no em-dash; no
##in body (framework/COMPARISON sections excepted)
Dependencies
oma-searchfor all fetches (oma search fetch --only api); never fetches directly- Serena
trust-registry-cache(read-only); Trust Registry labels inherited from oma-search resources/intent-rules.md,resources/operator-packs/,resources/output-laws.md
Control-flow features
- Branches by classified intent, window, source availability, and env key presence
- detect-trap gate before harvest (exit 2 on broad/ambiguous topic, exit 4 on invalid)
- Paid sources (X, TikTok, Instagram, YouTube, Perplexity) auto-skip when env key absent
- Framework auto-toggle by intent (see Routes table)
Structural Flow
Entry
1. Run oma market detect-trap "<topic>" to preflight the query. 2. Classify or confirm intent from user prompt or --intent flag. 3. Select operator pack and framework set for the intent.
Scenes
1. PREPARE: Parse topic and flags; run detect-trap; resolve intent, operator pack, window. 2. ACT: Build per-source oma search fetch URLs with operator pack query expansion. 3. ACQUIRE: Fan-out harvest via oma market harvest (parallel, per-source-limit 12, cache TTL 15m). 4. VERIFY: Score, fuse, and cluster candidates; validate JSON at each pipe stage. 5. FINALIZE: Render LAW-compliant markdown brief; run self-check; write to output path.
Transitions
- If detect-trap exits 2 (REFUSE), surface reframe suggestion and halt.
- If all sources blocked, exit 2 with per-source diagnostics.
- If partial harvest failure, proceed; render annotates "coverage: N/M sources".
- If zero clusters, emit preview message and suggest wider window.
- If
--vs <entity>flag is present, switch to COMPARISON template.
Failure and recovery
- detect-trap exit 2: surface REFUSE reason and suggested reframe; do not proceed to harvest.
- Network timeout (exit 6): report and suggest
--windowreduction or--no-cache. - Invalid JSON from any pipe stage: exit 4 with offending line in stderr.
- Render LAW self-check violation: strip or regenerate; exit 1 only if regeneration also fails.
- FS permission denied at write: exit 5.
Exit
- Success: brief file written; first 50 lines previewed; engine footer present.
- Partial success: source failures and framework skips are explicit in footer and stderr.
Logical Operations
Actions
| Action | SSL primitive | Evidence |
|---|---|---|
| Run detect-trap preflight | VALIDATE | Topic arg, trap pattern rules |
| Classify intent | SELECT | Intent rules, user flags |
| Select operator pack | SELECT | resources/operator-packs/ |
| Fan-out harvest | CALL_TOOL | oma market harvest -> oma search fetch |
| Score candidates | INFER | Engagement weights, freshness, intent blends |
| Fuse and deduplicate | INFER | URL canonicalize, RRF k=60, author cap |
| Cluster by entity overlap | INFER | Overlap coefficient >= 0.4, MMR lambda=0.75 |
| Select frameworks | SELECT | Intent-to-framework toggle table |
| Render and self-check | WRITE | Output LAWs, framework templates |
| Write brief | WRITE | .agents/results/market/ |
| Report preview | NOTIFY | First 50 lines of brief |
Tools and instruments
oma market detect-trap(preflight gate)oma market harvest(delegates tooma search fetch --only api)oma market score(engagement weights, log1p, intent blends)oma market fuse(URL canonical, RRF, diversity guard)oma market cluster(entity overlap, MMR)oma market render(md/json, LAW self-check, file write)
Canonical command path
TOPIC="VS Code pain points"
oma market detect-trap "$TOPIC" \
&& oma market harvest "vscode (broken OR bug OR migrate OR quit OR slow)" \
--sources reddit,hn,bluesky,mastodon,github-issues --window 30d \
--operator-pack pain \
| oma market score --intent pain \
| oma market fuse \
| oma market cluster \
| oma market render --format md --intent pain --frameworks autoResource scope
| Scope | Resource target |
|---|---|
NETWORK | Community sources via oma search fetch (reddit, hn, bluesky, mastodon, github-issues, grounding) |
LOCAL_FS | Brief output at .agents/results/market/; cache at ~/.cache/oma/market/ |
PROCESS | oma market subcommands; oma search fetch |
MEMORY | Intent classification, operator pack selection, cluster summaries |
Preconditions
- Topic is non-empty and passes detect-trap (not demographic-shopping, not single-noun-too-broad).
- At least one keyless source is reachable (reddit, hn, bluesky, mastodon, github-issues, or grounding).
Effects and side effects
- Writes brief markdown to
.agents/results/market/{topic-slug}-{YYYYMMDD}.md. - Populates local cache at
~/.cache/oma/market/{sha1-hash}/result.json(TTL 15m). - Reads Serena
trust-registry-cache(no write).
Guardrails
1. detect-trap first: never harvest without preflight; --force bypasses only in test mode. 2. Delegate all fetches: harvest calls oma search fetch --only api; no direct platform HTTP. 3. Trust labels read-only: no re-scoring; Trust Registry ownership stays with oma-search. 4. Paid sources auto-skip: drop silently with [INFO] stderr if env key absent; never error. 5. LAW self-check mandatory: render runs self-check before file write; --no-self-check for debug only. 6. No raw evidence dump: cluster internals (scores, item counts) stay in JSON output; markdown body paraphrases. 7. Stdout pure JSON per stage: each pipe stage (except render) emits valid JSON only; stderr for warnings. 8. No Serena writes in v1: trust-registry-cache is read-only; write authority stays with oma-search.
Routes
| Intent | Operator pack | Auto frameworks | Notes |
|---|---|---|---|
pain | resources/operator-packs/pain.md | SWOT | Weights: engagement 0.40, freshness 0.30, quality 0.30 |
trend | none (optional: resources/operator-packs/positive.md for pain/positive contrast) | SWOT | Weights: freshness 0.50, engagement 0.30, quality 0.20 |
competitor | resources/operator-packs/competitor.md | SWOT + Porter's 5F (v1.1 stub) | Weights: relevance 0.35, engagement 0.35, quality 0.30; --vs enables COMPARISON template |
discovery | resources/operator-packs/discovery.md | SWOT + PESTEL (v1.1 stub) | Weights: relevance 0.45, engagement 0.30, quality 0.25 |
Porter's 5F and PESTEL: the CLI renders empty framework slots; the host LLM fills them using the analyst prompts in resources/frameworks/porters-5f.md and pestel.md (execution-protocol Step 6).
Default Workflow
1. Preflight: oma market detect-trap exits 0 or halts. 2. Harvest: fan-out to keyless sources with operator-pack query; paid sources conditional on env keys. 3. Score: apply intent-specific engagement weights and log1p normalization. 4. Fuse: URL-canonicalize, deduplicate, RRF k=60, per-author cap <= 3. 5. Cluster: entity overlap coefficient >= 0.4, MMR lambda=0.75, <= 3 representatives. 6. Render: select frameworks, synthesize brief, run LAW self-check, write file.
Invocation
Standalone
/oma-market "Next.js pain points" --intent pain --window 30d
/oma-market "AI coding tools trend" --intent trend
/oma-market "Cursor vs Windsurf" --intent competitor --vs Windsurf
/oma-market "developer productivity market" --intent discoveryShared (from other skills or workflows)
Pass the rendered brief path (.agents/results/market/{slug}-{YYYYMMDD}.md) as a --use-market-research arg to Brainstorm or PM workflows. The brief is a static file; the calling skill reads it directly.
References
- Intent classification:
resources/intent-rules.md - Operator packs:
resources/operator-packs/(pain.md, positive.md, competitor.md, discovery.md) - Frameworks:
resources/frameworks/(swot.md, porters-5f.md, pestel.md — analyst prompts the host LLM fills into the rendered slots) - Execution steps:
resources/execution-protocol.md - Output LAWs and self-check rules:
resources/output-laws.md - Input/output examples:
resources/examples.md - Pre-flight checklist:
resources/checklist.md - Error recovery:
resources/error-playbook.md - Context loading:
../_shared/core/context-loading.md - Lessons learned:
../_shared/core/lessons-learned.md
Pre-submit checklist for oma-market — all items must pass before writing the brief to results/.
Self-Check Items
- [ ] detect-trap exit code is 0 or 2 with user reframe (never silently swallowed)
- [ ] harvest
sources_usedhas >= 2 entries, or warn coverage in brief footer - [ ] All cluster representatives have URL or plain-text fallback (no bare signal IDs)
- [ ] No
—(em-dash) or–(en-dash) characters in output body (use-instead) - [ ] No
##headers in output body outside framework sections (SWOT, Porter's 5F, PESTEL) - [ ] No
Sources:/References:/Further reading:block at end of brief - [ ] Badge present on line 1 in format
🔎 oma-market v{ver} · synced {YYYY-MM-DD} - [ ] Inline
[name](url)citation count > 0 (at least one cited source in body)
How to Use
Run this checklist in Step 7 of execution-protocol.md before writing the output file.
For each failing item, revise the render output and re-check before proceeding. Do NOT write the brief if any item fails.
If sources_used has only 1 entry (coverage warning case), write the brief but append a warning line in the engine footer:
⚠ Low coverage: only 1 source returned signals. Consider --window 90d or check env keys.Recovery steps for common oma-market failures — consult when a CLI stage exits with an error code.
harvest exit 2 - All Sources Blocked
Cause: Every configured source returned 429, 403, or auth failure.
Recovery steps: 1. Check paid-source env keys if those sources were requested (X_BEARER_TOKEN, SCRAPECREATORS_API_KEY, PERPLEXITY_API_KEY; GITHUB_TOKEN raises github-issues rate limits). reddit, hn, bluesky, mastodon, and grounding are keyless. 2. Retry with --window 90d to widen the harvest window (more cache-eligible content). 3. Try --sources reddit to isolate a single known-working source. 4. If all sources are blocked, the run cannot proceed. Report to user:
All configured sources are currently unavailable. Check your API keys or try again later.
Use --sources to restrict to a source you know is accessible.harvest exit 6 - Timeout
Cause: One or more source adapters exceeded the per-request time limit.
Recovery steps: 1. Reduce result set with --per-source-limit 6 (default is 12). 2. Add --no-cache to bypass a stale cache that may be causing retry loops. 3. Try --sources hn or --sources reddit to exclude slow sources. 4. If timeout persists on a single source, open an issue against the source adapter.
render zero clusters - No Clusters Produced
Cause: score + fuse + cluster pipeline produced 0 clusters above the trust threshold.
Recovery steps: 1. Widen window: re-harvest with --window 90d. 2. Drop operator pack: re-harvest with --operator-pack none (removes topic-narrowing clauses that may exclude all signals). 3. Loosen trust filter on render: --min-trust external (levels: verified > community > external). 4. If zero clusters persist after all three steps, report to user:
No market signals found for this topic in the configured window and sources.
The topic may be too niche, too new, or misspelled. Try a broader rephrasing.detect-trap exit 2 - Personal Advice Trap
Cause: Topic classified as a personal decision (interest, budget, relationship) rather than a market signal.
Recovery steps (user-facing): User must add a market qualifier before retrying. Provide examples:
Instead of: "should I learn Python"
Try: "Python developer job market trends"
Instead of: "best credit card for me"
Try: "credit card user pain points"
Instead of: "is my startup idea good"
Try: "async standup tools user pain"The skill does NOT retry automatically on exit 2. The user must re-invoke with a revised topic.
Sample briefs
These are reference outputs showing the expected shape of oma market render results. Citation URLs are synthetic for documentation; live runs cite real handles, threads, and posts.
---
Example 1 — pain intent: VS Code performance pain points (last 30 days)
oma-market v0.1.0 · intent: pain · synced 2026-05-14 · window: 30dWhat we learned: Extension host crashes and startup latency dominate the VS Code pain conversation this month, with remote SSH workflows amplifying both issues.
Extension host instability is the top complaint cluster. Users on r/vscode report the Python and Pylance extensions crashing the host 2-3 times per session on large monorepos. A HN thread from last week accumulated 140 comments on the same pattern, with many switching to Neovim LSP as a workaround.
Startup time degrades sharply beyond 40 extensions. A GitHub issue tracking extension activation order has 380 upvotes. Community benchmarks shared on r/programming show median cold-start at 4.2 s with 45+ extensions, versus 0.9 s for a clean profile — a 4.7x gap that users call "unacceptable for a daily driver."
KEY PATTERNS from the research:
1. Extension host crashes are triggered most often by language server extensions (Python, Go, Rust Analyzer) on files > 5 MB. 2. Remote SSH sessions compound latency: file-save round-trips average 800 ms vs. 120 ms local, per a community benchmark repo. 3. Memory footprint complaints cluster around the 16 GB RAM threshold — users with less RAM report swap-induced freezes during indexing. 4. Settings Sync conflicts after team upgrades generate a secondary pain cluster, particularly around keybinding merges.
SWOT
Strengths
- Extension ecosystem depth is unmatched; users tolerate pain because no alternative covers all languages.
- Remote development (SSH, Containers, Codespaces) has no comparable open-source rival.
Weaknesses
- Extension host is a single point of failure; one bad extension crashes the whole environment.
- Startup performance scales poorly with extension count, with no built-in lazy-load enforcement.
Opportunities
- Profiling tooling for extensions (similar to Chrome DevTools for extensions) would capture this pain directly.
- A curated "Lite Profile" for users with < 20 extensions could win back churned users.
Threats
- Zed and Neovim LSP setups are cited as direct alternatives in 23% of sampled complaints.
- JetBrains Fleet's remote story is maturing and targets the SSH pain cluster explicitly.
--- Engine: oma-market v0.1.0 · sources: reddit, hn, github · window: 30d · clusters: 4 · trust >= 0.6
---
Example 2 — trend intent: RAG framework adoption trend (last 30 days)
oma-market v0.1.0 · intent: trend · synced 2026-05-14 · window: 30dWhat we learned: LlamaIndex and LangChain are losing ground to lighter orchestration layers as teams prioritize latency and cost over feature breadth.
Minimal orchestration is the dominant emerging pattern. Posts on r/LocalLLaMA and r/MachineLearning show teams stripping LangChain from production and replacing it with direct API calls plus a single vector store client. The cited reason in 70% of cases is debugging difficulty — framework abstractions hide retrieval failures.
Hybrid retrieval (dense + sparse) is crossing from research to production. A HN thread on a Pinecone blog post about BM25 + ADA-002 hybrid search generated 200 comments, most positive. Teams report 12-18% precision gains on domain-specific corpora over pure dense retrieval.
KEY PATTERNS from the research:
1. LlamaIndex GitHub stars grew 8% month-over-month but issue volume grew 22%, suggesting adoption is outpacing maintainer capacity. 2. "RAG evaluation" is a rising search term — teams are investing in offline evals (RAGAS, TruLens) before production rollout. 3. Chunking strategy discussions dominate practitioner forums; semantic chunking libraries saw 3 new releases this month. 4. Cost-driven architectural shifts: teams moving retrieval to local embeddings (nomic-embed, mxbai) to cut OpenAI embedding spend by 60-80%.
SWOT
Strengths
- RAG is now a well-understood pattern; onboarding friction has dropped significantly since 2024.
- Retrieval-augmented generation outperforms fine-tuning for knowledge-update use cases, a validated claim driving enterprise adoption.
Weaknesses
- Evaluation tooling is fragmented; no single standard for measuring retrieval quality.
- Framework churn creates maintenance debt — teams upgrading LangChain face breaking changes every 3-4 months.
Opportunities
- Evaluation-as-a-service is an open market gap; no dominant player yet.
- Local embedding providers could build IDE plugins and attract the "no cloud data" enterprise segment.
Threats
- LLM providers building retrieval natively (OpenAI file search, Gemini grounding) reduce the need for custom RAG pipelines.
- Hallucination in retrieved context — a fundamental trust issue — could trigger regulatory scrutiny in healthcare and finance verticals.
--- Engine: oma-market v0.1.0 · sources: reddit, hn, github · window: 30d · clusters: 4 · trust >= 0.6
---
Example 3 — competitor intent: Cursor vs Windsurf market signal
oma-market v0.1.0 · intent: competitor · synced 2026-05-14 · window: 30dCursor vs Windsurf: 시장 신호
Quick Verdict
Cursor holds mindshare with experienced developers who value tab-completion quality and VS Code compatibility. Windsurf is gaining momentum with users who want a more autonomous "write it for me" flow, and its Cascade agent is generating the most organic word-of-mouth this month.
Cursor
Tab completion quality remains Cursor's strongest differentiator. A r/cursor thread with 600 upvotes attributes retention to multi-line completions that "feel predictive, not reactive." Users migrating from GitHub Copilot cite this as the primary pull factor in a HN discussion.
Pricing friction is the top churn signal. The $20/month Pro tier generates complaints when users hit the fast-model usage cap mid-month. r/cursor surfaces this monthly; the workaround community around BYOK (bring your own key) is growing, suggesting willingness to pay but not at current limits.
Windsurf
Cascade's autonomous multi-file editing is the breakout feature. A product launch thread reached the HN front page and stayed in the top 10 for 18 hours. Users describe Cascade as "closer to a junior dev than an autocomplete," with the ability to plan, edit, and run terminal commands in sequence.
Onboarding friction is lower than Cursor's. Multiple r/webdev posts note that Windsurf works well out-of-the-box without the VS Code extension configuration that Cursor sometimes requires. However, extension compatibility gaps with niche language plugins are a recurring complaint.
Head-to-Head
| Dimension | Cursor | Windsurf |
|---|---|---|
| Completion style | Inline tab, multi-line | Agent-first (Cascade) |
| VS Code compatibility | Near-complete | Partial (Codeium fork) |
| Pricing sentiment | Mixed (cap frustration) | Positive (free tier generous) |
| Mindshare source | r/cursor, HN power users | r/webdev, indie hackers |
| Primary pain | Usage limits | Extension gaps |
The Bottom Line
Cursor wins on depth for power users; Windsurf wins on accessibility for newcomers. If Windsurf closes the extension compatibility gap, it poses a credible threat to Cursor's mid-market position. The autonomous agent narrative (Cascade) is resonating in a way that tab-completion improvements no longer do.
SWOT
From Cursor's perspective:
Strengths
- Best-in-class inline completion quality, validated by repeat user testimony.
- Deep VS Code ecosystem compatibility; drops into existing workflows with no config.
Weaknesses
- Fast-model usage caps create predictable monthly churn spikes.
- Perception of being "just an autocomplete" limits appeal to the agentic-workflow segment.
Opportunities
- Shipping a competitive autonomous agent mode would neutralize Windsurf's primary differentiation.
- A teams/enterprise tier with higher limits could convert the BYOK workaround community.
Threats
- Windsurf's Cascade is generating stronger organic advocacy in growth-stage developer communities.
- GitHub Copilot Workspace targets the same agentic segment with Microsoft distribution behind it.
Porter's 5 Forces
_Porter's 5 Forces analysis is available in v1.1 (planned)._
--- Engine: oma-market v0.1.0 · sources: reddit, hn · window: 30d · clusters: 5 · trust >= 0.6
Step-by-step execution protocol for oma-market
The CLI handles deterministic compute (search, score, fuse, cluster, render skeleton + cluster bank). The HOST LLM (Claude / Codex / Gemini reading this skill) handles semantic work (intent detection, framework synthesis). Steps below alternate between the two responsibilities.
Step 0 — Trap Detection (CLI)
Run oma market detect-trap "<topic>". On exit 2, return the REFUSE message to the user and STOP.
Step 1 — Intent Classification (LLM)
Read the user's prompt and classify intent into one of pain | trend | competitor | discovery per intent-rules.md.
Mapping cues (non-exhaustive — read the prompt, do not pattern-match blindly):
- "X 페인 / X 불편 / X 이탈 이유 / why do users leave X" →
pain - "X 트렌드 / X 부상 / hot in X / what's hot in X" →
trend - "X vs Y / X 대 Y / X와 Y 비교" →
competitor(set--vs Y) - "X 시장 / X 카테고리 / opportunities in X / unmet needs around X"
→ discovery
Explicit --intent flag from the user always wins. Record result as $INTENT. Also derive $LOCALE (ko if topic contains Hangul, else en) and $SITES (Naver / tistory / brunch domains for ko, none otherwise).
Step 2 — Operator Pack Selection (LLM)
| Intent | Pack |
|---|---|
| pain | operator-packs/pain.md |
| trend | none |
| competitor | operator-packs/competitor.md |
| discovery | operator-packs/discovery.md |
Step 3 — Harvest (CLI)
oma market harvest "<topic>" \
--sources <list> \
--window <window> \
--operator-pack <pain|positive|competitor|discovery|none> \
--locale $LOCALE \
[--vs <competitor>] \
[--sites $SITES] \
[--per-source-limit <n>] \
[--query-strict] \
[--no-cache]Default --sources reddit,hn,bluesky,mastodon,grounding (paid sources auto-added when env keys present). Default --window 30d.
Step 4 — Score, Fuse, Cluster (CLI)
oma market score --intent $INTENT \
| oma market fuse \
| oma market cluster --overlap-threshold 0.2Tune --overlap-threshold if clusters are over-fragmented for KR queries (default 0.4 is too strict for n-gram tokens).
Step 5 — Render Skeleton (CLI)
oma market render --format md --intent $INTENT --frameworks auto \
--topic "<user-facing title>" \
[--vs <competitor>]This writes a skeleton brief: badge → body → KEY PATTERNS → Cluster Bank → empty SWOT/5F/PESTEL slots.
Step 6 — Analyst Synthesis (LLM)
Read the skeleton brief. For each framework section present:
1. Open the corresponding prompt under .agents/skills/oma-market/resources/frameworks/<name>.md. 2. Map clusters from the Cluster Bank into the framework slots per the classification rules in that prompt. 3. Replace every _(fill from cluster bank)_ placeholder with concrete bullets, each citing a cluster representative as [name](url) and tagged with its cluster ID (C#). 4. Apply the output LAWs (output-laws.md): no em-dash, no invented titles, inline [name](url) only, no trailing Sources: block. 5. Korean briefs: bullet text in Korean; structural labels stay as written in the framework prompt.
If a framework axis genuinely has no signal in the Cluster Bank, write _(no signal)_. Do not invent quotes.
Step 7 — Self-Check and Finalize (LLM)
1. Run mental checks from checklist.md. 2. Save the synthesized brief at .agents/results/market/{slug}-{YYYYMMDD}.md (render's default output path — overwrite the skeleton from Step 5). 3. Return the first 20 lines + file path to the user.
Responsibility split
| Step | Owner | Why |
|---|---|---|
| 0 | CLI | Deterministic regex preflight |
| 1, 2 | LLM | Semantic understanding of user intent |
| 3, 4, 5 | CLI | Deterministic search + scoring + rendering |
| 6 | LLM | Semantic classification + writing |
| 7 | LLM | Self-check on final prose |
The CLI never auto-classifies clusters into SWOT/5F/PESTEL — keyword classifiers do not generalize across domains and languages. The LLM hosting this skill performs all semantic mapping.
PESTEL — Analyst Prompt
You are filling the ## PESTEL section of an oma market brief. This framework applies when scanning a category for macro forces (default for intent=discovery).
Axis-by-axis classification rules
Political
Government action, policy direction, geopolitical alignment, sanctions, public-sector procurement signals.
- e.g. "EU mandates open-source documentation tools" → Political tailwind.
- e.g. "정부, 외국 SaaS에 정관계 로비 의혹" → Political risk.
Economic
Macro demand, currency, recession, supply chain, capital availability.
- e.g. "Startup funding for the category up 3x YoY" → Economic tailwind.
- e.g. "merchants cutting subscription spend after rate hikes" → Economic
headwind.
Social
Demographics, generational behavior, lifestyle shift, sentiment trends, cultural attitudes.
- e.g. "Gen Z prefers async over Zoom" → Social tailwind for async tools.
- e.g. "remote-work fatigue driving return-to-office" → Social headwind
for collaboration SaaS.
Technological
Platform shifts, AI capability waves, open-source adoption, dependency trends.
- e.g. "Every product is bolting on MCP servers" → Technological tailwind
for AI agent tooling.
- e.g. "WebAssembly maturity threatens native runtimes" → Technological
disruption.
Environmental
Sustainability, climate impact, ESG signals, regulatory carbon mandates.
- e.g. "Datacenter carbon costs cited in vendor selection" → Environmental
pressure on cloud-heavy categories.
- Often absent in pure software categories —
_(no signal)_is honest.
Legal
IP / copyright / antitrust / GDPR-PIPA / litigation / compliance.
- e.g. "GDPR fine on Notion for data residency" → Legal headwind.
- e.g. "Antitrust case against incumbent opens window for entrants"
→ Legal tailwind.
Output rules
1. Each axis: 1-3 cited bullets with direction (tailwind | headwind | neutral). Empty → _(no signal)_. 2. Every bullet cites a cluster representative as [name](url) with cluster ID tag (C#). 3. End with a **Net stance** paragraph: which 2-3 axes are most active right now and what they imply for the subject.
Output skeleton
**Political** — _direction_
- [name](url) - signal summary. (C#)
**Economic** — _direction_
- [name](url) - signal summary. (C#)
**Social** — _direction_
- ...
**Technological** — _direction_
- ...
**Environmental** — _direction_
- ...
**Legal** — _direction_
- ...
**Net stance**: 2-3 sentences identifying the dominant axes and
implications.Traps
- Don't classify cluster as Political just because the source is news
media — read the actual content.
- "AI" alone is not Technological signal — needs to show a shift the
subject must respond to.
- Environmental axis is genuinely thin in most software briefs; do not
fabricate signal. _(no signal)_ is fine.
Porter's 5 Forces — Analyst Prompt
You are filling the ## Porter's 5 Forces section of an oma market brief. This framework applies when the topic is competitive positioning of a named subject (default for intent=competitor).
Force-by-force classification rules
Threat of new entrants
Cluster signals: how easy is it for new players to enter this market?
- Quote barriers (capital, regulatory licenses, network effects, brand,
patents) → Low threat.
- Quote rising number of startup launches in this category, low entry
cost commentary, easy-to-fork OSS alternatives → High threat.
Bargaining power of suppliers
Cluster signals: dependency on upstream suppliers / vendors / partners.
- Single-source dependency, lock-in complaints, supplier price hikes
→ High supplier power.
- Multiple substitute suppliers, commoditized inputs → Low supplier power.
Bargaining power of buyers
Cluster signals: how price-sensitive / switching-able are customers?
- Price complaints, churn intent, easy switching cost mentions
→ High buyer power.
- Locked-in customers, high switching cost (data migration, retraining)
→ Low buyer power.
Threat of substitutes
Cluster signals: products from outside the category that satisfy the same need.
- "I replaced X with Y" / "we don't need X anymore because Z"
→ High substitute threat.
- No alternative discussion / users defend why this category is unique
→ Low substitute threat.
Industry rivalry
Cluster signals: intensity of competition within the category.
- Frequent head-to-head comparisons, price wars, public benchmark
fights → High rivalry.
- Sleepy category, incumbents stable, little competitive marketing
→ Low rivalry.
Output rules
1. Each force gets a verdict in Low | Moderate | High plus 1-3 cited bullets. Empty → _(insufficient signal)_. 2. Every bullet cites a cluster representative as [name](url) and tags the cluster ID (C#). 3. Bottom line: one sentence summarizing the overall attractiveness of the market position for the subject.
Output skeleton
**Threat of new entrants** — Verdict: Low | Moderate | High
- [name](url) - signal summary. (C#)
**Bargaining power of suppliers** — Verdict: ...
**Bargaining power of buyers** — Verdict: ...
**Threat of substitutes** — Verdict: ...
**Industry rivalry** — Verdict: ...
**Strategic implication**: 1-2 sentences. Where does the subject have
the most room to defend or grow?Traps
- Don't conflate "many competitors mentioned" with "high rivalry" — the
signal needs to be active competitive behavior (price, feature, switch).
- Substitute threat ≠ rivalry: Substitutes are OUT of category
(Notion vs Markdown files), rivalry is IN category (Notion vs Coda).
- One cluster can speak to multiple forces; pick the dominant force or
cite it twice with different framings.
SWOT — Analyst Prompt
You are filling the ## SWOT section of an oma market brief that was rendered as a skeleton. The Cluster Bank above the SWOT section lists every cluster the pipeline produced. Do not invent quotes or sources — only cite clusters present in the bank.
Classification rules
- Strength: cluster signals a positive, defensible quality OF the
subject (cost advantage, scale, brand trust, technical moat). Quote must be a positive sentiment about the subject itself, not the category.
- Weakness: cluster signals friction or shortcoming WITHIN the
subject's offering. Performance bug, UX gap, pricing complaint, churn triggered by the subject's own decision. External pressure → Threat, not Weakness.
- Opportunity: cluster signals an unmet need OR market shift the
subject could capture. Adjacent demand, regulatory window opening, technology adoption curve.
- Threat: cluster signals an external force the subject must
respond to. Competitor launch, regulatory crackdown, customer migration intent, sentiment swing against the subject.
Same cluster can map to multiple quadrants ONLY if its representatives clearly speak to different forces. Default: pick one quadrant per cluster.
Output rules
1. Each quadrant: 1-4 bullets. Empty → write _(no signal)_. 2. Every bullet cites a cluster representative as [name](url). Pull the URL straight from the Cluster Bank — never invent one. 3. Append a one-line cluster-ID tag at the end of each bullet, e.g. (C3), so readers can trace back to the bank. 4. Quadrant order: Strengths → Weaknesses → Opportunities → Threats. 5. Korean briefs: bullet text in Korean; structural labels and citation format stay as written here.
Output skeleton (replace _(fill from cluster bank)_ lines)
**Strengths**
- [author or title](url) - 1-sentence summary of the signal. (C#)
**Weaknesses**
- [author or title](url) - 1-sentence summary. (C#)
**Opportunities**
- [author or title](url) - 1-sentence summary. (C#)
**Threats**
- [author or title](url) - 1-sentence summary. (C#)Traps to avoid
- Don't map cluster to Strength just because the source domain is
vendor-owned (e.g. cafe24.com) — read the actual sentiment.
- Don't map a competitor's product launch news as the subject's
Opportunity. That's a Threat (or out of scope).
- Don't merge two unrelated clusters into one bullet to fill a quadrant.
Empty quadrants are honest signal.
Intent classification rules for oma-market — maps keyword patterns to one of 4 intents.
Precedence
1. Explicit flag --intent <pain|trend|competitor|discovery> always wins. 2. --vs <entity> present → intent = competitor (unless --intent overrides). 3. "vs " or " vs " substring in topic string → intent = competitor. 4. Keyword scan (table below) → highest-scoring intent wins. 5. Fallback chain: complaint keyword detected → pain; else → trend.
Keyword Pattern Table
| Intent | English keywords |
|---|---|
| pain | broken, bug, crash, slow, freeze, lag, outage, migrate, migrating, ditched, quit, ditch, alternative, replacing, painful, frustrating, hate, worst, unusable, deprecated |
| trend | trend, trending, growth, adoption, rising, popular, 2024, 2025, 2026, new, emerging, hot, forecast, survey, report, state of |
| competitor | vs, versus, alternative, replaced, switched, migrating from, comparison, compare, benchmark, better than, worse than, switch from |
| discovery | wish, need, missing, underrated, underserved, I want, if only, why doesn't, gap, overlooked, nobody, nobody builds, unmet |
Korean and other-locale intent routing is handled by the LLM reading the user's prompt directly; per-intent Korean keyword detection lives in the LLM's classification step, not in this file. Skill activation tokens for ko / ja / zh are owned by .agents/hooks/core/triggers.json §oma-market.keywords.
Scoring Rules
- Each matched keyword adds 1 point to its intent bucket.
- Tie-break order:
competitor > pain > discovery > trend. - If zero keywords match, apply fallback chain (rule 5 above).
Flag Override Examples
Intent is forced by passing --intent to score and render (there is no single research wrapper command — the pipeline is harvest | score | fuse | cluster | render).
# Force pain intent regardless of topic wording
oma market harvest "slack notifications (broken OR slow OR painful)" --operator-pack pain \
| oma market score --intent pain \
| oma market fuse | oma market cluster \
| oma market render --intent pain --topic "Slack notifications"
# Force competitor intent; --vs entity triggers fan-out harvest
oma market harvest "project management tools" --operator-pack competitor --vs Notion \
| oma market score --intent competitor \
| oma market fuse | oma market cluster \
| oma market render --intent competitor --vs Notion --topic "project management tools"
# Discovery scan without query operators
oma market harvest "async comms" --operator-pack none \
| oma market score --intent discovery \
| oma market fuse | oma market cluster \
| oma market render --intent discovery --topic "async comms"Notes
- Keyword matching is case-insensitive.
- Stemming is NOT applied; add both
migrateandmigratingexplicitly. - Domain-specific overrides via
oma-config.yaml(market_research.intent_overrides) are planned but not yet implemented; use the explicit--intentflag instead. - Discovery and competitor intents are NOT triggered by keyword scan alone when confidence < 2 points; require explicit flag or
--vsin that case.
Operator pack used when intent=competitor or --vs is set — surfaces switching signals and direct comparisons.
English OR Clause
(vs OR versus OR alternative OR replaced OR switched OR "migrating from" OR comparison OR compare OR benchmark OR "better than" OR "worse than" OR "switch from" OR "moved to")Korean OR Clause
(대안 OR 대체 OR 비교 OR 옮겨 OR 갈아탔 OR 전환 OR 스위치 OR 비교군 OR 옮겼다 OR 넘어갔다)Fan-Out Behavior with --vs
When --vs <entity> is provided, the harvest stage runs a separate query per entity:
# Single --vs:
"<topic> <entity> (vs OR alternative OR switched ...)"
# Multiple --vs entities (fan-out):
"<topic> NotionAI (vs OR alternative ...)"
"<topic> Asana (vs OR alternative ...)"
"<topic> Linear (vs OR alternative ...)"Each fan-out result is scored and clustered independently, then merged by the fuse stage into a side-by-side comparison structure.
Notes
- Without
--vs, the competitor pack returns generic switching signals — useful for discovering undocumented competitors. - With
--vs, the pack targets directed comparison — useful for positioning and win/loss analysis. - Noise reduction:
-is:retweetrecommended for X sources to avoid amplified hot-takes.
Operator pack used when intent=discovery — surfaces unmet needs, underserved gaps, and wish-list signals.
English OR Clause
(wish OR need OR missing OR underrated OR underserved OR "I want" OR "if only" OR "why doesn't" OR gap OR overlooked OR "nobody builds" OR unmet OR "would love" OR "please add" OR "feature request")Korean OR Clause
(있었으면 OR 필요하다 OR 아쉽다 OR "왜 없지" OR 부족하다 OR 못 찾겠다 OR 니즈 OR 발굴 OR "추가해줘" OR "기능 요청")Usage
Discovery intent is only triggered by explicit --intent discovery flag or by keyword scan with confidence >= 2. It is NOT triggered by fallback chain.
# Example invocation:
oma market research "async standup tools" --intent discoveryNotes
- Discovery signals are forward-looking; they reveal what users want to exist, not what exists and fails.
- Combine with trend pack results to distinguish "gap that nobody has filled" from "gap that is being filled but is unknown."
- Signal quality is lower than pain signals — apply higher
--min-trustthreshold in score stage. - Sources: Reddit (r/entrepreneur, r/startups, r/productivity) and HN "Ask HN" threads are highest-yield for discovery signals.
Operator pack injected as query preamble when intent=pain — surfaces complaints, churn signals, and migration stories.
English OR Clause
(broken OR bug OR crash OR slow OR freeze OR migrate OR migrating OR ditched OR quit OR ditch OR alternative OR replacing OR painful OR frustrating OR hate OR worst)Korean OR Clause
(불편 OR 버그 OR 느림 OR 이탈 OR 떠났다 OR 마이그레이션 OR 짜증 OR 답답 OR 최악 OR 문제)Noise Reduction Suffixes
For X (Twitter) and X-like sources, append:
-is:retweet -is:replyThis suppresses retweet amplification and reduces thread noise that lacks primary signal.
Rationale
Pain signals are the highest-value leading indicator for product-market gaps. Users venting about a tool are actively seeking alternatives and are primed for acquisition. The OR clause is deliberately broad to capture the linguistic diversity of complaint expression — from technical terms (crash, bug) to emotional language (frustrating, hate) to intent-revealing actions (ditched, migrating). The Korean clause mirrors the same emotional spectrum for KR-market harvests. Noise reduction strips amplified content so the fuse stage operates on original-expression signals only.
Usage
This pack is auto-injected when --intent pain is resolved. It can be suppressed with --no-operators.
# Query built by execution-protocol Step 3:
"<topic> (broken OR bug OR crash ... )"Optional positive operator pack — used as a sentiment comparator against pain signals.
English OR Clause
(love OR shipped OR fast OR clean OR delight OR sticky OR best OR favorite OR smooth OR reliable OR recommend)Korean OR Clause
(좋다 OR 사랑 OR 빠르다 OR 깔끔 OR 최고 OR 추천 OR 만족 OR 편하다 OR 훌륭)Usage
This pack is NOT injected by default. It is used when the research brief requests a pain/positive contrast — e.g., to compute a sentiment ratio or to populate the "What's working" column of a comparison table.
# Manual invocation example:
oma market harvest "<topic>" --operator-pack positiveIt can also be composed with the pain pack via a dual-run:
# Run 1 (pain signals):
oma market harvest "<topic>" --operator-pack pain
# Run 2 (positive signals, same topic):
oma market harvest "<topic>" --operator-pack positiveThe fuse stage merges both result sets and tags each cluster with a sentiment field (pain | positive | mixed).
Notes
- Positive signals alone are weak market research inputs; they reveal satisfaction but not opportunity.
- Use this pack primarily when the output format requires a contrast section or NPS-style polarity breakdown.
Output LAWs — oma-market
These 8 LAWs govern every brief emitted by oma market render. The render CLI runs a self-check before writing the result file; violations are auto-corrected when possible, otherwise rendering fails. Use --no-self-check only for debug.
LAWs apply to every QUERY_TYPE except where an explicit COMPARISON exception is noted.
---
LAW 1 — No Sources: block at the end
Do not append Sources:, References:, Further reading:, Citations:, or any bulleted list of publication names / handles / URLs after the closing line of the brief. The engine footer is the only visible citation list. The saved raw JSON sidecar (when emitted) is the durable record.
Self-check: scan the last 15 lines for ^Sources:, ^References:, ^Further reading:, ^Citations: (case-insensitive) followed by a bulleted list. If found, strip.
---
LAW 2 — No invented title line
For QUERY_TYPE pain, trend, discovery: the first line of the body (after the badge and one blank line) is the prose label What we learned: on its own line. Not a title, not a header, not # {Topic}.
For QUERY_TYPE competitor (or --vs <entity> set): use the COMPARISON template — first line is # {A} vs {B}: 시장 신호 (Korean) or # {A} vs {B}: Market Signal (English).
Self-check: detect intent from output frontmatter or topic. If pain/trend/discovery and first body line != What we learned:, regenerate or insert.
---
LAW 3 — No em-dash or en-dash
Use - (hyphen with spaces) instead of — or –. Em-dashes are the strongest AI-output tell. Apply to body, citation lines, footer.
Exception: quoted source text that literally used an em-dash. In quoted snippets, preserve the source character.
Self-check: regex /[–—]/ outside of > blockquotes. Replace — with - , — with -.
---
LAW 4 — No ## headers in body
For QUERY_TYPE pain/trend/discovery, body has no ## or ### headers. Structure: bold-lead-in paragraphs + a prose label KEY PATTERNS from the research: + numbered list. Framework sections (## SWOT, ## Porter's 5 Forces, ## PESTEL) are allowed when frameworks are active.
For QUERY_TYPE competitor (COMPARISON): the following ## headers are required and allowed:
## Quick Verdict## {Entity A},## {Entity B}(one per compared entity)## Head-to-Head## The Bottom Line
Any other ## is forbidden.
Self-check: scan body lines for ^#{2,3}\s. If header text is not in the allowed framework or COMPARISON list, flag for regenerate.
---
LAW 5 — Engine footer pass-through
The render command emits a footer block bounded by <!-- ENGINE FOOTER --> and <!-- END ENGINE FOOTER -->. It contains: sources_used, sources_failed coverage, cluster count, item count, p50/p95 latency, cache hit/miss, total cost (if paid sources used).
Render must emit this verbatim. Do not paraphrase, recompute, reorder, or replace with a synthesized ## Notable Stats.
---
LAW 6 — No raw evidence dump
Cluster JSON details (### N. (score, items, sources: ...), - Uncertainty: single-source, - Uncertainty: thin-evidence) are internal to the cluster output JSON. Markdown body must paraphrase as bold-lead-in paragraphs. If the body contains the literal pattern ### \d+. .* \(score \d+,, that's a LAW 6 violation.
Self-check: pattern /^### \d+\. .* \(score /m — fail.
---
LAW 7 — Inline citations as [name](url)
Every cited handle, subreddit, channel, publication is wrapped [name](url) at first mention in the body. Examples:
per [@octocat](https://github.com/octocat)[r/programming](https://reddit.com/r/programming) shipped a thread saying ...- NOT:
per https://github.com/octocat/repo(raw URL) - NOT:
per @octocat(plain handle when URL available) - NOT:
per [Rolling Stone]()(broken empty link)
Plain-text fallback: when the raw data genuinely has no URL, use plain text — never emit broken empty link.
Self-check: find bare URLs (/https?:\/\/[^\s)]+/ outside backticks and outside [..](..)) — flag for rewrite. Empty \[[^\]]+\]\(\s*\) — flag.
---
LAW 8 — Badge first line
Line 1 of every brief is the badge, exactly:
🔎 oma-market v{ver} · synced {YYYY-MM-DD}Where {ver} is the cli package version (read from cli/package.json version) and {YYYY-MM-DD} is today's UTC date. Line 2 is blank. Line 3 begins the body.
Self-check: line 1 must match /^🔎 oma-market v[\d.]+ · synced \d{4}-\d{2}-\d{2}$/. If missing, prepend.
---
Self-check execution order (render.ts)
1. LAW 8 (badge) — prepend if missing. 2. LAW 2 (body opener) — verify body starts with What we learned: (or COMPARISON title). 3. LAW 4 (forbidden headers) — flag/regenerate. 4. LAW 6 (evidence dump) — flag/regenerate. 5. LAW 7 (citation format) — rewrite raw URLs and empty links. 6. LAW 3 (em-dash) — replace. 7. LAW 1 (trailing Sources block) — strip. 8. LAW 5 (footer) — verify present.
LAWs 8, 7, 3, 1 auto-correct. LAWs 2, 4, 6, 5 are violations that fail render with exit 1 (use --no-self-check to bypass for debug).
---
Why these LAWs exist
Adapted from last30days-skill v3.0.x regression history: every LAW corresponds to a documented failure mode where a reasoning model produced output that the user later flagged as "AI slop", invented titles, missing citations, or wrong structure. The self-check enforces them at machine speed, not at user-review speed.