lee-spec-kit 0.6.8 → 0.6.9

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 CHANGED
@@ -67,7 +67,7 @@ npx lee-spec-kit doctor
67
67
  ### 🚀 Feature creation
68
68
 
69
69
  - Generates `spec.md`, `plan.md`, `tasks.md`, `decisions.md`
70
- - Multi mode supports flexible component separation (e.g. FE/BE/worker)
70
+ - Multi mode supports flexible component separation (e.g. app/api/worker)
71
71
  - Integrates Issue/PR templates (docs side)
72
72
 
73
73
  ### 📊 Status management
@@ -109,16 +109,14 @@ npx lee-spec-kit init --name my-project --type fullstack # alias
109
109
  | Option | Description | Default |
110
110
  | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------- |
111
111
  | `-n, --name <name>` | Project name | current folder |
112
- | `-t, --type <type>` | `single` or `multi` (`fullstack` alias supported) | interactive (`single` with `--yes`/`--non-interactive`) |
113
- | `--components <list>` | multi component list (comma-separated, e.g. `fe,be,worker`) | `fe,be` |
112
+ | `-t, --type <type>` | `single` or `multi` (`fullstack` alias supported) | interactive (`multi` with `--yes`/`--non-interactive`) |
113
+ | `--components <list>` | multi component list (comma-separated, e.g. `app,api,worker`) | `app` |
114
114
  | `-l, --lang <lang>` | `ko` or `en` | `en` |
115
115
  | `--workflow <mode>` | Workflow mode: `github` (issue/PR/review) or `local` (local-first) | `github` |
116
116
  | `-d, --dir <dir>` | Install directory | `./docs` |
117
117
  | `--docs-repo <mode>` | docs repo mode (`embedded` or `standalone`) | `embedded` |
118
- | `--project-root <path>` | standalone(single) project repo path or standalone(multi) JSON map (`{"fe":"/path/fe"}`) | - |
119
- | `--fe-project-root <path>` | standalone(multi) frontend repo path | - |
120
- | `--be-project-root <path>` | standalone(multi) backend repo path | - |
121
- | `--component-project-roots <pairs>` | standalone(multi) component roots (`fe=/path/fe,be=/path/be,worker=/path/worker`) | - |
118
+ | `--project-root <path>` | standalone(single) project repo path or standalone(multi) JSON map (`{"app":"/path/app","api":"/path/api"}`) | - |
119
+ | `--component-project-roots <pairs>` | standalone(multi) component roots (`app=/path/app,api=/path/api,worker=/path/worker`) | - |
122
120
  | `--push-docs` | enable standalone docs push (use with `--docs-remote`) | `false` |
123
121
  | `--docs-remote <url>` | standalone docs remote URL (used with `--push-docs`) | - |
124
122
  | `-y, --yes` | Skip most interactive inputs (overwrite confirmation still appears if target dir is not empty) | - |
@@ -149,8 +147,8 @@ The `--json` payload includes `isLeeSpecKitProject`, `reasonCode` (`PROJECT_DETE
149
147
  npx lee-spec-kit feature user-auth
150
148
 
151
149
  # Multi
152
- npx lee-spec-kit feature --component be user-auth
153
- npx lee-spec-kit feature --component fe user-profile
150
+ npx lee-spec-kit feature --component api user-auth
151
+ npx lee-spec-kit feature --component app user-profile
154
152
  npx lee-spec-kit feature --component worker queue-jobs
155
153
 
156
154
  # Specify Feature ID/description
@@ -196,7 +194,7 @@ Use advanced selectors (`--component`, `--all`, `--done`) only when you need mul
196
194
  | Option | Description |
197
195
  | -------------- | ----------------------------------------------- |
198
196
  | `--json` | JSON output for agents |
199
- | `--component <id>` | Select target component in multi mode (e.g. `fe`, `be`, `worker`) |
197
+ | `--component <id>` | Select target component in multi mode (e.g. `app`, `api`, `worker`) |
200
198
  | `--all` | Include completed features when auto-detecting |
201
199
  | `--done` | Show completed (workflow-done) features only |
202
200
  | `--approve <reply>` | Approve one labeled option using any reply that includes a label token (e.g. `A`, `A OK`, `A proceed`) |
@@ -266,7 +264,7 @@ npx lee-spec-kit view --json
266
264
  | Option | Description |
267
265
  | -------------- | ----------------------------------------------- |
268
266
  | `--json` | JSON output for agents |
269
- | `--component <id>` | Select target component in multi mode (e.g. `fe`, `be`, `worker`) |
267
+ | `--component <id>` | Select target component in multi mode (e.g. `app`, `api`, `worker`) |
270
268
  | `--all` | Include completed features when auto-detecting |
271
269
  | `--done` | Show completed (workflow-done) features only |
272
270
 
@@ -291,7 +289,7 @@ npx lee-spec-kit flow --strict
291
289
  | Option | Description |
292
290
  | ----------------- | ----------- |
293
291
  | `--json` | JSON output for agents |
294
- | `--component <id>`| Select target component in multi mode (e.g. `fe`, `be`, `worker`) |
292
+ | `--component <id>`| Select target component in multi mode (e.g. `app`, `api`, `worker`) |
295
293
  | `--all` | Include completed features when auto-detecting |
296
294
  | `--done` | Show completed (workflow-done) features only |
297
295
  | `--approve <reply>` | Pass through context label approval (e.g. `A`, `A OK`, `A proceed`) |
@@ -419,7 +417,7 @@ Running `init` creates `.lee-spec-kit.json` in your docs root (default: `docs/`)
419
417
  | ------------- | ------------------------------------------------ |
420
418
  | `projectName` | Project name |
421
419
  | `projectType` | `single` or `multi` (`fullstack` alias supported) |
422
- | `components` | (multi only) component list (e.g. `["fe","be","worker"]`) |
420
+ | `components` | (multi only) component list (e.g. `["app","api","worker"]`) |
423
421
  | `lang` | `ko` or `en` |
424
422
  | `createdAt` | Creation date |
425
423
  | `docsRepo` | `embedded` or `standalone` |
@@ -544,12 +542,12 @@ npx lee-spec-kit config --project-root /new/path
544
542
  npx lee-spec-kit config --dir ./docs2 --project-root /new/path
545
543
 
546
544
  # update projectRoot (multi)
547
- npx lee-spec-kit config --project-root /new/fe/path --component fe
548
- npx lee-spec-kit config --project-root /new/be/path --component be
545
+ npx lee-spec-kit config --project-root /new/app/path --component app
546
+ npx lee-spec-kit config --project-root /new/api/path --component api
549
547
  npx lee-spec-kit config --project-root /new/worker/path --component worker
550
548
 
551
549
  # non-interactive mode (fails immediately if required input is missing)
552
- npx lee-spec-kit config --project-root /new/fe/path --component fe --non-interactive
550
+ npx lee-spec-kit config --project-root /new/app/path --component app --non-interactive
553
551
  ```
554
552
 
555
553
  **Options:**
package/README.md CHANGED
@@ -80,7 +80,7 @@ npx lee-spec-kit doctor
80
80
  ### 🚀 Feature 생성
81
81
 
82
82
  - spec.md, plan.md, tasks.md, decisions.md 자동 생성
83
- - Fullstack 프로젝트의 경우 FE/BE 분리 지원
83
+ - Multi 프로젝트에서 임의 컴포넌트(app/api/worker 등) 분리 지원
84
84
  - GitHub Issue/PR 템플릿 연동
85
85
 
86
86
  ### 📊 상태 관리
@@ -125,16 +125,14 @@ npx lee-spec-kit init --name my-project --type fullstack # alias
125
125
  | 옵션 | 설명 | 기본값 |
126
126
  | ------------------- | -------------------------------------------------------------------- | ------------------------- |
127
127
  | `-n, --name <name>` | 프로젝트 이름 | 현재 폴더명 |
128
- | `-t, --type <type>` | `single` 또는 `multi` (`fullstack` alias 지원) | 대화형 선택 (`--yes`/`--non-interactive`면 `single`) |
129
- | `--components <list>` | multi 컴포넌트 목록 (쉼표 구분, 예: `fe,be,worker`) | `fe,be` |
128
+ | `-t, --type <type>` | `single` 또는 `multi` (`fullstack` alias 지원) | 대화형 선택 (`--yes`/`--non-interactive`면 `multi`) |
129
+ | `--components <list>` | multi 컴포넌트 목록 (쉼표 구분, 예: `app,api,worker`) | `app` |
130
130
  | `-l, --lang <lang>` | `ko` (한국어) 또는 `en` (영어) | `en` |
131
131
  | `--workflow <mode>` | 워크플로우 모드: `github`(issue/PR/review 포함) 또는 `local`(로컬 중심) | `github` |
132
132
  | `-d, --dir <dir>` | 설치 디렉토리 | `./docs` |
133
133
  | `--docs-repo <mode>` | docs 레포 모드 (`embedded` 또는 `standalone`) | `embedded` |
134
- | `--project-root <path>` | standalone(single) 프로젝트 레포 경로 또는 standalone(multi) JSON 매핑 (`{"fe":"/path/fe"}`) | - |
135
- | `--fe-project-root <path>` | standalone(multi) FE 레포 경로 | - |
136
- | `--be-project-root <path>` | standalone(multi) BE 레포 경로 | - |
137
- | `--component-project-roots <pairs>` | standalone(multi) 컴포넌트별 레포 경로 (`fe=/path/fe,be=/path/be,worker=/path/worker`) | - |
134
+ | `--project-root <path>` | standalone(single) 프로젝트 레포 경로 또는 standalone(multi) JSON 매핑 (`{"app":"/path/app","api":"/path/api"}`) | - |
135
+ | `--component-project-roots <pairs>` | standalone(multi) 컴포넌트별 레포 경로 (`app=/path/app,api=/path/api,worker=/path/worker`) | - |
138
136
  | `--push-docs` | standalone docs 원격 push 사용 (`--docs-remote`와 함께 사용) | `false` |
139
137
  | `--docs-remote <url>` | standalone docs 원격 URL (`--push-docs`와 함께 사용) | - |
140
138
  | `-y, --yes` | 대화형 입력을 대부분 스킵 (단, 대상 디렉토리가 비어있지 않으면 덮어쓰기 확인은 표시) | - |
@@ -165,8 +163,8 @@ npx lee-spec-kit detect --dir /path/to/workspace
165
163
  npx lee-spec-kit feature user-auth
166
164
 
167
165
  # Multi 프로젝트
168
- npx lee-spec-kit feature --component be user-auth
169
- npx lee-spec-kit feature --component fe user-profile
166
+ npx lee-spec-kit feature --component api user-auth
167
+ npx lee-spec-kit feature --component app user-profile
170
168
  npx lee-spec-kit feature --component worker queue-jobs
171
169
 
172
170
  # Feature ID/설명 지정
@@ -213,7 +211,7 @@ npx lee-spec-kit context F001 --approve A --execute --ticket <TICKET> --execute-
213
211
  | 옵션 | 설명 |
214
212
  | --------------- | ----------------------------------------------- |
215
213
  | `--json` | 에이전트용 JSON 출력 |
216
- | `--component <id>` | multi에서 대상 컴포넌트 지정 (예: `fe`, `be`, `worker`) |
214
+ | `--component <id>` | multi에서 대상 컴포넌트 지정 (예: `app`, `api`, `worker`) |
217
215
  | `--all` | 자동 감지 실패 시 완료된 Feature까지 포함해서 표시 |
218
216
  | `--done` | 완료(workflow-done) Feature만 표시 |
219
217
  | `--approve <reply>` | 라벨 포함 승인 응답으로 단일 옵션 선택 (예: `A`, `A OK`, `A 진행해`) |
@@ -283,7 +281,7 @@ npx lee-spec-kit view --json
283
281
  | 옵션 | 설명 |
284
282
  | --------------- | ----------------------------------------------- |
285
283
  | `--json` | 에이전트용 JSON 출력 |
286
- | `--component <id>` | multi에서 대상 컴포넌트 지정 (예: `fe`, `be`, `worker`) |
284
+ | `--component <id>` | multi에서 대상 컴포넌트 지정 (예: `app`, `api`, `worker`) |
287
285
  | `--all` | 자동 감지 실패 시 완료된 Feature까지 포함해서 표시 |
288
286
  | `--done` | 완료(workflow-done) Feature만 표시 |
289
287
 
@@ -308,7 +306,7 @@ npx lee-spec-kit flow --strict
308
306
  | 옵션 | 설명 |
309
307
  | ------------------ | ---- |
310
308
  | `--json` | 에이전트용 JSON 출력 |
311
- | `--component <id>` | multi에서 대상 컴포넌트 지정 (예: `fe`, `be`, `worker`) |
309
+ | `--component <id>` | multi에서 대상 컴포넌트 지정 (예: `app`, `api`, `worker`) |
312
310
  | `--all` | 자동 감지 실패 시 완료된 Feature까지 포함해서 표시 |
313
311
  | `--done` | 완료(workflow-done) Feature만 표시 |
314
312
  | `--approve <reply>`| context 라벨 승인 응답 전달 (예: `A`, `A OK`, `A 진행해`) |
@@ -466,7 +464,7 @@ npx lee-spec-kit update --force
466
464
  | ------------- | --------------------------------------- |
467
465
  | `projectName` | 프로젝트 이름 |
468
466
  | `projectType` | `single` 또는 `multi` (`fullstack` alias 지원) |
469
- | `components` | (multi만) 컴포넌트 목록 (예: `["fe","be","worker"]`) |
467
+ | `components` | (multi만) 컴포넌트 목록 (예: `["app","api","worker"]`) |
470
468
  | `lang` | `ko` 또는 `en` |
471
469
  | `createdAt` | 생성 날짜 |
472
470
  | `docsRepo` | `embedded` 또는 `standalone` |
@@ -603,8 +601,8 @@ npx lee-spec-kit update --force
603
601
  "docsRepo": "standalone",
604
602
  "pushDocs": false,
605
603
  "projectRoot": {
606
- "fe": "/path/to/frontend",
607
- "be": "/path/to/backend"
604
+ "app": "/path/to/app",
605
+ "api": "/path/to/api"
608
606
  }
609
607
  }
610
608
  ```
@@ -623,12 +621,12 @@ npx lee-spec-kit config --project-root /new/path
623
621
  npx lee-spec-kit config --dir ./docs2 --project-root /new/path
624
622
 
625
623
  # projectRoot 수정 (Multi)
626
- npx lee-spec-kit config --project-root /new/fe/path --component fe
627
- npx lee-spec-kit config --project-root /new/be/path --component be
624
+ npx lee-spec-kit config --project-root /new/app/path --component app
625
+ npx lee-spec-kit config --project-root /new/api/path --component api
628
626
  npx lee-spec-kit config --project-root /new/worker/path --component worker
629
627
 
630
628
  # 비대화형 모드 (필수 옵션 누락 시 즉시 실패)
631
- npx lee-spec-kit config --project-root /new/fe/path --component fe --non-interactive
629
+ npx lee-spec-kit config --project-root /new/app/path --component app --non-interactive
632
630
  ```
633
631
 
634
632
  **옵션:**
@@ -659,7 +657,7 @@ npx lee-spec-kit config --project-root /new/fe/path --component fe --non-interac
659
657
 
660
658
  ## 생성되는 구조
661
659
 
662
- ### Fullstack (FE/BE 분리)
660
+ ### Multi (컴포넌트 분리)
663
661
 
664
662
  ```
665
663
  docs/
@@ -675,8 +673,8 @@ docs/
675
673
  │ └── README.md
676
674
  └── features/
677
675
  ├── README.md
678
- ├── be/ # Backend Features
679
- └── fe/ # Frontend Features
676
+ ├── app/ # App Features
677
+ └── api/ # API Features
680
678
  ```
681
679
 
682
680
  ### Single (단일 레포)
@@ -715,7 +713,7 @@ flowchart LR
715
713
  | 프로젝트 타입 | 설명 |
716
714
  | ------------- | -------------------------------------------- |
717
715
  | `single` | 단일 레포 프로젝트 (모노레포 또는 단일 스택) |
718
- | `multi` | 멀티 컴포넌트 프로젝트 (예: FE/BE/worker) |
716
+ | `multi` | 멀티 컴포넌트 프로젝트 (예: app/api/worker) |
719
717
  | `fullstack` | `multi`의 하위호환 alias |
720
718
 
721
719
  ## 문제 해결
package/dist/index.js CHANGED
@@ -164,14 +164,12 @@ var I18N = {
164
164
  "init.choice.projectType.single.title": "Single - \uB2E8\uC77C \uB808\uD3EC \uD504\uB85C\uC81D\uD2B8",
165
165
  "init.choice.projectType.single.desc": "features/ \uD3F4\uB354 \uD558\uB098\uB85C \uAD00\uB9AC",
166
166
  "init.choice.projectType.fullstack.title": "Multi - \uBA40\uD2F0 \uCEF4\uD3EC\uB10C\uD2B8 \uD504\uB85C\uC81D\uD2B8",
167
- "init.choice.projectType.fullstack.desc": "Multi \uCEF4\uD3EC\uB10C\uD2B8 \uD504\uB85C\uC81D\uD2B8 (\uAE30\uBCF8: features/be/, features/fe/)",
167
+ "init.choice.projectType.fullstack.desc": "Multi \uCEF4\uD3EC\uB10C\uD2B8 \uD504\uB85C\uC81D\uD2B8 (\uAE30\uBCF8: features/{component}/)",
168
168
  "init.prompt.docsMode": "Docs \uAD00\uB9AC \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
169
169
  "init.choice.docsRepo.embedded.title": "embedded - \uD504\uB85C\uC81D\uD2B8 \uB0B4 \uD3EC\uD568 (./docs)",
170
170
  "init.choice.docsRepo.embedded.desc": "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4",
171
171
  "init.choice.docsRepo.standalone.title": "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
172
172
  "init.choice.docsRepo.standalone.desc": "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4",
173
- "init.prompt.feRepoPath": "Frontend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
174
- "init.prompt.beRepoPath": "Backend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
175
173
  "init.prompt.componentRepoPath": "{component} \uCEF4\uD3EC\uB10C\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
176
174
  "init.prompt.projectRepoPath": "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
177
175
  "init.validation.enterPath": "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694",
@@ -317,7 +315,7 @@ var I18N = {
317
315
  "cliError.configOrDocs.runFromDocsDir": "docs/\uAC00 \uC788\uB294 \uB514\uB809\uD130\uB9AC\uC5D0\uC11C \uBA85\uB839\uC744 \uC2E4\uD589\uD558\uC138\uC694.",
318
316
  "cliError.lock.retryLater": "\uC7A0\uC2DC \uAE30\uB2E4\uB9B0 \uB4A4 \uAC19\uC740 \uBA85\uB839\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
319
317
  "cliError.lock.checkOtherProcess": "\uB2E4\uB978 lee-spec-kit \uD504\uB85C\uC138\uC2A4\uAC00 \uC2E4\uD589 \uC911\uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694.",
320
- "cliError.lock.inspectLockFiles": "\uB77D \uD30C\uC77C(`docs/.lee-spec-kit.lock` \uB610\uB294 \uC0C1\uC704 \uACBD\uB85C `.lee-spec-kit.<docsDir>.lock`)\uC744 \uD655\uC778\uD558\uC138\uC694.",
318
+ "cliError.lock.inspectLockFiles": "\uB7F0\uD0C0\uC784 lock \uD30C\uC77C(\uD504\uB85C\uC81D\uD2B8 `.git/lee-spec-kit.runtime/locks` \uB610\uB294 OS temp)\uC744 \uD655\uC778\uD558\uC138\uC694.",
321
319
  "cliError.invalidArg.reviewUsage": "\uBA85\uB839 \uC0AC\uC6A9\uBC95\uACFC \uC720\uD6A8\uD55C \uD50C\uB798\uADF8\uB97C \uD655\uC778\uD558\uC138\uC694.",
322
320
  "cliError.invalidArg.fixValues": "\uC798\uBABB\uB41C \uAC12\uC744 \uC218\uC815\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
323
321
  "cliError.invalidArg.validateBeforeAutomation": "\uC790\uB3D9\uD654 \uD658\uACBD\uC774\uB77C\uBA74 CLI \uD638\uCD9C \uC804\uC5D0 \uC778\uC790\uB97C \uAC80\uC99D\uD558\uC138\uC694.",
@@ -353,7 +351,7 @@ var I18N = {
353
351
  "cliError.unknown.runDoctor": "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uC0C1\uD0DC\uB97C \uC9C4\uB2E8\uD558\uC138\uC694.",
354
352
  "cliError.unknown.reportReasonCode": "reasonCode\uC640 \uB85C\uADF8\uB97C \uC720\uC9C0\uBCF4\uC218\uC790\uC5D0\uAC8C \uC804\uB2EC\uD558\uC138\uC694.",
355
353
  "context.git.standaloneProjectRootMissing": "standalone \uBAA8\uB4DC\uC785\uB2C8\uB2E4. projectRoot\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58 \uD655\uC778\uC774 \uBD88\uAC00\uB2A5\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
356
- "context.git.multiProjectRootShapeInvalid": 'multi standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: { "fe": "...", "be": "...", "worker": "..." })',
354
+ "context.git.multiProjectRootShapeInvalid": 'multi standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: { "app": "...", "api": "...", "worker": "..." })',
357
355
  "context.git.multiProjectRootRepoMissing": "projectRoot.{repo}\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ... --component {repo})",
358
356
  "context.git.singleProjectRootShapeInvalid": 'single standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: "/path/to/project")',
359
357
  "validation.nameEmpty": "\uC774\uB984\uC740 \uBE44\uC5B4\uC788\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -562,14 +560,12 @@ var I18N = {
562
560
  "init.choice.projectType.single.title": "Single - single repo project",
563
561
  "init.choice.projectType.single.desc": "Manage with a single features/ folder",
564
562
  "init.choice.projectType.fullstack.title": "Multi - multi-component project",
565
- "init.choice.projectType.fullstack.desc": "Default structure uses features/be/ and features/fe/",
563
+ "init.choice.projectType.fullstack.desc": "Default structure uses features/{component}/",
566
564
  "init.prompt.docsMode": "Select docs mode:",
567
565
  "init.choice.docsRepo.embedded.title": "embedded - inside the project (./docs)",
568
566
  "init.choice.docsRepo.embedded.desc": "Pushed together with the project",
569
567
  "init.choice.docsRepo.standalone.title": "standalone - separate docs repo",
570
568
  "init.choice.docsRepo.standalone.desc": "Configure push settings separately",
571
- "init.prompt.feRepoPath": "Enter frontend repository path:",
572
- "init.prompt.beRepoPath": "Enter backend repository path:",
573
569
  "init.prompt.componentRepoPath": 'Enter repository path for component "{component}":',
574
570
  "init.prompt.projectRepoPath": "Enter project repository path:",
575
571
  "init.validation.enterPath": "Please enter a path",
@@ -715,7 +711,7 @@ var I18N = {
715
711
  "cliError.configOrDocs.runFromDocsDir": "Run command from the directory that contains docs/.",
716
712
  "cliError.lock.retryLater": "Wait briefly, then retry the same command.",
717
713
  "cliError.lock.checkOtherProcess": "Check whether another lee-spec-kit process is still running.",
718
- "cliError.lock.inspectLockFiles": "Inspect lock files (`docs/.lee-spec-kit.lock` or parent `.lee-spec-kit.<docsDir>.lock`).",
714
+ "cliError.lock.inspectLockFiles": "Inspect runtime lock files (project `.git/lee-spec-kit.runtime/locks` or OS temp).",
719
715
  "cliError.invalidArg.reviewUsage": "Review command usage and valid flags.",
720
716
  "cliError.invalidArg.fixValues": "Fix invalid value(s) and retry.",
721
717
  "cliError.invalidArg.validateBeforeAutomation": "If using automation, validate arguments before invoking CLI.",
@@ -751,7 +747,7 @@ var I18N = {
751
747
  "cliError.unknown.runDoctor": "Run diagnostics for workspace state.",
752
748
  "cliError.unknown.reportReasonCode": "Report the reasonCode and logs to maintainers.",
753
749
  "context.git.standaloneProjectRootMissing": "Standalone mode is enabled, but projectRoot is missing. Cannot resolve project branch. (npx lee-spec-kit config --project-root ...)",
754
- "context.git.multiProjectRootShapeInvalid": 'Multi standalone mode requires projectRoot as an object. (Example: { "fe": "...", "be": "...", "worker": "..." })',
750
+ "context.git.multiProjectRootShapeInvalid": 'Multi standalone mode requires projectRoot as an object. (Example: { "app": "...", "api": "...", "worker": "..." })',
755
751
  "context.git.multiProjectRootRepoMissing": "projectRoot.{repo} is empty. (npx lee-spec-kit config --project-root ... --component {repo})",
756
752
  "context.git.singleProjectRootShapeInvalid": 'Single standalone mode requires projectRoot as a string path. (Example: "/path/to/project")',
757
753
  "validation.nameEmpty": "Name cannot be empty.",
@@ -1077,7 +1073,7 @@ function printCliErrorSuggestions(suggestions, lang = DEFAULT_LANG) {
1077
1073
  }
1078
1074
 
1079
1075
  // src/utils/components.ts
1080
- var DEFAULT_FULLSTACK_COMPONENTS = ["fe", "be"];
1076
+ var DEFAULT_MULTI_COMPONENTS = ["app"];
1081
1077
  function unique(values) {
1082
1078
  const seen = /* @__PURE__ */ new Set();
1083
1079
  const out = [];
@@ -1114,7 +1110,7 @@ function resolveProjectComponents(projectType, configured) {
1114
1110
  if (projectType === "single") return [];
1115
1111
  const normalized = normalizeComponentList(configured);
1116
1112
  if (normalized.length > 0) return normalized;
1117
- return [...DEFAULT_FULLSTACK_COMPONENTS];
1113
+ return [...DEFAULT_MULTI_COMPONENTS];
1118
1114
  }
1119
1115
  function assertAllowedComponent(component, allowed) {
1120
1116
  if (!allowed.includes(component)) {
@@ -1124,11 +1120,6 @@ function assertAllowedComponent(component, allowed) {
1124
1120
  );
1125
1121
  }
1126
1122
  }
1127
- function isDefaultFullstackComponents(components) {
1128
- if (components.length !== 2) return false;
1129
- const set = new Set(components);
1130
- return set.has("fe") && set.has("be");
1131
- }
1132
1123
 
1133
1124
  // src/utils/project-type.ts
1134
1125
  function normalizeProjectType(input) {
@@ -1240,39 +1231,62 @@ function assertValid(result, context, lang = DEFAULT_LANG) {
1240
1231
  var DEFAULT_TIMEOUT_MS = 3e4;
1241
1232
  var DEFAULT_POLL_MS = 150;
1242
1233
  var DEFAULT_STALE_MS = 2 * 6e4;
1234
+ var RUNTIME_GIT_DIRNAME = "lee-spec-kit.runtime";
1235
+ var RUNTIME_TEMP_DIRNAME = "lee-spec-kit-runtime";
1243
1236
  function sleep(ms) {
1244
1237
  return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
1245
1238
  }
1246
- function getDocsLockPath(docsDir) {
1247
- return path18.join(docsDir, ".lee-spec-kit.lock");
1248
- }
1249
- function getInitLockPath(targetDir) {
1250
- return path18.join(
1251
- path18.dirname(targetDir),
1252
- `.lee-spec-kit.${path18.basename(targetDir)}.lock`
1253
- );
1239
+ function toScopeKey(value) {
1240
+ return createHash("sha1").update(path18.resolve(value)).digest("hex").slice(0, 16);
1254
1241
  }
1255
- function getTempProjectLockPath(cwd) {
1256
- const key = createHash("sha1").update(path18.resolve(cwd)).digest("hex");
1257
- return path18.join(os.tmpdir(), "lee-spec-kit-locks", `${key}.project.lock`);
1242
+ function getTempRuntimeDir(scopePath) {
1243
+ return path18.join(os.tmpdir(), RUNTIME_TEMP_DIRNAME, toScopeKey(scopePath));
1258
1244
  }
1259
- function getProjectExecutionLockPath(cwd) {
1245
+ function resolveGitRuntimeDir(cwd) {
1260
1246
  try {
1261
1247
  const out = execFileSync(
1262
1248
  "git",
1263
- ["rev-parse", "--git-path", "lee-spec-kit.project.lock"],
1249
+ ["rev-parse", "--git-path", RUNTIME_GIT_DIRNAME],
1264
1250
  {
1265
1251
  cwd,
1266
1252
  encoding: "utf-8",
1267
1253
  stdio: ["ignore", "pipe", "ignore"]
1268
1254
  }
1269
1255
  ).trim();
1270
- if (!out) return getTempProjectLockPath(cwd);
1256
+ if (!out) return null;
1271
1257
  return path18.isAbsolute(out) ? out : path18.resolve(cwd, out);
1272
1258
  } catch {
1273
- return getTempProjectLockPath(cwd);
1259
+ return null;
1274
1260
  }
1275
1261
  }
1262
+ function getRuntimeStateDir(cwd) {
1263
+ const resolved = path18.resolve(cwd);
1264
+ return resolveGitRuntimeDir(resolved) ?? getTempRuntimeDir(resolved);
1265
+ }
1266
+ function getDocsLockPath(docsDir) {
1267
+ return path18.join(
1268
+ getRuntimeStateDir(docsDir),
1269
+ "locks",
1270
+ `docs-${toScopeKey(docsDir)}.lock`
1271
+ );
1272
+ }
1273
+ function getInitLockPath(targetDir) {
1274
+ return path18.join(
1275
+ getRuntimeStateDir(path18.dirname(path18.resolve(targetDir))),
1276
+ "locks",
1277
+ `init-${toScopeKey(targetDir)}.lock`
1278
+ );
1279
+ }
1280
+ function getApprovalTicketStorePath(docsDir) {
1281
+ return path18.join(
1282
+ getRuntimeStateDir(docsDir),
1283
+ "tickets",
1284
+ `approval-${toScopeKey(docsDir)}.json`
1285
+ );
1286
+ }
1287
+ function getProjectExecutionLockPath(cwd) {
1288
+ return path18.join(getRuntimeStateDir(cwd), "locks", "project.lock");
1289
+ }
1276
1290
  async function isStaleLock(lockPath, staleMs) {
1277
1291
  try {
1278
1292
  const stat = await fs15.stat(lockPath);
@@ -1436,7 +1450,7 @@ function parseStandaloneMultiProjectRootJson(raw) {
1436
1450
  } catch {
1437
1451
  throw createCliError(
1438
1452
  "INVALID_ARGUMENT",
1439
- '`--project-root` for standalone multi must be a JSON object. Example: {"fe":"/path/fe","be":"/path/be"}'
1453
+ '`--project-root` for standalone multi must be a JSON object. Example: {"app":"/path/app","api":"/path/api"}'
1440
1454
  );
1441
1455
  }
1442
1456
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -1520,19 +1534,13 @@ function getComponentFeaturesReadme(lang, component) {
1520
1534
  function initCommand(program2) {
1521
1535
  program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option("-t, --type <type>", "Project type: single | multi (fullstack alias)").option(
1522
1536
  "--components <list>",
1523
- "Component list for multi (comma-separated, e.g. fe,be,worker)"
1537
+ "Component list for multi (comma-separated, e.g. app,api,worker)"
1524
1538
  ).option("-l, --lang <lang>", "Language: ko | en (default: en)").option("--workflow <mode>", "Workflow mode: github | local").option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("--docs-repo <mode>", "Docs repository mode: embedded | standalone").option(
1525
1539
  "--project-root <path>",
1526
1540
  "Project root path (standalone single) or JSON map for standalone multi"
1527
- ).option(
1528
- "--fe-project-root <path>",
1529
- "Frontend repository path (standalone multi)"
1530
- ).option(
1531
- "--be-project-root <path>",
1532
- "Backend repository path (standalone multi)"
1533
1541
  ).option(
1534
1542
  "--component-project-roots <pairs>",
1535
- "Component roots for standalone multi (comma-separated, e.g. fe=/path/fe,be=/path/be,worker=/path/worker)"
1543
+ "Component roots for standalone multi (comma-separated, e.g. app=/path/app,api=/path/api,worker=/path/worker)"
1536
1544
  ).option("--push-docs", "Push standalone docs to remote").option("--docs-remote <url>", "Remote URL for standalone docs repository").option("-y, --yes", "Skip prompts and use defaults").option("-f, --force", "Overwrite target directory if not empty").option("--non-interactive", "Fail instead of prompting for input").action(async (options) => {
1537
1545
  try {
1538
1546
  await runInit(options);
@@ -1670,7 +1678,7 @@ async function runInit(options) {
1670
1678
  description: tr(lang, "cli", "init.choice.projectType.fullstack.desc")
1671
1679
  }
1672
1680
  ],
1673
- initial: 0
1681
+ initial: 1
1674
1682
  },
1675
1683
  {
1676
1684
  type: options.docsRepo ? null : "select",
@@ -1702,10 +1710,10 @@ async function runInit(options) {
1702
1710
  docsRepo = response.docsRepo || "embedded";
1703
1711
  if (docsRepo === "standalone") {
1704
1712
  const resolvedType = normalizeProjectType(
1705
- String(projectType || response.projectType || "single")
1713
+ String(projectType || response.projectType || "multi")
1706
1714
  );
1707
1715
  if (resolvedType === "multi") {
1708
- const promptComponents = components.length > 0 ? components : ["fe", "be"];
1716
+ const promptComponents = components.length > 0 ? components : ["app"];
1709
1717
  const rootMap = {};
1710
1718
  if (typeof projectRoot === "string" && projectRoot.trim()) {
1711
1719
  Object.assign(rootMap, parseStandaloneMultiProjectRootJson(projectRoot));
@@ -1717,8 +1725,6 @@ async function runInit(options) {
1717
1725
  rootMap[normalized] = normalizedRoot;
1718
1726
  }
1719
1727
  }
1720
- if (options.feProjectRoot?.trim()) rootMap.fe = options.feProjectRoot.trim();
1721
- if (options.beProjectRoot?.trim()) rootMap.be = options.beProjectRoot.trim();
1722
1728
  Object.assign(rootMap, componentProjectRoots);
1723
1729
  for (const component of promptComponents) {
1724
1730
  const seeded = (rootMap[component] || "").trim();
@@ -1726,7 +1732,9 @@ async function runInit(options) {
1726
1732
  rootMap[component] = seeded;
1727
1733
  continue;
1728
1734
  }
1729
- const message = component === "fe" ? tr(lang, "cli", "init.prompt.feRepoPath") : component === "be" ? tr(lang, "cli", "init.prompt.beRepoPath") : tr(lang, "cli", "init.prompt.componentRepoPath", { component });
1735
+ const message = tr(lang, "cli", "init.prompt.componentRepoPath", {
1736
+ component
1737
+ });
1730
1738
  const response2 = await prompts(
1731
1739
  [
1732
1740
  {
@@ -1810,7 +1818,7 @@ async function runInit(options) {
1810
1818
  }
1811
1819
  }
1812
1820
  if (!projectType) {
1813
- projectType = "single";
1821
+ projectType = "multi";
1814
1822
  }
1815
1823
  assertValid(
1816
1824
  validateSafeNameWithLang(projectName, lang),
@@ -1842,12 +1850,12 @@ async function runInit(options) {
1842
1850
  }
1843
1851
  } else {
1844
1852
  if (components.length === 0) {
1845
- components = ["fe", "be"];
1853
+ components = ["app"];
1846
1854
  }
1847
1855
  components.forEach(assertValidComponentId);
1848
1856
  }
1849
1857
  if (docsRepo !== "standalone") {
1850
- if (options.projectRoot || options.feProjectRoot || options.beProjectRoot || options.componentProjectRoots || typeof options.pushDocs === "boolean" || options.docsRemote) {
1858
+ if (options.projectRoot || options.componentProjectRoots || typeof options.pushDocs === "boolean" || options.docsRemote) {
1851
1859
  throw createCliError(
1852
1860
  "INVALID_ARGUMENT",
1853
1861
  "Standalone-only options require `--docs-repo standalone`."
@@ -1869,8 +1877,6 @@ async function runInit(options) {
1869
1877
  multiRoot[normalized] = normalizedRoot;
1870
1878
  }
1871
1879
  }
1872
- if (options.feProjectRoot?.trim()) multiRoot.fe = options.feProjectRoot.trim();
1873
- if (options.beProjectRoot?.trim()) multiRoot.be = options.beProjectRoot.trim();
1874
1880
  Object.assign(multiRoot, componentProjectRoots);
1875
1881
  const unknownComponents = Object.keys(multiRoot).filter(
1876
1882
  (component) => !components.includes(component)
@@ -1900,12 +1906,6 @@ async function runInit(options) {
1900
1906
  "`--component-project-roots` can only be used when `--type multi`."
1901
1907
  );
1902
1908
  }
1903
- if (options.feProjectRoot || options.beProjectRoot) {
1904
- throw createCliError(
1905
- "INVALID_ARGUMENT",
1906
- "`--fe-project-root` and `--be-project-root` can only be used when `--type multi`."
1907
- );
1908
- }
1909
1909
  const singleRoot = typeof projectRoot === "string" ? projectRoot.trim() : "";
1910
1910
  if (!singleRoot) {
1911
1911
  throw createCliError(
@@ -1991,7 +1991,7 @@ async function runInit(options) {
1991
1991
  }
1992
1992
  }
1993
1993
  }
1994
- const featurePath = projectType === "multi" ? isDefaultFullstackComponents(components) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
1994
+ const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
1995
1995
  const replacements = {
1996
1996
  "{{projectName}}": projectName,
1997
1997
  "{{projectType}}": projectType,
@@ -2177,6 +2177,20 @@ function getSearchBaseDirs(cwd) {
2177
2177
  }
2178
2178
  return ancestors.slice(0, boundaryIndex + 1);
2179
2179
  }
2180
+ var FEATURE_FOLDER_PATTERN = /^F\d{3,}-/i;
2181
+ function normalizeComponentKeys(value) {
2182
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
2183
+ return Object.keys(value).map((key) => key.trim().toLowerCase()).filter(Boolean);
2184
+ }
2185
+ async function inferComponentsFromFeaturesDir(docsDir) {
2186
+ const featuresPath = path18.join(docsDir, "features");
2187
+ if (!await fs15.pathExists(featuresPath)) return [];
2188
+ const entries = await fs15.readdir(featuresPath, { withFileTypes: true });
2189
+ const inferred = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name.trim().toLowerCase()).filter(
2190
+ (name) => !!name && name !== "feature-base" && !FEATURE_FOLDER_PATTERN.test(name)
2191
+ );
2192
+ return [...new Set(inferred)];
2193
+ }
2180
2194
  async function getConfig(cwd) {
2181
2195
  const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
2182
2196
  const baseDirs = [
@@ -2199,9 +2213,13 @@ async function getConfig(cwd) {
2199
2213
  try {
2200
2214
  const configFile = await fs15.readJson(configPath);
2201
2215
  const projectType = normalizeProjectType(configFile.projectType);
2216
+ const inferredComponents = [
2217
+ ...normalizeComponentKeys(configFile.projectRoot),
2218
+ ...await inferComponentsFromFeaturesDir(resolvedDocsDir)
2219
+ ];
2202
2220
  const components = resolveProjectComponents(
2203
2221
  projectType,
2204
- configFile.components
2222
+ Array.isArray(configFile.components) && configFile.components.length > 0 ? configFile.components : inferredComponents
2205
2223
  );
2206
2224
  return {
2207
2225
  docsDir: resolvedDocsDir,
@@ -2223,10 +2241,9 @@ async function getConfig(cwd) {
2223
2241
  const agentsPath = path18.join(resolvedDocsDir, "agents");
2224
2242
  const featuresPath = path18.join(resolvedDocsDir, "features");
2225
2243
  if (await fs15.pathExists(agentsPath) && await fs15.pathExists(featuresPath)) {
2226
- const bePath = path18.join(featuresPath, "be");
2227
- const fePath = path18.join(featuresPath, "fe");
2228
- const projectType = await fs15.pathExists(bePath) || await fs15.pathExists(fePath) ? "multi" : "single";
2229
- const components = projectType === "multi" ? resolveProjectComponents("multi", ["fe", "be"]) : void 0;
2244
+ const inferredComponents = await inferComponentsFromFeaturesDir(resolvedDocsDir);
2245
+ const projectType = inferredComponents.length > 0 ? "multi" : "single";
2246
+ const components = projectType === "multi" ? resolveProjectComponents("multi", inferredComponents) : void 0;
2230
2247
  const langProbeCandidates = [
2231
2248
  path18.join(agentsPath, "custom.md"),
2232
2249
  path18.join(agentsPath, "constitution.md"),
@@ -2388,29 +2405,32 @@ async function runFeature(name, options) {
2388
2405
  );
2389
2406
  }
2390
2407
  if (projectType === "multi" && !component) {
2391
- if (options.nonInteractive) {
2408
+ if (configuredComponents.length === 1) {
2409
+ component = configuredComponents[0];
2410
+ } else if (options.nonInteractive) {
2392
2411
  throw createCliError(
2393
2412
  "PROMPT_BLOCKED",
2394
2413
  "`--component` is required in multi mode when using `--non-interactive`."
2395
2414
  );
2396
- }
2397
- const response = await prompts(
2398
- {
2399
- type: "select",
2400
- name: "component",
2401
- message: tr(lang, "cli", "feature.selectRepo"),
2402
- choices: configuredComponents.map((value) => ({
2403
- title: value.toUpperCase(),
2404
- value
2405
- }))
2406
- },
2407
- {
2408
- onCancel: () => {
2409
- throw new Error("canceled");
2415
+ } else {
2416
+ const response = await prompts(
2417
+ {
2418
+ type: "select",
2419
+ name: "component",
2420
+ message: tr(lang, "cli", "feature.selectRepo"),
2421
+ choices: configuredComponents.map((value) => ({
2422
+ title: value.toUpperCase(),
2423
+ value
2424
+ }))
2425
+ },
2426
+ {
2427
+ onCancel: () => {
2428
+ throw new Error("canceled");
2429
+ }
2410
2430
  }
2411
- }
2412
- );
2413
- component = response.component;
2431
+ );
2432
+ component = response.component;
2433
+ }
2414
2434
  }
2415
2435
  if (projectType === "multi") {
2416
2436
  assertAllowedComponent(component, configuredComponents);
@@ -2464,6 +2484,8 @@ async function runFeature(name, options) {
2464
2484
  "{\uBC88\uD638}": idNumber,
2465
2485
  "{\uACB0\uC815 \uC81C\uBAA9}": `${name} \uACB0\uC815`,
2466
2486
  "{YYYY-MM-DD}": getLocalDateString(),
2487
+ "{component}": component || "",
2488
+ "{{projectName}}-{component}": repoName,
2467
2489
  "{be|fe}": component || "",
2468
2490
  "{\uC774\uC288\uBC88\uD638}": "",
2469
2491
  "{{description}}": options.desc || "",
@@ -4477,7 +4499,7 @@ async function runUpdate(options) {
4477
4499
  const targetAgentsBase = path18.join(docsDir, "agents");
4478
4500
  const commonAgents = agentsMode === "skills" ? path18.join(commonAgentsBase, "skills") : commonAgentsBase;
4479
4501
  const targetAgents = agentsMode === "skills" ? path18.join(targetAgentsBase, "skills") : targetAgentsBase;
4480
- const featurePath = projectType === "multi" ? isDefaultFullstackComponents(config.components || []) ? "docs/features/{be|fe}" : "docs/features/{component}" : "docs/features";
4502
+ const featurePath = projectType === "multi" ? "docs/features/{component}" : "docs/features";
4481
4503
  const projectName = config.projectName ?? "{{projectName}}";
4482
4504
  const commonReplacements = {
4483
4505
  "{{projectName}}": projectName,
@@ -4821,31 +4843,34 @@ async function runConfig(options) {
4821
4843
  const components = resolveProjectComponents(projectType, configFile.components);
4822
4844
  let targetComponent = targetFromOptions;
4823
4845
  if (!targetComponent) {
4824
- if (options.nonInteractive) {
4846
+ if (components.length === 1) {
4847
+ targetComponent = components[0];
4848
+ } else if (options.nonInteractive) {
4825
4849
  throw createCliError(
4826
4850
  "PROMPT_BLOCKED",
4827
4851
  "`--component` is required for multi projectRoot update when using `--non-interactive`."
4828
4852
  );
4829
- }
4830
- const response = await prompts(
4831
- [
4853
+ } else {
4854
+ const response = await prompts(
4855
+ [
4856
+ {
4857
+ type: "select",
4858
+ name: "component",
4859
+ message: tr(config.lang, "cli", "config.selectRepoToUpdate"),
4860
+ choices: components.map((value) => ({
4861
+ title: value.toUpperCase(),
4862
+ value
4863
+ }))
4864
+ }
4865
+ ],
4832
4866
  {
4833
- type: "select",
4834
- name: "component",
4835
- message: tr(config.lang, "cli", "config.selectRepoToUpdate"),
4836
- choices: components.map((value) => ({
4837
- title: value.toUpperCase(),
4838
- value
4839
- }))
4840
- }
4841
- ],
4842
- {
4843
- onCancel: () => {
4844
- throw new Error("canceled");
4867
+ onCancel: () => {
4868
+ throw new Error("canceled");
4869
+ }
4845
4870
  }
4846
- }
4847
- );
4848
- targetComponent = response.component;
4871
+ );
4872
+ targetComponent = response.component;
4873
+ }
4849
4874
  }
4850
4875
  if (!targetComponent) {
4851
4876
  throw createCliError(
@@ -5274,7 +5299,7 @@ async function getBuiltinDoc(docId, projectType, lang) {
5274
5299
  }
5275
5300
 
5276
5301
  // src/commands/context.ts
5277
- var APPROVAL_TICKET_FILENAME = ".lee-spec-kit.approval-tickets.json";
5302
+ var LEGACY_APPROVAL_TICKET_FILENAME = ".lee-spec-kit.approval-tickets.json";
5278
5303
  var APPROVAL_TICKET_TTL_MS = 5 * 60 * 1e3;
5279
5304
  async function resolveContextState(config, featureName, options) {
5280
5305
  if (!config) {
@@ -5300,8 +5325,11 @@ function parseApprovalLabel(input, validLabels) {
5300
5325
  }
5301
5326
  return null;
5302
5327
  }
5303
- function getApprovalTicketPath(config) {
5304
- return path18.join(config.docsDir, APPROVAL_TICKET_FILENAME);
5328
+ function getApprovalTicketPaths(config) {
5329
+ return {
5330
+ runtimePath: getApprovalTicketStorePath(config.docsDir),
5331
+ legacyPath: path18.join(config.docsDir, LEGACY_APPROVAL_TICKET_FILENAME)
5332
+ };
5305
5333
  }
5306
5334
  function getApprovalSessionId() {
5307
5335
  const explicit = (process.env.LEE_SPEC_KIT_SESSION_ID || "").trim();
@@ -5327,6 +5355,41 @@ async function loadApprovalTicketStore(storePath) {
5327
5355
  return { tickets: [] };
5328
5356
  }
5329
5357
  }
5358
+ async function saveApprovalTicketStore(storePath, payload) {
5359
+ await fs15.ensureDir(path18.dirname(storePath));
5360
+ await fs15.writeJson(storePath, payload, { spaces: 2 });
5361
+ }
5362
+ async function resolveApprovalTicketStoreAndPath(config, nowMs) {
5363
+ const { runtimePath, legacyPath } = getApprovalTicketPaths(config);
5364
+ if (await fs15.pathExists(runtimePath)) {
5365
+ return {
5366
+ storePath: runtimePath,
5367
+ store: await loadApprovalTicketStore(runtimePath)
5368
+ };
5369
+ }
5370
+ if (!await fs15.pathExists(legacyPath)) {
5371
+ return {
5372
+ storePath: runtimePath,
5373
+ store: { tickets: [] }
5374
+ };
5375
+ }
5376
+ const legacyStore = await loadApprovalTicketStore(legacyPath);
5377
+ const migrated = pruneApprovalTickets(legacyStore.tickets, nowMs);
5378
+ await saveApprovalTicketStore(
5379
+ runtimePath,
5380
+ {
5381
+ tickets: migrated,
5382
+ updatedAt: new Date(nowMs).toISOString(),
5383
+ migratedFrom: legacyPath
5384
+ }
5385
+ );
5386
+ await fs15.remove(legacyPath).catch(() => {
5387
+ });
5388
+ return {
5389
+ storePath: runtimePath,
5390
+ store: { tickets: migrated }
5391
+ };
5392
+ }
5330
5393
  function pruneApprovalTickets(tickets, nowMs) {
5331
5394
  return tickets.filter((ticket) => {
5332
5395
  if (ticket.usedAt) return false;
@@ -5348,21 +5411,22 @@ async function issueApprovalTicket(config, payload) {
5348
5411
  createdAt: new Date(nowMs).toISOString(),
5349
5412
  expiresAt: new Date(nowMs + APPROVAL_TICKET_TTL_MS).toISOString()
5350
5413
  };
5351
- const storePath = getApprovalTicketPath(config);
5352
5414
  const lockPath = getDocsLockPath(config.docsDir);
5353
5415
  return withFileLock(
5354
5416
  lockPath,
5355
5417
  async () => {
5356
- const store = await loadApprovalTicketStore(storePath);
5418
+ const { storePath, store } = await resolveApprovalTicketStoreAndPath(
5419
+ config,
5420
+ nowMs
5421
+ );
5357
5422
  const nextTickets = pruneApprovalTickets(store.tickets, nowMs);
5358
5423
  nextTickets.push(record);
5359
- await fs15.writeJson(
5424
+ await saveApprovalTicketStore(
5360
5425
  storePath,
5361
5426
  {
5362
5427
  tickets: nextTickets,
5363
5428
  updatedAt: new Date(nowMs).toISOString()
5364
- },
5365
- { spaces: 2 }
5429
+ }
5366
5430
  );
5367
5431
  return record;
5368
5432
  },
@@ -5377,21 +5441,22 @@ async function consumeApprovalTicket(config, token, expected) {
5377
5441
  "Execution requires an approval ticket. Run `context --approve <reply> --json` first and pass `--ticket <token>`."
5378
5442
  );
5379
5443
  }
5380
- const storePath = getApprovalTicketPath(config);
5381
5444
  const lockPath = getDocsLockPath(config.docsDir);
5382
5445
  const sessionId = getApprovalSessionId();
5383
5446
  const nowMs = Date.now();
5384
5447
  return withFileLock(
5385
5448
  lockPath,
5386
5449
  async () => {
5387
- const store = await loadApprovalTicketStore(storePath);
5450
+ const { storePath, store } = await resolveApprovalTicketStoreAndPath(
5451
+ config,
5452
+ nowMs
5453
+ );
5388
5454
  const cleaned = pruneApprovalTickets(store.tickets, nowMs);
5389
5455
  const index = cleaned.findIndex((entry) => entry.token === normalizedToken);
5390
5456
  if (index < 0) {
5391
- await fs15.writeJson(
5457
+ await saveApprovalTicketStore(
5392
5458
  storePath,
5393
- { tickets: cleaned, updatedAt: new Date(nowMs).toISOString() },
5394
- { spaces: 2 }
5459
+ { tickets: cleaned, updatedAt: new Date(nowMs).toISOString() }
5395
5460
  );
5396
5461
  throw createCliError(
5397
5462
  "INVALID_APPROVAL",
@@ -5402,10 +5467,9 @@ async function consumeApprovalTicket(config, token, expected) {
5402
5467
  const expiresAtMs = Date.parse(record.expiresAt || "");
5403
5468
  if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) {
5404
5469
  cleaned.splice(index, 1);
5405
- await fs15.writeJson(
5470
+ await saveApprovalTicketStore(
5406
5471
  storePath,
5407
- { tickets: cleaned, updatedAt: new Date(nowMs).toISOString() },
5408
- { spaces: 2 }
5472
+ { tickets: cleaned, updatedAt: new Date(nowMs).toISOString() }
5409
5473
  );
5410
5474
  throw createCliError(
5411
5475
  "CONTEXT_STALE",
@@ -5443,13 +5507,12 @@ async function consumeApprovalTicket(config, token, expected) {
5443
5507
  );
5444
5508
  }
5445
5509
  cleaned.splice(index, 1);
5446
- await fs15.writeJson(
5510
+ await saveApprovalTicketStore(
5447
5511
  storePath,
5448
5512
  {
5449
5513
  tickets: cleaned,
5450
5514
  updatedAt: new Date(nowMs).toISOString()
5451
- },
5452
- { spaces: 2 }
5515
+ }
5453
5516
  );
5454
5517
  return record;
5455
5518
  },
@@ -6278,6 +6341,7 @@ function detectPlaceholders(content) {
6278
6341
  { key: "{feature-name}", re: /\{feature-name\}/g },
6279
6342
  { key: "{number}", re: /\{number\}/g },
6280
6343
  { key: "{issue-number}", re: /\{issue-number\}/g },
6344
+ { key: "{component}", re: /\{component\}/g },
6281
6345
  { key: "{be|fe}", re: /\{be\|fe\}/g },
6282
6346
  { key: "{Story Title}", re: /\{Story Title\}/g },
6283
6347
  { key: "{user type}", re: /\{user type\}/g },
@@ -6358,8 +6422,12 @@ ${line}
6358
6422
  }
6359
6423
  function applyPlaceholderFixes(content, context, lang) {
6360
6424
  const date = getLocalDateString();
6425
+ const projectName = context.projectName || "project";
6426
+ const repoName = context.repoType === "single" ? projectName : `${projectName}-${context.repoType}`;
6361
6427
  const replacements = {
6362
- "{{projectName}}": context.projectName || "project",
6428
+ "{{projectName}}": projectName,
6429
+ "{{projectName}}-{component}": repoName,
6430
+ "{{projectName}}-{be|fe}": repoName,
6363
6431
  "{{date}}": date,
6364
6432
  "{{featurePath}}": context.featurePath,
6365
6433
  "{{description}}": `${context.featureName} feature`,
@@ -6369,6 +6437,7 @@ function applyPlaceholderFixes(content, context, lang) {
6369
6437
  "{\uBC88\uD638}": context.featureNumber,
6370
6438
  "{issue-number}": "-",
6371
6439
  "{\uC774\uC288\uBC88\uD638}": "-",
6440
+ "{component}": context.repoType === "single" ? "" : context.repoType,
6372
6441
  "{be|fe}": context.repoType === "single" ? "" : context.repoType,
6373
6442
  "YYYY-MM-DD": date,
6374
6443
  "{Story Title}": `${context.featureName} user flow`,
@@ -7448,6 +7517,10 @@ function parsePrArtifactMode(raw, kind, lang) {
7448
7517
  tg(lang, "artifactModeInvalid", { kind, value })
7449
7518
  );
7450
7519
  }
7520
+ function isMermaidPreferredComponent(component) {
7521
+ const normalized = (component || "").trim().toLowerCase();
7522
+ return ["be", "backend", "api", "server", "core"].includes(normalized);
7523
+ }
7451
7524
  function resolvePrArtifactPolicy(config, feature, options) {
7452
7525
  const screenshotsMode = parsePrArtifactMode(
7453
7526
  options.screenshots,
@@ -7460,7 +7533,7 @@ function resolvePrArtifactPolicy(config, feature, options) {
7460
7533
  config.lang
7461
7534
  );
7462
7535
  const includeScreenshots = screenshotsMode === "on" ? true : screenshotsMode === "off" ? false : config.pr?.screenshots?.upload ?? false;
7463
- const includeMermaid = mermaidMode === "on" ? true : mermaidMode === "off" ? false : feature.type === "be";
7536
+ const includeMermaid = mermaidMode === "on" ? true : mermaidMode === "off" ? false : isMermaidPreferredComponent(feature.type);
7464
7537
  return {
7465
7538
  includeScreenshots,
7466
7539
  includeMermaid
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lee-spec-kit",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "description": "Project documentation structure generator for AI-assisted development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -58,8 +58,8 @@ In GitHub Issues, use different link formats **based on file location**:
58
58
  > Format: `- **{Label}**: \`{path}\``
59
59
 
60
60
  ```markdown
61
- - **Spec**: `docs/features/{be|fe}/F001-feature-name/spec.md`
62
- - **Tasks**: `docs/features/{be|fe}/F001-feature-name/tasks.md`
61
+ - **Spec**: `docs/features/{component}/F001-feature-name/spec.md`
62
+ - **Tasks**: `docs/features/{component}/F001-feature-name/tasks.md`
63
63
  ```
64
64
 
65
65
  > ⚠️ Local documents are not clickable on GitHub, so use **bold label + code block path** format instead of markdown links.
@@ -85,7 +85,7 @@ In GitHub Issues, use different link formats **based on file location**:
85
85
 
86
86
  ## Related Documents
87
87
 
88
- - **Spec**: `docs/features/{be|fe}/F{number}-{feature-name}/spec.md`
88
+ - **Spec**: `docs/features/{component}/F{number}-{feature-name}/spec.md`
89
89
 
90
90
  ## Labels
91
91
 
@@ -27,7 +27,7 @@ features/
27
27
  npx lee-spec-kit feature user-auth
28
28
 
29
29
  # Multi project
30
- npx lee-spec-kit feature --component fe user-profile
30
+ npx lee-spec-kit feature --component app user-profile
31
31
  ```
32
32
 
33
33
  > 💡 CLI copies templates from `feature-base/` and auto-assigns IDs.
@@ -7,7 +7,7 @@
7
7
  ## Overview
8
8
 
9
9
  - **Feature ID**: F{number}
10
- - **Target Repo**: {{projectName}}-{be|fe}
10
+ - **Target Repo**: {{projectName}}-{component}
11
11
  - **Created**: {YYYY-MM-DD}
12
12
  - **Status**: Review | Approved
13
13
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  - **Feature ID**: F{number}
10
10
  - **Feature Name**: {feature-name}
11
- - **Target Repo**: {{projectName}}-{be|fe}
11
+ - **Target Repo**: {{projectName}}-{component}
12
12
  - **Issue Number**: #{issue-number}
13
13
  - **Created**: {YYYY-MM-DD}
14
14
  - **Status**: Review | Approved
@@ -13,7 +13,7 @@
13
13
  ## GitHub Issue
14
14
 
15
15
  - **Doc Status**: Review | Approved
16
- - **Repo**: {{projectName}}-{be|fe}
16
+ - **Repo**: {{projectName}}-{component}
17
17
  - **Issue**: #{issue-number}
18
18
  - **Branch**: `feat/{issue-number}-{feature-name}`
19
19
  - **PR**: -
@@ -14,7 +14,7 @@ Core rule: once an idea becomes a Feature, the SSOT moves to `docs/features/`.
14
14
  - Put at least these at the top:
15
15
  - Goal / context
16
16
  - Rough scope (what’s in/out)
17
- - Target repo (optional): `be` / `fe` / `both`
17
+ - Target component (optional): `api` / `app` / `worker` / `all`
18
18
 
19
19
  ---
20
20
 
@@ -42,8 +42,8 @@ GitHub Issue에서 링크는 **파일 위치에 따라** 다르게 작성:
42
42
  > 형식: `- **{레이블}**: \`{경로}\``
43
43
 
44
44
  ```markdown
45
- - **Spec**: `docs/features/{be|fe}/F001-feature-name/spec.md`
46
- - **Tasks**: `docs/features/{be|fe}/F001-feature-name/tasks.md`
45
+ - **Spec**: `docs/features/{component}/F001-feature-name/spec.md`
46
+ - **Tasks**: `docs/features/{component}/F001-feature-name/tasks.md`
47
47
  ```
48
48
 
49
49
  > ⚠️ 로컬 문서는 GitHub에서 클릭되지 않으므로, 마크다운 링크 대신 **볼드 레이블 + 코드블록 경로** 형식을 사용합니다.
@@ -69,7 +69,7 @@ GitHub Issue에서 링크는 **파일 위치에 따라** 다르게 작성:
69
69
 
70
70
  ## 관련 문서
71
71
 
72
- - **Spec**: `docs/features/{be|fe}/F{번호}-{기능명}/spec.md`
72
+ - **Spec**: `docs/features/{component}/F{번호}-{기능명}/spec.md`
73
73
 
74
74
  ## 라벨
75
75
 
@@ -27,7 +27,7 @@ features/
27
27
  npx lee-spec-kit feature user-auth
28
28
 
29
29
  # Multi 프로젝트
30
- npx lee-spec-kit feature --component fe user-profile
30
+ npx lee-spec-kit feature --component app user-profile
31
31
  ```
32
32
 
33
33
  > 💡 CLI는 `feature-base/`에서 템플릿을 복사하고 ID를 자동 채번합니다.
@@ -7,7 +7,7 @@
7
7
  ## 개요
8
8
 
9
9
  - **기능 ID**: F{번호}
10
- - **대상 레포**: {{projectName}}-{be|fe}
10
+ - **대상 레포**: {{projectName}}-{component}
11
11
  - **작성일**: {YYYY-MM-DD}
12
12
  - **상태**: Review | Approved
13
13
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  - **기능 ID**: F{번호}
10
10
  - **기능명**: {기능명}
11
- - **대상 레포**: {{projectName}}-{be|fe}
11
+ - **대상 레포**: {{projectName}}-{component}
12
12
  - **이슈 번호**: #{이슈번호}
13
13
  - **작성일**: {YYYY-MM-DD}
14
14
  - **상태**: Review | Approved
@@ -13,7 +13,7 @@
13
13
  ## GitHub Issue
14
14
 
15
15
  - **문서 상태**: Review | Approved
16
- - **레포**: {{projectName}}-{be|fe}
16
+ - **레포**: {{projectName}}-{component}
17
17
  - **Issue**: #{이슈번호}
18
18
  - **브랜치**: `feat/{이슈번호}-{기능명}`
19
19
  - **PR**: -
@@ -14,7 +14,7 @@ Feature로 발전하기 전의 아이디어 / To-do / 실험 기록을 모아두
14
14
  - 상단에 최소한 아래 메타 정보를 둡니다:
15
15
  - 목적/배경
16
16
  - 대략 범위(뭘 할지/안 할지)
17
- - 대상 레포(필요 시): `be` / `fe` / `both`
17
+ - 대상 컴포넌트(필요 시): `api` / `app` / `worker` / `all`
18
18
 
19
19
  ---
20
20