lee-spec-kit 0.2.0 → 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.
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 path6 from 'path';
|
|
6
7
|
import fs6 from 'fs-extra';
|
|
7
8
|
import { glob } from 'glob';
|
|
8
|
-
import {
|
|
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 =
|
|
40
|
+
var __dirname2 = path7.dirname(__filename2);
|
|
36
41
|
function getTemplatesDir() {
|
|
37
|
-
const rootDir =
|
|
38
|
-
return
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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,8 +335,8 @@ async function runInit(options) {
|
|
|
232
335
|
console.log(chalk.gray(` \uACBD\uB85C: ${targetDir}`));
|
|
233
336
|
console.log();
|
|
234
337
|
const templatesDir = getTemplatesDir();
|
|
235
|
-
const commonPath =
|
|
236
|
-
const typePath =
|
|
338
|
+
const commonPath = path7.join(templatesDir, lang, "common");
|
|
339
|
+
const typePath = path7.join(templatesDir, lang, projectType);
|
|
237
340
|
if (await fs6.pathExists(commonPath)) {
|
|
238
341
|
await copyTemplates(commonPath, targetDir);
|
|
239
342
|
}
|
|
@@ -252,13 +355,20 @@ async function runInit(options) {
|
|
|
252
355
|
projectName,
|
|
253
356
|
projectType,
|
|
254
357
|
lang,
|
|
255
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
358
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
359
|
+
docsRepo
|
|
256
360
|
};
|
|
257
|
-
|
|
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");
|
|
258
368
|
await fs6.writeJson(configPath, config, { spaces: 2 });
|
|
259
369
|
console.log(chalk.green("\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!"));
|
|
260
370
|
console.log();
|
|
261
|
-
await initGit(cwd, targetDir);
|
|
371
|
+
await initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote);
|
|
262
372
|
console.log(chalk.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
|
|
263
373
|
console.log(chalk.gray(` 1. ${targetDir}/prd/README.md \uC791\uC131`));
|
|
264
374
|
console.log(
|
|
@@ -266,8 +376,7 @@ async function runInit(options) {
|
|
|
266
376
|
);
|
|
267
377
|
console.log();
|
|
268
378
|
}
|
|
269
|
-
async function initGit(cwd, targetDir) {
|
|
270
|
-
const { execSync } = await import('child_process');
|
|
379
|
+
async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
|
|
271
380
|
try {
|
|
272
381
|
try {
|
|
273
382
|
execSync("git rev-parse --is-inside-work-tree", {
|
|
@@ -279,12 +388,23 @@ async function initGit(cwd, targetDir) {
|
|
|
279
388
|
console.log(chalk.blue("\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911..."));
|
|
280
389
|
execSync("git init", { cwd, stdio: "ignore" });
|
|
281
390
|
}
|
|
282
|
-
const relativePath =
|
|
391
|
+
const relativePath = path7.relative(cwd, targetDir);
|
|
283
392
|
execSync(`git add "${relativePath}"`, { cwd, stdio: "ignore" });
|
|
284
393
|
execSync('git commit -m "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)"', {
|
|
285
394
|
cwd,
|
|
286
395
|
stdio: "ignore"
|
|
287
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
|
+
}
|
|
288
408
|
console.log(chalk.green("\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!"));
|
|
289
409
|
console.log();
|
|
290
410
|
} catch (error) {
|
|
@@ -296,12 +416,12 @@ async function initGit(cwd, targetDir) {
|
|
|
296
416
|
}
|
|
297
417
|
async function getConfig(cwd) {
|
|
298
418
|
const possibleDirs = [
|
|
299
|
-
|
|
419
|
+
path7.join(cwd, "docs"),
|
|
300
420
|
cwd
|
|
301
421
|
// 이미 docs 폴더 안에 있을 수 있음
|
|
302
422
|
];
|
|
303
423
|
for (const docsDir of possibleDirs) {
|
|
304
|
-
const configPath =
|
|
424
|
+
const configPath = path7.join(docsDir, ".lee-spec-kit.json");
|
|
305
425
|
if (await fs6.pathExists(configPath)) {
|
|
306
426
|
try {
|
|
307
427
|
const configFile = await fs6.readJson(configPath);
|
|
@@ -309,18 +429,21 @@ async function getConfig(cwd) {
|
|
|
309
429
|
docsDir,
|
|
310
430
|
projectName: configFile.projectName,
|
|
311
431
|
projectType: configFile.projectType,
|
|
312
|
-
lang: configFile.lang
|
|
432
|
+
lang: configFile.lang,
|
|
433
|
+
docsRepo: configFile.docsRepo,
|
|
434
|
+
pushDocs: configFile.pushDocs,
|
|
435
|
+
docsRemote: configFile.docsRemote
|
|
313
436
|
};
|
|
314
437
|
} catch {
|
|
315
438
|
}
|
|
316
439
|
}
|
|
317
|
-
const agentsPath =
|
|
318
|
-
const featuresPath =
|
|
440
|
+
const agentsPath = path7.join(docsDir, "agents");
|
|
441
|
+
const featuresPath = path7.join(docsDir, "features");
|
|
319
442
|
if (await fs6.pathExists(agentsPath) && await fs6.pathExists(featuresPath)) {
|
|
320
|
-
const bePath =
|
|
321
|
-
const fePath =
|
|
443
|
+
const bePath = path7.join(featuresPath, "be");
|
|
444
|
+
const fePath = path7.join(featuresPath, "fe");
|
|
322
445
|
const projectType = await fs6.pathExists(bePath) || await fs6.pathExists(fePath) ? "fullstack" : "single";
|
|
323
|
-
const agentsMdPath =
|
|
446
|
+
const agentsMdPath = path7.join(agentsPath, "agents.md");
|
|
324
447
|
let lang = "ko";
|
|
325
448
|
if (await fs6.pathExists(agentsMdPath)) {
|
|
326
449
|
const content = await fs6.readFile(agentsMdPath, "utf-8");
|
|
@@ -392,17 +515,17 @@ async function runFeature(name, options) {
|
|
|
392
515
|
}
|
|
393
516
|
let featuresDir;
|
|
394
517
|
if (projectType === "fullstack" && repo) {
|
|
395
|
-
featuresDir =
|
|
518
|
+
featuresDir = path7.join(docsDir, "features", repo);
|
|
396
519
|
} else {
|
|
397
|
-
featuresDir =
|
|
520
|
+
featuresDir = path7.join(docsDir, "features");
|
|
398
521
|
}
|
|
399
522
|
const featureFolderName = `${featureId}-${name}`;
|
|
400
|
-
const featureDir =
|
|
523
|
+
const featureDir = path7.join(featuresDir, featureFolderName);
|
|
401
524
|
if (await fs6.pathExists(featureDir)) {
|
|
402
525
|
console.error(chalk.red(`\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: ${featureDir}`));
|
|
403
526
|
process.exit(1);
|
|
404
527
|
}
|
|
405
|
-
const featureBasePath =
|
|
528
|
+
const featureBasePath = path7.join(docsDir, "features", "feature-base");
|
|
406
529
|
if (!await fs6.pathExists(featureBasePath)) {
|
|
407
530
|
console.error(chalk.red("feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
408
531
|
process.exit(1);
|
|
@@ -438,12 +561,12 @@ async function runFeature(name, options) {
|
|
|
438
561
|
console.log();
|
|
439
562
|
}
|
|
440
563
|
async function getNextFeatureId(docsDir, projectType) {
|
|
441
|
-
const featuresDir =
|
|
564
|
+
const featuresDir = path7.join(docsDir, "features");
|
|
442
565
|
let max = 0;
|
|
443
566
|
const scanDirs = [];
|
|
444
567
|
if (projectType === "fullstack") {
|
|
445
|
-
scanDirs.push(
|
|
446
|
-
scanDirs.push(
|
|
568
|
+
scanDirs.push(path7.join(featuresDir, "be"));
|
|
569
|
+
scanDirs.push(path7.join(featuresDir, "fe"));
|
|
447
570
|
} else {
|
|
448
571
|
scanDirs.push(featuresDir);
|
|
449
572
|
}
|
|
@@ -483,20 +606,20 @@ async function runStatus(options) {
|
|
|
483
606
|
process.exit(1);
|
|
484
607
|
}
|
|
485
608
|
const { docsDir, projectType } = config;
|
|
486
|
-
const featuresDir =
|
|
609
|
+
const featuresDir = path7.join(docsDir, "features");
|
|
487
610
|
const features = [];
|
|
488
611
|
const idMap = /* @__PURE__ */ new Map();
|
|
489
612
|
const scopes = projectType === "fullstack" ? ["be", "fe"] : [""];
|
|
490
613
|
for (const scope of scopes) {
|
|
491
|
-
const scanDir = scope ?
|
|
614
|
+
const scanDir = scope ? path7.join(featuresDir, scope) : featuresDir;
|
|
492
615
|
if (!await fs6.pathExists(scanDir)) continue;
|
|
493
616
|
const entries = await fs6.readdir(scanDir, { withFileTypes: true });
|
|
494
617
|
for (const entry of entries) {
|
|
495
618
|
if (!entry.isDirectory()) continue;
|
|
496
619
|
if (entry.name === "feature-base") continue;
|
|
497
|
-
const featureDir =
|
|
498
|
-
const specPath =
|
|
499
|
-
const tasksPath =
|
|
620
|
+
const featureDir = path7.join(scanDir, entry.name);
|
|
621
|
+
const specPath = path7.join(featureDir, "spec.md");
|
|
622
|
+
const tasksPath = path7.join(featureDir, "tasks.md");
|
|
500
623
|
if (!await fs6.pathExists(specPath)) continue;
|
|
501
624
|
if (!await fs6.pathExists(tasksPath)) continue;
|
|
502
625
|
const specContent = await fs6.readFile(specPath, "utf-8");
|
|
@@ -505,7 +628,7 @@ async function runStatus(options) {
|
|
|
505
628
|
const name = extractSpecValue(specContent, "\uAE30\uB2A5\uBA85") || extractSpecValue(specContent, "Feature Name") || entry.name;
|
|
506
629
|
const repo = extractSpecValue(specContent, "\uB300\uC0C1 \uB808\uD3EC") || extractSpecValue(specContent, "Target Repo") || (scope ? `{{projectName}}-${scope}` : "{{projectName}}");
|
|
507
630
|
const issue = extractSpecValue(specContent, "\uC774\uC288 \uBC88\uD638") || extractSpecValue(specContent, "Issue Number") || "-";
|
|
508
|
-
const relPath =
|
|
631
|
+
const relPath = path7.relative(docsDir, featureDir);
|
|
509
632
|
if (!idMap.has(id)) {
|
|
510
633
|
idMap.set(id, []);
|
|
511
634
|
}
|
|
@@ -575,7 +698,7 @@ async function runStatus(options) {
|
|
|
575
698
|
}
|
|
576
699
|
console.log();
|
|
577
700
|
if (options.write) {
|
|
578
|
-
const outputPath =
|
|
701
|
+
const outputPath = path7.join(featuresDir, "status.md");
|
|
579
702
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
580
703
|
const content = [
|
|
581
704
|
"# Feature Status",
|
|
@@ -642,7 +765,7 @@ async function runUpdate(options) {
|
|
|
642
765
|
}
|
|
643
766
|
const { docsDir, projectType, lang } = config;
|
|
644
767
|
const templatesDir = getTemplatesDir();
|
|
645
|
-
const sourceDir =
|
|
768
|
+
const sourceDir = path7.join(templatesDir, lang, projectType);
|
|
646
769
|
const updateAgents = options.agents || !options.agents && !options.templates;
|
|
647
770
|
const updateTemplates = options.templates || !options.agents && !options.templates;
|
|
648
771
|
console.log(chalk.blue("\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4..."));
|
|
@@ -652,9 +775,9 @@ async function runUpdate(options) {
|
|
|
652
775
|
let updatedCount = 0;
|
|
653
776
|
if (updateAgents) {
|
|
654
777
|
console.log(chalk.blue("\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
655
|
-
const commonAgents =
|
|
656
|
-
const typeAgents =
|
|
657
|
-
const targetAgents =
|
|
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");
|
|
658
781
|
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
659
782
|
const replacements = {
|
|
660
783
|
"{{featurePath}}": featurePath
|
|
@@ -676,8 +799,8 @@ async function runUpdate(options) {
|
|
|
676
799
|
}
|
|
677
800
|
if (updateTemplates) {
|
|
678
801
|
console.log(chalk.blue("\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
679
|
-
const sourceFeatureBase =
|
|
680
|
-
const targetFeatureBase =
|
|
802
|
+
const sourceFeatureBase = path7.join(sourceDir, "features", "feature-base");
|
|
803
|
+
const targetFeatureBase = path7.join(docsDir, "features", "feature-base");
|
|
681
804
|
if (await fs6.pathExists(sourceFeatureBase)) {
|
|
682
805
|
const count = await updateFolder(
|
|
683
806
|
sourceFeatureBase,
|
|
@@ -696,8 +819,8 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
|
696
819
|
const files = await fs6.readdir(sourceDir);
|
|
697
820
|
let updatedCount = 0;
|
|
698
821
|
for (const file of files) {
|
|
699
|
-
const sourcePath =
|
|
700
|
-
const targetPath =
|
|
822
|
+
const sourcePath = path7.join(sourceDir, file);
|
|
823
|
+
const targetPath = path7.join(targetDir, file);
|
|
701
824
|
const stat = await fs6.stat(sourcePath);
|
|
702
825
|
if (stat.isFile()) {
|
|
703
826
|
if (file === "custom.md") {
|
|
@@ -739,11 +862,90 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
|
739
862
|
}
|
|
740
863
|
return updatedCount;
|
|
741
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
|
+
}
|
|
742
943
|
|
|
743
944
|
// src/index.ts
|
|
945
|
+
checkForUpdates();
|
|
744
946
|
program.name("lee-spec-kit").description(
|
|
745
947
|
"Project documentation structure generator for AI-assisted development"
|
|
746
|
-
).version("0.
|
|
948
|
+
).version("0.2.0");
|
|
747
949
|
initCommand(program);
|
|
748
950
|
featureCommand(program);
|
|
749
951
|
statusCommand(program);
|
package/package.json
CHANGED
|
@@ -103,6 +103,24 @@ git checkout -b feat/{issue-number}-{feature-name}
|
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
|
+
## Docs Push Rules
|
|
107
|
+
|
|
108
|
+
> Refer to the `docsRepo` setting in `.lee-spec-kit.json`.
|
|
109
|
+
|
|
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 |
|
|
115
|
+
|
|
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**
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
106
124
|
## GitHub Setup Requirements
|
|
107
125
|
|
|
108
126
|
### Required
|
|
@@ -103,6 +103,24 @@ git checkout -b feat/{issue-number}-{feature-name}
|
|
|
103
103
|
|
|
104
104
|
---
|
|
105
105
|
|
|
106
|
+
## Docs Push 규칙
|
|
107
|
+
|
|
108
|
+
> `.lee-spec-kit.json`의 `docsRepo` 설정을 참조합니다.
|
|
109
|
+
|
|
110
|
+
| 설정 | 동작 |
|
|
111
|
+
| -------------------------------------------- | --------------------------------- |
|
|
112
|
+
| `docsRepo: "embedded"` | 프로젝트 push 시 docs도 함께 포함 |
|
|
113
|
+
| `docsRepo: "standalone"` + `pushDocs: false` | docs는 커밋만, push 안 함 |
|
|
114
|
+
| `docsRepo: "standalone"` + `pushDocs: true` | docs 변경 시 별도 push 진행 |
|
|
115
|
+
|
|
116
|
+
### Standalone 모드 주의사항
|
|
117
|
+
|
|
118
|
+
- `pushDocs: false`인 경우 docs 변경사항은 **로컬에만 커밋**
|
|
119
|
+
- `pushDocs: true`인 경우 docs 변경 후 **별도로 push** 필요
|
|
120
|
+
- 프로젝트 레포와 docs 레포가 분리되어 있으므로 **각각 관리**
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
106
124
|
## GitHub 설정 요구사항
|
|
107
125
|
|
|
108
126
|
### 필수
|