feat(gtm-guardian): Reorganize skill with dual-platform structure

- Add desktop/ directory for Claude Desktop (Phase 1-5: analysis, design, docs)
- Add code/ directory for Claude Code (Phase 6-7: automation, audit)
- Create SKILL.md with YAML frontmatter for Desktop compatibility
- Create CLAUDE.md for Code automation workflows
- Organize references by platform scope with shared files duplicated
- Add templates for tagging plan and event taxonomy
- Include README.md with overview and usage guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-31 19:59:22 +09:00
parent c6ab33726f
commit b859d0a266
22 changed files with 3214 additions and 0 deletions

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으로 직접 삽입.