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 +34 -3
- package/docs/sk-cli-guide.md +22 -6
- package/docs/sk-notion-workspace.md +4 -1
- package/package.json +3 -2
- package/scripts/sk.mjs +487 -53
- package/templates/agent/backend.md +22 -0
- package/templates/agent/base.md +18 -0
- package/templates/agent/codex.md +22 -0
- package/templates/agent/frontend.md +22 -0
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
|
-
- `--
|
|
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 작성
|
package/docs/sk-cli-guide.md
CHANGED
|
@@ -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
|
-
- `
|
|
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.
|
|
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
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
'
|
|
642
|
-
|
|
643
|
-
'
|
|
644
|
-
'
|
|
645
|
-
'
|
|
646
|
-
'
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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 === '
|
|
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도 같이 갱신합니다.
|