seokang-sk 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.MD CHANGED
@@ -50,6 +50,8 @@ sk setup --configure-notion
50
50
 
51
51
  - Notion 문서 기록이 `invalid_grant`, `unauthorized`, `forbidden` 같은 인증 오류로 실패하면 아래 명령으로 MCP 인증을 다시 연결한다.
52
52
  - `Tool ... not found`처럼 특정 Notion tool만 빠진 경우는 로그인 문제가 아닐 수 있으니, exact error를 남기고 남아 있는 Notion MCP tool로 우회 가능한지 먼저 확인한다.
53
+ - `notion-query-data-sources` / `query_data_sources`는 더 이상 Notion 기록의 1차 탐색 경로로 두지 않는다.
54
+ - local config에 data source id가 있으면 `fetch` / `create_pages` / `update_page`를 먼저 쓰고, lookup이 필요할 때만 `search -> fetch` 순서로 보완한다.
53
55
 
54
56
  ```bash
55
57
  codex mcp login notion
@@ -61,6 +63,11 @@ codex mcp login notion
61
63
  - `docs/sk-cli-guide.md`
62
64
  - `docs/sk-notion-workspace.md`
63
65
 
66
+ 자동화 기준 문서:
67
+
68
+ - `docs/automations/server-log-check.md`
69
+ - `docs/automations/daily-thread-retrospective.md`
70
+
64
71
  이 저장소는 SeoKang 개인 개발 워크플로를 Codex Skill과 공용 Codex 데스크톱 설정 원본으로 관리하기 위한 저장소다.
65
72
 
66
73
  기능 개발, 버그 수정, 리팩토링, 기획, 배포뿐 아니라 문서화와 Notion 기록까지 포함한 작업 방식을 정리한다.
@@ -85,6 +92,8 @@ codex mcp login notion
85
92
  | |- config/ # ~/.codex/config.toml 공용 원본
86
93
  | |- agents/ # ~/.codex/agents 공용 원본
87
94
  | |- rules/ # ~/.codex/rules 공용 원본
95
+ |- templates/
96
+ | |- agent/ # 프로젝트용 AGENT.MD bootstrap 템플릿
88
97
  |- scripts/ # 공용 설치 / 보조 스크립트
89
98
  ```
90
99
 
@@ -94,6 +103,7 @@ codex mcp login notion
94
103
 
95
104
  - skill 동작 변경: `skills/sk-*/SKILL.md`
96
105
  - 설치/런타임 설정 변경: `codex-config/**`, `scripts/**`
106
+ - 프로젝트용 AGENT.MD 기본 틀 변경: `templates/agent/*.md`
97
107
  - 사용법/운영 가이드 변경: `docs/**`, `README.MD`
98
108
 
99
109
  ---
@@ -108,7 +118,8 @@ codex mcp login notion
108
118
  ## 실행 방식
109
119
 
110
120
  - `sk`는 별도 앱 UI가 아니라 CLI 명령으로만 동작한다.
111
- - `sk setup`이 sync하는 대상은 `~/.codex`이고, `sk init`은 현재 프로젝트의 `.sk/` 상태를 준비한다.
121
+ - `sk setup`이 sync하는 대상은 `~/.codex`이고, `sk init`은 현재 프로젝트의 `.sk/` 상태를 준비한다. 필요하면 `--agent-md`로 프로젝트 루트 `AGENT.MD`도 함께 만든다.
122
+ - 옵션과 예시는 `sk help`, `sk help <command>`, `<command> --help`로 바로 확인한다. 예: `sk help init`, `sk init --help`
112
123
  - OMX가 `omx setup`처럼 짧아 보이는 이유는 npm 전역 CLI로 설치되기 때문이다.
113
124
  - 지금 `sk`도 최초 1회만 `npm link`로 등록하면 이후에는 `sk setup`, `sk init`만 기억하면 된다.
114
125
  - 실행 전제:
@@ -284,7 +295,7 @@ sk search "reviewer"
284
295
 
285
296
  상태 디렉터리:
286
297
 
287
- - `sk init`은 현재 작업 디렉터리 아래 `.sk/`를 만든다.
298
+ - `sk init`은 현재 작업 디렉터리 아래 `.sk/`를 만들고, `--agent-md`를 붙이면 프로젝트 루트 `AGENT.MD`도 함께 만든다.
288
299
  - `sk setup`은 관리용 저장소 `~/.agents/skills/.sk/`를 setup 상태/백업 기준 경로로 사용한다.
289
300
  - `.sk/setup-scope.json`: 마지막 init/setup 기록
290
301
  - `.sk/project-memory.json`: 다음 세션이 읽을 구조화 메모
@@ -374,7 +385,8 @@ LOCAL_JAR_PATH=./build/libs/km-admin-api.jar
374
385
  - `--dry-run`, `--verbose`, `--configure-notion`, `--skip-notion`을 지원한다.
375
386
  - `init`
376
387
  - 현재 프로젝트 루트의 `.sk/` 상태 파일을 준비한다.
377
- - `--cwd <path>`, `--dry-run`, `--force`, `--verbose`를 지원한다.
388
+ - `--agent-md`를 붙이면 프로젝트 루트에 `AGENT.MD` 초안도 함께 만든다.
389
+ - `--cwd <path>`, `--agent-md`, `--dry-run`, `--force`, `--verbose`를 지원한다.
378
390
  - `check`
379
391
  - 현재 설치가 source와 일치하는지 확인한다.
380
392
  - Notion workspace 설정 파일 존재 여부와 필수 필드도 함께 확인한다.
@@ -407,6 +419,25 @@ LOCAL_JAR_PATH=./build/libs/km-admin-api.jar
407
419
  - `sk-feature`, `sk-bugfix`, `sk-db`는 shared subagent가 설치되어 있으면 사용자가 따로 지시하지 않아도 내부적으로 dispatch할 수 있도록 설계한다.
408
420
  - 기본 분석용 subagent는 `Notion MCP`와 `Playwright`를 직접 사용하지 않고, 이런 tool 작업은 메인 agent가 맡는다.
409
421
 
422
+ ## 프로젝트 AGENT.MD
423
+
424
+ - `AGENT.MD`는 프로젝트별 고정 규칙 문서다.
425
+ - `README`는 사람용 개요/설치 문서, `.sk/*`는 세션 메모, `AGENT.MD`는 agent가 먼저 읽어야 할 프로젝트 계약서로 분리해서 쓴다.
426
+ - 성능 차이가 크지 않은 한 기본 초안은 한글로 생성하고, 명령어/식별자/경로만 원문을 유지한다.
427
+ - 새 프로젝트에서 바로 만들려면 아래처럼 시작한다.
428
+
429
+ ```bash
430
+ sk init --agent-md
431
+ ```
432
+
433
+ - 다른 프로젝트를 미리 준비할 때는 아래처럼 쓴다.
434
+
435
+ ```bash
436
+ sk init --cwd /path/to/project --agent-md
437
+ ```
438
+
439
+ - 기본 템플릿 원본은 `templates/agent/base.md`, `templates/agent/frontend.md`, `templates/agent/backend.md`, `templates/agent/codex.md`다.
440
+
410
441
  ## 작성자
411
442
 
412
443
  SeoKang 작성
@@ -58,7 +58,8 @@ sk setup
58
58
  이 명령은 크게 2가지를 한다.
59
59
 
60
60
  - `sk setup`은 `~/.agents/skills`를 준비하고 `~/.codex`에 공용 설정을 반영한다.
61
- - `sk init`은 현재 프로젝트의 `.sk/`에 작업 상태와 메모를 준비한다.
61
+ - `sk init`은 현재 프로젝트의 `.sk/`에 작업 상태와 메모를 준비한다. 필요하면 `--agent-md`로 프로젝트 루트 `AGENT.MD`도 함께 만든다.
62
+ - 각 명령의 옵션과 예시는 `sk help`, `sk help <command>`, `<command> --help`로 바로 확인할 수 있다.
62
63
 
63
64
  즉, `sk`는 단순 설치 스크립트가 아니라 "설치 + 점검 + 상태관리"를 묶은 진입점이다.
64
65
 
@@ -283,6 +284,17 @@ sk init
283
284
  - `.sk/project-memory.json`
284
285
  - `.sk/notepad.md`
285
286
 
287
+ 프로젝트 루트 `AGENT.MD` 초안도 같이 만들고 싶으면 아래처럼 실행한다.
288
+
289
+ ```bash
290
+ cd /path/to/project
291
+ sk init --agent-md
292
+ ```
293
+
294
+ 이 경우 아래 파일까지 함께 준비된다.
295
+
296
+ - `AGENT.MD`
297
+
286
298
  ## 5. 상황별로 어떻게 쓰면 되는가
287
299
 
288
300
  ### 상황 A. 새 PC 또는 새 세션에서 기본 셋업 반영
@@ -290,7 +302,7 @@ sk init
290
302
  ```bash
291
303
  sk setup
292
304
  cd /path/to/project
293
- sk init
305
+ sk init --agent-md
294
306
  sk check
295
307
  sk status
296
308
  ```
@@ -300,11 +312,11 @@ sk status
300
312
  ### 상황 B. 다른 프로젝트의 `.sk`를 미리 만들고 싶다
301
313
 
302
314
  ```bash
303
- sk init --cwd /path/to/project
315
+ sk init --cwd /path/to/project --agent-md
304
316
  sk status --cwd /path/to/project
305
317
  ```
306
318
 
307
- 이 경우 해당 프로젝트 밑 `.sk/`만 준비한다.
319
+ 이 경우 해당 프로젝트 밑 `.sk/`와 `AGENT.MD` 초안을 함께 준비한다.
308
320
 
309
321
  ### 상황 C. 이전 작업 맥락을 다시 보고 싶다
310
322
 
@@ -319,7 +331,7 @@ sk search "reviewer"
319
331
 
320
332
  ## 6. `.sk/`는 무엇인가
321
333
 
322
- `sk init`을 실행하면 현재 작업 디렉터리 아래 `.sk/`를 만든다.
334
+ `sk init`을 실행하면 현재 작업 디렉터리 아래 `.sk/`를 만들고, `--agent-md`를 붙이면 프로젝트 루트 `AGENT.MD`도 함께 만든다.
323
335
 
324
336
  `sk setup`은 설치/백업 기준 상태를 `~/.agents/skills/.sk/`에 둔다.
325
337
 
@@ -329,12 +341,16 @@ sk search "reviewer"
329
341
  다음 세션이 읽을 구조화 메모
330
342
  - `.sk/notepad.md`
331
343
  우선순위 메모와 작업 메모
344
+ - `AGENT.MD`
345
+ 프로젝트별 고정 규칙과 검증/운영 계약
332
346
  - `.sk/backups/setup/**`
333
347
  setup 중 덮어쓰기 전에 남긴 backup
334
348
  - `~/.codex/sk/notion/workspace.json`
335
349
  사용자별 Notion workspace 설정
336
350
 
337
351
  중요한 점은 `.sk/`가 "이 프로젝트에서 다음 세션이 바로 이어서 일하게 만드는 상태 폴더"라는 것이다. 그래서 프로젝트마다 별도로 두는 것이 맞다.
352
+ `AGENT.MD`는 그 위에 있는 장기 규칙 문서라서, `README`와 `.sk` 사이의 빈 구간을 메우는 역할로 본다.
353
+ 성능 차이가 크지 않은 한 기본 초안은 한글로 생성하고, 명령어/식별자/경로만 원문을 유지한다.
338
354
 
339
355
  ## 7. 지금 당장 외울 것은 무엇인가
340
356
 
@@ -384,7 +400,7 @@ sk search "keyword"
384
400
  ## 10. 가장 짧은 요약
385
401
 
386
402
  - `sk setup` = 설치 + 공용 Codex 설정 반영 + check
387
- - `sk init` = 현재 프로젝트 `.sk` 준비
403
+ - `sk init` = 현재 프로젝트 `.sk` 준비 (`--agent-md`면 `AGENT.MD`도 함께 생성)
388
404
  - `sk check` = 설치 결과 점검
389
405
  - `sk status` = 지금 상태 확인
390
406
  - `sk memory show`, `sk search` = 이전 맥락 복원
@@ -58,6 +58,9 @@ codex mcp login notion
58
58
  - public skill 본문에 개인 Notion URL, DB id, collection id를 직접 적지 않는다.
59
59
  - Notion 작업 전에는 먼저 local config 파일 존재 여부와 `configured` 상태를 확인한다.
60
60
  - local config가 없거나 `configured=false`면 Notion 기록 시도를 생략하고 Markdown fallback을 기본 출력으로 사용한다.
61
- - `Tool ... not found`처럼 특정 Notion MCP tool만 빠진 경우는 Notion 전체 장애로 단정하지 않는다. local config의 data source id와 project/history 위치가 있으면 `fetch`/`search`/`create_pages`/`update_page` 같은 생존 tool로 먼저 우회 시도한다.
61
+ - `notion-query-data-sources` / `query_data_sources`는 optional lookup tool로 취급한다. 기본 경로로 먼저 호출하지 않는다.
62
+ - local config의 data source id나 target page id가 있으면 `fetch` / `create_pages` / `update_page`를 먼저 사용한다.
63
+ - lookup이 추가로 필요할 때만 `search -> fetch -> create_pages/update_page` 순서로 진행한다.
64
+ - `Tool ... not found`처럼 특정 Notion MCP tool만 빠진 경우는 Notion 전체 장애로 단정하지 않는다. 생존 tool로 우회 가능하면 그 경로를 먼저 사용하고, 같은 missing tool을 같은 턴에 반복 재시도하지 않는다.
62
65
  - fallback Markdown를 나중에 Notion으로 옮길 때는 `Projects > History`의 `Date`를 이관 완료 시점의 로컬 datetime으로 다시 저장하고, `date:Date:is_datetime=1`을 유지한 뒤에만 fallback 파일을 제거한다.
63
66
  - 사용자가 이후 Notion 기록도 원하면 그때 `sk setup --configure-notion`을 안내하고, Notion MCP 실패 여부와 구분해서 보고한다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seokang-sk",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "SeoKang Codex 공용 설정과 스킬을 설치하는 CLI",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/Seokang/seokang-codex-setting",
@@ -26,7 +26,8 @@
26
26
  "docs/sk-cli-guide.md",
27
27
  "docs/sk-setup-architecture.md",
28
28
  "docs/sk-notion-workspace.md",
29
- "scripts/sk.mjs"
29
+ "scripts/sk.mjs",
30
+ "templates/agent"
30
31
  ],
31
32
  "publishConfig": {
32
33
  "access": "public"
package/scripts/sk.mjs CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  writeFile,
12
12
  } from 'node:fs/promises';
13
13
  import { homedir } from 'node:os';
14
- import { dirname, join, relative, resolve } from 'node:path';
14
+ import { basename, dirname, join, relative, resolve } from 'node:path';
15
15
  import { createInterface } from 'node:readline';
16
16
  import { fileURLToPath } from 'node:url';
17
17
 
@@ -22,10 +22,12 @@ const DEFAULT_SCOPE = 'user';
22
22
  const STATE_DIRNAME = '.sk';
23
23
  const BACKUP_SEGMENT = join('backups', 'setup');
24
24
  const SESSION_FILE_RE = /^rollout-.*\.jsonl$/;
25
+ const AGENT_MD_FILE = 'AGENT.MD';
25
26
  const PROJECT_MEMORY_FILE = 'project-memory.json';
26
27
  const NOTEPAD_FILE = 'notepad.md';
27
28
  const NOTION_CONFIG_SEGMENTS = ['sk', 'notion'];
28
29
  const NOTION_CONFIG_FILE = 'workspace.json';
30
+ const AGENT_TEMPLATE_SEGMENTS = ['templates', 'agent'];
29
31
  const GUIDE_RELATIVE_PATH = join('docs', 'sk-cli-guide.md');
30
32
  const DEFAULT_BOOTSTRAP_REPO = 'https://github.com/Seokang/seokang-codex-setting.git';
31
33
  const CANONICAL_SKILLS_SEGMENTS = ['.agents', 'skills'];
@@ -583,6 +585,14 @@ function notepadPath(cwd = process.cwd()) {
583
585
  return join(stateRoot(cwd), NOTEPAD_FILE);
584
586
  }
585
587
 
588
+ function agentMdPath(cwd = process.cwd()) {
589
+ return join(cwd, AGENT_MD_FILE);
590
+ }
591
+
592
+ function agentTemplatePath(name) {
593
+ return join(REPO_ROOT, ...AGENT_TEMPLATE_SEGMENTS, name);
594
+ }
595
+
586
596
  function notionConfigPathForHome(codexHomeDir) {
587
597
  return join(codexHomeDir, ...NOTION_CONFIG_SEGMENTS, NOTION_CONFIG_FILE);
588
598
  }
@@ -605,54 +615,220 @@ function setupBackupRoot(cwd = process.cwd()) {
605
615
  return join(stateRoot(cwd), BACKUP_SEGMENT, stamp);
606
616
  }
607
617
 
608
- function printUsage() {
618
+ function formatOptionLines(options) {
619
+ if (options.length === 0) return [' (옵션 없음)'];
620
+
621
+ const width = options.reduce((max, option) => Math.max(max, option.flag.length), 0);
622
+ return options.map((option) => ` ${option.flag.padEnd(width)} ${option.description}`);
623
+ }
624
+
625
+ function generalHelpLines() {
609
626
  const guidePath = join(REPO_ROOT, GUIDE_RELATIVE_PATH);
610
- console.log(
611
- [
612
- 'sk - SeoKang Codex 설정 도우미',
613
- '',
614
- ' 빠른 시작',
615
- ' 1. "seokang-sk"를 전역 설치했다면 먼저 "sk setup"을 1회 실행합니다.',
616
- ' 2. 저장소 자체를 개발 중이면 대신 "npm link"를 1회 실행합니다.',
617
- ' 3. 프로젝트 루트로 이동해 "sk init"으로 .sk 상태를 준비합니다.',
618
- ' 4. "sk check"로 설치 상태를 점검합니다.',
619
- ' 5. "sk status"로 현재 프로젝트 .sk 상태와 최근 백업을 확인합니다.',
620
- '',
621
- '🧰 자주 쓰는 명령',
622
- ' sk setup ~/.agents/skills 준비 + ~/.codex 동기화 + check 실행',
623
- ' sk init 현재 프로젝트의 .sk 상태 파일 초기화',
624
- ' sk check 설치 결과가 공용 원본과 같은지 점검',
625
- ' sk status 현재 프로젝트 .sk 상태와 최근 백업 확인',
626
- ' sk memory show 저장된 프로젝트 메모와 공용 메모 확인',
627
- ' sk search "<query>" 로컬 Codex 세션 기록 검색',
628
- '',
629
- '📝 사용법',
630
- ' sk setup [--dir <path>] [--cwd <path>] [--repo <url>] [--dry-run] [--verbose] [--configure-notion] [--configure-db-safe-read] [--track <name>] [--skip-notion]',
631
- ' sk init [--cwd <path>] [--dry-run] [--force] [--verbose]',
632
- ' sk check [--cwd <path>] [--json]',
633
- ' sk status [--cwd <path>] [--json]',
634
- ' sk memory show [--cwd <path>] [--json]',
635
- ' sk memory init [--cwd <path>] [--force]',
636
- ' sk memory add-note [--cwd <path>] --category <name> <content>',
637
- ' sk memory add-directive [--cwd <path>] [--priority high|normal] [--context <text>] <content>',
638
- ' sk search <query> [--cwd <path>] [--limit N] [--since 7d] [--project current] [--case-sensitive] [--json]',
627
+ const commands = [
628
+ { name: 'sk setup', description: '공용 원본을 ~/.codex에 동기화하고 check까지 실행' },
629
+ { name: 'sk init', description: '현재 프로젝트의 .sk 상태 파일 준비' },
630
+ { name: 'sk check', description: '설치 결과와 공용 원본의 차이 점검' },
631
+ { name: 'sk status', description: '현재 프로젝트 상태와 최근 백업 확인' },
632
+ { name: 'sk memory show', description: '저장된 프로젝트 메모와 메모장 확인' },
633
+ { name: 'sk search "<query>"', description: '로컬 Codex 세션 기록 검색' },
634
+ ];
635
+ const commandWidth = commands.reduce((max, entry) => Math.max(max, entry.name.length), 0);
636
+
637
+ return [
638
+ 'sk - SeoKang Codex 설정 도우미',
639
+ '',
640
+ ' 빠른 시작',
641
+ ' 1. "seokang-sk"를 전역 설치했다면 먼저 "sk setup"을 1회 실행합니다.',
642
+ ' 2. 저장소 자체를 개발 중이면 대신 "npm link"를 1회 실행합니다.',
643
+ ' 3. 프로젝트 루트에서 "sk init --agent-md"로 .sk와 AGENT.MD 초안을 함께 준비합니다.',
644
+ ' 4. "sk check" 설치 상태를 점검합니다.',
645
+ ' 5. "sk status"로 현재 프로젝트 상태를 확인합니다.',
646
+ '',
647
+ '🧰 주요 명령',
648
+ ...commands.map((entry) => ` ${entry.name.padEnd(commandWidth)} ${entry.description}`),
649
+ '',
650
+ '📝 상세 사용법',
651
+ ' sk help <command> 또는 sk <command> --help 로 명령어별 옵션과 예시를 확인합니다.',
652
+ ' 예: sk help init',
653
+ '',
654
+ '💡 참고',
655
+ ' - 배포된 npm 패키지는 설치 진입점과 디스패처 역할만 합니다.',
656
+ ` - setup은 수정 가능한 원본 저장소를 ${canonicalRepoRoot()} 경로에 준비합니다.`,
657
+ ' - init은 현재 프로젝트 루트에 .sk 상태 파일을 만들고, --agent-md를 붙이면 AGENT.MD 초안도 함께 생성합니다.',
658
+ ' - 다른 프로젝트를 대상으로 하려면 cd 대신 init/status/memory에 --cwd <path>를 사용할 수 있습니다.',
659
+ ' - Notion 기록이 invalid_grant/unauthorized/forbidden 같은 인증 오류로 실패하면 "codex mcp login notion"으로 MCP 인증을 다시 연결하세요.',
660
+ ` - 자세한 가이드: ${guidePath}`,
661
+ ];
662
+ }
663
+
664
+ function commandHelpLines(command) {
665
+ const helpByCommand = {
666
+ setup: {
667
+ title: 'sk setup - 공용 Codex 설정 초기화',
668
+ usage: 'sk setup [--dir <path>] [--cwd <path>] [--repo <url>] [--dry-run] [--verbose] [--configure-notion] [--configure-db-safe-read] [--track <name>] [--skip-notion]',
669
+ summary: [
670
+ '설치 진입점과 동기화 명령입니다.',
671
+ '관리용 skills 저장소를 준비하고, config / rules / shared agents를 ~/.codex에 반영한 뒤 check까지 실행합니다.',
672
+ ],
673
+ options: [
674
+ { flag: '--dir <path>', description: '관리용 skills 저장소를 둘 경로 지정' },
675
+ { flag: '--cwd <path>', description: '상태/백업 기준 프로젝트 경로 지정' },
676
+ { flag: '--repo <url>', description: '초기화에 사용할 원격 저장소 URL 지정' },
677
+ { flag: '--configure-notion', description: 'Notion workspace 설정 파일도 함께 준비' },
678
+ { flag: '--configure-db-safe-read', description: '현재 track 기준 DB login-path alias 설정' },
679
+ { flag: '--track <name>', description: 'DB safe-read 설정 시 track을 수동으로 지정' },
680
+ { flag: '--skip-notion', description: 'Notion 설정 파일 생성을 생략' },
681
+ { flag: '--dry-run', description: '실제 파일을 쓰지 않고 변경 결과만 확인' },
682
+ { flag: '--verbose', description: '동기화/백업 경로를 자세히 출력' },
683
+ ],
684
+ examples: [
685
+ 'sk setup',
686
+ 'sk setup --configure-notion',
687
+ 'sk setup --configure-db-safe-read --track seokang-skill',
688
+ ],
689
+ },
690
+ init: {
691
+ title: 'sk init - 현재 프로젝트 초기화',
692
+ usage: 'sk init [--cwd <path>] [--agent-md] [--dry-run] [--force] [--verbose]',
693
+ summary: [
694
+ '현재 프로젝트의 .sk 상태 파일을 준비합니다.',
695
+ '--agent-md를 붙이면 프로젝트 루트에 AGENT.MD 초안도 함께 생성합니다.',
696
+ ],
697
+ options: [
698
+ { flag: '--cwd <path>', description: '다른 프로젝트 경로를 대상으로 실행' },
699
+ { flag: '--agent-md', description: '프로젝트 루트 AGENT.MD 초안을 함께 생성' },
700
+ { flag: '--dry-run', description: '실제 파일을 만들지 않고 결과만 미리 확인' },
701
+ { flag: '--force', description: '이미 있는 .sk / AGENT.MD도 기본 템플릿으로 다시 생성' },
702
+ { flag: '--verbose', description: '생성한 파일 경로를 자세히 출력' },
703
+ ],
704
+ examples: [
705
+ 'sk init',
706
+ 'sk init --agent-md',
707
+ 'sk init --cwd /path/to/project --agent-md --dry-run',
708
+ ],
709
+ generatedFiles: [
710
+ '.sk/setup-scope.json',
711
+ '.sk/project-memory.json',
712
+ '.sk/notepad.md',
713
+ 'AGENT.MD (--agent-md를 붙였을 때만 생성)',
714
+ ],
715
+ },
716
+ check: {
717
+ title: 'sk check - 설치 차이 점검',
718
+ usage: 'sk check [--cwd <path>] [--json]',
719
+ summary: [
720
+ '공용 원본과 현재 설치 상태가 같은지 확인합니다.',
721
+ 'Notion 설정, DB safe-read alias, 상태 파일 존재 여부도 함께 점검합니다.',
722
+ ],
723
+ options: [
724
+ { flag: '--cwd <path>', description: '점검 기준 프로젝트 경로 지정' },
725
+ { flag: '--json', description: '점검 결과를 JSON으로 출력' },
726
+ ],
727
+ examples: [
728
+ 'sk check',
729
+ 'sk check --cwd /path/to/project',
730
+ 'sk check --json',
731
+ ],
732
+ },
733
+ status: {
734
+ title: 'sk status - 현재 프로젝트 상태 보기',
735
+ usage: 'sk status [--cwd <path>] [--json]',
736
+ summary: [
737
+ '.sk 상태, AGENT.MD 존재 여부, Notion 설정 상태, 최근 백업, check 결과를 함께 보여줍니다.',
738
+ ],
739
+ options: [
740
+ { flag: '--cwd <path>', description: '상태를 볼 프로젝트 경로 지정' },
741
+ { flag: '--json', description: '상태 결과를 JSON으로 출력' },
742
+ ],
743
+ examples: [
744
+ 'sk status',
745
+ 'sk status --cwd /path/to/project',
746
+ 'sk status --json',
747
+ ],
748
+ },
749
+ memory: {
750
+ title: 'sk memory - 프로젝트 메모 관리',
751
+ usage: 'sk memory <show|init|add-note|add-directive> [options]',
752
+ summary: [
753
+ '.sk/project-memory.json 과 .sk/notepad.md 를 읽고 갱신하는 보조 명령입니다.',
754
+ ],
755
+ options: [
756
+ { flag: 'show', description: '현재 프로젝트 메모와 메모장 경로/상태 출력' },
757
+ { flag: 'init', description: '메모 파일을 기본 템플릿으로 준비' },
758
+ { flag: 'add-note', description: '--category와 함께 일반 메모 추가' },
759
+ { flag: 'add-directive', description: '--priority, --context와 함께 지시문 추가' },
760
+ ],
761
+ examples: [
762
+ 'sk memory show',
763
+ 'sk memory init --force',
764
+ 'sk memory add-note --category decision "AGENT.MD를 기본 문서로 유지"',
765
+ ],
766
+ },
767
+ search: {
768
+ title: 'sk search - 로컬 Codex 세션 기록 검색',
769
+ usage: 'sk search <query> [--cwd <path>] [--limit N] [--since 7d] [--project current] [--case-sensitive] [--json]',
770
+ summary: [
771
+ '로컬 transcript에서 예전 작업 맥락을 찾습니다.',
772
+ ],
773
+ options: [
774
+ { flag: '--cwd <path>', description: 'current project 필터 기준 경로 지정' },
775
+ { flag: '--limit N', description: '반환할 최대 결과 수 지정' },
776
+ { flag: '--since 7d', description: '최근 기간 기준으로 검색 범위 제한' },
777
+ { flag: '--project current', description: '현재 프로젝트 cwd 기준으로 결과 필터' },
778
+ { flag: '--case-sensitive', description: '대소문자 구분 검색' },
779
+ { flag: '--json', description: '검색 결과를 JSON으로 출력' },
780
+ ],
781
+ examples: [
782
+ 'sk search reviewer',
783
+ 'sk search "AGENT.MD" --since 30d',
784
+ 'sk search deploy --project current --limit 20',
785
+ ],
786
+ },
787
+ };
788
+
789
+ const help = helpByCommand[command];
790
+ if (!help) {
791
+ return [
792
+ `알 수 없는 도움말 대상입니다: ${command}`,
639
793
  '',
640
- '💡 참고',
641
- ' - 배포된 npm 패키지는 설치 진입점과 디스패처 역할만 합니다.',
642
- ` - setup은 수정 가능한 원본 저장소를 ${canonicalRepoRoot()} 경로에 준비합니다.`,
643
- ' - setup은 로컬 원본 저장소를 기준으로 config, rules, 공용 agent를 동기화하고 check까지 실행합니다.',
644
- ' - setup에 --configure-db-safe-read를 붙이면 현재 프로젝트(track) 기준 DB login-path alias를 sk 안에서 맞출 수 있습니다.',
645
- ' - init은 현재 프로젝트 루트에 .sk 상태 파일만 준비합니다.',
646
- ' - setup은 ~/.codex/sk/notion/workspace.json 아래에 Notion 설정 파일도 만들 수 있습니다.',
647
- ` - 프로젝트 상태는 ${STATE_DIRNAME}/ 아래에 저장됩니다.`,
648
- ` - 프로젝트 메모리는 ${STATE_DIRNAME}/${PROJECT_MEMORY_FILE} 에 저장됩니다.`,
649
- ` - 공용 메모는 ${STATE_DIRNAME}/${NOTEPAD_FILE} 에 저장됩니다.`,
650
- ' - 다른 프로젝트를 대상으로 하려면 cd 대신 init/status/memory에 --cwd <path>를 사용할 수 있습니다.',
651
- ' - user 설정은 $CODEX_HOME 또는 ~/.codex를 대상으로 사용합니다.',
652
- ' - Notion 기록이 invalid_grant/unauthorized/forbidden 같은 인증 오류로 실패하면 "codex mcp login notion"으로 MCP 인증을 다시 연결하세요.',
653
- ` - 자세한 가이드: ${guidePath}`,
654
- ].join('\n'),
655
- );
794
+ '📝 사용 가능한 상세 도움말:',
795
+ ' sk help setup',
796
+ ' sk help init',
797
+ ' sk help check',
798
+ ' sk help status',
799
+ ' sk help memory',
800
+ ' sk help search',
801
+ ];
802
+ }
803
+
804
+ const lines = [
805
+ help.title,
806
+ '',
807
+ '🧭 사용법',
808
+ ` ${help.usage}`,
809
+ '',
810
+ '📖 설명',
811
+ ...help.summary.map((line) => ` - ${line}`),
812
+ '',
813
+ '⚙️ 옵션',
814
+ ...formatOptionLines(help.options),
815
+ '',
816
+ '🧪 예시',
817
+ ...help.examples.map((line) => ` ${line}`),
818
+ ];
819
+
820
+ if (help.generatedFiles) {
821
+ lines.push('', '📦 생성 파일', ...help.generatedFiles.map((line) => ` - ${line}`));
822
+ }
823
+
824
+ lines.push('', '💡 팁', ' 전체 명령 요약은 "sk help"로 다시 볼 수 있습니다.');
825
+ return lines;
826
+ }
827
+
828
+ function printUsage(command = 'general') {
829
+ const normalized = String(command || 'general').toLowerCase();
830
+ const lines = normalized === 'general' ? generalHelpLines() : commandHelpLines(normalized);
831
+ console.log(lines.join('\n'));
656
832
  }
657
833
 
658
834
  function fail(message, exitCode = 1) {
@@ -682,7 +858,7 @@ function parseFlags(args) {
682
858
  continue;
683
859
  }
684
860
 
685
- if (token === '--json' || token === '--dry-run' || token === '--verbose' || token === '--case-sensitive' || token === '--force' || token === '--configure-notion' || token === '--configure-db' || token === '--configure-db-safe-read' || token === '--skip-notion') {
861
+ if (token === '--json' || token === '--dry-run' || token === '--verbose' || token === '--case-sensitive' || token === '--force' || token === '--configure-notion' || token === '--configure-db' || token === '--configure-db-safe-read' || token === '--skip-notion' || token === '--agent-md') {
686
862
  flags.set(token.slice(2), true);
687
863
  continue;
688
864
  }
@@ -752,6 +928,219 @@ function createDefaultNotepad() {
752
928
  ].join('\n');
753
929
  }
754
930
 
931
+ function pathExists(cwd, relPath) {
932
+ return existsSync(join(cwd, ...relPath.split('/').filter(Boolean)));
933
+ }
934
+
935
+ function firstExistingPath(cwd, candidates) {
936
+ return candidates.find((candidate) => pathExists(cwd, candidate)) || null;
937
+ }
938
+
939
+ function formatBullets(items, fallback = 'TODO: Add project-specific guidance here.') {
940
+ const lines = items.length > 0 ? items : [fallback];
941
+ return `${lines.map((item) => (item.startsWith('- ') ? item : `- ${item}`)).join('\n')}\n`;
942
+ }
943
+
944
+ function detectNodePackageManager(cwd) {
945
+ if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
946
+ if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';
947
+ return 'npm';
948
+ }
949
+
950
+ function formatRunScriptCommand(packageManager, scriptName) {
951
+ if (packageManager === 'yarn') return `yarn ${scriptName}`;
952
+ if (packageManager === 'pnpm') return `pnpm ${scriptName}`;
953
+ return `npm run ${scriptName}`;
954
+ }
955
+
956
+ async function readPackageManifest(cwd) {
957
+ return readJsonOptional(join(cwd, 'package.json'));
958
+ }
959
+
960
+ function inferAgentTemplateKind(cwd, track) {
961
+ if (track === 'seokang-skill') return 'codex';
962
+ if (track === 'server' || track === 'slk-server') return 'backend';
963
+ if (track) return 'frontend';
964
+
965
+ if (pathExists(cwd, 'skills') && pathExists(cwd, 'codex-config')) {
966
+ return 'codex';
967
+ }
968
+
969
+ if (['gradlew', 'pom.xml', 'build.gradle', 'build.gradle.kts', 'src/main'].some((relPath) => pathExists(cwd, relPath))) {
970
+ return 'backend';
971
+ }
972
+
973
+ return 'frontend';
974
+ }
975
+
976
+ function buildAgentEntryPoints(kind, cwd) {
977
+ const items = [];
978
+ const readmePath = firstExistingPath(cwd, ['README.MD', 'README.md', 'README']);
979
+ if (readmePath) {
980
+ items.push(`\`${readmePath}\`: 프로젝트 개요와 설치/운영 문서`);
981
+ }
982
+
983
+ if (kind === 'codex') {
984
+ if (pathExists(cwd, 'skills')) items.push('`skills/`: 공용 skill 원본과 workflow 규칙');
985
+ if (pathExists(cwd, 'codex-config')) items.push('`codex-config/`: 동기화 대상 Codex config / rules / agents 원본');
986
+ if (pathExists(cwd, 'scripts')) items.push('`scripts/`: 설치/동기화 스크립트와 CLI 진입점');
987
+ if (pathExists(cwd, 'docs')) items.push('`docs/`: 운영 가이드와 변경 문서');
988
+ } else if (kind === 'backend') {
989
+ const mainPath = firstExistingPath(cwd, ['src/main/java', 'src/main/kotlin', 'src/main']);
990
+ const testPath = firstExistingPath(cwd, ['src/test/java', 'src/test/kotlin', 'src/test']);
991
+ const resourcePath = firstExistingPath(cwd, ['src/main/resources', 'resources']);
992
+ const dbPath = firstExistingPath(cwd, ['src/main/resources/db', 'db', 'database', 'sql']);
993
+ if (mainPath) items.push(`\`${mainPath}/\`: 서버 소스와 주요 엔트리포인트`);
994
+ if (testPath) items.push(`\`${testPath}/\`: 회귀 테스트와 검증 코드`);
995
+ if (resourcePath) items.push(`\`${resourcePath}/\`: 설정, profile, 리소스 파일`);
996
+ if (dbPath) items.push(`\`${dbPath}/\`: SQL, 마이그레이션, DB 관련 산출물`);
997
+ if (pathExists(cwd, 'docs')) items.push('`docs/`: 운영/계약/변경 문서');
998
+ } else {
999
+ const routePath = firstExistingPath(cwd, ['src/routes', 'src/pages', 'src/app', 'app']);
1000
+ const componentPath = firstExistingPath(cwd, ['src/components', 'components']);
1001
+ const assetPath = firstExistingPath(cwd, ['public', 'src/assets', 'assets']);
1002
+ if (pathExists(cwd, 'src')) items.push('`src/`: 화면, 상태, 도메인 로직의 기본 소스');
1003
+ if (routePath) items.push(`\`${routePath}/\`: 주요 라우트와 화면 진입점`);
1004
+ if (componentPath) items.push(`\`${componentPath}/\`: 공통 UI와 재사용 컴포넌트`);
1005
+ if (assetPath) items.push(`\`${assetPath}/\`: 정적 파일과 이미지 자산`);
1006
+ if (pathExists(cwd, 'docs')) items.push('`docs/`: 화면 흐름/운영/변경 문서');
1007
+ }
1008
+
1009
+ items.push('`.sk/`: 세션 메모와 작업 상태 파일');
1010
+ return formatBullets(items, 'TODO: 이 프로젝트의 핵심 소스 디렉터리와 엔트리포인트를 채워 주세요.');
1011
+ }
1012
+
1013
+ function buildRunbookFromPackageScripts(cwd, manifest, preferredScripts) {
1014
+ const scripts = manifest && typeof manifest.scripts === 'object' ? manifest.scripts : null;
1015
+ if (!scripts) return [];
1016
+
1017
+ const packageManager = detectNodePackageManager(cwd);
1018
+ const descriptions = {
1019
+ dev: '로컬 개발 서버 실행',
1020
+ start: '기본 실행 명령',
1021
+ build: '빌드 산출물 생성',
1022
+ test: '기본 테스트 실행',
1023
+ typecheck: '타입 검사',
1024
+ lint: '정적 규칙 검사',
1025
+ };
1026
+
1027
+ return preferredScripts
1028
+ .filter((scriptName) => Object.prototype.hasOwnProperty.call(scripts, scriptName))
1029
+ .map((scriptName) => `\`${formatRunScriptCommand(packageManager, scriptName)}\`: ${descriptions[scriptName] || '프로젝트 기본 스크립트'}`);
1030
+ }
1031
+
1032
+ function buildAgentRunbook(kind, cwd, manifest) {
1033
+ const items = [
1034
+ '`sk init --agent-md` 또는 `sk init --cwd <path> --agent-md`: 프로젝트 상태 파일과 AGENT.MD 초안 생성',
1035
+ ];
1036
+
1037
+ if (kind === 'codex') {
1038
+ items.push('`sk setup`: 공용 config / rules / agents 동기화');
1039
+ items.push('`sk check`: 공용 원본과 설치 차이 점검');
1040
+ items.push('`sk status`: 현재 .sk / AGENT.MD / Notion 설정 상태 확인');
1041
+ return formatBullets(items);
1042
+ }
1043
+
1044
+ if (pathExists(cwd, 'gradlew')) {
1045
+ items.push('`./gradlew test`: 기본 회귀 테스트');
1046
+ items.push('`./gradlew build`: 빌드/패키징 확인');
1047
+ return formatBullets(items);
1048
+ }
1049
+
1050
+ if (pathExists(cwd, 'mvnw')) {
1051
+ items.push('`./mvnw test`: 기본 테스트');
1052
+ items.push('`./mvnw verify`: 검증 포함 빌드');
1053
+ return formatBullets(items);
1054
+ }
1055
+
1056
+ const scriptItems = buildRunbookFromPackageScripts(
1057
+ cwd,
1058
+ manifest,
1059
+ kind === 'backend'
1060
+ ? ['start', 'dev', 'test', 'build', 'lint']
1061
+ : ['dev', 'start', 'typecheck', 'test', 'build', 'lint'],
1062
+ );
1063
+ if (scriptItems.length > 0) {
1064
+ items.push(...scriptItems);
1065
+ return formatBullets(items);
1066
+ }
1067
+
1068
+ items.push('TODO: 이 프로젝트에서 실제로 쓰는 설치/실행/테스트 명령으로 교체해 주세요.');
1069
+ return formatBullets(items);
1070
+ }
1071
+
1072
+ function buildAgentValidation(kind, cwd, manifest) {
1073
+ const items = [];
1074
+
1075
+ if (kind === 'codex') {
1076
+ items.push('`sk init --cwd <path> --agent-md --dry-run`: 새 프로젝트 초기화 결과 미리보기');
1077
+ items.push('`sk status`와 `sk check`: 상태 파일, AGENT.MD, 설치 차이 확인');
1078
+ items.push('설치/설정 변경이면 실제 홈 반영 전에 임시 `CODEX_HOME`에서 한 번 더 검증');
1079
+ return formatBullets(items);
1080
+ }
1081
+
1082
+ if (kind === 'backend') {
1083
+ if (pathExists(cwd, 'gradlew')) {
1084
+ items.push('`./gradlew test`와 `./gradlew build`를 기본 회귀 검증으로 사용');
1085
+ } else if (pathExists(cwd, 'mvnw')) {
1086
+ items.push('`./mvnw test`와 `./mvnw verify`를 기본 회귀 검증으로 사용');
1087
+ } else {
1088
+ const scriptItems = buildRunbookFromPackageScripts(cwd, manifest, ['test', 'build', 'lint']);
1089
+ if (scriptItems.length > 0) items.push(...scriptItems);
1090
+ }
1091
+ items.push('변경한 API 또는 저장 흐름에 대해 최소 1개의 startup/context 검증 경로를 남긴다.');
1092
+ items.push('Profile, secret, seed data, 외부 의존성 필요 여부를 이 파일이나 docs에 명시한다.');
1093
+ return formatBullets(items);
1094
+ }
1095
+
1096
+ const scriptItems = buildRunbookFromPackageScripts(cwd, manifest, ['typecheck', 'test', 'build', 'lint']);
1097
+ if (scriptItems.length > 0) {
1098
+ items.push(...scriptItems);
1099
+ } else {
1100
+ items.push('TODO: 이 UI 프로젝트에서 실제로 쓰는 테스트/typecheck/build 명령을 채워 주세요.');
1101
+ }
1102
+ items.push('영향 받은 화면/라우트에서 런타임 동작, 콘솔 에러, 필요한 계정/데이터 전제조건을 함께 검증한다.');
1103
+ items.push('고객 노출 UI면 실제 뷰포트 기준 시각 검증 경로를 남긴다.');
1104
+ return formatBullets(items);
1105
+ }
1106
+
1107
+ function buildAgentTemplateContext(cwd, manifest) {
1108
+ const track = inferTrackFromPath(cwd) || '수동확인필요';
1109
+ const templateKind = inferAgentTemplateKind(cwd, track);
1110
+ const projectName = isNonEmptyString(manifest?.name) ? manifest.name.trim() : basename(cwd);
1111
+ return {
1112
+ projectName,
1113
+ track,
1114
+ templateKind,
1115
+ entryPoints: buildAgentEntryPoints(templateKind, cwd),
1116
+ runbook: buildAgentRunbook(templateKind, cwd, manifest),
1117
+ validation: buildAgentValidation(templateKind, cwd, manifest),
1118
+ };
1119
+ }
1120
+
1121
+ async function renderAgentTemplate(cwd = process.cwd()) {
1122
+ const manifest = await readPackageManifest(cwd);
1123
+ const context = buildAgentTemplateContext(cwd, manifest);
1124
+ const baseTemplate = await readFile(agentTemplatePath('base.md'), 'utf-8');
1125
+ const overlayTemplate = await readFile(agentTemplatePath(`${context.templateKind}.md`), 'utf-8');
1126
+
1127
+ let content = baseTemplate.replace('__TRACK_CONTENT__', overlayTemplate.trim());
1128
+ const replacements = [
1129
+ ['__PROJECT_NAME__', context.projectName],
1130
+ ['__TRACK__', context.track],
1131
+ ['__TEMPLATE_KIND__', context.templateKind],
1132
+ ['__ENTRY_POINTS__', context.entryPoints.trimEnd()],
1133
+ ['__RUNBOOK__', context.runbook.trimEnd()],
1134
+ ['__VALIDATION__', context.validation.trimEnd()],
1135
+ ];
1136
+
1137
+ for (const [token, value] of replacements) {
1138
+ content = content.replaceAll(token, value);
1139
+ }
1140
+
1141
+ return `${content.trimEnd()}\n`;
1142
+ }
1143
+
755
1144
  function createDefaultNotionWorkspaceConfig() {
756
1145
  return {
757
1146
  version: 1,
@@ -1294,6 +1683,30 @@ async function ensureProjectStateFiles({
1294
1683
  return summary;
1295
1684
  }
1296
1685
 
1686
+ async function ensureProjectAgentFile({
1687
+ cwd = process.cwd(),
1688
+ dryRun = false,
1689
+ verbose = false,
1690
+ force = false,
1691
+ }) {
1692
+ const summary = createSummaryEntry();
1693
+ const targetPath = agentMdPath(cwd);
1694
+ const content = await renderAgentTemplate(cwd);
1695
+
1696
+ if (!existsSync(targetPath) || force) {
1697
+ if (!dryRun) {
1698
+ await ensureDir(dirname(targetPath));
1699
+ await writeFile(targetPath, content, 'utf-8');
1700
+ }
1701
+ if (verbose) console.log(` ✍️ 기록: ${targetPath}`);
1702
+ summary.updated += 1;
1703
+ } else {
1704
+ summary.unchanged += 1;
1705
+ }
1706
+
1707
+ return summary;
1708
+ }
1709
+
1297
1710
  async function readPersistedScope(cwd = process.cwd()) {
1298
1711
  const path = scopeFilePath(cwd);
1299
1712
  if (!existsSync(path)) return null;
@@ -1534,6 +1947,7 @@ async function runInit(flags) {
1534
1947
  const dryRun = Boolean(flags.get('dry-run'));
1535
1948
  const verbose = Boolean(flags.get('verbose'));
1536
1949
  const force = Boolean(flags.get('force'));
1950
+ const createAgentMd = Boolean(flags.get('agent-md'));
1537
1951
 
1538
1952
  if (!flags.get('cwd') && samePath(cwd, homedir())) {
1539
1953
  fail('sk init은 프로젝트 루트에서 실행하세요. 홈 디렉터리에서 실행하려면 --cwd <path>로 프로젝트 경로를 지정하세요.');
@@ -1541,14 +1955,21 @@ async function runInit(flags) {
1541
1955
 
1542
1956
  const scopePath = await writeScopeFile('project', cwd, dryRun);
1543
1957
  const stateSummary = await ensureProjectStateFiles({ cwd, dryRun, verbose, force });
1958
+ const agentSummary = createAgentMd
1959
+ ? await ensureProjectAgentFile({ cwd, dryRun, verbose, force })
1960
+ : createSummaryEntry();
1544
1961
 
1545
1962
  console.log(`sk init${dryRun ? ' (dry-run)' : ''}`);
1546
1963
  console.log(` 🗂️ 상태 파일: ${scopePath}`);
1547
1964
  console.log(` 🧠 프로젝트 메모리: ${projectMemoryPath(cwd)}`);
1548
1965
  console.log(` 📒 메모장: ${notepadPath(cwd)}`);
1966
+ if (createAgentMd || existsSync(agentMdPath(cwd))) {
1967
+ console.log(` 📄 AGENT.MD: ${agentMdPath(cwd)}`);
1968
+ }
1549
1969
  console.log(
1550
1970
  ` 📊 요약: 메모리(갱신=${stateSummary.memory.updated}, 유지=${stateSummary.memory.unchanged}) ` +
1551
- `메모장(갱신=${stateSummary.notepad.updated}, 유지=${stateSummary.notepad.unchanged})`,
1971
+ `메모장(갱신=${stateSummary.notepad.updated}, 유지=${stateSummary.notepad.unchanged})` +
1972
+ `${createAgentMd ? ` AGENT.MD(갱신=${agentSummary.updated}, 유지=${agentSummary.unchanged})` : ''}`,
1552
1973
  );
1553
1974
  console.log(' ✅ 현재 프로젝트의 .sk 상태가 준비되었습니다.');
1554
1975
  }
@@ -1647,6 +2068,10 @@ async function buildDoctorResult(cwd = process.cwd()) {
1647
2068
  ? check('pass', '메모장', notepadPath(cwd))
1648
2069
  : check('warn', '메모장', `${notepadPath(cwd)} 파일이 없습니다.`));
1649
2070
 
2071
+ if (existsSync(agentMdPath(cwd))) {
2072
+ checks.push(check('pass', 'AGENT.MD', agentMdPath(cwd)));
2073
+ }
2074
+
1650
2075
  checks.push(...buildDbSafeReadChecks(cwd));
1651
2076
 
1652
2077
  if (!existsSync(target.notionConfigPath)) {
@@ -1708,6 +2133,8 @@ async function runStatus(flags) {
1708
2133
  project_memory_exists: existsSync(projectMemoryPath(cwd)),
1709
2134
  notepad_path: notepadPath(cwd),
1710
2135
  notepad_exists: existsSync(notepadPath(cwd)),
2136
+ agent_md_path: agentMdPath(cwd),
2137
+ agent_md_exists: existsSync(agentMdPath(cwd)),
1711
2138
  notes_count: memory?.notes.length ?? 0,
1712
2139
  directives_count: memory?.directives.length ?? 0,
1713
2140
  notion_config_path: target.notionConfigPath,
@@ -1730,6 +2157,7 @@ async function runStatus(flags) {
1730
2157
  console.log(` 🗂️ 상태 디렉터리: ${status.state_dir}`);
1731
2158
  console.log(` 🧠 프로젝트 메모리: ${status.project_memory_exists ? '있음' : '없음'} (${status.project_memory_path})`);
1732
2159
  console.log(` 📒 메모장: ${status.notepad_exists ? '있음' : '없음'} (${status.notepad_path})`);
2160
+ console.log(` 📄 AGENT.MD: ${status.agent_md_exists ? '있음' : '없음'} (${status.agent_md_path})`);
1733
2161
  console.log(` 📝 Notion 설정: ${status.notion_config_exists ? (status.notion_configured ? '설정 완료' : '불완전') : '없음'} (${status.notion_config_path})`);
1734
2162
  console.log(` 📌 메모/지시문: ${status.notes_count}/${status.directives_count}`);
1735
2163
  console.log(` 💾 최근 백업: ${status.latest_backup || '없음'}`);
@@ -2149,11 +2577,17 @@ async function runMemory(flags, rest) {
2149
2577
 
2150
2578
  async function main() {
2151
2579
  const [command, ...args] = process.argv.slice(2);
2152
- if (!command || command === 'help' || command === '--help' || command === '-h') {
2580
+ if (!command || command === '--help' || command === '-h') {
2153
2581
  printUsage();
2154
2582
  return;
2155
2583
  }
2156
2584
 
2585
+ if (command === 'help') {
2586
+ const target = args[0];
2587
+ printUsage(target || 'general');
2588
+ return;
2589
+ }
2590
+
2157
2591
  const localDispatcher = resolveLocalDispatcher(command);
2158
2592
  if (localDispatcher) {
2159
2593
  runCommandOrFail(process.execPath, [localDispatcher, command, ...args], {
@@ -2179,7 +2613,7 @@ async function main() {
2179
2613
  if (command === 'search') {
2180
2614
  const { flags, rest } = parseFlags(args);
2181
2615
  if (flags.get('help')) {
2182
- printUsage();
2616
+ printUsage('search');
2183
2617
  return;
2184
2618
  }
2185
2619
  await runSessionSearch(flags, ['search', ...rest]);
@@ -2189,7 +2623,7 @@ async function main() {
2189
2623
  if (command === 'memory') {
2190
2624
  const { flags, rest } = parseFlags(args);
2191
2625
  if (flags.get('help')) {
2192
- printUsage();
2626
+ printUsage('memory');
2193
2627
  return;
2194
2628
  }
2195
2629
  await runMemory(flags, rest);
@@ -2198,7 +2632,7 @@ async function main() {
2198
2632
 
2199
2633
  const { flags } = parseFlags(args);
2200
2634
  if (flags.get('help')) {
2201
- printUsage();
2635
+ printUsage(command);
2202
2636
  return;
2203
2637
  }
2204
2638
 
@@ -0,0 +1,22 @@
1
+ ## 목적
2
+ - TODO: `__PROJECT_NAME__`가 책임지는 범위와 반드시 안정적으로 유지해야 하는 외부 계약을 적습니다.
3
+ - API, 인증, 저장소, 운영 가드레일은 여기에서 바로 확인할 수 있게 유지합니다.
4
+
5
+ ## 주요 진입점
6
+ __ENTRY_POINTS__
7
+
8
+ ## 실행/작업 명령
9
+ __RUNBOOK__
10
+
11
+ ## 작업 규칙
12
+ - 코드 수정 전에는 이 파일을 먼저 읽고, 그다음 `.sk/project-memory.json`, `.sk/notepad.md`, 관련 README/docs를 확인합니다.
13
+ - 동작을 바꿀 때는 controller/service/repository 경계와 외부 응답 계약이 의도적으로 유지되는지 확인합니다.
14
+ - DB 확인은 safe-read 근거 확보를 우선하고, 일상 워크플로에 raw credential이나 write query를 남기지 않습니다.
15
+ - 검증에 필요한 profile, secret, queue, storage, seed data 전제조건을 문서화합니다.
16
+
17
+ ## 검증 기준
18
+ __VALIDATION__
19
+
20
+ ## 문서/기록
21
+ - 백엔드 동작이 바뀌면 영향받는 API 경로, profile, rollout 메모, rollback 지점을 함께 남깁니다.
22
+ - 기준 문서가 코드 밖에 있으면 계약을 정의한 docs/spec 링크를 연결합니다.
@@ -0,0 +1,18 @@
1
+ <!-- `sk init --agent-md`로 생성한 초안입니다. 첫 프로젝트 맞춤 정리 이후 임시 문구를 교체하세요. -->
2
+ # AGENT.MD
3
+
4
+ > Codex와 팀원이 함께 따르는 프로젝트 단위 작업 기준서입니다.
5
+ > README보다 짧게 유지하고, 임시 메모는 `.sk/notepad.md`로 분리합니다.
6
+
7
+ ## 프로젝트 개요
8
+ - 이름: `__PROJECT_NAME__`
9
+ - 트랙: `__TRACK__`
10
+ - 템플릿: `__TEMPLATE_KIND__`
11
+ - 생성 명령: `sk init --agent-md`
12
+
13
+ __TRACK_CONTENT__
14
+
15
+ ## 작성 체크리스트
16
+ - 아직 `TODO`로 남아 있는 항목은 프로젝트 기준으로 교체합니다.
17
+ - 긴 설치 가이드나 제품 배경 설명은 여기보다 `README` 또는 `docs/`에 둡니다.
18
+ - 워크플로, 검증 방식, 배포 규칙이 바뀌면 이 파일도 함께 갱신합니다.
@@ -0,0 +1,22 @@
1
+ ## 목적
2
+ - `__PROJECT_NAME__`를 공용 Codex 워크플로, 설치/동기화, 문서 규칙의 기준 원본으로 사용합니다.
3
+ - 장기 수정은 여기 원본 파일에 먼저 반영하고, 생성된 실행 환경 파일만 임시로 고치고 끝내지 않습니다.
4
+
5
+ ## 주요 진입점
6
+ __ENTRY_POINTS__
7
+
8
+ ## 실행/작업 명령
9
+ __RUNBOOK__
10
+
11
+ ## 작업 규칙
12
+ - `~/.codex` 아래 생성 파일보다 저장소 원본 (`skills/`, `codex-config/`, `scripts/`, `docs/`)을 우선 수정합니다.
13
+ - 설치/동기화 설정 변경은 실제 사용자 환경 재동기화 전에 임시 환경에서 먼저 검증합니다.
14
+ - 프로젝트 단위의 고정 규칙은 여기에 두고, 세션 전용 메모는 `.sk/notepad.md`로 분리합니다.
15
+ - 같은 워크플로 문제가 반복되면 임시 로컬 수정이 아니라 원인이 된 skill/doc/설치 경로를 고칩니다.
16
+
17
+ ## 검증 기준
18
+ __VALIDATION__
19
+
20
+ ## 문서/기록
21
+ - 명령이나 워크플로를 바꾸면 같은 변경 안에서 README/docs도 함께 반영합니다.
22
+ - Notion 기록과 대체 문서 동작은 이 저장소의 기준 워크플로와 맞춰 유지합니다.
@@ -0,0 +1,22 @@
1
+ ## 목적
2
+ - TODO: `__PROJECT_NAME__`가 책임지는 화면/흐름과 의도적으로 제외한 범위를 짧게 정리합니다.
3
+ - 고객에게 보이는 흐름, 카피, 승인 조건은 여기서 바로 찾을 수 있게 유지합니다.
4
+
5
+ ## 주요 진입점
6
+ __ENTRY_POINTS__
7
+
8
+ ## 실행/작업 명령
9
+ __RUNBOOK__
10
+
11
+ ## 작업 규칙
12
+ - 라우트나 화면을 수정하기 전에는 이 파일을 먼저 읽고, 그다음 `.sk/project-memory.json`, `.sk/notepad.md`, 관련 README/docs를 확인합니다.
13
+ - 고객 노출 UI, 라우트 흐름, 승인된 디자인/스펙 동작은 합의된 기준 문서와 맞춰 유지합니다.
14
+ - 원격 디자인 파일 URL을 코드나 설정에 남기지 말고, export한 파일은 프로젝트 내부에 저장합니다.
15
+ - 대상 화면을 재현하는 데 필요한 라우트, 권한, fixture, 테스트 데이터 전제조건을 적어 둡니다.
16
+
17
+ ## 검증 기준
18
+ __VALIDATION__
19
+
20
+ ## 문서/기록
21
+ - 화면 변경이 보이는 작업이면 라우트/화면 이름, 필요한 계정/권한, 런타임 검증 경로를 함께 남깁니다.
22
+ - 흐름, 카피, 가드레일이 바뀌면 연결된 스펙/docs도 같이 갱신합니다.