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