From c750fa7f5ed9e3dc4a05820d0b6802b9ab2f1b58 Mon Sep 17 00:00:00 2001 From: Andrew Yim Date: Sun, 10 May 2026 23:02:45 +0900 Subject: [PATCH] =?UTF-8?q?feat(our-gdrive-organizer):=20add=20new=20skill?= =?UTF-8?q?=20at=20slot=2082,=20rename=20old=2082=20=E2=86=92=2092?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- AGENTS.md | 11 +- CLAUDE.md | 8 +- .../82-our-gdrive-organizer/README.md | 96 ++++ .../82-our-gdrive-organizer/code/SKILL.md | 209 +++++++ .../82-our-gdrive-organizer/code/organizer.py | 520 ++++++++++++++++++ .../82-our-gdrive-organizer/desktop/SKILL.md | 103 ++++ .../82-our-gdrive-organizer/docs/CHANGELOG.md | 80 +++ .../shared/conventions.md | 153 ++++++ .../shared/patterns/README.md | 31 ++ .../shared/patterns/canonical-files.md | 92 ++++ .../shared/patterns/canonical-folders.md | 96 ++++ .../shared/patterns/categorization-rules.md | 62 +++ .../shared/patterns/gotchas.md | 275 +++++++++ .../README.md | 0 .../code/SKILL.md | 0 .../shared/references/dtm-wizard-reference.md | 0 16 files changed, 1731 insertions(+), 5 deletions(-) create mode 100644 custom-skills/82-our-gdrive-organizer/README.md create mode 100644 custom-skills/82-our-gdrive-organizer/code/SKILL.md create mode 100755 custom-skills/82-our-gdrive-organizer/code/organizer.py create mode 100644 custom-skills/82-our-gdrive-organizer/desktop/SKILL.md create mode 100644 custom-skills/82-our-gdrive-organizer/docs/CHANGELOG.md create mode 100644 custom-skills/82-our-gdrive-organizer/shared/conventions.md create mode 100644 custom-skills/82-our-gdrive-organizer/shared/patterns/README.md create mode 100644 custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-files.md create mode 100644 custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-folders.md create mode 100644 custom-skills/82-our-gdrive-organizer/shared/patterns/categorization-rules.md create mode 100644 custom-skills/82-our-gdrive-organizer/shared/patterns/gotchas.md rename custom-skills/{82-tui-design-template => 92-tui-design-template}/README.md (100%) rename custom-skills/{82-tui-design-template => 92-tui-design-template}/code/SKILL.md (100%) rename custom-skills/{82-tui-design-template => 92-tui-design-template}/shared/references/dtm-wizard-reference.md (100%) diff --git a/AGENTS.md b/AGENTS.md index 68c0779..1a00c87 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -195,10 +195,17 @@ Task( - **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-tui-design-template**: TUI wizard interface design (Norton Commander / Gopher style, Rich) +- **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-91) +### 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) diff --git a/CLAUDE.md b/CLAUDE.md index 767119f..5c75cce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -158,7 +158,7 @@ This is a Claude Skills collection repository containing: |---|-------|---------|---------| | 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 | tui-design-template | TUI wizard interface design (Norton Commander / Gopher style, Rich) | "build TUI", "CLI wizard", "terminal UI", "Rich TUI" | +| 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" | ## Dual-Platform Skill Structure @@ -261,7 +261,7 @@ our-claude-skills/ │ │ │ ├── 80-claude-settings-optimizer/ │ ├── 81-mac-optimizer/ -│ ├── 82-tui-design-template/ +│ ├── 82-our-gdrive-organizer/ │ │ │ ├── 90-reference-curator/ # Modular reference documentation suite │ │ ├── 01-reference-discovery/ @@ -275,7 +275,9 @@ our-claude-skills/ │ │ ├── shared/ │ │ └── install.sh │ │ -│ └── 91-multi-agent-guide/ +│ ├── 91-multi-agent-guide/ +│ │ +│ └── 92-tui-design-template/ │ ├── example-skills/skills-main/ ├── official-skills/ diff --git a/custom-skills/82-our-gdrive-organizer/README.md b/custom-skills/82-our-gdrive-organizer/README.md new file mode 100644 index 0000000..115f777 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/README.md @@ -0,0 +1,96 @@ +# our-gdrive-organizer + +CLI + Claude skill for organizing OurDigital Google Drive folders. + +## Purpose + +Generalize the README-index-refresh pattern (originally built for +`02_SEO in Action/`) so it works on any 2nd-level subfolder of the user's +Google Drive Stream. Adds: + +- Per-subfolder `README.md` meta files (with AUTO-STRUCTURE blocks) +- Proposed filename normalization (`D.intelligence` → `OurDigital`) +- Proposed file moves for cluttered roots (screenshots, temp downloads) +- Sensitive-folder skip list so client archives are never touched + +## Activation + +- **As a Claude Code skill**: triggered by phrases like "organize the Drive + folder", "refresh the index", "/organize", or "/our-gdrive-organizer". +- **As a CLI tool**: run `our-gdrive-organize [TARGET]` from any terminal. + +## Structure + +``` +our-gdrive-organizer/ +├── code/ +│ ├── SKILL.md # Claude Code skill definition +│ └── organizer.py # Main Python CLI (stdlib only) +├── desktop/ +│ └── SKILL.md # Claude Desktop skill definition +├── shared/ +│ └── conventions.md # Canonical naming convention spec +├── docs/ +│ └── CHANGELOG.md +└── README.md # ← you are here +``` + +## Install + +The repository expects two symlinks: + +```bash +# Skill discovery for Claude Code +ln -s ~/Project/our-claude-skills/custom-skills/our-gdrive-organizer/code \ + ~/.claude/skills/our-gdrive-organizer + +# CLI on PATH +ln -s ~/Project/our-claude-skills/custom-skills/our-gdrive-organizer/code/organizer.py \ + ~/.local/bin/our-gdrive-organize +``` + +A slash-command alias lives at `~/.claude/commands/organize.md`. + +## Usage + +```bash +# Dry-run report (default — never modifies files except writing the README) +our-gdrive-organize ~/Library/CloudStorage/GoogleDrive-*/My\ Drive/02_SEO\ in\ Action + +# Apply all proposed renames + moves +our-gdrive-organize --apply + +# Only refresh the root README index +our-gdrive-organize --scope index + +# Only ensure each top-level subfolder has a README with AUTO block +our-gdrive-organize --scope subreadmes + +# Only propose renames (still requires --apply to execute) +our-gdrive-organize --scope rename + +# Machine-readable output +our-gdrive-organize --json +``` + +## Conventions + +See `shared/conventions.md` for the full naming and structure spec. The +script's rules (`RENAME_RULES`, `MOVE_RULES`, `SENSITIVE_SUBFOLDER_PATTERNS`) +in `code/organizer.py` are the source of truth for what's actually enforced; +keep them in sync with `conventions.md`. + +## What it never touches + +- Files inside `04_Case Studies/`, `99_Project Archive/`, any `*Archive*` + folder, or any folder starting with `진단` (real client material). +- The manually-curated Topics / Notes sections of the root README. +- Cell content of `.gsheet` / body of `.gdoc` / slides of `.gslides` — + filesystem renames only change the local stub filename. + +## Requirements + +- Python 3.9+ (uses `from __future__ import annotations`, dataclasses, pathlib) +- macOS (paths assume Google Drive Cloud Storage at + `~/Library/CloudStorage/GoogleDrive-*/My Drive/`); easily adapted for other + OSes by changing the target path. diff --git a/custom-skills/82-our-gdrive-organizer/code/SKILL.md b/custom-skills/82-our-gdrive-organizer/code/SKILL.md new file mode 100644 index 0000000..b138585 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/code/SKILL.md @@ -0,0 +1,209 @@ +--- +name: our-gdrive-organizer +description: | + Organize a Google Drive folder under OurDigital conventions: refresh root + README.md index, refresh per-subfolder README.md meta files, propose renames + for files using the old D.intelligence brand, and propose moves for cluttered + files (screenshots at the wrong level, temp/partial downloads). + + Triggers: + - "organize the Drive folder", "organize this folder" + - "refresh the index", "rescan the folder", "update README" + - "clean up cluttered files", "propose renames" + - "/organize", "/organize-drive", "/our-gdrive-organizer" + + Default target is the current working directory. Generalized to work on any + 2nd-level subfolder of the user's Google Drive Stream (My Drive/00_..., 01_..., + 02_..., etc.) — not specific to one folder. +version: "1.0" +author: OurDigital +environment: Code +--- + +# our-gdrive-organizer (Code) + +Walks a target directory (3 levels deep), refreshes the index README, proposes +renames + moves under OurDigital naming conventions, and optionally applies. + +Source of truth for the conventions: `../shared/conventions.md`. + +## Activation + +The user wants to organize one of their Drive folders. Cues: +- An explicit ask: "organize", "refresh index", "rename per convention", "scan for changes" +- The user is sitting inside a 2nd-level Drive folder (`~/Library/CloudStorage/GoogleDrive-*/My Drive/NN_*/`) +- Slash invocation: `/organize`, `/our-gdrive-organizer` + +## Workflow + +### Step 1 — Run dry-run, then summarize + +```bash +~/.local/bin/our-gdrive-organize "$TARGET" +``` + +(`$TARGET` defaults to cwd; pass an absolute path for a specific folder.) + +The script writes the README index immediately (idempotent — skips when no +structural change), but never renames or moves files without `--apply`. + +Read the report and tell the user, in plain language: +- How many structural changes were folded into the README +- Each rename proposal (old → new, reason) +- Each move proposal (file → destination subfolder, reason) +- Subfolders that were skipped because they're "sensitive" (`04_Case Studies`, + `99_Project Archive`, `*Archive*`, `진단*`) + +### Step 2 — Confirm with user before applying + +If the user says go ahead (or "apply", "yes", "do it"): + +```bash +~/.local/bin/our-gdrive-organize "$TARGET" --apply +``` + +If the user wants only part of the work: + +```bash +~/.local/bin/our-gdrive-organize "$TARGET" --scope rename --apply +~/.local/bin/our-gdrive-organize "$TARGET" --scope move --apply +~/.local/bin/our-gdrive-organize "$TARGET" --scope index # always writes +~/.local/bin/our-gdrive-organize "$TARGET" --scope subreadmes # always writes +``` + +### Step 3 — Verify + +After applying, run the dry-run once more and confirm the proposal list is +empty (or only contains items the user explicitly skipped). + +## Important guardrails + +- **Never rename or move files inside `04_Case Studies/`, `99_Project Archive/`, + any `*Archive*` folder, or any folder starting with `진단`.** Those contain + real client engagement records that must keep their original filenames. +- The script's rename/move rules live in `code/organizer.py` near the top of + the file (`RENAME_RULES`, `MOVE_RULES`, `SENSITIVE_SUBFOLDER_PATTERNS`). If + the user asks to add or change a rule, edit there and re-run. +- Filename renames on `.gsheet` / `.gdoc` stub files only change the local + filename — the actual Google Drive document and its sharing links are + preserved (the stub holds a Doc ID, not the content). + +## What the script does NOT do + +- Does not edit cell content inside `.gsheet` / `.gdoc` / `.xlsx` / `.pdf` — + only local-filesystem renames. Cell-level neutralization stays a manual task. +- Does not delete anything. +- Does not modify the manually-curated Topics / Notes sections of the root + README — only the AUTO-STRUCTURE block. +- Does not categorize files by reading their **content** — for that, use the + Content-based reorganization workflow below. + +## Folder-rename support + +The script proposes folder renames (in addition to file renames) using the +same `RENAME_RULES`. Guardrails: +- **Top-level subfolders are NEVER renamed automatically.** Names like + `00_Brand Management/` or `02_SEO Audit Toolkit/` are user-curated + practice areas. If the user wants one renamed, do it as a one-off `mv`. +- Sensitive folders are skipped entirely (not renamed, not recursed into). +- Eligible folders: depth-2 and deeper (e.g., + `00_Brand Management/D.intelligence SEO Audit/` → `…/OurDigital SEO Audit/`). + +When `--scope rename --apply` runs, file renames execute first, then folder +renames — order matters because renaming a folder first would invalidate +the file rename paths inside it. + +## Content-based reorganization (interactive) + +The script handles deterministic naming-pattern work. For judgment calls +(which folder does this file truly belong in?), use this workflow. + +### When to use + +The user says any of: +- "look at the contents and reorganize" +- "this folder feels cluttered, suggest a better layout" +- "categorize the files in {subfolder}" +- "audit my folder structure" + +Or you notice during a regular `/organize` run that: +- A subfolder root has many loose files that should plausibly be grouped +- Files appear duplicated across subfolders +- Filenames hint at content that doesn't match their location + +### Workflow + +1. **Anchor yourself** — read the patterns library before proposing anything: + - `../shared/patterns/canonical-folders.md` — what well-organized shapes look like + - `../shared/patterns/canonical-files.md` — what well-named files look like + - `../shared/patterns/categorization-rules.md` — IF→THEN placement rules + - `../shared/patterns/gotchas.md` — known edge cases + + These are the gotcha library. Re-read them every session — they grow over time. + +2. **Pick one subfolder at a time.** Don't try to reorganize an entire + 2nd-level folder in one pass — too much for the user to review. + +3. **Sample file contents.** For each file in the chosen subfolder: + - Markdown / txt / json: `Read` directly. + - `.gsheet` / `.gdoc` stubs: read the JSON to extract the doc_id, but + accept that you can't see actual cell content. Use the FILENAME + pattern + adjacent context. + - PDFs / .docx / .pptx / .xlsx: you can't read content with stdlib. + Either ask the user to summarize, or skip and rely on filename. + - For each file, note 1–2 sentences: what's it about, where would it + belong by content? + +4. **Build proposals**, grouped by destination: + ``` + Move from `02_SEO Audit Toolkit/` → `04_Case Studies/`: + - `signed-acme-contract.pdf` (real client name + signed contract content) + + Move from `02_SEO Audit Toolkit/` → `05_Working Template/`: + - `OurDigital-OOO Audit Template.gsheet` (placeholder name → template) + ``` + +5. **Present one batch (one source folder) at a time.** Ask: + "Should I apply these N moves from `{source}/`? Yes / No / partial (which)." + +6. **Apply via `Bash mv`** for each confirmed move: + ```bash + mv "/path/to/source/file" "/path/to/destination/file" + ``` + Then refresh the index: + ```bash + our-gdrive-organize "$TARGET" --scope index + ``` + +7. **Capture new gotchas.** If you encountered an ambiguous case the + patterns library didn't cover, append it to + `../shared/patterns/gotchas.md` before ending the session — that's how + the system learns. + +### Sensitive-folder reminder + +When proposing moves in content-based mode, the same guardrails apply: +**never propose moving files INTO or OUT OF**: +- `04_Case Studies/` +- `99_Project Archive/` +- Any `*Archive*` folder +- Any folder starting with `진단` + +If you notice something in those folders that looks misplaced, flag it to +the user as a manual review item — don't propose an automated move. + +## When the user asks to extend + +To add a new rename rule, edit `RENAME_RULES` in `code/organizer.py`: + +```python +(re.compile(r"oldpattern", re.I), "newpattern", "human-readable reason"), +``` + +To add a new move rule, edit `MOVE_RULES`: + +```python +(re.compile(r"^pattern\.ext$"), "destination_subfolder", "reason"), +``` + +Update `../shared/conventions.md` whenever rules change. diff --git a/custom-skills/82-our-gdrive-organizer/code/organizer.py b/custom-skills/82-our-gdrive-organizer/code/organizer.py new file mode 100755 index 0000000..2f4c6a7 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/code/organizer.py @@ -0,0 +1,520 @@ +#!/usr/bin/env python3 +""" +our-gdrive-organizer — organize a Google Drive folder under OurDigital conventions. + +Scans the target directory (default: 2nd-level Drive subfolder), refreshes the +README.md index, proposes renames + moves to bring files into convention, and +optionally applies them. + +Usage: + organizer.py [TARGET] # dry-run report + organizer.py [TARGET] --apply # apply all proposed changes + organizer.py [TARGET] --scope index # only refresh root README + organizer.py [TARGET] --scope subreadmes # refresh per-subfolder READMEs + organizer.py [TARGET] --scope rename # only propose/apply renames + organizer.py [TARGET] --scope move # only propose/apply moves + organizer.py [TARGET] --scope full # everything (default) + organizer.py [TARGET] --json # machine-readable output + +If TARGET is omitted, uses the current working directory. + +The script is stdlib-only (Python 3.9+). Safe by default — never writes unless +--apply is passed (or running --scope index, which always writes the README). +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import shutil +import sys +from dataclasses import dataclass, field, asdict +from datetime import datetime, timezone +from pathlib import Path +from typing import Iterable + +# --------------------------------------------------------------------------- +# Configuration: naming convention enforcement. +# Edit shared/conventions.md for the human-readable spec; this dict is the +# source of truth for the script. +# --------------------------------------------------------------------------- + +RENAME_RULES: list[tuple[re.Pattern[str], str, str]] = [ + # (pattern, replacement, reason) + # SEO context only — D.intelligence is the parent company; OurDigital is its + # SEO-specialty child brand. Only apply these renames when operating inside + # an SEO folder (02_SEO in Action, 04_SEO, etc.). Outside SEO context, keep + # D.intelligence names intact (they belong to the parent company). The + # script doesn't enforce this caveat — it relies on the caller pointing at + # the right target. See shared/patterns/gotchas.md "D.intelligence vs + # OurDigital" for the full rule. + (re.compile(r"D\.intelligence Lab-", re.I), "OurDigital-", + "parent-co SEO asset → OurDigital-"), + (re.compile(r"D\.intelligence", re.I), "OurDigital", + "parent-co SEO asset → OurDigital"), + # Variant without the dot (typo / informal spelling). + (re.compile(r"\bD intelligence\b", re.I), "OurDigital", + "parent-co SEO asset (no-dot variant) → OurDigital"), +] + +# Files matching these patterns at the root of a top-level subfolder are +# proposed to be moved into a sibling subfolder. +MOVE_RULES: list[tuple[re.Pattern[str], str, str]] = [ + (re.compile(r"^Screenshot \d{4}-\d{2}-\d{2}.*\.png$"), "screenshots", + "screenshot file → screenshots/ subfolder"), + (re.compile(r"^.*\.(crdownload|tmp|partial)$"), "_unsorted", + "incomplete / temp file → _unsorted/"), +] + +# Skip these subfolders for invasive changes (renames + moves). +# Indexing still includes them, but their internal files aren't reorganized. +SENSITIVE_SUBFOLDER_PATTERNS = [ + re.compile(r"^04_Case Studies$"), + re.compile(r"^99_Project Archive$"), + re.compile(r".*Archive$", re.I), + re.compile(r"^진단.*$"), +] + +EXCLUDE_NAMES = {".DS_Store", "Icon\r"} + +BEGIN_MARK = "" +END_MARK = "" + +# --------------------------------------------------------------------------- +# Data classes +# --------------------------------------------------------------------------- + + +@dataclass +class FolderScan: + path: Path + files: list[str] = field(default_factory=list) + subfolders: dict[str, "FolderScan"] = field(default_factory=dict) + + +@dataclass +class Proposal: + kind: str # "rename" | "move" + src: Path + dst: Path + reason: str + + def as_dict(self) -> dict: + return {"kind": self.kind, "src": str(self.src), + "dst": str(self.dst), "reason": self.reason} + + +# --------------------------------------------------------------------------- +# Scanning +# --------------------------------------------------------------------------- + + +def is_visible(name: str) -> bool: + return not name.startswith(".") and name not in EXCLUDE_NAMES + + +def list_visible_files(path: Path) -> list[str]: + try: + return sorted(e.name for e in os.scandir(path) + if e.is_file(follow_symlinks=False) and is_visible(e.name)) + except OSError: + return [] + + +def list_visible_dirs(path: Path) -> list[str]: + try: + return sorted(e.name for e in os.scandir(path) + if e.is_dir(follow_symlinks=False) and is_visible(e.name)) + except OSError: + return [] + + +def scan(path: Path, depth: int = 3) -> FolderScan: + """Walk depth levels deep. depth=3 → root + 2 levels of subfolders.""" + s = FolderScan(path=path, files=list_visible_files(path)) + if depth > 1: + for d in list_visible_dirs(path): + s.subfolders[d] = scan(path / d, depth - 1) + else: + for d in list_visible_dirs(path): + s.subfolders[d] = FolderScan(path=path / d) + return s + + +def is_sensitive(name: str) -> bool: + return any(p.search(name) for p in SENSITIVE_SUBFOLDER_PATTERNS) + + +# --------------------------------------------------------------------------- +# Index generation (root README.md and per-subfolder READMEs) +# --------------------------------------------------------------------------- + + +def render_structure_block(scan_root: FolderScan) -> str: + out: list[str] = [] + out.append("## Structure (3 levels)") + out.append("") + ts = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M %z") + out.append(f"_Auto-regenerated {ts}. Edits inside the AUTO-STRUCTURE markers will be overwritten._") + out.append("") + for d1_name in sorted(scan_root.subfolders): + d1 = scan_root.subfolders[d1_name] + nf1 = len(d1.files) + nd1 = len(d1.subfolders) + out.append(f"- **{d1_name}/** ({nf1} files, {nd1} subfolders)") + for d2_name in sorted(d1.subfolders): + d2 = d1.subfolders[d2_name] + nf2 = len(d2.files) + nd2 = len(d2.subfolders) + if nd2 > 0: + out.append(f" - {d2_name}/ ({nf2}f, {nd2}d)") + for d3_name in sorted(d2.subfolders): + d3 = d2.subfolders[d3_name] + nf3 = len(d3.files) + out.append(f" - {d3_name}/ ({nf3}f)") + else: + out.append(f" - {d2_name}/ ({nf2}f)") + if nf1 > 0: + preview = ", ".join(f"`{n}`" for n in d1.files[:5]) + out.append(f" - _files_: {preview}") + out.append("") + return "\n".join(out).rstrip() + "\n" + + +def render_subfolder_block(scan_node: FolderScan) -> str: + """Smaller block for per-subfolder README.md (2 levels deep).""" + out: list[str] = [] + out.append("## Contents") + out.append("") + ts = datetime.now(timezone.utc).astimezone().strftime("%Y-%m-%d %H:%M %z") + out.append(f"_Auto-regenerated {ts}._") + out.append("") + if scan_node.files: + out.append(f"**Files at root** ({len(scan_node.files)}):") + for f in scan_node.files[:20]: + out.append(f"- `{f}`") + if len(scan_node.files) > 20: + out.append(f"- … and {len(scan_node.files) - 20} more") + out.append("") + if scan_node.subfolders: + out.append(f"**Subfolders** ({len(scan_node.subfolders)}):") + for d_name in sorted(scan_node.subfolders): + d = scan_node.subfolders[d_name] + nf = len(d.files) + nd = len(d.subfolders) + extras = f", {nd} dirs" if nd else "" + out.append(f"- `{d_name}/` ({nf} files{extras})") + out.append("") + return "\n".join(out).rstrip() + "\n" + + +def splice_readme(readme_path: Path, new_block: str, *, header_default: str | None = None) -> tuple[bool, str]: + """Insert/replace the AUTO-STRUCTURE block inside readme_path. + + Returns (changed, summary). Creates the file with header_default if missing. + """ + if not readme_path.exists(): + if header_default is None: + return (False, f"skip: {readme_path} does not exist") + readme_path.write_text( + f"{header_default}\n\n{BEGIN_MARK}\n{new_block}{END_MARK}\n", + encoding="utf-8", + ) + return (True, f"created: {readme_path}") + + text = readme_path.read_text(encoding="utf-8") + + # Update top-level Snapshot counts if they exist + parent = readme_path.parent + total_files = len(list_visible_files(parent)) + total_dirs = len(list_visible_dirs(parent)) + text = re.sub(r"^- \*\*Top-level files\*\*:.*$", + f"- **Top-level files**: {total_files}", text, count=1, flags=re.M) + text = re.sub(r"^- \*\*Top-level subfolders\*\*:.*$", + f"- **Top-level subfolders**: {total_dirs}", text, count=1, flags=re.M) + + if BEGIN_MARK in text and END_MARK in text: + pattern = re.compile(re.escape(BEGIN_MARK) + r".*?" + re.escape(END_MARK), re.S) + new_text = pattern.sub(BEGIN_MARK + "\n" + new_block + END_MARK, text) + else: + # Append marker block before the trailing footer (if any) or at EOF + new_text = text.rstrip() + "\n\n" + BEGIN_MARK + "\n" + new_block + END_MARK + "\n" + + if structurally_equal(text, new_text): + return (False, f"no structural changes: {readme_path}") + readme_path.write_text(new_text, encoding="utf-8") + return (True, f"updated: {readme_path}") + + +def structurally_equal(a: str, b: str) -> bool: + """Strip the auto-regenerated timestamp lines before comparing.""" + strip = re.compile(r"^_Auto-regenerated .*$", re.M) + return strip.sub("", a) == strip.sub("", b) + + +# --------------------------------------------------------------------------- +# Rename + move proposals +# --------------------------------------------------------------------------- + + +def propose_renames(scan_root: FolderScan) -> list[Proposal]: + proposals: list[Proposal] = [] + def visit(node: FolderScan): + for f in node.files: + new = f + reasons: list[str] = [] + for pat, repl, why in RENAME_RULES: + next_new = pat.sub(repl, new) + if next_new != new: + reasons.append(why) + new = next_new + if new != f: + proposals.append(Proposal("rename", node.path / f, + node.path / new, "; ".join(reasons))) + for sub_name, sub in node.subfolders.items(): + if is_sensitive(sub_name): + continue + visit(sub) + visit(scan_root) + return proposals + + +def propose_folder_renames(scan_root: FolderScan) -> list[Proposal]: + """Propose folder renames matching RENAME_RULES. + + Guardrails: + - Top-level subfolders (depth 1, e.g. "00_Brand Management/") are NEVER + renamed automatically — they're the user's manually curated practice + areas. Use a one-off `mv` if you need to rename one. + - Sensitive folders are skipped entirely (not renamed, not recursed into). + """ + proposals: list[Proposal] = [] + + def visit(node: FolderScan, parent_depth: int): + sub_depth = parent_depth + 1 + for sub_name, sub in node.subfolders.items(): + if is_sensitive(sub_name): + continue + if sub_depth >= 2: # depth-2+ folders are eligible for rename + new_name = sub_name + reasons: list[str] = [] + for pat, repl, why in RENAME_RULES: + nxt = pat.sub(repl, new_name) + if nxt != new_name: + reasons.append(why) + new_name = nxt + if new_name != sub_name: + proposals.append(Proposal( + "rename-folder", sub.path, + sub.path.parent / new_name, + "; ".join(reasons), + )) + visit(sub, sub_depth) + + visit(scan_root, parent_depth=0) + return proposals + + +def propose_moves(scan_root: FolderScan) -> list[Proposal]: + proposals: list[Proposal] = [] + # Only operate on top-level subfolders of the target — that's where the + # "cluttered files at root" pattern most commonly appears. + for sub_name, sub in scan_root.subfolders.items(): + if is_sensitive(sub_name): + continue + for f in sub.files: + for pat, dest_subdir, why in MOVE_RULES: + if pat.search(f): + proposals.append(Proposal( + "move", + sub.path / f, + sub.path / dest_subdir / f, + why, + )) + break + return proposals + + +def apply_proposals(props: Iterable[Proposal]) -> list[tuple[Proposal, str]]: + results: list[tuple[Proposal, str]] = [] + for p in props: + try: + if p.kind == "move": + p.dst.parent.mkdir(parents=True, exist_ok=True) + if p.dst.exists(): + results.append((p, "skip: destination exists")) + continue + shutil.move(str(p.src), str(p.dst)) + results.append((p, "ok")) + except OSError as e: + results.append((p, f"error: {e}")) + return results + + +# --------------------------------------------------------------------------- +# Per-subfolder README.md ensuring + refresh +# --------------------------------------------------------------------------- + + +def refresh_subfolder_readmes(scan_root: FolderScan, *, write: bool) -> list[str]: + """For each top-level subfolder, ensure README.md exists with AUTO block.""" + log: list[str] = [] + for sub_name in sorted(scan_root.subfolders): + if is_sensitive(sub_name): + log.append(f"skip (sensitive): {sub_name}/") + continue + sub = scan_root.subfolders[sub_name] + readme = sub.path / "README.md" + block = render_subfolder_block(sub) + header = f"# {sub_name}\n\n_OurDigital — auto-indexed subfolder README._" + if not write: + log.append(f"would refresh: {readme}") + continue + changed, summary = splice_readme(readme, block, header_default=header) + log.append(summary) + return log + + +# --------------------------------------------------------------------------- +# Reporting +# --------------------------------------------------------------------------- + + +def text_report(target: Path, renames: list[Proposal], folder_renames: list[Proposal], + moves: list[Proposal], index_summary: str, sub_log: list[str]) -> str: + lines = [] + lines.append(f"# our-gdrive-organizer report") + lines.append(f"target: {target}") + lines.append(f"scan time: {datetime.now().strftime('%F %T')}") + lines.append("") + lines.append("## Index") + lines.append(index_summary or "(skipped)") + lines.append("") + lines.append("## Subfolder READMEs") + if sub_log: + for line in sub_log: + lines.append(f"- {line}") + else: + lines.append("(skipped)") + lines.append("") + lines.append(f"## File rename proposals ({len(renames)})") + if renames: + for p in renames: + lines.append(f"- {p.src.relative_to(target)}") + lines.append(f" → {p.dst.relative_to(target)}") + lines.append(f" ({p.reason})") + else: + lines.append("(none)") + lines.append("") + lines.append(f"## Folder rename proposals ({len(folder_renames)})") + if folder_renames: + for p in folder_renames: + lines.append(f"- {p.src.relative_to(target)}/") + lines.append(f" → {p.dst.relative_to(target)}/") + lines.append(f" ({p.reason})") + else: + lines.append("(none)") + lines.append("") + lines.append(f"## Move proposals ({len(moves)})") + if moves: + for p in moves: + lines.append(f"- {p.src.relative_to(target)}") + lines.append(f" → {p.dst.relative_to(target)}") + lines.append(f" ({p.reason})") + else: + lines.append("(none)") + return "\n".join(lines) + "\n" + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="OurDigital Google Drive folder organizer", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__, + ) + parser.add_argument("target", nargs="?", default=os.getcwd(), + help="target directory (default: cwd)") + parser.add_argument("--apply", action="store_true", + help="actually perform renames and moves (default: dry-run)") + parser.add_argument("--scope", choices=["full", "index", "subreadmes", + "rename", "move"], default="full", + help="restrict the operation (default: full)") + parser.add_argument("--json", action="store_true", + help="emit machine-readable JSON instead of a text report") + + args = parser.parse_args(argv) + target = Path(args.target).expanduser().resolve() + if not target.is_dir(): + print(f"target is not a directory: {target}", file=sys.stderr) + return 1 + + scan_root = scan(target, depth=3) + + index_summary = "" + sub_log: list[str] = [] + renames: list[Proposal] = [] + folder_renames: list[Proposal] = [] + moves: list[Proposal] = [] + + if args.scope in ("full", "index"): + readme = target / "README.md" + block = render_structure_block(scan_root) + header = f"# {target.name}\n\n_OurDigital — auto-indexed root README._\n\n## Snapshot\n\n- **Top-level files**: {len(scan_root.files)}\n- **Top-level subfolders**: {len(scan_root.subfolders)}" + changed, index_summary = splice_readme(readme, block, header_default=header) + + if args.scope in ("full", "subreadmes"): + sub_log = refresh_subfolder_readmes(scan_root, write=True) + + # Order matters when --apply: file renames first, then folder renames. + # If folders are renamed first, file paths inside them become invalid. + if args.scope in ("full", "rename"): + renames = propose_renames(scan_root) + if args.apply and renames: + results = apply_proposals(renames) + for p, status in results: + p.reason = f"{p.reason} [{status}]" + + folder_renames = propose_folder_renames(scan_root) + if args.apply and folder_renames: + # Apply deepest folders first — renaming a parent invalidates the + # path of any nested rename. shutil.move on the deepest target + # leaves the parent's name intact and avoids stale paths. + ordered = sorted(folder_renames, key=lambda p: len(p.src.parts), reverse=True) + results = apply_proposals(ordered) + for p, status in results: + p.reason = f"{p.reason} [{status}]" + + if args.scope in ("full", "move"): + moves = propose_moves(scan_root) + if args.apply and moves: + results = apply_proposals(moves) + for p, status in results: + p.reason = f"{p.reason} [{status}]" + + if args.json: + out = { + "target": str(target), + "scope": args.scope, + "applied": args.apply, + "index": index_summary, + "subfolder_readmes": sub_log, + "renames": [p.as_dict() for p in renames], + "folder_renames": [p.as_dict() for p in folder_renames], + "moves": [p.as_dict() for p in moves], + } + print(json.dumps(out, ensure_ascii=False, indent=2)) + else: + print(text_report(target, renames, folder_renames, moves, index_summary, sub_log)) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/custom-skills/82-our-gdrive-organizer/desktop/SKILL.md b/custom-skills/82-our-gdrive-organizer/desktop/SKILL.md new file mode 100644 index 0000000..82b3337 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/desktop/SKILL.md @@ -0,0 +1,103 @@ +--- +name: our-gdrive-organizer +description: | + Organize a Google Drive folder under OurDigital conventions: refresh the + root README.md index, refresh per-subfolder README.md meta files, propose + filename normalizations (D.intelligence → OurDigital, client name → OOO + placeholder), and propose moves for cluttered files (screenshots, temp + downloads). + + Triggers: + - "organize the Drive folder", "organize this folder" + - "refresh the index", "rescan the folder", "update README" + - "clean up cluttered files", "propose renames" + - "/organize", "/our-gdrive-organizer" + + Designed for any 2nd-level Drive folder (not specific to one project). + In Claude Desktop, this is a guide — the actual filesystem operations + are performed by the user via the bundled `organizer.py` CLI in a terminal, + or by Claude with a filesystem MCP tool if available. +version: "1.0" +author: OurDigital +environment: Desktop +--- + +# our-gdrive-organizer (Desktop) + +Guide for organizing a Drive folder under OurDigital naming + structure +conventions. In Claude Desktop without filesystem write access, walk the user +through running the bundled CLI script themselves. + +## Activation + +User says any of: +- "organize my Drive folder" +- "refresh the index README" +- "scan for changes and update the README" +- "rename files to follow OurDigital convention" +- "/organize" (slash command) + +## Workflow + +### Step 1 — Identify the target folder + +Ask the user which folder they want to organize, or assume the conversation's +current 2nd-level Drive folder (e.g., `02_SEO in Action/`, +`01_Brand in Action/`, `00_OurDigital/`). + +### Step 2 — Have the user run the CLI + +The Python script lives in the user's local `~/Project/our-claude-skills/` +repo and is symlinked to `~/.local/bin/our-gdrive-organize`. The user runs: + +```bash +our-gdrive-organize "/Users/ourdigital/Library/CloudStorage/GoogleDrive-andrew.yim@ourdigital.org/My Drive/NN_FolderName" +``` + +This is a dry-run by default. It always writes the README index (idempotent), +and it prints proposals for renames and moves without applying them. + +Ask the user to paste back the report. + +### Step 3 — Summarize and confirm + +Read the report, summarize each proposed rename and move in plain language, and +ask the user which (if any) they want to apply. They can apply all: + +```bash +our-gdrive-organize "/path/to/folder" --apply +``` + +Or just one scope at a time: + +```bash +our-gdrive-organize "/path/to/folder" --scope rename --apply +out-gdrive-organize "/path/to/folder" --scope move --apply +``` + +## Important guardrails + +The script automatically skips invasive changes (renames, moves) inside: +- `04_Case Studies/` +- `99_Project Archive/` +- Any folder ending in `Archive` +- Any folder starting with `진단` + +These are client-engagement records that must keep their original filenames. +If the user wants to override that, they need to edit +`SENSITIVE_SUBFOLDER_PATTERNS` in `~/Project/our-claude-skills/custom-skills/our-gdrive-organizer/code/organizer.py`. + +## Naming convention + +See `../shared/conventions.md` for the canonical spec. Highlights: +- `D.intelligence` → `OurDigital` (rebrand) +- Client-specific quote/template files → `OurDigital-{topic}-OOO {date}.{ext}` +- Top-level subfolders → `NN_descriptive name` +- Client subfolders inside Active Workspaces / Project Archive → `NN_{client name}` + +## Troubleshooting + +- **"command not found: our-gdrive-organize"** — the symlink may not be set up. + Ask the user to run: `ln -s ~/Project/our-claude-skills/custom-skills/our-gdrive-organizer/code/organizer.py ~/.local/bin/our-gdrive-organize` +- **Script changes README but the user doesn't see the update** — Google Drive + sync delay; usually 5-30 seconds. diff --git a/custom-skills/82-our-gdrive-organizer/docs/CHANGELOG.md b/custom-skills/82-our-gdrive-organizer/docs/CHANGELOG.md new file mode 100644 index 0000000..d3584b0 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/docs/CHANGELOG.md @@ -0,0 +1,80 @@ +# Changelog + +## 1.2 — 2026-05-10 + +First content-based reorganization session. Pattern library grew from 7 to +12 gotchas. RENAME_RULES extended. + +### Added + +- `RENAME_RULES` now catches `D intelligence` (no-dot variant) in addition + to the dot-form and `D.intelligence Lab-` prefix. +- Five new gotcha entries in `shared/patterns/gotchas.md`: + - "D.intelligence vs OurDigital" — parent company / SEO child brand; + rebrand only applies in SEO context + - "Brand-variant typos that escape the regex" — how to add new variants + without false positives + - "Real client names in 예시 자료 모음/" — half-done neutralization; + rename + TODO for cell content + - "Near-duplicate templates across 문서 양식 and 05_Working Template" — + different Doc IDs ≠ different content; needs human comparison + - "Stray screenshot that turns out to be a process diagram" — view + binary screenshots before applying default move rule + +### Changed + +- `shared/conventions.md` "Brand prefix" section rewritten to clarify the + parent-company / child-brand distinction and the SEO-context caveat. + +## 1.1 — 2026-05-10 + +Added folder-rename support and the patterns gotcha library. + +### Added + +- `propose_folder_renames()` in `organizer.py` — applies `RENAME_RULES` to + folder names at depth 2+. Top-level subfolders and sensitive folders are + guarded. +- File renames execute before folder renames during `--apply` so paths stay + valid. +- New report section "Folder rename proposals" in both text and JSON output. +- `shared/patterns/` gotcha library: + - `canonical-files.md` — examples of well-named files in their proper homes + - `canonical-folders.md` — well-organized folder shapes + - `categorization-rules.md` — IF→THEN placement rules + - `gotchas.md` — known edge cases with resolutions +- Content-based reorganization workflow in `code/SKILL.md`. Claude reads + files (not just names), consults the patterns library, and proposes moves + one folder at a time for user confirmation. Applied via `Bash mv`. + +### Changed + +- `text_report()` and `--json` output now include `folder_renames`. +- `code/SKILL.md` expanded with content-based workflow + patterns references. +- `shared/conventions.md` cross-references the patterns library. + +## 1.0 — 2026-05-10 + +Initial release. Generalizes the `seo-drive-index` skill pattern to work on +any 2nd-level Drive subfolder. + +### Added + +- Pure-Python CLI (`code/organizer.py`, stdlib only) +- Root README.md index refresh (Snapshot counts + Structure section between + AUTO-STRUCTURE markers; preserves manual Topics / Notes) +- Per-subfolder README.md meta-file ensuring (`--scope subreadmes`) +- Rename proposals enforcing `D.intelligence` → `OurDigital` rebrand +- Move proposals for screenshots and temp downloads cluttering folder roots +- Sensitive-folder skip list (`04_Case Studies`, `99_Project Archive`, + `*Archive*`, `진단*`) +- Dual SKILL.md (Claude Code + Claude Desktop) +- Slash-command alias `/organize` +- `shared/conventions.md` canonical naming spec + +### Removed (during this work) + +- `~/.claude/skills/seo-drive-index/` (superseded by this skill) +- `~/Library/LaunchAgents/org.ourdigital.seo-in-action-index.plist` (no + scheduled job; skill is invoked on demand instead) +- `~/.local/bin/seo-in-action-index.sh` (old bash version) diff --git a/custom-skills/82-our-gdrive-organizer/shared/conventions.md b/custom-skills/82-our-gdrive-organizer/shared/conventions.md new file mode 100644 index 0000000..5a7597e --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/shared/conventions.md @@ -0,0 +1,153 @@ +# OurDigital Drive Folder Conventions + +Canonical reference for naming and organization patterns enforced by +`our-gdrive-organizer`. The script's rules live in `code/organizer.py` — +keep this document and that code in sync. + +For the **gotcha library** (canonical examples + edge cases consulted +during interactive content-based reorganization), see `patterns/`: +- `patterns/canonical-files.md` +- `patterns/canonical-folders.md` +- `patterns/categorization-rules.md` +- `patterns/gotchas.md` + +The brand prefix rules (`D.intelligence …` → `OurDigital …`) now apply to +folder names too, at depth 2+. Top-level folders (e.g., `00_Brand Management/`) +are never auto-renamed. + +## Folder naming + +### 2nd-level Drive folders (top-level practice areas) + +`NN_descriptive name`, where NN is a 2-digit numeric prefix indicating the +"sort bucket": + +| Range | Meaning | +|-------|---------| +| 00–09 | Brand / core / cross-cutting | +| 10–19 | Product or specialty practice | +| 20–29 | Data / integrations | +| 90–99 | Archive | + +Examples observed: `00_OurDigital`, `01_Brand in Action`, `02_SEO in Action`, +`10_OurSEO Agent`, `20_DataForSEO`, `99_Project Archive`. + +### 3rd-level subfolders + +`NN_descriptive name` again, scoped to the parent. Maturity ordering: + +| Range | Meaning | +|-------|---------| +| 00–05 | Frameworks / always-on assets | +| 10–13 | Productized tooling / specialty modules | +| 20 | Data | +| 90+ | Archive within this practice | + +### Client / project subfolders + +Inside `01_Active Workspaces/` or `99_Project Archive/`: + +`NN_{client name}` — e.g., `00_Jamie Clinic`, `04_오현이혼상속센터`, +`05_1가다`. NN is assigned in chronological / engagement order. + +### Reserved / system subfolders + +- `screenshots/` — auto-created by the organizer when moving stray screenshot + files out of a parent folder root. +- `_unsorted/` — auto-created by the organizer for incomplete downloads + (`.crdownload`, `.tmp`, `.partial`). + +## File naming + +### Templates and quotes (in `05_Working Template/`) + +`OurDigital-{Service}-OOO {date}.{ext}` or +`OurDigital-{Service}-OOOOOO {modifier}.{ext}`. + +`OOO` and `OOOOOO` are the canonical placeholder strings for "client name +goes here" — chosen to be visually obvious and easy to grep for. Use +whichever makes more visual sense in the filename. + +Examples: +- `OurDigital-SEO Coaching-OOO 견적-20240612.gsheet` +- `OurDigital-SEO Treatment-OOO 호텔 견적-20240903.gsheet` +- `OurDigital-OOO 호텔 SEO Basic 패키지-20250416.gsheet` +- `OurDigital-Data Access & Management Agreement - 2026.gsheet` + +### Brand prefix — parent company / SEO child brand + +D.intelligence is the **parent company**; OurDigital is its **SEO-specialty +child brand**. The rebrand-to-OurDigital rule only applies when the asset +is SEO-related. + +Rules (filesystem rename only — does not touch cell/body content): + +- `D.intelligence Lab-` → `OurDigital-` +- `D.intelligence` (anywhere) → `OurDigital` +- `D intelligence` (no-dot variant) → `OurDigital` + +The organizer applies these to both files and folders at depth 2+. Top-level +folders (`NN_…`) are never auto-renamed. + +**Important**: The script doesn't enforce the SEO-context caveat. When you +point the organizer at the whole Drive Stream or a non-SEO folder, it will +rename ALL D.intelligence references — which is wrong outside SEO context. +Only run the organizer on SEO folders (`02_SEO in Action/`, +`00_OurDigital/04_SEO/`, etc.). For non-SEO D.intelligence assets, do not +rename — they belong to the parent company. + +See `patterns/gotchas.md` "D.intelligence vs OurDigital" for full guidance. + +### Archive client files + +Files inside `99_Project Archive/{client}/` should KEEP their original +filenames including real client names. The script skips these folders for +rename/move operations. + +### Date stamps + +Use `YYYYMMDD` (no separator) when embedding a date in a filename: +`OurDigital-Data Access & Management Agreement - 2026.gsheet` +`OurDigital-SEO Coaching-OOO 견적-20240612.gsheet` + +When a date range is needed: `YYYYMMDD-YYYYMMDD`. + +### Extensions and stub files + +Google Drive Apps formats appear as 180-byte JSON stubs on the local +filesystem with these extensions: + +| Drive type | Extension | +|-----------|-----------| +| Docs | `.gdoc` | +| Sheets | `.gsheet` | +| Slides | `.gslides`| +| Forms | `.gform` | + +Renaming the stub on the filesystem only changes the local filename — the +backing Google document, its Doc ID, sharing links, and revision history are +untouched. Safe to rename. Cell-level / body-level changes still require +opening the file in the corresponding Google app. + +## What the organizer does NOT enforce + +- Cell content inside `.gsheet`, body content inside `.gdoc`, slide content + inside `.gslides`, or any binary file (`.pdf`, `.pptx`, `.xlsx`). Those need + manual editing in the source app. Track them with a `TODO.md` if needed + (see `02_SEO in Action/98_Training/TODO.md` for an example pattern). +- Folder maturity ordering — the NN prefix system is a convention, but the + script doesn't reorder or renumber folders. +- Per-folder `README.md` content beyond the AUTO-STRUCTURE block — manual + Topics / Notes sections are preserved. + +## Sensitive folders (never auto-modified) + +Internal regex list (`SENSITIVE_SUBFOLDER_PATTERNS` in `organizer.py`): + +- `^04_Case Studies$` +- `^99_Project Archive$` +- `.*Archive$` (case-insensitive) +- `^진단.*$` + +These folders are still indexed (counts appear in the README structure), +but no files inside them are renamed or moved. diff --git a/custom-skills/82-our-gdrive-organizer/shared/patterns/README.md b/custom-skills/82-our-gdrive-organizer/shared/patterns/README.md new file mode 100644 index 0000000..8cf9b5c --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/shared/patterns/README.md @@ -0,0 +1,31 @@ +# Patterns library — gotcha folder + +The "gotcha folder" Claude consults when doing **content-based reorganization**. +Filename pattern matching is handled deterministically by `code/organizer.py`; +this folder is for the judgment-calls Claude makes when reading file contents +to decide where something belongs. + +## Files + +- `canonical-files.md` — examples of well-named files in their proper homes. + When Claude sees a file and isn't sure where it goes, it pattern-matches + against these canonical examples. +- `canonical-folders.md` — well-organized folder shapes. What goes in + `02_SEO Audit Toolkit/` vs `03_SEO Hack Library/` vs `99_Project Archive/`. +- `categorization-rules.md` — explicit IF→THEN rules for placement decisions. + These trump heuristics; if a rule applies, follow it. +- `gotchas.md` — edge cases, ambiguous files, common mistakes, and how to + resolve them. + +## How to extend + +When the user encounters a new edge case or pattern that the script can't +handle automatically, add it here: +1. If the rule is deterministic (filename pattern → action), put it in + `code/organizer.py` (`RENAME_RULES`, `MOVE_RULES`) and document in + `shared/conventions.md`. +2. If the rule is judgment-based (read content, decide), add a new entry + here and reference it from `code/SKILL.md`'s content-based workflow. + +The patterns library should grow over time as Claude + user encounter more +edge cases. Each addition is a "gotcha" the system has learned. diff --git a/custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-files.md b/custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-files.md new file mode 100644 index 0000000..1b0d443 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-files.md @@ -0,0 +1,92 @@ +# Canonical file examples + +Reference set of well-named files in their correct homes. When Claude sees +an unfamiliar file, it should pattern-match against these and ask: "what +home best fits this file's shape?" + +## Templates / blank artifacts → `05_Working Template/` + +Filenames are generic and use placeholder client names (`OOO`, `OOOOOO`): + +- `OurDigital-SEO Audit Template-20240922.gsheet` +- `OurDigital-Local SEO Audit 견적서.xlsx` +- `OurDigital-OOO 호텔 SEO Basic 패키지-20250416.gsheet` +- `OurDigital-OOOOOO SEO Audit & Treatment_20250120.gsheet` +- `OurDigital-SEO Coaching-OOO 견적-20240612.gsheet` +- `OurDigital-on-Page SEO-T&D Generator.gsheet` +- `OurDigital-SEO job tracker-2025.gsheet` +- `OurDigital-Data Access & Management Agreement - 2026.gsheet` + +Pattern: `OurDigital-{topic}-{OOO/OOOOOO placeholder}-{YYYYMMDD}.{ext}` + +## Audit toolkit reusable assets → `02_SEO Audit Toolkit/` + +Reusable proposal templates, on-page elements kits, framework documents: + +- `D.intelligence Lab-SEO Audit & Treatment 2025-Proposal-Template.pdf` + (legacy — should be renamed to `OurDigital-…`) +- `OurDigital-on-Pages Elements Kit-20250529.gsheet` +- `SEO Audit & Check_NotebookLM.wav` + +Subfolders typical here: `참고 자료/`, `예시 자료 모음/`, `문서 양식/`, +`교육 자료/`, `Google Business Profile/`, `robots.txt/`, `sitemap.xml/`. + +## Reference library / external research → `03_SEO Hack Library/` + +Third-party PDFs, webinar materials, structured-data references, academic +papers, framework PPTs: + +- `1-1-what-is-structured-data-structured-data-for-beginners.pdf` +- `2-1-the-yoast-seo-graph-structured-data-for-beginners.pdf` +- `SEO_Advanced_구조화 된 데이터 스키마의 이해.pptx` +- `OurDigital-SEO-curl-commands.pdf` +- `opensurvey_trend_search_2024.pdf` + +Pattern: external-source-derived names (paper IDs, course module numbering, +vendor names) — not OurDigital-prefixed. + +## Active engagement working files → `01_Active Workspaces/{NN_client}/` + +Real client name in path; files reference the engagement directly: + +- `01_Active Workspaces/00_Jamie Clinic/JAM-FAQ 엔트리 추출-정리-20260304.gsheet` +- `01_Active Workspaces/01_신라호텔 SEO/SLA - SEO KPIs and goal setup_20251020.gsheet` +- `01_Active Workspaces/01_신라호텔 SEO/Shilla_SEO_Performance_Scorecard.xlsx` +- `01_Active Workspaces/02_K Beauty CC 우승기업/[Note] K-Beauty CC 우승 기업 컨설팅_20260210.gdoc` + +Pattern: `{ClientPrefix}-{topic}-{date}.{ext}` or `[Note] {description}.gdoc`. +Client name appears in BOTH the folder name AND the filename — that's expected. + +## Past engagement deliverables → `04_Case Studies/` + +Polished proposal/audit PDFs from completed work, used as portfolio reference. +Real client names retained: + +- `신라호텔 SEO 제안서 개요.pdf` +- `소노 인터내셔널 Preliminary SEO Audit.pdf` +- `JHR-조선호텔 웹사이트 리뉴얼 SEO 전략 검토-20260303.gdoc` +- `그랜드 머큐어 임페리얼 팰리스 호텔 SEO 진단 컨설팅 계약서.pdf` + +Pattern: client name first, deliverable type, optional date. Always real +names (this folder is the case-study portfolio). + +## Archived completed projects → `99_Project Archive/{NN_client}/` + +Full engagement archive — all working files preserved with original names: + +- `99_Project Archive/01_그랜드 머큐어 임페리얼 SEO Basic/00_성과 측정 기준/...` +- `99_Project Archive/02_TNS 유학원 SEO 진단/Crawling data/...` +- `99_Project Archive/04_오현이혼상속센터/D.intelligence Lab-SEO Coaching-양제민 변호사-20240806.gsheet` + +Pattern: client name in folder, original delivery filenames preserved +(do NOT normalize — these are historical records). + +## Training materials → `98_Training/` + +Generic, anonymized lesson content. Client-specific lesson notes belong +in the archive, NOT here: + +- `98_Training/01_수업 계획_코스/Content Marketing Workout.md` +- `98_Training/01_수업 계획_코스/SEO 입문 기본교육 과정.gdoc` +- `98_Training/04_강의자료 스크린샷/Screenshot 2024-09-22 at *.png` +- `98_Training/09_외부 강의/Shopee e-Commerce SEO/Global e-Commerce SEO in Action 101.pdf` diff --git a/custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-folders.md b/custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-folders.md new file mode 100644 index 0000000..4cc626d --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/shared/patterns/canonical-folders.md @@ -0,0 +1,96 @@ +# Canonical folder shapes + +What "well-organized" looks like at each level. Use these as anchors when +deciding whether to create a new subfolder or which existing one a file +should live in. + +## Top-level practice area (e.g., `02_SEO in Action/`) + +``` +NN_Practice Area/ +├── README.md # auto-indexed root +├── 00_Brand Management/ # 00–05: frameworks / always-on +├── 01_Active Workspaces/ # current client engagements +├── 02_{Practice} Audit Toolkit/ # reusable audit assets +├── 03_{Practice} Hack Library/ # external reference / research +├── 04_Case Studies/ # past engagement portfolio +├── 05_Working Template/ # blank quote/audit/coaching templates +├── 10_{Productized Tool}/ # 10–13: productized specialty +├── 11_{Specialty Module}/ +├── 20_{Data Source}/ # 20s: data integrations +├── 98_Training/ # 98: training materials +└── 99_Project Archive/ # 99: completed engagements +``` + +Maturity convention by NN range: +- 00–05: foundational / framework / always-on +- 10–13: productized tools / specialties +- 20–29: data integrations +- 90–99: archive / training / closed work + +## Active workspace shape (`01_Active Workspaces/`) + +``` +01_Active Workspaces/ +├── README.md +├── 00_{Client A}/ +│ ├── {client-prefix}-{deliverable}-{date}.{ext} +│ ├── [Note] {topic} {date}.gdoc +│ └── AI Reference/ # if engagement uses AI tools +└── 01_{Client B}/ + └── ... +``` + +Each client gets one subfolder, NN-prefixed in chronological onboarding +order. Files use a short client prefix (e.g., `JAM-` for Jamie, `SLA-` for +신라호텔, `JHR-` for 조선호텔). + +## Case study shape (`04_Case Studies/`) + +Flat — files at the root, no subfolders: + +``` +04_Case Studies/ +├── README.md +├── {ClientName} SEO {DeliverableType}.pdf +├── {ClientName} {DeliverableType} {date}.gdoc +└── ... +``` + +Each file is a portfolio-quality deliverable. No working files here. + +## Project archive shape (`99_Project Archive/`) + +``` +99_Project Archive/ +├── README.md +├── 01_{First archived client}/ +│ ├── 00_{topic}/ # may have its own NN-prefixed subfolders +│ ├── 09_사전 진단 / # diagnostic phase +│ ├── 10_계약 관련/ # contract phase +│ ├── 90_고객사 수급자료/ # client-supplied materials +│ └── {original-filename}.{ext} # plus loose root files +└── 02_{Second archived client}/ + └── ... +``` + +Sub-folder structure inside each archived client mirrors the original +engagement folder layout — nothing renamed or normalized. + +## Audit toolkit shape (`02_SEO Audit Toolkit/`) + +``` +02_{Practice} Audit Toolkit/ +├── README.md +├── 참고 자료/ # references +├── 예시 자료 모음/ # neutralized example deliverables +├── 문서 양식/ # blank document forms +├── 교육 자료/ # internal training assets +├── {tool}_standard check_{date}/ # versioned tool snapshots +├── Google Business Profile/ # specialty subdomain +├── robots.txt/ # specialty subdomain +└── sitemap.xml/ # specialty subdomain +``` + +Subfolders here are **topic-grouped reusable assets**. Each subfolder is +purpose-named (Korean OK) — not NN-prefixed because order doesn't matter. diff --git a/custom-skills/82-our-gdrive-organizer/shared/patterns/categorization-rules.md b/custom-skills/82-our-gdrive-organizer/shared/patterns/categorization-rules.md new file mode 100644 index 0000000..ca34259 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/shared/patterns/categorization-rules.md @@ -0,0 +1,62 @@ +# Categorization rules + +IF→THEN rules for deciding where a file belongs. Apply in order; first +match wins. + +## Hard-coded rules (always honor) + +| If file… | Then it belongs in… | +|---|---| +| Lives inside `04_Case Studies/` or `99_Project Archive/` | leave it (sensitive — never auto-move) | +| Filename starts with `Screenshot YYYY-MM-DD at` | parent folder's `screenshots/` subdir | +| Has extension `.crdownload`, `.tmp`, `.partial` | `_unsorted/` (temp / interrupted) | +| Has `D.intelligence` in name | rename to `OurDigital` (filesystem rename only) | + +## Content + filename heuristics (judgment calls) + +### Templates vs Case Studies + +| Signal | Verdict | +|---|---| +| Contains placeholder strings `OOO` / `OOOOOO` in filename | Template — `05_Working Template/` | +| Filename is `OurDigital-{topic}-{specific-client}-{date}` AND client is real | Case Study or Active Workspace, NOT template | +| Real client name in filename + signed contract content | `04_Case Studies/` | +| Real client name + WIP / current-quarter date | `01_Active Workspaces/{NN_client}/` | +| Real client name + closure-marker content (final report, sign-off) | `99_Project Archive/{NN_client}/` | + +### Reference library vs Audit toolkit + +| Signal | Verdict | +|---|---| +| Source is external (academic paper, vendor whitepaper, foreign URL slug, paper ID `2286-Article…`) | `03_{Practice} Hack Library/` | +| Source is OurDigital-authored, intended as reusable proposal/audit asset | `02_{Practice} Audit Toolkit/` | +| Korean filename about a niche specialty (sitemap.xml, robots.txt) | `02_…/{specialty}/` subdirectory | + +### Training material vs Engagement notes + +| Signal | Verdict | +|---|---| +| Generic title, no client name (`SEO 입문 기본교육 과정.pdf`) | `98_Training/01_수업 계획_코스/` | +| Lesson notes referencing real lawyer/doctor/consultant by name | `99_Project Archive/{NN_client}/` (it's a record of the engagement) | +| Screenshot from a lesson | `98_Training/04_강의자료 스크린샷/` if generic; archive if it shows specific client data | +| External lecture deck (Shopee, Google event) | `98_Training/09_외부 강의/{Topic}/` | + +### Quote / proposal / contract documents + +| Signal | Verdict | +|---|---| +| Quote sheet for a specific client, engagement won → archived | `99_Project Archive/{NN_client}/` | +| Quote sheet for a specific client, engagement won → ongoing | `01_Active Workspaces/{NN_client}/` | +| Quote sheet that's been neutralized (client → OOO) | `05_Working Template/` | +| Signed contract PDF | usually `04_Case Studies/` (portfolio) or archive | + +## Anti-rules (do NOT do these) + +- **Do not rename files inside `04_Case Studies/` or `99_Project Archive/`** + even if they have `D.intelligence` in the name. Those are historical records + and the brand at the time was D.intelligence — preserving that is correct. +- **Do not change Korean folder names to English** (or vice versa). Language + is intentional. +- **Do not collapse client subfolders** even if they only have 1–2 files. +- **Do not move things to satisfy "everything balanced"** — empty subfolders + are fine if they represent reserved slots. diff --git a/custom-skills/82-our-gdrive-organizer/shared/patterns/gotchas.md b/custom-skills/82-our-gdrive-organizer/shared/patterns/gotchas.md new file mode 100644 index 0000000..e0873c4 --- /dev/null +++ b/custom-skills/82-our-gdrive-organizer/shared/patterns/gotchas.md @@ -0,0 +1,275 @@ +# Gotchas + +Edge cases the system has learned. When in doubt during interactive +content-based reorganization, check here first. + +Each gotcha follows the format: **Pattern → Why it's tricky → Resolution**. + +--- + +### Lesson notes for a specific lawyer/doctor/consultant + +**Pattern**: `Notes – [레슨] SEO 진단 & 관리 수업 - 양제민 변호사 6회차 (대면).gdoc` +or similar lesson-format file referencing a real client by name. + +**Why tricky**: Looks like training material (it IS a lesson note), but it's +client-specific (양제민 variant of 오현이혼상속센터 engagement). + +**Resolution**: Belongs in `99_Project Archive/{NN_그_클라이언트}/`, NOT in +`98_Training/`. Training is for generic, reusable content. Client-specific +lesson notes are engagement records. + +--- + +### Quote sheet with `D.intelligence Lab-` prefix and a client name + +**Pattern**: `D.intelligence Lab-SEO Coaching-오현법률사무소-20240612.gsheet` + +**Why tricky**: Has both a brand-rebrand candidate (`D.intelligence Lab-` → +`OurDigital-`) AND a real client name that should become `OOO`. But cell +content can't be normalized by filesystem rename. + +**Resolution**: +1. Filesystem rename: `OurDigital-SEO Coaching-OOO 견적-20240612.gsheet` +2. Add to a `TODO.md` reminding the user to open the sheet in Google Sheets + and replace `오현법률사무소` and contact info inside cells with `OOO` / + placeholder text. +3. Move to `05_Working Template/` once both filesystem AND cell content are + neutralized. Until then, leaving the file in place with the rename done + is a valid intermediate state. + +--- + +### Files at the root that look like they should be in a subfolder + +**Pattern**: A top-level subfolder root contains 30+ loose files plus 0 +subfolders. E.g., a `screenshots` collection directly at the top of +`98_Training/`. + +**Why tricky**: The script's `MOVE_RULES` only catches very specific +patterns (`Screenshot YYYY-MM-DD…`). Manual moves often need judgment — +which subfolder should be created, what should be its name? + +**Resolution**: Interactive content-based mode. Claude reads filenames in +batches, proposes a subfolder name (matching local language convention — +Korean if rest of folder is Korean), confirms with user, then moves with +`mv`. Update the parent README afterward via `our-gdrive-organize --scope index`. + +--- + +### Korean vs English filename mixing inside one subfolder + +**Pattern**: `02_SEO Audit Toolkit/` contains both English files +(`OurDigital-on-Pages Elements Kit-20250529.gsheet`) and Korean subfolders +(`참고 자료/`, `문서 양식/`). + +**Why tricky**: Looks inconsistent at first glance, but is intentional — +files use English when they're "OurDigital products" and Korean when +they're "Korean-language reference materials." + +**Resolution**: Don't normalize. Language tracks function: +- OurDigital-authored asset → English filename, OurDigital prefix +- External / Korean reference → Korean filename +- Subfolder for grouping Korean references → Korean folder name + +--- + +### Empty subfolders + +**Pattern**: `99_Project Archive/03_소노펠리체CC Local SEO/09_수급 정보/` +contains 0 files. + +**Why tricky**: Tempting to delete to "clean up." But empty subfolders often +represent reserved engagement phases that the project just didn't reach, +or pending document deliveries. + +**Resolution**: Leave empty subfolders alone unless the user explicitly +says to clean them up. They don't break anything. + +--- + +### Numbered duplicates: `(1)`, `(2)` suffixes + +**Pattern**: `D.intelligence Lab-SEO Coaching-오현법률사무소-20240612 (1).gsheet` +exists alongside the same name without the `(1)`. + +**Why tricky**: Looks like a Drive sync duplicate, but `cmp` shows different +bytes — they're DIFFERENT Drive documents that happen to have the same name. + +**Resolution**: `cmp` the two `.gsheet` stubs. If different (which they +usually are for `(N)`-suffixed files), preserve both with a suffix like +`(v1)`, `(v2)`, or `(legacy)`. Never overwrite blindly. If actually +identical bytes, ask user which to keep. + +--- + +### `_unsorted/` accumulates over time + +**Pattern**: After several `--scope move --apply` runs, `_unsorted/` +accumulates `.crdownload` / `.tmp` files that the user never went back to. + +**Why tricky**: These are usually legitimate trash but occasionally a real +in-progress download. + +**Resolution**: Don't auto-delete. Periodically prompt the user: "Your +`_unsorted/` has N files older than 30 days. Want to review?" + +--- + +### A folder that mixes archive + active work + +**Pattern**: A subfolder under `01_Active Workspaces/` contains both +ongoing work AND files from a finished engagement that should have been +archived. + +**Why tricky**: Hard to tell from filenames alone. Need to check mtimes +and content (last-modified-recently → active; older + closure-marker docs → +should be archived). + +**Resolution**: Interactive mode. Claude reads file mtimes + samples +content, proposes splitting into a new `99_Project Archive/{NN_client}/` +entry. User confirms before moving. + +--- + +### D.intelligence vs OurDigital — parent company / child brand + +**Pattern**: Files or folders named `D.intelligence …`, `D intelligence …`, +or with the legacy `D.intelligence Lab-` prefix. + +**Why tricky**: D.intelligence is the **parent company**; OurDigital is its +**SEO-specialty child brand**. The rebrand-to-OurDigital rule only applies +when the asset is SEO-related. Non-SEO D.intelligence assets (consulting, +data, training in other practices) keep the D.intelligence name because +they belong to the parent company, not to OurDigital. + +**Resolution**: +- Inside an SEO context (`02_SEO in Action/`, `00_OurDigital/04_SEO/`, or + any folder whose name contains "SEO"): apply the standard rename + `D.intelligence … → OurDigital …`. +- Outside SEO context: **do not rename**. Flag for user review and + document the asset's intended owning practice. +- The `RENAME_RULES` in `code/organizer.py` cover three variants + (`D.intelligence Lab-`, `D.intelligence`, and the no-dot `D intelligence` + typo). The rules don't enforce the SEO-context caveat — that's the + caller's responsibility (point the script at an SEO folder, not the + whole Drive Stream). + +--- + +### Brand-variant typos that escape the regex + +**Pattern**: A filename uses an off-spec spelling of `D.intelligence` — +e.g., `D intelligence SEO Audit & Treatment.pdf` (no dot), or +`OurDigitial-…` (transposed letters), or `Techincal SEO` (transposed). + +**Why tricky**: The standard `D\.intelligence` regex requires the literal +dot, so the no-dot variant slips through. Same for OurDigital typos — +they don't match the brand pattern at all and look like normal filenames. + +**Resolution**: +1. When you find one during a manual review, do the rename via `mv` and + immediately consider whether to add a regex variant to `RENAME_RULES`. +2. The current rules cover: `D.intelligence Lab-`, `D.intelligence`, and + `D intelligence` (no-dot, word-boundaries to avoid false positives). +3. Common typos that are NOT in regex (because they're one-off mistakes): + `OurDigitial`, `Techincal`. Catch with `mv` during manual review. + +--- + +### Real client names in `예시 자료 모음/` + +**Pattern**: Files in `02_…/예시 자료 모음/` that still have real client +names in the filename — e.g., `OurDigital-SEO Audit-1gada.com-20240703.xlsx`, +`OurDigital-Sono International-Preliminary SEO Audit-20240927.gdoc`. + +**Why tricky**: The folder's canonical role is "neutralized example +deliverables" — examples to show in pre-sales without exposing real client +data. A file with a real client name in this folder is a half-done +neutralization. The original engagement copy usually exists elsewhere +(`99_Project Archive/{NN_client}/` or `04_Case Studies/`). + +**Resolution**: +1. Filesystem rename to neutralize the FILENAME using `OOO`-style + placeholders: `OurDigital-OOO 호텔 체인-Preliminary SEO Audit-…gdoc`. +2. Add to a `TODO.md` reminding the user to also neutralize CELL CONTENT + (real names, contact info, URLs, keyword examples) inside the source + Sheet/Doc — filesystem rename doesn't touch cell content. +3. Don't delete the file even though the original exists elsewhere — the + neutralized example serves a different purpose (sales / training) than + the archived original (engagement record). +4. If the original doesn't exist elsewhere, copy it to the right archive + folder FIRST before neutralizing the example. + +--- + +### Near-duplicate templates across `문서 양식/` and `05_Working Template/` + +**Pattern**: Same template name in both +`02_…/문서 양식/OurDigital-SEO Audit Template-{date1}.gsheet` and +`05_Working Template/OurDigital-SEO Audit Template-{date2}.gsheet` with +different dates (and different Doc IDs). + +**Why tricky**: Looks like the same template at v1 and v2 (good cleanup +target — keep the newer, archive the older). But sometimes they're +genuinely different templates that just happen to share a name. + +**Resolution**: +1. `cmp` the .gsheet stubs first. Always different (different Doc IDs) + for files at different dates — that just confirms they're separate + Drive Docs, not bytes-identical stubs. +2. The Doc IDs alone can't tell you whether the cell content is similar. + Open both Sheets in Google Drive. Usually one is a direct refinement + of the other (older = v1, newer = v2 with added rows/columns). +3. If clearly v1 / v2 of same template: delete v1, OR move v1 to + `05_Working Template/` with `(legacy v1)` suffix. +4. If genuinely different (e.g., one is "quick check" and other is + "comprehensive"): rename to disambiguate explicitly. +5. Always defer to the user for the open-and-compare step. Add to + `TODO.md` with both Doc IDs + paths so the user knows what to compare. + +--- + +### Stray screenshot that turns out to be a process diagram + +**Pattern**: A `Screenshot_YYYY-MM-DD…png` in a folder of templates that +the script's `MOVE_RULES` would normally route to a `screenshots/` subdir. + +**Why tricky**: The MOVE_RULES regex (`^Screenshot \d{4}-\d{2}-\d{2}…`) +treats anything with that prefix as junk to be tucked away. But sometimes +the screenshot is actually a captured workflow diagram, org chart, or +reference visualization that has real value AND a meaningful home elsewhere. + +**Resolution**: +- Always view the screenshot before moving it (use `Read` on the .png). +- If it's a diagram / reference visualization: rename to a descriptive + filename and move to the most relevant subfolder (often `참고 자료/` + for audit-toolkit context, `docs/` for code-related). +- If it's an actual junk screenshot (UI snapshot during work): apply the + default rule and move to `screenshots/`. +- The MOVE_RULES regex pattern uses the macOS default + `Screenshot YYYY-MM-DD at HH.MM.SS AM/PM.png` (with spaces). The + underscore variant `Screenshot_YYYY-MM-DD_at_*` does NOT match — + catch those manually during content review. + +--- + +## Adding new gotchas + +When you (Claude) encounter a new ambiguous case during a content-based +reorganization session, add an entry here BEFORE moving on. Format: + +``` +### Short pattern title + +**Pattern**: filename / structure example. + +**Why tricky**: what makes this hard. + +**Resolution**: what to do. + +--- +``` + +This is how the system gets smarter over time. The patterns library is +the institutional memory. diff --git a/custom-skills/82-tui-design-template/README.md b/custom-skills/92-tui-design-template/README.md similarity index 100% rename from custom-skills/82-tui-design-template/README.md rename to custom-skills/92-tui-design-template/README.md diff --git a/custom-skills/82-tui-design-template/code/SKILL.md b/custom-skills/92-tui-design-template/code/SKILL.md similarity index 100% rename from custom-skills/82-tui-design-template/code/SKILL.md rename to custom-skills/92-tui-design-template/code/SKILL.md diff --git a/custom-skills/82-tui-design-template/shared/references/dtm-wizard-reference.md b/custom-skills/92-tui-design-template/shared/references/dtm-wizard-reference.md similarity index 100% rename from custom-skills/82-tui-design-template/shared/references/dtm-wizard-reference.md rename to custom-skills/92-tui-design-template/shared/references/dtm-wizard-reference.md