lee-spec-kit 0.1.3 → 0.1.7
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 +108 -55
- package/package.json +1 -1
- package/templates/en/fullstack/agents/agents.md +9 -1
- package/templates/en/fullstack/agents/constitution.md +5 -0
- package/templates/en/fullstack/agents/git-workflow.md +17 -3
- package/templates/en/fullstack/agents/issue-template.md +44 -1
- package/templates/en/fullstack/agents/pr-template.md +31 -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 +6 -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 +9 -1
- package/templates/en/single/agents/constitution.md +44 -39
- package/templates/en/single/agents/git-workflow.md +74 -60
- package/templates/en/single/agents/issue-template.md +76 -29
- package/templates/en/single/agents/pr-template.md +63 -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 +6 -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 +9 -1
- package/templates/ko/fullstack/agents/constitution.md +5 -0
- package/templates/ko/fullstack/agents/git-workflow.md +17 -3
- package/templates/ko/fullstack/agents/issue-template.md +45 -0
- package/templates/ko/fullstack/agents/pr-template.md +31 -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/features/feature-base/spec.md +2 -0
- package/templates/ko/fullstack/prd/README.md +5 -0
- package/templates/ko/single/agents/agents.md +9 -1
- package/templates/ko/single/agents/constitution.md +5 -0
- package/templates/ko/single/agents/git-workflow.md +17 -3
- package/templates/ko/single/agents/issue-template.md +45 -0
- package/templates/ko/single/agents/pr-template.md +31 -1
- package/templates/ko/single/features/README.md +2 -2
- package/templates/ko/single/features/feature-base/spec.md +2 -0
- 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
|
}
|
|
@@ -278,7 +330,7 @@ async function getConfig(cwd) {
|
|
|
278
330
|
|
|
279
331
|
// src/commands/feature.ts
|
|
280
332
|
function featureCommand(program2) {
|
|
281
|
-
program2.command("feature <name>").description("Create a new feature folder").option("-r, --repo <repo>", "Repository type: be | fe (fullstack only)").option("--id <id>", "Feature ID (default: auto)").action(async (name, options) => {
|
|
333
|
+
program2.command("feature <name>").description("Create a new feature folder").option("-r, --repo <repo>", "Repository type: be | fe (fullstack only)").option("--id <id>", "Feature ID (default: auto)").option("-d, --desc <description>", "Feature description for spec.md").action(async (name, options) => {
|
|
282
334
|
try {
|
|
283
335
|
await runFeature(name, options);
|
|
284
336
|
} catch (error) {
|
|
@@ -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 = {
|
|
@@ -358,7 +410,8 @@ async function runFeature(name, options) {
|
|
|
358
410
|
"YYYY-MM-DD": (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
359
411
|
"{be|fe}": repo || "",
|
|
360
412
|
"git-dungeon-{be|fe}": repoName,
|
|
361
|
-
"{\uC774\uC288\uBC88\uD638}": ""
|
|
413
|
+
"{\uC774\uC288\uBC88\uD638}": "",
|
|
414
|
+
"{{description}}": options.desc || ""
|
|
362
415
|
};
|
|
363
416
|
if (lang === "en") {
|
|
364
417
|
replacements["\uAE30\uB2A5 ID"] = "Feature ID";
|
|
@@ -379,18 +432,18 @@ async function runFeature(name, options) {
|
|
|
379
432
|
console.log();
|
|
380
433
|
}
|
|
381
434
|
async function getNextFeatureId(docsDir, projectType) {
|
|
382
|
-
const featuresDir =
|
|
435
|
+
const featuresDir = path3.join(docsDir, "features");
|
|
383
436
|
let max = 0;
|
|
384
437
|
const scanDirs = [];
|
|
385
438
|
if (projectType === "fullstack") {
|
|
386
|
-
scanDirs.push(
|
|
387
|
-
scanDirs.push(
|
|
439
|
+
scanDirs.push(path3.join(featuresDir, "be"));
|
|
440
|
+
scanDirs.push(path3.join(featuresDir, "fe"));
|
|
388
441
|
} else {
|
|
389
442
|
scanDirs.push(featuresDir);
|
|
390
443
|
}
|
|
391
444
|
for (const dir of scanDirs) {
|
|
392
|
-
if (!await
|
|
393
|
-
const entries = await
|
|
445
|
+
if (!await fs3.pathExists(dir)) continue;
|
|
446
|
+
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
394
447
|
for (const entry of entries) {
|
|
395
448
|
if (!entry.isDirectory()) continue;
|
|
396
449
|
const match = entry.name.match(/^F(\d+)-/);
|
|
@@ -424,29 +477,29 @@ async function runStatus(options) {
|
|
|
424
477
|
process.exit(1);
|
|
425
478
|
}
|
|
426
479
|
const { docsDir, projectType } = config;
|
|
427
|
-
const featuresDir =
|
|
480
|
+
const featuresDir = path3.join(docsDir, "features");
|
|
428
481
|
const features = [];
|
|
429
482
|
const idMap = /* @__PURE__ */ new Map();
|
|
430
483
|
const scopes = projectType === "fullstack" ? ["be", "fe"] : [""];
|
|
431
484
|
for (const scope of scopes) {
|
|
432
|
-
const scanDir = scope ?
|
|
433
|
-
if (!await
|
|
434
|
-
const entries = await
|
|
485
|
+
const scanDir = scope ? path3.join(featuresDir, scope) : featuresDir;
|
|
486
|
+
if (!await fs3.pathExists(scanDir)) continue;
|
|
487
|
+
const entries = await fs3.readdir(scanDir, { withFileTypes: true });
|
|
435
488
|
for (const entry of entries) {
|
|
436
489
|
if (!entry.isDirectory()) continue;
|
|
437
490
|
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
|
|
491
|
+
const featureDir = path3.join(scanDir, entry.name);
|
|
492
|
+
const specPath = path3.join(featureDir, "spec.md");
|
|
493
|
+
const tasksPath = path3.join(featureDir, "tasks.md");
|
|
494
|
+
if (!await fs3.pathExists(specPath)) continue;
|
|
495
|
+
if (!await fs3.pathExists(tasksPath)) continue;
|
|
496
|
+
const specContent = await fs3.readFile(specPath, "utf-8");
|
|
497
|
+
const tasksContent = await fs3.readFile(tasksPath, "utf-8");
|
|
445
498
|
const id = extractSpecValue(specContent, "\uAE30\uB2A5 ID") || extractSpecValue(specContent, "Feature ID") || "UNKNOWN";
|
|
446
499
|
const name = extractSpecValue(specContent, "\uAE30\uB2A5\uBA85") || extractSpecValue(specContent, "Feature Name") || entry.name;
|
|
447
500
|
const repo = extractSpecValue(specContent, "\uB300\uC0C1 \uB808\uD3EC") || extractSpecValue(specContent, "Target Repo") || (scope ? `{{projectName}}-${scope}` : "{{projectName}}");
|
|
448
501
|
const issue = extractSpecValue(specContent, "\uC774\uC288 \uBC88\uD638") || extractSpecValue(specContent, "Issue Number") || "-";
|
|
449
|
-
const relPath =
|
|
502
|
+
const relPath = path3.relative(docsDir, featureDir);
|
|
450
503
|
if (!idMap.has(id)) {
|
|
451
504
|
idMap.set(id, []);
|
|
452
505
|
}
|
|
@@ -516,7 +569,7 @@ async function runStatus(options) {
|
|
|
516
569
|
}
|
|
517
570
|
console.log();
|
|
518
571
|
if (options.write) {
|
|
519
|
-
const outputPath =
|
|
572
|
+
const outputPath = path3.join(featuresDir, "status.md");
|
|
520
573
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
521
574
|
const content = [
|
|
522
575
|
"# Feature Status",
|
|
@@ -531,7 +584,7 @@ async function runStatus(options) {
|
|
|
531
584
|
),
|
|
532
585
|
""
|
|
533
586
|
].join("\n");
|
|
534
|
-
await
|
|
587
|
+
await fs3.writeFile(outputPath, content, "utf-8");
|
|
535
588
|
console.log(chalk.green(`\u2705 ${outputPath} \uC0DD\uC131 \uC644\uB8CC`));
|
|
536
589
|
}
|
|
537
590
|
}
|
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
|
|
@@ -69,6 +69,14 @@ docs/
|
|
|
69
69
|
3. Transition status: `[TODO]` → `[DOING]` → `[DONE]`
|
|
70
70
|
4. Commit immediately after task completion
|
|
71
71
|
|
|
72
|
+
### 4. Handling Requests Outside Tasks
|
|
73
|
+
|
|
74
|
+
> When user requests **work not in tasks.md**:
|
|
75
|
+
|
|
76
|
+
1. Ask user if this should be **added to tasks.md**
|
|
77
|
+
2. If approved: Add to tasks.md then execute
|
|
78
|
+
3. If declined: Proceed as temporary work (still included in commit)
|
|
79
|
+
|
|
72
80
|
---
|
|
73
81
|
|
|
74
82
|
## 📋 ADR (Architecture Decision Records)
|
|
@@ -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
|
|
@@ -41,6 +41,8 @@ main
|
|
|
41
41
|
|
|
42
42
|
## Commit Convention
|
|
43
43
|
|
|
44
|
+
> 📖 Type and Description follow [Udacity Git Commit Message Style Guide](https://udacity.github.io/git-styleguide/).
|
|
45
|
+
|
|
44
46
|
### Format
|
|
45
47
|
|
|
46
48
|
```
|
|
@@ -71,14 +73,26 @@ main
|
|
|
71
73
|
git checkout -b feat/{issue-number}-{feature-name}
|
|
72
74
|
```
|
|
73
75
|
|
|
74
|
-
### 2.
|
|
76
|
+
### 2. Document Writing and Commit
|
|
77
|
+
|
|
78
|
+
| Document | Commit Timing | Commit Message Example |
|
|
79
|
+
| ------------ | ----------------------- | ------------------------------ |
|
|
80
|
+
| spec.md | After user approval | `docs(#123): write spec` |
|
|
81
|
+
| plan.md | After user approval | `docs(#123): write plan` |
|
|
82
|
+
| tasks.md | After user approval | `docs(#123): break down tasks` |
|
|
83
|
+
| decisions.md | Included in task commit | (no separate commit) |
|
|
84
|
+
|
|
85
|
+
> 📌 **Do not commit when creating Feature folder.**
|
|
86
|
+
> Commit each document individually **after user approval**.
|
|
87
|
+
|
|
88
|
+
### 3. Auto Commit on Task Completion
|
|
75
89
|
|
|
76
90
|
```bash
|
|
77
91
|
git add .
|
|
78
92
|
git commit -m "{type}(#{issue}): {task-description}"
|
|
79
93
|
```
|
|
80
94
|
|
|
81
|
-
###
|
|
95
|
+
### 4. Create PR on Feature Completion
|
|
82
96
|
|
|
83
97
|
```bash
|
|
84
98
|
git push origin feat/{issue-number}-{feature-name}
|
|
@@ -87,7 +101,7 @@ gh pr create --title "feat(#{issue}): {feature-title}" \
|
|
|
87
101
|
--base main
|
|
88
102
|
```
|
|
89
103
|
|
|
90
|
-
###
|
|
104
|
+
### 5. Merge
|
|
91
105
|
|
|
92
106
|
```bash
|
|
93
107
|
git checkout main
|
|
@@ -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
|
|
|
@@ -57,6 +82,24 @@ Example: `F001: user-auth (User authentication feature)`
|
|
|
57
82
|
| `frontend` | FE related |
|
|
58
83
|
| `priority:high` | High priority |
|
|
59
84
|
|
|
85
|
+
> ⚠️ If a label does not exist, create it first:
|
|
86
|
+
>
|
|
87
|
+
> ```bash
|
|
88
|
+
> gh label create "label-name" --description "description" --color "color-code"
|
|
89
|
+
> ```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Assignee Rules
|
|
94
|
+
|
|
95
|
+
- Default: Self-assign (`--assignee @me`)
|
|
96
|
+
- When assigning others, **confirm with user** first
|
|
97
|
+
- Examples:
|
|
98
|
+
```bash
|
|
99
|
+
gh issue create --assignee @me ...
|
|
100
|
+
gh issue create --assignee username ...
|
|
101
|
+
```
|
|
102
|
+
|
|
60
103
|
---
|
|
61
104
|
|
|
62
105
|
## Body Input Rules (Shell Execution Prevention)
|
|
@@ -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
|
|
|
@@ -58,6 +67,27 @@ Closes #{issue-number}
|
|
|
58
67
|
|
|
59
68
|
---
|
|
60
69
|
|
|
70
|
+
## Label Rules
|
|
71
|
+
|
|
72
|
+
- Specify appropriate labels when creating PR (`--label`)
|
|
73
|
+
- If a label does not exist, create it first:
|
|
74
|
+
```bash
|
|
75
|
+
gh label create "label-name" --description "description" --color "color-code"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Assignee Rules
|
|
81
|
+
|
|
82
|
+
- Default: Self-assign (`--assignee @me`)
|
|
83
|
+
- Use `--reviewer` option to specify reviewers
|
|
84
|
+
- Examples:
|
|
85
|
+
```bash
|
|
86
|
+
gh pr create --assignee @me --reviewer reviewer-username ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
61
91
|
## Body Input Rules (Shell Execution Prevention)
|
|
62
92
|
|
|
63
93
|
- PR body should use **`--body-file` by default**.
|
|
@@ -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
|
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
## Purpose
|
|
19
19
|
|
|
20
|
+
{{description}}
|
|
21
|
+
|
|
20
22
|
(Why is this feature needed? What problem does it solve?)
|
|
21
23
|
|
|
22
24
|
---
|
|
@@ -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
|