Route Researcher
Research mountain peaks across North America and generate comprehensive route beta reports combining data from multiple sources including PeakBagger, SummitPost, WTA, AllTrails, weather forecasts, avalanche conditions, and trip reports.
Data Sources: This skill aggregates information from specialized mountaineering websites (PeakBagger, SummitPost, Washington Trails Association, AllTrails, The Mountaineers, and regional avalanche centers). The quality of the generated report depends on the availability of information on these sources. If your target peak lacks coverage on these websites, the report may contain limited details. The skill works best for well-documented peaks in North America.
When to Use This Skill
Use this skill when the user requests:
- Research on a specific mountain peak
- Route beta or climbing information
- Trip planning information for peaks
- Current conditions for mountaineering objectives
Examples:
- "Research Mt Baker"
- "I'm planning to climb Sahale Peak next month, can you research the route?"
- "Generate route beta for Forbidden Peak"
Progress Checklist
Research Progress:
Orchestration Workflow
Phase 1: Peak Identification
Goal: Identify and validate the specific peak to research.
Extract Peak Name from user message
- Look for peak names, mountain names, or climbing objectives
- Common patterns: "Mt Baker", "Mount Rainier", "Sahale Peak", etc.
Search PeakBagger using peakbagger-cli:
uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak search "{peak_name}" --format json
- Parse JSON output to extract peak matches
- Each result includes: peak_id, name, elevation (feet/meters), location, url
Handle Multiple Matches:
Extract Peak ID:
- From search results JSON, extract the
peak_id field
- Store for use in subsequent peakbagger-cli commands
- Also store the PeakBagger URL for reference links
Phase 2: Peak Information Retrieval
Goal: Get detailed peak information and coordinates needed for location-based data gathering.
This phase must complete before Phase 3, as coordinates are required for weather, daylight, and avalanche data.
Retrieve detailed peak information using the peak ID from Phase 1:
uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak show {peak_id} --format json
This returns structured JSON with:
- Peak name and alternate names
- Elevation (feet and meters)
- Prominence (feet and meters)
- Isolation (miles and kilometers)
- Coordinates (latitude, longitude in decimal degrees)
- Location (county, state, country)
- Routes (if available): trailhead, distance, vertical gain
- Peak list memberships and rankings
- Standard route description (if available in routes data)
Error Handling:
- If peakbagger-cli fails: Fall back to WebSearch/WebFetch and note in "Information Gaps"
- If specific fields missing in JSON: Mark as "Not available" in gaps section
- Rate limiting: Built into peakbagger-cli (default 2 second delay)
Once coordinates are obtained from this step, immediately proceed to Phase 3.
Phase 3: Data Gathering
Goal: Gather comprehensive route information from all available sources.
Execution Strategy: Run Python script for deterministic API data + dispatch specialized agents in parallel for web research. This hybrid approach minimizes token usage while maximizing parallelism.
Step 3A: Fetch Conditions Data (Python Script)
Run the conditions fetcher script to gather all API-based data:
cd "{repo_root}/skills/route-researcher/tools"
uv run python fetch_conditions.py \
--coordinates "{latitude},{longitude}" \
--elevation {elevation_m} \
--peak-name "{peak_name}" \
--peak-id {peak_id} \
--trailhead "{trailhead_lat},{trailhead_lon}" \
--distance-mi {round_trip_distance_mi} \
--gain-ft {total_gain_ft} \
--start-time "{HH:MM}" \
--waypoint "{lat1},{lon1}" --waypoint "{lat2},{lon2}"
Optional args: --trailhead enables multi-county path sampling (trailhead→summit); hospital/ranger lookups always run from the summit regardless; --distance-mi/--gain-ft enable time_estimates; --start-time (with distance + gain) enables itinerary; --waypoint (2+) enables bearings.
This returns JSON with:
- weather: 7-day forecast with temperatures, precipitation, freezing levels; each day includes
snow_line_note (human-readable framing of freezing level as snow line) and near_summit (bool: true when freezing level within 2000 ft of summit)
- air_quality: AQI ratings and any concerns
- daylight: Full twilight table —
astronomical_dawn, nautical_dawn, civil_twilight (dawn), sunrise, sunset, civil_dusk, nautical_dusk, astronomical_dusk; values are null at high latitudes when sun doesn't reach threshold (white nights); daylight_hours, timezone
- time_estimates: Roped/unroped + 3-tier pacing (
roped_hr, unroped_hr, fast_hr, moderate_hr, leisurely_hr) — only present when --distance-mi and --gain-ft CLI args are provided
- itinerary: Trip schedule with safety signals (
start_time, summit_eta, turnaround_by, return_eta, total_hr, after_dark bool, dusk_cutoff, note) — only present when --start-time, --distance-mi, AND --gain-ft are all provided; after_dark: true is a safety warning that must be prominently surfaced; total_hr is the full round-trip duration in hours
- bearings: Navigation bearings between waypoints (
segments[] with bearing_deg, distance_mi, cumulative_distance_mi; total_distance_mi) — only present when 2 or more --waypoint "lat,lon" args are provided
- avalanche: NWAC region and URL for manual check
- peakbagger: Ascent statistics and recent ascents (if peak_id provided)
- counties: Counties traversed trailhead→summit (
counties[] with county_name, county_fips, state_name, state_code); sampled bool and sample_points int indicate whether path sampling ran (requires --trailhead); without --trailhead only the summit county is returned
- nearest_hospital: Nearest hospitals/ERs (
hospitals[] with name, lat, lon, distance_miles, emergency, and phone/website/address when OSM has them); sorted emergency-first then by distance; max 3
- ranger_station: Nearest ranger stations (
stations[] with name, lat, lon, distance_miles, and phone/website/address when present) + optional admin_district (district_name, forest_name, region) when the summit coordinates intersect a USFS ranger district
- campgrounds: Established campgrounds within ~12 mi (20 km) (
campgrounds[] with name, lat, lon, distance_miles, camp_type, backcountry, operator, and website when present); backcountry/high camps are NOT included — extract those from trip reports
- gaps: Any API failures noted for report
Run this in parallel with Step 3B — include both the Bash command for fetch_conditions.py and all 3 Task calls in the same response turn to maximize parallelism.
Step 3B: Dispatch Researcher Agents (Parallel)
Dispatch 3 Researcher agents in a single message (all Task calls together). Each agent researches assigned sources and fetches trip report content directly.
Agent 1: PeakBagger + SummitPost
Task(
subagent_type="general-purpose",
model="sonnet",
prompt="""You are a route researcher gathering mountaineering data for {peak_name}.
## Your Assignment
Research from these sources: PeakBagger, SummitPost
**Discover first (web sources):** for SummitPost, run a `site:summitpost.org` WebSearch to get exact URLs, then fetch those (don't WebFetch guessed paths).
## PeakBagger Research
1. Search: "{peak_name} site:peakbagger.com"
2. Extract route descriptions from peak page
3. List recent ascents with trip reports:
```bash
uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak ascents {peak_id} --format json --with-tr --limit 20
Identify trip reports with content (word_count > 0)
Fetch content for up to 5 recent trip reports using:
uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger ascent show {ascent_id} --format json
SummitPost Research
Search: "{peak_name} site:summitpost.org"
Use WebFetch to extract: route name, difficulty, approach, description, hazards
If WebFetch fails, use the fetching ladder:
# Fast path (httpx with browser-like headers, no browser)
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{url}"
# If the above returns {"error": ...} or content is blocked/JS-rendered:
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{url}"
# If --render still returns a Cloudflare challenge page, escalate (needs a display):
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{url}"
Trip Report Extraction
For each report fetched, extract:
- date, author, route conditions, gear mentioned
- Hazards (extract explicitly and separately):
- Rockfall zones: location on route, conditions, timing guidance mentioned
- Icefall/serac hazard: location, stability, pre-dawn/timing advice
- Cornice hazard: location, buildup direction, avoidance notes
- Terrain detail (extract if mentioned):
- Downclimb sections: location, difficulty, rappel anchors if any
- River/stream crossings: location, flow conditions, ford difficulty
- Water sources: named locations, seasonal availability
- Named camps or bivy sites: name/location, exposure notes
Output Format (return EXACTLY this JSON)
{
"sources": ["PeakBagger", "SummitPost"],
"route_info": [
{"source": "...", "name": "...", "difficulty": "...", "description": "...", "hazards": [...]}
],
"trip_reports": [
{"source": "...", "date": "...", "author": "...", "url": "...", "summary": "...", "conditions": "...", "has_gpx": false,
"rockfall": "...", "icefall": "...", "cornices": "...",
"downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}
],
"gaps": ["what couldn't be fetched and why"]
}
```"""
)
Agent 2: WTA + Mountaineers + Regional Sources
Task(
subagent_type="general-purpose",
model="sonnet",
prompt="""You are a route researcher gathering mountaineering data for {peak_name}.
## Your Assignment
Research from these sources: WTA, Mountaineers.org, northwesthikers.net, hikeoftheweek.com, Oregon Hikers Field Guide (oregonhikers.org), Cascade Climbers (cascadeclimbers.com), Mountain Project
**Retrieval strategy — discover URLs, then fetch.** For each web source below (except mountaineers.org — use the Mountaineers MCP, see below), FIRST run a `site:` WebSearch (e.g. `"{peak_name} site:wta.org"`, `site:nwhikers.net`, `site:cascadeclimbers.com`) to collect the exact hike-page and individual trip-report URLs. THEN fetch each discovered URL through the fetching ladder. Do not WebFetch a guessed URL — enumerate real URLs first. This recovers reports that one-pass fetching loses to 403/JS blocks.
## WTA Research
1. Search: "{peak_name} site:wta.org"
2. Find the hike page and extract: trail name, difficulty, distance, elevation gain, hazards
3. Get trip reports from AJAX endpoint: {wta_url}/@@related_tripreport_listing
4. Fetch content for up to 5 recent trip reports using the fetching ladder:
```bash
# Fast path first
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{trip_report_url}"
# If output contains {"error": ...} or content is blocked/JS-rendered:
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{trip_report_url}"
# If --render still returns a Cloudflare challenge page, escalate (needs a display):
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{trip_report_url}"
Mountaineers Research (use the Mountaineers MCP FIRST — do not scrape)
mountaineers.org reliably returns HTTP 403 to WebFetch/cloudscrape. Use the Mountaineers MCP tools instead — they return structured data:
mcp__mountaineers__search_routes (query "{peak_name}") and mcp__mountaineers__get_route to get the route/place page (difficulty, directions, gear).
mcp__mountaineers__search_trip_reports (query "{peak_name}") and, when you have a route URL, mcp__mountaineers__get_route_trip_reports to enumerate member trip reports.
mcp__mountaineers__get_trip_report to pull each relevant report's body + structured fields (date, author, result, road/conditions notes).
- Only if the MCP is unavailable, document the gap — mountaineers.org reliably returns HTTP 403 to WebFetch/cloudscrape, so scraping is not a viable fallback for this domain.
Note: the Mountaineers MCP is available to Task-dispatched general-purpose agents (this agent). Extract route beta, technical requirements, and hazards from the MCP results.
NWHikers Research (northwesthikers.net / nwhikers.net)
- Search: "{peak_name} site:nwhikers.net OR site:northwesthikers.net"
- Use WebFetch to extract first-person trip reports, GPS track notes, conditions
- If WebFetch fails, use
cloudscrape.py "{url}" (fast path usually sufficient)
Hike of the Week (hikeoftheweek.com — REQUIRES --render)
Search: "{peak_name} site:hikeoftheweek.com"
MUST use --render flag — site is Cloudflare-protected and blocks WebFetch:
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{url}"
# If --render still returns a Cloudflare challenge page, escalate (needs a display):
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{url}"
Extract: logistics, route narrative, access notes, trailhead directions
Oregon Hikers Field Guide (oregonhikers.org — Oregon objectives only)
- Search: "{peak_name} site:oregonhikers.org"
- Use WebFetch — site is static MediaWiki HTML, WebFetch-friendly
- Extract: route description, access, permits, conditions notes
Cascade Climbers (cascadeclimbers.com)
- Search: "{peak_name} site:cascadeclimbers.com"
- Use WebFetch; if blocked use
cloudscrape.py "{url}"
- Extract: technical route beta, gear lists, trip reports, conditions
Mountain Project (for technical/rock sections)
- Search: "{peak_name} site:mountainproject.com"
- Use WebFetch to extract: route name, grade, gear, description, rock quality
- If WebFetch fails, use
cloudscrape.py "{url}"
Fallback
If WebFetch fails for any page, use the fetching ladder: cloudscrape.py "{url}" (fast) → cloudscrape.py --render "{url}" for JS-rendered or Cloudflare-protected pages.
Trip Report Extraction
For each report fetched, extract:
- date, author, route conditions, gear mentioned
- Hazards (extract explicitly and separately):
- Rockfall zones: location on route, conditions, timing guidance mentioned
- Icefall/serac hazard: location, stability, pre-dawn/timing advice
- Cornice hazard: location, buildup direction, avoidance notes
- Terrain detail (extract if mentioned):
- Downclimb sections: location, difficulty, rappel anchors if any
- River/stream crossings: location, flow conditions, ford difficulty
- Water sources: named locations, seasonal availability
- Named camps or bivy sites: name/location, exposure notes
Output Format (return EXACTLY this JSON)
{
"sources": ["WTA", "Mountaineers", "NWHikers", "HikeOfTheWeek", "OregonHikers", "CascadeClimbers", "MountainProject"],
"route_info": [
{"source": "...", "name": "...", "difficulty": "...", "description": "...", "hazards": [...]}
],
"trip_reports": [
{"source": "...", "date": "...", "author": "...", "url": "...", "summary": "...", "conditions": "...", "has_gpx": false,
"rockfall": "...", "icefall": "...", "cornices": "...",
"downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}
],
"gaps": ["what couldn't be fetched and why"]
}
```"""
)
Agent 3: AllTrails
Task(
subagent_type="general-purpose",
model="sonnet",
prompt="""You are a route researcher gathering mountaineering data for {peak_name}.
## Your Assignment
Research from AllTrails
## AllTrails Research
1. Search: "{peak_name} site:alltrails.com"
2. Use WebFetch to extract: trail name, difficulty, distance, elevation gain, route type, best season, hazards
3. If WebFetch fails, use:
```bash
uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{url}"
- From route description and any visible reviews/comments, extract if present:
- Rockfall zones, icefall/serac hazard, cornice hazard
- Downclimb sections, river/stream crossings, water sources, named camps
Output Format (return EXACTLY this JSON)
{
"sources": ["AllTrails"],
"route_info": [
{"source": "...", "name": "...", "difficulty": "...", "distance_miles": N, "elevation_gain_ft": N, "description": "...", "hazards": [...],
"rockfall": "...", "icefall": "...", "cornices": "...",
"downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}
],
"trip_reports": [],
"gaps": ["what couldn't be fetched and why"]
}
```"""
)
Execute all 3 agents in parallel by including all Task calls in a single response.
Step 3C: Aggregate Results
After Python script and all agents return, aggregate into unified data structure:
{
"conditions": { /* from fetch_conditions.py */ },
"route_data": {
"sources": [ /* merged from all 3 agents */ ],
"trip_reports": [ /* merged from all agents */ ]
},
"gaps": [ /* merged gaps from all sources */ ]
}
Partial Failure Handling:
- If any agent fails entirely, proceed with data from successful agents
- Note failed sources in the gaps array
- Minimum viable: conditions data + at least one route source
Step 3D: Access, Permits, and Road/Gate Status (Inline)
Determine permits AND the current road/gate status to the trailhead — do not just tell the user to go check. Actively research and report the actual status with a source and date.
Permits:
WebSearch: "{peak_name} trailhead access" ; "{peak_name} permit requirements"
Road / gate status workflow (identify the access highway + forest road + managing agency first, then check sources in order; capture each source URL for the report):
- State DOT pass report (WA → WSDOT): fetch the relevant pass page, e.g.
https://wsdot.com/travel/real-time/mountainpasses/mt.-baker (SR-542) or .../north-cascades (SR-20). Read RoadCondition / TravelAdvisoryActive / restrictions. Other states: WebSearch "{state} DOT mountain pass report {highway}".
- USFS forest alerts/conditions: fetch
https://www.fs.usda.gov/{region}/{forest-shortname}/alerts (e.g. r06/mbs/alerts) and /conditions; search the page for the road number / trailhead name → closure milepost, reason, seasonal gate. Also WebSearch "{forest name} {road or trailhead} road open {year}" for seasonal-opening press releases.
- NPS road conditions (if in/through a national park): fetch
https://www.nps.gov/{park-code}/planyourvisit/road-conditions.htm (e.g. noca, mora, olym) → per-road OPEN/CLOSED + milepost.
- WTA ground truth (PNW):
WebSearch "site:wta.org {trail} gate road open closed {year}" or fetch the hike page; scan the 3-5 most recent trip reports for "gate"/"road open/closed"/"drove to" (use cloudscrape.py --render if WTA 403s).
- InciWeb fire closures (Jul-Oct):
WebSearch "inciweb {area} closure {trailhead} {year}"; if an active incident is near the trailhead, read its closure page.
Synthesize into a dated status statement for the report's Road Conditions section:
"Gate/road status (as of {date}): {road} is {OPEN/CLOSED/SEASONAL GATE/UNKNOWN} per source. {If closed: gate at {milepost}, adds ~{N} mi each way.}"
If no source confirms it, say so explicitly and include the managing ranger station phone as the fallback. Add trailhead names, permits, the status statement, and all source URLs to route_data.
Phase 4: Route Analysis
Goal: Analyze gathered data to determine route characteristics and synthesize information.
Step 4A: Determine Route Type
Based on route descriptions, elevation, and gear mentions, classify as:
- Glacier: Crevasses mentioned, glacier travel, typically >8000ft
- Rock: Technical climbing, YDS ratings (5.x), protection mentioned
- Scramble: Class 2-4, exposed but non-technical
- Hike: Class 1-2, trail-based, minimal exposure
Step 4B: Synthesize Route Information from Multiple Sources
Goal: Combine trip reports and route descriptions from Step 3B researcher agents, plus conditions data from Step 3A, into comprehensive route beta.
Source Priority:
- Trip reports (Step 3B agents) - first-hand experiences
- Route descriptions (Step 3B agents) - published beta baseline
- PeakBagger/ascent data (Step 3A Python script) - basic info, patterns
Synthesis Pattern for Route, Crux, and Hazards:
- Start with baseline from route descriptions (standard route name, published difficulty)
- Enrich with trip report details (landmarks, specific conditions, actual experiences)
- Note conflicts when trip reports disagree with published info
- Highlight consensus ("Multiple reports mention...")
- Include specifics (elevations, locations, quotes)
- Link every specific-report attribution to its source. Whenever a detail is drawn from a particular trip/climb report (a date, a quote, "one party found…", "a recent report noted…"), the attribution MUST be a Markdown hyperlink to that report's URL — never plain text. Carry each trip report's
url (and date/author) through synthesis so it can be linked; use the date/author as the link text. For consensus phrasing ("multiple reports mention…"), link 2-3 of the contributing reports inline. Only general/published beta with no specific source stays unlinked.
Example (Route Description):
"The standard route follows the East Ridge (Class 3). A Sep 2025 party found a well-cairned use trail branching right at 4,800 ft—the correct turn—through talus they called 'tedious' and 'ankle-rolling'. An Oct 2025 report noted the section was snow-covered, requiring microspikes."
Apply this pattern to:
- Route: Use baseline structure, add landmarks/navigation from trip reports, include actual times
- Crux: Describe location/difficulty, add trip report assessments, note conditions-dependent variations
- Hazards: Extract ALL hazards from trip reports. Organize by type with explicit, SEPARATE sub-sections — do NOT bury rockfall or icefall under generic "exposure":
- Rockfall: tag location, trigger (other parties / freeze-thaw / sun hitting the face), timing mitigation (pre-dawn passage, move quickly through zone)
- Icefall/Serac: tag location, stability assessment, timing mitigation (avoid afternoon, pre-dawn passage)
- Cornice: tag location, avoidance line, conditions (buildup direction, season)
- Other hazards (crevasses, exposure, route-finding, seasonal) as separate bullets
- Be comprehensive — safety-critical; include specific locations and mitigation strategies
- Terrain detail: Surface the following in the report when found in trip reports/beta:
- Downclimbs: location, difficulty, whether rappel anchors exist
- River/stream crossings: location, seasonal flow, ford difficulty
- Water sources: named locations and per-day availability by season
- Named camps/bivy sites: name, location, exposure; note these come from trip reports, not the campground database
Extract Key Information:
From all synthesized data, identify:
- Difficulty Rating: YDS class, scramble grade, or general difficulty (validated by trip reports)
- Crux: Hardest/most technical section of route (synthesized above)
- Hazards: All identified hazards (synthesized above)
- Notable Gear: Any unusual or important gear mentioned in trip reports or beta (to be included in relevant sections, not as standalone section)
- Trailhead: Name and approximate location
- Distance/Gain: Round-trip distance and elevation gain (compare published vs actual trip report data)
- Time Estimates: Use
conditions.time_estimates (keys: fast_hr, moderate_hr, leisurely_hr, roped_hr, unroped_hr) from fetch_conditions.py output — present only when it was called with --distance-mi and --gain-ft. If time_estimates is absent from the conditions data, note it in Information Gaps. To populate it: re-invoke fetch_conditions.py with --distance-mi {distance} and --gain-ft {gain} once route distance/gain are known from Step 3B research. At the same time, add --start-time HH:MM to get itinerary and --waypoint args to get bearings — these optional outputs only activate when the args are supplied.
- Freezing Level Analysis: Compare peak elevation with forecasted freezing levels:
- Include Freezing Level Alert if: Any day in forecast has freezing level within 2000 ft of peak elevation
- Omit if: Freezing level stays >2000 ft above peak throughout forecast (typical summer conditions)
- Example: 5,469 ft peak with 5,000-8,000 ft freezing levels → Include alert (marginal conditions)
- Example: 4,000 ft peak with 10,000+ ft freezing levels → Omit alert (well above summit)
Step 4C: Surface Geodata in Report
Include these geodata fields when available. Every place named in the report must be a link — see the link patterns below.
Place / map link patterns (build from a place's lat/lon and name):
- Google Maps place (named entity):
https://www.google.com/maps/search/?api=1&query={URL-encoded name + address} — use this (not bare coordinates) for hospitals, ranger stations, campgrounds, and any named place, so the link resolves to the actual entity. When only coordinates are meaningful, query={lat},{lon}.
- Gaia GPS:
https://www.gaiagps.com/map/?loc=14/{lon}/{lat} (zoom/lon/lat).
- CalTopo:
https://caltopo.com/map.html#ll={lat},{lon}&z=14&b=mbt.
Surfacing rules:
- Counties: list
county_name + state_name from conditions.counties.counties[] in the Overview. Empty/error → Information Gaps.
- Emergency contacts: build the table from
conditions.nearest_hospital.hospitals[] and conditions.ranger_station (stations + admin_district). Link each name to its website if present, else a Google Maps place search by name + address. Always include phone AND address columns — each entry now carries phone/website/address/lat/lon when OSM has them; if phone or address is missing, make a best effort to find the entity's real phone/address (its official site or Google Maps listing) before writing "—". Missing/error → note in Information Gaps.
- Campgrounds: build the Camping table from
conditions.campgrounds.campgrounds[]; link the name (website or Google Maps place) and add Google Maps + Gaia map links from each entry's lat/lon.
- Any named location report-wide (campsite, bivy, high camp, named feature, trailhead): accompany with at least Google Maps + Gaia links, per the patterns above. For trip-report-named camps without coordinates, use a Google Maps place search by name and do your best to locate it; if it cannot be located, say so explicitly. Backcountry/high camps come from trip reports, not the campground DB.
Step 4D: Identify Information Gaps
Explicitly document what data was not found or unreliable:
- Missing trip reports
- No GPS tracks available
- Script failures (weather, avalanche, daylight)
- Conflicting information between sources
- Limited seasonal data
Phase 5: Report Generation
Goal: Create comprehensive Markdown document by dispatching Report Writer agent.
Step 5A: Prepare Data Package
Organize all gathered and analyzed data into structured JSON:
{
"peak": {
"name": "{peak_name}",
"id": {peak_id},
"elevation_ft": {elevation},
"coordinates": [{latitude}, {longitude}],
"location": "{location}",
"peakbagger_url": "{url}"
},
"conditions": {
// From fetch_conditions.py output
"weather": {"forecast": [{"date": "...", "snow_line_note": "...", "near_summit": bool, "freezing_level_ft": N, ...}], ...},
"air_quality": {...},
"daylight": {"astronomical_dawn": "...", "nautical_dawn": "...", "civil_twilight": "...", "sunrise": "...", "sunset": "...", "civil_dusk": "...", "nautical_dusk": "...", "astronomical_dusk": "...", "daylight_hours": N},
"avalanche": {...},
"peakbagger": {...},
"counties": {"counties": [{"county_name": "...", "county_fips": "...", "state_name": "...", "state_code": "..."}], "sampled": bool, "sample_points": N}, // sampled + sample_points only present when --trailhead was given
"nearest_hospital": {"hospitals": [{"name": "...", "lat": N, "lon": N, "distance_miles": N, "emergency": "yes|null", "phone": "...", "website": "...", "address": "..." /* phone/website/address optional */}]},
"ranger_station": {"stations": [{"name": "...", "lat": N, "lon": N, "distance_miles": N, "phone": "...", "website": "...", "address": "..." /* optional */}], "admin_district": {"district_name": "...", "forest_name": "...", "region": "..."}},
"campgrounds": {"campgrounds": [{"name": "...", "lat": N, "lon": N, "distance_miles": N, "camp_type": "...", "operator": "...", "website": "..." /* optional */}], "note": "..."},
"time_estimates": {"roped_hr": N, "unroped_hr": N, "fast_hr": N, "moderate_hr": N, "leisurely_hr": N, "note": "..."},
"itinerary": {"start_time": "HH:MM", "summit_eta": "HH:MM", "turnaround_by": "HH:MM", "return_eta": "HH:MM", "total_hr": N, "after_dark": false, "dusk_cutoff": "9:15 PM" /* 12-hr AM/PM format, unlike other time fields */, "note": "..."},
"bearings": {"segments": [{"from": 0, "to": 1, "bearing_deg": N, "distance_mi": N, "cumulative_distance_mi": N}], "total_distance_mi": N}
},
"route_data": {
// Merged from all Researcher agents
"sources": [...],
"trip_reports": [...]
},
"analysis": {
// From Phase 4
"route_type": "{hike|scramble|technical|glacier}",
"difficulty": "{rating}",
"crux": "{description}",
"hazards": [...],
"access": {...}
},
"gaps": [...]
}
Step 5B: Dispatch Report Writer Agent
Task(
subagent_type="general-purpose",
model="sonnet",
prompt="""You are a Report Writer generating a mountaineering route report.
## Instructions
1. **Read the report template:**
Use the Read tool to read: {repo_root}/skills/route-researcher/assets/report-template.md
2. **Generate report following template structure exactly:**
- Header with peak name, elevation, location, date
- AI disclaimer (prominent safety warning)
- Overview: route type, difficulty, distance/gain, time estimates
- Route Description: synthesized from sources, include landmarks
- Crux: describe hardest section with specifics
- Known Hazards: comprehensive list
- Current Conditions: weather forecast, freezing levels, air quality, daylight
- Trip Reports: links organized by source with dates
- Information Gaps: explicitly list missing data
- Data Sources: links to all sources used
3. **Markdown Formatting Rules:**
- ALWAYS add blank line before lists
- ALWAYS add blank line after section headers
- Use `-` for bullets (not `*` or `+`)
- Use `**text**` for bold emphasis
- Break paragraphs >4 sentences
- **Link specific-report attributions.** Any statement attributed to a particular trip/climb report (a date, a quote, "one party…", "a recent report…") MUST be a Markdown link `[date/author](report_url)` to that report's source URL — never plain-text attribution. Pull the URL from the matching `trip_reports[].url` in the data package. Leave only generic/published beta (no specific source) unlinked.
4. **Save the report:**
Use the Write tool to save to the user's current working directory: {date}-{peak-name-slug}.md
## Data Package
{data_package_json}
## Output Format (return EXACTLY this JSON)
```json
{
"status": "SUCCESS",
"file_path": "/absolute/path/to/report.md",
"filename": "YYYY-MM-DD-peak-name.md",
"sections_generated": N
}
```"""
)
Step 5C: Capture Report File Path
Extract file_path from agent's JSON response for use in Phase 6.
Phase 6: Report Review & Validation
Goal: Validate report quality by dispatching Report Reviewer agent.
Step 6A: Dispatch Report Reviewer Agent
Task(
subagent_type="general-purpose",
model="opus",
prompt="""You are a Report Reviewer validating a mountaineering route report.
## Instructions
1. **Read the report:**
Use the Read tool to read: {report_file_path}
2. **Perform systematic quality checks:**
**Factual Consistency:**
- Dates match their stated day-of-week (e.g., "Thu Nov 6, 2025" is actually Thursday)
- Coordinates, elevations, distances consistent across all mentions
- Weather forecasts align logically (freezing levels match precipitation types)
**Mathematical Accuracy:**
- Elevation gains add up correctly
- Time estimates reasonable given distance and elevation gain
- Unit conversions correct (feet to meters, etc.)
**Internal Logic:**
- Hazard warnings align with route descriptions
- Recommendations match current conditions
- Crux descriptions match overall difficulty rating
**Completeness:**
- No placeholder texts like {{peak_name}} or {{YYYY-MM-DD}}
- All referenced links actually provided
- Mandatory sections present: Overview, Route, Current Conditions, Trip Reports, Information Gaps, Data Sources
**Formatting:**
- Markdown headers properly structured
- Lists have blank lines before them
- Tables properly formatted
**Safety & Responsibility:**
- AI disclaimer present and prominent
- Critical hazards properly emphasized
- Users directed to verify information from primary sources
**Emergency contacts & location links (verify INDEPENDENTLY):**
- Each emergency contact (hospital, ranger station) has a working name link (website or a Google Maps place link to the actual entity — NOT bare coordinates), a phone, and an address. Independently confirm the phone/address look right for that named entity (e.g. via its official site / Google Maps); fix or flag mismatches and fill blanks you can confirm.
- Road/gate status is a dated statement with a cited source, not a "go check it yourself" punt.
- Every named place in the report (campsite, bivy, high camp, trailhead, named feature) carries map links (Google Maps + Gaia GPS). Flag any named location missing links.
- Specific trip-report attributions are hyperlinks to their source, not plain text.
3. **Fix issues:**
- **Critical** (safety errors, factual errors, missing disclaimers): MUST fix using Edit tool
- **Important** (completeness, consistency): SHOULD fix
- **Minor** (formatting, polish): FIX if quick
## Output Format (return EXACTLY this JSON)
```json
{
"status": "PASS" | "PASS_WITH_FIXES" | "FAIL",
"issues_found": N,
"fixes_applied": ["description of fix 1", "description of fix 2"],
"remaining_issues": ["issues that couldn't be fixed"],
"report_path": "/absolute/path/to/report.md"
}
```"""
)
Step 6B: Process Validation Results
Handle the reviewer agent's response:
- PASS or PASS_WITH_FIXES: Proceed to Phase 7 with the
report_path
- FAIL: Present
remaining_issues to user and ask for guidance
The Report Reviewer automatically fixes issues and returns the corrected file path.
Phase 7: Completion
Goal: Inform user of completion and next steps.
Report to user:
- Success message: "Route research complete for {Peak Name}"
- File location: Full absolute path to generated report
- Summary: Brief 2-3 sentence overview:
- Route type and difficulty
- Key hazards or considerations
- Any significant information gaps
- Next steps: Encourage user to:
- Review the report
- Verify critical information from primary sources
- Check current conditions before attempting route
- Itinerary and navigation: If the user wants a start-time schedule and/or compass bearings, re-run
fetch_conditions.py with --start-time HH:MM (adds itinerary key) and/or --waypoint lat,lon flags (2+ waypoints add bearings key). Surface after_dark: true as a prominent safety warning.
- Post-climb trip report: After the climb, offer the trip-report template at
skills/route-researcher/assets/trip-report-template.md as a starting point for filing a trip report.
Example completion message:
Route research complete for Mount Baker!
Report saved to: 2025-10-20-mount-baker.md
Summary: Mount Baker via Coleman-Deming route is a moderate glacier climb (Class 3) with significant crevasse hazards. The route involves 5,000+ ft elevation gain and typically requires an alpine start. Weather and avalanche forecasts are included.
Next steps: Review the report and verify current conditions before your climb. Remember that mountain conditions change rapidly - check recent trip reports and weather forecasts immediately before your trip.
Error Handling Principles
Throughout execution, follow these error handling
…(truncated)
1---2name: route-researcher3description: Research mountain routes and generate comprehensive route beta reports for North American peaks, aggregating weather forecasts, avalanche conditions, daylight windows, trip reports, and access info from PeakBagger, SummitPost, WTA, AllTrails, and regional avalanche centers. Use when planning a climb, hike, or scramble, or when asked for route beta, trail conditions, peak research, or mountaineering trip planning.4---56# Route Researcher78Research mountain peaks across North America and generate comprehensive route beta reports combining data from multiple sources including PeakBagger, SummitPost, WTA, AllTrails, weather forecasts, avalanche conditions, and trip reports.910**Data Sources:** This skill aggregates information from specialized mountaineering websites (PeakBagger, SummitPost, Washington Trails Association, AllTrails, The Mountaineers, and regional avalanche centers). The quality of the generated report depends on the availability of information on these sources. If your target peak lacks coverage on these websites, the report may contain limited details. The skill works best for well-documented peaks in North America.1112## When to Use This Skill1314Use this skill when the user requests:1516- Research on a specific mountain peak17- Route beta or climbing information18- Trip planning information for peaks19- Current conditions for mountaineering objectives2021Examples:2223- "Research Mt Baker"24- "I'm planning to climb Sahale Peak next month, can you research the route?"25- "Generate route beta for Forbidden Peak"2627## Progress Checklist2829Research Progress:3031- [ ] Phase 1: Peak Identification (peak validated, ID obtained)32- [ ] Phase 2: Peak Information Retrieval (coordinates and details obtained)33- [ ] Phase 3: Data Gathering (parallel execution)34 - [ ] Phase 3a: Python conditions fetch (weather, air quality, daylight, avalanche, peakbagger stats/ascents)35 - [ ] Phase 3b: Researcher agents (3 in parallel - web sources + trip reports)36 - [ ] Phase 3c: Results aggregated37 - [ ] Phase 3d: Access/permits (inline WebSearch)38- [ ] Phase 4: Route Analysis (synthesize route, crux, hazards)39- [ ] Phase 5: Report Generation (Report Writer agent)40- [ ] Phase 6: Report Review & Validation (Report Reviewer agent)41- [ ] Phase 7: Completion (user notified, next steps provided)4243## Orchestration Workflow4445### Phase 1: Peak Identification4647**Goal:** Identify and validate the specific peak to research.48491. **Extract Peak Name** from user message50 - Look for peak names, mountain names, or climbing objectives51 - Common patterns: "Mt Baker", "Mount Rainier", "Sahale Peak", etc.52532. **Search PeakBagger** using peakbagger-cli:5455 ```bash56 uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak search "{peak_name}" --format json57 ```5859 - Parse JSON output to extract peak matches60 - Each result includes: peak_id, name, elevation (feet/meters), location, url61623. **Handle Multiple Matches:**63 - If **multiple peaks** found: Use AskUserQuestion to present options64 - For each option, show: peak name, elevation, location, AND PeakBagger URL65 - Format each option description as: "[Peak Name] ([Elevation], [Location]) - [PeakBagger URL]"66 - This allows user to click through and verify the correct peak67 - Let user select the correct peak68 - Provide "Other" option if none match6970 - If **single match** found: Confirm with user71 - Present confirmation message with peak details and PeakBagger link72 - Show: "Found: [Peak Name] ([Elevation], [Location])"73 - Include PeakBagger URL in the message so user can verify: "[PeakBagger URL]"74 - Use AskUserQuestion: "Is this the correct peak? You can verify at [PeakBagger URL]"7576 - If **no matches** found:77 - Try peak name variations systematically (see "Peak Name Variations" section):78 - **Word order reversal:** "Mountain Pratt" → "Pratt Mountain"79 - **Title variations:** Mt/Mount, St/Saint80 - **Add location:** Include state or range name81 - **Remove titles:** Try just the core name82 - Run multiple searches in parallel with different variations83 - Combine results and present best matches to user84 - If still no results, use AskUserQuestion to ask for:85 - A different peak name variation86 - Direct PeakBagger peak ID or URL87 - General PeakBagger search88894. **Extract Peak ID:**90 - From search results JSON, extract the `peak_id` field91 - Store for use in subsequent peakbagger-cli commands92 - Also store the PeakBagger URL for reference links9394### Phase 2: Peak Information Retrieval9596**Goal:** Get detailed peak information and coordinates needed for location-based data gathering.9798This phase must complete before Phase 3, as coordinates are required for weather, daylight, and avalanche data.99100Retrieve detailed peak information using the peak ID from Phase 1:101102```bash103uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak show {peak_id} --format json104```105106This returns structured JSON with:107108- Peak name and alternate names109- Elevation (feet and meters)110- Prominence (feet and meters)111- Isolation (miles and kilometers)112- Coordinates (latitude, longitude in decimal degrees)113- Location (county, state, country)114- Routes (if available): trailhead, distance, vertical gain115- Peak list memberships and rankings116- Standard route description (if available in routes data)117118**Error Handling:**119120- If peakbagger-cli fails: Fall back to WebSearch/WebFetch and note in "Information Gaps"121- If specific fields missing in JSON: Mark as "Not available" in gaps section122- Rate limiting: Built into peakbagger-cli (default 2 second delay)123124**Once coordinates are obtained from this step, immediately proceed to Phase 3.**125126### Phase 3: Data Gathering127128**Goal:** Gather comprehensive route information from all available sources.129130**Execution Strategy:** Run Python script for deterministic API data + dispatch specialized agents in parallel for web research. This hybrid approach minimizes token usage while maximizing parallelism.131132#### Step 3A: Fetch Conditions Data (Python Script)133134Run the conditions fetcher script to gather all API-based data:135136```bash137cd "{repo_root}/skills/route-researcher/tools"138uv run python fetch_conditions.py \139 --coordinates "{latitude},{longitude}" \140 --elevation {elevation_m} \141 --peak-name "{peak_name}" \142 --peak-id {peak_id} \143 --trailhead "{trailhead_lat},{trailhead_lon}" \144 --distance-mi {round_trip_distance_mi} \145 --gain-ft {total_gain_ft} \146 --start-time "{HH:MM}" \147 --waypoint "{lat1},{lon1}" --waypoint "{lat2},{lon2}"148```149150Optional args: `--trailhead` enables multi-county path sampling (trailhead→summit); hospital/ranger lookups always run from the summit regardless; `--distance-mi`/`--gain-ft` enable `time_estimates`; `--start-time` (with distance + gain) enables `itinerary`; `--waypoint` (2+) enables `bearings`.151152This returns JSON with:153154- **weather**: 7-day forecast with temperatures, precipitation, freezing levels; each day includes `snow_line_note` (human-readable framing of freezing level as snow line) and `near_summit` (bool: true when freezing level within 2000 ft of summit)155- **air_quality**: AQI ratings and any concerns156- **daylight**: Full twilight table — `astronomical_dawn`, `nautical_dawn`, `civil_twilight` (dawn), `sunrise`, `sunset`, `civil_dusk`, `nautical_dusk`, `astronomical_dusk`; values are `null` at high latitudes when sun doesn't reach threshold (white nights); `daylight_hours`, `timezone`157- **time_estimates**: Roped/unroped + 3-tier pacing (`roped_hr`, `unroped_hr`, `fast_hr`, `moderate_hr`, `leisurely_hr`) — only present when `--distance-mi` and `--gain-ft` CLI args are provided158- **itinerary**: Trip schedule with safety signals (`start_time`, `summit_eta`, `turnaround_by`, `return_eta`, `total_hr`, `after_dark` bool, `dusk_cutoff`, `note`) — only present when `--start-time`, `--distance-mi`, AND `--gain-ft` are all provided; `after_dark: true` is a safety warning that must be prominently surfaced; `total_hr` is the full round-trip duration in hours159- **bearings**: Navigation bearings between waypoints (`segments[]` with `bearing_deg`, `distance_mi`, `cumulative_distance_mi`; `total_distance_mi`) — only present when 2 or more `--waypoint "lat,lon"` args are provided160- **avalanche**: NWAC region and URL for manual check161- **peakbagger**: Ascent statistics and recent ascents (if peak_id provided)162- **counties**: Counties traversed trailhead→summit (`counties[]` with `county_name`, `county_fips`, `state_name`, `state_code`); `sampled` bool and `sample_points` int indicate whether path sampling ran (requires `--trailhead`); without `--trailhead` only the summit county is returned163- **nearest_hospital**: Nearest hospitals/ERs (`hospitals[]` with `name`, `lat`, `lon`, `distance_miles`, `emergency`, and `phone`/`website`/`address` when OSM has them); sorted emergency-first then by distance; max 3164- **ranger_station**: Nearest ranger stations (`stations[]` with `name`, `lat`, `lon`, `distance_miles`, and `phone`/`website`/`address` when present) + optional `admin_district` (`district_name`, `forest_name`, `region`) when the summit coordinates intersect a USFS ranger district165- **campgrounds**: Established campgrounds within ~12 mi (20 km) (`campgrounds[]` with `name`, `lat`, `lon`, `distance_miles`, `camp_type`, `backcountry`, `operator`, and `website` when present); backcountry/high camps are NOT included — extract those from trip reports166- **gaps**: Any API failures noted for report167168**Run this in parallel with Step 3B** — include both the Bash command for fetch_conditions.py and all 3 Task calls in the same response turn to maximize parallelism.169170#### Step 3B: Dispatch Researcher Agents (Parallel)171172Dispatch 3 Researcher agents in a single message (all Task calls together). Each agent researches assigned sources and fetches trip report content directly.173174**Agent 1: PeakBagger + SummitPost**175176```177Task(178 subagent_type="general-purpose",179 model="sonnet",180 prompt="""You are a route researcher gathering mountaineering data for {peak_name}.181182## Your Assignment183Research from these sources: PeakBagger, SummitPost184185**Discover first (web sources):** for SummitPost, run a `site:summitpost.org` WebSearch to get exact URLs, then fetch those (don't WebFetch guessed paths).186187## PeakBagger Research1881. Search: "{peak_name} site:peakbagger.com"1892. Extract route descriptions from peak page1903. List recent ascents with trip reports:191 ```bash192 uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger peak ascents {peak_id} --format json --with-tr --limit 20193 ```1941954. Identify trip reports with content (word_count > 0)1965. Fetch content for up to 5 recent trip reports using:197198 ```bash199 uvx --with patchright --from "git+https://github.com/dreamiurg/peakbagger-cli.git@v1.10.0" peakbagger ascent show {ascent_id} --format json200 ```201202## SummitPost Research2032041. Search: "{peak_name} site:summitpost.org"2052. Use WebFetch to extract: route name, difficulty, approach, description, hazards2063. If WebFetch fails, use the fetching ladder:207208 ```bash209 # Fast path (httpx with browser-like headers, no browser)210 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{url}"211212 # If the above returns {"error": ...} or content is blocked/JS-rendered:213 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{url}"214215 # If --render still returns a Cloudflare challenge page, escalate (needs a display):216 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{url}"217 ```218219## Trip Report Extraction220221For each report fetched, extract:222- date, author, route conditions, gear mentioned223- **Hazards (extract explicitly and separately):**224 - Rockfall zones: location on route, conditions, timing guidance mentioned225 - Icefall/serac hazard: location, stability, pre-dawn/timing advice226 - Cornice hazard: location, buildup direction, avoidance notes227- **Terrain detail (extract if mentioned):**228 - Downclimb sections: location, difficulty, rappel anchors if any229 - River/stream crossings: location, flow conditions, ford difficulty230 - Water sources: named locations, seasonal availability231 - Named camps or bivy sites: name/location, exposure notes232233## Output Format (return EXACTLY this JSON)234235```json236{237 "sources": ["PeakBagger", "SummitPost"],238 "route_info": [239 {"source": "...", "name": "...", "difficulty": "...", "description": "...", "hazards": [...]}240 ],241 "trip_reports": [242 {"source": "...", "date": "...", "author": "...", "url": "...", "summary": "...", "conditions": "...", "has_gpx": false,243 "rockfall": "...", "icefall": "...", "cornices": "...",244 "downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}245 ],246 "gaps": ["what couldn't be fetched and why"]247}248```"""249)250```251252**Agent 2: WTA + Mountaineers + Regional Sources**253254```255Task(256 subagent_type="general-purpose",257 model="sonnet",258 prompt="""You are a route researcher gathering mountaineering data for {peak_name}.259260## Your Assignment261Research from these sources: WTA, Mountaineers.org, northwesthikers.net, hikeoftheweek.com, Oregon Hikers Field Guide (oregonhikers.org), Cascade Climbers (cascadeclimbers.com), Mountain Project262263**Retrieval strategy — discover URLs, then fetch.** For each web source below (except mountaineers.org — use the Mountaineers MCP, see below), FIRST run a `site:` WebSearch (e.g. `"{peak_name} site:wta.org"`, `site:nwhikers.net`, `site:cascadeclimbers.com`) to collect the exact hike-page and individual trip-report URLs. THEN fetch each discovered URL through the fetching ladder. Do not WebFetch a guessed URL — enumerate real URLs first. This recovers reports that one-pass fetching loses to 403/JS blocks.264265## WTA Research2661. Search: "{peak_name} site:wta.org"2672. Find the hike page and extract: trail name, difficulty, distance, elevation gain, hazards2683. Get trip reports from AJAX endpoint: {wta_url}/@@related_tripreport_listing2694. Fetch content for up to 5 recent trip reports using the fetching ladder:270 ```bash271 # Fast path first272 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{trip_report_url}"273274 # If output contains {"error": ...} or content is blocked/JS-rendered:275 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{trip_report_url}"276277 # If --render still returns a Cloudflare challenge page, escalate (needs a display):278 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{trip_report_url}"279 ```280281## Mountaineers Research (use the Mountaineers MCP FIRST — do not scrape)282283mountaineers.org reliably returns HTTP 403 to WebFetch/cloudscrape. Use the Mountaineers MCP tools instead — they return structured data:2842851. `mcp__mountaineers__search_routes` (query "{peak_name}") and `mcp__mountaineers__get_route` to get the route/place page (difficulty, directions, gear).2862. `mcp__mountaineers__search_trip_reports` (query "{peak_name}") and, when you have a route URL, `mcp__mountaineers__get_route_trip_reports` to enumerate member trip reports.2873. `mcp__mountaineers__get_trip_report` to pull each relevant report's body + structured fields (date, author, result, road/conditions notes).2884. Only if the MCP is unavailable, document the gap — mountaineers.org reliably returns HTTP 403 to WebFetch/cloudscrape, so scraping is not a viable fallback for this domain.289290Note: the Mountaineers MCP is available to Task-dispatched `general-purpose` agents (this agent). Extract route beta, technical requirements, and hazards from the MCP results.291292## NWHikers Research (northwesthikers.net / nwhikers.net)2932941. Search: "{peak_name} site:nwhikers.net OR site:northwesthikers.net"2952. Use WebFetch to extract first-person trip reports, GPS track notes, conditions2963. If WebFetch fails, use `cloudscrape.py "{url}"` (fast path usually sufficient)297298## Hike of the Week (hikeoftheweek.com — REQUIRES --render)2993001. Search: "{peak_name} site:hikeoftheweek.com"3012. **MUST use `--render` flag** — site is Cloudflare-protected and blocks WebFetch:302303 ```bash304 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render "{url}"305 # If --render still returns a Cloudflare challenge page, escalate (needs a display):306 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py --render --headed "{url}"307 ```3083093. Extract: logistics, route narrative, access notes, trailhead directions310311## Oregon Hikers Field Guide (oregonhikers.org — Oregon objectives only)3123131. Search: "{peak_name} site:oregonhikers.org"3142. Use WebFetch — site is static MediaWiki HTML, WebFetch-friendly3153. Extract: route description, access, permits, conditions notes316317## Cascade Climbers (cascadeclimbers.com)3183191. Search: "{peak_name} site:cascadeclimbers.com"3202. Use WebFetch; if blocked use `cloudscrape.py "{url}"`3213. Extract: technical route beta, gear lists, trip reports, conditions322323## Mountain Project (for technical/rock sections)3243251. Search: "{peak_name} site:mountainproject.com"3262. Use WebFetch to extract: route name, grade, gear, description, rock quality3273. If WebFetch fails, use `cloudscrape.py "{url}"`328329## Fallback330331If WebFetch fails for any page, use the fetching ladder: `cloudscrape.py "{url}"` (fast) → `cloudscrape.py --render "{url}"` for JS-rendered or Cloudflare-protected pages.332333## Trip Report Extraction334335For each report fetched, extract:336- date, author, route conditions, gear mentioned337- **Hazards (extract explicitly and separately):**338 - Rockfall zones: location on route, conditions, timing guidance mentioned339 - Icefall/serac hazard: location, stability, pre-dawn/timing advice340 - Cornice hazard: location, buildup direction, avoidance notes341- **Terrain detail (extract if mentioned):**342 - Downclimb sections: location, difficulty, rappel anchors if any343 - River/stream crossings: location, flow conditions, ford difficulty344 - Water sources: named locations, seasonal availability345 - Named camps or bivy sites: name/location, exposure notes346347## Output Format (return EXACTLY this JSON)348349```json350{351 "sources": ["WTA", "Mountaineers", "NWHikers", "HikeOfTheWeek", "OregonHikers", "CascadeClimbers", "MountainProject"],352 "route_info": [353 {"source": "...", "name": "...", "difficulty": "...", "description": "...", "hazards": [...]}354 ],355 "trip_reports": [356 {"source": "...", "date": "...", "author": "...", "url": "...", "summary": "...", "conditions": "...", "has_gpx": false,357 "rockfall": "...", "icefall": "...", "cornices": "...",358 "downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}359 ],360 "gaps": ["what couldn't be fetched and why"]361}362```"""363)364```365366**Agent 3: AllTrails**367368```369Task(370 subagent_type="general-purpose",371 model="sonnet",372 prompt="""You are a route researcher gathering mountaineering data for {peak_name}.373374## Your Assignment375Research from AllTrails376377## AllTrails Research3781. Search: "{peak_name} site:alltrails.com"3792. Use WebFetch to extract: trail name, difficulty, distance, elevation gain, route type, best season, hazards3803. If WebFetch fails, use:381 ```bash382 uv run python {repo_root}/skills/route-researcher/tools/cloudscrape.py "{url}"383 ```3843854. From route description and any visible reviews/comments, extract if present:386 - Rockfall zones, icefall/serac hazard, cornice hazard387 - Downclimb sections, river/stream crossings, water sources, named camps388389## Output Format (return EXACTLY this JSON)390391```json392{393 "sources": ["AllTrails"],394 "route_info": [395 {"source": "...", "name": "...", "difficulty": "...", "distance_miles": N, "elevation_gain_ft": N, "description": "...", "hazards": [...],396 "rockfall": "...", "icefall": "...", "cornices": "...",397 "downclimbs": "...", "crossings": "...", "water_sources": "...", "camps": "..."}398 ],399 "trip_reports": [],400 "gaps": ["what couldn't be fetched and why"]401}402```"""403)404```405406**Execute all 3 agents in parallel by including all Task calls in a single response.**407408#### Step 3C: Aggregate Results409410After Python script and all agents return, aggregate into unified data structure:411412```json413{414 "conditions": { /* from fetch_conditions.py */ },415 "route_data": {416 "sources": [ /* merged from all 3 agents */ ],417 "trip_reports": [ /* merged from all agents */ ]418 },419 "gaps": [ /* merged gaps from all sources */ ]420}421```422423**Partial Failure Handling:**424425- If any agent fails entirely, proceed with data from successful agents426- Note failed sources in the gaps array427- Minimum viable: conditions data + at least one route source428429#### Step 3D: Access, Permits, and Road/Gate Status (Inline)430431Determine permits AND the **current road/gate status** to the trailhead — do not just tell the user to go check. Actively research and report the actual status with a source and date.432433**Permits:**434435```436WebSearch: "{peak_name} trailhead access" ; "{peak_name} permit requirements"437```438439**Road / gate status workflow** (identify the access highway + forest road + managing agency first, then check sources in order; capture each source URL for the report):4404411. **State DOT pass report** (WA → WSDOT): fetch the relevant pass page, e.g. `https://wsdot.com/travel/real-time/mountainpasses/mt.-baker` (SR-542) or `.../north-cascades` (SR-20). Read `RoadCondition` / `TravelAdvisoryActive` / restrictions. Other states: `WebSearch "{state} DOT mountain pass report {highway}"`.4422. **USFS forest alerts/conditions**: fetch `https://www.fs.usda.gov/{region}/{forest-shortname}/alerts` (e.g. `r06/mbs/alerts`) and `/conditions`; search the page for the road number / trailhead name → closure milepost, reason, seasonal gate. Also `WebSearch "{forest name} {road or trailhead} road open {year}"` for seasonal-opening press releases.4433. **NPS road conditions** (if in/through a national park): fetch `https://www.nps.gov/{park-code}/planyourvisit/road-conditions.htm` (e.g. `noca`, `mora`, `olym`) → per-road OPEN/CLOSED + milepost.4444. **WTA ground truth** (PNW): `WebSearch "site:wta.org {trail} gate road open closed {year}"` or fetch the hike page; scan the 3-5 most recent trip reports for "gate"/"road open/closed"/"drove to" (use `cloudscrape.py --render` if WTA 403s).4455. **InciWeb fire closures** (Jul-Oct): `WebSearch "inciweb {area} closure {trailhead} {year}"`; if an active incident is near the trailhead, read its closure page.446447**Synthesize** into a dated status statement for the report's Road Conditions section:448> "Gate/road status (as of {date}): {road} is {OPEN/CLOSED/SEASONAL GATE/UNKNOWN} per [source](url). {If closed: gate at {milepost}, adds ~{N} mi each way.}"449450If no source confirms it, say so explicitly and include the managing ranger station phone as the fallback. Add trailhead names, permits, the status statement, and all source URLs to route_data.451452### Phase 4: Route Analysis453454**Goal:** Analyze gathered data to determine route characteristics and synthesize information.455456#### Step 4A: Determine Route Type457458Based on route descriptions, elevation, and gear mentions, classify as:459460- **Glacier:** Crevasses mentioned, glacier travel, typically >8000ft461- **Rock:** Technical climbing, YDS ratings (5.x), protection mentioned462- **Scramble:** Class 2-4, exposed but non-technical463- **Hike:** Class 1-2, trail-based, minimal exposure464465#### Step 4B: Synthesize Route Information from Multiple Sources466467**Goal:** Combine trip reports and route descriptions from Step 3B researcher agents, plus conditions data from Step 3A, into comprehensive route beta.468469**Source Priority:**4704711. Trip reports (Step 3B agents) - first-hand experiences4722. Route descriptions (Step 3B agents) - published beta baseline4733. PeakBagger/ascent data (Step 3A Python script) - basic info, patterns474475**Synthesis Pattern for Route, Crux, and Hazards:**4764771. **Start with baseline** from route descriptions (standard route name, published difficulty)4782. **Enrich with trip report details** (landmarks, specific conditions, actual experiences)4793. **Note conflicts** when trip reports disagree with published info4804. **Highlight consensus** ("Multiple reports mention...")4815. **Include specifics** (elevations, locations, quotes)4826. **Link every specific-report attribution to its source.** Whenever a detail is drawn from a *particular* trip/climb report (a date, a quote, "one party found…", "a recent report noted…"), the attribution MUST be a Markdown hyperlink to that report's URL — never plain text. Carry each trip report's `url` (and date/author) through synthesis so it can be linked; use the date/author as the link text. For consensus phrasing ("multiple reports mention…"), link 2-3 of the contributing reports inline. Only general/published beta with no specific source stays unlinked.483484**Example (Route Description):**485> "The standard route follows the East Ridge (Class 3). A [Sep 2025 party](https://www.peakbagger.com/climber/ascent.aspx?aid=12345) found a well-cairned use trail branching right at 4,800 ft—the correct turn—through talus they called 'tedious' and 'ankle-rolling'. An [Oct 2025 report](https://www.wta.org/go-hiking/trip-reports/trip_report.123) noted the section was snow-covered, requiring microspikes."486487**Apply this pattern to:**488489- **Route:** Use baseline structure, add landmarks/navigation from trip reports, include actual times490- **Crux:** Describe location/difficulty, add trip report assessments, note conditions-dependent variations491- **Hazards:** Extract ALL hazards from trip reports. Organize by type with explicit, SEPARATE sub-sections — do NOT bury rockfall or icefall under generic "exposure":492 - **Rockfall:** tag location, trigger (other parties / freeze-thaw / sun hitting the face), timing mitigation (pre-dawn passage, move quickly through zone)493 - **Icefall/Serac:** tag location, stability assessment, timing mitigation (avoid afternoon, pre-dawn passage)494 - **Cornice:** tag location, avoidance line, conditions (buildup direction, season)495 - Other hazards (crevasses, exposure, route-finding, seasonal) as separate bullets496 - Be comprehensive — safety-critical; include specific locations and mitigation strategies497- **Terrain detail:** Surface the following in the report when found in trip reports/beta:498 - Downclimbs: location, difficulty, whether rappel anchors exist499 - River/stream crossings: location, seasonal flow, ford difficulty500 - Water sources: named locations and per-day availability by season501 - Named camps/bivy sites: name, location, exposure; note these come from trip reports, not the campground database502503**Extract Key Information:**504505From all synthesized data, identify:506507- **Difficulty Rating:** YDS class, scramble grade, or general difficulty (validated by trip reports)508- **Crux:** Hardest/most technical section of route (synthesized above)509- **Hazards:** All identified hazards (synthesized above)510- **Notable Gear:** Any unusual or important gear mentioned in trip reports or beta (to be included in relevant sections, not as standalone section)511- **Trailhead:** Name and approximate location512- **Distance/Gain:** Round-trip distance and elevation gain (compare published vs actual trip report data)513- **Time Estimates:** Use `conditions.time_estimates` (keys: `fast_hr`, `moderate_hr`, `leisurely_hr`, `roped_hr`, `unroped_hr`) from fetch_conditions.py output — present only when it was called with `--distance-mi` and `--gain-ft`. If `time_estimates` is absent from the conditions data, note it in Information Gaps. **To populate it:** re-invoke fetch_conditions.py with `--distance-mi {distance}` and `--gain-ft {gain}` once route distance/gain are known from Step 3B research. At the same time, add `--start-time HH:MM` to get `itinerary` and `--waypoint` args to get `bearings` — these optional outputs only activate when the args are supplied.514- **Freezing Level Analysis:** Compare peak elevation with forecasted freezing levels:515 - **Include Freezing Level Alert if:** Any day in forecast has freezing level within 2000 ft of peak elevation516 - **Omit if:** Freezing level stays >2000 ft above peak throughout forecast (typical summer conditions)517 - Example: 5,469 ft peak with 5,000-8,000 ft freezing levels → Include alert (marginal conditions)518 - Example: 4,000 ft peak with 10,000+ ft freezing levels → Omit alert (well above summit)519520#### Step 4C: Surface Geodata in Report521522Include these geodata fields when available. **Every place named in the report must be a link** — see the link patterns below.523524**Place / map link patterns** (build from a place's `lat`/`lon` and `name`):525526- Google Maps **place** (named entity): `https://www.google.com/maps/search/?api=1&query={URL-encoded name + address}` — use this (not bare coordinates) for hospitals, ranger stations, campgrounds, and any named place, so the link resolves to the actual entity. When only coordinates are meaningful, `query={lat},{lon}`.527- Gaia GPS: `https://www.gaiagps.com/map/?loc=14/{lon}/{lat}` (zoom/lon/lat).528- CalTopo: `https://caltopo.com/map.html#ll={lat},{lon}&z=14&b=mbt`.529530Surfacing rules:531532- **Counties:** list `county_name + state_name` from `conditions.counties.counties[]` in the Overview. Empty/`error` → Information Gaps.533- **Emergency contacts:** build the table from `conditions.nearest_hospital.hospitals[]` and `conditions.ranger_station` (stations + admin_district). **Link each name** to its `website` if present, else a Google Maps place search by `name + address`. **Always include phone AND address columns** — each entry now carries `phone`/`website`/`address`/`lat`/`lon` when OSM has them; if `phone` or `address` is missing, make a best effort to find the entity's real phone/address (its official site or Google Maps listing) before writing "—". Missing/`error` → note in Information Gaps.534- **Campgrounds:** build the Camping table from `conditions.campgrounds.campgrounds[]`; link the name (website or Google Maps place) and add Google Maps + Gaia map links from each entry's `lat`/`lon`.535- **Any named location report-wide** (campsite, bivy, high camp, named feature, trailhead): accompany with at least Google Maps + Gaia links, per the patterns above. For trip-report-named camps without coordinates, use a Google Maps place search by name and do your best to locate it; if it cannot be located, say so explicitly. Backcountry/high camps come from trip reports, not the campground DB.536537#### Step 4D: Identify Information Gaps538539Explicitly document what data was **not found or unreliable:**540541- Missing trip reports542- No GPS tracks available543- Script failures (weather, avalanche, daylight)544- Conflicting information between sources545- Limited seasonal data546547### Phase 5: Report Generation548549**Goal:** Create comprehensive Markdown document by dispatching Report Writer agent.550551#### Step 5A: Prepare Data Package552553Organize all gathered and analyzed data into structured JSON:554555```json556{557 "peak": {558 "name": "{peak_name}",559 "id": {peak_id},560 "elevation_ft": {elevation},561 "coordinates": [{latitude}, {longitude}],562 "location": "{location}",563 "peakbagger_url": "{url}"564 },565 "conditions": {566 // From fetch_conditions.py output567 "weather": {"forecast": [{"date": "...", "snow_line_note": "...", "near_summit": bool, "freezing_level_ft": N, ...}], ...},568 "air_quality": {...},569 "daylight": {"astronomical_dawn": "...", "nautical_dawn": "...", "civil_twilight": "...", "sunrise": "...", "sunset": "...", "civil_dusk": "...", "nautical_dusk": "...", "astronomical_dusk": "...", "daylight_hours": N},570 "avalanche": {...},571 "peakbagger": {...},572 "counties": {"counties": [{"county_name": "...", "county_fips": "...", "state_name": "...", "state_code": "..."}], "sampled": bool, "sample_points": N}, // sampled + sample_points only present when --trailhead was given573 "nearest_hospital": {"hospitals": [{"name": "...", "lat": N, "lon": N, "distance_miles": N, "emergency": "yes|null", "phone": "...", "website": "...", "address": "..." /* phone/website/address optional */}]},574 "ranger_station": {"stations": [{"name": "...", "lat": N, "lon": N, "distance_miles": N, "phone": "...", "website": "...", "address": "..." /* optional */}], "admin_district": {"district_name": "...", "forest_name": "...", "region": "..."}},575 "campgrounds": {"campgrounds": [{"name": "...", "lat": N, "lon": N, "distance_miles": N, "camp_type": "...", "operator": "...", "website": "..." /* optional */}], "note": "..."},576 "time_estimates": {"roped_hr": N, "unroped_hr": N, "fast_hr": N, "moderate_hr": N, "leisurely_hr": N, "note": "..."},577 "itinerary": {"start_time": "HH:MM", "summit_eta": "HH:MM", "turnaround_by": "HH:MM", "return_eta": "HH:MM", "total_hr": N, "after_dark": false, "dusk_cutoff": "9:15 PM" /* 12-hr AM/PM format, unlike other time fields */, "note": "..."},578 "bearings": {"segments": [{"from": 0, "to": 1, "bearing_deg": N, "distance_mi": N, "cumulative_distance_mi": N}], "total_distance_mi": N}579 },580 "route_data": {581 // Merged from all Researcher agents582 "sources": [...],583 "trip_reports": [...]584 },585 "analysis": {586 // From Phase 4587 "route_type": "{hike|scramble|technical|glacier}",588 "difficulty": "{rating}",589 "crux": "{description}",590 "hazards": [...],591 "access": {...}592 },593 "gaps": [...]594}595```596597#### Step 5B: Dispatch Report Writer Agent598599```600Task(601 subagent_type="general-purpose",602 model="sonnet",603 prompt="""You are a Report Writer generating a mountaineering route report.604605## Instructions6066071. **Read the report template:**608 Use the Read tool to read: {repo_root}/skills/route-researcher/assets/report-template.md6096102. **Generate report following template structure exactly:**611 - Header with peak name, elevation, location, date612 - AI disclaimer (prominent safety warning)613 - Overview: route type, difficulty, distance/gain, time estimates614 - Route Description: synthesized from sources, include landmarks615 - Crux: describe hardest section with specifics616 - Known Hazards: comprehensive list617 - Current Conditions: weather forecast, freezing levels, air quality, daylight618 - Trip Reports: links organized by source with dates619 - Information Gaps: explicitly list missing data620 - Data Sources: links to all sources used6216223. **Markdown Formatting Rules:**623 - ALWAYS add blank line before lists624 - ALWAYS add blank line after section headers625 - Use `-` for bullets (not `*` or `+`)626 - Use `**text**` for bold emphasis627 - Break paragraphs >4 sentences628 - **Link specific-report attributions.** Any statement attributed to a particular trip/climb report (a date, a quote, "one party…", "a recent report…") MUST be a Markdown link `[date/author](report_url)` to that report's source URL — never plain-text attribution. Pull the URL from the matching `trip_reports[].url` in the data package. Leave only generic/published beta (no specific source) unlinked.6296304. **Save the report:**631 Use the Write tool to save to the user's current working directory: {date}-{peak-name-slug}.md632633## Data Package634635{data_package_json}636637## Output Format (return EXACTLY this JSON)638```json639{640 "status": "SUCCESS",641 "file_path": "/absolute/path/to/report.md",642 "filename": "YYYY-MM-DD-peak-name.md",643 "sections_generated": N644}645```"""646)647```648649#### Step 5C: Capture Report File Path650651Extract `file_path` from agent's JSON response for use in Phase 6.652653### Phase 6: Report Review & Validation654655**Goal:** Validate report quality by dispatching Report Reviewer agent.656657#### Step 6A: Dispatch Report Reviewer Agent658659```660Task(661 subagent_type="general-purpose",662 model="opus",663 prompt="""You are a Report Reviewer validating a mountaineering route report.664665## Instructions6666671. **Read the report:**668 Use the Read tool to read: {report_file_path}6696702. **Perform systematic quality checks:**671672 **Factual Consistency:**673 - Dates match their stated day-of-week (e.g., "Thu Nov 6, 2025" is actually Thursday)674 - Coordinates, elevations, distances consistent across all mentions675 - Weather forecasts align logically (freezing levels match precipitation types)676677 **Mathematical Accuracy:**678 - Elevation gains add up correctly679 - Time estimates reasonable given distance and elevation gain680 - Unit conversions correct (feet to meters, etc.)681682 **Internal Logic:**683 - Hazard warnings align with route descriptions684 - Recommendations match current conditions685 - Crux descriptions match overall difficulty rating686687 **Completeness:**688 - No placeholder texts like {{peak_name}} or {{YYYY-MM-DD}}689 - All referenced links actually provided690 - Mandatory sections present: Overview, Route, Current Conditions, Trip Reports, Information Gaps, Data Sources691692 **Formatting:**693 - Markdown headers properly structured694 - Lists have blank lines before them695 - Tables properly formatted696697 **Safety & Responsibility:**698 - AI disclaimer present and prominent699 - Critical hazards properly emphasized700 - Users directed to verify information from primary sources701702 **Emergency contacts & location links (verify INDEPENDENTLY):**703 - Each emergency contact (hospital, ranger station) has a working name link (website or a Google Maps place link to the actual entity — NOT bare coordinates), a phone, and an address. Independently confirm the phone/address look right for that named entity (e.g. via its official site / Google Maps); fix or flag mismatches and fill blanks you can confirm.704 - Road/gate status is a dated statement with a cited source, not a "go check it yourself" punt.705 - Every named place in the report (campsite, bivy, high camp, trailhead, named feature) carries map links (Google Maps + Gaia GPS). Flag any named location missing links.706 - Specific trip-report attributions are hyperlinks to their source, not plain text.7077083. **Fix issues:**709 - **Critical** (safety errors, factual errors, missing disclaimers): MUST fix using Edit tool710 - **Important** (completeness, consistency): SHOULD fix711 - **Minor** (formatting, polish): FIX if quick712713## Output Format (return EXACTLY this JSON)714```json715{716 "status": "PASS" | "PASS_WITH_FIXES" | "FAIL",717 "issues_found": N,718 "fixes_applied": ["description of fix 1", "description of fix 2"],719 "remaining_issues": ["issues that couldn't be fixed"],720 "report_path": "/absolute/path/to/report.md"721}722```"""723)724```725726#### Step 6B: Process Validation Results727728Handle the reviewer agent's response:729730- **PASS or PASS_WITH_FIXES:** Proceed to Phase 7 with the `report_path`731- **FAIL:** Present `remaining_issues` to user and ask for guidance732733The Report Reviewer automatically fixes issues and returns the corrected file path.734735### Phase 7: Completion736737**Goal:** Inform user of completion and next steps.738739Report to user:7407411. **Success message:** "Route research complete for {Peak Name}"7422. **File location:** Full absolute path to generated report7433. **Summary:** Brief 2-3 sentence overview:744 - Route type and difficulty745 - Key hazards or considerations746 - Any significant information gaps7474. **Next steps:** Encourage user to:748 - Review the report749 - Verify critical information from primary sources750 - Check current conditions before attempting route751 - **Itinerary and navigation**: If the user wants a start-time schedule and/or compass bearings, re-run `fetch_conditions.py` with `--start-time HH:MM` (adds `itinerary` key) and/or `--waypoint lat,lon` flags (2+ waypoints add `bearings` key). Surface `after_dark: true` as a prominent safety warning.752 - **Post-climb trip report**: After the climb, offer the trip-report template at `skills/route-researcher/assets/trip-report-template.md` as a starting point for filing a trip report.753754**Example completion message:**755756```757Route research complete for Mount Baker!758759Report saved to: 2025-10-20-mount-baker.md760761Summary: Mount Baker via Coleman-Deming route is a moderate glacier climb (Class 3) with significant crevasse hazards. The route involves 5,000+ ft elevation gain and typically requires an alpine start. Weather and avalanche forecasts are included.762763Next steps: Review the report and verify current conditions before your climb. Remember that mountain conditions change rapidly - check recent trip reports and weather forecasts immediately before your trip.764```765766## Error Handling Principles767768Throughout execution, follow these error handling 769770…(truncated)