skills/aa-audit/SKILL.md
When the user wants to audit Adobe Analytics data for a property. Also use when the user mentions 'AA audit,' 'Adobe Analytics audit,' 'AA performance profile,' or 'AA traffic analysis.' Runs a Python script against the AA 2.0 Reporting API, interprets the JSON output, and produces a structured performance-profile.md context file (.claude/context/ L1). Single agent, no depth flag. Works with any AA implementation given a client config file.
npx skillsauth add FunnelEnvy/funnelenvy-skills aa-auditInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an analytics specialist. Your job is to run the AA audit script, interpret the structured JSON output, classify performance patterns, and produce a performance profile that powers downstream experiment planning and ICE scoring calibration.
You are an L1 skill. You run a Python script that queries Adobe Analytics, analyze the returned data, and produce a structured context file. This means:
aa_audit.py which handles all API calls and outputs JSON.claude/context/performance-profile.mdOutput location: .claude/context/performance-profile.md
Token budget: ~50-80K
Runtime: ~5-8 minutes
Agents: Single agent. No multi-agent pipeline.
Model: Opus
/aa-audit
/aa-audit --config /path/to/config.json
/aa-audit --config /path/to/config.json --days 30
/aa-audit --config /path/to/config.json --days 90 --no-compare
Flags:
| Flag | Default | Description |
|------|---------|-------------|
| --config | (required or from env) | Path to client AA config JSON |
| --days | 90 | Number of days to look back from today |
| --no-compare | false | Skip period-over-period comparison |
The Python script resolves config in this order:
--config /path/to/config.json CLI flagADOBE_AA_CONFIG env var pointing to file pathThese env vars must be set (never stored in repo):
ADOBE_AA_CLIENT_IDADOBE_AA_CLIENT_SECRETADOBE_AA_ORG_IDHard requirements:
requests package installedaa_audit.py script must be locatable (same directory as this SKILL.md)Soft requirements:
company-identity.md in .claude/context/: If present with confidence >= 2,
Step 8 enriches the output with product-line groupings, funnel stage mapping,
and tracking gap flags. If missing, Steps 1-7 produce complete output without it.Error states:
None. Analytics data is a time-bounded snapshot. Each run overwrites .claude/context/performance-profile.md entirely. No incremental extension, no confidence-only-rises rule.
Find aa_audit.py in the same directory as this SKILL.md. Run it with the user's flags:
python3 /path/to/aa_audit.py --config /path/to/config.json --days N [--no-compare]
Capture stdout (JSON) and stderr (progress messages). Display progress to the user as the script runs.
If the script exits with an error, display the error and stop. Do not retry.
Config notes (optional blocks):
scope: isolates one sub-property inside a blended report suite. scope.segment_id (preferred when a segment exists) applies that segment to every report; otherwise scope.prefixes builds a broad single-token search clause on scope.prefix_dimension (default variables/entrypage). When scope is unset, every report runs whole-suite (pre-change behavior). The script reports the resolved method in meta.scope_method (segment | prefix | none). When scope is prefix-only (approximate), note the post-consent page-attribution caveat in the profile and cap confidence.interaction_dimensions: array of element-click dimensions to enumerate (default customlink/clickmaplink/clickmappage). Legacy dimensions.link_text is honored as a single-eVar fallback when interaction_dimensions is absent.Parse the JSON output. Verify:
site_totals has non-zero visitspages array is non-emptychannels array is non-emptyIf validation fails, report which sections are empty and suggest checking the report suite ID.
Display a summary:
AA data pulled successfully.
Report suite: [rsid]
Date range: [start] to [end] ([N] days)
Site totals: [visits] visits, [visitors] visitors
Pages: [N] rows
Channels: [N] rows
Comparison: [enabled | disabled]
From the pages array and site_totals:
Compute site-wide averages:
For each page row, compute:
Failure mode classification (same thresholds as ga4-audit):
shallow_engagement: pages/session < 75% of site avg AND bounce > site avg + 10ppdeep_engagement: pages/session > 150% of site avg AND CVR < 50% of site avgnull: neither condition metHigh-bounce pages: Filter for bounce rate >50% AND >100 visits
Page grouping by URL pattern:
From the config's conversion_events and engagement_events:
page_conversions data, top pages by each conversion eventUser confirmation: Present the event list from config and ask user to confirm the primary conversion event. If they want to change it, adjust downstream analysis.
Channels (from channels array):
Channel detail (from channel_detail array):
Devices (from devices array):
New vs returning (from new_vs_returning array):
variables/visitnumber (AA's Visit Number dimension). Rows have values "1", "2", "3", etc.5x:
familiarity_dependent
normal_b2bstrong_first_visitacquisition_heavyLanding pages (from landing_pages array):
Source x Landing Page mismatches (from landing_page_channels):
This produces the REQUIRED Element-Level Interactions body section. It always emits a finding -- presence with volumes when elements are tracked, AND an explicit absence assertion when they are not. Never silently skip.
Element interactions (from element_interactions.by_dimension, keyed by interaction dimension):
tracked_elements[] (each name, count, status). status="dark" when the comparison block flagged a present-then-0 regression for that element; else live.Clickmap regions (from clickmap_regions array, if present):
Determine element_instrumentation_state:
present: interaction dimensions configured AND meaningful volume returned across the scoped unit.partial: some interaction dimensions return data but expected structural element classes (e.g., primary CTA) are missing.absent: no interaction dimensions configured, OR all configured dimensions return empty for the scoped unit.Absence assertion (when partial/absent):
missing_element_classes[] (e.g., primary CTA, nav links, form fields not instrumented).instrumentation_ask recommending the specific tracking to add (e.g., "Instrument primary CTA clicks via Custom Link s.tl() or Activity Map").Search-reliability caveat: element enumeration uses a broad single-token prefix search at an untruncated limit. Empty results from a narrow/over-scoped query are inconclusive, NOT proof of absence. The script already enumerates broadly; if a dimension returns empty under an approximate (prefix-only) scope, treat as inconclusive and note it.
This produces the REQUIRED Measurement Integrity body section from event_liveness and a friction pass. Always emit it.
Event liveness / dead bindings (from event_liveness[]):
count and status.dead: count == 0 over the full window -- a configured event whose binding never fired. Surface as a dead binding (the implementation is wired in config but not emitting).dark: present in the comparison window then 0 in primary (from comparison.event_liveness_regressions) -- a click/event that regressed dark partway through.spiked: 0 then present, or a large jump -- newly firing or a measurement spike worth flagging.measurement_integrity_flags[].Friction interactions (from element_interactions values + event names):
element_interactions row carries a friction boolean set by aa_audit.py's is_friction_token (the single source of truth for the token set, shared by script, SKILL, and tests). Use that flag rather than re-deriving tokens. For event names not run through the script, apply the same helper conceptually.ratio_to_baseline for each friction interaction (friction count / baseline count); else leave ratio_to_baseline null.friction_interactions[] (each interaction, count, ratio_to_baseline, type).If no events are dead/dark/spiked and no friction interactions are detected, state that explicitly ("No dead bindings, zero-crossings, or friction interactions detected") -- the section is still present.
Three formula types (same as ga4-audit):
| Type | Formula | When |
|------|---------|------|
| CVR Improvement | (target_rate - current_rate) * monthly_sessions * 0.4 | Page converts below group average |
| Bounce Reduction | bouncing_sessions * recovery_rate * site_cvr * 0.4 | High-bounce page |
| Traffic Reallocation | sessions * capture_rate * 0.4 | Pages with no conversion path |
Recovery rates: 0.15 (messaging), 0.10 (UX) Capture rates: 0.01 (informational), 0.03 (product/service pages)
Impact buckets (not point estimates):
small: <5 estimated additional conversions/monthmedium: 5-20large: >20Comparison period analysis (when comparison data present):
[WORSENING] (>10% or >5pp), [IMPROVING] (>10% or >5pp), [STABLE] (within)Construct .claude/context/performance-profile.md using the same schema as ga4-audit output.
All fields required unless noted.
schema ("performance-profile"), schema_version ("2.3"), generated_by ("aa-audit"), last_updated, last_updated_by ("aa-audit"), confidence (1-5), company (from config company_id), report_suite, date_range, daystotal_sessions (visits), total_users (visitors), device_mobile_pct (integer %)top_pages[] each with path, sessions, bounce_rate, pages_per_session, avg_engagement_sec, failure_modeconversion_events[] each with name, count, classification. Plus primary_conversion_event, primary_conversion_rate (%)top_channels[] each with channel, sessions, bounce_ratesource_page_mismatches[] each with page, better_channel, worse_channel, gap_type, better_value, worse_valuenew_vs_returning with new_sessions_pct, new_conversion_rate, returning_conversion_rate, returning_to_new_ratio, signalpage_groups[] each with group, url_pattern, monthly_sessions, conversion_rate, bounce_rate, page_counttop_opportunities[] each with page, issue, formula_type, current_metric, target_metric, monthly_sessions, estimated_monthly_impact, action_category, sizing_notetraffic_adequacy ("high" | "adequate" | "low"), sampling_applied (false for AA 2.0 virtual report suites)scope_applied (bool), scope_method ("segment" | "prefix" | "none", from meta.scope_method), scope_note (string | null; description + accuracy caveat when approximate/prefix-only)element_instrumentation_state ("present" | "partial" | "absent"), tracked_elements[] each name/count/status ("live" | "dark"), missing_element_classes[], instrumentation_ask (string | null). element_interactions_available retained for back-compat but is no longer a skip signal.event_liveness[] each event/count/status ("live" | "dead" | "dark" | "spiked"), measurement_integrity_flags[], friction_interactions[] each interaction/count/ratio_to_baseline (nullable)/typecomparison_period with start, end. trends with sessions_change_pct, primary_cvr_change_pp, bounce_rate_change_pp, mobile_bounce_change_ppl0_available (bool), l0_confidence (int | null)Use these mappings when writing the output to match the ga4-audit schema:
| Schema Field | AA Metric | Notes | |---|---|---| | sessions | metrics/visits | Direct | | users | metrics/visitors | Direct | | bounce_rate | metrics/bouncerate | Direct (%) | | engagement_rate | 100 - bouncerate | Computed | | avg_duration | metrics/averagetimespentonsite | Seconds | | pages_per_session | pageviews / visits | Computed from site totals | | conversions | from config conversion_events | Client-specific |
tracked_elements) and interaction rates when instrumented; when element_instrumentation_state is partial/absent, LEADS with the gap statement, missing_element_classes, and instrumentation_ask. Includes the broad-prefix search-reliability caveat. Friction interactions surface here.Scope + confidence caveat: when scope_method is prefix (approximate, no segment), document the post-consent page-attribution caveat in Property Overview and cap confidence (do not exceed 3 on scope grounds alone).
[WORSENING]: degraded >10% or >5pp[IMPROVING]: improved >10% or >5pp[STABLE]: within +/-10% or +/-5pp.claude/context/company-identity.mdl0_available: false, skip.Before writing the final file, verify:
schema_version is "2.3"element_instrumentation_state emitted; when partial/absent, missing_element_classes populated and instrumentation_ask non-null, section leads with the gap statement (no silent skip)event_liveness covers every configured event; dead bindings and dark/spiked zero-crossings surfaced; friction interactions populated (or "None detected")scope_applied, scope_method, scope_note); when scope_method is prefix, scope note + accuracy caveat in Property Overview and confidence capped accordinglyAfter writing the file, display:
Performance profile written to .claude/context/performance-profile.md
Report suite: [rsid] ([company_id])
Date range: [start] to [end] ([N] days)
Sessions: [N] | Users: [N] | Mobile: [N]%
Conversion events: [N] configured ([primary] as primary, [rate]% site-wide)
Traffic adequacy: [high/adequate/low]
Confidence: [N]
Comparison: [enabled, vs [start] to [end] | disabled (--no-compare)]
Scope: [segment | prefix (approximate) | whole-suite]
Element instrumentation: [present | partial | absent]
Measurement integrity: [N dead bindings, N dark/spiked, N friction]
Key findings:
- [top strength]
- [top weakness]
- [top experiment opportunity]
Run /hypothesis-generator to produce data-calibrated experiment hypotheses.
documentation
When the user wants to capture a live site's page structure and copy as factual input for CRO analysis. Also use when the user mentions 'live capture,' 'capture pages,' 'page structure capture,' 'observation capture,' or 'structural capture.' Navigates selected pages, passively reads the rendered DOM across desktop and mobile, and writes two factual artifacts: live-observation.md (structure) and live-copy.md (copy). Legacy mode writes L0 to .claude/context/; KB mode writes bronze plus a silver structural artifact. Facts only, no analysis.
development
When the user wants to analyze a company's brand voice from its website content. Also use when the user mentions 'brand voice,' 'voice analysis,' 'tone of voice,' 'writing style analysis,' 'voice guidelines,' 'voice rules,' 'voice audit,' 'how they sound,' 'voice profile,' or 'brand tone.' Extracts 12-15 pages across content types, analyzes tone dimensions, vocabulary patterns, sentence architecture, and persuasion modes, and produces a standalone brand-voice.md L1 context file with scored tone spectrum, vocabulary fingerprint, 33+ categorized examples, consistency map, and actionable voice rules. Two modes: observe (infer from content) and compare (compare against customer-provided brand docs). Auto-detects brand docs in context directory. Does NOT require positioning-framework to have been run first.
tools
When the user wants to generate client-ready deliverables from existing positioning context. Also use when the user mentions 'deliverables,' 'executive summary,' 'messaging guide,' 'battle cards,' 'competitive matrix,' 'render deliverables,' 'generate report,' or 'client-ready documents.' Reads L0 + L1 context files from .claude/context/ and produces polished, human-readable documents in .claude/deliverables/. No research, no analysis, no web fetches. Pure synthesis and formatting.
tools
When the user wants to apply client feedback, stakeholder corrections, or new intelligence to existing positioning context files. Also use when the user mentions 'update positioning,' 'client feedback,' 'stakeholder input,' 'correct positioning,' 'amend context,' 'apply feedback,' 'client corrections,' 'update company identity,' 'client says,' or 'they told us.' Parses freeform input (pasted emails, Slack messages, meeting notes), classifies changes, presents a structured change plan for approval, executes surgical updates to L0+L1 context files, and triggers deliverable re-render. No web research. Amendment skill, not research skill.