[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6EGX9YZS7FVZ3MKC8THF0YR":3,"skill-same:01M6EGX9YZS7FVZ3MKC8THF0YR":108},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":35,"linkOnly":36,"markdownContent":37,"markdownBody":38,"frontMatter":39,"refName":41,"commitSha":42,"skillDirectory":43,"forksCount":44,"repositoryHtmlUrl":45,"repositoryHomepageUrl":46,"ownerHtmlUrl":47,"ownerBlogUrl":6,"ownerDescription":48,"updatedAt":49,"install":50,"resources":63},"01M6EGX9YZS7FVZ3MKC8THF0YR","content-hash-cache-pattern",null,"Cache expensive file processing results using SHA-256 content hashes — path-independent, auto-invalidating, with service layer separation.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faffaan-m\u002Feverything-claude-code\u002Fcontent-hash-cache-pattern\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34],"content","cache","pattern","content hash cache pattern","file","sha-256","results","auto","service","processing","separation","2026-11-10 10:12:30",false,"---\nname: content-hash-cache-pattern\ndescription: Cache expensive file processing results using SHA-256 content hashes — path-independent, auto-invalidating, with service layer separation.\norigin: ECC\n---\n\n# Content-Hash File Cache Pattern\n\nCache expensive file processing results (PDF parsing, text extraction, image analysis) using SHA-256 content hashes as cache keys. Unlike path-based caching, this approach survives file moves\u002Frenames and auto-invalidates when content changes.\n\n## When to Activate\n\n- Building file processing pipelines (PDF, images, text extraction)\n- Processing cost is high and same files are processed repeatedly\n- Need a `--cache\u002F--no-cache` CLI option\n- Want to add caching to existing pure functions without modifying them\n\n## Core Pattern\n\n### 1. Content-Hash Based Cache Key\n\nUse file content (not path) as the cache key:\n\n```python\nimport hashlib\nfrom pathlib import Path\n\n_HASH_CHUNK_SIZE = 65536  # 64KB chunks for large files\n\ndef compute_file_hash(path: Path) -> str:\n    \"\"\"SHA-256 of file contents (chunked for large files).\"\"\"\n    if not path.is_file():\n        raise FileNotFoundError(f\"File not found: {path}\")\n    sha256 = hashlib.sha256()\n    with open(path, \"rb\") as f:\n        while True:\n            chunk = f.read(_HASH_CHUNK_SIZE)\n            if not chunk:\n                break\n            sha256.update(chunk)\n    return sha256.hexdigest()\n```\n\n**Why content hash?** File rename\u002Fmove = cache hit. Content change = automatic invalidation. No index file needed.\n\n### 2. Frozen Dataclass for Cache Entry\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass(frozen=True, slots=True)\nclass CacheEntry:\n    file_hash: str\n    source_path: str\n    document: ExtractedDocument  # The cached result\n```\n\n### 3. File-Based Cache Storage\n\nEach cache entry is stored as `{hash}.json` — O(1) lookup by hash, no index file required.\n\n```python\nimport json\nfrom typing import Any\n\ndef write_cache(cache_dir: Path, entry: CacheEntry) -> None:\n    cache_dir.mkdir(parents=True, exist_ok=True)\n    cache_file = cache_dir \u002F f\"{entry.file_hash}.json\"\n    data = serialize_entry(entry)\n    cache_file.write_text(json.dumps(data, ensure_ascii=False), encoding=\"utf-8\")\n\ndef read_cache(cache_dir: Path, file_hash: str) -> CacheEntry | None:\n    cache_file = cache_dir \u002F f\"{file_hash}.json\"\n    if not cache_file.is_file():\n        return None\n    try:\n        raw = cache_file.read_text(encoding=\"utf-8\")\n        data = json.loads(raw)\n        return deserialize_entry(data)\n    except (json.JSONDecodeError, ValueError, KeyError):\n        return None  # Treat corruption as cache miss\n```\n\n### 4. Service Layer Wrapper (SRP)\n\nKeep the processing function pure. Add caching as a separate service layer.\n\n```python\ndef extract_with_cache(\n    file_path: Path,\n    *,\n    cache_enabled: bool = True,\n    cache_dir: Path = Path(\".cache\"),\n) -> ExtractedDocument:\n    \"\"\"Service layer: cache check -> extraction -> cache write.\"\"\"\n    if not cache_enabled:\n        return extract_text(file_path)  # Pure function, no cache knowledge\n\n    file_hash = compute_file_hash(file_path)\n\n    # Check cache\n    cached = read_cache(cache_dir, file_hash)\n    if cached is not None:\n        logger.info(\"Cache hit: %s (hash=%s)\", file_path.name, file_hash[:12])\n        return cached.document\n\n    # Cache miss -> extract -> store\n    logger.info(\"Cache miss: %s (hash=%s)\", file_path.name, file_hash[:12])\n    doc = extract_text(file_path)\n    entry = CacheEntry(file_hash=file_hash, source_path=str(file_path), document=doc)\n    write_cache(cache_dir, entry)\n    return doc\n```\n\n## Key Design Decisions\n\n| Decision | Rationale |\n|----------|-----------|\n| SHA-256 content hash | Path-independent, auto-invalidates on content change |\n| `{hash}.json` file naming | O(1) lookup, no index file needed |\n| Service layer wrapper | SRP: extraction stays pure, cache is a separate concern |\n| Manual JSON serialization | Full control over frozen dataclass serialization |\n| Corruption returns `None` | Graceful degradation, re-processes on next run |\n| `cache_dir.mkdir(parents=True)` | Lazy directory creation on first write |\n\n## Best Practices\n\n- **Hash content, not paths** — paths change, content identity doesn't\n- **Chunk large files** when hashing — avoid loading entire files into memory\n- **Keep processing functions pure** — they should know nothing about caching\n- **Log cache hit\u002Fmiss** with truncated hashes for debugging\n- **Handle corruption gracefully** — treat invalid cache entries as misses, never crash\n\n## Anti-Patterns to Avoid\n\n```python\n# BAD: Path-based caching (breaks on file move\u002Frename)\ncache = {\"\u002Fpath\u002Fto\u002Ffile.pdf\": result}\n\n# BAD: Adding cache logic inside the processing function (SRP violation)\ndef extract_text(path, *, cache_enabled=False, cache_dir=None):\n    if cache_enabled:  # Now this function has two responsibilities\n        ...\n\n# BAD: Using dataclasses.asdict() with nested frozen dataclasses\n# (can cause issues with complex nested types)\ndata = dataclasses.asdict(entry)  # Use manual serialization instead\n```\n\n## When to Use\n\n- File processing pipelines (PDF parsing, OCR, text extraction, image analysis)\n- CLI tools that benefit from `--cache\u002F--no-cache` options\n- Batch processing where the same files appear across runs\n- Adding caching to existing pure functions without modifying them\n\n## When NOT to Use\n\n- Data that must always be fresh (real-time feeds)\n- Cache entries that would be extremely large (consider streaming instead)\n- Results that depend on parameters beyond file content (e.g., different extraction configs)\n\n","# Content-Hash File Cache Pattern\n\nCache expensive file processing results (PDF parsing, text extraction, image analysis) using SHA-256 content hashes as cache keys. Unlike path-based caching, this approach survives file moves\u002Frenames and auto-invalidates when content changes.\n\n## When to Activate\n\n- Building file processing pipelines (PDF, images, text extraction)\n- Processing cost is high and same files are processed repeatedly\n- Need a `--cache\u002F--no-cache` CLI option\n- Want to add caching to existing pure functions without modifying them\n\n## Core Pattern\n\n### 1. Content-Hash Based Cache Key\n\nUse file content (not path) as the cache key:\n\n```python\nimport hashlib\nfrom pathlib import Path\n\n_HASH_CHUNK_SIZE = 65536  # 64KB chunks for large files\n\ndef compute_file_hash(path: Path) -> str:\n    \"\"\"SHA-256 of file contents (chunked for large files).\"\"\"\n    if not path.is_file():\n        raise FileNotFoundError(f\"File not found: {path}\")\n    sha256 = hashlib.sha256()\n    with open(path, \"rb\") as f:\n        while True:\n            chunk = f.read(_HASH_CHUNK_SIZE)\n            if not chunk:\n                break\n            sha256.update(chunk)\n    return sha256.hexdigest()\n```\n\n**Why content hash?** File rename\u002Fmove = cache hit. Content change = automatic invalidation. No index file needed.\n\n### 2. Frozen Dataclass for Cache Entry\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass(frozen=True, slots=True)\nclass CacheEntry:\n    file_hash: str\n    source_path: str\n    document: ExtractedDocument  # The cached result\n```\n\n### 3. File-Based Cache Storage\n\nEach cache entry is stored as `{hash}.json` — O(1) lookup by hash, no index file required.\n\n```python\nimport json\nfrom typing import Any\n\ndef write_cache(cache_dir: Path, entry: CacheEntry) -> None:\n    cache_dir.mkdir(parents=True, exist_ok=True)\n    cache_file = cache_dir \u002F f\"{entry.file_hash}.json\"\n    data = serialize_entry(entry)\n    cache_file.write_text(json.dumps(data, ensure_ascii=False), encoding=\"utf-8\")\n\ndef read_cache(cache_dir: Path, file_hash: str) -> CacheEntry | None:\n    cache_file = cache_dir \u002F f\"{file_hash}.json\"\n    if not cache_file.is_file():\n        return None\n    try:\n        raw = cache_file.read_text(encoding=\"utf-8\")\n        data = json.loads(raw)\n        return deserialize_entry(data)\n    except (json.JSONDecodeError, ValueError, KeyError):\n        return None  # Treat corruption as cache miss\n```\n\n### 4. Service Layer Wrapper (SRP)\n\nKeep the processing function pure. Add caching as a separate service layer.\n\n```python\ndef extract_with_cache(\n    file_path: Path,\n    *,\n    cache_enabled: bool = True,\n    cache_dir: Path = Path(\".cache\"),\n) -> ExtractedDocument:\n    \"\"\"Service layer: cache check -> extraction -> cache write.\"\"\"\n    if not cache_enabled:\n        return extract_text(file_path)  # Pure function, no cache knowledge\n\n    file_hash = compute_file_hash(file_path)\n\n    # Check cache\n    cached = read_cache(cache_dir, file_hash)\n    if cached is not None:\n        logger.info(\"Cache hit: %s (hash=%s)\", file_path.name, file_hash[:12])\n        return cached.document\n\n    # Cache miss -> extract -> store\n    logger.info(\"Cache miss: %s (hash=%s)\", file_path.name, file_hash[:12])\n    doc = extract_text(file_path)\n    entry = CacheEntry(file_hash=file_hash, source_path=str(file_path), document=doc)\n    write_cache(cache_dir, entry)\n    return doc\n```\n\n## Key Design Decisions\n\n| Decision | Rationale |\n|----------|-----------|\n| SHA-256 content hash | Path-independent, auto-invalidates on content change |\n| `{hash}.json` file naming | O(1) lookup, no index file needed |\n| Service layer wrapper | SRP: extraction stays pure, cache is a separate concern |\n| Manual JSON serialization | Full control over frozen dataclass serialization |\n| Corruption returns `None` | Graceful degradation, re-processes on next run |\n| `cache_dir.mkdir(parents=True)` | Lazy directory creation on first write |\n\n## Best Practices\n\n- **Hash content, not paths** — paths change, content identity doesn't\n- **Chunk large files** when hashing — avoid loading entire files into memory\n- **Keep processing functions pure** — they should know nothing about caching\n- **Log cache hit\u002Fmiss** with truncated hashes for debugging\n- **Handle corruption gracefully** — treat invalid cache entries as misses, never crash\n\n## Anti-Patterns to Avoid\n\n```python\n# BAD: Path-based caching (breaks on file move\u002Frename)\ncache = {\"\u002Fpath\u002Fto\u002Ffile.pdf\": result}\n\n# BAD: Adding cache logic inside the processing function (SRP violation)\ndef extract_text(path, *, cache_enabled=False, cache_dir=None):\n    if cache_enabled:  # Now this function has two responsibilities\n        ...\n\n# BAD: Using dataclasses.asdict() with nested frozen dataclasses\n# (can cause issues with complex nested types)\ndata = dataclasses.asdict(entry)  # Use manual serialization instead\n```\n\n## When to Use\n\n- File processing pipelines (PDF parsing, OCR, text extraction, image analysis)\n- CLI tools that benefit from `--cache\u002F--no-cache` options\n- Batch processing where the same files appear across runs\n- Adding caching to existing pure functions without modifying them\n\n## When NOT to Use\n\n- Data that must always be fresh (real-time feeds)\n- Cache entries that would be extremely large (consider streaming instead)\n- Results that depend on parameters beyond file content (e.g., different extraction configs)\n\n",{"name":5,"origin":40,"description":7},"ECC","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Faffaan-m\u002Feverything-claude-code\u002Fcontent-hash-cache-pattern",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":51,"prompt":61,"zipAvailable":62},[52,55,58],{"tool":53,"command":54},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill content-hash-cache-pattern",{"tool":56,"command":57},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill content-hash-cache-pattern",{"tool":59,"command":60},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill content-hash-cache-pattern","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Faffaan-m\u002Feverything-claude-code\u002Fcontent-hash-cache-pattern\u002FSKILL.md",true,[64,70,74,78,82,86,90,94,98,104],{"path":65,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":68,"sourceHtmlUrl":69},"evals\u002Ftrigger_eval.json","REFERENCE","参考文档",2008,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Fevals\u002Ftrigger_eval.json",{"path":71,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":72,"sourceHtmlUrl":73},"references\u002Fadversarial_prompts.md",12571,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Freferences\u002Fadversarial_prompts.md",{"path":75,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":76,"sourceHtmlUrl":77},"references\u002Fattempt_agent.md",1961,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Freferences\u002Fattempt_agent.md",{"path":79,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":80,"sourceHtmlUrl":81},"references\u002Fknown_constructions.md",2024,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Freferences\u002Fknown_constructions.md",{"path":83,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":84,"sourceHtmlUrl":85},"references\u002Fmodel_tier_defaults.md",2970,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Freferences\u002Fmodel_tier_defaults.md",{"path":87,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":88,"sourceHtmlUrl":89},"references\u002Fpresentation_prompts.md",6150,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Freferences\u002Fpresentation_prompts.md",{"path":91,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":92,"sourceHtmlUrl":93},"references\u002Fsolver_heuristics.md",5998,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Freferences\u002Fsolver_heuristics.md",{"path":95,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":96,"sourceHtmlUrl":97},"references\u002Fverifier_patterns.md",8284,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Freferences\u002Fverifier_patterns.md",{"path":99,"resourceType":100,"resourceTypeDesc":101,"sizeBytes":102,"sourceHtmlUrl":103},"scripts\u002Fcheck_latex.sh","SCRIPT","脚本",205,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Fscripts\u002Fcheck_latex.sh",{"path":105,"resourceType":100,"resourceTypeDesc":101,"sizeBytes":106,"sourceHtmlUrl":107},"scripts\u002Fcompile_pdf.sh",1485,"https:\u002F\u002Fgithub.com\u002Fzaka33333-hash\u002Fclaude-skills\u002Fblob\u002Fmain\u002Floose-skills\u002Fmath-olympiad\u002Fscripts\u002Fcompile_pdf.sh",[109,123,133,142,148,155],{"skillNo":110,"name":111,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":119,"publishedAt":35},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[114],{"categoryNo":115,"categoryName":116,"taxonomyType":117,"taxonomyTypeDesc":118},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[120,121,122],"model","model hierarchy","model hierarchy skill",{"skillNo":124,"name":125,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":129,"publishedAt":35},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[128],{"categoryNo":115,"categoryName":116,"taxonomyType":117,"taxonomyTypeDesc":118},[130,131,132],"MCP","zoom","zoom mcp",{"skillNo":134,"name":135,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":136,"categories":137,"keywords":138,"publishedAt":35},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[139,140,141,131],"sdk","zoom apps sdk","apps",{"skillNo":143,"name":144,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":145,"categories":146,"keywords":147,"publishedAt":35},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[144],{"skillNo":149,"name":150,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":154,"publishedAt":35},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[153],{"categoryNo":115,"categoryName":116,"taxonomyType":117,"taxonomyTypeDesc":118},[150],{"skillNo":156,"name":157,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":160,"publishedAt":35},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[157]]