/opportunity-scan — detect skill + workflow opportunities from usage telemetry
Scan local telemetry (skill-usage.jsonl + recall.db) to surface 1-3 weekly opportunities for new skills, deprecations, or workflow formalizations. Internal self-improvement loop — paired with /steal (external pattern discovery) and /system-health (infrastructure health).
Where it runs — local launchd cron, NOT a cloud Routine
Critical architecture note: this skill consumes .claude/sessions/skill-usage.jsonl and .claude/sessions/recall.db, both of which are gitignored (they're per-session telemetry, not committed). A cloud Routine running from a fresh git checkout would have no access to them.
Solution: a local launchd cron job at .claude/automation/opportunity-scan/com.genesys.opportunity-scan.plist fires the scan every Sunday at 17:00 JST. The job invokes claude --print against the project so the full local toolchain is available (Slack MCP for the DM, local file access for telemetry).
Install:
cp.claude/automation/opportunity-scan/com.genesys.opportunity-scan.plist \
~/Library/LaunchAgents/com.genesys.opportunity-scan.plist
launchctl unload ~/Library/LaunchAgents/com.genesys.opportunity-scan.plist 2>/dev/null
launchctl load ~/Library/LaunchAgents/com.genesys.opportunity-scan.plist
Verify:
launchctl list | grep opportunity-scan
tail -f /tmp/genesys-opportunity-scan.log
If your machine is asleep at 17:00 Sunday, launchd fires the job on next wake — never strictly missed, just delayed.
Sibling skills + boundary: see .claude/rules/orchestration-patterns.md and the plan file ~/.claude/plans/<plan>.md §10.
Detection signal classes
Class 1 — Repeated manual sequences not covered by existing skills
Same multi-tool sequence appears in 3+ sessions where no Skill followed.
Detection:
- Query
recall.dbfor tool sequences of length 4+ - Cross-reference
skill-usage.jsonlto confirm no skill invocation accompanied the sequence - Cluster by shape (sequence of tool names ignoring args)
- Surface clusters with frequency ≥3
CYCLE-1 LEARNING: Ignore Bash×4 4-grams as noise (1192 occurrences in 7 days — Bash is the universal escape hatch). Look for mixed-tool 4-grams (e.g., Read → Grep → Bash → Edit).
Class 2 — Skills used <2× in 30 days (deprecation candidates)
Catalog skills rarely invoked. Surfaced for KILL verdict.
Detection:
- Glob
.claude/skills/**/SKILL.mdfor the catalog - Per skill: count invocations in
skill-usage.jsonlover last 30 days, summing bothsource: "skill-tool"(PostToolUse Skill capture) ANDsource: "slash"(UserPromptSubmit capture) - Surface skills with count < 2 AND catalog age > 30 days (grace period — never flag a skill less than 30 days old)
CYCLE-1 LEARNING: Depends on skill-usage.jsonl capturing slash-command invocations. This was DARK in cycle 1 (only Skill tool captured). Now resolved via .claude/hooks/slash-command-logger.sh (UserPromptSubmit). Source field disambiguates the two capture paths.
Class 3 — High-friction sessions (long single-tool chains)
Sessions with consecutive same-tool invocations indicating a friction point that could be a skill.
Detection:
- Query
recall.dbfor sessions where the same tool fires N+ consecutive times - Threshold: 8 for MCP tools (credit-gated, slower), 15 for built-in tools (cheaper, faster)
- Flag the session ID + tool name + chain length
CYCLE-1 LEARNING: The Apify cycle-1 finding (29× mcp__apify__fetch-actor-details in one session) was a class-3 hit at threshold 8. The 29× was visible because the chain happened in a single session — no need for cross-session detection.
Process — 7 phases
Phase 1 — Load
Read in parallel:
.claude/automation/opportunity-scan/state.json—last_run_at, runs counter, window config.claude/automation/opportunity-scan/opportunity-scan-strategy.md— scratchpad (decisions locked / things to stop doing / definitions refined).claude/sessions/skill-usage.jsonl— filter to last 7 days (per--window 7ddefault).claude/sessions/recall.db— query sessions in same windowOPPORTUNITIES.md— read existing entries to dedupe (match on suggested skill name)
Phase 2 — Detect (3 classes in parallel)
Run the three signal-class detectors against the loaded telemetry. Each emits raw candidates with evidence.
Phase 3 — Rank
Per candidate, score on 3 dimensions (each 0-3):
- Frequency — how often does this pattern fire? (3 = ≥5 occurrences/week, 2 = 3-4, 1 = 2, 0 = 1)
- Friction — how costly is the unautomated path? (3 = credit-gated MCP or 15+ tool calls, 2 = noticeable but cheap, 1 = trivial, 0 = no friction)
- Coverage gap — is there NO existing skill that covers it? (3 = no skill, 2 = adjacent skill exists but doesn't fit, 1 = partial coverage, 0 = covered)
Total = /9. Surface candidates with ≥5/9. Cap at 3 per run.
Phase 4 — Spec
For each surfaced candidate, generate:
Name:kebab-case (/<name>)Purpose:one linePrimitive / sub_primitive:best guess (see.claude/rules/ontology.md)Build estimate:S (<1 day), M (1-3 days), L (4+ days)
Phase 5 — Write
Append entries to repo-root OPPORTUNITIES.md matching the template at lines 12-24. Skip duplicates — if an entry with the same suggested skill name already exists, don't re-surface (the entry might be DEFERred or KILLed by Matteo).
Phase 6 — Scratchpad
Update .claude/automation/opportunity-scan/opportunity-scan-strategy.md per the .claude/rules/iterative-strategy-scratchpad.md discipline:
- New "decisions locked" entries (only if Matteo's feedback this cycle warrants)
- New "things to stop doing" entries (suppress patterns that surfaced wrong)
- "Open questions for next cycle" — what to investigate next time
- Cycle-N findings — counts + key observations from this run
Phase 7 — State writeback
Update .claude/automation/opportunity-scan/state.json:
last_run_at— ISO timestampruns+= 1candidates_surfaced_this_run— count this cyclecandidates_surfaced_lifetime+= count this cycleconsecutive_zero_correction_runs— for approval-loop discipline (see Constraints)
Subcommands
/opportunity-scan (default)
Run the full 7-phase scan against the default 7-day window. The cloud Routine fires this on Sunday cron.
/opportunity-scan review
Inline-review mode. Read OPPORTUNITIES.md, find pending entries (those with Verdict (Matteo to set): placeholder unchanged), and walk Matteo through verdicting one by one:
Candidate N/M — /<name> (<signal-class>, <score>/9)
Pattern: <one-line>
Suggested build: <S/M/L>. Primitive: <primitive>/<sub_primitive>.
Evidence: session <id> + N others.
Verdict? [B]uild [D]efer [K]ill [S]kip
On B / D / K: edit OPPORTUNITIES.md in place — replace Verdict (Matteo to set): BUILD / DEFER / KILL with Verdict: BUILD (or DEFER/KILL). Optionally prompt for one-line rationale.
On S: leave the entry untouched (still pending for next cycle).
Precedent: /discover status, /discover dismiss <name>, /discover built <name> <path> — same subcommand pattern.
Constraints
- Read-only on telemetry. Never modify
skill-usage.jsonlorrecall.db. - Cap 3 candidates per run. Past 3, save the rest for next cycle (better signal-to-noise per OPPORTUNITIES.md review).
- Respect the scratchpad's locked decisions. Per
.claude/rules/iterative-strategy-scratchpad.md: cycle 2+ reads "things to stop doing" first and suppresses matching patterns. - Approval-loop discipline. Per
.claude/rules/approval-loop-pattern.md: if 3 consecutive weeks pass with zero BUILD verdicts (all DEFER or KILL), the routine flags itself in the scratchpad and recommends retuning detection thresholds. - Never use the word "substrate" — per
.claude/rules/auto-memory.md.
Integration with other skills
- Producers:
.claude/hooks/skill-usage-logger.sh(PostToolUse + Skill captures),.claude/hooks/slash-command-logger.sh(UserPromptSubmit captures),.claude/hooks/session-indexer.py(writes recall.db). - Consumers: Matteo (Sunday review),
/weekly-plan(reads OPPORTUNITIES.md alongside Linear when building the week). - Sibling routines:
/steal weekly(external pattern discovery — different signal source),/system-health(infrastructure health — different signal source). Boundary documented in the plan §10.
Anti-patterns
- ❌ Don't run /opportunity-scan as the user's first action of the week to "see what came up" — it's a scheduled cloud Routine; opening OPPORTUNITIES.md is faster.
- ❌ Don't auto-build BUILD-verdicted candidates. Build them deliberately in a separate session via
/skill-creator. - ❌ Don't surface candidates from the with-space project dir if those sessions are stale (the cleanup of 2026-05-18 emptied that dir).
- ❌ Don't conflate "skill not invoked" with "skill not useful" — newly-built skills need a 30-day grace before deprecation flag fires.