feat(skills): bulk-add root SKILL.md to 61 skills (native Agent Skills loadability)
Run the additive migration pass from SKILL-MIGRATION-GUIDE: generate a root SKILL.md for every skill that lacked one, copied from its desktop/SKILL.md (or code/SKILL.md), with name set to the directory name and description + body preserved verbatim. - scripts/migrate_skill_root.py: the reusable, non-destructive migrator (dry-run default). - 61 new root SKILL.md (desktop source for most; code/SKILL.md for 61/62/92). - Untouched: 16/17/95 (already had root); desktop/ and code/ packaging left intact. - All 64 root SKILL.md validate: frontmatter <=1024, kebab name, description present. Still MANUAL (no SKILL.md source — commands/README only), need hand-authored root SKILL.md: 81-mac-optimizer, 90-reference-curator, 91-multi-agent-guide, 94-dintel-bootstrap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
294
custom-skills/61-gtm-editor/SKILL.md
Normal file
294
custom-skills/61-gtm-editor/SKILL.md
Normal file
@@ -0,0 +1,294 @@
|
||||
---
|
||||
name: 61-gtm-editor
|
||||
description: >
|
||||
GTM implementation toolkit. Creates, updates, and modifies GTM tags, triggers,
|
||||
variables via API. Generates Custom HTML with ES5 compliance. Handles workspace
|
||||
lifecycle, DOM analysis for trigger design, and dataLayer code generation.
|
||||
Triggers on "create GTM tag", "generate dataLayer", "modify trigger", "update variable",
|
||||
"inject tracking code", "write custom HTML", "manage GTM", "design tags",
|
||||
"create conversion tag", "set up tracking".
|
||||
NOT for auditing (use gtm-audit) or validation/QA (use gtm-validator).
|
||||
---
|
||||
|
||||
# GTM Editor Skill
|
||||
|
||||
Create, modify, and deploy GTM configurations via API. Generates ES5-compliant Custom HTML tags.
|
||||
|
||||
## Available Tools
|
||||
|
||||
### GTM Container Management (DTM Agent MCP)
|
||||
- `dtm_status` — Check auth and active account/container
|
||||
- `dtm_set_account` / `dtm_set_container` — Switch context
|
||||
- `dtm_list_tags` / `dtm_get_tag` / `dtm_create_tag` / `dtm_update_tag` / `dtm_delete_tag`
|
||||
- `dtm_list_triggers` / `dtm_get_trigger` / `dtm_create_trigger` / `dtm_delete_trigger`
|
||||
- `dtm_list_variables` / `dtm_get_variable` / `dtm_create_variable` / `dtm_delete_variable`
|
||||
- `dtm_list_folders` / `dtm_list_workspaces` / `dtm_get_workspace_status`
|
||||
- `dtm_list_versions` / `dtm_get_live_version` / `dtm_create_version`
|
||||
|
||||
### Browser Analysis (Chrome DevTools MCP)
|
||||
- `navigate_page` — Load page for DOM analysis
|
||||
- `evaluate_script` — Inspect forms, buttons, links, CSS selectors
|
||||
- `take_snapshot` — Get page structure for trigger design
|
||||
- `list_network_requests` — Verify tags fire after changes
|
||||
|
||||
### Reporting (Notion MCP)
|
||||
- Write implementation plans and tag configurations to Notion
|
||||
|
||||
## Tagging Workflow: dataLayer First (MANDATORY)
|
||||
|
||||
**Always push complexity into the dataLayer, not GTM triggers.**
|
||||
|
||||
### Step 1: Design the dataLayer push FIRST
|
||||
|
||||
Before creating any GTM configuration, design the `dataLayer.push()` that the website should implement. Present this to the user as a code snippet matching their tech stack:
|
||||
|
||||
**For vanilla JS / static HTML (ES5):**
|
||||
```javascript
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({
|
||||
'event': 'generate_lead',
|
||||
'lead_type': document.getElementById('requestType').value,
|
||||
'form_id': 'contact-form'
|
||||
});
|
||||
```
|
||||
|
||||
**For React / Next.js / TypeScript:**
|
||||
```typescript
|
||||
declare global { interface Window { dataLayer: Record<string, any>[]; } }
|
||||
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({
|
||||
event: 'generate_lead',
|
||||
lead_type: selectedType,
|
||||
form_id: 'contact-form',
|
||||
});
|
||||
```
|
||||
|
||||
**For Vue:**
|
||||
```javascript
|
||||
window.dataLayer?.push({
|
||||
event: 'generate_lead',
|
||||
lead_type: this.selectedType,
|
||||
form_id: 'contact-form',
|
||||
});
|
||||
```
|
||||
|
||||
**For PHP / WordPress:**
|
||||
```php
|
||||
add_action('wp_footer', function() { ?>
|
||||
<script>
|
||||
document.getElementById('contact-form').addEventListener('submit', function() {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({
|
||||
'event': 'generate_lead',
|
||||
'lead_type': document.getElementById('requestType').value
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php });
|
||||
```
|
||||
|
||||
**ASK the user:** "Here's the dataLayer code for your developers. Can they add this to the page? What's your tech stack?"
|
||||
|
||||
### Step 2: Create simple GTM config
|
||||
|
||||
Once dataLayer push is agreed:
|
||||
- **Trigger:** Custom Event `{{_event}}` equals `generate_lead` (simple, robust)
|
||||
- **Variables:** DataLayer Variable for each parameter (e.g., `dlv - lead_type`)
|
||||
- **Tag:** GA4 Event with `measurementIdOverride` + DLV parameters
|
||||
|
||||
### Step 3: cHTML fallback (LAST RESORT)
|
||||
|
||||
Only if user confirms they CANNOT modify the website code:
|
||||
1. Create a Custom HTML tag that listens for DOM events
|
||||
2. The cHTML pushes to dataLayer internally
|
||||
3. A Custom Event trigger picks it up (same clean pattern)
|
||||
|
||||
**All cHTML must be ES5-compatible** (see ES5 section below).
|
||||
|
||||
### Decision Tree
|
||||
```
|
||||
Track user action?
|
||||
├─ Can devs add dataLayer.push()? → YES → Simple CE trigger ✅
|
||||
├─ Can't modify code, element has ID? → Use Click ID trigger ✅
|
||||
├─ No ID? → Ask user to add one first
|
||||
└─ Nothing possible? → cHTML + CE trigger (last resort)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### 1. Tag Creation via GTM API
|
||||
|
||||
Create tags directly in GTM — no manual pasting. The API handles workspace lifecycle automatically (auto-creates new workspace if current one is published).
|
||||
|
||||
**GA4 Event Tags:**
|
||||
```
|
||||
GA4 event tags use type "gaawe" with:
|
||||
- eventName: the GA4 event name (snake_case)
|
||||
- measurementIdOverride: {{GA4 Measurement ID variable}} (NOT measurementId)
|
||||
- eventParameters: list of name/value maps
|
||||
```
|
||||
|
||||
**CRITICAL: measurementIdOverride, NOT measurementId**
|
||||
GA4 event tags inherit from config — use `measurementIdOverride` to reference the measurement ID variable. The API rejects `measurementId`.
|
||||
|
||||
### 2. Trigger Design from DOM Analysis
|
||||
|
||||
Analyze page DOM to design precise triggers:
|
||||
|
||||
```javascript
|
||||
// Extract forms
|
||||
(function(){var forms=document.querySelectorAll('form');return JSON.stringify(Array.from(forms).map(function(f){return{id:f.id,action:f.action,fields:Array.from(f.querySelectorAll('input,textarea,select')).map(function(el){return{name:el.name,type:el.type,id:el.id}})}}));})()
|
||||
|
||||
// Extract CTAs and buttons
|
||||
(function(){return JSON.stringify(Array.from(document.querySelectorAll('a[class*="btn"],button,[role="button"]')).map(function(el){return{text:(el.textContent||'').trim().substring(0,50),href:el.href||'',classes:el.className,id:el.id}}));})()
|
||||
```
|
||||
|
||||
**Trigger types:**
|
||||
| Type | Use For | Key Parameter |
|
||||
|------|---------|---------------|
|
||||
| `linkClick` | `<a>` elements | `autoEventFilter` with CSS selector |
|
||||
| `click` | Any element | `filter` on Click ID/Classes |
|
||||
| `formSubmission` | Form submit | `filter` on Form ID |
|
||||
| `customEvent` | dataLayer events | `custom_event_filter` on `{{_event}}` |
|
||||
| `scrollDepth` | Scroll tracking | `verticalThresholdsPercent` |
|
||||
| `timer` | Time on page | `interval`, `limit` |
|
||||
| `domReady` | DOM loaded | No filter needed |
|
||||
|
||||
### 3. Custom HTML Generation (ES5 MANDATORY)
|
||||
|
||||
GTM Custom HTML uses a JavaScript compiler that does NOT support ES2020+.
|
||||
|
||||
**FORBIDDEN:**
|
||||
```javascript
|
||||
// NO: optional chaining, arrow functions, const/let, template literals,
|
||||
// destructuring, spread, async/await
|
||||
```
|
||||
|
||||
**REQUIRED:**
|
||||
```javascript
|
||||
// YES: var, function(){}, string concatenation, explicit property access
|
||||
(function() {
|
||||
var form = document.getElementById('contact-form');
|
||||
if (!form) return;
|
||||
form.addEventListener('submit', function() {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({
|
||||
'event': 'generate_lead',
|
||||
'lead_type': form.querySelector('#requestType').value
|
||||
});
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
### 4. Workspace Lifecycle Management
|
||||
|
||||
GTM workspaces become **read-only after publishing**. The DTM Agent auto-handles this:
|
||||
- If create/update fails with "Workspace is already submitted"
|
||||
- Auto-creates a new workspace
|
||||
- Retries the operation
|
||||
- Caches the workspace ID for subsequent calls
|
||||
|
||||
**You don't need to manage workspaces manually.** Just call `dtm_create_tag` etc. and it works.
|
||||
|
||||
### 5. DataLayer Code Generation
|
||||
|
||||
Generate dataLayer push code for common tracking scenarios:
|
||||
|
||||
**E-commerce:**
|
||||
```javascript
|
||||
// purchase event
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({ ecommerce: null }); // Clear previous
|
||||
window.dataLayer.push({
|
||||
'event': 'purchase',
|
||||
'ecommerce': {
|
||||
'transaction_id': /* order ID */,
|
||||
'currency': 'KRW',
|
||||
'value': /* total */,
|
||||
'items': [{ item_id: '', item_name: '', price: 0, quantity: 1 }]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Forms/Leads:**
|
||||
```javascript
|
||||
window.dataLayer.push({
|
||||
'event': 'generate_lead',
|
||||
'lead_type': /* form type */,
|
||||
'form_id': /* form element ID */
|
||||
});
|
||||
```
|
||||
|
||||
## Korean Market Patterns
|
||||
|
||||
| Context | Pattern |
|
||||
|---------|---------|
|
||||
| Currency | KRW (no decimals) |
|
||||
| Payment buttons | 장바구니, 결제하기, 주문하기, 문의하기 |
|
||||
| Payment methods | 카카오페이, 네이버페이, 토스, 신용카드 |
|
||||
| Platforms | Kakao Pixel (`track.kakao.com`), Naver Analytics (`wcs.naver.net`) |
|
||||
|
||||
## Supported Tag Types
|
||||
|
||||
| GTM Type | Name | Platform |
|
||||
|----------|------|----------|
|
||||
| `googtag` | Google Tag | GA4 config |
|
||||
| `gaawe` | GA4 Event | GA4 events |
|
||||
| `gclidw` | Conversion Linker | Google Ads |
|
||||
| `awct` | Ads Conversion | Google Ads |
|
||||
| `html` | Custom HTML | Any (Meta, PostHog, Kakao, etc.) |
|
||||
| `cvt_MQDKZ` | Clarity | Microsoft Clarity |
|
||||
| `cvt_WF3R3` | Amplitude | Amplitude |
|
||||
|
||||
## FB Conversions API Integration
|
||||
|
||||
When creating tags that should trigger FB CAPI:
|
||||
1. Check existing FBEventName mapping table (Regex Table variable)
|
||||
2. If your custom event isn't mapped, add a new rule: `event_name → FB_Standard_Event`
|
||||
3. Key mappings: `generate_lead → Lead`, `purchase → Purchase`, `page_view → PageView`
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Analyze**: Use Chrome DevTools to inspect the page (or receive from gtm-audit Mode D)
|
||||
2. **Design**: Determine events, triggers, variables needed
|
||||
3. **Create**: Use DTM Agent MCP tools to create in GTM
|
||||
4. **Verify**: Hand off to gtm-validator for QA
|
||||
5. **Document**: Write implementation details to Notion
|
||||
|
||||
## Trigger Design: IDs First (MANDATORY)
|
||||
|
||||
**Always prefer element `id` over CSS selectors.** IDs survive redesigns; CSS selectors break.
|
||||
|
||||
**Before creating ANY click/form trigger:**
|
||||
1. Check if the target element has an `id` attribute via Chrome DevTools `evaluate_script`
|
||||
2. **If it has an ID** → use `Click ID` or `Form ID` filter
|
||||
3. **If it does NOT have an ID** → **ASK the user** before falling back to CSS:
|
||||
> "This element doesn't have an ID. Can you add one to the website/app code?
|
||||
> Suggested: `id="[section]-[element]-[action]"` e.g., `id="hero-btn-consult"`"
|
||||
4. Only use CSS selectors when the user explicitly confirms they cannot modify the HTML
|
||||
|
||||
**Priority order:**
|
||||
1. Element ID (`Click ID equals "hero-btn-consult"`)
|
||||
2. Data attribute (`Click Element matches CSS [data-track="consult"]`)
|
||||
3. Form ID (`Form ID equals "contact-form"`)
|
||||
4. CSS selector (last resort — document which selectors are used)
|
||||
|
||||
**Suggested ID naming:** `[section]-[element]-[action]`
|
||||
- Navigation: `nav-about`, `nav-services`, `nav-insights`
|
||||
- CTAs: `hero-btn-consult`, `cta-btn-booking`
|
||||
- Forms: `contact-form`, `newsletter-form`
|
||||
- Footer: `footer-social-linkedin`, `footer-email`
|
||||
|
||||
## Rules
|
||||
|
||||
- Always use `dtm_status` first to verify auth and active container
|
||||
- Always use `measurementIdOverride` for GA4 event tags (NOT `measurementId`)
|
||||
- **Always prefer element IDs for triggers — ask user to add IDs before using CSS selectors**
|
||||
- All Custom HTML must be ES5-compatible
|
||||
- Use `--dry-run` conceptually — verify trigger selectors via Chrome DevTools before creating
|
||||
- Rate limit: space API calls 1 second apart in batch operations
|
||||
- Never delete tags without explicit user confirmation — move to Archive folder instead
|
||||
- After creating tags, suggest user run gtm-validator to verify
|
||||
Reference in New Issue
Block a user