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.
Files changed (32) hide show
  1. package/dist/index.js +190 -61
  2. package/package.json +10 -8
  3. package/templates/en/common/agents/custom.md +29 -0
  4. package/templates/en/{fullstack → common}/agents/git-workflow.md +26 -35
  5. package/templates/en/{single → common}/agents/issue-template.md +10 -5
  6. package/templates/en/{single → common}/agents/pr-template.md +9 -2
  7. package/templates/en/common/agents/skills/create-feature.md +53 -0
  8. package/templates/en/common/agents/skills/create-issue.md +52 -0
  9. package/templates/en/common/agents/skills/create-pr.md +97 -0
  10. package/templates/en/common/agents/skills/execute-task.md +86 -0
  11. package/templates/en/fullstack/agents/agents.md +39 -26
  12. package/templates/en/single/agents/agents.md +35 -20
  13. package/templates/ko/common/agents/custom.md +29 -0
  14. package/templates/ko/{single → common}/agents/git-workflow.md +20 -74
  15. package/templates/ko/{single → common}/agents/issue-template.md +10 -5
  16. package/templates/ko/{fullstack → common}/agents/pr-template.md +9 -2
  17. package/templates/ko/common/agents/skills/create-feature.md +53 -0
  18. package/templates/ko/common/agents/skills/create-issue.md +52 -0
  19. package/templates/ko/common/agents/skills/create-pr.md +97 -0
  20. package/templates/ko/common/agents/skills/execute-task.md +86 -0
  21. package/templates/ko/fullstack/agents/agents.md +39 -33
  22. package/templates/ko/single/agents/agents.md +41 -21
  23. package/templates/en/fullstack/agents/constitution.md +0 -80
  24. package/templates/en/fullstack/agents/issue-template.md +0 -110
  25. package/templates/en/fullstack/agents/pr-template.md +0 -96
  26. package/templates/en/single/agents/git-workflow.md +0 -171
  27. package/templates/ko/fullstack/agents/git-workflow.md +0 -171
  28. package/templates/ko/fullstack/agents/issue-template.md +0 -114
  29. package/templates/ko/single/agents/constitution.md +0 -80
  30. package/templates/ko/single/agents/pr-template.md +0 -110
  31. /package/templates/en/{single → common}/agents/constitution.md +0 -0
  32. /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 path3 from 'path';
6
- import fs3 from 'fs-extra';
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 fs3.copy(src, dest, {
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 fs3.readFile(file, "utf-8");
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 fs3.writeFile(file, content, "utf-8");
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 fs3.readFile(file, "utf-8");
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 fs3.writeFile(file, content, "utf-8");
31
+ await fs6.writeFile(file, content, "utf-8");
32
32
  }
33
33
  }
34
34
  var __filename2 = fileURLToPath(import.meta.url);
35
- var __dirname2 = path3.dirname(__filename2);
35
+ var __dirname2 = path6.dirname(__filename2);
36
36
  function getTemplatesDir() {
37
- const rootDir = path3.resolve(__dirname2, "..");
38
- return path3.join(rootDir, "templates");
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 = path3.basename(cwd);
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 = path3.resolve(cwd, options.dir || "./docs");
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 fs3.pathExists(targetDir)) {
213
- const files = await fs3.readdir(targetDir);
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 templatePath = path3.join(templatesDir, lang, projectType);
236
- if (!await fs3.pathExists(templatePath)) {
237
- throw new Error(`\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${templatePath}`);
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 copyTemplates(templatePath, targetDir);
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 = path3.join(targetDir, ".lee-spec-kit.json");
252
- await fs3.writeJson(configPath, config, { spaces: 2 });
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 = path3.relative(cwd, targetDir);
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
- path3.join(cwd, "docs"),
299
+ path6.join(cwd, "docs"),
294
300
  cwd
295
301
  // 이미 docs 폴더 안에 있을 수 있음
296
302
  ];
297
303
  for (const docsDir of possibleDirs) {
298
- const configPath = path3.join(docsDir, ".lee-spec-kit.json");
299
- if (await fs3.pathExists(configPath)) {
304
+ const configPath = path6.join(docsDir, ".lee-spec-kit.json");
305
+ if (await fs6.pathExists(configPath)) {
300
306
  try {
301
- const configFile = await fs3.readJson(configPath);
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 = 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");
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 fs3.pathExists(agentsMdPath)) {
320
- const content = await fs3.readFile(agentsMdPath, "utf-8");
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 = path3.join(docsDir, "features", repo);
395
+ featuresDir = path6.join(docsDir, "features", repo);
390
396
  } else {
391
- featuresDir = path3.join(docsDir, "features");
397
+ featuresDir = path6.join(docsDir, "features");
392
398
  }
393
399
  const featureFolderName = `${featureId}-${name}`;
394
- const featureDir = path3.join(featuresDir, featureFolderName);
395
- if (await fs3.pathExists(featureDir)) {
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 = path3.join(docsDir, "features", "feature-base");
400
- if (!await fs3.pathExists(featureBasePath)) {
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 fs3.copy(featureBasePath, featureDir);
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 = path3.join(docsDir, "features");
441
+ const featuresDir = path6.join(docsDir, "features");
436
442
  let max = 0;
437
443
  const scanDirs = [];
438
444
  if (projectType === "fullstack") {
439
- scanDirs.push(path3.join(featuresDir, "be"));
440
- scanDirs.push(path3.join(featuresDir, "fe"));
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 fs3.pathExists(dir)) continue;
446
- const entries = await fs3.readdir(dir, { withFileTypes: true });
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 = path3.join(docsDir, "features");
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 ? path3.join(featuresDir, scope) : featuresDir;
486
- if (!await fs3.pathExists(scanDir)) continue;
487
- const entries = await fs3.readdir(scanDir, { withFileTypes: true });
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 = 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");
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 = path3.relative(docsDir, featureDir);
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 = path3.join(featuresDir, "status.md");
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 fs3.writeFile(outputPath, content, "utf-8");
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.1.7",
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
- "scripts": {
53
- "build": "tsup",
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
- ### 1. Feature Start
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
- | 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) |
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
- > 📌 **Do not commit when creating Feature folder.**
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 add .
92
- git commit -m "{type}(#{issue}): {task-description}"
85
+ git checkout -b feat/{issue-number}-{feature-name}
93
86
  ```
94
87
 
95
- ### 4. Create PR on Feature Completion
88
+ ### Document Commit Timing (docs repo)
96
89
 
97
- ```bash
98
- git push origin feat/{issue-number}-{feature-name}
99
- gh pr create --title "feat(#{issue}): {feature-title}" \
100
- --body "Closes #{issue}" \
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
- ### 5. Merge
95
+ > ⚠️ Do not commit when creating Feature folder.
105
96
 
106
- ```bash
107
- git checkout main
108
- git pull
109
- gh pr merge --squash --delete-branch
110
- git pull
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. **External/local documents** (no URL available): Use **relative path as text only**
40
- ```text
41
- ../docs/features/F001-feature-name/spec.md
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 provide path text only.
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: `docs/features/F{number}-{feature-name}/spec.md`
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: `docs/features/F{number}-{feature-name}/spec.md`
55
- - Tasks: `docs/features/F{number}-{feature-name}/tasks.md`
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`