lee-spec-kit 0.2.4 → 0.4.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/LICENSE +1 -1
- package/README.md +248 -20
- package/dist/index.js +1391 -135
- package/package.json +9 -10
- package/templates/en/common/agents/skills/create-feature.md +41 -47
- package/templates/en/common/agents/skills/create-pr.md +1 -0
- package/templates/en/common/agents/skills/execute-task.md +22 -74
- package/templates/en/common/designs/README.md +29 -0
- package/templates/en/common/ideas/README.md +30 -0
- package/templates/en/fullstack/README.md +45 -0
- package/templates/en/fullstack/agents/agents.md +4 -0
- package/templates/en/fullstack/features/feature-base/tasks.md +11 -2
- package/templates/en/single/README.md +45 -0
- package/templates/en/single/agents/agents.md +4 -0
- package/templates/en/single/features/feature-base/tasks.md +11 -2
- package/templates/ko/common/agents/skills/create-feature.md +42 -47
- package/templates/ko/common/agents/skills/create-pr.md +1 -0
- package/templates/ko/common/agents/skills/execute-task.md +33 -68
- package/templates/ko/common/designs/README.md +29 -0
- package/templates/ko/common/ideas/README.md +30 -0
- package/templates/ko/fullstack/README.md +45 -0
- package/templates/ko/fullstack/agents/agents.md +4 -0
- package/templates/ko/fullstack/features/feature-base/tasks.md +11 -2
- package/templates/ko/single/README.md +45 -0
- package/templates/ko/single/agents/agents.md +4 -0
- package/templates/ko/single/features/feature-base/tasks.md +11 -2
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import path4 from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { program } from 'commander';
|
|
5
|
-
import prompts from 'prompts';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
5
|
import fs6 from 'fs-extra';
|
|
6
|
+
import prompts from 'prompts';
|
|
7
|
+
import chalk6 from 'chalk';
|
|
8
8
|
import { glob } from 'glob';
|
|
9
9
|
import { spawn, execSync } from 'child_process';
|
|
10
10
|
import os from 'os';
|
|
11
11
|
|
|
12
12
|
var getFilename = () => fileURLToPath(import.meta.url);
|
|
13
|
-
var getDirname = () =>
|
|
13
|
+
var getDirname = () => path4.dirname(getFilename());
|
|
14
14
|
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
15
15
|
async function copyTemplates(src, dest) {
|
|
16
16
|
await fs6.copy(src, dest, {
|
|
@@ -37,10 +37,10 @@ async function replaceInFiles(dir, replacements) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
40
|
-
var __dirname2 =
|
|
40
|
+
var __dirname2 = path4.dirname(__filename2);
|
|
41
41
|
function getTemplatesDir() {
|
|
42
|
-
const rootDir =
|
|
43
|
-
return
|
|
42
|
+
const rootDir = path4.resolve(__dirname2, "..");
|
|
43
|
+
return path4.join(rootDir, "templates");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// src/utils/validation.ts
|
|
@@ -63,7 +63,7 @@ function validateSafeName(name) {
|
|
|
63
63
|
if (name.includes("\0")) {
|
|
64
64
|
return { valid: false, error: "\uC774\uB984\uC5D0 null \uBB38\uC790\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." };
|
|
65
65
|
}
|
|
66
|
-
const safePattern = /^[\w
|
|
66
|
+
const safePattern = /^[\w가-힣-]+$/;
|
|
67
67
|
if (!safePattern.test(name)) {
|
|
68
68
|
return {
|
|
69
69
|
valid: false,
|
|
@@ -154,44 +154,45 @@ function initCommand(program2) {
|
|
|
154
154
|
await runInit(options);
|
|
155
155
|
} catch (error) {
|
|
156
156
|
if (error instanceof Error && error.message === "canceled") {
|
|
157
|
-
console.log(
|
|
157
|
+
console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
158
158
|
process.exit(0);
|
|
159
159
|
}
|
|
160
|
-
console.error(
|
|
160
|
+
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
161
161
|
process.exit(1);
|
|
162
162
|
}
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
async function runInit(options) {
|
|
166
166
|
const cwd = process.cwd();
|
|
167
|
-
const defaultName =
|
|
167
|
+
const defaultName = path4.basename(cwd);
|
|
168
168
|
let projectName = options.name || defaultName;
|
|
169
169
|
let projectType = options.type;
|
|
170
170
|
let lang = options.lang || "ko";
|
|
171
171
|
let docsRepo = "embedded";
|
|
172
172
|
let pushDocs;
|
|
173
173
|
let docsRemote;
|
|
174
|
-
|
|
174
|
+
let projectRoot;
|
|
175
|
+
const targetDir = path4.resolve(cwd, options.dir || "./docs");
|
|
175
176
|
const isInsideGitRepo = checkGitRepo(cwd);
|
|
176
177
|
if (!options.yes) {
|
|
177
178
|
console.log();
|
|
178
|
-
console.log(
|
|
179
|
+
console.log(chalk6.blue(`\u{1F4CD} \uD604\uC7AC \uC704\uCE58: ${cwd}`));
|
|
179
180
|
if (isInsideGitRepo) {
|
|
180
|
-
console.log(
|
|
181
|
+
console.log(chalk6.green("\u2705 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0\uB428"));
|
|
181
182
|
console.log();
|
|
182
|
-
console.log(
|
|
183
|
+
console.log(chalk6.gray("\uD604\uC7AC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uB0B4\uC5D0\uC11C \uC2E4\uD589\uD558\uACE0 \uACC4\uC2ED\uB2C8\uB2E4."));
|
|
183
184
|
console.log(
|
|
184
|
-
|
|
185
|
+
chalk6.gray(
|
|
185
186
|
"\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
|
)
|
|
187
188
|
);
|
|
188
189
|
console.log(
|
|
189
|
-
|
|
190
|
+
chalk6.gray("\u2022 standalone: \uBCC4\uB3C4 \uD3F4\uB354\uC5D0\uC11C \uB3C5\uB9BD docs \uB808\uD3EC\uB85C \uAD00\uB9AC\uD558\uB824\uBA74,")
|
|
190
191
|
);
|
|
191
|
-
console.log(
|
|
192
|
+
console.log(chalk6.gray(" \uD574\uB2F9 \uD3F4\uB354\uB85C \uC774\uB3D9 \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD574\uC8FC\uC138\uC694."));
|
|
192
193
|
} else {
|
|
193
|
-
console.log(
|
|
194
|
-
console.log(
|
|
194
|
+
console.log(chalk6.yellow("\u26A0\uFE0F Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."));
|
|
195
|
+
console.log(chalk6.gray("\uC0C8\uB85C\uC6B4 Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uAC00 \uC0DD\uC131\uB429\uB2C8\uB2E4."));
|
|
195
196
|
}
|
|
196
197
|
console.log();
|
|
197
198
|
const response = await prompts(
|
|
@@ -260,6 +261,51 @@ async function runInit(options) {
|
|
|
260
261
|
lang = response.lang || lang;
|
|
261
262
|
docsRepo = response.docsRepo || "embedded";
|
|
262
263
|
if (docsRepo === "standalone") {
|
|
264
|
+
const resolvedType = projectType || response.projectType || "single";
|
|
265
|
+
if (resolvedType === "fullstack") {
|
|
266
|
+
const projectRootResponse = await prompts(
|
|
267
|
+
[
|
|
268
|
+
{
|
|
269
|
+
type: "text",
|
|
270
|
+
name: "feRoot",
|
|
271
|
+
message: "Frontend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
272
|
+
validate: (value) => value.trim() ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
name: "beRoot",
|
|
277
|
+
message: "Backend \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
278
|
+
validate: (value) => value.trim() ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694"
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
{
|
|
282
|
+
onCancel: () => {
|
|
283
|
+
throw new Error("canceled");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
projectRoot = {
|
|
288
|
+
fe: projectRootResponse.feRoot,
|
|
289
|
+
be: projectRootResponse.beRoot
|
|
290
|
+
};
|
|
291
|
+
} else {
|
|
292
|
+
const projectRootResponse = await prompts(
|
|
293
|
+
[
|
|
294
|
+
{
|
|
295
|
+
type: "text",
|
|
296
|
+
name: "projectRoot",
|
|
297
|
+
message: "\uD504\uB85C\uC81D\uD2B8 \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694:",
|
|
298
|
+
validate: (value) => value.trim() ? true : "\uACBD\uB85C\uB97C \uC785\uB825\uD574\uC8FC\uC138\uC694"
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
{
|
|
302
|
+
onCancel: () => {
|
|
303
|
+
throw new Error("canceled");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
projectRoot = projectRootResponse.projectRoot;
|
|
308
|
+
}
|
|
263
309
|
const standaloneResponse = await prompts(
|
|
264
310
|
[
|
|
265
311
|
{
|
|
@@ -322,21 +368,21 @@ async function runInit(options) {
|
|
|
322
368
|
initial: false
|
|
323
369
|
});
|
|
324
370
|
if (!overwrite) {
|
|
325
|
-
console.log(
|
|
371
|
+
console.log(chalk6.yellow("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
326
372
|
return;
|
|
327
373
|
}
|
|
328
374
|
}
|
|
329
375
|
}
|
|
330
376
|
console.log();
|
|
331
|
-
console.log(
|
|
332
|
-
console.log(
|
|
333
|
-
console.log(
|
|
334
|
-
console.log(
|
|
335
|
-
console.log(
|
|
377
|
+
console.log(chalk6.blue("\u{1F4C1} docs \uAD6C\uC870 \uC0DD\uC131 \uC911..."));
|
|
378
|
+
console.log(chalk6.gray(` \uD504\uB85C\uC81D\uD2B8: ${projectName}`));
|
|
379
|
+
console.log(chalk6.gray(` \uD0C0\uC785: ${projectType}`));
|
|
380
|
+
console.log(chalk6.gray(` \uC5B8\uC5B4: ${lang}`));
|
|
381
|
+
console.log(chalk6.gray(` \uACBD\uB85C: ${targetDir}`));
|
|
336
382
|
console.log();
|
|
337
383
|
const templatesDir = getTemplatesDir();
|
|
338
|
-
const commonPath =
|
|
339
|
-
const typePath =
|
|
384
|
+
const commonPath = path4.join(templatesDir, lang, "common");
|
|
385
|
+
const typePath = path4.join(templatesDir, lang, projectType);
|
|
340
386
|
if (await fs6.pathExists(commonPath)) {
|
|
341
387
|
await copyTemplates(commonPath, targetDir);
|
|
342
388
|
}
|
|
@@ -363,16 +409,19 @@ async function runInit(options) {
|
|
|
363
409
|
if (pushDocs && docsRemote) {
|
|
364
410
|
config.docsRemote = docsRemote;
|
|
365
411
|
}
|
|
412
|
+
if (projectRoot) {
|
|
413
|
+
config.projectRoot = projectRoot;
|
|
414
|
+
}
|
|
366
415
|
}
|
|
367
|
-
const configPath =
|
|
416
|
+
const configPath = path4.join(targetDir, ".lee-spec-kit.json");
|
|
368
417
|
await fs6.writeJson(configPath, config, { spaces: 2 });
|
|
369
|
-
console.log(
|
|
418
|
+
console.log(chalk6.green("\u2705 docs \uAD6C\uC870 \uC0DD\uC131 \uC644\uB8CC!"));
|
|
370
419
|
console.log();
|
|
371
420
|
await initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote);
|
|
372
|
-
console.log(
|
|
373
|
-
console.log(
|
|
421
|
+
console.log(chalk6.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
|
|
422
|
+
console.log(chalk6.gray(` 1. ${targetDir}/prd/README.md \uC791\uC131`));
|
|
374
423
|
console.log(
|
|
375
|
-
|
|
424
|
+
chalk6.gray(" 2. npx lee-spec-kit feature <name> \uC73C\uB85C \uAE30\uB2A5 \uCD94\uAC00")
|
|
376
425
|
);
|
|
377
426
|
console.log();
|
|
378
427
|
}
|
|
@@ -383,12 +432,12 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
|
|
|
383
432
|
cwd,
|
|
384
433
|
stdio: "ignore"
|
|
385
434
|
});
|
|
386
|
-
console.log(
|
|
435
|
+
console.log(chalk6.blue("\u{1F4E6} Git \uB808\uD3EC\uC9C0\uD1A0\uB9AC \uAC10\uC9C0, docs \uCEE4\uBC0B \uC911..."));
|
|
387
436
|
} catch {
|
|
388
|
-
console.log(
|
|
437
|
+
console.log(chalk6.blue("\u{1F4E6} Git \uCD08\uAE30\uD654 \uC911..."));
|
|
389
438
|
execSync("git init", { cwd, stdio: "ignore" });
|
|
390
439
|
}
|
|
391
|
-
const relativePath =
|
|
440
|
+
const relativePath = path4.relative(cwd, targetDir);
|
|
392
441
|
execSync(`git add "${relativePath}"`, { cwd, stdio: "ignore" });
|
|
393
442
|
execSync('git commit -m "init: docs \uAD6C\uC870 \uCD08\uAE30\uD654 (lee-spec-kit)"', {
|
|
394
443
|
cwd,
|
|
@@ -400,58 +449,81 @@ async function initGit(cwd, targetDir, docsRepo, pushDocs, docsRemote) {
|
|
|
400
449
|
cwd,
|
|
401
450
|
stdio: "ignore"
|
|
402
451
|
});
|
|
403
|
-
console.log(
|
|
452
|
+
console.log(chalk6.green(`\u2705 Git remote \uC124\uC815 \uC644\uB8CC: ${docsRemote}`));
|
|
404
453
|
} catch {
|
|
405
|
-
console.log(
|
|
454
|
+
console.log(chalk6.yellow("\u26A0\uFE0F Git remote\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4."));
|
|
406
455
|
}
|
|
407
456
|
}
|
|
408
|
-
console.log(
|
|
457
|
+
console.log(chalk6.green("\u2705 Git \uCD08\uAE30 \uCEE4\uBC0B \uC644\uB8CC!"));
|
|
409
458
|
console.log();
|
|
410
|
-
} catch
|
|
459
|
+
} catch {
|
|
411
460
|
console.log(
|
|
412
|
-
|
|
461
|
+
chalk6.yellow("\u26A0\uFE0F Git \uCD08\uAE30\uD654\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4 (\uC218\uB3D9\uC73C\uB85C \uCEE4\uBC0B\uD574\uC8FC\uC138\uC694)")
|
|
413
462
|
);
|
|
414
463
|
console.log();
|
|
415
464
|
}
|
|
416
465
|
}
|
|
466
|
+
function getAncestorDirs(startDir) {
|
|
467
|
+
const dirs = [];
|
|
468
|
+
let current = path4.resolve(startDir);
|
|
469
|
+
while (true) {
|
|
470
|
+
dirs.push(current);
|
|
471
|
+
const parent = path4.dirname(current);
|
|
472
|
+
if (parent === current) break;
|
|
473
|
+
current = parent;
|
|
474
|
+
}
|
|
475
|
+
return dirs;
|
|
476
|
+
}
|
|
417
477
|
async function getConfig(cwd) {
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
478
|
+
const explicitDocsDir = (process.env.LEE_SPEC_KIT_DOCS_DIR || process.env.LSK_DOCS_DIR || "").trim();
|
|
479
|
+
const baseDirs = [
|
|
480
|
+
...explicitDocsDir ? [path4.resolve(explicitDocsDir)] : [],
|
|
481
|
+
...getAncestorDirs(cwd)
|
|
422
482
|
];
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
483
|
+
const visitedBaseDirs = /* @__PURE__ */ new Set();
|
|
484
|
+
const visitedDocsDirs = /* @__PURE__ */ new Set();
|
|
485
|
+
for (const baseDir of baseDirs) {
|
|
486
|
+
const resolvedBaseDir = path4.resolve(baseDir);
|
|
487
|
+
if (visitedBaseDirs.has(resolvedBaseDir)) continue;
|
|
488
|
+
visitedBaseDirs.add(resolvedBaseDir);
|
|
489
|
+
const possibleDocsDirs = [path4.join(resolvedBaseDir, "docs"), resolvedBaseDir];
|
|
490
|
+
for (const docsDir of possibleDocsDirs) {
|
|
491
|
+
const resolvedDocsDir = path4.resolve(docsDir);
|
|
492
|
+
if (visitedDocsDirs.has(resolvedDocsDir)) continue;
|
|
493
|
+
visitedDocsDirs.add(resolvedDocsDir);
|
|
494
|
+
const configPath = path4.join(resolvedDocsDir, ".lee-spec-kit.json");
|
|
495
|
+
if (await fs6.pathExists(configPath)) {
|
|
496
|
+
try {
|
|
497
|
+
const configFile = await fs6.readJson(configPath);
|
|
498
|
+
return {
|
|
499
|
+
docsDir: resolvedDocsDir,
|
|
500
|
+
projectName: configFile.projectName,
|
|
501
|
+
projectType: configFile.projectType,
|
|
502
|
+
lang: configFile.lang,
|
|
503
|
+
docsRepo: configFile.docsRepo,
|
|
504
|
+
pushDocs: configFile.pushDocs,
|
|
505
|
+
docsRemote: configFile.docsRemote,
|
|
506
|
+
projectRoot: configFile.projectRoot
|
|
507
|
+
};
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
438
510
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
511
|
+
const agentsPath = path4.join(resolvedDocsDir, "agents");
|
|
512
|
+
const featuresPath = path4.join(resolvedDocsDir, "features");
|
|
513
|
+
if (await fs6.pathExists(agentsPath) && await fs6.pathExists(featuresPath)) {
|
|
514
|
+
const bePath = path4.join(featuresPath, "be");
|
|
515
|
+
const fePath = path4.join(featuresPath, "fe");
|
|
516
|
+
const projectType = await fs6.pathExists(bePath) || await fs6.pathExists(fePath) ? "fullstack" : "single";
|
|
517
|
+
const agentsMdPath = path4.join(agentsPath, "agents.md");
|
|
518
|
+
let lang = "ko";
|
|
519
|
+
if (await fs6.pathExists(agentsMdPath)) {
|
|
520
|
+
const content = await fs6.readFile(agentsMdPath, "utf-8");
|
|
521
|
+
if (!/[가-힣]/.test(content)) {
|
|
522
|
+
lang = "en";
|
|
523
|
+
}
|
|
452
524
|
}
|
|
525
|
+
return { docsDir: resolvedDocsDir, projectType, lang };
|
|
453
526
|
}
|
|
454
|
-
return { docsDir, projectType, lang };
|
|
455
527
|
}
|
|
456
528
|
}
|
|
457
529
|
return null;
|
|
@@ -464,10 +536,10 @@ function featureCommand(program2) {
|
|
|
464
536
|
await runFeature(name, options);
|
|
465
537
|
} catch (error) {
|
|
466
538
|
if (error instanceof Error && error.message === "canceled") {
|
|
467
|
-
console.log(
|
|
539
|
+
console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
468
540
|
process.exit(0);
|
|
469
541
|
}
|
|
470
|
-
console.error(
|
|
542
|
+
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
471
543
|
process.exit(1);
|
|
472
544
|
}
|
|
473
545
|
});
|
|
@@ -477,7 +549,7 @@ async function runFeature(name, options) {
|
|
|
477
549
|
const config = await getConfig(cwd);
|
|
478
550
|
if (!config) {
|
|
479
551
|
console.error(
|
|
480
|
-
|
|
552
|
+
chalk6.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
|
|
481
553
|
);
|
|
482
554
|
process.exit(1);
|
|
483
555
|
}
|
|
@@ -515,19 +587,19 @@ async function runFeature(name, options) {
|
|
|
515
587
|
}
|
|
516
588
|
let featuresDir;
|
|
517
589
|
if (projectType === "fullstack" && repo) {
|
|
518
|
-
featuresDir =
|
|
590
|
+
featuresDir = path4.join(docsDir, "features", repo);
|
|
519
591
|
} else {
|
|
520
|
-
featuresDir =
|
|
592
|
+
featuresDir = path4.join(docsDir, "features");
|
|
521
593
|
}
|
|
522
594
|
const featureFolderName = `${featureId}-${name}`;
|
|
523
|
-
const featureDir =
|
|
595
|
+
const featureDir = path4.join(featuresDir, featureFolderName);
|
|
524
596
|
if (await fs6.pathExists(featureDir)) {
|
|
525
|
-
console.error(
|
|
597
|
+
console.error(chalk6.red(`\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uD3F4\uB354\uC785\uB2C8\uB2E4: ${featureDir}`));
|
|
526
598
|
process.exit(1);
|
|
527
599
|
}
|
|
528
|
-
const featureBasePath =
|
|
600
|
+
const featureBasePath = path4.join(docsDir, "features", "feature-base");
|
|
529
601
|
if (!await fs6.pathExists(featureBasePath)) {
|
|
530
|
-
console.error(
|
|
602
|
+
console.error(chalk6.red("feature-base \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
531
603
|
process.exit(1);
|
|
532
604
|
}
|
|
533
605
|
await fs6.copy(featureBasePath, featureDir);
|
|
@@ -552,21 +624,21 @@ async function runFeature(name, options) {
|
|
|
552
624
|
}
|
|
553
625
|
await replaceInFiles(featureDir, replacements);
|
|
554
626
|
console.log();
|
|
555
|
-
console.log(
|
|
627
|
+
console.log(chalk6.green(`\u2705 Feature \uD3F4\uB354 \uC0DD\uC131 \uC644\uB8CC: ${featureDir}`));
|
|
556
628
|
console.log();
|
|
557
|
-
console.log(
|
|
558
|
-
console.log(
|
|
559
|
-
console.log(
|
|
560
|
-
console.log(
|
|
629
|
+
console.log(chalk6.blue("\uB2E4\uC74C \uB2E8\uACC4:"));
|
|
630
|
+
console.log(chalk6.gray(` 1. ${featureDir}/spec.md \uC791\uC131`));
|
|
631
|
+
console.log(chalk6.gray(" 2. \uC0AC\uC6A9\uC790 \uB9AC\uBDF0 \uC694\uCCAD"));
|
|
632
|
+
console.log(chalk6.gray(" 3. \uC2B9\uC778 \uD6C4 plan.md \uC791\uC131"));
|
|
561
633
|
console.log();
|
|
562
634
|
}
|
|
563
635
|
async function getNextFeatureId(docsDir, projectType) {
|
|
564
|
-
const featuresDir =
|
|
636
|
+
const featuresDir = path4.join(docsDir, "features");
|
|
565
637
|
let max = 0;
|
|
566
638
|
const scanDirs = [];
|
|
567
639
|
if (projectType === "fullstack") {
|
|
568
|
-
scanDirs.push(
|
|
569
|
-
scanDirs.push(
|
|
640
|
+
scanDirs.push(path4.join(featuresDir, "be"));
|
|
641
|
+
scanDirs.push(path4.join(featuresDir, "fe"));
|
|
570
642
|
} else {
|
|
571
643
|
scanDirs.push(featuresDir);
|
|
572
644
|
}
|
|
@@ -591,7 +663,7 @@ function statusCommand(program2) {
|
|
|
591
663
|
try {
|
|
592
664
|
await runStatus(options);
|
|
593
665
|
} catch (error) {
|
|
594
|
-
console.error(
|
|
666
|
+
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
595
667
|
process.exit(1);
|
|
596
668
|
}
|
|
597
669
|
});
|
|
@@ -601,25 +673,25 @@ async function runStatus(options) {
|
|
|
601
673
|
const config = await getConfig(cwd);
|
|
602
674
|
if (!config) {
|
|
603
675
|
console.error(
|
|
604
|
-
|
|
676
|
+
chalk6.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
|
|
605
677
|
);
|
|
606
678
|
process.exit(1);
|
|
607
679
|
}
|
|
608
680
|
const { docsDir, projectType } = config;
|
|
609
|
-
const featuresDir =
|
|
681
|
+
const featuresDir = path4.join(docsDir, "features");
|
|
610
682
|
const features = [];
|
|
611
683
|
const idMap = /* @__PURE__ */ new Map();
|
|
612
684
|
const scopes = projectType === "fullstack" ? ["be", "fe"] : [""];
|
|
613
685
|
for (const scope of scopes) {
|
|
614
|
-
const scanDir = scope ?
|
|
686
|
+
const scanDir = scope ? path4.join(featuresDir, scope) : featuresDir;
|
|
615
687
|
if (!await fs6.pathExists(scanDir)) continue;
|
|
616
688
|
const entries = await fs6.readdir(scanDir, { withFileTypes: true });
|
|
617
689
|
for (const entry of entries) {
|
|
618
690
|
if (!entry.isDirectory()) continue;
|
|
619
691
|
if (entry.name === "feature-base") continue;
|
|
620
|
-
const featureDir =
|
|
621
|
-
const specPath =
|
|
622
|
-
const tasksPath =
|
|
692
|
+
const featureDir = path4.join(scanDir, entry.name);
|
|
693
|
+
const specPath = path4.join(featureDir, "spec.md");
|
|
694
|
+
const tasksPath = path4.join(featureDir, "tasks.md");
|
|
623
695
|
if (!await fs6.pathExists(specPath)) continue;
|
|
624
696
|
if (!await fs6.pathExists(tasksPath)) continue;
|
|
625
697
|
const specContent = await fs6.readFile(specPath, "utf-8");
|
|
@@ -628,7 +700,7 @@ async function runStatus(options) {
|
|
|
628
700
|
const name = extractSpecValue(specContent, "\uAE30\uB2A5\uBA85") || extractSpecValue(specContent, "Feature Name") || entry.name;
|
|
629
701
|
const repo = extractSpecValue(specContent, "\uB300\uC0C1 \uB808\uD3EC") || extractSpecValue(specContent, "Target Repo") || (scope ? `{{projectName}}-${scope}` : "{{projectName}}");
|
|
630
702
|
const issue = extractSpecValue(specContent, "\uC774\uC288 \uBC88\uD638") || extractSpecValue(specContent, "Issue Number") || "-";
|
|
631
|
-
const relPath =
|
|
703
|
+
const relPath = path4.relative(docsDir, featureDir);
|
|
632
704
|
if (!idMap.has(id)) {
|
|
633
705
|
idMap.set(id, []);
|
|
634
706
|
}
|
|
@@ -656,7 +728,7 @@ async function runStatus(options) {
|
|
|
656
728
|
}
|
|
657
729
|
}
|
|
658
730
|
if (features.length === 0) {
|
|
659
|
-
console.log(
|
|
731
|
+
console.log(chalk6.yellow("Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
660
732
|
return;
|
|
661
733
|
}
|
|
662
734
|
if (options.strict) {
|
|
@@ -664,21 +736,21 @@ async function runStatus(options) {
|
|
|
664
736
|
([, paths]) => paths.length > 1
|
|
665
737
|
);
|
|
666
738
|
if (duplicates.length > 0) {
|
|
667
|
-
console.error(
|
|
739
|
+
console.error(chalk6.red("\uC911\uBCF5 Feature ID \uBC1C\uACAC:"));
|
|
668
740
|
for (const [id, paths] of duplicates) {
|
|
669
|
-
console.error(
|
|
741
|
+
console.error(chalk6.red(` ${id}:`));
|
|
670
742
|
for (const p of paths) {
|
|
671
|
-
console.error(
|
|
743
|
+
console.error(chalk6.red(` - ${p}`));
|
|
672
744
|
}
|
|
673
745
|
}
|
|
674
746
|
process.exit(1);
|
|
675
747
|
}
|
|
676
748
|
const unknowns = [...idMap.entries()].filter(([id]) => id === "UNKNOWN");
|
|
677
749
|
if (unknowns.length > 0) {
|
|
678
|
-
console.error(
|
|
750
|
+
console.error(chalk6.red("Feature ID\uAC00 \uC5C6\uB294 \uD56D\uBAA9:"));
|
|
679
751
|
for (const [, paths] of unknowns) {
|
|
680
752
|
for (const p of paths) {
|
|
681
|
-
console.error(
|
|
753
|
+
console.error(chalk6.red(` - ${p}`));
|
|
682
754
|
}
|
|
683
755
|
}
|
|
684
756
|
process.exit(1);
|
|
@@ -691,14 +763,14 @@ async function runStatus(options) {
|
|
|
691
763
|
console.log(header);
|
|
692
764
|
console.log(separator);
|
|
693
765
|
for (const f of features) {
|
|
694
|
-
const statusColor = f.status === "DONE" ?
|
|
766
|
+
const statusColor = f.status === "DONE" ? chalk6.green : f.status === "DOING" ? chalk6.yellow : chalk6.gray;
|
|
695
767
|
console.log(
|
|
696
768
|
`| ${f.id} | ${f.name} | ${f.repo} | ${f.issue} | ${statusColor(f.status)} | ${f.progress} | ${f.path} |`
|
|
697
769
|
);
|
|
698
770
|
}
|
|
699
771
|
console.log();
|
|
700
772
|
if (options.write) {
|
|
701
|
-
const outputPath =
|
|
773
|
+
const outputPath = path4.join(featuresDir, "status.md");
|
|
702
774
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
703
775
|
const content = [
|
|
704
776
|
"# Feature Status",
|
|
@@ -714,7 +786,7 @@ async function runStatus(options) {
|
|
|
714
786
|
""
|
|
715
787
|
].join("\n");
|
|
716
788
|
await fs6.writeFile(outputPath, content, "utf-8");
|
|
717
|
-
console.log(
|
|
789
|
+
console.log(chalk6.green(`\u2705 ${outputPath} \uC0DD\uC131 \uC644\uB8CC`));
|
|
718
790
|
}
|
|
719
791
|
}
|
|
720
792
|
function extractSpecValue(content, key) {
|
|
@@ -746,10 +818,10 @@ function updateCommand(program2) {
|
|
|
746
818
|
await runUpdate(options);
|
|
747
819
|
} catch (error) {
|
|
748
820
|
if (error instanceof Error && error.message === "canceled") {
|
|
749
|
-
console.log(
|
|
821
|
+
console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
750
822
|
process.exit(0);
|
|
751
823
|
}
|
|
752
|
-
console.error(
|
|
824
|
+
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
753
825
|
process.exit(1);
|
|
754
826
|
}
|
|
755
827
|
});
|
|
@@ -759,25 +831,25 @@ async function runUpdate(options) {
|
|
|
759
831
|
const config = await getConfig(cwd);
|
|
760
832
|
if (!config) {
|
|
761
833
|
console.error(
|
|
762
|
-
|
|
834
|
+
chalk6.red("docs \uD3F4\uB354\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD558\uC138\uC694.")
|
|
763
835
|
);
|
|
764
836
|
process.exit(1);
|
|
765
837
|
}
|
|
766
838
|
const { docsDir, projectType, lang } = config;
|
|
767
839
|
const templatesDir = getTemplatesDir();
|
|
768
|
-
const sourceDir =
|
|
840
|
+
const sourceDir = path4.join(templatesDir, lang, projectType);
|
|
769
841
|
const updateAgents = options.agents || !options.agents && !options.templates;
|
|
770
842
|
const updateTemplates = options.templates || !options.agents && !options.templates;
|
|
771
|
-
console.log(
|
|
772
|
-
console.log(
|
|
773
|
-
console.log(
|
|
843
|
+
console.log(chalk6.blue("\u{1F4E6} \uD15C\uD50C\uB9BF \uC5C5\uB370\uC774\uD2B8\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4..."));
|
|
844
|
+
console.log(chalk6.gray(` - \uC5B8\uC5B4: ${lang}`));
|
|
845
|
+
console.log(chalk6.gray(` - \uD0C0\uC785: ${projectType}`));
|
|
774
846
|
console.log();
|
|
775
847
|
let updatedCount = 0;
|
|
776
848
|
if (updateAgents) {
|
|
777
|
-
console.log(
|
|
778
|
-
const commonAgents =
|
|
779
|
-
const typeAgents =
|
|
780
|
-
const targetAgents =
|
|
849
|
+
console.log(chalk6.blue("\u{1F4C1} agents/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
850
|
+
const commonAgents = path4.join(templatesDir, lang, "common", "agents");
|
|
851
|
+
const typeAgents = path4.join(templatesDir, lang, projectType, "agents");
|
|
852
|
+
const targetAgents = path4.join(docsDir, "agents");
|
|
781
853
|
const featurePath = projectType === "fullstack" ? "docs/features/{be|fe}" : "docs/features";
|
|
782
854
|
const replacements = {
|
|
783
855
|
"{{featurePath}}": featurePath
|
|
@@ -795,12 +867,12 @@ async function runUpdate(options) {
|
|
|
795
867
|
const count = await updateFolder(typeAgents, targetAgents, options.force);
|
|
796
868
|
updatedCount += count;
|
|
797
869
|
}
|
|
798
|
-
console.log(
|
|
870
|
+
console.log(chalk6.green(` \u2705 agents/ \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
|
|
799
871
|
}
|
|
800
872
|
if (updateTemplates) {
|
|
801
|
-
console.log(
|
|
802
|
-
const sourceFeatureBase =
|
|
803
|
-
const targetFeatureBase =
|
|
873
|
+
console.log(chalk6.blue("\u{1F4C1} features/feature-base/ \uD3F4\uB354 \uC5C5\uB370\uC774\uD2B8 \uC911..."));
|
|
874
|
+
const sourceFeatureBase = path4.join(sourceDir, "features", "feature-base");
|
|
875
|
+
const targetFeatureBase = path4.join(docsDir, "features", "feature-base");
|
|
804
876
|
if (await fs6.pathExists(sourceFeatureBase)) {
|
|
805
877
|
const count = await updateFolder(
|
|
806
878
|
sourceFeatureBase,
|
|
@@ -808,22 +880,23 @@ async function runUpdate(options) {
|
|
|
808
880
|
options.force
|
|
809
881
|
);
|
|
810
882
|
updatedCount += count;
|
|
811
|
-
console.log(
|
|
883
|
+
console.log(chalk6.green(` \u2705 ${count}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC`));
|
|
812
884
|
}
|
|
813
885
|
}
|
|
814
886
|
console.log();
|
|
815
|
-
console.log(
|
|
887
|
+
console.log(chalk6.green(`\u2705 \uCD1D ${updatedCount}\uAC1C \uD30C\uC77C \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!`));
|
|
816
888
|
}
|
|
817
889
|
async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
890
|
+
const protectedFiles = /* @__PURE__ */ new Set(["custom.md", "constitution.md"]);
|
|
818
891
|
await fs6.ensureDir(targetDir);
|
|
819
892
|
const files = await fs6.readdir(sourceDir);
|
|
820
893
|
let updatedCount = 0;
|
|
821
894
|
for (const file of files) {
|
|
822
|
-
const sourcePath =
|
|
823
|
-
const targetPath =
|
|
895
|
+
const sourcePath = path4.join(sourceDir, file);
|
|
896
|
+
const targetPath = path4.join(targetDir, file);
|
|
824
897
|
const stat = await fs6.stat(sourcePath);
|
|
825
898
|
if (stat.isFile()) {
|
|
826
|
-
if (file
|
|
899
|
+
if (protectedFiles.has(file)) {
|
|
827
900
|
continue;
|
|
828
901
|
}
|
|
829
902
|
let sourceContent = await fs6.readFile(sourcePath, "utf-8");
|
|
@@ -840,14 +913,14 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
|
840
913
|
}
|
|
841
914
|
if (!force) {
|
|
842
915
|
console.log(
|
|
843
|
-
|
|
916
|
+
chalk6.yellow(` \u26A0\uFE0F ${file} - \uBCC0\uACBD \uAC10\uC9C0 (--force\uB85C \uB36E\uC5B4\uC4F0\uAE30)`)
|
|
844
917
|
);
|
|
845
918
|
shouldUpdate = false;
|
|
846
919
|
}
|
|
847
920
|
}
|
|
848
921
|
if (shouldUpdate) {
|
|
849
922
|
await fs6.writeFile(targetPath, sourceContent);
|
|
850
|
-
console.log(
|
|
923
|
+
console.log(chalk6.gray(` \u{1F4C4} ${file} \uC5C5\uB370\uC774\uD2B8`));
|
|
851
924
|
updatedCount++;
|
|
852
925
|
}
|
|
853
926
|
} else if (stat.isDirectory()) {
|
|
@@ -862,11 +935,1181 @@ async function updateFolder(sourceDir, targetDir, force, replacements) {
|
|
|
862
935
|
}
|
|
863
936
|
return updatedCount;
|
|
864
937
|
}
|
|
865
|
-
|
|
938
|
+
function configCommand(program2) {
|
|
939
|
+
program2.command("config").description("View or modify project configuration").option("--project-root <path>", "Set project root path").option("--repo <repo>", "Repository type for fullstack: fe | be").action(async (options) => {
|
|
940
|
+
try {
|
|
941
|
+
await runConfig(options);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
if (error instanceof Error && error.message === "canceled") {
|
|
944
|
+
console.log(chalk6.yellow("\n\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
|
|
945
|
+
process.exit(0);
|
|
946
|
+
}
|
|
947
|
+
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
async function runConfig(options) {
|
|
953
|
+
const cwd = process.cwd();
|
|
954
|
+
const config = await getConfig(cwd);
|
|
955
|
+
if (!config) {
|
|
956
|
+
console.log(
|
|
957
|
+
chalk6.red("\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.")
|
|
958
|
+
);
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
const configPath = path4.join(config.docsDir, ".lee-spec-kit.json");
|
|
962
|
+
if (!options.projectRoot) {
|
|
963
|
+
console.log();
|
|
964
|
+
console.log(chalk6.blue("\u{1F4CB} \uD604\uC7AC \uC124\uC815:"));
|
|
965
|
+
console.log();
|
|
966
|
+
console.log(chalk6.gray(` \uACBD\uB85C: ${configPath}`));
|
|
967
|
+
console.log();
|
|
968
|
+
const configFile2 = await fs6.readJson(configPath);
|
|
969
|
+
console.log(JSON.stringify(configFile2, null, 2));
|
|
970
|
+
console.log();
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const configFile = await fs6.readJson(configPath);
|
|
974
|
+
if (configFile.docsRepo !== "standalone") {
|
|
975
|
+
console.log(
|
|
976
|
+
chalk6.yellow("\u26A0\uFE0F projectRoot\uB294 standalone \uBAA8\uB4DC\uC5D0\uC11C\uB9CC \uC124\uC815 \uAC00\uB2A5\uD569\uB2C8\uB2E4.")
|
|
977
|
+
);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const projectType = configFile.projectType;
|
|
981
|
+
if (projectType === "fullstack") {
|
|
982
|
+
if (!options.repo) {
|
|
983
|
+
const response = await prompts(
|
|
984
|
+
[
|
|
985
|
+
{
|
|
986
|
+
type: "select",
|
|
987
|
+
name: "repo",
|
|
988
|
+
message: "\uC218\uC815\uD560 \uB808\uD3EC\uC9C0\uD1A0\uB9AC\uB97C \uC120\uD0DD\uD558\uC138\uC694:",
|
|
989
|
+
choices: [
|
|
990
|
+
{ title: "Frontend (fe)", value: "fe" },
|
|
991
|
+
{ title: "Backend (be)", value: "be" }
|
|
992
|
+
]
|
|
993
|
+
}
|
|
994
|
+
],
|
|
995
|
+
{
|
|
996
|
+
onCancel: () => {
|
|
997
|
+
throw new Error("canceled");
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
);
|
|
1001
|
+
options.repo = response.repo;
|
|
1002
|
+
}
|
|
1003
|
+
if (!options.repo || !["fe", "be"].includes(options.repo)) {
|
|
1004
|
+
console.log(
|
|
1005
|
+
chalk6.red(
|
|
1006
|
+
"Fullstack \uD504\uB85C\uC81D\uD2B8\uB294 --repo fe \uB610\uB294 --repo be\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4."
|
|
1007
|
+
)
|
|
1008
|
+
);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const currentRoot = configFile.projectRoot || { fe: "", be: "" };
|
|
1012
|
+
if (typeof currentRoot === "string") {
|
|
1013
|
+
configFile.projectRoot = {
|
|
1014
|
+
fe: options.repo === "fe" ? options.projectRoot : "",
|
|
1015
|
+
be: options.repo === "be" ? options.projectRoot : ""
|
|
1016
|
+
};
|
|
1017
|
+
} else {
|
|
1018
|
+
currentRoot[options.repo] = options.projectRoot;
|
|
1019
|
+
configFile.projectRoot = currentRoot;
|
|
1020
|
+
}
|
|
1021
|
+
console.log(
|
|
1022
|
+
chalk6.green(
|
|
1023
|
+
`\u2705 ${options.repo.toUpperCase()} projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`
|
|
1024
|
+
)
|
|
1025
|
+
);
|
|
1026
|
+
} else {
|
|
1027
|
+
configFile.projectRoot = options.projectRoot;
|
|
1028
|
+
console.log(
|
|
1029
|
+
chalk6.green(`\u2705 projectRoot \uC124\uC815 \uC644\uB8CC: ${options.projectRoot}`)
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
await fs6.writeJson(configPath, configFile, { spaces: 2 });
|
|
1033
|
+
console.log();
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/utils/context/i18n.ts
|
|
1037
|
+
function formatTemplate(template, vars) {
|
|
1038
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
1039
|
+
const value = vars[key];
|
|
1040
|
+
return value === void 0 ? `{${key}}` : String(value);
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
var I18N = {
|
|
1044
|
+
ko: {
|
|
1045
|
+
steps: {
|
|
1046
|
+
featureFolder: "Feature \uD3F4\uB354 \uC0DD\uC131",
|
|
1047
|
+
specWrite: "spec.md \uC791\uC131",
|
|
1048
|
+
specApprove: "spec.md \uC2B9\uC778",
|
|
1049
|
+
planWrite: "plan.md \uC791\uC131",
|
|
1050
|
+
planApprove: "plan.md \uC2B9\uC778",
|
|
1051
|
+
tasksWrite: "tasks.md \uC791\uC131",
|
|
1052
|
+
docsCommitPlanning: "\uBB38\uC11C \uCEE4\uBC0B(\uAE30\uD68D)",
|
|
1053
|
+
issueCreate: "GitHub Issue \uC0DD\uC131",
|
|
1054
|
+
branchCreate: "\uBE0C\uB79C\uCE58 \uC0DD\uC131",
|
|
1055
|
+
tasksExecute: "\uD0DC\uC2A4\uD06C \uC2E4\uD589",
|
|
1056
|
+
prCreate: "PR \uC0DD\uC131",
|
|
1057
|
+
codeReview: "\uCF54\uB4DC \uB9AC\uBDF0",
|
|
1058
|
+
featureDone: "Feature \uC644\uB8CC"
|
|
1059
|
+
},
|
|
1060
|
+
messages: {
|
|
1061
|
+
specCreate: "spec.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/spec.md \uCC38\uACE0)",
|
|
1062
|
+
specImprove: "spec.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
1063
|
+
specApproval: "spec.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
1064
|
+
planCreate: "plan.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uC791\uC131\uD558\uC138\uC694. (features/feature-base/plan.md \uCC38\uACE0)",
|
|
1065
|
+
planImprove: "plan.md\uB97C \uBCF4\uC644\uD558\uACE0 \uC0C1\uD0DC\uB97C Review\uB85C \uBCC0\uACBD\uD558\uC138\uC694.",
|
|
1066
|
+
planApproval: "plan.md \uB0B4\uC6A9\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uACF5\uC720\uD558\uACE0 \uC2B9\uC778(OK)\uC744 \uBC1B\uC73C\uC138\uC694.",
|
|
1067
|
+
tasksCreate: "tasks.md \uD15C\uD50C\uB9BF\uC744 \uBCF5\uC0AC\uD574 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694. (features/feature-base/tasks.md \uCC38\uACE0)",
|
|
1068
|
+
tasksNeedAtLeastOne: "tasks.md\uC5D0 \uCD5C\uC18C 1\uAC1C \uC774\uC0C1\uC758 \uD0DC\uC2A4\uD06C\uB97C \uC791\uC131\uD558\uC138\uC694.",
|
|
1069
|
+
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): \uAE30\uD68D \uBB38\uC11C \uC791\uC131"',
|
|
1070
|
+
issueCreateAndWrite: "GitHub Issue\uB97C \uC0DD\uC131\uD55C \uB4A4, spec.md/tasks.md\uC758 \uC774\uC288 \uBC88\uD638\uB97C \uCC44\uC6B0\uACE0 \uBB38\uC11C \uCEE4\uBC0B\uC744 \uC900\uBE44\uD558\uC138\uC694. (skills/create-issue.md \uCC38\uACE0)",
|
|
1071
|
+
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): \uC774\uC288 #{issueNumber} \uBC18\uC601"',
|
|
1072
|
+
standaloneNeedsProjectRoot: "standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)",
|
|
1073
|
+
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
1074
|
+
tasksAllDoneButNoChecklist: '\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC139\uC158\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. tasks.md\uC758 "\uC644\uB8CC \uC870\uAC74" \uC139\uC158\uC744 \uCD94\uAC00/\uD655\uC778\uD558\uC138\uC694.',
|
|
1075
|
+
tasksAllDoneButChecklist: "\uBAA8\uB4E0 \uD0DC\uC2A4\uD06C\uAC00 DONE\uC774\uC9C0\uB9CC \uC644\uB8CC \uC870\uAC74 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8\uAC00 \uC644\uC804\uD788 \uCCB4\uD06C\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. ({checked}/{total})",
|
|
1076
|
+
finishDoingTask: '\uD604\uC7AC DOING/REVIEW \uC911\uC778 \uD0DC\uC2A4\uD06C\uB97C \uC644\uB8CC\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
1077
|
+
startNextTodoTask: '\uB2E4\uC74C TODO \uD0DC\uC2A4\uD06C\uB97C \uC2DC\uC791\uD558\uC138\uC694: "{title}" ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)',
|
|
1078
|
+
checkTaskStatuses: "\uD0DC\uC2A4\uD06C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694. ({done}/{total}) (skills/execute-task.md \uCC38\uACE0)",
|
|
1079
|
+
prLegacyAsk: "tasks.md\uC5D0 PR/PR \uC0C1\uD0DC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD15C\uD50C\uB9BF\uC744 \uCD5C\uC2E0 \uD3EC\uB9F7\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694? (OK \uD544\uC694)",
|
|
1080
|
+
prCreate: "PR\uC744 \uC0DD\uC131\uD558\uACE0 tasks.md\uC5D0 PR \uB9C1\uD06C\uB97C \uAE30\uB85D\uD558\uC138\uC694. (skills/create-pr.md \uCC38\uACE0)",
|
|
1081
|
+
prResolveReview: "\uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uD574\uACB0\uD558\uACE0 PR \uC0C1\uD0DC\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694. (PR \uC0C1\uD0DC: Review \u2192 Approved)",
|
|
1082
|
+
prRequestReview: "\uB9AC\uBDF0\uC5B4\uC5D0\uAC8C \uB9AC\uBDF0\uB97C \uC694\uCCAD\uD558\uACE0 PR \uC0C1\uD0DC\uB97C Review\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.",
|
|
1083
|
+
featureDone: "PR\uC774 Approved\uC774\uACE0 \uBAA8\uB4E0 \uD0DC\uC2A4\uD06C/\uC644\uB8CC \uC870\uAC74\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC774 Feature\uB294 \uC644\uB8CC \uC0C1\uD0DC\uC785\uB2C8\uB2E4.",
|
|
1084
|
+
fallbackRerunContext: "\uC0C1\uD0DC\uB97C \uD310\uBCC4\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBB38\uC11C\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC context\uB97C \uC2E4\uD589\uD558\uC138\uC694."
|
|
1085
|
+
},
|
|
1086
|
+
warnings: {
|
|
1087
|
+
projectBranchUnavailable: "\uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (standalone \uBAA8\uB4DC\uC5D0\uC11C\uB294 projectRoot\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.)",
|
|
1088
|
+
docsGitUnavailable: "docs \uB808\uD3EC\uC758 git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. (\uB808\uD3EC \uC704\uCE58 / git init \uD655\uC778)",
|
|
1089
|
+
legacyTasksPrFields: "\uAD6C\uBC84\uC804 tasks.md \uD3EC\uB9F7\uC785\uB2C8\uB2E4. PR \uB2E8\uACC4 \uC804\uC5D0 `PR` \uBC0F `PR \uC0C1\uD0DC` \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694."
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
en: {
|
|
1093
|
+
steps: {
|
|
1094
|
+
featureFolder: "Create feature folder",
|
|
1095
|
+
specWrite: "Write spec.md",
|
|
1096
|
+
specApprove: "Approve spec.md",
|
|
1097
|
+
planWrite: "Write plan.md",
|
|
1098
|
+
planApprove: "Approve plan.md",
|
|
1099
|
+
tasksWrite: "Write tasks.md",
|
|
1100
|
+
docsCommitPlanning: "Commit planning docs",
|
|
1101
|
+
issueCreate: "Create GitHub Issue",
|
|
1102
|
+
branchCreate: "Create branch",
|
|
1103
|
+
tasksExecute: "Execute tasks",
|
|
1104
|
+
prCreate: "Create PR",
|
|
1105
|
+
codeReview: "Code review",
|
|
1106
|
+
featureDone: "Feature done"
|
|
1107
|
+
},
|
|
1108
|
+
messages: {
|
|
1109
|
+
specCreate: "Copy the spec.md template and write it. (See features/feature-base/spec.md)",
|
|
1110
|
+
specImprove: "Improve spec.md and set Status to Review.",
|
|
1111
|
+
specApproval: "Share spec.md with the user and get approval (OK).",
|
|
1112
|
+
planCreate: "Copy the plan.md template and write it. (See features/feature-base/plan.md)",
|
|
1113
|
+
planImprove: "Improve plan.md and set Status to Review.",
|
|
1114
|
+
planApproval: "Share plan.md with the user and get approval (OK).",
|
|
1115
|
+
tasksCreate: "Copy the tasks.md template and write tasks. (See features/feature-base/tasks.md)",
|
|
1116
|
+
tasksNeedAtLeastOne: "Add at least one task to tasks.md.",
|
|
1117
|
+
docsCommitPlanning: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): planning docs"',
|
|
1118
|
+
issueCreateAndWrite: "Create a GitHub Issue, then fill in the issue number in spec.md/tasks.md and prepare to commit docs. (See skills/create-issue.md)",
|
|
1119
|
+
docsCommitIssueUpdate: 'cd "{docsGitCwd}" && git add "{featurePath}" && git commit -m "docs({folderName}): issue #{issueNumber}"',
|
|
1120
|
+
standaloneNeedsProjectRoot: "In standalone mode, projectRoot is required. (npx lee-spec-kit config --project-root ...)",
|
|
1121
|
+
createBranch: 'cd "{projectGitCwd}" && git checkout -b feat/{issueNumber}-{slug}',
|
|
1122
|
+
tasksAllDoneButNoChecklist: 'All tasks are DONE but no completion checklist section was found. Add/verify the "Completion Criteria" section in tasks.md.',
|
|
1123
|
+
tasksAllDoneButChecklist: "All tasks are DONE but the completion checklist is not fully checked. ({checked}/{total})",
|
|
1124
|
+
finishDoingTask: 'Finish the active DOING/REVIEW task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
1125
|
+
startNextTodoTask: 'Start the next TODO task: "{title}" ({done}/{total}) (See skills/execute-task.md)',
|
|
1126
|
+
checkTaskStatuses: "Check task statuses. ({done}/{total}) (See skills/execute-task.md)",
|
|
1127
|
+
prLegacyAsk: "Legacy tasks.md format detected (missing PR/PR Status fields). Update to the latest format? (OK required)",
|
|
1128
|
+
prCreate: "Create a PR and record the PR link in tasks.md. (See skills/create-pr.md)",
|
|
1129
|
+
prResolveReview: "Resolve review comments and update PR status. (PR Status: Review \u2192 Approved)",
|
|
1130
|
+
prRequestReview: "Request reviews and update PR status to Review.",
|
|
1131
|
+
featureDone: "PR is Approved and all tasks/completion criteria are satisfied. This feature is done.",
|
|
1132
|
+
fallbackRerunContext: "Unable to determine current state. Verify docs and run context again."
|
|
1133
|
+
},
|
|
1134
|
+
warnings: {
|
|
1135
|
+
projectBranchUnavailable: "Cannot determine project branch. (In standalone mode, projectRoot is required.)",
|
|
1136
|
+
docsGitUnavailable: "Cannot read git status for the docs repo. (Check repo location / git init.)",
|
|
1137
|
+
legacyTasksPrFields: "Legacy tasks.md format detected. Add `PR` and `PR Status` fields before PR steps."
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
function tr(lang, category, key, vars = {}) {
|
|
1142
|
+
const template = I18N[lang][category][key] ?? I18N.ko[category][key] ?? `${category}.${key}`;
|
|
1143
|
+
return formatTemplate(template, vars);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/utils/context/steps.ts
|
|
1147
|
+
function isCompletionChecklistDone(feature) {
|
|
1148
|
+
return !!feature.completionChecklist && feature.completionChecklist.total > 0 && feature.completionChecklist.checked === feature.completionChecklist.total;
|
|
1149
|
+
}
|
|
1150
|
+
function isPrMetadataConfigured(feature) {
|
|
1151
|
+
return feature.docs.prFieldExists && feature.docs.prStatusFieldExists;
|
|
1152
|
+
}
|
|
1153
|
+
function isFeatureDone(feature) {
|
|
1154
|
+
return feature.docs.tasksExists && feature.tasks.total > 0 && feature.tasks.total === feature.tasks.done && isCompletionChecklistDone(feature) && isPrMetadataConfigured(feature) && !!feature.pr.link && feature.pr.status === "Approved";
|
|
1155
|
+
}
|
|
1156
|
+
function getStepDefinitions(lang) {
|
|
1157
|
+
return [
|
|
1158
|
+
{
|
|
1159
|
+
step: 1,
|
|
1160
|
+
name: tr(lang, "steps", "featureFolder"),
|
|
1161
|
+
checklist: { done: () => true }
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
step: 2,
|
|
1165
|
+
name: tr(lang, "steps", "specWrite"),
|
|
1166
|
+
checklist: {
|
|
1167
|
+
done: (f) => f.specStatus === "Review" || f.specStatus === "Approved"
|
|
1168
|
+
},
|
|
1169
|
+
current: {
|
|
1170
|
+
when: (f) => !f.docs.specExists || !f.specStatus || f.specStatus === "Draft",
|
|
1171
|
+
actions: (f) => [
|
|
1172
|
+
{
|
|
1173
|
+
type: "instruction",
|
|
1174
|
+
message: !f.docs.specExists ? tr(lang, "messages", "specCreate") : tr(lang, "messages", "specImprove")
|
|
1175
|
+
}
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
step: 3,
|
|
1181
|
+
name: tr(lang, "steps", "specApprove"),
|
|
1182
|
+
checklist: { done: (f) => f.specStatus === "Approved" },
|
|
1183
|
+
current: {
|
|
1184
|
+
when: (f) => f.specStatus === "Review",
|
|
1185
|
+
actions: () => [
|
|
1186
|
+
{
|
|
1187
|
+
type: "instruction",
|
|
1188
|
+
requiresUserOk: true,
|
|
1189
|
+
message: tr(lang, "messages", "specApproval")
|
|
1190
|
+
}
|
|
1191
|
+
]
|
|
1192
|
+
}
|
|
1193
|
+
},
|
|
1194
|
+
{
|
|
1195
|
+
step: 4,
|
|
1196
|
+
name: tr(lang, "steps", "planWrite"),
|
|
1197
|
+
checklist: {
|
|
1198
|
+
done: (f) => f.planStatus === "Review" || f.planStatus === "Approved"
|
|
1199
|
+
},
|
|
1200
|
+
current: {
|
|
1201
|
+
when: (f) => f.specStatus === "Approved" && (!f.docs.planExists || !f.planStatus || f.planStatus === "Draft"),
|
|
1202
|
+
actions: (f) => [
|
|
1203
|
+
{
|
|
1204
|
+
type: "instruction",
|
|
1205
|
+
message: !f.docs.planExists ? tr(lang, "messages", "planCreate") : tr(lang, "messages", "planImprove")
|
|
1206
|
+
}
|
|
1207
|
+
]
|
|
1208
|
+
}
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
step: 5,
|
|
1212
|
+
name: tr(lang, "steps", "planApprove"),
|
|
1213
|
+
checklist: { done: (f) => f.planStatus === "Approved" },
|
|
1214
|
+
current: {
|
|
1215
|
+
when: (f) => f.planStatus === "Review",
|
|
1216
|
+
actions: () => [
|
|
1217
|
+
{
|
|
1218
|
+
type: "instruction",
|
|
1219
|
+
requiresUserOk: true,
|
|
1220
|
+
message: tr(lang, "messages", "planApproval")
|
|
1221
|
+
}
|
|
1222
|
+
]
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
step: 6,
|
|
1227
|
+
name: tr(lang, "steps", "tasksWrite"),
|
|
1228
|
+
checklist: {
|
|
1229
|
+
done: (f) => f.docs.tasksExists && f.tasks.total > 0,
|
|
1230
|
+
detail: (f) => f.tasks.total > 0 ? `(${f.tasks.total})` : ""
|
|
1231
|
+
},
|
|
1232
|
+
current: {
|
|
1233
|
+
when: (f) => f.planStatus === "Approved" && (!f.docs.tasksExists || f.tasks.total === 0),
|
|
1234
|
+
actions: (f) => {
|
|
1235
|
+
if (!f.docs.tasksExists) {
|
|
1236
|
+
return [
|
|
1237
|
+
{
|
|
1238
|
+
type: "instruction",
|
|
1239
|
+
message: tr(lang, "messages", "tasksCreate")
|
|
1240
|
+
}
|
|
1241
|
+
];
|
|
1242
|
+
}
|
|
1243
|
+
return [
|
|
1244
|
+
{
|
|
1245
|
+
type: "instruction",
|
|
1246
|
+
message: tr(lang, "messages", "tasksNeedAtLeastOne")
|
|
1247
|
+
}
|
|
1248
|
+
];
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
step: 7,
|
|
1254
|
+
name: tr(lang, "steps", "docsCommitPlanning"),
|
|
1255
|
+
checklist: {
|
|
1256
|
+
done: (f) => f.issueNumber ? true : f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.git.docsHasUncommittedChanges
|
|
1257
|
+
},
|
|
1258
|
+
current: {
|
|
1259
|
+
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && !f.issueNumber && f.git.docsHasUncommittedChanges,
|
|
1260
|
+
actions: (f) => [
|
|
1261
|
+
{
|
|
1262
|
+
type: "command",
|
|
1263
|
+
requiresUserOk: true,
|
|
1264
|
+
scope: "docs",
|
|
1265
|
+
cwd: f.git.docsGitCwd,
|
|
1266
|
+
cmd: tr(lang, "messages", "docsCommitPlanning", {
|
|
1267
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1268
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1269
|
+
folderName: f.folderName
|
|
1270
|
+
})
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
step: 8,
|
|
1277
|
+
name: tr(lang, "steps", "issueCreate"),
|
|
1278
|
+
checklist: {
|
|
1279
|
+
done: (f) => !!f.issueNumber && !f.git.docsHasUncommittedChanges
|
|
1280
|
+
},
|
|
1281
|
+
current: {
|
|
1282
|
+
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.specStatus === "Approved" && f.planStatus === "Approved" && (!f.issueNumber || f.git.docsHasUncommittedChanges),
|
|
1283
|
+
actions: (f) => {
|
|
1284
|
+
if (!f.issueNumber) {
|
|
1285
|
+
return [
|
|
1286
|
+
{
|
|
1287
|
+
type: "instruction",
|
|
1288
|
+
requiresUserOk: true,
|
|
1289
|
+
message: tr(lang, "messages", "issueCreateAndWrite")
|
|
1290
|
+
}
|
|
1291
|
+
];
|
|
1292
|
+
}
|
|
1293
|
+
return [
|
|
1294
|
+
{
|
|
1295
|
+
type: "command",
|
|
1296
|
+
requiresUserOk: true,
|
|
1297
|
+
scope: "docs",
|
|
1298
|
+
cwd: f.git.docsGitCwd,
|
|
1299
|
+
cmd: tr(lang, "messages", "docsCommitIssueUpdate", {
|
|
1300
|
+
docsGitCwd: f.git.docsGitCwd,
|
|
1301
|
+
featurePath: f.docs.featurePathFromDocs,
|
|
1302
|
+
issueNumber: f.issueNumber,
|
|
1303
|
+
folderName: f.folderName
|
|
1304
|
+
})
|
|
1305
|
+
}
|
|
1306
|
+
];
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
step: 9,
|
|
1312
|
+
name: tr(lang, "steps", "branchCreate"),
|
|
1313
|
+
checklist: { done: (f) => f.git.onExpectedBranch },
|
|
1314
|
+
current: {
|
|
1315
|
+
when: (f) => !!f.issueNumber && (!f.git.projectBranchAvailable || !f.git.onExpectedBranch),
|
|
1316
|
+
actions: (f) => {
|
|
1317
|
+
if (!f.git.projectBranchAvailable || !f.git.projectGitCwd) {
|
|
1318
|
+
return [
|
|
1319
|
+
{
|
|
1320
|
+
type: "instruction",
|
|
1321
|
+
message: tr(lang, "messages", "standaloneNeedsProjectRoot")
|
|
1322
|
+
}
|
|
1323
|
+
];
|
|
1324
|
+
}
|
|
1325
|
+
return [
|
|
1326
|
+
{
|
|
1327
|
+
type: "command",
|
|
1328
|
+
scope: "project",
|
|
1329
|
+
cwd: f.git.projectGitCwd,
|
|
1330
|
+
cmd: tr(lang, "messages", "createBranch", {
|
|
1331
|
+
projectGitCwd: f.git.projectGitCwd,
|
|
1332
|
+
issueNumber: f.issueNumber,
|
|
1333
|
+
slug: f.slug
|
|
1334
|
+
})
|
|
1335
|
+
}
|
|
1336
|
+
];
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
{
|
|
1341
|
+
step: 10,
|
|
1342
|
+
name: tr(lang, "steps", "tasksExecute"),
|
|
1343
|
+
checklist: {
|
|
1344
|
+
done: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f),
|
|
1345
|
+
detail: (f) => f.tasks.total > 0 ? `(${f.tasks.done}/${f.tasks.total})` : ""
|
|
1346
|
+
},
|
|
1347
|
+
current: {
|
|
1348
|
+
when: (f) => f.git.onExpectedBranch && f.docs.tasksExists && f.tasks.total > 0 && (f.tasks.done < f.tasks.total || !isCompletionChecklistDone(f)),
|
|
1349
|
+
actions: (f) => {
|
|
1350
|
+
if (f.tasks.total === f.tasks.done && !isCompletionChecklistDone(f)) {
|
|
1351
|
+
return [
|
|
1352
|
+
{
|
|
1353
|
+
type: "instruction",
|
|
1354
|
+
requiresUserOk: true,
|
|
1355
|
+
message: !f.completionChecklist ? tr(lang, "messages", "tasksAllDoneButNoChecklist") : tr(lang, "messages", "tasksAllDoneButChecklist", {
|
|
1356
|
+
checked: f.completionChecklist.checked,
|
|
1357
|
+
total: f.completionChecklist.total
|
|
1358
|
+
})
|
|
1359
|
+
}
|
|
1360
|
+
];
|
|
1361
|
+
}
|
|
1362
|
+
if (f.activeTask) {
|
|
1363
|
+
return [
|
|
1364
|
+
{
|
|
1365
|
+
type: "instruction",
|
|
1366
|
+
requiresUserOk: true,
|
|
1367
|
+
message: tr(lang, "messages", "finishDoingTask", {
|
|
1368
|
+
title: f.activeTask.title,
|
|
1369
|
+
done: f.tasks.done,
|
|
1370
|
+
total: f.tasks.total
|
|
1371
|
+
})
|
|
1372
|
+
}
|
|
1373
|
+
];
|
|
1374
|
+
}
|
|
1375
|
+
if (f.nextTodoTask) {
|
|
1376
|
+
return [
|
|
1377
|
+
{
|
|
1378
|
+
type: "instruction",
|
|
1379
|
+
requiresUserOk: true,
|
|
1380
|
+
message: tr(lang, "messages", "startNextTodoTask", {
|
|
1381
|
+
title: f.nextTodoTask.title,
|
|
1382
|
+
done: f.tasks.done,
|
|
1383
|
+
total: f.tasks.total
|
|
1384
|
+
})
|
|
1385
|
+
}
|
|
1386
|
+
];
|
|
1387
|
+
}
|
|
1388
|
+
return [
|
|
1389
|
+
{
|
|
1390
|
+
type: "instruction",
|
|
1391
|
+
requiresUserOk: true,
|
|
1392
|
+
message: tr(lang, "messages", "checkTaskStatuses", {
|
|
1393
|
+
done: f.tasks.done,
|
|
1394
|
+
total: f.tasks.total
|
|
1395
|
+
})
|
|
1396
|
+
}
|
|
1397
|
+
];
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
step: 11,
|
|
1403
|
+
name: tr(lang, "steps", "prCreate"),
|
|
1404
|
+
checklist: { done: (f) => isPrMetadataConfigured(f) && !!f.pr.link },
|
|
1405
|
+
current: {
|
|
1406
|
+
when: (f) => f.docs.tasksExists && f.tasks.total > 0 && f.tasks.total === f.tasks.done && isCompletionChecklistDone(f) && (!isPrMetadataConfigured(f) || !f.pr.link),
|
|
1407
|
+
actions: (f) => {
|
|
1408
|
+
if (!isPrMetadataConfigured(f)) {
|
|
1409
|
+
return [
|
|
1410
|
+
{
|
|
1411
|
+
type: "instruction",
|
|
1412
|
+
requiresUserOk: true,
|
|
1413
|
+
message: tr(lang, "messages", "prLegacyAsk")
|
|
1414
|
+
}
|
|
1415
|
+
];
|
|
1416
|
+
}
|
|
1417
|
+
return [
|
|
1418
|
+
{
|
|
1419
|
+
type: "instruction",
|
|
1420
|
+
requiresUserOk: true,
|
|
1421
|
+
message: tr(lang, "messages", "prCreate")
|
|
1422
|
+
}
|
|
1423
|
+
];
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
step: 12,
|
|
1429
|
+
name: tr(lang, "steps", "codeReview"),
|
|
1430
|
+
checklist: {
|
|
1431
|
+
done: (f) => isPrMetadataConfigured(f) && f.pr.status === "Approved"
|
|
1432
|
+
},
|
|
1433
|
+
current: {
|
|
1434
|
+
when: (f) => isPrMetadataConfigured(f) && !!f.pr.link && f.pr.status !== "Approved",
|
|
1435
|
+
actions: (f) => {
|
|
1436
|
+
if (f.pr.status === "Review") {
|
|
1437
|
+
return [
|
|
1438
|
+
{
|
|
1439
|
+
type: "instruction",
|
|
1440
|
+
message: tr(lang, "messages", "prResolveReview")
|
|
1441
|
+
}
|
|
1442
|
+
];
|
|
1443
|
+
}
|
|
1444
|
+
return [
|
|
1445
|
+
{
|
|
1446
|
+
type: "instruction",
|
|
1447
|
+
message: tr(lang, "messages", "prRequestReview")
|
|
1448
|
+
}
|
|
1449
|
+
];
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
step: 13,
|
|
1455
|
+
name: tr(lang, "steps", "featureDone"),
|
|
1456
|
+
checklist: { done: (f) => isFeatureDone(f) },
|
|
1457
|
+
current: {
|
|
1458
|
+
when: (f) => isFeatureDone(f),
|
|
1459
|
+
actions: () => [
|
|
1460
|
+
{
|
|
1461
|
+
type: "instruction",
|
|
1462
|
+
message: tr(lang, "messages", "featureDone")
|
|
1463
|
+
}
|
|
1464
|
+
]
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
];
|
|
1468
|
+
}
|
|
1469
|
+
function getStepsMap(lang) {
|
|
1470
|
+
return Object.fromEntries(getStepDefinitions(lang).map((d) => [d.step, d.name]));
|
|
1471
|
+
}
|
|
1472
|
+
getStepDefinitions("ko");
|
|
1473
|
+
getStepsMap("ko");
|
|
1474
|
+
|
|
1475
|
+
// src/utils/context/progress.ts
|
|
1476
|
+
function resolveFeatureProgress(feature, stepDefinitions, lang) {
|
|
1477
|
+
const ordered = [...stepDefinitions].sort((a, b) => a.step - b.step);
|
|
1478
|
+
for (const definition of ordered) {
|
|
1479
|
+
if (!definition.current) continue;
|
|
1480
|
+
if (definition.current.when(feature)) {
|
|
1481
|
+
const actions = definition.current.actions(feature);
|
|
1482
|
+
return {
|
|
1483
|
+
currentStep: definition.step,
|
|
1484
|
+
actions,
|
|
1485
|
+
nextAction: actions.map((a) => a.type === "command" ? a.cmd : a.message).join("\n")
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
const lastStep = ordered[ordered.length - 1];
|
|
1490
|
+
return {
|
|
1491
|
+
currentStep: lastStep?.step ?? 10,
|
|
1492
|
+
actions: [
|
|
1493
|
+
{
|
|
1494
|
+
type: "instruction",
|
|
1495
|
+
message: tr(lang, "messages", "fallbackRerunContext")
|
|
1496
|
+
}
|
|
1497
|
+
],
|
|
1498
|
+
nextAction: tr(lang, "messages", "fallbackRerunContext")
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
function getCurrentBranch(cwd) {
|
|
1502
|
+
try {
|
|
1503
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
1504
|
+
cwd,
|
|
1505
|
+
encoding: "utf-8",
|
|
1506
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1507
|
+
}).trim();
|
|
1508
|
+
} catch {
|
|
1509
|
+
return "";
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function getGitStatusPorcelain(cwd, relativePaths) {
|
|
1513
|
+
try {
|
|
1514
|
+
const args = relativePaths.length > 0 ? ` -- ${relativePaths.map((p) => `"${p}"`).join(" ")}` : "";
|
|
1515
|
+
return execSync(`git status --porcelain=v1${args}`, {
|
|
1516
|
+
cwd,
|
|
1517
|
+
encoding: "utf-8",
|
|
1518
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1519
|
+
});
|
|
1520
|
+
} catch {
|
|
1521
|
+
return void 0;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function getGitTopLevel(cwd) {
|
|
1525
|
+
try {
|
|
1526
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
1527
|
+
cwd,
|
|
1528
|
+
encoding: "utf-8",
|
|
1529
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1530
|
+
}).trim();
|
|
1531
|
+
} catch {
|
|
1532
|
+
return null;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function resolveProjectGitCwd(config, repo) {
|
|
1536
|
+
const docsRepo = config.docsRepo;
|
|
1537
|
+
if (docsRepo !== "standalone") {
|
|
1538
|
+
const topLevel = getGitTopLevel(process.cwd());
|
|
1539
|
+
return { cwd: topLevel || process.cwd() };
|
|
1540
|
+
}
|
|
1541
|
+
if (!config.projectRoot) {
|
|
1542
|
+
return {
|
|
1543
|
+
cwd: null,
|
|
1544
|
+
warning: "standalone \uBAA8\uB4DC\uC785\uB2C8\uB2E4. projectRoot\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC544 \uD504\uB85C\uC81D\uD2B8 \uBE0C\uB79C\uCE58 \uD655\uC778\uC774 \uBD88\uAC00\uB2A5\uD569\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ...)"
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
if (config.projectType === "fullstack") {
|
|
1548
|
+
if (typeof config.projectRoot === "string") {
|
|
1549
|
+
return {
|
|
1550
|
+
cwd: null,
|
|
1551
|
+
warning: 'fullstack standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: { "fe": "...", "be": "..." })'
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
const root = config.projectRoot[repo];
|
|
1555
|
+
if (!root) {
|
|
1556
|
+
return {
|
|
1557
|
+
cwd: null,
|
|
1558
|
+
warning: `projectRoot.${repo}\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4. (npx lee-spec-kit config --project-root ... --repo ${repo})`
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
return { cwd: getGitTopLevel(root) || root };
|
|
1562
|
+
}
|
|
1563
|
+
if (typeof config.projectRoot !== "string") {
|
|
1564
|
+
return {
|
|
1565
|
+
cwd: null,
|
|
1566
|
+
warning: 'single standalone \uBAA8\uB4DC\uC778\uB370 projectRoot \uD615\uD0DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. (\uC608: "/path/to/project")'
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
return { cwd: getGitTopLevel(config.projectRoot) || config.projectRoot };
|
|
1570
|
+
}
|
|
1571
|
+
function isExpectedFeatureBranch(branchName, issueNumber, slug, folderName) {
|
|
1572
|
+
if (!branchName || !issueNumber) return false;
|
|
1573
|
+
const match = branchName.match(new RegExp(`^feat\\/${issueNumber}-(.+)$`));
|
|
1574
|
+
if (!match) return false;
|
|
1575
|
+
const rest = match[1];
|
|
1576
|
+
return rest === slug || rest === folderName;
|
|
1577
|
+
}
|
|
1578
|
+
function escapeRegExp(value) {
|
|
1579
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1580
|
+
}
|
|
1581
|
+
function extractSpecValue2(content, key) {
|
|
1582
|
+
const regex = new RegExp(
|
|
1583
|
+
`^\\s*-\\s*\\*\\*${escapeRegExp(key)}\\*\\*\\s*:\\s*(.*)$`,
|
|
1584
|
+
"m"
|
|
1585
|
+
);
|
|
1586
|
+
const match = content.match(regex);
|
|
1587
|
+
return match ? match[1].trim() : void 0;
|
|
1588
|
+
}
|
|
1589
|
+
function extractFirstSpecValue(content, keys) {
|
|
1590
|
+
for (const key of keys) {
|
|
1591
|
+
const value = extractSpecValue2(content, key);
|
|
1592
|
+
if (value) return value;
|
|
1593
|
+
}
|
|
1594
|
+
return void 0;
|
|
1595
|
+
}
|
|
1596
|
+
function parseDocStatus(value) {
|
|
1597
|
+
if (!value) return void 0;
|
|
1598
|
+
const trimmed = value.trim();
|
|
1599
|
+
if (trimmed.includes("|")) return void 0;
|
|
1600
|
+
const match = trimmed.match(/\b(Draft|Review|Approved)\b/i);
|
|
1601
|
+
if (!match) return void 0;
|
|
1602
|
+
const normalized = match[1].toLowerCase();
|
|
1603
|
+
if (normalized === "draft") return "Draft";
|
|
1604
|
+
if (normalized === "review") return "Review";
|
|
1605
|
+
return "Approved";
|
|
1606
|
+
}
|
|
1607
|
+
function parseIssueNumber(value) {
|
|
1608
|
+
if (!value) return void 0;
|
|
1609
|
+
const match = value.match(/#?(\d+)/);
|
|
1610
|
+
return match ? match[1] : void 0;
|
|
1611
|
+
}
|
|
1612
|
+
function parsePrLink(value) {
|
|
1613
|
+
if (!value) return void 0;
|
|
1614
|
+
const trimmed = value.trim();
|
|
1615
|
+
if (!trimmed) return void 0;
|
|
1616
|
+
if (trimmed === "#" || trimmed === "-") return void 0;
|
|
1617
|
+
if (trimmed.includes("{") || trimmed.includes("}")) return void 0;
|
|
1618
|
+
return trimmed;
|
|
1619
|
+
}
|
|
1620
|
+
function parseTasks(content) {
|
|
1621
|
+
const summary = { total: 0, todo: 0, doing: 0, done: 0 };
|
|
1622
|
+
let activeTask;
|
|
1623
|
+
let nextTodoTask;
|
|
1624
|
+
const lines = content.split("\n");
|
|
1625
|
+
for (const line of lines) {
|
|
1626
|
+
const match = line.match(/^\s*-\s*\[([A-Z]+)\]((?:\[[^\]]+\])*)\s*(.+?)\s*$/);
|
|
1627
|
+
if (!match) continue;
|
|
1628
|
+
const status = match[1].toUpperCase();
|
|
1629
|
+
const title = match[3].trim();
|
|
1630
|
+
summary.total++;
|
|
1631
|
+
if (status === "DONE") summary.done++;
|
|
1632
|
+
else if (status === "DOING" || status === "REVIEW") summary.doing++;
|
|
1633
|
+
else if (status === "TODO") summary.todo++;
|
|
1634
|
+
if (!activeTask && (status === "DOING" || status === "REVIEW")) {
|
|
1635
|
+
activeTask = { status, title };
|
|
1636
|
+
}
|
|
1637
|
+
if (!nextTodoTask && status === "TODO") {
|
|
1638
|
+
nextTodoTask = { status: "TODO", title };
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return { summary, activeTask, nextTodoTask };
|
|
1642
|
+
}
|
|
1643
|
+
function parseCompletionChecklist(content) {
|
|
1644
|
+
const lines = content.split("\n");
|
|
1645
|
+
const startIndex = lines.findIndex(
|
|
1646
|
+
(line) => /^\s*##\s+(완료 조건|Completion Criteria)\s*$/.test(line)
|
|
1647
|
+
);
|
|
1648
|
+
if (startIndex === -1) return void 0;
|
|
1649
|
+
let total = 0;
|
|
1650
|
+
let checked = 0;
|
|
1651
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
1652
|
+
const line = lines[i];
|
|
1653
|
+
if (/^\s*##\s+/.test(line)) break;
|
|
1654
|
+
const match = line.match(/^\s*-\s*\[([ xX])\]\s+/);
|
|
1655
|
+
if (!match) continue;
|
|
1656
|
+
total++;
|
|
1657
|
+
if (match[1].toLowerCase() === "x") checked++;
|
|
1658
|
+
}
|
|
1659
|
+
return total > 0 ? { total, checked } : void 0;
|
|
1660
|
+
}
|
|
1661
|
+
async function parseFeature(featurePath, type, context, options) {
|
|
1662
|
+
const lang = options.lang;
|
|
1663
|
+
const folderName = path4.basename(featurePath);
|
|
1664
|
+
const match = folderName.match(/^(F\\d+)-(.+)$/);
|
|
1665
|
+
const id = match?.[1];
|
|
1666
|
+
const slug = match?.[2] || folderName;
|
|
1667
|
+
const specPath = path4.join(featurePath, "spec.md");
|
|
1668
|
+
const planPath = path4.join(featurePath, "plan.md");
|
|
1669
|
+
const tasksPath = path4.join(featurePath, "tasks.md");
|
|
1670
|
+
let specStatus;
|
|
1671
|
+
let issueNumber;
|
|
1672
|
+
const specExists = await fs6.pathExists(specPath);
|
|
1673
|
+
if (specExists) {
|
|
1674
|
+
const content = await fs6.readFile(specPath, "utf-8");
|
|
1675
|
+
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
1676
|
+
specStatus = parseDocStatus(statusValue);
|
|
1677
|
+
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
1678
|
+
issueNumber = parseIssueNumber(issueValue);
|
|
1679
|
+
}
|
|
1680
|
+
let planStatus;
|
|
1681
|
+
const planExists = await fs6.pathExists(planPath);
|
|
1682
|
+
if (planExists) {
|
|
1683
|
+
const content = await fs6.readFile(planPath, "utf-8");
|
|
1684
|
+
const statusValue = extractFirstSpecValue(content, ["\uC0C1\uD0DC", "Status"]);
|
|
1685
|
+
planStatus = parseDocStatus(statusValue);
|
|
1686
|
+
}
|
|
1687
|
+
const tasksExists = await fs6.pathExists(tasksPath);
|
|
1688
|
+
const tasksSummary = { total: 0, todo: 0, doing: 0, done: 0 };
|
|
1689
|
+
let activeTask;
|
|
1690
|
+
let nextTodoTask;
|
|
1691
|
+
let completionChecklist;
|
|
1692
|
+
let prLink;
|
|
1693
|
+
let prStatus;
|
|
1694
|
+
let prFieldExists = false;
|
|
1695
|
+
let prStatusFieldExists = false;
|
|
1696
|
+
if (tasksExists) {
|
|
1697
|
+
const content = await fs6.readFile(tasksPath, "utf-8");
|
|
1698
|
+
const { summary, activeTask: active, nextTodoTask: nextTodo } = parseTasks(content);
|
|
1699
|
+
tasksSummary.total = summary.total;
|
|
1700
|
+
tasksSummary.todo = summary.todo;
|
|
1701
|
+
tasksSummary.doing = summary.doing;
|
|
1702
|
+
tasksSummary.done = summary.done;
|
|
1703
|
+
activeTask = active;
|
|
1704
|
+
nextTodoTask = nextTodo;
|
|
1705
|
+
completionChecklist = parseCompletionChecklist(content);
|
|
1706
|
+
if (!issueNumber) {
|
|
1707
|
+
const issueValue = extractFirstSpecValue(content, ["\uC774\uC288 \uBC88\uD638", "Issue Number", "Issue"]);
|
|
1708
|
+
issueNumber = parseIssueNumber(issueValue);
|
|
1709
|
+
}
|
|
1710
|
+
const prValue = extractFirstSpecValue(content, ["PR", "Pull Request"]);
|
|
1711
|
+
prFieldExists = prValue !== void 0;
|
|
1712
|
+
prLink = parsePrLink(prValue);
|
|
1713
|
+
const prStatusValue = extractFirstSpecValue(content, ["PR \uC0C1\uD0DC", "PR Status"]);
|
|
1714
|
+
prStatusFieldExists = prStatusValue !== void 0;
|
|
1715
|
+
prStatus = parseDocStatus(prStatusValue);
|
|
1716
|
+
}
|
|
1717
|
+
const warnings = [];
|
|
1718
|
+
if (context.projectBranchAvailable === false) {
|
|
1719
|
+
warnings.push(tr(lang, "warnings", "projectBranchUnavailable"));
|
|
1720
|
+
}
|
|
1721
|
+
const onExpectedBranch = isExpectedFeatureBranch(
|
|
1722
|
+
context.projectBranch,
|
|
1723
|
+
issueNumber,
|
|
1724
|
+
slug,
|
|
1725
|
+
folderName
|
|
1726
|
+
);
|
|
1727
|
+
const relativeFeaturePathFromDocs = path4.relative(context.docsDir, featurePath);
|
|
1728
|
+
const docsStatus = getGitStatusPorcelain(context.docsGitCwd, [relativeFeaturePathFromDocs]);
|
|
1729
|
+
const docsHasUncommittedChanges = docsStatus === void 0 ? true : docsStatus.trim().length > 0;
|
|
1730
|
+
if (docsStatus === void 0) {
|
|
1731
|
+
warnings.push(tr(lang, "warnings", "docsGitUnavailable"));
|
|
1732
|
+
}
|
|
1733
|
+
if (tasksExists && (!prFieldExists || !prStatusFieldExists)) {
|
|
1734
|
+
warnings.push(tr(lang, "warnings", "legacyTasksPrFields"));
|
|
1735
|
+
}
|
|
1736
|
+
const featureState = {
|
|
1737
|
+
id,
|
|
1738
|
+
slug,
|
|
1739
|
+
folderName,
|
|
1740
|
+
type,
|
|
1741
|
+
path: featurePath,
|
|
1742
|
+
issueNumber,
|
|
1743
|
+
specStatus,
|
|
1744
|
+
planStatus,
|
|
1745
|
+
tasks: tasksSummary,
|
|
1746
|
+
activeTask,
|
|
1747
|
+
nextTodoTask,
|
|
1748
|
+
completionChecklist,
|
|
1749
|
+
pr: { link: prLink, status: prStatus },
|
|
1750
|
+
git: {
|
|
1751
|
+
docsBranch: context.docsBranch,
|
|
1752
|
+
projectBranch: context.projectBranch,
|
|
1753
|
+
projectBranchAvailable: context.projectBranchAvailable,
|
|
1754
|
+
docsGitCwd: context.docsGitCwd,
|
|
1755
|
+
projectGitCwd: context.projectGitCwd,
|
|
1756
|
+
onExpectedBranch,
|
|
1757
|
+
docsHasUncommittedChanges
|
|
1758
|
+
},
|
|
1759
|
+
docs: {
|
|
1760
|
+
featurePathFromDocs: relativeFeaturePathFromDocs,
|
|
1761
|
+
specExists,
|
|
1762
|
+
planExists,
|
|
1763
|
+
tasksExists,
|
|
1764
|
+
prFieldExists,
|
|
1765
|
+
prStatusFieldExists
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
const { currentStep, actions, nextAction } = resolveFeatureProgress(
|
|
1769
|
+
featureState,
|
|
1770
|
+
options.stepDefinitions,
|
|
1771
|
+
lang
|
|
1772
|
+
);
|
|
1773
|
+
return { ...featureState, currentStep, actions, nextAction, warnings };
|
|
1774
|
+
}
|
|
1775
|
+
async function scanFeatures(config) {
|
|
1776
|
+
const features = [];
|
|
1777
|
+
const warnings = [];
|
|
1778
|
+
const stepDefinitions = getStepDefinitions(config.lang);
|
|
1779
|
+
const docsBranch = getCurrentBranch(config.docsDir);
|
|
1780
|
+
const projectBranches = {
|
|
1781
|
+
single: "",
|
|
1782
|
+
fe: "",
|
|
1783
|
+
be: ""
|
|
1784
|
+
};
|
|
1785
|
+
let singleProject;
|
|
1786
|
+
let feProject;
|
|
1787
|
+
let beProject;
|
|
1788
|
+
if (config.projectType === "single") {
|
|
1789
|
+
singleProject = resolveProjectGitCwd(config, "single");
|
|
1790
|
+
if (singleProject.warning) warnings.push(singleProject.warning);
|
|
1791
|
+
projectBranches.single = singleProject.cwd ? getCurrentBranch(singleProject.cwd) : "";
|
|
1792
|
+
} else {
|
|
1793
|
+
feProject = resolveProjectGitCwd(config, "fe");
|
|
1794
|
+
beProject = resolveProjectGitCwd(config, "be");
|
|
1795
|
+
if (feProject.warning) warnings.push(feProject.warning);
|
|
1796
|
+
if (beProject.warning) warnings.push(beProject.warning);
|
|
1797
|
+
projectBranches.fe = feProject.cwd ? getCurrentBranch(feProject.cwd) : "";
|
|
1798
|
+
projectBranches.be = beProject.cwd ? getCurrentBranch(beProject.cwd) : "";
|
|
1799
|
+
}
|
|
1800
|
+
if (config.projectType === "single") {
|
|
1801
|
+
const featureDirs = await glob("features/*/", {
|
|
1802
|
+
cwd: config.docsDir,
|
|
1803
|
+
absolute: true,
|
|
1804
|
+
ignore: ["**/feature-base/**"]
|
|
1805
|
+
});
|
|
1806
|
+
for (const dir of featureDirs) {
|
|
1807
|
+
if ((await fs6.stat(dir)).isDirectory()) {
|
|
1808
|
+
features.push(
|
|
1809
|
+
await parseFeature(
|
|
1810
|
+
dir,
|
|
1811
|
+
"single",
|
|
1812
|
+
{
|
|
1813
|
+
projectBranch: projectBranches.single,
|
|
1814
|
+
docsBranch,
|
|
1815
|
+
docsGitCwd: config.docsDir,
|
|
1816
|
+
projectGitCwd: singleProject?.cwd ?? void 0,
|
|
1817
|
+
docsDir: config.docsDir,
|
|
1818
|
+
projectBranchAvailable: Boolean(singleProject?.cwd)
|
|
1819
|
+
},
|
|
1820
|
+
{ lang: config.lang, stepDefinitions }
|
|
1821
|
+
)
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
} else {
|
|
1826
|
+
const feDirs = await glob("features/fe/*/", { cwd: config.docsDir, absolute: true });
|
|
1827
|
+
const beDirs = await glob("features/be/*/", { cwd: config.docsDir, absolute: true });
|
|
1828
|
+
for (const dir of feDirs) {
|
|
1829
|
+
if ((await fs6.stat(dir)).isDirectory()) {
|
|
1830
|
+
features.push(
|
|
1831
|
+
await parseFeature(
|
|
1832
|
+
dir,
|
|
1833
|
+
"fe",
|
|
1834
|
+
{
|
|
1835
|
+
projectBranch: projectBranches.fe,
|
|
1836
|
+
docsBranch,
|
|
1837
|
+
docsGitCwd: config.docsDir,
|
|
1838
|
+
projectGitCwd: feProject?.cwd ?? void 0,
|
|
1839
|
+
docsDir: config.docsDir,
|
|
1840
|
+
projectBranchAvailable: Boolean(feProject?.cwd)
|
|
1841
|
+
},
|
|
1842
|
+
{ lang: config.lang, stepDefinitions }
|
|
1843
|
+
)
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
for (const dir of beDirs) {
|
|
1848
|
+
if ((await fs6.stat(dir)).isDirectory()) {
|
|
1849
|
+
features.push(
|
|
1850
|
+
await parseFeature(
|
|
1851
|
+
dir,
|
|
1852
|
+
"be",
|
|
1853
|
+
{
|
|
1854
|
+
projectBranch: projectBranches.be,
|
|
1855
|
+
docsBranch,
|
|
1856
|
+
docsGitCwd: config.docsDir,
|
|
1857
|
+
projectGitCwd: beProject?.cwd ?? void 0,
|
|
1858
|
+
docsDir: config.docsDir,
|
|
1859
|
+
projectBranchAvailable: Boolean(beProject?.cwd)
|
|
1860
|
+
},
|
|
1861
|
+
{ lang: config.lang, stepDefinitions }
|
|
1862
|
+
)
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return {
|
|
1868
|
+
features,
|
|
1869
|
+
branches: {
|
|
1870
|
+
docs: docsBranch,
|
|
1871
|
+
project: config.projectType === "single" ? { single: projectBranches.single } : { fe: projectBranches.fe, be: projectBranches.be }
|
|
1872
|
+
},
|
|
1873
|
+
warnings
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// src/commands/context.ts
|
|
1878
|
+
function contextCommand(program2) {
|
|
1879
|
+
program2.command("context [feature-name]").description("Show current feature context and next actions").option("--json", "Output in JSON format for agents").option("--repo <repo>", "Repository type for fullstack: fe | be").action(
|
|
1880
|
+
async (featureName, options) => {
|
|
1881
|
+
try {
|
|
1882
|
+
await runContext(featureName, options);
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
if (options.json) {
|
|
1885
|
+
console.log(
|
|
1886
|
+
JSON.stringify({
|
|
1887
|
+
status: "error",
|
|
1888
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1889
|
+
})
|
|
1890
|
+
);
|
|
1891
|
+
} else {
|
|
1892
|
+
console.error(chalk6.red("\uC624\uB958:"), error);
|
|
1893
|
+
}
|
|
1894
|
+
process.exit(1);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
function matchesFeatureSelector(f, selector) {
|
|
1900
|
+
const s = selector.trim();
|
|
1901
|
+
if (!s) return false;
|
|
1902
|
+
if (f.folderName.toLowerCase() === s.toLowerCase()) return true;
|
|
1903
|
+
if (f.slug.toLowerCase() === s.toLowerCase()) return true;
|
|
1904
|
+
if (f.id && f.id.toLowerCase() === s.toLowerCase()) return true;
|
|
1905
|
+
return false;
|
|
1906
|
+
}
|
|
1907
|
+
function detectFromBranch(branchName, features) {
|
|
1908
|
+
const match = branchName.match(/^feat\/\d+-(.+)$/);
|
|
1909
|
+
if (!match) return [];
|
|
1910
|
+
const detected = match[1];
|
|
1911
|
+
return features.filter(
|
|
1912
|
+
(f) => f.slug.toLowerCase() === detected.toLowerCase() || f.folderName.toLowerCase() === detected.toLowerCase()
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
async function runContext(featureName, options) {
|
|
1916
|
+
const cwd = process.cwd();
|
|
1917
|
+
const config = await getConfig(cwd);
|
|
1918
|
+
const lang = config?.lang ?? "ko";
|
|
1919
|
+
if (!config) {
|
|
1920
|
+
throw new Error("\uC124\uC815 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
|
|
1921
|
+
}
|
|
1922
|
+
const stepDefinitions = getStepDefinitions(lang);
|
|
1923
|
+
const stepsMap = getStepsMap(lang);
|
|
1924
|
+
const { features, branches, warnings } = await scanFeatures(config);
|
|
1925
|
+
let targetFeatures = [];
|
|
1926
|
+
if (featureName) {
|
|
1927
|
+
targetFeatures = features.filter((f2) => matchesFeatureSelector(f2, featureName));
|
|
1928
|
+
if (options.repo) {
|
|
1929
|
+
targetFeatures = targetFeatures.filter((f2) => f2.type === options.repo);
|
|
1930
|
+
}
|
|
1931
|
+
} else {
|
|
1932
|
+
if (config.projectType === "single") {
|
|
1933
|
+
const branchName = branches.project.single || "";
|
|
1934
|
+
targetFeatures = detectFromBranch(branchName, features);
|
|
1935
|
+
} else if (options.repo) {
|
|
1936
|
+
const branchName = branches.project[options.repo] || "";
|
|
1937
|
+
targetFeatures = detectFromBranch(
|
|
1938
|
+
branchName,
|
|
1939
|
+
features.filter((f2) => f2.type === options.repo)
|
|
1940
|
+
);
|
|
1941
|
+
} else {
|
|
1942
|
+
const feMatches = branches.project.fe ? detectFromBranch(
|
|
1943
|
+
branches.project.fe,
|
|
1944
|
+
features.filter((f2) => f2.type === "fe")
|
|
1945
|
+
) : [];
|
|
1946
|
+
const beMatches = branches.project.be ? detectFromBranch(
|
|
1947
|
+
branches.project.be,
|
|
1948
|
+
features.filter((f2) => f2.type === "be")
|
|
1949
|
+
) : [];
|
|
1950
|
+
targetFeatures = [...feMatches, ...beMatches];
|
|
1951
|
+
}
|
|
1952
|
+
if (targetFeatures.length === 0) targetFeatures = features;
|
|
1953
|
+
}
|
|
1954
|
+
if (options.json) {
|
|
1955
|
+
const result = {
|
|
1956
|
+
status: features.length === 0 ? "no_features" : targetFeatures.length === 1 ? "single_matched" : targetFeatures.length > 1 ? "multiple_active" : "no_match",
|
|
1957
|
+
branches,
|
|
1958
|
+
warnings,
|
|
1959
|
+
matchedFeature: targetFeatures.length === 1 ? targetFeatures[0] : null,
|
|
1960
|
+
candidates: targetFeatures.length > 1 ? targetFeatures : [],
|
|
1961
|
+
actions: targetFeatures.length === 1 ? targetFeatures[0].actions : [],
|
|
1962
|
+
recommendation: ""
|
|
1963
|
+
};
|
|
1964
|
+
if (result.status === "multiple_active") {
|
|
1965
|
+
result.recommendation = "Multiple features detected. Please specify feature name (slug | F001 | F001-slug) or use --repo.";
|
|
1966
|
+
} else if (result.status === "no_features") {
|
|
1967
|
+
result.recommendation = "No features found. Create a feature first.";
|
|
1968
|
+
} else if (result.status === "no_match") {
|
|
1969
|
+
result.recommendation = "No features found.";
|
|
1970
|
+
} else {
|
|
1971
|
+
result.recommendation = targetFeatures[0].nextAction;
|
|
1972
|
+
}
|
|
1973
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
console.log();
|
|
1977
|
+
console.log(chalk6.bold("\u{1F4CD} Current Context Check"));
|
|
1978
|
+
if (config.projectType === "single") {
|
|
1979
|
+
if (branches.project.single) {
|
|
1980
|
+
console.log(
|
|
1981
|
+
chalk6.gray(` (Detected from Project Branch: ${branches.project.single})`)
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
} else if (options.repo) {
|
|
1985
|
+
const branchName = branches.project[options.repo] || "";
|
|
1986
|
+
if (branchName) {
|
|
1987
|
+
console.log(
|
|
1988
|
+
chalk6.gray(
|
|
1989
|
+
` (Detected from Project Branch: ${options.repo.toUpperCase()} ${branchName})`
|
|
1990
|
+
)
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
} else if (branches.project.fe || branches.project.be) {
|
|
1994
|
+
const parts = [
|
|
1995
|
+
branches.project.fe ? `FE ${branches.project.fe}` : null,
|
|
1996
|
+
branches.project.be ? `BE ${branches.project.be}` : null
|
|
1997
|
+
].filter(Boolean);
|
|
1998
|
+
console.log(chalk6.gray(` (Detected from Project Branch: ${parts.join(" / ")})`));
|
|
1999
|
+
}
|
|
2000
|
+
if (config.docsRepo === "standalone" && branches.docs) {
|
|
2001
|
+
console.log(chalk6.gray(` (Docs Branch: ${branches.docs})`));
|
|
2002
|
+
}
|
|
2003
|
+
console.log(chalk6.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
2004
|
+
console.log();
|
|
2005
|
+
if (features.length === 0) {
|
|
2006
|
+
console.log(chalk6.yellow("\u26A0\uFE0F \uC9C4\uD589 \uC911\uC778 Feature\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
2007
|
+
console.log();
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
if (warnings.length > 0) {
|
|
2011
|
+
console.log(chalk6.yellow("\u26A0\uFE0F \uD658\uACBD \uACBD\uACE0:"));
|
|
2012
|
+
warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
|
|
2013
|
+
console.log();
|
|
2014
|
+
}
|
|
2015
|
+
if (targetFeatures.length > 1) {
|
|
2016
|
+
console.log(
|
|
2017
|
+
chalk6.blue(`\u{1F539} ${targetFeatures.length} Active Features Detected:`)
|
|
2018
|
+
);
|
|
2019
|
+
console.log();
|
|
2020
|
+
targetFeatures.forEach((f2) => {
|
|
2021
|
+
const stepName2 = stepsMap[f2.currentStep] || "Unknown";
|
|
2022
|
+
const typeStr = config.projectType === "fullstack" ? chalk6.cyan(`(${f2.type})`) : "";
|
|
2023
|
+
console.log(
|
|
2024
|
+
` \u2022 ${chalk6.bold(f2.folderName)} ${typeStr} - ${chalk6.yellow(stepName2)}`
|
|
2025
|
+
);
|
|
2026
|
+
});
|
|
2027
|
+
console.log();
|
|
2028
|
+
console.log(chalk6.gray("Tip: \uD2B9\uC815 Feature\uC758 \uC0C1\uC138 \uC815\uBCF4\uB97C \uBCF4\uB824\uBA74:"));
|
|
2029
|
+
console.log(
|
|
2030
|
+
chalk6.gray(" $ npx lee-spec-kit context <slug|F001|F001-slug> [--repo fe|be]")
|
|
2031
|
+
);
|
|
2032
|
+
console.log();
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const f = targetFeatures[0];
|
|
2036
|
+
const stepName = stepsMap[f.currentStep] || "Unknown";
|
|
2037
|
+
const okTag = (requiresUserOk) => requiresUserOk ? chalk6.yellow(lang === "ko" ? "[OK \uD544\uC694] " : "[OK required] ") : "";
|
|
2038
|
+
console.log(
|
|
2039
|
+
`\u{1F539} Feature: ${chalk6.bold(f.folderName)} ${config.projectType === "fullstack" ? chalk6.cyan(`(${f.type})`) : ""}`
|
|
2040
|
+
);
|
|
2041
|
+
if (f.issueNumber) {
|
|
2042
|
+
console.log(` \u2022 Issue: #${f.issueNumber}`);
|
|
2043
|
+
}
|
|
2044
|
+
console.log(` \u2022 Path: ${path4.relative(cwd, f.path)}`);
|
|
2045
|
+
if (f.git.projectBranch) {
|
|
2046
|
+
console.log(` \u2022 Project Branch: ${f.git.projectBranch}`);
|
|
2047
|
+
}
|
|
2048
|
+
console.log();
|
|
2049
|
+
console.log(
|
|
2050
|
+
`\u{1F539} Progress: ${chalk6.yellow(`Step ${f.currentStep}. ${stepName}`)}`
|
|
2051
|
+
);
|
|
2052
|
+
if (f.activeTask) {
|
|
2053
|
+
console.log(
|
|
2054
|
+
` \u2022 Active Task: ${chalk6.yellow(`[${f.activeTask.status}]`)} ${f.activeTask.title}`
|
|
2055
|
+
);
|
|
2056
|
+
} else if (f.nextTodoTask && f.currentStep === 10) {
|
|
2057
|
+
console.log(
|
|
2058
|
+
` \u2022 Next TODO: ${chalk6.gray(`[${f.nextTodoTask.status}]`)} ${f.nextTodoTask.title}`
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
printChecklist(f, stepDefinitions);
|
|
2062
|
+
if (f.warnings.length > 0) {
|
|
2063
|
+
console.log();
|
|
2064
|
+
console.log(chalk6.yellow("\u26A0\uFE0F Feature Warnings:"));
|
|
2065
|
+
f.warnings.forEach((w) => console.log(chalk6.yellow(` - ${w}`)));
|
|
2066
|
+
}
|
|
2067
|
+
console.log();
|
|
2068
|
+
console.log(chalk6.gray("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
|
|
2069
|
+
if (!f.actions || f.actions.length === 0) {
|
|
2070
|
+
console.log(`\u{1F449} Next Action: ${chalk6.green(chalk6.bold(f.nextAction))}`);
|
|
2071
|
+
console.log();
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
if (f.actions.length === 1) {
|
|
2075
|
+
const action = f.actions[0];
|
|
2076
|
+
if (action.type === "command") {
|
|
2077
|
+
console.log(
|
|
2078
|
+
`\u{1F449} Next Action (${chalk6.cyan(action.scope)}): ${okTag(action.requiresUserOk)}${chalk6.green(chalk6.bold(action.cmd))}`
|
|
2079
|
+
);
|
|
2080
|
+
} else {
|
|
2081
|
+
console.log(
|
|
2082
|
+
`\u{1F449} Next Action: ${okTag(action.requiresUserOk)}${chalk6.green(chalk6.bold(action.message))}`
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
console.log();
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
console.log(chalk6.green(chalk6.bold("\u{1F449} Next Actions:")));
|
|
2089
|
+
f.actions.forEach((action) => {
|
|
2090
|
+
if (action.type === "command") {
|
|
2091
|
+
console.log(` \u2022 (${action.scope}) ${okTag(action.requiresUserOk)}${action.cmd}`);
|
|
2092
|
+
} else {
|
|
2093
|
+
console.log(` \u2022 ${okTag(action.requiresUserOk)}${action.message}`);
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
console.log();
|
|
2097
|
+
}
|
|
2098
|
+
function printChecklist(f, stepDefinitions) {
|
|
2099
|
+
const checklistSteps = [...stepDefinitions].sort((a, b) => a.step - b.step);
|
|
2100
|
+
checklistSteps.forEach((definition) => {
|
|
2101
|
+
const done = definition.checklist.done(f);
|
|
2102
|
+
const detail = definition.checklist.detail?.(f) ?? "";
|
|
2103
|
+
const mark = done ? chalk6.green("\u2705") : chalk6.gray("\u25EF");
|
|
2104
|
+
const label = definition.step === f.currentStep ? chalk6.bold(definition.name) : definition.name;
|
|
2105
|
+
console.log(` ${mark} ${definition.step}. ${label} ${detail}`);
|
|
2106
|
+
});
|
|
2107
|
+
}
|
|
2108
|
+
var CACHE_FILE = path4.join(os.homedir(), ".lee-spec-kit-version-cache.json");
|
|
866
2109
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
867
2110
|
function getCurrentVersion() {
|
|
868
2111
|
try {
|
|
869
|
-
const packageJsonPath =
|
|
2112
|
+
const packageJsonPath = path4.join(__dirname$1, "..", "package.json");
|
|
870
2113
|
if (fs6.existsSync(packageJsonPath)) {
|
|
871
2114
|
const pkg = fs6.readJsonSync(packageJsonPath);
|
|
872
2115
|
return pkg.version;
|
|
@@ -896,9 +2139,9 @@ function isNewerVersion(current, latest) {
|
|
|
896
2139
|
function printUpdateNotice(current, latest) {
|
|
897
2140
|
console.log();
|
|
898
2141
|
console.log(
|
|
899
|
-
|
|
2142
|
+
chalk6.yellow(`\u{1F4E6} lee-spec-kit v${latest} \uC0AC\uC6A9 \uAC00\uB2A5 (\uD604\uC7AC: v${current})`)
|
|
900
2143
|
);
|
|
901
|
-
console.log(
|
|
2144
|
+
console.log(chalk6.gray(" \uC5C5\uB370\uC774\uD2B8: npm update -g lee-spec-kit"));
|
|
902
2145
|
console.log();
|
|
903
2146
|
}
|
|
904
2147
|
function spawnBackgroundVersionCheck() {
|
|
@@ -943,11 +2186,24 @@ function checkForUpdates() {
|
|
|
943
2186
|
|
|
944
2187
|
// src/index.ts
|
|
945
2188
|
checkForUpdates();
|
|
2189
|
+
function getCliVersion() {
|
|
2190
|
+
try {
|
|
2191
|
+
const packageJsonPath = path4.join(__dirname$1, "..", "package.json");
|
|
2192
|
+
if (fs6.existsSync(packageJsonPath)) {
|
|
2193
|
+
const pkg = fs6.readJsonSync(packageJsonPath);
|
|
2194
|
+
if (pkg?.version) return String(pkg.version);
|
|
2195
|
+
}
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
return "0.0.0";
|
|
2199
|
+
}
|
|
946
2200
|
program.name("lee-spec-kit").description(
|
|
947
2201
|
"Project documentation structure generator for AI-assisted development"
|
|
948
|
-
).version(
|
|
2202
|
+
).version(getCliVersion());
|
|
949
2203
|
initCommand(program);
|
|
950
2204
|
featureCommand(program);
|
|
951
2205
|
statusCommand(program);
|
|
952
2206
|
updateCommand(program);
|
|
2207
|
+
configCommand(program);
|
|
2208
|
+
contextCommand(program);
|
|
953
2209
|
program.parse();
|