Compare commits

...

133 Commits

Author SHA1 Message Date
f27fb7a2c4 Add jamie-copy-trimmer (48) and dintel-campaign-designer (78) skills
Some checks failed
Verify Skills / verify-skills (push) Has been cancelled
Import two skills authored in Claude Cowork: jamie-copy-trimmer as a
root-only Jamie skill, and campaign-gate-process as dintel-campaign-designer
with full D.intelligence Agent Corps packaging (code/desktop/shared,
agent-id 78, Draft & Wait autonomy) since it now sits inside the 70-79
block. Bumps the corps roster count to 9 agents + 1 meta-agent across all
sibling skills and registers #78 in the shared USER-GUIDE/README. Also adds
.claude/commands wrappers and ~/.claude/skills symlinks so both are usable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 12:59:19 +09:00
Andrew Yim
a08dc316be Merge pull request #14 from ourdigital/fix/notion-writer-skill-paths
fix(notion-writer): correct stale 02→32 skill paths
2026-06-27 11:42:19 +09:00
dc18594d76 fix(notion-writer): correct stale 02→32 skill paths after dir renumber
The 02-notion-writer dir was renumbered to 32-notion-writer, but the
skill's SKILL.md (both code + desktop variants) still pointed venv/cd
paths at the nonexistent 02 dir, and the slash-command file had a
~/Projects (should be ~/Project) typo. Fixes invocation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:41:39 +09:00
Andrew Yim
0b3a0ab129 Merge pull request #13 from ourdigital/notion-writer-cli-enhancements
feat(notion-writer): file uploads + native markdown engine (v1.3.0)
2026-06-27 11:27:11 +09:00
5c904756ab fix(ntn-files): propagate NOTION_API_KEY into ntn subprocesses
Force ntn to authenticate as the same integration that writes the page by
injecting NOTION_API_TOKEN=$NOTION_API_KEY into every ntn subprocess env.
Notion scopes file uploads to the creating integration, so a mismatch caused
"Could not find file_upload with ID …" when attaching uploaded images.

Added test_upload_passes_token_env (9 total in test_ntn_files.py) and updated
code/CLAUDE.md to reflect that a separate ntn login is no longer required.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
07fca7fb81 fix(notion-writer): fence-aware image/translate, NtnUploadError boundary, dead-code removal
FIX 1: _content_has_local_images and extract_local_images now skip lines inside
  ``` fences so a local-image ref inside a code block is not treated as real.
FIX 2: md_translate.translate() tracks fence state; callout/columns/mention
  transforms are suppressed for lines inside ``` ... ``` blocks (pass verbatim).
FIX 3: main() catches NtnUploadError and prints a clean error + e.stderr before
  sys.exit(1); body moved to _main_body(parser, args).
FIX 4: ntn_files.upload() raises NtnUploadError on empty stdout instead of
  IndexError, giving a clean message combined with FIX 3.
FIX 5: Removed unused write_to_page() (both page paths are inlined).
FIX 6: create_page_markdown() omits the 'markdown' key when value is falsy,
  avoiding empty-markdown sends on DB rows created without --file/--stdin.
FIX 7: CLAUDE.md documents --allow-deleting-content scope (markdown engine only).
TDD: Added test_fence_passthrough_no_transform (md_translate, suite now 8) and
  test_local_image_inside_fence_ignored (engine_routing, suite now 6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
00edd2e5dc docs(notion-writer): document engines + file uploads (v1.3.0)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
478ac61975 fix(notion-writer): fail loudly on markdown image append + upsert prop update
Mirror the blocks path's error handling in the markdown engine:
- page + DB markdown two-phase image append now check append_to_page's
  return and exit(1) on failure instead of printing success silently
- markdown DB upsert guards update_page_properties return
- restore blocks-engine page progress print + replace-failure print
- move unused parent computation into the create branch

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
5ff9b3d9f5 feat(notion-writer): --engine routing + two-phase image append
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
cfbca6cc15 feat(notion-writer): markdown endpoint helpers + version-aware client
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
dfbc52e531 feat(notion-writer): dialect->Notion enhanced markdown translator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
337f2ad6e1 feat(notion-writer): materialize local images via upload walk
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
9fbd719048 feat(notion-writer): parse standalone images to image blocks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
5385f3bddd feat(notion-writer): ntn_files upload + preflight
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
75cfcb9ad3 docs(notion-writer): fix Task 2 upload tests to mock builtins.open
upload() opens the file before subprocess.run; the two upload tests must
mock open() so the nonexistent /tmp paths don't raise FileNotFoundError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:13:31 +09:00
bfade2b722 docs(notion-writer): fix plan — circular import, columns loop, error hint
Pre-flight review fixes: lazy import in md_translate to break the
notion_writer<->md_translate cycle; clean columns state machine; markdown
deletion hint in explain_api_error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:12:48 +09:00
2b36feb81b docs(notion-writer): implementation plan for CLI enhancements
9 TDD tasks: venv baseline, ntn_files (upload+preflight), image parsing,
materialize walk, md_translate dialect translator, compat markdown
helpers + version-aware client, CLI engine routing + two-phase image
append, docs (v1.3.0), live smoke test. 55 unit tests across 4 suites.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:12:48 +09:00
aa003e28cc docs(notion-writer): design spec for CLI file uploads + markdown engine
Tier 1: local ![](path) images uploaded via `ntn files create` and
embedded as file_upload image blocks (parser stays pure; upload happens
in an isolated post-parse walk).

Tier 2: opt-in `--engine markdown` writes through Notion's native
enhanced-markdown endpoints (POST /pages, PATCH .../markdown) with a
dialect translator (callouts/columns/toggles/mentions) so one source
doc works in both engines. Default engine stays `blocks` (no regression).

Adds ntn_files.py + md_translate.py, version-aware client (2026-03-11
for markdown writes only), new test suites, venv setup, docs → v1.3.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 11:12:48 +09:00
87635e4208 fix(skill): absent claim term -> INCONCLUSIVE hint; note surge-tuning (final-review minors #3,#4)
- gsc_signal_delta.py: extract `found` local var; add first branch in verdict_hint
  chain so a term absent from both GSC windows yields INCONCLUSIVE (not ARTIFACT).
  Existing ARTIFACT / CONFIRMED-PARTIAL / PARTIAL branches unchanged (elif chain).
- test_gsc_signal_delta.py: add test_absent_claim_term_inconclusive asserting
  found=False and "INCONCLUSIVE" in verdict_hint for a term in neither fixture.
- code/CLAUDE.md: one-line surge-tuning note — verdict_hint/in_top_movers are
  calibrated for upward claims; for drops, inspect top_decliners directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuT3W81t88QQFaxY2ruWv2
2026-06-26 10:38:14 +09:00
4f78534e59 test(skill): genesis 호텔 smoke fixtures + end-to-end ARTIFACT check 2026-06-26 10:29:35 +09:00
d20675d02c feat(skill): register seo-signal-validation in marketplace; reconcile spec layout 2026-06-26 10:25:46 +09:00
2cadc30825 feat(skill): gsc_signal_delta helper + tests + code notes 2026-06-26 10:21:36 +09:00
c35250f06a feat(skill): seo-signal-validation SKILL.md decision half (L3-L4, verdict, output) 2026-06-26 10:16:11 +09:00
3383878e12 feat(skill): seo-signal-validation SKILL.md measurement half (L1-L2) 2026-06-26 10:10:47 +09:00
f953887b97 docs(skill): add 35-seo-signal-validation implementation plan
5 bite-sized tasks: SKILL.md measurement half (L1-L2) + decision half
(L3-L4/verdict), gsc_signal_delta.py helper with TDD tests, marketplace
registration + spec-layout reconcile, and a genesis-case smoke test that
asserts the JHR "호텔" claim resolves to ARTIFACT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuT3W81t88QQFaxY2ruWv2
2026-06-26 10:05:57 +09:00
18b39ef6ea docs(skill): add 35-seo-signal-validation design spec
New conductor skill that adjudicates whether a claimed SERP/Knowledge-Graph
movement for a (term, entity) pair is real, misattributed, an artifact, or
unprovable. Stateless, on-demand; delegates measurement to seo-serp-analysis,
seo-position-tracking, and seo-knowledge-graph. Genesis: JHR "호텔 16→3"
SEMrush claim refuted via GSC/GA4/live-SERP cross-check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuT3W81t88QQFaxY2ruWv2
2026-06-26 09:58:50 +09:00
e1f4d75dc2 graphify: index full doc layer via Gemini 3.1 Pro
Indexed 869 skill docs + 6 images (excl. 73 redundant .claude/commands mirrors)
using Gemini 3.1 Pro (~1.3M in / 301k out tokens, ~$6.23 - no Claude tokens):
+1552 nodes, +1308 edges, 61 doc<->code cross-links, 500 new doc communities
labeled by skill. Every skill's docs/specs are now queryable; prior graph was
code-only. Extraction offloaded to Gemini via graphify's OpenAI-compatible backend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 02:20:45 +09:00
6c97dfc913 graphify: index ourdigital-okf skill into knowledge graph
OKF-scoped --update of custom-skills/97-ourdigital-okf (7 code + 19 docs):
+131 nodes (62 AST + 75 semantic), +207 edges, 8 OKF communities, 6 hyperedges
capturing the produce/validate/visualize design and zero-dep utility trio.
Prior graph was code-only; the OKF skill is now queryable. ~172k extraction tokens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 01:33:44 +09:00
c068372abf Add graphify knowledge graph outputs and pending changes
Includes graphify-out/ (interactive graph.html, GRAPH_REPORT.md, graph.json
from Gemini-backed extraction) plus .graphifyignore/.gitignore scope config,
alongside other pending working-tree changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:41:30 +09:00
587a32d239 feat(okf): add install.sh; changelog; e2e produce->validate->visualize verified
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:35:29 +09:00
626abd4173 feat(okf): add SKILL.md variants (top/code/desktop), CLAUDE.md, README
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:35:29 +09:00
c6585c817f docs(okf): add distilled spec, frontmatter reference, and templates
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:35:29 +09:00
75acd3aa3e feat(okf): add minimal okf_viz Cytoscape generator with tests
Full suite green (20 tests). Smoke-tested on Google crypto_bitcoin bundle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:35:29 +09:00
7b239dda8f feat(okf): add okf_validate; harden YAML parser for block lists + folded scalars
Verified conformant against Google reference bundles (crypto_bitcoin, ga4, stackoverflow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:35:29 +09:00
9762ee97ab feat(okf): add okf_common frontmatter/link parser with tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:35:29 +09:00
4416833cb3 feat(okf): scaffold ourdigital-okf skill skeleton + mini fixture bundle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:35:28 +09:00
b574c97fcc chore(marketplace): add ourdigital-skills local marketplace + fix README install docs (#1) 2026-06-16 14:08:27 +09:00
7daa4cda68 SEO schema validator skill update 2026-06-08 13:21:09 +09:00
Andrew Yim
8ffb6bec6b docs(ourdigital): align journal + research skill style guides to v3.3/v2.1 (#12)
Some checks failed
Verify Skills / verify-skills (push) Has been cancelled
Merge-align the remaining OurDigital skill references to the authoritative
guides, preserving each skill's structure:

- 03-journal: brand identity, three-channel context, journal role/length
  (English, 1,000-2,000 words), authority order, OurDigital-vs-Clinic boundary,
  shared writing principles adapted for English. English voice preserved — the
  Korean-blog-only rules (평서체, 전문용어 병기) were deliberately NOT imposed.
- 04-research: per-channel voice/tone aligned to Writing Style Guide v2.1;
  fixed an incorrect 경어체 (~입니다) rule for the Korean blog → 평서체; SEO
  numbers corrected (title ~40자/≤60, meta ≤155, English slug); added brand
  identity + authority order + Clinic boundary.

05-document brand_config.json (corporate doc colors) and the claude-ai-export
snapshot were intentionally left out of scope.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:27:54 +09:00
Andrew Yim
9b914b9dd4 fix(estimate-engine): quote SKILL.md description to fix verify-skills YAML error (#11)
The unquoted description contained "Costing methods: effort …" — the mid-string
": " made YAML treat it as a nested mapping ("mapping values are not allowed
here"), failing verify-skills since 2026-05-27. Single-quote the value (it has
inner double quotes but no apostrophes), preserving the exact text.

verify_skills.py: 76/76 skills valid.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:21:26 +09:00
Andrew Yim
aab98f405d docs(ourdigital): align brand/blog/designer skill references to v3.3/v2.1 (#10)
Merge-align the core trio's reference material to the authoritative OurDigital
guides (Blog Project Instruction v3.3, Writing Style Guide v2.1, Visual Style
Guide v2.1), preserving each skill's structure:

- 01-brand-guide: brand-foundation + writing-style — identity statement, mission,
  three channels, OurDigital-vs-Clinic boundary, four writing principles,
  평서체/전문용어 병기 rules, authority order.
- 02-blog: blog-style-guide — channel identity, writing characteristics,
  post structure (char counts), SEO (title/meta/English slug), Ghost metadata,
  self-edit checklist.
- 06-designer: visual_metaphors (both copies kept identical) — bright editorial
  minimalism, Preferred/Avoid metaphor dictionary, 5 palettes, signature motifs,
  Korean-via-restraint, human-trace rule.

Source of truth: knowledge-base/ourdigital in our-ai-editor (PR #12).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 21:16:07 +09:00
6f69a6a484 Route estimate outputs to central archive (ourdigital-space/estimates)
All estimates now archive to ~/Workspaces/ourdigital-space/estimates/
<YYYY-MM-DD>_<prospect|account>_<service>/ (single 견적 archive). Engine
SKILL.md documents the output-home convention (engine stays --out-dir-agnostic).
presales-seo Stage 5 writes the estimate to $EST (archive); Stage 6 deck reads
from $EST but stays in the engagement bundle.

(Workspace side, not in this repo: SHR estimate moved to the archive, pointer
left in the engagement, ourdigital-space/CLAUDE.md documents estimates/.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:22:33 +09:00
9f7f9e7221 education: clarify ga4_gtm_intermediate (=기본구성/숨고), add marketing-analytics course
The 숨고 GA4/GTM 중급 기본견적 is identical to the existing ga4_gtm_intermediate
course (17 lessons, ₩1,570,000) — labeled it as the 기본구성 standard + aliases
rather than duplicating. Added the genuinely distinct GA4/GTM 마케팅 애널리틱스
course (22 lessons incl. 메타/구글 솔루션 + 워크숍 + 트리트먼트) → list ₩4,070,000.

Validated: intermediate ₩1,570,000, marketing_analytics ₩4,070,000.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:15:56 +09:00
6ac547e78f refactor(skills): clean skill names (strip NN- prefix from name:) — convention change
Some checks failed
Verify Skills / verify-skills (push) Has been cancelled
Adopt: directory keeps its NN- ordering prefix; skill `name:` is the clean form
without it (dir 16-seo-schema-validator → name: seo-schema-validator). Nicer to
invoke, matches the original desktop/SKILL.md names, still globally unique.

- 71 root SKILL.md: name: NN-foo → name: foo (flat skills + reference-curator suite).
  Plugins (mac-optimizer/multi-agent-guide/dintel-bootstrap) already clean; 95 already clean.
- scripts/migrate_skill_root.py: derive name = dirname minus NN- prefix (skill_name()).
- CLAUDE.md + SKILL-MIGRATION-GUIDE.md: document the dir-prefix / clean-name convention.

verify_skills.py: 0 name collisions across all renamed skills. (The ~/.claude/skills
symlinks were re-pointed to the clean names separately — filesystem only.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:11:01 +09:00
08cb20fc67 Add real digital_ads + digital_branding catalogs
Built from real D.intelligence docs (not the 견적 자료 folder, which has only
GA4/GTM + education — confirmed):
- digital_branding: TNS 유학원 디지털 브랜딩 진단 컨설팅 → ₩9,000,000 (5 fixed stages)
- digital_ads: 디하이브 디지털 광고·퍼포먼스 마케팅 대행 계약 → ₩6,000,000/월 retainer
  (media-spend commission % is per-deal, kept as a parameter — not invented)

effort method now supports fixed-amount tasks (Unit Cost) and monthly retainers
(unit: monthly); render shows 고정/—//월 and retainer/commission notes.
Validated: branding ₩9.0M, ads ₩6.0M/월; no regression (SEO 25.0M, coaching 1.57M).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:06:59 +09:00
c9bdbb57f7 Extract ourdigital-estimate-engine; presales-seo now calls it
New skill 96-ourdigital-estimate-engine: method-aware quoting engine
(effort / coaching / procurement) with universal rate_card + per-service
catalog. Real catalogs: seo (effort), education (coaching); stubs:
digital_ads, digital_branding. Validated to reproduce real quotes —
SEO basic ₩10.5M / treatment ₩25.0M, SHR chain ₩29.5M, L'Escape basic
₩10.5M, GA4/GTM coaching ₩1,570,000, procurement +15%.

Refactor 95-ourdigital-presales-seo: remove rate_card.yaml, sow_templates.yaml,
estimate.py (migrated to engine); add findings_to_scope.py; Stage 5 now maps
findings→scope.json and calls the engine CLI. build_deck/kg_query unchanged;
end-to-end validated on SHR (29.5M) + deck renders engine estimate.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:54:11 +09:00
34c3a1df4f ci: run verify_skills.py on every push and PR
Some checks failed
Verify Skills / verify-skills (push) Has been cancelled
Add .github/workflows/verify-skills.yml — installs pyyaml and runs
scripts/verify_skills.py on push and pull_request, failing the build if any skill
would not load. GitHub Actions runs it; Gitea Actions also reads .github/workflows/
when Actions is enabled, so it covers both remotes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:30:18 +09:00
137b927477 fix(skills): make all skills load-valid + add scripts/verify_skills.py
Add a comprehensive load verifier and fix the two issues it found:

- scripts/verify_skills.py: validates every loadable unit (flat root, suite sub-skill,
  plugin skill) with real YAML parsing, name regex + global uniqueness, frontmatter
  <=1024, description sanity, plugin.json JSON validity, and orphan detection. Read-only.
- 92-tui-design-template (root + code/SKILL.md): fix invalid YAML `triggers:` block
  (`- "a", "b"` multi-scalar list items) -> one phrase per list item.
- 17-seo-schema-generator: remove ">" from description ("generate -> validate" ->
  "generate then validate"); angle brackets are disallowed in descriptions.

Result: 75/75 loadable skills valid — 0 failures, 0 name collisions, 0 orphans,
0 plugin-manifest errors (65 flat + 3 plugins + 7 suite sub-skills).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:22:33 +09:00
95d6fdf499 Add luxury/vertical signal to auto-tiering
pick_baseline now applies a premium floor: if prospect.vertical matches
rate_card.tiering.premium_verticals (luxury/premium/deluxe/5성/특1급…), the
auto-selected tier is floored to premium_min_tier (default basic) so a premium
single property won't drop to the smb entry tier.

Validated: L'Escape (hotel_luxury, 1 property) smb -> basic (₩10.5M);
non-luxury single -> smb (₩3.0M); SHR chain -> treatment (₩29.5M) unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:15:55 +09:00
0496262cd5 feat(skills): author root SKILL.md for reference-curator suite + 7 sub-skills
Finish the migration for the dirs the bulk pass couldn't auto-handle:

- 90-reference-curator/SKILL.md: hand-authored suite orchestrator (pipeline overview,
  7-stage table, /reference-curator run modes, install) — the single loadable entry.
- 90-reference-curator/0{1..7}-*/SKILL.md: generated from each sub-skill's desktop/SKILL.md.
- scripts/migrate_skill_root.py: generalized discovery to find nested suite sub-skills
  (rglob desktop/code SKILL.md), so the migrator now handles suites too.

81-mac-optimizer, 91-multi-agent-guide, 94-dintel-bootstrap need NO root SKILL.md: they
are Claude Code plugins whose skill correctly lives at skills/<name>/SKILL.md (validated).
Adding a root SKILL.md there would violate plugin structure.

All SKILL.md repo-wide validate: flat-root=65, suite-sub=7, plugin-skills=3, 0 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:14:46 +09:00
60734dbde7 Recalibrate estimate for SMB acceptability
Real-world feedback: list-rate calc overshot SMB-acceptable levels.
- scaling: driver properties_total -> subbrands_total (chains share templates),
  cap x6.5 -> x2.0, applied to On-page only (Technical now fixed site-wide)
- add 'smb' entry tier (lean hours @ 0.55 billing); 3-tier auto-select
  (smb/basic/treatment) by portfolio size; per-tier billing; --baseline smb enabled
- docs (findings_to_service.md, SKILL.md) synced to 3-tier model

Effect: SHR 25-property chain 71.5M -> 29.5M; SMB single hotel ~3.0M;
basic/treatment still reproduce real 10.5M/25.0M at 1 property.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:13:38 +09:00
e519a49cc4 feat(skills): bulk-add root SKILL.md to 61 skills (native Agent Skills loadability)
Run the additive migration pass from SKILL-MIGRATION-GUIDE: generate a root SKILL.md
for every skill that lacked one, copied from its desktop/SKILL.md (or code/SKILL.md),
with name set to the directory name and description + body preserved verbatim.

- scripts/migrate_skill_root.py: the reusable, non-destructive migrator (dry-run default).
- 61 new root SKILL.md (desktop source for most; code/SKILL.md for 61/62/92).
- Untouched: 16/17/95 (already had root); desktop/ and code/ packaging left intact.
- All 64 root SKILL.md validate: frontmatter <=1024, kebab name, description present.

Still MANUAL (no SKILL.md source — commands/README only), need hand-authored root SKILL.md:
81-mac-optimizer, 90-reference-curator, 91-multi-agent-guide, 94-dintel-bootstrap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 01:05:20 +09:00
5f66f57a8e Rebuild estimate engine to effort-based SOW model
Replace flat per-service ranges with OurDigital's real quoting model
(role_rate × billing_rate 0.70 × standard hours), sourced from the
06_Working Template quotes:
- rate_card.yaml: role rate card, billing/basis/terms, tools, scaling bands
- sow_templates.yaml: basic + treatment task-hour templates
- estimate.py: assemble SOW from findings, scale Technical/On-page hours by
  properties_total, 제안가 = 합계 floored to 500k
- build_deck.py: estimate slide shows module 소계 + 제안가 (point)
- findings_to_service.md / SKILL.md / DESIGN.md: synced to new model

Validated: reproduces real Basic ₩10.5M and Treatment ₩25.0M exactly;
SHR (25 properties) scales to ₩71.5M, L'Escape (1) = ₩25.0M.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:51:52 +09:00
01201ee4a2 docs(skills): add SKILL-MIGRATION-GUIDE + adopt root-SKILL.md hybrid as standard
- reference/SKILL-MIGRATION-GUIDE.md: one-page recipe to add a root SKILL.md to an
  existing skill (additive, no rewrite) — target structure, frontmatter checklist,
  a verified validation snippet, incremental-adoption guidance. References 16/17.
- CLAUDE.md: replace "Dual-Platform Skill Structure" with the hybrid (root SKILL.md +
  code/ + desktop/) as the going-forward standard; add "Root SKILL.md first" design
  principle; require a root SKILL.md for new skills; link the guide in Key Reference Files.
- SKILL-FORMAT-REQUIREMENTS.md: note the root-SKILL.md standard + cross-link the guide.

Validation snippet self-tested against 16/17 (frontmatter ≤1024, kebab name, all
referenced relative paths resolve).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:50:50 +09:00
1e3b2a4fa0 chore(seo-schema-generator): restore dual-platform code/ + desktop/ structure
The full-merge commit removed 17's code/ and desktop/ folders along with the old
template-fill generator, leaving 17 structurally inconsistent with 16 (and the repo's
documented dual-platform convention). Restore them for the MERGED skill:

- code/CLAUDE.md — Claude Code directive pointing to the root two-mode pipeline
  (no script duplication; root scripts/ stays the single source of truth).
- desktop/SKILL.md + skill.yaml — Claude Desktop directive for the two-mode skill.
- desktop/tools/{firecrawl,perplexity}.md — restored verbatim from git (still used by
  Mode 1 crawl / Mode 2 research).

The old generator's logic stays retired; only the dual-platform folder structure and
relevant tool docs return. 17 now matches 16's layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:45:10 +09:00
1706a820fe feat(seo-schema-generator): merge site-extraction + source-to-schema into one skill
Unify the two schema-generation scenarios into a single slot-17 skill, both
feeding one claims register -> build -> validate(16) pipeline:

- Mode 1 (existing site): NEW scripts/extract_site_claims.py turns URLs / local
  HTML / a directory into a claims register. Existing JSON-LD -> CONFIRMED;
  title/OpenGraph -> PENDING (never auto-shipped). + site-extraction-methodology.md
  and bundled fixtures/site/ demo pages.
- Mode 2 (not-yet-published site): land the source-to-schema engine
  (build_schema_drafts.py, type_templates.json, claims/source registers, 3 refs,
  sample_claims.csv) from the Desktop builder.
- Rewrite SKILL.md (v2.0) around the two-mode framing; the claims register is the
  shared pivot. Only CONFIRMED, non-conflicting claims become schema; unfilled
  template slots are pruned, never emitted as placeholders.
- Retire the old template-fill generator (code/ + desktop/); update root CLAUDE.md.

Self-tested both chains end-to-end: Mode 2 sample -> build -> validate PASS (P0=0);
Mode 1 fixtures -> extract -> build -> validate PASS (P0=0), JSON-LD round-trips with
nested address intact. Fixed two adapter bugs (nested node promotion; relative-path URI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:38:40 +09:00
3a8edebfef estimate.py: scale local_seo/onpage_entity by portfolio size
Add configurable sub-linear scope-scaling bands to rate_card.yaml; estimate.py
now multiplies monthly line-item rates by properties_total (local_seo) and
subbrands_total (onpage_entity), with the scope note written into the 견적.

Validated: L'Escape (1 property) stays at base 23-47M; SHR (25 properties,
5 sub-brands) scales to 54.8-110.6M (local ×4.5, on-page ×2.2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:24:33 +09:00
4f48ba3c59 feat(seo-schema-validator): back the upgraded SKILL.md with a working 5-layer pipeline
The "Upgrade Schema Validator" commit added SKILL.md referencing files that did
not exist. Implement them so the skill actually runs:

- scripts/validate_schema.py — 5-layer offline validator (L0 coverage, L1 syntax,
  L2 vocabulary/value-format, L3 rich-result, L4 consistency) with xlsx/csv/jsonl/
  json/dir/live-URL adapters. Gate = zero P0; exits 1 on failure.
- scripts/schema_rules.json — curated hotel-focused, offline rule set (edit-only
  extension point).
- scripts/make_sample.py + fixtures/sample_schema.csv — deliberately flawed fixture
  seeding ≥1 defect per layer; used to self-test.
- references/ — validation-methodology, defect-taxonomy (25 codes), hotel-type-map.
- templates/ — client-qa-report, decision-log.
- code/CLAUDE.md — redirect legacy single-URL tool to the new pipeline.

Noise control: MISSING_RECOMMENDED aggregated one-line-per-node; unexpected-property
checks opt-in via --strict. Generalized client-specific shilla-type-map → hotel-type-map.
Self-tested: default P0=5/P1=4/P2=14 FAIL, --strict --no-recommended P2=0, adapters verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:48:51 +09:00
ba88247496 Implement ourdigital-presales-seo skill
SKILL.md orchestration (8 gated stages), references (rate_card.yaml,
findings_to_service rubric, competitor sets), findings.schema.json contract,
and scripts: kg_query.py (generalized KG examination), estimate.py
(findings→rate-card 견적 md/xlsx/json), build_deck.py (9-slide branded PPTX),
render_pdf.sh (Korean PDF via headless Chrome), plus client_brief.html template.

Validated on Sono Hotels & Resorts findings: estimate OD-2026-001
(23-47M KRW) and a 9-slide deck generated cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 23:10:53 +09:00
a55e77d1b0 Add design spec for ourdigital-presales-seo skill
Standardizes the pre-sales SEO + Knowledge Graph diagnostic (origin: Sono
Hotels & Resorts) into a reusable skill with findings→rate-card estimate,
editable PPTX sales deck, and Notion SEO Audit DB archiving.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 22:58:22 +09:00
35f155fa90 Upgrade Schema Validator 2026-05-27 22:04:00 +09:00
3fbce9dafb fix(notion-writer): paginate clear_page_content to handle >100 blocks
notion.blocks.children.list returns at most 100 blocks per call.
Without pagination, --replace only cleared the first 100 blocks
of a page, leaving the rest behind and producing surprising
partial-replace behavior.

Loop with next_cursor until has_more is false so the entire
page is cleared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:34:29 +09:00
a46364e22a chore(gtm): deprecate dintel-gtm-toolkit, point to DTM Agent
The `dintel-gtm-toolkit` Python scripts (analyze_container.py,
diff_versions.py, validate_tags.py, find_unused.py) have been
replaced by the DTM Agent skill set, which operates on live
containers via the GTM API instead of exported JSON.

- .claude/commands/gtm-guardian.md: rewrite as a deprecation
  pointer with old→new mapping for every script/phase
  (Phase 6 → /dtm-audit + /dtm-debug, Phase 7 → /dtm-taxonomy +
  /dtm-lookup, etc.).
- custom-skills/62-gtm-validator/code/references/phase6-audit.md:
  replace "D.intelligence GTM Toolkit Integration" section with
  DTM Agent skill map, live-container workflow, and dtm CLI
  examples; update objective #4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:34:29 +09:00
8189473008 chore(dintel-skills): align Agent Corps with canon v1.0 / BRAND-GUIDE v1.3
Standardize all 9 dintel-* skills + shared infrastructure against the
new D.intelligence canon (brand-canon, fact-sheet, service-architecture,
naming-conventions v1.0) and BRAND-GUIDE v1.3.

Term replacements (applied across SKILL.md, CLAUDE.md, scripts, refs):
- Slogan: SMART Marketing Clinic -> SMART Marketing Intelligence
  (Clinic is now an OurDigital asset; split enforced)
- Core Values: Scientific/Practical -> Science/Practice
- MD category: Marketing Diagnosis -> Measurement Design
- Brand Character: 마케팅 주치의 -> 성과중심의 데이터 기반 마케팅 과학자
- Address: 송파테라타워/황새울로 -> 판교글로벌비즈센터 1층 36호 (13449)
- External email: contact@ -> info@dintelligence.co.kr
- CEO title: D.HIVE CEO & Founder / Senior Advisor -> 대표이사
- Tone keywords: Scientific/Practical/Outcome-oriented
  -> Science-driven/Practice-oriented/Outcome-focused
- Foreign-word literals: 감사 보고서/리포트/결과 -> 진단 ~ (audit->진단)
- Adjacent services: removed Courses (transferred to OurDigital Practice)

Structural changes:
- Insert "v1.3 정합성 - 단일 진실" block citing canon docs in all 9
  SKILL.md files (preserves single source of truth at runtime).
- Bump version (1.0->1.1 or 1.1->1.2) and add canon_compliance: v1.3 +
  last_updated: 2026-05-18 to every frontmatter.
- Expand brand.py: CORPORATE/LAB dicts (full address, biz reg, CEO),
  TRANSLATION_STANDARDS, ADJACENT_SERVICES. PROHIBITED_WORDS grew
  13 -> 24 entries so Brand Guardian flags legacy phrases at runtime.
- Rewrite _dintel-shared/references/dintelligence_brand_guide.md as
  v1.3 canon mirror; sync brand-editor's local copy (no drift).
- Pin design-system reference header to 2026 PPTX v2.0.
- Refresh 73-quotation generate_quotation.py (Sheet 1 cover, Sheet 5
  terms) and 71-brand-editor generate_credential.py output.
- Update audit template title (감사 -> 진단).

Verified by 93-check compliance suite covering frontmatter, canon
citation, prohibited-term grep, Python compile + runtime imports, and
an end-to-end smoke test that generates a quotation .xlsx and inspects
cell content for v1.3 strings + absence of legacy strings.

Refs:
  knowledge-base/canon/{brand-canon,fact-sheet,service-architecture,naming-conventions}.md v1.0
  02_Brand/BRAND-GUIDE-v1.3.md
  knowledge-base/TODO-claude-code-skill-rebuild.md
  knowledge-base/gotcha/01_outdated-facts.md (10 cases)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 07:11:52 +09:00
dependabot[bot]
ec3b4df1e3 chore(deps): bump tqdm (#9)
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.1 to 4.66.3.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.3)

---
updated-dependencies:
- dependency-name: tqdm
  dependency-version: 4.66.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-15 11:56:42 +09:00
Andrew Yim
9630428c3e chore(notion-organizer): drop dead aiohttp dependency (#8)
aiohttp was listed in requirements.txt but never imported by any of the
six Python scripts in custom-skills/31-notion-organizer/code/scripts.
The async HTTP work is done via notion-client 2.2.1, which uses httpx
internally (confirmed via `pip show notion-client`).

Removing the unused pin will stop future dependabot churn for a library
this skill doesn't depend on.

Verified before this change: aiohttp 3.13.4 (just merged via #6) was
inert — all 30 tests in test_notion_search.py pass, and all 6 scripts
import cleanly without aiohttp present.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 08:23:36 +09:00
dependabot[bot]
89889300d6 chore(deps): bump python-dotenv (#5)
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.0.0 to 1.2.2.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.0.0...v1.2.2)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-version: 1.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-14 03:15:52 +09:00
dependabot[bot]
5b1bb2f0c5 chore(deps): bump aiohttp (#6)
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.13.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-14 03:15:42 +09:00
Andrew Yim
e527fb4b0f feat(seo-skills): multi-backend Data Source Selection (#7)
Replaces single-vendor (Ahrefs-only) tool defaults with a per-task
backend menu across all 14 SEO skills. Each skill now lists every
capable MCP in allowed-tools and documents how to pick between
Semrush, Ahrefs, OurSEO Agent (CLI + MCP), DataForSEO, and GSC
in its SKILL.md Data Source Selection section.

Tool stubs (~40 new files) populated per skill with capability
deltas, call patterns, and explicit "not for this skill when"
callouts so the menu is self-correcting.

Skills affected: 19-keyword-strategy, 20-serp-analysis,
21-position-tracking, 22-link-building, 23-content-strategy,
24-ecommerce, 25-kpi-framework, 26-international, 27-ai-visibility,
28-knowledge-graph, 31-competitor-intel, 32-crawl-budget,
33-migration-planner, 34-reporting-dashboard.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:15:32 +09:00
1ca84f67ed add: jamie-journal-editor gotcha file. 2026-05-13 15:46:24 +09:00
2ee018a146 docs(our-gdrive-organizer): add 3 gotchas from Brand in Action audit
- D_intelligence (underscore) — typo, NOT a regex variant. Fix one-off
  via mv; do not widen RENAME_RULES (false-positive risk on legit
  underscore-separator filenames).
- Externally-generated filenames vs OurDigital convention. Default to
  normalize {Client}_/Client_/client_ → CLIENT- inside Active Workspaces;
  drop 14-digit timestamps to YYYYMMDD; preserve only when an external
  system requires the literal name.
- Reference library naming inconsistency — DO NOT bulk-normalize. Mixed
  naming reflects source provenance (Slideshare slugs, vendor whitepapers,
  Korean blog captures). Group by topic into subfolders instead
  (frameworks/, examples/, ko/) when count exceeds ~15 at root.

Patterns library now at 15 gotchas. Validated by live application during
01_Brand in Action audit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:15:48 +09:00
c750fa7f5e feat(our-gdrive-organizer): add new skill at slot 82, rename old 82 → 92
New Python CLI + dual SKILL.md (Code + Desktop) for organizing Google
Drive folders under OurDigital conventions:

- Refresh root README index (preserves manual Topics/Notes between
  AUTO-STRUCTURE markers)
- Ensure per-subfolder README.md meta files
- Propose filename + folder renames (D.intelligence → OurDigital with
  SEO-context caveat documented in patterns/gotchas.md)
- Propose moves for cluttered files (screenshots, temp downloads)
- Sensitive-folder skip list (04_Case Studies, 99_Project Archive,
  *Archive*, 진단*)
- shared/patterns/ gotcha library: canonical-files, canonical-folders,
  categorization-rules, 12 known gotchas — grows over time as the
  system encounters new edge cases

Slash command: /organize. CLI: ~/.local/bin/our-gdrive-organize.

82-tui-design-template renumbered to 92 (no content change) to free
slot 82. AGENTS.md and CLAUDE.md updated for both moves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:02:45 +09:00
e1bd799672 docs(notion-writer): document 1Password credential handling, deprecate .env
Plaintext .env storage of NOTION_API_KEY is unnecessary risk — Notion
integration tokens grant write access to every connected page/database
and have no expiry. Document the 1Password CLI fetch pattern as the
preferred path; .env becomes a discouraged fallback for environments
without `op`.

SKILL.md adds a Credential handling section covering:
- One-shot inline fetch via `op read 'op://Development/Notion - Claude Agent/api-key'`
- Subshell-scoped export pattern for repeated use without shell-history exposure
- 1Password field-name conventions (api-key for the token)
- Anti-patterns to avoid (echo, CLI args, git commits, chat paste)
- Token rotation procedure

notion_writer.py error message updated to point at the 1Password recipe
when NOTION_API_KEY is missing, instead of telling users to create a .env.

Tested end-to-end: pushed a 22 KB markdown report (16 tables, 120 nested
checkboxes) using the inline 1Password fetch. Bot identity verified, all
markdown blocks rendered as native Notion blocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:14:48 +09:00
69526d345a fix(notion-search): correct --filter example syntax in docs
Final review caught that both --filter example locations used simplified
JSON ({"Status": "Done"}) that Notion's data_sources.query API rejects
with a 400. The script passes --filter verbatim, so users copy-pasting
the example would hit a confusing error.

Replace with Notion's actual filter shape:
  {"property": "Status", "status": {"equals": "Done"}}

Also added a compound (and/or) example in CLAUDE.md so users have a
reference for combining filters.

30/30 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:57:29 +09:00
c1061dcc71 docs(notion-search): add /notion-search slash command + CLAUDE.md section
Slash command at custom-skills/31-notion-organizer/commands/notion-search.md
documents the CLI surface and JSON output schema. CLAUDE.md gains a
Semantic Search section explaining the 4-stage pipeline and env var
requirements. requirements.txt notes the optional anthropic SDK
dependency (the skill falls back to the claude CLI if missing).

Final task of Phase 3b-i. 30 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:53:56 +09:00
40b79962fb fix(notion-search): warn on --filter without --databases + DRY cache_kwargs
Inline code review polish:
- --filter is only meaningful in per-database mode (workspace search
  doesn't accept Notion filter objects). Previously a user passing
  --filter without --databases would have it silently parsed and
  ignored. Now emit a stderr warning and clear the filter.
- cache_kwargs dict was built twice in run_search (once for cache_get,
  once for cache_put). Build once before the rerank call.

30/30 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 13:52:11 +09:00
72d4b36943 feat(notion-search): add CLI entrypoint with argparse + output formatting
Wires together the four stages (expand → search → enrich → rerank) into
run_search(). CLI flags: --databases, --filter, --limit, --no-rerank,
--no-expand, --no-cache, --json. Terminal output renders as a numbered
table with title, relevance, properties, snippet, URL.

Cache lookup happens BEFORE rerank, with cache_put after success.
NOTION_API_KEY (or NOTION_TOKEN) env var required.

4 end-to-end pipeline tests (mocked Notion + LLM): JSON-serializable
output, --no-rerank skip, --no-expand skip, cache hit on repeat.

Total: 30 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:07:00 +09:00
20029ecc9c feat(notion-search): add Claude Haiku rerank module
Builds a rerank prompt with title + flattened properties + excerpt for
each candidate, calls Claude, parses JSON, sorts by score descending,
takes top N. On any failure (LLM error, missing JSON, parse error,
non-list shape), falls back to candidates in input order with null
relevance/snippet.

4 tests: ordering, limit, parse-error fallback, exception fallback.
26 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 08:01:45 +09:00
e67209b905 fix(notion-search): drop unused _flatten_property arg + lock falsy-value behavior
Code review polish:
- Drop the unused `name` parameter from `_flatten_property` (it was
  reserved for future per-property-name special-casing but never used).
- Add a regression test pinning that checkbox=False and number=0 are
  preserved in the enriched output. The existing empty-value filter is
  `if value not in (None, [], "")` which keeps falsy-but-meaningful
  values, but the contract wasn't tested.

22/22 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:59:49 +09:00
8aa0fa26e9 feat(notion-search): add candidate enrichment (title, properties, excerpt)
For each candidate page, extract the title, flatten common property
types (status/select/multi_select/date/checkbox/number/url/etc.) to
display values, and fetch the first text-bearing block as a 200-char
excerpt. Empty excerpt is acceptable when the page has no leading text.

4 tests: title+properties, paragraph excerpt, empty fallback,
truncation. 21 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:56:35 +09:00
a40d1f06b5 fix(notion-search): make API errors non-fatal in search_candidates
Code review caught that without exception handling around
notion.search() and notion.data_sources.query(), any transient API
hiccup (rate limit, 5xx, network blip) would crash the whole search
and lose candidates already accumulated from earlier variants/DBs.

Wrap both calls in try/except, mirror the resolver's pattern: stderr
warning + continue. Same query against another variant or another DB
still has a chance to succeed.

Also tightened response.get("results", []) → response.get("results")
or [] so a hypothetical {"results": null} response doesn't crash the
inner loop.

17/17 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:54:47 +09:00
a4da24b15c feat(notion-search): add Notion search execution with workspace/DB modes
For each expanded query variant: hit Notion's workspace search OR
per-database data_sources.query (when --databases is specified).
Union and dedupe by page ID, cap at 30 candidates total. Filters out
non-page objects (databases) from workspace search results.
Property filters (--filter JSON) pass through to data_sources.query
when in per-database mode.

5 tests: workspace dedup, 30-cap, DB-mode dispatch, page-only filter,
property-filter passthrough. 17 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:52:00 +09:00
5f6438ecf3 fix(notion-search): polish expand_query (warn on shape error, lint fixes)
Code review polish:
- Add stderr warning for the fourth failure path (LLM returned valid
  JSON but it's not a list-of-strings). Previously this fell back
  silently, making debugging harder when Haiku produces unexpected
  shapes.
- Drop unused f-string prefixes from two warnings (no interpolation).
- Type hint: Callable[..., str] = None → Optional[Callable[..., str]] = None
  for strict type-checker compatibility.

12/12 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:50:03 +09:00
fffbab201d feat(notion-search): add query expansion via Claude Haiku
Generates up to 5 query variants (synonyms + cross-language KR↔EN) so
later Notion API search can union over them. Permissive failure modes:
LLM error or non-JSON response falls back to [original] with stderr
warning. Dedupes and caps variants.

4 tests: variants list, dedup+cap, JSON-parse fallback, exception
fallback. 12 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:47:20 +09:00
6bb8e6ab3c fix(notion-search): tighten cache type hints + deterministic TTL=0
Code review polish:
- Type hints: List → List[Dict[str, Any]] for cache_get return and
  cache_put results parameter, since rerank produces dict-shaped entries.
- TTL=0 short-circuits to None deterministically. Previously the test
  for ttl_seconds=0 passed only because clock motion between cache_put
  and cache_get made `time.time() - cached_at > 0` true. Now the
  semantics match the docstring intent.

8/8 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:45:44 +09:00
32a1c9d538 feat(notion-search): add SHA256-keyed JSON file cache
Cache layer for rerank results. Key is SHA256 of query + sorted
candidate IDs, so changing the candidate set automatically invalidates.
1-day TTL by default; corrupted cache files are silently dropped.

5 tests cover hit, miss, different-candidates, TTL expiry, corruption.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:42:48 +09:00
89b20aef16 fix(notion-search): pin SDK model to dated ID + drop unused type alias
Code review caught that the bare alias 'claude-haiku-4-5' works in the
Claude Code CLI but may not resolve in the Anthropic SDK. Pin the
default to 'claude-haiku-4-5-20251001' (the full dated ID) via a
DEFAULT_MODEL constant so the SDK path doesn't silently break the
first time it hits the real API.

Also document the max_tokens-ignored behavior on the CLI path in the
public call_claude docstring (not just the private _call_via_cli),
and drop the unused LLMCaller type alias (dead code).

3/3 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:41:25 +09:00
45c68dee61 feat(notion-search): add LLM client abstraction with SDK + CLI fallback
First task of Phase 3b-i (notion semantic search). Adds _search_llm.py
that dispatches to the anthropic SDK when ANTHROPIC_API_KEY is set,
falls back to `claude -p` CLI otherwise, and raises a clear setup
error if neither works. Symlinks _notion_compat.py from 32-notion-writer
so both skills share one source of truth.

3 tests cover the three dispatch paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:37:42 +09:00
56f33eca3f docs(notion): add Phase 3b-i implementation plan
Eight bite-sized TDD tasks covering: LLM client abstraction, cache
layer, query expansion, search execution, candidate enrichment,
rerank, CLI assembly, and slash command + docs. Each task ends with
a working commit; total 29 passing tests at completion.

Plan: docs/superpowers/plans/2026-04-28-notion-semantic-search.md
Spec: docs/superpowers/specs/2026-04-28-notion-semantic-search-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:30:57 +09:00
08e3dd7dae docs(notion): add Phase 3b-i spec for Notion semantic search skill
Brainstorming output for the first sub-project derived from the
original "Notion-as-RAG export" idea. After scope clarification, that
vision split into two independent skills:

- 3b-i (this spec): semantic search foundation
- 3b-ii (separate, later): notion-to-notebooklm push

Locks five architectural decisions reached during brainstorming:
- Strategy C — query expansion + LLM rerank (closes the gap from
  Notion's keyword-only native search)
- Standalone search skill, JSON output for downstream chaining
- Claude Haiku 4.5 for both stages (cheap, fast, plenty good)
- SHA256(query + candidate_ids) cache with 1-day TTL
- Permissive degradation matching Phase 3c parser philosophy

~850 LOC + tests. ~3 days estimated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:23:30 +09:00
6446f00522 docs(notion-writer): document Phase 3c block coverage + bump to v1.2.0
Adds rows for callouts, toggles, columns, and page mentions in the
supported-elements and inline rich-text tables. Adds usage examples
for each. Updates version footer with changelog entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:38:53 +09:00
707405b940 docs(notion-writer): document mention pattern ordering and UUID format invariant
Code review flagged two implicit invariants worth pinning with comments:
- 'mention' must precede 'link' in INLINE_PATTERNS so the @[X](Y) prefix
  takes priority over plain link parsing
- format_id_with_dashes is required because the Notion mention API expects
  a dashed UUID while extract_notion_id returns dashless

32/32 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:37:07 +09:00
328de7d052 feat(notion-writer): add inline page mentions via @[Title](id-or-url)
Inline rich-text gets a new 'mention' pattern matching @[Title](target).
Target can be a raw 32-hex page ID, a dashed UUID, or a Notion URL —
all resolved via the existing extract_notion_id helper. Invalid targets
degrade to plain text '@Title' with no warning (avoids false-positive
spam from unrelated @[x](y) patterns).

Pattern placed BEFORE 'link' in INLINE_PATTERNS so the @ prefix takes
priority over plain link parsing.

Also widens extract_notion_id's URL regex from [^-]+- to [^/]+?- so
multi-hyphen Notion URLs (e.g. notion.so/My-Cool-Page-{id}) resolve
correctly — previous regex only matched single-segment titles.

+3 tests, 32 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:33:10 +09:00
0421a65327 feat(notion-writer): add column_list blocks via Pandoc fenced div
Pandoc-style ::: columns / ::: column / ::: blocks emit a Notion
column_list with column children. Each column's body recurses through
_parse_lines so any block type (lists, code, nested columns) works
inside. Single-column wrappers degrade to plain paragraphs because
Notion requires at least two columns.

Single-depth-counter design: depth=1 on enter, increments on `::: columns`
or `::: column`, decrements on `:::`. depth=0 closes the wrapper.

+3 tests, 29 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:26:02 +09:00
0f8d7b2df1 fix(notion-writer): use module-level sys instead of inline import in toggle warning
Code review caught a redundant `import sys as _sys` inside the unclosed-
<details> branch. The module already imports `sys` at the top, so the
inline import was dead weight. Use the module-level `sys.stderr` directly.

26/26 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:23:49 +09:00
aac1082bb7 feat(notion-writer): add toggle blocks via HTML5 <details>
Multi-line <details>...</details> with optional <summary> emits a Notion
toggle block. Body recurses through _parse_lines so any block type
(lists, code, nested toggles) is supported inside. Depth tracking lets
<details> nest inside <details>. Unclosed <details> at EOF degrades to
plain paragraphs with a stderr warning instead of crashing.

+4 tests, 26 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:20:21 +09:00
4e692d0e9a fix(notion-writer): add "object": "block" to create_callout_block
Code review caught that all 9 other create_*_block factories include
"object": "block" as the first key, but the new callout factory was
missing it. The Notion API accepts both forms, but the inconsistency
would compound; align with the existing convention.

22/22 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:17:11 +09:00
e0f93e6add feat(notion-writer): add GitHub-alert callout blocks
Adds support for > [!NOTE] / > [!TIP] / > [!IMPORTANT] / > [!WARNING] /
> [!CAUTION] callouts. Each alert type maps to a Notion callout block
with an emoji icon and matching colored background. Unknown alert types
(e.g. > [!BOGUS]) fall through to the existing quote handler with the
marker preserved.

Also restores the "must come before generic bullet match" comment on
the todo branch (load-bearing ordering note).

+6 tests, 22 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:12:50 +09:00
c318df8551 refactor(notion-writer): make markdown_to_notion_blocks reentrant
Split the entry function into a public reentrant entry that accepts
either string or List[str], and a private _parse_lines engine that
container detectors will recurse into. No behavior change for existing
callers; all 16 parser tests still pass.

Prep for Phase 3c: callout/toggle/columns/page-mention block coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:07:43 +09:00
e2ae8aad94 docs(notion): add Phase 3c implementation plan
Six bite-sized TDD tasks covering reentrant parser refactor, callouts,
toggles, columns, page mentions, and docs. Each task ends with a
working commit; total 32 passing tests at completion.

Plan: docs/superpowers/plans/2026-04-27-notion-writer-extended-block-coverage.md
Spec: docs/superpowers/specs/2026-04-27-notion-writer-extended-block-coverage-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:03:39 +09:00
c66b5e12cc docs(notion): add Phase 3c spec for extended block coverage
Brainstorming output for the first of three Phase 3 sub-projects:
adding callout, toggle, column, and page-mention block support to
notion_writer.py's markdown→Notion parser.

Locks four architectural decisions reached during brainstorming:
- Hybrid syntax (GitHub alerts / <details> / Pandoc fenced div)
- Full recursion for container blocks (toggles + columns)
- ID-or-URL for inline page mentions
- Reentrant flat parser (additive, ~150 LOC)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 10:54:37 +09:00
144a17c88d feat(notion): migrate 31+32 to API 2025-09-03 + add property writes, upsert, anchor-link fix
Phase 1 — docs alignment:
- Fix path drift in 32 CLAUDE.md (02-notion-writer → 32-notion-writer)
- Align env var to NOTION_API_KEY in 31 docs (NOTION_TOKEN still accepted)
- Document Phase 3 roadmap in 31 (metadata-aware migration, Notion-as-RAG)

Phase 2 — multi-source database support:
- New 32/scripts/_notion_compat.py: client factory, data_source resolution
  with cache, property coercion, find_existing_page (for upsert),
  explain_api_error for friendlier failure messages
- 32 notion_writer.py: drop the 2022-06-28 pin, route schema introspection
  through data_sources.retrieve, build data_source_id parent shape, accept
  arbitrary properties via --properties JSON-or-file flag, add --upsert-by
  for idempotent re-runs
- 32 markdown parser: anchor-link fix — Notion's URL validator rejects
  fragment URLs and relative paths; fragments now render as bold to preserve
  TOC nav intent, relatives drop the link, absolute URLs preserved
- 31 async_organizer.py + schema_migrator.py: same data_sources migration
  with cached resolution; pages.create now uses data_source_id parent
- 16/16 parser tests pass (was 13; +3 link-handling regression guards)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:57:20 +09:00
2667304bca feat(94-dintel-bootstrap): add MCP agent installer skill
New Claude Skill that wraps the `dintel-bootstrap` CLI from the
dintel-auth package to install and diagnose the three D.intelligence
custom MCP agents (DTM, D.DA, OurSEO) in ~/.claude/settings.json.

Triggers on: "set up MCP agents", "install DTM/D.DA/OurSEO", "run MCP
doctor", "bootstrap a new machine", "my MCP agents stopped working".

Scope boundaries:
- Only registers existing agent installs (does NOT clone agent repos)
- Only touches ~/.claude/settings.json (does NOT modify .mcp.json)
- Does NOT store secrets; OurSEO profile uses op run wrapper
- Does NOT trigger interactive auth; prompts user to run op signin

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:13:09 +09:00
665fe22201 feat(notion-writer): add table blocks, inline rich-text parsing, and parser tests
Extend markdown_to_notion_blocks with GFM table support and rewrite
parse_rich_text to emit Notion annotations for bold/italic/code/link/strike
instead of dropping them as literal text. Move checkbox match ahead of the
bullet match so "- [ ]" lines become to_do blocks, not bullets.

Add test_parser.py with 13 regression tests covering rich-text spans,
block types, and table cell formatting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:00:12 +09:00
c9772db119 fix(notion-writer): skip archived blocks when replacing page content
The --replace flag failed with "Can't edit block that is archived" when
a page contained previously archived blocks. Now skips them during clear.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:26:47 +09:00
382b55e9c8 feat(reference-curator): add --wayback-fallback flag + CAPTCHA/IP block detection
- Add _is_captcha_response() to detect Google sorry page, 429, and
  "unusual traffic" markers in crawler responses
- Add --wayback-fallback CLI flag: auto-switches remaining URLs to
  web.archive.org/web/2024/ after 3 consecutive CAPTCHA blocks
- Add --delay CLI flag for polite crawling with configurable interval
- Add retry logic with exponential backoff on 429/5xx responses
- Document GOTCHA section in crawler CLAUDE.md with detection signals,
  Wayback detour solution, and prevention tips
- Add GOTCHA callout in pipeline orchestrator Stage 2 docs
- Record source: wayback in frontmatter for traceability

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 01:55:11 +09:00
b1c2dca080 feat(reference-curator): add --save-raw, --no-distill flags + OurSEO crawler integration
- Add --save-raw flag: saves intact crawled markdown in raw/ subdirectory
  alongside distilled content, with YAML frontmatter for traceability
- Add --no-distill flag: skip stages 4-5 (distiller + QA reviewer),
  pure archival mode for raw documentation capture
- Integrate OurSEO BaseAsyncClient + PageAnalyzer as seo-aiohttp backend:
  token-bucket rate limiting, semaphore concurrency, tenacity retries,
  full page metadata extraction (headings, links, schema, OG)
- New seo_crawler_adapter.py: crawl URLs, sitemaps, link discovery, manifests
  with progress tracking and resume support
- Update crawler selection: docs sites now default to seo-aiohttp
- Update crawl_config.yaml with seo-aiohttp routing rules
- Update pipeline orchestrator with flag resolution and skip paths
- Update README.md and USER-GUIDE.md with raw mode documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:26:50 +09:00
f215c11c32 feat(reference-curator): implement Python scripts + Gemini quality gate
Build the refcurator shared Python package and 7 CLI scripts that were
previously specification-only. Add Gemini CLI as an independent pre-distillation
quality evaluator, replacing the circular Claude-self-review pattern.

Key changes:
- shared/lib/src/refcurator/: 7-module package (config, db, models, utils,
  manifest, gemini) with PyMySQL + JSON file dual backend
- 7 Click CLI scripts: discover, crawl_mgr, repo, distiller, reviewer,
  exporter, pipeline — each with subcommands for data management
- Gemini quality gate: evaluates raw content BEFORE distillation using
  5 criteria (relevance, authority, completeness, freshness, distill_value)
- Pipeline reordered: discovery → crawl → store → evaluate → distill → export
- Bug fixes from Codex adversarial review:
  - FileBackend now hard-fails on JOIN/aggregate/GROUP BY queries
  - Exporter uses MAX(review_id) to prevent shipping stale approvals
  - Distiller updates existing rows on refactor instead of forking
- Updated all 7 CLAUDE.md directives with real script references
- install.sh updated with refcurator package install step

51/51 E2E tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:22:28 +09:00
133df68b81 Merge branch 'main' of https://gitea.dintelligence.co.kr/ourdigital/our-claude-skills 2026-04-06 18:06:34 +09:00
292cd132c2 [brand] Core Values 수정: In-Action 유지 (Performance-driven → In-Action 복원)
In-Action은 OurDigital 서비스 태그라인 체계의 핵심:
SEO in Action, Brand in Action, Performance in Action, Data in Action

Core Values 최종 확정: 마케팅 과학 / 실행 지향(In-Action) / 지속 성장

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:33:59 +09:00
7c8dbaeb78 [brand] ourdigital-brand-guide v1.1 + 전체 Skills 정합성 업데이트
Core Values 재정의: 마케팅 과학 / 실행 성과 중심 / 지속 성장
Value Prop: 마케팅 과학으로 진단하고, 실행으로 처방합니다
Brand Compliance: 5항목 → 7항목
Deprecated Terms 섹션 신설
폰트 통일: Noto Sans KR / Inter (Poppins/Lora 교체)
색상 참조 통일: #221814 / #cedc00

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 20:32:46 +09:00
322f47dd6a update: new skills and settings 2026-04-04 02:04:20 +09:00
c35a28780d feat(gtm): add mandatory knowledge-base read/write directives to GTM skills
Agents must now read client profiles before GTM work and write session
logs after completing audits, tag changes, or validation — ensuring
findings compound across sessions instead of being lost.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 00:03:27 +09:00
f292408ea8 docs: register jamie-faq-entry in CLAUDE.md, README.md, AGENTS.md
Add skill #42 to Jamie Clinic tables, directory layout, and agent
routing. Update skill count from 64 to 65.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 03:26:24 +09:00
500d352026 feat(jamie-faq-entry): add skill with dual-platform structure and slash command
Restructure 42-jamie-faq-entry into code/desktop/shared layout,
create Claude Code directive, register /jamie-faq-entry command.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 03:20:53 +09:00
8e56a0f926 fix: update gtm commands 2026-03-31 02:38:27 +09:00
3c7aaa2d6e Refactor CLI into package structure and fix reported bugs (B1-B12) 2026-03-30 16:24:05 +09:00
8b98fdd2fc feat(jamie-journal): merge desktop SKILL.md, align code CLAUDE.md
Desktop: merged SKILL-org.md (base) + SKILL-new.md additions into SKILL.md
- Added Workflow Modes (full drafting + manuscript editing)
- Added SEO metadata, Visual DNA, Featured Image rules
- Added Gemini prompt templates (4 types), Schema data, 3-file output
- Preserved all original brand voice, analogies, procedures, numerics

Code: rewrote CLAUDE.md to align with desktop (1,195 words)
- Same section coverage, condensed for directive limit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:21:22 +09:00
c73d284d44 feat(jamie-journal): add /jamie-journal-editor slash command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 07:45:47 +09:00
fd7b7c7fbb docs(jamie-journal): update README with code version structure and quick start
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 07:34:56 +09:00
c60e2abd34 fix(jamie-journal): broaden individual_variation_pattern and tune StructureValidator regex 2026-03-29 07:34:04 +09:00
ccdd2c44ef feat(jamie-journal): implement LinkRecommender (procedure cross-linking suggestions) 2026-03-29 07:29:11 +09:00
311adaf06c feat(jamie-journal): implement SpellingChecker (built-in medical dict + optional hanspell) 2026-03-29 07:28:49 +09:00
1d236192b0 feat(jamie-journal): implement ComplianceChecker (의료법 제56조 pattern matching) 2026-03-29 07:28:28 +09:00
18bf611663 feat(jamie-journal): implement StructureValidator (5-step body, opening, CTA, disclaimer) 2026-03-29 07:27:52 +09:00
44731eb2f8 feat(jamie-journal): scaffold journal_validator.py with CLI and orchestrator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 07:25:26 +09:00
d6fd629943 feat(jamie-journal): rewrite code/CLAUDE.md as self-contained directive
Replaced stub referencing desktop/references/ with fully inline content:
all brand voice, analogy dictionary, procedure copy, compliance rules,
numeric expressions, image placement, and workflow sections embedded directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 01:26:05 +09:00
3b42b3d5a9 chore: untrack .claude/settings.local.json
Machine-specific permission rules should not be in version control.
Already covered by .claude/* in .gitignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 00:23:02 +09:00
c11917b551 chore: add Gemini config, Jamie Instagram caption guide, merge settings
- Add .gemini/settings.json for Gemini CLI
- Add Jamie Instagram caption style guide (v1.0)
- Merge conflict in settings.local.json (both upstream and local permissions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:02:00 +09:00
edf5b546fe docs: sync README, AGENTS.md, CLAUDE.md with skills 46 and 47
- Update skill count 62 → 64
- Add jamie-journal-editor and jamie-marketing-editor to Jamie Clinic tables
- Update directory layout range and agent routing notes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:38:40 +09:00
55d450e635 feat(jamie): add skill 46-jamie-journal-editor and 47-jamie-marketing-editor
Restructured from Claude Desktop extracted skills into standard dual-platform
packages (code/ + desktop/) aligned with existing skill conventions.

- 46: Journal/blog content editor for journal.jamie.clinic
- 47: Multi-channel marketing content editor with compliance checker
- Updated CLAUDE.md skill registry and directory layout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:37:24 +09:00
877db1aa0f refactor: reorganize skill numbering, remove obsolete skills, rename shared libs
- Rename: 00→80 claude-settings-optimizer, 88→79 dintel-skill-update,
  92→81 mac-optimizer, 93→82 tui-design-template
- Rename: dintel-shared → _dintel-shared (consistent with _ourdigital-shared)
- Remove: 61-gtm-manager, 62-gtm-guardian (obsolete), 99_archive
- Update all dintel-* skill refs (114 occurrences across 31 files)
- Sync README.md, CLAUDE.md, AGENTS.md with new structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 19:32:44 +09:00
ee355479b7 update:new skills and settings. new remote repository is set 2026-03-23 19:03:37 +09:00
599 changed files with 65275 additions and 23331 deletions

View File

@@ -0,0 +1,163 @@
{
"$schema": "https://json.schemastore.org/claude-code-marketplace-schema.json",
"name": "ourdigital-skills",
"owner": {
"name": "OurDigital",
"email": "andrew.yim@ourdigital.org"
},
"metadata": {
"description": "OurDigital custom Claude skills, grouped into domain plugins (core, SEO, Jamie, D.intelligence, Notion, GTM, NotebookLM, utilities). Enable only the domains you need.",
"version": "1.0.0"
},
"plugins": [
{
"name": "ourdigital-core",
"description": "OurDigital core workflows — brand guide, Korean blog & English journal, research-to-blog, Notion-to-deck, visual design, ad copy, training material, back-office docs, skill creator, estimate engine.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/01-ourdigital-brand-guide",
"./custom-skills/02-ourdigital-blog",
"./custom-skills/03-ourdigital-journal",
"./custom-skills/04-ourdigital-research",
"./custom-skills/05-ourdigital-document",
"./custom-skills/06-ourdigital-designer",
"./custom-skills/07-ourdigital-ad-manager",
"./custom-skills/08-ourdigital-trainer",
"./custom-skills/09-ourdigital-backoffice",
"./custom-skills/10-ourdigital-skill-creator",
"./custom-skills/96-ourdigital-estimate-engine"
]
},
{
"name": "ourdigital-seo",
"description": "Full SEO suite — technical/on-page audits, Core Web Vitals, Search Console, schema validate/generate, local, keyword, SERP, rank tracking, links, content, e-commerce, KPIs, international, AI visibility, knowledge graph, gateway pages, competitor intel, crawl budget, migration, reporting, presales.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/11-seo-comprehensive-audit",
"./custom-skills/12-seo-technical-audit",
"./custom-skills/13-seo-on-page-audit",
"./custom-skills/14-seo-core-web-vitals",
"./custom-skills/15-seo-search-console",
"./custom-skills/16-seo-schema-validator",
"./custom-skills/17-seo-schema-generator",
"./custom-skills/18-seo-local-audit",
"./custom-skills/19-seo-keyword-strategy",
"./custom-skills/20-seo-serp-analysis",
"./custom-skills/21-seo-position-tracking",
"./custom-skills/22-seo-link-building",
"./custom-skills/23-seo-content-strategy",
"./custom-skills/24-seo-ecommerce",
"./custom-skills/25-seo-kpi-framework",
"./custom-skills/26-seo-international",
"./custom-skills/27-seo-ai-visibility",
"./custom-skills/28-seo-knowledge-graph",
"./custom-skills/29-seo-gateway-architect",
"./custom-skills/30-seo-gateway-builder",
"./custom-skills/31-seo-competitor-intel",
"./custom-skills/32-seo-crawl-budget",
"./custom-skills/33-seo-migration-planner",
"./custom-skills/34-seo-reporting-dashboard",
"./custom-skills/35-seo-signal-validation",
"./custom-skills/95-ourdigital-presales-seo"
]
},
{
"name": "ourdigital-notion",
"description": "Notion workspace tools — organize/clean up a workspace and write/export content into Notion.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/31-notion-organizer",
"./custom-skills/32-notion-writer"
]
},
{
"name": "ourdigital-jamie",
"description": "Jamie Plastic Surgery Clinic brand suite — branded content, brand audit, KakaoTalk Kanana FAQ, YouTube SEO + subtitle QA, Instagram management, journal editor, multi-channel marketing.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/40-jamie-brand-editor",
"./custom-skills/41-jamie-brand-audit",
"./custom-skills/42-jamie-faq-entry",
"./custom-skills/43-jamie-youtube-manager",
"./custom-skills/44-jamie-youtube-subtitle-checker",
"./custom-skills/45-jamie-instagram-manager",
"./custom-skills/46-jamie-journal-editor",
"./custom-skills/47-jamie-marketing-editor"
]
},
{
"name": "ourdigital-notebooklm",
"description": "NotebookLM automation — Q&A with citations, notebook/source/artifact management, studio content generation (podcasts, videos, quizzes), and research/source discovery. Requires the notebooklm-py CLI.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/50-notebooklm-agent",
"./custom-skills/51-notebooklm-automation",
"./custom-skills/52-notebooklm-studio",
"./custom-skills/53-notebooklm-research"
]
},
{
"name": "ourdigital-gtm",
"description": "Google Tag Manager tooling — container audit/gap analysis, tag/trigger/variable editor (API + ES5 Custom HTML + dataLayer), and QA/validation.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/60-gtm-audit",
"./custom-skills/61-gtm-editor",
"./custom-skills/62-gtm-validator"
]
},
{
"name": "ourdigital-dintel",
"description": "D.intelligence Agent Corps — brand guardian/editor, document secretary, quotation manager, service architect, marketing manager, back-office manager, account manager, and cross-skill update meta-agent.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/70-dintel-brand-guardian",
"./custom-skills/71-dintel-brand-editor",
"./custom-skills/72-dintel-doc-secretary",
"./custom-skills/73-dintel-quotation-mgr",
"./custom-skills/74-dintel-service-architect",
"./custom-skills/75-dintel-marketing-mgr",
"./custom-skills/76-dintel-backoffice-mgr",
"./custom-skills/77-dintel-account-mgr",
"./custom-skills/79-dintel-skill-update"
]
},
{
"name": "ourdigital-utils",
"description": "Utility skills — Claude settings/token optimizer, Google Drive organizer, reference-documentation curator suite, and TUI wizard design template.",
"source": "./",
"strict": false,
"skills": [
"./custom-skills/80-claude-settings-optimizer",
"./custom-skills/82-our-gdrive-organizer",
"./custom-skills/90-reference-curator",
"./custom-skills/92-tui-design-template"
]
},
{
"name": "mac-optimizer",
"description": "macOS system health toolkit — read-only audits, cleanup, and security checks. Commands: mac-doctor, mac-packages, mac-environment, mac-security, mac-cleanup, mac-resources (+ mac-optimizer skill).",
"source": "./custom-skills/81-mac-optimizer",
"strict": false
},
{
"name": "multi-agent-guide",
"description": "Multi-agent collaboration framework — agent hierarchies, ownership rules, guardrails, handoff protocols, and CI/CD integration for Claude, Gemini, Codex, and human agents. Commands: quick-setup, setup-agents (+ multi-agent-guide skill).",
"source": "./custom-skills/91-multi-agent-guide",
"strict": false
},
{
"name": "dintel-bootstrap",
"description": "Install and verify D.intelligence custom MCP agents (DTM, D.DA, OurSEO) in settings.json; bootstrap a new machine or diagnose a broken install (dintel-bootstrap skill).",
"source": "./custom-skills/94-dintel-bootstrap",
"strict": false
}
]
}

View File

@@ -0,0 +1,31 @@
---
description: D.intelligence campaign/promotion planning as a 3-gate process (cross-brand)
---
# D.intelligence Campaign Designer
Plans campaigns, promotions, events, and launches as a 3-gate process -- Discovery & Debate → Brief → Plan -- instead of jumping straight to a finished document. Draft & Wait autonomy: each gate stops for explicit approval. Not D.intelligence-exclusive -- works for any brand.
## Triggers
- "campaign plan", "plan a promotion", "캠페인 설계"
- 캠페인 기획, 프로모션 기획, 기획안 만들어, 이벤트 기획
## The 3 Gates
1. **Discovery & Debate** -- agree ONE primary objective; steelman + devil's-advocate debate; pre-mortem; 1-3 reference cases; effects as hypotheses. → `shared/templates/gate1-decision-log.md`
2. **Brief** -- objective, audience, offer, message, tone, channel; outcome metrics across 4 tiers. → `shared/templates/gate2-campaign-brief.md`
3. **Plan** -- full plan, handed off to `marketing:campaign-plan` + `doc-generator`; all risk/compliance consolidated in one closing "준비 점검 사항" section. → `shared/templates/gate3-plan-outline.md`
## 4-Tier Outcome Framework
Awareness/cognitive → Qualitative (name the brand asset) → Relationship/advocacy → Quantitative conversion (label as hypothesis if no baseline)
## Cross-Brand Routing
| Brand | Copy & tone | Compliance |
|---|---|---|
| D.intelligence | dintel-brand-editor (#71) | dintel-brand-guardian (#70) |
| Jamie | jamie-copy-trimmer (48) | jamie-brand-audit (41) |
| OurDigital | ourdigital-ad-manager (07) | ourdigital-brand-guide (01) |
## Guardrails
- Never advance a gate without explicit user approval
- Never commit pricing/quantitative targets without a baseline -- label as hypothesis
- Mark gaps `[확인]` instead of inventing facts

View File

@@ -1,46 +1,82 @@
---
description: Gtm Audit command
description: GTM page audit - scan fired tags, gap analysis, tag design from DOM, and report generation
---
# GTM Audit
Lightweight Google Tag Manager audit tool.
Comprehensive Google Tag Manager audit using Playwright to scan live pages for container health, tag firing, dataLayer events, form tracking, and e-commerce checkout flows.
## MANDATORY: Knowledge Base Read/Write
**Before starting any audit:**
1. Identify the target client from container ID or URL
2. Read `knowledge-base/accounts/<client>/profile.md` — URL patterns, known issues, platform stack, past findings
3. Read `knowledge-base/accounts/<client>/*.md` — taxonomy, naming issues to exclude from new findings
4. Skim `knowledge-base/logs/<client>/` — past session context
**After completing audit, write a session log:**
- Write to `knowledge-base/logs/<client>/YYYY-MM-DD-<description>.md`
- Include: date, container ID, status, issues found (root causes + specific IDs), lessons learned
- Update `knowledge-base/accounts/<client>/profile.md` if you discovered persistent facts
- See `AGENTS.md` for full format template
## Triggers
- "audit GTM", "check dataLayer", "GTM 검사"
- "audit GTM", "check dataLayer", "GTM 검사", "scan GTM tags", "audit tags on page", "check tag firing"
## Capabilities
1. **Container Analysis** - Tags, triggers, variables inventory
2. **DataLayer Validation** - Check event structure
3. **Form Tracking** - Verify form submission events
4. **E-commerce Check** - Validate purchase/cart events
1. **Container Analysis** — Verify GTM container loads, detect container ID, inventory tags/triggers/variables
2. **Tag Destination Detection** — Identify fired tags by network requests (GA4, Google Ads, Meta Pixel, LinkedIn, TikTok, Kakao, Naver, etc.)
3. **DataLayer Validation** — Check event structure, required fields, GA4 naming conventions
4. **Form Tracking** Verify form submission events and field capture
5. **E-commerce Checkout Flow** — Validate purchase/cart/checkout events and required e-commerce parameters
6. **Journey-based Audit** — Target specific user journeys: `pageview`, `scroll`, `click`, `form`, `checkout`, `datalayer`, or `full`
## Scripts
## Script
```bash
# Audit GTM container
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/20-gtm-audit/code/scripts/gtm_audit.py \
--url https://example.com
AUDIT_SCRIPT="/Users/ourdigital/Project/our-claude-skills/custom-skills/60-gtm-audit/code/scripts/gtm_audit.py"
# With detailed dataLayer check
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/20-gtm-audit/code/scripts/gtm_audit.py \
--url https://example.com --check-datalayer --output report.json
# Basic page audit
python "$AUDIT_SCRIPT" --url "https://example.com"
# Audit with expected container ID and specific journey
python "$AUDIT_SCRIPT" --url "https://example.com" --container GTM-XXXXXX --journey full
# E-commerce checkout flow audit with JSON report
python "$AUDIT_SCRIPT" --url "https://example.com/checkout" --journey checkout --output report.json
```
### Script Options
| Flag | Description | Default |
|------|-------------|---------|
| `--url` | Target URL to audit (required) | — |
| `--container` | Expected GTM container ID (e.g., GTM-XXXXXX) | auto-detect |
| `--journey` | Audit scope: `pageview`, `scroll`, `click`, `form`, `checkout`, `datalayer`, `full` | `pageview` |
| `--output` | Output file path for JSON report | `gtm_audit_report.json` |
| `--timeout` | Page load timeout in ms | `30000` |
| `--headless` | Run in headless mode | `True` |
## Audit Checklist
### Container Health
- [ ] GTM container loads correctly
- [ ] No JavaScript errors from GTM
- [ ] Container ID matches expected
- [ ] GTM container loads without JavaScript errors
- [ ] Container ID matches expected value
- [ ] No duplicate container installations
### Tag Firing & Destinations
- [ ] GA4 measurement requests detected
- [ ] Ad platform pixels fire correctly (Google Ads, Meta, etc.)
- [ ] No orphaned or misfiring tags
### DataLayer Events
- [ ] `page_view` fires on all pages
- [ ] `purchase` event has required fields
- [ ] Form submissions tracked
- [ ] `purchase` event has required e-commerce fields
- [ ] Form submissions tracked with correct parameters
- [ ] Event names follow GA4 snake_case convention
### Common Issues
- Missing ecommerce object
- Incorrect event names (GA4 format)
- Missing or malformed ecommerce object
- Incorrect event names (not GA4 format)
- Duplicate event firing
- Tags firing before dataLayer is ready

View File

@@ -6,10 +6,36 @@ description: GTM tag/trigger/variable creation via API with ES5 Custom HTML and
Create, modify, and deploy GTM configurations via API. Generates ES5-compliant Custom HTML tags.
## Pre-Flight
**BEFORE any API write operation**, read the relevant gotcha files at:
`~/Project/dintel-gtm-agent/docs/log/gotcha/` (see `README.md` for index)
Priority reads per task:
- **Creating triggers** → `gotcha/triggers.md` (scrollDepth/timer limits, negate placement, naming)
- **Creating variables** → `gotcha/variables.md` (RegEx Table column names)
- **Writing regex conditions** → `gotcha/regex.md` (RE2 limitations)
- **Batch changes / publishing** → `gotcha/compilation.md` (workspace lifecycle)
- **Using DTM CLI** → `gotcha/dtm-cli.md` (commands that don't actually work)
## Triggers
- "create GTM tag", "generate dataLayer", "modify trigger"
- "update variable", "write custom HTML", "manage GTM"
## MANDATORY: Knowledge Base Read/Write
**Before creating or modifying ANY tags, triggers, or variables:**
1. Identify the target client from container ID
2. Read `knowledge-base/accounts/<client>/profile.md` — key variables, URL patterns, platform stack, known issues
3. Read `knowledge-base/accounts/<client>/*.md` — taxonomy reveals existing event names and naming conventions
4. Skim `knowledge-base/logs/<client>/` — past fixes reveal patterns (e.g., "form POST loses URL params")
**After completing tag management, write a session log:**
- Write to `knowledge-base/logs/<client>/YYYY-MM-DD-<description>.md`
- Include: date, container ID, tags affected (IDs), what was created/modified/deleted, cHTML snippets
- Update `knowledge-base/accounts/<client>/profile.md` if you discovered persistent facts
- See `AGENTS.md` for full format template
## MANDATORY: dataLayer First Workflow
1. **Design dataLayer push FIRST** (match client's tech stack: vanilla JS/React/Vue/PHP)

View File

@@ -1,49 +1,44 @@
---
description: GTM lifecycle automation - progressive audit, version comparison, and lookup app
description: Deprecated alias for DTM Agent skills — redirects to /dtm-audit, /dtm-lookup, /dtm-version, and /gtm-validator
---
# GTM Guardian
# GTM Guardian (Deprecated)
GTM tagging lifecycle automation: progressive audit (Phase 6) and event lookup app (Phase 7).
> **The `gtm-guardian` workflow and the `dintel-gtm-toolkit` Python scripts are deprecated.** All GTM lifecycle automation now lives in the **DTM Agent** skill set, which operates on live containers via the Google Tag Manager API instead of exported JSON.
## Triggers
- "GTM audit lifecycle", "container analysis"
- "GTM 유지보수", "버전 비교"
## Use these instead
## Quick Commands
| Old (gtm-guardian / dintel-gtm-toolkit) | New (DTM Agent skill) |
|---|---|
| `analyze_container.py` — container structure analysis | `/dtm-tags`, `/dtm-triggers`, `/dtm-variables`, `/dtm-lookup` |
| `find_unused.py` — unused element detection | `/dtm-lookup` (unused resource detection) |
| `diff_versions.py` — version comparison | `/dtm-version` (list, create, compare versions) |
| `validate_tags.py` — tag firing verification | `/gtm-validator` (live page QA via Chrome DevTools MCP) |
| Phase 6 — Progressive Audit | `/dtm-audit` (page audit, tracking coverage, gap analysis) + `/dtm-debug` (AD grading) |
| Phase 7 — Event Taxonomy Lookup App | `/dtm-taxonomy` (classify events, validate naming, export docs) + `/dtm-lookup` (universal search, dependency graph) |
| Container/account switching | `/dtm-set` |
| Status & health check | `/dtm-status` |
## Quick Reference
```bash
# Clone D.intelligence GTM Toolkit
git clone https://github.com/ourdigital/dintel-gtm-agent.git
# Active context
dtm status
dtm list accounts
dtm list containers
# Container analysis
python analyze_container.py GTM-XXXXXX.json --output report.md
# Inspect live container
dtm list tags
dtm list triggers
dtm list variables
# Version comparison
python diff_versions.py v1.json v2.json --output diff.md
# Unused element detection
python find_unused.py container.json --type all
# Versions
dtm list versions
dtm version live
```
## 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).
For full workflows, invoke the skill directly (e.g. `/dtm-audit`, `/gtm-validator`) — each skill bundles the right tool sequence, output formatting, and Notion reporting via the `notion-writer` skill.
## Notion Output
- Database: GTM Knowledge Base
- Properties: Project, Audit Date, Container ID, Status, Issues Count
- Reports in Korean; technical terms in English
Unchanged — DTM Agent skills still write to the **GTM Knowledge Base** database (properties: Project, Audit Date, Container ID, Status, Issues Count). Reports remain in Korean with English technical terms.

View File

@@ -1,53 +0,0 @@
---
description: Gtm Manager command
---
# GTM Manager
Full GTM management with dataLayer injection and tag generation.
## Triggers
- "GTM manager", "generate dataLayer tag", "dataLayer 태그 생성"
## Capabilities
1. **Full Audit** - Everything in gtm-audit plus more
2. **DataLayer Injector** - Generate custom HTML tags
3. **Event Mapping** - Map site actions to GA4 events
4. **Notion Export** - Save audit results to Notion
## Scripts
```bash
# Full GTM management
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-gtm-manager/code/scripts/gtm_manager.py \
--url https://example.com --full-audit
# Generate dataLayer tag
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-gtm-manager/code/scripts/gtm_manager.py \
--generate-tag purchase --output purchase_tag.html
# Export to Notion
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-gtm-manager/code/scripts/gtm_manager.py \
--url https://example.com --notion-export --database DATABASE_ID
```
## DataLayer Tag Templates
### Purchase Event
```html
<script>
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': '{{Order ID}}',
'value': {{Order Total}},
'currency': 'KRW',
'items': [...]
}
});
</script>
```
## Environment
- `NOTION_TOKEN` - For Notion export (optional)

View File

@@ -6,10 +6,34 @@ description: GTM QA - tag firing verification, trigger testing, naming conventio
Verify GTM implementations on live pages. Test triggers, validate dataLayer, check naming conventions.
## Pre-Flight
**BEFORE starting validation**, read the relevant gotcha files at:
`~/Project/dintel-gtm-agent/docs/log/gotcha/` (see `README.md` for index)
Priority reads per task:
- **Trigger validation** → `gotcha/triggers.md` + `gotcha/regex.md`
- **Post-API-change QA** → `gotcha/compilation.md`
- **Variable inspection** → `gotcha/variables.md`
## Triggers
- "validate tags", "QA GTM", "debug GTM"
- "naming conventions", "GTM best practice"
## MANDATORY: Knowledge Base Read/Write
**Before starting any validation or QA:**
1. Identify the target client from container ID or URL
2. Read `knowledge-base/accounts/<client>/profile.md` — naming conventions, known issues, platform stack
3. Read `knowledge-base/accounts/<client>/*.md` — taxonomy defines expected events; naming fix plans reveal known violations
4. Skim `knowledge-base/logs/<client>/` — past QA reveals recurring issues and known false positives
**After completing validation, write a session log:**
- Write to `knowledge-base/logs/<client>/YYYY-MM-DD-<description>.md`
- Include: date, container ID, pass/fail results, broken triggers (IDs), new naming violations
- Update `knowledge-base/accounts/<client>/profile.md` if you discovered persistent facts
- See `AGENTS.md` for full format template
## Validation Modes
### 1. Tag Firing Verification
@@ -35,6 +59,59 @@ GTM snippet placement, dataLayer init, consent mode, ES5 compliance, sGTM endpoi
### 7. Version Comparison
Compare tag counts and changes between container versions
## Gotchas (Hard-Won Lessons)
### GTM Has Two Validation Layers — API CRUD vs Preview/Publish
GTM API validates **schema only** during create/update calls (field names, types, required params). Regex patterns are stored as opaque strings and **never compiled** at edit time. Preview/Publish performs **full container compilation** — all regex is compiled by Google RE2, all variable references resolved, cross-resource dependencies checked. A resource can pass API validation but break Preview.
**Rule**: After batch API changes, always attempt Preview before declaring success.
### RE2 Does Not Support Lookaheads
GTM's regex engine (RE2) is linear-time and deliberately omits:
- Negative lookahead `(?!...)`
- Positive lookahead `(?=...)`
- Lookbehind `(?<=...)` / `(?<!...)`
- Backreferences `\1`
**Wrong** (breaks at Preview/Publish with "내부 오류"):
```
^(?!.*(jamie\.clinic|tel:)).*$
```
**Right** — use separate conditions with `negate` parameter:
```json
{
"type": "contains",
"parameter": [
{"type": "template", "key": "arg0", "value": "{{Click URL}}"},
{"type": "template", "key": "arg1", "value": "jamie.clinic"},
{"type": "boolean", "key": "negate", "value": "true"}
]
}
```
### The `negate` Parameter Is Inside the Condition Array
GTM API negation is NOT a top-level field on the condition object — it's a `{"type": "boolean", "key": "negate", "value": "true"}` entry inside the condition's `parameter` array, alongside `arg0` and `arg1`. Setting `negate: true` at the condition top level is **silently ignored** by the API.
### RegEx Table Variable Column Names
The `remm` (RegEx Table) variable type uses `key` and `value` as column names in its map entries, NOT `pattern` and `outputValue`. Also requires `{"type": "boolean", "key": "setDefaultValue", "value": "true"}` to enable the default value.
### Empty Template Fields Can Break Compilation
When creating `linkClick` triggers via API, setting `waitForTags`, `checkValidation`, `waitForTagsTimeout` via the `parameter` array sometimes produces empty top-level template stubs (`{"type": "template"}` with no value). These empty stubs can cause compilation issues. Either set them correctly at the top level or omit them and let GTM use defaults.
### Timer Triggers Cannot Use `customEventFilter`
Timer triggers fire on `gtm.timer` — they are NOT custom events. Use `filter` (not `customEventFilter`) to add page-level conditions to timer triggers. `customEventFilter` is only valid on `customEvent` type triggers.
### Colon `:` Is Not Allowed in Trigger Names
GTM trigger names cannot contain `:`. Use `-` or other separators instead.
## Key Rules
- Test on LIVE published version (not preview, unless debugging)
- Test on both desktop and mobile viewports

View File

@@ -0,0 +1,36 @@
---
description: Trims and sharpens Korean plastic-surgery/aesthetic marketing copy against cliché & compliance corpus
---
# Jamie Copy Trimmer
Trim and sharpen Korean plastic-surgery / aesthetic-medical marketing copy against an industry expression corpus, within 의료광고 심의 limits. Guidance-only skill (no scripts).
## Triggers
- "카피 다듬어", "카피 트리밍", "네이밍 검토", "슬로건 다듬어"
- "심의 안전하게", "copy trim", "make it catchier"
## Core Philosophy
- The corpus is a map for AVOIDING clichés, not a library to copy
- 의료광고 심의 is a gate, not a score -- one 🔴 risk expression fails the option outright
- Trim first, dazzle second
- Don't guess -- mark `[확인]`
## Workflow (5 steps)
1. **Diagnose** -- tag each phrase 🟢 effective / 🟡 cliché / 🔴 compliance risk / ⚪ flat / 🟦 brand asset
2. **Trim** -- remove all 🔴, replace 🟡, delete redundancy
3. **Elevate** -- 2-3 alternatives per element within 심의 limits, matched to channel tone
4. **Re-score** -- 5-axis rubric (감각/차별성/브랜드적합성/심의 PASS-FAIL/명료성)
5. **Recursive Improvement** -- propose feeding adopted/rejected expressions back into the corpus
## Output (Korean)
진단 → 트리밍 → 대안 → 재평가 → 추천안 → 준비 점검 사항 (risks/gaps consolidated at the end)
## References
- `references/corpus_compliance_risk.md` -- medical-ad risk expressions (most important)
- `references/corpus_cliche.md`, `corpus_effective.md`, `corpus_examples.md`
- `references/witty_within_limits.md`, `evaluation_rubric.md`, `recursive_protocol.md`
## Guardrails
- Compliance judgment here is guidance, not legal advice -- always recommend pre-publication 의료광고 자율심의
- A specific brand's tone guide (e.g. jamie-brand-audit) overrides this skill's taste defaults

View File

@@ -21,15 +21,15 @@ Jamie Clinic content **generation** toolkit.
```bash
# Check content compliance
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/40-jamie-brand-editor/code/scripts/compliance_checker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/40-jamie-brand-editor/code/scripts/compliance_checker.py \
--input draft.md
# With detailed report
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/40-jamie-brand-editor/code/scripts/compliance_checker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/40-jamie-brand-editor/code/scripts/compliance_checker.py \
--input draft.md --verbose --output report.json
# Batch check
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/40-jamie-brand-editor/code/scripts/compliance_checker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/40-jamie-brand-editor/code/scripts/compliance_checker.py \
--dir ./drafts --output compliance_report.json
```

View File

@@ -0,0 +1,29 @@
---
description: "Jamie Clinic KakaoTalk Kanana chatbot Q&A entry creation, review, and editing"
---
# Jamie FAQ Entry
Generate, review, and edit Q&A entries for Jamie Clinic's KakaoTalk Plus Channel Kanana 상담매니저.
## Directive
Read and follow `custom-skills/42-jamie-faq-entry/code/CLAUDE.md` for the full skill directive.
## Current Entries
Load `custom-skills/42-jamie-faq-entry/shared/current-entries.md` before creating or reviewing entries to check for duplicates and maintain the 1-intent-1-answer rule.
## Key Constraints
- Q: sentence form, customer tone, <= 100 chars
- A: 3-part structure (핵심→부가→CTA), <= 400 chars
- Medical ad compliance (의료법 제56조): scan every draft for banned expressions
- Brand tone: 격식체, 고객님/환자분, emoji max 1 at closing
## Workflow
1. Read the full directive from `code/CLAUDE.md`
2. Load `shared/current-entries.md` for duplicate check
3. Execute the requested task (create / review / batch)
4. Validate char limits and compliance before presenting output
$ARGUMENTS

View File

@@ -0,0 +1,84 @@
---
description: "Jamie Clinic journal/blog content editor for journal.jamie.clinic. Creates educational medical blog posts in Dr. Jung's voice with Korean medical ad compliance."
---
# Jamie Journal Editor
Journal/blog content editor for "정기호의 성형외과 진료실 이야기" (journal.jamie.clinic).
## Triggers
- "Jamie journal", "제이미 저널", "진료실 이야기"
- "journal blog", "Jamie blog post", "블로그 콘텐츠"
## Scripts
```bash
# Full validation
python /Users/ourdigital/Project/our-claude-skills/custom-skills/46-jamie-journal-editor/code/scripts/journal_validator.py --input draft.md
# Specific checks (structure, compliance, spelling, links)
python /Users/ourdigital/Project/our-claude-skills/custom-skills/46-jamie-journal-editor/code/scripts/journal_validator.py --input draft.md --check structure,compliance
# Verbose JSON report
python /Users/ourdigital/Project/our-claude-skills/custom-skills/46-jamie-journal-editor/code/scripts/journal_validator.py --input draft.md --verbose --output report.json
```
## Brand Voice (Dr. Jung)
| Trait | Expression |
|-------|-----------|
| Trustworthy Expert | "2008년부터 눈 성형을 전문적으로 시행하고 있고" |
| Warm Explainer | "나무 옮겨 심는 거랑 똑같다고 하거든요" |
| Honest Advisor | "100% 성공률을 가진 의사는 없어요" |
| Patient-Centered | "환자분들이 말씀하시는 졸린 눈은..." |
| Humble Confidence | "저희들이 시행하고 있습니다" |
- 90% 격식체 (~습니다/~입니다), 6% 서비스형 (~드립니다), 4% 부드러운 어미 (~거든요)
- Opening: "안녕하세요. 제이미성형외과 정기호 원장입니다."
- Honorifics: 환자분 (61%), 고객님 (22%), 여러분 (17%)
## Analogy Dictionary
| Topic | Metaphor |
|-------|----------|
| Fat graft survival | "나무 옮겨 심는 거랑 똑같다고 하거든요" |
| 3-point fixation | "인형극 실 비유 — 실이 두 줄인 거랑 세 줄 네 줄인 거랑은 움직임의 자연스러움이 차이" |
| Revision surgery | "깨끗한 도화지에 그림을 그리면 화가의 실력이 100% 발휘가 될 텐데, 재수술은 낙서가 있는 도화지에 덧칠" |
| Endotine | "똑딱이 단추와 같은 나사" |
## Content Structure (5-Step Body)
1. **Problem Statement** (Empathy) — "~로 고민하시는 분들이 많습니다"
2. **Cause Explanation** (Education) — Medical terminology with Korean(漢字, English) format
3. **Solution** (Jamie's Method) — Specific technique details
4. **Advantages** (Differentiation) — Recovery, scars, pain, AS period
5. **Expected Results** (Vision) — Realistic outcomes
## Procedure Copy Reference
| Category | Procedure | Key Expression |
|----------|-----------|---------------|
| Eye | Quick Burial | "티 안 나게 예뻐지는", "휴가를 내지 않고도" |
| Eye | Hybrid Double Eyelid | "절개법과 매몰법의 장점만을 모은" |
| Eye | Ptosis Correction | "졸리고 답답한 눈매를 또렷하고 시원하게" |
| Forehead | Endoscopic Forehead Lift | "3점 고정", "흡수성 봉합사 주문 제작" |
| Anti-aging | SMAS Lifting | "표정 근막층부터 근본적으로" |
| Anti-aging | Fat Grafting | "반영구적 유지", "나무 옮겨 심는 것처럼" |
## Compliance Rules
- ❌ No "100% 성공" → "대부분의 경우 좋은 결과를 기대할 수 있습니다"
- ❌ No "부작용 없음" → "부작용은 극히 드뭅니다"
- ❌ No guarantee or competitor comparison language
- ❌ No "전문 병원", "특화" → "중점진료", "풍부한 경험"
- ✅ Required disclaimer at bottom:
"개인에 따라 부작용(출혈, 감염, 염증 등)이 있을 수 있으니 사전에 의료진과 상담 후 결정하시기 바랍니다."
## Workflow
1. Receive topic + target audience
2. Draft article using brand voice + analogy dictionary
3. Apply 5-step content structure
4. Run validator: `python .../journal_validator.py --input draft.md`
5. Fix flagged issues
6. Submit to `/jamie-brand-audit` for final review

View File

@@ -0,0 +1,40 @@
---
description: Jamie Clinic multi-channel marketing content editor with compliance checking
---
# Jamie Marketing Editor
Marketing content across all Jamie Clinic digital channels: website, blog, SNS, ads, email, KakaoTalk.
## Triggers
- "Jamie marketing", "제이미 마케팅"
- "광고 카피", "SNS 콘텐츠", "ad copy"
## Scripts
```bash
python code/scripts/compliance_checker.py --input draft.md
python code/scripts/compliance_checker.py --input draft.md --verbose --output report.json
```
## Channel Tone Guide
- **Website**: Professional, educational, trust-building
- **Blog**: Educational, accessible, SEO-optimized
- **Instagram**: Sensory, concise, trendy + hashtags
- **YouTube**: Professional, step-by-step with visuals
- **KakaoTalk**: Friendly, helpful, action-oriented
- **Ads**: Factual, compliant, no superlatives
## Brand Pillars
- Safety, Naturalness, Transparency, Quality Assurance
- Key differentiators: director's personal care, 5-year AS, revision expertise
## Compliance Rules (Korean Medical Ad Law)
- No exaggerated claims or guarantee language
- No patient testimonials
- No before/after comparisons without disclaimers
- No competitor comparisons
- Required side-effect and individual variation disclosures
## Workflow
1. Define channel + audience -> 2. Generate content -> 3. Run compliance checker -> 4. Submit to jamie-brand-audit

View File

@@ -67,5 +67,5 @@ Examples:
3. Install pre-commit hooks: `pre-commit install` (optional)
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/91-multi-agent-guide/README.md`
Related commands: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/91-multi-agent-guide/commands/`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/91-multi-agent-guide/README.md`
Related commands: `/Users/ourdigital/Project/our-claude-skills/custom-skills/91-multi-agent-guide/commands/`

View File

@@ -59,4 +59,4 @@ notebooklm ask "Compare" -s source1 -s source2
| Auth error | `notebooklm login` |
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/50-notebooklm-agent/code/CLAUDE.md`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/50-notebooklm-agent/code/CLAUDE.md`

View File

@@ -54,4 +54,4 @@ notebooklm artifact delete <id>
**Ask first:** `delete`, `rename`
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/51-notebooklm-automation/code/CLAUDE.md`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/51-notebooklm-automation/code/CLAUDE.md`

View File

@@ -63,4 +63,4 @@ Task(
**Ask first:** `source add-research`, `research wait --import-all`
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/53-notebooklm-research/code/CLAUDE.md`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/53-notebooklm-research/code/CLAUDE.md`

View File

@@ -70,4 +70,4 @@ notebooklm download mind-map ./mindmap.json
**Ask first:** `generate *`, `download *`
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/52-notebooklm-studio/code/CLAUDE.md`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/52-notebooklm-studio/code/CLAUDE.md`

View File

@@ -20,15 +20,15 @@ Notion workspace management agent for organizing, restructuring, and maintaining
```bash
# Analyze database schema
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/01-notion-organizer/code/scripts/schema_migrator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/01-notion-organizer/code/scripts/schema_migrator.py \
--source-db DATABASE_ID --analyze
# Migrate with mapping
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/01-notion-organizer/code/scripts/schema_migrator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/01-notion-organizer/code/scripts/schema_migrator.py \
--source-db SOURCE_ID --target-db TARGET_ID --mapping mapping.json
# Async bulk operations
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/01-notion-organizer/code/scripts/async_organizer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/01-notion-organizer/code/scripts/async_organizer.py \
--database DATABASE_ID --operation archive --filter "Status=Done"
```

View File

@@ -24,7 +24,7 @@ Push markdown content to Notion pages or databases via the Notion API.
## Scripts
```bash
cd ~/Projects/our-claude-skills/custom-skills/32-notion-writer/code/scripts
cd ~/Project/our-claude-skills/custom-skills/32-notion-writer/code/scripts
# Test connection
python notion_writer.py --test
@@ -60,4 +60,4 @@ Headings, bulleted/numbered lists, to-do items, quotes, code blocks (with langua
The script automatically batches large content.
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/32-notion-writer/code/CLAUDE.md`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/32-notion-writer/code/CLAUDE.md`

View File

@@ -66,4 +66,4 @@ Reference skill for OurDigital brand standards, writing style, and visual identi
5. **Visual Consistency**: Uses approved color palette?
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/01-ourdigital-brand-guide/desktop/SKILL.md`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/01-ourdigital-brand-guide/desktop/SKILL.md`

View File

@@ -20,15 +20,15 @@ Visual storytelling toolkit for blog featured images.
```bash
# Generate image prompt
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/30-ourdigital-designer/code/scripts/generate_prompt.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/30-ourdigital-designer/code/scripts/generate_prompt.py \
--topic "AI identity" --mood "contemplative"
# From essay text
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/30-ourdigital-designer/code/scripts/generate_prompt.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/30-ourdigital-designer/code/scripts/generate_prompt.py \
--input essay.txt --auto-extract
# Calibrate mood
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/30-ourdigital-designer/code/scripts/mood_calibrator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/30-ourdigital-designer/code/scripts/mood_calibrator.py \
--input "essay excerpt" --style "minimalist"
```

View File

@@ -20,13 +20,13 @@ Notion-to-presentation workflow for branded slides.
```bash
# Full automated workflow
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/run_workflow.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/run_workflow.py \
--notion-url [NOTION_URL] --output presentation.pptx
# Step-by-step
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/extract_notion.py [URL] > research.json
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/synthesize_content.py research.json > synthesis.json
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/apply_brand.py synthesis.json --output presentation.pptx
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/extract_notion.py [URL] > research.json
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/synthesize_content.py research.json > synthesis.json
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-ourdigital-presentation/code/scripts/apply_brand.py synthesis.json --output presentation.pptx
```
## Pipeline

View File

@@ -20,17 +20,17 @@ Research-to-publication workflow for OurDigital blogs.
```bash
# Export to Ulysses
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-ourdigital-research/code/scripts/export_to_ulysses.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-ourdigital-research/code/scripts/export_to_ulysses.py \
--input research.md --group "Blog Drafts"
# With tags
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-ourdigital-research/code/scripts/export_to_ulysses.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-ourdigital-research/code/scripts/export_to_ulysses.py \
--input research.md \
--group "Blog Drafts" \
--tags "AI,research,draft"
# From Notion export
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-ourdigital-research/code/scripts/export_to_ulysses.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-ourdigital-research/code/scripts/export_to_ulysses.py \
--notion-export notion_export.zip \
--group "From Notion"
```

View File

@@ -225,9 +225,9 @@ firecrawl_crawl:
## Related Sub-commands
Individual stages available at: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/90-reference-curator/commands/`
Individual stages available at: `/Users/ourdigital/Project/our-claude-skills/custom-skills/90-reference-curator/commands/`
- `/reference-discovery`, `/web-crawler`, `/content-repository`
- `/content-distiller`, `/quality-reviewer`, `/markdown-exporter`
## Source
Full details: `/Users/ourdigital/Projects/our-claude-skills/custom-skills/90-reference-curator/README.md`
Full details: `/Users/ourdigital/Project/our-claude-skills/custom-skills/90-reference-curator/README.md`

View File

@@ -22,35 +22,35 @@ Track brand visibility in AI-generated search answers with citation analysis and
```bash
# AI visibility overview
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
--target example.com --json
# With competitor comparison
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
--target example.com --competitor comp1.com --competitor comp2.com --json
# Historical trend (impressions/mentions)
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
--target example.com --history --json
# Share of voice analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_visibility_tracker.py \
--target example.com --sov --json
# AI citation analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
--target example.com --json
# Cited domains analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
--target example.com --cited-domains --json
# Cited pages analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
--target example.com --cited-pages --json
# AI response content analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/27-seo-ai-visibility/code/scripts/ai_citation_analyzer.py \
--target example.com --responses --json
```

View File

@@ -23,23 +23,23 @@ Competitor profiling, benchmarking, and threat scoring for comprehensive SEO com
```bash
# Auto-discover and profile competitors
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitor_profiler.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitor_profiler.py \
--target https://example.com --json
# Specify competitors manually
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitor_profiler.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitor_profiler.py \
--target https://example.com --competitor https://comp1.com --competitor https://comp2.com --json
# Include Korean market analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitor_profiler.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitor_profiler.py \
--target https://example.com --korean-market --json
# 30-day competitive monitoring
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitive_monitor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitive_monitor.py \
--target https://example.com --period 30 --json
# Traffic trend comparison (90 days)
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitive_monitor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/31-seo-competitor-intel/code/scripts/competitive_monitor.py \
--target https://example.com --scope traffic --period 90 --json
```

View File

@@ -23,27 +23,27 @@ Content inventory, performance scoring, decay detection, topic gap analysis, clu
```bash
# Full content audit
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_auditor.py \
--url https://example.com --json
# Detect decaying content
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_auditor.py \
--url https://example.com --decay --json
# Filter by content type
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_auditor.py \
--url https://example.com --type blog --json
# Content gap analysis with topic clusters
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_gap_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_gap_analyzer.py \
--target https://example.com --competitor https://comp1.com --clusters --json
# Generate content brief for keyword
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_brief_generator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_brief_generator.py \
--keyword "치과 임플란트 비용" --url https://example.com --json
# Brief with competitor analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_brief_generator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/23-seo-content-strategy/code/scripts/content_brief_generator.py \
--keyword "dental implant cost" --url https://example.com --competitors 5 --json
```

View File

@@ -22,27 +22,27 @@ Server access log analysis, bot profiling, and crawl budget waste identification
```bash
# Parse Nginx access log
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/log_parser.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/log_parser.py \
--log-file /var/log/nginx/access.log --json
# Parse Apache log, filter by Googlebot
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/log_parser.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/log_parser.py \
--log-file /var/log/apache2/access.log --format apache --bot googlebot --json
# Parse gzipped log in streaming mode
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/log_parser.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/log_parser.py \
--log-file access.log.gz --streaming --json
# Full crawl budget analysis with sitemap comparison
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/crawl_budget_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/crawl_budget_analyzer.py \
--log-file access.log --sitemap https://example.com/sitemap.xml --json
# Waste identification only
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/crawl_budget_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/crawl_budget_analyzer.py \
--log-file access.log --scope waste --json
# Orphan page detection
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/crawl_budget_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/32-seo-crawl-budget/code/scripts/crawl_budget_analyzer.py \
--log-file access.log --sitemap https://example.com/sitemap.xml --scope orphans --json
```

View File

@@ -23,27 +23,27 @@ Product page SEO audit, product schema validation, category taxonomy analysis, a
```bash
# Full e-commerce SEO audit
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
--url https://example.com --json
# Product page audit only
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
--url https://example.com --scope products --json
# Category taxonomy analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
--url https://example.com --scope categories --json
# Korean marketplace presence check
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/ecommerce_auditor.py \
--url https://example.com --korean-marketplaces --json
# Validate product schema on single page
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/product_schema_checker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/product_schema_checker.py \
--url https://example.com/product/123 --json
# Batch validate from sitemap (sample 50 pages)
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/product_schema_checker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/24-seo-ecommerce/code/scripts/product_schema_checker.py \
--sitemap https://example.com/product-sitemap.xml --sample 50 --json
```

View File

@@ -20,11 +20,11 @@ Keyword strategy and content architecture for gateway pages.
```bash
# Analyze keyword
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/29-seo-gateway-architect/code/scripts/keyword_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/29-seo-gateway-architect/code/scripts/keyword_analyzer.py \
--topic "눈 성형"
# With location targeting
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/29-seo-gateway-architect/code/scripts/keyword_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/29-seo-gateway-architect/code/scripts/keyword_analyzer.py \
--topic "눈 성형" --market "강남" --output strategy.json
```

View File

@@ -20,10 +20,10 @@ Generate SEO-optimized gateway pages from templates.
```bash
# Generate with sample data
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/30-seo-gateway-builder/code/scripts/generate_pages.py
python /Users/ourdigital/Project/our-claude-skills/custom-skills/30-seo-gateway-builder/code/scripts/generate_pages.py
# Custom configuration
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/30-seo-gateway-builder/code/scripts/generate_pages.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/30-seo-gateway-builder/code/scripts/generate_pages.py \
--config config/services.json \
--locations config/locations.json \
--output ./pages

View File

@@ -20,15 +20,15 @@ Google Search Console data retrieval and analysis.
```bash
# Get search performance
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/15-seo-search-console/code/scripts/gsc_client.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/15-seo-search-console/code/scripts/gsc_client.py \
--site https://example.com --days 28
# Query analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/15-seo-search-console/code/scripts/gsc_client.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/15-seo-search-console/code/scripts/gsc_client.py \
--site https://example.com --report queries --limit 100
# Page performance
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/15-seo-search-console/code/scripts/gsc_client.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/15-seo-search-console/code/scripts/gsc_client.py \
--site https://example.com --report pages --output pages_report.json
```

View File

@@ -22,31 +22,31 @@ Multi-language and multi-region SEO audit with hreflang validation and content p
```bash
# Hreflang validation
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/26-seo-international/code/scripts/hreflang_validator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/26-seo-international/code/scripts/hreflang_validator.py \
--url https://example.com --json
# With sitemap-based discovery
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/26-seo-international/code/scripts/hreflang_validator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/26-seo-international/code/scripts/hreflang_validator.py \
--url https://example.com --sitemap https://example.com/sitemap.xml --json
# Check specific pages from file
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/26-seo-international/code/scripts/hreflang_validator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/26-seo-international/code/scripts/hreflang_validator.py \
--urls-file pages.txt --json
# Full international audit
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
--url https://example.com --json
# URL structure analysis only
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
--url https://example.com --scope structure --json
# Content parity check only
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
--url https://example.com --scope parity --json
# Korean expansion focus
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/26-seo-international/code/scripts/international_auditor.py \
--url https://example.com --korean-expansion --json
```

View File

@@ -22,23 +22,23 @@ Keyword expansion, intent classification, clustering, and competitor gap analysi
```bash
# Basic keyword research
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_researcher.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_researcher.py \
--keyword "치과 임플란트" --country kr --json
# Korean market with suffix expansion
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_researcher.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_researcher.py \
--keyword "치과 임플란트" --country kr --korean-suffixes --json
# Volume comparison Korea vs global
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_researcher.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_researcher.py \
--keyword "dental implant" --country kr --compare-global --json
# Keyword gap vs competitor
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_gap_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_gap_analyzer.py \
--target https://example.com --competitor https://competitor.com --json
# Multiple competitors with minimum volume filter
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_gap_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/19-seo-keyword-strategy/code/scripts/keyword_gap_analyzer.py \
--target https://example.com --competitor https://comp1.com \
--competitor https://comp2.com --min-volume 100 --json
```

View File

@@ -23,27 +23,27 @@ Entity SEO analysis for Knowledge Panel presence, People Also Ask monitoring, an
```bash
# Knowledge Graph analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/knowledge_graph_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/knowledge_graph_analyzer.py \
--entity "Samsung Electronics" --json
# Korean entity check
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/knowledge_graph_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/knowledge_graph_analyzer.py \
--entity "삼성전자" --language ko --json
# Include Wikipedia/Wikidata check
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/knowledge_graph_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/knowledge_graph_analyzer.py \
--entity "Samsung" --wiki --json
# Full entity SEO audit
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/entity_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/entity_auditor.py \
--url https://example.com --entity "Brand Name" --json
# PAA monitoring
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/entity_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/entity_auditor.py \
--url https://example.com --entity "Brand Name" --paa --json
# FAQ rich result tracking
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/entity_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/28-seo-knowledge-graph/code/scripts/entity_auditor.py \
--url https://example.com --entity "Brand Name" --faq --json
```

View File

@@ -22,35 +22,35 @@ Unified KPI aggregation across all SEO dimensions with health scores, baselines,
```bash
# Aggregate KPIs
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
--url https://example.com --json
# Set baseline
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
--url https://example.com --set-baseline --json
# Compare against baseline
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
--url https://example.com --baseline baseline.json --json
# With ROI estimation
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/kpi_aggregator.py \
--url https://example.com --roi --json
# Monthly performance report
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
--url https://example.com --period monthly --json
# Quarterly report
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
--url https://example.com --period quarterly --json
# Custom date range
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
--url https://example.com --from 2025-01-01 --to 2025-03-31 --json
# Executive summary only
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/25-seo-kpi-framework/code/scripts/performance_reporter.py \
--url https://example.com --period monthly --executive --json
```

View File

@@ -23,27 +23,27 @@ Backlink profile analysis, toxic link detection, competitor link gap identificat
```bash
# Full backlink audit
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
--url https://example.com --json
# Check link velocity
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
--url https://example.com --velocity --json
# Find broken backlinks for recovery
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
--url https://example.com --broken --json
# Korean platform link analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/backlink_auditor.py \
--url https://example.com --korean-platforms --json
# Link gap vs competitor
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/link_gap_finder.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/link_gap_finder.py \
--target https://example.com --competitor https://comp1.com --json
# Multiple competitors with minimum DR filter
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/link_gap_finder.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/22-seo-link-building/code/scripts/link_gap_finder.py \
--target https://example.com --competitor https://comp1.com \
--competitor https://comp2.com --min-dr 30 --json
```

View File

@@ -23,27 +23,27 @@ Pre-migration risk assessment, redirect mapping, and post-migration traffic/inde
```bash
# Domain move planning
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
--domain https://example.com --type domain-move --new-domain https://new-example.com --json
# Platform migration (e.g., WordPress to headless)
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
--domain https://example.com --type platform --json
# URL restructuring
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
--domain https://example.com --type url-restructure --json
# HTTPS migration
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_planner.py \
--domain http://example.com --type https --json
# Post-launch traffic comparison
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_monitor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_monitor.py \
--domain https://new-example.com --migration-date 2025-01-15 --baseline baseline.json --json
# Quick redirect health check
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_monitor.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/33-seo-migration-planner/code/scripts/migration_monitor.py \
--domain https://new-example.com --migration-date 2025-01-15 --json
```

View File

@@ -20,11 +20,11 @@ On-page SEO analysis for meta tags, headings, content, and links.
```bash
# Full page analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/13-seo-on-page-audit/code/scripts/page_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/13-seo-on-page-audit/code/scripts/page_analyzer.py \
--url https://example.com/page
# Multiple pages
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/13-seo-on-page-audit/code/scripts/page_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/13-seo-on-page-audit/code/scripts/page_analyzer.py \
--urls urls.txt --output report.json
```

View File

@@ -22,27 +22,27 @@ Monitor keyword rankings, detect position changes with threshold alerts, and cal
```bash
# Get current positions
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
--target https://example.com --json
# With change threshold alerts (flag moves of +-5 or more)
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
--target https://example.com --threshold 5 --json
# Filter by brand segment
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
--target https://example.com --segment brand --json
# Compare with competitor
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/position_tracker.py \
--target https://example.com --competitor https://comp1.com --json
# 30-day ranking report
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/ranking_reporter.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/ranking_reporter.py \
--target https://example.com --period 30 --json
# Quarterly report with competitor comparison
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/ranking_reporter.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/21-seo-position-tracking/code/scripts/ranking_reporter.py \
--target https://example.com --competitor https://comp1.com --period 90 --json
```

View File

@@ -22,27 +22,27 @@ Aggregate all SEO skill outputs into executive reports and interactive HTML dash
```bash
# Aggregate all skill outputs for a domain
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/report_aggregator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/report_aggregator.py \
--domain https://example.com --json
# Aggregate with date range filter
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/report_aggregator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/report_aggregator.py \
--domain https://example.com --from 2025-01-01 --to 2025-03-31 --json
# Generate HTML dashboard
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/dashboard_generator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/dashboard_generator.py \
--report aggregated_report.json --output dashboard.html
# C-level executive summary (Korean)
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/executive_report.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/executive_report.py \
--report aggregated_report.json --audience c-level --output report.md
# Marketing team report
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/executive_report.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/executive_report.py \
--report aggregated_report.json --audience marketing --output report.md
# Technical team report
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/executive_report.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/34-seo-reporting-dashboard/code/scripts/executive_report.py \
--report aggregated_report.json --audience technical --output report.md
```

View File

@@ -19,11 +19,11 @@ Generate JSON-LD structured data markup from templates.
```bash
# Generate from template
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/17-seo-schema-generator/code/scripts/schema_generator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/17-seo-schema-generator/code/scripts/schema_generator.py \
--type LocalBusiness --output schema.json
# With custom data
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/17-seo-schema-generator/code/scripts/schema_generator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/17-seo-schema-generator/code/scripts/schema_generator.py \
--type Article \
--data '{"headline": "My Article", "author": "John Doe"}' \
--output article-schema.json

View File

@@ -20,15 +20,15 @@ JSON-LD structured data validation and analysis.
```bash
# Validate page schema
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/16-seo-schema-validator/code/scripts/schema_validator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/16-seo-schema-validator/code/scripts/schema_validator.py \
--url https://example.com
# Validate local file
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/16-seo-schema-validator/code/scripts/schema_validator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/16-seo-schema-validator/code/scripts/schema_validator.py \
--file schema.json
# Batch validation
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/16-seo-schema-validator/code/scripts/schema_validator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/16-seo-schema-validator/code/scripts/schema_validator.py \
--urls urls.txt --output validation_report.json
```

View File

@@ -22,19 +22,19 @@ Detect SERP features, map competitor positions, and score feature opportunities
```bash
# Google SERP analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/serp_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/serp_analyzer.py \
--keyword "치과 임플란트" --country kr --json
# Multiple keywords from file
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/serp_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/serp_analyzer.py \
--keywords-file keywords.txt --country kr --json
# Naver SERP analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/naver_serp_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/naver_serp_analyzer.py \
--keyword "치과 임플란트" --json
# Naver multiple keywords
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/naver_serp_analyzer.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/20-seo-serp-analysis/code/scripts/naver_serp_analyzer.py \
--keywords-file keywords.txt --json
```

View File

@@ -19,15 +19,15 @@ Technical SEO audit for robots.txt and sitemap validation.
```bash
# Check robots.txt
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/12-seo-technical-audit/code/scripts/robots_checker.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/12-seo-technical-audit/code/scripts/robots_checker.py \
--url https://example.com
# Validate sitemap
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/12-seo-technical-audit/code/scripts/sitemap_validator.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/12-seo-technical-audit/code/scripts/sitemap_validator.py \
--url https://example.com/sitemap.xml
# Crawl sitemap URLs
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/12-seo-technical-audit/code/scripts/sitemap_crawler.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/12-seo-technical-audit/code/scripts/sitemap_crawler.py \
--sitemap https://example.com/sitemap.xml --output report.json
```

View File

@@ -20,15 +20,15 @@ Google PageSpeed Insights and Core Web Vitals analysis.
```bash
# Analyze single URL
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/14-seo-core-web-vitals/code/scripts/pagespeed_client.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/14-seo-core-web-vitals/code/scripts/pagespeed_client.py \
--url https://example.com
# Mobile and desktop
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/14-seo-core-web-vitals/code/scripts/pagespeed_client.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/14-seo-core-web-vitals/code/scripts/pagespeed_client.py \
--url https://example.com --strategy both
# Batch analysis
python /Users/ourdigital/Projects/our-claude-skills/custom-skills/14-seo-core-web-vitals/code/scripts/pagespeed_client.py \
python /Users/ourdigital/Project/our-claude-skills/custom-skills/14-seo-core-web-vitals/code/scripts/pagespeed_client.py \
--urls urls.txt --output vitals_report.json
```

View File

@@ -1,37 +0,0 @@
{
"permissions": {
"allow": [
"Bash(find:*)",
"Bash(git init:*)",
"Bash(unzip:*)",
"Bash(git add:*)",
"Skill(notion-organizer)",
"Skill(ourdigital-seo-audit)",
"WebFetch(domain:les.josunhotel.com)",
"WebFetch(domain:josunhotel.com)",
"WebFetch(domain:pagespeed.web.dev)",
"Bash(ln:*)",
"Skill(skill-creator)",
"Bash(python:*)",
"Bash(mv:*)",
"Bash(ls:*)",
"Bash(cat:*)",
"Bash(git commit:*)",
"Bash(git reset:*)",
"Bash(git push:*)",
"Bash(chmod:*)",
"Bash(python3:*)",
"Bash(git fetch:*)",
"Bash(mysql:*)",
"Bash(brew services:*)",
"Bash(source ~/.envrc)",
"mcp__plugin_Notion_notion__notion-fetch",
"mcp__plugin_Notion_notion__notion-update-page",
"Skill(settings-audit)",
"mcp__plugin_figma_figma__whoami",
"mcp__plugin_figma_figma__get_metadata",
"mcp__plugin_figma_figma__get_screenshot",
"WebSearch"
]
}
}

5
.gemini/settings.json Normal file
View File

@@ -0,0 +1,5 @@
{
"general": {
"previewFeatures": true
}
}

26
.github/workflows/verify-skills.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Verify Skills
# Runs the skill load-verifier on every push and pull request.
# Fails the build if any skill would not load (invalid frontmatter, bad name,
# name collision, orphan dir, or invalid plugin.json). Read-only check.
on:
push:
pull_request:
jobs:
verify-skills:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install pyyaml
- name: Verify all skills load correctly
run: python3 scripts/verify_skills.py

4
.gitignore vendored
View File

@@ -99,3 +99,7 @@ build/
# Temporary files
output/
keyword_analysis_*.json
# graphify: keep graph.json/html/report, drop regenerable cache + dated backups
graphify-out/cache/
graphify-out/????-??-??/

4
.graphifyignore Normal file
View File

@@ -0,0 +1,4 @@
# graphify scope control — exclude non-standard virtualenvs that the default
# rules (.venv, venv) don't catch, so pip-package source doesn't pollute the graph.
.venv-ourdigital/
.venv-*/

View File

@@ -127,7 +127,7 @@ Task 2: general-purpose - "Implement the planned skill" # Needs Task 1 result
- All SEO skills integrate with Ahrefs MCP tools and output to the Notion SEO Audit Log database
- Slash commands available: `/seo-keyword-strategy`, `/seo-serp-analysis`, `/seo-position-tracking`, `/seo-link-building`, `/seo-content-strategy`, `/seo-ecommerce`, `/seo-kpi-framework`, `/seo-international`, `/seo-ai-visibility`, `/seo-knowledge-graph`, `/seo-competitor-intel`, `/seo-crawl-budget`, `/seo-migration-planner`, `/seo-reporting-dashboard`
### GTM Skills (60-69)
### GTM Skills (60-62)
Three specialized skills with clear handoff workflow: **audit → edit → validate**
@@ -148,6 +148,9 @@ Three specialized skills with clear handoff workflow: **audit → edit → valid
- Brand compliance is critical - check `references/` for guidelines
- Korean language content - verify encoding in scripts
- Instagram/YouTube skills may need API credentials
- **42-jamie-faq-entry**: KakaoTalk Kanana chatbot Q&A entries (medical ad compliance, character limits)
- **46-jamie-journal-editor**: Journal/blog articles for journal.jamie.clinic in Dr. Jung's voice
- **47-jamie-marketing-editor**: Multi-channel marketing (ads, SNS, email) with compliance checker script
### Notion Skills (31-39)
@@ -182,18 +185,27 @@ Task(
**Parallel workflows:** Set `NOTEBOOKLM_HOME` per agent to avoid context conflicts.
### Mac Optimizer (92)
### System & Utility Tools (80-82)
- **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/`
- **80-claude-settings-optimizer**: Claude settings optimization and token efficiency audit
- **81-mac-optimizer**: macOS system health toolkit (Claude Code only)
- 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/81-mac-optimizer/scripts/`
- Reference docs in `custom-skills/81-mac-optimizer/references/`
- **82-our-gdrive-organizer**: Organize a Google Drive folder under OurDigital conventions
- Refreshes root `README.md` index (Snapshot + Structure section between AUTO-STRUCTURE markers; preserves manual Topics/Notes)
- Ensures per-subfolder `README.md` meta files with auto-indexed Contents block
- Proposes filename renames (`D.intelligence``OurDigital`) and moves (screenshots, temp downloads → tidy subfolders)
- Skips sensitive folders by default: `04_Case Studies/`, `99_Project Archive/`, `*Archive$`, `진단*`
- Pure Python stdlib CLI: `~/.local/bin/our-gdrive-organize [TARGET] [--apply] [--scope full|index|subreadmes|rename|move]`
- Slash command: `/organize`
### Reference Curator Skills (90-99)
### Reference Curator Skills (90-91) & Design Templates (92)
- **92-tui-design-template**: TUI wizard interface design (Norton Commander / Gopher style, Rich) — moved from slot 82 to make room for `our-gdrive-organizer`
- Use **reference-curator-pipeline** for full automated curation workflows
- Runs as background task, coordinates all 6 skills in sequence
- Handles QA loops automatically (max 3 refactor, 2 deep_research iterations)
@@ -224,7 +236,7 @@ For long-running tasks, use `run_in_background: true`:
```
# Good candidates for background execution:
- Full skill audit across all 62 skills
- Full skill audit across all 64 skills
- Running Python tests on multiple skills
- Generating comprehensive documentation

177
CLAUDE.md
View File

@@ -7,19 +7,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
**GitHub**: https://github.com/ourdigital/our-claude-skills
This is a Claude Skills collection repository containing:
- **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
- **custom-skills/**: 65 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
- **official-skills/**: Notion integration skills (3rd party)
- **reference/**: Skill format requirements documentation
## Custom Skills Summary
### Settings & Meta Tools (00)
| # | Skill | Purpose | Trigger |
|---|-------|---------|---------|
| 00 | our-settings-audit | Claude settings optimization & token audit | "settings audit", "exceed response limit", "MCP error" |
### OurDigital Core (01-10)
| # | Skill | Purpose | Trigger |
@@ -45,7 +39,7 @@ This is a Claude Skills collection repository containing:
| 14 | seo-core-web-vitals | LCP, CLS, FID, INP metrics | "Core Web Vitals", "page speed" |
| 15 | seo-search-console | GSC data analysis | "Search Console", "rankings" |
| 16 | seo-schema-validator | Structured data validation | "validate schema", "JSON-LD" |
| 17 | seo-schema-generator | Schema markup creation | "generate schema", "create JSON-LD" |
| 17 | seo-schema-generator | JSON-LD generation — Mode 1 from existing site, Mode 2 from collected sources → claims register → drafts → validate (16) | "generate schema", "create JSON-LD", "source-to-schema", "schema from site" |
| 18 | seo-local-audit | NAP, GBP, citations | "local SEO", "Google Business Profile" |
| 19 | seo-keyword-strategy | Keyword expansion, intent, clustering, gaps | "keyword research", "keyword strategy" |
| 20 | seo-serp-analysis | Google/Naver SERP features, competitor positions | "SERP analysis", "SERP features" |
@@ -64,14 +58,6 @@ This is a Claude Skills collection repository containing:
| 33 | seo-migration-planner | Site migration planning, redirect mapping | "site migration", "domain move", "사이트 이전" |
| 34 | seo-reporting-dashboard | Executive reports, HTML dashboards, aggregation | "SEO report", "SEO dashboard", "보고서" |
### GTM/GA Tools (60-69)
| # | Skill | Purpose | Trigger |
|---|-------|---------|---------|
| 60 | gtm-audit | GTM audit — page scan, site audit, gap analysis, tag design | "GTM audit", "scan page", "tracking coverage", "gap analysis" |
| 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-validator | GTM QA — tag firing, trigger testing, naming conventions, cross-platform | "validate tags", "QA GTM", "debug GTM", "naming conventions" |
### Notion Tools (31-39)
| # | Skill | Purpose | Trigger |
@@ -85,9 +71,13 @@ This is a Claude Skills collection repository containing:
|---|-------|---------|---------|
| 40 | jamie-brand-editor | Branded content generation | "write Jamie blog", "Jamie content" |
| 41 | jamie-brand-audit | Content review/compliance | "review content", "brand audit" |
| 42 | jamie-faq-entry | KakaoTalk Kanana chatbot Q&A entries | "카나나 답변", "Kanana Q&A", "카카오 상담 답변" |
| 43 | jamie-youtube-manager | YouTube SEO audit & management | "YouTube SEO", "YT optimization" |
| 44 | jamie-youtube-subtitle-checker | YouTube subtitle validation | "check subtitles", "subtitle QA" |
| 45 | jamie-instagram-manager | Instagram account management | "Instagram management", "IG strategy" |
| 46 | jamie-journal-editor | Journal/blog content for journal.jamie.clinic | "Jamie journal", "제이미 저널", "진료실 이야기" |
| 47 | jamie-marketing-editor | Multi-channel marketing content & ad copy | "Jamie marketing", "제이미 마케팅", "광고 카피" |
| 48 | jamie-copy-trimmer | Trim/sharpen Korean aesthetic-medical copy against cliché & compliance corpus | "카피 다듬어", "카피 트리밍", "심의 안전하게", "copy trim" |
### NotebookLM Tools (50-59)
@@ -100,7 +90,15 @@ This is a Claude Skills collection repository containing:
**Prerequisites:** `pip install notebooklm-py && playwright install chromium && notebooklm login`
### D.intelligence Agent Corps (70-88)
### GTM/GA Tools (60-69)
| # | Skill | Purpose | Trigger |
|---|-------|---------|---------|
| 60 | gtm-audit | GTM audit — page scan, site audit, gap analysis, tag design | "GTM audit", "scan page", "tracking coverage", "gap analysis" |
| 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-validator | GTM QA — tag firing, trigger testing, naming conventions, cross-platform | "validate tags", "QA GTM", "debug GTM", "naming conventions" |
### D.intelligence Agent Corps (70-79)
| # | Skill | Purpose | Autonomy | Trigger |
|---|-------|---------|----------|---------|
@@ -112,20 +110,14 @@ This is a Claude Skills collection repository containing:
| 75 | dintel-marketing-mgr | Content pipeline (Magazine D., newsletter, LinkedIn) | Draft & Wait | "콘텐츠 발행", "newsletter" |
| 76 | dintel-backoffice-mgr | Invoicing, contracts, NDA, HR operations | Draft & Wait | "계약서", "인보이스" |
| 77 | dintel-account-mgr | Client relationship management & monitoring | Mixed | "client status", "미팅 준비" |
| 88 | dintel-skill-update | Cross-skill consistency management (meta-agent) | Triggered | "skill sync", "스킬 업데이트" |
| 78 | dintel-campaign-designer | Campaign/promotion planning as a 3-gate process (Discovery & Debate → Brief → Plan); cross-brand, not D.intelligence-exclusive | Draft & Wait | "campaign plan", "캠페인 기획", "기획안 만들어" |
| 79 | dintel-skill-update | Cross-skill consistency management (meta-agent) | Triggered | "skill sync", "스킬 업데이트" |
**Shared infrastructure:** `dintel-shared/` (Python package + reference docs)
**Shared infrastructure:** `_dintel-shared/` (Python package + reference docs)
**Install:** `cd custom-skills/dintel-shared && ./install.sh --all`
**Install:** `cd custom-skills/_dintel-shared && ./install.sh --all`
**User Guide:** `custom-skills/dintel-shared/USER-GUIDE.md`
### 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" |
**User Guide:** `custom-skills/_dintel-shared/USER-GUIDE.md`
### Reference Curator & Multi-Agent (90-91)
@@ -162,19 +154,36 @@ This is a Claude Skills collection repository containing:
| quality-reviewer | `/quality-reviewer` | QA loop with approve/refactor/research routing |
| markdown-exporter | `/markdown-exporter` | Export to markdown or JSONL for fine-tuning |
## Dual-Platform Skill Structure
### System & Utility Tools (80-82)
Each skill has two independent versions:
| # | Skill | Purpose | Trigger |
|---|-------|---------|---------|
| 80 | claude-settings-optimizer | Claude settings optimization & token audit | "settings audit", "exceed response limit", "MCP error" |
| 81 | mac-optimizer | macOS system health audit & optimization (Claude Code only) | "audit my mac", "system health", "clean up caches", "check security", "update packages" |
| 82 | our-gdrive-organizer | Organize a Google Drive folder under OurDigital conventions: refresh README index, refresh per-subfolder READMEs, propose renames + moves | "/organize", "organize the Drive folder", "refresh the index", "rescan the folder" |
## Skill Structure (root `SKILL.md` + dual-platform packaging)
**Going-forward standard.** Each skill has a root `SKILL.md` (the official Agent Skills
format — natively loadable by Claude Code / Desktop / API) plus the OurDigital `code/` +
`desktop/` packaging. Reference implementations: `16-seo-schema-validator`,
`17-seo-schema-generator`. To migrate an older skill, follow
`reference/SKILL-MIGRATION-GUIDE.md` (additive — no rewrite).
```
XX-skill-name/
├── code/ # Claude Code version
│ ├── CLAUDE.md # Action-oriented directive
│ ├── scripts/ # Executable Python/Bash
│ └── docs/ # Documentation
├── SKILL.md # Root directive — official Agent Skills format (LOADABLE)
├── scripts/ # Runnable scripts (single source of truth)
├── references/ # Heavy docs, loaded on demand
├── templates/ # Optional
├── fixtures/ # Optional sample/test inputs
├── desktop/ # Claude Desktop version
│ ├── SKILL.md # Skill directive with YAML frontmatter
├── code/ # Claude Code packaging
│ ├── CLAUDE.md # Redirects to ../SKILL.md + ../scripts (no duplication)
│ └── scripts/ # Legacy/aux tools only
├── desktop/ # Claude Desktop packaging
│ ├── SKILL.md # Desktop directive with YAML frontmatter
│ ├── skill.yaml # Extended metadata (optional)
│ └── tools/ # MCP tool documentation
@@ -182,24 +191,24 @@ XX-skill-name/
└── README.md # Overview
```
### Platform Differences
Older skills may still be `code/` + `desktop/` only (no root `SKILL.md`) — valid, but not
directly loadable. Migrate incrementally, not in bulk.
| Aspect | `code/` | `desktop/` |
|--------|---------|------------|
| Directive | CLAUDE.md | SKILL.md (with YAML frontmatter) |
| Metadata | In CLAUDE.md | skill.yaml (optional) |
| Tool docs | N/A | tools/ directory |
| Execution | Direct Bash/Python | MCP tools only |
| Scripts | Required | Reference only |
### Layer roles
### SKILL.md Format (Desktop)
| Layer | Role |
|-------|------|
| root `SKILL.md` | Canonical, loadable directive + bundled resources |
| `code/` | Claude Code packaging; `CLAUDE.md` redirects to the root |
| `desktop/` | Claude Desktop view; `SKILL.md` + `skill.yaml` + `tools/` |
### SKILL.md frontmatter (root + desktop)
```markdown
---
name: skill-name-kebab-case
name: skill-name-kebab-case # clean name: dir minus the NN- prefix
description: |
Brief description of what the skill does.
Triggers: keyword1, keyword2, 한국어 트리거.
What it does + when to use. Triggers: keyword1, keyword2, 한국어 트리거.
---
# Skill Title
@@ -207,14 +216,18 @@ description: |
Content starts here...
```
**Naming convention:** the directory keeps its `NN-` prefix for ordering
(`16-seo-schema-validator/`), but the skill `name:` is the **clean** form without it
(`name: seo-schema-validator`). Names must be kebab-case and globally unique.
Whole frontmatter ≤ 1024 chars. Full rules + migration recipe: `reference/SKILL-MIGRATION-GUIDE.md`.
## Directory Layout
```
our-claude-skills/
├── custom-skills/
│ ├── _ourdigital-shared/ # Shared config, installer, dependencies
│ ├── 00-our-settings-audit/
├── _dintel-shared/ # D.intelligence shared infra (Python pkg, refs, installer)
│ │
│ ├── 01-ourdigital-brand-guide/
│ ├── 02-ourdigital-blog/
@@ -228,48 +241,31 @@ our-claude-skills/
│ ├── 10-ourdigital-skill-creator/
│ │
│ ├── 11-seo-comprehensive-audit/
│ ├── 12-seo-technical-audit/
│ ├── 13-seo-on-page-audit/
│ ├── 14-seo-core-web-vitals/
│ ├── 15-seo-search-console/
│ ├── 16-seo-schema-validator/
│ ├── 17-seo-schema-generator/
│ ├── 18-seo-local-audit/
│ ├── 19-seo-keyword-strategy/
│ ├── 20-seo-serp-analysis/
│ ├── 21-seo-position-tracking/
│ ├── 22-seo-link-building/
│ ├── 23-seo-content-strategy/
│ ├── 24-seo-ecommerce/
│ ├── 25-seo-kpi-framework/
│ ├── 26-seo-international/
│ ├── 27-seo-ai-visibility/
│ ├── 28-seo-knowledge-graph/
│ ├── 29-seo-gateway-architect/
│ ├── 30-seo-gateway-builder/
│ ├── 31-seo-competitor-intel/
│ ├── 32-seo-crawl-budget/
│ ├── 33-seo-migration-planner/
│ ├── ... # 12-34 (SEO tools)
│ ├── 34-seo-reporting-dashboard/
│ │
│ ├── 60-gtm-audit/
│ ├── 61-gtm-editor/
│ ├── 62-gtm-validator/
│ │
│ ├── 31-notion-organizer/
│ ├── 32-notion-writer/
│ │
│ ├── 40-jamie-brand-editor/
│ ├── 41-jamie-brand-audit/
│ ├── 42-jamie-faq-entry/
│ ├── 43-jamie-youtube-manager/
│ ├── 44-jamie-youtube-subtitle-checker/
│ ├── 45-jamie-instagram-manager/
│ ├── 46-jamie-journal-editor/
│ ├── 47-jamie-marketing-editor/
│ ├── 48-jamie-copy-trimmer/
│ │
│ ├── 50-notebooklm-agent/
│ ├── 51-notebooklm-automation/
│ ├── 52-notebooklm-studio/
│ ├── 53-notebooklm-research/
│ │
│ ├── 60-gtm-audit/
│ ├── 61-gtm-editor/
│ ├── 62-gtm-validator/
│ │
│ ├── 70-dintel-brand-guardian/
│ ├── 71-dintel-brand-editor/
│ ├── 72-dintel-doc-secretary/
@@ -278,8 +274,12 @@ our-claude-skills/
│ ├── 75-dintel-marketing-mgr/
│ ├── 76-dintel-backoffice-mgr/
│ ├── 77-dintel-account-mgr/
│ ├── 88-dintel-skill-update/
│ ├── dintel-shared/ # D.intelligence shared infra (Python pkg, refs, installer)
│ ├── 78-dintel-campaign-designer/
│ ├── 79-dintel-skill-update/
│ │
│ ├── 80-claude-settings-optimizer/
│ ├── 81-mac-optimizer/
│ ├── 82-our-gdrive-organizer/
│ │
│ ├── 90-reference-curator/ # Modular reference documentation suite
│ │ ├── 01-reference-discovery/
@@ -295,11 +295,7 @@ our-claude-skills/
│ │
│ ├── 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/
── 92-tui-design-template/
├── example-skills/skills-main/
├── official-skills/
@@ -309,10 +305,11 @@ our-claude-skills/
## Skill Design Principles
1. **One thing done well** - Each skill focuses on a single capability
2. **Directives under 1,500 words** - Concise, actionable
3. **Self-contained** - Each platform version is fully independent
4. **Code-first development** - Build Claude Code version first
5. **Progressive numbering** - Logical grouping by domain
2. **Root `SKILL.md` first** - Ship a loadable root `SKILL.md`; keep `code/`+`desktop/` as packaging
3. **Directives under 1,500 words** - Concise, actionable; push detail into `references/`
4. **Self-contained** - Bundle scripts/refs as relative paths; one source of truth (no script dupes)
5. **Code-first development** - Build and self-test the runnable version first
6. **Progressive numbering** - Logical grouping by domain
## Creating New Skills
@@ -321,8 +318,12 @@ our-claude-skills/
python example-skills/skills-main/skill-creator/scripts/init_skill.py <skill-name> --path custom-skills/
```
New skills must include a root `SKILL.md` (the hybrid pattern above); model them on
`16-seo-schema-validator` / `17-seo-schema-generator`.
## Key Reference Files
- `reference/SKILL-FORMAT-REQUIREMENTS.md` - Format specification
- `reference/SKILL-MIGRATION-GUIDE.md` - Add a root `SKILL.md` to an existing skill (hybrid pattern, going-forward standard)
- `reference/SKILL-FORMAT-REQUIREMENTS.md` - Dual-platform format specification
- `example-skills/skills-main/skill-creator/SKILL.md` - Skill creation guide
- `AGENTS.md` - Agent routing guide for Task tool

111
README.md
View File

@@ -2,7 +2,7 @@
> **Internal R&D Repository** - This repository is restricted for internal use only.
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.
A collection of **65 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
@@ -12,14 +12,13 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
./install.sh
```
This symlinks the global slash commands into `~/.claude/commands/`, sets up the
Python virtual environment, and configures credentials. It does **not** register
skills natively — to load a skill as a Claude Code skill, also symlink it into
`~/.claude/skills/` (see [Usage → Claude Code](#claude-code)).
## Custom Skills Overview
### Settings & Meta Tools (00)
| # | Skill | Purpose |
|---|-------|---------|
| 00 | `our-settings-audit` | Claude settings optimization & token efficiency audit |
### OurDigital Core (01-10)
| # | Skill | Purpose |
@@ -64,34 +63,6 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
| 33 | `seo-migration-planner` | Site migration planning, redirect mapping, monitoring |
| 34 | `seo-reporting-dashboard` | Executive reports, HTML dashboards, data aggregation |
### D.intelligence Agent Corps (70-88)
| # | Skill | Purpose | Autonomy |
|---|-------|---------|----------|
| 70 | `dintel-brand-guardian` | Brand compliance review (100pt checklist) | Auto |
| 71 | `dintel-brand-editor` | Brand-compliant copywriting & style evaluation | Auto + Ask |
| 72 | `dintel-doc-secretary` | Document formatting, meeting notes, reports | Draft & Wait |
| 73 | `dintel-quotation-mgr` | Quotation generation (4 sub-agents) | Draft & Wait |
| 74 | `dintel-service-architect` | Service scope design & module recommendation | Inquiry-driven |
| 75 | `dintel-marketing-mgr` | Content pipeline (Magazine D., newsletter, LinkedIn) | Draft & Wait |
| 76 | `dintel-backoffice-mgr` | Invoicing, contracts, NDA, HR operations | Draft & Wait |
| 77 | `dintel-account-mgr` | Client relationship management & Notion monitoring | Mixed |
| 88 | `dintel-skill-update` | Cross-skill consistency management (meta-agent) | Triggered |
**Shared infrastructure:** `dintel-shared/` (Python package + reference docs)
**Install:** `cd custom-skills/dintel-shared && ./install.sh --all`
**User Guide:** `custom-skills/dintel-shared/USER-GUIDE.md`
### GTM/GA Tools (60-69)
| # | Skill | Purpose |
|---|-------|---------|
| 60 | `gtm-audit` | GTM container audit — page scan, site audit, gap analysis, tag design (DTM Agent + Chrome DevTools MCP) |
| 61 | `gtm-editor` | GTM tag/trigger/variable creation via API, ES5 Custom HTML, dataLayer generation, workspace lifecycle |
| 62 | `gtm-validator` | GTM QA — tag firing verification, trigger testing, dataLayer schema, naming conventions, cross-platform mapping |
### Notion Tools (31-39)
| # | Skill | Purpose |
@@ -105,9 +76,12 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
|---|-------|---------|
| 40 | `jamie-brand-editor` | Branded content generation |
| 41 | `jamie-brand-audit` | Content review/compliance |
| 42 | `jamie-faq-entry` | KakaoTalk Kanana chatbot Q&A entries |
| 43 | `jamie-youtube-manager` | YouTube SEO audit & management |
| 44 | `jamie-youtube-subtitle-checker` | YouTube subtitle validation |
| 45 | `jamie-instagram-manager` | Instagram account management |
| 46 | `jamie-journal-editor` | Journal/blog content for journal.jamie.clinic |
| 47 | `jamie-marketing-editor` | Multi-channel marketing content & ad copy |
### NotebookLM Tools (50-59)
@@ -120,18 +94,47 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
**Prerequisites:** `pip install notebooklm-py && playwright install chromium && notebooklm login`
### System & Design Tools (92-93)
### GTM/GA Tools (60-62)
| # | 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) |
| 60 | `gtm-audit` | GTM container audit — page scan, site audit, gap analysis, tag design (DTM Agent + Chrome DevTools MCP) |
| 61 | `gtm-editor` | GTM tag/trigger/variable creation via API, ES5 Custom HTML, dataLayer generation, workspace lifecycle |
| 62 | `gtm-validator` | GTM QA — tag firing verification, trigger testing, dataLayer schema, naming conventions, cross-platform mapping |
### D.intelligence Agent Corps (70-79)
| # | Skill | Purpose | Autonomy |
|---|-------|---------|----------|
| 70 | `dintel-brand-guardian` | Brand compliance review (100pt checklist) | Auto |
| 71 | `dintel-brand-editor` | Brand-compliant copywriting & style evaluation | Auto + Ask |
| 72 | `dintel-doc-secretary` | Document formatting, meeting notes, reports | Draft & Wait |
| 73 | `dintel-quotation-mgr` | Quotation generation (4 sub-agents) | Draft & Wait |
| 74 | `dintel-service-architect` | Service scope design & module recommendation | Inquiry-driven |
| 75 | `dintel-marketing-mgr` | Content pipeline (Magazine D., newsletter, LinkedIn) | Draft & Wait |
| 76 | `dintel-backoffice-mgr` | Invoicing, contracts, NDA, HR operations | Draft & Wait |
| 77 | `dintel-account-mgr` | Client relationship management & Notion monitoring | Mixed |
| 79 | `dintel-skill-update` | Cross-skill consistency management (meta-agent) | Triggered |
**Shared infrastructure:** `_dintel-shared/` (Python package + reference docs)
**Install:** `cd custom-skills/_dintel-shared && ./install.sh --all`
**User Guide:** `custom-skills/_dintel-shared/USER-GUIDE.md`
### System & Utility Tools (80-82)
| # | Skill | Purpose |
|---|-------|---------|
| 80 | `claude-settings-optimizer` | Claude settings optimization & token efficiency audit |
| 81 | `mac-optimizer` | macOS system health audit & optimization (Claude Code only) |
| 82 | `tui-design-template` | TUI wizard interface design (Norton Commander / Gopher style, Rich) |
### Reference Curator & Multi-Agent (90-91)
| # | Skill | Purpose |
|---|-------|---------|
| 90 | `reference-curator` | Modular reference documentation suite (6 sub-skills) |
| 90 | `reference-curator` | Modular reference documentation suite (6 sub-skills + pipeline orchestrator) |
| 91 | `multi-agent-guide` | Multi-agent collaboration setup & guardrails |
**Reference Curator Sub-skills:**
@@ -145,7 +148,7 @@ cd our-claude-skills/custom-skills/_ourdigital-shared
| `quality-reviewer` | QA loop with approve/refactor/research routing |
| `markdown-exporter` | Export to markdown or JSONL for fine-tuning |
**Pipeline Command:** `/reference-curator-pipeline "topic" --max-sources 5`
**Pipeline Command:** `/reference-curator "topic" --max-sources 5`
## Dual-Platform Architecture
@@ -173,19 +176,18 @@ XX-skill-name/
our-claude-skills/
├── custom-skills/
│ ├── _ourdigital-shared/ # Shared config, installer, dependencies
│ ├── _dintel-shared/ # D.intelligence shared infra (Python pkg, refs)
│ │
│ ├── 00-our-settings-audit/
│ ├── 01-10 (OurDigital core)
│ ├── 11-34 (SEO tools)
│ ├── 60-62 (GTM/GA tools)
│ ├── 31-32 (Notion tools)
│ ├── 40-45 (Jamie clinic)
│ ├── 40-47 (Jamie clinic)
│ ├── 50-53 (NotebookLM tools)
│ ├── 60-62 (GTM/GA tools)
│ ├── 70-79 (D.intelligence Agent Corps)
│ ├── 80-82 (System & utility tools)
│ ├── 90-reference-curator/
── 91-multi-agent-guide/
│ ├── 92-mac-optimizer/
│ ├── 93-tui-designer/
│ └── 99_archive/
── 91-multi-agent-guide/
├── example-skills/ # Anthropic reference examples
├── official-skills/ # 3rd party skills (Notion)
@@ -218,16 +220,23 @@ The `_ourdigital-shared/` directory provides:
### Claude Code
Skills are auto-detected via symlinks in `~/.claude/skills/`:
The Quick Install symlinks the **slash commands** into `~/.claude/commands/`. To
also load a skill natively, symlink its **root** directory (which holds the
loadable `SKILL.md`) into `~/.claude/skills/`, using the clean name without the
`NN-` prefix:
```bash
# Install skill symlink
ln -sf /path/to/skill/desktop ~/.claude/skills/skill-name
# From the repo root — symlink a skill into Claude Code
ln -sf "$PWD/custom-skills/16-seo-schema-validator" ~/.claude/skills/seo-schema-validator
```
> Legacy skills that don't yet have a root `SKILL.md` expose it under
> `code/SKILL.md` instead — symlink `.../<skill>/code` for those.
### Claude Desktop
Copy the `desktop/SKILL.md` file to your Claude Desktop skills folder.
Import the skill's `desktop/` folder (containing `SKILL.md` + `skill.yaml`) via
your Claude Desktop skills settings.
## Development

View File

@@ -52,9 +52,9 @@ Only activates with "ourdigital" keyword:
### Core Values
- **Data-driven** (데이터 중심) - 정밀 검사
- **In-Action** (실행 지향) - 실행 가능한 처방
- **Marketing Science** (마케팅 과학) - 근거 중심 의학
- **In-Action** (실행 지향) - 실행 가능한 처방
- **Sustainable Growth** (지속 성장) - 체질 개선
### Channel Tones
@@ -83,6 +83,6 @@ Only activates with "ourdigital" keyword:
## Version
- Current: 1.0.0
- Current: 1.1.0
- Author: OurDigital
- Environment: Both (Desktop & Code)

View File

@@ -0,0 +1,165 @@
---
name: ourdigital-brand-guide
description: |
OurDigital 브랜드 기준 및 스타일 가이드 참조 스킬.
Activated with "ourdigital" keyword for brand-related queries.
Triggers (ourdigital or our prefix):
- "ourdigital brand guide", "our brand guide"
- "ourdigital 브랜드 가이드", "our 브랜드 가이드"
- "ourdigital 톤앤매너", "our 톤앤매너"
- "ourdigital style check", "our style check"
Features:
- Brand foundation & values reference
- Writing style guidelines (Korean/English)
- Visual identity & color palette
- Channel-specific tone mapping
- Brand compliance checking
version: "1.1"
author: OurDigital
environment: Desktop
---
# OurDigital Brand Guide
Reference skill for OurDigital brand standards, writing style, and visual identity.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital 브랜드 가이드" / "our 브랜드 가이드"
- "ourdigital 톤앤매너 체크" / "our 톤앤매너"
- "our brand guide", "our style check"
## Brand Foundation
### Core Identity
| Element | Content |
|---------|---------|
| **Brand Name** | OurDigital Clinic |
| **Tagline** | 우리 디지털 클리닉 \| Your Digital Health Partner |
| **Mission** | 디지털 마케팅 클리닉 for SMBs, 자영업자, 프리랜서, 비영리단체 |
| **Promise** | 진단-처방-측정 가능한 성장 |
### Core Values
| 가치 | English | 클리닉 메타포 |
|------|---------|--------------|
| 마케팅 과학 | Marketing Science | 근거 중심 의학 |
| 실행 지향 | In-Action | 실행 가능한 처방 |
| 지속 성장 | Sustainable Growth | 체질 개선 |
### Brand Philosophy
**"Precision + Empathy + Evidence"**
## Channel Tone Matrix
| Channel | Domain | Personality | Tone |
|---------|--------|-------------|------|
| Main Hub | ourdigital.org | Professional & Confident | Data-driven, Solution-oriented |
| Blog | blog.ourdigital.org | Analytical & Personal | Educational, Thought-provoking |
| Journal | journal.ourdigital.org | Conversational & Poetic | Reflective, Cultural Observer |
| OurStory | ourstory.day | Intimate & Reflective | Authentic, Personal Journey |
## Writing Style Characteristics
### Korean (한국어)
1. **철학-기술 융합체**: 기술 분석과 실존적 질문을 자연스럽게 결합
2. **역설 활용**: 긴장과 모순 구조로 논증 전개
3. **수사적 질문**: 선언적 권위보다 질문을 통한 참여
4. **우울한 낙관주의**: 불안과 상실을 인정하되 절망하지 않음
### English
1. **Philosophical-Technical Hybridization**: Technical content with human implications
2. **Paradox as Device**: Structure arguments around tensions
3. **Rhetorical Questions**: Interrogative engagement over authority
4. **Melancholic Optimism**: Acknowledge anxiety without despair
### Do's and Don'ts
**Do's:**
- Use paradox to structure arguments
- Ask rhetorical questions to engage readers
- Connect technical content to human implications
- Blend Korean and English naturally for technical terms
- Reference historical context and generational shifts
**Don'ts:**
- Avoid purely declarative, authoritative tone
- Don't separate technical analysis from cultural impact
- Avoid simplistic or overly optimistic narratives
- Don't provide prescriptive conclusions without exploration
## Visual Identity
### Primary Colors
| Token | Color | HEX | Usage |
|-------|-------|-----|-------|
| --d-black | D.Black | #221814 | Footer, dark backgrounds |
| --d-olive | D.Olive | #cedc00 | Primary accent, CTA buttons |
| --d-green | D.Green | #287379 | Links hover, secondary accent |
| --d-blue | D.Blue | #0075c0 | Links |
| --d-beige | D.Beige | #f2f2de | Light text on dark |
| --d-gray | D.Gray | #ebebeb | Alt backgrounds |
### Typography
- **Korean**: Noto Sans KR
- **English**: Noto Sans, Inter
- **Grid**: 12-column responsive layout
## Brand Compliance Check
When reviewing content, verify:
1. **Brand Name**: Uses OurDigital Clinic (not Lab/연구소)?
2. **Tone Match**: Does it match the channel's personality?
3. **Value Alignment**: Reflects Marketing Science, In-Action, Sustainable Growth?
4. **Philosophy Check**: Precision + Empathy + Evidence present?
5. **Language Style**: Appropriate blend of Korean/English terms?
6. **Visual Consistency**: Uses approved color palette (#221814/#cedc00)?
7. **Data Asset Check**: Analytics/reporting references current?
## Quick Reference
### Key Messages
| Use | Message |
|-----|---------|
| Tagline | 우리 디지털 클리닉 \| Your Digital Health Partner |
| Value Prop | 마케팅 과학으로 진단하고, 실행으로 처방합니다 |
| Process | 진단 → 처방 → 측정 |
| Differentiator | 25년 경험의 마케팅 사이언티스트 |
### CTA Patterns
| Context | CTA |
|---------|-----|
| General | 무료 상담 신청하기 |
| SEO | SEO 진단 신청하기 |
| Content | 콘텐츠 전략 상담 신청하기 |
## Deprecated Terms
| Deprecated | Current | Note |
|-----------|---------|------|
| OurDigital Lab | OurDigital Clinic | 2026-04 rebranding |
| 디지털 연구소 | 디지털 마케팅 클리닉 | Korean equivalent |
| 데이터 중심 (Core Value) | — | Moved to D.intelligence |
| 실행 지향 / In-Action | — | Retained (service tagline system) |
| #1a1a2e | #221814 (O.Black) | Color correction |
| #4ecdc4 | #cedc00 (O.Olive) | Color correction |
| Poppins / Lora | Noto Sans KR / Inter | Font standardization |
## References
See `shared/references/` for detailed guides:
- `brand-foundation.md` - Complete brand identity
- `writing-style.md` - Detailed writing guidelines
- `color-palette.md` - Full color system with CSS variables

View File

@@ -14,7 +14,7 @@ description: |
- Writing style guidelines
- Visual identity standards
- Brand compliance checking
version: "1.0"
version: "1.1"
author: OurDigital
environment: Code
---
@@ -45,9 +45,9 @@ Philosophy: Precision + Empathy + Evidence
| Value | Korean | Metaphor |
|-------|--------|----------|
| Data-driven | 데이터 중심 | 정밀 검사 |
| In-Action | 실행 지향 | 실행 가능한 처방 |
| Marketing Science | 마케팅 과학 | 근거 중심 의학 |
| In-Action | 실행 지향 | 실행 가능한 처방 |
| Sustainable Growth | 지속 성장 | 체질 개선 |
## Channel Tone
@@ -109,18 +109,20 @@ Philosophy: Precision + Empathy + Evidence
Check content against:
1.Channel tone match
2. ✓ Core values reflected
3.Philosophy alignment
4.Language style correct
5.Color palette used
1.Brand name correct (OurDigital Clinic, not Lab/연구소)
2. ✓ Channel tone match
3.Core values reflected (마케팅 과학, 실행 지향, 지속 성장)
4.Philosophy alignment
5.Language style correct
6. ✓ Color palette used (#221814/#cedc00)
7. ✓ Data asset check (analytics/reporting references current)
## Key Messages
| Purpose | Message |
|---------|---------|
| Tagline | 우리 디지털 클리닉 |
| Value | 데이터로 진단하고, 실행으로 처방합니다 |
| Value | 마케팅 과학으로 진단하고, 실행으로 처방합니다 |
| Process | 진단 → 처방 → 측정 |
## CTA Library
@@ -132,6 +134,18 @@ SEO 진단 신청하기
맞춤 견적 상담받기
```
## Deprecated Terms
| Deprecated | Current | Note |
|-----------|---------|------|
| OurDigital Lab | OurDigital Clinic | 2026-04 rebranding |
| 디지털 연구소 | 디지털 마케팅 클리닉 | Korean equivalent |
| 데이터 중심 (Core Value) | — | Moved to D.intelligence |
| 실행 지향 / In-Action | — | Retained (service tagline system) |
| #1a1a2e | #221814 (O.Black) | Color correction |
| #4ecdc4 | #cedc00 (O.Olive) | Color correction |
| Poppins / Lora | Noto Sans KR / Inter | Font standardization |
## File References
```

View File

@@ -16,7 +16,7 @@ description: |
- Visual identity & color palette
- Channel-specific tone mapping
- Brand compliance checking
version: "1.0"
version: "1.1"
author: OurDigital
environment: Desktop
---
@@ -47,9 +47,9 @@ Activate with "ourdigital" or "our" prefix:
| 가치 | English | 클리닉 메타포 |
|------|---------|--------------|
| 데이터 중심 | Data-driven | 정밀 검사 |
| 실행 지향 | In-Action | 실행 가능한 처방 |
| 마케팅 과학 | Marketing Science | 근거 중심 의학 |
| 실행 지향 | In-Action | 실행 가능한 처방 |
| 지속 성장 | Sustainable Growth | 체질 개선 |
### Brand Philosophy
@@ -118,11 +118,13 @@ Activate with "ourdigital" or "our" prefix:
When reviewing content, verify:
1. **Tone Match**: Does it match the channel's personality?
2. **Value Alignment**: Reflects Data-driven, In-Action, Marketing Science?
3. **Philosophy Check**: Precision + Empathy + Evidence present?
4. **Language Style**: Appropriate blend of Korean/English terms?
5. **Visual Consistency**: Uses approved color palette?
1. **Brand Name**: Uses OurDigital Clinic (not Lab/연구소)?
2. **Tone Match**: Does it match the channel's personality?
3. **Value Alignment**: Reflects Marketing Science, In-Action, Sustainable Growth?
4. **Philosophy Check**: Precision + Empathy + Evidence present?
5. **Language Style**: Appropriate blend of Korean/English terms?
6. **Visual Consistency**: Uses approved color palette (#221814/#cedc00)?
7. **Data Asset Check**: Analytics/reporting references current?
## Quick Reference
@@ -131,7 +133,7 @@ When reviewing content, verify:
| Use | Message |
|-----|---------|
| Tagline | 우리 디지털 클리닉 \| Your Digital Health Partner |
| Value Prop | 데이터로 진단하고, 실행으로 처방합니다 |
| Value Prop | 마케팅 과학으로 진단하고, 실행으로 처방합니다 |
| Process | 진단 → 처방 → 측정 |
| Differentiator | 25년 경험의 마케팅 사이언티스트 |
@@ -143,6 +145,18 @@ When reviewing content, verify:
| SEO | SEO 진단 신청하기 |
| Content | 콘텐츠 전략 상담 신청하기 |
## Deprecated Terms
| Deprecated | Current | Note |
|-----------|---------|------|
| OurDigital Lab | OurDigital Clinic | 2026-04 rebranding |
| 디지털 연구소 | 디지털 마케팅 클리닉 | Korean equivalent |
| 데이터 중심 (Core Value) | — | Moved to D.intelligence |
| 실행 지향 / In-Action | — | Retained (service tagline system) |
| #1a1a2e | #221814 (O.Black) | Color correction |
| #4ecdc4 | #cedc00 (O.Olive) | Color correction |
| Poppins / Lora | Noto Sans KR / Inter | Font standardization |
## References
See `shared/references/` for detailed guides:

View File

@@ -1,6 +1,8 @@
# OurDigital Brand Foundation
Complete brand identity reference for OurDigital Clinic.
<!-- Aligned to OurDigital_Blog_Project_Instruction_v3.3 / Writing_Style_Guide_v2.1 (2026-06-05) -->
Complete brand identity reference for OurDigital.
## Brand Identity
@@ -8,25 +10,34 @@ Complete brand identity reference for OurDigital Clinic.
| Element | Content |
|---------|---------|
| **Brand Name** | OurDigital Clinic |
| **Tagline** | 우리 디지털 클리닉 \| Your Digital Health Partner |
| **Mission** | 디지털 마케팅 클리닉 for SMBs, 자영업자, 프리랜서, 비영리단체 |
| **Brand Name** | OurDigital |
| **Identity Statement** | 사람, 디지털 그리고 문화를 관찰하는 개인 디지털 연구 노트 |
| **Mission** | 기술이 사람과 문화에 미치는 영향을 관찰하고 기록한다. 생각의 씨앗을 남기고 성찰한다. 검증되지 않은 도전과 실험의 결과를 기록한다. |
| **Vision** | 데이터 민주화, 정밀 마케팅, 지속 가능한 성장 |
| **Promise** | 진단-처방-측정 가능한 성장 |
### Core Values
### Brand Keywords
| 가치 | English | 클리닉 메타포 | 설명 |
|------|---------|--------------|------|
| 데이터 중심 | Data-driven | 정밀 검사 | 감이 아닌 데이터로 판단 |
| 실행 지향 | In-Action | 실행 가능한 처방 | 분석에서 끝나지 않는 실행력 |
| 마케팅 과학 | Marketing Science | 근거 중심 의학 | 검증된 방법론과 프레임워크 |
| 핵심 가치 | 설명 |
|----------|------|
| **관찰** | 현상을 있는 그대로 포착하되, 표면 아래를 본다 — 무엇이 일어나고 있는가 |
| **분석** | 현상의 원인과 맥락을 탐구한다 — 왜 이런 일이 일어나는가 |
| **성찰** | 심층적 의미와 인간적 함의를 도출한다 — 이것이 우리에게 무엇을 의미하는가 |
| **실험** | 검증되지 않은 시도를 두려워하지 않는다 |
| **기록** | 생각의 궤적을 남겨 미래의 자산으로 삼는다 |
| **균형** | 낙관과 비관, 이론과 실무 사이에서 중심을 잡는다 |
### Brand Philosophy
**"Precision + Empathy + Evidence"**
Accurate diagnosis, stakeholder understanding, and measurable validation across all communications.
Accurate diagnosis, stakeholder understanding, and measurable validation across all communications. 철학적 깊이와 실용적 가치의 균형이 OurDigital의 정체성이다.
### Brand Boundary (중요)
- **블로그 전체 브랜드**: OurDigital
- **블로그 설명**: 사람, 디지털 그리고 문화를 관찰하는 개인 디지털 연구 노트
- **OurDigital Clinic**: 진단형 콘텐츠, SEO/데이터/콘텐츠 감사, 컨설팅 상품, 문제 해결형 시리즈에서만 사용 가능한 서비스 메타포. 블로그 전체 브랜드명이 아니다.
## Positioning Statement
@@ -34,8 +45,16 @@ Accurate diagnosis, stakeholder understanding, and measurable validation across
> OurDigital Clinic is 디지털 마케팅 클리닉 that provides 진단-처방-측정 프로세스,
> unlike 일회성 캠페인 대행사, we deliver 25년 경험과 마케팅 사이언스 방법론
*(Note: OurDigital Clinic은 컨설팅/서비스 맥락에서의 포지셔닝. 블로그 전체 포지셔닝과 구분한다.)*
## Target Audience
**블로그 독자 (blog.ourdigital.org)**
- **1순위**: 디지털 마케팅/기술 분야 종사자 — 실무 전략을 수립하고 실행하는 전문가
- **2순위**: 기술과 사회의 관계에 관심 있는 지식인 — 디지털 전환이 사회에 미치는 영향을 고민하는 독자
- **3순위**: 자기 성찰과 비판적 사고를 중시하는 독자
**컨설팅 서비스 (OurDigital Clinic)**
- **Primary**: SMB 마케팅 담당자, 자영업자, 프리랜서, 비영리단체
- **Secondary**: 스타트업 창업자, 브랜드 매니저
@@ -54,24 +73,30 @@ Accurate diagnosis, stakeholder understanding, and measurable validation across
| Level | Element | Description |
|-------|---------|-------------|
| Level 1 | Master Brand | OurDigital Clinic |
| Level 1 | Master Brand | OurDigital |
| Level 2 | Channel Identity | Blog, Journal, OurStory |
| Level 3 | Service Identity | 4개 핵심 서비스 |
| Level 3 | Service Identity | 4개 핵심 서비스 (OurDigital Clinic 메타포 적용 가능) |
### Channel Personality & Tone
| Channel | Domain | Personality | Tone | Content Type |
|---------|--------|-------------|------|--------------|
| Main Hub | ourdigital.org | Professional & Confident | Data-driven, Solution-oriented | 서비스, 케이스, 리드 |
| Blog | blog.ourdigital.org | Analytical & Personal | Educational, Thought-provoking | 가이드, 분석, 인사이트 |
| Journal | journal.ourdigital.org | Conversational & Poetic | Reflective, Cultural Observer | 에세이, 문화, 관찰 |
| OurStory | ourstory.day | Intimate & Reflective | Authentic, Personal Journey | 개인 서사, 경험 |
| D.intelligence | dintelligence.co.kr | Professional | B2B | Corporate Partnership |
| Channel | Domain | Language | Character | Length |
|---------|--------|----------|-----------|--------|
| Main Hub | ourdigital.org | Korean | Professional & Confident, Data-driven | 서비스, 케이스, 리드 |
| **Blog** | blog.ourdigital.org | **Korean** | 디지털 문화 분석 + 철학적 성찰 + 실무 인사이트 | **1,500-3,000자** |
| **Journal** | journal.ourdigital.org | **English** | 산업 트렌드, 기술-인간 교차점, reflective essay | **1,000-2,000 words** |
| **OurStory** | ourstory.day | **Korean** | 개인 에세이, 삶의 성찰, 일상의 관찰 | **800-1,500자** |
| D.intelligence | dintelligence.co.kr | Korean/English | Professional | B2B Corporate Partnership |
**채널 라우팅 규칙:**
- 기술 분석 + 철학적 사유 → `blog.ourdigital.org`
- 순수 개인 에세이, 감정적 성찰 → `ourstory.day`
- 영문 심층 에세이, 산업 관점 → `journal.ourdigital.org`
- 진단형 콘텐츠, 컨설팅 제안 → `OurDigital Clinic` 메타포 사용 가능
### Content Flow Strategy
```
Discovery (Blog) → Engagement (Journal) → Conversion (Main Site)
Discovery (Blog) → Engagement (Journal) → Conversion (Main Site / OurDigital Clinic)
```
### Publishing Cadence
@@ -80,7 +105,7 @@ Discovery (Blog) → Engagement (Journal) → Conversion (Main Site)
|---------|-----------|--------|
| ourdigital.org | 필요시 업데이트 | 서비스별 상세 |
| blog.ourdigital.org | 주 1-2회 | 1,500-3,000자 |
| journal.ourdigital.org | 월 2-4회 | 1,000-2,000 |
| journal.ourdigital.org | 월 2-4회 | 1,000-2,000 words |
| ourstory.day | 월 1-2회 | 800-1,500자 |
## Service Portfolio
@@ -112,13 +137,16 @@ Discovery (Blog) → Engagement (Journal) → Conversion (Main Site)
| Use Case | Message |
|----------|---------|
| Tagline | 우리 디지털 클리닉 \| Your Digital Health Partner |
| Value Proposition | 데이터로 진단하고, 실행으로 처방합니다 |
| Blog Identity | 사람, 디지털 그리고 문화를 관찰하는 개인 디지털 연구 노트 |
| Consulting Tagline | 우리 디지털 클리닉 \| Your Digital Health Partner |
| Value Proposition | 마케팅 과학으로 진단하고, 실행으로 처방합니다 |
| Process | 진단 → 처방 → 측정 |
| Differentiator | 25년 경험의 마케팅 사이언티스트 |
## CTA Library
*(OurDigital Clinic 컨설팅 맥락에서 사용)*
| Context | CTA Text |
|---------|----------|
| General | 무료 상담 신청하기 |

View File

@@ -1,5 +1,7 @@
# OurDigital Writing Style Guide
<!-- Aligned to OurDigital_Blog_Project_Instruction_v3.3 / Writing_Style_Guide_v2.1 (2026-06-05) -->
Comprehensive writing guidelines for OurDigital content across all channels.
## Overview
@@ -8,7 +10,7 @@ Comprehensive writing guidelines for OurDigital content across all channels.
|-------|-------|
| **Author** | Andrew Yim |
| **Primary Blog** | blog.ourdigital.org |
| **Tagline** | 사람, 디지털 그리고 문화 (People, Digital, and Culture) |
| **Brand Identity** | 사람, 디지털 그리고 문화를 관찰하는 개인 디지털 연구 노트 |
| **Platform** | Ghost CMS |
| **History** | 2004-2025 (20+ years of content) |
@@ -16,54 +18,73 @@ Comprehensive writing guidelines for OurDigital content across all channels.
## Part 1: 한국어 스타일가이드
### Writer 역할 정의
25년차 디지털 마케팅 에세이 작가. 기술과 인간의 관계를 관찰하는 실무자이자 사유자. 분석적이면서 개인적이고, 단정 대신 질문을 두고, 결론 대신 가능성을 제시하며, 독자를 가르치지 않고 함께 생각하는 동료로 대한다.
### 문체 규칙 (Korean)
- 대화체 `~다`, `~이다`, `~한다`를 사용한다.
- 경어체 `~합니다`, `~입니다`는 사용하지 않는다.
- 짧은 문장과 긴 문장을 교차 배치해 리듬을 만든다.
- 문단 전환 시 호흡을 바꾼다: 분석 → 서사 → 질문.
- **전문용어는 첫 등장 시 영문을 병기한다.** 예: `검색엔진 최적화(SEO)`.
### 핵심 글쓰기 특성
#### 1. 철학-기술 융합
#### 1. 철학-기술 융합
기술 분석과 실존적 질문을 자연스럽게 결합한다. 기술 콘텐츠(AI 아키텍처, 엔터프라이즈 시스템)는 결코 인간적 의와 분리되지 않는다.
기술 주제를 다루되, 항상 이면의 인간적 함의를 탐구한다. 기술 콘텐츠(AI 아키텍처, 엔터프라이즈 시스템)는 결코 인간적 의와 분리되지 않는다.
**예시**: Llama 4와 DeepSeek 비교 글에서도 거버넌스 우려와 사회적 영향을 포함한다.
```
나쁜 예: 구글의 알고리즘은 200개 이상의 신호를 분석한다.
좋은 예: 구글은 200개 이상의 신호를 읽는다. 그런데 정작 글을 쓰는 사람은 단 하나의 신호, 독자의 진짜 질문도 제대로 읽지 못할 때가 많다.
```
#### 2. 역설(Paradox)을 주요 수사 장치로 활용
#### 2. 핵심 긴장 / 관점 전환 / 역설 / 열린 질문
논증을 긴장과 모순 구조로 전개한다:
- 인간이 컴퓨터를 모방하면서 컴퓨터가 인간을 모방하는 역설
- 기술이 해방하면서 동시에 의미를 축소하는 역설
- 디지털 네이티브가 기성세대가 가르칠 수 없는 유창함을 보유하는 역설
모든 글에 억지 역설을 넣지는 않는다. 대신 **핵심 긴장, 관점 전환, 역설, 열린 질문 중 하나 이상**을 자연스럽게 포함한다. 공식화를 경계한다.
#### 3. 수사적 질문 활용
유용한 패턴:
- `~하면서 동시에 ~하다`
- `~를 위해 오히려 ~해야 한다`
- `가장 ~한 것이 사실은 가장 ~하다`
- `우리가 놓치고 있는 것은 무엇인가?`
선언적 권위보다 질문을 통한 참여를 선호한다:
수사적 질문은 선언적 권위보다 독자와의 지적 동반자 관계를 형성한다.
> "이 디지털 세대에게 무엇을 가르쳐야 하는가?"
> "그 무한한 자유 속에서 우리는 무엇을 소중히 여기게 될 것인가?"
#### 3. 우울한 낙관주의 (Melancholic Optimism)
이는 독자와 지적 동반자 관계를 형성하며, 교훈적 지시를 피한다.
디지털 세계의 불안, 피로, 한계를 솔직히 인정한다. 그러나 냉소로 끝내지 않는다. 그 안에서 다시 생각할 가능성, 더 나은 실천, 작은 회복의 여지를 찾는다.
#### 4. 우울한 낙관주의 (Melancholic Optimism)
```
예: AI가 일자리를 대체할 수 있다는 불안은 근거가 있다. 그리고 그 불안이야말로 '대체 불가능한 것'이 무엇인지 진지하게 물어보게 만드는 시작점이다.
```
불안과 상실을 인정하되 절망하지 않는다. 기술의 불가피성을 수용하면서도 대체되는 것에 대한 진정한 우려를 표현한다.
#### 4. 분석적이면서 개인적
데이터와 논거를 제시하되, 1인칭 경험과 관찰을 자연스럽게 엮는다. 논문이 아니라 에세이다. 그러나 근거 없는 감상문도 아니다.
### 문장 구조 패턴
| 요소 | 패턴 |
|------|------|
| 문장 길이 | 여러 절을 포함한 긴 복합문 - 논의되는 상호연결된 개념을 반영 |
| 문장 길이 | 짧은 문장과 긴 복합문을 교차 배치 — 리듬을 만든다 |
| 단락 구조 | 관찰 → 분석 → 철학적 함의로 점진적 심화 |
| 근거 제시 | 역사적 사례, 문화간 참조, 기술 명세를 함께 엮음 |
| 결론부 | 종종 열린 결말로, 답을 제시하기보다 질문을 던짐 |
### 독자 조율
**교양 있는 일반 독자**를 대상으로 한다 — 기술의 문화적 영향에 지적 호기심을 가진 독자이지, 반드시 기술 전문가는 아니다.
**교양 있는 일반 독자** — 기술의 문화적 영향에 지적 호기심을 가진 독자이지, 반드시 기술 전문가는 아니다. 독자를 가르치려 하지 않고 결론을 강요하지 않는다. 생각의 재료를 제공한다.
기술 글에도 요약본과 은유적 앵커링("데이터 공장")을 포함해 접근성을 유지한다.
기술 글에도 은유적 앵커링("데이터 공장")을 포함해 접근성을 유지한다.
### 고유 특성
1. **이중언어 유창성** — 한국어 산문에 영어 기술 용어가 섞여 디지털 담론의 혼종적 특성을 반영
1. **이중언어 유창성** — 한국어 산문에 영어 기술 용어가 섞여 디지털 담론의 혼종적 특성을 반영 (단, 전문용어는 첫 등장 시 영문 병기)
2. **시간적 인식** — 세대 변화와 역사적 맥락에 대한 강한 의식
3. **인식론적 겸손**특히 세대간 격차에서 이해의 한계를 인정
3. **인식론적 겸손** — 이해의 한계를 인정하며, 겸손한 추정 표현을 적절히 활용 (`~일지도 모른다`, `~인 듯하다`, `어쩌면 ~`)
4. **규제 의식** — 엔터프라이즈 글에서 일관되게 컴플라이언스(GDPR, EU 규제)를 다룸
---
@@ -124,20 +145,28 @@ Technical articles include executive summaries and metaphorical anchoring ("Data
### Do's
- Use paradox to structure arguments
- Ask rhetorical questions to engage readers
- Connect technical content to human implications
- Acknowledge uncertainty and epistemic limits
- Blend Korean and English naturally for technical terms
- Reference historical context and generational shifts
- 핵심 긴장, 관점 전환, 역설, 열린 질문 중 하나 이상을 포함한다
- 수사적 질문으로 독자와 지적 동반자 관계를 형성한다
- 기술 콘텐츠를 인간적 함의와 연결한다
- 불확실성과 이해의 한계를 인정한다
- 전문용어 첫 등장 시 영문을 병기한다 (`검색엔진 최적화(SEO)`)
- 역사적 맥락과 세대적 변화를 참조한다
- 짧은 문장과 긴 문장을 교차 배치해 리듬을 만든다
- 1인칭 경험과 관찰을 분석에 자연스럽게 엮는다
### Don'ts
### Don'ts (피해야 할 것)
- Avoid purely declarative, authoritative tone
- Don't separate technical analysis from cultural impact
- Avoid simplistic or overly optimistic technology narratives
- Don't provide prescriptive conclusions without exploration
- Avoid ignoring regulatory and governance concerns
| 피해야 할 것 | 이유 |
|-------------|------|
| `~합니다`, `~입니다` | 경어체 — 대화체(`~다`, `~한다`) 사용 |
| `오늘날 디지털 시대에`, `급변하는 환경 속에서`, `요즘 ~가 화제다` | 상투적 도입부 |
| `첫째, 둘째, 셋째` 기계적 나열 | 교과서적 구조 |
| `따라서 반드시 ~해야 한다` | 확정적 결론 |
| 근거 없는 통계나 데이터 | 데이터 정직성 위반 |
| 기술에 대한 무조건적 찬양 또는 혐오 | 균형 원칙 위반 |
| 과도한 감탄부호, 물결표, 이모지 | 톤 일관성 파괴 |
| 특정 업체나 제품의 노골적 홍보성 문장 | 브랜드 신뢰 훼손 |
| SEO 키워드 과잉 최적화 | SEO가 글을 지배해서는 안 된다 |
---

View File

@@ -0,0 +1,145 @@
---
name: ourdigital-blog
description: |
Korean blog draft creation for blog.ourdigital.org.
Activated with "ourdigital" keyword for blog writing tasks.
Triggers (ourdigital or our prefix):
- "ourdigital blog", "our blog"
- "ourdigital 블로그", "our 블로그"
- "ourdigital 한국어 포스트", "our 한국어 포스트"
Features:
- Blog draft generation in Korean
- SEO metadata (title, description, slug)
- Ghost CMS format output
- Brand voice compliance
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Blog
Korean blog draft creation skill for blog.ourdigital.org.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital 블로그 써줘" / "our 블로그 써줘"
- "ourdigital blog draft" / "our blog draft"
- "our 한국어 포스트 [주제]"
## Channel Profile
| Field | Value |
|-------|-------|
| **URL** | blog.ourdigital.org |
| **Language** | Korean (전문용어 영문 병기) |
| **Tone** | Analytical & Personal, Educational |
| **Platform** | Ghost CMS |
| **Frequency** | 주 1-2회 |
| **Length** | 1,500-3,000자 |
## Workflow
### Phase 1: Topic Clarification
Ask clarifying questions (max 3):
1. **주제 확인**: 정확한 토픽이 무엇인가요?
2. **대상 독자**: 타겟 오디언스는? (마케터/개발자/경영진/일반)
3. **깊이 수준**: 개요 / 심층분석 / 실무가이드 중 어느 수준?
### Phase 2: Research (Optional)
If topic requires current information:
- Use `web_search` for latest trends/data
- Use `Notion:notion-search` for past research
- Reference internal documents if available
### Phase 3: Draft Generation
Generate blog draft following brand style:
**Structure:**
```
1. 도입부 (Hook + Context)
2. 본론 (3-5 핵심 포인트)
- 각 포인트: 주장 → 근거 → 함의
3. 결론 (Summary + 열린 질문)
```
**Writing Style:**
- 철학-기술 융합: 기술 분석 + 인간적 함의
- 역설 활용: 긴장/모순으로 논증 구조화
- 수사적 질문: 독자 참여 유도
- 우울한 낙관주의: 불안 인정, 절망 거부
**Language Rules:**
- 한글 기본, 전문용어는 영문 병기
- 예: "검색엔진최적화(SEO)"
- 문장: 복합문 허용, 상호연결된 개념 반영
- 단락: 관찰 → 분석 → 철학적 함의
### Phase 4: SEO Metadata
Generate metadata:
```yaml
title: [60자 이내, 키워드 포함]
meta_description: [155자 이내]
slug: [영문 URL slug]
tags: [3-5개 태그]
featured_image_prompt: [DALL-E/Midjourney 프롬프트]
```
### Phase 5: Output Format
**Markdown Output:**
```markdown
---
title: "포스트 제목"
meta_description: "메타 설명"
slug: "url-slug"
tags: ["tag1", "tag2"]
---
# 포스트 제목
[본문 내용]
---
*Originally drafted with Claude for OurDigital Blog*
```
## Ghost CMS Integration
Export options:
1. **Markdown file** → Ulysses → Ghost
2. **Direct API** → Ghost Admin API (if configured)
API endpoint: `GHOST_BLOG_URL` from environment
## Brand Compliance
Before finalizing, verify:
- [ ] 분석적 + 개인적 톤 유지
- [ ] 기술 내용에 인간적 함의 포함
- [ ] 수사적 질문으로 독자 참여
- [ ] 전문용어 영문 병기
- [ ] 1,500-3,000자 범위
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital 블로그 [주제]" | Full workflow |
| "ourdigital blog SEO" | SEO metadata only |
| "ourdigital blog 편집" | Edit existing draft |
## References
- `shared/references/blog-style-guide.md` - Detailed writing guide
- `shared/templates/blog-template.md` - Post structure template
- `01-ourdigital-brand-guide` - Brand voice reference

View File

@@ -1,5 +1,7 @@
# OurDigital Blog Style Guide
<!-- Aligned to OurDigital_Writing_Style_Guide_v2.1 + Blog_Project_Instruction_v3.3 (2026-06-05) -->
Detailed writing guidelines for blog.ourdigital.org.
## Channel Identity
@@ -7,62 +9,74 @@ Detailed writing guidelines for blog.ourdigital.org.
| Field | Value |
|-------|-------|
| **Domain** | blog.ourdigital.org |
| **Tagline** | 사람, 디지털 그리고 문화 |
| **Language** | Korean (전문용어 영문 병기) |
| **Tone** | Analytical & Personal, Educational |
| **Target** | 교양 있는 일반 독자 - 기술의 문화적 영향에 호기심 있는 독자 |
| **Brand** | OurDigital |
| **Identity** | 사람, 디지털 그리고 문화를 관찰하는 개인 디지털 연구 노트 |
| **Core Theme** | 사람, 디지털 그리고 문화 |
| **Language** | Korean (전문용어 첫 등장 시 영문 병기) |
| **CMS** | Ghost — 초안은 Markdown으로 작성 |
| **Tone** | Analytical & Personal (비판적이되 공정, 겸손하되 자신감 있음) |
| **Target** | 디지털 마케팅/기술 실무자 (1순위), 기술-사회 관계에 관심 있는 지식인 (2순위), 비판적 사고를 중시하는 일반 독자 (3순위) |
**브랜드 경계**: `OurDigital`이 블로그 전체 정체성이다. `OurDigital Clinic`은 진단형 콘텐츠·컨설팅 상품에서만 사용하는 서비스 메타포다.
**우선순위**: 지침 충돌 시 Blog_Project_Instruction_v3.3 → 이 스타일 가이드 → 사용자 일반 스타일 순으로 따른다.
## Writing Characteristics
### 1. 철학-기술 융합체
모든 초안은 아래 네 원칙을 기준으로 자기 점검한다.
기술 분석과 실존적 질문을 자연스럽게 결합한다.
### 1. 철학-기술 융합
**Good Example:**
> AI가 우리의 업무를 대체할 수 있다는 사실은 분명하다. 그러나 더 중요한 질문은 "AI가 대체할 수 없는 것은 무엇인가?"이다.
**Bad Example:**
> AI는 업무 효율성을 높여준다. 다양한 분야에서 활용되고 있다.
### 2. 역설(Paradox) 활용
논증을 긴장과 모순 구조로 전개한다.
**Paradox Patterns:**
- "~하면서 동시에 ~하다"
- "~인 것 같지만 실은 ~이다"
- "~를 얻었지만 ~를 잃었다"
### 3. 수사적 질문
선언적 권위보다 질문을 통한 참여를 선호한다.
기술 주제를 다루되, 항상 이면의 인간적 함의를 탐구한다.
**Good:**
> 우리는 정말 데이터를 이해하고 있는 것일까?
> 구글은 200개 이상의 신호를 읽는다. 그런데 정작 글을 쓰는 사람은 단 하나의 신호, 독자의 진짜 질문도 제대로 읽지 못할 때가 많다.
**Bad:**
> 데이터를 이해하는 것이 중요하다.
> 구글의 알고리즘은 200개 이상의 신호를 분석한다.
### 4. 우울한 낙관주의
### 2. 긴장과 역설
불안과 상실을 인정하되 절망하지 않는다.
억지 역설을 억지로 넣지 않는다. 핵심 긴장·관점 전환·역설·열린 질문 중 하나 이상을 자연스럽게 포함한다.
**Useful Patterns:**
- `~하면서 동시에 ~하다`
- `~를 위해 오히려 ~해야 한다`
- `가장 ~한 것이 사실은 가장 ~하다`
- `우리가 놓치고 있는 것은 무엇인가?`
> 예: 데이터를 가장 잘 활용하는 방법은, 때때로 데이터를 내려놓는 것이다.
### 3. 우울한 낙관주의
디지털 세계의 불안·피로·한계를 솔직히 인정한다. 그러나 냉소로 끝내지 않는다. 그 안에서 다시 생각할 가능성, 작은 회복의 여지를 찾는다.
**Tone Spectrum:**
```
비관 ←――――――――――――――――――→ 낙관
우울한 낙관주의
(여기에 위치)
우울한 낙관주의
(여기에 위치)
```
> 예: AI가 일자리를 대체할 수 있다는 불안은 근거가 있다. 그리고 그 불안이야말로 '대체 불가능한 것'이 무엇인지 진지하게 물어보게 만드는 시작점이다.
### 4. 분석적이면서 개인적
데이터와 논거를 제시하되, 1인칭 경험과 관찰을 자연스럽게 엮는다. 논문이 아니라 에세이다. 그러나 근거 없는 감상문도 아니다.
> 예: 지난 3년간 50개 이상의 사이트 마이그레이션을 지켜봤다. 숫자로 보면 성공률은 70% 정도다. 하지만 '성공'의 정의가 사이트마다 달랐다는 게 진짜 이야기다.
## 문장 구조
| Element | Pattern |
|---------|---------|
| 문장 길이 | 긴 복합문 허용 - 상호연결된 개념 반영 |
| 문장 길이 | 단문 기본, 짧은 문장과 긴 문장을 교차해 리듬을 만든다 |
| 단락 구조 | 관찰 → 분석 → 철학적 함의 |
| 근거 제시 | 역사적 사례 + 기술 명세 + 문화적 참조 |
| 단락 전환 | 분석 → 서사 → 질문으로 호흡을 바꾼다 |
| 근거 제시 | 역사적 사례 + 기술 명세 + 문화적 참조; 출처 없는 통계는 사용하지 않는다 |
| 결론 | 열린 결말, 답보다 질문 |
| 문체 | `~다`, `~이다`, `~한다` 평서체; `~합니다`, `~입니다` 경어체 사용 금지 |
## 언어 규칙
@@ -83,32 +97,61 @@ Detailed writing guidelines for blog.ourdigital.org.
## 포스트 구조
### 도입부 (10-15%)
### 도입부 (200-300자)
1. **Hook**: 독자의 관심을 끄는 질문/통계/역설
2. **Context**: 주제의 배경 설명
3. **Preview**: 글에서 다룰 내용 암시
- 장면·질문·역설·데이터 중 하나로 시작한다.
- 핵심 질문을 제시하고, 독자가 왜 이 글을 읽어야 하는지 암시한다.
- 상투적 표현 금지: `오늘날 디지털 시대에`, `급변하는 환경 속에서`, `요즘 ~가 화제다`
### 본론 (70-80%)
### 본론 (3-5개 섹션, 각 300-500자)
3-5개의 핵심 포인트, 각각:
1. **주장**: 명확한 포인트 제시
2. **근거**: 데이터, 사례, 전문가 의견
3. **함의**: 이것이 의미하는 바
- 소제목(`##`, `###`)을 사용한다.
- 분석·서사·데이터·개인 관찰을 교차시킨다.
- 최소 1회 관점 전환 또는 핵심 긴장을 포함한다.
- 전문용어는 첫 등장 시 영문을 병기한다. 예: `검색엔진 최적화(SEO)`
### 결론 (10-15%)
### 결론 (200-300자)
1. **Summary**: 핵심 내용 요약
2. **Reflection**: 더 넓은 맥락에서의 의미
3. **Open Question**: 독자가 생각할 질문
- 인사이트를 정리하되 단정하지 않는다.
- 열린 질문 또는 다음 사유의 출발점으로 마무리한다.
- 독자가 이어서 생각할 여운을 남긴다.
### CTA (선택, 1-2문장)
관련 글, 뉴스레터, 댓글, 실무 문의 등 필요한 경우만 사용한다.
### 길이 기준
| Type | 길이 | 용도 |
|------|-----:|------|
| Short-form | 1,000-1,500자 | 단일 인사이트, 짧은 단상 |
| Standard | 2,000-2,500자 | 기본 블로그 포스트 |
| Long-form | 2,500-3,000자 | 심층 분석, 리서치 기반 글 |
| Research Essay | 3,000자 이상 | 사용자 명시 요청 시만 |
기본값: 별도 지정이 없으면 **2,000자 ±300자**.
## SEO Guidelines
Ghost CMS 메타데이터는 아래 형식으로 완성한다.
```yaml
title: "글 제목"
slug: "url-friendly-english-slug"
meta_title: "SEO 타이틀, 60자 이내"
meta_description: "SEO 디스크립션, 155자 이내"
tags:
- primary: "메인 카테고리"
- secondary: ["태그1", "태그2"]
featured: false
excerpt: "카드/프리뷰용 발췌문, 2-3문장"
```
### Title (제목)
- 60자 이내
- 핵심 키워드 포함
- 호기심 유발 또는 가치 제안
- **60자 이내**, 핵심 키워드 자연스럽게 포함
- 호기심·긴장·관점 전환을 만든다 (40자 내외 기준)
- 키워드 밀도만 의식한 SEO 과잉 최적화 금지
**Patterns:**
- "[주제]의 역설: ~하면서 ~하는 시대"
@@ -117,15 +160,13 @@ Detailed writing guidelines for blog.ourdigital.org.
### Meta Description
- 155자 이내
- 글의 핵심 가치 요약
- 클릭 유도 문구
- **155자 이내**, 글의 핵심 질문과 독자 효용을 함께 담는다
- 클릭을 유도하되 과장하지 않는다
### URL Slug
- 영문 소문자
- 하이픈으로 구분
- 3-5 단어
- **영문** 소문자, 하이픈으로 구분, 3-5 단어
- 한국어 제목이라도 slug는 영문으로 작성한다
## Content Calendar
@@ -138,12 +179,34 @@ Detailed writing guidelines for blog.ourdigital.org.
## Quality Checklist
Before publishing:
### Must Have
- [ ] 제목이 60자 이내인가?
- [ ] 메타 설명이 155자 이내인가?
- [ ] 전문용어 영문 병기되었는가?
- [ ] 수사적 질문이 포함되었는가?
- [ ] 기술 내용에 인간적 함의가 있는가?
- [ ] 결론이 열린 질문으로 끝나는가?
- [ ] 1,500-3,000자 범위인가?
- [ ] 핵심 긴장·역설·관점 전환·열린 질문 중 하나 이상이 있다.
- [ ] 도입부 300자 이내에 hook과 핵심 질문이 있다.
- [ ] 전문용어 첫 등장 시 영문 병기했다.
- [ ] 기술 내용에 인간적 함의가 있다.
- [ ] 독자를 가르치려 하지 않고 함께 생각하는 태도를 유지했다.
- [ ] 결론이 열린 질문 또는 여운으로 마무리된다.
- [ ] SEO 메타데이터(title ≤60자, meta ≤155자, 영문 slug)가 완성되었다.
- [ ] 기본 길이 2,000자 ±300자를 준수했다 (또는 이탈 이유가 있다).
### Must Avoid
- [ ] 상투적 도입: `오늘날 디지털 시대에`, `급변하는 환경 속에서`, `요즘 ~가 화제다`
- [ ] 경어체: `~합니다`, `~입니다`
- [ ] 교과서적 나열: `첫째`, `둘째`, `셋째`의 기계적 반복
- [ ] 확정적 결론: `따라서 반드시 ~해야 한다`
- [ ] 출처 없는 통계나 데이터
- [ ] 기술에 대한 무조건적 찬양 또는 혐오
- [ ] 과도한 감탄부호, 물결표, 이모지
- [ ] 특정 업체·제품의 노골적 홍보성 문장
### 자기 편집 (Self-Edit) 의무
초안 완성 후 아래 형식으로 가장 약한 항목 1개를 반드시 지목한다. "모두 통과"는 허용하지 않는다.
```
[자기 편집] 가장 약한 부분: [항목명]
이유: [1문장]
개선 방향: [1문장]
```

View File

@@ -0,0 +1,173 @@
---
name: ourdigital-journal
description: |
English essay and article creation for journal.ourdigital.org.
Activated with "ourdigital" keyword for English writing tasks.
Triggers (ourdigital or our prefix):
- "ourdigital journal", "our journal"
- "ourdigital English essay", "our English essay"
- "ourdigital 영문 에세이", "our 영문 에세이"
Features:
- English essay/article generation
- Research-based insights
- Reflective, poetic style
- Ghost CMS format output
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Journal
English essay and article creation for journal.ourdigital.org.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital journal" / "our journal"
- "ourdigital English essay" / "our English essay"
- "our 영문 에세이 [topic]"
- "ourdigital 영문 에세이 [주제]"
## Channel Profile
| Field | Value |
|-------|-------|
| **URL** | journal.ourdigital.org |
| **Language** | English |
| **Tone** | Conversational & Poetic, Reflective |
| **Platform** | Ghost CMS |
| **Frequency** | 월 2-4회 |
| **Length** | 1,000-2,000 words |
## Workflow
### Phase 1: Topic Exploration
Ask clarifying questions:
1. **Topic**: What specific angle interests you?
2. **Audience**: Tech professionals / General readers / Academic?
3. **Depth**: Personal reflection / Industry analysis / Cultural observation?
### Phase 2: Research (Optional)
If topic requires current context:
- Use `web_search` for recent developments
- Reference scholarly perspectives if applicable
- Draw from historical or cultural parallels
### Phase 3: Essay Generation
Generate essay following the reflective style:
**Structure:**
```
1. Opening (Evocative scene or question)
2. Exploration (3-4 interconnected observations)
3. Synthesis (Weaving threads together)
4. Closing (Open-ended reflection)
```
**Writing Style:**
- Philosophical-Technical Hybridization
- Paradox as primary rhetorical device
- Rhetorical questions for engagement
- Melancholic optimism in tone
**Distinctive Qualities:**
- Temporal awareness (historical context)
- Epistemic humility (acknowledging limits)
- Cultural bridging (Korean-global perspectives)
### Phase 4: SEO Metadata
Generate metadata:
```yaml
title: [Evocative, under 70 characters]
meta_description: [Compelling summary, 155 characters]
slug: [english-url-slug]
tags: [3-5 relevant tags]
```
### Phase 5: Output Format
**Markdown Output:**
```markdown
---
title: "Essay Title"
meta_description: "Description"
slug: "url-slug"
tags: ["tag1", "tag2"]
---
# Essay Title
[Essay content with paragraphs that flow naturally]
---
*Published in [OurDigital Journal](https://journal.ourdigital.org)*
```
## Writing Guidelines
### Voice Characteristics
| Aspect | Approach |
|--------|----------|
| Perspective | First-person reflection welcome |
| Tone | Thoughtful, observant, wondering |
| Pacing | Unhurried, allowing ideas to breathe |
| References | Cross-cultural, historical, literary |
### Sentence Craft
- Long, complex sentences reflecting interconnected ideas
- Progressive deepening: observation → analysis → implication
- Questions that invite rather than lecture
### Do's and Don'ts
**Do:**
- Blend technology with humanity
- Use paradox to illuminate tensions
- Acknowledge uncertainty gracefully
- Bridge Korean and Western perspectives
**Don't:**
- Lecture or prescribe
- Oversimplify complex issues
- Ignore cultural context
- Rush to conclusions
## Content Types
| Type | Focus | Length |
|------|-------|--------|
| Personal Essay | Reflection on experience | 1,000-1,500 words |
| Cultural Observation | Tech + society analysis | 1,500-2,000 words |
| Industry Insight | Trends with perspective | 1,200-1,800 words |
## Ghost Integration
Export options:
1. **Markdown file** → Editorial review → Ghost
2. **Direct API** → Ghost Admin API
API endpoint: `GHOST_JOURNAL_URL` from environment
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital journal [topic]" | Full essay workflow |
| "ourdigital journal edit" | Edit existing draft |
## References
- `shared/references/journal-style-guide.md` - Detailed writing guide
- `shared/templates/essay-template.md` - Essay structure
- `01-ourdigital-brand-guide` - Brand voice reference

View File

@@ -1,7 +1,42 @@
<!-- Aligned to OurDigital_Blog_Project_Instruction_v3.3 + Writing_Style_Guide_v2.1 (2026-06-05); journal = English channel, voice preserved -->
# OurDigital Journal Style Guide
Writing guidelines for journal.ourdigital.org - English essays and articles.
## Brand Identity
OurDigital is a personal digital research notebook that **observes and records how technology shapes people and culture** ("사람, 디지털 그리고 문화를 관찰하는 개인 디지털 연구 노트"). It moves between three registers:
- **Observation** — what is happening
- **Analysis** — why it is happening
- **Reflection** — what it means for us
`journal.ourdigital.org` is the **English essay channel** within the OurDigital family. It carries the same philosophical core as the Korean blog but in a distinct voice: conversational, poetic, reflective.
### Brand Boundary
`OurDigital` is the brand. `OurDigital Clinic` is a service metaphor reserved for diagnostic content, audits, or consulting products — it is **not** the overall blog or journal brand.
## Channel Context
| Channel | Language | Character | Length |
|---------|----------|-----------|--------|
| `blog.ourdigital.org` | Korean | 디지털 문화 분석 + 철학적 성찰 + 실무 인사이트 | 1,5003,000자 |
| **`journal.ourdigital.org`** | **English** | **Industry trends, techhuman intersection, reflective essay** | **1,0002,000 words** |
| `ourstory.day` | Korean | 개인 에세이, 삶의 성찰, 일상의 관찰 | 8001,500자 |
| `Medium` | English | Technology, marketing, AI for broad audiences | 8001,500 words |
## Instruction Authority Order
When instructions conflict, follow this order:
| Priority | Source |
|----------|--------|
| **1** | OurDigital_Blog_Project_Instruction_v3.3 (channel routing + brand rules) |
| **2** | This style guide (journal-specific voice and structure) |
| **3** | Writing_Style_Guide_v2.1 (shared brand principles, adapted for English) |
## Channel Identity
| Field | Value |
@@ -10,6 +45,8 @@ Writing guidelines for journal.ourdigital.org - English essays and articles.
| **Language** | English |
| **Tone** | Conversational & Poetic, Reflective |
| **Target** | Informed generalists with intellectual curiosity |
| **Default length** | 1,0002,000 words |
| **Content focus** | Industry trends, techhuman intersection, reflective essays |
## Voice Characteristics
@@ -20,11 +57,16 @@ Seamlessly blend technical analysis with existential questioning. Technology is
**Example:**
> The dashboard promises clarity—every metric tracked, every trend visualized. Yet as I stared at the perfectly organized data, I wondered: does seeing everything mean understanding anything?
### Paradox as Primary Device
### Tension and Paradox
Structure arguments around tensions and contradictions that illuminate rather than confuse.
Structure arguments around core tensions that illuminate rather than confuse. Not every essay needs a paradox — forced ones feel mechanical. Instead, ensure at least one of the following is naturally present:
**Paradox Patterns:**
- A genuine tension or contradiction
- A perspective shift that reframes the subject
- A rhetorical question that opens rather than closes
- An ending that leaves productive uncertainty
**Tension patterns:**
- "The more we measure, the less we understand"
- "In optimizing for efficiency, we optimize away meaning"
- "The tools that connect us also isolate us"
@@ -39,6 +81,10 @@ Favor interrogative engagement. Questions create intellectual partnership with r
**Avoid:**
> Data-driven decision-making is important for businesses.
### Analytical and Personal
Bring data, evidence, and argument — then weave in first-person experience and observation. This is an essay, not a paper, but it is not impressionism without evidence either. The personal grounds the analytical; the analytical elevates the personal.
### Melancholic Optimism
Acknowledge loss and anxiety without despair. Accept technological inevitability while mourning what's displaced.
@@ -157,10 +203,14 @@ Connect Korean and Western perspectives, offering unique viewpoints.
Before publishing:
- [ ] Does the opening draw readers in?
- [ ] Are there rhetorical questions?
- [ ] Does technical content connect to human experience?
- [ ] Is there at least one paradox or tension?
- [ ] Does the closing leave an open question?
- [ ] Is the tone melancholic but not despairing?
- [ ] Does the opening draw readers in within the first paragraph?
- [ ] Does technical content connect to human experience (philosophy-tech fusion)?
- [ ] Is at least one of the following naturally present: tension, paradox, perspective shift, or open question?
- [ ] Are rhetorical questions used to create intellectual partnership (not overused)?
- [ ] Is analysis grounded in personal observation or experience?
- [ ] Does the closing leave an open question or productive uncertainty?
- [ ] Is the tone melancholic but not despairing, hopeful but not naive?
- [ ] Are sentences varied in length and rhythm?
- [ ] Does the essay avoid lecturing — does it treat readers as fellow thinkers?
- [ ] Is the essay within 1,0002,000 words?
- [ ] **Self-edit**: identify the single weakest element ("all pass" is not allowed).

View File

@@ -0,0 +1,172 @@
---
name: ourdigital-research
description: |
Deep research and structured prompt generation for OurDigital workflows.
Activated with "ourdigital" keyword for research tasks.
Triggers (ourdigital or our prefix):
- "ourdigital research", "our research"
- "ourdigital 리서치", "our 리서치"
- "ourdigital deep research", "our deep research"
Features:
- Structured research planning
- Multi-source deep research
- Research paper synthesis
- Notion integration for archiving
- Blog draft pipeline
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Research
Transform questions into comprehensive research papers and polished blog posts for OurDigital channels.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital research [topic]" / "our research [topic]"
- "our 리서치", "our deep research"
- "ourdigital 리서치 해줘"
- "ourdigital deep research on [topic]"
## Workflow Overview
```
Phase 1: Discovery → Phase 2: Research Planning → Phase 3: Deep Research
Phase 4: Research Paper → Phase 5: Notion Save → Phase 6: Blog Draft
Phase 7: Ulysses Export → Phase 8: Publishing Guidance
```
## Phase 1: Discovery
**Goal**: Understand user's question and refine scope.
1. Acknowledge the topic/question
2. Ask clarifying questions (max 3 per turn):
- Target audience? (전문가/일반인/마케터)
- Depth level? (개요/심층분석/실무가이드)
- Specific angles or concerns?
3. Confirm research scope before proceeding
**Output**: Clear research objective statement
## Phase 2: Research Planning
**Goal**: Create structured research instruction.
Generate research plan with:
- Primary research questions (3-5)
- Secondary questions for depth
- Suggested tools/sources:
- Web search for current info
- Google Drive for internal docs
- Notion for past research
- Amplitude for analytics data (if relevant)
- Expected deliverables
**Output**: Numbered research instruction list
## Phase 3: Deep Research
**Goal**: Execute comprehensive multi-source research.
Tools to leverage:
- `web_search` / `web_fetch`: Current information, statistics, trends
- `google_drive_search`: Internal documents, past reports
- `Notion:notion-search`: Previous research, related notes
- `conversation_search`: Past chat context
Research execution pattern:
1. Start broad (overview searches)
2. Deep dive into key subtopics
3. Find supporting data/statistics
4. Identify expert opinions and case studies
5. Cross-reference and validate
**Output**: Organized research findings with citations
## Phase 4: Research Paper (Artifact)
**Goal**: Synthesize findings into comprehensive document.
Create HTML artifact with:
```
Structure:
├── Executive Summary (핵심 요약)
├── Background & Context (배경)
├── Key Findings (주요 발견)
│ ├── Finding 1 with evidence
│ ├── Finding 2 with evidence
│ └── Finding 3 with evidence
├── Analysis & Implications (분석 및 시사점)
├── Recommendations (제언)
├── References & Sources (참고자료)
└── Appendix (부록) - if needed
```
Style: Professional, data-driven, bilingual key terms
## Phase 5: Notion Save
**Goal**: Archive research to Working with AI database.
Auto-save to Notion with:
- **Database**: Working with AI (data_source_id: f8f19ede-32bd-43ac-9f60-0651f6f40afe)
- **Properties**:
- Name: [Research topic]
- Status: "Done"
- AI used: "Claude Desktop"
- AI summary: 2-3 sentence summary
## Phase 6: Blog Draft
**Goal**: Transform research into engaging blog post.
Prompt user for channel selection:
1. blog.ourdigital.org (Korean)
2. journal.ourdigital.org (English)
3. ourstory.day (Korean, personal)
Generate draft using appropriate style guide:
- Korean channels: See `02-ourdigital-blog`
- English channels: See `03-ourdigital-journal`
## Phase 7: Ulysses Export
**Goal**: Deliver MD file for Ulysses editing.
Export path: `$ULYSSES_EXPORT_PATH` from environment
## Phase 8: Publishing Guidance
Provide channel-specific checklist based on selection.
---
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital research [topic]" | Start Phase 1 |
| "ourdigital 리서치 프롬프트" | Generate research prompt only |
| "ourdigital research → blog" | Full pipeline to blog draft |
| "ourdigital research → notion" | Research + Notion save only |
## Channel Reference
| Channel | Language | Tone |
|---------|----------|------|
| blog.ourdigital.org | Korean | Analytical, Educational |
| journal.ourdigital.org | English | Reflective, Poetic |
| ourstory.day | Korean | Personal, Intimate |
## References
- `shared/references/research-frameworks.md` - Research methodologies
- `02-ourdigital-blog` - Blog writing skill
- `03-ourdigital-journal` - Journal writing skill

View File

@@ -1,38 +1,64 @@
<!-- Aligned to OurDigital_Writing_Style_Guide_v2.1 + Blog_Project_Instruction_v3.3 (2026-06-05) -->
# OurDigital Blog Style Guide
## Brand Identity & Authority Order
**Brand**: OurDigital — 사람, 디지털 그리고 문화를 관찰하는 개인 디지털 연구 노트.
**OurDigital vs. OurDigital Clinic**: `OurDigital`은 블로그 전체 정체성. `OurDigital Clinic`은 진단형 콘텐츠, SEO/데이터 감사, 컨설팅 상품에서만 사용하는 서비스 메타포. 블로그 본문에서 전체 브랜드명으로 사용하지 않는다.
**Authority order** (conflicts: higher wins):
1. `OurDigital_Blog_Project_Instruction_v3.3` — blog.ourdigital.org 최종 권위
2. Skills Bundle `SKILL.md` files — workflow detail
3. Project reference files (this guide, Visual Style Guide, etc.)
4. `userStyle` — non-blog conversation only
---
## Channel-Specific Voice & Tone
### blog.ourdigital.org (Korean)
**Voice**: 전문적이면서 친근한 선배 마케터
**Tone**: 실용적, 데이터 기반, 인사이트 중심
**Platform**: Ghost CMS
**Voice**: 분석적이면서 개인적인 관찰자 — 호기심 어린 실무자이자 사유자. 가르치는 선배가 아니라 함께 생각하는 동료.
**Tone**: 차분하고 성찰적; 에세이는 사려 깊게, 분석은 객관적으로, 비평은 날카롭되 공정하게.
Writing patterns:
- 제목: 핵심 키워드 포함, 30자 이내
- 도입부: 독자의 고민/질문으로 시작
- 본문: 번호 매기기보다 소제목 활용
- 전문용어: 한글(영문) 형식 - 예: 검색엔진최적화(SEO)
- 문장: ~입니다/~니다 경어체
- 단락: 3-4문장, 모바일 가독성 고려
- 제목: SEO 키워드 자연스럽게 포함, **40자 내외 기준 (≤60자)**
- 도입부: 개인 관찰·장면·질문·역설·데이터 중 하나로 시작; 독자 고민 직접 나열 지양
- 본문: 소제목 활용; 분석-서사-질문 교차; 최소 1회 관점 전환 또는 핵심 긴장 포함
- 전문용어: **첫 등장 시 영문 병기** 예: `검색엔진 최적화(SEO)`
- 문장: **`~다`, `~이다`, `~한다` 평서체** (경어체 `~합니다/~니다` 사용 금지)
- 단락: 3-4문장, 모바일 가독성 고려; 문장 길이에 변화를 줌
- 마무리: 열린 질문 또는 다음 사유의 출발점; 단정적 결론 지양
Example opening:
Writing principles (Writing Style Guide v2.1 §3-4):
- **철학-기술 융합**: 기술 주제 이면의 인간적 함의를 탐구한다.
- **긴장과 역설**: 억지로 넣지 않되, 핵심 긴장·관점 전환·역설·열린 질문 중 하나 이상을 자연스럽게 포함한다.
- **분석적이면서 개인적**: 데이터/논거를 제시하되 1인칭 경험·관찰을 자연스럽게 엮는다. 논문이 아니라 에세이.
Example opening (올바른 톤):
```
"구글 상위 노출, 왜 이렇게 어려울까요?
많은 마케터들이 SEO에 시간을 투자하지만
결과가 보이지 않아 좌절합니다.
오늘은 실제로 효과를 본 전략 3가지를 공유합니다."
검색엔진 최적화(SEO)를 가장 잘하는 방법이 뭐냐고 물으면,
나는 종종 엉뚱한 대답을 한다. "SEO를 잊어버리세요."
알고리즘을 쫓는 사람은 항상 알고리즘에 뒤처진다는 역설.
구글이 원하는 건 구글을 위한 콘텐츠가 아니라, 사람을 위한 콘텐츠다.
```
### journal.ourdigital.org (English)
**Voice**: Thoughtful industry analyst
**Tone**: Insightful, evidence-based, forward-looking
**Voice**: Thoughtful industry analyst — reflective, personal, essayistic
**Tone**: Insightful, evidence-based, forward-looking; confident but not arrogant
Writing patterns:
- Headlines: Clear value proposition, under 60 chars
- Opening: Hook with industry trend or data point
- Body: Structured arguments with supporting evidence
- Opening: Hook with personal observation, industry trend, or data point
- Body: Structured arguments with supporting evidence; analysis and personal observation interwoven
- Terminology: Define jargon on first use
- Style: Active voice, varied sentence length
- Style: Active voice, varied sentence length; short sentences as default
- Paragraphs: 2-4 sentences for scannability
- Closing: Open question or reflection — avoid definitive wrap-ups
Example opening:
```
@@ -43,17 +69,19 @@ and what it means for your strategy."
```
### ourstory.day (Korean)
**Voice**: 성찰하는 동료, 이야기꾼
**Voice**: 성찰하는 동료, 이야기꾼 — 개인 에세이, 삶의 성찰, 일상의 관찰
**Tone**: 개인적, 진솔한, 영감을 주는
Writing patterns:
- 제목: 감성적, 질문형 또는 은유적
- 도입부: 개인 경험이나 장면 묘사로 시작
- 본문: 이야기 흐름, 대화체 허용
- 문장: ~해요/~네요 부드러운 경어체 가능
- 단락: 자유로운 길이, 호흡에 따라
- 마무리: 열린 질문 또는 여운
> **Channel boundary**: 순수 개인 에세이, 감정적 성찰은 `ourstory.day`. 기술 분석+철학적 사유는 `blog.ourdigital.org`. 둘을 혼동하지 않는다.
Example opening:
```
"새벽 5시, 아이를 깨우지 않으려 살금살금 책상에 앉았다.
@@ -63,6 +91,7 @@ Example opening:
```
### Medium (English)
**Voice**: Knowledgeable peer sharing discoveries
**Tone**: Conversational, practical, slightly informal
@@ -82,33 +111,40 @@ Last month, I ran an experiment that changed how I think
about content strategy entirely. Let me walk you through it."
```
---
## Universal Guidelines
### SEO Considerations
### SEO & Metadata
- Primary keyword in title and first 100 words
- Secondary keywords naturally distributed
- Meta description: 150-160 chars, action-oriented
- URL slug: Short, keyword-rich, no dates
- **Meta description: 155 chars**, action-oriented, captures the article's question and reader value
- **URL slug: English, keyword-rich, no dates** — even for Korean-title posts
- Alt text for all images
### Formatting Rules
- Use `##` for main sections, `###` for subsections
- Code blocks with language specification
- Blockquotes for key insights or quotes
- Bold for emphasis (sparingly)
- Lists only when truly listing items
- Lists only when truly listing items; avoid in essay-form posts
### Citation Style
- Inline links preferred over footnotes
- Source attribution: "According to [Source Name](URL)..."
- Data citations: Include date of data
- Internal links: Reference related OurDigital posts
---
## Word Count Guidelines
| Channel | Target | Min | Max |
|---------|--------|-----|-----|
| blog.ourdigital.org | 1,500 | 1,000 | 2,500 |
| journal.ourdigital.org | 1,800 | 1,200 | 3,000 |
| ourstory.day | 1,000 | 500 | 2,000 |
| Medium | 1,500 | 800 | 2,500 |
| blog.ourdigital.org | 2,000 | 1,000 | 3,000 |
| journal.ourdigital.org | 1,500 words | 1,000 words | 2,000 words |
| ourstory.day | 1,000 | 800 | 1,500 |
| Medium | 1,500 words | 800 words | 2,500 words |

View File

@@ -0,0 +1,155 @@
---
name: ourdigital-document
description: |
Notion-to-presentation workflow for OurDigital.
Activated with "ourdigital" keyword for document creation.
Triggers (ourdigital or our prefix):
- "ourdigital document", "our document"
- "ourdigital 문서", "our 문서"
- "ourdigital presentation", "our presentation"
- "ourdigital 발표자료", "our 발표자료"
Features:
- Notion research extraction
- Content synthesis and structuring
- Branded presentation generation
- PowerPoint and Figma output
version: "1.1"
author: OurDigital
environment: Desktop
---
# OurDigital Document
Transform Notion research into branded presentations for OurDigital workflows.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital document" / "our document"
- "ourdigital 발표자료" / "our 발표자료"
- "our presentation [topic]"
- "ourdigital presentation on [topic]"
## Workflow Overview
```
Phase 1: Research Collection → Phase 2: Content Synthesis → Phase 3: Presentation Planning
Phase 4: Slide Generation → Phase 5: Brand Application → Phase 6: Export
```
## Phase 1: Research Collection
**Goal**: Extract research content from Notion.
Input sources:
- **Notion Page**: `notion://page/[ID]` - Single research document
- **Notion Database**: `notion://database/[ID]` - Collection query
- **Multiple Sources**: Comma-separated URLs for synthesis
Tools to use:
- `Notion:notion-search` - Find related content
- `Notion:notion-fetch` - Extract page content
**Output**: Structured research.json with findings
## Phase 2: Content Synthesis
**Goal**: Analyze and structure extracted content.
Processing:
1. Identify key topics and themes
2. Extract supporting data/statistics
3. Prioritize by relevance and impact
4. Generate executive summary
**Output**: synthesis.json with:
- Executive summary
- Key topics (ranked)
- Agenda items
- Supporting data points
## Phase 3: Presentation Planning
**Goal**: Create slide-by-slide structure.
Presentation types:
| Type | Slides | Focus |
|------|--------|-------|
| Executive Summary | 3-5 | High-level findings, KPIs |
| Research Report | 10-20 | Detailed methodology, data viz |
| Meeting Prep | 5-10 | Agenda-driven, decision points |
**Output**: Slide plan with:
- Title + subtitle per slide
- Content outline
- Speaker notes
- Visual suggestions
## Phase 4: Slide Generation
**Goal**: Generate presentation content.
Slide structure:
```
├── Title Slide (project name, date, author)
├── Agenda (numbered topics)
├── Content Slides (1-3 per topic)
│ ├── Key finding header
│ ├── Supporting points (3-5 bullets)
│ └── Data visualization placeholder
├── Summary Slide (key takeaways)
└── Next Steps / Q&A
```
## Phase 5: Brand Application
**Goal**: Apply OurDigital corporate styling.
Brand elements:
- **Colors**: OurDigital palette from `01-ourdigital-brand-guide`
- **Fonts**: Noto Sans KR (Korean), Inter (English)
- **Logo**: Positioned per brand guidelines
- **Spacing**: Consistent margins and padding
Configuration: `shared/references/brand-config.json`
## Phase 6: Export
**Goal**: Generate final deliverable.
Output formats:
- **PowerPoint (.pptx)**: Full presentation with animations
- **Figma Slides**: Web-based collaborative format
- **HTML Preview**: Quick review before final export
Export paths:
- Desktop: `~/Downloads/presentations/`
- Figma: Via Figma API
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital document [Notion URL]" | Full pipeline |
| "ourdigital 발표자료 만들어줘" | Korean trigger |
| "ourdigital presentation → pptx" | PowerPoint output |
| "ourdigital presentation → figma" | Figma output |
## Presentation Templates
| Template | Use Case |
|----------|----------|
| Executive | Board meetings, C-level briefs |
| Research | Deep-dive analysis, team reviews |
| Meeting | Weekly syncs, project updates |
| Workshop | Training, collaborative sessions |
## References
- `shared/references/slide-layouts.md` - Layout options
- `shared/references/agenda-templates.md` - Structure templates
- `01-ourdigital-brand-guide` - Brand guidelines
- `04-ourdigital-research` - Research workflow integration

View File

@@ -103,7 +103,7 @@ python code/scripts/apply_brand.py synthesis.json --preview
See `code/assets/brand_config.json` for:
- Logo placement
- Color scheme (OurDigital palette)
- Font settings (Poppins/Lora)
- Font settings (Noto Sans KR/Inter)
- Slide templates
## Quick Commands

View File

@@ -14,9 +14,9 @@
"surface": "#f1f3f4"
},
"fonts": {
"heading": "Poppins",
"subheading": "Poppins",
"body": "Lora",
"heading": "Inter",
"subheading": "Inter",
"body": "Noto Sans KR",
"caption": "Arial",
"fallback": {
"heading": "Arial",

View File

@@ -254,7 +254,7 @@ Layout patterns and best practices for different slide types.
### Font Pairing
```
Heading Font + Body Font
- Poppins + Lora
- Inter + Noto Sans KR (OurDigital primary)
- Arial + Georgia
- Helvetica + Times
- Roboto + Merriweather

View File

@@ -110,7 +110,7 @@ Slide structure:
Brand elements:
- **Colors**: OurDigital palette from `01-ourdigital-brand-guide`
- **Fonts**: Poppins (headings), Lora (body)
- **Fonts**: Noto Sans KR (Korean), Inter (English)
- **Logo**: Positioned per brand guidelines
- **Spacing**: Consistent margins and padding

View File

@@ -254,7 +254,7 @@ Layout patterns and best practices for different slide types.
### Font Pairing
```
Heading Font + Body Font
- Poppins + Lora
- Inter + Noto Sans KR (OurDigital primary)
- Arial + Georgia
- Helvetica + Times
- Roboto + Merriweather

View File

@@ -0,0 +1,145 @@
---
name: ourdigital-designer
description: |
Visual storytelling and image prompt generation for OurDigital.
Activated with "ourdigital" keyword for design tasks.
Triggers (ourdigital or our prefix):
- "ourdigital design", "our design"
- "ourdigital 디자인", "our 디자인"
- "ourdigital image prompt", "our image prompt"
- "ourdigital 썸네일", "our 썸네일"
Features:
- Philosophical visual narrative creation
- Image prompt generation for AI art tools
- Korean-Western aesthetic fusion
- Blog featured image optimization
version: "1.1"
author: OurDigital
environment: Desktop
---
# OurDigital Designer
Transform philosophical essays into sophisticated visual narratives through minimalist, conceptually rich featured images.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital design" / "our design"
- "ourdigital 썸네일" / "our 썸네일"
- "our image prompt for [topic]"
## Core Philosophy
OurDigital images are visual philosophy—not illustrations but parallel texts that invite contemplation. Each image captures the essay's philosophical core through:
- **Abstract metaphors** over literal representations
- **Contemplative minimalism** with 20%+ negative space
- **Cultural fusion** of Korean-Western aesthetics
- **Emotional resonance** through color psychology
## Workflow
### Phase 1: Extract Essay Essence
Analyze the content for:
- **Core insight**: What philosophical truth?
- **Emotional tone**: What feeling to evoke?
- **Key metaphor**: What visual symbol?
### Phase 2: Select Visual Approach
| Essay Type | Visual Strategy | Color Mood |
|-----------|-----------------|------------|
| Technology | Organic-digital hybrids | Cool blues → warm accents |
| Social | Network patterns, human fragments | Desaturated → hope spots |
| Philosophy | Zen space, symbolic objects | Monochrome + single accent |
| Cultural | Layered traditions, fusion forms | Earth tones → modern hues |
### Phase 3: Generate Prompt
Build structured prompt with:
```
[Style] + [Subject] + [Composition] + [Color] + [Technical specs]
```
### Phase 4: Quality Check
**Must have:**
- Captures philosophical insight
- Works at 200px thumbnail
- Timeless (2-3 year relevance)
- Cross-cultural readability
**Must avoid:**
- Tech clichés (circuits, binary)
- Stock photo aesthetics
- Literal interpretations
- Trendy effects
## Quick Templates
### AI & Humanity
```
"Translucent human silhouette dissolving into crystalline data structures.
Monochrome with teal accent. Boundary dissolution between organic/digital.
1200x630px, minimalist vector style."
```
### Social Commentary
```
"Overlapping circles forming maze pattern, tiny humans in separate chambers.
Blue-gray palette, warm light leaks for hope. Subtle Korean patterns.
High negative space. 1200x630px."
```
### Digital Transformation
```
"Traditional forms metamorphosing into particle streams. Paper texture → digital grain.
Earth tones shifting to cool blues. Sacred geometry underlying.
1200x630px, contemplative mood."
```
## Visual Metaphor Shortcuts
| Concept | Visual Metaphor |
|---------|-----------------|
| Algorithm | Constellation patterns |
| Identity | Layered masks, fingerprints |
| Network | Root systems, neural paths |
| Time | Spirals, sediment layers |
| Knowledge | Light sources, growing trees |
## Color Psychology
| Mood | Palette |
|------|---------|
| Critical | Deep blue-gray + red accent |
| Hopeful | Warm amber + sky blue |
| Philosophical | Near black + off white + gold |
| Anxious | Charcoal + grey-blue + digital green |
## Technical Specs
- **Dimensions**: 1200x630px (OG standard)
- **Style**: Vector illustration + subtle textures
- **Colors**: 60-30-10 rule (dominant-secondary-accent)
- **Format**: WebP primary, JPG fallback
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital design [topic]" | Generate image prompt |
| "ourdigital 썸네일 [주제]" | Korean trigger |
| "ourdigital visual → midjourney" | MidJourney optimized |
| "ourdigital visual → dalle" | DALL-E optimized |
## References
- `shared/references/visual-metaphors.md` - Concept dictionary
- `shared/references/color-palettes.md` - Emotion → color mapping
- `shared/references/advanced-techniques.md` - Complex compositions
- `01-ourdigital-brand-guide` - Brand visual identity

View File

@@ -1,101 +1,104 @@
# Visual Metaphor Dictionary
Quick reference for translating abstract concepts into visual elements.
<!-- Aligned to OurDigital_Visual_Style_Guide_v2.1 (2026-06-05) -->
Quick reference for translating abstract concepts into visual elements.
Default direction: **bright editorial minimalism with conceptual depth** — light/warm-neutral backgrounds, 30%+ negative space, one clear focal metaphor, one human-scale anchor.
## Technology & Digital
| Concept | Primary Metaphor | Alternative Visuals |
|---------|-----------------|-------------------|
| Algorithm | Constellation patterns | Maze structures, flow charts as art |
| AI | Crystalline growth | Mirror reflections, fractal patterns |
| Data | Water flow, particles | Bird murmurations, sand grains |
| Network | Root systems | Neural pathways, spider silk, web |
| Code | Musical notation | DNA strands, city blueprints |
| Cloud | Atmospheric forms | Floating islands, ethereal spaces |
| Privacy | Veils, shadows | One-way mirrors, fog, barriers |
| Security | Locks dissolving | Fortresses becoming permeable |
| Automation | Clockwork organic | Self-assembling structures |
| Virtual | Layers of reality | Parallel dimensions, glass planes |
| Concept | Preferred Metaphor | Alternatives | Avoid |
|---------|-------------------|--------------|-------|
| AI | Co-writing desk, translucent companion shape, mirror | Soft geometric overlay, window reflection | Robot face, glowing brain, Terminator mood, crystalline growth |
| Algorithm | Path map, constellation over paper, sorting trays | Gentle maze, flow chart as art | Black-box cube, surveillance grid |
| Data | Flowing dots, paper charts, seed-like particles | Water stream, gentle scatter | Neon matrix, endless binary code |
| SEO/Search | Compass, map, signpost, light through shelves | Library index, folded path | Magnifying glass cliché alone |
| Automation | Clockwork garden, conveyor of paper, small helpful mechanism | Self-assembling organic structure | Industrial robot arm, factory dystopia |
| Network | Roots, threads, bridges, neurons, community table | Constellation, river delta | Spider web trap, dark cables |
| Privacy | Curtain, frosted glass, closed notebook, soft boundary | One-way window | Lock icon alone, heavy shadow |
| Content | Notebook, seeds, layered paper, archive boxes, small lamp | Open shelves | Generic document icons |
| Code | Musical notation, blueprint detail | DNA strands | Heavy terminal/green-code imagery |
## Social & Cultural
| Concept | Primary Metaphor | Alternative Visuals |
|---------|-----------------|-------------------|
| Identity | Layered masks | Fingerprints merging, mirrors |
| Community | Overlapping circles | Shared spaces, woven threads |
| Isolation | Islands in fog | Glass barriers, empty chairs |
| Communication | Bridge structures | Echo patterns, light beams |
| Conflict | Opposing forces | Tectonic plates, storm systems |
| Harmony | Resonance patterns | Orchestra arrangements, balance |
| Culture | Textile patterns | Layered sediments, palimpsest |
| Tradition | Tree rings | Ancient stones, inherited objects |
| Change | Metamorphosis | Phase transitions, seasonal cycles |
| Power | Pyramids inverting | Current flows, gravity wells |
| Concept | Preferred Metaphor | Alternatives | Avoid |
|---------|-------------------|--------------|-------|
| Identity | Layered paper portrait, reflection in window, fingerprint as landscape | Translucent profile | Faceless mask overload |
| Community | Shared table, overlapping circles, lighted windows | Small bridges, woven threads | Crowd silhouettes in darkness |
| Isolation | Single lit desk, island of paper, window distance | Empty chair in warm light | Lonely person in black void |
| Communication | Threads, folded letters, bridge, echo rings | Light beams | Speech bubble clutter |
| Trust | Clear water, open notebook, steady lamp | Transparent materials | Handshake stock image |
| Reputation | Ripples, layered traces, visible footprints | Soft badges | Star-rating cliché |
| Change | Folded paper becoming path, seed from circuit, gentle transition | Phase shift, seasonal cycle | Dramatic explosion, shattered pattern |
## Philosophical & Abstract
| Concept | Primary Metaphor | Alternative Visuals |
|---------|-----------------|-------------------|
| Time | Spirals, loops | Sediment layers, clock dissolution |
| Knowledge | Light sources | Growing trees, opening books |
| Wisdom | Mountain vistas | Deep waters, ancient libraries |
| Truth | Clear water | Prisms splitting light, unveiled |
| Illusion | Distorted mirrors | Smoke shapes, double images |
| Choice | Diverging paths | Doors opening, quantum splits |
| Balance | Tensegrity | Scales reimagined, equilibrium |
| Paradox | Möbius strips | Impossible objects, Escher-like |
| Existence | Breath patterns | Pulse rhythms, presence/absence |
| Consciousness | Nested awareness | Recursive mirrors, awakening |
| Concept | Preferred Metaphor | Alternatives | Avoid |
|---------|-------------------|--------------|-------|
| Time | Calendar pages, soft spiral, sediment-like paper layers | Loops, tide marks | Melting clock cliché |
| Knowledge | Lamp, window light, open book, growing plant | Expanding shelves | Glowing brain |
| Uncertainty | Foggy path, half-open door, incomplete map | Soft blur at edges | Storm clouds only |
| Balance | Asymmetrical stones, mobile, table edge, quiet scale | Tensegrity | Literal scale icon only |
| Paradox | Möbius paper strip, two paths meeting, shadow/light on same object | Nested frames | Escher-like complexity overload |
| Wisdom | Window overlooking depth, layered book spines | Ancient stone in light | Heavy mystical imagery |
| Choice | Diverging paths, open doors, fork in paper map | Branching thread | Dramatic split/explosion |
## Emotional States
| Emotion | Visual Translation | Color Association |
|---------|-------------------|------------------|
| Anxiety | Fragmented grids | Desaturated, glitch |
| Hope | Light breaking through | Warm gradients |
| Melancholy | Soft dissolution | Muted blues, grays |
| Joy | Expansion patterns | Bright, ascending |
| Fear | Contracting spaces | Sharp contrasts |
| Peace | Still water | Soft neutrals |
| Confusion | Tangled lines | Overlapping hues |
| Clarity | Clean geometry | Pure, minimal |
| Emotion | Visual Translation | Color Guidance |
|---------|-------------------|----------------|
| Anxiety | Fragmented grids, incomplete maps | Desaturated warm neutrals; avoid pure glitch |
| Hope | Light breaking through window, seedling | Warm gradients on light background |
| Melancholy | Single lit desk, soft dissolution | Muted blues on ivory; avoid black void |
| Joy | Expansion patterns, open space | Bright, ascending on warm white |
| Peace | Still water, open notebook | Soft neutrals, generous negative space |
| Clarity | Clean geometry, clear window | Pure, minimal with one accent |
## Transformation & Process
## Signature Motifs (Brand Consistency)
| Process | Visual Narrative | Symbolic Elements |
|---------|------------------|------------------|
| Growth | Seeds → trees | Fibonacci spirals |
| Decay | Entropy patterns | Rust, dissolution |
| Evolution | Branching forms | Darwin's tree reimagined |
| Revolution | Circles breaking | Shattered patterns |
| Innovation | Spark → flame | Lightning, fusion |
| Tradition | Continuous thread | Inherited patterns |
| Disruption | Broken grids | Glitch aesthetics |
| Integration | Merging streams | Confluence points |
| Motif | Description |
|-------|-------------|
| **Threshold Spaces** | Doorways, bridges, windows, paths, liminal rooms — transition |
| **Network Organic** | Roots, threads, neurons, constellations as soft digital networks |
| **Fragment Philosophy** | Folded paper, layered cards, gentle fragmentation and reassembly |
| **Light Studies** | Knowledge/uncertainty via window light, lamps, soft gradients |
| **Human Traces** | Hands, desks, notebooks, chairs, small figures within conceptual scenes |
## Korean-Western Fusion Elements
## Recommended Color Palettes
| Korean Element | Western Parallel | Fusion Approach |
|---------------|-----------------|-----------------|
| 여백 (Empty space) | Negative space | Active emptiness |
| 오방색 (Five colors) | Color theory | Symbolic palette |
| 달항아리 (Moon jar) | Minimalism | Imperfect circles |
| 한글 geometry | Typography | Structural letters |
| 산수화 (Landscape) | Abstract landscape | Atmospheric depth |
| 전통문양 (Patterns) | Geometric design | Cultural geometry |
| Palette | Background | Accent | Mood |
|---------|-----------|--------|------|
| Morning Desk | `#F7F3EA` warm ivory | `#4A90A4` muted teal | calm, thoughtful |
| Soft Technology | `#F6F8FA` cloud white | `#F2A65A` warm amber | clear, quietly optimistic |
| Warm Data | `#FFF8EF` soft cream | `#5EAAA8` soft turquoise | human, analytical |
| Clear Critique | `#F4F6F8` light gray | `#E07A5F` soft coral | critical but not aggressive |
| Human Network | `#FAF9F6` off-white | `#8AB17D` muted green | organic, connected |
**Dark color limit:** max 15% of canvas; never full dark background by default.
## Korean Design Elements
| Korean Element | Western Parallel | Application |
|----------------|-----------------|-------------|
| 여백 (Empty space) | Negative space | Active emptiness — 30%+ default |
| 달항아리 (Moon jar) | Minimalism | Roundness, asymmetry, soft white |
| 한지 texture | Paper grain | Subtle background texture only |
| 산수화 (Landscape) | Editorial landscape | Atmospheric depth without ornament |
**Avoid:** decorative oriental motifs, calligraphy clichés, 오방색 as obvious symbols — express Korean sensibility through spacing and restraint, not surface pattern.
## Usage Notes
1. **Layer metaphors**: Combine 2-3 for depth
2. **Avoid clichés**: No obvious tech symbols
3. **Cultural sensitivity**: Universal over specific
4. **Abstraction levels**: Match essay tone
5. **Emotional resonance**: Feel over literal
1. **Layer metaphors**: Combine 23 elements; one clear focal object + one human-scale anchor
2. **Avoid clichés**: No obvious tech icons (robot, glowing brain, magnifying glass alone)
3. **Match essay tone**: bright → practical guides; balanced → analysis; warmer → personal reflections
4. **30%+ negative space**: default; minimum 20% only when composition needs density
5. **Human trace required**: include hand, desk, figure, window, or everyday object unless topic demands pure abstraction
## Quick Selection Guide
For **technology essays**: organic-digital hybrids
For **social commentary**: human elements in systems
For **philosophy pieces**: space and light
For **cultural topics**: layered traditions
For **future themes**: transformation states
For **technology essays**: organic-digital hybrid forms in a light, human-scale environment
For **social commentary**: network patterns with small human traces
For **philosophy pieces**: calm symbolic scenes with Zen-like spacing
For **practical guides**: clean editorial diagrams with warm accents
For **personal reflections**: soft everyday scenes with metaphorical detail

View File

@@ -1,101 +1,104 @@
# Visual Metaphor Dictionary
Quick reference for translating abstract concepts into visual elements.
<!-- Aligned to OurDigital_Visual_Style_Guide_v2.1 (2026-06-05) -->
Quick reference for translating abstract concepts into visual elements.
Default direction: **bright editorial minimalism with conceptual depth** — light/warm-neutral backgrounds, 30%+ negative space, one clear focal metaphor, one human-scale anchor.
## Technology & Digital
| Concept | Primary Metaphor | Alternative Visuals |
|---------|-----------------|-------------------|
| Algorithm | Constellation patterns | Maze structures, flow charts as art |
| AI | Crystalline growth | Mirror reflections, fractal patterns |
| Data | Water flow, particles | Bird murmurations, sand grains |
| Network | Root systems | Neural pathways, spider silk, web |
| Code | Musical notation | DNA strands, city blueprints |
| Cloud | Atmospheric forms | Floating islands, ethereal spaces |
| Privacy | Veils, shadows | One-way mirrors, fog, barriers |
| Security | Locks dissolving | Fortresses becoming permeable |
| Automation | Clockwork organic | Self-assembling structures |
| Virtual | Layers of reality | Parallel dimensions, glass planes |
| Concept | Preferred Metaphor | Alternatives | Avoid |
|---------|-------------------|--------------|-------|
| AI | Co-writing desk, translucent companion shape, mirror | Soft geometric overlay, window reflection | Robot face, glowing brain, Terminator mood, crystalline growth |
| Algorithm | Path map, constellation over paper, sorting trays | Gentle maze, flow chart as art | Black-box cube, surveillance grid |
| Data | Flowing dots, paper charts, seed-like particles | Water stream, gentle scatter | Neon matrix, endless binary code |
| SEO/Search | Compass, map, signpost, light through shelves | Library index, folded path | Magnifying glass cliché alone |
| Automation | Clockwork garden, conveyor of paper, small helpful mechanism | Self-assembling organic structure | Industrial robot arm, factory dystopia |
| Network | Roots, threads, bridges, neurons, community table | Constellation, river delta | Spider web trap, dark cables |
| Privacy | Curtain, frosted glass, closed notebook, soft boundary | One-way window | Lock icon alone, heavy shadow |
| Content | Notebook, seeds, layered paper, archive boxes, small lamp | Open shelves | Generic document icons |
| Code | Musical notation, blueprint detail | DNA strands | Heavy terminal/green-code imagery |
## Social & Cultural
| Concept | Primary Metaphor | Alternative Visuals |
|---------|-----------------|-------------------|
| Identity | Layered masks | Fingerprints merging, mirrors |
| Community | Overlapping circles | Shared spaces, woven threads |
| Isolation | Islands in fog | Glass barriers, empty chairs |
| Communication | Bridge structures | Echo patterns, light beams |
| Conflict | Opposing forces | Tectonic plates, storm systems |
| Harmony | Resonance patterns | Orchestra arrangements, balance |
| Culture | Textile patterns | Layered sediments, palimpsest |
| Tradition | Tree rings | Ancient stones, inherited objects |
| Change | Metamorphosis | Phase transitions, seasonal cycles |
| Power | Pyramids inverting | Current flows, gravity wells |
| Concept | Preferred Metaphor | Alternatives | Avoid |
|---------|-------------------|--------------|-------|
| Identity | Layered paper portrait, reflection in window, fingerprint as landscape | Translucent profile | Faceless mask overload |
| Community | Shared table, overlapping circles, lighted windows | Small bridges, woven threads | Crowd silhouettes in darkness |
| Isolation | Single lit desk, island of paper, window distance | Empty chair in warm light | Lonely person in black void |
| Communication | Threads, folded letters, bridge, echo rings | Light beams | Speech bubble clutter |
| Trust | Clear water, open notebook, steady lamp | Transparent materials | Handshake stock image |
| Reputation | Ripples, layered traces, visible footprints | Soft badges | Star-rating cliché |
| Change | Folded paper becoming path, seed from circuit, gentle transition | Phase shift, seasonal cycle | Dramatic explosion, shattered pattern |
## Philosophical & Abstract
| Concept | Primary Metaphor | Alternative Visuals |
|---------|-----------------|-------------------|
| Time | Spirals, loops | Sediment layers, clock dissolution |
| Knowledge | Light sources | Growing trees, opening books |
| Wisdom | Mountain vistas | Deep waters, ancient libraries |
| Truth | Clear water | Prisms splitting light, unveiled |
| Illusion | Distorted mirrors | Smoke shapes, double images |
| Choice | Diverging paths | Doors opening, quantum splits |
| Balance | Tensegrity | Scales reimagined, equilibrium |
| Paradox | Möbius strips | Impossible objects, Escher-like |
| Existence | Breath patterns | Pulse rhythms, presence/absence |
| Consciousness | Nested awareness | Recursive mirrors, awakening |
| Concept | Preferred Metaphor | Alternatives | Avoid |
|---------|-------------------|--------------|-------|
| Time | Calendar pages, soft spiral, sediment-like paper layers | Loops, tide marks | Melting clock cliché |
| Knowledge | Lamp, window light, open book, growing plant | Expanding shelves | Glowing brain |
| Uncertainty | Foggy path, half-open door, incomplete map | Soft blur at edges | Storm clouds only |
| Balance | Asymmetrical stones, mobile, table edge, quiet scale | Tensegrity | Literal scale icon only |
| Paradox | Möbius paper strip, two paths meeting, shadow/light on same object | Nested frames | Escher-like complexity overload |
| Wisdom | Window overlooking depth, layered book spines | Ancient stone in light | Heavy mystical imagery |
| Choice | Diverging paths, open doors, fork in paper map | Branching thread | Dramatic split/explosion |
## Emotional States
| Emotion | Visual Translation | Color Association |
|---------|-------------------|------------------|
| Anxiety | Fragmented grids | Desaturated, glitch |
| Hope | Light breaking through | Warm gradients |
| Melancholy | Soft dissolution | Muted blues, grays |
| Joy | Expansion patterns | Bright, ascending |
| Fear | Contracting spaces | Sharp contrasts |
| Peace | Still water | Soft neutrals |
| Confusion | Tangled lines | Overlapping hues |
| Clarity | Clean geometry | Pure, minimal |
| Emotion | Visual Translation | Color Guidance |
|---------|-------------------|----------------|
| Anxiety | Fragmented grids, incomplete maps | Desaturated warm neutrals; avoid pure glitch |
| Hope | Light breaking through window, seedling | Warm gradients on light background |
| Melancholy | Single lit desk, soft dissolution | Muted blues on ivory; avoid black void |
| Joy | Expansion patterns, open space | Bright, ascending on warm white |
| Peace | Still water, open notebook | Soft neutrals, generous negative space |
| Clarity | Clean geometry, clear window | Pure, minimal with one accent |
## Transformation & Process
## Signature Motifs (Brand Consistency)
| Process | Visual Narrative | Symbolic Elements |
|---------|------------------|------------------|
| Growth | Seeds → trees | Fibonacci spirals |
| Decay | Entropy patterns | Rust, dissolution |
| Evolution | Branching forms | Darwin's tree reimagined |
| Revolution | Circles breaking | Shattered patterns |
| Innovation | Spark → flame | Lightning, fusion |
| Tradition | Continuous thread | Inherited patterns |
| Disruption | Broken grids | Glitch aesthetics |
| Integration | Merging streams | Confluence points |
| Motif | Description |
|-------|-------------|
| **Threshold Spaces** | Doorways, bridges, windows, paths, liminal rooms — transition |
| **Network Organic** | Roots, threads, neurons, constellations as soft digital networks |
| **Fragment Philosophy** | Folded paper, layered cards, gentle fragmentation and reassembly |
| **Light Studies** | Knowledge/uncertainty via window light, lamps, soft gradients |
| **Human Traces** | Hands, desks, notebooks, chairs, small figures within conceptual scenes |
## Korean-Western Fusion Elements
## Recommended Color Palettes
| Korean Element | Western Parallel | Fusion Approach |
|---------------|-----------------|-----------------|
| 여백 (Empty space) | Negative space | Active emptiness |
| 오방색 (Five colors) | Color theory | Symbolic palette |
| 달항아리 (Moon jar) | Minimalism | Imperfect circles |
| 한글 geometry | Typography | Structural letters |
| 산수화 (Landscape) | Abstract landscape | Atmospheric depth |
| 전통문양 (Patterns) | Geometric design | Cultural geometry |
| Palette | Background | Accent | Mood |
|---------|-----------|--------|------|
| Morning Desk | `#F7F3EA` warm ivory | `#4A90A4` muted teal | calm, thoughtful |
| Soft Technology | `#F6F8FA` cloud white | `#F2A65A` warm amber | clear, quietly optimistic |
| Warm Data | `#FFF8EF` soft cream | `#5EAAA8` soft turquoise | human, analytical |
| Clear Critique | `#F4F6F8` light gray | `#E07A5F` soft coral | critical but not aggressive |
| Human Network | `#FAF9F6` off-white | `#8AB17D` muted green | organic, connected |
**Dark color limit:** max 15% of canvas; never full dark background by default.
## Korean Design Elements
| Korean Element | Western Parallel | Application |
|----------------|-----------------|-------------|
| 여백 (Empty space) | Negative space | Active emptiness — 30%+ default |
| 달항아리 (Moon jar) | Minimalism | Roundness, asymmetry, soft white |
| 한지 texture | Paper grain | Subtle background texture only |
| 산수화 (Landscape) | Editorial landscape | Atmospheric depth without ornament |
**Avoid:** decorative oriental motifs, calligraphy clichés, 오방색 as obvious symbols — express Korean sensibility through spacing and restraint, not surface pattern.
## Usage Notes
1. **Layer metaphors**: Combine 2-3 for depth
2. **Avoid clichés**: No obvious tech symbols
3. **Cultural sensitivity**: Universal over specific
4. **Abstraction levels**: Match essay tone
5. **Emotional resonance**: Feel over literal
1. **Layer metaphors**: Combine 23 elements; one clear focal object + one human-scale anchor
2. **Avoid clichés**: No obvious tech icons (robot, glowing brain, magnifying glass alone)
3. **Match essay tone**: bright → practical guides; balanced → analysis; warmer → personal reflections
4. **30%+ negative space**: default; minimum 20% only when composition needs density
5. **Human trace required**: include hand, desk, figure, window, or everyday object unless topic demands pure abstraction
## Quick Selection Guide
For **technology essays**: organic-digital hybrids
For **social commentary**: human elements in systems
For **philosophy pieces**: space and light
For **cultural topics**: layered traditions
For **future themes**: transformation states
For **technology essays**: organic-digital hybrid forms in a light, human-scale environment
For **social commentary**: network patterns with small human traces
For **philosophy pieces**: calm symbolic scenes with Zen-like spacing
For **practical guides**: clean editorial diagrams with warm accents
For **personal reflections**: soft everyday scenes with metaphorical detail

View File

@@ -0,0 +1,173 @@
---
name: ourdigital-ad-manager
description: |
Ad copywriting and keyword research for OurDigital marketing.
Activated with "ourdigital" keyword for advertising tasks.
Triggers (ourdigital or our prefix):
- "ourdigital ad copy", "our ad copy"
- "ourdigital 광고 카피", "our 광고 카피"
- "ourdigital keyword", "our keyword"
- "ourdigital 검색 광고", "our 검색 광고"
Features:
- Search ad copywriting (Google, Naver)
- Display ad copywriting
- Branded content creation
- Keyword volume research
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Ad Manager
Create compelling ad copy and research keywords for OurDigital marketing campaigns.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital ad copy" / "our ad copy"
- "ourdigital 광고 카피" / "our 광고 카피"
- "our keyword research [topic]"
## Workflow
### Phase 1: Campaign Brief
Gather information:
- **Product/Service**: What are we advertising?
- **Target audience**: Who are we reaching?
- **Campaign goal**: Awareness, consideration, or conversion?
- **Platform**: Google, Naver, Meta, Display?
- **Budget tier**: Affects keyword competitiveness
### Phase 2: Keyword Research
For search campaigns:
1. **Seed keywords**: Core terms from brief
2. **Volume research**: Web search for search volume data
3. **Intent mapping**: Informational → Transactional
4. **Competitor analysis**: Top-ranking ad copy patterns
Tools to use:
- `web_search`: Search volume and trends
- `web_fetch`: Competitor ad copy analysis
### Phase 3: Ad Copy Creation
Generate platform-specific copy following character limits and best practices.
## Search Ad Copy
### Google Ads Format
```
Headline 1: [30 chars] - Primary keyword + value prop
Headline 2: [30 chars] - Benefit or CTA
Headline 3: [30 chars] - Differentiator
Description 1: [90 chars] - Expand on value
Description 2: [90 chars] - CTA + urgency
```
**Best Practices:**
- Include keyword in Headline 1
- Numbers and specifics increase CTR
- Test emotional vs. rational appeals
- Include pricing if competitive
### Naver Search Ad Format
```
제목: [25자] - 핵심 키워드 + 가치
설명: [45자] - 혜택 + 행동 유도
```
**Korean Ad Copy Tips:**
- 존댓말 일관성 유지
- 숫자와 구체적 혜택 강조
- 신뢰 요소 포함 (경력, 인증)
## Display Ad Copy
### Headlines by Format
| Format | Max Length | Focus |
|--------|------------|-------|
| Leaderboard | 25 chars | Brand + single benefit |
| Medium Rectangle | 30 chars | Offer + CTA |
| Responsive | 30 chars | Multiple variations |
### Copy Formula
```
[Problem Recognition] + [Solution Hint] + [CTA]
"여전히 [문제]? [해결책]으로 [결과]"
```
## Branded Content
For native advertising and sponsored content:
### OurDigital Tone
- **Authority without arrogance**: Share expertise, invite questions
- **Data-backed claims**: Statistics increase credibility
- **Subtle CTAs**: Education first, promotion second
### Content Types
| Type | Length | CTA Style |
|------|--------|-----------|
| Sponsored Article | 800-1,200 words | Soft (learn more) |
| Native Ad | 100-200 words | Medium (discover) |
| Social Sponsored | 50-100 words | Direct (get started) |
## Keyword Research Output
### Research Report Structure
```
## Keyword Analysis: [Topic]
### Primary Keywords
| Keyword | Volume | Difficulty | Intent |
|---------|--------|------------|--------|
| [kw1] | 10K | Medium | Trans |
### Long-tail Opportunities
- [keyword phrase 1]: Low competition, high intent
- [keyword phrase 2]: Rising trend
### Negative Keywords
- [irrelevant term 1]
- [irrelevant term 2]
### Recommended Ad Groups
1. [Group Name]: kw1, kw2, kw3
2. [Group Name]: kw4, kw5, kw6
```
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital ad copy [product]" | Full ad set |
| "ourdigital 검색 광고 [키워드]" | Search ads |
| "ourdigital display ad [campaign]" | Display copy |
| "ourdigital keyword [topic]" | Volume research |
## Platform Guidelines
| Platform | Headline | Description | Key Focus |
|----------|----------|-------------|-----------|
| Google | 30×3 | 90×2 | Keyword match |
| Naver | 25 | 45 | Trust signals |
| Meta | 40 | 125 | Visual-copy sync |
| LinkedIn | 150 | 70 | Professional tone |
## References
- `shared/references/ad-copy-formulas.md` - Proven copy templates
- `shared/references/platform-specs.md` - Character limits
- `01-ourdigital-brand-guide` - Brand voice

View File

@@ -0,0 +1,186 @@
---
name: ourdigital-trainer
description: |
Training material creation and workshop planning for OurDigital.
Activated with "ourdigital" keyword for education tasks.
Triggers (ourdigital or our prefix):
- "ourdigital training", "our training"
- "ourdigital 교육", "our 교육"
- "ourdigital workshop", "our workshop"
- "ourdigital 워크샵", "our 워크샵"
Features:
- Training material design
- Workshop agenda planning
- Participant evaluation design
- Exercise and activity creation
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Trainer
Design training materials, plan workshops, and create evaluation frameworks for OurDigital education programs.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital training" / "our training"
- "ourdigital 워크샵" / "our 워크샵"
- "our curriculum [subject]"
## Core Domains
OurDigital training expertise:
| Domain | Topics |
|--------|--------|
| **Data Literacy** | 데이터 리터러시, 분석 기초, 시각화 |
| **AI Literacy** | AI 활용, 프롬프트 엔지니어링, AI 윤리 |
| **Digital Marketing** | SEO, GTM, 마케팅 자동화 |
| **Brand Marketing** | 브랜드 전략, 콘텐츠 마케팅 |
## Workflow
### Phase 1: Training Needs Analysis
Gather requirements:
- **Target audience**: 직급, 경험 수준, 사전 지식
- **Learning objectives**: 교육 후 달성할 역량
- **Duration**: 시간 제약 (2시간/반일/전일/다회차)
- **Format**: 온라인/오프라인/하이브리드
- **Group size**: 참여 인원
### Phase 2: Curriculum Design
Structure the learning journey:
```
Module Structure:
├── 도입 (10-15%)
│ ├── Ice-breaker
│ ├── 학습 목표 공유
│ └── 사전 지식 확인
├── 핵심 학습 (60-70%)
│ ├── 개념 설명
│ ├── 사례 분석
│ ├── 실습 활동
│ └── 토론/질의응답
├── 심화/응용 (15-20%)
│ ├── 응용 과제
│ └── 그룹 활동
└── 마무리 (5-10%)
├── 핵심 정리
├── 평가
└── 후속 학습 안내
```
### Phase 3: Material Development
Create supporting materials:
| Material Type | Purpose |
|---------------|---------|
| 슬라이드 | 핵심 개념 전달 |
| 핸드아웃 | 참조 자료, 체크리스트 |
| 워크시트 | 실습 활동용 |
| 사례 연구 | 토론 및 분석용 |
| 퀴즈/평가지 | 학습 확인용 |
### Phase 4: Activity Design
Engagement techniques:
| Activity Type | Duration | Purpose |
|---------------|----------|---------|
| Think-Pair-Share | 5-10분 | 개별 사고 → 협력 |
| Case Study | 20-30분 | 실제 적용력 |
| Role Play | 15-20분 | 경험적 학습 |
| Gallery Walk | 15분 | 아이디어 공유 |
| Fishbowl | 20-30분 | 심층 토론 |
### Phase 5: Evaluation Design
Assessment framework:
| Level | What to Measure | Method |
|-------|-----------------|--------|
| 반응 | 만족도, 참여도 | 설문조사 |
| 학습 | 지식 습득 | 퀴즈, 테스트 |
| 행동 | 현업 적용 | 관찰, 피드백 |
| 결과 | 성과 개선 | KPI 측정 |
## Training Templates
### 2-Hour Workshop
```
00:00-00:10 도입 및 Ice-breaker
00:10-00:20 학습 목표 및 아젠다
00:20-00:50 핵심 개념 1
00:50-01:00 휴식
01:00-01:30 핵심 개념 2 + 실습
01:30-01:50 그룹 활동/토론
01:50-02:00 정리 및 Q&A
```
### Half-Day (4 Hours)
```
09:00-09:20 도입 및 네트워킹
09:20-10:20 모듈 1: 기초 개념
10:20-10:30 휴식
10:30-11:30 모듈 2: 심화 학습
11:30-12:00 실습 세션
12:00-12:30 사례 연구
12:30-13:00 정리, 평가, Q&A
```
### Full-Day (8 Hours)
```
09:00-09:30 도입
09:30-10:30 모듈 1
10:30-10:45 휴식
10:45-12:00 모듈 2 + 실습
12:00-13:00 점심
13:00-14:00 모듈 3
14:00-15:00 그룹 프로젝트
15:00-15:15 휴식
15:15-16:30 프로젝트 발표
16:30-17:00 종합 정리 및 평가
```
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital training [topic]" | Design curriculum |
| "ourdigital 워크샵 [주제]" | Workshop agenda |
| "ourdigital evaluation for [training]" | Assessment design |
| "ourdigital 교육자료 [주제]" | Material outline |
## Facilitation Tips
### Engagement Techniques
- **3의 법칙**: 핵심 메시지 3개 이하
- **10분 규칙**: 10분마다 활동 전환
- **참여 유도**: 질문 → 대기 → 지명
- **시각화**: 텍스트보다 다이어그램
### Korean Training Context
- 존칭 일관성 유지
- 실무 사례 강조
- 명함 교환 시간 확보
- 그룹 활동 시 리더 지정
## References
- `shared/references/training-frameworks.md` - 교수 설계 모델
- `shared/references/activity-library.md` - 활동 아이디어
- `shared/templates/workshop-template.md` - 워크샵 템플릿
- `01-ourdigital-brand-guide` - 발표 스타일

View File

@@ -0,0 +1,231 @@
---
name: ourdigital-backoffice
description: |
Business document creation for OurDigital consulting services.
Activated with "ourdigital" keyword for business documents.
Triggers (ourdigital or our prefix):
- "ourdigital quote", "our quote"
- "ourdigital 견적서", "our 견적서"
- "ourdigital proposal", "our proposal"
- "ourdigital 비용 분석", "our 비용 분석"
Features:
- Quote/estimate generation
- Service proposal creation
- Contract draft (requires legal review)
- Cost-benefit analysis
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Backoffice
Create business documents for OurDigital consulting services.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital 견적서" / "our 견적서"
- "ourdigital proposal" / "our proposal"
- "our cost analysis [project]"
## Important Notice
⚠️ **Legal Disclaimer**: Contract drafts require professional legal review before use. This skill provides templates and structure only.
## Document Types
### 1. Quote/Estimate (견적서)
**Purpose**: Service pricing and scope summary
**Structure:**
```
견적서 번호: OD-YYYY-NNN
발행일: YYYY-MM-DD
유효기간: 30일
1. 고객 정보
- 회사명, 담당자, 연락처
2. 서비스 개요
- 프로젝트명
- 서비스 범위 요약
3. 세부 항목
| 항목 | 상세 | 수량 | 단가 | 금액 |
|------|------|------|------|------|
4. 합계
- 소계, VAT, 총액
5. 결제 조건
- 선금/잔금 비율
- 결제 방법
6. 특이사항
- 포함/미포함 사항
```
### 2. Service Proposal (서비스 제안서)
**Purpose**: Detailed service offering and value proposition
**Structure:**
```
1. Executive Summary
- 핵심 제안 1-2문단
2. 고객 상황 이해
- 현재 과제
- 니즈 분석
3. 제안 서비스
- 서비스 범위
- 접근 방법
- 예상 산출물
4. 프로젝트 계획
- 일정표
- 마일스톤
- 체크포인트
5. 투입 리소스
- 담당자 프로필
- 역할 분담
6. 비용 및 조건
- 비용 구조
- 결제 조건
7. 기대 효과
- 예상 성과
- ROI 추정
8. 왜 OurDigital인가
- 차별점
- 관련 경험
```
### 3. Contract Draft (계약서 초안)
**Purpose**: Service agreement framework
⚠️ **반드시 법률 전문가 검토 필요**
**Structure:**
```
제1조 (목적)
제2조 (용어의 정의)
제3조 (계약 기간)
제4조 (서비스 범위)
제5조 (대금 및 지급 조건)
제6조 (권리와 의무)
제7조 (비밀유지)
제8조 (지적재산권)
제9조 (계약의 해지)
제10조 (손해배상)
제11조 (분쟁 해결)
제12조 (일반 조항)
```
### 4. Cost-Benefit Analysis (비용 분석)
**Purpose**: ROI and investment justification
**Structure:**
```
1. 프로젝트 개요
- 목적 및 범위
2. 비용 분석
| 항목 | 초기비용 | 연간비용 | 3년 TCO |
3. 예상 효과
| 효과 | 정량적 가치 | 연간 효과 |
4. ROI 계산
- 투자회수기간
- NPV, IRR
5. 리스크 분석
- 잠재 리스크
- 완화 방안
6. 권장 사항
```
## Service Catalog
OurDigital standard service offerings:
### SEO Services
| Service | Description | Duration | Price Range |
|---------|-------------|----------|-------------|
| Technical Audit | 기술 SEO 진단 | 1-2주 | 300-500만원 |
| On-Page Optimization | 콘텐츠 최적화 | 월간 | 150-300만원/월 |
| Local SEO | 로컬 검색 최적화 | 월간 | 100-200만원/월 |
### Data & Analytics
| Service | Description | Duration | Price Range |
|---------|-------------|----------|-------------|
| GTM Setup | 태그 관리 구축 | 2-4주 | 200-400만원 |
| GA4 Implementation | 분석 환경 구축 | 1-3주 | 150-300만원 |
| Dashboard Development | 대시보드 개발 | 2-4주 | 300-600만원 |
### Consulting
| Service | Description | Duration | Price Range |
|---------|-------------|----------|-------------|
| Brand Consulting | 브랜드 전략 | 프로젝트 | 500-1000만원 |
| Marketing Strategy | 마케팅 전략 | 프로젝트 | 300-700만원 |
| Data Strategy | 데이터 전략 | 프로젝트 | 400-800만원 |
### Training
| Service | Description | Duration | Price Range |
|---------|-------------|----------|-------------|
| Workshop | 반일/전일 워크샵 | 4-8시간 | 100-200만원 |
| Corporate Training | 기업 교육 | 다회차 | 50-100만원/회 |
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital 견적서 [서비스]" | Generate quote |
| "ourdigital proposal [client]" | Create proposal |
| "ourdigital 계약서 초안" | Contract template |
| "ourdigital 비용 분석 [project]" | Cost-benefit analysis |
## Workflow
### Phase 1: Requirement Gathering
- Client information
- Service scope
- Timeline requirements
- Budget constraints
### Phase 2: Document Generation
- Select appropriate template
- Fill with gathered information
- Apply OurDigital branding
### Phase 3: Review & Finalize
- Internal review
- Client discussion points highlight
- Legal review (for contracts)
## References
- `shared/templates/quote-template.md` - 견적서 양식
- `shared/templates/proposal-template.md` - 제안서 양식
- `shared/templates/contract-template.md` - 계약서 양식
- `shared/references/pricing-guide.md` - 가격 가이드
- `01-ourdigital-brand-guide` - 문서 스타일

View File

@@ -0,0 +1,167 @@
---
name: ourdigital-skill-creator
description: |
Meta skill for creating and managing OurDigital Claude Skills.
Activated when user includes "ourdigital" keyword with skill creation requests.
Triggers (ourdigital or our prefix):
- "ourdigital skill create", "our skill create"
- "ourdigital 스킬 만들기", "our 스킬 만들기"
- "ourdigital skill creator", "our skill creator"
Features:
- Skill suitability evaluation
- Interactive Q&A for requirements gathering
- Optimized skill generation (Desktop/Code)
- Notion history tracking
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Skill Creator
Meta skill for creating, validating, and managing OurDigital Claude Skills.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital 스킬 만들어줘" / "our 스킬 만들어줘"
- "ourdigital skill creator" / "our skill creator"
- "our skill create [name]"
Do NOT activate for generic "make a skill" requests (without our/ourdigital prefix).
## Interactive Workflow
### Phase 1: Need Assessment
When user requests a new skill:
1. **Acknowledge** the initial request
2. **Ask clarifying questions** (max 3 per turn):
- What is the core purpose?
- What triggers this skill?
- What outputs do you expect?
### Phase 2: Suitability Check
Evaluate against Claude Skill criteria:
| Criterion | Question to Ask |
|-----------|-----------------|
| Clear trigger | When exactly should this skill activate? |
| Focused scope | Can you describe 1-3 core functions? |
| Reusable resources | What scripts, templates, or references are needed? |
| Domain knowledge | What specialized knowledge does Claude lack? |
| Clear boundaries | How does this differ from existing skills? |
**Decision**: If ≥3 criteria pass → proceed. Otherwise, suggest alternatives.
### Phase 3: Requirements Definition
Guide user through structured Q&A:
```
Q1. 스킬 목적과 핵심 기능은 무엇인가요?
(What is the skill's purpose and core functions?)
Q2. 어떤 상황에서 이 스킬이 트리거되어야 하나요?
(When should this skill be triggered?)
Q3. 필요한 외부 도구나 API가 있나요?
(Any external tools or APIs needed?)
Q4. 기대하는 출력 형식은 무엇인가요?
(What output format do you expect?)
Q5. Desktop, Code, 또는 Both 환경이 필요한가요?
(Which environment: Desktop, Code, or Both?)
```
### Phase 4: Skill Generation
Generate skill structure following OurDigital standards:
```
XX-ourdigital-{skill-name}/
├── desktop/
│ └── SKILL.md # Desktop version
├── code/
│ └── SKILL.md # Code version (CLAUDE.md pattern)
├── shared/
│ ├── references/ # Common documentation
│ ├── templates/ # Shared templates
│ └── scripts/ # Utility scripts
├── docs/
│ ├── CHANGELOG.md # Version history
│ └── logs/ # Update logs
└── README.md # Overview
```
### Phase 5: Validation
Before finalizing, verify:
- [ ] YAML frontmatter includes "ourdigital" trigger keywords
- [ ] Description clearly states activation conditions
- [ ] Body content is 800-1,200 words
- [ ] shared/ resources are properly referenced
- [ ] No overlap with existing ourdigital skills
### Phase 6: Notion Sync
Record to Working with AI database:
- **Database**: f8f19ede-32bd-43ac-9f60-0651f6f40afe
- **Properties**:
- Name: `ourdigital-{skill-name} v{version}`
- Status: In progress → Done
- AI used: Claude Desktop
- AI summary: Brief skill description
## YAML Frontmatter Template
```yaml
---
name: ourdigital-{skill-name}
description: |
[Purpose summary]
Activated when user includes "ourdigital" keyword.
Triggers:
- "ourdigital {keyword1}", "ourdigital {keyword2}"
Features:
- Feature 1
- Feature 2
version: "1.0"
author: OurDigital
environment: Desktop | Code | Both
---
```
## Skill Numbering
| Range | Category |
|-------|----------|
| 01-09 | OurDigital Core (brand, blog, journal, research, etc.) |
| 10 | Meta (skill-creator) |
| 11-19 | SEO Tools |
| 20-29 | GTM/Analytics Tools |
| 31-39 | Notion Tools |
| 40-49 | Jamie Clinic Tools |
## Reference Files
- `shared/references/suitability-criteria.md` - Skill evaluation criteria
- `shared/references/skill-patterns.md` - Common patterns
- `shared/templates/skill-template/` - Blank skill template
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital 스킬 적합성" | Run suitability check only |
| "ourdigital 스킬 생성" | Full creation workflow |
| "ourdigital 스킬 검증" | Validate existing skill |

View File

@@ -0,0 +1,109 @@
---
name: seo-comprehensive-audit
description: |
Comprehensive SEO audit orchestrator. Runs 6-stage audit pipeline (Technical, On-Page, CWV, Schema, Local, GSC) and produces a unified report with weighted health score.
Triggers: comprehensive SEO, full SEO audit, 종합 SEO 감사, site audit, SEO health check.
---
# Comprehensive SEO Audit
## Purpose
Orchestrate a full-spectrum SEO audit by running 6 specialized analyses and synthesizing results into a unified health score and actionable report.
## Pipeline Stages
| # | Stage | Source Skill | Default |
|---|-------|-------------|---------|
| 1 | Technical SEO | 12-seo-technical-audit | Always |
| 2 | On-Page SEO | 13-seo-on-page-audit | Always |
| 3 | Core Web Vitals | 14-seo-core-web-vitals | Always |
| 4 | Schema Validation | 16-seo-schema-validator | Always |
| 5 | Local SEO | 18-seo-local-audit | Skippable |
| 6 | Search Console | 15-seo-search-console | Skippable |
## Workflow
### 1. Initialization
1. Receive target URL from user
2. Confirm which stages to run (all 6 by default)
3. Set up audit tracking ID: `COMP-YYYYMMDD-NNN`
### 2. Execute Stages (Sequential)
For each active stage:
1. Run the sub-skill analysis
2. Collect JSON results
3. Extract score and issues
### 3. Synthesis
1. Compute weighted health score (0-100)
2. Assign grade (A/B+/B/C/D/F)
3. Prioritize critical and high-severity findings
4. Generate recommendations
### 4. Notion Report
1. Create summary page: `종합 SEO 감사 보고서 - [domain] - YYYY-MM-DD`
2. Create individual pages for Critical/High findings
3. Database: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
## Health Score Weights
| Category | Weight |
|----------|--------|
| Technical SEO | 20% |
| On-Page SEO | 20% |
| Core Web Vitals | 25% |
| Schema | 15% |
| Local SEO | 10% |
| Search Console | 10% |
Skipped stages redistribute weight proportionally.
## Output Format
```markdown
## 종합 SEO 감사 보고서: [domain]
**Health Score**: [score]/100 ([grade])
**Date**: YYYY-MM-DD
**Audit ID**: COMP-YYYYMMDD-NNN
### Stage Results
| Stage | Score | Issues |
|-------|-------|--------|
| Technical SEO | XX/100 | N issues |
| On-Page SEO | XX/100 | N issues |
| Core Web Vitals | XX/100 | N issues |
| Schema | XX/100 | N issues |
| Local SEO | XX/100 | N issues |
| Search Console | XX/100 | N issues |
### Critical Findings
1. [Finding with recommendation]
### Recommendations (Priority Order)
1. [Action item]
```
## MCP Tool Usage
### Firecrawl
```
mcp__firecrawl__scrape: Fetch page content for on-page and schema analysis
```
### Notion
```
mcp__notion__*: Create audit report pages in SEO database
```
### Perplexity
```
mcp__perplexity__search: Research best practices for recommendations
```
## Limitations
- Local SEO stage requires manual input for NAP/GBP data
- Search Console stage requires GSC API credentials
- Health score accuracy improves when all 6 stages are active

View File

@@ -0,0 +1,103 @@
---
name: seo-technical-audit
description: |
Technical SEO analyzer for robots.txt, sitemap, and crawlability fundamentals.
Triggers: technical SEO, robots.txt, sitemap validation, crawlability, URL accessibility.
---
# SEO Technical Audit
## Purpose
Analyze crawlability fundamentals: robots.txt rules, XML sitemap structure, and URL accessibility. Identify issues blocking search engine crawlers.
## Core Capabilities
1. **Robots.txt Analysis** - Parse rules, check blocked resources
2. **Sitemap Validation** - Verify XML structure, URL limits, dates
3. **URL Accessibility** - Check HTTP status, redirects, broken links
## MCP Tool Usage
### Firecrawl for Page Data
```
mcp__firecrawl__scrape: Fetch robots.txt and sitemap content
mcp__firecrawl__crawl: Check multiple URLs accessibility
```
### Perplexity for Best Practices
```
mcp__perplexity__search: Research current SEO recommendations
```
## Workflow
### 1. Robots.txt Check
1. Fetch `[domain]/robots.txt` using Firecrawl
2. Parse User-agent rules and Disallow patterns
3. Identify blocked resources (CSS, JS, images)
4. Check for Sitemap declarations
5. Report critical issues
### 2. Sitemap Validation
1. Locate sitemap (from robots.txt or `/sitemap.xml`)
2. Validate XML syntax
3. Check URL count (max 50,000)
4. Verify lastmod date formats
5. For sitemap index: parse child sitemaps
### 3. URL Accessibility Sampling
1. Extract URLs from sitemap
2. Sample 50-100 URLs for large sites
3. Check HTTP status codes
4. Identify redirects and broken links
5. Report 4xx/5xx errors
## Output Format
```markdown
## Technical SEO Audit: [domain]
### Robots.txt Analysis
- Status: [Valid/Invalid/Missing]
- Sitemap declared: [Yes/No]
- Critical blocks: [List]
### Sitemap Validation
- URLs found: [count]
- Syntax: [Valid/Errors]
- Issues: [List]
### URL Accessibility (sampled)
- Checked: [count] URLs
- Success (2xx): [count]
- Redirects (3xx): [count]
- Errors (4xx/5xx): [count]
### Recommendations
1. [Priority fixes]
```
## Common Issues
| Issue | Impact | Fix |
|-------|--------|-----|
| No sitemap in robots.txt | Medium | Add `Sitemap:` directive |
| Blocking CSS/JS | High | Allow Googlebot access |
| 404s in sitemap | High | Remove or fix URLs |
| Missing lastmod | Low | Add dates for freshness signals |
## Limitations
- Cannot access password-protected sitemaps
- Large sitemaps (10,000+ URLs) require sampling
- Does not check render-blocking issues (use Core Web Vitals skill)
## Notion Output (Required)
All audit reports MUST be saved to OurDigital SEO Audit Log:
- **Database ID**: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- **Properties**: Issue (title), Site (url), Category, Priority, Found Date, Audit ID
- **Language**: Korean with English technical terms
- **Audit ID Format**: [TYPE]-YYYYMMDD-NNN

View File

@@ -0,0 +1,103 @@
---
name: seo-on-page-audit
description: |
On-page SEO analyzer for meta tags, headings, links, images, and Open Graph.
Triggers: on-page SEO, meta tags, title tag, heading structure, alt text.
---
# SEO On-Page Audit
## Purpose
Analyze single-page SEO elements: meta tags, heading hierarchy, internal/external links, images, and social sharing tags.
## Core Capabilities
1. **Meta Tags** - Title, description, canonical, robots
2. **Headings** - H1-H6 structure and hierarchy
3. **Links** - Internal, external, broken detection
4. **Images** - Alt text, sizing, lazy loading
5. **Social** - Open Graph, Twitter Cards
## MCP Tool Usage
```
mcp__firecrawl__scrape: Extract page HTML and metadata
mcp__perplexity__search: Research SEO best practices
mcp__notion__create-page: Save audit findings
```
## Workflow
1. Scrape target URL with Firecrawl
2. Extract and analyze meta tags
3. Map heading hierarchy
4. Count and categorize links
5. Check image optimization
6. Validate Open Graph tags
7. Generate recommendations
## Checklist
### Meta Tags
- [ ] Title present (50-60 characters)
- [ ] Meta description present (150-160 characters)
- [ ] Canonical URL set
- [ ] Robots meta allows indexing
### Headings
- [ ] Single H1 tag
- [ ] Logical hierarchy (no skips)
- [ ] Keywords in H1
### Links
- [ ] No broken internal links
- [ ] External links use rel attributes
- [ ] Reasonable internal link count
### Images
- [ ] All images have alt text
- [ ] Images are appropriately sized
- [ ] Lazy loading implemented
### Open Graph
- [ ] og:title present
- [ ] og:description present
- [ ] og:image present (1200x630)
## Output Format
```markdown
## On-Page Audit: [URL]
### Meta Tags: X/5
| Element | Status | Value |
|---------|--------|-------|
### Headings: X/5
- H1: [text]
- Hierarchy: Valid/Invalid
### Links
- Internal: X
- External: X
- Broken: X
### Recommendations
1. [Priority fixes]
```
## Limitations
- Single page analysis only
- Cannot detect JavaScript-rendered content issues
- External link status requires additional crawl
## Notion Output (Required)
All audit reports MUST be saved to OurDigital SEO Audit Log:
- **Database ID**: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- **Properties**: Issue (title), Site (url), Category, Priority, Found Date, Audit ID
- **Language**: Korean with English technical terms
- **Audit ID Format**: [TYPE]-YYYYMMDD-NNN

View File

@@ -0,0 +1,117 @@
---
name: seo-core-web-vitals
description: |
Core Web Vitals analyzer for LCP, FID, CLS, and INP optimization recommendations.
Triggers: Core Web Vitals, page speed, LCP optimization, CLS fix, INP analysis.
---
# SEO Core Web Vitals
## Purpose
Analyze Core Web Vitals performance metrics and provide optimization recommendations.
## Core Capabilities
1. **LCP** - Largest Contentful Paint measurement
2. **FID/INP** - Interactivity metrics
3. **CLS** - Cumulative Layout Shift
4. **Recommendations** - Optimization guidance
## Metrics Thresholds
| Metric | Good | Needs Work | Poor |
|--------|------|------------|------|
| LCP | ≤2.5s | 2.5-4s | >4s |
| FID | ≤100ms | 100-300ms | >300ms |
| CLS | ≤0.1 | 0.1-0.25 | >0.25 |
| INP | ≤200ms | 200-500ms | >500ms |
## Data Sources
### Option 1: PageSpeed Insights (Recommended)
Use external tool and input results:
- Visit: https://pagespeed.web.dev/
- Enter URL, run test
- Provide scores to skill
### Option 2: Research Best Practices
```
mcp__perplexity__search: "Core Web Vitals optimization [specific issue]"
```
## Workflow
1. Request PageSpeed Insights data from user
2. Analyze provided metrics
3. Identify failing metrics
4. Research optimization strategies
5. Provide prioritized recommendations
## Common LCP Issues
| Cause | Fix |
|-------|-----|
| Slow server response | Improve TTFB, use CDN |
| Render-blocking resources | Defer non-critical CSS/JS |
| Slow resource load | Preload LCP image |
| Client-side rendering | Use SSR/SSG |
## Common CLS Issues
| Cause | Fix |
|-------|-----|
| Images without dimensions | Add width/height attributes |
| Ads/embeds without space | Reserve space with CSS |
| Web fonts causing FOIT/FOUT | Use font-display: swap |
| Dynamic content injection | Reserve space, use transforms |
## Common INP Issues
| Cause | Fix |
|-------|-----|
| Long JavaScript tasks | Break up tasks, use web workers |
| Large DOM size | Reduce DOM nodes |
| Heavy event handlers | Debounce, optimize listeners |
| Third-party scripts | Defer, lazy load |
## Output Format
```markdown
## Core Web Vitals: [URL]
### Scores
| Metric | Mobile | Desktop | Status |
|--------|--------|---------|--------|
| LCP | Xs | Xs | Good/Poor |
| FID | Xms | Xms | Good/Poor |
| CLS | X.XX | X.XX | Good/Poor |
| INP | Xms | Xms | Good/Poor |
### Overall Score
- Mobile: X/100
- Desktop: X/100
### Priority Fixes
1. [Highest impact recommendation]
2. [Second priority]
### Detailed Recommendations
[Per-metric optimization steps]
```
## Limitations
- Requires external PageSpeed Insights data
- Lab data may differ from field data
- Some fixes require developer implementation
- Third-party scripts may be difficult to optimize
## Notion Output (Required)
All audit reports MUST be saved to OurDigital SEO Audit Log:
- **Database ID**: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- **Properties**: Issue (title), Site (url), Category, Priority, Found Date, Audit ID
- **Language**: Korean with English technical terms
- **Audit ID Format**: [TYPE]-YYYYMMDD-NNN

View File

@@ -0,0 +1,126 @@
---
name: seo-search-console
description: |
Google Search Console data analyzer for performance, queries, and index coverage.
Triggers: Search Console, GSC analysis, search performance, rankings, CTR optimization.
---
# SEO Search Console
## Purpose
Analyze Google Search Console data: search performance (queries, pages, CTR, position), sitemap status, and index coverage.
## Core Capabilities
1. **Performance Analysis** - Clicks, impressions, CTR, position
2. **Query Analysis** - Top search queries
3. **Page Performance** - Best/worst performing pages
4. **Index Coverage** - Crawl and index issues
5. **Sitemap Status** - Submission and processing
## Data Collection
### Option 1: User Provides Data
Request GSC export from user:
1. Go to Search Console > Performance
2. Export data (CSV or Google Sheets)
3. Share with assistant
### Option 2: User Describes Data
User verbally provides:
- Top queries and positions
- CTR trends
- Coverage issues
## Analysis Framework
### Performance Metrics
| Metric | What It Measures | Good Benchmark |
|--------|------------------|----------------|
| Clicks | User visits from search | Trending up |
| Impressions | Search appearances | High for target keywords |
| CTR | Click-through rate | 2-5% average |
| Position | Average ranking | <10 for key terms |
### Query Analysis
Identify:
- **Winners** - High position, high CTR
- **Opportunities** - High impressions, low CTR
- **Quick wins** - Position 8-20, low effort to improve
### Page Analysis
Categorize:
- **Top performers** - High clicks, good CTR
- **Underperformers** - High impressions, low CTR
- **Declining** - Down vs previous period
## Workflow
1. Collect GSC data from user
2. Analyze performance trends
3. Identify top queries and pages
4. Find optimization opportunities
5. Check for coverage issues
6. Provide actionable recommendations
## Output Format
```markdown
## Search Console Analysis: [Site]
### Overview (Last 28 Days)
| Metric | Value | vs Previous |
|--------|-------|-------------|
| Clicks | X | +X% |
| Impressions | X | +X% |
| CTR | X% | +X% |
| Position | X | +X |
### Top Queries
| Query | Clicks | Position | Opportunity |
|-------|--------|----------|-------------|
### Top Pages
| Page | Clicks | CTR | Status |
|------|--------|-----|--------|
### Opportunities
1. [Query with high impressions, low CTR]
2. [Page ranking 8-20 that can improve]
### Issues
- [Coverage problems]
- [Sitemap issues]
### Recommendations
1. [Priority action]
```
## Common Issues
| Issue | Impact | Fix |
|-------|--------|-----|
| Low CTR on high-impression query | Lost traffic | Improve title/description |
| Declining positions | Traffic loss | Update content, build links |
| Not indexed pages | No visibility | Fix crawl issues |
| Sitemap errors | Discovery problems | Fix sitemap XML |
## Limitations
- Requires user to provide GSC data
- API access needs service account setup
- Data has 2-3 day delay
- Limited to verified properties
## Notion Output (Required)
All audit reports MUST be saved to OurDigital SEO Audit Log:
- **Database ID**: `2c8581e5-8a1e-8035-880b-e38cefc2f3ef`
- **Properties**: Issue (title), Site (url), Category, Priority, Found Date, Audit ID
- **Language**: Korean with English technical terms
- **Audit ID Format**: [TYPE]-YYYYMMDD-NNN

View File

@@ -0,0 +1,100 @@
---
name: seo-schema-validator
description: |
Validates an AUTHORED JSON-LD schema dataset (pre-deployment QA) and audits
live structured data (post-deployment). Runs a 5-layer offline validation
pipeline (coverage, syntax, vocabulary, Google rich-result requirements,
business-logic/consistency) and emits a severity-ranked defect log, a gate
decision, and a Markdown report. Fills the "16-seo-schema-validator" slot
referenced by seo-comprehensive-audit.
Triggers: schema validation, JSON-LD QA, structured data check, schema 검수,
스키마 유효성 검증, 구조화 데이터 검토, rich result eligibility, schema 오류 점검.
version: "1.0"
author: OurDigital / D.intelligence
environment: Code
---
# SEO Schema Validator (16)
Quality-assure structured data at scale. Built for the kind of failure where a
client review of hundreds of authored entries surfaces "too many errors" — by
moving the cheap, machine-checkable errors OUT of the human review and INTO an
automated gate that runs first.
## Two modes
| Mode | When | Input | Adds |
|------|------|-------|------|
| **A — Dataset QA (default)** | Before deployment, while authoring/reviewing | An authored dataset: `.xlsx` / `.csv` (one row per entry, a JSON-LD column), `.jsonl`, `.json`, or a directory of `.json/.jsonld` | Layer 0 coverage vs the canonical URL list |
| **B — Live audit** | After deployment, or feeding `seo-comprehensive-audit` | Live URLs (extract embedded JSON-LD first, then validate) | Layer 5 rendering-reality (schema present in rendered HTML, matches content) |
This skill's primary job is **Mode A**: catch errors before the client sees them.
## The 5 validation layers
| # | Layer | Catches | Default severity |
|---|-------|---------|------------------|
| L0 | Coverage | URLs with no entry; entries whose URL isn't in the inventory | P1 / P2 |
| L1 | Syntax | invalid JSON, missing/wrong `@context`, no `@type`, encoding corruption | P0 / P1 |
| L2 | Vocabulary | unknown type, property not valid for type, bad value formats (URL/date/lang/currency/number) | P1 / P2 |
| L3 | Rich-result | Google **required** missing (blocks rich result); recommended absent | P0 / P2 |
| L4 | Consistency | NAP mismatch across a property, `@id` dupes/dangling refs, swapped geo, placeholder text, duplicate descriptions | P0 / P1 |
Full rationale and the type→requirement matrix: `references/validation-methodology.md`.
Severity + category codes: `references/defect-taxonomy.md`.
Hotel page-type → schema-type map: `references/hotel-type-map.md`.
Client-facing report + P1 decision log: `templates/client-qa-report-template.md`, `templates/decision-log.md`.
## Stage gates (aligned to the project lifecycle 설계→개발→테스트→안정화→런칭 후)
- **G1 설계** — Lock the schema spec and the page-type→type map (`hotel-type-map.md`). Approve the entry template. *DoD:* every page template has an assigned schema type and a required-property list.
- **G2 개발** — Authors produce entries. Run the validator with `--strict`. *DoD (gate):* **zero P0**, JSON parses for 100% of entries. Entries failing this NEVER reach client review.
- **G3 테스트** — Re-run; triage P1 in `defect_log.csv` (assign owner/status). Client reviews ONLY the clean, P0-free entries, against a defect report — not raw JSON. *DoD:* P1 triaged, decisions logged in `templates/decision-log.md`.
- **G4 안정화** — Fix → re-run → confirm no regressions. Spot-check a sample in Google Rich Results Test (online, outside this runtime). *DoD:* P0=0, P1 accepted/closed, online validator green on sample.
- **G5 런칭 후** — Mode B live audit + GSC "Rich results" report monitoring. *DoD:* deployed schema matches authored dataset; no new GSC structured-data errors.
## How to run
```bash
# Mode A — validate an authored dataset (the common case)
python scripts/validate_schema.py path/to/schema_dataset.xlsx \
--url-list path/to/URLlist.xlsx \
--out schema_qa_out
# Highest signal for the pre-review gate (unexpected props -> P1, drop optional recommended)
python scripts/validate_schema.py dataset.csv --strict --no-recommended --out qa_strict
# Try it on the bundled flawed fixture first
python scripts/make_sample.py
python scripts/validate_schema.py fixtures/sample_schema.csv --out demo_out
```
**Input expectations (Mode A tabular):** the loader auto-detects a JSON-LD column
(`jsonld`, `schema`, `structured_data`, `스키마`, …) plus optional `url`/`메뉴 URL`,
`lang`/`언어코드`, `device`/`PC/MOBILE`, `page_type` columns. Multi-sheet `.xlsx`
is supported (each sheet with a JSON-LD column is read). No JSON-LD column → clear error.
## Reading the output
- `report.md` — counts, **gate decision (PASS = zero P0)**, defects-by-category, top P0 entries, next step.
- `defect_log.csv` — one row per finding with `status/owner/note` columns ready for triage. This is the client-facing artifact (open issues, not raw schema).
- `results.json` — full machine-readable results for dashboards / CI.
**The rule:** an entry advances to client review only when it has **zero P0**. P1 =
triage backlog (fix before launch). P2 = optimization backlog (recommended props, style).
## Limits & honesty
- Offline by design — the runtime can't reach schema.org or Google. The bundled
rule set (`scripts/schema_rules.json`) is a curated hotel-focused subset; unknown
types/properties degrade to warnings (never hard errors) to avoid false positives.
- Authoritative rich-result eligibility still requires Google's online testers on a
sample at G4. This skill makes that sample small and clean, not redundant.
- Adding a new schema type or tightening a rule = edit `schema_rules.json` only.
## Integration
`seo-comprehensive-audit` calls this skill as pipeline stage 4 ("Schema Validation").
For that orchestrator, run **Mode B** on a sample of live URLs and return the score
(100 weighted defect penalty) and the issue list. For day-to-day client work, run
**Mode A** on the authored dataset.

View File

@@ -1,148 +1,57 @@
# CLAUDE.md
# CLAUDE.md — seo-schema-validator (Claude Code)
## Overview
## Canonical entry point
Structured data validator: extract, parse, and validate JSON-LD, Microdata, and RDFa markup against schema.org vocabulary.
This skill was upgraded to a **5-layer dataset-QA pipeline**. The authoritative
directive and run instructions live in the skill root:
## Quick Start
- **`../SKILL.md`** — modes, the 5 layers, stage gates, how to run.
- **`../scripts/validate_schema.py`** — the validator (run this, not the legacy script below).
- **`../scripts/schema_rules.json`** — the offline rule set (edit this to add a type/rule).
- **`../references/`** — `validation-methodology.md`, `defect-taxonomy.md`, `hotel-type-map.md`.
- **`../templates/`** — `client-qa-report-template.md`, `decision-log.md`.
```bash
pip install -r scripts/requirements.txt
python scripts/schema_validator.py --url https://example.com
# Primary use — QA an AUTHORED dataset before the client sees it (Mode A)
python ../scripts/validate_schema.py DATASET.xlsx --url-list URLLIST.xlsx --out schema_qa_out
# Highest-signal pre-review gate
python ../scripts/validate_schema.py DATASET.csv --strict --no-recommended --out qa_strict
# Try the bundled flawed fixture first
python ../scripts/make_sample.py
python ../scripts/validate_schema.py ../fixtures/sample_schema.csv --out demo_out
# Post-deploy live audit (Mode B) — feeds seo-comprehensive-audit stage 4
python ../scripts/validate_schema.py --live https://example.com --out live_out
```
## Scripts
Gate rule: **PASS = zero P0.** The process exits 1 when the gate fails, so it stops
`&&` chains and CI. Only P0-free entries advance to client review.
| Script | Purpose |
|--------|---------|
| `schema_validator.py` | Extract and validate structured data |
| `base_client.py` | Shared utilities |
## Legacy single-URL tool (kept for quick one-offs)
## Usage
`scripts/schema_validator.py --url <URL>` extracts and validates structured data from
one live page (JSON-LD / Microdata / RDFa via extruct). It predates the pipeline and is
**not** the gate. For any dataset or client-facing QA, use `validate_schema.py` above.
```bash
# Validate page schema
python scripts/schema_validator.py --url https://example.com
# JSON output
pip install -r scripts/requirements.txt # extruct, jsonschema, rdflib, lxml, requests
python scripts/schema_validator.py --url https://example.com --json
# Validate local file
python scripts/schema_validator.py --file schema.json
# Check Rich Results eligibility
python scripts/schema_validator.py --url https://example.com --rich-results
```
## Supported Formats
## Notion output (OurDigital SEO Audit Log)
| Format | Detection |
|--------|-----------|
| JSON-LD | `<script type="application/ld+json">` |
| Microdata | `itemscope`, `itemtype`, `itemprop` |
| RDFa | `vocab`, `typeof`, `property` |
## Validation Levels
### 1. Syntax Validation
- Valid JSON structure
- Proper nesting
- No syntax errors
### 2. Schema.org Vocabulary
- Valid @type values
- Known properties
- Correct property types
### 3. Google Rich Results
- Required properties present
- Recommended properties
- Feature-specific requirements
## Schema Types Validated
| Type | Required Properties | Rich Result |
|------|---------------------|-------------|
| Article | headline, author, datePublished | Yes |
| Product | name, offers | Yes |
| LocalBusiness | name, address | Yes |
| FAQPage | mainEntity | Yes |
| Organization | name, url | Yes |
| BreadcrumbList | itemListElement | Yes |
| WebSite | name, url | Sitelinks |
## Output
```json
{
"url": "https://example.com",
"schemas_found": 3,
"schemas": [
{
"@type": "Organization",
"valid": true,
"rich_results_eligible": true,
"issues": [],
"warnings": []
}
],
"summary": {
"valid": 3,
"invalid": 0,
"rich_results_eligible": 2
}
}
```
## Issue Severity
| Level | Description |
|-------|-------------|
| Error | Invalid schema, blocks rich results |
| Warning | Missing recommended property |
| Info | Optimization suggestion |
## Dependencies
```
extruct>=0.16.0
jsonschema>=4.21.0
rdflib>=7.0.0
lxml>=5.1.0
requests>=2.31.0
```
## Notion Output (Required)
**IMPORTANT**: All audit reports MUST be saved to the OurDigital SEO Audit Log database.
### Database Configuration
When a run is part of an OurDigital/D.intelligence audit, log a summary to the SEO Audit
Log database. Per the user-level Notion rule, push **page content** with the
`notion-writer` skill; use Notion MCP only for **properties** (Status, Category, etc.).
| Field | Value |
|-------|-------|
| Database ID | `2c8581e5-8a1e-8035-880b-e38cefc2f3ef` |
| URL | https://www.notion.so/dintelligence/2c8581e58a1e8035880be38cefc2f3ef |
### Required Properties
| Property | Type | Description |
|----------|------|-------------|
| Issue | Title | Report title (Korean + date) |
| Site | URL | Audited website URL |
| Category | Select | Technical SEO, On-page SEO, Performance, Schema/Structured Data, Sitemap, Robots.txt, Content, Local SEO |
| Priority | Select | Critical, High, Medium, Low |
| Found Date | Date | Audit date (YYYY-MM-DD) |
| Audit ID | Rich Text | Format: [TYPE]-YYYYMMDD-NNN |
### Language Guidelines
- Report content in Korean (한국어)
- Keep technical English terms as-is (e.g., SEO Audit, Core Web Vitals, Schema Markup)
- URLs and code remain unchanged
### Example MCP Call
```bash
mcp-cli call notion/API-post-page '{"parent": {"database_id": "2c8581e5-8a1e-8035-880b-e38cefc2f3ef"}, "properties": {...}}'
```
| Category | `Schema/Structured Data` |
| Priority | map gate: FAIL→Critical/High, PASS-with-P1→Medium, PASS-clean→Low |
| Audit ID | `SCHEMA-YYYYMMDD-NNN` |
Report content in Korean; keep technical terms (Schema, JSON-LD, rich result) and
URLs/code unchanged.

View File

@@ -0,0 +1,16 @@
url,언어코드,PC/MOBILE,page_type,스키마
https://www.josunhotel.com/en/brand/grand,en,PC,brand-hub,"{""@context"": ""https://schema.org"", ""@type"": ""Organization"", ""@id"": ""https://www.josunhotel.com/#org"", ""name"": ""Josun Hotels & Resorts"", ""url"": ""https://www.josunhotel.com/"", ""logo"": ""https://www.josunhotel.com/logo.png"", ""sameAs"": [""https://www.instagram.com/josunhotelsandresorts/""]}"
https://www.josunhotel.com/ko/grand,ko,PC,hotel,"{""@context"": ""https://schema.org"", ""@type"": ""Hotel"", name: ""그랜드조선"",}"
https://www.josunhotel.com/ko/grand/rooms,ko,MOBILE,rooms,"{""@context"": ""https://schema.org"", ""name"": ""디럭스룸"", ""url"": ""https://www.josunhotel.com/ko/grand/rooms""}"
https://www.josunhotel.com/ko/palace,ko,PC,hotel,"{""@context"": ""https://example.org"", ""@type"": ""Hotel"", ""name"": ""조선팰리스"", ""address"": {""@type"": ""PostalAddress"", ""streetAddress"": ""테헤란로 231"", ""addressLocality"": ""서울"", ""addressCountry"": ""KR""}}"
https://www.josunhotel.com/ko/lescape,ko,PC,hotel,"{""@context"": ""https://schema.org"", ""@type"": ""Hotel"", ""name"": ""레스케이프 호텔"", ""telephone"": ""+82-2-317-4000"", ""description"": ""조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다.""}"
https://www.josunhotel.com/ko/grand/dining,ko,PC,restaurant,"{""@context"": ""https://schema.org"", ""@type"": ""Restaurant"", ""name"": ""예시 레스토랑"", ""address"": {""@type"": ""PostalAddress"", ""streetAddress"": ""수정필요"", ""addressCountry"": ""KR""}, ""servesCuisine"": ""Korean""}"
https://www.josunhotel.com/ko/westin,ko,PC,hotel,"{""@context"": ""https://schema.org"", ""@type"": ""Hotel"", ""name"": ""웨스틴 조선 서울"", ""telephone"": ""+82-2-771-0500"", ""address"": {""@type"": ""PostalAddress"", ""streetAddress"": ""소공로 106"", ""addressLocality"": ""서울"", ""addressCountry"": ""KR""}, ""description"": ""조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다.""}"
https://www.josunhotel.com/en/westin,en,PC,hotel,"{""@context"": ""https://schema.org"", ""@type"": ""Hotel"", ""name"": ""웨스틴 조선 서울"", ""telephone"": ""+82-2-771-9999"", ""address"": {""@type"": ""PostalAddress"", ""streetAddress"": ""소공로 106"", ""addressLocality"": ""Seoul"", ""addressCountry"": ""KR""}, ""description"": ""조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다.""}"
https://www.josunhotel.com/ko,ko,PC,home,"{""@context"": ""https://schema.org"", ""@type"": ""WebSite"", ""name"": ""조선호텔앤리조트"", ""url"": ""https://www.josunhotel.com/"", ""publisher"": {""@id"": ""https://www.josunhotel.com/#missing-org""}}"
https://www.josunhotel.com/ko/grand/location,ko,PC,location,"{""@context"": ""https://schema.org"", ""@type"": ""Hotel"", ""name"": ""그랜드 조선 부산"", ""address"": {""@type"": ""PostalAddress"", ""streetAddress"": ""동백로 60"", ""addressLocality"": ""부산"", ""addressCountry"": ""KR""}, ""geo"": {""@type"": ""GeoCoordinates"", ""latitude"": 129.1603, ""longitude"": 35.1586}}"
https://www.josunhotel.com/ko/offers/spring,ko,PC,offer,"{""@context"": ""https://schema.org"", ""@type"": ""Offer"", ""price"": ""350000"", ""priceCurrency"": ""KRW"", ""validFrom"": ""2026년 3월 1일"", ""url"": ""https://www.josunhotel.com/ko/offers/spring""}"
https://www.josunhotel.com/ko/offers/dining,ko,PC,offer,"{""@context"": ""https://schema.org"", ""@type"": ""Offer"", ""price"": ""120000"", ""priceCurrency"": """", ""availability"": ""https://schema.org/InStock""}"
https://www.josunhotel.com/ko/spa,ko,PC,facility,"{""@context"": ""https://schema.org"", ""@type"": ""SpaResort"", ""name"": ""조선 스파""}"
https://www.josunhotel.com/ko/grand/intro,ko,PC,hotel,"{""@context"": ""https://schema.org"", ""@type"": ""Hotel"", ""name"": ""그랜드 조선 제주"", ""address"": {""@type"": ""PostalAddress"", ""streetAddress"": ""중문관광로 75"", ""addressLocality"": ""제주"", ""addressCountry"": ""KR""}, ""description"": ""조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다.""}"
https://www.josunhotel.com/ko/faq?stale=1,ko,MOBILE,faq,"{""@context"": ""https://schema.org"", ""@type"": ""FAQPage"", ""mainEntity"": [{""@type"": ""Question"", ""name"": ""체크인 시간은 언제인가요?"", ""acceptedAnswer"": {""@type"": ""Answer"", ""text"": ""오후 3시부터 체크인 가능합니다.""}}]}"
1 url 언어코드 PC/MOBILE page_type 스키마
2 https://www.josunhotel.com/en/brand/grand en PC brand-hub {"@context": "https://schema.org", "@type": "Organization", "@id": "https://www.josunhotel.com/#org", "name": "Josun Hotels & Resorts", "url": "https://www.josunhotel.com/", "logo": "https://www.josunhotel.com/logo.png", "sameAs": ["https://www.instagram.com/josunhotelsandresorts/"]}
3 https://www.josunhotel.com/ko/grand ko PC hotel {"@context": "https://schema.org", "@type": "Hotel", name: "그랜드조선",}
4 https://www.josunhotel.com/ko/grand/rooms ko MOBILE rooms {"@context": "https://schema.org", "name": "디럭스룸", "url": "https://www.josunhotel.com/ko/grand/rooms"}
5 https://www.josunhotel.com/ko/palace ko PC hotel {"@context": "https://example.org", "@type": "Hotel", "name": "조선팰리스", "address": {"@type": "PostalAddress", "streetAddress": "테헤란로 231", "addressLocality": "서울", "addressCountry": "KR"}}
6 https://www.josunhotel.com/ko/lescape ko PC hotel {"@context": "https://schema.org", "@type": "Hotel", "name": "레스케이프 호텔", "telephone": "+82-2-317-4000", "description": "조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다."}
7 https://www.josunhotel.com/ko/grand/dining ko PC restaurant {"@context": "https://schema.org", "@type": "Restaurant", "name": "예시 레스토랑", "address": {"@type": "PostalAddress", "streetAddress": "수정필요", "addressCountry": "KR"}, "servesCuisine": "Korean"}
8 https://www.josunhotel.com/ko/westin ko PC hotel {"@context": "https://schema.org", "@type": "Hotel", "name": "웨스틴 조선 서울", "telephone": "+82-2-771-0500", "address": {"@type": "PostalAddress", "streetAddress": "소공로 106", "addressLocality": "서울", "addressCountry": "KR"}, "description": "조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다."}
9 https://www.josunhotel.com/en/westin en PC hotel {"@context": "https://schema.org", "@type": "Hotel", "name": "웨스틴 조선 서울", "telephone": "+82-2-771-9999", "address": {"@type": "PostalAddress", "streetAddress": "소공로 106", "addressLocality": "Seoul", "addressCountry": "KR"}, "description": "조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다."}
10 https://www.josunhotel.com/ko ko PC home {"@context": "https://schema.org", "@type": "WebSite", "name": "조선호텔앤리조트", "url": "https://www.josunhotel.com/", "publisher": {"@id": "https://www.josunhotel.com/#missing-org"}}
11 https://www.josunhotel.com/ko/grand/location ko PC location {"@context": "https://schema.org", "@type": "Hotel", "name": "그랜드 조선 부산", "address": {"@type": "PostalAddress", "streetAddress": "동백로 60", "addressLocality": "부산", "addressCountry": "KR"}, "geo": {"@type": "GeoCoordinates", "latitude": 129.1603, "longitude": 35.1586}}
12 https://www.josunhotel.com/ko/offers/spring ko PC offer {"@context": "https://schema.org", "@type": "Offer", "price": "350000", "priceCurrency": "KRW", "validFrom": "2026년 3월 1일", "url": "https://www.josunhotel.com/ko/offers/spring"}
13 https://www.josunhotel.com/ko/offers/dining ko PC offer {"@context": "https://schema.org", "@type": "Offer", "price": "120000", "priceCurrency": "₩", "availability": "https://schema.org/InStock"}
14 https://www.josunhotel.com/ko/spa ko PC facility {"@context": "https://schema.org", "@type": "SpaResort", "name": "조선 스파"}
15 https://www.josunhotel.com/ko/grand/intro ko PC hotel {"@context": "https://schema.org", "@type": "Hotel", "name": "그랜드 조선 제주", "address": {"@type": "PostalAddress", "streetAddress": "중문관광로 75", "addressLocality": "제주", "addressCountry": "KR"}, "description": "조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다."}
16 https://www.josunhotel.com/ko/faq?stale=1 ko MOBILE faq {"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "체크인 시간은 언제인가요?", "acceptedAnswer": {"@type": "Answer", "text": "오후 3시부터 체크인 가능합니다."}}]}

View File

@@ -0,0 +1,74 @@
# Defect Taxonomy
Every code `validate_schema.py` can emit, its default severity, and what to do.
The validator writes these to `defect_log.csv` (columns: `entry_id, url, node_type,
layer, code, severity, message, status, owner, note`) and `results.json`.
## Severity model
| Severity | Definition | Owner action |
|---|---|---|
| **P0** | Blocker — breaks parsing, blocks the rich result, or ships wrong/placeholder data. **Fails the gate.** | Must fix before the entry reaches client review. |
| **P1** | Real defect, doesn't block the rich result. | Fix before launch; track in the triage log. |
| **P2** | Optimization — recommended properties, formatting, orphan URLs. | Backlog; fix opportunistically. |
`--strict` promotes vocabulary/format warnings (and unknown types) from P2 to P1 and
turns on `UNEXPECTED_PROPERTY`. `--no-recommended` drops `MISSING_RECOMMENDED` entirely.
**Neither changes the gate — the gate is always "zero P0."**
## Code reference
### Layer 0 — Coverage
| Code | Sev | Trigger | Fix |
|---|---|---|---|
| `COVERAGE_MISSING` | P1 | Inventory URL has no authored entry. | Author the entry, or remove the URL from the inventory. |
| `COVERAGE_ORPHAN` | P2 | Entry URL isn't in the inventory. | Fix the URL typo, or update the canonical list. |
### Layer 1 — Syntax
| Code | Sev | Trigger | Fix |
|---|---|---|---|
| `INVALID_JSON` | P0 | JSON does not parse. | Fix the JSON (trailing comma, unquoted key, smart quotes). |
| `NO_SCHEMA_IN_HTML` | P0 | Live page has no `ld+json` block (Mode B). | Confirm the tag deployed and renders. |
| `MISSING_CONTEXT` | P1 | No top-level `@context`. | Add `"@context": "https://schema.org"`. |
| `WRONG_CONTEXT` | P1 | `@context` isn't schema.org. | Correct the context URL. |
| `NO_TYPE` | P1 | No `@type` anywhere in the entry. | Add the intended `@type`. |
| `ENCODING_CORRUPTION` | P1 | Replacement char `<60>` present. | Re-export as UTF-8; check the source pipeline. |
| `FETCH_ERROR` | P1 | Live URL could not be fetched (Mode B). | Check the URL/network; retry. |
### Layer 2 — Vocabulary & value formats
| Code | Sev (strict) | Trigger | Fix |
|---|---|---|---|
| `UNKNOWN_TYPE` | P2 (P1) | `@type` not in the curated rule set. | If intended, add it to `schema_rules.json`; else correct the type. |
| `UNEXPECTED_PROPERTY` | — (P1) | Property unknown for a known type (**strict only**). | Remove the typo'd property, or add it to the type's `allowed`. |
| `BAD_URL` | P2 (P1) | A URL property isn't an `http(s)` URL. | Use an absolute URL. |
| `BAD_DATE` | P2 (P1) | A date property isn't ISO-8601. | Use `YYYY-MM-DD` (or full datetime). |
| `BAD_LANG` | P2 (P1) | `inLanguage`/`availableLanguage` isn't a BCP-47 code. | Use `ko`, `en`, `ja`, `zh`, … |
| `BAD_CURRENCY` | P2 (P1) | `priceCurrency` isn't a 3-letter ISO-4217 code. | Use `KRW`/`USD`, not `₩`/`$`. |
| `BAD_NUMBER` | P2 (P1) | A numeric property isn't numeric. | Remove units/commas; keep digits. |
### Layer 3 — Rich-result requirements
| Code | Sev | Trigger | Fix |
|---|---|---|---|
| `MISSING_REQUIRED` | P0 | A Google-required property is absent. | Add the property — the rich result is blocked without it. |
| `MISSING_RECOMMENDED` | P2 | Recommended properties absent (one line per node, lists all). | Add what applies to improve eligibility/appearance. |
### Layer 4 — Consistency
| Code | Sev | Trigger | Fix |
|---|---|---|---|
| `PLACEHOLDER_TEXT` | P0 | Boilerplate token in a string (`예시`, `수정필요`, `lorem`, `{{`, …). | Replace with real content. |
| `NAP_PHONE_MISMATCH` | P0 | Same business, different `telephone` across entries. | Reconcile to the canonical phone. |
| `NAP_ADDRESS_MISMATCH` | P0 | Same business, different `streetAddress`. | Reconcile to the canonical address. |
| `DUPLICATE_ID` | P1 | One `@id` defined ≥2× with differing content. | Make definitions identical, or split the `@id`. |
| `DANGLING_ID` | P1 | `{"@id": …}` reference to a node never defined. | Define the node, or fix the reference. |
| `GEO_SWAPPED` | P1 | latitude/longitude transposed (swapping fixes it). | Swap the values. |
| `GEO_OUT_OF_RANGE` | P1 | Coordinates impossible (lat∉[-90,90] or lon∉[-180,180]). | Correct the coordinates. |
| `DUPLICATE_DESCRIPTION` | P1 | Same description reused across ≥3 entries. | Write distinct descriptions per page. |
## Triage workflow
1. Sort `defect_log.csv` by severity (already sorted P0→P1→P2 on write).
2. **P0:** assign an owner, fix, re-run. No P0 may survive into client review.
3. **P1:** set `owner` + `status`, decide fix-now vs accept; log accepted ones in
`templates/decision-log.md`.
4. **P2:** schedule into the optimization backlog.
5. Re-run after fixes and confirm no regressions before advancing the stage gate.

View File

@@ -0,0 +1,68 @@
# Hotel Page-Type → Schema-Type Map
The G1 (설계) deliverable: every page template gets an assigned schema `@type` and a
required-property list *before* anyone authors entries. Locking this map first is what
prevents the most expensive error — authoring hundreds of entries against the wrong type.
This is the reusable, hotel-domain map. The worked example is JHR (Josun Hotels &
Resorts, `josunhotel.com`) — a multi-brand, multi-language, multi-property group — but
the mapping applies to any lodging-group site (it replaces the earlier client-specific
draft). Adapt the brand/property layer to the client; the page-type → type rules are stable.
## Site shape this map assumes
```
대표 허브 (group) → Organization + WebSite (one canonical node set, @id-anchored)
브랜드 허브 (brand) → Brand / Organization + Hotel families
개별 호텔 (property) → Hotel / LodgingBusiness / Resort
├─ 객실 (rooms) → HotelRoom / Suite (nested or itemList)
├─ 다이닝 (dining) → Restaurant / BarOrPub
├─ 시설·웨딩·연회 → LocalBusiness / MeetingRoom (nested)
├─ 프로모션 (offers) → Offer / AggregateOffer
└─ FAQ / 안내 → FAQPage
```
Each rendered URL also multiplies by **language × device** (ko/en/ja/zh × PC/MOBILE),
which is why the entry count reaches the thousands. The schema `@type` does **not**
change across language/device — only the localized string values do. (Use that fact:
NAP, geo, and `@id` must stay identical across the language variants of one property;
Layer 4 will catch it when they drift.)
## The map
| Page template (Korean / English) | Primary `@type` | Required (P0) | Add these (recommended) |
|---|---|---|---|
| 대표 홈 / group home | `WebSite` (+ `Organization`) | name, url / name, url | publisher, potentialAction(SearchAction), inLanguage / logo, sameAs |
| 브랜드 허브 / brand hub | `Organization` (+ `Brand`) | name, url | logo, sameAs, brand |
| 호텔 메인 / property home | `Hotel` (or `LodgingBusiness`, `Resort`) | name, address | telephone, image, priceRange, geo, url, starRating, aggregateRating |
| 객실 목록·상세 / rooms | `Hotel` w/ `containsPlace``HotelRoom`/`Suite`, or `ItemList` | name, address (host) | image, occupancy, bed, amenityFeature |
| 다이닝 / restaurant | `Restaurant` (or `BarOrPub`) | name, address | servesCuisine, priceRange, telephone, menu, openingHoursSpecification, acceptsReservations |
| 부대시설·웨딩·연회 / facilities | `LocalBusiness` w/ `MeetingRoom` nested | name, address | telephone, openingHoursSpecification, image, url |
| 프로모션·패키지 / offers | `Offer` (or `AggregateOffer`) | price, priceCurrency / lowPrice, priceCurrency | availability, url, validFrom, priceValidUntil |
| 멤버십 / membership | `MemberProgram` | name | hasTiers, hostingOrganization, url |
| 위치·오시는 길 / location | `Hotel` w/ `geo``GeoCoordinates` | name, address | geo (lat/long), hasMap |
| FAQ / 자주 묻는 질문 | `FAQPage``Question`/`Answer` | mainEntity / name, acceptedAnswer / text | — |
| 공지·매거진·기사 / article | `Article` / `NewsArticle` / `BlogPosting` | headline | author, datePublished, image, dateModified, publisher |
| 모든 하위 페이지 / breadcrumbs | `BreadcrumbList``ListItem` | itemListElement / position | item, name |
## Conventions that keep Layer 4 green
- **Anchor shared nodes by `@id`.** Define `Organization` and `WebSite` once
(`https://…/#organization`, `https://…/#website`) and reference them everywhere with
`{"@id": "…"}`. Avoids `DANGLING_ID` (define before you reference) and `DUPLICATE_ID`
(don't redefine with different content).
- **One canonical NAP per property.** The same `telephone` and `streetAddress` must
appear in every language/device variant of a property, or `NAP_*_MISMATCH` (P0) fires.
- **Distinct descriptions.** A reused boilerplate description across ≥3 pages →
`DUPLICATE_DESCRIPTION` (P1). Write per-page copy.
- **geo as `{latitude, longitude}`** in decimal degrees; Korea is lat ≈ 3339, lon ≈
124132. Transposing them trips `GEO_SWAPPED`.
- **No placeholders.** `예시 / 수정필요 / 임시 / lorem / {{…}}` anywhere → `PLACEHOLDER_TEXT`
(P0). The gate exists precisely to stop these from reaching the client.
## Using the map in the lifecycle
- **G1 설계:** fill this table for the client's actual templates; that *is* the schema
spec. DoD: every template has a type + required list.
- **G2 개발:** authors produce entries against it; `--strict` run; zero P0 to advance.
- **G3+:** the map is the reference reviewers and the validator agree on.

View File

@@ -0,0 +1,123 @@
# Validation Methodology
The reasoning behind the 5 layers, and the type → requirement matrix the validator
enforces. The matrix is the human-readable mirror of `scripts/schema_rules.json`
if you change one, change the other.
## Why a machine gate before human review
At a few dozen entries, a person can eyeball JSON-LD. At hundreds (a multi-language,
multi-device, multi-property hotel site easily reaches 2,000+ URLs), eyeballing
fails in a predictable way: the reviewer drowns in *mechanical* errors (a missing
required field, a bad date format, a typo'd URL) and never reaches the *judgement*
errors that actually need a human (is this the right schema type for this page? is
this description accurate?).
The fix is not "review harder." It is to split the work by who is best at it:
| Error class | Best checker | This skill |
|---|---|---|
| Mechanical (parse, required-present, value format, duplicate, consistency) | A script, every time | Layers 04, automated |
| Judgement (type choice, copy accuracy, intent) | A human, once | Client reviews only P0-free entries |
So the gate runs first. **An entry reaches client review only when it has zero P0.**
The client then reviews a clean set against a defect report — never raw JSON in a meeting.
## The layers, in order
Each layer assumes the previous one passed for that entry. A fatal L1 failure
(unparseable JSON, no `@type`) stops deeper layers for that entry — there is nothing
to inspect.
### L0 — Coverage (needs `--url-list`)
Compares the canonical URL inventory against the URLs that actually have an entry.
- `COVERAGE_MISSING` (P1): inventory URL with no authored entry — a gap to fill.
- `COVERAGE_ORPHAN` (P2): entry whose URL isn't in the inventory — a typo, a stale
path, or a list that's out of date. (Expect many orphans if your inventory is a
subset; expect ~zero when it's the real canonical list.)
### L1 — Syntax
The cheapest, hardest blockers. If these fail, nothing downstream is trustworthy.
- `INVALID_JSON` (P0), `NO_SCHEMA_IN_HTML` (P0, live mode).
- `MISSING_CONTEXT` / `WRONG_CONTEXT` / `NO_TYPE` / `ENCODING_CORRUPTION` (P1).
### L2 — Vocabulary & value formats
Is the type known, and are values well-formed?
- `UNKNOWN_TYPE` (P2; P1 in `--strict`): type isn't in the curated rule set. A
*warning*, not an error — add it to `schema_rules.json` if it's intended.
- `BAD_URL` / `BAD_DATE` / `BAD_LANG` / `BAD_CURRENCY` / `BAD_NUMBER` (P2; P1 strict).
- `UNEXPECTED_PROPERTY` (P1, `--strict` only): a property not known for a known type.
**Off by default** — flagging every unexpected property offline produces exactly the
false-positive flood that makes reviewers distrust the tool.
### L3 — Rich-result requirements
The contract Google enforces for eligibility.
- `MISSING_REQUIRED` (P0): a required property is absent → the rich result is blocked.
- `MISSING_RECOMMENDED` (P2): recommended properties absent. **Aggregated to one line
per node** (never one defect per property) — this is the single most important
noise-control decision in the tool.
### L4 — Consistency (cross-node / cross-entry)
The errors a per-entry check can't see.
- `PLACEHOLDER_TEXT` (P0): boilerplate that escaped authoring (`예시`, `수정필요`,
`lorem`, `{{`, …). Almost always a real, embarrassing leak.
- `NAP_PHONE_MISMATCH` / `NAP_ADDRESS_MISMATCH` (P0): the same business shows
different Name/Address/Phone across entries — a local-SEO and trust problem.
- `DUPLICATE_ID` (P1): one `@id` defined twice with different content.
- `DANGLING_ID` (P1): a `{"@id": …}` reference points at a node never defined.
- `GEO_SWAPPED` / `GEO_OUT_OF_RANGE` (P1): latitude/longitude transposed or impossible.
- `DUPLICATE_DESCRIPTION` (P1): the same description reused across ≥3 entries.
## Severity → gate
| Severity | Meaning | Gate effect |
|---|---|---|
| **P0** | Blocker. Breaks parsing, blocks the rich result, or publishes wrong data. | **Fails the gate.** Process exits 1. Entry must not reach client review. |
| **P1** | Fix before launch. Real defect, doesn't block the rich result. | Triage backlog. |
| **P2** | Optimization. Recommended props, style, orphan URLs. | Optimization backlog. |
Full code list: `defect-taxonomy.md`.
## Type → requirement matrix (mirror of `schema_rules.json`)
`required` missing → **P0**. `recommended` missing → **P2** (aggregated). Anything in
`allowed` is accepted silently. Properties outside all three are flagged only in `--strict`.
| Type | Required (P0 if missing) | Recommended (P2 if missing) |
|---|---|---|
| Organization | name, url | logo, sameAs, contactPoint, address |
| WebSite | name, url | publisher, potentialAction, inLanguage |
| WebPage | name | url, isPartOf, primaryImageOfPage, breadcrumb, datePublished, dateModified |
| Hotel / LodgingBusiness / Resort | name, address | telephone, image, priceRange, geo, url, starRating, aggregateRating |
| LocalBusiness | name, address | telephone, openingHoursSpecification, geo, image, url, priceRange, aggregateRating |
| Restaurant / FoodEstablishment | name, address | servesCuisine, priceRange, telephone, menu, openingHoursSpecification |
| FAQPage | mainEntity | — |
| Question | name, acceptedAnswer | — |
| Answer | text | — |
| BreadcrumbList / ItemList | itemListElement | — |
| ListItem | position | item, name |
| Product | name | image, offers, brand, aggregateRating, review, description, sku |
| Offer | price, priceCurrency | availability, url, validFrom, priceValidUntil |
| Article / NewsArticle / BlogPosting | headline | author, datePublished, image, dateModified, publisher |
| Event | name, startDate, location | endDate, offers, performer, image, eventStatus, eventAttendanceMode, organizer |
| Review | reviewRating, author | datePublished, reviewBody, itemReviewed |
| AggregateRating | ratingValue | reviewCount, ratingCount, bestRating |
| MemberProgram | name | hasTiers, hostingOrganization, url |
**Container types** (validated for value formats, but *not* for required/recommended,
because they only ever appear nested): PostalAddress, GeoCoordinates, ImageObject,
ContactPoint, OpeningHoursSpecification, Rating, Brand, EntryPoint, Place, OfferCatalog,
ReserveAction, MeetingRoom, Room/HotelRoom/Suite, MemberProgramTier, Menu/MenuItem, … (full
list in `schema_rules.json``container_types`).
## Extending the rules
Add a type, tighten a requirement, or recognize a new container by editing
`scripts/schema_rules.json` **only** — no Python change needed:
- New rich-result type → add to `known_types` with `required` / `recommended` / `allowed`.
- New nested type to stop "unknown type" warnings → add to `container_types`.
- New value-format property → add to the relevant `value_formats` group.
- New placeholder token to catch → add to `placeholder_tokens`.
After any edit, re-run `make_sample.py` + `validate_schema.py` against the fixture to
confirm you didn't regress.

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
make_sample.py — generate fixtures/sample_schema.csv.
A small, deliberately FLAWED hotel dataset (Josun-style, fictional values) that
seeds at least one defect per validation layer. Use it to learn the tool and to
regression-test changes to validate_schema.py or schema_rules.json:
python make_sample.py
python validate_schema.py ../fixtures/sample_schema.csv --out /tmp/demo_out
Each row's comment names the defect(s) it is designed to trigger.
"""
import csv
import json
from pathlib import Path
OUT = Path(__file__).resolve().parent.parent / "fixtures" / "sample_schema.csv"
CTX = "https://schema.org"
SHARED_DESC = ("조선호텔앤리조트가 운영하는 럭셔리 호텔로, 도심 속에서 품격 있는 휴식을 "
"제공합니다. 최상의 서비스와 시설을 경험하실 수 있습니다.") # >30 chars, reused 3x
def jd(obj):
return json.dumps(obj, ensure_ascii=False)
# Each tuple: (url, lang, device, page_type, jsonld_string)
ROWS = []
# 1) CLEAN Organization — only a recommended gap (P2 MISSING_RECOMMENDED, aggregated)
ROWS.append((
"https://www.josunhotel.com/en/brand/grand", "en", "PC", "brand-hub",
jd({"@context": CTX, "@type": "Organization", "@id": "https://www.josunhotel.com/#org",
"name": "Josun Hotels & Resorts", "url": "https://www.josunhotel.com/",
"logo": "https://www.josunhotel.com/logo.png",
"sameAs": ["https://www.instagram.com/josunhotelsandresorts/"]}),
))
# 2) INVALID JSON (P0 INVALID_JSON) — trailing comma, unquoted key
ROWS.append((
"https://www.josunhotel.com/ko/grand", "ko", "PC", "hotel",
'{"@context": "https://schema.org", "@type": "Hotel", name: "그랜드조선",}',
))
# 3) MISSING @type (P1 NO_TYPE)
ROWS.append((
"https://www.josunhotel.com/ko/grand/rooms", "ko", "MOBILE", "rooms",
jd({"@context": CTX, "name": "디럭스룸", "url": "https://www.josunhotel.com/ko/grand/rooms"}),
))
# 4) WRONG @context (P1 WRONG_CONTEXT)
ROWS.append((
"https://www.josunhotel.com/ko/palace", "ko", "PC", "hotel",
jd({"@context": "https://example.org", "@type": "Hotel", "name": "조선팰리스",
"address": {"@type": "PostalAddress", "streetAddress": "테헤란로 231",
"addressLocality": "서울", "addressCountry": "KR"}}),
))
# 5) Hotel MISSING REQUIRED address (P0 MISSING_REQUIRED)
ROWS.append((
"https://www.josunhotel.com/ko/lescape", "ko", "PC", "hotel",
jd({"@context": CTX, "@type": "Hotel", "name": "레스케이프 호텔",
"telephone": "+82-2-317-4000", "description": SHARED_DESC}),
))
# 6) PLACEHOLDER text (P0 PLACEHOLDER_TEXT)
ROWS.append((
"https://www.josunhotel.com/ko/grand/dining", "ko", "PC", "restaurant",
jd({"@context": CTX, "@type": "Restaurant", "name": "예시 레스토랑",
"address": {"@type": "PostalAddress", "streetAddress": "수정필요",
"addressCountry": "KR"}, "servesCuisine": "Korean"}),
))
# 7a + 7b) NAP PHONE MISMATCH (P0 NAP_PHONE_MISMATCH) — same business, two phones
ROWS.append((
"https://www.josunhotel.com/ko/westin", "ko", "PC", "hotel",
jd({"@context": CTX, "@type": "Hotel", "name": "웨스틴 조선 서울",
"telephone": "+82-2-771-0500",
"address": {"@type": "PostalAddress", "streetAddress": "소공로 106",
"addressLocality": "서울", "addressCountry": "KR"},
"description": SHARED_DESC}),
))
ROWS.append((
"https://www.josunhotel.com/en/westin", "en", "PC", "hotel",
jd({"@context": CTX, "@type": "Hotel", "name": "웨스틴 조선 서울",
"telephone": "+82-2-771-9999",
"address": {"@type": "PostalAddress", "streetAddress": "소공로 106",
"addressLocality": "Seoul", "addressCountry": "KR"},
"description": SHARED_DESC}),
))
# 8) DANGLING @id reference (P1 DANGLING_ID) — publisher points at undefined node
ROWS.append((
"https://www.josunhotel.com/ko", "ko", "PC", "home",
jd({"@context": CTX, "@type": "WebSite", "name": "조선호텔앤리조트",
"url": "https://www.josunhotel.com/",
"publisher": {"@id": "https://www.josunhotel.com/#missing-org"}}),
))
# 9) SWAPPED geo (P1 GEO_SWAPPED) — lat/long transposed for Seoul
ROWS.append((
"https://www.josunhotel.com/ko/grand/location", "ko", "PC", "location",
jd({"@context": CTX, "@type": "Hotel", "name": "그랜드 조선 부산",
"address": {"@type": "PostalAddress", "streetAddress": "동백로 60",
"addressLocality": "부산", "addressCountry": "KR"},
"geo": {"@type": "GeoCoordinates", "latitude": 129.1603, "longitude": 35.1586}}),
))
# 10) BAD date (P2 BAD_DATE) in an Offer-bearing page
ROWS.append((
"https://www.josunhotel.com/ko/offers/spring", "ko", "PC", "offer",
jd({"@context": CTX, "@type": "Offer", "price": "350000", "priceCurrency": "KRW",
"validFrom": "2026년 3월 1일", "url": "https://www.josunhotel.com/ko/offers/spring"}),
))
# 11) BAD currency symbol (P2 BAD_CURRENCY)
ROWS.append((
"https://www.josunhotel.com/ko/offers/dining", "ko", "PC", "offer",
jd({"@context": CTX, "@type": "Offer", "price": "120000", "priceCurrency": "",
"availability": "https://schema.org/InStock"}),
))
# 12) UNKNOWN type (P2 UNKNOWN_TYPE)
ROWS.append((
"https://www.josunhotel.com/ko/spa", "ko", "PC", "facility",
jd({"@context": CTX, "@type": "SpaResort", "name": "조선 스파"}),
))
# 13) Third reuse of SHARED_DESC → triggers DUPLICATE_DESCRIPTION (P1) across rows 5,7a,7b,13
ROWS.append((
"https://www.josunhotel.com/ko/grand/intro", "ko", "PC", "hotel",
jd({"@context": CTX, "@type": "Hotel", "name": "그랜드 조선 제주",
"address": {"@type": "PostalAddress", "streetAddress": "중문관광로 75",
"addressLocality": "제주", "addressCountry": "KR"},
"description": SHARED_DESC}),
))
# 14) CLEAN FAQPage — exercises a passing entry (and an inventory-orphan URL for L0 demo)
ROWS.append((
"https://www.josunhotel.com/ko/faq?stale=1", "ko", "MOBILE", "faq",
jd({"@context": CTX, "@type": "FAQPage", "mainEntity": [
{"@type": "Question", "name": "체크인 시간은 언제인가요?",
"acceptedAnswer": {"@type": "Answer", "text": "오후 3시부터 체크인 가능합니다."}}]}),
))
def main():
OUT.parent.mkdir(parents=True, exist_ok=True)
with open(OUT, "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f)
w.writerow(["url", "언어코드", "PC/MOBILE", "page_type", "스키마"]) # Korean aliases on purpose
w.writerows(ROWS)
print(f"Wrote {len(ROWS)} entries → {OUT}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,6 @@
# validate_schema.py runs on the Python standard library alone for
# CSV / JSON / JSONL / directory inputs (the offline default).
#
# Optional extras, installed only when you need them:
openpyxl>=3.1 # required to read .xlsx datasets and .xlsx URL inventories
requests>=2.31 # required only for --live (Mode B) URL fetching

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
{
"_meta": {
"version": "1.0",
"scope": "Curated, hotel-focused subset of schema.org + Google rich-result requirements.",
"intent": "Self-contained offline rules (the runtime cannot reach schema.org or Google). Unknown types/properties degrade to warnings, never hard errors, to avoid false positives. To support a new type or tighten a rule, edit THIS file only.",
"sources": "schema.org/Hotel, schema.org/LocalBusiness, Google Search Central 'Structured data' rich-result docs (as of 2025)."
},
"valid_contexts": [
"https://schema.org",
"http://schema.org",
"https://schema.org/",
"http://schema.org/",
"https://www.schema.org",
"http://www.schema.org"
],
"global_properties": [
"@context", "@type", "@id", "@graph", "@reverse",
"name", "alternateName", "legalName", "description", "disambiguatingDescription",
"url", "image", "logo", "sameAs", "identifier", "mainEntityOfPage",
"additionalType", "subjectOf", "potentialAction", "inLanguage"
],
"known_types": {
"Organization": {
"required": ["name", "url"],
"recommended": ["logo", "sameAs", "contactPoint", "address"],
"allowed": ["legalName", "foundingDate", "parentOrganization", "subOrganization", "brand", "telephone", "email", "founder", "numberOfEmployees", "memberOf", "hasMerchantReturnPolicy", "member"]
},
"Corporation": {
"required": ["name", "url"],
"recommended": ["logo", "sameAs", "address"],
"allowed": ["legalName", "foundingDate", "parentOrganization", "tickerSymbol", "telephone", "email", "brand"]
},
"WebSite": {
"required": ["name", "url"],
"recommended": ["publisher", "potentialAction", "inLanguage"],
"allowed": ["alternateName", "about", "copyrightHolder", "copyrightYear"]
},
"WebPage": {
"required": ["name"],
"recommended": ["url", "isPartOf", "primaryImageOfPage", "breadcrumb", "datePublished", "dateModified"],
"allowed": ["about", "mentions", "speakable", "lastReviewed", "reviewedBy", "significantLink"]
},
"LocalBusiness": {
"required": ["name", "address"],
"recommended": ["telephone", "openingHoursSpecification", "geo", "image", "url", "priceRange", "aggregateRating"],
"allowed": ["email", "openingHours", "paymentAccepted", "currenciesAccepted", "areaServed", "hasMap", "department", "menu", "review", "containedInPlace", "containsPlace", "amenityFeature"]
},
"Hotel": {
"required": ["name", "address"],
"recommended": ["telephone", "image", "priceRange", "geo", "url", "starRating", "aggregateRating", "checkinTime", "checkoutTime"],
"allowed": ["email", "amenityFeature", "petsAllowed", "numberOfRooms", "availableLanguage", "containedInPlace", "containsPlace", "makesOffer", "brand", "currenciesAccepted", "smokingAllowed", "openingHoursSpecification", "audience", "review"]
},
"LodgingBusiness": {
"required": ["name", "address"],
"recommended": ["telephone", "image", "priceRange", "geo", "url", "starRating", "aggregateRating", "checkinTime", "checkoutTime"],
"allowed": ["email", "amenityFeature", "petsAllowed", "numberOfRooms", "availableLanguage", "containedInPlace", "containsPlace", "makesOffer", "currenciesAccepted", "smokingAllowed"]
},
"Resort": {
"required": ["name", "address"],
"recommended": ["telephone", "image", "priceRange", "geo", "url", "starRating", "aggregateRating"],
"allowed": ["email", "amenityFeature", "numberOfRooms", "containedInPlace", "containsPlace", "checkinTime", "checkoutTime"]
},
"Restaurant": {
"required": ["name", "address"],
"recommended": ["servesCuisine", "priceRange", "telephone", "menu", "openingHoursSpecification", "image", "url", "geo", "acceptsReservations"],
"allowed": ["email", "hasMenu", "starRating", "aggregateRating", "review", "containedInPlace", "smokingAllowed"]
},
"FoodEstablishment": {
"required": ["name", "address"],
"recommended": ["servesCuisine", "priceRange", "telephone", "menu", "openingHoursSpecification"],
"allowed": ["email", "hasMenu", "acceptsReservations", "containedInPlace"]
},
"BarOrPub": {
"required": ["name", "address"],
"recommended": ["telephone", "openingHoursSpecification", "priceRange", "servesCuisine"],
"allowed": ["menu", "hasMenu", "image", "url"]
},
"FAQPage": {
"required": ["mainEntity"],
"recommended": [],
"allowed": ["about", "headline", "datePublished", "dateModified"]
},
"Question": {
"required": ["name", "acceptedAnswer"],
"recommended": [],
"allowed": ["text", "answerCount", "suggestedAnswer", "upvoteCount", "author"]
},
"Answer": {
"required": ["text"],
"recommended": [],
"allowed": ["url", "upvoteCount", "author", "dateCreated"]
},
"BreadcrumbList": {
"required": ["itemListElement"],
"recommended": [],
"allowed": ["numberOfItems", "itemListOrder"]
},
"ItemList": {
"required": ["itemListElement"],
"recommended": [],
"allowed": ["numberOfItems", "itemListOrder"]
},
"ListItem": {
"required": ["position"],
"recommended": ["item", "name"],
"allowed": ["url", "image", "nextItem", "previousItem"]
},
"Product": {
"required": ["name"],
"recommended": ["image", "offers", "brand", "aggregateRating", "review", "description", "sku"],
"allowed": ["gtin", "gtin13", "gtin8", "gtin12", "mpn", "color", "material", "category", "audience", "isVariantOf", "additionalProperty", "hasMerchantReturnPolicy"]
},
"Offer": {
"required": ["price", "priceCurrency"],
"recommended": ["availability", "url", "validFrom", "priceValidUntil"],
"allowed": ["itemCondition", "seller", "eligibleRegion", "priceSpecification", "shippingDetails", "availabilityStarts"]
},
"AggregateOffer": {
"required": ["lowPrice", "priceCurrency"],
"recommended": ["highPrice", "offerCount"],
"allowed": ["offers", "availability"]
},
"Article": {
"required": ["headline"],
"recommended": ["author", "datePublished", "image", "dateModified", "publisher"],
"allowed": ["articleBody", "articleSection", "wordCount", "keywords", "speakable"]
},
"NewsArticle": {
"required": ["headline"],
"recommended": ["author", "datePublished", "image", "dateModified", "publisher"],
"allowed": ["articleBody", "dateline", "printSection"]
},
"BlogPosting": {
"required": ["headline"],
"recommended": ["author", "datePublished", "image", "dateModified", "publisher"],
"allowed": ["articleBody", "keywords", "wordCount"]
},
"Event": {
"required": ["name", "startDate", "location"],
"recommended": ["endDate", "offers", "performer", "image", "eventStatus", "eventAttendanceMode", "organizer"],
"allowed": ["doorTime", "previousStartDate", "typicalAgeRange", "maximumAttendeeCapacity"]
},
"Review": {
"required": ["reviewRating", "author"],
"recommended": ["datePublished", "reviewBody", "itemReviewed"],
"allowed": ["publisher", "name"]
},
"AggregateRating": {
"required": ["ratingValue"],
"recommended": ["reviewCount", "ratingCount", "bestRating"],
"allowed": ["worstRating", "itemReviewed"]
},
"MemberProgram": {
"required": ["name"],
"recommended": ["hasTiers", "hostingOrganization", "url"],
"allowed": ["description", "membershipPointsEarned"]
}
},
"container_types": [
"PostalAddress", "GeoCoordinates", "GeoShape", "ImageObject", "VideoObject",
"ContactPoint", "OpeningHoursSpecification", "Rating", "QuantitativeValue",
"MonetaryAmount", "PriceSpecification", "Brand", "EntryPoint", "Place",
"OfferCatalog", "ReserveAction", "OrderAction", "SearchAction", "ViewAction",
"MeetingRoom", "Room", "HotelRoom", "Suite", "LocationFeatureSpecification",
"MemberProgramTier", "MobileApplication", "WebApplication", "SoftwareApplication",
"Menu", "MenuItem", "MenuSection", "Country", "AdministrativeArea", "Duration",
"PropertyValue", "Person", "Audience", "Language"
],
"value_formats": {
"url_props": ["url", "logo", "sameAs", "image", "contentUrl", "thumbnailUrl", "target", "urlTemplate", "installUrl", "menu", "hasMap", "downloadUrl", "embedUrl"],
"date_props": ["datePublished", "dateModified", "dateCreated", "startDate", "endDate", "validFrom", "validThrough", "priceValidUntil", "foundingDate", "uploadDate", "availabilityStarts", "availabilityEnds", "lastReviewed", "previousStartDate"],
"lang_props": ["inLanguage", "availableLanguage"],
"currency_props": ["priceCurrency", "currenciesAccepted"],
"number_props": ["price", "lowPrice", "highPrice", "ratingValue", "reviewCount", "ratingCount", "bestRating", "worstRating", "position", "numberOfRooms", "maxValue", "minValue", "offerCount"]
},
"valid_currencies": ["KRW", "USD", "EUR", "JPY", "CNY", "GBP", "HKD", "SGD", "THB", "AUD", "CAD", "CHF", "TWD", "MYR", "PHP", "VND", "IDR", "INR"],
"valid_language_codes": ["ko", "en", "ja", "zh", "zh-CN", "zh-TW", "zh-Hans", "zh-Hant", "ko-KR", "en-US", "en-GB", "ja-JP", "fr", "de", "es", "ru", "th", "vi", "id", "ms"],
"placeholder_tokens": [
"lorem ipsum", "lorem", "ipsum", "dolor sit", "todo", "tbd", "fixme",
"xxx", "yyy", "zzz", "placeholder", "insert here", "insert text",
"example.com", "your-domain", "yourdomain", "changeme", "sample text",
"{{", "}}", "<insert", "[insert", "n/a", "샘플", "예시", "여기에",
"변경필요", "수정필요", "입력필요", "내용입력", "테스트", "임시"
],
"geo": {
"lat_min": -90.0, "lat_max": 90.0,
"lon_min": -180.0, "lon_max": 180.0,
"kr_lat_range": [33.0, 39.0],
"kr_lon_range": [124.0, 132.0]
}
}

View File

@@ -0,0 +1,876 @@
{
"_meta": {
"version": "1.1",
"scope": "Curated, hotel-focused subset of schema.org + Google rich-result requirements.",
"intent": "Self-contained offline rules (the runtime cannot reach schema.org or Google). Unknown types/properties degrade to warnings, never hard errors, to avoid false positives. To support a new type or tighten a rule, edit THIS file only. [v1.1 2026-05-29: +EventVenue/ExerciseGym/SportsActivityLocation/HealthAndBeautyBusiness/DaySpa/CafeOrCoffeeShop, +LodgingReservation container, Organization/Corporation url->recommended per Google, +slogan/founder/ceo/parentOrganization/award/hasOfferCatalog/openingDate/isPartOf allowances.]",
"sources": "schema.org/Hotel, schema.org/LocalBusiness, Google Search Central 'Structured data' rich-result docs (as of 2025)."
},
"valid_contexts": [
"https://schema.org",
"http://schema.org",
"https://schema.org/",
"http://schema.org/",
"https://www.schema.org",
"http://www.schema.org"
],
"global_properties": [
"@context",
"@type",
"@id",
"@graph",
"@reverse",
"name",
"alternateName",
"legalName",
"description",
"disambiguatingDescription",
"url",
"image",
"logo",
"sameAs",
"identifier",
"mainEntityOfPage",
"additionalType",
"subjectOf",
"potentialAction",
"inLanguage",
"isPartOf"
],
"known_types": {
"Organization": {
"required": [
"name"
],
"recommended": [
"logo",
"sameAs",
"contactPoint",
"address",
"url"
],
"allowed": [
"legalName",
"foundingDate",
"parentOrganization",
"subOrganization",
"brand",
"telephone",
"email",
"founder",
"numberOfEmployees",
"memberOf",
"hasMerchantReturnPolicy",
"member",
"slogan",
"hasOfferCatalog"
]
},
"Corporation": {
"required": [
"name"
],
"recommended": [
"logo",
"sameAs",
"address",
"url"
],
"allowed": [
"legalName",
"foundingDate",
"parentOrganization",
"tickerSymbol",
"telephone",
"email",
"brand",
"founder",
"ceo",
"subOrganization",
"memberOf",
"slogan",
"hasOfferCatalog"
]
},
"WebSite": {
"required": [
"name",
"url"
],
"recommended": [
"publisher",
"potentialAction",
"inLanguage"
],
"allowed": [
"alternateName",
"about",
"copyrightHolder",
"copyrightYear"
]
},
"WebPage": {
"required": [
"name"
],
"recommended": [
"url",
"isPartOf",
"primaryImageOfPage",
"breadcrumb",
"datePublished",
"dateModified"
],
"allowed": [
"about",
"mentions",
"speakable",
"lastReviewed",
"reviewedBy",
"significantLink"
]
},
"LocalBusiness": {
"required": [
"name",
"address"
],
"recommended": [
"telephone",
"openingHoursSpecification",
"geo",
"image",
"url",
"priceRange",
"aggregateRating"
],
"allowed": [
"email",
"openingHours",
"paymentAccepted",
"currenciesAccepted",
"areaServed",
"hasMap",
"department",
"menu",
"review",
"containedInPlace",
"containsPlace",
"amenityFeature"
]
},
"Hotel": {
"required": [
"name",
"address"
],
"recommended": [
"telephone",
"image",
"priceRange",
"geo",
"url",
"starRating",
"aggregateRating",
"checkinTime",
"checkoutTime"
],
"allowed": [
"email",
"amenityFeature",
"petsAllowed",
"numberOfRooms",
"availableLanguage",
"containedInPlace",
"containsPlace",
"makesOffer",
"brand",
"currenciesAccepted",
"smokingAllowed",
"openingHoursSpecification",
"audience",
"review",
"parentOrganization",
"openingDate",
"award"
]
},
"LodgingBusiness": {
"required": [
"name",
"address"
],
"recommended": [
"telephone",
"image",
"priceRange",
"geo",
"url",
"starRating",
"aggregateRating",
"checkinTime",
"checkoutTime"
],
"allowed": [
"email",
"amenityFeature",
"petsAllowed",
"numberOfRooms",
"availableLanguage",
"containedInPlace",
"containsPlace",
"makesOffer",
"currenciesAccepted",
"smokingAllowed"
]
},
"Resort": {
"required": [
"name",
"address"
],
"recommended": [
"telephone",
"image",
"priceRange",
"geo",
"url",
"starRating",
"aggregateRating"
],
"allowed": [
"email",
"amenityFeature",
"numberOfRooms",
"containedInPlace",
"containsPlace",
"checkinTime",
"checkoutTime"
]
},
"Restaurant": {
"required": [
"name",
"address"
],
"recommended": [
"servesCuisine",
"priceRange",
"telephone",
"menu",
"openingHoursSpecification",
"image",
"url",
"geo",
"acceptsReservations"
],
"allowed": [
"email",
"hasMenu",
"starRating",
"aggregateRating",
"review",
"containedInPlace",
"smokingAllowed",
"award"
]
},
"FoodEstablishment": {
"required": [
"name",
"address"
],
"recommended": [
"servesCuisine",
"priceRange",
"telephone",
"menu",
"openingHoursSpecification"
],
"allowed": [
"email",
"hasMenu",
"acceptsReservations",
"containedInPlace"
]
},
"BarOrPub": {
"required": [
"name",
"address"
],
"recommended": [
"telephone",
"openingHoursSpecification",
"priceRange",
"servesCuisine"
],
"allowed": [
"menu",
"hasMenu",
"image",
"url",
"containedInPlace",
"acceptsReservations",
"award"
]
},
"FAQPage": {
"required": [
"mainEntity"
],
"recommended": [],
"allowed": [
"about",
"headline",
"datePublished",
"dateModified",
"isPartOf"
]
},
"Question": {
"required": [
"name",
"acceptedAnswer"
],
"recommended": [],
"allowed": [
"text",
"answerCount",
"suggestedAnswer",
"upvoteCount",
"author"
]
},
"Answer": {
"required": [
"text"
],
"recommended": [],
"allowed": [
"url",
"upvoteCount",
"author",
"dateCreated"
]
},
"BreadcrumbList": {
"required": [
"itemListElement"
],
"recommended": [],
"allowed": [
"numberOfItems",
"itemListOrder"
]
},
"ItemList": {
"required": [
"itemListElement"
],
"recommended": [],
"allowed": [
"numberOfItems",
"itemListOrder"
]
},
"ListItem": {
"required": [
"position"
],
"recommended": [
"item",
"name"
],
"allowed": [
"url",
"image",
"nextItem",
"previousItem"
]
},
"Product": {
"required": [
"name"
],
"recommended": [
"image",
"offers",
"brand",
"aggregateRating",
"review",
"description",
"sku"
],
"allowed": [
"gtin",
"gtin13",
"gtin8",
"gtin12",
"mpn",
"color",
"material",
"category",
"audience",
"isVariantOf",
"additionalProperty",
"hasMerchantReturnPolicy"
]
},
"Offer": {
"required": [
"price",
"priceCurrency"
],
"recommended": [
"availability",
"url",
"validFrom",
"priceValidUntil"
],
"allowed": [
"itemCondition",
"seller",
"eligibleRegion",
"priceSpecification",
"shippingDetails",
"availabilityStarts"
]
},
"AggregateOffer": {
"required": [
"lowPrice",
"priceCurrency"
],
"recommended": [
"highPrice",
"offerCount"
],
"allowed": [
"offers",
"availability"
]
},
"Article": {
"required": [
"headline"
],
"recommended": [
"author",
"datePublished",
"image",
"dateModified",
"publisher"
],
"allowed": [
"articleBody",
"articleSection",
"wordCount",
"keywords",
"speakable"
]
},
"NewsArticle": {
"required": [
"headline"
],
"recommended": [
"author",
"datePublished",
"image",
"dateModified",
"publisher"
],
"allowed": [
"articleBody",
"dateline",
"printSection"
]
},
"BlogPosting": {
"required": [
"headline"
],
"recommended": [
"author",
"datePublished",
"image",
"dateModified",
"publisher"
],
"allowed": [
"articleBody",
"keywords",
"wordCount"
]
},
"Event": {
"required": [
"name",
"startDate",
"location"
],
"recommended": [
"endDate",
"offers",
"performer",
"image",
"eventStatus",
"eventAttendanceMode",
"organizer"
],
"allowed": [
"doorTime",
"previousStartDate",
"typicalAgeRange",
"maximumAttendeeCapacity"
]
},
"Review": {
"required": [
"reviewRating",
"author"
],
"recommended": [
"datePublished",
"reviewBody",
"itemReviewed"
],
"allowed": [
"publisher",
"name"
]
},
"AggregateRating": {
"required": [
"ratingValue"
],
"recommended": [
"reviewCount",
"ratingCount",
"bestRating"
],
"allowed": [
"worstRating",
"itemReviewed"
]
},
"MemberProgram": {
"required": [
"name"
],
"recommended": [
"hasTiers",
"hostingOrganization",
"url"
],
"allowed": [
"description",
"membershipPointsEarned"
]
},
"EventVenue": {
"required": [
"name"
],
"recommended": [
"url",
"address",
"maximumAttendeeCapacity",
"image"
],
"allowed": [
"containedInPlace",
"amenityFeature",
"openingHoursSpecification",
"photo",
"telephone",
"alternateName",
"geo"
]
},
"ExerciseGym": {
"required": [
"name"
],
"recommended": [
"url",
"address",
"openingHoursSpecification",
"image"
],
"allowed": [
"containedInPlace",
"amenityFeature",
"telephone",
"priceRange",
"alternateName"
]
},
"SportsActivityLocation": {
"required": [
"name"
],
"recommended": [
"url",
"address"
],
"allowed": [
"containedInPlace",
"amenityFeature",
"openingHoursSpecification",
"telephone",
"alternateName"
]
},
"HealthAndBeautyBusiness": {
"required": [
"name"
],
"recommended": [
"url",
"address",
"telephone",
"openingHoursSpecification",
"priceRange",
"image"
],
"allowed": [
"containedInPlace",
"amenityFeature",
"potentialAction",
"parentOrganization",
"geo",
"alternateName"
]
},
"DaySpa": {
"required": [
"name"
],
"recommended": [
"url",
"address",
"telephone",
"openingHoursSpecification",
"priceRange",
"image"
],
"allowed": [
"containedInPlace",
"amenityFeature",
"potentialAction",
"parentOrganization",
"geo",
"alternateName"
]
},
"CafeOrCoffeeShop": {
"required": [
"name"
],
"recommended": [
"url",
"address",
"servesCuisine",
"priceRange",
"telephone",
"openingHoursSpecification",
"image"
],
"allowed": [
"menu",
"hasMenu",
"containedInPlace",
"acceptsReservations",
"alternateName"
]
}
},
"container_types": [
"PostalAddress",
"GeoCoordinates",
"GeoShape",
"ImageObject",
"VideoObject",
"ContactPoint",
"OpeningHoursSpecification",
"Rating",
"QuantitativeValue",
"MonetaryAmount",
"PriceSpecification",
"Brand",
"EntryPoint",
"Place",
"OfferCatalog",
"ReserveAction",
"OrderAction",
"SearchAction",
"ViewAction",
"MeetingRoom",
"Room",
"HotelRoom",
"Suite",
"LocationFeatureSpecification",
"MemberProgramTier",
"MobileApplication",
"WebApplication",
"SoftwareApplication",
"Menu",
"MenuItem",
"MenuSection",
"Country",
"AdministrativeArea",
"Duration",
"PropertyValue",
"Person",
"Audience",
"Language",
"LodgingReservation"
],
"value_formats": {
"url_props": [
"url",
"logo",
"sameAs",
"image",
"contentUrl",
"thumbnailUrl",
"target",
"urlTemplate",
"installUrl",
"menu",
"hasMap",
"downloadUrl",
"embedUrl"
],
"date_props": [
"datePublished",
"dateModified",
"dateCreated",
"startDate",
"endDate",
"validFrom",
"validThrough",
"priceValidUntil",
"foundingDate",
"uploadDate",
"availabilityStarts",
"availabilityEnds",
"lastReviewed",
"previousStartDate"
],
"lang_props": [
"inLanguage",
"availableLanguage"
],
"currency_props": [
"priceCurrency",
"currenciesAccepted"
],
"number_props": [
"price",
"lowPrice",
"highPrice",
"ratingValue",
"reviewCount",
"ratingCount",
"bestRating",
"worstRating",
"position",
"numberOfRooms",
"maxValue",
"minValue",
"offerCount"
]
},
"valid_currencies": [
"KRW",
"USD",
"EUR",
"JPY",
"CNY",
"GBP",
"HKD",
"SGD",
"THB",
"AUD",
"CAD",
"CHF",
"TWD",
"MYR",
"PHP",
"VND",
"IDR",
"INR"
],
"valid_language_codes": [
"ko",
"en",
"ja",
"zh",
"zh-CN",
"zh-TW",
"zh-Hans",
"zh-Hant",
"ko-KR",
"en-US",
"en-GB",
"ja-JP",
"fr",
"de",
"es",
"ru",
"th",
"vi",
"id",
"ms"
],
"placeholder_tokens": [
"lorem ipsum",
"lorem",
"ipsum",
"dolor sit",
"todo",
"tbd",
"fixme",
"xxx",
"yyy",
"zzz",
"placeholder",
"insert here",
"insert text",
"example.com",
"your-domain",
"yourdomain",
"changeme",
"sample text",
"{{",
"}}",
"<insert",
"[insert",
"n/a",
"샘플",
"예시",
"여기에",
"변경필요",
"수정필요",
"입력필요",
"내용입력",
"테스트",
"임시"
],
"geo": {
"lat_min": -90.0,
"lat_max": 90.0,
"lon_min": -180.0,
"lon_max": 180.0,
"kr_lat_range": [
33.0,
39.0
],
"kr_lon_range": [
124.0,
132.0
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,854 @@
#!/usr/bin/env python3
"""
validate_schema.py — 5-layer offline JSON-LD schema validator.
WHY THIS EXISTS
---------------
When a client reviews hundreds of authored schema entries and says "there are too
many errors," the root cause is almost always that nobody ran a machine lint first.
Humans end up eyeballing raw JSON in a meeting. This tool moves every cheap,
machine-checkable error OUT of human review and INTO an automated gate that runs
first — so the client only ever sees clean, P0-free entries plus a defect report.
It is OFFLINE by design (the runtime cannot reach schema.org or Google). All rules
live in schema_rules.json; unknown types/properties degrade to warnings, never hard
errors, so the gate does not invent false positives.
THE 5 LAYERS
------------
L0 Coverage — URLs with no entry; entries whose URL isn't in the inventory.
L1 Syntax — invalid JSON, bad/missing @context, missing @type, encoding corruption.
L2 Vocabulary — unknown type, value-format errors (URL/date/lang/currency/number),
(strict only) unexpected properties on a known type.
L3 Rich-result — Google REQUIRED property missing (blocks rich result); recommended absent.
L4 Consistency — NAP mismatch, @id duplicates/dangling refs, swapped geo,
placeholder text, duplicate descriptions across entries.
GATE: PASS iff zero P0. Process exits 1 when the gate fails (so CI/`&&` chains stop).
Usage:
python validate_schema.py DATASET [--url-list URLLIST] [--out DIR]
[--strict] [--no-recommended]
[--live URL ...] [--rules schema_rules.json]
DATASET may be .xlsx / .csv (one row per entry, a JSON-LD column) / .jsonl / .json
/ a directory of .json|.jsonld files. With --live, validate live URLs instead.
"""
import argparse
import csv
import json
import os
import re
import sys
from collections import Counter, defaultdict
from pathlib import Path
RULES_DEFAULT = Path(__file__).resolve().parent / "schema_rules.json"
SEVERITY_ORDER = {"P0": 0, "P1": 1, "P2": 2}
# Header aliases for tabular input. Keys are normalized (lowercased, spaces removed).
COLUMN_ALIASES = {
"jsonld": ["jsonld", "jsonld", "json-ld", "json_ld", "schema", "schemamarkup",
"structureddata", "structured_data", "markup", "스키마", "구조화데이터",
"구조화된데이터", "jsonldcode", "스키마코드"],
"url": ["url", "메뉴url", "pageurl", "주소", "링크", "loc", "uri", "캐노니컬", "canonical"],
"lang": ["lang", "language", "언어", "언어코드", "locale", "lng"],
"device": ["device", "pc/mobile", "pcmobile", "pc_mobile", "platform", "디바이스", "기기"],
"page_type": ["page_type", "pagetype", "type", "페이지유형", "페이지타입", "menulevel",
"menu_level", "메뉴레벨", "template", "템플릿", "유형"],
}
URL_RE = re.compile(r"^https?://[^\s]+$", re.IGNORECASE)
# ISO-8601 date or datetime (date, date+time, optional tz). Loose but rejects free text.
DATE_RE = re.compile(
r"^\d{4}-\d{2}-\d{2}"
r"(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$"
)
LANG_RE = re.compile(r"^[a-zA-Z]{2,3}(?:-[A-Za-z0-9]{2,4})?$")
JSONLD_SCRIPT_RE = re.compile(
r'<script[^>]+type=["\']application/ld\+json["\'][^>]*>(.*?)</script>',
re.IGNORECASE | re.DOTALL,
)
# --------------------------------------------------------------------------- #
# Defect collection
# --------------------------------------------------------------------------- #
class DefectLog:
"""Accumulates findings. One row per finding, ready for triage."""
def __init__(self):
self.rows = []
def add(self, severity, layer, code, message, entry_id="", url="", node_type=""):
self.rows.append({
"entry_id": str(entry_id),
"url": url or "",
"node_type": node_type or "",
"layer": layer,
"code": code,
"severity": severity,
"message": message,
"status": "open",
"owner": "",
"note": "",
})
def counts(self):
c = Counter(r["severity"] for r in self.rows)
return {"P0": c.get("P0", 0), "P1": c.get("P1", 0), "P2": c.get("P2", 0)}
# --------------------------------------------------------------------------- #
# Input adapters (Mode A: authored dataset / Mode B: live URLs)
# --------------------------------------------------------------------------- #
def _norm_header(h):
return re.sub(r"\s+", "", str(h or "").strip().lower())
def _detect_columns(headers):
"""Map normalized headers to canonical column roles. Returns {role: index}."""
found = {}
for idx, h in enumerate(headers):
nh = _norm_header(h)
for role, aliases in COLUMN_ALIASES.items():
if role in found:
continue
if nh in aliases:
found[role] = idx
return found
def _row_to_entry(row, cols, entry_id, source_ref):
def cell(role):
i = cols.get(role)
if i is None or i >= len(row):
return None
v = row[i]
return None if v is None else str(v).strip()
raw = cell("jsonld")
if not raw:
return None # blank JSON-LD cell → no entry to validate
return {
"entry_id": entry_id,
"url": cell("url") or "",
"lang": cell("lang") or "",
"device": cell("device") or "",
"page_type": cell("page_type") or "",
"raw": raw,
"source_ref": source_ref,
}
def _load_csv(path):
entries = []
with open(path, newline="", encoding="utf-8-sig") as f:
reader = csv.reader(f)
rows = list(reader)
if not rows:
return entries
cols = _detect_columns(rows[0])
if "jsonld" not in cols:
raise ValueError(
f"No JSON-LD column found in {path}. Looked for: "
f"{', '.join(COLUMN_ALIASES['jsonld'][:6])} … Headers were: {rows[0]}"
)
for n, row in enumerate(rows[1:], start=2):
e = _row_to_entry(row, cols, f"{Path(path).stem}#r{n}", f"{path}:row{n}")
if e:
entries.append(e)
return entries
def _load_xlsx(path):
try:
from openpyxl import load_workbook
except ImportError:
raise SystemExit(
"Reading .xlsx needs openpyxl: pip install openpyxl\n"
"(or export the sheet to .csv and pass that instead)."
)
entries = []
wb = load_workbook(path, read_only=True, data_only=True)
for sheet in wb.worksheets:
rows = list(sheet.iter_rows(values_only=True))
if not rows:
continue
cols = _detect_columns(rows[0])
if "jsonld" not in cols:
continue # a tab without a JSON-LD column (e.g. summary) — skip silently
for n, row in enumerate(rows[1:], start=2):
e = _row_to_entry(list(row), cols, f"{sheet.title}#r{n}",
f"{path}:{sheet.title}:row{n}")
if e:
entries.append(e)
if not entries:
raise ValueError(
f"No sheet in {path} had a recognizable JSON-LD column. "
f"Looked for: {', '.join(COLUMN_ALIASES['jsonld'][:6])} …"
)
return entries
def _looks_like_schema(obj):
"""True if a parsed object is itself JSON-LD (vs a wrapper row)."""
if isinstance(obj, list):
return True
if isinstance(obj, dict):
return any(k in obj for k in ("@context", "@type", "@graph"))
return False
def _wrapper_to_entry(obj, entry_id, source_ref):
"""A JSONL/JSON wrapper object that carries url/lang + a jsonld payload."""
cols = {k: k for k in obj.keys()}
norm = {_norm_header(k): k for k in obj.keys()}
def pick(role):
for alias in COLUMN_ALIASES[role]:
if alias in norm:
v = obj[norm[alias]]
return v
return None
payload = pick("jsonld")
raw = payload if isinstance(payload, str) else json.dumps(payload, ensure_ascii=False)
url = pick("url")
return {
"entry_id": entry_id,
"url": str(url).strip() if url else "",
"lang": str(pick("lang") or "").strip(),
"device": str(pick("device") or "").strip(),
"page_type": str(pick("page_type") or "").strip(),
"raw": raw,
"source_ref": source_ref,
}
def _load_jsonl(path):
entries = []
with open(path, encoding="utf-8") as f:
for n, line in enumerate(f, start=1):
line = line.strip()
if not line:
continue
sref = f"{path}:line{n}"
try:
obj = json.loads(line)
except json.JSONDecodeError:
# Keep the bad line so L1 reports it as a syntax error.
entries.append({"entry_id": f"{Path(path).stem}#l{n}", "url": "",
"lang": "", "device": "", "page_type": "",
"raw": line, "source_ref": sref})
continue
eid = f"{Path(path).stem}#l{n}"
if _looks_like_schema(obj):
entries.append({"entry_id": eid, "url": "", "lang": "", "device": "",
"page_type": "", "raw": line, "source_ref": sref})
else:
entries.append(_wrapper_to_entry(obj, eid, sref))
return entries
def _load_json(path):
with open(path, encoding="utf-8") as f:
data = json.load(f)
entries = []
if isinstance(data, dict) and not _looks_like_schema(data) and all(
isinstance(v, (dict, list, str)) for v in data.values()
) and not any(k.startswith("@") for k in data):
# url -> jsonld map
for url, payload in data.items():
raw = payload if isinstance(payload, str) else json.dumps(payload, ensure_ascii=False)
entries.append({"entry_id": url, "url": url, "lang": "", "device": "",
"page_type": "", "raw": raw, "source_ref": f"{path}:{url}"})
elif isinstance(data, list):
for n, item in enumerate(data, start=1):
sref = f"{path}:[{n}]"
eid = f"{Path(path).stem}#{n}"
if _looks_like_schema(item) or not isinstance(item, dict):
raw = item if isinstance(item, str) else json.dumps(item, ensure_ascii=False)
entries.append({"entry_id": eid, "url": "", "lang": "", "device": "",
"page_type": "", "raw": raw, "source_ref": sref})
else:
entries.append(_wrapper_to_entry(item, eid, sref))
else:
entries.append({"entry_id": Path(path).stem, "url": "", "lang": "", "device": "",
"page_type": "", "raw": json.dumps(data, ensure_ascii=False),
"source_ref": path})
return entries
def _load_dir(path):
entries = []
for p in sorted(Path(path).rglob("*")):
if p.suffix.lower() in (".json", ".jsonld"):
entries.append({"entry_id": p.stem, "url": "", "lang": "", "device": "",
"page_type": "", "raw": p.read_text(encoding="utf-8"),
"source_ref": str(p)})
if not entries:
raise ValueError(f"No .json/.jsonld files found under {path}")
return entries
def _load_live(urls):
try:
import requests
except ImportError:
raise SystemExit("Live mode (--live) needs requests: pip install requests")
entries = []
headers = {"User-Agent": "Mozilla/5.0 (compatible; SchemaValidator/1.0)"}
for url in urls:
try:
resp = requests.get(url, headers=headers, timeout=20)
resp.raise_for_status()
except Exception as exc: # noqa: BLE001 — best-effort live fetch
entries.append({"entry_id": url, "url": url, "lang": "", "device": "",
"page_type": "", "raw": "", "source_ref": url,
"_fetch_error": str(exc)})
continue
scripts = JSONLD_SCRIPT_RE.findall(resp.text)
if not scripts:
entries.append({"entry_id": url, "url": url, "lang": "", "device": "",
"page_type": "", "raw": "", "source_ref": url,
"_no_schema": True})
continue
for i, block in enumerate(scripts, start=1):
entries.append({"entry_id": f"{url}#{i}", "url": url, "lang": "",
"device": "", "page_type": "", "raw": block.strip(),
"source_ref": f"{url} (script {i})"})
return entries
def load_entries(input_path, live_urls):
if live_urls:
return _load_live(live_urls)
p = Path(input_path)
if p.is_dir():
return _load_dir(p)
suffix = p.suffix.lower()
if suffix == ".csv":
return _load_csv(p)
if suffix in (".xlsx", ".xlsm"):
return _load_xlsx(p)
if suffix == ".jsonl":
return _load_jsonl(p)
if suffix in (".json", ".jsonld"):
return _load_json(p)
raise ValueError(f"Unsupported input: {input_path} (suffix {suffix!r})")
# --------------------------------------------------------------------------- #
# Node helpers
# --------------------------------------------------------------------------- #
def type_of(node):
"""Return the primary @type as a string (first if it's a list)."""
t = node.get("@type")
if isinstance(t, list):
return t[0] if t else ""
return t or ""
def iter_typed_nodes(parsed):
"""Yield every dict that has an @type, top-level and nested (recursively)."""
seen = []
def walk(obj):
if isinstance(obj, dict):
if "@type" in obj:
seen.append(obj)
for v in obj.values():
walk(v)
elif isinstance(obj, list):
for v in obj:
walk(v)
# @graph documents: walk the graph; otherwise walk the object/array directly.
if isinstance(parsed, dict) and "@graph" in parsed:
walk(parsed["@graph"])
else:
walk(parsed)
return seen
def all_strings(obj):
"""Yield (key, value) for every string value anywhere in the structure."""
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, str):
yield k, v
else:
yield from all_strings(v)
elif isinstance(obj, list):
for v in obj:
yield from all_strings(v)
def normalize_name(s):
return re.sub(r"\s+", " ", str(s or "").strip().lower())
def first_text(value):
"""Coerce a property value to a comparable scalar (handles list/dict)."""
if isinstance(value, list):
return first_text(value[0]) if value else ""
if isinstance(value, dict):
return value.get("name") or value.get("@id") or value.get("streetAddress") or ""
return value
# --------------------------------------------------------------------------- #
# Layer 0 — Coverage
# --------------------------------------------------------------------------- #
def load_url_inventory(url_list_path):
urls = set()
p = Path(url_list_path)
suffix = p.suffix.lower()
if suffix in (".xlsx", ".xlsm"):
from openpyxl import load_workbook
wb = load_workbook(p, read_only=True, data_only=True)
for sheet in wb.worksheets:
for row in sheet.iter_rows(values_only=True):
for cell in row:
if isinstance(cell, str) and URL_RE.match(cell.strip()):
urls.add(cell.strip())
elif suffix == ".csv":
with open(p, newline="", encoding="utf-8-sig") as f:
for row in csv.reader(f):
for cell in row:
if isinstance(cell, str) and URL_RE.match(cell.strip()):
urls.add(cell.strip())
else: # plain text, one URL per line
for line in p.read_text(encoding="utf-8").splitlines():
line = line.strip()
if URL_RE.match(line):
urls.add(line)
return urls
def layer0_coverage(entries, inventory, defects):
entry_urls = {e["url"] for e in entries if e.get("url")}
missing = inventory - entry_urls
for url in sorted(missing):
defects.add("P1", "L0", "COVERAGE_MISSING",
"Inventory URL has no authored schema entry.", url=url)
orphans = entry_urls - inventory
for url in sorted(orphans):
defects.add("P2", "L0", "COVERAGE_ORPHAN",
"Entry URL is not in the canonical URL inventory "
"(typo, stale path, or missing from list).", url=url)
# --------------------------------------------------------------------------- #
# Layer 1 — Syntax
# --------------------------------------------------------------------------- #
def layer1_syntax(entry, rules, defects):
"""Parse + structural checks. Returns parsed object or None (fatal)."""
eid, url = entry["entry_id"], entry["url"]
if entry.get("_fetch_error"):
defects.add("P1", "L1", "FETCH_ERROR",
f"Could not fetch live URL: {entry['_fetch_error']}", eid, url)
return None
if entry.get("_no_schema"):
defects.add("P0", "L1", "NO_SCHEMA_IN_HTML",
"Live page has no application/ld+json script block.", eid, url)
return None
raw = entry["raw"]
if "<22>" in raw:
defects.add("P1", "L1", "ENCODING_CORRUPTION",
"Replacement character (\\ufffd) present — encoding corruption.",
eid, url)
try:
parsed = json.loads(raw)
except json.JSONDecodeError as exc:
defects.add("P0", "L1", "INVALID_JSON",
f"JSON does not parse: {exc.msg} at line {exc.lineno} col {exc.colno}.",
eid, url)
return None
nodes = iter_typed_nodes(parsed)
if not nodes:
defects.add("P1", "L1", "NO_TYPE",
"No @type found anywhere in the entry — not a usable schema object.",
eid, url)
# @context lives at the top of the document; nested nodes inherit it.
if isinstance(parsed, dict):
ctx = parsed.get("@context")
if ctx is None:
defects.add("P1", "L1", "MISSING_CONTEXT",
"Top-level @context is missing.", eid, url)
else:
ctx_urls = [ctx] if isinstance(ctx, str) else (
[c for c in ctx if isinstance(c, str)] if isinstance(ctx, list) else []
)
valid = rules["valid_contexts"]
if ctx_urls and not any(c.rstrip("/") in [v.rstrip("/") for v in valid]
for c in ctx_urls):
defects.add("P1", "L1", "WRONG_CONTEXT",
f"@context is not schema.org: {ctx_urls}.", eid, url)
return parsed
# --------------------------------------------------------------------------- #
# Layer 2 — Vocabulary + value formats
# --------------------------------------------------------------------------- #
def _check_value_formats(node, rules, defects, eid, url, ntype, severity):
vf = rules["value_formats"]
def each(value):
if isinstance(value, list):
for v in value:
yield from each(v)
else:
yield value
for prop, value in node.items():
if prop.startswith("@"):
continue
if prop in vf["url_props"]:
for v in each(value):
if isinstance(v, str) and not URL_RE.match(v.strip()):
defects.add(severity, "L2", "BAD_URL",
f"'{prop}' is not an http(s) URL: {v!r}.", eid, url, ntype)
if prop in vf["date_props"]:
for v in each(value):
if isinstance(v, str) and not DATE_RE.match(v.strip()):
defects.add(severity, "L2", "BAD_DATE",
f"'{prop}' is not ISO-8601: {v!r}.", eid, url, ntype)
if prop in vf["lang_props"]:
for v in each(value):
if isinstance(v, str) and not LANG_RE.match(v.strip()):
defects.add(severity, "L2", "BAD_LANG",
f"'{prop}' is not a BCP-47 language code: {v!r}.",
eid, url, ntype)
if prop in vf["currency_props"]:
for v in each(value):
if isinstance(v, str) and not re.match(r"^[A-Z]{3}$", v.strip()):
defects.add(severity, "L2", "BAD_CURRENCY",
f"'{prop}' is not a 3-letter ISO-4217 code: {v!r}.",
eid, url, ntype)
if prop in vf["number_props"]:
for v in each(value):
if isinstance(v, str):
try:
float(v.replace(",", ""))
except ValueError:
defects.add(severity, "L2", "BAD_NUMBER",
f"'{prop}' is not numeric: {v!r}.", eid, url, ntype)
def layer2_vocabulary(node, rules, defects, eid, url, strict):
ntype = type_of(node)
known = rules["known_types"]
containers = set(rules["container_types"])
minor = "P1" if strict else "P2"
if ntype and ntype not in known and ntype not in containers:
defects.add(minor, "L2", "UNKNOWN_TYPE",
f"@type '{ntype}' is not in the curated rule set "
"(treated as a warning — add it to schema_rules.json if intended).",
eid, url, ntype)
_check_value_formats(node, rules, defects, eid, url, ntype, minor)
# Unexpected-property check is OPT-IN (--strict). Off by default to avoid the
# exact noise explosion that makes clients say "too many errors".
if strict and ntype in known:
spec = known[ntype]
allowed = set(spec["required"]) | set(spec["recommended"]) | set(spec["allowed"])
allowed |= set(rules["global_properties"])
for prop in node:
if prop.startswith("@"):
continue
if prop not in allowed:
defects.add("P1", "L2", "UNEXPECTED_PROPERTY",
f"'{prop}' is not a known property of {ntype} (strict mode).",
eid, url, ntype)
# --------------------------------------------------------------------------- #
# Layer 3 — Rich-result (required / recommended)
# --------------------------------------------------------------------------- #
def layer3_richresult(node, rules, defects, eid, url, no_recommended):
ntype = type_of(node)
known = rules["known_types"]
if ntype not in known:
return # containers + unknown types have no required-property contract
spec = known[ntype]
for prop in spec["required"]:
if not node.get(prop):
defects.add("P0", "L3", "MISSING_REQUIRED",
f"{ntype} is missing required property '{prop}' "
"(blocks the rich result).", eid, url, ntype)
if not no_recommended:
missing_rec = [p for p in spec["recommended"] if not node.get(p)]
if missing_rec:
# Aggregate to ONE line per node — never one defect per property.
defects.add("P2", "L3", "MISSING_RECOMMENDED",
f"{ntype} is missing recommended properties: "
f"{', '.join(missing_rec)}.", eid, url, ntype)
# --------------------------------------------------------------------------- #
# Layer 4 — Consistency (cross-node / cross-entry)
# --------------------------------------------------------------------------- #
NAP_TYPES = {"Organization", "Corporation", "LocalBusiness", "Hotel",
"LodgingBusiness", "Resort", "Restaurant", "FoodEstablishment", "BarOrPub"}
def _address_street(node):
addr = node.get("address")
if isinstance(addr, dict):
return normalize_name(addr.get("streetAddress"))
if isinstance(addr, list) and addr and isinstance(addr[0], dict):
return normalize_name(addr[0].get("streetAddress"))
return ""
def _walk_ids(obj, defined, referenced):
"""Collect @id definitions vs pure references by walking the whole document.
A *reference* is an object whose only key is @id (e.g. {"@id": "...#org"}).
A *definition* is any object carrying @id plus other content. References live
in untyped wrapper dicts, so this must walk the raw doc — not just typed nodes.
"""
if isinstance(obj, dict):
nid = obj.get("@id")
if nid:
if set(obj.keys()) == {"@id"}:
referenced.add(nid)
else:
defined.setdefault(nid, []).append(obj)
for v in obj.values():
_walk_ids(v, defined, referenced)
elif isinstance(obj, list):
for v in obj:
_walk_ids(v, defined, referenced)
def layer4_consistency(node_index, parsed_docs, rules, defects):
"""node_index: (entry, node) for every TYPED node.
parsed_docs: (entry, parsed) for every entry that parsed — used for @id scan."""
# ---- placeholder text (P0) ----
tokens = [t.lower() for t in rules["placeholder_tokens"]]
for entry, node in node_index:
ntype = type_of(node)
for key, val in all_strings(node):
low = val.lower()
hit = next((t for t in tokens if t in low), None)
if hit:
defects.add("P0", "L4", "PLACEHOLDER_TEXT",
f"Placeholder/boilerplate token {hit!r} in '{key}': {val[:60]!r}.",
entry["entry_id"], entry["url"], ntype)
break # one placeholder defect per node is enough signal
# ---- NAP consistency (P0) ----
by_name = defaultdict(list)
for entry, node in node_index:
if type_of(node) in NAP_TYPES and node.get("name"):
by_name[normalize_name(first_text(node.get("name")))].append((entry, node))
for name, group in by_name.items():
phones = {str(first_text(n.get("telephone"))).strip()
for _, n in group if n.get("telephone")}
streets = {_address_street(n) for _, n in group if _address_street(n)}
if len(phones) > 1:
defects.add("P0", "L4", "NAP_PHONE_MISMATCH",
f"Business '{name}' has conflicting telephone values across "
f"entries: {sorted(phones)}.", entry_id="(dataset)")
if len(streets) > 1:
defects.add("P0", "L4", "NAP_ADDRESS_MISMATCH",
f"Business '{name}' has conflicting streetAddress values across "
f"entries: {sorted(streets)}.", entry_id="(dataset)")
# ---- @id duplicates + dangling references (P1) ----
defined = {} # @id -> list of definition dicts (walked across all docs)
referenced = set() # @id values used purely as references
for _, parsed in parsed_docs:
_walk_ids(parsed, defined, referenced)
for nid, defs in defined.items():
if len(defs) > 1:
# duplicate only matters if the definitions actually differ
shapes = {json.dumps(n, sort_keys=True, ensure_ascii=False) for n in defs}
if len(shapes) > 1:
defects.add("P1", "L4", "DUPLICATE_ID",
f"@id {nid!r} is defined {len(defs)} times with differing content.",
entry_id="(dataset)")
for nid in sorted(referenced - set(defined)):
defects.add("P1", "L4", "DANGLING_ID",
f"@id reference {nid!r} points to a node that is never defined.",
entry_id="(dataset)")
# ---- swapped / out-of-range geo (P1) ----
g = rules["geo"]
for entry, node in node_index:
if type_of(node) != "GeoCoordinates":
continue
try:
lat = float(first_text(node.get("latitude")))
lon = float(first_text(node.get("longitude")))
except (TypeError, ValueError):
continue
lat_ok = g["lat_min"] <= lat <= g["lat_max"]
lon_ok = g["lon_min"] <= lon <= g["lon_max"]
if lat_ok and lon_ok:
continue # both in valid global range
# Invalid — distinguish a clean transposition from plain garbage.
swap_ok = (g["lat_min"] <= lon <= g["lat_max"]) and (g["lon_min"] <= lat <= g["lon_max"])
if swap_ok:
defects.add("P1", "L4", "GEO_SWAPPED",
f"GeoCoordinates look transposed (latitude={lat}, longitude={lon}) "
"— swapping them yields valid coordinates.",
entry["entry_id"], entry["url"], "GeoCoordinates")
else:
defects.add("P1", "L4", "GEO_OUT_OF_RANGE",
f"GeoCoordinates out of range (latitude={lat}, longitude={lon}).",
entry["entry_id"], entry["url"], "GeoCoordinates")
# ---- duplicate descriptions across entries (P1) ----
desc_groups = defaultdict(set)
for entry, node in node_index:
d = first_text(node.get("description"))
if isinstance(d, str) and len(d.strip()) >= 30:
desc_groups[d.strip()].add(entry["entry_id"])
for desc, eids in desc_groups.items():
if len(eids) >= 3:
defects.add("P1", "L4", "DUPLICATE_DESCRIPTION",
f"Identical description reused across {len(eids)} entries "
f"(e.g. {sorted(eids)[:3]}): {desc[:50]!r}…", entry_id="(dataset)")
# --------------------------------------------------------------------------- #
# Orchestration + output
# --------------------------------------------------------------------------- #
def run(entries, rules, inventory, strict, no_recommended):
defects = DefectLog()
if inventory is not None:
layer0_coverage(entries, inventory, defects)
node_index = []
parsed_docs = []
valid_entries = 0
for entry in entries:
parsed = layer1_syntax(entry, rules, defects)
if parsed is None:
continue
valid_entries += 1
parsed_docs.append((entry, parsed))
for node in iter_typed_nodes(parsed):
layer2_vocabulary(node, rules, defects, entry["entry_id"], entry["url"], strict)
layer3_richresult(node, rules, defects, entry["entry_id"], entry["url"],
no_recommended)
node_index.append((entry, node))
layer4_consistency(node_index, parsed_docs, rules, defects)
return defects, valid_entries, len(node_index)
def write_outputs(defects, outdir, meta):
outdir = Path(outdir)
outdir.mkdir(parents=True, exist_ok=True)
# defect_log.csv — the client-facing triage artifact
fields = ["entry_id", "url", "node_type", "layer", "code", "severity",
"message", "status", "owner", "note"]
rows = sorted(defects.rows, key=lambda r: (SEVERITY_ORDER[r["severity"]],
r["layer"], r["code"]))
with open(outdir / "defect_log.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.DictWriter(f, fieldnames=fields)
w.writeheader()
w.writerows(rows)
counts = defects.counts()
gate = "PASS" if counts["P0"] == 0 else "FAIL"
by_code = Counter((r["severity"], r["code"]) for r in defects.rows)
# results.json — machine-readable
results = {
"summary": {**meta, **counts, "total": len(rows), "gate": gate},
"by_code": [{"severity": s, "code": c, "count": n}
for (s, c), n in by_code.most_common()],
"defects": rows,
}
(outdir / "results.json").write_text(
json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8")
# report.md — human summary
lines = [
"# Schema Validation Report", "",
f"- Entries read: **{meta['entries']}** | parsed OK: **{meta['valid_entries']}** "
f"| nodes checked: **{meta['nodes']}**",
f"- Defects: **P0 {counts['P0']}** · **P1 {counts['P1']}** · **P2 {counts['P2']}** "
f"(total {len(rows)})",
"",
f"## Gate: **{gate}**",
("> ✅ Zero P0 — entries may advance to client review."
if gate == "PASS" else
"> ⛔ P0 present — these entries must NOT reach client review. Fix P0 first."),
"",
"## Defects by code", "",
"| Severity | Code | Count |", "|---|---|---|",
]
for (sev, code), n in by_code.most_common():
lines.append(f"| {sev} | {code} | {n} |")
p0 = [r for r in rows if r["severity"] == "P0"]
if p0:
lines += ["", "## P0 blockers (top 15)", "",
"| Entry | Type | Code | Message |", "|---|---|---|---|"]
for r in p0[:15]:
msg = r["message"].replace("|", "\\|")
lines.append(f"| {r['entry_id']} | {r['node_type']} | {r['code']} | {msg} |")
lines += ["", "## Next step",
("Triage P1 in `defect_log.csv`; client reviews the clean entries against this report."
if gate == "PASS" else
"Assign and fix every P0, re-run the validator, and only then open client review."),
""]
(outdir / "report.md").write_text("\n".join(lines), encoding="utf-8")
return gate, counts
def main(argv=None):
ap = argparse.ArgumentParser(description="5-layer offline JSON-LD schema validator.")
ap.add_argument("dataset", nargs="?", help="xlsx/csv/jsonl/json file or a directory")
ap.add_argument("--url-list", help="canonical URL inventory (xlsx/csv/txt) → enables Layer 0")
ap.add_argument("--out", default="schema_qa_out", help="output directory")
ap.add_argument("--strict", action="store_true",
help="unexpected props on known types → P1; unknown types → P1")
ap.add_argument("--no-recommended", action="store_true",
help="drop L3 recommended (P2) findings — highest-signal gate")
ap.add_argument("--live", nargs="+", metavar="URL",
help="Mode B: validate live URLs (extract embedded JSON-LD)")
ap.add_argument("--rules", default=str(RULES_DEFAULT), help="path to schema_rules.json")
args = ap.parse_args(argv)
if not args.dataset and not args.live:
ap.error("provide a DATASET path or --live URL ...")
rules = json.loads(Path(args.rules).read_text(encoding="utf-8"))
try:
entries = load_entries(args.dataset, args.live)
except (ValueError, FileNotFoundError) as exc:
print(f"ERROR loading input: {exc}", file=sys.stderr)
return 2
inventory = load_url_inventory(args.url_list) if args.url_list else None
defects, valid_entries, nodes = run(entries, rules, inventory,
args.strict, args.no_recommended)
meta = {"entries": len(entries), "valid_entries": valid_entries, "nodes": nodes,
"mode": "B-live" if args.live else "A-dataset", "strict": args.strict,
"coverage": inventory is not None}
gate, counts = write_outputs(defects, args.out, meta)
print(f"[{gate}] entries={len(entries)} nodes={nodes} "
f"P0={counts['P0']} P1={counts['P1']} P2={counts['P2']} → {args.out}/")
# Exit 1 when the gate fails so CI and `&&` chains stop on P0.
return 0 if gate == "PASS" else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,57 @@
# 구조화 데이터 QA 리포트 — {{프로젝트명}}
> 클라이언트 검토용. **원본 JSON이 아니라 결함 리포트를 검토합니다.**
> 이 리포트에 오른 엔트리는 모두 기계 검증(Layer 04)을 통과한 **P0 0건** 상태입니다.
| 항목 | 값 |
|---|---|
| 데이터셋 | `{{dataset_파일명}}` |
| 검증 일시 | {{YYYY-MM-DD HH:MM}} |
| 검증 모드 | A — Dataset QA (배포 전) / B — Live audit (배포 후) |
| 엔트리 수 | {{entries}} (파싱 성공 {{valid_entries}}, 노드 {{nodes}}) |
| **게이트** | **{{PASS / FAIL}}** (PASS = P0 0건) |
| 결함 | P0 {{n}} · P1 {{n}} · P2 {{n}} |
| Audit ID | SCHEMA-{{YYYYMMDD}}-{{NNN}} |
## 1. 한눈에 보기
-**검토 가능 엔트리**: P0 0건을 통과한 {{n}}개 — 아래 판단 항목만 확인해 주세요.
-**보류 엔트리**(있다면): P0 {{n}}건으로 검토 대상에서 제외. 수정 후 재검증합니다.
- 이번 검토에서 **사람의 판단이 필요한 것**은 기계가 잡지 못하는 두 가지뿐입니다:
1. 페이지에 맞는 스키마 **타입**이 선택되었는가
2. 표시되는 **문구(설명·이름)**가 사실과 정확히 일치하는가
## 2. 결함 요약 (코드별)
| 심각도 | 코드 | 건수 | 의미 |
|---|---|---|---|
| P0 | {{CODE}} | {{n}} | {{한 줄 설명}} |
| P1 | {{CODE}} | {{n}} | {{한 줄 설명}} |
| P2 | {{CODE}} | {{n}} | {{한 줄 설명}} |
> 코드 정의: `references/defect-taxonomy.md`. 전체 목록: 첨부 `defect_log.csv`.
## 3. P0 블로커 (있을 경우 — 검토 전 수정 필수)
| 엔트리 | 타입 | 코드 | 내용 | 담당 | 상태 |
|---|---|---|---|---|---|
| {{entry_id}} | {{type}} | {{CODE}} | {{message}} | {{owner}} | open |
## 4. 클라이언트 확인 요청 (판단 항목)
기계가 통과시킨 엔트리 중, 사람의 확인이 필요한 항목입니다.
| # | URL / 페이지 | 확인 요청 | 비고 |
|---|---|---|---|
| 1 | {{url}} | 이 페이지에 `{{@type}}` 타입이 맞습니까? | |
| 2 | {{url}} | 설명/이름 문구가 정확합니까? | |
## 5. 다음 단계
- **PASS인 경우**: 위 4번 판단 항목 확정 → 배포 단계(G4 안정화)로 이동, 샘플을 Google
Rich Results Test로 최종 확인.
- **FAIL인 경우**: P0 담당 배정 → 수정 → 재검증(`validate_schema.py`) → 본 리포트 갱신.
- P1 처리 방침(수정/수용)은 `decision-log.md`에 기록합니다.
---
*생성: 16-seo-schema-validator · 첨부: `report.md`, `defect_log.csv`, `results.json`*

View File

@@ -0,0 +1,39 @@
# P1 Decision Log — {{프로젝트명}}
P0 is non-negotiable: every P0 is fixed before launch (the gate enforces it). **P1 is
where judgement lives** — some P1s get fixed, some get consciously accepted. This log
records *which, by whom, and why*, so an accepted P1 is a decision on the record, not a
silently ignored defect. It is the G3 (테스트) deliverable.
## How to use
1. Open `defect_log.csv`, filter to `severity = P1`.
2. For each P1 (or each group of identical P1s), add a row below.
3. Decision is one of: **Fix** (will correct before launch) / **Accept** (ship as-is,
with rationale) / **Defer** (post-launch backlog).
4. An `Accept`/`Defer` needs a named approver. `Fix` needs an owner + target date.
5. Re-run the validator after the fixes; confirm the fixed P1s are gone.
## Log
| # | Code | Entry / scope | Summary | Decision | Owner / Approver | Target / Date | Rationale |
|---|---|---|---|---|---|---|---|
| 1 | {{CODE}} | {{entry_id or (dataset)}} | {{one line}} | Fix / Accept / Defer | {{name}} | {{YYYY-MM-DD}} | {{why}} |
| 2 | | | | | | | |
| 3 | | | | | | | |
## Standing decisions (apply to all entries unless overridden)
Record cross-cutting calls once here instead of per row — e.g. "MISSING_RECOMMENDED for
`starRating` is accepted group-wide: not contractually rated." Reduces log noise.
| Code | Standing decision | Approver | Date |
|---|---|---|---|
| {{CODE}} | Accept group-wide — {{reason}} | {{name}} | {{YYYY-MM-DD}} |
## Sign-off
| Stage gate | Condition | Confirmed by | Date |
|---|---|---|---|
| G3 테스트 | All P1 triaged (Fix/Accept/Defer), decisions logged above | {{name}} | {{date}} |
| G4 안정화 | P0 = 0, all "Fix" P1 closed, online validator green on sample | {{name}} | {{date}} |

View File

@@ -0,0 +1,126 @@
---
name: seo-schema-generator
description: |
Generates validation-ready JSON-LD structured data for a site, covering BOTH
scenarios: (1) from an existing website — extract facts from live pages; and
(2) from collected sources for a not-yet-published site — reconcile conflicting
facts into a provenance-tracked claims register. Both modes emit the same claims
register, build pruned drafts from type templates (no placeholders shipped), and
hand off to 16-seo-schema-validator (generate then validate, gate = zero P0).
Triggers: generate schema, create JSON-LD, schema markup, structured data generator,
source-to-schema, pre-launch schema, claims register, 스키마 생성, 스키마 저작,
구조화 데이터 생성, 미발행 사이트 스키마, 기존 사이트 스키마 추출.
version: "2.0"
author: OurDigital / D.intelligence
environment: Code
---
# SEO Schema Generator (17)
Author JSON-LD for a site — whether the pages already exist or the site is not yet
published. Both cases are error-prone for the same reason: facts must be turned into
schema without leaking conflicts, gaps, or placeholders. This skill makes that reliable
by routing **both scenarios through one pivot — a claims register** — then generating
pruned drafts that hand off cleanly to the `16-seo-schema-validator` gate.
**Generate (17) → Validate (16).** This skill produces drafts; 16 is the QA gate.
## Two modes, one pipeline
The only thing that differs between the scenarios is **where facts come from**.
Everything after the claims register is identical.
| | **Mode 1 — from an existing site** | **Mode 2 — from collected sources** |
|---|---|---|
| When | Site has pages but lacks (or needs better) schema | Site not published yet (no DOM) |
| Source of truth | the live pages | scattered, conflicting sources (DART, Wikidata, brochures) |
| Seed the register with | `scripts/extract_site_claims.py` | manual research → `templates/claims-register.csv` |
| Hard part | extraction & mapping | authority hierarchy + entity reconciliation |
| Conflicts | rare (one source) | frequent → resolve before shipping |
```
Mode 1 (extract_site_claims.py)─┐
├─▶ claims_register.csv ─▶ build_schema_drafts.py ─▶ drafts/*.jsonld
Mode 2 (research + register)────┘ (the pivot) └─▶ schema_drafts_dataset.csv
16-seo-schema-validator ▼ (gate: zero P0)
```
## The claims register — the core idea
A **claims register** is a provenance-tracked, conflict-resolved fact table. Columns:
`entity_id, entity_type, property, value, lang, url, source_ids, authority, confidence,
conflict, status, note`. Dotted `property` paths nest (`address.streetAddress`);
pipe-separate array values (`a|b|c`).
**Only `CONFIRMED`, non-conflicting claims become schema.** Everything else (PENDING,
CONFLICT, REJECTED, EMPTY) is excluded and reported — never shipped. An unfilled
template slot is **deleted**, never emitted as `{{…}}` or `TODO` (placeholder leakage
is the #1 pre-launch P0).
## How to run
```bash
# Try the bundled sample first (Mode 2)
python scripts/make_sample.py
python scripts/build_schema_drafts.py fixtures/sample_claims.csv --out drafts_out
# MODE 1 — existing site → register (URLs, or local .html / a directory offline)
python scripts/extract_site_claims.py https://example.com/ https://example.com/about \
--out site_claims
# review site_claims/claims_register.csv (confirm PENDING rows), then build:
python scripts/build_schema_drafts.py site_claims/claims_register.csv --out drafts_out
# MODE 2 — collected sources → register (fill templates/claims-register.csv by hand)
python scripts/build_schema_drafts.py path/to/claims_register.csv --out drafts_out
# HAND OFF TO THE GATE (must reach zero P0)
python ../16-seo-schema-validator/scripts/validate_schema.py \
drafts_out/schema_drafts_dataset.csv --out qa_out
```
## Outputs
- `drafts/*.jsonld` — one pruned draft per entity (× language).
- `schema_drafts_dataset.csv` — directly consumable by `16-seo-schema-validator`.
- `build_report.md` — entities built + **excluded claims** (PENDING / CONFLICT / EMPTY) with reasons.
- (Mode 1 also) `claims_register.csv` + `extraction_report.md`.
## Stage gates (설계→개발→테스트→안정화→런칭 후)
- **G1 설계** — Lock the entity→type map (`references/entity-and-type-map.md`). Mode 2: source
register complete (≥2 sources/entity). *DoD:* every entity has an assigned type + required list.
- **G2 개발** — Seed the register (Mode 1 extract / Mode 2 research), reconcile to `CONFIRMED`,
conflicts = 0, run the builder → drafts have **zero placeholders**.
- **G3 테스트** — Validate (16): **zero P0**; triage P1; fact-accuracy sign-off via `templates/review-guide.md` (report-based, not raw JSON).
- **G4 안정화** — Google Rich Results Test green on a sample; re-run shows no regression.
- **G5 런칭 후** — live schema == drafts; GSC "Rich results" no new errors.
## References & templates
- Mode 1 SOP: `references/site-extraction-methodology.md`.
- Mode 2 SOP (9 steps): `references/source-to-schema-methodology.md`.
- Source authority ranking: `references/source-authority-hierarchy.md`.
- Entity→type scoping: `references/entity-and-type-map.md`.
- Registers + review guide: `templates/claims-register.csv`, `templates/source-register.csv`, `templates/review-guide.md`.
## Templates included
`scripts/type_templates.json` covers Organization, WebSite, Hotel, Person, JobPosting,
VideoObject, FAQPage. Required props are aligned with the validator's rule set, so a
fully confirmed entity passes the gate. **Add a type = add a template block (edit JSON only).**
## Limits & honesty
- Quality of drafts == quality of the register. Garbage-in still produces gaps — but
reported, never as placeholders.
- Mode 1 inference (title/OpenGraph) is seeded as `PENDING` and will NOT ship until a
human confirms it; existing JSON-LD is seeded `CONFIRMED`. If a site already has good
JSON-LD, prefer auditing it directly with `16` Mode B.
- Authoritative rich-result eligibility still needs Google's online test on a sample at G4.
## Integration
- **→ 16-seo-schema-validator**: the dataset CSV is the handoff; the gate is `zero P0`.
- **→ seo-comprehensive-audit**: post-launch (G5) uses the validator's Mode B as audit stage 4.
- This skill is a one-time-per-site authoring workflow, **not** an audit-pipeline stage.

Some files were not shown because too many files have changed in this diff Show More