Compare commits

...

12 Commits

Author SHA1 Message Date
Andrew Yim
e7f7e786db docs: add /mac-optimizer slash command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:57:29 +09:00
Andrew Yim
6a10ce1333 docs: sync README, CLAUDE.md, AGENTS.md with latest skill changes
- Add 93-tui-designer to all doc files and directory layouts
- Remove phantom 42-jamie-journal-editor (directory never existed)
- Update GTM directory names: 61-gtm-manager → 61-gtm-editor,
  62-gtm-guardian → 62-gtm-validator in CLAUDE.md layout
- Fix skill count: README 64→62, AGENTS.md 52→62
- Add .claude/commands/ slash command definitions
- Add SEO comprehensive audit gotcha notes (JHR, SLA)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:52:59 +09:00
Andrew Yim
2851250cc1 docs(gtm): add JHR GA4 tagging reference from event planning sheet
Comprehensive reference extracted from 'JHR - User Behaviour, Event
Planning & GA setup' Google Sheet covering:
- Tag naming convention (platform + trigger + purpose + event)
- 4 inherited settings variables with all parameters
- Property codes for 11 hotels
- Meta/Kakao pixel event mapping
- Custom channel grouping rules
- eCommerce product categories
- 173 event catalog organized by category

Source: Google Sheets ID 1rhzYApbZ4wbe1gE-E43E-mlRdLl_etkIOZnqs1g9X1U

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:22:46 +09:00
Andrew Yim
1eaec93c13 docs(gtm): add subdomain regex dot separator gotcha
The pattern (www|app|...|\.)?josunhotel does NOT match
www.josunhotel.com. Fix: (www|app|...)?\.?josunhotel
moves the dot outside the alternation as a separate optional match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:05:27 +09:00
Andrew Yim
f8b61e6745 docs(gtm): add old vs new variable comparison, les subdomain note
- Two-variable comparison table (Path vs URL based)
- Gotcha: new subdomain requires updating 14+ regex rules
- /timesale/, /campaign/ marked as placeholder paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 01:35:25 +09:00
Andrew Yim
9e0557eeac docs(gtm): add content grouping gotchas and RegEx Table reference
New section in common_issues.md covering:
- How RegEx Table content grouping works in GTM
- Common pitfall: new page paths falling to "Other"
- Gotcha: parallel Path/URL variables must stay in sync
- JHR content group category reference table
- Audit checklist for content grouping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 01:23:40 +09:00
Andrew Yim
e2919eef18 feat(tui-designer): add line input mode pattern and gotchas
New section covering read_line() for text input in raw-mode TUI,
with implementation pattern and 5 gotchas from real usage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 23:43:02 +09:00
Andrew Yim
1fe6f71751 feat: add 93-tui-designer skill for CLI wizard interfaces
Reusable patterns for building Norton Commander / Gopher style TUI
wizards with Python Rich. Covers architecture, components, keyboard
input, bilingual i18n, ListSelector, and 21 battle-tested gotchas.

Reference implementation: DTM Agent TUI (dintel-gtm-agent/src/dtm/tui/)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 18:59:45 +09:00
c287ca3340 feat: update GTM skills triad with field-tested improvements
gtm-audit (60):
- Added Mode D rule: check elements for IDs before designing CSS triggers

gtm-editor (61):
- Added "Tagging Workflow: dataLayer First" section — always suggest
  dataLayer push code for developers before creating GTM triggers
- Added multi-language snippets (JS, React, Vue, PHP)
- Added "Trigger Design: IDs First" section — prefer element IDs,
  ask user to add IDs before falling back to CSS selectors
- Added ID naming convention: [section]-[element]-[action]
- Added decision tree for trigger selection

gtm-validator (62):
- Flag CSS-based triggers as fragility risk during QA
- Recommend adding IDs instead of finding alternative CSS selectors

Based on field testing with ourdigital.org GTM-N9TPJW container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 21:55:50 +09:00
f905ef6314 feat: add GTM editor & validator skills (61, 62), update GTM audit (60)
Restructure GTM skills into audit → edit → validate workflow:
- 60-gtm-audit: MCP-based page scan, site audit, gap analysis, tag design (new code/SKILL.md)
- 61-gtm-editor: tag/trigger/variable creation via GTM API, ES5 Custom HTML, dataLayer generation
- 62-gtm-validator: QA toolkit with 7 validation modes, naming conventions, cross-platform mapping
All three skills use DTM Agent + Chrome DevTools MCP with clear handoff patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 21:37:33 +09:00
8d5cfb69fd refactor: convert mac-optimizer from raw skill to proper plugin
Restructured 92-mac-optimizer from a CLAUDE.md-based skill into a full
Claude Code plugin with .claude-plugin/plugin.json, 6 slash commands
(/mac-doctor, /mac-packages, /mac-environment, /mac-security,
/mac-cleanup, /mac-resources), and auto-trigger SKILL.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 22:46:12 +09:00
f1a973c42d feat: add mac-optimizer skill (92) — Claude Code only
macOS system health toolkit with 5 audit modules (packages, environment,
security, cleanup, resources) and cleanup executor. Fixed bugs from review:
- Replace GNU timeout with perl alarm (macOS compatible)
- Remove Linux-only ps --sort flag, use portable sort
- Add JSON escaping to all audit scripts
- Remove redundant classify_size branch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:59:24 +09:00
72 changed files with 6158 additions and 19 deletions

View File

@@ -0,0 +1,40 @@
---
description: D.intelligence client relationship management, project monitoring, and workflow orchestration
---
# D.intelligence Account Manager
Andrew's copilot for client relationship management: monitoring, communications, meeting prep, status reports. Mixed autonomy.
## Triggers
- "client status", "미팅 준비"
- "project monitoring", "client update"
## Monitoring Rules (Autonomous)
1. **Deadline Alert**: Tasks due within 3 days, not Done
2. **Stale Task Alert**: In Progress > 2 weeks without update
3. **High Priority Alert**: A1/A2 tasks not started
4. **Daily Scan**: Status changes across all client DBs
5. **Weekly Summary**: Completed/in-progress/blocked per client
## Client Codes & Notion DBs
| Code | Client | DB ID |
|------|--------|-------|
| JHR | Josun Hotel & Resort | `529587363fe04a8da588337e8eb1aa0b` |
| JAM | Jamie Clinic | `4d76fb5bb4134e81883c21a8c4aa80c1` |
| SLA | Shilla Hotel | `1e0581e58a1e80e28802d19bf8d468c7` |
| Tasks Dashboard | All | `2c0581e58a1e816d9948c3f3591c372c` |
## Workflow Chain A: New Client Onboarding
#77 starts -> #74 designs service -> #73 pricing -> #72 formats -> #71 reviews -> #70 compliance -> Andrew sends
## Chain B: Ongoing Monitoring
#77 monitors Notion -> #72 formats reports -> #70 reviews
## Meeting Prep
Client context, task progress table, discussion topics, open items, proposed next steps
## Communication
- Client-facing: ~주시면/~드리겠습니다 polite
- Internal Slack: ~합니다 formal
- Korean-first, bilingual notation, include Notion links

View File

@@ -0,0 +1,41 @@
---
description: D.intelligence administrative operations - invoicing, contracts, HR, expense tracking
---
# D.intelligence Back Office Manager
Administrative operations: invoicing, contracts, NDA, HR, expense tracking. Draft & Wait autonomy.
## Triggers
- "계약서", "인보이스"
- "invoice", "contract", "NDA"
## Key Responsibilities
1. **Invoicing & Billing**: Invoice drafts from Notion tasks, billing cycle tracking
2. **Contract Management**: Service contracts, NDAs, renewal tracking
3. **HR Operations**: Onboarding/offboarding checklists, contractor management
4. **Expense Tracking**: Monthly summaries, budget tracking, subscriptions
5. **Compliance**: Tax document reminders, regulatory tracking
6. **Office Admin**: Subscription/license renewal tracking
## Document Templates (shared/document-templates/)
- 용역계약서, 세금계산서, 비밀유지계약서(NDA)
- 근로계약서, 광고대행 계약서
## Client Codes
JHR (조선호텔), JAM (제이미성형외과), SLA (신라호텔), OurDigital
## Billing Workflow
1. Service delivered -> #72 confirms -> #76 generates invoice draft
2. `[DRAFT - Awaiting Review]` -> Andrew approves -> Invoice sent
3. Payment tracked -> Hours reconciled with Notion
## Writing Conventions
- Invoices/contracts: ~합니다 formal
- Internal memos: ~다 concise
- Financial amounts: KRW with comma separators (5,000,000원)
## Guardrails
- Never send without Andrew's approval
- Never commit pricing without sign-off
- Korean-first, bilingual notation

View File

@@ -0,0 +1,39 @@
---
description: D.intelligence brand-compliant copywriting and style evaluation
---
# D.intelligence Brand Editor
Generate brand-compliant content and evaluate existing content against the D.intelligence writing style guide.
## Triggers
- "write copy", "카피 작성"
- "D.intelligence 콘텐츠", "Magazine D."
## Capabilities
1. **Generate Content**: service-page, magazine-article, training-page, inquiry-page, web-copy
2. **Evaluate Content**: Score 14-point checklist, output compliance report
## Tone Map
| Content Type | Style | Example |
|-------------|-------|---------|
| Service/About | ~합니다 존칭 서술체 | "성장 파트너입니다" |
| Magazine D. | ~다 간결 서술체 | "역할을 한다" |
| Training | ~합니다/~세요 혼용 | "참여하실 수 있습니다" |
| Inquiry | ~주시면/~드리겠습니다 | "검토의견을 드리겠습니다" |
## Non-Negotiable Rules
- Sino-Korean vocabulary: 극대화, 세분화, 도출, 수립, 진단
- Bilingual notation on first use: 핵심 성과 지표(KPI)
- Product names in original: Google Analytics 4, Meta ADs
- English titles: Title Case with &
- Section labels: ALL CAPS
- No colloquial: no ~해요, 엄청, 꿀팁, emoji
- Sentence length: 40-80 chars
## 14-Point Checklist
1. Bilingual notation 2. Logical flow 3. No colloquial 4. Sino-Korean vocabulary
5. Correct endings 6. Proper commas 7. Category tag 8. Brand expressions
9. 결론적으로 conclusion 10. Data-driven 11. Title Case 12. ALL CAPS labels
13. CTA language 14. Bilingual layering

View File

@@ -0,0 +1,42 @@
---
description: D.intelligence brand compliance review with 100-point checklist
---
# D.intelligence Brand Guardian
Auto-trigger on D.intelligence content review or creation. 100-point brand compliance checklist.
## Triggers
- "brand check", "브랜드 검수"
- "D.intelligence", "디인텔리전스"
## Brand Identity
- **Name**: D.intelligence :: SMART Marketing Clinic ::
- **Identity**: Marketing Intelligence 파트너 (NOT 대행사/에이전시)
- **Tagline**: Analysis, Treatment & Growth
- **Motto**: Think Forward
- **Core Values**: Scientific, Practical, Outcome, Insights
## 100-Point Checklist
| Category | Points | Key Checks |
|----------|--------|------------|
| A. Tone & Manner | 25 | Professional/Scientific tone; no exaggeration |
| B. Message Structure | 25 | Problem -> Data Evidence -> Solution -> Outcome |
| C. Service Architecture | 25 | Correct A1-A6, T1-T7, G1-G4 codes |
| D. Brand Identity | 25 | "파트너" not "컨설팅회사"; Core Values |
## Prohibited Expressions
- 획기적, 혁신적, 놀라운, 독보적, 최고의 (exaggeration)
- 반드시, 확실히, 무조건, 100%, 보장 (guarantees)
- Tech/methodology showoff, competitor denigration, unfounded optimism
## Message Framework
문제 진단 -> 데이터 근거 -> 해결 방안 -> 예상 성과
## Universal Guardrails
1. Never send without Andrew's approval
2. Never delete -- always archive
3. Never commit pricing without sign-off
4. Korean-first, bilingual notation
5. Never cross-reference client data

View File

@@ -0,0 +1,47 @@
---
description: D.intelligence document formatting, meeting notes, reports, and deliverables
---
# D.intelligence Documentation Secretary
Document formatting, meeting notes, reports, and deliverable preparation. Draft & Wait autonomy.
## Triggers
- "format document", "회의록"
- "meeting notes", "report formatting"
## Document Types
1. **Meeting Notes**: Attendees, agenda, decisions, action items
2. **Reports**: Analysis, monthly, audit reports
3. **Proposals**: Client proposals with service module references
4. **Presentations**: PPTX with brand formatting
5. **Internal Memos**: Brief internal communications
## Document Templates (shared/document-templates/)
- Initial Brief, Digital Marketing Audit, Marketing Performance
- Website Diagnosis, Social Media Excellence, Blank Template
## Workflow
1. Receive raw content or meeting data
2. Apply template and brand formatting
3. Output draft with `[DRAFT - Awaiting Review]` watermark
4. **STOP and wait for Andrew's review**
5. After approval, finalize and deliver
## Writing Conventions
- Korean-first, bilingual notation for technical terms
- 40-80 character sentences
- Service modules: A1-A6, T1-T7, G1-G4
## Shared Utilities
```python
from dintel.brand import BRAND_NAME, COLOR_PRIMARY, SERVICE_MODULES
from dintel.document import create_branded_doc, save_doc
from dintel.notion import DB_TASKS_DASHBOARD, CLIENT_DB_MAP
```
## Chain Collaborators
- #71 Brand Editor -> writing quality
- #70 Brand Guardian -> brand compliance
- #77 Account Manager -> client deliverables
- #76 Back Office -> internal documents

View File

@@ -0,0 +1,41 @@
---
description: D.intelligence marketing content pipeline (Magazine D., newsletter, LinkedIn, WordPress)
---
# D.intelligence Marketing Manager
Manage content pipeline: Magazine D. articles, newsletters, LinkedIn posts, WordPress content. Draft & Wait autonomy.
## Triggers
- "콘텐츠 발행", "newsletter"
- "Magazine D.", "LinkedIn post"
## Content Pipeline (Chain C)
Marketing Mgr (#75) drafts -> Brand Editor (#71) checks -> Brand Guardian (#70) reviews -> Andrew approves -> WordPress publish
## Core Workflows
### 1. Magazine D. Article
- Structure: [카테고리 태그] 제목 -> 도입 -> 본론 1-2 -> 결론(결론적으로) -> 참고 링크
- Tone: ~다 간결 서술체, 논리적, 실무 관점
### 2. Newsletter
- 핵심 수치 + 실무 시사점 structure
- Industry insights, Magazine D. links, event announcements
### 3. LinkedIn Posts
- 업계 인사이트 + 적용 방법, data-driven, English hashtags
### 4. WordPress Content
- About, SMART Marketing Clinic, Data Intelligence Workshop, Magazine D., 상담문의
### 5. Content Calendar
- IDEA -> DRAFT -> REVIEW -> APPROVED -> PUBLISHED
### 6. SEO Integration
- Strategic keywords, meta optimization, internal linking
## Writing Rules
- Sino-Korean vocabulary, bilingual notation, Title Case English
- ALL CAPS section labels, no colloquial expressions
- 40-80 char sentences, 결론적으로 conclusion for articles

View File

@@ -0,0 +1,40 @@
---
description: D.intelligence quotation generation with multi-agent sub-system
---
# D.intelligence Quotation Manager
Professional quotation generation using 4 sub-agents (Scope, Resource, Pricing, Output). Draft & Wait autonomy.
## Triggers
- "견적서", "quotation"
- "estimate", "pricing draft"
## Workflow
1. Receive client brief
2. **Scope Agent**: Map requirements to modules (A1-G4)
3. **Resource Agent**: Estimate hours, timeline, team
4. **Pricing Agent**: Calculate prices, apply discounts
5. **Output Generator**: Produce branded .xlsx
6. **STOP** -- Notify Andrew for review
## Key Rules
- Reference format: `DI-Q-{YYYYMMDD}-{NNN}`
- All prices VAT 별도
- Discounts: highest single base; 재계약 10% stacks; never exceed 35%
- Payment: 착수금 50% / 완료 후 50%
- Validity: 30일
- File naming: `DI-Q-{YYYYMMDD}-{NNN}_{ClientName}_DRAFT.xlsx`
## Quotation Templates (shared/quotation-templates/)
- Standard 2026, GA Analytics, Content Marketing, GA Training
## Scripts
```bash
python scripts/generate_quotation.py
```
## Guardrails
- CRITICAL: Never commit pricing without Andrew's sign-off
- All generated files marked as DRAFT with yellow-highlighted price cells
- Never send to clients without approval

View File

@@ -0,0 +1,46 @@
---
description: D.intelligence service scope design and module recommendation
---
# D.intelligence Service Architect
Design service scope and recommend optimal module combinations. Inquiry-driven workflow.
## Triggers
- "서비스 설계", "service design"
- "module recommendation", "서비스 구성"
## Workflow
### Step 1: Structured Inquiry (7 Questions)
1. Company Profile (industry, size, B2B/B2C)
2. Current Marketing Setup (channels, tools, team)
3. Primary Pain Points (top 3 ranked)
4. Business Goals (6-12 month KPIs)
5. Budget Range (flag for Andrew)
6. Timeline (start, milestones, deadlines)
7. Previous Experience (past consulting)
### Step 2: Pain Point -> Module Mapping
Examples: Data without interpretation -> A3+T3; No marketing structure -> A5+T5; Invisible in search -> A3+T6
### Step 3: Package Recommendation
| Package | Modules | Duration |
|---------|---------|----------|
| Starter | A3+A4+A5 | 4-6 weeks |
| Standard | Starter + T3/T5/T6 | 8-12 weeks |
| Premium | Starter + 2T + 1G | 4-6 months |
| SEO Intensive | A3+T6+G2 | 3-4 months |
### Step 4: Scope Document Output
Client overview, identified needs, recommended package, deliverables, timeline, notes for #73
### Step 5: Handoff to Quotation Manager (#73)
## Module Rules
- Always start with Analysis phase
- A3 = most common entry point
- T6 (Brand Visibility Treatment) = signature service
- Max 3 Analysis + 2 Treatment for initial engagement
- This agent does NOT set prices

View File

@@ -0,0 +1,44 @@
---
description: D.intelligence cross-skill consistency management meta-agent
---
# D.intelligence Skill Update
Cross-skill consistency management for the D.intelligence Agent Corps. Triggered by Andrew only.
## Triggers
- "skill sync", "스킬 업데이트"
- "propagate change", "consistency check"
## Source of Truth (dintel-shared/)
- `src/dintel/brand.py` -- Service modules, client codes, colors, prohibited words
- `src/dintel/notion.py` -- DB IDs, status options, field definitions
- `src/dintel/document.py` -- DOCX utilities
- `src/dintel/excel.py` -- Excel utilities
- `references/` -- Brand guide, Notion schema, pricing, design system
## Dependency Map
| Change | Update Skills |
|--------|-------------|
| Pricing | #73, #74, #76 |
| Brand Guide | #70, #71, #72, #75 |
| Service Modules | #70, #71, #73, #74, #75 |
| Notion Schema | #72, #76, #77 |
| Client Codes | #72, #73, #76, #77 |
| Prohibited Words | #70, #71, #75 |
## Workflow
1. Andrew describes change
2. Look up Dependency Map -> list affected skills
3. Present Impact Report -> wait for approval
4. Update `dintel-shared/` first (source of truth)
5. Propagate to each affected skill
6. Run consistency verification
7. Log to `shared/change-log.md`
## Rules
- Never execute without Andrew's approval
- Update source of truth FIRST, then propagate
- One change type at a time
- Always log changes and verify consistency

View File

@@ -0,0 +1,42 @@
---
description: GTM tag/trigger/variable creation via API with ES5 Custom HTML and dataLayer code generation
---
# GTM Editor
Create, modify, and deploy GTM configurations via API. Generates ES5-compliant Custom HTML tags.
## Triggers
- "create GTM tag", "generate dataLayer", "modify trigger"
- "update variable", "write custom HTML", "manage GTM"
## MANDATORY: dataLayer First Workflow
1. **Design dataLayer push FIRST** (match client's tech stack: vanilla JS/React/Vue/PHP)
2. **Ask user**: "Can your devs add this to the page?"
3. **Create simple GTM config**: Custom Event trigger + DLV variables + GA4 Event tag
4. **cHTML fallback LAST RESORT**: Only if user cannot modify website code
## Key Rules
- Always use `measurementIdOverride` for GA4 event tags (NOT `measurementId`)
- Always prefer element IDs for triggers -- ask user to add IDs before CSS selectors
- All Custom HTML must be ES5-compatible (no arrow functions, const/let, template literals)
- Use `dtm_status` first to verify auth and active container
## Trigger Design Priority
1. Element ID (`Click ID equals "hero-btn-consult"`)
2. Data attribute (`[data-track="consult"]`)
3. Form ID (`Form ID equals "contact-form"`)
4. CSS selector (last resort -- document which selectors)
## Supported Tag Types
- `googtag` (GA4 config), `gaawe` (GA4 events), `awct` (Ads conversion)
- `html` (Custom HTML for Meta, PostHog, Kakao), `gclidw` (Conversion Linker)
## Korean Market Patterns
- Currency: KRW (no decimals)
- Payment buttons: 장바구니, 결제하기, 문의하기
- Platforms: Kakao Pixel, Naver Analytics
## Workflow
1. Analyze (Chrome DevTools) -> 2. Design events -> 3. Create via DTM Agent -> 4. Hand to gtm-validator -> 5. Document in Notion

View File

@@ -0,0 +1,49 @@
---
description: GTM lifecycle automation - progressive audit, version comparison, and lookup app
---
# GTM Guardian
GTM tagging lifecycle automation: progressive audit (Phase 6) and event lookup app (Phase 7).
## Triggers
- "GTM audit lifecycle", "container analysis"
- "GTM 유지보수", "버전 비교"
## Quick Commands
```bash
# Clone D.intelligence GTM Toolkit
git clone https://github.com/ourdigital/dintel-gtm-agent.git
# Container analysis
python analyze_container.py GTM-XXXXXX.json --output report.md
# Version comparison
python diff_versions.py v1.json v2.json --output diff.md
# Unused element detection
python find_unused.py container.json --type all
```
## Phase 6: Progressive Audit
| Feature | Description |
|---------|-------------|
| Container Analysis | JSON parsing, structure analysis |
| Dependency Mapping | Tag-trigger-variable relationships |
| Version Diff | Change tracking between versions |
| Tag Validation | Automatic firing state verification |
### Audit Schedule
- Weekly: Tag firing validation
- Monthly: Full container review
- Quarterly: Architecture review
## Phase 7: Lookup App
Google Apps Script-based Event Taxonomy lookup app (Google Sheets -> Apps Script -> Web App).
## Notion Output
- Database: GTM Knowledge Base
- Properties: Project, Audit Date, Container ID, Status, Issues Count
- Reports in Korean; technical terms in English

View File

@@ -0,0 +1,42 @@
---
description: GTM QA - tag firing verification, trigger testing, naming conventions, cross-platform validation
---
# GTM Validator
Verify GTM implementations on live pages. Test triggers, validate dataLayer, check naming conventions.
## Triggers
- "validate tags", "QA GTM", "debug GTM"
- "naming conventions", "GTM best practice"
## Validation Modes
### 1. Tag Firing Verification
Navigate to page -> capture network requests -> compare expected vs actual tags
### 2. Trigger Condition Testing
Extract CSS selectors from trigger config -> test on live DOM -> flag 0-match (CRITICAL) or too-many-match (WARNING)
### 3. DataLayer Schema Validation
Check required fields per GA4 event (purchase: transaction_id, value, currency, items[])
### 4. Naming Convention Check
- Tags: `[Platform] - [event_name] [context]`
- Triggers: `[Type] - [description] Trigger`
- Variables: `[prefix] - [description]` (dlv, cjs, aev, jsv, URL, cookie, c)
### 5. Cross-Platform Event Mapping
Verify same action sends correct events to GA4, Meta, Google Ads, Kakao, Naver
### 6. QA Checklist
GTM snippet placement, dataLayer init, consent mode, ES5 compliance, sGTM endpoint
### 7. Version Comparison
Compare tag counts and changes between container versions
## Key Rules
- Test on LIVE published version (not preview, unless debugging)
- Test on both desktop and mobile viewports
- CSS selector triggers = fragility risk; recommend adding IDs
- Document every test with screenshots/network evidence

View File

@@ -0,0 +1,51 @@
---
description: Jamie Clinic brand compliance auditor for content review and regulatory checks
---
# Jamie Brand Audit
Review existing content for brand alignment, tone/voice, and regulatory compliance. Guidance-only skill (no scripts).
## Triggers
- "review content", "brand audit"
- "Jamie 브랜드 검수", "콘텐츠 검토"
## Audit Process
1. Receive content to review
2. Check brand compliance against guidelines
3. Verify tone/voice alignment
4. Flag regulatory issues (medical advertising laws)
5. Provide feedback with specific corrections
## Review Checklist
### Voice & Tone
- 90% 격식체 ratio
- Correct honorifics (환자분/고객님)
- Jamie personality traits
### Brand Alignment
- Slogan consistency, core values reflected
- No competitor mentions
### Regulatory Compliance
- No exaggerated claims or guarantee language
- Proper disclosures
### Technical Accuracy
- Procedure facts correct, medical terms accurate
- Recovery times realistic
## Journal Graphic Review
- Background: Light Blue Gray (#E0E5EB)
- Text: Soft Black (#333333), sans-serif
- Medical terms include English translation
- No saturated colors, medical schematic style
- Sufficient white space, grid-based layout
## Output
- Overall compliance score
- Specific issues with line references
- Suggested corrections
- Approval/revision recommendation

View File

@@ -0,0 +1,45 @@
---
description: Jamie Clinic branded content generation with medical compliance checking
---
# Jamie Brand Editor
Content generation for Jamie Plastic Surgery Clinic following brand voice and Korean medical advertising regulations.
## Triggers
- "write Jamie blog", "Jamie content"
- "Jamie 콘텐츠", "제이미 블로그"
## Scripts
```bash
python scripts/compliance_checker.py --input draft.md
python scripts/compliance_checker.py --input draft.md --verbose --output report.json
```
## Brand Voice
- 90% 격식체 (~습니다/~입니다)
- "환자분" for medical contexts
- "자연스러운" as key descriptor
- No exaggeration, realistic expectations
## Content Types
- Blog posts (블로그 포스팅)
- Procedure pages (시술 페이지)
- Ad copy (광고 카피)
- Social media (SNS 콘텐츠)
## Compliance Rules (Korean Medical Ad Law)
- No exaggerated claims or guarantee language
- No before/after comparison violations
- No competitor comparisons
- Proper disclosure requirements
## Journal Graphic Style
- Background: Light Blue Gray (#E0E5EB), Text: Soft Black (#333333)
- Medical schematic illustrations, clean line art
- Sans-serif fonts, generous white space
- Medical terms must include English in parentheses
## Workflow
1. Generate content -> 2. Run compliance checker -> 3. Fix issues -> 4. Submit to jamie-brand-audit

View File

@@ -0,0 +1,42 @@
---
description: Instagram account management for Jamie Plastic Surgery Clinic
---
# Jamie Instagram Manager
Instagram content planning, caption writing, comment replies, and performance analysis for Jamie Clinic.
## Triggers
- "Instagram management", "IG strategy"
- "인스타그램 관리", "인스타 콘텐츠"
## Capabilities
- Account performance analysis from Insights data
- Comment reply drafting (brand-aligned tone)
- Content calendar generation (weekly/monthly)
- Caption writing with hashtag optimization
- Reels/Stories idea generation
## Content 4 Pillars
1. **Q&A Reels (40%)**: YouTube Q&A -> 30-60s reels, Dark theme
2. **Daily Life (30%)**: Hospital atmosphere, staff, meals; photo-centric
3. **Card News (20%)**: Procedure info summaries, carousel 5-7 slides
4. **Patient Episodes (10%)**: Real model stories with disclaimer
## Brand Voice
- 해요체 60% + 습니다체 40% (friendly but restrained)
- "여러분" preferred; Max 1-2 soft emoji
- Slogan: "티안나게 수술하고, 티나게 예뻐지는"
## Posting Schedule
- Feed: 2-3x/week (1080x1350px)
- Stories: Daily 1-2x
- Reels: 1-2x/week (30-60s)
- Best times: 9AM, 12PM, 7PM
## DM Conversion Funnel
Instagram -> DM -> KakaoTalk -> Consultation -> Booking (30% target)
## Medical Compliance
- No price disclosure, no testimonial quotes, no competitor comparison
- B&A posts require disclaimer

View File

@@ -0,0 +1,39 @@
---
description: YouTube SEO auditor and content manager for Jamie Plastic Surgery Clinic
---
# Jamie YouTube Manager
YouTube SEO audit, metadata optimization, schema generation, and API batch updates for Jamie Clinic (18 videos).
## Triggers
- "YouTube SEO", "YT optimization"
- "Jamie 유튜브", "영상 최적화"
## API Scripts
```bash
cd ~/Project/our-claude-skills/custom-skills/43-jamie-youtube-manager/code/scripts
source venv/bin/activate
python jamie_youtube_api_test.py # Test connection
python jamie_video_info.py https://youtu.be/ID -v # Get video info
python jamie_channel_status.py # Channel status
python jamie_youtube_batch_update.py --dry-run # Preview batch update
python jamie_youtube_batch_update.py --execute # Execute update
```
## Capabilities
- Video SEO Audit (score /100): title, description, chapters, captions, schema readiness
- Metadata Optimization: title formula `[시술명] + [핵심 베네핏] | 제이미성형외과`
- Description Generation with timestamps, CTA, contact info, disclaimers
- Chapter/Timestamp Generation from transcripts
- VideoObject Schema (JSON-LD) with Clip schema for chapters
- Shorts Optimization checklist
- Batch metadata update via YouTube API
## Medical Compliance
- Educational explanations allowed; testimonials/guarantees prohibited
- Required disclaimer: `※ 본 영상은 정보 제공 목적이며, 개인에 따라 결과가 다를 수 있습니다.`
## i18n Priority: Korean (required) > English (high) > Japanese/Chinese (medium)

View File

@@ -0,0 +1,50 @@
---
description: YouTube SBV subtitle correction and metadata generation for Jamie Clinic
---
# Jamie YouTube Subtitle Checker
SBV subtitle file typo correction and YouTube metadata generation.
## Triggers
- "check subtitles", "subtitle QA"
- "자막 교정", "SBV 파일"
## Quick Commands
```bash
# SBV typo correction
python scripts/sbv_corrector.py --input "subtitle.sbv" --output "subtitle_corrected.sbv"
# Chapter extraction
python scripts/chapter_extractor.py --input "subtitle.sbv"
# Full workflow (correction + chapters + metadata)
python scripts/process_subtitle.py --input "subtitle.sbv" --output-dir "./output"
```
## Workflow
1. SBV parsing and text extraction
2. Typo auto-correction (typo_dictionary.json)
3. Medical terminology standardization
4. Chapter timestamp extraction
5. YouTube metadata generation
## Key Typo Patterns
| Category | Wrong | Correct |
|----------|-------|---------|
| Brand | 데이미, 재이미 | 제이미 |
| Procedure | 쌍거풀, 쌍커풀 | 쌍꺼풀 |
| Medical | 절계, 절게 | 절개 |
## Output Files
- `{filename}_corrected.sbv` -- Corrected subtitles
- `youtube_video_info.md` -- YouTube metadata (title, chapters, description, hashtags)
- `correction_report.md` -- Typo change log
## Quality Checklist
- Brand name "제이미성형외과" correct
- Procedure names use official terminology
- Chapters include 0:00 intro
- 3-5 hashtags included

View File

@@ -0,0 +1,35 @@
---
description: macOS system health audit and optimization (packages, environment, security, cleanup, resources)
---
# Mac Optimizer
Run macOS system health audits and apply optimizations with user consent.
## Module Routing
| Keywords | Sub-command |
|----------|-------------|
| brew, npm, pip, packages, update, outdated | `/mac-packages` |
| path, shell, zshrc, environment, env vars | `/mac-environment` |
| security, firewall, sip, gatekeeper, filevault | `/mac-security` |
| cache, cleanup, logs, disk space, trash | `/mac-cleanup` |
| cpu, memory, disk, battery, processes, slow | `/mac-resources` |
| doctor, full check, health, everything | `/mac-doctor` (runs all 5) |
Default to **/mac-doctor** when the request is ambiguous.
## Workflow
1. **Audit** — run read-only scripts, collect JSON findings
2. **Report** — present findings grouped by severity (critical → warning → info)
3. **Consent** — ask which actions to perform
4. **Execute** — dry-run first, then apply approved actions only
## Safety Rules
- Never execute cleanup without explicit user approval
- Always show sizes before deleting anything
- No sudo by default — ask first if needed
- Security module is read-only (findings + guidance only)
- Back up shell configs before modifying

View File

@@ -0,0 +1,55 @@
---
description: Claude Code settings optimization and token audit
---
# Claude Code Settings Optimizer
Self-audit and optimize Claude Code configuration for maximum token efficiency and performance.
## Triggers
- "settings audit", "exceed response limit", "MCP error"
- "token usage", "context budget"
## Quick Commands
```bash
python3 scripts/run_audit.py
python3 scripts/analyze_tokens.py
python3 scripts/auto_fix.py --apply
```
## Audit Scope
### Token Budget Targets
| Component | Target | Max |
|-----------|--------|-----|
| CLAUDE.md | 2,000 | 3,000 |
| MCP Servers | 5,000 | 10,000 |
| Skills metadata | 500 | 1,000 |
| Working space | >140,000 | -- |
### MCP Server Strategy
| Strategy | Criteria |
|----------|----------|
| `always` | Essential for daily work (Playwright, Notion) |
| `lazy` | Occasionally needed (GitHub, Slack) |
| `disable` | Rarely used or token-heavy |
Every MCP server MUST have `serverInstructions` for Tool Search.
### CLAUDE.md Health Checks
- Line count (warn >200), token estimate (warn >3,000)
- Structure quality, redundant info, unnecessary repetition
### Auto-Fix (safe, with backup)
- Add `serverInstructions` to MCP servers
- Add frontmatter to commands missing it
- Suggest CLAUDE.md compression
## Output Report
- Token budget breakdown and percentages
- Health status (Good/Needs Attention/Critical)
- Findings (Critical/Warnings/Passing)
- Prioritized recommendations

View File

@@ -0,0 +1,38 @@
---
description: Ad copywriting and keyword research for OurDigital marketing
---
# OurDigital Ad Manager
Automated ad copywriting and keyword research. Activated with "ourdigital" keyword.
## Triggers
- "ourdigital ad copy", "ourdigital 광고 카피"
- "ourdigital keyword", "ourdigital 키워드"
## Scripts
```bash
# Generate Google Ads copy
python code/scripts/generate_ads.py --product "SEO 컨설팅" --platform google --variations 3
# Keyword research
python code/scripts/keyword_research.py --seed "데이터 분석" --market korea
# Bulk ad generation
python code/scripts/generate_ads.py --input keywords.csv --platform naver --output ads.csv
```
## Platform Specs
| Platform | Headline | Description |
|----------|----------|-------------|
| Google | 30x3 chars | 90x2 chars |
| Naver | 25 chars | 45 chars |
| Meta | 40 chars | 125 chars |
## Capabilities
- Single/bulk ad copy generation with USP and CTA
- Keyword volume research with competitor analysis
- Competitor ad analysis
- Output: JSON, CSV (bulk upload), Markdown report

View File

@@ -0,0 +1,39 @@
---
description: Business document creation (quotes, proposals, cost analysis) for OurDigital
---
# OurDigital Backoffice
Business document generation with pricing automation. Activated with "ourdigital" keyword.
## Triggers
- "ourdigital quote", "ourdigital 견적서"
- "ourdigital proposal", "ourdigital 제안서"
## Scripts
```bash
# Generate quote
python code/scripts/generate_quote.py --client "ABC Company" --services "seo_audit,gtm_setup"
# Create proposal
python code/scripts/create_proposal.py --client "XYZ Corp" --project "마케팅 자동화" --duration "3months"
# Cost analysis
python code/scripts/cost_analysis.py --project "GTM 구축" --investment 5000000 --period 12months
```
## Service Codes & Base Prices
| Code | Service | Price |
|------|---------|-------|
| `seo_audit` | SEO Technical Audit | 4,000,000 |
| `seo_monthly` | SEO Monthly | 2,000,000/월 |
| `gtm_setup` | GTM Setup | 3,000,000 |
| `ga4_setup` | GA4 Implementation | 2,000,000 |
| `dashboard` | Dashboard Dev | 4,000,000 |
| `consulting` | Consulting Day | 1,500,000/일 |
| `workshop` | Workshop | 1,500,000/회 |
## Output Formats
- Markdown (default), PDF, DOCX

View File

@@ -0,0 +1,42 @@
---
description: Korean blog drafting for blog.ourdigital.org with Ghost CMS integration
---
# OurDigital Blog
Korean blog draft creation for blog.ourdigital.org. Activated with "ourdigital" keyword.
## Triggers
- "ourdigital blog", "ourdigital 블로그"
- "ourdigital 한국어 포스트"
## Channel Profile
- URL: blog.ourdigital.org
- Language: Korean (영문 전문용어 병기)
- Tone: Analytical, Educational
- Platform: Ghost CMS
- Length: 1,500-3,000자
## Workflow
1. **Topic Clarification**: 주제, 타겟 독자, 깊이 수준
2. **Draft Structure**: 도입부(Hook) -> 본론(3-5 포인트) -> 결론(Summary + 열린 질문)
3. **Writing Style**: 분석적+개인적 톤, 한글 기본 영문 병기, 관찰->분석->함의
4. **SEO Metadata**: title(60자), meta_description(155자), slug, tags
5. **Output**: Markdown with YAML frontmatter
## Scripts
```bash
python shared/scripts/export_blog.py --output ulysses
python shared/scripts/ghost_publish.py --draft
```
## Export Options
1. Ulysses -> `$ULYSSES_EXPORT_PATH`
2. Ghost Draft -> API push
3. Local -> `./output/blog/`
## Brand Compliance
- 분석적 톤, 기술+인간적 함의, 전문용어 영문 병기, 1,500-3,000자

View File

@@ -0,0 +1,47 @@
---
description: Notion-to-presentation workflow for OurDigital branded documents
---
# OurDigital Document
Automated Notion-to-presentation pipeline with brand styling.
## Triggers
- "ourdigital document", "ourdigital 문서"
- "ourdigital presentation", "ourdigital 프레젠테이션"
## Pipeline
```
extract_notion.py -> synthesize_content.py -> apply_brand.py
| | |
research.json synthesis.json presentation.pptx
```
## Quick Start
```bash
python scripts/run_workflow.py --notion-url [URL] --output presentation.pptx
# Step-by-step
python scripts/extract_notion.py [URL] > research.json
python scripts/synthesize_content.py research.json > synthesis.json
python scripts/apply_brand.py synthesis.json --output presentation.pptx
```
## Scripts
| Script | Purpose |
|--------|---------|
| `run_workflow.py` | Orchestrate full pipeline |
| `extract_notion.py` | Extract content from Notion |
| `synthesize_content.py` | Analyze and structure |
| `apply_brand.py` | Apply corporate styling |
## Output Formats
- PowerPoint (.pptx)
- Figma (via API)
- HTML preview
## Brand Configuration
See `code/assets/brand_config.json` for logo, colors (OurDigital palette), fonts (Poppins/Lora), slide templates.

View File

@@ -0,0 +1,44 @@
---
description: English essay creation for journal.ourdigital.org with Ghost CMS integration
---
# OurDigital Journal
English essay and article creation for journal.ourdigital.org. Activated with "ourdigital" keyword.
## Triggers
- "ourdigital journal", "ourdigital essay"
- "ourdigital English article"
## Channel Profile
- URL: journal.ourdigital.org
- Language: English
- Tone: Conversational, Poetic, Reflective
- Platform: Ghost CMS
- Length: 1,000-2,000 words
## Workflow
1. **Topic Exploration**: Angle, audience, depth level
2. **Essay Structure**: Opening (evocative) -> Exploration (3-4 observations) -> Synthesis -> Closing (open-ended)
3. **Writing Style**: First-person reflective, philosophical-technical hybrid, epistemic humility, cultural bridging
4. **Output**: Markdown with YAML frontmatter (title, meta_description, slug, tags)
## Content Types
| Type | Length |
|------|--------|
| Personal Essay | 1,000-1,500 words |
| Cultural Observation | 1,500-2,000 words |
| Industry Insight | 1,200-1,800 words |
## Scripts
```bash
python shared/scripts/ghost_publish.py --file essay.md --channel journal --draft
```
## Export Options
1. Ghost Draft -> API push
2. Local -> `./output/journal/`

View File

@@ -0,0 +1,39 @@
---
description: Meta skill for creating new OurDigital Claude Skills with scaffolding
---
# OurDigital Skill Creator
Create and manage OurDigital Claude Skills with automated scaffolding and Notion sync.
## Triggers
- "ourdigital 스킬 만들기", "ourdigital skill creator"
- "create ourdigital skill", "new ourdigital skill"
## Quick Start
```bash
python shared/scripts/init_skill.py {skill-name} --number XX
python shared/scripts/validate_skill.py XX-ourdigital-{skill-name}
python shared/scripts/sync_notion.py XX-ourdigital-{skill-name} --action=create
```
## Workflow
1. **Suitability Check**: Clear trigger, focused scope, reusable resources, no overlap (>=3 passes)
2. **Requirements Q&A**: Purpose, trigger, tools, output, environment
3. **Generate Structure**: `mkdir -p XX-ourdigital-{name}/{code,desktop,shared/{references,templates,scripts},docs/logs}`
4. **YAML Frontmatter**: Standard template with name, description, triggers, features
5. **Validation**: Directory structure, SKILL.md in both versions, content 800-1,200 words
6. **Notion Sync**: Record to Working with AI database
## Skill Categories
| Number | Category |
|--------|----------|
| 01-09 | OurDigital Core |
| 10 | Meta (this skill) |
| 11-19 | SEO |
| 20-29 | GTM/Analytics |
| 31-39 | Notion |
| 40-49 | Jamie Clinic |

View File

@@ -0,0 +1,42 @@
---
description: Training material creation and workshop planning for OurDigital
---
# OurDigital Trainer
Training material generation with template-based curriculum and evaluation forms.
## Triggers
- "ourdigital training", "ourdigital 교육"
- "ourdigital workshop", "ourdigital 워크샵"
## Scripts
```bash
# Generate curriculum
python code/scripts/generate_curriculum.py --topic "데이터 리터러시" --duration "4hours" --audience "마케터"
# Create workshop agenda
python code/scripts/create_workshop.py --topic "AI 활용법" --format "half-day" --participants 20
# Generate evaluation form
python code/scripts/create_evaluation.py --training "SEO 기초" --type "pre-post"
```
## Workshop Formats
| Format | Duration | Modules |
|--------|----------|---------|
| mini | 2 hours | 2 |
| half-day | 4 hours | 3-4 |
| full-day | 8 hours | 5-6 |
| series | Multiple | Custom |
## Evaluation Types
- pre-post: 학습 전후 비교
- satisfaction: 만족도 조사
- knowledge: 지식 평가
- application: 현업 적용도
## Output Formats
- Markdown (default), JSON, Google Slides export

View File

@@ -0,0 +1,40 @@
---
description: Unified 6-stage SEO audit orchestrator with weighted health score
---
# SEO Comprehensive Audit
Runs 6 sub-skill audits, merges results into unified report with weighted health score (0-100), pushes to Notion.
## Triggers
- "comprehensive SEO", "full SEO audit"
- "종합 SEO 감사"
## Quick Start
```bash
python scripts/seo_audit_orchestrator.py --url https://example.com
python scripts/seo_audit_orchestrator.py --url https://example.com --skip-local --skip-gsc
python scripts/seo_audit_orchestrator.py --url https://example.com --json
```
## Pipeline (6 Stages)
| Stage | Audit | Weight |
|-------|-------|--------|
| 1 | Technical SEO (robots, sitemap) | 20% |
| 2 | On-Page SEO (meta, headings, links) | 20% |
| 3 | Core Web Vitals (LCP, CLS, INP) | 25% |
| 4 | Schema Validation | 15% |
| 5 | Local SEO (GBP, Naver, NAP) | 10% |
| 6 | Search Console (rankings, CTR) | 10% |
## Stage 5: Local SEO Key Requirements
- Business Identity extraction is MANDATORY first step
- Korean market: GBP and Naver Smart Place = Critical; Kakao Map = High
- GBP/Naver Map are JS-rendered; report unfound as "not discoverable via web search"
## Notion Output
- Database ID: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- Report in Korean; technical English terms as-is
- Audit ID format: COMP-YYYYMMDD-NNN

View File

@@ -0,0 +1,45 @@
---
description: Core Web Vitals analyzer using PageSpeed Insights API (LCP, CLS, INP, FID)
---
# SEO Core Web Vitals
PageSpeed Insights API client for CWV measurement and recommendations.
## Triggers
- "Core Web Vitals", "page speed"
- "LCP", "CLS", "INP", "performance audit"
## Quick Start
```bash
export PAGESPEED_API_KEY=your_api_key
python scripts/pagespeed_client.py --url https://example.com
python scripts/pagespeed_client.py --url https://example.com --strategy both
python scripts/pagespeed_client.py --urls urls.txt --output results.json
```
## CWV Thresholds
| Metric | Good | Poor |
|--------|------|------|
| LCP | <=2.5s | >4s |
| FID | <=100ms | >300ms |
| CLS | <=0.1 | >0.25 |
| INP | <=200ms | >500ms |
## Common Fixes
| Issue | Fix |
|-------|-----|
| Large LCP | Optimize images, preload critical resources |
| High CLS | Set image dimensions, avoid injected content |
| Poor INP | Reduce JavaScript, optimize event handlers |
| Slow TTFB | Improve server response, use CDN |
## Rate Limits
- No API key: 25/day; With key: 25,000/day
## Notion Output
- Database: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- Korean report, Audit ID: [TYPE]-YYYYMMDD-NNN

View File

@@ -0,0 +1,42 @@
---
description: Local SEO audit for Korean-market businesses (GBP, Naver Place, NAP, citations)
---
# SEO Local Audit
Korean-market local SEO: business identity, GBP, Naver Smart Place, Kakao Map, NAP consistency, citations, LocalBusiness schema.
## Triggers
- "local SEO", "Google Business Profile"
- "Naver Place", "NAP consistency"
## Workflow (7 Steps)
1. **Business Identity (MANDATORY FIRST)**: Extract from JSON-LD, contact page, footer
2. **Website NAP Extraction**: Establish canonical NAP baseline
3. **GBP Verification**: Layered discovery (provided URL -> website -> WebSearch fallback)
4. **Naver Smart Place**: Layered discovery with Korean search queries
5. **Kakao Map Verification**: Check website links, then WebSearch
6. **Citation Discovery**: Industry directories (강남언니, 바비톡, 굿닥, etc.)
7. **LocalBusiness Schema Validation**: JSON-LD completeness check
## Scoring Weights
| Component | Weight |
|-----------|--------|
| NAP Consistency | 20% |
| GBP Optimization | 20% |
| Naver Smart Place | 20% |
| LocalBusiness Schema | 15% |
| Kakao Map | 10% |
| Citations | 10% |
| Business Identity | 5% |
## Key Rules
- GBP and Naver Map are JS-rendered -- use WebSearch for discovery
- "Not discoverable via web search" != "does not exist"
- Korean market: Naver Smart Place is as important as GBP
## Notion Output
- Database: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- Category: Local SEO, Audit ID: LOCAL-YYYYMMDD-NNN

View File

@@ -0,0 +1,40 @@
---
description: On-page SEO analyzer for meta tags, headings, links, images, and Open Graph
---
# SEO On-Page Audit
Single-page optimization analysis: meta tags, heading structure, links, images, OG data.
## Triggers
- "on-page SEO", "meta tags"
- "heading analysis", "page SEO check"
## Quick Start
```bash
python scripts/page_analyzer.py --url https://example.com
python scripts/page_analyzer.py --url https://example.com --json
python scripts/page_analyzer.py --urls urls.txt
```
## Analysis Categories
- **Meta Tags**: Title (length, keywords), description, canonical, robots
- **Heading Structure**: H1 presence/count, hierarchy validation, keyword placement
- **Links**: Internal/external count, broken links, nofollow distribution
- **Images**: Alt attributes, file sizes, lazy loading
- **Open Graph**: OG title/description/image, Twitter Cards
## Common Issues
| Issue | Severity |
|-------|----------|
| Missing H1 | High |
| Title too long (>60) | Medium |
| No meta description | High |
| Images without alt | Medium |
| Multiple H1 tags | Medium |
## Notion Output
- Database: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- Korean report, Audit ID: [TYPE]-YYYYMMDD-NNN

View File

@@ -0,0 +1,42 @@
---
description: Google Search Console data retrieval (rankings, CTR, impressions, sitemaps)
---
# SEO Search Console
GSC API client for search analytics, sitemap status, and index coverage.
## Triggers
- "Search Console", "rankings"
- "GSC data", "search performance"
## Quick Start
```bash
python scripts/gsc_client.py --site sc-domain:example.com --action summary
python scripts/gsc_client.py --site sc-domain:example.com --action queries --limit 100
python scripts/gsc_client.py --site sc-domain:example.com --action pages
```
## Configuration
- Credentials: `~/.credential/ourdigital-seo-agent.json`
- Service account: `ourdigital-seo-agent@ourdigital-insights.iam.gserviceaccount.com`
## Actions
| Action | Description |
|--------|-------------|
| `summary` | Clicks, impressions, CTR, position overview |
| `queries` | Top search queries |
| `pages` | Top pages by clicks |
| `sitemaps` | Sitemap submission status |
| `coverage` | Index coverage issues |
## Site Property Formats
- Domain: `sc-domain:example.com`
- URL prefix: `https://www.example.com/`
## Notion Output
- Database: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- Korean report, Audit ID: [TYPE]-YYYYMMDD-NNN

View File

@@ -0,0 +1,44 @@
---
description: Technical SEO audit for robots.txt, XML sitemap, and URL crawlability
---
# SEO Technical Audit
Crawlability fundamentals: robots.txt validation, XML sitemap analysis, URL accessibility checking.
## Triggers
- "technical SEO", "crawlability"
- "robots.txt", "sitemap validation"
## Quick Start
```bash
python scripts/robots_checker.py --url https://example.com
python scripts/sitemap_validator.py --url https://example.com/sitemap.xml
python scripts/sitemap_crawler.py --sitemap https://example.com/sitemap.xml
```
## Scripts
| Script | Purpose |
|--------|---------|
| `robots_checker.py` | Parse robots.txt, test URL rules |
| `sitemap_validator.py` | Validate XML structure, URL count, lastmod |
| `sitemap_crawler.py` | Async check URL accessibility, HTTP status |
## Common Issues Detected
| Issue | Severity |
|-------|----------|
| Missing sitemap declaration | Medium |
| Blocking CSS/JS resources | High |
| URLs returning 404 | High |
| Exceeds 50,000 URL limit | High |
| Non-canonical URLs included | Medium |
## Config
- `CRAWL_DELAY=1.0`, `MAX_CONCURRENT=20`, `REQUEST_TIMEOUT=30`
## Notion Output
- Database: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- Korean report, Audit ID: [TYPE]-YYYYMMDD-NNN

View File

@@ -129,9 +129,19 @@ Task 2: general-purpose - "Implement the planned skill" # Needs Task 1 result
### GTM Skills (60-69) ### GTM Skills (60-69)
- Use **gtm-manager** agent for GTM-specific debugging Three specialized skills with clear handoff workflow: **audit → edit → validate**
- Requires Chrome GTM Debug profile for live testing
- Scripts interact with GTM API and dataLayer | Skill | Purpose | MCP Tools |
|-------|---------|-----------|
| 60-gtm-audit | Scan pages, gap analysis, tag design blueprints | DTM Agent + Chrome DevTools |
| 61-gtm-editor | Create/modify tags, triggers, variables via GTM API | DTM Agent + Chrome DevTools |
| 62-gtm-validator | QA tag firing, trigger selectors, naming conventions | DTM Agent + Chrome DevTools |
- **gtm-audit** Mode D outputs blueprints → hand off to **gtm-editor** for implementation
- **gtm-editor** creates tags → hand off to **gtm-validator** for QA verification
- **gtm-validator** finds broken selectors → hand back to **gtm-editor** for fixes
- All three use DTM Agent MCP for container operations and Chrome DevTools MCP for browser inspection
- Audit reports write to Notion GTM Audit Log database
### Jamie Clinic Skills (40-49) ### Jamie Clinic Skills (40-49)
@@ -172,6 +182,16 @@ Task(
**Parallel workflows:** Set `NOTEBOOKLM_HOME` per agent to avoid context conflicts. **Parallel workflows:** Set `NOTEBOOKLM_HOME` per agent to avoid context conflicts.
### Mac Optimizer (92)
- **Claude Code only** — no Desktop version
- Runs read-only audit scripts that output JSON lines, then presents findings as severity-ranked tables
- 5 audit modules: packages, environment, security, cleanup, resources
- `doctor` mode runs all 5 sequentially and presents unified report
- **Always ask user consent** before executing any cleanup or system changes
- Scripts are in `custom-skills/92-mac-optimizer/scripts/`
- Reference docs in `custom-skills/92-mac-optimizer/references/`
### Reference Curator Skills (90-99) ### Reference Curator Skills (90-99)
- Use **reference-curator-pipeline** for full automated curation workflows - Use **reference-curator-pipeline** for full automated curation workflows
@@ -204,7 +224,7 @@ For long-running tasks, use `run_in_background: true`:
``` ```
# Good candidates for background execution: # Good candidates for background execution:
- Full skill audit across all 52 skills - Full skill audit across all 62 skills
- Running Python tests on multiple skills - Running Python tests on multiple skills
- Generating comprehensive documentation - Generating comprehensive documentation

View File

@@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**GitHub**: https://github.com/ourdigital/our-claude-skills **GitHub**: https://github.com/ourdigital/our-claude-skills
This is a Claude Skills collection repository containing: This is a Claude Skills collection repository containing:
- **custom-skills/**: 61 custom skills for OurDigital workflows, SEO, GTM, Jamie Brand, NotebookLM, Notion, D.intelligence Agent Corps, Reference Curation, and Multi-Agent Collaboration - **custom-skills/**: 62 custom skills for OurDigital workflows, SEO, GTM, Jamie Brand, NotebookLM, Notion, D.intelligence Agent Corps, Reference Curation, Mac Optimizer, TUI Designer, and Multi-Agent Collaboration
- **example-skills/**: Reference examples from Anthropic's official skills repository - **example-skills/**: Reference examples from Anthropic's official skills repository
- **official-skills/**: Notion integration skills (3rd party) - **official-skills/**: Notion integration skills (3rd party)
- **reference/**: Skill format requirements documentation - **reference/**: Skill format requirements documentation
@@ -68,9 +68,9 @@ This is a Claude Skills collection repository containing:
| # | Skill | Purpose | Trigger | | # | Skill | Purpose | Trigger |
|---|-------|---------|---------| |---|-------|---------|---------|
| 60 | gtm-audit | GTM container audit | "audit GTM", "GTM analysis" | | 60 | gtm-audit | GTM audit — page scan, site audit, gap analysis, tag design | "GTM audit", "scan page", "tracking coverage", "gap analysis" |
| 61 | gtm-manager | GTM management + dataLayer | "GTM manager", "dataLayer" | | 61 | gtm-editor | GTM tag/trigger/variable creation via API, ES5 Custom HTML, dataLayer | "create GTM tag", "generate dataLayer", "modify trigger", "manage GTM" |
| 62 | gtm-guardian | Live tag monitoring & validation | "monitor GTM", "tag validation" | | 62 | gtm-validator | GTM QA — tag firing, trigger testing, naming conventions, cross-platform | "validate tags", "QA GTM", "debug GTM", "naming conventions" |
### Notion Tools (31-39) ### Notion Tools (31-39)
@@ -85,7 +85,6 @@ This is a Claude Skills collection repository containing:
|---|-------|---------|---------| |---|-------|---------|---------|
| 40 | jamie-brand-editor | Branded content generation | "write Jamie blog", "Jamie content" | | 40 | jamie-brand-editor | Branded content generation | "write Jamie blog", "Jamie content" |
| 41 | jamie-brand-audit | Content review/compliance | "review content", "brand audit" | | 41 | jamie-brand-audit | Content review/compliance | "review content", "brand audit" |
| 42 | jamie-journal-editor | Clinic journal editing | "Jamie journal", "진료 일지" |
| 43 | jamie-youtube-manager | YouTube SEO audit & management | "YouTube SEO", "YT optimization" | | 43 | jamie-youtube-manager | YouTube SEO audit & management | "YouTube SEO", "YT optimization" |
| 44 | jamie-youtube-subtitle-checker | YouTube subtitle validation | "check subtitles", "subtitle QA" | | 44 | jamie-youtube-subtitle-checker | YouTube subtitle validation | "check subtitles", "subtitle QA" |
| 45 | jamie-instagram-manager | Instagram account management | "Instagram management", "IG strategy" | | 45 | jamie-instagram-manager | Instagram account management | "Instagram management", "IG strategy" |
@@ -121,7 +120,14 @@ This is a Claude Skills collection repository containing:
**User Guide:** `custom-skills/dintel-shared/USER-GUIDE.md` **User Guide:** `custom-skills/dintel-shared/USER-GUIDE.md`
### Reference Curator & Multi-Agent (90-99) ### System & Design Tools (92-93)
| # | Skill | Purpose | Trigger |
|---|-------|---------|---------|
| 92 | mac-optimizer | macOS system health audit & optimization (Claude Code only) | "audit my mac", "system health", "clean up caches", "check security", "update packages" |
| 93 | tui-designer | TUI wizard interface design (Norton Commander / Gopher style, Rich) | "build TUI", "CLI wizard", "terminal UI", "Rich TUI" |
### Reference Curator & Multi-Agent (90-91)
| # | Skill | Purpose | Trigger | | # | Skill | Purpose | Trigger |
|---|-------|---------|---------| |---|-------|---------|---------|
@@ -247,15 +253,14 @@ our-claude-skills/
│ ├── 34-seo-reporting-dashboard/ │ ├── 34-seo-reporting-dashboard/
│ │ │ │
│ ├── 60-gtm-audit/ │ ├── 60-gtm-audit/
│ ├── 61-gtm-manager/ │ ├── 61-gtm-editor/
│ ├── 62-gtm-guardian/ │ ├── 62-gtm-validator/
│ │ │ │
│ ├── 31-notion-organizer/ │ ├── 31-notion-organizer/
│ ├── 32-notion-writer/ │ ├── 32-notion-writer/
│ │ │ │
│ ├── 40-jamie-brand-editor/ │ ├── 40-jamie-brand-editor/
│ ├── 41-jamie-brand-audit/ │ ├── 41-jamie-brand-audit/
│ ├── 42-jamie-journal-editor/
│ ├── 43-jamie-youtube-manager/ │ ├── 43-jamie-youtube-manager/
│ ├── 44-jamie-youtube-subtitle-checker/ │ ├── 44-jamie-youtube-subtitle-checker/
│ ├── 45-jamie-instagram-manager/ │ ├── 45-jamie-instagram-manager/
@@ -290,6 +295,10 @@ our-claude-skills/
│ │ │ │
│ ├── 91-multi-agent-guide/ │ ├── 91-multi-agent-guide/
│ │ │ │
│ ├── 92-mac-optimizer/ # macOS system health toolkit (Claude Code only)
│ │
│ ├── 93-tui-designer/ # TUI wizard design (Rich, Norton Commander style)
│ │
│ └── 99_archive/ │ └── 99_archive/
├── example-skills/skills-main/ ├── example-skills/skills-main/

View File

@@ -2,7 +2,7 @@
> **Internal R&D Repository** - This repository is restricted for internal use only. > **Internal R&D Repository** - This repository is restricted for internal use only.
A collection of **61 custom Claude Skills** for OurDigital workflows, D.intelligence Agent Corps (9-agent business operations suite), Jamie Plastic Surgery Clinic brand management, SEO/GTM tools, NotebookLM automation, Notion integrations, reference documentation curation, and multi-agent collaboration. A collection of **62 custom Claude Skills** for OurDigital workflows, D.intelligence Agent Corps (9-agent business operations suite), Jamie Plastic Surgery Clinic brand management, SEO/GTM tools, NotebookLM automation, Notion integrations, reference documentation curation, macOS system optimization, TUI wizard design, and multi-agent collaboration.
## Quick Install ## Quick Install
@@ -88,9 +88,9 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
| # | Skill | Purpose | | # | Skill | Purpose |
|---|-------|---------| |---|-------|---------|
| 60 | `gtm-audit` | GTM container audit with Chrome DevTools | | 60 | `gtm-audit` | GTM container audit — page scan, site audit, gap analysis, tag design (DTM Agent + Chrome DevTools MCP) |
| 61 | `gtm-manager` | GTM management + dataLayer generation | | 61 | `gtm-editor` | GTM tag/trigger/variable creation via API, ES5 Custom HTML, dataLayer generation, workspace lifecycle |
| 62 | `gtm-guardian` | Live tag monitoring & validation | | 62 | `gtm-validator` | GTM QA — tag firing verification, trigger testing, dataLayer schema, naming conventions, cross-platform mapping |
### Notion Tools (31-39) ### Notion Tools (31-39)
@@ -105,7 +105,6 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
|---|-------|---------| |---|-------|---------|
| 40 | `jamie-brand-editor` | Branded content generation | | 40 | `jamie-brand-editor` | Branded content generation |
| 41 | `jamie-brand-audit` | Content review/compliance | | 41 | `jamie-brand-audit` | Content review/compliance |
| 42 | `jamie-journal-editor` | Clinic journal editing |
| 43 | `jamie-youtube-manager` | YouTube SEO audit & management | | 43 | `jamie-youtube-manager` | YouTube SEO audit & management |
| 44 | `jamie-youtube-subtitle-checker` | YouTube subtitle validation | | 44 | `jamie-youtube-subtitle-checker` | YouTube subtitle validation |
| 45 | `jamie-instagram-manager` | Instagram account management | | 45 | `jamie-instagram-manager` | Instagram account management |
@@ -121,7 +120,14 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
**Prerequisites:** `pip install notebooklm-py && playwright install chromium && notebooklm login` **Prerequisites:** `pip install notebooklm-py && playwright install chromium && notebooklm login`
### Reference Curator & Multi-Agent (90-99) ### System & Design Tools (92-93)
| # | Skill | Purpose |
|---|-------|---------|
| 92 | `mac-optimizer` | macOS system health audit & optimization (Claude Code only) |
| 93 | `tui-designer` | TUI wizard interface design (Norton Commander / Gopher style, Rich) |
### Reference Curator & Multi-Agent (90-91)
| # | Skill | Purpose | | # | Skill | Purpose |
|---|-------|---------| |---|-------|---------|
@@ -177,6 +183,8 @@ our-claude-skills/
│ ├── 50-53 (NotebookLM tools) │ ├── 50-53 (NotebookLM tools)
│ ├── 90-reference-curator/ │ ├── 90-reference-curator/
│ ├── 91-multi-agent-guide/ │ ├── 91-multi-agent-guide/
│ ├── 92-mac-optimizer/
│ ├── 93-tui-designer/
│ └── 99_archive/ │ └── 99_archive/
├── example-skills/ # Anthropic reference examples ├── example-skills/ # Anthropic reference examples

View File

@@ -0,0 +1,28 @@
# JHR — josunhotel.com Gotchas
Client: **JHR** (Josun Hotels & Resorts / 조선호텔앤리조트)
Domain: josunhotel.com + subdomains (gjb, gjj, jpg, grp)
---
## 1. Intentionally Truncated Directory Names in robots.txt
**Date discovered**: 2026-03-20
**Audit ID**: COMP-20260320-001
The following `Disallow` paths in robots.txt look like typos but are **intentional directory names** on the server:
| robots.txt path | Looks like it should be | Actual status |
|-----------------|------------------------|---------------|
| `/cnfirm` | `/confirm` | **Intentional** — real directory name |
| `/mber` | `/member` | **Intentional** — real directory name |
**Do NOT recommend renaming these** in audit reports. The server-side application uses these truncated names by design.
### Context
The josunhotel.com backend (Java/.do endpoints) uses shortened directory names for certain internal paths. This is likely a legacy naming convention from the original application architecture.
### Audit Impact
When running Technical SEO audits (Stage 1), skip the "typo in Disallow paths" finding for `/cnfirm` and `/mber`. These are valid paths that correctly block crawler access to confirmation and member-related pages.

View File

@@ -0,0 +1,30 @@
# JHR — lescapehotel.com Gotchas
Client: **JHR** (Josun Hotels & Resorts)
Domain: lescapehotel.com → les.josunhotel.com (migrated)
---
## 1. Domain Migration Complete — Monitor Redirects & Link Juice
**Date discovered**: 2025-12-14
**Updated**: 2026-03-20
**Status**: Migration COMPLETE
The L'Escape Hotel domain migration from `lescapehotel.com` to `les.josunhotel.com` has been completed.
### What to Monitor (Watchtower)
- **301 redirects**: Verify all old lescapehotel.com URLs still 301 to correct les.josunhotel.com counterparts
- **Link juice preservation**: Check that backlinks from external sites pointing to lescapehotel.com are passing authority through redirects
- **Redirect chains**: Watch for double redirects (lescapehotel.com → old intermediate → les.josunhotel.com)
- **GSC coverage**: Monitor les.josunhotel.com indexing in Google Search Console
- **Referring domains**: Track referring domain count in Ahrefs/GSC to ensure backlink equity is maintained
### Audit Checklist (Run Periodically)
1. Sample 10-20 old lescapehotel.com URLs — verify 301 status
2. Check for any 404s on les.josunhotel.com that should have redirects
3. Compare organic traffic trends pre/post migration
4. Verify old sitemap URLs are all redirecting properly
5. Check Naver Search Advisor for indexing status

View File

@@ -0,0 +1,40 @@
# SLA — shillahotels.com Gotchas
Client: **SLA** (Shilla Hotels & Resorts / 신라호텔)
Domain: shillahotels.com
---
## 1. www1 / www2 Are Dev/Stage Hosts, NOT Production
**Date discovered**: 2026-03-20
**Context**: SLA new integrated website under construction
The Shilla Hotels domain uses numbered subdomains for development environments:
| Subdomain | Purpose | Status |
|-----------|---------|--------|
| `www1.shillahotels.com` | Development | Active — deployed intermittently |
| `www2.shillahotels.com` | Staging | Active — deployed intermittently |
| `www.shillahotels.com` | Production (current) | Live |
| TBD (new integrated site) | Production (future) | **Target launch: end of June 2026** |
**Do NOT run technical SEO audits against www1 or www2** unless specifically instructed. These are pre-production environments and will have:
- No robots.txt
- No XML sitemap
- Incomplete meta tags
- Missing canonical/hreflang tags
These are expected states for dev/stage, not audit findings.
### When to Scan
The client will notify when www1/www2 need technical SEO element checks. Do NOT proactively audit these subdomains.
## 2. No robots.txt or Sitemap on Dev/Stage Hosts
**Date discovered**: 2026-03-20
robots.txt and sitemap.xml are intentionally not deployed to www1 and www2. This is expected for pre-production environments. Do NOT flag as issues.
Wait for client instruction before scanning technical SEO elements on these hosts.

View File

@@ -0,0 +1,141 @@
---
name: gtm-audit
description: >
Automated GTM audit and analysis. Scans websites for tracking coverage, compares
against GTM container config, identifies gaps, and reports to Notion. Uses Chrome
DevTools MCP for browser inspection and DTM Agent MCP for container operations.
Triggers on "GTM audit", "audit website", "scan page", "check tracking",
"tracking coverage", "gap analysis", "site audit", "tag firing check".
NOT for creating/modifying tags (use gtm-editor) or validating/QA (use gtm-validator).
---
# GTM Audit Skill
Analyze GTM implementations by scanning websites and comparing against container configuration.
## Available Tools
### Browser Inspection (Chrome DevTools MCP)
- `navigate_page` — Load target URL
- `evaluate_script` — Capture dataLayer, detect GTM containers, check JS globals
- `list_network_requests` — Capture tag firing network requests
- `take_screenshot` — Document page state
- `click` / `hover` — Simulate user interactions
- `list_console_messages` — Check for JS errors
### GTM Container (DTM Agent MCP)
- `dtm_status` — Check auth and active container
- `dtm_list_tags` / `dtm_list_triggers` / `dtm_list_variables` — Get container config
- `dtm_audit_scan` — Scan page with collected data (returns structured PageData)
- `dtm_audit_gap` — Run gap analysis (returns GapReport with OK/MISSING/ORPHAN)
### Reporting (Notion MCP)
- Write findings to GTM Audit Log: `collection://2cf581e5-8a1e-815a-a7f8-000b88b93585`
## 4 Audit Modes
### Mode A: Page Scan (`/gtm-audit scan <url>`)
Quick single-page diagnostics. No GTM container needed.
1. `navigate_page` to URL, wait for load
2. `evaluate_script`: capture dataLayer
```javascript
JSON.stringify(window.dataLayer || [])
```
3. `evaluate_script`: detect GTM containers
```javascript
(function(){var s=document.querySelectorAll('script[src*="googletagmanager.com/gtm.js"]');var ids=[];s.forEach(function(el){var m=el.src.match(/id=(GTM-[A-Z0-9]+)/);if(m)ids.push(m[1]);});return JSON.stringify(ids);})()
```
4. `evaluate_script`: detect tracking pixels
```javascript
(function(){var f=[];if(window.fbq)f.push('Meta Pixel');if(window.gtag)f.push('GA4');if(window.clarity)f.push('Clarity');if(window.kakaoPixel)f.push('Kakao');if(window.wcs)f.push('Naver');if(window.ttq)f.push('TikTok');return JSON.stringify(f);})()
```
5. `list_network_requests` — filter for tag domains
6. Call `dtm_audit_scan` with collected html, network_requests, datalayer_json
7. Report findings
### Mode B: Site Audit (`/gtm-audit site <url>`)
Full website coverage audit with auto-discovery.
1. Navigate to root URL
2. `evaluate_script`: extract all internal links
3. Classify pages by type (home, product, service, form, blog, about)
4. For each page type: run Mode A scan
5. Build coverage matrix (page type × event × platform)
6. Run Mode C gap analysis against container
7. Write comprehensive report to Notion
### Mode C: Gap Analysis (`/gtm-audit gap <url>`) — MOST IMPORTANT
Compare GTM container config against actual tag firing.
1. `dtm_status` — verify auth and active container
2. `dtm_list_tags` + `dtm_list_triggers` — get expected configuration
3. `navigate_page` to URL
4. `evaluate_script` — capture dataLayer
5. `list_network_requests` — capture actual tag firings
6. Call `dtm_audit_gap` with network_requests
7. Analyze gaps: MISSING (in GTM, not firing), ORPHAN (firing, not in GTM)
8. Write to Notion GTM Audit Log
### Mode D: Tag Design (`/gtm-audit design <url>`)
AI-powered analysis of what SHOULD be tracked on a page.
1. `navigate_page` to URL
2. `take_screenshot` — visual reference
3. `evaluate_script` — inspect DOM: forms, buttons, links, product elements
4. `evaluate_script` — capture existing dataLayer schema
5. `dtm_list_tags` — check existing configuration
6. Design recommendations: which events, triggers, variables are needed
7. Output as structured blueprint (hand off to gtm-editor for implementation)
## Detection Patterns
### Cloudflare Zaraz Proxy
URLs with `/1tbv/` path proxy GA4/Ads through the site's domain:
```
https://site.com/1tbv/ag/g/c?tid=G-XXXXX → GA4
https://site.com/1tbv/ag/g/c?tid=AW-XXXXX → Google Ads
```
### Server-Side GTM (sGTM)
Custom subdomains: `stms.*`, `sgtm.*`, `ss.*`, `tag.*`
```
https://stms.site.com/g/collect?tid=G-XXXXX → GA4 via sGTM
```
### GA4 Multiple Endpoints
One GA4 page_view fires to multiple endpoints — this is normal:
- `google-analytics.com/g/collect`
- `analytics.google.com/g/s/collect`
- `stats.g.doubleclick.net/g/collect`
- `www.google.com/ccm/collect` (Consent Mode)
- Zaraz proxy endpoints
## Notion Report Fields
| Field | Value |
|-------|-------|
| Site | domain name |
| URL | full URL |
| Container IDs | GTM-XXXXX |
| Audit Status | Pass / Warning / Fail |
| GTM Status | Installed / Not Found / Multiple Containers |
| Tags Fired | [GA4, Google Ads, Meta Pixel, Kakao, Naver, ...] |
| Journey Type | full / pageview / scroll / form / checkout / datalayer |
| Critical Issues | count |
| Issues Count | count |
| Summary | AI-generated findings |
## Rules
- Always check `dtm_status` before gap analysis
- Wait for page to fully load before capturing network requests
- Use `evaluate_script` to check consent mode before interpreting missing tags
- Orphan count may be high due to GA4 multi-endpoint — note this in reports
- **In Mode D (tag design), check elements for `id` attributes** — if missing, recommend adding IDs before designing CSS-based triggers. Suggest naming: `[section]-[element]-[action]`
- Hand off tag creation work to `gtm-editor` skill
- Hand off QA/validation work to `gtm-validator` skill

View File

@@ -196,6 +196,121 @@ dataLayer.push({
--- ---
## Content Grouping Issues (RegEx Table Variables)
### How Content Grouping Works in GTM
Content grouping uses **RegEx Table** variables to categorize pages by URL/path patterns.
The variable takes an input (typically `{{Page Path}}` or `{{Page URL}}`), tests it against
an ordered list of regex patterns, and returns the first matching output value.
If no pattern matches, it falls to the **Default Value** (usually "Other").
**Key settings:**
- **Full Matches Only**: OFF — partial regex match is sufficient
- **Capture Groups and Replace**: OFF — output is the literal value, not a regex replacement
- **Ignore Case**: ON — case-insensitive matching
### New Page Paths Fall Through to "Other"
**Symptoms**: New sections (campaigns, special events, landing pages) show as "Other" in GA4 content group reports.
**Root cause**: The RegEx Table was built for the site's original URL structure. New paths like `/specialEvent/`, `/campaign/`, `/timesale/` don't match any existing pattern.
**Example**: JHR site has `/specialEvent/landersshoppingfesta2026/teaser.do` but only `/package/` and `/event/` are in the regex table, not `/specialEvent/`.
**Fix**: Add regex rule for the new path pattern. Place it BEFORE the default.
**Gotcha — two variables to update**: Sites often have parallel content group variables:
1. **Page Path based** (e.g., `(\/m)?\/specialEvent\/(.*)`) — for internal reports
2. **Page URL based** (e.g., full URL with domain regex) — for cross-domain or subdomain tracking
Both must be updated or they'll produce inconsistent groupings.
### RegEx Pattern Conventions (JHR Example)
Page Path patterns follow this structure:
```
(\/m)?\/section\/(.*) → Korean Category Label
```
- `(\/m)?` — optional mobile path prefix
- `\/section\/` — the URL path segment to match
- `(.*)` — match anything after
Page URL patterns include domain matching:
```
^https\:\/\/(www|app|jpg|gjb|gjj|grp|\.)?josunhotel\.com(\/m)?\/section\/(.*)
```
- Multi-subdomain support via alternation group
- Same `(\/m)?` mobile prefix handling
### Content Group Categories (Hospitality Reference)
| Pattern | Category (KR) | Category (EN) |
|---------|---------------|---------------|
| `/intro.do`, `/main.do` | 메인페이지 | Main Page |
| `/about/`, `/press/` | 기업 소개 | Corporate Info |
| `/hotel/` | 호텔 소개 | Hotel Info |
| `/rooms/` | 객실 소개 | Room Info |
| `/dining/` | 다이닝 이용 | Dining |
| `/facilities/`, `/meeting/`, `/fitness/` | 부대시설 소개 | Facilities |
| `/package/`, `/event/` | 이벤트 \| 패키지 조회 | Events & Packages |
| `/specialEvent/` | 캠페인 \| 프로모션 | Campaign & Promotion |
| `/timesale/`, `/campaign/` *(placeholder — not actual paths yet, replace when specific paths are registered)* | 캠페인 \| 프로모션 | Campaign & Promotion |
| `/activity/` | 액티비티 소개 | Activities |
| `/step[0-4]` | 룸, 패키지 예약 프로세스 | Booking Process |
| `/resve/dining/` | 다이닝 예약 프로세스 | Dining Booking |
| `/membership/` | 멤버쉽 | Membership |
| `/mypage/`, `/login/`, `/join/` | 웹회원 관련 | Member Portal |
| `/retail/`, `/product/`, `/office/` | 리테일 관련 | Retail |
| `/voc/`, `/customer/` | 고객지원 | Customer Support |
| `/policy/` | 웹사이트 정책 | Website Policy |
| `/upload_file/event/html/` | 캠페인 \| 프로모션 | Campaign & Promotion |
| Default | Other | Other |
### Two Content Group Variables — Old vs New (JHR)
The JHR container has two content group variables for menu categories:
| | **Contents Group 2** (ID:1234) | **Contents Group new** (ID:1826) |
|---|---|---|
| Input | `{{Page Path}}` | `{{Page URL}}` |
| Rules | 20 | 24 (replacement) |
| Domain matching | No — path only | Yes — full URL with subdomain alternation |
| Subdomain coverage | N/A | `www\|app\|jpg\|gjb\|gjj\|grp\|les\|\.` |
| Cross-domain | No | Yes — booking, josuntns, lounge subdomains |
| Mobile path | `(\/m)?` prefix | `(\/m)?` prefix within URL pattern |
**"Contents Group new" (ID:1826) is the replacement** — it handles subdomains (booking, T&S, lounge, les) that the old path-based variable cannot distinguish. When auditing, check which variable is referenced in the GA4 config tag — the newer one should be used.
**Gotcha — adding new subdomains**: When a new hotel brand subdomain launches (e.g., `les.josunhotel.com`), the subdomain alternation group `(www|app|...|les|\.)` in ALL main-domain rules must be updated. This affects 14+ rules in the URL-based variable.
### Gotcha — Subdomain Regex Dot Separator
**Bug**: `(www|app|jpg|les|\.)?josunhotel\.com` does NOT match `www.josunhotel.com`.
The `\.` inside the alternation group matches a literal dot OR `www` matches `www`, but neither consumes the `.` between the subdomain and `josunhotel`. The regex engine sees `www` then expects `josunhotel` but finds `.josunhotel`.
**Fix**: Move the dot outside the group as an optional separator:
```
BROKEN: (www|app|jpg|gjb|gjj|grp|les|\.)?josunhotel\.com
FIXED: (www|app|jpg|gjb|gjj|grp|les)?\.?josunhotel\.com
```
This correctly matches: `www.josunhotel.com`, `les.josunhotel.com`, `josunhotel.com` (bare), `app.josunhotel.com`.
**Note**: GTM's "Full Matches Only = OFF" means `re.search` (partial match) is used, which may accidentally match the broken pattern on some URLs. But strict `re.match` or JavaScript `pattern.test()` will fail. Always test with the full URL in JavaScript console.
### Audit Checklist for Content Grouping
- [ ] Run `dtm var info <var_id>` to dump the full regex table
- [ ] Check if new site sections have matching patterns
- [ ] Verify both Path and URL variables are in sync
- [ ] Check GA4 config tag references the correct variable
- [ ] Test in GTM Preview: navigate to new page, check variable value
- [ ] After update, verify in GA4 Realtime > Content Group report
---
## Performance Issues ## Performance Issues
### Tags Slowing Page Load ### Tags Slowing Page Load

View File

@@ -0,0 +1,298 @@
# JHR GA4 Tagging Reference
Source: [JHR - User Behaviour, Event Planning & GA setup](https://docs.google.com/spreadsheets/d/1rhzYApbZ4wbe1gE-E43E-mlRdLl_etkIOZnqs1g9X1U)
Container: GTM-NP6NJMP (JHR Website - 통합)
---
## 1. Tag Naming Convention
Format: `{Platform} {TriggerElement} - {Purpose} - {EventName}`
### Platform Categories
| Platform | Usage |
|----------|-------|
| GA4 | Google Analytics 4 events |
| Google Ads | Conversion tracking |
| Meta | Facebook/Instagram pixel |
| Kakao | Kakao pixel/moment |
| Marketing | Cross-platform marketing tags |
| Analytics | Analytics configuration tags |
| Site | Site-specific utility tags |
| SEO | SEO-related tags |
| Test | Testing/debug tags |
| {{Campaign Name}} | Campaign-specific tags (e.g., "LandersFesta") |
### Trigger Elements (what triggers the tag)
| Element | Description |
|---------|-------------|
| pageview | Page load / virtual pageview |
| click | Generic click |
| link | Link click |
| button | Button click |
| tab | Tab selection |
| form | Form submission |
| layer | Layer/modal/popup view |
| popup | Popup interaction |
| banner | Banner click/impression |
| dropdown | Dropdown selection |
| radio | Radio button selection |
| checkbox | Checkbox toggle |
| ajax | AJAX request complete |
| dataLayer | DataLayer push event |
### Tag Purposes (page/feature area)
| Purpose | Area |
|---------|------|
| booking | Room/package reservation process |
| dining | Dining reservation process |
| membership | Club Josun membership |
| package | Package/event listing & detail |
| theme | Theme package pages |
| campaign | Campaign/promotion pages |
| event | Event pages |
| timesale | Time-limited sale pages |
| survey | Survey/feedback |
| ecommerce | E-commerce tracking |
| SEO | SEO schema/config |
| site | Site-wide interactions |
| config | Configuration/settings |
| ux | UX tracking |
| test | Testing |
### Examples
```
GA4 pageview - booking - room_booking_step1
GA4 button - theme - theme_pkg_detail
GA4 click - campaign - kakao_channel_add
Kakao banner - timesale - {{Event Name}}
Meta form - event - {{Event Name}}
Marketing - meta pixel - join_complete
Marketing - Kakao Pixel - package detail
```
---
## 2. Inherited Settings Variables
Tags inherit common parameters through shared settings variables.
This avoids duplicating parameters across hundreds of tags.
### GA4 Configuration Setting (shared across ALL GA4 tags)
| Parameter | Variable | Description |
|-----------|----------|-------------|
| login_status | {{cjs - login status Variable}} | Login state |
| user_lang | locale={{cjs - page language Variable}} | Page language |
| contents_group_hotel | {{Contents Group 1 - Hotel Site}} | Hotel brand grouping |
| contents_group_menu | {{Contents Group new - Menu Categories}} | Page section grouping |
| naver_query | {{URL referrer - naver query}} | Naver search query |
| naver_oquery | {{URL referrer - naver oquery}} | Naver original query |
### GA4 Event Settings (added to event tags)
| Parameter | Variable |
|-----------|----------|
| page_path | {{Page Path}} |
| login_status | {{cjs - login status Variable}} |
| user_lang | {{cjs - page language Variable}} |
### GA4 eCommerce Settings (added to ecommerce tags)
| Parameter | Variable |
|-----------|----------|
| pkg_name | {{dlv - eCommerce - item_name}} |
| pkg_hotel | {{dlv - eCommerce - item_brand}} |
| pkg_price | {{dlv - eCommerce - price}} |
| pkg_room_type | {{dlv - eCommerce - item_variant}} |
| pkg_id | {{dlv - eCommerce - item_id}} |
| page_path | {{Page Path}} |
### GA4 Promotion Settings (added to package/event detail tags)
| Parameter | Variable |
|-----------|----------|
| page_path | {{Page Path}} |
| login_status | {{cjs - login status Variable}} |
| user_lang | {{cjs - page language Variable}} |
| packageSn | {{URL query - packageSn Variable}} |
| themeSn | {{URL query - themeSn Variable}} |
| eventSn | {{URL query - eventSn Variable}} |
---
## 3. Standard Parameter Keys
| Parameter Name | Allowed Values / Variable |
|----------------|--------------------------|
| event_type | click |
| ui | GNB, modal, button, tab, link, banner, footer |
| page_path | {{Page Path}} |
| login_status | (from cjs variable) |
| user_lang | (from cjs variable) |
| pkg_name | (package/event title) |
| click_text | (clicked element text) |
| click_url | (clicked element URL) |
---
## 4. Property Codes
| # | Hotel Name | URL | Website Code | Report Code |
|---|-----------|-----|-------------|-------------|
| 1 | 조선 팰리스 | jpg.josunhotel.com | JPY | JPG |
| 2 | 웨스틴 조선 서울 | josunhotel.com/hotel/westinSeoul.do | TWC | WJS |
| 3 | 웨스틴 조선 부산 | josunhotel.com/hotel/westinBusan.do | TWCB | WJB |
| 4 | 그랜드 조선 부산 | gjb.josunhotel.com | GJB | GJB |
| 5 | 그랜드 조선 제주 | gjj.josunhotel.com | GJJ | GJJ |
| 6 | 레스케이프 호텔 | les.josunhotel.com | LESCAPE | LES |
| 7 | 그래비티 조선 서울 판교 | grp.josunhotel.com | GRP | GVP |
| 8 | 포포인츠 서울역 | josunhotel.com/hotel/fpbsSeoul.do | FPBSS | FPS |
| 9 | 포포인츠 서울 명동 | josunhotel.com/hotel/fpbsMyeongdong.do | FPBSM | FPM |
| - | 전사 공통 | josunhotel.com | JOSUN HOTEL | JHR |
| 10 | 파라스파라 | paraspara.co.kr | - | - |
| 11 | 코랄로 바이 조선 | coralloyangyang.com | CBJ | YYH |
---
## 5. Meta / Kakao Pixel Event Mapping
### Meta (Facebook) Pixel
| User Action | Event Name | Pixel Code | GTM Tag Name |
|-------------|-----------|------------|--------------|
| 회원가입 완료 | 등록완료 | CompleteRegistration | Marketing - meta pixel - join_complete |
| 이벤트 자세히 보기 | 콘텐츠 조회 | ViewContent | Marketing - meta Pixel - package detail |
| 관심상품 담기 | 위시리스트에 추가 | AddToWishlist | Marketing - meta Pixel - bookmark |
| 룸/패키지 예약 클릭 | 예약 | Schedule | Marketing - mata Pixel - bookmark |
| 잠재고객 | 잠재고객 | Lead | (not configured) |
### Kakao Pixel
| User Action | Event Name | Event Code | GTM Tag Name |
|-------------|-----------|------------|--------------|
| 회원가입 완료 | 회원가입 | CompleteRegistration | Marketing - Kakao Pixel - join member complete |
| 이벤트 자세히 보기 | 콘텐츠/상품 조회 | ViewContent | Marketing - Kakao Pixel - package detail |
| 관심상품 담기 | 관심상품추가 | AddToWishList | Marketing - Kakao Pixel - bookmark |
| 룸/패키지 예약 클릭 | 구매 | Purchase | Marketing - Kakao Pixel - click booking |
| 잠재고객 (멤버십) | 잠재고객 | Participation - Consulting | Marketing - Kakao Pixel - button - membership_detail_view |
| 잠재고객 (매스프로모션) | 잠재고객 | Participation - PreBooking | Marketing - Kakao pixel - button - massPromotion_detail |
---
## 6. Custom Channel Grouping (JHR)
| Order | Channel | Custom Rules |
|-------|---------|-------------|
| 1 | Direct | (default) |
| 2 | Cross-network | (default) |
| 3 | Paid Shopping | (default) |
| 4 | Paid Search | (default) |
| 5 | Paid Social | + kakao / sms |
| 6 | Paid Video | (default) |
| 7 | Paid Other | (default) |
| 8 | Display | (default) |
| 9 | Map Search | place.map.kakao.com, m.place.naver.com, pcmap.place.naver.com, map.kakao.com |
| 10 | Organic Shopping | (default) |
| 11 | Organic Social | (default) |
| 12 | Organic Video | (default) |
| 13 | Organic Search | (default) |
| 14 | Email | (default) |
| 15 | Affiliates | + event.ssg.com, marriott.com, blossom.shinsegae.com |
| 16 | Referral | (default) |
| 17-19 | Audio / SMS / Mobile Push | (default) |
| - | Unassigned | hotelQR / QRcoupon |
---
## 7. eCommerce Product Categories
| Category | Description |
|----------|-------------|
| ROOM ONLY | 객실 상품만 제공 |
| 멤버십 전용 | CJM 회원 전용 상품 |
| 선결제 | 선결제 상품 (현재 미등록) |
| 얼리버드 | 조기 예약 상품 |
| 조식 | 조식 포함 패키지 |
| 패키지 | 프로모션 오퍼 포함 패키지 |
---
## 8. Event Catalog (173 events)
### Booking Process Events
| Event Name | Description |
|------------|-------------|
| room_search | Room search initiated |
| room_search_home | Room search from homepage |
| room_tab_room_only | Room only tab selected |
| room_tab_package | Package tab selected |
| room_tab_members_only | Members only tab selected |
| room_booking_step4 | Booking step 4 (confirmation) |
| room_booking_promo | Promo code applied |
| book_room | Room booking completed |
| begin_checkout | Checkout started |
| purchase | Purchase completed |
| refund | Refund processed |
### Dining Events
| Event Name | Description |
|------------|-------------|
| dining_booking_start | Dining booking initiated |
| dining_hotel_select | Hotel selected for dining |
| dining_restaurant_select | Restaurant selected |
| dining_step0_view ~ dining_step4_view | Dining booking steps |
| dining_book | Dining reservation completed |
| dining_menu_view_click | Menu viewed |
| dining_search_home | Dining search from home |
### Package & Event Events
| Event Name | Description |
|------------|-------------|
| package_list_view | Package listing viewed |
| event_list_view | Event listing viewed |
| event_see_detail | Event detail clicked |
| theme_pkg_detail | Theme package detail clicked |
| pageview_theme_pkg | Theme package page viewed |
| bookmark_promotion | Package/event bookmarked |
| event_share | Event shared (Kakao/email/SMS/URL) |
| massPromotion_detail | Mass promotion detail viewed |
### Membership Events
| Event Name | Description |
|------------|-------------|
| memberED2_list_view | Membership list viewed |
| memberED2_join | Membership join started |
| memberED2_detail_{TIER} | Tier detail viewed (BLACK/GOLD/PLATINUM/PREMIER/GRANDJOSUN) |
| memberED2_detail_share | Membership shared |
| memberED2_inquiry | Membership inquiry |
### Navigation & Site Events
| Event Name | Description |
|------------|-------------|
| GNB_all_menu | All menu opened |
| GNB_sub_menu_click | Sub menu clicked |
| main_select_hotel | Hotel selected from main |
| main_room_booking | Room booking from main |
| main_dining_booking | Dining booking from main |
| hotel_layer_view | Hotel info layer viewed |
| login_click | Login clicked |
| join_click | Join clicked |
| lang_change | Language changed |
### E-commerce Events (GA4 Standard)
| Event Name | Description |
|------------|-------------|
| view_item_list | Item list viewed |
| view_item | Item detail viewed |
| add_to_cart | Added to cart |
| add_to_wishlist | Added to wishlist |
| remove_from_wishlist | Removed from wishlist |
| begin_checkout | Checkout started |
| purchase | Purchase completed |
| refund | Refund |

View File

@@ -0,0 +1,294 @@
---
name: 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

View File

@@ -196,6 +196,121 @@ dataLayer.push({
--- ---
## Content Grouping Issues (RegEx Table Variables)
### How Content Grouping Works in GTM
Content grouping uses **RegEx Table** variables to categorize pages by URL/path patterns.
The variable takes an input (typically `{{Page Path}}` or `{{Page URL}}`), tests it against
an ordered list of regex patterns, and returns the first matching output value.
If no pattern matches, it falls to the **Default Value** (usually "Other").
**Key settings:**
- **Full Matches Only**: OFF — partial regex match is sufficient
- **Capture Groups and Replace**: OFF — output is the literal value, not a regex replacement
- **Ignore Case**: ON — case-insensitive matching
### New Page Paths Fall Through to "Other"
**Symptoms**: New sections (campaigns, special events, landing pages) show as "Other" in GA4 content group reports.
**Root cause**: The RegEx Table was built for the site's original URL structure. New paths like `/specialEvent/`, `/campaign/`, `/timesale/` don't match any existing pattern.
**Example**: JHR site has `/specialEvent/landersshoppingfesta2026/teaser.do` but only `/package/` and `/event/` are in the regex table, not `/specialEvent/`.
**Fix**: Add regex rule for the new path pattern. Place it BEFORE the default.
**Gotcha — two variables to update**: Sites often have parallel content group variables:
1. **Page Path based** (e.g., `(\/m)?\/specialEvent\/(.*)`) — for internal reports
2. **Page URL based** (e.g., full URL with domain regex) — for cross-domain or subdomain tracking
Both must be updated or they'll produce inconsistent groupings.
### RegEx Pattern Conventions (JHR Example)
Page Path patterns follow this structure:
```
(\/m)?\/section\/(.*) → Korean Category Label
```
- `(\/m)?` — optional mobile path prefix
- `\/section\/` — the URL path segment to match
- `(.*)` — match anything after
Page URL patterns include domain matching:
```
^https\:\/\/(www|app|jpg|gjb|gjj|grp|\.)?josunhotel\.com(\/m)?\/section\/(.*)
```
- Multi-subdomain support via alternation group
- Same `(\/m)?` mobile prefix handling
### Content Group Categories (Hospitality Reference)
| Pattern | Category (KR) | Category (EN) |
|---------|---------------|---------------|
| `/intro.do`, `/main.do` | 메인페이지 | Main Page |
| `/about/`, `/press/` | 기업 소개 | Corporate Info |
| `/hotel/` | 호텔 소개 | Hotel Info |
| `/rooms/` | 객실 소개 | Room Info |
| `/dining/` | 다이닝 이용 | Dining |
| `/facilities/`, `/meeting/`, `/fitness/` | 부대시설 소개 | Facilities |
| `/package/`, `/event/` | 이벤트 \| 패키지 조회 | Events & Packages |
| `/specialEvent/` | 캠페인 \| 프로모션 | Campaign & Promotion |
| `/timesale/`, `/campaign/` *(placeholder — not actual paths yet, replace when specific paths are registered)* | 캠페인 \| 프로모션 | Campaign & Promotion |
| `/activity/` | 액티비티 소개 | Activities |
| `/step[0-4]` | 룸, 패키지 예약 프로세스 | Booking Process |
| `/resve/dining/` | 다이닝 예약 프로세스 | Dining Booking |
| `/membership/` | 멤버쉽 | Membership |
| `/mypage/`, `/login/`, `/join/` | 웹회원 관련 | Member Portal |
| `/retail/`, `/product/`, `/office/` | 리테일 관련 | Retail |
| `/voc/`, `/customer/` | 고객지원 | Customer Support |
| `/policy/` | 웹사이트 정책 | Website Policy |
| `/upload_file/event/html/` | 캠페인 \| 프로모션 | Campaign & Promotion |
| Default | Other | Other |
### Two Content Group Variables — Old vs New (JHR)
The JHR container has two content group variables for menu categories:
| | **Contents Group 2** (ID:1234) | **Contents Group new** (ID:1826) |
|---|---|---|
| Input | `{{Page Path}}` | `{{Page URL}}` |
| Rules | 20 | 24 (replacement) |
| Domain matching | No — path only | Yes — full URL with subdomain alternation |
| Subdomain coverage | N/A | `www\|app\|jpg\|gjb\|gjj\|grp\|les\|\.` |
| Cross-domain | No | Yes — booking, josuntns, lounge subdomains |
| Mobile path | `(\/m)?` prefix | `(\/m)?` prefix within URL pattern |
**"Contents Group new" (ID:1826) is the replacement** — it handles subdomains (booking, T&S, lounge, les) that the old path-based variable cannot distinguish. When auditing, check which variable is referenced in the GA4 config tag — the newer one should be used.
**Gotcha — adding new subdomains**: When a new hotel brand subdomain launches (e.g., `les.josunhotel.com`), the subdomain alternation group `(www|app|...|les|\.)` in ALL main-domain rules must be updated. This affects 14+ rules in the URL-based variable.
### Gotcha — Subdomain Regex Dot Separator
**Bug**: `(www|app|jpg|les|\.)?josunhotel\.com` does NOT match `www.josunhotel.com`.
The `\.` inside the alternation group matches a literal dot OR `www` matches `www`, but neither consumes the `.` between the subdomain and `josunhotel`. The regex engine sees `www` then expects `josunhotel` but finds `.josunhotel`.
**Fix**: Move the dot outside the group as an optional separator:
```
BROKEN: (www|app|jpg|gjb|gjj|grp|les|\.)?josunhotel\.com
FIXED: (www|app|jpg|gjb|gjj|grp|les)?\.?josunhotel\.com
```
This correctly matches: `www.josunhotel.com`, `les.josunhotel.com`, `josunhotel.com` (bare), `app.josunhotel.com`.
**Note**: GTM's "Full Matches Only = OFF" means `re.search` (partial match) is used, which may accidentally match the broken pattern on some URLs. But strict `re.match` or JavaScript `pattern.test()` will fail. Always test with the full URL in JavaScript console.
### Audit Checklist for Content Grouping
- [ ] Run `dtm var info <var_id>` to dump the full regex table
- [ ] Check if new site sections have matching patterns
- [ ] Verify both Path and URL variables are in sync
- [ ] Check GA4 config tag references the correct variable
- [ ] Test in GTM Preview: navigate to new page, check variable value
- [ ] After update, verify in GA4 Realtime > Content Group report
---
## Performance Issues ## Performance Issues
### Tags Slowing Page Load ### Tags Slowing Page Load

View File

@@ -0,0 +1,298 @@
# JHR GA4 Tagging Reference
Source: [JHR - User Behaviour, Event Planning & GA setup](https://docs.google.com/spreadsheets/d/1rhzYApbZ4wbe1gE-E43E-mlRdLl_etkIOZnqs1g9X1U)
Container: GTM-NP6NJMP (JHR Website - 통합)
---
## 1. Tag Naming Convention
Format: `{Platform} {TriggerElement} - {Purpose} - {EventName}`
### Platform Categories
| Platform | Usage |
|----------|-------|
| GA4 | Google Analytics 4 events |
| Google Ads | Conversion tracking |
| Meta | Facebook/Instagram pixel |
| Kakao | Kakao pixel/moment |
| Marketing | Cross-platform marketing tags |
| Analytics | Analytics configuration tags |
| Site | Site-specific utility tags |
| SEO | SEO-related tags |
| Test | Testing/debug tags |
| {{Campaign Name}} | Campaign-specific tags (e.g., "LandersFesta") |
### Trigger Elements (what triggers the tag)
| Element | Description |
|---------|-------------|
| pageview | Page load / virtual pageview |
| click | Generic click |
| link | Link click |
| button | Button click |
| tab | Tab selection |
| form | Form submission |
| layer | Layer/modal/popup view |
| popup | Popup interaction |
| banner | Banner click/impression |
| dropdown | Dropdown selection |
| radio | Radio button selection |
| checkbox | Checkbox toggle |
| ajax | AJAX request complete |
| dataLayer | DataLayer push event |
### Tag Purposes (page/feature area)
| Purpose | Area |
|---------|------|
| booking | Room/package reservation process |
| dining | Dining reservation process |
| membership | Club Josun membership |
| package | Package/event listing & detail |
| theme | Theme package pages |
| campaign | Campaign/promotion pages |
| event | Event pages |
| timesale | Time-limited sale pages |
| survey | Survey/feedback |
| ecommerce | E-commerce tracking |
| SEO | SEO schema/config |
| site | Site-wide interactions |
| config | Configuration/settings |
| ux | UX tracking |
| test | Testing |
### Examples
```
GA4 pageview - booking - room_booking_step1
GA4 button - theme - theme_pkg_detail
GA4 click - campaign - kakao_channel_add
Kakao banner - timesale - {{Event Name}}
Meta form - event - {{Event Name}}
Marketing - meta pixel - join_complete
Marketing - Kakao Pixel - package detail
```
---
## 2. Inherited Settings Variables
Tags inherit common parameters through shared settings variables.
This avoids duplicating parameters across hundreds of tags.
### GA4 Configuration Setting (shared across ALL GA4 tags)
| Parameter | Variable | Description |
|-----------|----------|-------------|
| login_status | {{cjs - login status Variable}} | Login state |
| user_lang | locale={{cjs - page language Variable}} | Page language |
| contents_group_hotel | {{Contents Group 1 - Hotel Site}} | Hotel brand grouping |
| contents_group_menu | {{Contents Group new - Menu Categories}} | Page section grouping |
| naver_query | {{URL referrer - naver query}} | Naver search query |
| naver_oquery | {{URL referrer - naver oquery}} | Naver original query |
### GA4 Event Settings (added to event tags)
| Parameter | Variable |
|-----------|----------|
| page_path | {{Page Path}} |
| login_status | {{cjs - login status Variable}} |
| user_lang | {{cjs - page language Variable}} |
### GA4 eCommerce Settings (added to ecommerce tags)
| Parameter | Variable |
|-----------|----------|
| pkg_name | {{dlv - eCommerce - item_name}} |
| pkg_hotel | {{dlv - eCommerce - item_brand}} |
| pkg_price | {{dlv - eCommerce - price}} |
| pkg_room_type | {{dlv - eCommerce - item_variant}} |
| pkg_id | {{dlv - eCommerce - item_id}} |
| page_path | {{Page Path}} |
### GA4 Promotion Settings (added to package/event detail tags)
| Parameter | Variable |
|-----------|----------|
| page_path | {{Page Path}} |
| login_status | {{cjs - login status Variable}} |
| user_lang | {{cjs - page language Variable}} |
| packageSn | {{URL query - packageSn Variable}} |
| themeSn | {{URL query - themeSn Variable}} |
| eventSn | {{URL query - eventSn Variable}} |
---
## 3. Standard Parameter Keys
| Parameter Name | Allowed Values / Variable |
|----------------|--------------------------|
| event_type | click |
| ui | GNB, modal, button, tab, link, banner, footer |
| page_path | {{Page Path}} |
| login_status | (from cjs variable) |
| user_lang | (from cjs variable) |
| pkg_name | (package/event title) |
| click_text | (clicked element text) |
| click_url | (clicked element URL) |
---
## 4. Property Codes
| # | Hotel Name | URL | Website Code | Report Code |
|---|-----------|-----|-------------|-------------|
| 1 | 조선 팰리스 | jpg.josunhotel.com | JPY | JPG |
| 2 | 웨스틴 조선 서울 | josunhotel.com/hotel/westinSeoul.do | TWC | WJS |
| 3 | 웨스틴 조선 부산 | josunhotel.com/hotel/westinBusan.do | TWCB | WJB |
| 4 | 그랜드 조선 부산 | gjb.josunhotel.com | GJB | GJB |
| 5 | 그랜드 조선 제주 | gjj.josunhotel.com | GJJ | GJJ |
| 6 | 레스케이프 호텔 | les.josunhotel.com | LESCAPE | LES |
| 7 | 그래비티 조선 서울 판교 | grp.josunhotel.com | GRP | GVP |
| 8 | 포포인츠 서울역 | josunhotel.com/hotel/fpbsSeoul.do | FPBSS | FPS |
| 9 | 포포인츠 서울 명동 | josunhotel.com/hotel/fpbsMyeongdong.do | FPBSM | FPM |
| - | 전사 공통 | josunhotel.com | JOSUN HOTEL | JHR |
| 10 | 파라스파라 | paraspara.co.kr | - | - |
| 11 | 코랄로 바이 조선 | coralloyangyang.com | CBJ | YYH |
---
## 5. Meta / Kakao Pixel Event Mapping
### Meta (Facebook) Pixel
| User Action | Event Name | Pixel Code | GTM Tag Name |
|-------------|-----------|------------|--------------|
| 회원가입 완료 | 등록완료 | CompleteRegistration | Marketing - meta pixel - join_complete |
| 이벤트 자세히 보기 | 콘텐츠 조회 | ViewContent | Marketing - meta Pixel - package detail |
| 관심상품 담기 | 위시리스트에 추가 | AddToWishlist | Marketing - meta Pixel - bookmark |
| 룸/패키지 예약 클릭 | 예약 | Schedule | Marketing - mata Pixel - bookmark |
| 잠재고객 | 잠재고객 | Lead | (not configured) |
### Kakao Pixel
| User Action | Event Name | Event Code | GTM Tag Name |
|-------------|-----------|------------|--------------|
| 회원가입 완료 | 회원가입 | CompleteRegistration | Marketing - Kakao Pixel - join member complete |
| 이벤트 자세히 보기 | 콘텐츠/상품 조회 | ViewContent | Marketing - Kakao Pixel - package detail |
| 관심상품 담기 | 관심상품추가 | AddToWishList | Marketing - Kakao Pixel - bookmark |
| 룸/패키지 예약 클릭 | 구매 | Purchase | Marketing - Kakao Pixel - click booking |
| 잠재고객 (멤버십) | 잠재고객 | Participation - Consulting | Marketing - Kakao Pixel - button - membership_detail_view |
| 잠재고객 (매스프로모션) | 잠재고객 | Participation - PreBooking | Marketing - Kakao pixel - button - massPromotion_detail |
---
## 6. Custom Channel Grouping (JHR)
| Order | Channel | Custom Rules |
|-------|---------|-------------|
| 1 | Direct | (default) |
| 2 | Cross-network | (default) |
| 3 | Paid Shopping | (default) |
| 4 | Paid Search | (default) |
| 5 | Paid Social | + kakao / sms |
| 6 | Paid Video | (default) |
| 7 | Paid Other | (default) |
| 8 | Display | (default) |
| 9 | Map Search | place.map.kakao.com, m.place.naver.com, pcmap.place.naver.com, map.kakao.com |
| 10 | Organic Shopping | (default) |
| 11 | Organic Social | (default) |
| 12 | Organic Video | (default) |
| 13 | Organic Search | (default) |
| 14 | Email | (default) |
| 15 | Affiliates | + event.ssg.com, marriott.com, blossom.shinsegae.com |
| 16 | Referral | (default) |
| 17-19 | Audio / SMS / Mobile Push | (default) |
| - | Unassigned | hotelQR / QRcoupon |
---
## 7. eCommerce Product Categories
| Category | Description |
|----------|-------------|
| ROOM ONLY | 객실 상품만 제공 |
| 멤버십 전용 | CJM 회원 전용 상품 |
| 선결제 | 선결제 상품 (현재 미등록) |
| 얼리버드 | 조기 예약 상품 |
| 조식 | 조식 포함 패키지 |
| 패키지 | 프로모션 오퍼 포함 패키지 |
---
## 8. Event Catalog (173 events)
### Booking Process Events
| Event Name | Description |
|------------|-------------|
| room_search | Room search initiated |
| room_search_home | Room search from homepage |
| room_tab_room_only | Room only tab selected |
| room_tab_package | Package tab selected |
| room_tab_members_only | Members only tab selected |
| room_booking_step4 | Booking step 4 (confirmation) |
| room_booking_promo | Promo code applied |
| book_room | Room booking completed |
| begin_checkout | Checkout started |
| purchase | Purchase completed |
| refund | Refund processed |
### Dining Events
| Event Name | Description |
|------------|-------------|
| dining_booking_start | Dining booking initiated |
| dining_hotel_select | Hotel selected for dining |
| dining_restaurant_select | Restaurant selected |
| dining_step0_view ~ dining_step4_view | Dining booking steps |
| dining_book | Dining reservation completed |
| dining_menu_view_click | Menu viewed |
| dining_search_home | Dining search from home |
### Package & Event Events
| Event Name | Description |
|------------|-------------|
| package_list_view | Package listing viewed |
| event_list_view | Event listing viewed |
| event_see_detail | Event detail clicked |
| theme_pkg_detail | Theme package detail clicked |
| pageview_theme_pkg | Theme package page viewed |
| bookmark_promotion | Package/event bookmarked |
| event_share | Event shared (Kakao/email/SMS/URL) |
| massPromotion_detail | Mass promotion detail viewed |
### Membership Events
| Event Name | Description |
|------------|-------------|
| memberED2_list_view | Membership list viewed |
| memberED2_join | Membership join started |
| memberED2_detail_{TIER} | Tier detail viewed (BLACK/GOLD/PLATINUM/PREMIER/GRANDJOSUN) |
| memberED2_detail_share | Membership shared |
| memberED2_inquiry | Membership inquiry |
### Navigation & Site Events
| Event Name | Description |
|------------|-------------|
| GNB_all_menu | All menu opened |
| GNB_sub_menu_click | Sub menu clicked |
| main_select_hotel | Hotel selected from main |
| main_room_booking | Room booking from main |
| main_dining_booking | Dining booking from main |
| hotel_layer_view | Hotel info layer viewed |
| login_click | Login clicked |
| join_click | Join clicked |
| lang_change | Language changed |
### E-commerce Events (GA4 Standard)
| Event Name | Description |
|------------|-------------|
| view_item_list | Item list viewed |
| view_item | Item detail viewed |
| add_to_cart | Added to cart |
| add_to_wishlist | Added to wishlist |
| remove_from_wishlist | Removed from wishlist |
| begin_checkout | Checkout started |
| purchase | Purchase completed |
| refund | Refund |

View File

@@ -0,0 +1,298 @@
# JHR GA4 Tagging Reference
Source: [JHR - User Behaviour, Event Planning & GA setup](https://docs.google.com/spreadsheets/d/1rhzYApbZ4wbe1gE-E43E-mlRdLl_etkIOZnqs1g9X1U)
Container: GTM-NP6NJMP (JHR Website - 통합)
---
## 1. Tag Naming Convention
Format: `{Platform} {TriggerElement} - {Purpose} - {EventName}`
### Platform Categories
| Platform | Usage |
|----------|-------|
| GA4 | Google Analytics 4 events |
| Google Ads | Conversion tracking |
| Meta | Facebook/Instagram pixel |
| Kakao | Kakao pixel/moment |
| Marketing | Cross-platform marketing tags |
| Analytics | Analytics configuration tags |
| Site | Site-specific utility tags |
| SEO | SEO-related tags |
| Test | Testing/debug tags |
| {{Campaign Name}} | Campaign-specific tags (e.g., "LandersFesta") |
### Trigger Elements (what triggers the tag)
| Element | Description |
|---------|-------------|
| pageview | Page load / virtual pageview |
| click | Generic click |
| link | Link click |
| button | Button click |
| tab | Tab selection |
| form | Form submission |
| layer | Layer/modal/popup view |
| popup | Popup interaction |
| banner | Banner click/impression |
| dropdown | Dropdown selection |
| radio | Radio button selection |
| checkbox | Checkbox toggle |
| ajax | AJAX request complete |
| dataLayer | DataLayer push event |
### Tag Purposes (page/feature area)
| Purpose | Area |
|---------|------|
| booking | Room/package reservation process |
| dining | Dining reservation process |
| membership | Club Josun membership |
| package | Package/event listing & detail |
| theme | Theme package pages |
| campaign | Campaign/promotion pages |
| event | Event pages |
| timesale | Time-limited sale pages |
| survey | Survey/feedback |
| ecommerce | E-commerce tracking |
| SEO | SEO schema/config |
| site | Site-wide interactions |
| config | Configuration/settings |
| ux | UX tracking |
| test | Testing |
### Examples
```
GA4 pageview - booking - room_booking_step1
GA4 button - theme - theme_pkg_detail
GA4 click - campaign - kakao_channel_add
Kakao banner - timesale - {{Event Name}}
Meta form - event - {{Event Name}}
Marketing - meta pixel - join_complete
Marketing - Kakao Pixel - package detail
```
---
## 2. Inherited Settings Variables
Tags inherit common parameters through shared settings variables.
This avoids duplicating parameters across hundreds of tags.
### GA4 Configuration Setting (shared across ALL GA4 tags)
| Parameter | Variable | Description |
|-----------|----------|-------------|
| login_status | {{cjs - login status Variable}} | Login state |
| user_lang | locale={{cjs - page language Variable}} | Page language |
| contents_group_hotel | {{Contents Group 1 - Hotel Site}} | Hotel brand grouping |
| contents_group_menu | {{Contents Group new - Menu Categories}} | Page section grouping |
| naver_query | {{URL referrer - naver query}} | Naver search query |
| naver_oquery | {{URL referrer - naver oquery}} | Naver original query |
### GA4 Event Settings (added to event tags)
| Parameter | Variable |
|-----------|----------|
| page_path | {{Page Path}} |
| login_status | {{cjs - login status Variable}} |
| user_lang | {{cjs - page language Variable}} |
### GA4 eCommerce Settings (added to ecommerce tags)
| Parameter | Variable |
|-----------|----------|
| pkg_name | {{dlv - eCommerce - item_name}} |
| pkg_hotel | {{dlv - eCommerce - item_brand}} |
| pkg_price | {{dlv - eCommerce - price}} |
| pkg_room_type | {{dlv - eCommerce - item_variant}} |
| pkg_id | {{dlv - eCommerce - item_id}} |
| page_path | {{Page Path}} |
### GA4 Promotion Settings (added to package/event detail tags)
| Parameter | Variable |
|-----------|----------|
| page_path | {{Page Path}} |
| login_status | {{cjs - login status Variable}} |
| user_lang | {{cjs - page language Variable}} |
| packageSn | {{URL query - packageSn Variable}} |
| themeSn | {{URL query - themeSn Variable}} |
| eventSn | {{URL query - eventSn Variable}} |
---
## 3. Standard Parameter Keys
| Parameter Name | Allowed Values / Variable |
|----------------|--------------------------|
| event_type | click |
| ui | GNB, modal, button, tab, link, banner, footer |
| page_path | {{Page Path}} |
| login_status | (from cjs variable) |
| user_lang | (from cjs variable) |
| pkg_name | (package/event title) |
| click_text | (clicked element text) |
| click_url | (clicked element URL) |
---
## 4. Property Codes
| # | Hotel Name | URL | Website Code | Report Code |
|---|-----------|-----|-------------|-------------|
| 1 | 조선 팰리스 | jpg.josunhotel.com | JPY | JPG |
| 2 | 웨스틴 조선 서울 | josunhotel.com/hotel/westinSeoul.do | TWC | WJS |
| 3 | 웨스틴 조선 부산 | josunhotel.com/hotel/westinBusan.do | TWCB | WJB |
| 4 | 그랜드 조선 부산 | gjb.josunhotel.com | GJB | GJB |
| 5 | 그랜드 조선 제주 | gjj.josunhotel.com | GJJ | GJJ |
| 6 | 레스케이프 호텔 | les.josunhotel.com | LESCAPE | LES |
| 7 | 그래비티 조선 서울 판교 | grp.josunhotel.com | GRP | GVP |
| 8 | 포포인츠 서울역 | josunhotel.com/hotel/fpbsSeoul.do | FPBSS | FPS |
| 9 | 포포인츠 서울 명동 | josunhotel.com/hotel/fpbsMyeongdong.do | FPBSM | FPM |
| - | 전사 공통 | josunhotel.com | JOSUN HOTEL | JHR |
| 10 | 파라스파라 | paraspara.co.kr | - | - |
| 11 | 코랄로 바이 조선 | coralloyangyang.com | CBJ | YYH |
---
## 5. Meta / Kakao Pixel Event Mapping
### Meta (Facebook) Pixel
| User Action | Event Name | Pixel Code | GTM Tag Name |
|-------------|-----------|------------|--------------|
| 회원가입 완료 | 등록완료 | CompleteRegistration | Marketing - meta pixel - join_complete |
| 이벤트 자세히 보기 | 콘텐츠 조회 | ViewContent | Marketing - meta Pixel - package detail |
| 관심상품 담기 | 위시리스트에 추가 | AddToWishlist | Marketing - meta Pixel - bookmark |
| 룸/패키지 예약 클릭 | 예약 | Schedule | Marketing - mata Pixel - bookmark |
| 잠재고객 | 잠재고객 | Lead | (not configured) |
### Kakao Pixel
| User Action | Event Name | Event Code | GTM Tag Name |
|-------------|-----------|------------|--------------|
| 회원가입 완료 | 회원가입 | CompleteRegistration | Marketing - Kakao Pixel - join member complete |
| 이벤트 자세히 보기 | 콘텐츠/상품 조회 | ViewContent | Marketing - Kakao Pixel - package detail |
| 관심상품 담기 | 관심상품추가 | AddToWishList | Marketing - Kakao Pixel - bookmark |
| 룸/패키지 예약 클릭 | 구매 | Purchase | Marketing - Kakao Pixel - click booking |
| 잠재고객 (멤버십) | 잠재고객 | Participation - Consulting | Marketing - Kakao Pixel - button - membership_detail_view |
| 잠재고객 (매스프로모션) | 잠재고객 | Participation - PreBooking | Marketing - Kakao pixel - button - massPromotion_detail |
---
## 6. Custom Channel Grouping (JHR)
| Order | Channel | Custom Rules |
|-------|---------|-------------|
| 1 | Direct | (default) |
| 2 | Cross-network | (default) |
| 3 | Paid Shopping | (default) |
| 4 | Paid Search | (default) |
| 5 | Paid Social | + kakao / sms |
| 6 | Paid Video | (default) |
| 7 | Paid Other | (default) |
| 8 | Display | (default) |
| 9 | Map Search | place.map.kakao.com, m.place.naver.com, pcmap.place.naver.com, map.kakao.com |
| 10 | Organic Shopping | (default) |
| 11 | Organic Social | (default) |
| 12 | Organic Video | (default) |
| 13 | Organic Search | (default) |
| 14 | Email | (default) |
| 15 | Affiliates | + event.ssg.com, marriott.com, blossom.shinsegae.com |
| 16 | Referral | (default) |
| 17-19 | Audio / SMS / Mobile Push | (default) |
| - | Unassigned | hotelQR / QRcoupon |
---
## 7. eCommerce Product Categories
| Category | Description |
|----------|-------------|
| ROOM ONLY | 객실 상품만 제공 |
| 멤버십 전용 | CJM 회원 전용 상품 |
| 선결제 | 선결제 상품 (현재 미등록) |
| 얼리버드 | 조기 예약 상품 |
| 조식 | 조식 포함 패키지 |
| 패키지 | 프로모션 오퍼 포함 패키지 |
---
## 8. Event Catalog (173 events)
### Booking Process Events
| Event Name | Description |
|------------|-------------|
| room_search | Room search initiated |
| room_search_home | Room search from homepage |
| room_tab_room_only | Room only tab selected |
| room_tab_package | Package tab selected |
| room_tab_members_only | Members only tab selected |
| room_booking_step4 | Booking step 4 (confirmation) |
| room_booking_promo | Promo code applied |
| book_room | Room booking completed |
| begin_checkout | Checkout started |
| purchase | Purchase completed |
| refund | Refund processed |
### Dining Events
| Event Name | Description |
|------------|-------------|
| dining_booking_start | Dining booking initiated |
| dining_hotel_select | Hotel selected for dining |
| dining_restaurant_select | Restaurant selected |
| dining_step0_view ~ dining_step4_view | Dining booking steps |
| dining_book | Dining reservation completed |
| dining_menu_view_click | Menu viewed |
| dining_search_home | Dining search from home |
### Package & Event Events
| Event Name | Description |
|------------|-------------|
| package_list_view | Package listing viewed |
| event_list_view | Event listing viewed |
| event_see_detail | Event detail clicked |
| theme_pkg_detail | Theme package detail clicked |
| pageview_theme_pkg | Theme package page viewed |
| bookmark_promotion | Package/event bookmarked |
| event_share | Event shared (Kakao/email/SMS/URL) |
| massPromotion_detail | Mass promotion detail viewed |
### Membership Events
| Event Name | Description |
|------------|-------------|
| memberED2_list_view | Membership list viewed |
| memberED2_join | Membership join started |
| memberED2_detail_{TIER} | Tier detail viewed (BLACK/GOLD/PLATINUM/PREMIER/GRANDJOSUN) |
| memberED2_detail_share | Membership shared |
| memberED2_inquiry | Membership inquiry |
### Navigation & Site Events
| Event Name | Description |
|------------|-------------|
| GNB_all_menu | All menu opened |
| GNB_sub_menu_click | Sub menu clicked |
| main_select_hotel | Hotel selected from main |
| main_room_booking | Room booking from main |
| main_dining_booking | Dining booking from main |
| hotel_layer_view | Hotel info layer viewed |
| login_click | Login clicked |
| join_click | Join clicked |
| lang_change | Language changed |
### E-commerce Events (GA4 Standard)
| Event Name | Description |
|------------|-------------|
| view_item_list | Item list viewed |
| view_item | Item detail viewed |
| add_to_cart | Added to cart |
| add_to_wishlist | Added to wishlist |
| remove_from_wishlist | Removed from wishlist |
| begin_checkout | Checkout started |
| purchase | Purchase completed |
| refund | Refund |

View File

@@ -0,0 +1,225 @@
---
name: gtm-validator
description: >
GTM QA and validation toolkit. Verifies tags fire correctly on live pages,
tests trigger conditions against actual DOM, validates dataLayer schemas,
checks naming conventions, compares cross-platform events, and runs QA checklists.
Uses Chrome DevTools MCP for live page testing.
Triggers on "validate tags", "check triggers", "verify tracking", "QA GTM",
"test events", "debug GTM", "troubleshoot", "naming conventions",
"GTM question", "GTM best practice", "compare versions".
NOT for auditing (use gtm-audit) or creating/modifying (use gtm-editor).
---
# GTM Validator Skill
Verify GTM implementations work correctly on live pages. Test triggers, validate dataLayer, check naming conventions.
## Available Tools
### Browser Testing (Chrome DevTools MCP)
- `navigate_page` — Load page to test
- `evaluate_script` — Check dataLayer state, test selectors, verify globals
- `list_network_requests` — Verify tag firing after interactions
- `click` / `hover` — Simulate user actions to trigger events
- `list_console_messages` — Check for JS errors
- `take_screenshot` — Document test results
### GTM Container (DTM Agent MCP)
- `dtm_status` — Check active container
- `dtm_list_tags` / `dtm_get_tag` — Get tag configurations
- `dtm_list_triggers` / `dtm_get_trigger` — Get trigger conditions
- `dtm_list_variables` / `dtm_get_variable` — Get variable definitions
- `dtm_list_versions` / `dtm_get_live_version` — Compare versions
## Validation Modes
### 1. Tag Firing Verification
Test that specific tags fire on the correct pages and interactions.
**Steps:**
1. `dtm_list_tags` — get all active tags with their triggers
2. `navigate_page` to target URL
3. `list_network_requests` — capture initial tag firings
4. For click triggers: `click` the element, then check `list_network_requests` again
5. Compare: expected tags (from GTM config) vs actual network requests
**Verification script for specific tag:**
```javascript
// Check if GA4 event fired
(function(){
var dl = window.dataLayer || [];
var events = dl.filter(function(e){return e.event && e.event !== 'gtm.js' && e.event !== 'gtm.dom' && e.event !== 'gtm.load';});
return JSON.stringify(events.map(function(e){return {event: e.event, params: Object.keys(e).filter(function(k){return k !== 'event' && !k.startsWith('gtm.')})}}));
})()
```
### 2. Trigger Condition Testing
Verify trigger CSS selectors and conditions match the actual page DOM.
**Steps:**
1. `dtm_get_trigger` — get trigger filter/autoEventFilter
2. Extract CSS selectors from trigger config
3. `evaluate_script` — test if selectors match elements on page:
```javascript
// Test CSS selector
(function(selector){
var els = document.querySelectorAll(selector);
return JSON.stringify({
selector: selector,
matchCount: els.length,
elements: Array.from(els).slice(0,5).map(function(el){
return {tag: el.tagName, id: el.id, classes: el.className, text: (el.textContent||'').trim().substring(0,30)};
})
});
})('.hero a.btn')
```
4. If selector matches 0 elements → trigger will NEVER fire → **CRITICAL issue**
5. If selector matches too many elements → trigger may fire incorrectly → **WARNING**
### 3. DataLayer Schema Validation
Verify dataLayer pushes contain required fields with correct types.
**GA4 Recommended Event Schemas:**
| Event | Required Fields |
|-------|----------------|
| `page_view` | (automatic) |
| `generate_lead` | — (value, currency optional) |
| `purchase` | transaction_id, value, currency, items[] |
| `add_to_cart` | items[] (item_id, item_name, price, quantity) |
| `form_start` | form_id, form_name |
| `form_submit` | form_id, form_name |
**Validation script:**
```javascript
// Check dataLayer for specific event schema
(function(eventName){
var dl = window.dataLayer || [];
var events = dl.filter(function(e){return e.event === eventName;});
if(events.length === 0) return JSON.stringify({found: false, event: eventName});
var e = events[events.length-1];
return JSON.stringify({found: true, event: eventName, keys: Object.keys(e), data: e});
})('generate_lead')
```
### 4. Naming Convention Check
Verify all GTM resources follow D.intelligence naming standards.
**Tag naming:**
```
[Platform] - [event_name] [context]
Examples:
GA4 - generate_lead (contact form)
sGTM - purchase
cHTML - Contact Form dataLayer Events
FB_CONVERSIONS_API-PIXEL_ID-Web-Tag-GA4_Event
```
**Trigger naming:**
```
[Type] - [description] Trigger
Examples:
Click - Hero CTA (consult)
CE - contact_form_submit
Form Submit - contact-form
Scroll - 50pct Depth
PV - consult booking Trigger
```
**Variable naming:**
```
[prefix] - [description]
Prefixes: dlv (dataLayer), cjs (Custom JS), aev (Auto-Event),
jsv (JS Variable), URL query, cookie, c (Constant)
Examples:
dlv - contact_form.request_type
cjs - Social platform
aev - click url hostname
```
**Validation steps:**
1. `dtm_list_tags` / `dtm_list_triggers` / `dtm_list_variables`
2. Check each name against patterns above
3. Flag violations: missing prefix, wrong case, unclear description
### 5. Cross-Platform Event Mapping Verification
Verify the same user action sends consistent events to all platforms.
| User Action | GA4 | Meta (FB) | Google Ads | Kakao | Naver |
|------------|-----|-----------|------------|-------|-------|
| Page view | page_view | PageView | — | pageView | — |
| Product view | view_item | ViewContent | — | viewContent | — |
| Add to cart | add_to_cart | AddToCart | — | addToCart | — |
| Purchase | purchase | Purchase | purchase | purchase | — |
| Lead form | generate_lead | Lead | submit_lead_form | participation | — |
**Steps:**
1. Trigger a user action (click, form submit)
2. `list_network_requests` — capture all resulting requests
3. Check each platform received the correct event:
- GA4: `google-analytics.com/g/collect?en=...`
- Meta: `facebook.com/tr/?ev=...`
- Google Ads: `googleads.g.doubleclick.net/pagead/...`
### 6. QA Checklist Execution
Run through a standard QA checklist for GTM implementation:
- [ ] GTM container snippet in `<head>` (not `<body>`)
- [ ] GTM noscript fallback in `<body>`
- [ ] No duplicate GTM containers on same page
- [ ] dataLayer initialized before GTM snippet
- [ ] All P1 tags fire on page load (GA4 config, Consent Mode)
- [ ] Click triggers use specific selectors (not generic classes)
- [ ] Form triggers match form IDs that exist on page
- [ ] Custom HTML tags use ES5 syntax only
- [ ] No console errors related to GTM/tags
- [ ] Consent Mode properly blocks tags before consent
- [ ] sGTM endpoint responding (no SSL errors)
- [ ] Cross-domain tracking configured if needed
### 7. Version Comparison
Compare tags between two container versions to identify changes.
1. `dtm_list_versions` — get version list
2. `dtm_get_live_version` — get current live version with all tags
3. Compare tag counts, trigger counts, new/modified/deleted items
4. Report changes since last publish
## Reference Files
| File | Content |
|------|---------|
| `references/datalayer-schema.md` | GA4 recommended event schemas |
| `references/platform-mapping.md` | GA4 ↔ Meta ↔ Kakao ↔ Ads event mapping |
| `references/naming-conventions.md` | Tag/trigger/variable naming rules |
| `references/qa-checklist.md` | Full QA checklist |
## Workflow
1. **Receive** tag list from gtm-editor (or run `dtm_list_tags`)
2. **Navigate** to the target page via Chrome DevTools
3. **Test** each trigger's CSS selector matches actual DOM elements
4. **Simulate** user actions (clicks, form submits, scrolls)
5. **Capture** network requests after each action
6. **Verify** correct events fired to correct platforms
7. **Report** pass/fail with evidence (screenshots, network logs)
## Rules
- Always test on the LIVE published version, not preview (unless debugging)
- Test on both desktop and mobile viewports when possible
- Check consent mode state before flagging missing tags
- Document every test result with screenshots or network request evidence
- **When a trigger uses CSS selectors instead of IDs, flag it as a fragility risk** — recommend adding `id` attributes to the HTML
- If a trigger selector doesn't match, suggest adding an ID to the element first (not a different CSS selector) and hand off to gtm-editor
- GTM questions, best practices, and architecture advice are also in scope

View File

@@ -0,0 +1,193 @@
# DataLayer Schema Reference
GA4 권장 이벤트 기반 DataLayer 스키마 정의.
## Core Principles
1. GTM 스니펫 이전에 dataLayer 초기화
2. 이벤트 발생 시점에 push (사전 아님)
3. ecommerce 객체는 push 전 clear 필수
4. PII 데이터 직접 수집 금지
## Base Structure
### Page Load
```javascript
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'page_data',
'page': {
'type': 'home|category|product|cart|checkout|thankyou|content|search',
'name': 'Page Name',
'category': 'Category/Subcategory',
'language': 'ko'
},
'user': {
'status': 'logged_in|logged_out',
'id': 'hashed_user_id', // SHA256, 로그인 시에만
'type': 'new|returning|member|guest',
'loginMethod': 'email|kakao|naver|google'
}
});
```
## E-commerce Events
### view_item
```javascript
dataLayer.push({ ecommerce: null });
dataLayer.push({
'event': 'view_item',
'ecommerce': {
'currency': 'KRW',
'value': 29000,
'items': [{
'item_id': 'SKU_12345',
'item_name': 'Product Name',
'item_brand': 'Brand Name',
'item_category': 'Category',
'item_variant': 'Blue / Large',
'price': 29000,
'quantity': 1
}]
}
});
```
### add_to_cart
```javascript
dataLayer.push({ ecommerce: null });
dataLayer.push({
'event': 'add_to_cart',
'ecommerce': {
'currency': 'KRW',
'value': 29000,
'items': [{
'item_id': 'SKU_12345',
'item_name': 'Product Name',
'price': 29000,
'quantity': 1
}]
}
});
```
### begin_checkout
```javascript
dataLayer.push({ ecommerce: null });
dataLayer.push({
'event': 'begin_checkout',
'ecommerce': {
'currency': 'KRW',
'value': 58000,
'coupon': 'WELCOME10',
'items': [/* cart items */]
}
});
```
### purchase
```javascript
dataLayer.push({ ecommerce: null });
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': 'T_20250115_001',
'affiliation': 'Store Name',
'value': 53000,
'tax': 4818,
'shipping': 3000,
'currency': 'KRW',
'coupon': 'WELCOME10',
'items': [{
'item_id': 'SKU_12345',
'item_name': 'Product Name',
'price': 29000,
'quantity': 1
}]
}
});
```
## Lead Gen Events
### generate_lead
```javascript
dataLayer.push({
'event': 'generate_lead',
'currency': 'KRW',
'value': 50000,
'lead': {
'source': 'contact_form|landing_page',
'type': 'inquiry|quote_request'
}
});
```
### form_submit
```javascript
dataLayer.push({
'event': 'form_submit',
'form': {
'id': 'contact_form',
'name': 'Contact Us Form',
'type': 'contact',
'success': true
}
});
```
## Engagement Events
### login / sign_up
```javascript
dataLayer.push({
'event': 'login', // or 'sign_up'
'method': 'email|kakao|naver|google'
});
```
### search
```javascript
dataLayer.push({
'event': 'search',
'search_term': 'user search query',
'search': {
'results_count': 42,
'category': 'product|content'
}
});
```
## Item Object Reference
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| item_id | string | Yes | SKU |
| item_name | string | Yes | 상품명 |
| price | number | Yes | 단가 |
| quantity | number | Yes | 수량 |
| item_brand | string | No | 브랜드명 |
| item_category | string | No | 카테고리 |
| item_category2-5 | string | No | 하위 카테고리 |
| item_variant | string | No | 옵션 |
| coupon | string | No | 쿠폰 코드 |
| discount | number | No | 할인 금액 |
| index | number | No | 목록 위치 |
## Validation Rules
```javascript
// ✅ Correct
'price': 29000 // number
'quantity': 1 // number
'currency': 'KRW' // uppercase
// ❌ Wrong
'price': '29000' // string
'currency': 'krw' // lowercase
```
### Array Limits
- items[]: 최대 200개
- 초과 시 분할 전송

View File

@@ -0,0 +1,130 @@
# GTM Naming Conventions
일관된 GTM 컨테이너 관리를 위한 네이밍 규칙.
## Tag Naming
### Pattern
```
[Platform] - [Type] - [Description]
```
### Examples
| Platform | Type | Description | Tag Name |
|----------|------|-------------|----------|
| GA4 | Config | Main | GA4 - Config - Main |
| GA4 | Event | purchase | GA4 - Event - purchase |
| GAds | Conversion | Purchase | GAds - Conversion - Purchase |
| Meta | Event | Purchase | Meta - Event - Purchase |
| Kakao | Event | purchase | Kakao - Event - purchase |
## Trigger Naming
### Pattern
```
[Type] - [Condition] - [Context]
```
### Type Codes
| Code | Type | Example |
|------|------|---------|
| PV | Page View | PV - All Pages |
| DL | DataLayer Event | DL - purchase |
| Click | Click | Click - CTA Button |
| Form | Form Submit | Form - Contact |
| Scroll | Scroll Depth | Scroll - 75% |
| Vis | Element Visibility | Vis - Video Player |
### Examples
```
PV - All Pages
PV - Thank You Page
DL - purchase
DL - add_to_cart
Click - CTA Button - Hero
Form - Contact Form
Scroll - 90%
```
## Variable Naming
### Pattern
```
[Type] - [Name/Path]
```
### Type Codes
| Code | Type | Example |
|------|------|---------|
| DLV | DataLayer Variable | DLV - ecommerce.value |
| CJS | Custom JavaScript | CJS - Item IDs Array |
| Const | Constant | Const - GA4 Measurement ID |
| LT | Lookup Table | LT - Page Type Mapping |
| URL | URL Variable | URL - Path |
| DOM | DOM Element | DOM - Product Title |
### Examples
```
DLV - event
DLV - ecommerce.transaction_id
DLV - ecommerce.items
CJS - Items for Meta
CJS - Formatted Price
Const - GA4 Measurement ID
Const - Meta Pixel ID
LT - Payment Method Mapping
```
## Folder Structure
```
📁 01. Configuration
📁 02. GA4 Events
📁 03. Google Ads
📁 04. Meta Pixel
📁 05. Kakao Pixel
📁 06. Other Platforms
📁 07. Utilities
📁 99. Deprecated
```
## Built-in Variables to Enable
```
Pages: ✅ Page URL, Path, Hostname, Referrer
Utilities: ✅ Event, Container ID, Random Number
Clicks: ✅ Element, Classes, ID, URL, Text
Forms: ✅ Element, Classes, ID, URL, Text
History: ✅ New/Old History Fragment/State
Videos: ✅ All YouTube variables
Scrolling: ✅ Depth Threshold, Units, Direction
Visibility: ✅ Percent Visible, On-Screen Duration
```
## Anti-patterns
```
❌ Tag 1, New Tag, Test
❌ GA4 purchase event (일관성 없음)
❌ G4-E-Pch (과도한 약어)
❌ Very Long Tag Name for Purchase Complete Event (너무 김)
✅ GA4 - Event - purchase
✅ DL - purchase
✅ CJS - Items for Meta
```
## Version Notes
```
v[Major].[Minor] - [Description]
예: v12.1 - Added form_submit event
## Changes
- Added:
- Modified:
- Removed:
## Reason
[변경 이유]
```

View File

@@ -0,0 +1,215 @@
# Phase 6: Progressive Audit & Update
설정된 GTM Container에 대한 주기적 진단, 유효성 검증, 마이너 업데이트 및 태그 추가.
## Objectives
1. GTM 컨테이너 건강 상태 점검
2. 태그 발화 및 데이터 정합성 검증
3. 최신 요구사항 반영 업데이트
4. D.intelligence GTM Toolkit 연계 자동화
## Audit Schedule
| Frequency | Scope | Trigger |
|-----------|-------|---------|
| Weekly | Tag firing validation | 자동 스케줄 |
| Monthly | Full container review | 월간 리포트 |
| Quarterly | Architecture review | 분기 회의 |
| Ad-hoc | Issue investigation | 이슈 발생 시 |
## Audit Checklist
### 1. Container Health Check
- [ ] 버전 히스토리 확인 (최근 변경사항)
- [ ] 미사용 태그/트리거/변수 식별
- [ ] 명명 규칙 준수 여부
- [ ] 폴더 구조 정리 상태
### 2. Tag Firing Validation
- [ ] 모든 P1 태그 정상 발화
- [ ] 트리거 조건 정확성
- [ ] 변수 값 정확성
- [ ] 차단 트리거 작동
### 3. Data Quality
- [ ] GA4 Realtime 데이터 확인
- [ ] 전환 데이터 정합성
- [ ] 파라미터 값 검증
- [ ] 이상치 탐지
### 4. Platform Sync
- [ ] GA4 ↔ GTM 동기화
- [ ] Google Ads 전환 데이터
- [ ] Meta Events Manager 상태
- [ ] Kakao Pixel 상태
## D.intelligence GTM Toolkit Integration
### Repository
```
https://github.com/ourdigital/dintel-gtm-toolkit
```
### Capabilities
| Feature | Description | Use Case |
|---------|-------------|----------|
| Container Analysis | JSON 파싱, 구조 분석 | 컨테이너 리뷰 |
| Dependency Mapping | 태그↔트리거↔변수 관계 | 영향도 분석 |
| Version Diff | 버전 간 변경사항 비교 | 변경 추적 |
| Tag Validation | 발화 상태 자동 검증 | 정기 감사 |
### Integration Workflow
```
1. GTM Container Export
└── JSON 파일 다운로드
2. D.intel Toolkit Analysis
├── python analyze_container.py container.json
├── Dependency graph 생성
└── Issue detection
3. Report Generation
└── Markdown/HTML 리포트
4. GTM Guardian Update
└── Notion에 리포트 저장
```
### Sample Toolkit Commands
```bash
# Container 분석
python analyze_container.py GTM-XXXXXX.json --output report.md
# 버전 비교
python diff_versions.py v1.json v2.json --output diff.md
# 태그 검증
python validate_tags.py container.json --events events.csv
# 미사용 요소 탐지
python find_unused.py container.json --type all
```
## Audit Report Template
```markdown
# GTM Audit Report
## Summary
- **Container**: GTM-XXXXXX
- **Audit Date**: YYYY-MM-DD
- **Version Reviewed**: XX
- **Overall Status**: ✅ Healthy / ⚠️ Needs Attention / ❌ Critical
## Findings
### ✅ Passing
- [항목]
### ⚠️ Warnings
| Issue | Impact | Recommendation |
|-------|--------|----------------|
### ❌ Critical
| Issue | Impact | Action Required |
|-------|--------|-----------------|
## Tag Firing Status
| Tag | Expected | Actual | Status |
|-----|----------|--------|--------|
| GA4 - Event - purchase | Thank You | Thank You | ✅ |
## Data Quality Check
| Metric | Expected | Actual | Variance |
|--------|----------|--------|----------|
| Daily Purchases | ~50 | 48 | -4% |
## Recommendations
1.
2.
## Next Steps
- [ ]
- [ ]
## Appendix
- Container JSON snapshot
- D.intel Toolkit output
```
## Update Procedures
### Minor Update (태그 수정)
```
1. 변경 필요 태그 식별
2. Preview 모드에서 수정
3. 테스트 완료
4. Publish with version note
5. Notion 문서 업데이트
```
### Major Update (신규 태그 추가)
```
1. Phase 3-4 문서 업데이트
2. DataLayer 요구사항 개발팀 전달
3. GTM 태그/트리거/변수 설정
4. QA 체크리스트 수행
5. 단계적 배포
6. Notion 문서 업데이트
```
### Emergency Fix
```
1. 이슈 원인 파악
2. 최소 변경으로 수정
3. Publish (emergency version note)
4. 근본 원인 분석
5. 영구 수정 계획
```
## Versioning Best Practices
### Version Naming
```
v[Major].[Minor] - [Description]
예: v12.1 - Added form_submit event
```
### Version Notes Template
```
## Changes
- Added: [새로 추가된 항목]
- Modified: [수정된 항목]
- Removed: [삭제된 항목]
## Reason
[변경 이유]
## Testing
- [테스트 항목 및 결과]
## Rollback Plan
[롤백 필요 시 절차]
```
## Monitoring Setup
### GA4 Alerts
- 일일 전환 수 급감 (>30%)
- 세션 수 급감 (>50%)
- 이벤트 수집 오류
### Custom Monitoring
- DataLayer 오류 로깅
- Tag Manager 오류 추적
- 서버 사이드 태그 상태 (sGTM 시)
## Next Phase
주기적 감사 완료 후 → Phase 7: Lookup App (필요 시)

View File

@@ -0,0 +1,283 @@
# Phase 7: Event Lookup & Parameter Library Web App
Google Apps Script를 활용한 Event Taxonomy Sheet 기반 업무 지원용 앱 배포.
## Objectives
1. Event Taxonomy 데이터 조회 앱
2. 실시간 파라미터 참조 도구
3. 비개발자용 Self-service 도구
4. 팀 간 일관된 이벤트 정보 공유
## App Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Google Sheets │────▶│ Apps Script │────▶│ Web App │
│ (Taxonomy DB) │ │ (Backend) │ │ (Frontend) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
## Google Sheets Data Source
### Required Sheets
- Events Master
- Parameters Reference
- Custom Definitions
- Platform Mapping
### Column Requirements
→ Phase 4 참조: [phase4-taxonomy.md](phase4-taxonomy.md)
## Apps Script Setup
### 1. Project Creation
```
1. Google Sheets 열기
2. Extensions > Apps Script
3. 프로젝트 이름 설정
```
### 2. Core Functions
#### Data Retrieval
```javascript
function getEventsData() {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Events Master');
const data = sheet.getDataRange().getValues();
const headers = data[0];
return data.slice(1).map(row => {
const obj = {};
headers.forEach((header, i) => {
obj[header] = row[i];
});
return obj;
});
}
function getParametersData() {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Parameters Reference');
const data = sheet.getDataRange().getValues();
const headers = data[0];
return data.slice(1).map(row => {
const obj = {};
headers.forEach((header, i) => {
obj[header] = row[i];
});
return obj;
});
}
```
#### Search Function
```javascript
function searchEvents(query) {
const events = getEventsData();
const searchTerm = query.toLowerCase();
return events.filter(event =>
event.event_name.toLowerCase().includes(searchTerm) ||
event.event_category.toLowerCase().includes(searchTerm) ||
event.notes.toLowerCase().includes(searchTerm)
);
}
function getEventDetails(eventName) {
const events = getEventsData();
const params = getParametersData();
const event = events.find(e => e.event_name === eventName);
if (!event) return null;
const eventParams = event.parameters.split(',').map(p => p.trim());
const paramDetails = params.filter(p =>
eventParams.includes(p.parameter_name)
);
return {
event: event,
parameters: paramDetails
};
}
```
### 3. Web App Handler
```javascript
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('index')
.setTitle('GTM Event Lookup')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function doPost(e) {
const action = e.parameter.action;
switch(action) {
case 'search':
return ContentService.createTextOutput(
JSON.stringify(searchEvents(e.parameter.query))
).setMimeType(ContentService.MimeType.JSON);
case 'details':
return ContentService.createTextOutput(
JSON.stringify(getEventDetails(e.parameter.eventName))
).setMimeType(ContentService.MimeType.JSON);
default:
return ContentService.createTextOutput(
JSON.stringify({error: 'Invalid action'})
).setMimeType(ContentService.MimeType.JSON);
}
}
```
### 4. HTML Frontend (index.html)
```html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body { font-family: 'Roboto', sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }
.search-box { width: 100%; padding: 12px; font-size: 16px; margin-bottom: 20px; }
.event-card { border: 1px solid #ddd; padding: 16px; margin: 10px 0; border-radius: 8px; }
.event-name { font-size: 18px; font-weight: bold; color: #1a73e8; }
.param-badge { background: #e8f0fe; color: #1967d2; padding: 4px 8px; border-radius: 4px; margin: 2px; display: inline-block; }
.category { color: #5f6368; font-size: 14px; }
.priority-p1 { border-left: 4px solid #ea4335; }
.priority-p2 { border-left: 4px solid #fbbc04; }
.priority-p3 { border-left: 4px solid #34a853; }
</style>
</head>
<body>
<h1>🏷️ GTM Event Lookup</h1>
<input type="text" class="search-box" id="searchInput" placeholder="이벤트명 또는 카테고리 검색...">
<div id="results"></div>
<script>
const searchInput = document.getElementById('searchInput');
const results = document.getElementById('results');
searchInput.addEventListener('input', debounce(function() {
const query = this.value;
if (query.length < 2) {
results.innerHTML = '';
return;
}
google.script.run
.withSuccessHandler(displayResults)
.searchEvents(query);
}, 300));
function displayResults(events) {
results.innerHTML = events.map(event => `
<div class="event-card priority-${event.priority.toLowerCase()}">
<div class="event-name">${event.event_name}</div>
<div class="category">${event.event_category} | ${event.priority}</div>
<div style="margin-top: 10px;">
${event.parameters.split(',').map(p =>
`<span class="param-badge">${p.trim()}</span>`
).join('')}
</div>
<div style="margin-top: 10px; color: #5f6368;">${event.notes}</div>
</div>
`).join('');
}
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
</script>
</body>
</html>
```
## Deployment
### 1. Deploy as Web App
```
1. Deploy > New deployment
2. Type: Web app
3. Execute as: Me
4. Access: Anyone within organization (또는 Anyone)
5. Deploy
```
### 2. Get Web App URL
```
배포 후 제공되는 URL 복사
https://script.google.com/macros/s/[ID]/exec
```
### 3. Share with Team
- URL을 Notion/Slack에 공유
- 북마크 권장
## Features Roadmap
### Phase 1 (MVP)
- [x] 이벤트 검색
- [x] 파라미터 조회
- [x] 우선순위 필터
### Phase 2
- [ ] DataLayer 코드 스니펫 복사
- [ ] 플랫폼별 코드 예시
- [ ] 최근 조회 히스토리
### Phase 3
- [ ] 이벤트 추가/수정 폼
- [ ] 변경 이력 추적
- [ ] Slack 알림 연동
## Maintenance
### Data Update
1. Google Sheets 원본 데이터 수정
2. Web App 자동 반영 (실시간)
### Code Update
1. Apps Script 수정
2. Deploy > Manage deployments
3. New version 배포
### Backup
- Google Sheets 자동 버전 히스토리
- 주기적 수동 백업 권장
## Access Control
| Role | Access Level |
|------|--------------|
| Admin | 시트 편집 + 스크립트 수정 |
| Editor | 시트 편집 |
| Viewer | Web App 조회만 |
## Troubleshooting
| Issue | Solution |
|-------|----------|
| 앱 로딩 느림 | 데이터 캐싱 구현 |
| 검색 결과 없음 | 검색어 확인, 데이터 존재 확인 |
| 권한 오류 | 배포 설정에서 접근 권한 확인 |
| 데이터 미반영 | 시트 새로고침, 캐시 클리어 |
## Integration with Notion
Web App URL을 Notion에 embed:
```
/embed [Web App URL]
```
또는 iframe으로 직접 삽입.

View File

@@ -0,0 +1,125 @@
# Platform Event Mapping Reference
GA4, Google Ads, Meta Pixel, Kakao Pixel 간 이벤트 매핑.
## E-commerce Events
| User Action | GA4 | Meta | Kakao | Google Ads |
|-------------|-----|------|-------|------------|
| 상품 목록 조회 | view_item_list | ViewContent | viewContent | - |
| 상품 상세 조회 | view_item | ViewContent | viewContent | - |
| 상품 검색 | search | Search | search | - |
| 장바구니 담기 | add_to_cart | AddToCart | addToCart | - |
| 위시리스트 | add_to_wishlist | AddToWishlist | addToWishlist | - |
| 결제 시작 | begin_checkout | InitiateCheckout | beginCheckout | - |
| 배송정보 입력 | add_shipping_info | - | - | - |
| 결제정보 입력 | add_payment_info | AddPaymentInfo | addPaymentInfo | - |
| 구매 완료 | purchase | Purchase | purchase | purchase |
## Lead Gen Events
| User Action | GA4 | Meta | Kakao | Google Ads |
|-------------|-----|------|-------|------------|
| 회원가입 | sign_up | CompleteRegistration | completeRegistration | sign_up |
| 로그인 | login | - | login | - |
| 리드 생성 | generate_lead | Lead | participation | submit_lead_form |
## Engagement Events
| User Action | GA4 | Meta | Kakao | Google Ads |
|-------------|-----|------|-------|------------|
| 페이지 조회 | page_view | PageView | pageView | page_view |
| 검색 | search | Search | search | - |
| 공유 | share | - | - | - |
## Parameter Transformation
### GA4 → Meta
```javascript
// GA4 items[] → Meta content_ids[]
const contentIds = items.map(item => item.item_id);
// Meta Purchase
fbq('track', 'Purchase', {
content_ids: contentIds,
content_type: 'product',
value: ecommerce.value,
currency: 'KRW',
num_items: items.length
});
```
### GA4 → Kakao
```javascript
// GA4 items[] → Kakao products[]
const products = items.map(item => ({
id: item.item_id,
name: item.item_name,
price: item.price,
quantity: item.quantity
}));
// Kakao purchase
kakaoPixel('PIXEL_ID').purchase({
total_price: ecommerce.value,
currency: 'KRW',
products: products
});
```
## GTM Variables
### CJS - Item IDs for Meta
```javascript
function() {
var items = {{DLV - ecommerce.items}} || [];
return items.map(function(item) {
return item.item_id;
});
}
```
### CJS - Products for Kakao
```javascript
function() {
var items = {{DLV - ecommerce.items}} || [];
return items.map(function(item) {
return {
id: item.item_id,
name: item.item_name,
price: item.price,
quantity: item.quantity
};
});
}
```
## Deduplication
### Event ID Pattern
```javascript
var eventId = 'evt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// Client-side
fbq('track', 'Purchase', {...}, {eventID: eventId});
// Server-side (CAPI)
// 동일한 eventId 사용 → Meta 자동 중복 제거
```
## Platform-specific Notes
### Meta Pixel
- content_type: 'product' (상품) / 'product_group' (변형)
- currency: ISO 4217 필수
- value: 숫자 (소수점 가능)
### Kakao Pixel
- Server-side 미지원 (Client-only)
- products[]: 최대 10개 권장
- total_price: 숫자
### Google Ads
- Enhanced Conversions 권장
- transaction_id 필수 (중복 제거용)
- value: KRW 정수

View File

@@ -0,0 +1,149 @@
# GTM Implementation QA Checklist
태깅 구현 품질 검증 체크리스트.
## 1. Pre-Implementation
### Documentation
- [ ] Tagging Plan 승인 완료
- [ ] DataLayer 스키마 개발팀 공유
- [ ] 이벤트 택소노미 합의 완료
### Access
- [ ] GTM 컨테이너 접근 권한
- [ ] GA4 속성 접근 권한
- [ ] 광고 플랫폼 접근 권한
### Tools
- [ ] GTM Preview 모드 정상
- [ ] GA4 DebugView 활성화
- [ ] Chrome DevTools 준비
## 2. DataLayer Validation
### Base Setup
- [ ] GTM 스니펫 이전 dataLayer 초기화
- [ ] 페이지 로드 시 기본 데이터 push
- [ ] pageType, userId 등 기본 변수 존재
### Data Types
- [ ] 숫자 값이 Number 타입
- [ ] 금액에 통화 코드 포함 (KRW)
- [ ] transaction_id 유니크
### E-commerce
- [ ] ecommerce: null 선행 push
- [ ] items[] 배열 구조 정확
- [ ] 필수 파라미터 완비
### Console Check
```javascript
console.log(window.dataLayer);
dataLayer.filter(e => e.event === 'purchase');
```
## 3. Tag Firing
### GA4
| Tag | Trigger | Status |
|-----|---------|--------|
| Config | All Pages | [ ] |
| view_item | Product page | [ ] |
| add_to_cart | Add button | [ ] |
| begin_checkout | Checkout start | [ ] |
| purchase | Thank you | [ ] |
### Google Ads
| Conversion | Trigger | Status |
|------------|---------|--------|
| Purchase | DL - purchase | [ ] |
| Lead | DL - generate_lead | [ ] |
### Meta Pixel
| Event | Trigger | Status |
|-------|---------|--------|
| PageView | All Pages | [ ] |
| ViewContent | Product | [ ] |
| AddToCart | Add | [ ] |
| Purchase | Thank you | [ ] |
### Kakao Pixel
| Event | Trigger | Status |
|-------|---------|--------|
| pageView | All Pages | [ ] |
| purchase | Thank you | [ ] |
## 4. Network Validation
### Endpoints
```
GA4: google-analytics.com/g/collect
GAds: googleads.g.doubleclick.net/pagead/conversion/
Meta: facebook.com/tr/
```
### Check Points
- [ ] 요청 전송됨
- [ ] 파라미터 정확
- [ ] 응답 성공 (200/204)
## 5. Cross-browser Testing
### Desktop
- [ ] Chrome
- [ ] Safari
- [ ] Firefox
- [ ] Edge
### Mobile
- [ ] iOS Safari
- [ ] iOS Chrome
- [ ] Android Chrome
## 6. Data Validation (Platform)
### GA4
- [ ] Realtime 보고서 이벤트 표시
- [ ] 파라미터 정확히 수집
- [ ] E-commerce 보고서 정확
### Google Ads
- [ ] 전환 액션 데이터 유입
- [ ] 전환 값 정확
### Meta
- [ ] Events Manager 수신 확인
- [ ] Event match quality 확인
## 7. Edge Cases
- [ ] 빈 장바구니 결제 시작 처리
- [ ] 0개 구매 처리
- [ ] 쿠폰 없는 구매
- [ ] 게스트 체크아웃
- [ ] 새로고침 시 중복 방지
- [ ] 뒤로가기 시 처리
## 8. Performance & Privacy
### Performance
- [ ] 태그 로딩이 렌더링 차단 안 함
- [ ] 비동기 로딩
### Privacy
- [ ] 동의 없이 태그 미발화 (CMP 시)
- [ ] PII 직접 수집 없음
## Sign-off
| Role | Name | Date | ✓ |
|------|------|------|---|
| QA Lead | | | |
| Analytics Lead | | | |
| Dev Lead | | | |
## Issues Log
| # | Issue | Severity | Status |
|---|-------|----------|--------|
| 1 | | High/Med/Low | Open/Resolved |

View File

@@ -0,0 +1,45 @@
{
"name": "mac-optimizer",
"version": "1.0.0",
"description": "Modular macOS system health toolkit with read-only audits, cleanup, and security checks",
"author": {
"name": "OurDigital"
},
"keywords": [
"macos",
"system",
"optimization",
"cleanup",
"security",
"homebrew",
"packages",
"disk-space"
],
"license": "MIT",
"commands": [
{
"name": "mac-doctor",
"description": "Full macOS system health check — runs all 5 audit modules and presents unified findings"
},
{
"name": "mac-packages",
"description": "Audit package managers (Homebrew, npm, pip, pyenv) for outdated packages and issues"
},
{
"name": "mac-environment",
"description": "Audit shell environment — PATH, symlinks, shell configs, and startup time"
},
{
"name": "mac-security",
"description": "Security posture assessment — SIP, Gatekeeper, Firewall, FileVault, SSH, ports"
},
{
"name": "mac-cleanup",
"description": "Scan and clean caches, logs, and clutter — shows sizes first, cleans only with consent"
},
{
"name": "mac-resources",
"description": "Monitor CPU, memory, disk, battery, and identify resource-hungry processes"
}
]
}

View File

@@ -0,0 +1,53 @@
---
allowed-tools: Bash(bash *audit_cleanup.sh*), Bash(bash *cleanup_execute.sh*), Bash(du *), Bash(brew *), Bash(npm *), Bash(pip *), Bash(docker *), Bash(rm *)
description: Scan and clean caches, logs, and clutter — shows sizes first, cleans only with consent
---
## Your task
Scan for reclaimable disk space and offer cleanup.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_cleanup.sh
```
### Step 2 — Report
Parse JSON output and present as a table sorted by size (largest first):
| Category | Size | Risk | Target |
|---|---|---|---|
Show the total reclaimable space.
### Step 3 — Ask for consent
Ask which categories to clean. Group by risk level (Safe / Moderate / Risky).
### Step 4 — Dry run first
For approved targets, always run dry-run first:
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/cleanup_execute.sh --dry-run <targets>
```
Show the output and ask for final confirmation.
### Step 5 — Execute
Only after explicit confirmation:
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/cleanup_execute.sh --execute <targets>
```
### Safety rules
- Never execute cleanup without explicit user approval
- Always show sizes before deleting anything
- Always run --dry-run before --execute
- Docker cleanup uses `docker system prune`, never direct file deletion
- No sudo by default — if an action needs sudo, state why and ask first

View File

@@ -0,0 +1,60 @@
---
allowed-tools: Bash(bash *audit_*.sh*), Bash(bash *cleanup_execute.sh*), Bash(du *), Bash(df *), Bash(brew *), Bash(npm *), Bash(pip *), Bash(docker *), Bash(cp *)
description: Full macOS system health check — runs all 5 audit modules and presents unified findings
---
## Your task
Run a full macOS system health check using all 5 audit modules. Follow this workflow exactly:
### Step 1 — Run all audits
Run these scripts sequentially, collecting all JSON output:
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_packages.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_environment.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_security.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_cleanup.sh
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_resources.sh
```
If a script exits non-zero, report the error and continue with other modules.
### Step 2 — Parse and report
Parse all JSON lines. Each has: `{"module":"...","severity":"...","finding":"...","action":"...","details":"..."}`
Present findings grouped by severity (critical first, then warning, then info) as markdown tables:
```
### Critical
| Module | Finding | Recommended Action |
|---|---|---|
### Warning
| Module | Finding | Recommended Action |
|---|---|---|
### Info
| Module | Finding | Recommended Action |
|---|---|---|
```
### Step 3 — Ask for consent
STOP and ask: "Which actions would you like me to perform? You can approve by category (e.g., 'update packages and clean caches') or review each item."
### Step 4 — Execute approved actions only
For cleanup actions, always run `--dry-run` first and show what will happen before running `--execute`.
### Safety rules
- Never execute cleanup without explicit user approval
- Always show sizes before deleting anything
- Security module is read-only — present findings and remediation guidance only
- Back up shell configs before modifying: `cp ~/.zshrc ~/.config/mac-optimizer-backups/.zshrc.$(date +%s)`
- Process deny-list: never suggest killing kernel_task, launchd, WindowServer, loginwindow, mds, mds_stores, opendirectoryd, coreaudiod, SystemUIServer, Finder, Dock
- No sudo by default — if an action needs sudo, state why and ask first
- Docker cleanup uses `docker system prune`, never direct file deletion

View File

@@ -0,0 +1,30 @@
---
allowed-tools: Bash(bash *audit_environment.sh*), Bash(echo *), Bash(cp *)
description: Audit shell environment — PATH, symlinks, shell configs, and startup time
---
## Your task
Audit the shell environment on this Mac.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_environment.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Ask for consent
If there are fixable issues (duplicate PATH entries, broken symlinks, non-existent directories), list them and ask which to fix.
### Safety rules
- Back up shell configs before modifying: `cp ~/.zshrc ~/.config/mac-optimizer-backups/.zshrc.$(date +%s)`
- Present PATH consolidation suggestions but don't auto-modify

View File

@@ -0,0 +1,32 @@
---
allowed-tools: Bash(bash *audit_packages.sh*), Bash(brew *), Bash(npm *), Bash(pip *), Bash(nvm *), Bash(pyenv *)
description: Audit package managers (Homebrew, npm, pip, pyenv) for outdated packages and issues
---
## Your task
Audit all package managers on this Mac.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_packages.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Ask for consent
List available update actions grouped by risk. Ask which actions to perform.
### Safety notes
- `brew upgrade` is generally safe but can break projects pinned to specific versions
- Global npm packages are version-independent from project dependencies
- pip upgrades can break dependency chains — suggest `--dry-run` first
- Only audit pyenv-managed environments (not system Python)

View File

@@ -0,0 +1,29 @@
---
allowed-tools: Bash(bash *audit_resources.sh*), Bash(ps *), Bash(kill *), Bash(df *)
description: Monitor CPU, memory, disk, battery, and identify resource-hungry processes
---
## Your task
Check system resource usage on this Mac.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_resources.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Offer actions
If high-resource processes are found, offer to help. But respect the deny-list:
**Never suggest killing**: kernel_task, launchd, WindowServer, loginwindow, mds, mds_stores, opendirectoryd, coreaudiod, SystemUIServer, Finder, Dock
For user processes consuming excessive resources, ask before terminating.

View File

@@ -0,0 +1,25 @@
---
allowed-tools: Bash(bash *audit_security.sh*)
description: Security posture assessment — SIP, Gatekeeper, Firewall, FileVault, SSH, ports
---
## Your task
Run a read-only security posture assessment.
### Step 1 — Run the audit
```bash
bash $CLAUDE_PLUGIN_ROOT/skills/mac-optimizer/scripts/audit_security.sh
```
### Step 2 — Report
Parse JSON output and present as a severity-ranked markdown table:
| Severity | Finding | Recommended Action | Details |
|---|---|---|---|
### Step 3 — Guidance only
The security module is **read-only**. Present findings and remediation guidance. Do NOT attempt to change security settings. Explain how the user can fix issues themselves via System Settings or Terminal commands.

View File

@@ -0,0 +1,44 @@
---
name: mac-optimizer
description: macOS system health toolkit — use when user mentions system optimization, cleanup, packages, security audit, disk space, performance, brew updates, cache clearing, or system health on Mac
version: 1.0.0
---
# Mac Optimizer
Modular macOS system health toolkit. Runs read-only audits first, then recommends actions with user consent.
## Module Routing
| Keywords | Command |
|---|---|
| brew, homebrew, npm, nvm, pip, pyenv, packages, update, outdated | /mac-packages |
| path, shell, zshrc, environment, env, config, symlink | /mac-environment |
| security, firewall, sip, gatekeeper, filevault, ports, ssh | /mac-security |
| cache, cleanup, clean, logs, clutter, disk space, free space, trash | /mac-cleanup |
| cpu, memory, ram, disk, battery, processes, resources, slow | /mac-resources |
| doctor, audit, health, full check, everything, system check | /mac-doctor (all) |
Default to **/mac-doctor** when the request is ambiguous.
When the user's request matches a specific module, suggest the appropriate slash command. When the request is broad (e.g., "check my system", "run a health check"), use /mac-doctor.
## Execution Model
Every module follows this flow:
1. **Audit** — run the module's script (read-only)
2. **Report** — parse JSON output, present findings as a severity-ranked table
3. **Recommend** — list available actions grouped by risk
4. **Consent** — ask user which actions to approve
5. **Act** — execute only approved actions
## Safety Rules
- **Never execute cleanup without explicit user approval**
- **Always show sizes before deleting anything**
- **Security module is read-only** — present findings and remediation guidance only
- **Back up shell configs** before modifying
- **Process deny-list**: never suggest killing kernel_task, launchd, WindowServer, loginwindow, mds, mds_stores, opendirectoryd, coreaudiod, SystemUIServer, Finder, Dock
- **No sudo by default** — if an action needs sudo, state why and ask first
- Docker cleanup uses `docker system prune`, never direct file deletion

View File

@@ -0,0 +1,74 @@
# Cleanup Targets Reference
## Risk Ratings
| Risk | Meaning |
|---|---|
| Safe | Can be deleted without consequence; regenerated automatically |
| Moderate | Review before deleting; may lose useful data |
| Risky | May break applications; requires careful inspection |
| Info only | Report size but do not offer deletion |
## Target Details
### User Logs (Safe)
- **Path**: `~/Library/Logs/`
- **Cleanup target**: `user-logs`
- **Notes**: Application crash reports and debug logs. Regenerated as needed.
### System Logs (Moderate, requires sudo)
- **Path**: `/var/log/`
- **Cleanup target**: not available via cleanup_execute.sh (requires sudo)
- **Notes**: System-level logs. Only clean old/rotated files. Active logs should not be deleted.
### User Caches (Safe, per-app)
- **Path**: `~/Library/Caches/`
- **Cleanup target**: `user-caches`
- **Notes**: App caches regenerated on next use. Some apps may need to re-download data. Browser caches can be large.
### Homebrew Cache (Safe)
- **Path**: `$(brew --cache)` (typically `~/Library/Caches/Homebrew/`)
- **Cleanup target**: `brew-cache`
- **Notes**: Downloaded bottles and source archives. `brew cleanup` handles this properly.
### npm Cache (Safe)
- **Path**: `~/.npm/_cacache/`
- **Cleanup target**: `npm-cache`
- **Notes**: Package download cache. `npm cache clean --force` is the proper way to clear.
### pip Cache (Safe)
- **Path**: `~/Library/Caches/pip/`
- **Cleanup target**: `pip-cache`
- **Notes**: Downloaded wheel/sdist cache. `pip cache purge` is the proper way to clear.
### Xcode DerivedData (Safe)
- **Path**: `~/Library/Developer/Xcode/DerivedData/`
- **Cleanup target**: `xcode-derived`
- **Notes**: Build artifacts and indexes. Rebuilt on next Xcode build. Can grow very large.
### Xcode Archives (Moderate)
- **Path**: `~/Library/Developer/Xcode/Archives/`
- **Cleanup target**: `xcode-archives`
- **Notes**: App Store submission archives. May be needed for symbolication of crash reports. Review before deleting.
### iOS DeviceSupport (Safe)
- **Path**: `~/Library/Developer/Xcode/iOS DeviceSupport/`
- **Cleanup target**: `ios-support`
- **Notes**: Debug symbols for connected iOS devices. Re-downloaded when device connects again.
### Docker (Moderate)
- **Cleanup target**: `docker`
- **Notes**: Uses `docker system prune -f`. Removes stopped containers, unused networks, dangling images. Does NOT remove named volumes. Never delete Docker's filesystem directly.
### Trash (Safe)
- **Path**: `~/.Trash/`
- **Cleanup target**: `trash`
- **Notes**: Items in Trash. User has already "deleted" these.
### Old Downloads (Info only)
- **Path**: `~/Downloads/` (files >90 days old)
- **Notes**: Report only. User should review manually. Never auto-delete downloads.
### Application Support Remnants (Risky)
- **Path**: `~/Library/Application Support/` orphaned directories
- **Notes**: Report only. Directories from uninstalled apps. Difficult to determine automatically which are truly orphaned. Manual review required.

View File

@@ -0,0 +1,52 @@
# Package Manager Reference
## Homebrew
### Update workflow
```bash
brew update # Update Homebrew itself and formulae list
brew outdated # List outdated formulae
brew outdated --cask # List outdated casks
brew upgrade # Upgrade all outdated formulae
brew upgrade <name> # Upgrade specific formula
brew upgrade --cask # Upgrade all casks
brew cleanup --prune=0 # Remove all cached downloads
brew autoremove # Remove orphaned dependencies
brew doctor # Diagnose issues
```
### Safety notes
- `brew upgrade` is generally safe but can break projects pinned to specific versions
- Always check `brew doctor` warnings — some are cosmetic, some indicate real issues
- `brew autoremove` only removes packages not depended on by anything
## npm / nvm
### Update workflow
```bash
nvm ls # List installed Node versions
nvm install --lts # Install latest LTS
npm outdated -g # Check global packages
npm update -g # Update all global packages
npm cache clean --force # Clear npm cache
```
### Safety notes
- Global npm packages are version-independent from project dependencies
- Updating global packages won't affect project-level node_modules
## Python / pyenv
### Update workflow
```bash
pyenv versions # List installed versions
pyenv install --list # Available versions
pip list --outdated # Outdated packages in current env
pip install --upgrade <pkg> # Upgrade specific package
pip cache purge # Clear pip cache
```
### Safety notes
- Only audit pyenv-managed environments (not system Python)
- pip upgrades can break dependency chains — suggest `--dry-run` first
- Virtual environments in project directories are not scanned (out of scope v1)

View File

@@ -0,0 +1,53 @@
# Security Checks Reference
## SIP (System Integrity Protection)
- **Check**: `csrutil status`
- **Expected**: "System Integrity Protection status: enabled."
- **Remediation**: Reboot to Recovery Mode (Cmd+R), open Terminal, run `csrutil enable`
- **Severity**: Critical if disabled
## Gatekeeper
- **Check**: `spctl --status`
- **Expected**: "assessments enabled"
- **Remediation**: `sudo spctl --master-enable`
- **Severity**: Critical if disabled
## Firewall
- **Check**: `/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate`
- **Fallback**: `defaults read /Library/Preferences/com.apple.alf globalstate` (0=off, 1=on, 2=essential)
- **Remediation**: System Settings > Network > Firewall > Turn On
- **Severity**: Critical if disabled
## FileVault
- **Check**: `fdesetup status`
- **Expected**: "FileVault is On."
- **Remediation**: System Settings > Privacy & Security > FileVault > Turn On
- **Severity**: Critical if disabled (disk not encrypted)
## Open Ports
- **Check**: `lsof -iTCP -sTCP:LISTEN -nP`
- **Review**: unexpected services listening on all interfaces (0.0.0.0 or *)
- **Severity**: Info (review needed, not automatically bad)
## SSH Configuration
- **Check**: `/etc/ssh/sshd_config`
- **Flags**: `PasswordAuthentication yes`, `PermitRootLogin yes`
- **Remediation**: Set to `no` and restart sshd
- **Severity**: Warning
## Remote Services
- **Remote Login**: `systemsetup -getremotelogin`
- **Remote Management**: `defaults read /Library/Preferences/com.apple.RemoteManagement ARD_AllLocalUsers`
- **Remediation**: Disable in System Settings > General > Sharing
- **Severity**: Info/Warning depending on context
## Software Updates
- **Check**: `softwareupdate -l` (can be slow, 30s timeout)
- **Remediation**: System Settings > General > Software Update
- **Severity**: Warning if updates available
## Screen Lock
- **Check**: `defaults -currentHost read com.apple.screensaver idleTime`
- **Check**: `defaults read com.apple.screensaver askForPassword`
- **Remediation**: System Settings > Lock Screen
- **Severity**: Warning if password not required

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
# audit_cleanup.sh — Scan caches, logs, and clutter on macOS, report sizes
# Output: JSON lines to stdout, one per finding
set -euo pipefail
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' '; }
json_finding() {
local severity="$1" finding action details
finding=$(json_escape "$2")
action=$(json_escape "$3")
details=$(json_escape "$4")
printf '{"module":"cleanup","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# Get directory size in human-readable and bytes
dir_size() {
local path="$1"
if [ -d "$path" ]; then
local bytes=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
local human=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
echo "${bytes:-0}|${human:-0B}"
else
echo "0|0B"
fi
}
classify_size() {
local kb="$1"
if [ "$kb" -gt 1048576 ] 2>/dev/null; then # > 1GB
echo "warning"
else
echo "info"
fi
}
total_reclaimable_kb=0
# --- User logs ---
result=$(dir_size "$HOME/Library/Logs")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "User logs: ${human}" "clean" "~/Library/Logs/ [safe, no sudo]"
# --- System logs ---
result=$(dir_size "/var/log" 2>/dev/null || echo "0|0B")
IFS='|' read -r kb human <<< "$result"
if [ "$kb" -gt 0 ] 2>/dev/null; then
severity=$(classify_size "$kb")
json_finding "$severity" "System logs: ${human}" "clean" "/var/log/ [moderate, requires sudo]"
fi
# --- User caches ---
result=$(dir_size "$HOME/Library/Caches")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "User caches: ${human}" "clean" "~/Library/Caches/ [safe, per-app]"
# --- Homebrew cache ---
if command -v brew &>/dev/null; then
brew_cache=$(brew --cache 2>/dev/null || echo "")
if [ -n "$brew_cache" ] && [ -d "$brew_cache" ]; then
result=$(dir_size "$brew_cache")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "Homebrew cache: ${human}" "brew cleanup" "$brew_cache [safe]"
fi
fi
# --- npm cache ---
npm_cache="$HOME/.npm/_cacache"
if [ -d "$npm_cache" ]; then
result=$(dir_size "$npm_cache")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "npm cache: ${human}" "npm cache clean --force" "$npm_cache [safe]"
fi
# --- pip cache ---
pip_cache="$HOME/Library/Caches/pip"
if [ -d "$pip_cache" ]; then
result=$(dir_size "$pip_cache")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "pip cache: ${human}" "pip cache purge" "$pip_cache [safe]"
fi
# --- Xcode derived data ---
xcode_derived="$HOME/Library/Developer/Xcode/DerivedData"
if [ -d "$xcode_derived" ]; then
result=$(dir_size "$xcode_derived")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "Xcode DerivedData: ${human}" "clean" "$xcode_derived [safe]"
fi
# --- Xcode archives ---
xcode_archives="$HOME/Library/Developer/Xcode/Archives"
if [ -d "$xcode_archives" ]; then
result=$(dir_size "$xcode_archives")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "Xcode Archives: ${human}" "clean" "$xcode_archives [moderate - check before deleting]"
fi
# --- iOS Device Support ---
ios_support="$HOME/Library/Developer/Xcode/iOS DeviceSupport"
if [ -d "$ios_support" ]; then
result=$(dir_size "$ios_support")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
severity=$(classify_size "$kb")
json_finding "$severity" "iOS DeviceSupport: ${human}" "clean" "$ios_support [safe]"
fi
# --- Docker ---
if command -v docker &>/dev/null; then
docker_df=$(docker system df 2>/dev/null || echo "")
if [ -n "$docker_df" ]; then
reclaimable=$(echo "$docker_df" | grep -i "reclaimable" | head -1 || echo "")
json_finding "info" "Docker space usage detected" "docker system prune" "Run docker system df for details [use docker system prune to clean]"
fi
else
# Skip silently if docker not installed
true
fi
# --- Trash ---
trash_dir="$HOME/.Trash"
if [ -d "$trash_dir" ]; then
result=$(dir_size "$trash_dir")
IFS='|' read -r kb human <<< "$result"
total_reclaimable_kb=$((total_reclaimable_kb + kb))
if [ "$kb" -gt 0 ] 2>/dev/null; then
severity=$(classify_size "$kb")
json_finding "$severity" "Trash: ${human}" "empty trash" "$trash_dir [safe]"
fi
fi
# --- Old downloads (report only) ---
if [ -d "$HOME/Downloads" ]; then
old_files=$(find "$HOME/Downloads" -maxdepth 1 -mtime +90 -type f 2>/dev/null | wc -l | tr -d ' ')
if [ "$old_files" -gt 0 ] 2>/dev/null; then
old_size=$(find "$HOME/Downloads" -maxdepth 1 -mtime +90 -type f -exec du -ck {} + 2>/dev/null | tail -1 | awk '{print $1}')
old_human=$(echo "$old_size" | awk '{printf "%.1fM", $1/1024}')
json_finding "info" "${old_files} files in Downloads older than 90 days (${old_human})" "review" "Info only - review manually"
fi
fi
# --- Total summary ---
total_human=$(echo "$total_reclaimable_kb" | awk '{if($1>1048576) printf "%.1fG",$1/1048576; else if($1>1024) printf "%.1fM",$1/1024; else printf "%dK",$1}')
json_finding "info" "Total scannable space: ${total_human}" "review" "Approx reclaimable across all categories"

View File

@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# audit_environment.sh — Audit shell environment and paths on macOS
# Output: JSON lines to stdout, one per finding
set -euo pipefail
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' '; }
json_finding() {
local severity="$1" finding action details
finding=$(json_escape "$2")
action=$(json_escape "$3")
details=$(json_escape "$4")
printf '{"module":"environment","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# --- PATH analysis ---
IFS=':' read -ra path_dirs <<< "$PATH"
total_dirs=${#path_dirs[@]}
# Duplicate detection (compatible with bash 3)
duplicates=()
for i in "${!path_dirs[@]}"; do
dir="${path_dirs[$i]}"
is_dup=false
for j in "${!path_dirs[@]}"; do
if [ "$j" -lt "$i" ] && [ "${path_dirs[$j]}" = "$dir" ]; then
is_dup=true
break
fi
done
if $is_dup; then
duplicates+=("$dir")
fi
done
if [ ${#duplicates[@]} -gt 0 ]; then
dup_list=$(printf '%s, ' "${duplicates[@]}" | sed 's/, $//')
json_finding "warning" "${#duplicates[@]} duplicate PATH entries" "deduplicate" "$dup_list"
else
json_finding "info" "No duplicate PATH entries" "none" "${total_dirs} directories in PATH"
fi
# Non-existent directories
missing=()
for dir in "${path_dirs[@]}"; do
if [ ! -d "$dir" ]; then
missing+=("$dir")
fi
done
if [ ${#missing[@]} -gt 0 ]; then
missing_list=$(printf '%s, ' "${missing[@]}" | sed 's/, $//')
json_finding "warning" "${#missing[@]} non-existent directories in PATH" "remove" "$missing_list"
fi
# --- Broken symlinks ---
broken_symlinks=()
for check_dir in /usr/local/bin "$HOME/.local/bin"; do
if [ -d "$check_dir" ]; then
while IFS= read -r link; do
broken_symlinks+=("$link")
done < <(find "$check_dir" -maxdepth 1 -type l ! -exec test -e {} \; -print 2>/dev/null || true)
fi
done
if [ ${#broken_symlinks[@]} -gt 0 ]; then
broken_list=$(printf '%s, ' "${broken_symlinks[@]}" | sed 's/, $//')
json_finding "warning" "${#broken_symlinks[@]} broken symlinks found" "remove" "$broken_list"
else
json_finding "info" "No broken symlinks in bin directories" "none" ""
fi
# --- Shell config analysis ---
shell_configs=()
for cfg in "$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.zshenv" "$HOME/.bash_profile" "$HOME/.bashrc"; do
if [ -f "$cfg" ]; then
shell_configs+=("$(basename "$cfg")")
fi
done
json_finding "info" "Shell configs found: ${shell_configs[*]}" "none" ""
# Check for PATH modifications across multiple files
path_mods=0
for cfg in "$HOME/.zshrc" "$HOME/.zprofile" "$HOME/.zshenv" "$HOME/.bash_profile" "$HOME/.bashrc"; do
if [ -f "$cfg" ]; then
count=$(grep -c -E 'export PATH|PATH=' "$cfg" 2>/dev/null || echo 0)
count=$(echo "$count" | tr -d '[:space:]')
path_mods=$((path_mods + count))
fi
done
if [ "$path_mods" -gt 5 ]; then
json_finding "warning" "PATH modified ${path_mods} times across shell configs" "consolidate" "Consider consolidating PATH modifications"
fi
# --- Environment variable checks ---
if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
json_finding "info" "ANTHROPIC_API_KEY not set" "none" "Set if using Claude API"
fi
if [ -n "${JAVA_HOME:-}" ] && [ ! -d "$JAVA_HOME" ]; then
json_finding "warning" "JAVA_HOME points to non-existent directory" "fix" "$JAVA_HOME"
fi
# --- Shell startup time ---
if command -v zsh &>/dev/null; then
startup_time=$( { time zsh -i -c exit; } 2>&1 | grep real | awk '{print $2}' || echo "unknown")
# Parse the time - handle both formats like "0m0.123s" and "0.123"
if echo "$startup_time" | grep -q 'm'; then
seconds=$(echo "$startup_time" | sed 's/.*m//' | sed 's/s//')
else
seconds="$startup_time"
fi
if command -v python3 &>/dev/null; then
is_slow=$(python3 -c "print('slow' if float('${seconds}') > 0.5 else 'ok')" 2>/dev/null || echo "ok")
else
is_slow="ok"
fi
if [ "$is_slow" = "slow" ]; then
json_finding "warning" "Shell startup is slow (${startup_time})" "optimize" "Consider profiling with zsh -xv"
else
json_finding "info" "Shell startup time: ${startup_time}" "none" ""
fi
fi

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# audit_packages.sh — Audit package managers on macOS
# Output: JSON lines to stdout, one per finding
set -euo pipefail
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' '; }
json_finding() {
local severity="$1" finding action details
finding=$(json_escape "$2")
action=$(json_escape "$3")
details=$(json_escape "$4")
printf '{"module":"packages","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# --- Homebrew ---
if command -v brew &>/dev/null; then
echo '{"module":"packages","severity":"info","finding":"Homebrew detected","action":"none","details":"'$(brew --version | head -1)'"}'
# brew update
brew update --quiet 2>/dev/null || true
# outdated formulae
outdated=$(brew outdated --json=v2 2>/dev/null || echo '{}')
formula_count=$(echo "$outdated" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('formulae',[])))" 2>/dev/null || echo 0)
cask_count=$(echo "$outdated" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('casks',[])))" 2>/dev/null || echo 0)
if [ "$formula_count" -gt 0 ] 2>/dev/null; then
formula_list=$(echo "$outdated" | python3 -c "
import sys,json
d=json.load(sys.stdin)
names=[f['name'] for f in d.get('formulae',[])]
print(', '.join(names[:10]))
if len(names)>10: print(f'... and {len(names)-10} more')
" 2>/dev/null || echo "")
json_finding "warning" "${formula_count} Homebrew formulae outdated" "brew upgrade" "$formula_list"
fi
if [ "$cask_count" -gt 0 ] 2>/dev/null; then
json_finding "warning" "${cask_count} Homebrew casks outdated" "brew upgrade --cask" ""
fi
if [ "$formula_count" -eq 0 ] && [ "$cask_count" -eq 0 ] 2>/dev/null; then
json_finding "info" "All Homebrew packages up to date" "none" ""
fi
# brew doctor
doctor_output=$(brew doctor 2>&1 || true)
doctor_warnings=$(echo "$doctor_output" | grep -c "Warning:" 2>/dev/null || echo 0)
if [ "$doctor_warnings" -gt 0 ] 2>/dev/null; then
json_finding "warning" "brew doctor found ${doctor_warnings} warning(s)" "brew doctor" "Run brew doctor for details"
fi
# autoremove candidates
autoremove_output=$(brew autoremove --dry-run 2>/dev/null || echo "")
orphan_count=$(echo "$autoremove_output" | grep -c "^=" 2>/dev/null || echo 0)
if echo "$autoremove_output" | grep -q "would remove" 2>/dev/null; then
json_finding "info" "Orphaned Homebrew dependencies found" "brew autoremove" "$autoremove_output"
fi
else
json_finding "info" "Homebrew not installed" "none" "Consider installing from https://brew.sh"
fi
# --- npm / nvm ---
if command -v nvm &>/dev/null || [ -d "$HOME/.nvm" ]; then
# Source nvm if available
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
[ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" 2>/dev/null
if command -v nvm &>/dev/null; then
node_versions=$(nvm ls --no-colors 2>/dev/null | grep -c "v[0-9]" || echo 0)
current_node=$(nvm current 2>/dev/null || echo "none")
json_finding "info" "nvm detected: ${node_versions} version(s), current: ${current_node}" "none" ""
fi
fi
if command -v npm &>/dev/null; then
json_finding "info" "npm detected" "none" "$(npm --version 2>/dev/null)"
npm_outdated=$(npm outdated -g --json 2>/dev/null || echo '{}')
npm_outdated_count=$(echo "$npm_outdated" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
if [ "$npm_outdated_count" -gt 0 ] 2>/dev/null; then
json_finding "warning" "${npm_outdated_count} global npm packages outdated" "npm update -g" ""
fi
else
json_finding "info" "npm not installed" "none" ""
fi
# --- Python / pyenv ---
if command -v pyenv &>/dev/null; then
pyenv_versions=$(pyenv versions --bare 2>/dev/null | wc -l | tr -d ' ')
current_python=$(pyenv version-name 2>/dev/null || echo "system")
json_finding "info" "pyenv detected: ${pyenv_versions} version(s), current: ${current_python}" "none" ""
# Check pip outdated for current environment
if command -v pip &>/dev/null || command -v pip3 &>/dev/null; then
pip_cmd=$(command -v pip3 || command -v pip)
pip_outdated=$($pip_cmd list --outdated --format=json 2>/dev/null || echo '[]')
pip_count=$(echo "$pip_outdated" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo 0)
if [ "$pip_count" -gt 0 ] 2>/dev/null; then
json_finding "warning" "${pip_count} pip packages outdated in current env" "pip install --upgrade" ""
fi
fi
elif command -v python3 &>/dev/null; then
py_version=$(python3 --version 2>/dev/null || echo "unknown")
json_finding "info" "System Python: ${py_version} (no pyenv)" "none" ""
fi
# --- Other package managers (presence detection only) ---
for mgr in yarn pnpm cargo go gem composer; do
if command -v "$mgr" &>/dev/null; then
version=$($mgr --version 2>/dev/null | head -1 || echo "unknown")
json_finding "info" "${mgr} detected: ${version}" "none" "Presence check only"
fi
done

View File

@@ -0,0 +1,143 @@
#!/usr/bin/env bash
# audit_resources.sh — Monitor CPU, memory, disk, battery on macOS
# Output: JSON lines to stdout, one per finding
set -euo pipefail
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' '; }
json_finding() {
local severity="$1" finding action details
finding=$(json_escape "$2")
action=$(json_escape "$3")
details=$(json_escape "$4")
printf '{"module":"resources","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# Process deny-list (never suggest termination)
DENY_LIST="kernel_task|launchd|WindowServer|loginwindow|mds|mds_stores|opendirectoryd|coreaudiod|SystemUIServer|Finder|Dock"
# --- CPU ---
load_avg=$(sysctl -n vm.loadavg 2>/dev/null | tr -d '{}' | awk '{print $1}')
cpu_count=$(sysctl -n hw.ncpu 2>/dev/null || echo 1)
if command -v python3 &>/dev/null; then
cpu_severity=$(python3 -c "
load=$load_avg; cores=$cpu_count
if load > cores * 2: print('critical')
elif load > cores: print('warning')
else: print('info')
" 2>/dev/null || echo "info")
else
cpu_severity="info"
fi
json_finding "$cpu_severity" "Load average: ${load_avg} (${cpu_count} cores)" "review" ""
# Top CPU-consuming processes (excluding deny-list from action suggestions)
top_cpu=$(ps aux | sort -nrk 3 | head -5 || echo "")
if [ -n "$top_cpu" ]; then
while IFS= read -r line; do
proc_name=$(echo "$line" | awk '{print $11}' | xargs basename 2>/dev/null || echo "unknown")
cpu_pct=$(echo "$line" | awk '{print $3}')
mem_pct=$(echo "$line" | awk '{print $4}')
pid=$(echo "$line" | awk '{print $2}')
# Skip low-usage processes
is_high=$(python3 -c "print('yes' if float('${cpu_pct}') > 10 else 'no')" 2>/dev/null || echo "no")
if [ "$is_high" = "yes" ]; then
if echo "$proc_name" | grep -qE "$DENY_LIST"; then
json_finding "info" "High CPU: ${proc_name} (${cpu_pct}% CPU, PID ${pid})" "none" "System process - do not terminate"
else
json_finding "warning" "High CPU: ${proc_name} (${cpu_pct}% CPU, PID ${pid})" "quit" "User process - can be terminated"
fi
fi
done <<< "$top_cpu"
fi
# --- Memory ---
vm_stat_output=$(vm_stat 2>/dev/null || echo "")
if [ -n "$vm_stat_output" ]; then
page_size=$(vm_stat | head -1 | grep -o '[0-9]*' || echo 16384)
free_pages=$(echo "$vm_stat_output" | grep "Pages free" | awk '{print $3}' | tr -d '.')
active_pages=$(echo "$vm_stat_output" | grep "Pages active" | awk '{print $3}' | tr -d '.')
inactive_pages=$(echo "$vm_stat_output" | grep "Pages inactive" | awk '{print $3}' | tr -d '.')
wired_pages=$(echo "$vm_stat_output" | grep "Pages wired" | awk '{print $4}' | tr -d '.')
compressed_pages=$(echo "$vm_stat_output" | grep "Pages stored in compressor" | awk '{print $5}' | tr -d '.' || echo 0)
if command -v python3 &>/dev/null; then
mem_summary=$(python3 -c "
ps=${page_size}
free_mb = int(${free_pages:-0}) * ps / 1048576
active_mb = int(${active_pages:-0}) * ps / 1048576
inactive_mb = int(${inactive_pages:-0}) * ps / 1048576
wired_mb = int(${wired_pages:-0}) * ps / 1048576
compressed_mb = int(${compressed_pages:-0}) * ps / 1048576
used_mb = active_mb + wired_mb + compressed_mb
total_mb = free_mb + active_mb + inactive_mb + wired_mb + compressed_mb
pct = used_mb / total_mb * 100 if total_mb > 0 else 0
print(f'{used_mb:.0f}MB used / {total_mb:.0f}MB total ({pct:.0f}%)')
if pct > 90: print('SEVERITY:critical')
elif pct > 75: print('SEVERITY:warning')
else: print('SEVERITY:info')
" 2>/dev/null || echo "unknown")
mem_severity=$(echo "$mem_summary" | grep "SEVERITY:" | cut -d: -f2)
mem_info=$(echo "$mem_summary" | head -1)
json_finding "${mem_severity:-info}" "Memory: ${mem_info}" "review" ""
fi
fi
# --- Swap ---
swap_info=$(sysctl vm.swapusage 2>/dev/null || echo "")
if [ -n "$swap_info" ]; then
swap_used=$(echo "$swap_info" | grep -o "used = [0-9.]*M" | awk '{print $3}' | tr -d 'M')
if [ -n "$swap_used" ] && command -v python3 &>/dev/null; then
swap_severity=$(python3 -c "
s=float('${swap_used}')
if s > 4096: print('warning')
elif s > 1024: print('info')
else: print('info')
" 2>/dev/null || echo "info")
json_finding "$swap_severity" "Swap: ${swap_used}MB used" "review" "$swap_info"
fi
fi
# --- Disk usage ---
disk_info=$(df -h / 2>/dev/null | tail -1)
if [ -n "$disk_info" ]; then
disk_used_pct=$(echo "$disk_info" | awk '{print $5}' | tr -d '%')
disk_avail=$(echo "$disk_info" | awk '{print $4}')
disk_total=$(echo "$disk_info" | awk '{print $2}')
if [ "$disk_used_pct" -gt 90 ] 2>/dev/null; then
json_finding "critical" "Disk ${disk_used_pct}% full (${disk_avail} free of ${disk_total})" "cleanup" "Consider running cleanup module"
elif [ "$disk_used_pct" -gt 75 ] 2>/dev/null; then
json_finding "warning" "Disk ${disk_used_pct}% full (${disk_avail} free of ${disk_total})" "monitor" ""
else
json_finding "info" "Disk ${disk_used_pct}% full (${disk_avail} free of ${disk_total})" "none" ""
fi
fi
# --- Large directories (top 5 in home) ---
if command -v du &>/dev/null; then
large_dirs=$(du -sh "$HOME"/*/ "$HOME"/.[!.]* 2>/dev/null | sort -hr | head -5 || echo "")
if [ -n "$large_dirs" ]; then
dir_summary=$(echo "$large_dirs" | awk '{printf "%s (%s), ", $2, $1}' | sed 's/, $//')
json_finding "info" "Largest directories in home" "review" "$dir_summary"
fi
fi
# --- Battery (laptops only) ---
battery_info=$(system_profiler SPPowerDataType 2>/dev/null || echo "")
if echo "$battery_info" | grep -q "Cycle Count"; then
cycle_count=$(echo "$battery_info" | grep "Cycle Count" | awk '{print $NF}')
condition=$(echo "$battery_info" | grep "Condition" | awk '{print $NF}')
max_capacity=$(echo "$battery_info" | grep "Maximum Capacity" | awk '{print $NF}' || echo "unknown")
if [ "$condition" = "Normal" ]; then
json_finding "info" "Battery: ${condition}, ${cycle_count} cycles, ${max_capacity} capacity" "none" ""
else
json_finding "warning" "Battery: ${condition}, ${cycle_count} cycles, ${max_capacity} capacity" "review" "Battery may need service"
fi
fi

View File

@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# audit_security.sh — Security posture assessment for macOS (read-only)
# Output: JSON lines to stdout, one per finding
set -euo pipefail
json_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' ' '; }
json_finding() {
local severity="$1" finding action details
finding=$(json_escape "$2")
action=$(json_escape "$3")
details=$(json_escape "$4")
printf '{"module":"security","severity":"%s","finding":"%s","action":"%s","details":"%s"}\n' \
"$severity" "$finding" "$action" "$details"
}
# --- SIP (System Integrity Protection) ---
sip_status=$(csrutil status 2>/dev/null || echo "unknown")
if echo "$sip_status" | grep -q "enabled"; then
json_finding "info" "SIP enabled" "none" ""
elif echo "$sip_status" | grep -q "disabled"; then
json_finding "critical" "SIP is disabled" "report" "System Integrity Protection should be enabled"
else
json_finding "warning" "SIP status unknown" "report" "$sip_status"
fi
# --- Gatekeeper ---
gatekeeper_status=$(spctl --status 2>/dev/null || echo "unknown")
if echo "$gatekeeper_status" | grep -q "assessments enabled"; then
json_finding "info" "Gatekeeper enabled" "none" ""
elif echo "$gatekeeper_status" | grep -q "assessments disabled"; then
json_finding "critical" "Gatekeeper is disabled" "report" "Enable via: sudo spctl --master-enable"
else
json_finding "warning" "Gatekeeper status unknown" "report" "$gatekeeper_status"
fi
# --- Firewall ---
fw_tool="/usr/libexec/ApplicationFirewall/socketfilterfw"
if [ -x "$fw_tool" ]; then
fw_status=$("$fw_tool" --getglobalstate 2>/dev/null || echo "unknown")
if echo "$fw_status" | grep -qi "enabled"; then
json_finding "info" "Firewall enabled" "none" ""
else
json_finding "critical" "Firewall is disabled" "report" "Enable in System Settings > Network > Firewall"
fi
else
# Fallback to defaults
fw_default=$(defaults read /Library/Preferences/com.apple.alf globalstate 2>/dev/null || echo "unknown")
case "$fw_default" in
0) json_finding "critical" "Firewall is disabled" "report" "Enable in System Settings > Network > Firewall" ;;
1|2) json_finding "info" "Firewall enabled" "none" "" ;;
*) json_finding "warning" "Firewall status unknown" "report" "" ;;
esac
fi
# --- FileVault ---
fv_status=$(fdesetup status 2>/dev/null || echo "unknown")
if echo "$fv_status" | grep -q "FileVault is On"; then
json_finding "info" "FileVault enabled (disk encryption)" "none" ""
elif echo "$fv_status" | grep -q "FileVault is Off"; then
json_finding "critical" "FileVault is off (disk not encrypted)" "report" "Enable in System Settings > Privacy and Security > FileVault"
else
json_finding "warning" "FileVault status unknown" "report" "$fv_status"
fi
# --- Open listening ports ---
listening_ports=$(lsof -iTCP -sTCP:LISTEN -nP 2>/dev/null | tail -n +2 || echo "")
port_count=$(echo "$listening_ports" | grep -c . 2>/dev/null || echo 0)
if [ "$port_count" -gt 0 ]; then
# Extract unique port/process combos
port_summary=$(echo "$listening_ports" | awk '{print $1":"$9}' | sort -u | head -10 | tr '\n' ', ' | sed 's/,$//')
json_finding "info" "${port_count} listening TCP connections" "review" "$port_summary"
fi
# --- SSH config ---
ssh_config="/etc/ssh/sshd_config"
if [ -f "$ssh_config" ]; then
# Check password auth
if grep -qi "^PasswordAuthentication yes" "$ssh_config" 2>/dev/null; then
json_finding "warning" "SSH password authentication enabled" "report" "Consider key-based auth only"
fi
# Check root login
if grep -qi "^PermitRootLogin yes" "$ssh_config" 2>/dev/null; then
json_finding "warning" "SSH root login permitted" "report" "Consider disabling root SSH login"
fi
fi
# --- Remote login ---
remote_login=$(systemsetup -getremotelogin 2>/dev/null || echo "unknown")
if echo "$remote_login" | grep -qi "on"; then
json_finding "info" "Remote Login (SSH) is enabled" "review" "Disable if not needed: System Settings > General > Sharing"
fi
# --- Remote management ---
remote_mgmt=$(defaults read /Library/Preferences/com.apple.RemoteManagement ARD_AllLocalUsers 2>/dev/null || echo "not configured")
if [ "$remote_mgmt" = "1" ]; then
json_finding "warning" "Remote Management enabled for all users" "review" "Check System Settings > General > Sharing"
fi
# --- Software updates ---
# Just check if updates are available (this can be slow, so we time-limit it)
# macOS lacks GNU timeout — use perl alarm as a portable alternative
update_check=$(perl -e 'alarm 30; exec @ARGV' -- softwareupdate -l 2>&1 || echo "timeout or error")
if echo "$update_check" | grep -q "No new software available"; then
json_finding "info" "macOS software is up to date" "none" ""
elif echo "$update_check" | grep -q "timeout or error"; then
json_finding "info" "Software update check timed out" "none" "Run softwareupdate -l manually"
else
update_count=$(echo "$update_check" | grep -c "^\*" 2>/dev/null || echo 0)
json_finding "warning" "${update_count} software update(s) available" "softwareupdate" "Run: softwareupdate -l for details"
fi
# --- Screen lock ---
screen_lock_delay=$(defaults -currentHost read com.apple.screensaver idleTime 2>/dev/null || echo "not set")
ask_password=$(defaults read com.apple.screensaver askForPassword 2>/dev/null || echo "unknown")
if [ "$ask_password" = "0" ]; then
json_finding "warning" "Screen lock password not required" "report" "Enable in System Settings > Lock Screen"
elif [ "$screen_lock_delay" != "not set" ] && [ "$screen_lock_delay" -gt 300 ] 2>/dev/null; then
json_finding "info" "Screen lock delay: ${screen_lock_delay}s (consider reducing)" "report" ""
fi

View File

@@ -0,0 +1,169 @@
#!/usr/bin/env bash
# cleanup_execute.sh — Execute cleanup actions on macOS
# Usage: cleanup_execute.sh [--dry-run|--execute] <target> [<target>...]
# Targets: user-logs, user-caches, brew-cache, npm-cache, pip-cache,
# xcode-derived, xcode-archives, ios-support, docker, trash
set -euo pipefail
MODE="dry-run"
TARGETS=()
BACKUP_DIR="${BACKUP_DIR:-$HOME/.config/mac-optimizer-backups}"
usage() {
echo "Usage: $0 [--dry-run|--execute] <target> [<target>...]"
echo ""
echo "Options:"
echo " --dry-run Show what would be deleted (default)"
echo " --execute Actually perform cleanup"
echo ""
echo "Targets:"
echo " user-logs ~/Library/Logs/"
echo " user-caches ~/Library/Caches/"
echo " brew-cache Homebrew download cache"
echo " npm-cache npm cache"
echo " pip-cache pip cache"
echo " xcode-derived Xcode DerivedData"
echo " xcode-archives Xcode Archives"
echo " ios-support iOS DeviceSupport files"
echo " docker Docker system prune"
echo " trash Empty Trash"
exit 1
}
# Parse args
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) MODE="dry-run"; shift ;;
--execute) MODE="execute"; shift ;;
--help|-h) usage ;;
*) TARGETS+=("$1"); shift ;;
esac
done
if [ ${#TARGETS[@]} -eq 0 ]; then
echo "Error: No targets specified"
usage
fi
log_action() {
local target="$1" action="$2" size="$3"
echo "[${MODE}] ${target}: ${action} (${size})"
}
clean_dir() {
local name="$1" path="$2"
if [ ! -d "$path" ]; then
echo "[skip] ${name}: directory not found (${path})"
return
fi
local size=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "$name" "would remove contents of ${path}" "$size"
else
log_action "$name" "removing contents of ${path}" "$size"
find "$path" -mindepth 1 -delete 2>/dev/null || {
# Fallback: remove what we can
find "$path" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
}
echo "[done] ${name}: cleaned"
fi
}
for target in "${TARGETS[@]}"; do
case "$target" in
user-logs)
clean_dir "User Logs" "$HOME/Library/Logs"
;;
user-caches)
clean_dir "User Caches" "$HOME/Library/Caches"
;;
brew-cache)
if command -v brew &>/dev/null; then
brew_cache=$(brew --cache 2>/dev/null || echo "")
if [ -n "$brew_cache" ]; then
size=$(du -sh "$brew_cache" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "Homebrew Cache" "would run brew cleanup" "$size"
else
log_action "Homebrew Cache" "running brew cleanup" "$size"
brew cleanup --prune=0 2>/dev/null || true
echo "[done] Homebrew Cache: cleaned"
fi
fi
else
echo "[skip] brew-cache: Homebrew not installed"
fi
;;
npm-cache)
if command -v npm &>/dev/null; then
npm_cache="$HOME/.npm/_cacache"
if [ -d "$npm_cache" ]; then
size=$(du -sh "$npm_cache" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "npm Cache" "would run npm cache clean --force" "$size"
else
log_action "npm Cache" "running npm cache clean" "$size"
npm cache clean --force 2>/dev/null || true
echo "[done] npm Cache: cleaned"
fi
fi
else
echo "[skip] npm-cache: npm not installed"
fi
;;
pip-cache)
pip_cache="$HOME/Library/Caches/pip"
if [ -d "$pip_cache" ]; then
size=$(du -sh "$pip_cache" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "pip Cache" "would run pip cache purge" "$size"
else
log_action "pip Cache" "running pip cache purge" "$size"
pip3 cache purge 2>/dev/null || pip cache purge 2>/dev/null || true
echo "[done] pip Cache: cleaned"
fi
else
echo "[skip] pip-cache: no pip cache found"
fi
;;
xcode-derived)
clean_dir "Xcode DerivedData" "$HOME/Library/Developer/Xcode/DerivedData"
;;
xcode-archives)
clean_dir "Xcode Archives" "$HOME/Library/Developer/Xcode/Archives"
;;
ios-support)
clean_dir "iOS DeviceSupport" "$HOME/Library/Developer/Xcode/iOS DeviceSupport"
;;
docker)
if command -v docker &>/dev/null; then
if [ "$MODE" = "dry-run" ]; then
echo "[dry-run] Docker: would run docker system prune"
docker system df 2>/dev/null || echo " (Docker not running)"
else
echo "[execute] Docker: running docker system prune -f"
docker system prune -f 2>/dev/null || echo " Docker prune failed (is Docker running?)"
fi
else
echo "[skip] docker: Docker not installed"
fi
;;
trash)
trash_dir="$HOME/.Trash"
if [ -d "$trash_dir" ]; then
size=$(du -sh "$trash_dir" 2>/dev/null | awk '{print $1}')
if [ "$MODE" = "dry-run" ]; then
log_action "Trash" "would empty Trash" "$size"
else
log_action "Trash" "emptying Trash" "$size"
rm -rf "$trash_dir"/* 2>/dev/null || true
rm -rf "$trash_dir"/.[!.]* 2>/dev/null || true
echo "[done] Trash: emptied"
fi
fi
;;
*)
echo "[error] Unknown target: ${target}"
;;
esac
done

View File

@@ -0,0 +1,28 @@
# 93-tui-designer
Build Norton Commander / Gopher style TUI wizard interfaces for any Python CLI tool using Rich.
## Purpose
Reusable patterns and battle-tested gotchas for building retro-style terminal wizards with:
- Dual-panel layout (status + content)
- Stack-based Gopher navigation (numbered menus, Back, Home)
- Arrow-key list selector for long option lists
- Bilingual i18n with runtime toggle
- Keyboard-driven input (F-keys with alphanumeric fallbacks)
- Three-tier responsive layout (wide/narrow/single panel)
## Structure
```
93-tui-designer/
code/SKILL.md # Main skill (architecture, patterns, gotchas)
shared/references/
dtm-wizard-reference.md # DTM Agent implementation reference
docs/ # Future: logs, lessons learned
```
## First Implementation
DTM Agent TUI Wizard — 20 files, 21 screens, 29 tests.
See `shared/references/dtm-wizard-reference.md` for details.

View File

@@ -0,0 +1,303 @@
---
name: tui-designer
description: Build Norton Commander / Gopher style TUI wizard interfaces for CLI tools using Python Rich. Covers architecture, components, keyboard input, bilingual i18n, and battle-tested gotchas.
version: 1.0.0
triggers:
- "build TUI", "TUI wizard", "terminal UI", "CLI wizard"
- "Norton Commander style", "Gopher style", "retro TUI"
- "Rich TUI", "interactive CLI", "keyboard navigation"
- "dual panel interface", "terminal wizard"
tools:
- Read
- Write
- Edit
- Bash
- Grep
- Glob
---
# TUI Designer
Build retro-style terminal wizard interfaces (Norton Commander + Gopher) for any Python CLI tool using the Rich library. No new dependencies — Rich + stdlib only.
## When to Use
- Building an interactive CLI wizard or setup flow
- Adding keyboard-driven navigation to an existing CLI tool
- Creating dual-panel terminal interfaces with status + menus
- Building bilingual (or multi-language) terminal interfaces
## Architecture Blueprint
### Component Structure
```
src/{project}/tui/
__init__.py # Public API: launch_tui()
i18n.py # String registry with runtime language toggle
input.py # Raw tty/termios keypress reader + escape parser
themes.py # Color scheme + NO_COLOR support
widgets.py # Shared primitives (status icons, mini-tables)
breadcrumb.py # Top navigation path bar
function_bar.py # Bottom F-key shortcut bar
status_panel.py # Left panel: system health / status
menu.py # Right panel: numbered menu items
dialog.py # Modal overlays (confirm, input, error)
core.py # Layout engine (assembles frame)
runner.py # Main event loop + screen stack
selector.py # Arrow-key list selector for long lists
renderers.py # Content renderers for leaf screens
screens/
__init__.py # Screen/MenuItem/Action dataclasses + registry
home.py # Root screen
{category}.py # One file per menu category
```
### Data Flow
```
User keypress
-> input.py (parse_key_bytes -> normalize_key)
-> runner.py (_handle_key dispatches)
-> Navigation: ScreenStack push/pop
-> F-keys: toggle language, refresh, help, quit
-> Digits: jump cursor or push screen
-> Arrows: ListSelector cursor movement
-> Enter: confirm selection via key_handler callback
-> core.py (render_frame assembles panels)
-> Console output
```
## Screen System
### Screen Dataclass
```python
@dataclass
class Screen:
id: str # "setup.credentials"
title: str # i18n key
breadcrumb: list[str] # i18n key path
menu_items: list[MenuItem] # For menu screens
content_renderer: Callable | None # For leaf screens: (console, status_data) -> str
key_handler: Callable | None # For interactive leaves: (app, key) -> bool
on_enter: Callable | None # Runs when screen is pushed
```
### Navigation Model (Gopher-style stack)
```
ScreenStack (LIFO):
[1-9] = push screen (menu) or jump cursor (leaf)
[B] = pop (back)
[H] = clear to root
[Q] = quit with confirm
Enter = confirm selection on leaf screens
Up/Down = navigate ListSelector items
```
### Screen Registration Pattern
```python
# screens/setup.py
from myapp.tui.screens import Screen, MenuItem, register_screen
from myapp.tui.renderers import render_credentials, handle_credential_key
register_screen(Screen(
id="setup.credentials",
title="setup.credentials",
breadcrumb=["nav.home", "home.setup", "setup.credentials"],
content_renderer=render_credentials,
key_handler=handle_credential_key, # For interactive leaves
))
```
## Keyboard Input
### Escape Sequence Parser
```python
# input.py — stdlib only, no dependencies
import tty, termios, select, sys, os
def read_key() -> str:
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
first = os.read(fd, 1)
if first == b"\x1b":
ready, _, _ = select.select([fd], [], [], 0.05) # 50ms timeout
if ready:
rest = os.read(fd, 5)
return parse_escape(first + rest)
return "escape"
return parse_single(first)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
```
### F-key Alphanumeric Fallbacks
Always provide fallbacks — terminals intercept F-keys unpredictably:
| F-key | Fallback | Action |
|-------|----------|--------|
| F1 | ? | Help |
| F3 | l | Language toggle |
| F5 | r | Refresh |
| F10 | q | Quit |
## ListSelector Component
For screens with 10+ selectable items, use `ListSelector` instead of number-only input:
```python
selector = ListSelector(
items=[SelectableItem(label="model-name", status="ready", data={...})],
on_select=my_callback,
header="Select model:", # str or Callable[[], str] for dynamic headers
footer="Hint text",
)
# In content_renderer: return selector.render()
# In key_handler: return selector.handle_key(app, key)
```
- Arrow Up/Down moves visible `>` cursor
- Enter/Space confirms selection
- 1-9 jumps cursor (does NOT auto-select — user must press Enter)
- Sections headers group items visually
- Callable headers re-evaluate on each render (for dynamic "Active:" display)
## Line Input Mode
For screens requiring text input (file paths, names, search), use `read_line()`:
```python
from myapp.tui.input import read_line
# In a key_handler:
def handle_my_key(app, key):
if key == "p":
app.console.print("Enter file path: ", end="")
path = read_line()
if path is None: # Escape pressed
return True
# Use the path...
return True
```
### Implementation pattern
```python
def read_line(prompt: str = "") -> str | None:
"""Read a full line in raw mode. Returns None on Escape/Ctrl-C."""
# Uses tty.setraw() same as read_key()
# Accumulates chars in buffer, prints each as typed
# Handles: Enter (return string), Escape (return None),
# Ctrl-C (return None), Backspace (delete last)
# Escape sequences (arrow keys pressed during input) are consumed and ignored
```
### Line Input Gotchas
| Gotcha | Problem | Fix |
|--------|---------|-----|
| Pasted paths with quotes | Users paste `'/path/to/file'` with quotes | Strip outer quotes: `path.strip("'\"")` |
| Tilde expansion | `~/.config/...` not expanded | Use `Path(path).expanduser()` |
| Raw mode echo | Characters not visible while typing | Manually `sys.stdout.write(ch)` each keystroke |
| Escape during input | Arrow keys produce garbage in buffer | Detect `0x1B`, consume rest of sequence, ignore |
| Module-level imports for testability | `import select` inside function can't be patched | Import `select`, `termios`, `tty` at module level (in `try/except`) |
## Bilingual i18n
Simple dict registry — no framework needed:
```python
STRINGS = {
"nav.back": {"en": "Back", "ko": "뒤로"},
...
}
_lang = "en"
def t(key: str) -> str:
return STRINGS.get(key, {}).get(_lang, key)
def toggle_language():
global _lang
_lang = "ko" if _lang == "en" else "en"
```
On toggle, clear ALL cached screen content so every screen re-renders.
## Three-Tier Responsive Layout
```python
if cols >= 100: # Full dual-panel
Columns([status_panel, content_panel], padding=(0, 1))
elif cols >= 80: # Narrow status
Columns([narrow_status, content_panel], padding=(0, 1))
else: # Single panel (status line + content stacked)
console.print(f"Status: {health}")
console.print(content)
```
Use `rich.columns.Columns` (NOT `rich.layout.Layout`) — Columns auto-fits content height.
---
## Gotchas (Battle-Tested)
### Rich Library
| Gotcha | Problem | Fix |
|--------|---------|-----|
| `Panel(box=None)` | `AttributeError: NoneType has no attribute substitute` | Use `box=SIMPLE` from `rich.box` |
| `Layout` stretches panels | Panels expand to fill available height with empty space | Use `Columns` with `expand=False` instead |
| `Status` spinner nesting | "Only one live display may be active at once" crash | Never nest `Status()` inside other Rich output. Use plain `console.print("Loading...")` |
| `[1]` in markup | Rich interprets `[1]` as a potential style tag | Escape: `\[1]` |
| Rich + raw stdin | `tty.setraw()` conflicts with Rich's terminal state | Always restore termios in `finally` block |
| `NO_COLOR` env var | Must respect `NO_COLOR` and `DTM_NO_COLOR` | Check env vars, return plain theme dict |
### Keyboard Input
| Gotcha | Problem | Fix |
|--------|---------|-----|
| F-key escape sequences | Vary between Terminal.app, iTerm2, VS Code | Map multiple sequences per key + provide alphanumeric fallbacks |
| Items 10+ unreachable | Pressing `1` then `0` selects item 1 immediately | Digits jump cursor only, Enter confirms. Items 10+ use arrows |
| `Ctrl+C` in raw mode | Crashes without terminal restoration | Wrap main loop in `try/except KeyboardInterrupt` |
| Non-interactive detection | Piped input hangs on `read_key()` | Check `sys.stdin.isatty()` before entering TUI mode |
### Content & Caching
| Gotcha | Problem | Fix |
|--------|---------|-----|
| API calls per render | `content_renderer` called on every keystroke | Cache rendered content in `_cached_content[screen.id]`; clear on F5 |
| Stale cached content | Model/account change doesn't update display | `invalidate_screen()` clears cache; use callable headers for dynamic data |
| Config has null names | `set_active_account(id)` saves id but not name | Resolve names from API as fallback; backfill to config on success |
| Korean char alignment | `{label:<12}` misaligns CJK double-width chars | Calculate display width: `sum(2 if ord(c) > 0x7F else 1 for c in s)` |
| Language toggle stale | Screens show old language after F3 | Clear ALL `_cached_content` on language toggle, not just current screen |
### UX Patterns
| Pattern | Why |
|---------|-----|
| Confirmation panels (0.8s delay) | Users need visual feedback that their action took effect |
| `<< active` markers | Users must see which item is currently selected at a glance |
| Section headers in lists | Group related items (Recommended / Installed / LM Studio) |
| Guidance hints at bottom | Tell users how to reach features that require other steps first |
| Breadcrumb path always visible | Users always know where they are in the hierarchy |
## Checklist for New TUI Projects
- [ ] `from __future__ import annotations` in all TUI files
- [ ] `_is_interactive()` guard before entering TUI mode
- [ ] `--no-tui` and `--legacy` CLI flags for fallback
- [ ] Non-TTY detection falls back to plain text
- [ ] `NO_COLOR` / `FORCE_COLOR` env var support
- [ ] F-key alphanumeric fallbacks defined
- [ ] `KeyboardInterrupt` handled in main loop
- [ ] Escape sequences for F1-F10, arrows mapped
- [ ] Content cached, invalidated on refresh/change
- [ ] All user-facing strings through `t()` i18n function
- [ ] Tests for: i18n, input parser, screen registry, navigation stack

View File

@@ -0,0 +1,84 @@
# DTM Wizard — Reference Implementation
The DTM Agent TUI wizard is the first project built with this skill's patterns.
## Repository
- **Project**: D.intelligence Tag Manager Agent
- **Location**: `github.com/D-intelligence/dintel-gtm-agent`
- **TUI code**: `src/dtm/tui/` (20 files, ~2500 lines)
- **Tests**: `tools/tests/unit/test_tui_*.py` (29 tests)
## File Inventory
| File | Lines | Purpose |
|------|-------|---------|
| `__init__.py` | 13 | Public API |
| `i18n.py` | 93 | 60+ bilingual EN/KR strings |
| `input.py` | 93 | Escape sequence parser |
| `themes.py` | 48 | Norton Commander color scheme |
| `widgets.py` | 35 | Status icons, mini-tables |
| `breadcrumb.py` | 22 | Navigation path bar |
| `function_bar.py` | 33 | F-key shortcut bar |
| `status_panel.py` | 85 | Left panel health display |
| `menu.py` | 35 | Gopher-style numbered menu |
| `dialog.py` | 70 | Modal overlays |
| `core.py` | 100 | Three-tier layout engine |
| `runner.py` | 170 | Event loop + ScreenStack |
| `selector.py` | 156 | Arrow-key list selector |
| `renderers.py` | ~1100 | 15 leaf screen content renderers |
| `screens/*.py` | ~250 | 21 screen definitions |
## Screen Hierarchy (21 screens)
```
home
setup
setup.credentials
setup.account
setup.account.containers (dynamic sub-screen)
setup.ai
setup.connectivity
operations
ops.workflow
ops.container_analysis
ops.ai_analysis
ops.performance
configuration
config.review
config.change_account
config.change_container
config.export
diagnostics
diag.health
diag.test
diag.recommendations
```
## PRs and Evolution
| PR | Title | Key Changes |
|----|-------|-------------|
| #10 | Core TUI redesign | 19 modules, 28 tests |
| #11 | 7 usability fixes | Panel height, no-color, bilingual icons |
| #12 | Interactive selection | Account/container by number, help screen |
| #13 | Name resolution | Resolve IDs to names from API |
| #14 | Loading spinners | Visual feedback during API calls |
| #15 | AI model selector | Ollama + LM Studio detection |
| #16 | Batch UX fixes | ListSelector, export formats, crash fixes |
## External Service Detection Pattern
```python
def _detect_lm_studio() -> dict:
"""Detect LM Studio on localhost:1234."""
import requests
try:
resp = requests.get("http://localhost:1234/v1/models", timeout=3)
if resp.status_code == 200:
return {"available": True, "models": [m["id"] for m in resp.json().get("data", [])]}
except Exception:
pass
return {"available": False, "models": []}
```
This pattern works for any OpenAI-compatible local LLM server.