lee-spec-kit 0.1.7 → 0.2.0
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/dist/index.js +190 -61
- package/package.json +10 -8
- package/templates/en/common/agents/custom.md +29 -0
- package/templates/en/{fullstack → common}/agents/git-workflow.md +26 -35
- package/templates/en/{single → common}/agents/issue-template.md +10 -5
- package/templates/en/{single → common}/agents/pr-template.md +9 -2
- package/templates/en/common/agents/skills/create-feature.md +53 -0
- package/templates/en/common/agents/skills/create-issue.md +52 -0
- package/templates/en/common/agents/skills/create-pr.md +97 -0
- package/templates/en/common/agents/skills/execute-task.md +86 -0
- package/templates/en/fullstack/agents/agents.md +39 -26
- package/templates/en/single/agents/agents.md +35 -20
- package/templates/ko/common/agents/custom.md +29 -0
- package/templates/ko/{single → common}/agents/git-workflow.md +20 -74
- package/templates/ko/{single → common}/agents/issue-template.md +10 -5
- package/templates/ko/{fullstack → common}/agents/pr-template.md +9 -2
- package/templates/ko/common/agents/skills/create-feature.md +53 -0
- package/templates/ko/common/agents/skills/create-issue.md +52 -0
- package/templates/ko/common/agents/skills/create-pr.md +97 -0
- package/templates/ko/common/agents/skills/execute-task.md +86 -0
- package/templates/ko/fullstack/agents/agents.md +39 -33
- package/templates/ko/single/agents/agents.md +41 -21
- package/templates/en/fullstack/agents/constitution.md +0 -80
- package/templates/en/fullstack/agents/issue-template.md +0 -110
- package/templates/en/fullstack/agents/pr-template.md +0 -96
- package/templates/en/single/agents/git-workflow.md +0 -171
- package/templates/ko/fullstack/agents/git-workflow.md +0 -171
- package/templates/ko/fullstack/agents/issue-template.md +0 -114
- package/templates/ko/single/agents/constitution.md +0 -80
- package/templates/ko/single/agents/pr-template.md +0 -110
- /package/templates/en/{single → common}/agents/constitution.md +0 -0
- /package/templates/ko/{fullstack → common}/agents/constitution.md +0 -0
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 path6 from 'path';
|
|
6
|
+
import fs6 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 fs6.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 fs6.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 fs6.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 fs6.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 fs6.writeFile(file, content, "utf-8");
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
35
|
-
var __dirname2 =
|
|
35
|
+
var __dirname2 = path6.dirname(__filename2);
|
|
36
36
|
function getTemplatesDir() {
|
|
37
|
-
const rootDir =
|
|
38
|
-
return
|
|
37
|
+
const rootDir = path6.resolve(__dirname2, "..");
|
|
38
|
+
return path6.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 = path6.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 = path6.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 fs6.pathExists(targetDir)) {
|
|
213
|
+
const files = await fs6.readdir(targetDir);
|
|
214
214
|
if (files.length > 0) {
|
|
215
215
|
const { overwrite } = await prompts({
|
|
216
216
|
type: "confirm",
|
|
@@ -232,14 +232,20 @@ async function runInit(options) {
|
|
|
232
232
|
console.log(chalk.gray(` \uACBD\uB85C: ${targetDir}`));
|
|
233
233
|
console.log();
|
|
234
234
|
const templatesDir = getTemplatesDir();
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
const commonPath = path6.join(templatesDir, lang, "common");
|
|
236
|
+
const typePath = path6.join(templatesDir, lang, projectType);
|
|
237
|
+
if (await fs6.pathExists(commonPath)) {
|
|
238
|
+
await copyTemplates(commonPath, targetDir);
|
|
238
239
|
}
|
|
239
|
-
await
|
|
240
|
+
if (!await fs6.pathExists(typePath)) {
|
|
241
|
+
throw new Error(`\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${typePath}`);
|
|
242
|
+
}
|
|
243
|
+
await copyTemplates(typePath, targetDir);
|
|
244
|
+
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
240
245
|
const replacements = {
|
|
241
246
|
"{{projectName}}": projectName,
|
|
242
|
-
"{{date}}": (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
247
|
+
"{{date}}": (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
248
|
+
"{{featurePath}}": featurePath
|
|
243
249
|
};
|
|
244
250
|
await replaceInFiles(targetDir, replacements);
|
|
245
251
|
const config = {
|
|
@@ -248,8 +254,8 @@ async function runInit(options) {
|
|
|
248
254
|
lang,
|
|
249
255
|
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
250
256
|
};
|
|
251
|
-
const configPath =
|
|
252
|
-
await
|
|
257
|
+
const configPath = path6.join(targetDir, ".lee-spec-kit.json");
|
|
258
|
+
await fs6.writeJson(configPath, config, { spaces: 2 });
|
|
253
259
|
console.log(chalk.green("\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!"));
|
|
254
260
|
console.log();
|
|
255
261
|
await initGit(cwd, targetDir);
|
|
@@ -273,7 +279,7 @@ async function initGit(cwd, targetDir) {
|
|
|
273
279
|
console.log(chalk.blue("\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911..."));
|
|
274
280
|
execSync("git init", { cwd, stdio: "ignore" });
|
|
275
281
|
}
|
|
276
|
-
const relativePath =
|
|
282
|
+
const relativePath = path6.relative(cwd, targetDir);
|
|
277
283
|
execSync(`git add "${relativePath}"`, { cwd, stdio: "ignore" });
|
|
278
284
|
execSync('git commit -m "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)"', {
|
|
279
285
|
cwd,
|
|
@@ -290,15 +296,15 @@ async function initGit(cwd, targetDir) {
|
|
|
290
296
|
}
|
|
291
297
|
async function getConfig(cwd) {
|
|
292
298
|
const possibleDirs = [
|
|
293
|
-
|
|
299
|
+
path6.join(cwd, "docs"),
|
|
294
300
|
cwd
|
|
295
301
|
// 이미 docs 폴더 안에 있을 수 있음
|
|
296
302
|
];
|
|
297
303
|
for (const docsDir of possibleDirs) {
|
|
298
|
-
const configPath =
|
|
299
|
-
if (await
|
|
304
|
+
const configPath = path6.join(docsDir, ".lee-spec-kit.json");
|
|
305
|
+
if (await fs6.pathExists(configPath)) {
|
|
300
306
|
try {
|
|
301
|
-
const configFile = await
|
|
307
|
+
const configFile = await fs6.readJson(configPath);
|
|
302
308
|
return {
|
|
303
309
|
docsDir,
|
|
304
310
|
projectName: configFile.projectName,
|
|
@@ -308,16 +314,16 @@ async function getConfig(cwd) {
|
|
|
308
314
|
} catch {
|
|
309
315
|
}
|
|
310
316
|
}
|
|
311
|
-
const agentsPath =
|
|
312
|
-
const featuresPath =
|
|
313
|
-
if (await
|
|
314
|
-
const bePath =
|
|
315
|
-
const fePath =
|
|
316
|
-
const projectType = await
|
|
317
|
-
const agentsMdPath =
|
|
317
|
+
const agentsPath = path6.join(docsDir, "agents");
|
|
318
|
+
const featuresPath = path6.join(docsDir, "features");
|
|
319
|
+
if (await fs6.pathExists(agentsPath) && await fs6.pathExists(featuresPath)) {
|
|
320
|
+
const bePath = path6.join(featuresPath, "be");
|
|
321
|
+
const fePath = path6.join(featuresPath, "fe");
|
|
322
|
+
const projectType = await fs6.pathExists(bePath) || await fs6.pathExists(fePath) ? "fullstack" : "single";
|
|
323
|
+
const agentsMdPath = path6.join(agentsPath, "agents.md");
|
|
318
324
|
let lang = "ko";
|
|
319
|
-
if (await
|
|
320
|
-
const content = await
|
|
325
|
+
if (await fs6.pathExists(agentsMdPath)) {
|
|
326
|
+
const content = await fs6.readFile(agentsMdPath, "utf-8");
|
|
321
327
|
if (!/[가-힣]/.test(content)) {
|
|
322
328
|
lang = "en";
|
|
323
329
|
}
|
|
@@ -386,22 +392,22 @@ async function runFeature(name, options) {
|
|
|
386
392
|
}
|
|
387
393
|
let featuresDir;
|
|
388
394
|
if (projectType === "fullstack" && repo) {
|
|
389
|
-
featuresDir =
|
|
395
|
+
featuresDir = path6.join(docsDir, "features", repo);
|
|
390
396
|
} else {
|
|
391
|
-
featuresDir =
|
|
397
|
+
featuresDir = path6.join(docsDir, "features");
|
|
392
398
|
}
|
|
393
399
|
const featureFolderName = `${featureId}-${name}`;
|
|
394
|
-
const featureDir =
|
|
395
|
-
if (await
|
|
400
|
+
const featureDir = path6.join(featuresDir, featureFolderName);
|
|
401
|
+
if (await fs6.pathExists(featureDir)) {
|
|
396
402
|
console.error(chalk.red(`\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: ${featureDir}`));
|
|
397
403
|
process.exit(1);
|
|
398
404
|
}
|
|
399
|
-
const featureBasePath =
|
|
400
|
-
if (!await
|
|
405
|
+
const featureBasePath = path6.join(docsDir, "features", "feature-base");
|
|
406
|
+
if (!await fs6.pathExists(featureBasePath)) {
|
|
401
407
|
console.error(chalk.red("feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
402
408
|
process.exit(1);
|
|
403
409
|
}
|
|
404
|
-
await
|
|
410
|
+
await fs6.copy(featureBasePath, featureDir);
|
|
405
411
|
const idNumber = featureId.replace("F", "");
|
|
406
412
|
const repoName = projectType === "fullstack" && repo ? `{{projectName}}-${repo}` : "{{projectName}}";
|
|
407
413
|
const replacements = {
|
|
@@ -432,18 +438,18 @@ async function runFeature(name, options) {
|
|
|
432
438
|
console.log();
|
|
433
439
|
}
|
|
434
440
|
async function getNextFeatureId(docsDir, projectType) {
|
|
435
|
-
const featuresDir =
|
|
441
|
+
const featuresDir = path6.join(docsDir, "features");
|
|
436
442
|
let max = 0;
|
|
437
443
|
const scanDirs = [];
|
|
438
444
|
if (projectType === "fullstack") {
|
|
439
|
-
scanDirs.push(
|
|
440
|
-
scanDirs.push(
|
|
445
|
+
scanDirs.push(path6.join(featuresDir, "be"));
|
|
446
|
+
scanDirs.push(path6.join(featuresDir, "fe"));
|
|
441
447
|
} else {
|
|
442
448
|
scanDirs.push(featuresDir);
|
|
443
449
|
}
|
|
444
450
|
for (const dir of scanDirs) {
|
|
445
|
-
if (!await
|
|
446
|
-
const entries = await
|
|
451
|
+
if (!await fs6.pathExists(dir)) continue;
|
|
452
|
+
const entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
447
453
|
for (const entry of entries) {
|
|
448
454
|
if (!entry.isDirectory()) continue;
|
|
449
455
|
const match = entry.name.match(/^F(\d+)-/);
|
|
@@ -477,29 +483,29 @@ async function runStatus(options) {
|
|
|
477
483
|
process.exit(1);
|
|
478
484
|
}
|
|
479
485
|
const { docsDir, projectType } = config;
|
|
480
|
-
const featuresDir =
|
|
486
|
+
const featuresDir = path6.join(docsDir, "features");
|
|
481
487
|
const features = [];
|
|
482
488
|
const idMap = /* @__PURE__ */ new Map();
|
|
483
489
|
const scopes = projectType === "fullstack" ? ["be", "fe"] : [""];
|
|
484
490
|
for (const scope of scopes) {
|
|
485
|
-
const scanDir = scope ?
|
|
486
|
-
if (!await
|
|
487
|
-
const entries = await
|
|
491
|
+
const scanDir = scope ? path6.join(featuresDir, scope) : featuresDir;
|
|
492
|
+
if (!await fs6.pathExists(scanDir)) continue;
|
|
493
|
+
const entries = await fs6.readdir(scanDir, { withFileTypes: true });
|
|
488
494
|
for (const entry of entries) {
|
|
489
495
|
if (!entry.isDirectory()) continue;
|
|
490
496
|
if (entry.name === "feature-base") continue;
|
|
491
|
-
const featureDir =
|
|
492
|
-
const specPath =
|
|
493
|
-
const tasksPath =
|
|
494
|
-
if (!await
|
|
495
|
-
if (!await
|
|
496
|
-
const specContent = await
|
|
497
|
-
const tasksContent = await
|
|
497
|
+
const featureDir = path6.join(scanDir, entry.name);
|
|
498
|
+
const specPath = path6.join(featureDir, "spec.md");
|
|
499
|
+
const tasksPath = path6.join(featureDir, "tasks.md");
|
|
500
|
+
if (!await fs6.pathExists(specPath)) continue;
|
|
501
|
+
if (!await fs6.pathExists(tasksPath)) continue;
|
|
502
|
+
const specContent = await fs6.readFile(specPath, "utf-8");
|
|
503
|
+
const tasksContent = await fs6.readFile(tasksPath, "utf-8");
|
|
498
504
|
const id = extractSpecValue(specContent, "\uAE30\uB2A5 ID") || extractSpecValue(specContent, "Feature ID") || "UNKNOWN";
|
|
499
505
|
const name = extractSpecValue(specContent, "\uAE30\uB2A5\uBA85") || extractSpecValue(specContent, "Feature Name") || entry.name;
|
|
500
506
|
const repo = extractSpecValue(specContent, "\uB300\uC0C1 \uB808\uD3EC") || extractSpecValue(specContent, "Target Repo") || (scope ? `{{projectName}}-${scope}` : "{{projectName}}");
|
|
501
507
|
const issue = extractSpecValue(specContent, "\uC774\uC288 \uBC88\uD638") || extractSpecValue(specContent, "Issue Number") || "-";
|
|
502
|
-
const relPath =
|
|
508
|
+
const relPath = path6.relative(docsDir, featureDir);
|
|
503
509
|
if (!idMap.has(id)) {
|
|
504
510
|
idMap.set(id, []);
|
|
505
511
|
}
|
|
@@ -569,7 +575,7 @@ async function runStatus(options) {
|
|
|
569
575
|
}
|
|
570
576
|
console.log();
|
|
571
577
|
if (options.write) {
|
|
572
|
-
const outputPath =
|
|
578
|
+
const outputPath = path6.join(featuresDir, "status.md");
|
|
573
579
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
574
580
|
const content = [
|
|
575
581
|
"# Feature Status",
|
|
@@ -584,7 +590,7 @@ async function runStatus(options) {
|
|
|
584
590
|
),
|
|
585
591
|
""
|
|
586
592
|
].join("\n");
|
|
587
|
-
await
|
|
593
|
+
await fs6.writeFile(outputPath, content, "utf-8");
|
|
588
594
|
console.log(chalk.green(`\u2705 ${outputPath} \uC0DD\uC131 \uC644\uB8CC`));
|
|
589
595
|
}
|
|
590
596
|
}
|
|
@@ -611,6 +617,128 @@ function countTasks(content) {
|
|
|
611
617
|
}
|
|
612
618
|
return { total, done, doing, todo };
|
|
613
619
|
}
|
|
620
|
+
function updateCommand(program2) {
|
|
621
|
+
program2.command("update").description("Update docs templates to the latest version").option("--agents", "Update agents/ folder only").option("--templates", "Update feature-base/ folder only").option("-f, --force", "Force overwrite without confirmation").action(async (options) => {
|
|
622
|
+
try {
|
|
623
|
+
await runUpdate(options);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (error instanceof Error && error.message === "canceled") {
|
|
626
|
+
console.log(chalk.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
627
|
+
process.exit(0);
|
|
628
|
+
}
|
|
629
|
+
console.error(chalk.red("\uC624\uB958:"), error);
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
async function runUpdate(options) {
|
|
635
|
+
const cwd = process.cwd();
|
|
636
|
+
const config = await getConfig(cwd);
|
|
637
|
+
if (!config) {
|
|
638
|
+
console.error(
|
|
639
|
+
chalk.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
|
|
640
|
+
);
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
const { docsDir, projectType, lang } = config;
|
|
644
|
+
const templatesDir = getTemplatesDir();
|
|
645
|
+
const sourceDir = path6.join(templatesDir, lang, projectType);
|
|
646
|
+
const updateAgents = options.agents || !options.agents && !options.templates;
|
|
647
|
+
const updateTemplates = options.templates || !options.agents && !options.templates;
|
|
648
|
+
console.log(chalk.blue("\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4..."));
|
|
649
|
+
console.log(chalk.gray(` - \uC5B8\uC5B4: ${lang}`));
|
|
650
|
+
console.log(chalk.gray(` - \uD0C0\uC785: ${projectType}`));
|
|
651
|
+
console.log();
|
|
652
|
+
let updatedCount = 0;
|
|
653
|
+
if (updateAgents) {
|
|
654
|
+
console.log(chalk.blue("\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
655
|
+
const commonAgents = path6.join(templatesDir, lang, "common", "agents");
|
|
656
|
+
const typeAgents = path6.join(templatesDir, lang, projectType, "agents");
|
|
657
|
+
const targetAgents = path6.join(docsDir, "agents");
|
|
658
|
+
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
659
|
+
const replacements = {
|
|
660
|
+
"{{featurePath}}": featurePath
|
|
661
|
+
};
|
|
662
|
+
if (await fs6.pathExists(commonAgents)) {
|
|
663
|
+
const count = await updateFolder(
|
|
664
|
+
commonAgents,
|
|
665
|
+
targetAgents,
|
|
666
|
+
options.force,
|
|
667
|
+
replacements
|
|
668
|
+
);
|
|
669
|
+
updatedCount += count;
|
|
670
|
+
}
|
|
671
|
+
if (await fs6.pathExists(typeAgents)) {
|
|
672
|
+
const count = await updateFolder(typeAgents, targetAgents, options.force);
|
|
673
|
+
updatedCount += count;
|
|
674
|
+
}
|
|
675
|
+
console.log(chalk.green(` \u2705 agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
|
|
676
|
+
}
|
|
677
|
+
if (updateTemplates) {
|
|
678
|
+
console.log(chalk.blue("\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
679
|
+
const sourceFeatureBase = path6.join(sourceDir, "features", "feature-base");
|
|
680
|
+
const targetFeatureBase = path6.join(docsDir, "features", "feature-base");
|
|
681
|
+
if (await fs6.pathExists(sourceFeatureBase)) {
|
|
682
|
+
const count = await updateFolder(
|
|
683
|
+
sourceFeatureBase,
|
|
684
|
+
targetFeatureBase,
|
|
685
|
+
options.force
|
|
686
|
+
);
|
|
687
|
+
updatedCount += count;
|
|
688
|
+
console.log(chalk.green(` \u2705 ${count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
console.log();
|
|
692
|
+
console.log(chalk.green(`\u2705 \uCD1D ${updatedCount}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
|
|
693
|
+
}
|
|
694
|
+
async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
695
|
+
await fs6.ensureDir(targetDir);
|
|
696
|
+
const files = await fs6.readdir(sourceDir);
|
|
697
|
+
let updatedCount = 0;
|
|
698
|
+
for (const file of files) {
|
|
699
|
+
const sourcePath = path6.join(sourceDir, file);
|
|
700
|
+
const targetPath = path6.join(targetDir, file);
|
|
701
|
+
const stat = await fs6.stat(sourcePath);
|
|
702
|
+
if (stat.isFile()) {
|
|
703
|
+
if (file === "custom.md") {
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
let sourceContent = await fs6.readFile(sourcePath, "utf-8");
|
|
707
|
+
if (replacements) {
|
|
708
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
709
|
+
sourceContent = sourceContent.replaceAll(key, value);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
let shouldUpdate = true;
|
|
713
|
+
if (await fs6.pathExists(targetPath)) {
|
|
714
|
+
const targetContent = await fs6.readFile(targetPath, "utf-8");
|
|
715
|
+
if (sourceContent === targetContent) {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (!force) {
|
|
719
|
+
console.log(
|
|
720
|
+
chalk.yellow(` \u26A0\uFE0F ${file} - \uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)`)
|
|
721
|
+
);
|
|
722
|
+
shouldUpdate = false;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (shouldUpdate) {
|
|
726
|
+
await fs6.writeFile(targetPath, sourceContent);
|
|
727
|
+
console.log(chalk.gray(` \u{1F4C4} ${file} \uC5C5\uB370\uC774\uD2B8`));
|
|
728
|
+
updatedCount++;
|
|
729
|
+
}
|
|
730
|
+
} else if (stat.isDirectory()) {
|
|
731
|
+
const subCount = await updateFolder(
|
|
732
|
+
sourcePath,
|
|
733
|
+
targetPath,
|
|
734
|
+
force,
|
|
735
|
+
replacements
|
|
736
|
+
);
|
|
737
|
+
updatedCount += subCount;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return updatedCount;
|
|
741
|
+
}
|
|
614
742
|
|
|
615
743
|
// src/index.ts
|
|
616
744
|
program.name("lee-spec-kit").description(
|
|
@@ -619,4 +747,5 @@ program.name("lee-spec-kit").description(
|
|
|
619
747
|
initCommand(program);
|
|
620
748
|
featureCommand(program);
|
|
621
749
|
statusCommand(program);
|
|
750
|
+
updateCommand(program);
|
|
622
751
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lee-spec-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Project documentation structure generator for AI-assisted development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
"dist",
|
|
13
13
|
"templates"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"lint": "eslint src",
|
|
19
|
+
"format": "prettier --write .",
|
|
20
|
+
"prepublishOnly": "pnpm build"
|
|
21
|
+
},
|
|
15
22
|
"keywords": [
|
|
16
23
|
"docs",
|
|
17
24
|
"template",
|
|
@@ -49,10 +56,5 @@
|
|
|
49
56
|
"tsup": "^8.5.1",
|
|
50
57
|
"typescript": "^5.9.3"
|
|
51
58
|
},
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
"dev": "tsup --watch",
|
|
55
|
-
"lint": "eslint src",
|
|
56
|
-
"format": "prettier --write ."
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
+
"packageManager": "pnpm@10.7.0"
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Custom Rules
|
|
2
|
+
|
|
3
|
+
> ⚠️ This document contains **user-defined rules**.
|
|
4
|
+
> It is NOT affected by `npx lee-spec-kit update`.
|
|
5
|
+
> **Rules in this document take precedence over all other agent rules.**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Project-Specific Rules
|
|
10
|
+
|
|
11
|
+
(Write your project-specific rules here)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Additional Language/Code Rules
|
|
16
|
+
|
|
17
|
+
(Override default rules or add additional rules here)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Custom Workflows
|
|
22
|
+
|
|
23
|
+
(Write project-specific workflows here)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Other
|
|
28
|
+
|
|
29
|
+
(Write other rules here)
|
|
@@ -37,6 +37,11 @@ main
|
|
|
37
37
|
| `refactor` | Refactoring |
|
|
38
38
|
| `docs` | Documentation |
|
|
39
39
|
|
|
40
|
+
**Examples:**
|
|
41
|
+
|
|
42
|
+
- `feat/123-user-auth`
|
|
43
|
+
- `fix/456-login-error`
|
|
44
|
+
|
|
40
45
|
---
|
|
41
46
|
|
|
42
47
|
## Commit Convention
|
|
@@ -65,50 +70,36 @@ main
|
|
|
65
70
|
|
|
66
71
|
## Automation Workflow
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
# 1. Create GitHub Issue (Feature = Issue)
|
|
72
|
-
# 2. Create branch
|
|
73
|
-
git checkout -b feat/{issue-number}-{feature-name}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### 2. Document Writing and Commit
|
|
73
|
+
> 📖 Refer to `skills/` folder for step-by-step guides.
|
|
77
74
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
75
|
+
| Workflow | Guide |
|
|
76
|
+
| -------------- | -------------------------- |
|
|
77
|
+
| Feature Start | `skills/create-feature.md` |
|
|
78
|
+
| Issue Creation | `skills/create-issue.md` |
|
|
79
|
+
| Task Execution | `skills/execute-task.md` |
|
|
80
|
+
| PR Creation | `skills/create-pr.md` |
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
> Commit each document individually **after user approval**.
|
|
87
|
-
|
|
88
|
-
### 3. Auto Commit on Task Completion
|
|
82
|
+
### Branch Creation
|
|
89
83
|
|
|
90
84
|
```bash
|
|
91
|
-
git
|
|
92
|
-
git commit -m "{type}(#{issue}): {task-description}"
|
|
85
|
+
git checkout -b feat/{issue-number}-{feature-name}
|
|
93
86
|
```
|
|
94
87
|
|
|
95
|
-
###
|
|
88
|
+
### Document Commit Timing (docs repo)
|
|
96
89
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
--base main
|
|
102
|
-
```
|
|
90
|
+
| Commit Timing | Included Content | Commit Message Example |
|
|
91
|
+
| ------------------------------------------------- | ----------------------------------- | --------------------------------------------- |
|
|
92
|
+
| When planning complete (spec+plan+tasks approved) | `F{number}-{feature-name}/` folder | `docs(#{issue}): F{number} spec, plan, tasks` |
|
|
93
|
+
| When Feature complete (all tasks done) | `F{number}-{feature-name}/` changes | `docs(#{issue}): F{number} Feature complete` |
|
|
103
94
|
|
|
104
|
-
|
|
95
|
+
> ⚠️ Do not commit when creating Feature folder.
|
|
105
96
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
97
|
+
### Merge Strategy
|
|
98
|
+
|
|
99
|
+
| Situation | Merge Method |
|
|
100
|
+
| -------------- | ---------------- |
|
|
101
|
+
| Normal Feature | Squash and Merge |
|
|
102
|
+
| Urgent Hotfix | Squash and Merge |
|
|
112
103
|
|
|
113
104
|
---
|
|
114
105
|
|
|
@@ -36,12 +36,17 @@ In GitHub Issues, use different link formats **based on file location**:
|
|
|
36
36
|
[react-i18next](https://react.i18next.com/)
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
3. **
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
3. **Local documents** (no URL available): **Path from project root**
|
|
40
|
+
|
|
41
|
+
> 📁 Local documents use paths **from project root**.
|
|
42
|
+
> Format: `- **{Label}**: \`{path}\``
|
|
43
|
+
|
|
44
|
+
```markdown
|
|
45
|
+
- **Spec**: `{{featurePath}}/F001-feature-name/spec.md`
|
|
46
|
+
- **Tasks**: `{{featurePath}}/F001-feature-name/tasks.md`
|
|
42
47
|
```
|
|
43
48
|
|
|
44
|
-
> ⚠️ Local documents are not clickable on GitHub, so
|
|
49
|
+
> ⚠️ Local documents are not clickable on GitHub, so use **bold label + code block path** format instead of markdown links.
|
|
45
50
|
|
|
46
51
|
---
|
|
47
52
|
|
|
@@ -64,7 +69,7 @@ In GitHub Issues, use different link formats **based on file location**:
|
|
|
64
69
|
|
|
65
70
|
## Related Documents
|
|
66
71
|
|
|
67
|
-
- Spec
|
|
72
|
+
- **Spec**: `{{featurePath}}/F{number}-{feature-name}/spec.md`
|
|
68
73
|
|
|
69
74
|
## Labels
|
|
70
75
|
|
|
@@ -42,17 +42,24 @@ For file links within the repo in PR body, **always use current branch name**:
|
|
|
42
42
|
|
|
43
43
|
## Tests
|
|
44
44
|
|
|
45
|
+
> ⚠️ **Check only after running tests. Do NOT check items that were not executed.**
|
|
46
|
+
|
|
45
47
|
- [ ] Unit tests passed
|
|
46
48
|
- [ ] Integration tests completed
|
|
47
49
|
|
|
50
|
+
### Execution Results
|
|
51
|
+
|
|
52
|
+
- Command: `{test command executed}`
|
|
53
|
+
- Result: `{PASS/FAIL summary}`
|
|
54
|
+
|
|
48
55
|
## Screenshots (for UI changes)
|
|
49
56
|
|
|
50
57
|
{Attach if applicable}
|
|
51
58
|
|
|
52
59
|
## Related Documents
|
|
53
60
|
|
|
54
|
-
- Spec
|
|
55
|
-
- Tasks
|
|
61
|
+
- **Spec**: `{{featurePath}}/F{number}-{feature-name}/spec.md`
|
|
62
|
+
- **Tasks**: `{{featurePath}}/F{number}-{feature-name}/tasks.md`
|
|
56
63
|
|
|
57
64
|
Closes #{issue-number}
|
|
58
65
|
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# New Feature Creation Process
|
|
2
|
+
|
|
3
|
+
Step-by-step guide for adding a new Feature.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
### 1. Create Feature Folder
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx lee-spec-kit feature <name> -d "<description>"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- `<name>`: Feature name (lowercase, hyphens allowed)
|
|
16
|
+
- `-d`: Feature description (auto-filled in spec.md)
|
|
17
|
+
|
|
18
|
+
**Example:**
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx lee-spec-kit feature user-auth -d "User authentication and session management"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Write spec.md
|
|
25
|
+
|
|
26
|
+
- **What**: Clearly describe what the feature does
|
|
27
|
+
- **Why**: Explain why this feature is needed
|
|
28
|
+
- ❌ Do NOT include tech stack (covered in plan.md)
|
|
29
|
+
|
|
30
|
+
### 3. Request User Approval
|
|
31
|
+
|
|
32
|
+
> 🚨 **User Approval Required**
|
|
33
|
+
|
|
34
|
+
Share full spec.md content with user and wait for **explicit approval (OK)**
|
|
35
|
+
|
|
36
|
+
### 4. Create GitHub Issue
|
|
37
|
+
|
|
38
|
+
→ See `skills/create-issue.md`
|
|
39
|
+
|
|
40
|
+
### 5. Pre-Commit Checklist
|
|
41
|
+
|
|
42
|
+
> ⚠️ **Before committing, verify:**
|
|
43
|
+
|
|
44
|
+
- [ ] Issue number in spec.md (`- **Issue Number**: #{issue}`)
|
|
45
|
+
- [ ] Issue number in tasks.md (`- **Issue**: #{issue}`)
|
|
46
|
+
- [ ] Branch name in tasks.md (`feat/{issue-number}-{feature-name}`)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Reference Documents
|
|
51
|
+
|
|
52
|
+
- **Feature Template**: `features/feature-base/`
|
|
53
|
+
- **Issue Creation Guide**: `skills/create-issue.md`
|