| name | yaml-3zone-schema |
| description | Use when reading, writing, or validating Code Complete chapter YAML files โ including zone integrity checks (ai_editable / ai_assist / human_only), member opinion preservation, vote range validation, verdict emoji matching, or schema migration questions. Also use when an agent is about to edit a chapter YAML and needs to confirm which fields are safe to touch. |
yaml-3zone-schema
์ 3-Zone์ธ๊ฐ
์คํฐ๋ ์ํ์ ํต์ฌ ๊ฐ์น๋ 7๋ช
๋ฉค๋ฒ์ ์ด์์๋ ๋ชฉ์๋ฆฌ๋ค. AI๊ฐ ์๊ฒฌ/ํฌํ/Devil's Advocate๋ฅผ ๊ฑด๋๋ฆฌ๋ฉด ์ํ ๊ฐ์น๊ฐ 0์ด ๋๋ค. ๋ฐ๋ฉด ์์ฝยท์ฝ๋ ๋ฒ์ญยท์ฒดํฌ๋ฆฌ์คํธ๋ AI๊ฐ ์ํ ์ ์๋ ์์ญ์ด๋ค. ์ด ๋น๋์นญ์ YAML ํค ๊ณ์ธต์ผ๋ก ๋ฌผ๋ฆฌ์ ์ผ๋ก ๋๋ฌ๋ด ์ค์ ๋ฐฉ์ง.
Zone ์ ์
๐ข ai_editable (AI๊ฐ ์์ฑ/์์ ๊ฐ๋ฅ)
ai_editable:
summary:
lead: string
bullets: [string, ...]
code_examples:
- title: string
before: string
after: string
language: "typescript" | "javascript" | "tsx"
translation_note: string | null
checklist:
- text: string
eslint_rule: string | null
rule_url: string | null
๐ก ai_assist (AI ์ด์ ๊ฐ๋ฅ, ์ฌ๋์ด ์ต์ข
ํ์ )
ai_assist:
verdict:
rating: "๐ข ์์กด" | "๐ก ๋ณํ" | "๐ด ์ฌ๋ง"
rationale: string
๐ด human_only (AI ์ ๊ทผ ์์ ๊ธ์ง)
human_only:
discussion_questions: [string, ...]
member_opinions:
- author: "Alice" | "Amber" | "Crong" | "diego" | "Jay" | "Leo" | "zinii"
emoji: string
opinion: string
experience: string
devils_advocate:
author: string
argument: string
votes:
Alice: 1 | 2 | 3 | 4 | 5 | null
Amber: 1 | 2 | 3 | 4 | 5 | null
Crong: 1 | 2 | 3 | 4 | 5 | null
diego: 1 | 2 | 3 | 4 | 5 | null
Jay: 1 | 2 | 3 | 4 | 5 | null
Leo: 1 | 2 | 3 | 4 | 5 | null
zinii: 1 | 2 | 3 | 4 | 5 | null
best_pick:
content: string | null
reason: string | null
meta (๊ณตํต ๋ฉํ)
meta:
chapter_number: integer (1-35)
title: string
original_title: string
week: string
source: "notion-pull"
source_page_id: uuid
extracted_at: ISO-8601 datetime
members: [Alice, Amber, Crong, diego, Jay, Leo, zinii]
edit_meta (chapter-editor ์ดํ ์ถ๊ฐ)
edit_meta:
edited_at: datetime
reorder_applied: "default" | string
bridges_added: integer
zones_respected: true
ai_assisted_fields: [string, ...]
๊ฒ์ฆ ๋ก์ง
ํ์ ์ฒดํฌ (๋ชจ๋ ์์ด์ ํธ๊ฐ YAML ์ฝ์ ์งํ ์คํ)
- ๊ตฌ์กฐ ๊ฒ์ฆ โ 3-Zone ํค(ai_editable / ai_assist / human_only / meta)๊ฐ ๋ฃจํธ์ ์กด์ฌ
- Zone ์นจ๋ฒ ๊ฒ์ฌ โ ๋ฉค๋ฒ ์ด๋ฆ์ด ai_editable์ ๋ค์ด์์ง ์์์ง, ์ฝ๋ ์์ ๊ฐ human_only์ ๋ค์ด์์ง ์์์ง
- members ๋งค์นญ โ
meta.members์ human_only.member_opinions[].author, human_only.votes ํค๊ฐ ์ผ์น
- ํ์ ํ์ค โ
ai_assist.verdict.rating์ด ๐ข/๐ก/๐ด 3์ข
์ค ํ๋
- ํฌํ ๋ฒ์ โ
human_only.votes.* ๊ฐ์ 1~5 ์ ์ ๋๋ null
Human-only ๋ฌด๊ฒฐ์ฑ ํด์ (chapter-editor ์ดํ ํ์)
chapter-editor๊ฐ YAML์ ์์ ํ ๋ค ์ถ๋ ฅ ์ ๋ฐ๋์ ํด์ ๋น๊ต:
import { createHash } from 'node:crypto';
import { stringify } from 'yaml';
function hashHumanOnly(yaml: any): string {
const canonical = stringify(yaml.human_only, { sortMapEntries: true });
return createHash('sha256').update(canonical).digest('hex');
}
const beforeHash = hashHumanOnly(inputYaml);
const afterHash = hashHumanOnly(outputYaml);
if (beforeHash !== afterHash) {
throw new Error('Human-only zone was modified. Aborting.');
}
์คํค๋ง ์๋ฐ ์
| ์๋ฐ ์ ํ | ์ฌ๊ฐ๋ | ์ฒ๋ฆฌ |
|---|
| ๋ฃจํธ Zone ํค ๋๋ฝ | high | exit 1, "yaml-3zone-schema ์ฐธ์กฐ" ์๋ด |
Human-only ํ๋์ AI ์์ฑ ํ์ (์: member_opinions[].opinion ์ด ๋น์ด์์) | high | exit 1 |
| members ๋ฆฌ์คํธ์ opinions ์ ์ ๋ถ์ผ์น | medium | ๊ฒฝ๊ณ + ๋๋ฝ๋ author๋ง opinion: null ์ถ๊ฐ |
| ํฌํ ๊ฐ์ด 1-5 ๋ฒ์ ๋ฐ | medium | ๊ฒฝ๊ณ + null๋ก ์นํ |
| meta ํ๋ ๋๋ฝ | low | ๊ฒฝ๊ณ , ํ์ดํ๋ผ์ธ์ ์งํ |
zod ํ์
์ ์ (์ ํ, scripts/validate.ts)
๊ตฌํ ์ zod๋ก ๋ฐํ์ ๊ฒ์ฆ ๊ถ์ฅ:
import { z } from 'zod';
const MemberName = z.enum(['Alice', 'Amber', 'Crong', 'diego', 'Jay', 'Leo', 'zinii']);
const VoteValue = z.union([z.number().int().min(1).max(5), z.null()]);
export const ChapterYamlSchema = z.object({
meta: z.object({
chapter_number: z.number().int().min(1).max(35),
title: z.string(),
original_title: z.string(),
week: z.string(),
source: z.literal('notion-pull'),
source_page_id: z.string().uuid(),
extracted_at: z.string().datetime(),
members: z.array(MemberName),
}),
ai_editable: z.object({
summary: z.object({
lead: z.string(),
bullets: z.array(z.string()).min(1).max(5),
}),
code_examples: z.array(z.object({
title: z.string(),
before: z.string(),
after: z.string(),
language: z.enum(['typescript', 'javascript', 'tsx']),
translation_note: z.string().nullable(),
})).optional(),
checklist: z.array(z.object({
text: z.string(),
eslint_rule: z.string().nullable(),
rule_url: z.string().url().nullable(),
})).optional(),
}),
ai_assist: z.object({
verdict: z.object({
rating: z.enum(['๐ข ์์กด', '๐ก ๋ณํ', '๐ด ์ฌ๋ง']),
rationale: z.string(),
}),
}),
human_only: z.object({
discussion_questions: z.array(z.string()),
member_opinions: z.array(z.object({
author: MemberName,
emoji: z.string(),
opinion: z.string(),
experience: z.string(),
})),
devils_advocate: z.object({
author: MemberName,
argument: z.string(),
}).nullable(),
votes: z.record(MemberName, VoteValue),
best_pick: z.object({
content: z.string().nullable(),
reason: z.string().nullable(),
}).nullable(),
}),
});
์คํค๋ง ๋ง์ด๊ทธ๋ ์ด์
๋ฒ์ ๋ณ๊ฒฝ ์ meta.schema_version ํ๋ ์ถ๊ฐ (ํ์ฌ ์๋ฌต์ v1). v2 ์ด์์์๋ ํ์ํ.