lee-spec-kit 0.4.8 → 0.4.10
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 +7 -2
- package/README.md +13 -2
- package/dist/index.js +140 -23
- package/package.json +1 -1
- package/templates/en/common/agents/pr-template.md +14 -4
- package/templates/en/common/agents/skills/create-pr.md +49 -2
- package/templates/en/common/agents/skills/execute-task.md +6 -0
- package/templates/en/fullstack/features/feature-base/tasks.md +1 -0
- package/templates/en/single/features/feature-base/tasks.md +1 -0
- package/templates/ko/common/agents/pr-template.md +14 -4
- package/templates/ko/common/agents/skills/create-pr.md +49 -2
- package/templates/ko/common/agents/skills/execute-task.md +4 -2
- package/templates/ko/fullstack/features/feature-base/tasks.md +1 -0
- package/templates/ko/single/features/feature-base/tasks.md +1 -0
package/README.en.md
CHANGED
|
@@ -145,6 +145,9 @@ npx lee-spec-kit doctor --json
|
|
|
145
145
|
|
|
146
146
|
### Update templates
|
|
147
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
|
+
|
|
148
151
|
```bash
|
|
149
152
|
npx lee-spec-kit update
|
|
150
153
|
npx lee-spec-kit update --agents
|
|
@@ -176,11 +179,13 @@ Running `init` creates `.lee-spec-kit.json` in your docs root (default: `docs/`)
|
|
|
176
179
|
| `lang` | `ko` or `en` |
|
|
177
180
|
| `createdAt` | Creation date |
|
|
178
181
|
| `docsRepo` | `embedded` or `standalone` |
|
|
179
|
-
| `
|
|
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}) |
|
|
180
185
|
|
|
186
|
+
> In standalone mode, `init` can add `pushDocs`, `docsRemote`, and `projectRoot` to this config.
|
|
181
187
|
> If you run the CLI outside the docs repo in standalone mode, set `LEE_SPEC_KIT_DOCS_DIR` to the docs repo path.
|
|
182
188
|
|
|
183
189
|
## Generated Structure
|
|
184
190
|
|
|
185
191
|
See the Korean README for the full tree examples and workflow details: `README.md`.
|
|
186
|
-
|
package/README.md
CHANGED
|
@@ -177,6 +177,9 @@ npx lee-spec-kit doctor --json
|
|
|
177
177
|
|
|
178
178
|
### 템플릿 업데이트
|
|
179
179
|
|
|
180
|
+
기본 동작은 `docs/` 작업트리에 변경사항이 없을 때만 업데이트를 진행하며, 이 경우 변경된 파일은 확인 없이 덮어씁니다.
|
|
181
|
+
변경사항이 있는 상태에서 업데이트하려면 `--force`를 사용하세요.
|
|
182
|
+
|
|
180
183
|
```bash
|
|
181
184
|
# 전체 업데이트
|
|
182
185
|
npx lee-spec-kit update
|
|
@@ -190,7 +193,7 @@ npx lee-spec-kit update --skills
|
|
|
190
193
|
# feature-base/ 폴더만 업데이트
|
|
191
194
|
npx lee-spec-kit update --templates
|
|
192
195
|
|
|
193
|
-
#
|
|
196
|
+
# 변경사항이 있어도 강제 덮어쓰기
|
|
194
197
|
npx lee-spec-kit update --force
|
|
195
198
|
```
|
|
196
199
|
|
|
@@ -217,7 +220,9 @@ npx lee-spec-kit update --force
|
|
|
217
220
|
| `lang` | `ko` 또는 `en` |
|
|
218
221
|
| `createdAt` | 생성 날짜 |
|
|
219
222
|
| `docsRepo` | `embedded` 또는 `standalone` |
|
|
220
|
-
| `
|
|
223
|
+
| `pushDocs` | (standalone만) docs 레포를 별도 Git으로 관리/푸시할지 여부 |
|
|
224
|
+
| `docsRemote` | (standalone+pushDocs) docs 레포 remote URL |
|
|
225
|
+
| `projectRoot` | (standalone만) 프로젝트 레포지토리 경로 (single: string, fullstack: {fe, be}) |
|
|
221
226
|
|
|
222
227
|
> `docsRepo: "standalone"`을 선택하면 `pushDocs`, `docsRemote`, `projectRoot`가 추가됩니다.
|
|
223
228
|
|
|
@@ -232,7 +237,10 @@ npx lee-spec-kit update --force
|
|
|
232
237
|
{
|
|
233
238
|
"projectName": "my-project",
|
|
234
239
|
"projectType": "single",
|
|
240
|
+
"lang": "ko",
|
|
241
|
+
"createdAt": "YYYY-MM-DD",
|
|
235
242
|
"docsRepo": "standalone",
|
|
243
|
+
"pushDocs": false,
|
|
236
244
|
"projectRoot": "/path/to/my-project"
|
|
237
245
|
}
|
|
238
246
|
```
|
|
@@ -243,7 +251,10 @@ npx lee-spec-kit update --force
|
|
|
243
251
|
{
|
|
244
252
|
"projectName": "my-project",
|
|
245
253
|
"projectType": "fullstack",
|
|
254
|
+
"lang": "ko",
|
|
255
|
+
"createdAt": "YYYY-MM-DD",
|
|
246
256
|
"docsRepo": "standalone",
|
|
257
|
+
"pushDocs": false,
|
|
247
258
|
"projectRoot": {
|
|
248
259
|
"fe": "/path/to/frontend",
|
|
249
260
|
"be": "/path/to/backend"
|
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.",
|
|
@@ -182,10 +188,12 @@ var I18N = {
|
|
|
182
188
|
planWrite: "plan.md \uC791\uC131",
|
|
183
189
|
planApprove: "plan.md \uC2B9\uC778",
|
|
184
190
|
tasksWrite: "tasks.md \uC791\uC131",
|
|
191
|
+
docsInitialCommit: "\uCD08\uAE30 \uBB38\uC11C \uCEE4\uBC0B",
|
|
185
192
|
docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
|
|
186
193
|
issueCreate: "GitHub Issue \uC0DD\uC131",
|
|
187
194
|
branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
|
|
188
195
|
tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
|
|
196
|
+
docsCommitSync: "\uBB38\uC11C \uCEE4\uBC0B(\uB3D9\uAE30\uD654)",
|
|
189
197
|
prCreate: "PR \uC0DD\uC131",
|
|
190
198
|
codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
|
|
191
199
|
featureDone: "Feature \uC644\uB8CC"
|
|
@@ -202,6 +210,7 @@ var I18N = {
|
|
|
202
210
|
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} \uAE30\uD68D \uBB38\uC11C"',
|
|
203
211
|
issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (skills/create-issue.md \uCC38\uACE0)",
|
|
204
212
|
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
213
|
+
docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"',
|
|
205
214
|
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
206
215
|
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
207
216
|
tasksAllDoneButNoChecklist: '\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC139\uC158\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC744 \uCD94\uAC00/\uD655\uC778\uD558\uC138\uC694.',
|
|
@@ -266,6 +275,8 @@ var I18N = {
|
|
|
266
275
|
"update.updatedTotal": "Updated {count} files!",
|
|
267
276
|
"update.changeDetected": "changes detected (use --force to overwrite)",
|
|
268
277
|
"update.fileUpdated": "{file} updated",
|
|
278
|
+
"update.gitStatusUnavailable": "Cannot determine git status (not a git repo or git unavailable). Use --force to overwrite.",
|
|
279
|
+
"update.docsWorktreeDirty": "Docs working tree has changes. Commit/stash your changes, or run with --force to overwrite.",
|
|
269
280
|
"doctor.title": "\u{1F50E} Docs Doctor",
|
|
270
281
|
"doctor.envWarnings": "\u26A0\uFE0F Environment warnings:",
|
|
271
282
|
"doctor.noIssues": "\u2705 No issues found.",
|
|
@@ -355,10 +366,12 @@ var I18N = {
|
|
|
355
366
|
planWrite: "Write plan.md",
|
|
356
367
|
planApprove: "Approve plan.md",
|
|
357
368
|
tasksWrite: "Write tasks.md",
|
|
369
|
+
docsInitialCommit: "Initial docs commit",
|
|
358
370
|
docsCommitPlanning: "Commit docs (sync)",
|
|
359
371
|
issueCreate: "Create GitHub Issue",
|
|
360
372
|
branchCreate: "Create branch",
|
|
361
373
|
tasksExecute: "Execute tasks",
|
|
374
|
+
docsCommitSync: "Commit docs (sync)",
|
|
362
375
|
prCreate: "Create PR",
|
|
363
376
|
codeReview: "Code review",
|
|
364
377
|
featureDone: "Feature done"
|
|
@@ -375,6 +388,7 @@ var I18N = {
|
|
|
375
388
|
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(planning): {folderName} planning docs"',
|
|
376
389
|
issueCreateAndWrite: "Create a GitHub Issue, fill the issue number in spec.md/tasks.md, then prepare a docs commit. (See skills/create-issue.md)",
|
|
377
390
|
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs(#{issueNumber}): {folderName} docs update"',
|
|
391
|
+
docsCommitUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs: {folderName} docs update"',
|
|
378
392
|
standaloneNeedsProjectRoot: "Standalone mode requires projectRoot. (npx lee-spec-kit config --project-root ...)",
|
|
379
393
|
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
380
394
|
tasksAllDoneButNoChecklist: 'All tasks are DONE, but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
|
|
@@ -983,6 +997,7 @@ async function runFeature(name, options) {
|
|
|
983
997
|
process.exit(1);
|
|
984
998
|
}
|
|
985
999
|
const { docsDir, projectType, lang } = config;
|
|
1000
|
+
const projectName = config.projectName;
|
|
986
1001
|
assertValid(validateSafeName(name), "\uAE30\uB2A5 \uC774\uB984");
|
|
987
1002
|
let repo = options.repo;
|
|
988
1003
|
if (projectType === "fullstack" && !repo) {
|
|
@@ -1043,6 +1058,7 @@ async function runFeature(name, options) {
|
|
|
1043
1058
|
const idNumber = featureId.replace("F", "");
|
|
1044
1059
|
const repoName = projectType === "fullstack" && repo ? `{{projectName}}-${repo}` : "{{projectName}}";
|
|
1045
1060
|
const replacements = {
|
|
1061
|
+
"{{projectName}}": projectName ?? "{{projectName}}",
|
|
1046
1062
|
// ko placeholders
|
|
1047
1063
|
"{\uAE30\uB2A5\uBA85}": name,
|
|
1048
1064
|
"{\uBC88\uD638}": idNumber,
|
|
@@ -1224,7 +1240,7 @@ function getStepDefinitions(lang) {
|
|
|
1224
1240
|
},
|
|
1225
1241
|
{
|
|
1226
1242
|
step: 7,
|
|
1227
|
-
name: tr(lang, "steps", "
|
|
1243
|
+
name: tr(lang, "steps", "docsInitialCommit"),
|
|
1228
1244
|
checklist: {
|
|
1229
1245
|
done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && f.git.docsEverCommitted
|
|
1230
1246
|
},
|
|
@@ -1356,6 +1372,26 @@ function getStepDefinitions(lang) {
|
|
|
1356
1372
|
];
|
|
1357
1373
|
}
|
|
1358
1374
|
if (f.nextTodoTask) {
|
|
1375
|
+
if (f.git.docsHasUncommittedChanges) {
|
|
1376
|
+
return [
|
|
1377
|
+
{
|
|
1378
|
+
type: "command",
|
|
1379
|
+
requiresUserOk: true,
|
|
1380
|
+
scope: "docs",
|
|
1381
|
+
cwd: f.git.docsGitCwd,
|
|
1382
|
+
cmd: f.issueNumber ? tr(lang, "messages", "docsCommitIssueUpdate", {
|
|
1383
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1384
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1385
|
+
issueNumber: f.issueNumber,
|
|
1386
|
+
folderName: f.folderName
|
|
1387
|
+
}) : tr(lang, "messages", "docsCommitUpdate", {
|
|
1388
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1389
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1390
|
+
folderName: f.folderName
|
|
1391
|
+
})
|
|
1392
|
+
}
|
|
1393
|
+
];
|
|
1394
|
+
}
|
|
1359
1395
|
return [
|
|
1360
1396
|
{
|
|
1361
1397
|
type: "instruction",
|
|
@@ -1383,6 +1419,34 @@ function getStepDefinitions(lang) {
|
|
|
1383
1419
|
},
|
|
1384
1420
|
{
|
|
1385
1421
|
step: 11,
|
|
1422
|
+
name: tr(lang, "steps", "docsCommitSync"),
|
|
1423
|
+
checklist: {
|
|
1424
|
+
done: (f) => !f.git.docsHasUncommittedChanges
|
|
1425
|
+
},
|
|
1426
|
+
current: {
|
|
1427
|
+
when: (f) => isImplementationDone(f) && f.git.docsHasUncommittedChanges,
|
|
1428
|
+
actions: (f) => [
|
|
1429
|
+
{
|
|
1430
|
+
type: "command",
|
|
1431
|
+
requiresUserOk: true,
|
|
1432
|
+
scope: "docs",
|
|
1433
|
+
cwd: f.git.docsGitCwd,
|
|
1434
|
+
cmd: f.issueNumber ? tr(lang, "messages", "docsCommitIssueUpdate", {
|
|
1435
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1436
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1437
|
+
issueNumber: f.issueNumber,
|
|
1438
|
+
folderName: f.folderName
|
|
1439
|
+
}) : tr(lang, "messages", "docsCommitUpdate", {
|
|
1440
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1441
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1442
|
+
folderName: f.folderName
|
|
1443
|
+
})
|
|
1444
|
+
}
|
|
1445
|
+
]
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
step: 12,
|
|
1386
1450
|
name: tr(lang, "steps", "prCreate"),
|
|
1387
1451
|
checklist: { done: (f) => isPrMetadataConfigured(f) && !!f.pr.link },
|
|
1388
1452
|
current: {
|
|
@@ -1408,7 +1472,7 @@ function getStepDefinitions(lang) {
|
|
|
1408
1472
|
}
|
|
1409
1473
|
},
|
|
1410
1474
|
{
|
|
1411
|
-
step:
|
|
1475
|
+
step: 13,
|
|
1412
1476
|
name: tr(lang, "steps", "codeReview"),
|
|
1413
1477
|
checklist: {
|
|
1414
1478
|
done: (f) => isPrMetadataConfigured(f) && f.pr.status === "Approved"
|
|
@@ -1443,7 +1507,7 @@ function getStepDefinitions(lang) {
|
|
|
1443
1507
|
}
|
|
1444
1508
|
},
|
|
1445
1509
|
{
|
|
1446
|
-
step:
|
|
1510
|
+
step: 14,
|
|
1447
1511
|
name: tr(lang, "steps", "featureDone"),
|
|
1448
1512
|
checklist: { done: (f) => isFeatureDone(f) },
|
|
1449
1513
|
current: {
|
|
@@ -2065,18 +2129,28 @@ async function getFeatureNameFromSpec(featureDir, fallbackSlug, fallbackFolderNa
|
|
|
2065
2129
|
return fallbackSlug || fallbackFolderName;
|
|
2066
2130
|
}
|
|
2067
2131
|
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(
|
|
2132
|
+
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(
|
|
2133
|
+
"-f, --force",
|
|
2134
|
+
"Force overwrite even if docs has uncommitted changes"
|
|
2135
|
+
).action(async (options) => {
|
|
2069
2136
|
try {
|
|
2070
2137
|
await runUpdate(options);
|
|
2071
2138
|
} catch (error) {
|
|
2139
|
+
const config = await getConfig(process.cwd());
|
|
2140
|
+
const lang = config?.lang ?? DEFAULT_LANG;
|
|
2072
2141
|
if (error instanceof Error && error.message === "canceled") {
|
|
2073
|
-
const config = await getConfig(process.cwd());
|
|
2074
|
-
const lang = config?.lang ?? DEFAULT_LANG;
|
|
2075
2142
|
console.log(chalk6.yellow(`
|
|
2076
2143
|
${tr(lang, "cli", "common.canceled")}`));
|
|
2077
2144
|
process.exit(0);
|
|
2078
2145
|
}
|
|
2079
|
-
|
|
2146
|
+
if (error instanceof Error) {
|
|
2147
|
+
console.error(
|
|
2148
|
+
chalk6.red(tr(lang, "cli", "common.errorLabel")),
|
|
2149
|
+
chalk6.red(error.message)
|
|
2150
|
+
);
|
|
2151
|
+
} else {
|
|
2152
|
+
console.error(chalk6.red(tr(lang, "cli", "common.errorLabel")), error);
|
|
2153
|
+
}
|
|
2080
2154
|
process.exit(1);
|
|
2081
2155
|
}
|
|
2082
2156
|
});
|
|
@@ -2096,6 +2170,7 @@ async function runUpdate(options) {
|
|
|
2096
2170
|
const { docsDir, projectType, lang } = config;
|
|
2097
2171
|
const templatesDir = getTemplatesDir();
|
|
2098
2172
|
const sourceDir = path4.join(templatesDir, lang, projectType);
|
|
2173
|
+
const forceOverwrite = !!options.force || await isDocsWorktreeCleanOrThrow(docsDir, lang);
|
|
2099
2174
|
const hasExplicitSelection = !!(options.agents || options.skills || options.templates);
|
|
2100
2175
|
const updateAgents = options.agents || options.skills || !hasExplicitSelection;
|
|
2101
2176
|
const updateTemplates = options.templates || !hasExplicitSelection;
|
|
@@ -2120,15 +2195,20 @@ async function runUpdate(options) {
|
|
|
2120
2195
|
const typeAgents = agentsMode === "skills" ? path4.join(typeAgentsBase, "skills") : typeAgentsBase;
|
|
2121
2196
|
const targetAgents = agentsMode === "skills" ? path4.join(targetAgentsBase, "skills") : targetAgentsBase;
|
|
2122
2197
|
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
2123
|
-
const
|
|
2198
|
+
const projectName = config.projectName ?? "{{projectName}}";
|
|
2199
|
+
const commonReplacements = {
|
|
2200
|
+
"{{projectName}}": projectName,
|
|
2124
2201
|
"{{featurePath}}": featurePath
|
|
2125
2202
|
};
|
|
2203
|
+
const typeReplacements = {
|
|
2204
|
+
"{{projectName}}": projectName
|
|
2205
|
+
};
|
|
2126
2206
|
if (await fs8.pathExists(commonAgents)) {
|
|
2127
2207
|
const count = await updateFolder(
|
|
2128
2208
|
commonAgents,
|
|
2129
2209
|
targetAgents,
|
|
2130
|
-
|
|
2131
|
-
|
|
2210
|
+
forceOverwrite,
|
|
2211
|
+
commonReplacements,
|
|
2132
2212
|
lang
|
|
2133
2213
|
);
|
|
2134
2214
|
updatedCount += count;
|
|
@@ -2137,8 +2217,8 @@ async function runUpdate(options) {
|
|
|
2137
2217
|
const count = await updateFolder(
|
|
2138
2218
|
typeAgents,
|
|
2139
2219
|
targetAgents,
|
|
2140
|
-
|
|
2141
|
-
|
|
2220
|
+
forceOverwrite,
|
|
2221
|
+
typeReplacements,
|
|
2142
2222
|
lang
|
|
2143
2223
|
);
|
|
2144
2224
|
updatedCount += count;
|
|
@@ -2154,11 +2234,14 @@ async function runUpdate(options) {
|
|
|
2154
2234
|
const sourceFeatureBase = path4.join(sourceDir, "features", "feature-base");
|
|
2155
2235
|
const targetFeatureBase = path4.join(docsDir, "features", "feature-base");
|
|
2156
2236
|
if (await fs8.pathExists(sourceFeatureBase)) {
|
|
2237
|
+
const replacements = {
|
|
2238
|
+
"{{projectName}}": config.projectName ?? "{{projectName}}"
|
|
2239
|
+
};
|
|
2157
2240
|
const count = await updateFolder(
|
|
2158
2241
|
sourceFeatureBase,
|
|
2159
2242
|
targetFeatureBase,
|
|
2160
|
-
|
|
2161
|
-
|
|
2243
|
+
forceOverwrite,
|
|
2244
|
+
replacements,
|
|
2162
2245
|
lang
|
|
2163
2246
|
);
|
|
2164
2247
|
updatedCount += count;
|
|
@@ -2191,9 +2274,7 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
2191
2274
|
}
|
|
2192
2275
|
let sourceContent = await fs8.readFile(sourcePath, "utf-8");
|
|
2193
2276
|
if (replacements) {
|
|
2194
|
-
|
|
2195
|
-
sourceContent = sourceContent.replaceAll(key, value);
|
|
2196
|
-
}
|
|
2277
|
+
sourceContent = applyReplacements(sourceContent, replacements);
|
|
2197
2278
|
}
|
|
2198
2279
|
let shouldUpdate = true;
|
|
2199
2280
|
if (await fs8.pathExists(targetPath)) {
|
|
@@ -2230,6 +2311,42 @@ async function updateFolder(sourceDir, targetDir, force, replacements, lang = DE
|
|
|
2230
2311
|
}
|
|
2231
2312
|
return updatedCount;
|
|
2232
2313
|
}
|
|
2314
|
+
function getGitTopLevel2(cwd) {
|
|
2315
|
+
try {
|
|
2316
|
+
const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
2317
|
+
cwd,
|
|
2318
|
+
encoding: "utf-8",
|
|
2319
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2320
|
+
}).trim();
|
|
2321
|
+
return out || null;
|
|
2322
|
+
} catch {
|
|
2323
|
+
return null;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
function getDocsPorcelainStatus(docsDir) {
|
|
2327
|
+
const top = getGitTopLevel2(docsDir);
|
|
2328
|
+
if (!top) return null;
|
|
2329
|
+
const rel = path4.relative(top, docsDir) || ".";
|
|
2330
|
+
try {
|
|
2331
|
+
return execFileSync("git", ["status", "--porcelain=v1", "--", rel], {
|
|
2332
|
+
cwd: top,
|
|
2333
|
+
encoding: "utf-8",
|
|
2334
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2335
|
+
});
|
|
2336
|
+
} catch {
|
|
2337
|
+
return null;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
async function isDocsWorktreeCleanOrThrow(docsDir, lang) {
|
|
2341
|
+
const status = getDocsPorcelainStatus(docsDir);
|
|
2342
|
+
if (status === null) {
|
|
2343
|
+
throw new Error(tr(lang, "cli", "update.gitStatusUnavailable"));
|
|
2344
|
+
}
|
|
2345
|
+
if (status.trim().length > 0) {
|
|
2346
|
+
throw new Error(tr(lang, "cli", "update.docsWorktreeDirty"));
|
|
2347
|
+
}
|
|
2348
|
+
return true;
|
|
2349
|
+
}
|
|
2233
2350
|
function configCommand(program2) {
|
|
2234
2351
|
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
2352
|
try {
|
package/package.json
CHANGED
|
@@ -52,14 +52,24 @@ For file links within the repo in PR body, **always use current branch name**:
|
|
|
52
52
|
- Command: `{test command executed}`
|
|
53
53
|
- Result: `{PASS/FAIL summary}`
|
|
54
54
|
|
|
55
|
-
## Screenshots (
|
|
55
|
+
## Screenshots (Frontend / UI changes)
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
> If you follow the Release assets upload flow in `skills/create-pr.md`, you can include images in the PR body without committing files to your branch.
|
|
58
|
+
|
|
59
|
+
{Screenshot markdown (e.g. )}
|
|
60
|
+
|
|
61
|
+
## Architecture Diagram (Backend / core structure changes)
|
|
62
|
+
|
|
63
|
+
```mermaid
|
|
64
|
+
flowchart LR
|
|
65
|
+
A[Client] --> B[API]
|
|
66
|
+
B --> C[DB]
|
|
67
|
+
```
|
|
58
68
|
|
|
59
69
|
## Related Documents
|
|
60
70
|
|
|
61
|
-
- **Spec**: `
|
|
62
|
-
- **Tasks**: `
|
|
71
|
+
- **Spec**: `{{featurePath}}/F{number}-{feature-name}/spec.md`
|
|
72
|
+
- **Tasks**: `{{featurePath}}/F{number}-{feature-name}/tasks.md`
|
|
63
73
|
|
|
64
74
|
Closes #{issue-number}
|
|
65
75
|
```
|
|
@@ -35,7 +35,54 @@ Guide for creating Pull Requests.
|
|
|
35
35
|
3. Record **execution results** in the "Tests" section of PR body
|
|
36
36
|
4. All checkboxes must be checked
|
|
37
37
|
|
|
38
|
-
### 3.
|
|
38
|
+
### 3. Prepare Screenshots / Diagrams (Include in PR Body)
|
|
39
|
+
|
|
40
|
+
Include the artifacts in the PR body.
|
|
41
|
+
|
|
42
|
+
#### Frontend PR (UI changes)
|
|
43
|
+
|
|
44
|
+
- Use `agent-browser` to generate screenshots.
|
|
45
|
+
- Save files under a local temp folder (`/tmp/lee-spec-kit/pr-assets/`).
|
|
46
|
+
- Upload them as Release assets, then put the image URLs into the "Screenshots" section of the PR body.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# (one-time) install agent-browser
|
|
50
|
+
npm i -g agent-browser
|
|
51
|
+
agent-browser install # install Playwright browsers
|
|
52
|
+
|
|
53
|
+
# Start a dev server: ports are often already taken, so prefer a free port.
|
|
54
|
+
# - If you already have a running dev server, you can just set PREVIEW_URL to that URL.
|
|
55
|
+
PORT=$(node -e "const net=require('net');const s=net.createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close();});")
|
|
56
|
+
# (example) Vite
|
|
57
|
+
pnpm dev --host 127.0.0.1 --port \"$PORT\"
|
|
58
|
+
PREVIEW_URL=\"http://127.0.0.1:${PORT}\"
|
|
59
|
+
|
|
60
|
+
# (example) capture from a preview URL
|
|
61
|
+
mkdir -p /tmp/lee-spec-kit/pr-assets
|
|
62
|
+
agent-browser open "$PREVIEW_URL"
|
|
63
|
+
agent-browser screenshot /tmp/lee-spec-kit/pr-assets/ui-1.png --full
|
|
64
|
+
agent-browser close
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Upload to Release assets and generate the URL to paste into the PR body
|
|
69
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
70
|
+
SAFE_BRANCH=$(git branch --show-current | tr '/' '-')
|
|
71
|
+
TAG="pr-assets/${SAFE_BRANCH}"
|
|
72
|
+
|
|
73
|
+
gh release view "$TAG" >/dev/null 2>&1 || \
|
|
74
|
+
gh release create "$TAG" --prerelease --title "pr-assets: ${SAFE_BRANCH}" --notes ""
|
|
75
|
+
|
|
76
|
+
gh release upload "$TAG" /tmp/lee-spec-kit/pr-assets/* --clobber
|
|
77
|
+
|
|
78
|
+
echo \"\"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Backend PR (Core structure)
|
|
82
|
+
|
|
83
|
+
- Write a Mermaid diagram (flowchart/sequence/etc.) in the PR body (see the "Architecture Diagram" section in `pr-template.md`).
|
|
84
|
+
|
|
85
|
+
### 4. Request User Approval
|
|
39
86
|
|
|
40
87
|
> 🚨 **User Approval Required**
|
|
41
88
|
|
|
@@ -45,7 +92,7 @@ Before creating PR, share the following **in a code block** and wait for **expli
|
|
|
45
92
|
- Full body (`pr-template.md` format)
|
|
46
93
|
- Labels
|
|
47
94
|
|
|
48
|
-
###
|
|
95
|
+
### 5. Create PR
|
|
49
96
|
|
|
50
97
|
```bash
|
|
51
98
|
gh pr create \
|
|
@@ -30,6 +30,12 @@ Keep `tasks.md` aligned with reality.
|
|
|
30
30
|
- Do not mark `[DONE]` without actually completing the work and verifying criteria.
|
|
31
31
|
- If you need to change a completed task, add a new task instead of rewriting history.
|
|
32
32
|
|
|
33
|
+
### Step 3.25: Commit per task (important)
|
|
34
|
+
|
|
35
|
+
- Complete **only one task at a time** (do not batch-finish multiple tasks in one commit).
|
|
36
|
+
- After marking a task `[DONE]` (and updating any checklist items), create commits (code commit + docs commit) so each task has its own history.
|
|
37
|
+
- Once all tasks are `[DONE]`, share the "Completion Criteria" checklist with the user and get approval (OK), then check it (especially the **User approval (OK) received** item).
|
|
38
|
+
|
|
33
39
|
### Step 3.5: Record decisions (strongly recommended, effectively required)
|
|
34
40
|
|
|
35
41
|
To avoid “why did we implement it like this?” losing context, **record any non-obvious or tradeoff-heavy implementation choice** in `decisions.md`.
|
|
@@ -50,14 +50,24 @@ PR 본문에서 레포 내 파일 링크는 **반드시 현재 브랜치명을
|
|
|
50
50
|
- 명령어: `{실행한 테스트 명령어}`
|
|
51
51
|
- 결과: `{PASS/FAIL 요약}`
|
|
52
52
|
|
|
53
|
-
## 스크린샷 (UI 변경 시)
|
|
53
|
+
## 스크린샷 (프론트엔드 / UI 변경 시)
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
> `skills/create-pr.md`의 Release assets 업로드 절차를 사용하면 브랜치에 파일을 커밋하지 않고도 이미지를 본문에 포함할 수 있습니다.
|
|
56
|
+
|
|
57
|
+
{스크린샷 마크다운 (예: )}
|
|
58
|
+
|
|
59
|
+
## 아키텍처 다이어그램 (백엔드 / 핵심 구조 변경 시)
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
flowchart LR
|
|
63
|
+
A[Client] --> B[API]
|
|
64
|
+
B --> C[DB]
|
|
65
|
+
```
|
|
56
66
|
|
|
57
67
|
## 관련 문서
|
|
58
68
|
|
|
59
|
-
- **Spec**: `
|
|
60
|
-
- **Tasks**: `
|
|
69
|
+
- **Spec**: `{{featurePath}}/F{번호}-{기능명}/spec.md`
|
|
70
|
+
- **Tasks**: `{{featurePath}}/F{번호}-{기능명}/tasks.md`
|
|
61
71
|
|
|
62
72
|
Closes #{이슈번호}
|
|
63
73
|
```
|
|
@@ -35,7 +35,54 @@ Pull Request를 생성할 때 따르는 가이드입니다.
|
|
|
35
35
|
3. PR 본문 "테스트" 섹션에 **실행 결과** 기록
|
|
36
36
|
4. 모든 체크박스가 체크되어야 함
|
|
37
37
|
|
|
38
|
-
### 3.
|
|
38
|
+
### 3. 스크린샷/다이어그램 작성 (PR 본문에 포함)
|
|
39
|
+
|
|
40
|
+
PR 본문에 결과물을 포함합니다.
|
|
41
|
+
|
|
42
|
+
#### 프론트엔드 PR (UI 변경)
|
|
43
|
+
|
|
44
|
+
- `agent-browser`로 스크린샷을 생성합니다.
|
|
45
|
+
- 스크린샷 파일은 로컬 임시 폴더(`/tmp/lee-spec-kit/pr-assets/`)에 저장합니다.
|
|
46
|
+
- 릴리스 자산(Release assets)으로 업로드한 뒤, 생성된 이미지 URL을 PR 본문 "스크린샷" 섹션에 넣습니다.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# (최초 1회) agent-browser 설치
|
|
50
|
+
npm i -g agent-browser
|
|
51
|
+
agent-browser install # Playwright 브라우저 설치
|
|
52
|
+
|
|
53
|
+
# 개발 서버 실행: 이미 사용 중인 포트가 많으므로 "빈 포트"를 권장합니다.
|
|
54
|
+
# - 이미 떠있는 개발 서버가 있다면 그 URL을 PREVIEW_URL로 지정해도 됩니다.
|
|
55
|
+
PORT=$(node -e "const net=require('net');const s=net.createServer();s.listen(0,'127.0.0.1',()=>{console.log(s.address().port);s.close();});")
|
|
56
|
+
# (예시) Vite
|
|
57
|
+
pnpm dev --host 127.0.0.1 --port \"$PORT\"
|
|
58
|
+
PREVIEW_URL=\"http://127.0.0.1:${PORT}\"
|
|
59
|
+
|
|
60
|
+
# (예시) 미리보기 URL을 정해 스크린샷 생성
|
|
61
|
+
mkdir -p /tmp/lee-spec-kit/pr-assets
|
|
62
|
+
agent-browser open "$PREVIEW_URL"
|
|
63
|
+
agent-browser screenshot /tmp/lee-spec-kit/pr-assets/ui-1.png --full
|
|
64
|
+
agent-browser close
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 스크린샷을 Release assets로 업로드하고, PR 본문에 넣을 URL 만들기
|
|
69
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
70
|
+
SAFE_BRANCH=$(git branch --show-current | tr '/' '-')
|
|
71
|
+
TAG="pr-assets/${SAFE_BRANCH}"
|
|
72
|
+
|
|
73
|
+
gh release view "$TAG" >/dev/null 2>&1 || \
|
|
74
|
+
gh release create "$TAG" --prerelease --title "pr-assets: ${SAFE_BRANCH}" --notes ""
|
|
75
|
+
|
|
76
|
+
gh release upload "$TAG" /tmp/lee-spec-kit/pr-assets/* --clobber
|
|
77
|
+
|
|
78
|
+
echo \"\"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### 백엔드 PR (핵심 구조)
|
|
82
|
+
|
|
83
|
+
- PR 본문에 Mermaid 다이어그램(예: flowchart/sequence)을 작성합니다. (`pr-template.md`의 "아키텍처 다이어그램" 섹션 참고)
|
|
84
|
+
|
|
85
|
+
### 4. 사용자 확인 요청
|
|
39
86
|
|
|
40
87
|
> 🚨 **사용자 확인 필수**
|
|
41
88
|
|
|
@@ -45,7 +92,7 @@ PR 생성 전 다음 내용을 **코드블록으로** 사용자에게 공유하
|
|
|
45
92
|
- 본문 전체 (`pr-template.md` 형식)
|
|
46
93
|
- 라벨
|
|
47
94
|
|
|
48
|
-
###
|
|
95
|
+
### 5. PR 생성
|
|
49
96
|
|
|
50
97
|
```bash
|
|
51
98
|
gh pr create \
|
|
@@ -47,8 +47,10 @@ CLI가 가리키는 **Active Task** 또는 **Next Action**을 수행합니다.
|
|
|
47
47
|
#### 3-2) 태스크/체크리스트 업데이트 + 커밋
|
|
48
48
|
|
|
49
49
|
1. 작업이 끝나면 해당 태스크의 상태를 `[DONE]`으로 변경하고, `Acceptance/Checklist` 항목을 `[x]`로 체크합니다.
|
|
50
|
-
2.
|
|
51
|
-
3.
|
|
50
|
+
2. **한 번에 하나의 태스크만** `[DONE]` 처리합니다. (태스크 2개 이상을 한 번에 완료/커밋으로 묶지 않기)
|
|
51
|
+
3. 커밋을 생성합니다 (코드 커밋 + 문서 커밋). 태스크 단위로 커밋이 남아야 합니다.
|
|
52
|
+
4. 모든 태스크가 `[DONE]`가 되면, "완료 조건" 체크리스트를 사용자에게 공유하고 승인(OK)을 받은 뒤 체크합니다. (특히 **사용자 승인(OK) 완료** 항목)
|
|
53
|
+
5. **즉시 1단계로 돌아가** 다음 할 일을 CLI에게 물어봅니다.
|
|
52
54
|
|
|
53
55
|
---
|
|
54
56
|
|