lee-spec-kit 0.4.7 → 0.4.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 +191 -0
- package/README.md +18 -3
- package/assets/logo.png +0 -0
- package/dist/index.js +130 -23
- package/package.json +3 -2
package/README.en.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<strong>lee-spec-kit</strong>
|
|
3
|
+
</h1>
|
|
4
|
+
|
|
5
|
+
<div align="center">
|
|
6
|
+
<img src="./assets/logo.png" alt="lee-spec-kit logo" width="620" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>CLI to generate a project docs structure for AI-assisted development</strong>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://www.npmjs.com/package/lee-spec-kit"><img src="https://img.shields.io/npm/v/lee-spec-kit.svg" alt="npm version"></a>
|
|
15
|
+
<img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node.js">
|
|
16
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="#quick-start">Quick Start</a> •
|
|
21
|
+
<a href="#features">Features</a> •
|
|
22
|
+
<a href="#usage">Usage</a> •
|
|
23
|
+
<a href="#generated-structure">Generated Structure</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="./README.en.md">
|
|
28
|
+
<img src="https://img.shields.io/badge/lang-en-red.svg" alt="English">
|
|
29
|
+
</a>
|
|
30
|
+
<a href="./README.md">
|
|
31
|
+
<img src="https://img.shields.io/badge/lang-ko-blue.svg" alt="한국어">
|
|
32
|
+
</a>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 1) Initialize docs structure
|
|
41
|
+
npx lee-spec-kit init
|
|
42
|
+
|
|
43
|
+
# 2) Create a feature
|
|
44
|
+
npx lee-spec-kit feature user-auth
|
|
45
|
+
|
|
46
|
+
# 3) Show next steps (for agents)
|
|
47
|
+
npx lee-spec-kit context
|
|
48
|
+
|
|
49
|
+
# 4) Show overall status
|
|
50
|
+
npx lee-spec-kit status
|
|
51
|
+
|
|
52
|
+
# 5) Validate docs / feature metadata
|
|
53
|
+
npx lee-spec-kit doctor
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
### 📁 Project initialization
|
|
59
|
+
|
|
60
|
+
- Interactive init or CLI options
|
|
61
|
+
- Supports `single` and `fullstack` (FE/BE split)
|
|
62
|
+
- Korean/English templates
|
|
63
|
+
|
|
64
|
+
### 🚀 Feature creation
|
|
65
|
+
|
|
66
|
+
- Generates `spec.md`, `plan.md`, `tasks.md`, `decisions.md`
|
|
67
|
+
- Fullstack mode supports FE/BE separation
|
|
68
|
+
- Integrates Issue/PR templates (docs side)
|
|
69
|
+
|
|
70
|
+
### 📊 Status management
|
|
71
|
+
|
|
72
|
+
- View feature progress at a glance
|
|
73
|
+
- Print to terminal or write a Markdown report
|
|
74
|
+
|
|
75
|
+
### 🩺 Doctor
|
|
76
|
+
|
|
77
|
+
- Checks docs structure and feature metadata (missing status, duplicate IDs, placeholders, etc.)
|
|
78
|
+
- `--json` output for automation/agents
|
|
79
|
+
|
|
80
|
+
### 🔄 Template updates
|
|
81
|
+
|
|
82
|
+
- Updates docs templates to the latest version
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
### Init
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx lee-spec-kit init
|
|
90
|
+
npx lee-spec-kit init --name my-project --type fullstack
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Options:**
|
|
94
|
+
|
|
95
|
+
| Option | Description | Default |
|
|
96
|
+
| ------------------- | ----------------------------------------- | ------------- |
|
|
97
|
+
| `-n, --name <name>` | Project name | current folder |
|
|
98
|
+
| `-t, --type <type>` | `single` or `fullstack` | interactive |
|
|
99
|
+
| `-l, --lang <lang>` | `ko` or `en` | `en` |
|
|
100
|
+
| `-d, --dir <dir>` | Install directory | `./docs` |
|
|
101
|
+
| `-y, --yes` | Skip interactive prompts | - |
|
|
102
|
+
|
|
103
|
+
### Create a feature
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Single
|
|
107
|
+
npx lee-spec-kit feature user-auth
|
|
108
|
+
|
|
109
|
+
# Fullstack
|
|
110
|
+
npx lee-spec-kit feature --repo be user-auth
|
|
111
|
+
npx lee-spec-kit feature --repo fe user-profile
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Context (agent guide)
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Auto-detect (based on git branch)
|
|
118
|
+
npx lee-spec-kit context
|
|
119
|
+
|
|
120
|
+
# Specify a feature
|
|
121
|
+
npx lee-spec-kit context user-auth
|
|
122
|
+
|
|
123
|
+
# Selector: Feature ID / folder name
|
|
124
|
+
npx lee-spec-kit context F001
|
|
125
|
+
npx lee-spec-kit context F001-user-auth
|
|
126
|
+
|
|
127
|
+
# JSON output (for agents)
|
|
128
|
+
npx lee-spec-kit context --json
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Status
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx lee-spec-kit status
|
|
135
|
+
npx lee-spec-kit status --write
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Doctor
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx lee-spec-kit doctor
|
|
142
|
+
npx lee-spec-kit doctor --strict
|
|
143
|
+
npx lee-spec-kit doctor --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Update templates
|
|
147
|
+
|
|
148
|
+
By default, `update` runs only when the `docs/` working tree is clean; in that case it overwrites changed files without prompting.
|
|
149
|
+
If you want to update while you have uncommitted changes, use `--force`.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx lee-spec-kit update
|
|
153
|
+
npx lee-spec-kit update --agents
|
|
154
|
+
npx lee-spec-kit update --skills
|
|
155
|
+
npx lee-spec-kit update --templates
|
|
156
|
+
npx lee-spec-kit update --force
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Configuration
|
|
160
|
+
|
|
161
|
+
### `.lee-spec-kit.json`
|
|
162
|
+
|
|
163
|
+
Running `init` creates `.lee-spec-kit.json` in your docs root (default: `docs/`).
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"projectName": "my-project",
|
|
168
|
+
"projectType": "single",
|
|
169
|
+
"lang": "en",
|
|
170
|
+
"createdAt": "YYYY-MM-DD",
|
|
171
|
+
"docsRepo": "embedded"
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
| Field | Description |
|
|
176
|
+
| ------------- | ------------------------------------------------ |
|
|
177
|
+
| `projectName` | Project name |
|
|
178
|
+
| `projectType` | `single` or `fullstack` |
|
|
179
|
+
| `lang` | `ko` or `en` |
|
|
180
|
+
| `createdAt` | Creation date |
|
|
181
|
+
| `docsRepo` | `embedded` or `standalone` |
|
|
182
|
+
| `pushDocs` | (standalone only) whether to manage/push docs repo as a separate git repo |
|
|
183
|
+
| `docsRemote` | (standalone + pushDocs) docs repo remote URL |
|
|
184
|
+
| `projectRoot` | (standalone only) project repo path (single: string, fullstack: {fe, be}) |
|
|
185
|
+
|
|
186
|
+
> In standalone mode, `init` can add `pushDocs`, `docsRemote`, and `projectRoot` to this config.
|
|
187
|
+
> If you run the CLI outside the docs repo in standalone mode, set `LEE_SPEC_KIT_DOCS_DIR` to the docs repo path.
|
|
188
|
+
|
|
189
|
+
## Generated Structure
|
|
190
|
+
|
|
191
|
+
See the Korean README for the full tree examples and workflow details: `README.md`.
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
<strong>lee-spec-kit</strong>
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
+
<div align="center">
|
|
6
|
+
<img src="./assets/logo.png" alt="lee-spec-kit logo" width="620" />
|
|
7
|
+
</div>
|
|
8
|
+
|
|
5
9
|
<p align="center">
|
|
6
10
|
<strong>AI 에이전트 기반 개발을 위한 프로젝트 문서 구조 생성 CLI</strong>
|
|
7
11
|
</p>
|
|
@@ -173,6 +177,9 @@ npx lee-spec-kit doctor --json
|
|
|
173
177
|
|
|
174
178
|
### 템플릿 업데이트
|
|
175
179
|
|
|
180
|
+
기본 동작은 `docs/` 작업트리에 변경사항이 없을 때만 업데이트를 진행하며, 이 경우 변경된 파일은 확인 없이 덮어씁니다.
|
|
181
|
+
변경사항이 있는 상태에서 업데이트하려면 `--force`를 사용하세요.
|
|
182
|
+
|
|
176
183
|
```bash
|
|
177
184
|
# 전체 업데이트
|
|
178
185
|
npx lee-spec-kit update
|
|
@@ -186,7 +193,7 @@ npx lee-spec-kit update --skills
|
|
|
186
193
|
# feature-base/ 폴더만 업데이트
|
|
187
194
|
npx lee-spec-kit update --templates
|
|
188
195
|
|
|
189
|
-
#
|
|
196
|
+
# 변경사항이 있어도 강제 덮어쓰기
|
|
190
197
|
npx lee-spec-kit update --force
|
|
191
198
|
```
|
|
192
199
|
|
|
@@ -213,12 +220,14 @@ npx lee-spec-kit update --force
|
|
|
213
220
|
| `lang` | `ko` 또는 `en` |
|
|
214
221
|
| `createdAt` | 생성 날짜 |
|
|
215
222
|
| `docsRepo` | `embedded` 또는 `standalone` |
|
|
216
|
-
| `
|
|
223
|
+
| `pushDocs` | (standalone만) docs 레포를 별도 Git으로 관리/푸시할지 여부 |
|
|
224
|
+
| `docsRemote` | (standalone+pushDocs) docs 레포 remote URL |
|
|
225
|
+
| `projectRoot` | (standalone만) 프로젝트 레포지토리 경로 (single: string, fullstack: {fe, be}) |
|
|
217
226
|
|
|
218
227
|
> `docsRepo: "standalone"`을 선택하면 `pushDocs`, `docsRemote`, `projectRoot`가 추가됩니다.
|
|
219
228
|
|
|
220
229
|
> 어디서 실행하든 설정을 찾을 수 있도록, CLI는 현재 디렉토리에서 상위로 올라가며 `.lee-spec-kit.json` 또는 `docs/.lee-spec-kit.json`을 탐색합니다.
|
|
221
|
-
> standalone 환경에서 docs 레포 바깥(예: 프로젝트 레포)에서 실행해야 한다면 `LEE_SPEC_KIT_DOCS_DIR
|
|
230
|
+
> standalone 환경에서 docs 레포 바깥(예: 프로젝트 레포)에서 실행해야 한다면 `LEE_SPEC_KIT_DOCS_DIR`에 docs 레포 경로를 지정할 수 있습니다.
|
|
222
231
|
|
|
223
232
|
### Standalone 프로젝트 설정 예시
|
|
224
233
|
|
|
@@ -228,7 +237,10 @@ npx lee-spec-kit update --force
|
|
|
228
237
|
{
|
|
229
238
|
"projectName": "my-project",
|
|
230
239
|
"projectType": "single",
|
|
240
|
+
"lang": "ko",
|
|
241
|
+
"createdAt": "YYYY-MM-DD",
|
|
231
242
|
"docsRepo": "standalone",
|
|
243
|
+
"pushDocs": false,
|
|
232
244
|
"projectRoot": "/path/to/my-project"
|
|
233
245
|
}
|
|
234
246
|
```
|
|
@@ -239,7 +251,10 @@ npx lee-spec-kit update --force
|
|
|
239
251
|
{
|
|
240
252
|
"projectName": "my-project",
|
|
241
253
|
"projectType": "fullstack",
|
|
254
|
+
"lang": "ko",
|
|
255
|
+
"createdAt": "YYYY-MM-DD",
|
|
242
256
|
"docsRepo": "standalone",
|
|
257
|
+
"pushDocs": false,
|
|
243
258
|
"projectRoot": {
|
|
244
259
|
"fe": "/path/to/frontend",
|
|
245
260
|
"be": "/path/to/backend"
|
package/assets/logo.png
ADDED
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -18,21 +18,25 @@ async function copyTemplates(src, dest) {
|
|
|
18
18
|
errorOnExist: false
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
+
function applyReplacements(content, replacements) {
|
|
22
|
+
const keys = Object.keys(replacements).sort((a, b) => b.length - a.length);
|
|
23
|
+
let next = content;
|
|
24
|
+
for (const key of keys) {
|
|
25
|
+
next = next.replaceAll(key, replacements[key]);
|
|
26
|
+
}
|
|
27
|
+
return next;
|
|
28
|
+
}
|
|
21
29
|
async function replaceInFiles(dir, replacements) {
|
|
22
30
|
const files = await glob("**/*.md", { cwd: dir, absolute: true });
|
|
23
31
|
for (const file of files) {
|
|
24
32
|
let content = await fs8.readFile(file, "utf-8");
|
|
25
|
-
|
|
26
|
-
content = content.replaceAll(search, replace);
|
|
27
|
-
}
|
|
33
|
+
content = applyReplacements(content, replacements);
|
|
28
34
|
await fs8.writeFile(file, content, "utf-8");
|
|
29
35
|
}
|
|
30
36
|
const shFiles = await glob("**/*.sh", { cwd: dir, absolute: true });
|
|
31
37
|
for (const file of shFiles) {
|
|
32
38
|
let content = await fs8.readFile(file, "utf-8");
|
|
33
|
-
|
|
34
|
-
content = content.replaceAll(search, replace);
|
|
35
|
-
}
|
|
39
|
+
content = applyReplacements(content, replacements);
|
|
36
40
|
await fs8.writeFile(file, content, "utf-8");
|
|
37
41
|
}
|
|
38
42
|
}
|
|
@@ -93,6 +97,8 @@ var I18N = {
|
|
|
93
97
|
"update.updatedTotal": "\uCD1D {count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!",
|
|
94
98
|
"update.changeDetected": "\uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)",
|
|
95
99
|
"update.fileUpdated": "{file} \uC5C5\uB370\uC774\uD2B8",
|
|
100
|
+
"update.gitStatusUnavailable": "git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (git repo\uAC00 \uC544\uB2C8\uAC70\uB098 git \uC2E4\uD589 \uBD88\uAC00) --force\uB85C \uAC15\uC81C \uB36E\uC5B4\uC4F0\uAE30\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.",
|
|
101
|
+
"update.docsWorktreeDirty": "docs \uC791\uC5C5\uD2B8\uB9AC\uC5D0 \uBCC0\uACBD\uC0AC\uD56D\uC774 \uC788\uC5B4 update\uB97C \uC9C4\uD589\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBCC0\uACBD\uC0AC\uD56D\uC744 \uCEE4\uBC0B/\uC2A4\uD0DC\uC2DC \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uAC70\uB098 --force\uB85C \uB36E\uC5B4\uC4F0\uC138\uC694.",
|
|
96
102
|
"doctor.title": "\u{1F50E} \uBB38\uC11C \uC9C4\uB2E8",
|
|
97
103
|
"doctor.envWarnings": "\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:",
|
|
98
104
|
"doctor.noIssues": "\u2705 \uBB38\uC81C\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
|
|
@@ -266,6 +272,8 @@ var I18N = {
|
|
|
266
272
|
"update.updatedTotal": "Updated {count} files!",
|
|
267
273
|
"update.changeDetected": "changes detected (use --force to overwrite)",
|
|
268
274
|
"update.fileUpdated": "{file} updated",
|
|
275
|
+
"update.gitStatusUnavailable": "Cannot determine git status (not a git repo or git unavailable). Use --force to overwrite.",
|
|
276
|
+
"update.docsWorktreeDirty": "Docs working tree has changes. Commit/stash your changes, or run with --force to overwrite.",
|
|
269
277
|
"doctor.title": "\u{1F50E} Docs Doctor",
|
|
270
278
|
"doctor.envWarnings": "\u26A0\uFE0F Environment warnings:",
|
|
271
279
|
"doctor.noIssues": "\u2705 No issues found.",
|
|
@@ -901,7 +909,7 @@ function getAncestorDirs(startDir) {
|
|
|
901
909
|
return dirs;
|
|
902
910
|
}
|
|
903
911
|
async function getConfig(cwd) {
|
|
904
|
-
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR ||
|
|
912
|
+
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || "").trim();
|
|
905
913
|
const baseDirs = [
|
|
906
914
|
...explicitDocsDir ? [path4.resolve(explicitDocsDir)] : [],
|
|
907
915
|
...getAncestorDirs(cwd)
|
|
@@ -983,6 +991,7 @@ async function runFeature(name, options) {
|
|
|
983
991
|
process.exit(1);
|
|
984
992
|
}
|
|
985
993
|
const { docsDir, projectType, lang } = config;
|
|
994
|
+
const projectName = config.projectName;
|
|
986
995
|
assertValid(validateSafeName(name), "\uAE30\uB2A5 \uC774\uB984");
|
|
987
996
|
let repo = options.repo;
|
|
988
997
|
if (projectType === "fullstack" && !repo) {
|
|
@@ -1043,6 +1052,7 @@ async function runFeature(name, options) {
|
|
|
1043
1052
|
const idNumber = featureId.replace("F", "");
|
|
1044
1053
|
const repoName = projectType === "fullstack" && repo ? `{{projectName}}-${repo}` : "{{projectName}}";
|
|
1045
1054
|
const replacements = {
|
|
1055
|
+
"{{projectName}}": projectName ?? "{{projectName}}",
|
|
1046
1056
|
// ko placeholders
|
|
1047
1057
|
"{\uAE30\uB2A5\uBA85}": name,
|
|
1048
1058
|
"{\uBC88\uD638}": idNumber,
|
|
@@ -2065,18 +2075,28 @@ async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderNa
|
|
|
2065
2075
|
return fallbackSlug || fallbackFolderName;
|
|
2066
2076
|
}
|
|
2067
2077
|
function updateCommand(program2) {
|
|
2068
|
-
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "Update agents/skills folder only").option("--templates", "Update feature-base/ folder only").option(
|
|
2078
|
+
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--skills", "Update agents/skills folder only").option("--templates", "Update feature-base/ folder only").option(
|
|
2079
|
+
"-f, --force",
|
|
2080
|
+
"Force overwrite even if docs has uncommitted changes"
|
|
2081
|
+
).action(async (options) => {
|
|
2069
2082
|
try {
|
|
2070
2083
|
await runUpdate(options);
|
|
2071
2084
|
} catch (error) {
|
|
2085
|
+
const config = await getConfig(process.cwd());
|
|
2086
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
2072
2087
|
if (error instanceof Error && error.message === "canceled") {
|
|
2073
|
-
const config = await getConfig(process.cwd());
|
|
2074
|
-
const lang = config?.lang ?? DEFAULT_LANG;
|
|
2075
2088
|
console.log(chalk6.yellow(`
|
|
2076
2089
|
${tr(lang, "cli", "common.canceled")}`));
|
|
2077
2090
|
process.exit(0);
|
|
2078
2091
|
}
|
|
2079
|
-
|
|
2092
|
+
if (error instanceof Error) {
|
|
2093
|
+
console.error(
|
|
2094
|
+
chalk6.red(tr(lang, "cli", "common.errorLabel")),
|
|
2095
|
+
chalk6.red(error.message)
|
|
2096
|
+
);
|
|
2097
|
+
} else {
|
|
2098
|
+
console.error(chalk6.red(tr(lang, "cli", "common.errorLabel")), error);
|
|
2099
|
+
}
|
|
2080
2100
|
process.exit(1);
|
|
2081
2101
|
}
|
|
2082
2102
|
});
|
|
@@ -2096,6 +2116,7 @@ async function runUpdate(options) {
|
|
|
2096
2116
|
const { docsDir, projectType, lang } = config;
|
|
2097
2117
|
const templatesDir = getTemplatesDir();
|
|
2098
2118
|
const sourceDir = path4.join(templatesDir, lang, projectType);
|
|
2119
|
+
const forceOverwrite = !!options.force || await isDocsWorktreeCleanOrThrow(docsDir, lang);
|
|
2099
2120
|
const hasExplicitSelection = !!(options.agents || options.skills || options.templates);
|
|
2100
2121
|
const updateAgents = options.agents || options.skills || !hasExplicitSelection;
|
|
2101
2122
|
const updateTemplates = options.templates || !hasExplicitSelection;
|
|
@@ -2120,15 +2141,20 @@ async function runUpdate(options) {
|
|
|
2120
2141
|
const typeAgents = agentsMode === "skills" ? path4.join(typeAgentsBase, "skills") : typeAgentsBase;
|
|
2121
2142
|
const targetAgents = agentsMode === "skills" ? path4.join(targetAgentsBase, "skills") : targetAgentsBase;
|
|
2122
2143
|
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
2123
|
-
const
|
|
2144
|
+
const projectName = config.projectName ?? "{{projectName}}";
|
|
2145
|
+
const commonReplacements = {
|
|
2146
|
+
"{{projectName}}": projectName,
|
|
2124
2147
|
"{{featurePath}}": featurePath
|
|
2125
2148
|
};
|
|
2149
|
+
const typeReplacements = {
|
|
2150
|
+
"{{projectName}}": projectName
|
|
2151
|
+
};
|
|
2126
2152
|
if (await fs8.pathExists(commonAgents)) {
|
|
2127
2153
|
const count = await updateFolder(
|
|
2128
2154
|
commonAgents,
|
|
2129
2155
|
targetAgents,
|
|
2130
|
-
|
|
2131
|
-
|
|
2156
|
+
forceOverwrite,
|
|
2157
|
+
commonReplacements,
|
|
2132
2158
|
lang
|
|
2133
2159
|
);
|
|
2134
2160
|
updatedCount += count;
|
|
@@ -2137,8 +2163,8 @@ async function runUpdate(options) {
|
|
|
2137
2163
|
const count = await updateFolder(
|
|
2138
2164
|
typeAgents,
|
|
2139
2165
|
targetAgents,
|
|
2140
|
-
|
|
2141
|
-
|
|
2166
|
+
forceOverwrite,
|
|
2167
|
+
typeReplacements,
|
|
2142
2168
|
lang
|
|
2143
2169
|
);
|
|
2144
2170
|
updatedCount += count;
|
|
@@ -2154,11 +2180,14 @@ async function runUpdate(options) {
|
|
|
2154
2180
|
const sourceFeatureBase = path4.join(sourceDir, "features", "feature-base");
|
|
2155
2181
|
const targetFeatureBase = path4.join(docsDir, "features", "feature-base");
|
|
2156
2182
|
if (await fs8.pathExists(sourceFeatureBase)) {
|
|
2183
|
+
const replacements = {
|
|
2184
|
+
"{{projectName}}": config.projectName ?? "{{projectName}}"
|
|
2185
|
+
};
|
|
2157
2186
|
const count = await updateFolder(
|
|
2158
2187
|
sourceFeatureBase,
|
|
2159
2188
|
targetFeatureBase,
|
|
2160
|
-
|
|
2161
|
-
|
|
2189
|
+
forceOverwrite,
|
|
2190
|
+
replacements,
|
|
2162
2191
|
lang
|
|
2163
2192
|
);
|
|
2164
2193
|
updatedCount += count;
|
|
@@ -2191,9 +2220,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
2191
2220
|
}
|
|
2192
2221
|
let sourceContent = await fs8.readFile(sourcePath, "utf-8");
|
|
2193
2222
|
if (replacements) {
|
|
2194
|
-
|
|
2195
|
-
sourceContent = sourceContent.replaceAll(key, value);
|
|
2196
|
-
}
|
|
2223
|
+
sourceContent = applyReplacements(sourceContent, replacements);
|
|
2197
2224
|
}
|
|
2198
2225
|
let shouldUpdate = true;
|
|
2199
2226
|
if (await fs8.pathExists(targetPath)) {
|
|
@@ -2230,6 +2257,42 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
2230
2257
|
}
|
|
2231
2258
|
return updatedCount;
|
|
2232
2259
|
}
|
|
2260
|
+
function getGitTopLevel2(cwd) {
|
|
2261
|
+
try {
|
|
2262
|
+
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
2263
|
+
cwd,
|
|
2264
|
+
encoding: "utf-8",
|
|
2265
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2266
|
+
}).trim();
|
|
2267
|
+
return out || null;
|
|
2268
|
+
} catch {
|
|
2269
|
+
return null;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function getDocsPorcelainStatus(docsDir) {
|
|
2273
|
+
const top = getGitTopLevel2(docsDir);
|
|
2274
|
+
if (!top) return null;
|
|
2275
|
+
const rel = path4.relative(top, docsDir) || ".";
|
|
2276
|
+
try {
|
|
2277
|
+
return execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
2278
|
+
cwd: top,
|
|
2279
|
+
encoding: "utf-8",
|
|
2280
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2281
|
+
});
|
|
2282
|
+
} catch {
|
|
2283
|
+
return null;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
async function isDocsWorktreeCleanOrThrow(docsDir, lang) {
|
|
2287
|
+
const status = getDocsPorcelainStatus(docsDir);
|
|
2288
|
+
if (status === null) {
|
|
2289
|
+
throw new Error(tr(lang, "cli", "update.gitStatusUnavailable"));
|
|
2290
|
+
}
|
|
2291
|
+
if (status.trim().length > 0) {
|
|
2292
|
+
throw new Error(tr(lang, "cli", "update.docsWorktreeDirty"));
|
|
2293
|
+
}
|
|
2294
|
+
return true;
|
|
2295
|
+
}
|
|
2233
2296
|
function configCommand(program2) {
|
|
2234
2297
|
program2.command("config").description("View or modify project configuration").option("--project-root <path>", "Set project root path").option("--repo <repo>", "Repository type for fullstack: fe | be").action(async (options) => {
|
|
2235
2298
|
try {
|
|
@@ -2930,6 +2993,49 @@ function doctorCommand(program2) {
|
|
|
2930
2993
|
process.exit(exitCode);
|
|
2931
2994
|
});
|
|
2932
2995
|
}
|
|
2996
|
+
function isBannerDisabled() {
|
|
2997
|
+
const v = (process.env.LEE_SPEC_KIT_NO_BANNER || "").trim();
|
|
2998
|
+
return v === "1";
|
|
2999
|
+
}
|
|
3000
|
+
function getBanner(opts) {
|
|
3001
|
+
if (isBannerDisabled()) return "";
|
|
3002
|
+
const lee = `
|
|
3003
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3004
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3005
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3006
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3007
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3008
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3009
|
+
\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3010
|
+
`;
|
|
3011
|
+
const spec = `
|
|
3012
|
+
\u2591\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3013
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3014
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3015
|
+
\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588
|
|
3016
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3017
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3018
|
+
\u2591\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3019
|
+
`;
|
|
3020
|
+
const kit = `
|
|
3021
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588
|
|
3022
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3023
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3024
|
+
\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3025
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3026
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588
|
|
3027
|
+
\u2591\u2588\u2588 \u2591\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588
|
|
3028
|
+
`;
|
|
3029
|
+
const ascii = `${lee}${spec}${kit}`;
|
|
3030
|
+
const version = opts?.version ? `v${opts.version}` : "";
|
|
3031
|
+
const footer = version ? `
|
|
3032
|
+
${version}
|
|
3033
|
+
` : "\n";
|
|
3034
|
+
if (process.stdout.isTTY) {
|
|
3035
|
+
return `${chalk6.cyan(ascii)}${chalk6.gray(footer)}`;
|
|
3036
|
+
}
|
|
3037
|
+
return `${ascii}${footer}`;
|
|
3038
|
+
}
|
|
2933
3039
|
var CACHE_FILE = path4.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
2934
3040
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
2935
3041
|
function getCurrentVersion() {
|
|
@@ -3014,7 +3120,7 @@ function shouldCheckForUpdates() {
|
|
|
3014
3120
|
const argv = process.argv.slice(2);
|
|
3015
3121
|
const hasJsonFlag = argv.includes("--json");
|
|
3016
3122
|
const isHelpOrVersion = argv.includes("--help") || argv.includes("-h") || argv.includes("--version") || argv.includes("-V");
|
|
3017
|
-
const disabledByEnv = (process.env.
|
|
3123
|
+
const disabledByEnv = (process.env.LEE_SPEC_KIT_NO_UPDATE_CHECK || "").trim() === "1";
|
|
3018
3124
|
if (hasJsonFlag) return false;
|
|
3019
3125
|
if (!process.stdout.isTTY) return false;
|
|
3020
3126
|
if (isHelpOrVersion) return false;
|
|
@@ -3033,9 +3139,10 @@ function getCliVersion() {
|
|
|
3033
3139
|
}
|
|
3034
3140
|
return "0.0.0";
|
|
3035
3141
|
}
|
|
3142
|
+
var cliVersion = getCliVersion();
|
|
3036
3143
|
program.name("lee-spec-kit").description(
|
|
3037
3144
|
"Project documentation structure generator for AI-assisted development"
|
|
3038
|
-
).version(
|
|
3145
|
+
).version(cliVersion).addHelpText("beforeAll", getBanner({ version: cliVersion }));
|
|
3039
3146
|
initCommand(program);
|
|
3040
3147
|
featureCommand(program);
|
|
3041
3148
|
statusCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lee-spec-kit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Project documentation structure generator for AI-assisted development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
-
"templates"
|
|
13
|
+
"templates",
|
|
14
|
+
"assets"
|
|
14
15
|
],
|
|
15
16
|
"keywords": [
|
|
16
17
|
"docs",
|