[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6C206FH97FVPT97WGXS3ZX7":3,"skill-same:01M6C206FH97FVPT97WGXS3ZX7":58},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":32,"linkOnly":33,"markdownContent":34,"markdownBody":35,"frontMatter":36,"refName":37,"commitSha":38,"skillDirectory":39,"forksCount":40,"repositoryHtmlUrl":41,"repositoryHomepageUrl":6,"ownerHtmlUrl":42,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":43,"install":44,"resources":57},"01M6C206FH97FVPT97WGXS3ZX7","code-centralization",null,"Investigate existing utilities before adding code to avoid duplication and centralize shared behavior.","jleechanorg\u002Fjleechan-skills","jleechanorg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F221404966?v=4","01M6C1VJ99XG950X1DWPEBCBGX","01M6C1VJDK5YVDNT3362RT5WSB",6,"MIT","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcode-centralization\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[23,24,25,26,27,28,29,30,31],"code","code centralization","centralization","investigate","behavior","duplication","shared","Utilities","Existing","2026-11-10 10:12:30",false,"---\nname: code-centralization\ndescription: Investigate existing utilities before adding code to avoid duplication and centralize shared behavior.\n---\n\n# Code Centralization - Mandatory Investigation Protocol\n\n**Purpose**: Before writing ANY new code, you MUST investigate existing code to prevent duplication. This is not optional.\n\n## CRITICAL: Investigation Before Implementation\n\n**DEFAULT: REUSE EXISTING CODE** - You must prove similar code doesn't already exist before writing new code.\n\n### Mandatory Pre-Implementation Checklist\n\nBefore writing ANY new function, class, or module:\n\n1. **Search for existing implementations**\n   ```bash\n   # Search for similar function names\n   grep -r \"def similar_name\" --include=\"*.py\"\n\n   # Search for similar logic patterns\n   grep -r \"pattern_keyword\" --include=\"*.py\"\n\n   # Search imports to find utility modules\n   grep -r \"from.*utils import\" --include=\"*.py\"\n   ```\n\n2. **Document what you found**\n   - List ALL similar functions\u002Fmodules discovered\n   - Explain why each cannot be reused (or CAN be reused)\n\n3. **Justify new code only if reuse is impossible**\n   - \"Couldn't find similar code\" is only valid with search evidence\n   - \"Existing code doesn't quite fit\" requires explanation of gap\n\n### Evidence Required\n\n```markdown\n### Code Investigation for: \u003Cwhat you're implementing>\n\n**Search performed:**\n- `grep -r \"keyword1\" --include=\"*.py\"` → Found: \u003Cresults>\n- `grep -r \"keyword2\" --include=\"*.py\"` → Found: \u003Cresults>\n\n**Similar code found:**\n- `file1.py:function_name()` - Does X, but not Y (can't reuse because...)\n- `file2.py:other_function()` - Does Y, can be extended to also do X\n\n**Decision:**\n- [ ] Reuse existing: \u003Cwhich function>\n- [ ] Extend existing: \u003Cwhich function + what extension>\n- [ ] New code justified: \u003Cwhy reuse\u002Fextension impossible>\n```\n\n## Core Principle\n\n**Duplication is a maintenance liability.** When the same logic exists in multiple places, changes must be made in multiple locations, increasing the risk of inconsistencies and bugs.\n\n## When to Centralize\n\n### Red Flags: Code That Should Be Centralized\n\n1. **Identical Logic in Multiple Files**\n   - Same `hasattr()` checks repeated\n   - Same fallback logic duplicated\n   - Same validation patterns copied\n\n2. **Similar Patterns with Minor Variations**\n   - Type coercion logic repeated with slight differences\n   - Property accessors with same fallback chains\n   - API response building with duplicated field handling\n\n3. **Maintenance Burden**\n   - Bug fixes require changes in multiple places\n   - Feature additions need updates across files\n   - Tests must cover the same logic multiple times\n\n### Example: action_resolution\u002Foutcome_resolution Duplication\n\n**Before (Duplicated Logic)**:\n\n```python\n# ❌ BAD - llm_response.py (22 lines)\n@property\ndef action_resolution(self) -> dict[str, Any]:\n    if self.structured_response:\n        if hasattr(self.structured_response, \"action_resolution\"):\n            ar = self.structured_response.action_resolution\n            if ar is not None:\n                return ar\n        if hasattr(self.structured_response, \"outcome_resolution\"):\n            or_val = self.structured_response.outcome_resolution\n            if or_val is not None:\n                return or_val\n    return {}\n\n# ❌ BAD - world_logic.py (13 lines) - Same logic duplicated\nif hasattr(structured_response, \"action_resolution\"):\n    action_resolution = getattr(structured_response, \"action_resolution\", None)\n    if action_resolution is not None:\n        unified_response[\"action_resolution\"] = (\n            action_resolution if isinstance(action_resolution, dict) else {}\n        )\nif hasattr(structured_response, \"outcome_resolution\"):\n    outcome_resolution = getattr(structured_response, \"outcome_resolution\", None)\n    if outcome_resolution is not None:\n        unified_response[\"outcome_resolution\"] = (\n            outcome_resolution if isinstance(outcome_resolution, dict) else {}\n        )\n```\n\n**After (Centralized)**:\n\n```python\n# ✅ GOOD - action_resolution_utils.py (single source of truth)\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Get action_resolution with backward compat fallback.\"\"\"\n    if structured_response is None:\n        return {}\n    if hasattr(structured_response, \"action_resolution\"):\n        ar = structured_response.action_resolution\n        if ar is not None:\n            return ar\n    if hasattr(structured_response, \"outcome_resolution\"):\n        or_val = structured_response.outcome_resolution\n        if or_val is not None:\n            return or_val\n    return {}\n\ndef add_action_resolution_to_response(structured_response: Any, unified_response: dict[str, Any]) -> None:\n    \"\"\"Add action_resolution\u002Foutcome_resolution to API response.\"\"\"\n    if structured_response is None:\n        return\n    if hasattr(structured_response, \"action_resolution\"):\n        ar = getattr(structured_response, \"action_resolution\", None)\n        if ar is not None:\n            unified_response[\"action_resolution\"] = (\n                ar if isinstance(ar, dict) else {}\n            )\n    if hasattr(structured_response, \"outcome_resolution\"):\n        or_val = getattr(structured_response, \"outcome_resolution\", None)\n        if or_val is not None:\n            unified_response[\"outcome_resolution\"] = (\n                or_val if isinstance(or_val, dict) else {}\n            )\n\n# ✅ GOOD - llm_response.py (2 lines)\n@property\ndef action_resolution(self) -> dict[str, Any]:\n    return get_action_resolution(self.structured_response)\n\n# ✅ GOOD - world_logic.py (1 line)\nadd_action_resolution_to_response(structured_response, unified_response)\n```\n\n**Results**:\n- **Before**: 35 lines of duplicated logic across 2 files\n- **After**: 20 lines in helper module + 3 lines total in consuming files\n- **Net reduction**: 12 lines + single source of truth\n\n## TDD Approach to Centralization\n\n### Step 1: Write Tests First (RED)\n\nBefore extracting code, write comprehensive tests for the helper functions:\n\n```python\n# ✅ GOOD - Test all edge cases before extraction\ndef test_get_action_resolution_with_action_resolution(self):\n    \"\"\"Test returns action_resolution when present\"\"\"\n    mock_response = MagicMock()\n    mock_response.action_resolution = {\"player_input\": \"I attack\"}\n    result = get_action_resolution(mock_response)\n    self.assertEqual(result[\"player_input\"], \"I attack\")\n\ndef test_get_action_resolution_falls_back_to_outcome_resolution(self):\n    \"\"\"Test falls back to outcome_resolution when action_resolution missing\"\"\"\n    # ... test implementation\n\ndef test_get_action_resolution_handles_none(self):\n    \"\"\"Test handles None structured_response\"\"\"\n    result = get_action_resolution(None)\n    self.assertEqual(result, {})\n\n# ... additional tests for the helper’s actual branches and boundaries\n```\n\n### Step 2: Extract Helper Functions (GREEN)\n\nCreate helper module with functions that make tests pass:\n\n```python\n# ✅ GOOD - Helper module with clear responsibilities\n# $PROJECT_ROOT\u002Faction_resolution_utils.py\n\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Single source of truth for fallback logic.\"\"\"\n    # Implementation that passes all tests\n\ndef add_action_resolution_to_response(structured_response: Any, unified_response: dict[str, Any]) -> None:\n    \"\"\"API response builder with type coercion.\"\"\"\n    # Implementation that passes all tests\n```\n\n### Step 3: Refactor Existing Code (REFACTOR)\n\nReplace duplicated logic with helper calls:\n\n```python\n# ✅ GOOD - Replace 22 lines with 1 function call\n@property\ndef action_resolution(self) -> dict[str, Any]:\n    return get_action_resolution(self.structured_response)\n```\n\n### Step 4: Verify No Regressions\n\nRun the relevant existing and new checks selected by the repository's testing\nowner. Compare unexplained failures with the unchanged baseline and record their\ndisposition; a failed required check is not green.\n\n```bash\n# ✅ GOOD - Verify backward compatibility\npytest $PROJECT_ROOT\u002Ftests\u002Ftest_action_resolution.py\npytest $PROJECT_ROOT\u002Ftests\u002Ftest_end2end\u002Ftest_action_resolution_backward_compat_end2end.py\npytest $PROJECT_ROOT\u002Ftests\u002Ftest_action_resolution_utils.py  # New helper tests\n```\n\n## Helper Module Design Principles\n\n### 1. Single Responsibility\n\nEach helper function should do one thing well:\n\n```python\n# ✅ GOOD - Clear, focused function\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Get action_resolution with backward compat fallback.\"\"\"\n    # Only handles retrieval logic\n\n# ❌ BAD - Does too much\ndef process_action_resolution(structured_response: Any, validate: bool, coerce: bool) -> dict[str, Any]:\n    \"\"\"Gets, validates, and coerces action_resolution.\"\"\"\n    # Mixes retrieval, validation, and coercion\n```\n\n### 2. Backward Compatibility\n\nHelper functions must maintain exact same behavior:\n\n```python\n# ✅ GOOD - Preserves empty dict {} as \"present\" (not None)\nif ar is not None:  # Empty dict {} passes this check\n    return ar\n\n# ❌ BAD - Would break existing behavior\nif ar:  # Empty dict {} fails this check\n    return ar\n```\n\n### 3. Type Safety\n\nHandle None and invalid types gracefully:\n\n```python\n# ✅ GOOD - Defensive None checks\nif structured_response is None:\n    return {}\n\n# ✅ GOOD - Type coercion for API responses\nunified_response[\"action_resolution\"] = (\n    action_resolution if isinstance(action_resolution, dict) else {}\n)\n```\n\n### 4. Clear Naming\n\nFunction names should clearly indicate purpose:\n\n```python\n# ✅ GOOD - Self-documenting names\nget_action_resolution()  # Retrieves with fallback\nadd_action_resolution_to_response()  # Adds to API response\n\n# ❌ BAD - Ambiguous names\nprocess_resolution()  # What does it do?\nhandle_action()  # Too generic\n```\n\n## When NOT to Centralize\n\n### Keep Separate If:\n\n1. **Different Purposes**\n   - Initialization-time validation vs runtime property access\n   - Schema normalization vs API response building\n   - Different contexts require different logic\n\n2. **Performance Critical**\n   - Inline logic avoids function call overhead\n   - Hot paths where microseconds matter\n   - (Rare - usually premature optimization)\n\n3. **Tight Coupling**\n   - Logic is tightly coupled to specific class internals\n   - Extraction would require exposing private state\n   - Helper would need too many parameters\n\n**Example**: `narrative_response_schema.py` initialization logic stays separate because it handles validation\u002Fnormalization at object creation time, which is different from runtime property access.\n\n## Test Coverage Requirements\n\n### Before Refactoring\n\nEnsure existing tests provide safety net:\n\n- ✅ Unit tests for schema layer\n- ✅ End-to-end tests for integration paths\n- ✅ Edge case coverage (None, empty dict, type coercion)\n\n### After Extraction\n\nAdd comprehensive tests for helpers:\n\n- ✅ Unit tests for each helper function\n- ✅ Edge cases (None, empty dict, invalid types)\n- ✅ Backward compatibility verification\n- ✅ Integration tests with refactored code\n\nChoose tests for the helper’s actual branches, boundary conditions, and caller contracts.\nTest count is not a coverage target; use the scoped testing and evidence owners\nto select sufficient checks for the change.\n\n## Code Reduction Metrics\n\nTrack improvements:\n\n| Metric | Before | After | Improvement |\n|--------|--------|-------|-------------|\n| Lines of duplicated logic | 35 | 0 | -35 lines |\n| Helper module lines | 0 | 20 | +20 lines |\n| Consuming code lines | 35 | 3 | -32 lines |\n| **Net change** | 35 | 23 | **-12 lines** |\n| **Test coverage** | 19 tests | 37 tests | **+18 tests** |\n\n## Banned Patterns (ZERO TOLERANCE)\n\nThese patterns are **explicitly banned** and must never appear in code:\n\n### 1. `_v2`\u002F`_new`\u002F`_backup`\u002F`_old` File Names\n\n```python\n# ❌ BANNED - Never create these files\nauth_service_v2.py      # Edit the original instead\nuser_model_new.py       # Edit the original instead\nconfig_backup.py        # Use git for backups\nhelper_old.py           # Delete and replace\n\n# ✅ CORRECT - Edit existing files directly\nauth_service.py         # Modify in place\nuser_model.py           # Modify in place\n```\n\n**Why banned:** Creates confusion about which file is authoritative. Use git for version history.\n\n### 2. Dismissing or Misclassifying Test Failures\n\nInvestigate failures and compare against the unchanged baseline when attribution\nis uncertain. Fix failures caused by the change and other failures within the\nauthorized task scope. Record the command, output, baseline, and relevant SHA when\na failure predates the change; do not label it pre-existing without that evidence.\n\nA baseline failure does not authorize unrelated repairs or automatically stop\nindependent work. Continue useful in-scope checks and fixes, and report any\nrequired check that remains unproven. Follow the repository's readiness rules;\nnever call a failed required check green.\n\n### 3. Direct `import logging` (in `$PROJECT_ROOT\u002F`)\n\n```python\n# ❌ BANNED - Direct logging module in $PROJECT_ROOT\u002F\nimport logging\nlogger = logging.getLogger(__name__)\nlogger.info(\"message\")\n\n# ✅ CORRECT - Use unified logging_util\nfrom mvp_site import logging_util\nlogging_util.info(\"message\")\nlogging_util.warning(\"something concerning\")\nlogging_util.error(\"something failed\")\n```\n\n**Why banned:** `logging_util` provides unified output to both GCP Cloud Logging and local files with consistent formatting. Direct `import logging` bypasses this.\n\n**Exception:** Test files (`$PROJECT_ROOT\u002Ftests\u002F*`) may use direct logging.\n\n---\n\n## Anti-Patterns\n\n### ❌ Premature Centralization\n\nDon't extract code that's only used once:\n\n```python\n# ❌ BAD - Over-engineering for single use\ndef get_single_use_helper():\n    \"\"\"Only called once - not worth extracting\"\"\"\n    pass\n```\n\n### ❌ Over-Abstracting\n\nDon't create helpers that are harder to use than inline code:\n\n```python\n# ❌ BAD - More complex than original\ndef process_with_fallback(obj, primary_field, fallback_field, default_value, type_check, coerce_func):\n    \"\"\"Too many parameters - harder to use than inline\"\"\"\n    pass\n\n# ✅ GOOD - Simple, focused helper\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Clear purpose, minimal parameters\"\"\"\n    pass\n```\n\n### ❌ Breaking Backward Compatibility\n\nDon't change behavior when centralizing:\n\n```python\n# ❌ BAD - Changed behavior (empty dict now returns fallback)\nif ar:  # Changed from \"if ar is not None\"\n    return ar\n\n# ✅ GOOD - Preserves exact behavior\nif ar is not None:  # Same as original\n    return ar\n```\n\n## Checklist for Centralization\n\nBefore extracting duplicated code:\n\n- [ ] Identified 2+ locations with similar logic\n- [ ] Written comprehensive tests for helper functions (TDD)\n- [ ] Verified existing tests provide safety net\n- [ ] Extracted helper functions pass the applicable scoped checks\n- [ ] Refactored consuming code to use helpers\n- [ ] No regressions in the affected behavior; any pre-existing failure has an evidence-backed baseline disposition\n- [ ] Current required checks satisfy the repository's readiness rules; no failed required check is described as green\n- [ ] Helper tests cover the actual branches, boundaries, and caller contracts\n- [ ] Duplication reduced; net LOC recorded as an outcome rather than a readiness quota\n- [ ] Documentation updated if needed\n\n## Related Patterns\n\n- **DRY Principle**: Don't Repeat Yourself\n- **Single Source of Truth**: One place for each piece of logic\n- **Test-Driven Development**: Write tests before refactoring\n- **Backward Compatibility**: Maintain exact same behavior\n\n## Real-World Example\n\nSee `$PROJECT_ROOT\u002Faction_resolution_utils.py` for a complete example:\n- Helper functions with comprehensive tests\n- Refactored `llm_response.py` and `world_logic.py`\n- 37 tests passing (18 new + 19 existing)\n- 20 lines saved + eliminated duplication\n","# Code Centralization - Mandatory Investigation Protocol\n\n**Purpose**: Before writing ANY new code, you MUST investigate existing code to prevent duplication. This is not optional.\n\n## CRITICAL: Investigation Before Implementation\n\n**DEFAULT: REUSE EXISTING CODE** - You must prove similar code doesn't already exist before writing new code.\n\n### Mandatory Pre-Implementation Checklist\n\nBefore writing ANY new function, class, or module:\n\n1. **Search for existing implementations**\n   ```bash\n   # Search for similar function names\n   grep -r \"def similar_name\" --include=\"*.py\"\n\n   # Search for similar logic patterns\n   grep -r \"pattern_keyword\" --include=\"*.py\"\n\n   # Search imports to find utility modules\n   grep -r \"from.*utils import\" --include=\"*.py\"\n   ```\n\n2. **Document what you found**\n   - List ALL similar functions\u002Fmodules discovered\n   - Explain why each cannot be reused (or CAN be reused)\n\n3. **Justify new code only if reuse is impossible**\n   - \"Couldn't find similar code\" is only valid with search evidence\n   - \"Existing code doesn't quite fit\" requires explanation of gap\n\n### Evidence Required\n\n```markdown\n### Code Investigation for: \u003Cwhat you're implementing>\n\n**Search performed:**\n- `grep -r \"keyword1\" --include=\"*.py\"` → Found: \u003Cresults>\n- `grep -r \"keyword2\" --include=\"*.py\"` → Found: \u003Cresults>\n\n**Similar code found:**\n- `file1.py:function_name()` - Does X, but not Y (can't reuse because...)\n- `file2.py:other_function()` - Does Y, can be extended to also do X\n\n**Decision:**\n- [ ] Reuse existing: \u003Cwhich function>\n- [ ] Extend existing: \u003Cwhich function + what extension>\n- [ ] New code justified: \u003Cwhy reuse\u002Fextension impossible>\n```\n\n## Core Principle\n\n**Duplication is a maintenance liability.** When the same logic exists in multiple places, changes must be made in multiple locations, increasing the risk of inconsistencies and bugs.\n\n## When to Centralize\n\n### Red Flags: Code That Should Be Centralized\n\n1. **Identical Logic in Multiple Files**\n   - Same `hasattr()` checks repeated\n   - Same fallback logic duplicated\n   - Same validation patterns copied\n\n2. **Similar Patterns with Minor Variations**\n   - Type coercion logic repeated with slight differences\n   - Property accessors with same fallback chains\n   - API response building with duplicated field handling\n\n3. **Maintenance Burden**\n   - Bug fixes require changes in multiple places\n   - Feature additions need updates across files\n   - Tests must cover the same logic multiple times\n\n### Example: action_resolution\u002Foutcome_resolution Duplication\n\n**Before (Duplicated Logic)**:\n\n```python\n# ❌ BAD - llm_response.py (22 lines)\n@property\ndef action_resolution(self) -> dict[str, Any]:\n    if self.structured_response:\n        if hasattr(self.structured_response, \"action_resolution\"):\n            ar = self.structured_response.action_resolution\n            if ar is not None:\n                return ar\n        if hasattr(self.structured_response, \"outcome_resolution\"):\n            or_val = self.structured_response.outcome_resolution\n            if or_val is not None:\n                return or_val\n    return {}\n\n# ❌ BAD - world_logic.py (13 lines) - Same logic duplicated\nif hasattr(structured_response, \"action_resolution\"):\n    action_resolution = getattr(structured_response, \"action_resolution\", None)\n    if action_resolution is not None:\n        unified_response[\"action_resolution\"] = (\n            action_resolution if isinstance(action_resolution, dict) else {}\n        )\nif hasattr(structured_response, \"outcome_resolution\"):\n    outcome_resolution = getattr(structured_response, \"outcome_resolution\", None)\n    if outcome_resolution is not None:\n        unified_response[\"outcome_resolution\"] = (\n            outcome_resolution if isinstance(outcome_resolution, dict) else {}\n        )\n```\n\n**After (Centralized)**:\n\n```python\n# ✅ GOOD - action_resolution_utils.py (single source of truth)\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Get action_resolution with backward compat fallback.\"\"\"\n    if structured_response is None:\n        return {}\n    if hasattr(structured_response, \"action_resolution\"):\n        ar = structured_response.action_resolution\n        if ar is not None:\n            return ar\n    if hasattr(structured_response, \"outcome_resolution\"):\n        or_val = structured_response.outcome_resolution\n        if or_val is not None:\n            return or_val\n    return {}\n\ndef add_action_resolution_to_response(structured_response: Any, unified_response: dict[str, Any]) -> None:\n    \"\"\"Add action_resolution\u002Foutcome_resolution to API response.\"\"\"\n    if structured_response is None:\n        return\n    if hasattr(structured_response, \"action_resolution\"):\n        ar = getattr(structured_response, \"action_resolution\", None)\n        if ar is not None:\n            unified_response[\"action_resolution\"] = (\n                ar if isinstance(ar, dict) else {}\n            )\n    if hasattr(structured_response, \"outcome_resolution\"):\n        or_val = getattr(structured_response, \"outcome_resolution\", None)\n        if or_val is not None:\n            unified_response[\"outcome_resolution\"] = (\n                or_val if isinstance(or_val, dict) else {}\n            )\n\n# ✅ GOOD - llm_response.py (2 lines)\n@property\ndef action_resolution(self) -> dict[str, Any]:\n    return get_action_resolution(self.structured_response)\n\n# ✅ GOOD - world_logic.py (1 line)\nadd_action_resolution_to_response(structured_response, unified_response)\n```\n\n**Results**:\n- **Before**: 35 lines of duplicated logic across 2 files\n- **After**: 20 lines in helper module + 3 lines total in consuming files\n- **Net reduction**: 12 lines + single source of truth\n\n## TDD Approach to Centralization\n\n### Step 1: Write Tests First (RED)\n\nBefore extracting code, write comprehensive tests for the helper functions:\n\n```python\n# ✅ GOOD - Test all edge cases before extraction\ndef test_get_action_resolution_with_action_resolution(self):\n    \"\"\"Test returns action_resolution when present\"\"\"\n    mock_response = MagicMock()\n    mock_response.action_resolution = {\"player_input\": \"I attack\"}\n    result = get_action_resolution(mock_response)\n    self.assertEqual(result[\"player_input\"], \"I attack\")\n\ndef test_get_action_resolution_falls_back_to_outcome_resolution(self):\n    \"\"\"Test falls back to outcome_resolution when action_resolution missing\"\"\"\n    # ... test implementation\n\ndef test_get_action_resolution_handles_none(self):\n    \"\"\"Test handles None structured_response\"\"\"\n    result = get_action_resolution(None)\n    self.assertEqual(result, {})\n\n# ... additional tests for the helper’s actual branches and boundaries\n```\n\n### Step 2: Extract Helper Functions (GREEN)\n\nCreate helper module with functions that make tests pass:\n\n```python\n# ✅ GOOD - Helper module with clear responsibilities\n# $PROJECT_ROOT\u002Faction_resolution_utils.py\n\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Single source of truth for fallback logic.\"\"\"\n    # Implementation that passes all tests\n\ndef add_action_resolution_to_response(structured_response: Any, unified_response: dict[str, Any]) -> None:\n    \"\"\"API response builder with type coercion.\"\"\"\n    # Implementation that passes all tests\n```\n\n### Step 3: Refactor Existing Code (REFACTOR)\n\nReplace duplicated logic with helper calls:\n\n```python\n# ✅ GOOD - Replace 22 lines with 1 function call\n@property\ndef action_resolution(self) -> dict[str, Any]:\n    return get_action_resolution(self.structured_response)\n```\n\n### Step 4: Verify No Regressions\n\nRun the relevant existing and new checks selected by the repository's testing\nowner. Compare unexplained failures with the unchanged baseline and record their\ndisposition; a failed required check is not green.\n\n```bash\n# ✅ GOOD - Verify backward compatibility\npytest $PROJECT_ROOT\u002Ftests\u002Ftest_action_resolution.py\npytest $PROJECT_ROOT\u002Ftests\u002Ftest_end2end\u002Ftest_action_resolution_backward_compat_end2end.py\npytest $PROJECT_ROOT\u002Ftests\u002Ftest_action_resolution_utils.py  # New helper tests\n```\n\n## Helper Module Design Principles\n\n### 1. Single Responsibility\n\nEach helper function should do one thing well:\n\n```python\n# ✅ GOOD - Clear, focused function\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Get action_resolution with backward compat fallback.\"\"\"\n    # Only handles retrieval logic\n\n# ❌ BAD - Does too much\ndef process_action_resolution(structured_response: Any, validate: bool, coerce: bool) -> dict[str, Any]:\n    \"\"\"Gets, validates, and coerces action_resolution.\"\"\"\n    # Mixes retrieval, validation, and coercion\n```\n\n### 2. Backward Compatibility\n\nHelper functions must maintain exact same behavior:\n\n```python\n# ✅ GOOD - Preserves empty dict {} as \"present\" (not None)\nif ar is not None:  # Empty dict {} passes this check\n    return ar\n\n# ❌ BAD - Would break existing behavior\nif ar:  # Empty dict {} fails this check\n    return ar\n```\n\n### 3. Type Safety\n\nHandle None and invalid types gracefully:\n\n```python\n# ✅ GOOD - Defensive None checks\nif structured_response is None:\n    return {}\n\n# ✅ GOOD - Type coercion for API responses\nunified_response[\"action_resolution\"] = (\n    action_resolution if isinstance(action_resolution, dict) else {}\n)\n```\n\n### 4. Clear Naming\n\nFunction names should clearly indicate purpose:\n\n```python\n# ✅ GOOD - Self-documenting names\nget_action_resolution()  # Retrieves with fallback\nadd_action_resolution_to_response()  # Adds to API response\n\n# ❌ BAD - Ambiguous names\nprocess_resolution()  # What does it do?\nhandle_action()  # Too generic\n```\n\n## When NOT to Centralize\n\n### Keep Separate If:\n\n1. **Different Purposes**\n   - Initialization-time validation vs runtime property access\n   - Schema normalization vs API response building\n   - Different contexts require different logic\n\n2. **Performance Critical**\n   - Inline logic avoids function call overhead\n   - Hot paths where microseconds matter\n   - (Rare - usually premature optimization)\n\n3. **Tight Coupling**\n   - Logic is tightly coupled to specific class internals\n   - Extraction would require exposing private state\n   - Helper would need too many parameters\n\n**Example**: `narrative_response_schema.py` initialization logic stays separate because it handles validation\u002Fnormalization at object creation time, which is different from runtime property access.\n\n## Test Coverage Requirements\n\n### Before Refactoring\n\nEnsure existing tests provide safety net:\n\n- ✅ Unit tests for schema layer\n- ✅ End-to-end tests for integration paths\n- ✅ Edge case coverage (None, empty dict, type coercion)\n\n### After Extraction\n\nAdd comprehensive tests for helpers:\n\n- ✅ Unit tests for each helper function\n- ✅ Edge cases (None, empty dict, invalid types)\n- ✅ Backward compatibility verification\n- ✅ Integration tests with refactored code\n\nChoose tests for the helper’s actual branches, boundary conditions, and caller contracts.\nTest count is not a coverage target; use the scoped testing and evidence owners\nto select sufficient checks for the change.\n\n## Code Reduction Metrics\n\nTrack improvements:\n\n| Metric | Before | After | Improvement |\n|--------|--------|-------|-------------|\n| Lines of duplicated logic | 35 | 0 | -35 lines |\n| Helper module lines | 0 | 20 | +20 lines |\n| Consuming code lines | 35 | 3 | -32 lines |\n| **Net change** | 35 | 23 | **-12 lines** |\n| **Test coverage** | 19 tests | 37 tests | **+18 tests** |\n\n## Banned Patterns (ZERO TOLERANCE)\n\nThese patterns are **explicitly banned** and must never appear in code:\n\n### 1. `_v2`\u002F`_new`\u002F`_backup`\u002F`_old` File Names\n\n```python\n# ❌ BANNED - Never create these files\nauth_service_v2.py      # Edit the original instead\nuser_model_new.py       # Edit the original instead\nconfig_backup.py        # Use git for backups\nhelper_old.py           # Delete and replace\n\n# ✅ CORRECT - Edit existing files directly\nauth_service.py         # Modify in place\nuser_model.py           # Modify in place\n```\n\n**Why banned:** Creates confusion about which file is authoritative. Use git for version history.\n\n### 2. Dismissing or Misclassifying Test Failures\n\nInvestigate failures and compare against the unchanged baseline when attribution\nis uncertain. Fix failures caused by the change and other failures within the\nauthorized task scope. Record the command, output, baseline, and relevant SHA when\na failure predates the change; do not label it pre-existing without that evidence.\n\nA baseline failure does not authorize unrelated repairs or automatically stop\nindependent work. Continue useful in-scope checks and fixes, and report any\nrequired check that remains unproven. Follow the repository's readiness rules;\nnever call a failed required check green.\n\n### 3. Direct `import logging` (in `$PROJECT_ROOT\u002F`)\n\n```python\n# ❌ BANNED - Direct logging module in $PROJECT_ROOT\u002F\nimport logging\nlogger = logging.getLogger(__name__)\nlogger.info(\"message\")\n\n# ✅ CORRECT - Use unified logging_util\nfrom mvp_site import logging_util\nlogging_util.info(\"message\")\nlogging_util.warning(\"something concerning\")\nlogging_util.error(\"something failed\")\n```\n\n**Why banned:** `logging_util` provides unified output to both GCP Cloud Logging and local files with consistent formatting. Direct `import logging` bypasses this.\n\n**Exception:** Test files (`$PROJECT_ROOT\u002Ftests\u002F*`) may use direct logging.\n\n---\n\n## Anti-Patterns\n\n### ❌ Premature Centralization\n\nDon't extract code that's only used once:\n\n```python\n# ❌ BAD - Over-engineering for single use\ndef get_single_use_helper():\n    \"\"\"Only called once - not worth extracting\"\"\"\n    pass\n```\n\n### ❌ Over-Abstracting\n\nDon't create helpers that are harder to use than inline code:\n\n```python\n# ❌ BAD - More complex than original\ndef process_with_fallback(obj, primary_field, fallback_field, default_value, type_check, coerce_func):\n    \"\"\"Too many parameters - harder to use than inline\"\"\"\n    pass\n\n# ✅ GOOD - Simple, focused helper\ndef get_action_resolution(structured_response: Any) -> dict[str, Any]:\n    \"\"\"Clear purpose, minimal parameters\"\"\"\n    pass\n```\n\n### ❌ Breaking Backward Compatibility\n\nDon't change behavior when centralizing:\n\n```python\n# ❌ BAD - Changed behavior (empty dict now returns fallback)\nif ar:  # Changed from \"if ar is not None\"\n    return ar\n\n# ✅ GOOD - Preserves exact behavior\nif ar is not None:  # Same as original\n    return ar\n```\n\n## Checklist for Centralization\n\nBefore extracting duplicated code:\n\n- [ ] Identified 2+ locations with similar logic\n- [ ] Written comprehensive tests for helper functions (TDD)\n- [ ] Verified existing tests provide safety net\n- [ ] Extracted helper functions pass the applicable scoped checks\n- [ ] Refactored consuming code to use helpers\n- [ ] No regressions in the affected behavior; any pre-existing failure has an evidence-backed baseline disposition\n- [ ] Current required checks satisfy the repository's readiness rules; no failed required check is described as green\n- [ ] Helper tests cover the actual branches, boundaries, and caller contracts\n- [ ] Duplication reduced; net LOC recorded as an outcome rather than a readiness quota\n- [ ] Documentation updated if needed\n\n## Related Patterns\n\n- **DRY Principle**: Don't Repeat Yourself\n- **Single Source of Truth**: One place for each piece of logic\n- **Test-Driven Development**: Write tests before refactoring\n- **Backward Compatibility**: Maintain exact same behavior\n\n## Real-World Example\n\nSee `$PROJECT_ROOT\u002Faction_resolution_utils.py` for a complete example:\n- Helper functions with comprehensive tests\n- Refactored `llm_response.py` and `world_logic.py`\n- 37 tests passing (18 new + 19 existing)\n- 20 lines saved + eliminated duplication\n",{"name":5,"description":7},"main","42ccd02cbf5e8e47839dc3eab6fc754dc6b339cf",".claude\u002Fskills\u002Fcode-centralization",0,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills","https:\u002F\u002Fgithub.com\u002Fjleechanorg","2026-11-14 03:18:13",{"source":8,"skillName":5,"commands":45,"prompt":55,"zipAvailable":56},[46,49,52],{"tool":47,"command":48},"npx","npx skills add jleechanorg\u002Fjleechan-skills --skill code-centralization",{"tool":50,"command":51},"bunx","bunx skills add jleechanorg\u002Fjleechan-skills --skill code-centralization",{"tool":53,"command":54},"pnpm","pnpm dlx skills add jleechanorg\u002Fjleechan-skills --skill code-centralization","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcode-centralization\u002FSKILL.md",true,[],[59,77,97,111,125,141],{"skillNo":60,"name":61,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":32},"01M716Y5MTJ6GYJZ2ATXCJK8G4","instinct","Use when the user invokes \u002Finstinct or \u002Fin, or asks to \"message Instinct\", \"ask Instinct\", \"tell Instinct\", \"check Instinct\", or read replies from the configured peer assistant. iMessage first, WhatsApp Web as fallback.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Finstinct\u002FSKILL.md",[],[61,66,67,68,69,70,71,72,73,74,75,76],"user","when","replies","check","use","identity","whatsapp","gmail","read","message","ask",{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":86,"publishedAt":32},"01M716Y4HF7FVVMJSA2CCVN5VF","hr","Compatibility alias for history-resume; produce the same fresh-state, provenance-aware resume briefing.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhr\u002FSKILL.md",[83],{"categoryNo":84,"categoryName":85,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000038","人力资源",[79,87,88,89,90,91,92,93,94,95,96],"resume","compatibility","provenance","for","history","produce","alias","briefing","state","fresh",{"skillNo":98,"name":99,"nameZh":6,"description":100,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":104,"publishedAt":32},"01M716Y1FFNFKFJYYKF8HQA6T8","history-resume","Resume briefing for the current repository branch or worktree using fresh state and admitted history sources.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhistory-resume\u002FSKILL.md",[103],{"categoryNo":84,"categoryName":85,"taxonomyType":20,"taxonomyTypeDesc":21},[91,87,105,106,66,107,94,108,90,109,110,95,96],"history resume","branch","analysis","repository","worktree","sources",{"skillNo":112,"name":113,"nameZh":6,"description":114,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":115,"categories":116,"keywords":117,"publishedAt":32},"01M716XWXJ8EPHK4RH9T14TR85","grok","Alias for the canonical grokbot browser conversation workflow; \u002Fgrok talks to the Grok bot the same way \u002Fdot talks to Dot.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgrok\u002FSKILL.md",[],[113,118,119,120,90,121,93,122,123,124],"dot","bot","workflow","canonical","conversation","browser","grokbot",{"skillNo":126,"name":127,"nameZh":6,"description":128,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":131,"publishedAt":32},"01M70N9Q07DTE0EW5KFXMMMSJ0","codexa-bench","Compare `codexa` and normal `codex` on a fair split of comparable work by default; use `--strict` for duplicate runs of the exact same task.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcodexa-bench\u002FSKILL.md",[],[132,133,134,70,135,90,136,137,138,139,140],"codexa bench","work","split","default","duplicate","task","compare","normal","codex",{"skillNo":142,"name":143,"nameZh":6,"description":144,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":145,"licenseFileUrl":146,"sourceHtmlUrl":147,"categories":148,"keywords":160,"publishedAt":32},"01M6C24S2X60SP8BBFGAEKPEGW","frontend-design","Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML\u002FCSS layouts, or when styling\u002Fbeautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ffrontend-design\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ffrontend-design\u002FSKILL.md",[149,154,157],{"categoryNo":150,"categoryName":151,"taxonomyType":152,"taxonomyTypeDesc":153},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":155,"categoryName":156,"taxonomyType":152,"taxonomyTypeDesc":153},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":158,"categoryName":159,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000032","设计师",[161,162,163,70,164,165,166,66,167,168,23,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,67,184],"frontend","design","frontend design","generic","ui design","react","html","components","artifacts","ui","aesthetics","Production","web components","landing","applications","quality","web","css","create","examples","with","react components","web ui","pages"]