lee-spec-kit 0.1.8 → 0.2.1

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 (34) hide show
  1. package/dist/index.js +285 -57
  2. package/package.json +1 -1
  3. package/templates/en/{fullstack → common}/agents/git-workflow.md +39 -29
  4. package/templates/en/{single → common}/agents/issue-template.md +10 -5
  5. package/templates/en/{single → common}/agents/pr-template.md +9 -2
  6. package/templates/en/common/agents/skills/create-feature.md +53 -0
  7. package/templates/en/common/agents/skills/create-issue.md +52 -0
  8. package/templates/en/common/agents/skills/create-pr.md +97 -0
  9. package/templates/en/common/agents/skills/execute-task.md +86 -0
  10. package/templates/en/fullstack/agents/agents.md +11 -26
  11. package/templates/en/single/agents/agents.md +8 -21
  12. package/templates/ko/{fullstack → common}/agents/git-workflow.md +31 -66
  13. package/templates/ko/{fullstack → common}/agents/issue-template.md +10 -5
  14. package/templates/ko/{fullstack → common}/agents/pr-template.md +9 -2
  15. package/templates/ko/common/agents/skills/create-feature.md +53 -0
  16. package/templates/ko/common/agents/skills/create-issue.md +52 -0
  17. package/templates/ko/common/agents/skills/create-pr.md +97 -0
  18. package/templates/ko/common/agents/skills/execute-task.md +86 -0
  19. package/templates/ko/fullstack/agents/agents.md +11 -33
  20. package/templates/ko/single/agents/agents.md +13 -21
  21. package/templates/en/fullstack/agents/constitution.md +0 -80
  22. package/templates/en/fullstack/agents/issue-template.md +0 -110
  23. package/templates/en/fullstack/agents/pr-template.md +0 -96
  24. package/templates/en/single/agents/custom.md +0 -29
  25. package/templates/en/single/agents/git-workflow.md +0 -170
  26. package/templates/ko/single/agents/constitution.md +0 -80
  27. package/templates/ko/single/agents/custom.md +0 -29
  28. package/templates/ko/single/agents/git-workflow.md +0 -170
  29. package/templates/ko/single/agents/issue-template.md +0 -114
  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/en/{fullstack → common}/agents/custom.md +0 -0
  33. /package/templates/ko/{fullstack → common}/agents/constitution.md +0 -0
  34. /package/templates/ko/{fullstack → common}/agents/custom.md +0 -0
package/dist/index.js CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import path7 from 'path';
3
+ import { fileURLToPath } from 'url';
2
4
  import { program } from 'commander';
3
5
  import prompts from 'prompts';
4
6
  import chalk from 'chalk';
5
- import path3 from 'path';
6
7
  import fs6 from 'fs-extra';
7
8
  import { glob } from 'glob';
8
- import { fileURLToPath } from 'url';
9
+ import { spawn, execSync } from 'child_process';
10
+ import os from 'os';
9
11
 
12
+ var getFilename = () => fileURLToPath(import.meta.url);
13
+ var getDirname = () => path7.dirname(getFilename());
14
+ var __dirname$1 = /* @__PURE__ */ getDirname();
10
15
  async function copyTemplates(src, dest) {
11
16
  await fs6.copy(src, dest, {
12
17
  overwrite: true,
@@ -32,10 +37,10 @@ async function replaceInFiles(dir, replacements) {
32
37
  }
33
38
  }
34
39
  var __filename2 = fileURLToPath(import.meta.url);
35
- var __dirname2 = path3.dirname(__filename2);
40
+ var __dirname2 = path7.dirname(__filename2);
36
41
  function getTemplatesDir() {
37
- const rootDir = path3.resolve(__dirname2, "..");
38
- return path3.join(rootDir, "templates");
42
+ const rootDir = path7.resolve(__dirname2, "..");
43
+ return path7.join(rootDir, "templates");
39
44
  }
40
45
 
41
46
  // src/utils/validation.ts
@@ -132,8 +137,17 @@ function assertValid(result, context) {
132
137
  throw new Error(message);
133
138
  }
134
139
  }
135
-
136
- // src/commands/init.ts
140
+ function checkGitRepo(cwd) {
141
+ try {
142
+ execSync("git rev-parse --is-inside-work-tree", {
143
+ cwd,
144
+ stdio: "ignore"
145
+ });
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
137
151
  function initCommand(program2) {
138
152
  program2.command("init").description("Initialize project documentation structure").option("-n, --name <name>", "Project name (default: current folder name)").option("-t, --type <type>", "Project type: single | fullstack").option("-l, --lang <lang>", "Language: ko | en (default: ko)").option("-d, --dir <dir>", "Target directory (default: ./docs)", "./docs").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
139
153
  try {
@@ -150,12 +164,36 @@ function initCommand(program2) {
150
164
  }
151
165
  async function runInit(options) {
152
166
  const cwd = process.cwd();
153
- const defaultName = path3.basename(cwd);
167
+ const defaultName = path7.basename(cwd);
154
168
  let projectName = options.name || defaultName;
155
169
  let projectType = options.type;
156
170
  let lang = options.lang || "ko";
157
- const targetDir = path3.resolve(cwd, options.dir || "./docs");
171
+ let docsRepo = "embedded";
172
+ let pushDocs;
173
+ let docsRemote;
174
+ const targetDir = path7.resolve(cwd, options.dir || "./docs");
175
+ const isInsideGitRepo = checkGitRepo(cwd);
158
176
  if (!options.yes) {
177
+ console.log();
178
+ console.log(chalk.blue(`\u{1F4CD} \uD604\uC7AC \uC704\uCE58: ${cwd}`));
179
+ if (isInsideGitRepo) {
180
+ console.log(chalk.green("\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428"));
181
+ console.log();
182
+ console.log(chalk.gray("\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD558\uACE0 \uACC4\uC2ED\uB2C8\uB2E4."));
183
+ console.log(
184
+ chalk.gray(
185
+ "\u2022 embedded: \uC5EC\uAE30\uC5D0 ./docs \uD3F4\uB354\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 \uAD00\uB9AC\uB429\uB2C8\uB2E4."
186
+ )
187
+ );
188
+ console.log(
189
+ chalk.gray("\u2022 standalone: \uBCC4\uB3C4 \uD3F4\uB354\uC5D0\uC11C \uB3C5\uB9BD docs \uB808\uD3EC\uB85C \uAD00\uB9AC\uD558\uB824\uBA74,")
190
+ );
191
+ console.log(chalk.gray(" \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694."));
192
+ } else {
193
+ console.log(chalk.yellow("\u26A0\uFE0F Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."));
194
+ console.log(chalk.gray("\uC0C8\uB85C\uC6B4 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4."));
195
+ }
196
+ console.log();
159
197
  const response = await prompts(
160
198
  [
161
199
  {
@@ -191,6 +229,24 @@ async function runInit(options) {
191
229
  { title: "English (en)", value: "en" }
192
230
  ],
193
231
  initial: 0
232
+ },
233
+ {
234
+ type: "select",
235
+ name: "docsRepo",
236
+ message: "Docs \uAD00\uB9AC \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
237
+ choices: [
238
+ {
239
+ title: "embedded - \uD504\uB85C\uC81D\uD2B8 \uB0B4 \uD3EC\uD568 (./docs)",
240
+ value: "embedded",
241
+ description: "\uD504\uB85C\uC81D\uD2B8\uC640 \uD568\uAED8 push\uB429\uB2C8\uB2E4"
242
+ },
243
+ {
244
+ title: "standalone - \uBCC4\uB3C4 \uB3C5\uB9BD \uB808\uD3EC",
245
+ value: "standalone",
246
+ description: "push \uC5EC\uBD80\uB97C \uBCC4\uB3C4\uB85C \uC124\uC815\uD569\uB2C8\uB2E4"
247
+ }
248
+ ],
249
+ initial: 0
194
250
  }
195
251
  ],
196
252
  {
@@ -202,6 +258,53 @@ async function runInit(options) {
202
258
  projectName = response.projectName || projectName;
203
259
  projectType = response.projectType || projectType;
204
260
  lang = response.lang || lang;
261
+ docsRepo = response.docsRepo || "embedded";
262
+ if (docsRepo === "standalone") {
263
+ const standaloneResponse = await prompts(
264
+ [
265
+ {
266
+ type: "select",
267
+ name: "pushDocs",
268
+ message: "Docs push \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:",
269
+ choices: [
270
+ {
271
+ title: "local - \uB85C\uCEEC\uC5D0\uC11C\uB9CC \uAD00\uB9AC (push \uC548 \uD568)",
272
+ value: false
273
+ },
274
+ {
275
+ title: "remote - \uC6D0\uACA9\uC5D0\uB3C4 push",
276
+ value: true
277
+ }
278
+ ],
279
+ initial: 0
280
+ }
281
+ ],
282
+ {
283
+ onCancel: () => {
284
+ throw new Error("canceled");
285
+ }
286
+ }
287
+ );
288
+ pushDocs = standaloneResponse.pushDocs;
289
+ if (pushDocs === true) {
290
+ const remoteResponse = await prompts(
291
+ [
292
+ {
293
+ type: "text",
294
+ name: "docsRemote",
295
+ message: "\uC6D0\uACA9 \uB808\uD3EC URL\uC744 \uC785\uB825\uD558\uC138\uC694:",
296
+ validate: (value) => value.trim() ? true : "URL\uC744 \uC785\uB825\uD574\uC8FC\uC138\uC694"
297
+ }
298
+ ],
299
+ {
300
+ onCancel: () => {
301
+ throw new Error("canceled");
302
+ }
303
+ }
304
+ );
305
+ docsRemote = remoteResponse.docsRemote;
306
+ }
307
+ }
205
308
  }
206
309
  if (!projectType) {
207
310
  projectType = "single";
@@ -232,27 +335,40 @@ async function runInit(options) {
232
335
  console.log(chalk.gray(` \uACBD\uB85C: ${targetDir}`));
233
336
  console.log();
234
337
  const templatesDir = getTemplatesDir();
235
- const templatePath = path3.join(templatesDir, lang, projectType);
236
- if (!await fs6.pathExists(templatePath)) {
237
- throw new Error(`\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${templatePath}`);
338
+ const commonPath = path7.join(templatesDir, lang, "common");
339
+ const typePath = path7.join(templatesDir, lang, projectType);
340
+ if (await fs6.pathExists(commonPath)) {
341
+ await copyTemplates(commonPath, targetDir);
342
+ }
343
+ if (!await fs6.pathExists(typePath)) {
344
+ throw new Error(`\uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${typePath}`);
238
345
  }
239
- await copyTemplates(templatePath, targetDir);
346
+ await copyTemplates(typePath, targetDir);
347
+ const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
240
348
  const replacements = {
241
349
  "{{projectName}}": projectName,
242
- "{{date}}": (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
350
+ "{{date}}": (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
351
+ "{{featurePath}}": featurePath
243
352
  };
244
353
  await replaceInFiles(targetDir, replacements);
245
354
  const config = {
246
355
  projectName,
247
356
  projectType,
248
357
  lang,
249
- createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
358
+ createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
359
+ docsRepo
250
360
  };
251
- const configPath = path3.join(targetDir, ".lee-spec-kit.json");
361
+ if (docsRepo === "standalone") {
362
+ config.pushDocs = pushDocs;
363
+ if (pushDocs && docsRemote) {
364
+ config.docsRemote = docsRemote;
365
+ }
366
+ }
367
+ const configPath = path7.join(targetDir, ".lee-spec-kit.json");
252
368
  await fs6.writeJson(configPath, config, { spaces: 2 });
253
369
  console.log(chalk.green("\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!"));
254
370
  console.log();
255
- await initGit(cwd, targetDir);
371
+ await initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote);
256
372
  console.log(chalk.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
257
373
  console.log(chalk.gray(` 1. ${targetDir}/prd/README.md \uC791\uC131`));
258
374
  console.log(
@@ -260,8 +376,7 @@ async function runInit(options) {
260
376
  );
261
377
  console.log();
262
378
  }
263
- async function initGit(cwd, targetDir) {
264
- const { execSync } = await import('child_process');
379
+ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
265
380
  try {
266
381
  try {
267
382
  execSync("git rev-parse --is-inside-work-tree", {
@@ -273,12 +388,23 @@ async function initGit(cwd, targetDir) {
273
388
  console.log(chalk.blue("\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911..."));
274
389
  execSync("git init", { cwd, stdio: "ignore" });
275
390
  }
276
- const relativePath = path3.relative(cwd, targetDir);
391
+ const relativePath = path7.relative(cwd, targetDir);
277
392
  execSync(`git add "${relativePath}"`, { cwd, stdio: "ignore" });
278
393
  execSync('git commit -m "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)"', {
279
394
  cwd,
280
395
  stdio: "ignore"
281
396
  });
397
+ if (docsRepo === "standalone" && pushDocs && docsRemote) {
398
+ try {
399
+ execSync(`git remote add origin "${docsRemote}"`, {
400
+ cwd,
401
+ stdio: "ignore"
402
+ });
403
+ console.log(chalk.green(`\u2705 Git remote \uC124\uC815 \uC644\uB8CC: ${docsRemote}`));
404
+ } catch {
405
+ console.log(chalk.yellow("\u26A0\uFE0F Git remote\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4."));
406
+ }
407
+ }
282
408
  console.log(chalk.green("\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!"));
283
409
  console.log();
284
410
  } catch (error) {
@@ -290,12 +416,12 @@ async function initGit(cwd, targetDir) {
290
416
  }
291
417
  async function getConfig(cwd) {
292
418
  const possibleDirs = [
293
- path3.join(cwd, "docs"),
419
+ path7.join(cwd, "docs"),
294
420
  cwd
295
421
  // 이미 docs 폴더 안에 있을 수 있음
296
422
  ];
297
423
  for (const docsDir of possibleDirs) {
298
- const configPath = path3.join(docsDir, ".lee-spec-kit.json");
424
+ const configPath = path7.join(docsDir, ".lee-spec-kit.json");
299
425
  if (await fs6.pathExists(configPath)) {
300
426
  try {
301
427
  const configFile = await fs6.readJson(configPath);
@@ -303,18 +429,21 @@ async function getConfig(cwd) {
303
429
  docsDir,
304
430
  projectName: configFile.projectName,
305
431
  projectType: configFile.projectType,
306
- lang: configFile.lang
432
+ lang: configFile.lang,
433
+ docsRepo: configFile.docsRepo,
434
+ pushDocs: configFile.pushDocs,
435
+ docsRemote: configFile.docsRemote
307
436
  };
308
437
  } catch {
309
438
  }
310
439
  }
311
- const agentsPath = path3.join(docsDir, "agents");
312
- const featuresPath = path3.join(docsDir, "features");
440
+ const agentsPath = path7.join(docsDir, "agents");
441
+ const featuresPath = path7.join(docsDir, "features");
313
442
  if (await fs6.pathExists(agentsPath) && await fs6.pathExists(featuresPath)) {
314
- const bePath = path3.join(featuresPath, "be");
315
- const fePath = path3.join(featuresPath, "fe");
443
+ const bePath = path7.join(featuresPath, "be");
444
+ const fePath = path7.join(featuresPath, "fe");
316
445
  const projectType = await fs6.pathExists(bePath) || await fs6.pathExists(fePath) ? "fullstack" : "single";
317
- const agentsMdPath = path3.join(agentsPath, "agents.md");
446
+ const agentsMdPath = path7.join(agentsPath, "agents.md");
318
447
  let lang = "ko";
319
448
  if (await fs6.pathExists(agentsMdPath)) {
320
449
  const content = await fs6.readFile(agentsMdPath, "utf-8");
@@ -386,17 +515,17 @@ async function runFeature(name, options) {
386
515
  }
387
516
  let featuresDir;
388
517
  if (projectType === "fullstack" && repo) {
389
- featuresDir = path3.join(docsDir, "features", repo);
518
+ featuresDir = path7.join(docsDir, "features", repo);
390
519
  } else {
391
- featuresDir = path3.join(docsDir, "features");
520
+ featuresDir = path7.join(docsDir, "features");
392
521
  }
393
522
  const featureFolderName = `${featureId}-${name}`;
394
- const featureDir = path3.join(featuresDir, featureFolderName);
523
+ const featureDir = path7.join(featuresDir, featureFolderName);
395
524
  if (await fs6.pathExists(featureDir)) {
396
525
  console.error(chalk.red(`\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: ${featureDir}`));
397
526
  process.exit(1);
398
527
  }
399
- const featureBasePath = path3.join(docsDir, "features", "feature-base");
528
+ const featureBasePath = path7.join(docsDir, "features", "feature-base");
400
529
  if (!await fs6.pathExists(featureBasePath)) {
401
530
  console.error(chalk.red("feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
402
531
  process.exit(1);
@@ -432,12 +561,12 @@ async function runFeature(name, options) {
432
561
  console.log();
433
562
  }
434
563
  async function getNextFeatureId(docsDir, projectType) {
435
- const featuresDir = path3.join(docsDir, "features");
564
+ const featuresDir = path7.join(docsDir, "features");
436
565
  let max = 0;
437
566
  const scanDirs = [];
438
567
  if (projectType === "fullstack") {
439
- scanDirs.push(path3.join(featuresDir, "be"));
440
- scanDirs.push(path3.join(featuresDir, "fe"));
568
+ scanDirs.push(path7.join(featuresDir, "be"));
569
+ scanDirs.push(path7.join(featuresDir, "fe"));
441
570
  } else {
442
571
  scanDirs.push(featuresDir);
443
572
  }
@@ -477,20 +606,20 @@ async function runStatus(options) {
477
606
  process.exit(1);
478
607
  }
479
608
  const { docsDir, projectType } = config;
480
- const featuresDir = path3.join(docsDir, "features");
609
+ const featuresDir = path7.join(docsDir, "features");
481
610
  const features = [];
482
611
  const idMap = /* @__PURE__ */ new Map();
483
612
  const scopes = projectType === "fullstack" ? ["be", "fe"] : [""];
484
613
  for (const scope of scopes) {
485
- const scanDir = scope ? path3.join(featuresDir, scope) : featuresDir;
614
+ const scanDir = scope ? path7.join(featuresDir, scope) : featuresDir;
486
615
  if (!await fs6.pathExists(scanDir)) continue;
487
616
  const entries = await fs6.readdir(scanDir, { withFileTypes: true });
488
617
  for (const entry of entries) {
489
618
  if (!entry.isDirectory()) continue;
490
619
  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");
620
+ const featureDir = path7.join(scanDir, entry.name);
621
+ const specPath = path7.join(featureDir, "spec.md");
622
+ const tasksPath = path7.join(featureDir, "tasks.md");
494
623
  if (!await fs6.pathExists(specPath)) continue;
495
624
  if (!await fs6.pathExists(tasksPath)) continue;
496
625
  const specContent = await fs6.readFile(specPath, "utf-8");
@@ -499,7 +628,7 @@ async function runStatus(options) {
499
628
  const name = extractSpecValue(specContent, "\uAE30\uB2A5\uBA85") || extractSpecValue(specContent, "Feature Name") || entry.name;
500
629
  const repo = extractSpecValue(specContent, "\uB300\uC0C1 \uB808\uD3EC") || extractSpecValue(specContent, "Target Repo") || (scope ? `{{projectName}}-${scope}` : "{{projectName}}");
501
630
  const issue = extractSpecValue(specContent, "\uC774\uC288 \uBC88\uD638") || extractSpecValue(specContent, "Issue Number") || "-";
502
- const relPath = path3.relative(docsDir, featureDir);
631
+ const relPath = path7.relative(docsDir, featureDir);
503
632
  if (!idMap.has(id)) {
504
633
  idMap.set(id, []);
505
634
  }
@@ -569,7 +698,7 @@ async function runStatus(options) {
569
698
  }
570
699
  console.log();
571
700
  if (options.write) {
572
- const outputPath = path3.join(featuresDir, "status.md");
701
+ const outputPath = path7.join(featuresDir, "status.md");
573
702
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
574
703
  const content = [
575
704
  "# Feature Status",
@@ -636,7 +765,7 @@ async function runUpdate(options) {
636
765
  }
637
766
  const { docsDir, projectType, lang } = config;
638
767
  const templatesDir = getTemplatesDir();
639
- const sourceDir = path3.join(templatesDir, lang, projectType);
768
+ const sourceDir = path7.join(templatesDir, lang, projectType);
640
769
  const updateAgents = options.agents || !options.agents && !options.templates;
641
770
  const updateTemplates = options.templates || !options.agents && !options.templates;
642
771
  console.log(chalk.blue("\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4..."));
@@ -646,22 +775,32 @@ async function runUpdate(options) {
646
775
  let updatedCount = 0;
647
776
  if (updateAgents) {
648
777
  console.log(chalk.blue("\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
649
- const sourceAgents = path3.join(sourceDir, "agents");
650
- const targetAgents = path3.join(docsDir, "agents");
651
- if (await fs6.pathExists(sourceAgents)) {
778
+ const commonAgents = path7.join(templatesDir, lang, "common", "agents");
779
+ const typeAgents = path7.join(templatesDir, lang, projectType, "agents");
780
+ const targetAgents = path7.join(docsDir, "agents");
781
+ const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
782
+ const replacements = {
783
+ "{{featurePath}}": featurePath
784
+ };
785
+ if (await fs6.pathExists(commonAgents)) {
652
786
  const count = await updateFolder(
653
- sourceAgents,
787
+ commonAgents,
654
788
  targetAgents,
655
- options.force
789
+ options.force,
790
+ replacements
656
791
  );
657
792
  updatedCount += count;
658
- console.log(chalk.green(` \u2705 ${count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
659
793
  }
794
+ if (await fs6.pathExists(typeAgents)) {
795
+ const count = await updateFolder(typeAgents, targetAgents, options.force);
796
+ updatedCount += count;
797
+ }
798
+ console.log(chalk.green(` \u2705 agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
660
799
  }
661
800
  if (updateTemplates) {
662
801
  console.log(chalk.blue("\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
663
- const sourceFeatureBase = path3.join(sourceDir, "features", "feature-base");
664
- const targetFeatureBase = path3.join(docsDir, "features", "feature-base");
802
+ const sourceFeatureBase = path7.join(sourceDir, "features", "feature-base");
803
+ const targetFeatureBase = path7.join(docsDir, "features", "feature-base");
665
804
  if (await fs6.pathExists(sourceFeatureBase)) {
666
805
  const count = await updateFolder(
667
806
  sourceFeatureBase,
@@ -675,19 +814,24 @@ async function runUpdate(options) {
675
814
  console.log();
676
815
  console.log(chalk.green(`\u2705 \uCD1D ${updatedCount}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
677
816
  }
678
- async function updateFolder(sourceDir, targetDir, force) {
817
+ async function updateFolder(sourceDir, targetDir, force, replacements) {
679
818
  await fs6.ensureDir(targetDir);
680
819
  const files = await fs6.readdir(sourceDir);
681
820
  let updatedCount = 0;
682
821
  for (const file of files) {
683
- const sourcePath = path3.join(sourceDir, file);
684
- const targetPath = path3.join(targetDir, file);
822
+ const sourcePath = path7.join(sourceDir, file);
823
+ const targetPath = path7.join(targetDir, file);
685
824
  const stat = await fs6.stat(sourcePath);
686
825
  if (stat.isFile()) {
687
826
  if (file === "custom.md") {
688
827
  continue;
689
828
  }
690
- const sourceContent = await fs6.readFile(sourcePath, "utf-8");
829
+ let sourceContent = await fs6.readFile(sourcePath, "utf-8");
830
+ if (replacements) {
831
+ for (const [key, value] of Object.entries(replacements)) {
832
+ sourceContent = sourceContent.replaceAll(key, value);
833
+ }
834
+ }
691
835
  let shouldUpdate = true;
692
836
  if (await fs6.pathExists(targetPath)) {
693
837
  const targetContent = await fs6.readFile(targetPath, "utf-8");
@@ -707,17 +851,101 @@ async function updateFolder(sourceDir, targetDir, force) {
707
851
  updatedCount++;
708
852
  }
709
853
  } else if (stat.isDirectory()) {
710
- const subCount = await updateFolder(sourcePath, targetPath, force);
854
+ const subCount = await updateFolder(
855
+ sourcePath,
856
+ targetPath,
857
+ force,
858
+ replacements
859
+ );
711
860
  updatedCount += subCount;
712
861
  }
713
862
  }
714
863
  return updatedCount;
715
864
  }
865
+ var CACHE_FILE = path7.join(os.homedir(), ".lee-spec-kit-version-cache.json");
866
+ var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
867
+ function getCurrentVersion() {
868
+ try {
869
+ const packageJsonPath = path7.join(__dirname$1, "..", "package.json");
870
+ if (fs6.existsSync(packageJsonPath)) {
871
+ const pkg = fs6.readJsonSync(packageJsonPath);
872
+ return pkg.version;
873
+ }
874
+ } catch {
875
+ }
876
+ return "0.0.0";
877
+ }
878
+ function readCache() {
879
+ try {
880
+ if (fs6.existsSync(CACHE_FILE)) {
881
+ return fs6.readJsonSync(CACHE_FILE);
882
+ }
883
+ } catch {
884
+ }
885
+ return null;
886
+ }
887
+ function isNewerVersion(current, latest) {
888
+ const currentParts = current.split(".").map(Number);
889
+ const latestParts = latest.split(".").map(Number);
890
+ for (let i = 0; i < 3; i++) {
891
+ if ((latestParts[i] || 0) > (currentParts[i] || 0)) return true;
892
+ if ((latestParts[i] || 0) < (currentParts[i] || 0)) return false;
893
+ }
894
+ return false;
895
+ }
896
+ function printUpdateNotice(current, latest) {
897
+ console.log();
898
+ console.log(
899
+ chalk.yellow(`\u{1F4E6} lee-spec-kit v${latest} \uC0AC\uC6A9 \uAC00\uB2A5 (\uD604\uC7AC: v${current})`)
900
+ );
901
+ console.log(chalk.gray(" \uC5C5\uB370\uC774\uD2B8: npm update -g lee-spec-kit"));
902
+ console.log();
903
+ }
904
+ function spawnBackgroundVersionCheck() {
905
+ const script = `
906
+ const fs = require('fs');
907
+ const path = require('path');
908
+ const os = require('os');
909
+
910
+ const CACHE_FILE = path.join(os.homedir(), '.lee-spec-kit-version-cache.json');
911
+
912
+ fetch('https://registry.npmjs.org/lee-spec-kit/latest')
913
+ .then(res => res.json())
914
+ .then(data => {
915
+ const cache = { lastCheck: Date.now(), latestVersion: data.version };
916
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(cache));
917
+ })
918
+ .catch(() => {});
919
+ `;
920
+ const child = spawn("node", ["-e", script], {
921
+ detached: true,
922
+ stdio: "ignore"
923
+ });
924
+ child.unref();
925
+ }
926
+ function checkForUpdates() {
927
+ try {
928
+ const cache = readCache();
929
+ const now = Date.now();
930
+ if (cache && now - cache.lastCheck < CHECK_INTERVAL) {
931
+ if (cache.latestVersion) {
932
+ const currentVersion = getCurrentVersion();
933
+ if (isNewerVersion(currentVersion, cache.latestVersion)) {
934
+ printUpdateNotice(currentVersion, cache.latestVersion);
935
+ }
936
+ }
937
+ return;
938
+ }
939
+ spawnBackgroundVersionCheck();
940
+ } catch {
941
+ }
942
+ }
716
943
 
717
944
  // src/index.ts
945
+ checkForUpdates();
718
946
  program.name("lee-spec-kit").description(
719
947
  "Project documentation structure generator for AI-assisted development"
720
- ).version("0.1.0");
948
+ ).version("0.2.0");
721
949
  initCommand(program);
722
950
  featureCommand(program);
723
951
  statusCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lee-spec-kit",
3
- "version": "0.1.8",
3
+ "version": "0.2.1",
4
4
  "description": "Project documentation structure generator for AI-assisted development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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,49 +70,54 @@ main
65
70
 
66
71
  ## Automation Workflow
67
72
 
68
- ### 1. Feature Start
73
+ > 📖 Refer to `skills/` folder for step-by-step guides.
74
+
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` |
81
+
82
+ ### Branch Creation
69
83
 
70
84
  ```bash
71
- # 1. Create GitHub Issue (Feature = Issue)
72
- # 2. Create branch
73
85
  git checkout -b feat/{issue-number}-{feature-name}
74
86
  ```
75
87
 
76
- ### 2. Document Commit (docs repo)
88
+ ### Document Commit Timing (docs repo)
77
89
 
78
- > 📌 The docs folder is managed as a separate git, so a separate commit strategy is used.
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` |
79
94
 
80
- | # | Commit Timing | Included Documents | Commit Message Example |
81
- | --- | -------------------------------------------------------- | ------------------------------- | ------------------------------- |
82
- | 1 | **When planning is complete** (spec+plan+tasks approved) | spec.md, plan.md, tasks.md | `docs(#123): spec, plan, tasks` |
83
- | 2 | **When Feature is complete** (all tasks done) | tasks.md (status), decisions.md | `docs(#123): Feature complete` |
95
+ > ⚠️ Do not commit when creating Feature folder.
84
96
 
85
- > ⚠️ **Do not commit when creating Feature folder.**
97
+ ### Merge Strategy
86
98
 
87
- ### 3. Auto Commit on Task Completion
99
+ | Situation | Merge Method |
100
+ | -------------- | ---------------- |
101
+ | Normal Feature | Squash and Merge |
102
+ | Urgent Hotfix | Squash and Merge |
88
103
 
89
- ```bash
90
- git add .
91
- git commit -m "{type}(#{issue}): {task-description}"
92
- ```
104
+ ---
93
105
 
94
- ### 4. Create PR on Feature Completion
106
+ ## Docs Push Rules
95
107
 
96
- ```bash
97
- git push origin feat/{issue-number}-{feature-name}
98
- gh pr create --title "feat(#{issue}): {feature-title}" \
99
- --body "Closes #{issue}" \
100
- --base main
101
- ```
108
+ > Refer to the `docsRepo` setting in `.lee-spec-kit.json`.
102
109
 
103
- ### 5. Merge
110
+ | Setting | Behavior |
111
+ | -------------------------------------------- | ------------------------------- |
112
+ | `docsRepo: "embedded"` | docs included with project push |
113
+ | `docsRepo: "standalone"` + `pushDocs: false` | docs commit only, no push |
114
+ | `docsRepo: "standalone"` + `pushDocs: true` | push docs changes separately |
104
115
 
105
- ```bash
106
- git checkout main
107
- git pull
108
- gh pr merge --squash --delete-branch
109
- git pull
110
- ```
116
+ ### Standalone Mode Notes
117
+
118
+ - If `pushDocs: false`, docs changes are **committed locally only**
119
+ - If `pushDocs: true`, **push separately** after docs changes
120
+ - Project repo and docs repo are separate, **manage each independently**
111
121
 
112
122
  ---
113
123