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>
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>
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>
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>
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>
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>
Tier 1: local  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>
- 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
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
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
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>
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>
Verified conformant against Google reference bundles (crypto_bitcoin, ga4, stackoverflow).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>