lee-spec-kit 0.1.2 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/index.js +105 -53
- package/package.json +1 -1
- package/templates/en/fullstack/agents/agents.md +1 -1
- package/templates/en/fullstack/agents/constitution.md +5 -0
- package/templates/en/fullstack/agents/git-workflow.md +2 -0
- package/templates/en/fullstack/agents/issue-template.md +35 -1
- package/templates/en/fullstack/agents/pr-template.md +19 -1
- package/templates/en/fullstack/features/README.md +4 -4
- package/templates/en/fullstack/features/be/README.md +1 -1
- package/templates/en/fullstack/features/fe/README.md +1 -1
- package/templates/en/fullstack/features/feature-base/plan.md +2 -2
- package/templates/en/fullstack/features/feature-base/spec.md +4 -4
- package/templates/en/fullstack/features/feature-base/tasks.md +4 -4
- package/templates/en/fullstack/prd/README.md +5 -0
- package/templates/en/single/agents/agents.md +1 -1
- package/templates/en/single/agents/constitution.md +44 -39
- package/templates/en/single/agents/git-workflow.md +62 -60
- package/templates/en/single/agents/issue-template.md +66 -30
- package/templates/en/single/agents/pr-template.md +51 -31
- package/templates/en/single/features/README.md +2 -2
- package/templates/en/single/features/feature-base/plan.md +2 -2
- package/templates/en/single/features/feature-base/spec.md +4 -4
- package/templates/en/single/features/feature-base/tasks.md +4 -4
- package/templates/en/single/prd/README.md +14 -9
- package/templates/ko/fullstack/agents/agents.md +1 -1
- package/templates/ko/fullstack/agents/constitution.md +5 -0
- package/templates/ko/fullstack/agents/git-workflow.md +2 -0
- package/templates/ko/fullstack/agents/issue-template.md +36 -0
- package/templates/ko/fullstack/agents/pr-template.md +19 -1
- package/templates/ko/fullstack/features/README.md +4 -4
- package/templates/ko/fullstack/features/be/README.md +1 -1
- package/templates/ko/fullstack/features/fe/README.md +1 -1
- package/templates/ko/fullstack/prd/README.md +5 -0
- package/templates/ko/single/agents/agents.md +1 -1
- package/templates/ko/single/agents/constitution.md +5 -0
- package/templates/ko/single/agents/git-workflow.md +2 -0
- package/templates/ko/single/agents/issue-template.md +36 -0
- package/templates/ko/single/agents/pr-template.md +19 -1
- package/templates/ko/single/features/README.md +2 -2
- package/templates/ko/single/prd/README.md +5 -0
package/README.md
CHANGED
|
@@ -41,21 +41,21 @@ npx lee-spec-kit init --name my-project --type fullstack --lang ko
|
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
43
|
# Single 프로젝트
|
|
44
|
-
lee-spec-kit feature user-auth
|
|
44
|
+
npx lee-spec-kit feature user-auth
|
|
45
45
|
|
|
46
46
|
# Fullstack 프로젝트
|
|
47
|
-
lee-spec-kit feature --repo be user-auth
|
|
48
|
-
lee-spec-kit feature --repo fe user-profile
|
|
47
|
+
npx lee-spec-kit feature --repo be user-auth
|
|
48
|
+
npx lee-spec-kit feature --repo fe user-profile
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### 상태 확인
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
# 터미널에 출력
|
|
55
|
-
lee-spec-kit status
|
|
55
|
+
npx lee-spec-kit status
|
|
56
56
|
|
|
57
57
|
# 파일로 저장
|
|
58
|
-
lee-spec-kit status --write
|
|
58
|
+
npx lee-spec-kit status --write
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
## 생성되는 구조
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { program } from 'commander';
|
|
3
3
|
import prompts from 'prompts';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import path3 from 'path';
|
|
6
|
+
import fs3 from 'fs-extra';
|
|
7
7
|
import { glob } from 'glob';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
|
|
10
10
|
async function copyTemplates(src, dest) {
|
|
11
|
-
await
|
|
11
|
+
await fs3.copy(src, dest, {
|
|
12
12
|
overwrite: true,
|
|
13
13
|
errorOnExist: false
|
|
14
14
|
});
|
|
@@ -16,26 +16,26 @@ async function copyTemplates(src, dest) {
|
|
|
16
16
|
async function replaceInFiles(dir, replacements) {
|
|
17
17
|
const files = await glob("**/*.md", { cwd: dir, absolute: true });
|
|
18
18
|
for (const file of files) {
|
|
19
|
-
let content = await
|
|
19
|
+
let content = await fs3.readFile(file, "utf-8");
|
|
20
20
|
for (const [search, replace] of Object.entries(replacements)) {
|
|
21
21
|
content = content.replaceAll(search, replace);
|
|
22
22
|
}
|
|
23
|
-
await
|
|
23
|
+
await fs3.writeFile(file, content, "utf-8");
|
|
24
24
|
}
|
|
25
25
|
const shFiles = await glob("**/*.sh", { cwd: dir, absolute: true });
|
|
26
26
|
for (const file of shFiles) {
|
|
27
|
-
let content = await
|
|
27
|
+
let content = await fs3.readFile(file, "utf-8");
|
|
28
28
|
for (const [search, replace] of Object.entries(replacements)) {
|
|
29
29
|
content = content.replaceAll(search, replace);
|
|
30
30
|
}
|
|
31
|
-
await
|
|
31
|
+
await fs3.writeFile(file, content, "utf-8");
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
35
|
-
var __dirname2 =
|
|
35
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
36
36
|
function getTemplatesDir() {
|
|
37
|
-
const rootDir =
|
|
38
|
-
return
|
|
37
|
+
const rootDir = path3.resolve(__dirname2, "..");
|
|
38
|
+
return path3.join(rootDir, "templates");
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// src/utils/validation.ts
|
|
@@ -150,11 +150,11 @@ function initCommand(program2) {
|
|
|
150
150
|
}
|
|
151
151
|
async function runInit(options) {
|
|
152
152
|
const cwd = process.cwd();
|
|
153
|
-
const defaultName =
|
|
153
|
+
const defaultName = path3.basename(cwd);
|
|
154
154
|
let projectName = options.name || defaultName;
|
|
155
155
|
let projectType = options.type;
|
|
156
156
|
let lang = options.lang || "ko";
|
|
157
|
-
const targetDir =
|
|
157
|
+
const targetDir = path3.resolve(cwd, options.dir || "./docs");
|
|
158
158
|
if (!options.yes) {
|
|
159
159
|
const response = await prompts(
|
|
160
160
|
[
|
|
@@ -209,8 +209,8 @@ async function runInit(options) {
|
|
|
209
209
|
assertValid(validateSafeName(projectName), "\uD504\uB85C\uC81D\uD2B8 \uC774\uB984");
|
|
210
210
|
assertValid(validateProjectType(projectType), "\uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785");
|
|
211
211
|
assertValid(validateLanguage(lang), "\uC5B8\uC5B4");
|
|
212
|
-
if (await
|
|
213
|
-
const files = await
|
|
212
|
+
if (await fs3.pathExists(targetDir)) {
|
|
213
|
+
const files = await fs3.readdir(targetDir);
|
|
214
214
|
if (files.length > 0) {
|
|
215
215
|
const { overwrite } = await prompts({
|
|
216
216
|
type: "confirm",
|
|
@@ -232,8 +232,8 @@ async function runInit(options) {
|
|
|
232
232
|
console.log(chalk.gray(` \uACBD\uB85C: ${targetDir}`));
|
|
233
233
|
console.log();
|
|
234
234
|
const templatesDir = getTemplatesDir();
|
|
235
|
-
const templatePath =
|
|
236
|
-
if (!await
|
|
235
|
+
const templatePath = path3.join(templatesDir, lang, projectType);
|
|
236
|
+
if (!await fs3.pathExists(templatePath)) {
|
|
237
237
|
throw new Error(`\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${templatePath}`);
|
|
238
238
|
}
|
|
239
239
|
await copyTemplates(templatePath, targetDir);
|
|
@@ -242,30 +242,82 @@ async function runInit(options) {
|
|
|
242
242
|
"{{date}}": (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
243
243
|
};
|
|
244
244
|
await replaceInFiles(targetDir, replacements);
|
|
245
|
+
const config = {
|
|
246
|
+
projectName,
|
|
247
|
+
projectType,
|
|
248
|
+
lang,
|
|
249
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
250
|
+
};
|
|
251
|
+
const configPath = path3.join(targetDir, ".lee-spec-kit.json");
|
|
252
|
+
await fs3.writeJson(configPath, config, { spaces: 2 });
|
|
245
253
|
console.log(chalk.green("\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!"));
|
|
246
254
|
console.log();
|
|
255
|
+
await initGit(cwd, targetDir);
|
|
247
256
|
console.log(chalk.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
|
|
248
257
|
console.log(chalk.gray(` 1. ${targetDir}/prd/README.md \uC791\uC131`));
|
|
249
|
-
console.log(
|
|
258
|
+
console.log(
|
|
259
|
+
chalk.gray(" 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00")
|
|
260
|
+
);
|
|
250
261
|
console.log();
|
|
251
262
|
}
|
|
263
|
+
async function initGit(cwd, targetDir) {
|
|
264
|
+
const { execSync } = await import('child_process');
|
|
265
|
+
try {
|
|
266
|
+
try {
|
|
267
|
+
execSync("git rev-parse --is-inside-work-tree", {
|
|
268
|
+
cwd,
|
|
269
|
+
stdio: "ignore"
|
|
270
|
+
});
|
|
271
|
+
console.log(chalk.blue("\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911..."));
|
|
272
|
+
} catch {
|
|
273
|
+
console.log(chalk.blue("\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911..."));
|
|
274
|
+
execSync("git init", { cwd, stdio: "ignore" });
|
|
275
|
+
}
|
|
276
|
+
const relativePath = path3.relative(cwd, targetDir);
|
|
277
|
+
execSync(`git add "${relativePath}"`, { cwd, stdio: "ignore" });
|
|
278
|
+
execSync('git commit -m "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)"', {
|
|
279
|
+
cwd,
|
|
280
|
+
stdio: "ignore"
|
|
281
|
+
});
|
|
282
|
+
console.log(chalk.green("\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!"));
|
|
283
|
+
console.log();
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.log(
|
|
286
|
+
chalk.yellow("\u26A0\uFE0F Git \uCD08\uAE30\uD654\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 (\uC218\uB3D9\uC73C\uB85C \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694)")
|
|
287
|
+
);
|
|
288
|
+
console.log();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
252
291
|
async function getConfig(cwd) {
|
|
253
292
|
const possibleDirs = [
|
|
254
|
-
|
|
293
|
+
path3.join(cwd, "docs"),
|
|
255
294
|
cwd
|
|
256
295
|
// 이미 docs 폴더 안에 있을 수 있음
|
|
257
296
|
];
|
|
258
297
|
for (const docsDir of possibleDirs) {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
298
|
+
const configPath = path3.join(docsDir, ".lee-spec-kit.json");
|
|
299
|
+
if (await fs3.pathExists(configPath)) {
|
|
300
|
+
try {
|
|
301
|
+
const configFile = await fs3.readJson(configPath);
|
|
302
|
+
return {
|
|
303
|
+
docsDir,
|
|
304
|
+
projectName: configFile.projectName,
|
|
305
|
+
projectType: configFile.projectType,
|
|
306
|
+
lang: configFile.lang
|
|
307
|
+
};
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const agentsPath = path3.join(docsDir, "agents");
|
|
312
|
+
const featuresPath = path3.join(docsDir, "features");
|
|
313
|
+
if (await fs3.pathExists(agentsPath) && await fs3.pathExists(featuresPath)) {
|
|
314
|
+
const bePath = path3.join(featuresPath, "be");
|
|
315
|
+
const fePath = path3.join(featuresPath, "fe");
|
|
316
|
+
const projectType = await fs3.pathExists(bePath) || await fs3.pathExists(fePath) ? "fullstack" : "single";
|
|
317
|
+
const agentsMdPath = path3.join(agentsPath, "agents.md");
|
|
266
318
|
let lang = "ko";
|
|
267
|
-
if (await
|
|
268
|
-
const content = await
|
|
319
|
+
if (await fs3.pathExists(agentsMdPath)) {
|
|
320
|
+
const content = await fs3.readFile(agentsMdPath, "utf-8");
|
|
269
321
|
if (!/[가-힣]/.test(content)) {
|
|
270
322
|
lang = "en";
|
|
271
323
|
}
|
|
@@ -334,22 +386,22 @@ async function runFeature(name, options) {
|
|
|
334
386
|
}
|
|
335
387
|
let featuresDir;
|
|
336
388
|
if (projectType === "fullstack" && repo) {
|
|
337
|
-
featuresDir =
|
|
389
|
+
featuresDir = path3.join(docsDir, "features", repo);
|
|
338
390
|
} else {
|
|
339
|
-
featuresDir =
|
|
391
|
+
featuresDir = path3.join(docsDir, "features");
|
|
340
392
|
}
|
|
341
393
|
const featureFolderName = `${featureId}-${name}`;
|
|
342
|
-
const featureDir =
|
|
343
|
-
if (await
|
|
394
|
+
const featureDir = path3.join(featuresDir, featureFolderName);
|
|
395
|
+
if (await fs3.pathExists(featureDir)) {
|
|
344
396
|
console.error(chalk.red(`\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: ${featureDir}`));
|
|
345
397
|
process.exit(1);
|
|
346
398
|
}
|
|
347
|
-
const featureBasePath =
|
|
348
|
-
if (!await
|
|
399
|
+
const featureBasePath = path3.join(docsDir, "features", "feature-base");
|
|
400
|
+
if (!await fs3.pathExists(featureBasePath)) {
|
|
349
401
|
console.error(chalk.red("feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
350
402
|
process.exit(1);
|
|
351
403
|
}
|
|
352
|
-
await
|
|
404
|
+
await fs3.copy(featureBasePath, featureDir);
|
|
353
405
|
const idNumber = featureId.replace("F", "");
|
|
354
406
|
const repoName = projectType === "fullstack" && repo ? `{{projectName}}-${repo}` : "{{projectName}}";
|
|
355
407
|
const replacements = {
|
|
@@ -379,18 +431,18 @@ async function runFeature(name, options) {
|
|
|
379
431
|
console.log();
|
|
380
432
|
}
|
|
381
433
|
async function getNextFeatureId(docsDir, projectType) {
|
|
382
|
-
const featuresDir =
|
|
434
|
+
const featuresDir = path3.join(docsDir, "features");
|
|
383
435
|
let max = 0;
|
|
384
436
|
const scanDirs = [];
|
|
385
437
|
if (projectType === "fullstack") {
|
|
386
|
-
scanDirs.push(
|
|
387
|
-
scanDirs.push(
|
|
438
|
+
scanDirs.push(path3.join(featuresDir, "be"));
|
|
439
|
+
scanDirs.push(path3.join(featuresDir, "fe"));
|
|
388
440
|
} else {
|
|
389
441
|
scanDirs.push(featuresDir);
|
|
390
442
|
}
|
|
391
443
|
for (const dir of scanDirs) {
|
|
392
|
-
if (!await
|
|
393
|
-
const entries = await
|
|
444
|
+
if (!await fs3.pathExists(dir)) continue;
|
|
445
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
394
446
|
for (const entry of entries) {
|
|
395
447
|
if (!entry.isDirectory()) continue;
|
|
396
448
|
const match = entry.name.match(/^F(\d+)-/);
|
|
@@ -424,29 +476,29 @@ async function runStatus(options) {
|
|
|
424
476
|
process.exit(1);
|
|
425
477
|
}
|
|
426
478
|
const { docsDir, projectType } = config;
|
|
427
|
-
const featuresDir =
|
|
479
|
+
const featuresDir = path3.join(docsDir, "features");
|
|
428
480
|
const features = [];
|
|
429
481
|
const idMap = /* @__PURE__ */ new Map();
|
|
430
482
|
const scopes = projectType === "fullstack" ? ["be", "fe"] : [""];
|
|
431
483
|
for (const scope of scopes) {
|
|
432
|
-
const scanDir = scope ?
|
|
433
|
-
if (!await
|
|
434
|
-
const entries = await
|
|
484
|
+
const scanDir = scope ? path3.join(featuresDir, scope) : featuresDir;
|
|
485
|
+
if (!await fs3.pathExists(scanDir)) continue;
|
|
486
|
+
const entries = await fs3.readdir(scanDir, { withFileTypes: true });
|
|
435
487
|
for (const entry of entries) {
|
|
436
488
|
if (!entry.isDirectory()) continue;
|
|
437
489
|
if (entry.name === "feature-base") continue;
|
|
438
|
-
const featureDir =
|
|
439
|
-
const specPath =
|
|
440
|
-
const tasksPath =
|
|
441
|
-
if (!await
|
|
442
|
-
if (!await
|
|
443
|
-
const specContent = await
|
|
444
|
-
const tasksContent = await
|
|
490
|
+
const featureDir = path3.join(scanDir, entry.name);
|
|
491
|
+
const specPath = path3.join(featureDir, "spec.md");
|
|
492
|
+
const tasksPath = path3.join(featureDir, "tasks.md");
|
|
493
|
+
if (!await fs3.pathExists(specPath)) continue;
|
|
494
|
+
if (!await fs3.pathExists(tasksPath)) continue;
|
|
495
|
+
const specContent = await fs3.readFile(specPath, "utf-8");
|
|
496
|
+
const tasksContent = await fs3.readFile(tasksPath, "utf-8");
|
|
445
497
|
const id = extractSpecValue(specContent, "\uAE30\uB2A5 ID") || extractSpecValue(specContent, "Feature ID") || "UNKNOWN";
|
|
446
498
|
const name = extractSpecValue(specContent, "\uAE30\uB2A5\uBA85") || extractSpecValue(specContent, "Feature Name") || entry.name;
|
|
447
499
|
const repo = extractSpecValue(specContent, "\uB300\uC0C1 \uB808\uD3EC") || extractSpecValue(specContent, "Target Repo") || (scope ? `{{projectName}}-${scope}` : "{{projectName}}");
|
|
448
500
|
const issue = extractSpecValue(specContent, "\uC774\uC288 \uBC88\uD638") || extractSpecValue(specContent, "Issue Number") || "-";
|
|
449
|
-
const relPath =
|
|
501
|
+
const relPath = path3.relative(docsDir, featureDir);
|
|
450
502
|
if (!idMap.has(id)) {
|
|
451
503
|
idMap.set(id, []);
|
|
452
504
|
}
|
|
@@ -516,7 +568,7 @@ async function runStatus(options) {
|
|
|
516
568
|
}
|
|
517
569
|
console.log();
|
|
518
570
|
if (options.write) {
|
|
519
|
-
const outputPath =
|
|
571
|
+
const outputPath = path3.join(featuresDir, "status.md");
|
|
520
572
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
521
573
|
const content = [
|
|
522
574
|
"# Feature Status",
|
|
@@ -531,7 +583,7 @@ async function runStatus(options) {
|
|
|
531
583
|
),
|
|
532
584
|
""
|
|
533
585
|
].join("\n");
|
|
534
|
-
await
|
|
586
|
+
await fs3.writeFile(outputPath, content, "utf-8");
|
|
535
587
|
console.log(chalk.green(`\u2705 ${outputPath} \uC0DD\uC131 \uC644\uB8CC`));
|
|
536
588
|
}
|
|
537
589
|
}
|
package/package.json
CHANGED
|
@@ -50,7 +50,7 @@ docs/
|
|
|
50
50
|
### 1. New Feature Request
|
|
51
51
|
|
|
52
52
|
1. Identify target repo (BE or FE)
|
|
53
|
-
2. Create feature folder: `lee-spec-kit feature <name>`
|
|
53
|
+
2. Create feature folder: `npx lee-spec-kit feature <name>`
|
|
54
54
|
3. Write `spec.md` - what and why (no tech stack)
|
|
55
55
|
4. Request spec review from user
|
|
56
56
|
5. Create GitHub Issue
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
Core principles and technical decision guidelines for the project.
|
|
4
4
|
All development decisions should be based on this document.
|
|
5
5
|
|
|
6
|
+
> **📌 Document Scope**
|
|
7
|
+
>
|
|
8
|
+
> - **This document**: Tech stack, architecture principles, code quality, security principles
|
|
9
|
+
> - **PRD**: Product requirements, business logic, user stories → `prd/*.md`
|
|
10
|
+
|
|
6
11
|
---
|
|
7
12
|
|
|
8
13
|
## Project Mission
|
|
@@ -14,7 +14,32 @@ F{number}: {feature-name} ({short description})
|
|
|
14
14
|
|
|
15
15
|
Example: `F001: user-auth (User authentication feature)`
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### Link Format (Important!)
|
|
18
|
+
|
|
19
|
+
In GitHub Issues, use different link formats **based on file location**:
|
|
20
|
+
|
|
21
|
+
1. **Files within project repo**: Use full URL (clickable)
|
|
22
|
+
- **Merged documents/code**: Use `main` branch
|
|
23
|
+
```markdown
|
|
24
|
+
[filename](https://github.com/{owner}/{repo}/blob/main/path/to/file)
|
|
25
|
+
```
|
|
26
|
+
- **In-progress documents** (not merged yet): Use **Feature branch**
|
|
27
|
+
```markdown
|
|
28
|
+
[filename](https://github.com/{owner}/{repo}/blob/{feat-branch}/path/to/file)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. **External documents (with public URL)**: Use **absolute URL**
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
[react-i18next](https://react.i18next.com/)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. **External/local documents** (no URL available): Use **relative path as text only**
|
|
38
|
+
```text
|
|
39
|
+
../docs/features/F001-feature-name/spec.md
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> ⚠️ Local documents are not clickable on GitHub, so provide path text only.
|
|
18
43
|
|
|
19
44
|
## Issue Body Template
|
|
20
45
|
|
|
@@ -56,3 +81,12 @@ Example: `F001: user-auth (User authentication feature)`
|
|
|
56
81
|
| `backend` | BE related |
|
|
57
82
|
| `frontend` | FE related |
|
|
58
83
|
| `priority:high` | High priority |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Body Input Rules (Shell Execution Prevention)
|
|
88
|
+
|
|
89
|
+
- Issue body should use **`--body-file` by default**.
|
|
90
|
+
- If the body contains backticks (`) or `$()`and is placed directly in`"..."`, it may be **interpreted by the shell**.
|
|
91
|
+
- For multi-line bodies, use **single-quoted heredoc** like `cat <<'EOF'`,
|
|
92
|
+
and handle variables via **placeholder → sed substitution**.
|
|
@@ -14,7 +14,16 @@ feat(#{issue-number}): {feature-name}
|
|
|
14
14
|
|
|
15
15
|
Example: `feat(#1): Implement user authentication`
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### Link Format (Important!)
|
|
18
|
+
|
|
19
|
+
For file links within the repo in PR body, **always use current branch name**:
|
|
20
|
+
|
|
21
|
+
```markdown
|
|
22
|
+
[filename](https://github.com/{owner}/{repo}/blob/{branch-name}/docs/path/to/file.md)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
> ⚠️ `main` branch links will return 404 until merged!
|
|
26
|
+
> Always use the **current feature branch name** (e.g., `feat/5-feature-name`).
|
|
18
27
|
|
|
19
28
|
## PR Body Template
|
|
20
29
|
|
|
@@ -55,3 +64,12 @@ Closes #{issue-number}
|
|
|
55
64
|
| Normal Feature | Squash and Merge |
|
|
56
65
|
| Urgent Hotfix | Merge or Rebase |
|
|
57
66
|
| Documentation | Squash and Merge |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Body Input Rules (Shell Execution Prevention)
|
|
71
|
+
|
|
72
|
+
- PR body should use **`--body-file` by default**.
|
|
73
|
+
- If the body contains backticks (`) or `$()`and is placed directly in`"..."`, it may be **interpreted by the shell**.
|
|
74
|
+
- For multi-line bodies, use **single-quoted heredoc** like `cat <<'EOF'`,
|
|
75
|
+
and handle variables via **placeholder → sed substitution**.
|
|
@@ -29,10 +29,10 @@ features/
|
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
# Backend Feature
|
|
32
|
-
lee-spec-kit feature --repo be user-auth
|
|
32
|
+
npx lee-spec-kit feature --repo be user-auth
|
|
33
33
|
|
|
34
34
|
# Frontend Feature
|
|
35
|
-
lee-spec-kit feature --repo fe user-profile
|
|
35
|
+
npx lee-spec-kit feature --repo fe user-profile
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
> 💡 CLI copies templates from `feature-base/` and auto-assigns IDs.
|
|
@@ -55,13 +55,13 @@ lee-spec-kit feature --repo fe user-profile
|
|
|
55
55
|
Check feature progress with CLI:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
lee-spec-kit status
|
|
58
|
+
npx lee-spec-kit status
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
Save to file:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
lee-spec-kit status --write
|
|
64
|
+
npx lee-spec-kit status --write
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Implementation Plan: {
|
|
1
|
+
# Implementation Plan: {feature-name}
|
|
2
2
|
|
|
3
3
|
> Write after spec is approved.
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
8
|
|
|
9
|
-
- **Feature ID**: F{
|
|
9
|
+
- **Feature ID**: F{number}
|
|
10
10
|
- **Target Repo**: {{projectName}}-{be|fe}
|
|
11
11
|
- **Created**: YYYY-MM-DD
|
|
12
12
|
- **Status**: Draft | Review | Approved
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Feature Spec: {
|
|
1
|
+
# Feature Spec: {feature-name}
|
|
2
2
|
|
|
3
3
|
> Tech stack is covered in plan.md.
|
|
4
4
|
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
## Overview
|
|
8
8
|
|
|
9
|
-
- **Feature ID**: F{
|
|
10
|
-
- **Feature Name**: {
|
|
9
|
+
- **Feature ID**: F{number}
|
|
10
|
+
- **Feature Name**: {feature-name}
|
|
11
11
|
- **Target Repo**: {{projectName}}-{be|fe}
|
|
12
|
-
- **Issue Number**: #{
|
|
12
|
+
- **Issue Number**: #{issue-number}
|
|
13
13
|
- **Created**: YYYY-MM-DD
|
|
14
14
|
- **Status**: Draft | Review | Approved
|
|
15
15
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Tasks: {
|
|
1
|
+
# Tasks: {feature-name}
|
|
2
2
|
|
|
3
3
|
## Task Rules
|
|
4
4
|
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
## GitHub Issue
|
|
11
11
|
|
|
12
12
|
- **Repo**: {{projectName}}-{be|fe}
|
|
13
|
-
- **Issue**: #{
|
|
14
|
-
- **Branch**: `feat/{
|
|
13
|
+
- **Issue**: #{issue-number}
|
|
14
|
+
- **Branch**: `feat/{issue-number}-{feature-name}`
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
### Phase 1: {Phase Name}
|
|
21
21
|
|
|
22
|
-
- [TODO][P1] T-F{
|
|
22
|
+
- [TODO][P1] T-F{number}-01 {Task Title}
|
|
23
23
|
- Acceptance:
|
|
24
24
|
- (verification condition)
|
|
25
25
|
- Checklist:
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
This folder contains product requirements documentation.
|
|
4
4
|
|
|
5
|
+
> **📌 Document Scope**
|
|
6
|
+
>
|
|
7
|
+
> - **This folder**: Product requirements, business logic, user stories
|
|
8
|
+
> - **Constitution**: Tech stack, architecture principles, code quality, security principles → `agents/constitution.md`
|
|
9
|
+
|
|
5
10
|
## Writing Guide
|
|
6
11
|
|
|
7
12
|
1. Define project overview and goals
|