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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>