lee-spec-kit 0.6.16 → 0.6.17
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.en.md +53 -17
- package/README.md +53 -17
- package/dist/index.js +665 -36
- package/package.json +1 -1
- package/templates/en/common/README.md +6 -2
- package/templates/en/common/agents/agents.md +1 -1
- package/templates/en/common/features/feature-base/issue.md +3 -7
- package/templates/en/common/features/feature-base/pr.md +3 -3
- package/templates/ko/common/README.md +6 -2
- package/templates/ko/common/agents/agents.md +1 -1
- package/templates/ko/common/features/feature-base/issue.md +3 -7
- package/templates/ko/common/features/feature-base/pr.md +3 -3
package/README.en.md
CHANGED
|
@@ -40,26 +40,29 @@
|
|
|
40
40
|
# 1) Initialize docs structure
|
|
41
41
|
npx lee-spec-kit init
|
|
42
42
|
|
|
43
|
-
# 2)
|
|
43
|
+
# 2) Run initial onboarding checks
|
|
44
|
+
npx lee-spec-kit onboard --strict
|
|
45
|
+
|
|
46
|
+
# 3) Create a feature
|
|
44
47
|
npx lee-spec-kit feature user-auth
|
|
45
48
|
|
|
46
|
-
#
|
|
49
|
+
# 4) Show next steps (for agents)
|
|
47
50
|
npx lee-spec-kit context
|
|
48
51
|
|
|
49
|
-
#
|
|
52
|
+
# 5) Show workflow dashboard
|
|
50
53
|
npx lee-spec-kit view
|
|
51
54
|
|
|
52
|
-
#
|
|
55
|
+
# 6) Show overall status
|
|
53
56
|
npx lee-spec-kit status
|
|
54
57
|
|
|
55
|
-
#
|
|
58
|
+
# 7) Validate docs / feature metadata
|
|
56
59
|
npx lee-spec-kit doctor
|
|
57
60
|
```
|
|
58
61
|
|
|
59
62
|
## New Project Start Order
|
|
60
63
|
|
|
61
64
|
For a brand-new project, scaffold the **codebase first**, then initialize docs.
|
|
62
|
-
|
|
65
|
+
For most users (default: embedded), running `npx lee-spec-kit init` in project root is enough.
|
|
63
66
|
|
|
64
67
|
```bash
|
|
65
68
|
# 0) Create/init the code project first (example: Next.js)
|
|
@@ -69,17 +72,32 @@ cd my-app
|
|
|
69
72
|
# 1) Initialize docs structure
|
|
70
73
|
npx lee-spec-kit init
|
|
71
74
|
|
|
72
|
-
# 2)
|
|
75
|
+
# 2) Run initial onboarding checks
|
|
76
|
+
npx lee-spec-kit onboard --strict
|
|
77
|
+
|
|
78
|
+
# 3) Detect project (agent entrypoint)
|
|
73
79
|
npx lee-spec-kit detect --json
|
|
74
80
|
|
|
75
|
-
#
|
|
81
|
+
# 4) Create feature and start workflow
|
|
76
82
|
npx lee-spec-kit feature user-auth
|
|
77
|
-
npx lee-spec-kit context --json
|
|
83
|
+
npx lee-spec-kit context --json-compact
|
|
78
84
|
```
|
|
79
85
|
|
|
80
86
|
- Apply lee-spec-kit workflow only when `detect --json` returns `isLeeSpecKitProject: true`.
|
|
81
87
|
- If `isLeeSpecKitProject: false`, continue with normal non-lee-spec-kit workflow.
|
|
82
|
-
|
|
88
|
+
|
|
89
|
+
For teams that keep docs separate from the code repo (standalone), the recommended start point is the **parent workspace folder**.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Recommended layout:
|
|
93
|
+
# workspace/
|
|
94
|
+
# ├─ docs/ (lee-spec-kit docs)
|
|
95
|
+
# └─ project/ (actual code repo)
|
|
96
|
+
#
|
|
97
|
+
# Run from workspace root
|
|
98
|
+
npx lee-spec-kit init --docs-repo standalone --dir ./docs --project-root ./project
|
|
99
|
+
npx lee-spec-kit detect --json
|
|
100
|
+
```
|
|
83
101
|
|
|
84
102
|
## Agent Kickoff Prompt
|
|
85
103
|
|
|
@@ -88,7 +106,7 @@ You can paste the following as an agent session-start instruction.
|
|
|
88
106
|
```text
|
|
89
107
|
Start procedure:
|
|
90
108
|
1) Run npx lee-spec-kit detect --json
|
|
91
|
-
2) If isLeeSpecKitProject === true, run npx lee-spec-kit context --json
|
|
109
|
+
2) If isLeeSpecKitProject === true, run npx lee-spec-kit context --json-compact (use --json only when full detail is needed)
|
|
92
110
|
3) If actionOptions exist, show approvalPrompt and finalPrompt exactly as provided, then wait for user approval (<LABEL> or <LABEL> OK)
|
|
93
111
|
4) Do not execute before approval; execute requiresUserCheck=true actions only after approval
|
|
94
112
|
5) If isLeeSpecKitProject === false, skip lee-spec-kit-specific flow and continue with normal workflow
|
|
@@ -99,7 +117,7 @@ Start procedure:
|
|
|
99
117
|
### 📁 Project initialization
|
|
100
118
|
|
|
101
119
|
- Interactive init or CLI options
|
|
102
|
-
-
|
|
120
|
+
- Default is `multi`; `single` remains supported for simple single-repo and backward-compatibility scenarios (`fullstack` is a backward-compatible alias of `multi`)
|
|
103
121
|
- Korean/English templates
|
|
104
122
|
|
|
105
123
|
### 🚀 Feature creation
|
|
@@ -178,6 +196,21 @@ npx lee-spec-kit detect --dir /path/to/workspace
|
|
|
178
196
|
|
|
179
197
|
The `--json` payload includes `isLeeSpecKitProject`, `reasonCode` (`PROJECT_DETECTED` | `PROJECT_NOT_DETECTED`), `docsDir`, `configPath`, and `detectionSource` (`config` | `heuristic`).
|
|
180
198
|
|
|
199
|
+
### Onboarding checks
|
|
200
|
+
|
|
201
|
+
Validate initial setup readiness (Constitution/PRD/git remotes, etc.).
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# human-readable output
|
|
205
|
+
npx lee-spec-kit onboard
|
|
206
|
+
|
|
207
|
+
# JSON output for agents/automation
|
|
208
|
+
npx lee-spec-kit onboard --json
|
|
209
|
+
|
|
210
|
+
# exit code 1 when WARN/BLOCK exists
|
|
211
|
+
npx lee-spec-kit onboard --strict
|
|
212
|
+
```
|
|
213
|
+
|
|
181
214
|
### Create a feature
|
|
182
215
|
|
|
183
216
|
```bash
|
|
@@ -214,6 +247,7 @@ npx lee-spec-kit context
|
|
|
214
247
|
# recommended: one feature + labels
|
|
215
248
|
npx lee-spec-kit context F001
|
|
216
249
|
npx lee-spec-kit context F001 --json
|
|
250
|
+
npx lee-spec-kit context F001 --json-compact
|
|
217
251
|
|
|
218
252
|
# approve + execute (common path)
|
|
219
253
|
npx lee-spec-kit context F001 --approve A --execute
|
|
@@ -232,6 +266,7 @@ Use advanced selectors (`--component`, `--all`, `--done`) only when you need mul
|
|
|
232
266
|
| Option | Description |
|
|
233
267
|
| -------------- | ----------------------------------------------- |
|
|
234
268
|
| `--json` | JSON output for agents |
|
|
269
|
+
| `--json-compact` | Compact JSON for agents (implies `--json`, minimizes duplicated fields) |
|
|
235
270
|
| `--component <id>` | Select target component in multi mode (e.g. `app`, `api`, `worker`) |
|
|
236
271
|
| `--all` | Include completed features when auto-detecting |
|
|
237
272
|
| `--done` | Show completed (workflow-done) features only |
|
|
@@ -246,7 +281,8 @@ Use advanced selectors (`--component`, `--all`, `--done`) only when you need mul
|
|
|
246
281
|
- `--ticket` is required for `--execute` only when the selected action has `requiresUserCheck=true`.
|
|
247
282
|
- It is short-lived (5 minutes by default) and cannot be reused after one execution.
|
|
248
283
|
|
|
249
|
-
`context --json` is
|
|
284
|
+
`context --json-compact` is the default recommended format, providing a reduced and deduplicated decision state.
|
|
285
|
+
Use `context --json` only when full-detail debugging fields are required.
|
|
250
286
|
|
|
251
287
|
**Core fields (recommended for normal agent flows)**
|
|
252
288
|
|
|
@@ -475,8 +511,8 @@ Running `init` creates `.lee-spec-kit.json` in your docs root (default: `docs/`)
|
|
|
475
511
|
`approval` only affects the following values produced by `context`:
|
|
476
512
|
|
|
477
513
|
- the `[CHECK required]` tag in text output
|
|
478
|
-
- `
|
|
479
|
-
- `checkPolicy.token` (`context --json`): approval token format (`<LABEL>`)
|
|
514
|
+
- `actionOptions[].requiresUserCheck` in `context --json-compact` (`actions[].requiresUserCheck` in `--json`)
|
|
515
|
+
- `checkPolicy.token` (`context --json-compact`/`--json`): approval token format (`<LABEL>`)
|
|
480
516
|
- `checkPolicy.acceptedTokens`: accepted reply templates (e.g. `["<LABEL>", "<LABEL> OK", "<LABEL> ...", "... <LABEL> ..."]`)
|
|
481
517
|
- `checkPolicy.tokenPattern`: input validation regex for approval replies
|
|
482
518
|
- `checkPolicy.validLabels`: currently selectable labels (`A`, `B`, `C`...)
|
|
@@ -536,7 +572,7 @@ Example:
|
|
|
536
572
|
#### Modes
|
|
537
573
|
|
|
538
574
|
- `builtin` (default): keep built-in `requiresUserCheck` in steps/actions
|
|
539
|
-
- `category` (recommended): control CHECK policy by `actions[].category`
|
|
575
|
+
- `category` (recommended): control CHECK policy by `actionOptions[].category` (`actions[].category` in `--json`)
|
|
540
576
|
- `steps`: control by step numbers (not recommended; fragile)
|
|
541
577
|
|
|
542
578
|
#### Fields
|
|
@@ -564,7 +600,7 @@ Example:
|
|
|
564
600
|
}
|
|
565
601
|
```
|
|
566
602
|
|
|
567
|
-
> To discover available `category` values, check `actions[].category` in `context --json
|
|
603
|
+
> To discover available `category` values, check `actionOptions[].category` in `context --json-compact` first, and use `actions[].category` in `context --json` when needed.
|
|
568
604
|
|
|
569
605
|
### pr (PR artifacts policy)
|
|
570
606
|
|
package/README.md
CHANGED
|
@@ -55,26 +55,29 @@
|
|
|
55
55
|
# 1. 프로젝트 문서 구조 생성
|
|
56
56
|
npx lee-spec-kit init
|
|
57
57
|
|
|
58
|
-
# 2.
|
|
58
|
+
# 2. 초기 온보딩 점검
|
|
59
|
+
npx lee-spec-kit onboard --strict
|
|
60
|
+
|
|
61
|
+
# 3. 새 기능 생성
|
|
59
62
|
npx lee-spec-kit feature user-auth
|
|
60
63
|
|
|
61
|
-
#
|
|
64
|
+
# 4. 진행 상황 및 다음 단계 확인 (AI 에이전트용)
|
|
62
65
|
npx lee-spec-kit context
|
|
63
66
|
|
|
64
|
-
#
|
|
67
|
+
# 5. 워크플로우 대시보드 확인
|
|
65
68
|
npx lee-spec-kit view
|
|
66
69
|
|
|
67
|
-
#
|
|
70
|
+
# 6. 전체 상태 확인
|
|
68
71
|
npx lee-spec-kit status
|
|
69
72
|
|
|
70
|
-
#
|
|
73
|
+
# 7. 문서/Feature 진단
|
|
71
74
|
npx lee-spec-kit doctor
|
|
72
75
|
```
|
|
73
76
|
|
|
74
77
|
## 신규 프로젝트 시작 순서
|
|
75
78
|
|
|
76
79
|
신규 프로젝트에서는 **코드 스캐폴딩을 먼저** 하고, 그 다음 docs를 초기화하세요.
|
|
77
|
-
|
|
80
|
+
대부분의 사용자(기본값: embedded)는 프로젝트 루트에서 `npx lee-spec-kit init`만 실행하면 됩니다.
|
|
78
81
|
|
|
79
82
|
```bash
|
|
80
83
|
# 0. 코드 프로젝트 생성/초기화 (예: Next.js)
|
|
@@ -84,17 +87,32 @@ cd my-app
|
|
|
84
87
|
# 1. docs 구조 초기화
|
|
85
88
|
npx lee-spec-kit init
|
|
86
89
|
|
|
87
|
-
# 2.
|
|
90
|
+
# 2. 초기 온보딩 점검
|
|
91
|
+
npx lee-spec-kit onboard --strict
|
|
92
|
+
|
|
93
|
+
# 3. 감지 확인 (에이전트 시작점)
|
|
88
94
|
npx lee-spec-kit detect --json
|
|
89
95
|
|
|
90
|
-
#
|
|
96
|
+
# 4. Feature 생성 후 작업 시작
|
|
91
97
|
npx lee-spec-kit feature user-auth
|
|
92
|
-
npx lee-spec-kit context --json
|
|
98
|
+
npx lee-spec-kit context --json-compact
|
|
93
99
|
```
|
|
94
100
|
|
|
95
101
|
- `detect --json` 결과가 `isLeeSpecKitProject: true`일 때 lee-spec-kit 워크플로우를 적용하세요.
|
|
96
102
|
- `isLeeSpecKitProject: false`면 일반 프로젝트 워크플로우로 진행하세요.
|
|
97
|
-
|
|
103
|
+
|
|
104
|
+
docs를 코드 저장소와 분리해 운영하는 팀(standalone)은 **상위 워크스페이스 폴더**를 기준으로 시작하는 것을 권장합니다.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 권장 레이아웃:
|
|
108
|
+
# workspace/
|
|
109
|
+
# ├─ docs/ (lee-spec-kit 문서)
|
|
110
|
+
# └─ project/ (실제 코드 저장소)
|
|
111
|
+
#
|
|
112
|
+
# workspace 루트에서 실행
|
|
113
|
+
npx lee-spec-kit init --docs-repo standalone --dir ./docs --project-root ./project
|
|
114
|
+
npx lee-spec-kit detect --json
|
|
115
|
+
```
|
|
98
116
|
|
|
99
117
|
## 에이전트 킥오프 프롬프트
|
|
100
118
|
|
|
@@ -103,7 +121,7 @@ npx lee-spec-kit context --json
|
|
|
103
121
|
```text
|
|
104
122
|
작업 시작 절차:
|
|
105
123
|
1) npx lee-spec-kit detect --json
|
|
106
|
-
2) isLeeSpecKitProject === true 이면 npx lee-spec-kit context --json 실행
|
|
124
|
+
2) isLeeSpecKitProject === true 이면 npx lee-spec-kit context --json-compact 실행 (필요 시 --json으로 상세 조회)
|
|
107
125
|
3) actionOptions가 있으면 approvalPrompt와 finalPrompt를 그대로 사용자에게 제시하고 승인(<LABEL> 또는 <LABEL> OK) 대기
|
|
108
126
|
4) 승인 전에는 실행하지 말고, requiresUserCheck=true 액션은 승인 후에만 실행
|
|
109
127
|
5) isLeeSpecKitProject === false 이면 lee-spec-kit 전용 절차를 건너뛰고 일반 워크플로우로 진행
|
|
@@ -114,7 +132,7 @@ npx lee-spec-kit context --json
|
|
|
114
132
|
### 📁 프로젝트 초기화
|
|
115
133
|
|
|
116
134
|
- 대화형 모드 또는 CLI 옵션으로 프로젝트 설정
|
|
117
|
-
- Single
|
|
135
|
+
- 기본값은 Multi이며, Single도 단순 단일 레포/기존 호환 시나리오를 위해 지원합니다. (`fullstack`는 `multi` 하위호환 alias)
|
|
118
136
|
- 한국어/영어 템플릿 선택
|
|
119
137
|
|
|
120
138
|
### 🚀 Feature 생성
|
|
@@ -196,6 +214,21 @@ npx lee-spec-kit detect --dir /path/to/workspace
|
|
|
196
214
|
|
|
197
215
|
`--json` 출력은 `isLeeSpecKitProject`, `reasonCode`(`PROJECT_DETECTED` | `PROJECT_NOT_DETECTED`), `docsDir`, `configPath`, `detectionSource`(`config` | `heuristic`)를 포함합니다.
|
|
198
216
|
|
|
217
|
+
### 온보딩 점검
|
|
218
|
+
|
|
219
|
+
초기 설정(Constitution/PRD/Git remote 등) 준비 상태를 점검합니다.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# 사람 읽기 쉬운 출력
|
|
223
|
+
npx lee-spec-kit onboard
|
|
224
|
+
|
|
225
|
+
# 에이전트/자동화용 JSON
|
|
226
|
+
npx lee-spec-kit onboard --json
|
|
227
|
+
|
|
228
|
+
# WARN/BLOCK이 있으면 종료 코드 1
|
|
229
|
+
npx lee-spec-kit onboard --strict
|
|
230
|
+
```
|
|
231
|
+
|
|
199
232
|
### 새 기능 생성
|
|
200
233
|
|
|
201
234
|
```bash
|
|
@@ -233,6 +266,7 @@ npx lee-spec-kit context
|
|
|
233
266
|
# 특정 Feature 상태 + 라벨 확인 (에이전트 권장)
|
|
234
267
|
npx lee-spec-kit context F001
|
|
235
268
|
npx lee-spec-kit context F001 --json
|
|
269
|
+
npx lee-spec-kit context F001 --json-compact
|
|
236
270
|
|
|
237
271
|
# 승인 + 실행 (일반 케이스)
|
|
238
272
|
npx lee-spec-kit context F001 --approve A --execute
|
|
@@ -251,6 +285,7 @@ npx lee-spec-kit context F001 --approve A --execute --ticket <TICKET> --execute-
|
|
|
251
285
|
| 옵션 | 설명 |
|
|
252
286
|
| --------------- | ----------------------------------------------- |
|
|
253
287
|
| `--json` | 에이전트용 JSON 출력 |
|
|
288
|
+
| `--json-compact` | 에이전트용 압축 JSON 출력 (`--json` 포함, 중복 필드 최소화) |
|
|
254
289
|
| `--component <id>` | multi에서 대상 컴포넌트 지정 (예: `app`, `api`, `worker`) |
|
|
255
290
|
| `--all` | 자동 감지 실패 시 완료된 Feature까지 포함해서 표시 |
|
|
256
291
|
| `--done` | 완료(workflow-done) Feature만 표시 |
|
|
@@ -265,7 +300,8 @@ npx lee-spec-kit context F001 --approve A --execute --ticket <TICKET> --execute-
|
|
|
265
300
|
- 선택한 액션이 `requiresUserCheck=true`인 경우에만 `--execute`에서 `--ticket`이 필요합니다.
|
|
266
301
|
- 발급 후 짧은 시간(기본 5분)만 유효하며, 한 번 사용하면 재사용할 수 없습니다.
|
|
267
302
|
|
|
268
|
-
`context --json
|
|
303
|
+
기본 권장 포맷은 `context --json-compact`이며, 같은 판단 정보를 중복 없이 축약해 전달합니다.
|
|
304
|
+
`context --json`은 디버깅/세부 필드 확인이 필요할 때만 사용하세요.
|
|
269
305
|
|
|
270
306
|
**핵심 필드 (실사용 권장)**
|
|
271
307
|
|
|
@@ -526,8 +562,8 @@ npx lee-spec-kit update --force
|
|
|
526
562
|
`approval`은 `context`가 출력하는 다음 값에만 영향을 줍니다:
|
|
527
563
|
|
|
528
564
|
- 텍스트 출력의 `[확인 필요]` 표시
|
|
529
|
-
- `context --json`의 `actions[].requiresUserCheck`
|
|
530
|
-
- `checkPolicy.token` (`context --json`): 승인 토큰 형식 (`<LABEL>`)
|
|
565
|
+
- `context --json-compact`의 `actionOptions[].requiresUserCheck` (`--json`에서는 `actions[].requiresUserCheck`)
|
|
566
|
+
- `checkPolicy.token` (`context --json-compact`/`--json`): 승인 토큰 형식 (`<LABEL>`)
|
|
531
567
|
- `checkPolicy.acceptedTokens`: 허용되는 승인 응답 템플릿 (예: `["<LABEL>", "<LABEL> OK", "<LABEL> ...", "... <LABEL> ..."]`)
|
|
532
568
|
- `checkPolicy.tokenPattern`: 승인 응답 검증용 정규식
|
|
533
569
|
- `checkPolicy.validLabels`: 현재 선택 가능한 라벨 목록 (`A`, `B`, `C`...)
|
|
@@ -584,7 +620,7 @@ npx lee-spec-kit update --force
|
|
|
584
620
|
#### 모드
|
|
585
621
|
|
|
586
622
|
- `builtin` (기본): 코드에 내장된 `requiresUserCheck`를 그대로 사용
|
|
587
|
-
- `category` (권장): `
|
|
623
|
+
- `category` (권장): `actionOptions[].category` 기준으로 확인 정책을 제어 (`--json`에서는 `actions[].category`)
|
|
588
624
|
- `steps`: step 번호 기준(변경에 취약하므로 권장하지 않음)
|
|
589
625
|
|
|
590
626
|
#### 설정 필드
|
|
@@ -612,7 +648,7 @@ npx lee-spec-kit update --force
|
|
|
612
648
|
}
|
|
613
649
|
```
|
|
614
650
|
|
|
615
|
-
> 사용 가능한 `category` 값은 `context --json`의 `
|
|
651
|
+
> 사용 가능한 `category` 값은 `context --json-compact`의 `actionOptions[].category`에서 우선 확인하고, 필요 시 `context --json`의 `actions[].category`를 참고하세요.
|
|
616
652
|
|
|
617
653
|
### pr (PR 결과물 정책)
|
|
618
654
|
|
package/dist/index.js
CHANGED
|
@@ -139,6 +139,7 @@ var I18N = {
|
|
|
139
139
|
"context.suggestionCommandHint": "\uB77C\uBCA8 \uCC38\uACE0 \uBA85\uB839: {command}",
|
|
140
140
|
"context.suggestionFinalPrompt": "\uD604\uC7AC \uCD94\uCC9C \uB77C\uBCA8: {labels}. \uC751\uB2F5\uC740 \uB77C\uBCA8 \uD1A0\uD070 \uD3EC\uD568 \uD615\uC2DD\uC73C\uB85C \uD574\uC8FC\uC138\uC694. (\uC608: {example}, `A \uC9C4\uD589\uD574`)",
|
|
141
141
|
"context.suggestion.createFeature": "\uC0C8 Feature\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4",
|
|
142
|
+
"context.suggestion.runOnboard": "\uCD08\uAE30 \uC124\uC815 \uC810\uAC80(onboard)\uC744 \uC2E4\uD589\uD569\uB2C8\uB2E4",
|
|
142
143
|
"context.suggestion.showDone": "\uC644\uB8CC\uB41C Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
|
|
143
144
|
"context.suggestion.showAll": "\uC804\uCCB4 Feature \uBAA9\uB85D\uC744 \uD655\uC778\uD569\uB2C8\uB2E4",
|
|
144
145
|
"context.suggestion.selectFeature": "\uC9C4\uD589\uD560 Feature\uB97C \uC120\uD0DD\uD574 \uC0C1\uC138 \uCEE8\uD14D\uC2A4\uD2B8\uB97C \uC5FD\uB2C8\uB2E4",
|
|
@@ -202,6 +203,7 @@ var I18N = {
|
|
|
202
203
|
"init.log.nextStepsTitle": "\uB2E4\uC74C \uB2E8\uACC4:",
|
|
203
204
|
"init.log.nextSteps1": " 1. {docsDir}/prd/README.md \uC791\uC131",
|
|
204
205
|
"init.log.nextSteps2": " 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00",
|
|
206
|
+
"init.log.nextSteps3": " 3. npx lee-spec-kit onboard --strict \uB85C \uCD08\uAE30 \uC124\uC815 \uC810\uAC80",
|
|
205
207
|
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911...",
|
|
206
208
|
"init.log.gitInit": "\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911...",
|
|
207
209
|
"init.warn.stagedChangesSkip": '\u26A0\uFE0F \uD604\uC7AC Git index\uC5D0 \uC774\uBBF8 stage\uB41C \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. (--dir "." \uC778 \uACBD\uC6B0 \uCEE4\uBC0B \uBC94\uC704\uB97C \uC548\uC804\uD558\uAC8C \uC81C\uD55C\uD560 \uC218 \uC5C6\uC5B4 \uC790\uB3D9 \uCEE4\uBC0B\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4)',
|
|
@@ -589,6 +591,7 @@ var I18N = {
|
|
|
589
591
|
"context.suggestionCommandHint": "Reference command: {command}",
|
|
590
592
|
"context.suggestionFinalPrompt": "Recommended labels now: {labels}. Please reply with a format that includes a label token. (e.g. {example}, `A proceed`)",
|
|
591
593
|
"context.suggestion.createFeature": "Create a new feature",
|
|
594
|
+
"context.suggestion.runOnboard": "Run onboarding checks",
|
|
592
595
|
"context.suggestion.showDone": "Show completed features",
|
|
593
596
|
"context.suggestion.showAll": "Show all features",
|
|
594
597
|
"context.suggestion.selectFeature": "Select a feature and open detailed context",
|
|
@@ -652,6 +655,7 @@ var I18N = {
|
|
|
652
655
|
"init.log.nextStepsTitle": "Next steps:",
|
|
653
656
|
"init.log.nextSteps1": " 1. Write {docsDir}/prd/README.md",
|
|
654
657
|
"init.log.nextSteps2": " 2. Add a feature with: npx lee-spec-kit feature <name>",
|
|
658
|
+
"init.log.nextSteps3": " 3. Run setup checks: npx lee-spec-kit onboard --strict",
|
|
655
659
|
"init.log.gitRepoDetectedCommit": "\u{1F4E6} Git repo detected, committing docs...",
|
|
656
660
|
"init.log.gitInit": "\u{1F4E6} Initializing Git...",
|
|
657
661
|
"init.warn.stagedChangesSkip": '\u26A0\uFE0F There are already staged changes in the Git index. (With --dir ".", commit scope cannot be safely restricted, so auto-commit is skipped.)',
|
|
@@ -2152,6 +2156,7 @@ async function runInit(options) {
|
|
|
2152
2156
|
chalk6.gray(tr(lang, "cli", "init.log.nextSteps1", { docsDir: targetDir }))
|
|
2153
2157
|
);
|
|
2154
2158
|
console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps2")));
|
|
2159
|
+
console.log(chalk6.gray(tr(lang, "cli", "init.log.nextSteps3")));
|
|
2155
2160
|
console.log();
|
|
2156
2161
|
},
|
|
2157
2162
|
{ owner: "init" }
|
|
@@ -2159,7 +2164,8 @@ async function runInit(options) {
|
|
|
2159
2164
|
}
|
|
2160
2165
|
async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
2161
2166
|
try {
|
|
2162
|
-
const
|
|
2167
|
+
const gitWorkdir = docsRepo === "standalone" ? targetDir : cwd;
|
|
2168
|
+
const runGit2 = (args, workdir) => {
|
|
2163
2169
|
execFileSync("git", args, { cwd: workdir, stdio: "ignore" });
|
|
2164
2170
|
};
|
|
2165
2171
|
const getCachedStagedFiles = (workdir) => {
|
|
@@ -2205,14 +2211,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2205
2211
|
}
|
|
2206
2212
|
};
|
|
2207
2213
|
try {
|
|
2208
|
-
|
|
2214
|
+
runGit2(["rev-parse", "--is-inside-work-tree"], gitWorkdir);
|
|
2209
2215
|
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitRepoDetectedCommit")));
|
|
2210
2216
|
} catch {
|
|
2211
2217
|
console.log(chalk6.blue(tr(lang, "cli", "init.log.gitInit")));
|
|
2212
|
-
|
|
2218
|
+
runGit2(["init"], gitWorkdir);
|
|
2213
2219
|
}
|
|
2214
|
-
const relativePath = path19.relative(cwd, targetDir);
|
|
2215
|
-
const stagedBeforeAdd = getCachedStagedFiles(
|
|
2220
|
+
const relativePath = docsRepo === "standalone" ? "." : path19.relative(cwd, targetDir);
|
|
2221
|
+
const stagedBeforeAdd = getCachedStagedFiles(gitWorkdir);
|
|
2216
2222
|
if (relativePath === "." && stagedBeforeAdd && stagedBeforeAdd.length > 0) {
|
|
2217
2223
|
console.log(
|
|
2218
2224
|
chalk6.yellow(
|
|
@@ -2223,8 +2229,8 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2223
2229
|
console.log();
|
|
2224
2230
|
return;
|
|
2225
2231
|
}
|
|
2226
|
-
if (relativePath !== "." && isPathIgnored(
|
|
2227
|
-
const repoRelativePath = toRepoRelativePath(
|
|
2232
|
+
if (relativePath !== "." && isPathIgnored(gitWorkdir, relativePath)) {
|
|
2233
|
+
const repoRelativePath = toRepoRelativePath(gitWorkdir, relativePath);
|
|
2228
2234
|
console.log(
|
|
2229
2235
|
chalk6.yellow(
|
|
2230
2236
|
tr(lang, "cli", "init.warn.docsPathIgnoredSkipCommit", {
|
|
@@ -2242,14 +2248,14 @@ async function initGit(cwd, targetDir, docsRepo, lang, pushDocs, docsRemote) {
|
|
|
2242
2248
|
console.log();
|
|
2243
2249
|
return;
|
|
2244
2250
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2251
|
+
runGit2(["add", relativePath], gitWorkdir);
|
|
2252
|
+
runGit2(
|
|
2247
2253
|
["commit", "-m", "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)", "--", relativePath],
|
|
2248
|
-
|
|
2254
|
+
gitWorkdir
|
|
2249
2255
|
);
|
|
2250
2256
|
if (docsRepo === "standalone" && pushDocs && docsRemote) {
|
|
2251
2257
|
try {
|
|
2252
|
-
|
|
2258
|
+
runGit2(["remote", "add", "origin", docsRemote], gitWorkdir);
|
|
2253
2259
|
console.log(
|
|
2254
2260
|
chalk6.green(tr(lang, "cli", "init.log.gitRemoteSet", { remote: docsRemote }))
|
|
2255
2261
|
);
|
|
@@ -6376,9 +6382,14 @@ function buildSuggestionOptions(lang, state, projectType, selectedComponent) {
|
|
|
6376
6382
|
const showDoneCommand = `npx lee-spec-kit context --done${componentArg}`;
|
|
6377
6383
|
const showAllCommand = `npx lee-spec-kit context --all${componentArg}`;
|
|
6378
6384
|
const showOpenCommand = `npx lee-spec-kit context${componentArg}`;
|
|
6385
|
+
const runOnboardCommand = "npx lee-spec-kit onboard --strict";
|
|
6379
6386
|
const rawSuggestions = [];
|
|
6380
6387
|
switch (state.status) {
|
|
6381
6388
|
case "no_features":
|
|
6389
|
+
rawSuggestions.push({
|
|
6390
|
+
summary: tr(lang, "cli", "context.suggestion.runOnboard"),
|
|
6391
|
+
command: runOnboardCommand
|
|
6392
|
+
});
|
|
6382
6393
|
rawSuggestions.push({
|
|
6383
6394
|
summary: tr(lang, "cli", "context.suggestion.createFeature"),
|
|
6384
6395
|
command: createFeatureCommand
|
|
@@ -6487,7 +6498,10 @@ function getCommandExecutionLockPath(action, config) {
|
|
|
6487
6498
|
return getProjectExecutionLockPath(action.cwd);
|
|
6488
6499
|
}
|
|
6489
6500
|
function contextCommand(program2) {
|
|
6490
|
-
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option(
|
|
6501
|
+
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option(
|
|
6502
|
+
"--json-compact",
|
|
6503
|
+
"Output compact JSON for agents (implies --json, reduced duplication)"
|
|
6504
|
+
).option("--component <component>", "Component name for multi projects").option("--all", "Include completed features when auto-detecting").option("--done", "Show completed (workflow-done) features only").option(
|
|
6491
6505
|
"--approve <reply>",
|
|
6492
6506
|
"Approve one labeled option (examples: A, A OK, A proceed, A \uC9C4\uD589\uD574)"
|
|
6493
6507
|
).option(
|
|
@@ -6508,7 +6522,7 @@ function contextCommand(program2) {
|
|
|
6508
6522
|
const lang = config?.lang ?? DEFAULT_LANG;
|
|
6509
6523
|
const cliError = toCliError(error);
|
|
6510
6524
|
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
6511
|
-
if (options.json) {
|
|
6525
|
+
if (options.json || options.jsonCompact) {
|
|
6512
6526
|
console.log(
|
|
6513
6527
|
JSON.stringify({
|
|
6514
6528
|
status: "error",
|
|
@@ -6600,6 +6614,111 @@ function getMultipleFeaturesRecommendation(projectType, selectedComponent) {
|
|
|
6600
6614
|
}
|
|
6601
6615
|
return "Multiple features detected across components. Please specify feature name (slug | F001 | F001-slug) or use --component.";
|
|
6602
6616
|
}
|
|
6617
|
+
function getFeatureRef(feature) {
|
|
6618
|
+
return feature.folderName || `${feature.type}:${feature.slug}`;
|
|
6619
|
+
}
|
|
6620
|
+
function toCompactFeature(feature) {
|
|
6621
|
+
if (!feature) return null;
|
|
6622
|
+
return {
|
|
6623
|
+
ref: getFeatureRef(feature),
|
|
6624
|
+
id: feature.id ?? null,
|
|
6625
|
+
slug: feature.slug,
|
|
6626
|
+
folderName: feature.folderName,
|
|
6627
|
+
type: feature.type,
|
|
6628
|
+
path: feature.path,
|
|
6629
|
+
currentStep: feature.currentStep,
|
|
6630
|
+
nextAction: feature.nextAction,
|
|
6631
|
+
completion: feature.completion,
|
|
6632
|
+
specStatus: feature.specStatus,
|
|
6633
|
+
planStatus: feature.planStatus,
|
|
6634
|
+
tasks: feature.tasks,
|
|
6635
|
+
prePrReview: {
|
|
6636
|
+
status: feature.prePrReview.status,
|
|
6637
|
+
findings: feature.prePrReview.findings,
|
|
6638
|
+
evidenceProvided: feature.prePrReview.evidenceProvided
|
|
6639
|
+
},
|
|
6640
|
+
prReview: {
|
|
6641
|
+
findings: feature.prReview.findings,
|
|
6642
|
+
evidenceProvided: feature.prReview.evidenceProvided
|
|
6643
|
+
},
|
|
6644
|
+
pr: {
|
|
6645
|
+
link: feature.pr.link,
|
|
6646
|
+
status: feature.pr.status,
|
|
6647
|
+
remote: feature.pr.remote
|
|
6648
|
+
},
|
|
6649
|
+
git: {
|
|
6650
|
+
docsBranch: feature.git.docsBranch,
|
|
6651
|
+
projectBranch: feature.git.projectBranch,
|
|
6652
|
+
projectBranchAvailable: feature.git.projectBranchAvailable,
|
|
6653
|
+
onExpectedBranch: feature.git.onExpectedBranch,
|
|
6654
|
+
docsEverCommitted: feature.git.docsEverCommitted,
|
|
6655
|
+
docsHasUncommittedChanges: feature.git.docsHasUncommittedChanges,
|
|
6656
|
+
projectHasUncommittedChanges: feature.git.projectHasUncommittedChanges,
|
|
6657
|
+
docsPathIgnored: feature.git.docsPathIgnored
|
|
6658
|
+
},
|
|
6659
|
+
docs: {
|
|
6660
|
+
specExists: feature.docs.specExists,
|
|
6661
|
+
planExists: feature.docs.planExists,
|
|
6662
|
+
tasksExists: feature.docs.tasksExists,
|
|
6663
|
+
issueDocIssueFieldExists: feature.docs.issueDocIssueFieldExists,
|
|
6664
|
+
prDocPrFieldExists: feature.docs.prDocPrFieldExists,
|
|
6665
|
+
prDocReviewStatusFieldExists: feature.docs.prDocReviewStatusFieldExists,
|
|
6666
|
+
prFieldExists: feature.docs.prFieldExists,
|
|
6667
|
+
prStatusFieldExists: feature.docs.prStatusFieldExists,
|
|
6668
|
+
prePrReviewFieldExists: feature.docs.prePrReviewFieldExists,
|
|
6669
|
+
prePrFindingsFieldExists: feature.docs.prePrFindingsFieldExists,
|
|
6670
|
+
prePrEvidenceFieldExists: feature.docs.prePrEvidenceFieldExists,
|
|
6671
|
+
prReviewFindingsFieldExists: feature.docs.prReviewFindingsFieldExists,
|
|
6672
|
+
prReviewEvidenceFieldExists: feature.docs.prReviewEvidenceFieldExists
|
|
6673
|
+
},
|
|
6674
|
+
warnings: feature.warnings
|
|
6675
|
+
};
|
|
6676
|
+
}
|
|
6677
|
+
function toCompactActionOption(option) {
|
|
6678
|
+
const base = {
|
|
6679
|
+
label: option.label,
|
|
6680
|
+
summary: option.summary,
|
|
6681
|
+
detail: option.detail,
|
|
6682
|
+
approvalPrompt: option.approvalPrompt,
|
|
6683
|
+
actionType: option.action.type,
|
|
6684
|
+
category: option.action.category,
|
|
6685
|
+
operationType: option.action.operationType,
|
|
6686
|
+
requiresUserCheck: !!option.action.requiresUserCheck
|
|
6687
|
+
};
|
|
6688
|
+
if (option.action.type === "command") {
|
|
6689
|
+
base.scope = option.action.scope;
|
|
6690
|
+
base.cwd = option.action.cwd;
|
|
6691
|
+
base.cmd = option.action.cmd;
|
|
6692
|
+
return base;
|
|
6693
|
+
}
|
|
6694
|
+
base.message = option.action.message;
|
|
6695
|
+
return base;
|
|
6696
|
+
}
|
|
6697
|
+
function toCompactSuggestionOption(option) {
|
|
6698
|
+
return {
|
|
6699
|
+
label: option.label,
|
|
6700
|
+
summary: option.summary,
|
|
6701
|
+
command: option.command
|
|
6702
|
+
};
|
|
6703
|
+
}
|
|
6704
|
+
function resolveContextRecommendation(state, projectType, selectedComponent) {
|
|
6705
|
+
if (state.status === "multiple_active") {
|
|
6706
|
+
return getMultipleFeaturesRecommendation(projectType, selectedComponent);
|
|
6707
|
+
}
|
|
6708
|
+
if (state.status === "no_features") {
|
|
6709
|
+
return "No features found. Run onboarding checks first, then create a feature.";
|
|
6710
|
+
}
|
|
6711
|
+
if (state.status === "no_open") {
|
|
6712
|
+
return "No open features found. Use `context --done` to inspect completed features.";
|
|
6713
|
+
}
|
|
6714
|
+
if (state.status === "no_match") {
|
|
6715
|
+
return "No features found.";
|
|
6716
|
+
}
|
|
6717
|
+
if (state.targetFeatures.length === 1) {
|
|
6718
|
+
return state.targetFeatures[0].nextAction;
|
|
6719
|
+
}
|
|
6720
|
+
return "No matched feature.";
|
|
6721
|
+
}
|
|
6603
6722
|
async function runContext(featureName, options) {
|
|
6604
6723
|
const cwd = process.cwd();
|
|
6605
6724
|
const config = await getConfig(cwd);
|
|
@@ -6659,7 +6778,8 @@ async function runContext(featureName, options) {
|
|
|
6659
6778
|
);
|
|
6660
6779
|
return;
|
|
6661
6780
|
}
|
|
6662
|
-
|
|
6781
|
+
const jsonMode = !!options.json || !!options.jsonCompact;
|
|
6782
|
+
if (jsonMode) {
|
|
6663
6783
|
const primaryAction = state.actionOptions[0] ?? null;
|
|
6664
6784
|
const finalApprovalPrompt = buildFinalApprovalPrompt(lang, state.actionOptions);
|
|
6665
6785
|
const approveCommand = buildApprovalCommand(
|
|
@@ -6674,6 +6794,76 @@ async function runContext(featureName, options) {
|
|
|
6674
6794
|
selectedComponent,
|
|
6675
6795
|
true
|
|
6676
6796
|
);
|
|
6797
|
+
const recommendation = resolveContextRecommendation(
|
|
6798
|
+
state,
|
|
6799
|
+
config.projectType,
|
|
6800
|
+
selectedComponent
|
|
6801
|
+
);
|
|
6802
|
+
if (options.jsonCompact) {
|
|
6803
|
+
const compactResult = {
|
|
6804
|
+
schema: "context.v2.compact",
|
|
6805
|
+
status: state.status,
|
|
6806
|
+
reasonCode: toReasonCode(state.status),
|
|
6807
|
+
selectionMode: state.selectionMode,
|
|
6808
|
+
selectionFallback: state.selectionFallback,
|
|
6809
|
+
branches: state.branches,
|
|
6810
|
+
warnings: state.warnings,
|
|
6811
|
+
contextVersion: state.contextVersion,
|
|
6812
|
+
matchedFeature: toCompactFeature(state.matchedFeature),
|
|
6813
|
+
candidateRefs: state.targetFeatures.length > 1 ? state.targetFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6814
|
+
completedCandidateRefs: state.selectionMode === "open" ? state.doneFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6815
|
+
openCandidateRefs: state.selectionMode === "open" ? state.openFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6816
|
+
inProgressCandidateRefs: state.selectionMode === "open" ? state.inProgressFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6817
|
+
readyToCloseCandidateRefs: state.selectionMode === "open" ? state.readyToCloseFeatures.map((feature) => getFeatureRef(feature)) : [],
|
|
6818
|
+
actionOptions: state.actionOptions.map((option) => toCompactActionOption(option)),
|
|
6819
|
+
suggestionOptions: suggestionOptions.map(
|
|
6820
|
+
(option) => toCompactSuggestionOption(option)
|
|
6821
|
+
),
|
|
6822
|
+
primaryActionLabel: primaryAction?.label ?? null,
|
|
6823
|
+
workflowPolicy,
|
|
6824
|
+
taskCommitGatePolicy,
|
|
6825
|
+
prePrReviewPolicy,
|
|
6826
|
+
checkPolicy: {
|
|
6827
|
+
docPath: "builtin://agents/policy",
|
|
6828
|
+
token: "<LABEL>",
|
|
6829
|
+
acceptedTokens: ["<LABEL>", "<LABEL> OK", "<LABEL> ...", "... <LABEL> ..."],
|
|
6830
|
+
tokenPattern: "^.*\\b([A-Z]+)\\b.*$",
|
|
6831
|
+
validLabels: state.actionOptions.map((o) => o.label),
|
|
6832
|
+
oneApprovalPerAction: true,
|
|
6833
|
+
requireFreshContext: true,
|
|
6834
|
+
contextVersion: state.contextVersion,
|
|
6835
|
+
config: config.approval ?? { mode: "builtin" }
|
|
6836
|
+
},
|
|
6837
|
+
approvalRequest: {
|
|
6838
|
+
finalPrompt: finalApprovalPrompt,
|
|
6839
|
+
userFacingLines: [
|
|
6840
|
+
...state.actionOptions.map((o) => o.approvalPrompt),
|
|
6841
|
+
finalApprovalPrompt
|
|
6842
|
+
].filter((line) => line.length > 0),
|
|
6843
|
+
labels: state.actionOptions.map((o) => o.label),
|
|
6844
|
+
approveCommand,
|
|
6845
|
+
executeCommand,
|
|
6846
|
+
executeRequiresTicket: !!state.actionOptions[0]?.action?.requiresUserCheck
|
|
6847
|
+
},
|
|
6848
|
+
suggestionRequest: {
|
|
6849
|
+
finalPrompt: suggestionFinalPrompt,
|
|
6850
|
+
userFacingLines: [
|
|
6851
|
+
...suggestionOptions.map((o) => `${o.label}: ${o.summary}`),
|
|
6852
|
+
suggestionFinalPrompt
|
|
6853
|
+
].filter((line) => line.length > 0),
|
|
6854
|
+
labels: suggestionOptions.map((o) => o.label)
|
|
6855
|
+
},
|
|
6856
|
+
prPolicy: {
|
|
6857
|
+
screenshots: {
|
|
6858
|
+
upload: config.pr?.screenshots?.upload ?? false
|
|
6859
|
+
}
|
|
6860
|
+
},
|
|
6861
|
+
requiredDocs,
|
|
6862
|
+
recommendation
|
|
6863
|
+
};
|
|
6864
|
+
console.log(JSON.stringify(compactResult, null, 2));
|
|
6865
|
+
return;
|
|
6866
|
+
}
|
|
6677
6867
|
const result = {
|
|
6678
6868
|
status: state.status,
|
|
6679
6869
|
reasonCode: toReasonCode(state.status),
|
|
@@ -6765,24 +6955,8 @@ async function runContext(featureName, options) {
|
|
|
6765
6955
|
}
|
|
6766
6956
|
},
|
|
6767
6957
|
requiredDocs,
|
|
6768
|
-
recommendation
|
|
6958
|
+
recommendation
|
|
6769
6959
|
};
|
|
6770
|
-
if (result.status === "multiple_active") {
|
|
6771
|
-
result.recommendation = getMultipleFeaturesRecommendation(
|
|
6772
|
-
config.projectType,
|
|
6773
|
-
selectedComponent
|
|
6774
|
-
);
|
|
6775
|
-
} else if (result.status === "no_features") {
|
|
6776
|
-
result.recommendation = "No features found. Create a feature first.";
|
|
6777
|
-
} else if (result.status === "no_open") {
|
|
6778
|
-
result.recommendation = "No open features found. Use `context --done` to inspect completed features.";
|
|
6779
|
-
} else if (result.status === "no_match") {
|
|
6780
|
-
result.recommendation = "No features found.";
|
|
6781
|
-
} else if (state.targetFeatures.length === 1) {
|
|
6782
|
-
result.recommendation = state.targetFeatures[0].nextAction;
|
|
6783
|
-
} else {
|
|
6784
|
-
result.recommendation = "No matched feature.";
|
|
6785
|
-
}
|
|
6786
6960
|
console.log(JSON.stringify(result, null, 2));
|
|
6787
6961
|
return;
|
|
6788
6962
|
}
|
|
@@ -7055,6 +7229,7 @@ async function runContext(featureName, options) {
|
|
|
7055
7229
|
async function runApprovedOption(state, config, lang, featureName, selectionOptions, options) {
|
|
7056
7230
|
const approval = options.approve || "";
|
|
7057
7231
|
const ticketToken = (options.ticket || "").trim();
|
|
7232
|
+
const jsonMode = !!options.json || !!options.jsonCompact;
|
|
7058
7233
|
let parsedLabel = null;
|
|
7059
7234
|
if (state.status !== "single_matched" || !state.matchedFeature) {
|
|
7060
7235
|
throw createCliError(
|
|
@@ -7115,7 +7290,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7115
7290
|
label: parsedLabel,
|
|
7116
7291
|
featureRef
|
|
7117
7292
|
}) : null;
|
|
7118
|
-
if (
|
|
7293
|
+
if (jsonMode) {
|
|
7119
7294
|
console.log(
|
|
7120
7295
|
JSON.stringify(
|
|
7121
7296
|
{
|
|
@@ -7194,7 +7369,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7194
7369
|
`Approved label "${parsedLabel}" is instruction-only. Re-run without \`--execute\` or pick a command option.`
|
|
7195
7370
|
);
|
|
7196
7371
|
}
|
|
7197
|
-
if (
|
|
7372
|
+
if (jsonMode) {
|
|
7198
7373
|
console.log(
|
|
7199
7374
|
JSON.stringify(
|
|
7200
7375
|
{
|
|
@@ -7219,7 +7394,7 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7219
7394
|
console.log();
|
|
7220
7395
|
return;
|
|
7221
7396
|
}
|
|
7222
|
-
if (!
|
|
7397
|
+
if (!jsonMode) {
|
|
7223
7398
|
console.log();
|
|
7224
7399
|
console.log(chalk6.blue(`\u25B6 Executing option ${parsedLabel}...`));
|
|
7225
7400
|
console.log(chalk6.gray(` ${selectedAction.cmd}`));
|
|
@@ -7231,12 +7406,12 @@ async function runApprovedOption(state, config, lang, featureName, selectionOpti
|
|
|
7231
7406
|
lockPath,
|
|
7232
7407
|
async () => executeCommandAction(
|
|
7233
7408
|
selectedAction.cmd,
|
|
7234
|
-
|
|
7409
|
+
jsonMode,
|
|
7235
7410
|
selectedAction.cwd
|
|
7236
7411
|
),
|
|
7237
7412
|
{ owner: `context-execute:${selectedAction.scope}` }
|
|
7238
7413
|
);
|
|
7239
|
-
if (
|
|
7414
|
+
if (jsonMode) {
|
|
7240
7415
|
console.log(
|
|
7241
7416
|
JSON.stringify(
|
|
7242
7417
|
{
|
|
@@ -10054,6 +10229,459 @@ async function runDetect(options) {
|
|
|
10054
10229
|
}
|
|
10055
10230
|
console.log();
|
|
10056
10231
|
}
|
|
10232
|
+
function t(lang, ko, en) {
|
|
10233
|
+
return lang === "ko" ? ko : en;
|
|
10234
|
+
}
|
|
10235
|
+
function quotePath(value) {
|
|
10236
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
10237
|
+
}
|
|
10238
|
+
function toSlug(value) {
|
|
10239
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
10240
|
+
}
|
|
10241
|
+
function runGit(args, cwd) {
|
|
10242
|
+
try {
|
|
10243
|
+
return execFileSync("git", args, {
|
|
10244
|
+
cwd,
|
|
10245
|
+
encoding: "utf-8",
|
|
10246
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
10247
|
+
}).trim();
|
|
10248
|
+
} catch {
|
|
10249
|
+
return void 0;
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
function isGitRepo(cwd) {
|
|
10253
|
+
return runGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
|
|
10254
|
+
}
|
|
10255
|
+
function hasHeadCommit(cwd) {
|
|
10256
|
+
return !!runGit(["rev-parse", "--verify", "HEAD"], cwd);
|
|
10257
|
+
}
|
|
10258
|
+
function getOriginUrl(cwd) {
|
|
10259
|
+
const out = runGit(["remote", "get-url", "origin"], cwd);
|
|
10260
|
+
return out || void 0;
|
|
10261
|
+
}
|
|
10262
|
+
function hasTemplateMarkers(content) {
|
|
10263
|
+
const patterns = [
|
|
10264
|
+
/\{\{projectName\}\}/,
|
|
10265
|
+
/\{\{date\}\}/,
|
|
10266
|
+
/\(Write your project mission here\)/,
|
|
10267
|
+
/\(Write project-specific architecture principles here/i,
|
|
10268
|
+
/\(Write project code quality standards here/i,
|
|
10269
|
+
/\(Write project security principles here/i,
|
|
10270
|
+
/\(Write your project-specific rules here\)/,
|
|
10271
|
+
/\(Override default rules or add additional rules here\)/,
|
|
10272
|
+
/\(Write project-specific workflows here\)/,
|
|
10273
|
+
/\(Write other rules here\)/,
|
|
10274
|
+
/\(프로젝트의 미션을 작성하세요\)/,
|
|
10275
|
+
/\(프로젝트별 아키텍처 원칙을 작성하세요/,
|
|
10276
|
+
/\(프로젝트의 코드 품질 기준을 작성하세요/,
|
|
10277
|
+
/\(프로젝트의 보안 원칙을 작성하세요/,
|
|
10278
|
+
/\(여기에 프로젝트만의 규칙을 작성하세요\)/,
|
|
10279
|
+
/\(기본 규칙을 오버라이드하거나 추가 규칙을 작성하세요\)/,
|
|
10280
|
+
/\(프로젝트만의 워크플로우가 있다면 작성하세요\)/,
|
|
10281
|
+
/\(기타 규칙을 작성하세요\)/
|
|
10282
|
+
];
|
|
10283
|
+
return patterns.some((pattern) => pattern.test(content));
|
|
10284
|
+
}
|
|
10285
|
+
async function countFeatureDirs(docsDir, projectType) {
|
|
10286
|
+
const pattern = projectType === "single" ? "features/*/" : "features/*/*/";
|
|
10287
|
+
const dirs = await glob(pattern, {
|
|
10288
|
+
cwd: docsDir,
|
|
10289
|
+
absolute: false,
|
|
10290
|
+
ignore: ["**/feature-base/**"]
|
|
10291
|
+
});
|
|
10292
|
+
return dirs.map((value) => value.replace(/\\/g, "/").replace(/\/+$/, "")).filter((value) => {
|
|
10293
|
+
const base = path19.posix.basename(value);
|
|
10294
|
+
return !!base && base !== "feature-base";
|
|
10295
|
+
}).length;
|
|
10296
|
+
}
|
|
10297
|
+
async function hasUserPrdFile(prdDir) {
|
|
10298
|
+
if (!await fs15.pathExists(prdDir)) return false;
|
|
10299
|
+
const files = await glob("**/*.md", {
|
|
10300
|
+
cwd: prdDir,
|
|
10301
|
+
nodir: true,
|
|
10302
|
+
absolute: false,
|
|
10303
|
+
ignore: ["**/node_modules/**"]
|
|
10304
|
+
});
|
|
10305
|
+
return files.some((relativePath) => path19.basename(relativePath).toLowerCase() !== "readme.md");
|
|
10306
|
+
}
|
|
10307
|
+
function finalizeChecks(checks) {
|
|
10308
|
+
const summary = checks.reduce(
|
|
10309
|
+
(acc, check) => {
|
|
10310
|
+
acc[check.status] += 1;
|
|
10311
|
+
return acc;
|
|
10312
|
+
},
|
|
10313
|
+
{ ok: 0, warn: 0, block: 0 }
|
|
10314
|
+
);
|
|
10315
|
+
const status = summary.block > 0 ? "blocked" : summary.warn > 0 ? "needs_action" : "ready";
|
|
10316
|
+
return { checks, summary, status };
|
|
10317
|
+
}
|
|
10318
|
+
function printOnboardResult(lang, result) {
|
|
10319
|
+
console.log();
|
|
10320
|
+
console.log(chalk6.bold(t(lang, "\u{1F9ED} Onboarding \uC810\uAC80", "\u{1F9ED} Onboarding Checks")));
|
|
10321
|
+
for (const check of result.checks) {
|
|
10322
|
+
const mark = check.status === "ok" ? chalk6.green("\u2705") : check.status === "warn" ? chalk6.yellow("\u26A0\uFE0F") : chalk6.red("\u274C");
|
|
10323
|
+
const level = check.status === "ok" ? chalk6.green("OK") : check.status === "warn" ? chalk6.yellow("WARN") : chalk6.red("BLOCK");
|
|
10324
|
+
console.log(`${mark} [${level}] ${check.title}`);
|
|
10325
|
+
console.log(` ${check.message}`);
|
|
10326
|
+
if (check.path) console.log(chalk6.gray(` path: ${check.path}`));
|
|
10327
|
+
if (check.suggestedCommand) {
|
|
10328
|
+
console.log(chalk6.gray(` ${t(lang, "\uB2E4\uC74C \uBA85\uB839", "next")}: ${check.suggestedCommand}`));
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
console.log();
|
|
10332
|
+
console.log(
|
|
10333
|
+
chalk6.bold(
|
|
10334
|
+
t(
|
|
10335
|
+
lang,
|
|
10336
|
+
`\uC694\uC57D: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`,
|
|
10337
|
+
`Summary: OK ${result.summary.ok}, WARN ${result.summary.warn}, BLOCK ${result.summary.block}`
|
|
10338
|
+
)
|
|
10339
|
+
)
|
|
10340
|
+
);
|
|
10341
|
+
if (result.status === "ready") {
|
|
10342
|
+
console.log(chalk6.green(t(lang, "\uC628\uBCF4\uB529 \uC900\uBE44\uAC00 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Onboarding checks passed.")));
|
|
10343
|
+
} else if (result.status === "needs_action") {
|
|
10344
|
+
console.log(chalk6.yellow(t(lang, "\uCD94\uAC00 \uC815\uB9AC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", "Some onboarding actions are required.")));
|
|
10345
|
+
} else {
|
|
10346
|
+
console.log(chalk6.red(t(lang, "\uC628\uBCF4\uB529 \uC120\uD589 \uC791\uC5C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "Onboarding is blocked by required setup.")));
|
|
10347
|
+
}
|
|
10348
|
+
console.log();
|
|
10349
|
+
}
|
|
10350
|
+
async function runOnboardChecks(config) {
|
|
10351
|
+
const lang = config.lang;
|
|
10352
|
+
const checks = [];
|
|
10353
|
+
const docsDir = config.docsDir;
|
|
10354
|
+
const docsGitReady = isGitRepo(docsDir);
|
|
10355
|
+
if (!docsGitReady) {
|
|
10356
|
+
checks.push({
|
|
10357
|
+
id: "docs_git_repo",
|
|
10358
|
+
status: "block",
|
|
10359
|
+
title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
|
|
10360
|
+
message: t(
|
|
10361
|
+
lang,
|
|
10362
|
+
"docs \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
10363
|
+
"Docs directory is not a git repository."
|
|
10364
|
+
),
|
|
10365
|
+
path: docsDir,
|
|
10366
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} init`
|
|
10367
|
+
});
|
|
10368
|
+
} else {
|
|
10369
|
+
checks.push({
|
|
10370
|
+
id: "docs_git_repo",
|
|
10371
|
+
status: "ok",
|
|
10372
|
+
title: t(lang, "docs Git \uB808\uD3EC \uCD08\uAE30\uD654", "Docs git repository initialized"),
|
|
10373
|
+
message: t(lang, "docs Git \uB808\uD3EC\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Docs git repository is available."),
|
|
10374
|
+
path: docsDir
|
|
10375
|
+
});
|
|
10376
|
+
if (!hasHeadCommit(docsDir)) {
|
|
10377
|
+
checks.push({
|
|
10378
|
+
id: "docs_initial_commit",
|
|
10379
|
+
status: "warn",
|
|
10380
|
+
title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
|
|
10381
|
+
message: t(
|
|
10382
|
+
lang,
|
|
10383
|
+
"docs \uCCAB \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD08\uAE30 \uC124\uC815 \uCEE4\uBC0B\uC744 \uBA3C\uC800 \uC0DD\uC131\uD558\uC138\uC694.",
|
|
10384
|
+
"No initial commit found in docs repo. Create an initial setup commit first."
|
|
10385
|
+
),
|
|
10386
|
+
path: docsDir,
|
|
10387
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard setup"`
|
|
10388
|
+
});
|
|
10389
|
+
} else {
|
|
10390
|
+
checks.push({
|
|
10391
|
+
id: "docs_initial_commit",
|
|
10392
|
+
status: "ok",
|
|
10393
|
+
title: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B", "Docs initial commit"),
|
|
10394
|
+
message: t(lang, "docs \uCD08\uAE30 \uCEE4\uBC0B\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4.", "Initial commit exists in docs repo."),
|
|
10395
|
+
path: docsDir
|
|
10396
|
+
});
|
|
10397
|
+
}
|
|
10398
|
+
const docsDirty = runGit(["status", "--porcelain=v1"], docsDir);
|
|
10399
|
+
if (docsDirty === void 0) {
|
|
10400
|
+
checks.push({
|
|
10401
|
+
id: "docs_worktree",
|
|
10402
|
+
status: "warn",
|
|
10403
|
+
title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
|
|
10404
|
+
message: t(
|
|
10405
|
+
lang,
|
|
10406
|
+
"docs \uBCC0\uACBD \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
10407
|
+
"Unable to read docs worktree status."
|
|
10408
|
+
),
|
|
10409
|
+
path: docsDir
|
|
10410
|
+
});
|
|
10411
|
+
} else if (docsDirty.trim().length > 0) {
|
|
10412
|
+
checks.push({
|
|
10413
|
+
id: "docs_worktree",
|
|
10414
|
+
status: "warn",
|
|
10415
|
+
title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
|
|
10416
|
+
message: t(
|
|
10417
|
+
lang,
|
|
10418
|
+
"\uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 docs \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
10419
|
+
"Uncommitted docs changes were found."
|
|
10420
|
+
),
|
|
10421
|
+
path: docsDir,
|
|
10422
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} add . && git -C ${quotePath(docsDir)} commit -m "docs: onboard updates"`
|
|
10423
|
+
});
|
|
10424
|
+
} else {
|
|
10425
|
+
checks.push({
|
|
10426
|
+
id: "docs_worktree",
|
|
10427
|
+
status: "ok",
|
|
10428
|
+
title: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC \uC0C1\uD0DC", "Docs worktree status"),
|
|
10429
|
+
message: t(lang, "docs \uC791\uC5C5 \uD2B8\uB9AC\uAC00 \uAE68\uB057\uD569\uB2C8\uB2E4.", "Docs worktree is clean."),
|
|
10430
|
+
path: docsDir
|
|
10431
|
+
});
|
|
10432
|
+
}
|
|
10433
|
+
}
|
|
10434
|
+
const constitutionPath = path19.join(docsDir, "agents", "constitution.md");
|
|
10435
|
+
if (!await fs15.pathExists(constitutionPath)) {
|
|
10436
|
+
checks.push({
|
|
10437
|
+
id: "constitution_exists",
|
|
10438
|
+
status: "block",
|
|
10439
|
+
title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
|
|
10440
|
+
message: t(
|
|
10441
|
+
lang,
|
|
10442
|
+
"`agents/constitution.md` \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
10443
|
+
"`agents/constitution.md` is missing."
|
|
10444
|
+
),
|
|
10445
|
+
path: constitutionPath,
|
|
10446
|
+
suggestedCommand: `npx lee-spec-kit update --agents`
|
|
10447
|
+
});
|
|
10448
|
+
} else {
|
|
10449
|
+
const content = await fs15.readFile(constitutionPath, "utf-8");
|
|
10450
|
+
if (hasTemplateMarkers(content)) {
|
|
10451
|
+
checks.push({
|
|
10452
|
+
id: "constitution_filled",
|
|
10453
|
+
status: "block",
|
|
10454
|
+
title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
|
|
10455
|
+
message: t(
|
|
10456
|
+
lang,
|
|
10457
|
+
"Constitution\uC5D0 \uD15C\uD50C\uB9BF placeholder\uAC00 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uC73C\uB85C \uBA3C\uC800 \uC791\uC131\uD558\uC138\uC694.",
|
|
10458
|
+
"Constitution still contains template placeholders. Fill project-specific content first."
|
|
10459
|
+
),
|
|
10460
|
+
path: constitutionPath
|
|
10461
|
+
});
|
|
10462
|
+
} else {
|
|
10463
|
+
checks.push({
|
|
10464
|
+
id: "constitution_filled",
|
|
10465
|
+
status: "ok",
|
|
10466
|
+
title: t(lang, "Constitution \uC791\uC131", "Constitution setup"),
|
|
10467
|
+
message: t(lang, "Constitution\uC774 \uC791\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Constitution looks filled."),
|
|
10468
|
+
path: constitutionPath
|
|
10469
|
+
});
|
|
10470
|
+
}
|
|
10471
|
+
}
|
|
10472
|
+
const customPath = path19.join(docsDir, "agents", "custom.md");
|
|
10473
|
+
if (await fs15.pathExists(customPath)) {
|
|
10474
|
+
const content = await fs15.readFile(customPath, "utf-8");
|
|
10475
|
+
if (hasTemplateMarkers(content)) {
|
|
10476
|
+
checks.push({
|
|
10477
|
+
id: "custom_optional",
|
|
10478
|
+
status: "warn",
|
|
10479
|
+
title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
|
|
10480
|
+
message: t(
|
|
10481
|
+
lang,
|
|
10482
|
+
"`agents/custom.md`\uB294 \uC120\uD0DD \uD56D\uBAA9\uC774\uC9C0\uB9CC, \uD604\uC7AC \uD15C\uD50C\uB9BF \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uD544\uC694\uD558\uBA74 \uADDC\uCE59\uC744 \uC791\uC131\uD558\uC138\uC694.",
|
|
10483
|
+
"`agents/custom.md` is optional, but it still looks like template content. Fill it if your project needs custom rules."
|
|
10484
|
+
),
|
|
10485
|
+
path: customPath
|
|
10486
|
+
});
|
|
10487
|
+
} else {
|
|
10488
|
+
checks.push({
|
|
10489
|
+
id: "custom_optional",
|
|
10490
|
+
status: "ok",
|
|
10491
|
+
title: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C", "Custom rules doc"),
|
|
10492
|
+
message: t(lang, "Custom \uADDC\uCE59 \uBB38\uC11C\uAC00 \uAD6C\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "Custom rules doc looks configured."),
|
|
10493
|
+
path: customPath
|
|
10494
|
+
});
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
const prdDir = path19.join(docsDir, "prd");
|
|
10498
|
+
const featureCount = await countFeatureDirs(docsDir, config.projectType);
|
|
10499
|
+
const prdReady = await hasUserPrdFile(prdDir);
|
|
10500
|
+
if (!prdReady) {
|
|
10501
|
+
checks.push({
|
|
10502
|
+
id: "prd_ready",
|
|
10503
|
+
status: featureCount === 0 ? "block" : "warn",
|
|
10504
|
+
title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
|
|
10505
|
+
message: featureCount === 0 ? t(
|
|
10506
|
+
lang,
|
|
10507
|
+
"PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. Feature \uC0DD\uC131 \uC804\uC5D0 PRD\uBD80\uD130 \uC791\uC131\uD558\uC138\uC694.",
|
|
10508
|
+
"PRD is empty. Write PRD first before creating features."
|
|
10509
|
+
) : t(
|
|
10510
|
+
lang,
|
|
10511
|
+
"PRD \uBB38\uC11C\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 Feature\uAC00 \uC788\uB2E4\uBA74 PRD\uB97C \uBCF4\uAC15\uD558\uC138\uC694.",
|
|
10512
|
+
"PRD is empty. If features already exist, fill PRD as soon as possible."
|
|
10513
|
+
),
|
|
10514
|
+
path: prdDir,
|
|
10515
|
+
suggestedCommand: `touch ${quotePath(path19.join(prdDir, `${toSlug(config.projectName || "project")}-prd.md`))}`
|
|
10516
|
+
});
|
|
10517
|
+
} else {
|
|
10518
|
+
checks.push({
|
|
10519
|
+
id: "prd_ready",
|
|
10520
|
+
status: "ok",
|
|
10521
|
+
title: t(lang, "PRD \uC900\uBE44 \uC0C1\uD0DC", "PRD readiness"),
|
|
10522
|
+
message: t(lang, "PRD \uBB38\uC11C\uAC00 \uD655\uC778\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "PRD document is present."),
|
|
10523
|
+
path: prdDir
|
|
10524
|
+
});
|
|
10525
|
+
}
|
|
10526
|
+
const workflowPolicy = resolveWorkflowPolicy(config.workflow);
|
|
10527
|
+
if (workflowPolicy.mode === "github") {
|
|
10528
|
+
const projectKeys = config.projectType === "multi" ? resolveProjectComponents(config.projectType, config.components) : ["single"];
|
|
10529
|
+
for (const key of projectKeys) {
|
|
10530
|
+
const resolved = resolveProjectGitCwd(config, key, lang);
|
|
10531
|
+
const title = t(
|
|
10532
|
+
lang,
|
|
10533
|
+
config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0 (${key})` : "\uD504\uB85C\uC81D\uD2B8 Git \uC5F0\uACB0",
|
|
10534
|
+
config.projectType === "multi" ? `Project git connectivity (${key})` : "Project git connectivity"
|
|
10535
|
+
);
|
|
10536
|
+
if (resolved.warning || !resolved.cwd) {
|
|
10537
|
+
checks.push({
|
|
10538
|
+
id: `project_git_${key}`,
|
|
10539
|
+
status: "block",
|
|
10540
|
+
title,
|
|
10541
|
+
message: resolved.warning || t(
|
|
10542
|
+
lang,
|
|
10543
|
+
"\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC \uACBD\uB85C\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
10544
|
+
"Project repository path could not be resolved."
|
|
10545
|
+
)
|
|
10546
|
+
});
|
|
10547
|
+
continue;
|
|
10548
|
+
}
|
|
10549
|
+
if (!isGitRepo(resolved.cwd)) {
|
|
10550
|
+
checks.push({
|
|
10551
|
+
id: `project_git_${key}`,
|
|
10552
|
+
status: "block",
|
|
10553
|
+
title,
|
|
10554
|
+
message: t(
|
|
10555
|
+
lang,
|
|
10556
|
+
"\uD504\uB85C\uC81D\uD2B8 \uACBD\uB85C\uAC00 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
|
|
10557
|
+
"Project path is not a git repository."
|
|
10558
|
+
),
|
|
10559
|
+
path: resolved.cwd,
|
|
10560
|
+
suggestedCommand: `git -C ${quotePath(resolved.cwd)} init`
|
|
10561
|
+
});
|
|
10562
|
+
continue;
|
|
10563
|
+
}
|
|
10564
|
+
const origin = getOriginUrl(resolved.cwd);
|
|
10565
|
+
if (!origin) {
|
|
10566
|
+
checks.push({
|
|
10567
|
+
id: `project_origin_${key}`,
|
|
10568
|
+
status: "block",
|
|
10569
|
+
title: t(
|
|
10570
|
+
lang,
|
|
10571
|
+
config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
|
|
10572
|
+
config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
|
|
10573
|
+
),
|
|
10574
|
+
message: t(
|
|
10575
|
+
lang,
|
|
10576
|
+
"GitHub \uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uC704\uD574 \uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC758 origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
10577
|
+
"Project repo origin remote is required for github workflow."
|
|
10578
|
+
),
|
|
10579
|
+
path: resolved.cwd,
|
|
10580
|
+
suggestedCommand: `git -C ${quotePath(resolved.cwd)} remote add origin <git-url>`
|
|
10581
|
+
});
|
|
10582
|
+
} else {
|
|
10583
|
+
checks.push({
|
|
10584
|
+
id: `project_origin_${key}`,
|
|
10585
|
+
status: "ok",
|
|
10586
|
+
title: t(
|
|
10587
|
+
lang,
|
|
10588
|
+
config.projectType === "multi" ? `\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815 (${key})` : "\uD504\uB85C\uC81D\uD2B8 origin \uC124\uC815",
|
|
10589
|
+
config.projectType === "multi" ? `Project origin configured (${key})` : "Project origin configured"
|
|
10590
|
+
),
|
|
10591
|
+
message: t(
|
|
10592
|
+
lang,
|
|
10593
|
+
`origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`,
|
|
10594
|
+
`origin is configured: ${origin}`
|
|
10595
|
+
),
|
|
10596
|
+
path: resolved.cwd
|
|
10597
|
+
});
|
|
10598
|
+
}
|
|
10599
|
+
}
|
|
10600
|
+
}
|
|
10601
|
+
if (config.docsRepo === "standalone" && config.pushDocs) {
|
|
10602
|
+
const origin = getOriginUrl(docsDir);
|
|
10603
|
+
if (!origin) {
|
|
10604
|
+
checks.push({
|
|
10605
|
+
id: "docs_origin",
|
|
10606
|
+
status: "block",
|
|
10607
|
+
title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
|
|
10608
|
+
message: t(
|
|
10609
|
+
lang,
|
|
10610
|
+
"standalone + pushDocs=true \uC124\uC815\uC5D0\uC11C\uB294 docs origin remote\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
10611
|
+
"docs origin remote is required when standalone + pushDocs=true."
|
|
10612
|
+
),
|
|
10613
|
+
path: docsDir,
|
|
10614
|
+
suggestedCommand: `git -C ${quotePath(docsDir)} remote add origin <docs-git-url>`
|
|
10615
|
+
});
|
|
10616
|
+
} else {
|
|
10617
|
+
checks.push({
|
|
10618
|
+
id: "docs_origin",
|
|
10619
|
+
status: "ok",
|
|
10620
|
+
title: t(lang, "docs origin \uC124\uC815", "Docs origin configured"),
|
|
10621
|
+
message: t(lang, `origin\uC774 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${origin}`, `origin is configured: ${origin}`),
|
|
10622
|
+
path: docsDir
|
|
10623
|
+
});
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
return finalizeChecks(checks);
|
|
10627
|
+
}
|
|
10628
|
+
function onboardCommand(program2) {
|
|
10629
|
+
program2.command("onboard").description("Run onboarding checks for initial setup").option("--json", "Output in JSON format for agents").option("--strict", "Exit with code 1 when WARN/BLOCK exists").action(async (options) => {
|
|
10630
|
+
try {
|
|
10631
|
+
await runOnboard(options);
|
|
10632
|
+
} catch (error) {
|
|
10633
|
+
const config = await getConfig(process.cwd());
|
|
10634
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
10635
|
+
const cliError = toCliError(error);
|
|
10636
|
+
const suggestions = getCliErrorSuggestions(cliError.code, lang);
|
|
10637
|
+
if (options.json) {
|
|
10638
|
+
console.log(
|
|
10639
|
+
JSON.stringify({
|
|
10640
|
+
status: "error",
|
|
10641
|
+
reasonCode: cliError.code,
|
|
10642
|
+
error: cliError.message,
|
|
10643
|
+
suggestions
|
|
10644
|
+
})
|
|
10645
|
+
);
|
|
10646
|
+
} else {
|
|
10647
|
+
console.error(
|
|
10648
|
+
chalk6.red(tr(lang, "cli", "common.errorLabel")),
|
|
10649
|
+
chalk6.red(`[${cliError.code}] ${cliError.message}`)
|
|
10650
|
+
);
|
|
10651
|
+
printCliErrorSuggestions(suggestions, lang);
|
|
10652
|
+
}
|
|
10653
|
+
process.exit(1);
|
|
10654
|
+
}
|
|
10655
|
+
});
|
|
10656
|
+
}
|
|
10657
|
+
async function runOnboard(options) {
|
|
10658
|
+
const config = await getConfig(process.cwd());
|
|
10659
|
+
if (!config) {
|
|
10660
|
+
throw createCliError(
|
|
10661
|
+
"CONFIG_NOT_FOUND",
|
|
10662
|
+
tr(DEFAULT_LANG, "cli", "common.configNotFound")
|
|
10663
|
+
);
|
|
10664
|
+
}
|
|
10665
|
+
const lang = config.lang;
|
|
10666
|
+
const result = await runOnboardChecks(config);
|
|
10667
|
+
if (options.json) {
|
|
10668
|
+
const payload = {
|
|
10669
|
+
status: "ok",
|
|
10670
|
+
reasonCode: result.status === "ready" ? "ONBOARD_READY" : result.status === "needs_action" ? "ONBOARD_NEEDS_ACTION" : "ONBOARD_BLOCKED",
|
|
10671
|
+
docsDir: config.docsDir,
|
|
10672
|
+
docsRepo: config.docsRepo || "embedded",
|
|
10673
|
+
workflow: resolveWorkflowPolicy(config.workflow),
|
|
10674
|
+
summary: result.summary,
|
|
10675
|
+
checks: result.checks
|
|
10676
|
+
};
|
|
10677
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
10678
|
+
} else {
|
|
10679
|
+
printOnboardResult(lang, result);
|
|
10680
|
+
}
|
|
10681
|
+
if (options.strict && (result.summary.warn > 0 || result.summary.block > 0)) {
|
|
10682
|
+
process.exitCode = 1;
|
|
10683
|
+
}
|
|
10684
|
+
}
|
|
10057
10685
|
function isBannerDisabled() {
|
|
10058
10686
|
const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
|
|
10059
10687
|
return v === "1";
|
|
@@ -10233,4 +10861,5 @@ flowCommand(program);
|
|
|
10233
10861
|
githubCommand(program);
|
|
10234
10862
|
docsCommand(program);
|
|
10235
10863
|
detectCommand(program);
|
|
10864
|
+
onboardCommand(program);
|
|
10236
10865
|
await program.parseAsync();
|
package/package.json
CHANGED
|
@@ -5,11 +5,14 @@ This documentation is organized by feature to help agents quickly understand the
|
|
|
5
5
|
## Agent Session Start Checklist
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
# (recommended once at start) run onboarding checks
|
|
9
|
+
npx lee-spec-kit onboard --strict
|
|
10
|
+
|
|
8
11
|
# 1) Detect project
|
|
9
12
|
npx lee-spec-kit detect --json
|
|
10
13
|
|
|
11
14
|
# 2) If detected, read context first
|
|
12
|
-
npx lee-spec-kit context --json
|
|
15
|
+
npx lee-spec-kit context --json-compact
|
|
13
16
|
```
|
|
14
17
|
|
|
15
18
|
- Apply lee-spec-kit workflow only when `isLeeSpecKitProject: true`.
|
|
@@ -20,7 +23,8 @@ npx lee-spec-kit context --json
|
|
|
20
23
|
|
|
21
24
|
- Scaffold the code project first (for example Next.js/NestJS), then run `lee-spec-kit init`.
|
|
22
25
|
- After that, verify detection with `detect --json`, then continue with `feature` and `context`.
|
|
23
|
-
-
|
|
26
|
+
- In most cases (default: embedded), the steps above are all you need.
|
|
27
|
+
- Choose standalone only when docs are managed separately from the code repo. In that case, prefer running init from a parent workspace folder (for example `workspace/docs`, `workspace/project`) and set both docs/project paths together. (e.g. `npx lee-spec-kit init --docs-repo standalone --dir ./docs --project-root ./project`)
|
|
24
28
|
|
|
25
29
|
## Directory Structure
|
|
26
30
|
|
|
@@ -33,7 +33,7 @@ Prohibited:
|
|
|
33
33
|
## 🧾 Label Response Contract (SSOT)
|
|
34
34
|
|
|
35
35
|
- End **every user-facing reply** with current status + available labels.
|
|
36
|
-
- Use the latest `npx lee-spec-kit context --json` (or `flow --json`
|
|
36
|
+
- Use the latest `npx lee-spec-kit context --json-compact` as the default source (fallback: `context --json` or `flow --json` when full detail is required).
|
|
37
37
|
- Use `actionOptions[].detail` or command `cmd` **verbatim**. Do not paraphrase.
|
|
38
38
|
- Even when the user asks something else, append the same label block at the end if executable labels exist.
|
|
39
39
|
- If no executable labels exist, print `Available labels: none` and guide re-check with `npx lee-spec-kit context`.
|
|
@@ -22,12 +22,8 @@
|
|
|
22
22
|
- [ ] (verifiable criterion 1)
|
|
23
23
|
- [ ] (verifiable criterion 2)
|
|
24
24
|
|
|
25
|
-
## Out of Scope
|
|
26
|
-
|
|
27
|
-
- (what this issue does not cover)
|
|
28
|
-
|
|
29
25
|
## Related Docs
|
|
30
26
|
|
|
31
|
-
- Spec:
|
|
32
|
-
- Plan:
|
|
33
|
-
- Tasks:
|
|
27
|
+
- Spec: `docs/.../spec.md`
|
|
28
|
+
- Plan: `docs/.../plan.md`
|
|
29
|
+
- Tasks: `docs/.../tasks.md`
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
## 에이전트 세션 시작 체크리스트
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
# (최초 1회 권장) 초기 온보딩 점검
|
|
9
|
+
npx lee-spec-kit onboard --strict
|
|
10
|
+
|
|
8
11
|
# 1) 프로젝트 감지
|
|
9
12
|
npx lee-spec-kit detect --json
|
|
10
13
|
|
|
11
14
|
# 2) 감지 성공 시 컨텍스트 조회
|
|
12
|
-
npx lee-spec-kit context --json
|
|
15
|
+
npx lee-spec-kit context --json-compact
|
|
13
16
|
```
|
|
14
17
|
|
|
15
18
|
- `isLeeSpecKitProject: true`일 때만 lee-spec-kit 워크플로우를 적용합니다.
|
|
@@ -20,7 +23,8 @@ npx lee-spec-kit context --json
|
|
|
20
23
|
|
|
21
24
|
- 코드 프로젝트 스캐폴딩(예: Next.js/NestJS) 후 `lee-spec-kit init`을 실행하세요.
|
|
22
25
|
- 그 다음 `detect --json`으로 감지 결과를 확인하고, `feature`/`context` 순서로 진행하세요.
|
|
23
|
-
-
|
|
26
|
+
- 대부분의 경우(기본값: embedded) 위 순서만 따르면 됩니다.
|
|
27
|
+
- docs를 코드 저장소와 분리해 운영할 때만 standalone을 선택하세요. 이때는 상위 워크스페이스 폴더(예: `workspace/docs`, `workspace/project`)에서 `init`을 실행해 docs/project 경로를 함께 지정하는 방식을 권장합니다. (예: `npx lee-spec-kit init --docs-repo standalone --dir ./docs --project-root ./project`)
|
|
24
28
|
|
|
25
29
|
## 상위 구조 요약
|
|
26
30
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
## 🧾 라벨 응답 계약 (SSOT)
|
|
34
34
|
|
|
35
35
|
- 사용자에게 보내는 **모든 응답의 마지막**에 현재 상태 + 선택 가능한 라벨을 표시합니다.
|
|
36
|
-
- 기준 데이터는 최신 `npx lee-spec-kit context --json`(또는 `flow --json`)
|
|
36
|
+
- 기준 데이터는 최신 `npx lee-spec-kit context --json-compact`를 기본으로 사용하고, 상세 필드가 필요할 때만 `context --json`(또는 `flow --json`)을 사용합니다.
|
|
37
37
|
- 라벨 설명은 `actionOptions[].detail` 또는 command `cmd`를 **원문 그대로** 사용합니다. (요약/의역 금지)
|
|
38
38
|
- 사용자가 다른 질문을 하더라도, 실행 가능한 라벨이 있으면 응답 마지막에 동일 블록을 다시 표시합니다.
|
|
39
39
|
- 실행 가능한 라벨이 없으면 `선택 가능: 없음` + `npx lee-spec-kit context` 재확인을 안내합니다.
|
|
@@ -22,12 +22,8 @@
|
|
|
22
22
|
- [ ] (검증 가능한 완료 기준 1)
|
|
23
23
|
- [ ] (검증 가능한 완료 기준 2)
|
|
24
24
|
|
|
25
|
-
## 제외 범위
|
|
26
|
-
|
|
27
|
-
- (이번 이슈에서 다루지 않는 내용)
|
|
28
|
-
|
|
29
25
|
## 관련 문서
|
|
30
26
|
|
|
31
|
-
- Spec:
|
|
32
|
-
- Plan:
|
|
33
|
-
- Tasks:
|
|
27
|
+
- Spec: `docs/.../spec.md`
|
|
28
|
+
- Plan: `docs/.../plan.md`
|
|
29
|
+
- Tasks: `docs/.../tasks.md`
|