[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CBW8SRGFP4X45RY57T6QNE":3,"skill-same:01M6CBW8SRGFP4X45RY57T6QNE":45},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":20,"linkOnly":21,"markdownContent":22,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":13,"repositoryHtmlUrl":27,"repositoryHomepageUrl":6,"ownerHtmlUrl":28,"ownerBlogUrl":6,"ownerDescription":29,"updatedAt":30,"install":31,"resources":44},"01M6CBW8SRGFP4X45RY57T6QNE","51-table-codegen",null,"devwinsoft\u002Fdevian","devwinsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F22570160?v=4","01M6C6RR8W05NH8NPKQNCE3Q72","maoshy","01M6C6RRDJ5TJJCZXRTY801YDH",0,"MIT","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F11-builder\u002F51-table-codegen\u002FSKILL.md",[],[18,19],"table","51 table codegen","2026-11-10 10:12:30",false,"# 51-table-codegen\n\nStatus: ACTIVE  \nAppliesTo: v10  \nSSOT: skills\u002Fdevian\u002F10-module\u002F03-ssot\u002FSKILL.md\n\n## Purpose\n\nTable generator의 **정책적 규칙**만 정의한다.\n\n구체적인 구현\u002F산출 API는 **런타임\u002F제너레이터 코드**를 정답으로 본다.\n\n---\n\n## Naming Rules (정책)\n\n### Sheet Name @ 규칙 (Hard Rule)\n\nSheet 이름이 `{TableName}@{Description}` 형식일 때:\n\n| 항목 | 값 |\n|------|-----|\n| SheetFullName | `{TableName}@{Description}` (원본) |\n| CodeTableName | `@` 앞 문자열 (`{TableName}`) |\n\n**코드 생성에 사용되는 이름은 CodeTableName만이다.**\n\n```\n예: Sheet \"Monsters@몬스터테이블\"\n→ CodeTableName = \"Monsters\"\n→ C# class = Monsters\n→ Container = TB_Monsters\n→ Runtime에서 로드 시 TB_Monsters에 insert\n```\n\n### 중복 codeTableName FAIL 규칙 (Hard Rule)\n\n**같은 XLSX 파일 내에서 동일한 codeTableName을 가진 시트가 2개 이상 있으면 빌드 FAIL.**\n\n```\n예: FAIL 케이스\n- Sheet1: \"Monsters@보스몬스터\"\n- Sheet2: \"Monsters@일반몬스터\"\n→ codeTableName이 둘 다 \"Monsters\" → FAIL\n```\n\n에러 메시지에는 XLSX 파일명과 충돌하는 시트 이름 목록이 포함되어야 한다.\n\n### Entity 클래스\u002F인터페이스\n\n- **CodeTableName을 그대로 사용**한다.\n- 예: Sheet `TestSheet` → C# `class TestSheet`, TS `interface TestSheet`\n- 예: Sheet `Monsters@몬스터테이블` → C# `class Monsters`\n\n### Container 클래스\n\n- C#: `TB_{SheetName}` (namespace 바로 아래, static class)\n- TS: `TB_{SheetName}` (최상위, static 멤버)\n- 예: Sheet `TestSheet` → `TB_TestSheet`\n\n**PrimaryKey(`key:true`)가 없는 sheet는 Container 클래스를 생성하지 않는다.**\n\n- PrimaryKey가 있는 sheet → `TB_{SheetName}` 생성\n- PrimaryKey가 없는 sheet → Container 생성 안함 (Entity만 생성)\n\n### Enum Naming (Hard Rule)\n\n- generated enum 타입명은 **대문자 SNAKE_CASE**를 사용한다.\n- generated enum 멤버명도 **대문자 SNAKE_CASE**를 사용한다.\n- 예:\n  - Good: `MISSION_TYPE`, `APP_OPEN`\n  - Bad: `MissionType`, `AppOpen`\n\n### Namespace (C#)\n\n- `Devian.Domain.{DomainKey}`를 사용한다.\n- 예: Domain `Common` → `namespace Devian.Domain.Common`\n\n---\n\n## Column Naming Convention (Hard Rule)\n\n**XLSX 컬럼명을 모든 곳에서 그대로 사용한다. 변환하지 않는다.**\n\n| 위치 | 이름 | 예시 |\n|------|------|------|\n| XLSX 컬럼명 | 그대로 | `item_id` |\n| C# 프로퍼티 | 그대로 | `item_id` |\n| TS 프로퍼티 | 그대로 | `item_id` |\n| JSON 키 (NDJSON\u002Fpb64) | 그대로 | `\"item_id\"` |\n\n```\nXLSX column: item_id      → C#: item_id,      TS: item_id,      JSON: \"item_id\"\nXLSX column: stat_type00  → C#: stat_type00,   TS: stat_type00,  JSON: \"stat_type00\"\nXLSX column: name_id      → C#: name_id,       TS: name_id,      JSON: \"name_id\"\n```\n\n---\n\n## IEntity Interface (정책)\n\n### 인터페이스 정의\n\n```csharp\n\u002F\u002F C#\npublic interface IEntity { }\npublic interface IEntityKey\u003CT> : IEntity { T GetKey(); }\n```\n\n```typescript\n\u002F\u002F TypeScript\nexport interface IEntity { }\nexport interface IEntityKey\u003CT> extends IEntity { getKey(): T; }\n```\n\n### 적용 규칙\n\n| 유형 | 예시 | 상속 | GetKey |\n|------|------|------|--------|\n| Contract class | `UserProfile` | `IEntity` | ❌ 없음 |\n| Table Entity (Key 있음) | `TestSheet` | `IEntityKey\u003CKeyType>` | ✅ 있음 |\n| Table Entity (Key 없음) | `VECTOR3` | `IEntity` | ❌ 없음 |\n\n### 생성 예시\n\n> **주의:** 프로퍼티 이름은 § Column Naming Convention 에 따라 XLSX 컬럼명 \u002F Contract JSON field name을 **그대로** 사용한다. 변환 없음.\n\n**C#:**\n```csharp\n\u002F\u002F Contract - IEntity만 상속 (contract JSON field name 그대로)\npublic sealed class UserProfile : IEntity\n{\n    public int id { get; set; }\n    \u002F\u002F GetKey 없음\n}\n\n\u002F\u002F Table with Key - IEntityKey\u003CT> 상속 (XLSX 컬럼명 그대로)\npublic sealed class TestSheet : IEntityKey\u003Cint>\n{\n    public int number { get; set; }\n    public int GetKey() => number;\n}\n\n\u002F\u002F Table without Key - IEntity만 상속 (XLSX 컬럼명 그대로)\npublic sealed class VECTOR3 : IEntity\n{\n    public float x { get; set; }\n    \u002F\u002F GetKey 없음\n}\n```\n\n**TypeScript:**\n```typescript\n\u002F\u002F Contract - IEntity만 extends (contract JSON field name 그대로)\nexport interface UserProfile extends IEntity {\n    id: number;\n    \u002F\u002F getKey 없음\n}\n\n\u002F\u002F Table with Key - IEntityKey\u003CT> extends (XLSX 컬럼명 그대로)\nexport interface TestSheet extends IEntityKey\u003Cnumber> {\n    number: number;\n    getKey(): number;\n}\n\n\u002F\u002F Table without Key - IEntity만 extends (XLSX 컬럼명 그대로)\nexport interface VECTOR3 extends IEntity {\n    x: number;\n    \u002F\u002F getKey 없음\n}\n```\n\n---\n\n## Output Files (정책)\n\nDomain 내 모든 Contract, Table Entity, Table Container는 **단일 파일에 통합** 생성된다.\n\n| 산출물 | 파일명 패턴 | 예시 | 생성 조건 |\n|--------|-------------|------|-----------|\n| C# (통합) | `{DomainKey}.g.cs` | `Common.g.cs` | 항상 |\n| TS (통합) | `{DomainKey}.g.ts` | `Common.g.ts` | 항상 |\n| NDJSON Data | `{SheetName}.json` | `TestSheet.json` | PrimaryKey 있는 sheet만 |\n| pb64 Data | `{SheetName}.asset` | `TestSheet.asset` | PrimaryKey 있는 sheet만 |\n\n> **NDJSON 저장 규약:** 내용은 NDJSON이며 확장자는 `.json` 고정. 정본: `skills\u002Fdevian\u002F80-tools\u002F11-builder\u002F53-data-ndjson\u002FSKILL.md`\n> **pb64 저장 규약:** Unity TextAsset `.asset` 형식. 정본: `skills\u002Fdevian\u002F80-tools\u002F11-builder\u002F54-data-pb64\u002FSKILL.md`\n\n---\n\n## Domain 모듈 의존성 (Hard Rule)\n\n**C# DATA Domain 모듈 의존성:**\n- `Devian.Domain.{DomainKey}.csproj`는 `..\\Devian\\Devian.csproj`만 ProjectReference 한다.\n- Common 모듈을 포함한 모든 Domain 모듈이 동일한 규칙을 따른다.\n\n**TS DATA Domain 패키지 의존성:**\n- `@devian\u002Fmodule-{domainkey}`는 `@devian\u002Fcore`만 의존한다.\n\n---\n\n## Unity Compatibility (Hard Rule)\n\nUnity 환경에서의 호환성을 위해 다음 규칙을 강제한다.\n\n**C# Domain 코드 생성 시:**\n\n1. **System.Text.Json 사용 금지**\n   - Unity는 `System.Text.Json`을 기본 제공하지 않음\n   - `using System.Text.Json;` 생성 금지\n   - `JsonDocument`, `JsonSerializer` 등 사용 금지\n\n2. **Newtonsoft.Json 사용**\n   - Unity는 `Newtonsoft.Json`을 기본 제공 (com.unity.nuget.newtonsoft-json)\n   - `using Newtonsoft.Json;` 사용\n   - `JsonConvert.DeserializeObject\u003CT>()` 사용\n\n**UPM package.json 종속성:**\n\n생성 코드가 Newtonsoft.Json을 사용하므로, 상위 패키지(`com.devian.foundation`)의 dependencies에 종속성이 포함되어야 한다.\n\n> **Note**: 모든 Domain 샘플(CommonPackage, SoundPackage, GamePackage 등)은 `com.devian.foundation` 패키지의 Samples~\u002F 하위에 제공되며, 개별 package.json을 가지지 않는다.\n\n- `com.devian.foundation\u002Fpackage.json`의 dependencies에 `com.unity.nuget.newtonsoft-json` 포함\n- 코드와 종속성 일치 보장\n\n### 파일 구조 (C#)\n\n```csharp\nnamespace Devian.Domain.{DomainKey}\n{\n    \u002F\u002F Contracts (enum, class : IEntity)\n    \u002F\u002F Table Entities (class : IEntity 또는 IEntityKey\u003CT>)\n    \u002F\u002F Table Containers (static class TB_*)\n}\n```\n\n### 파일 구조 (TS)\n\n```typescript\nimport { IEntity, IEntityKey } from '@devian\u002Fcore';\n\n\u002F\u002F Contracts (enum, interface extends IEntity)\n\u002F\u002F Table Interfaces (extends IEntity 또는 IEntityKey\u003CT>)\n\u002F\u002F Table Containers (export class TB_*)\n```\n\n---\n\n## Container API (정책)\n\n> Container(`TB_{SheetName}`)는 PrimaryKey가 있는 sheet에 대해서만 생성된다.\n\n### C# Container (static class TB_{SheetName})\n\n| 메서드 | 설명 |\n|--------|------|\n| `Count` | Row 개수 |\n| `Clear()` | 캐시 비우기 |\n| `GetAll()` | 전체 Row 반환 |\n| `Get(key)` | Key로 조회, nullable 반환 (Key 있는 경우) |\n| `TryGet(key, out row)` | Key로 조회, out 패턴 (Key 있는 경우) |\n| `LoadFromJson(json)` | JSON 배열 로드 |\n| `LoadFromNdjson(ndjson)` | NDJSON 로드 |\n| `_AfterLoad()` | AfterLoad 훅 (internal, 아래 섹션 참조) |\n\n**Group API (group:true 컬럼 있을 때):**\n\n| 메서드 | 설명 |\n|--------|------|\n| `GetGroupKeys()` | 중복 제거된 groupKey 리스트 |\n| `GetByGroup(groupKey)` | groupKey에 속한 row 리스트 |\n| `TryGetGroupPrimaryKey(groupKey, out key)` | groupKey의 대표 PK(min PK) 반환 |\n| `TryGetGroupKeyByKey(key, out groupKey)` | PK로 groupKey 역조회 |\n\n### TS Container (export class TB_{SheetName})\n\n| 메서드 | 설명 |\n|--------|------|\n| `count` | Row 개수 |\n| `clear()` | 캐시 비우기 |\n| `getAll()` | 전체 Row 반환 |\n| `get(key)` | Key로 조회 (Key 있는 경우) |\n| `has(key)` | Key 존재 여부 (Key 있는 경우) |\n| `loadFromJson(json)` | NDJSON 로드 |\n| `saveToJson()` | NDJSON 저장 |\n\n---\n\n## AfterLoad Hook Contract (Hard Rule)\n\n빌더는 TableManager가 TB insert를 수행한 직후, 각 TB 컨테이너에 대해 AfterLoad 훅을 호출한다.\n\n- 빌더는 `TB_*` insert 직후 반드시 `TB_*._AfterLoad()`를 호출해야 한다.\n- `TB_*` 컨테이너는 항상 다음을 제공해야 한다:\n  - `internal static void _AfterLoad()` (항상 존재)\n  - `static partial void _OnAfterLoad()` (옵션 훅, 구현 없으면 no-op)\n- `_AfterLoad()`는 내부에서 `_OnAfterLoad()`를 호출한다.\n\n이 훅은 \"인덱스 빌드\u002F캐시 초기화\u002F도메인 내부 후처리\"를 **결정적으로** 수행하기 위한 표준 진입점이다.\n\n---\n\n## Implementation is Code Truth\n\n아래 항목은 SKILL이 아니라 **코드가 정답**이다.\n\n- 생성되는 C#\u002FTS 코드의 구체적 구조, 메서드, 프로퍼티\n- Entity\u002FContainer의 정확한 인터페이스 시그니처\n- JSON 파싱\u002F직렬화 구현\n- NDJSON 필드 순서, 값 변환 규칙\n\nSKILL은 위 내용을 단정해서는 안 된다.\n\n---\n\n## Reference\n\n- Policy SSOT: `skills\u002Fdevian\u002F10-module\u002F03-ssot\u002FSKILL.md`\n- 입력 규칙: `skills\u002Fdevian\u002F80-tools\u002F11-builder\u002F32-table-authoring\u002FSKILL.md`\n- 동작 정본: 런타임\u002F제너레이터 코드\n",{},"main","8cb5bd877a3a7004867d2f55697cb9e4ff2f1734","skills\u002Fdevian\u002F80-tools\u002F11-builder\u002F51-table-codegen","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft","Kim Hyong Joon","2026-04-12 18:44:10",{"source":7,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add devwinsoft\u002Fdevian --skill 51-table-codegen",{"tool":37,"command":38},"bunx","bunx skills add devwinsoft\u002Fdevian --skill 51-table-codegen",{"tool":40,"command":41},"pnpm","pnpm dlx skills add devwinsoft\u002Fdevian --skill 51-table-codegen","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F11-builder\u002F51-table-codegen\u002FSKILL.md",true,[],[46,52,60,68,77,84],{"skillNo":47,"name":48,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":49,"categories":50,"keywords":51,"publishedAt":20},"01M6CBX3K2JJV38ANHVGCAT8A0","devian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002FSKILL.md",[],[48],{"skillNo":53,"name":54,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":20},"01M6CBX210BFMM8AMN770J9WY8","90-project-archive","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F90-project-archive\u002FSKILL.md",[],[58,59],"archive","90 project archive",{"skillNo":61,"name":62,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":20},"01M6CBWZSGRR9R3YJZB3177667","30-env-management","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F30-env-management\u002FSKILL.md",[],[66,67],"management","30 env management",{"skillNo":69,"name":70,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":20},"01M6CBWYD3TGWBQV7V35551THF","19-page-push-send","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F19-page-push-send\u002FSKILL.md",[],[74,75,76],"push","page","19 page push send",{"skillNo":78,"name":79,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":82,"publishedAt":20},"01M6CBWWRQ05A7604GHJKTPJTF","18-page-savedata","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F18-page-savedata\u002FSKILL.md",[],[75,83],"18 page savedata",{"skillNo":85,"name":86,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":89,"publishedAt":20},"01M6CBWVH4S2VF1V7B2GBTESCT","17-page-deobfuscate","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F17-page-deobfuscate\u002FSKILL.md",[],[75,90],"17 page deobfuscate"]