sdd-tool 0.5.0 → 0.5.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/cli/index.js +1439 -1022
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2503,28 +2503,9 @@ sdd transition guide
|
|
|
2503
2503
|
}
|
|
2504
2504
|
|
|
2505
2505
|
// src/cli/commands/init.ts
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
await runInit(options);
|
|
2510
|
-
} catch (error2) {
|
|
2511
|
-
error(error2 instanceof Error ? error2.message : String(error2));
|
|
2512
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
2513
|
-
}
|
|
2514
|
-
});
|
|
2515
|
-
}
|
|
2516
|
-
async function runInit(options) {
|
|
2517
|
-
const cwd = process.cwd();
|
|
2518
|
-
const sddPath = path2.join(cwd, ".sdd");
|
|
2519
|
-
if (await directoryExists(sddPath)) {
|
|
2520
|
-
if (!options.force) {
|
|
2521
|
-
error(".sdd/ \uB514\uB809\uD1A0\uB9AC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. --force \uC635\uC158\uC73C\uB85C \uB36E\uC5B4\uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
2522
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
2523
|
-
}
|
|
2524
|
-
warn("\uAE30\uC874 .sdd/ \uB514\uB809\uD1A0\uB9AC\uB97C \uB36E\uC5B4\uC501\uB2C8\uB2E4.");
|
|
2525
|
-
}
|
|
2526
|
-
info("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD569\uB2C8\uB2E4...");
|
|
2527
|
-
const directories = [
|
|
2506
|
+
init_types();
|
|
2507
|
+
function getInitDirectories() {
|
|
2508
|
+
return [
|
|
2528
2509
|
".sdd",
|
|
2529
2510
|
".sdd/specs",
|
|
2530
2511
|
".sdd/changes",
|
|
@@ -2533,48 +2514,10 @@ async function runInit(options) {
|
|
|
2533
2514
|
".claude",
|
|
2534
2515
|
".claude/commands"
|
|
2535
2516
|
];
|
|
2536
|
-
for (const dir of directories) {
|
|
2537
|
-
const result = await ensureDir(path2.join(cwd, dir));
|
|
2538
|
-
if (!result.success) {
|
|
2539
|
-
error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${dir}`);
|
|
2540
|
-
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
await createDefaultFiles(cwd);
|
|
2544
|
-
await copyTemplates(cwd);
|
|
2545
|
-
await createClaudeCommands(cwd);
|
|
2546
|
-
success2("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
2547
|
-
newline();
|
|
2548
|
-
info("\uC0DD\uC131\uB41C \uAD6C\uC870:");
|
|
2549
|
-
listItem(".sdd/");
|
|
2550
|
-
listItem("AGENTS.md", 1);
|
|
2551
|
-
listItem("constitution.md", 1);
|
|
2552
|
-
listItem("specs/", 1);
|
|
2553
|
-
listItem("changes/", 1);
|
|
2554
|
-
listItem("archive/", 1);
|
|
2555
|
-
listItem("templates/", 1);
|
|
2556
|
-
listItem(".claude/");
|
|
2557
|
-
listItem("commands/", 1);
|
|
2558
|
-
newline();
|
|
2559
|
-
info("Claude \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC:");
|
|
2560
|
-
listItem("/sdd.start - \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC2DC\uC791 (\uD1B5\uD569 \uC9C4\uC785\uC810)");
|
|
2561
|
-
listItem("/sdd.constitution - \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59 \uAD00\uB9AC");
|
|
2562
|
-
listItem("/sdd.new - \uC0C8 \uAE30\uB2A5 \uBA85\uC138 \uC791\uC131");
|
|
2563
|
-
listItem("/sdd.plan - \uAD6C\uD604 \uACC4\uD68D \uC791\uC131");
|
|
2564
|
-
listItem("/sdd.tasks - \uC791\uC5C5 \uBD84\uD574");
|
|
2565
|
-
listItem("/sdd.implement - \uAD6C\uD604 \uC9C4\uD589");
|
|
2566
|
-
listItem("/sdd.validate - \uC2A4\uD399 \uAC80\uC99D");
|
|
2567
|
-
listItem("/sdd.status - \uC0C1\uD0DC \uD655\uC778");
|
|
2568
|
-
listItem("/sdd.change - \uBCC0\uACBD \uC81C\uC548");
|
|
2569
|
-
newline();
|
|
2570
|
-
info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
2571
|
-
listItem("constitution.md\uB97C \uC218\uC815\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59\uC744 \uC815\uC758\uD558\uC138\uC694");
|
|
2572
|
-
listItem("/sdd.new \uB85C \uCCAB \uBC88\uC9F8 \uAE30\uB2A5 \uBA85\uC138\uB97C \uC791\uC131\uD558\uC138\uC694");
|
|
2573
2517
|
}
|
|
2574
|
-
|
|
2575
|
-
const projectName = path2.basename(cwd);
|
|
2518
|
+
function generateConstitutionContent(projectName) {
|
|
2576
2519
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2577
|
-
|
|
2520
|
+
return `---
|
|
2578
2521
|
version: 1.0.0
|
|
2579
2522
|
created: ${today}
|
|
2580
2523
|
---
|
|
@@ -2609,13 +2552,10 @@ created: ${today}
|
|
|
2609
2552
|
|
|
2610
2553
|
- \uD14C\uC2A4\uD2B8 \uCEE4\uBC84\uB9AC\uC9C0: 80% \uC774\uC0C1(SHOULD)
|
|
2611
2554
|
`;
|
|
2612
|
-
await writeFile(path2.join(cwd, ".sdd", "constitution.md"), constitution);
|
|
2613
|
-
const agents = generateAgentsMd({ projectName });
|
|
2614
|
-
await writeFile(path2.join(cwd, ".sdd", "AGENTS.md"), agents);
|
|
2615
2555
|
}
|
|
2616
|
-
|
|
2556
|
+
function generateSpecTemplate() {
|
|
2617
2557
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2618
|
-
|
|
2558
|
+
return `---
|
|
2619
2559
|
status: draft
|
|
2620
2560
|
created: ${today}
|
|
2621
2561
|
depends: null
|
|
@@ -2643,7 +2583,69 @@ depends: null
|
|
|
2643
2583
|
|
|
2644
2584
|
\uCD94\uAC00 \uC124\uBA85\uC774\uB098 \uC81C\uC57D \uC870\uAC74
|
|
2645
2585
|
`;
|
|
2646
|
-
|
|
2586
|
+
}
|
|
2587
|
+
async function executeInit(projectPath, options) {
|
|
2588
|
+
const sddPath = path2.join(projectPath, ".sdd");
|
|
2589
|
+
const claudePath = path2.join(projectPath, ".claude");
|
|
2590
|
+
if (await directoryExists(sddPath)) {
|
|
2591
|
+
if (!options.force) {
|
|
2592
|
+
return failure(new Error(".sdd/ \uB514\uB809\uD1A0\uB9AC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. --force \uC635\uC158\uC73C\uB85C \uB36E\uC5B4\uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
const directories = getInitDirectories();
|
|
2596
|
+
const createdDirs = [];
|
|
2597
|
+
for (const dir of directories) {
|
|
2598
|
+
const result = await ensureDir(path2.join(projectPath, dir));
|
|
2599
|
+
if (!result.success) {
|
|
2600
|
+
return failure(new Error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${dir}`));
|
|
2601
|
+
}
|
|
2602
|
+
createdDirs.push(dir);
|
|
2603
|
+
}
|
|
2604
|
+
const createdFiles = [];
|
|
2605
|
+
const projectName = path2.basename(projectPath);
|
|
2606
|
+
const constitutionContent = generateConstitutionContent(projectName);
|
|
2607
|
+
await writeFile(path2.join(sddPath, "constitution.md"), constitutionContent);
|
|
2608
|
+
createdFiles.push(".sdd/constitution.md");
|
|
2609
|
+
const agentsContent = generateAgentsMd({ projectName });
|
|
2610
|
+
await writeFile(path2.join(sddPath, "AGENTS.md"), agentsContent);
|
|
2611
|
+
createdFiles.push(".sdd/AGENTS.md");
|
|
2612
|
+
const templateFiles = await createTemplateFiles(projectPath);
|
|
2613
|
+
createdFiles.push(...templateFiles);
|
|
2614
|
+
const commandFiles = await createCommandFiles(projectPath);
|
|
2615
|
+
createdFiles.push(...commandFiles);
|
|
2616
|
+
return success({
|
|
2617
|
+
sddPath,
|
|
2618
|
+
claudePath,
|
|
2619
|
+
directories: createdDirs,
|
|
2620
|
+
files: createdFiles
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
async function createTemplateFiles(projectPath) {
|
|
2624
|
+
const templatesPath = path2.join(projectPath, ".sdd", "templates");
|
|
2625
|
+
const files = [];
|
|
2626
|
+
await writeFile(path2.join(templatesPath, "spec.md"), generateSpecTemplate());
|
|
2627
|
+
files.push(".sdd/templates/spec.md");
|
|
2628
|
+
await writeFile(path2.join(templatesPath, "proposal.md"), generateProposalTemplate());
|
|
2629
|
+
files.push(".sdd/templates/proposal.md");
|
|
2630
|
+
await writeFile(path2.join(templatesPath, "delta.md"), generateDeltaTemplate());
|
|
2631
|
+
files.push(".sdd/templates/delta.md");
|
|
2632
|
+
await writeFile(path2.join(templatesPath, "tasks.md"), generateTasksTemplate());
|
|
2633
|
+
files.push(".sdd/templates/tasks.md");
|
|
2634
|
+
return files;
|
|
2635
|
+
}
|
|
2636
|
+
async function createCommandFiles(projectPath) {
|
|
2637
|
+
const commandsPath = path2.join(projectPath, ".claude", "commands");
|
|
2638
|
+
const files = [];
|
|
2639
|
+
const commands = generateClaudeCommands();
|
|
2640
|
+
for (const cmd of commands) {
|
|
2641
|
+
await writeFile(path2.join(commandsPath, `${cmd.name}.md`), cmd.content);
|
|
2642
|
+
files.push(`.claude/commands/${cmd.name}.md`);
|
|
2643
|
+
}
|
|
2644
|
+
return files;
|
|
2645
|
+
}
|
|
2646
|
+
function generateProposalTemplate() {
|
|
2647
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2648
|
+
return `---
|
|
2647
2649
|
id: CHG-{{ID}}
|
|
2648
2650
|
status: draft
|
|
2649
2651
|
created: ${today}
|
|
@@ -2706,7 +2708,10 @@ created: ${today}
|
|
|
2706
2708
|
- \uC601\uD5A5\uB3C4: \uB0AE\uC74C/\uC911\uAC04/\uB192\uC74C
|
|
2707
2709
|
- \uBCF5\uC7A1\uB3C4: \uB0AE\uC74C/\uC911\uAC04/\uB192\uC74C
|
|
2708
2710
|
`;
|
|
2709
|
-
|
|
2711
|
+
}
|
|
2712
|
+
function generateDeltaTemplate() {
|
|
2713
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2714
|
+
return `---
|
|
2710
2715
|
proposal: CHG-{{ID}}
|
|
2711
2716
|
created: ${today}
|
|
2712
2717
|
---
|
|
@@ -2737,7 +2742,10 @@ created: ${today}
|
|
|
2737
2742
|
|
|
2738
2743
|
(\uC0AD\uC81C\uB418\uB294 \uC2A4\uD399 \uCC38\uC870)
|
|
2739
2744
|
`;
|
|
2740
|
-
|
|
2745
|
+
}
|
|
2746
|
+
function generateTasksTemplate() {
|
|
2747
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2748
|
+
return `---
|
|
2741
2749
|
spec: {{SPEC_ID}}
|
|
2742
2750
|
created: ${today}
|
|
2743
2751
|
---
|
|
@@ -2788,19 +2796,57 @@ graph LR
|
|
|
2788
2796
|
| [\u2192T] | \uD14C\uC2A4\uD2B8 \uD544\uC694 |
|
|
2789
2797
|
| [US] | \uBD88\uD655\uC2E4/\uAC80\uD1A0 \uD544\uC694 |
|
|
2790
2798
|
`;
|
|
2791
|
-
await writeFile(path2.join(cwd, ".sdd", "templates", "spec.md"), specTemplate);
|
|
2792
|
-
await writeFile(path2.join(cwd, ".sdd", "templates", "proposal.md"), proposalTemplate);
|
|
2793
|
-
await writeFile(path2.join(cwd, ".sdd", "templates", "delta.md"), deltaTemplate);
|
|
2794
|
-
await writeFile(path2.join(cwd, ".sdd", "templates", "tasks.md"), tasksTemplate);
|
|
2795
2799
|
}
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2800
|
+
function registerInitCommand(program2) {
|
|
2801
|
+
program2.command("init").description("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD569\uB2C8\uB2E4").option("-f, --force", "\uAE30\uC874 .sdd/ \uB514\uB809\uD1A0\uB9AC \uB36E\uC5B4\uC4F0\uAE30").action(async (options) => {
|
|
2802
|
+
try {
|
|
2803
|
+
await runInit(options);
|
|
2804
|
+
} catch (error2) {
|
|
2805
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
2806
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
2807
|
+
}
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
async function runInit(options) {
|
|
2811
|
+
const cwd = process.cwd();
|
|
2812
|
+
if (await directoryExists(path2.join(cwd, ".sdd"))) {
|
|
2813
|
+
if (options.force) {
|
|
2814
|
+
warn("\uAE30\uC874 .sdd/ \uB514\uB809\uD1A0\uB9AC\uB97C \uB36E\uC5B4\uC501\uB2C8\uB2E4.");
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
info("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD569\uB2C8\uB2E4...");
|
|
2818
|
+
const result = await executeInit(cwd, options);
|
|
2819
|
+
if (!result.success) {
|
|
2820
|
+
error(result.error.message);
|
|
2821
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
2803
2822
|
}
|
|
2823
|
+
success2("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
2824
|
+
newline();
|
|
2825
|
+
info("\uC0DD\uC131\uB41C \uAD6C\uC870:");
|
|
2826
|
+
listItem(".sdd/");
|
|
2827
|
+
listItem("AGENTS.md", 1);
|
|
2828
|
+
listItem("constitution.md", 1);
|
|
2829
|
+
listItem("specs/", 1);
|
|
2830
|
+
listItem("changes/", 1);
|
|
2831
|
+
listItem("archive/", 1);
|
|
2832
|
+
listItem("templates/", 1);
|
|
2833
|
+
listItem(".claude/");
|
|
2834
|
+
listItem("commands/", 1);
|
|
2835
|
+
newline();
|
|
2836
|
+
info("Claude \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC:");
|
|
2837
|
+
listItem("/sdd.start - \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC2DC\uC791 (\uD1B5\uD569 \uC9C4\uC785\uC810)");
|
|
2838
|
+
listItem("/sdd.constitution - \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59 \uAD00\uB9AC");
|
|
2839
|
+
listItem("/sdd.new - \uC0C8 \uAE30\uB2A5 \uBA85\uC138 \uC791\uC131");
|
|
2840
|
+
listItem("/sdd.plan - \uAD6C\uD604 \uACC4\uD68D \uC791\uC131");
|
|
2841
|
+
listItem("/sdd.tasks - \uC791\uC5C5 \uBD84\uD574");
|
|
2842
|
+
listItem("/sdd.implement - \uAD6C\uD604 \uC9C4\uD589");
|
|
2843
|
+
listItem("/sdd.validate - \uC2A4\uD399 \uAC80\uC99D");
|
|
2844
|
+
listItem("/sdd.status - \uC0C1\uD0DC \uD655\uC778");
|
|
2845
|
+
listItem("/sdd.change - \uBCC0\uACBD \uC81C\uC548");
|
|
2846
|
+
newline();
|
|
2847
|
+
info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
2848
|
+
listItem("constitution.md\uB97C \uC218\uC815\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59\uC744 \uC815\uC758\uD558\uC138\uC694");
|
|
2849
|
+
listItem("/sdd.new \uB85C \uCCAB \uBC88\uC9F8 \uAE30\uB2A5 \uBA85\uC138\uB97C \uC791\uC131\uD558\uC138\uC694");
|
|
2804
2850
|
}
|
|
2805
2851
|
|
|
2806
2852
|
// src/cli/commands/validate.ts
|
|
@@ -3661,26 +3707,15 @@ async function findSpecFiles(dirPath) {
|
|
|
3661
3707
|
// src/cli/commands/validate.ts
|
|
3662
3708
|
init_errors();
|
|
3663
3709
|
init_fs();
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
try {
|
|
3667
|
-
await runValidate(targetPath, options);
|
|
3668
|
-
} catch (error2) {
|
|
3669
|
-
error(error2 instanceof Error ? error2.message : String(error2));
|
|
3670
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
3671
|
-
}
|
|
3672
|
-
});
|
|
3673
|
-
}
|
|
3674
|
-
async function runValidate(targetPath, options) {
|
|
3710
|
+
init_types();
|
|
3711
|
+
async function createValidateContext(targetPath, options, sddRoot) {
|
|
3675
3712
|
let resolvedPath;
|
|
3676
3713
|
let specsRoot;
|
|
3677
|
-
const sddRoot = await findSddRoot();
|
|
3678
3714
|
if (targetPath) {
|
|
3679
3715
|
resolvedPath = path4.resolve(targetPath);
|
|
3680
3716
|
} else {
|
|
3681
3717
|
if (!sddRoot) {
|
|
3682
|
-
|
|
3683
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
3718
|
+
return failure(new Error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."));
|
|
3684
3719
|
}
|
|
3685
3720
|
resolvedPath = path4.join(sddRoot, ".sdd", "specs");
|
|
3686
3721
|
}
|
|
@@ -3693,39 +3728,75 @@ async function runValidate(targetPath, options) {
|
|
|
3693
3728
|
const constitutionPath = path4.join(sddRoot, ".sdd", "constitution.md");
|
|
3694
3729
|
hasConstitution = await fileExists(constitutionPath);
|
|
3695
3730
|
}
|
|
3731
|
+
return success({
|
|
3732
|
+
resolvedPath,
|
|
3733
|
+
specsRoot,
|
|
3734
|
+
checkConstitution,
|
|
3735
|
+
hasConstitution,
|
|
3736
|
+
sddRoot: sddRoot || void 0
|
|
3737
|
+
});
|
|
3738
|
+
}
|
|
3739
|
+
async function executeValidate(options, context) {
|
|
3740
|
+
const result = await validateSpecs(context.resolvedPath, {
|
|
3741
|
+
strict: options.strict,
|
|
3742
|
+
checkLinks: options.checkLinks,
|
|
3743
|
+
specsRoot: context.specsRoot,
|
|
3744
|
+
checkConstitution: context.checkConstitution && context.hasConstitution,
|
|
3745
|
+
sddRoot: context.sddRoot
|
|
3746
|
+
});
|
|
3747
|
+
if (!result.success) {
|
|
3748
|
+
return failure(result.error);
|
|
3749
|
+
}
|
|
3750
|
+
return success(result.data);
|
|
3751
|
+
}
|
|
3752
|
+
function formatValidateSummary(result, useColors = true) {
|
|
3753
|
+
const { passed, failed, warnings } = result;
|
|
3754
|
+
const passedText = useColors ? chalk2.green(`${passed} passed`) : `${passed} passed`;
|
|
3755
|
+
const failedText = failed > 0 ? useColors ? chalk2.red(`${failed} failed`) : `${failed} failed` : `${failed} failed`;
|
|
3756
|
+
const warningsText = warnings > 0 ? useColors ? chalk2.yellow(`${warnings} warnings`) : `${warnings} warnings` : "";
|
|
3757
|
+
return [passedText, failedText, warningsText].filter(Boolean).join(", ");
|
|
3758
|
+
}
|
|
3759
|
+
function registerValidateCommand(program2) {
|
|
3760
|
+
program2.command("validate").description("\uC2A4\uD399 \uD30C\uC77C \uD615\uC2DD\uC744 \uAC80\uC99D\uD569\uB2C8\uB2E4").argument("[path]", "\uAC80\uC99D\uD560 \uD30C\uC77C \uB610\uB294 \uB514\uB809\uD1A0\uB9AC", "").option("-s, --strict", "\uACBD\uACE0\uB3C4 \uC5D0\uB7EC\uB85C \uCC98\uB9AC").option("-q, --quiet", "\uC694\uC57D\uB9CC \uCD9C\uB825").option("-l, --check-links", "\uCC38\uC870 \uB9C1\uD06C \uC720\uD6A8\uC131 \uAC80\uC0AC").option("-c, --constitution", "Constitution \uC704\uBC18 \uAC80\uC0AC (\uAE30\uBCF8\uAC12)").option("--no-constitution", "Constitution \uAC80\uC0AC \uAC74\uB108\uB6F0\uAE30").action(async (targetPath, options) => {
|
|
3761
|
+
try {
|
|
3762
|
+
await runValidate(targetPath, options);
|
|
3763
|
+
} catch (error2) {
|
|
3764
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
3765
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
3766
|
+
}
|
|
3767
|
+
});
|
|
3768
|
+
}
|
|
3769
|
+
async function runValidate(targetPath, options) {
|
|
3770
|
+
const sddRoot = await findSddRoot();
|
|
3771
|
+
const contextResult = await createValidateContext(targetPath, options, sddRoot);
|
|
3772
|
+
if (!contextResult.success) {
|
|
3773
|
+
error(contextResult.error.message);
|
|
3774
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
3775
|
+
}
|
|
3776
|
+
const context = contextResult.data;
|
|
3696
3777
|
if (!options.quiet) {
|
|
3697
|
-
info(`\uAC80\uC99D \uC911: ${resolvedPath}`);
|
|
3778
|
+
info(`\uAC80\uC99D \uC911: ${context.resolvedPath}`);
|
|
3698
3779
|
if (options.checkLinks) {
|
|
3699
3780
|
info("(\uCC38\uC870 \uB9C1\uD06C \uAC80\uC99D \uD3EC\uD568)");
|
|
3700
3781
|
}
|
|
3701
|
-
if (checkConstitution && hasConstitution) {
|
|
3782
|
+
if (context.checkConstitution && context.hasConstitution) {
|
|
3702
3783
|
info("(Constitution \uC704\uBC18 \uAC80\uC0AC \uD3EC\uD568)");
|
|
3703
3784
|
}
|
|
3704
3785
|
newline();
|
|
3705
3786
|
}
|
|
3706
|
-
const result = await
|
|
3707
|
-
strict: options.strict,
|
|
3708
|
-
checkLinks: options.checkLinks,
|
|
3709
|
-
specsRoot,
|
|
3710
|
-
checkConstitution: checkConstitution && hasConstitution,
|
|
3711
|
-
sddRoot: sddRoot || void 0
|
|
3712
|
-
});
|
|
3787
|
+
const result = await executeValidate(options, context);
|
|
3713
3788
|
if (!result.success) {
|
|
3714
3789
|
error(result.error.message);
|
|
3715
3790
|
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
3716
3791
|
}
|
|
3717
|
-
const {
|
|
3792
|
+
const { failed, files } = result.data;
|
|
3718
3793
|
if (!options.quiet) {
|
|
3719
3794
|
for (const file of files) {
|
|
3720
|
-
printFileResult(file, resolvedPath);
|
|
3795
|
+
printFileResult(file, context.resolvedPath);
|
|
3721
3796
|
}
|
|
3722
3797
|
newline();
|
|
3723
3798
|
}
|
|
3724
|
-
|
|
3725
|
-
const failedText = failed > 0 ? chalk2.red(`${failed} failed`) : `${failed} failed`;
|
|
3726
|
-
const warningsText = warnings > 0 ? chalk2.yellow(`${warnings} warnings`) : "";
|
|
3727
|
-
const summary = [passedText, failedText, warningsText].filter(Boolean).join(", ");
|
|
3728
|
-
console.log(`Result: ${summary}`);
|
|
3799
|
+
console.log(`Result: ${formatValidateSummary(result.data)}`);
|
|
3729
3800
|
if (failed > 0) {
|
|
3730
3801
|
process.exit(ExitCode.VALIDATION_FAILED);
|
|
3731
3802
|
}
|
|
@@ -4917,57 +4988,200 @@ async function listPendingChanges(sddPath) {
|
|
|
4917
4988
|
// src/cli/commands/change.ts
|
|
4918
4989
|
init_fs();
|
|
4919
4990
|
init_errors();
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4991
|
+
init_types();
|
|
4992
|
+
async function getChangeListItems(sddPath) {
|
|
4993
|
+
const result = await listPendingChanges(sddPath);
|
|
4994
|
+
if (!result.success) {
|
|
4995
|
+
return failure(result.error);
|
|
4996
|
+
}
|
|
4997
|
+
return success(result.data.map((change) => ({
|
|
4998
|
+
id: change.id,
|
|
4999
|
+
title: change.title || null,
|
|
5000
|
+
status: change.status
|
|
5001
|
+
})));
|
|
5002
|
+
}
|
|
5003
|
+
async function getChangeInfo(changePath) {
|
|
5004
|
+
const proposalPath = path6.join(changePath, "proposal.md");
|
|
5005
|
+
if (!await fileExists(proposalPath)) {
|
|
5006
|
+
return failure(new Error("proposal.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
5007
|
+
}
|
|
5008
|
+
const contentResult = await readFile(proposalPath);
|
|
5009
|
+
if (!contentResult.success) {
|
|
5010
|
+
return failure(new Error("proposal.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
5011
|
+
}
|
|
5012
|
+
const parseResult = parseProposal(contentResult.data);
|
|
5013
|
+
if (!parseResult.success) {
|
|
5014
|
+
return failure(new Error(`proposal.md \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
|
|
5015
|
+
}
|
|
5016
|
+
return success({
|
|
5017
|
+
id: parseResult.data.metadata.id,
|
|
5018
|
+
title: parseResult.data.title,
|
|
5019
|
+
status: parseResult.data.metadata.status,
|
|
5020
|
+
created: parseResult.data.metadata.created,
|
|
5021
|
+
affectedSpecs: parseResult.data.affectedSpecs
|
|
4936
5022
|
});
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
5023
|
+
}
|
|
5024
|
+
async function createChange(sddPath, options) {
|
|
5025
|
+
const changesPath = path6.join(sddPath, "changes");
|
|
5026
|
+
await ensureDir(changesPath);
|
|
5027
|
+
const existingIds = [];
|
|
5028
|
+
try {
|
|
5029
|
+
const dirs = await fs3.readdir(changesPath);
|
|
5030
|
+
existingIds.push(...dirs.filter((d) => d.startsWith("CHG-")));
|
|
5031
|
+
} catch {
|
|
5032
|
+
}
|
|
5033
|
+
const newId = generateChangeId(existingIds);
|
|
5034
|
+
const title2 = options.title || "\uC0C8 \uBCC0\uACBD \uC81C\uC548";
|
|
5035
|
+
const affectedSpecs = options.spec ? [options.spec] : [];
|
|
5036
|
+
const changePath = path6.join(changesPath, newId);
|
|
5037
|
+
await ensureDir(changePath);
|
|
5038
|
+
const proposal = generateProposal({
|
|
5039
|
+
id: newId,
|
|
5040
|
+
title: title2,
|
|
5041
|
+
affectedSpecs
|
|
4944
5042
|
});
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
4951
|
-
}
|
|
5043
|
+
const proposalPath = path6.join(changePath, "proposal.md");
|
|
5044
|
+
await writeFile(proposalPath, proposal);
|
|
5045
|
+
const delta = generateDelta({
|
|
5046
|
+
proposalId: newId,
|
|
5047
|
+
title: title2
|
|
4952
5048
|
});
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
}
|
|
5049
|
+
const deltaPath = path6.join(changePath, "delta.md");
|
|
5050
|
+
await writeFile(deltaPath, delta);
|
|
5051
|
+
return success({
|
|
5052
|
+
id: newId,
|
|
5053
|
+
proposalPath,
|
|
5054
|
+
deltaPath
|
|
4960
5055
|
});
|
|
4961
5056
|
}
|
|
4962
|
-
async function
|
|
4963
|
-
const
|
|
4964
|
-
if (!
|
|
4965
|
-
|
|
4966
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
5057
|
+
async function applyChange(changePath) {
|
|
5058
|
+
const proposalPath = path6.join(changePath, "proposal.md");
|
|
5059
|
+
if (!await fileExists(proposalPath)) {
|
|
5060
|
+
return failure(new Error("proposal.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
4967
5061
|
}
|
|
4968
|
-
const
|
|
4969
|
-
if (
|
|
4970
|
-
|
|
5062
|
+
const contentResult = await readFile(proposalPath);
|
|
5063
|
+
if (!contentResult.success) {
|
|
5064
|
+
return failure(new Error("proposal.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
5065
|
+
}
|
|
5066
|
+
const updateResult = updateProposalStatus(contentResult.data, "applied");
|
|
5067
|
+
if (!updateResult.success) {
|
|
5068
|
+
return failure(new Error("proposal.md\uB97C \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
5069
|
+
}
|
|
5070
|
+
await writeFile(proposalPath, updateResult.data);
|
|
5071
|
+
return success(void 0);
|
|
5072
|
+
}
|
|
5073
|
+
async function getDeltaInfo(changePath) {
|
|
5074
|
+
const deltaPath = path6.join(changePath, "delta.md");
|
|
5075
|
+
if (!await fileExists(deltaPath)) {
|
|
5076
|
+
return failure(new Error("delta.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
5077
|
+
}
|
|
5078
|
+
const deltaResult = await readFile(deltaPath);
|
|
5079
|
+
if (!deltaResult.success) {
|
|
5080
|
+
return failure(new Error("delta.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
5081
|
+
}
|
|
5082
|
+
const parseResult = parseDelta(deltaResult.data);
|
|
5083
|
+
if (!parseResult.success) {
|
|
5084
|
+
return failure(new Error(`Delta \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
|
|
5085
|
+
}
|
|
5086
|
+
return success({
|
|
5087
|
+
added: parseResult.data.added,
|
|
5088
|
+
modified: parseResult.data.modified,
|
|
5089
|
+
removed: parseResult.data.removed
|
|
5090
|
+
});
|
|
5091
|
+
}
|
|
5092
|
+
async function validateChange(changePath) {
|
|
5093
|
+
const result = {
|
|
5094
|
+
proposalValid: false,
|
|
5095
|
+
deltaValid: false,
|
|
5096
|
+
hasDelta: false
|
|
5097
|
+
};
|
|
5098
|
+
const proposalPath = path6.join(changePath, "proposal.md");
|
|
5099
|
+
if (await fileExists(proposalPath)) {
|
|
5100
|
+
const proposalResult = await readFile(proposalPath);
|
|
5101
|
+
if (proposalResult.success) {
|
|
5102
|
+
const parsed = parseProposal(proposalResult.data);
|
|
5103
|
+
if (parsed.success) {
|
|
5104
|
+
result.proposalValid = true;
|
|
5105
|
+
result.proposalTitle = parsed.data.title;
|
|
5106
|
+
} else {
|
|
5107
|
+
result.proposalError = parsed.error.message;
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
} else {
|
|
5111
|
+
result.proposalError = "proposal.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
5112
|
+
}
|
|
5113
|
+
const deltaPath = path6.join(changePath, "delta.md");
|
|
5114
|
+
if (await fileExists(deltaPath)) {
|
|
5115
|
+
result.hasDelta = true;
|
|
5116
|
+
const deltaResult = await readFile(deltaPath);
|
|
5117
|
+
if (deltaResult.success) {
|
|
5118
|
+
const validation = validateDelta(deltaResult.data);
|
|
5119
|
+
if (validation.valid) {
|
|
5120
|
+
result.deltaValid = true;
|
|
5121
|
+
const types = [];
|
|
5122
|
+
if (validation.hasAdded) types.push("ADDED");
|
|
5123
|
+
if (validation.hasModified) types.push("MODIFIED");
|
|
5124
|
+
if (validation.hasRemoved) types.push("REMOVED");
|
|
5125
|
+
result.deltaTypes = types;
|
|
5126
|
+
result.deltaWarnings = validation.warnings;
|
|
5127
|
+
} else {
|
|
5128
|
+
result.deltaErrors = validation.errors;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
return success(result);
|
|
5133
|
+
}
|
|
5134
|
+
function registerChangeCommand(program2) {
|
|
5135
|
+
const change = program2.command("change [id]").description("\uBCC0\uACBD \uC81C\uC548\uC744 \uC0DD\uC131\uD558\uAC70\uB098 \uAD00\uB9AC\uD569\uB2C8\uB2E4").option("-l, --list", "\uC9C4\uD589 \uC911\uC778 \uBCC0\uACBD \uBAA9\uB85D").option("-t, --title <title>", "\uBCC0\uACBD \uC81C\uC548 \uC81C\uBAA9").option("-s, --spec <spec>", "\uB300\uC0C1 \uC2A4\uD399 \uACBD\uB85C").action(async (id, options) => {
|
|
5136
|
+
try {
|
|
5137
|
+
await runChange(id, options);
|
|
5138
|
+
} catch (error2) {
|
|
5139
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
5140
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5141
|
+
}
|
|
5142
|
+
});
|
|
5143
|
+
change.command("apply <id>").description("\uBCC0\uACBD \uC81C\uC548\uC744 \uC2A4\uD399\uC5D0 \uC801\uC6A9\uD569\uB2C8\uB2E4").action(async (id) => {
|
|
5144
|
+
try {
|
|
5145
|
+
await runApply(id);
|
|
5146
|
+
} catch (error2) {
|
|
5147
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
5148
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5149
|
+
}
|
|
5150
|
+
});
|
|
5151
|
+
change.command("archive <id>").description("\uC644\uB8CC\uB41C \uBCC0\uACBD\uC744 \uC544\uCE74\uC774\uBE0C\uD569\uB2C8\uB2E4").action(async (id) => {
|
|
5152
|
+
try {
|
|
5153
|
+
await runArchive(id);
|
|
5154
|
+
} catch (error2) {
|
|
5155
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
5156
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5157
|
+
}
|
|
5158
|
+
});
|
|
5159
|
+
change.command("diff <id>").description("\uBCC0\uACBD \uC81C\uC548\uC758 diff\uB97C \uD45C\uC2DC\uD569\uB2C8\uB2E4").action(async (id) => {
|
|
5160
|
+
try {
|
|
5161
|
+
await runDiff(id);
|
|
5162
|
+
} catch (error2) {
|
|
5163
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
5164
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5165
|
+
}
|
|
5166
|
+
});
|
|
5167
|
+
change.command("validate <id>").description("\uBCC0\uACBD \uC81C\uC548\uC758 \uC720\uD6A8\uC131\uC744 \uAC80\uC99D\uD569\uB2C8\uB2E4").action(async (id) => {
|
|
5168
|
+
try {
|
|
5169
|
+
await runValidateChange(id);
|
|
5170
|
+
} catch (error2) {
|
|
5171
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
5172
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5173
|
+
}
|
|
5174
|
+
});
|
|
5175
|
+
}
|
|
5176
|
+
async function runChange(id, options) {
|
|
5177
|
+
const projectRoot = await findSddRoot();
|
|
5178
|
+
if (!projectRoot) {
|
|
5179
|
+
error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
5180
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5181
|
+
}
|
|
5182
|
+
const sddPath = path6.join(projectRoot, ".sdd");
|
|
5183
|
+
if (options.list) {
|
|
5184
|
+
const result = await getChangeListItems(sddPath);
|
|
4971
5185
|
if (!result.success) {
|
|
4972
5186
|
error(result.error.message);
|
|
4973
5187
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
@@ -4985,53 +5199,31 @@ async function runChange(id, options) {
|
|
|
4985
5199
|
return;
|
|
4986
5200
|
}
|
|
4987
5201
|
if (id) {
|
|
4988
|
-
const
|
|
4989
|
-
if (!await directoryExists(
|
|
5202
|
+
const changePath = path6.join(sddPath, "changes", id);
|
|
5203
|
+
if (!await directoryExists(changePath)) {
|
|
4990
5204
|
error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
|
|
4991
5205
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
4992
5206
|
}
|
|
4993
|
-
const
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
info(
|
|
5000
|
-
|
|
5001
|
-
if (parseResult.data.affectedSpecs.length > 0) {
|
|
5002
|
-
info("\uC601\uD5A5 \uC2A4\uD399:");
|
|
5003
|
-
parseResult.data.affectedSpecs.forEach((spec) => listItem(spec, 1));
|
|
5004
|
-
}
|
|
5207
|
+
const infoResult = await getChangeInfo(changePath);
|
|
5208
|
+
if (infoResult.success) {
|
|
5209
|
+
info(`\uBCC0\uACBD \uC81C\uC548: ${infoResult.data.title}`);
|
|
5210
|
+
info(`\uC0C1\uD0DC: ${infoResult.data.status}`);
|
|
5211
|
+
info(`\uC0DD\uC131: ${infoResult.data.created}`);
|
|
5212
|
+
if (infoResult.data.affectedSpecs.length > 0) {
|
|
5213
|
+
info("\uC601\uD5A5 \uC2A4\uD399:");
|
|
5214
|
+
infoResult.data.affectedSpecs.forEach((spec) => listItem(spec, 1));
|
|
5005
5215
|
}
|
|
5006
|
-
}
|
|
5007
|
-
error(
|
|
5216
|
+
} else {
|
|
5217
|
+
error(infoResult.error.message);
|
|
5008
5218
|
}
|
|
5009
5219
|
return;
|
|
5010
5220
|
}
|
|
5011
|
-
const
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
const dirs = await fs3.readdir(changesPath);
|
|
5016
|
-
existingIds.push(...dirs.filter((d) => d.startsWith("CHG-")));
|
|
5017
|
-
} catch {
|
|
5221
|
+
const createResult = await createChange(sddPath, options);
|
|
5222
|
+
if (!createResult.success) {
|
|
5223
|
+
error(createResult.error.message);
|
|
5224
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5018
5225
|
}
|
|
5019
|
-
const newId =
|
|
5020
|
-
const title2 = options.title || "\uC0C8 \uBCC0\uACBD \uC81C\uC548";
|
|
5021
|
-
const affectedSpecs = options.spec ? [options.spec] : [];
|
|
5022
|
-
const changePath = path6.join(changesPath, newId);
|
|
5023
|
-
await ensureDir(changePath);
|
|
5024
|
-
const proposal = generateProposal({
|
|
5025
|
-
id: newId,
|
|
5026
|
-
title: title2,
|
|
5027
|
-
affectedSpecs
|
|
5028
|
-
});
|
|
5029
|
-
await writeFile(path6.join(changePath, "proposal.md"), proposal);
|
|
5030
|
-
const delta = generateDelta({
|
|
5031
|
-
proposalId: newId,
|
|
5032
|
-
title: title2
|
|
5033
|
-
});
|
|
5034
|
-
await writeFile(path6.join(changePath, "delta.md"), delta);
|
|
5226
|
+
const newId = createResult.data.id;
|
|
5035
5227
|
success2(`\uBCC0\uACBD \uC81C\uC548\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${newId}`);
|
|
5036
5228
|
newline();
|
|
5037
5229
|
info("\uC0DD\uC131\uB41C \uD30C\uC77C:");
|
|
@@ -5055,15 +5247,9 @@ async function runApply(id) {
|
|
|
5055
5247
|
error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
|
|
5056
5248
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
5057
5249
|
}
|
|
5058
|
-
const
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
const updateResult = updateProposalStatus(content, "applied");
|
|
5062
|
-
if (updateResult.success) {
|
|
5063
|
-
await fs3.writeFile(proposalPath, updateResult.data);
|
|
5064
|
-
}
|
|
5065
|
-
} catch {
|
|
5066
|
-
error("proposal.md\uB97C \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
5250
|
+
const result = await applyChange(changePath);
|
|
5251
|
+
if (!result.success) {
|
|
5252
|
+
error(result.error.message);
|
|
5067
5253
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
5068
5254
|
}
|
|
5069
5255
|
success2(`\uBCC0\uACBD\uC774 \uC801\uC6A9 \uC0C1\uD0DC\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${id}`);
|
|
@@ -5099,22 +5285,12 @@ async function runDiff(id) {
|
|
|
5099
5285
|
error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
|
|
5100
5286
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
5101
5287
|
}
|
|
5102
|
-
const
|
|
5103
|
-
if (!await fileExists(deltaPath)) {
|
|
5104
|
-
error("delta.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
5105
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
5106
|
-
}
|
|
5107
|
-
const deltaResult = await readFile(deltaPath);
|
|
5288
|
+
const deltaResult = await getDeltaInfo(changePath);
|
|
5108
5289
|
if (!deltaResult.success) {
|
|
5109
|
-
error(
|
|
5290
|
+
error(deltaResult.error.message);
|
|
5110
5291
|
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
5111
5292
|
}
|
|
5112
|
-
const
|
|
5113
|
-
if (!parseResult.success) {
|
|
5114
|
-
error(`Delta \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
|
|
5115
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
5116
|
-
}
|
|
5117
|
-
const delta = parseResult.data;
|
|
5293
|
+
const delta = deltaResult.data;
|
|
5118
5294
|
info(`\uBCC0\uACBD Diff: ${id}`);
|
|
5119
5295
|
newline();
|
|
5120
5296
|
if (delta.added.length > 0 && delta.added[0].content !== "(\uCD94\uAC00\uB418\uB294 \uC2A4\uD399 \uB0B4\uC6A9)") {
|
|
@@ -5162,44 +5338,35 @@ async function runValidateChange(id) {
|
|
|
5162
5338
|
error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
|
|
5163
5339
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
5164
5340
|
}
|
|
5341
|
+
const validationResult = await validateChange(changePath);
|
|
5342
|
+
if (!validationResult.success) {
|
|
5343
|
+
error(validationResult.error.message);
|
|
5344
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
5345
|
+
}
|
|
5346
|
+
const result = validationResult.data;
|
|
5165
5347
|
let hasErrors = false;
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
const proposalResult = await readFile(proposalPath);
|
|
5169
|
-
if (proposalResult.success) {
|
|
5170
|
-
const parsed = parseProposal(proposalResult.data);
|
|
5171
|
-
if (parsed.success) {
|
|
5172
|
-
success2(`\u2713 proposal.md \uC720\uD6A8 (${parsed.data.title})`);
|
|
5173
|
-
} else {
|
|
5174
|
-
error(`\u2717 proposal.md \uC624\uB958: ${parsed.error.message}`);
|
|
5175
|
-
hasErrors = true;
|
|
5176
|
-
}
|
|
5177
|
-
}
|
|
5348
|
+
if (result.proposalValid) {
|
|
5349
|
+
success2(`\u2713 proposal.md \uC720\uD6A8 (${result.proposalTitle})`);
|
|
5178
5350
|
} else {
|
|
5179
|
-
error(
|
|
5351
|
+
error(`\u2717 proposal.md \uC624\uB958: ${result.proposalError}`);
|
|
5180
5352
|
hasErrors = true;
|
|
5181
5353
|
}
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
if (validation.valid) {
|
|
5188
|
-
const types = [];
|
|
5189
|
-
if (validation.hasAdded) types.push("ADDED");
|
|
5190
|
-
if (validation.hasModified) types.push("MODIFIED");
|
|
5191
|
-
if (validation.hasRemoved) types.push("REMOVED");
|
|
5192
|
-
success2(`\u2713 delta.md \uC720\uD6A8 (${types.join(", ")})`);
|
|
5193
|
-
for (const warning of validation.warnings) {
|
|
5354
|
+
if (result.hasDelta) {
|
|
5355
|
+
if (result.deltaValid) {
|
|
5356
|
+
success2(`\u2713 delta.md \uC720\uD6A8 (${result.deltaTypes?.join(", ")})`);
|
|
5357
|
+
if (result.deltaWarnings) {
|
|
5358
|
+
for (const warning of result.deltaWarnings) {
|
|
5194
5359
|
warn(` \u26A0 ${warning}`);
|
|
5195
5360
|
}
|
|
5196
|
-
}
|
|
5197
|
-
|
|
5198
|
-
|
|
5361
|
+
}
|
|
5362
|
+
} else {
|
|
5363
|
+
error(`\u2717 delta.md \uC624\uB958:`);
|
|
5364
|
+
if (result.deltaErrors) {
|
|
5365
|
+
for (const error2 of result.deltaErrors) {
|
|
5199
5366
|
error(` - ${error2}`);
|
|
5200
5367
|
}
|
|
5201
|
-
hasErrors = true;
|
|
5202
5368
|
}
|
|
5369
|
+
hasErrors = true;
|
|
5203
5370
|
}
|
|
5204
5371
|
} else {
|
|
5205
5372
|
warn("\u26A0 delta.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
@@ -5207,7 +5374,7 @@ async function runValidateChange(id) {
|
|
|
5207
5374
|
newline();
|
|
5208
5375
|
if (hasErrors) {
|
|
5209
5376
|
error(`\uAC80\uC99D \uC2E4\uD328: ${id}`);
|
|
5210
|
-
process.exit(ExitCode.
|
|
5377
|
+
process.exit(ExitCode.VALIDATION_FAILED);
|
|
5211
5378
|
} else {
|
|
5212
5379
|
success2(`\uAC80\uC99D \uD1B5\uACFC: ${id}`);
|
|
5213
5380
|
}
|
|
@@ -6635,6 +6802,108 @@ function formatCodeImpactResult(result) {
|
|
|
6635
6802
|
// src/cli/commands/impact.ts
|
|
6636
6803
|
init_fs();
|
|
6637
6804
|
init_errors();
|
|
6805
|
+
init_types();
|
|
6806
|
+
function resolveProposalPath(proposalPath, sddPath) {
|
|
6807
|
+
if (path10.isAbsolute(proposalPath)) {
|
|
6808
|
+
return proposalPath;
|
|
6809
|
+
}
|
|
6810
|
+
const changesPath = path10.join(sddPath, "changes", proposalPath);
|
|
6811
|
+
if (proposalPath.endsWith(".md")) {
|
|
6812
|
+
return changesPath;
|
|
6813
|
+
}
|
|
6814
|
+
return path10.join(changesPath, "proposal.md");
|
|
6815
|
+
}
|
|
6816
|
+
async function executeImpactAnalysis(sddPath, feature) {
|
|
6817
|
+
const result = await analyzeImpact(sddPath, feature);
|
|
6818
|
+
if (!result.success) {
|
|
6819
|
+
return failure(result.error);
|
|
6820
|
+
}
|
|
6821
|
+
return success(result.data);
|
|
6822
|
+
}
|
|
6823
|
+
async function executeGraphAnalysis(specsPath, feature, asJson = false) {
|
|
6824
|
+
const graphResult = await buildDependencyGraph(specsPath);
|
|
6825
|
+
if (!graphResult.success) {
|
|
6826
|
+
return failure(graphResult.error);
|
|
6827
|
+
}
|
|
6828
|
+
const mermaid = generateMermaidGraph(graphResult.data, feature);
|
|
6829
|
+
if (asJson) {
|
|
6830
|
+
return success({
|
|
6831
|
+
mermaid,
|
|
6832
|
+
nodes: Array.from(graphResult.data.nodes.values()),
|
|
6833
|
+
edges: graphResult.data.edges
|
|
6834
|
+
});
|
|
6835
|
+
}
|
|
6836
|
+
return success({ mermaid });
|
|
6837
|
+
}
|
|
6838
|
+
async function executeCodeImpactAnalysis(projectRoot, sddPath, feature) {
|
|
6839
|
+
const result = await analyzeCodeImpact(projectRoot, sddPath, feature);
|
|
6840
|
+
if (!result.success) {
|
|
6841
|
+
return failure(result.error);
|
|
6842
|
+
}
|
|
6843
|
+
return success(result.data);
|
|
6844
|
+
}
|
|
6845
|
+
async function executeImpactReport(sddPath) {
|
|
6846
|
+
const result = await generateImpactReport(sddPath);
|
|
6847
|
+
if (!result.success) {
|
|
6848
|
+
return failure(result.error);
|
|
6849
|
+
}
|
|
6850
|
+
return success(result.data);
|
|
6851
|
+
}
|
|
6852
|
+
async function executeChangeImpact(sddPath, changeId) {
|
|
6853
|
+
const result = await analyzeChangeImpact(sddPath, changeId);
|
|
6854
|
+
if (!result.success) {
|
|
6855
|
+
return failure(result.error);
|
|
6856
|
+
}
|
|
6857
|
+
return success(result.data);
|
|
6858
|
+
}
|
|
6859
|
+
function formatChangeImpactOutput(data) {
|
|
6860
|
+
const lines = [];
|
|
6861
|
+
lines.push(`\u{1F4CA} \uBCC0\uACBD \uC601\uD5A5 \uBD84\uC11D: ${data.changeId}`);
|
|
6862
|
+
if (data.title) {
|
|
6863
|
+
lines.push(`\uC81C\uBAA9: ${data.title}`);
|
|
6864
|
+
}
|
|
6865
|
+
lines.push(`\uC0C1\uD0DC: ${data.status}`);
|
|
6866
|
+
lines.push("");
|
|
6867
|
+
if (data.affectedSpecs.length > 0) {
|
|
6868
|
+
lines.push("\u26A0\uFE0F \uC9C1\uC811 \uC601\uD5A5 \uBC1B\uB294 \uC2A4\uD399:");
|
|
6869
|
+
for (const spec of data.affectedSpecs) {
|
|
6870
|
+
lines.push(` - ${spec.id} - ${spec.reason}`);
|
|
6871
|
+
}
|
|
6872
|
+
lines.push("");
|
|
6873
|
+
}
|
|
6874
|
+
if (data.transitiveAffected.length > 0) {
|
|
6875
|
+
lines.push("\u{1F504} \uAC04\uC811 \uC601\uD5A5 \uBC1B\uB294 \uC2A4\uD399:");
|
|
6876
|
+
for (const spec of data.transitiveAffected) {
|
|
6877
|
+
lines.push(` - ${spec.id} (${spec.reason})`);
|
|
6878
|
+
}
|
|
6879
|
+
lines.push("");
|
|
6880
|
+
}
|
|
6881
|
+
const riskIcon = data.riskLevel === "high" ? "\u{1F534}" : data.riskLevel === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6882
|
+
lines.push(`\uCD1D \uC601\uD5A5 \uBC94\uC704: ${data.totalImpact}\uAC1C \uC2A4\uD399 ${riskIcon}`);
|
|
6883
|
+
lines.push("");
|
|
6884
|
+
if (data.recommendations.length > 0) {
|
|
6885
|
+
lines.push("\u{1F4A1} \uAD8C\uC7A5\uC0AC\uD56D:");
|
|
6886
|
+
for (const rec of data.recommendations) {
|
|
6887
|
+
lines.push(` - ${rec}`);
|
|
6888
|
+
}
|
|
6889
|
+
}
|
|
6890
|
+
return lines.join("\n");
|
|
6891
|
+
}
|
|
6892
|
+
async function executeSimulation(specsPath, feature, proposalPath) {
|
|
6893
|
+
const deltaResult = await parseDeltaFromProposal(proposalPath);
|
|
6894
|
+
if (!deltaResult.success) {
|
|
6895
|
+
return failure(deltaResult.error);
|
|
6896
|
+
}
|
|
6897
|
+
const deltas = deltaResult.data;
|
|
6898
|
+
if (deltas.length === 0) {
|
|
6899
|
+
return failure(new Error("\uBCC0\uACBD \uC81C\uC548\uC5D0\uC11C \uB378\uD0C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
6900
|
+
}
|
|
6901
|
+
const simResult = await runSimulation(specsPath, feature, deltas);
|
|
6902
|
+
if (!simResult.success) {
|
|
6903
|
+
return failure(simResult.error);
|
|
6904
|
+
}
|
|
6905
|
+
return success({ deltas, result: simResult.data });
|
|
6906
|
+
}
|
|
6638
6907
|
function registerImpactCommand(program2) {
|
|
6639
6908
|
const impact = program2.command("impact [feature]").description("\uC2A4\uD399 \uBCC0\uACBD\uC758 \uC601\uD5A5\uB3C4\uB97C \uBD84\uC11D\uD569\uB2C8\uB2E4").option("-g, --graph", "\uC758\uC874\uC131 \uADF8\uB798\uD504 \uCD9C\uB825 (Mermaid)").option("-r, --reverse", "\uC5ED\uBC29\uD5A5 \uC601\uD5A5\uB3C4 \uBD84\uC11D").option("-c, --code", "\uCF54\uB4DC \uC601\uD5A5\uB3C4 \uBD84\uC11D").option("--json", "JSON \uD615\uC2DD \uCD9C\uB825").action(async (feature, options) => {
|
|
6640
6909
|
try {
|
|
@@ -6676,25 +6945,25 @@ async function runImpact(feature, options) {
|
|
|
6676
6945
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
6677
6946
|
}
|
|
6678
6947
|
const sddPath = path10.join(projectRoot, ".sdd");
|
|
6948
|
+
const specsPath = path10.join(sddPath, "specs");
|
|
6679
6949
|
if (options.graph) {
|
|
6680
|
-
const graphResult = await
|
|
6950
|
+
const graphResult = await executeGraphAnalysis(specsPath, feature, options.json);
|
|
6681
6951
|
if (!graphResult.success) {
|
|
6682
6952
|
error(graphResult.error.message);
|
|
6683
6953
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
6684
6954
|
}
|
|
6685
|
-
const mermaid = generateMermaidGraph(graphResult.data, feature);
|
|
6686
6955
|
if (options.json) {
|
|
6687
6956
|
console.log(JSON.stringify({
|
|
6688
6957
|
format: "mermaid",
|
|
6689
|
-
content: mermaid,
|
|
6690
|
-
nodes:
|
|
6958
|
+
content: graphResult.data.mermaid,
|
|
6959
|
+
nodes: graphResult.data.nodes,
|
|
6691
6960
|
edges: graphResult.data.edges
|
|
6692
6961
|
}, null, 2));
|
|
6693
6962
|
} else {
|
|
6694
6963
|
info("\uC758\uC874\uC131 \uADF8\uB798\uD504 (Mermaid):");
|
|
6695
6964
|
newline();
|
|
6696
6965
|
console.log("```mermaid");
|
|
6697
|
-
console.log(mermaid);
|
|
6966
|
+
console.log(graphResult.data.mermaid);
|
|
6698
6967
|
console.log("```");
|
|
6699
6968
|
}
|
|
6700
6969
|
return;
|
|
@@ -6708,7 +6977,7 @@ async function runImpact(feature, options) {
|
|
|
6708
6977
|
}
|
|
6709
6978
|
info(`\u{1F4BB} \uCF54\uB4DC \uC601\uD5A5\uB3C4 \uBD84\uC11D: ${feature}`);
|
|
6710
6979
|
newline();
|
|
6711
|
-
const codeResult = await
|
|
6980
|
+
const codeResult = await executeCodeImpactAnalysis(projectRoot, sddPath, feature);
|
|
6712
6981
|
if (!codeResult.success) {
|
|
6713
6982
|
error(codeResult.error.message);
|
|
6714
6983
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
@@ -6726,7 +6995,7 @@ async function runImpact(feature, options) {
|
|
|
6726
6995
|
info("\uC608\uC2DC: sdd impact auth");
|
|
6727
6996
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
6728
6997
|
}
|
|
6729
|
-
const result = await
|
|
6998
|
+
const result = await executeImpactAnalysis(sddPath, feature);
|
|
6730
6999
|
if (!result.success) {
|
|
6731
7000
|
error(result.error.message);
|
|
6732
7001
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
@@ -6744,7 +7013,7 @@ async function runImpactReport(options) {
|
|
|
6744
7013
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
6745
7014
|
}
|
|
6746
7015
|
const sddPath = path10.join(projectRoot, ".sdd");
|
|
6747
|
-
const result = await
|
|
7016
|
+
const result = await executeImpactReport(sddPath);
|
|
6748
7017
|
if (!result.success) {
|
|
6749
7018
|
error(result.error.message);
|
|
6750
7019
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
@@ -6762,7 +7031,7 @@ async function runChangeImpact(changeId, options) {
|
|
|
6762
7031
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
6763
7032
|
}
|
|
6764
7033
|
const sddPath = path10.join(projectRoot, ".sdd");
|
|
6765
|
-
const result = await
|
|
7034
|
+
const result = await executeChangeImpact(sddPath, changeId);
|
|
6766
7035
|
if (!result.success) {
|
|
6767
7036
|
error(result.error.message);
|
|
6768
7037
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
@@ -6770,36 +7039,7 @@ async function runChangeImpact(changeId, options) {
|
|
|
6770
7039
|
if (options.json) {
|
|
6771
7040
|
console.log(JSON.stringify(result.data, null, 2));
|
|
6772
7041
|
} else {
|
|
6773
|
-
|
|
6774
|
-
info(`\u{1F4CA} \uBCC0\uACBD \uC601\uD5A5 \uBD84\uC11D: ${data.changeId}`);
|
|
6775
|
-
if (data.title) {
|
|
6776
|
-
info(`\uC81C\uBAA9: ${data.title}`);
|
|
6777
|
-
}
|
|
6778
|
-
info(`\uC0C1\uD0DC: ${data.status}`);
|
|
6779
|
-
newline();
|
|
6780
|
-
if (data.affectedSpecs.length > 0) {
|
|
6781
|
-
info("\u26A0\uFE0F \uC9C1\uC811 \uC601\uD5A5 \uBC1B\uB294 \uC2A4\uD399:");
|
|
6782
|
-
for (const spec of data.affectedSpecs) {
|
|
6783
|
-
listItem(`${spec.id} - ${spec.reason}`, 1);
|
|
6784
|
-
}
|
|
6785
|
-
newline();
|
|
6786
|
-
}
|
|
6787
|
-
if (data.transitiveAffected.length > 0) {
|
|
6788
|
-
info("\u{1F504} \uAC04\uC811 \uC601\uD5A5 \uBC1B\uB294 \uC2A4\uD399:");
|
|
6789
|
-
for (const spec of data.transitiveAffected) {
|
|
6790
|
-
listItem(`${spec.id} (${spec.reason})`, 1);
|
|
6791
|
-
}
|
|
6792
|
-
newline();
|
|
6793
|
-
}
|
|
6794
|
-
const riskIcon = data.riskLevel === "high" ? "\u{1F534}" : data.riskLevel === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
6795
|
-
info(`\uCD1D \uC601\uD5A5 \uBC94\uC704: ${data.totalImpact}\uAC1C \uC2A4\uD399 ${riskIcon}`);
|
|
6796
|
-
newline();
|
|
6797
|
-
if (data.recommendations.length > 0) {
|
|
6798
|
-
info("\u{1F4A1} \uAD8C\uC7A5\uC0AC\uD56D:");
|
|
6799
|
-
for (const rec of data.recommendations) {
|
|
6800
|
-
listItem(rec, 1);
|
|
6801
|
-
}
|
|
6802
|
-
}
|
|
7042
|
+
console.log(formatChangeImpactOutput(result.data));
|
|
6803
7043
|
}
|
|
6804
7044
|
}
|
|
6805
7045
|
async function runSimulate(feature, proposalPath, options) {
|
|
@@ -6810,65 +7050,208 @@ async function runSimulate(feature, proposalPath, options) {
|
|
|
6810
7050
|
}
|
|
6811
7051
|
const sddPath = path10.join(projectRoot, ".sdd");
|
|
6812
7052
|
const specsPath = path10.join(sddPath, "specs");
|
|
6813
|
-
|
|
6814
|
-
if (!path10.isAbsolute(proposalPath)) {
|
|
6815
|
-
const changesPath = path10.join(sddPath, "changes", proposalPath);
|
|
6816
|
-
if (proposalPath.endsWith(".md")) {
|
|
6817
|
-
fullProposalPath = changesPath;
|
|
6818
|
-
} else {
|
|
6819
|
-
fullProposalPath = path10.join(changesPath, "proposal.md");
|
|
6820
|
-
}
|
|
6821
|
-
}
|
|
7053
|
+
const fullProposalPath = resolveProposalPath(proposalPath, sddPath);
|
|
6822
7054
|
info(`\u{1F4CA} What-if \uC2DC\uBBAC\uB808\uC774\uC158`);
|
|
6823
7055
|
info(`\uB300\uC0C1 \uC2A4\uD399: ${feature}`);
|
|
6824
7056
|
info(`\uBCC0\uACBD \uC81C\uC548: ${fullProposalPath}`);
|
|
6825
7057
|
newline();
|
|
6826
|
-
const
|
|
6827
|
-
if (!
|
|
6828
|
-
|
|
7058
|
+
const result = await executeSimulation(specsPath, feature, fullProposalPath);
|
|
7059
|
+
if (!result.success) {
|
|
7060
|
+
if (result.error.message.includes("\uB378\uD0C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4")) {
|
|
7061
|
+
warn(result.error.message);
|
|
7062
|
+
info("ADDED, MODIFIED, REMOVED \uC139\uC158\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
7063
|
+
return;
|
|
7064
|
+
}
|
|
7065
|
+
error(result.error.message);
|
|
6829
7066
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
6830
7067
|
}
|
|
6831
|
-
const deltas =
|
|
6832
|
-
if (deltas.length === 0) {
|
|
6833
|
-
warn("\uBCC0\uACBD \uC81C\uC548\uC5D0\uC11C \uB378\uD0C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6834
|
-
info("ADDED, MODIFIED, REMOVED \uC139\uC158\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
6835
|
-
return;
|
|
6836
|
-
}
|
|
7068
|
+
const { deltas, result: simResult } = result.data;
|
|
6837
7069
|
info(`\uAC10\uC9C0\uB41C \uBCC0\uACBD: ${deltas.length}\uAC74`);
|
|
6838
7070
|
for (const delta of deltas) {
|
|
6839
7071
|
const icon = delta.type === "ADDED" ? "\u2795" : delta.type === "REMOVED" ? "\u2796" : "\u270F\uFE0F";
|
|
6840
7072
|
listItem(`${icon} ${delta.type}: ${delta.specId}`, 1);
|
|
6841
7073
|
}
|
|
6842
7074
|
newline();
|
|
6843
|
-
const simResult = await runSimulation(specsPath, feature, deltas);
|
|
6844
|
-
if (!simResult.success) {
|
|
6845
|
-
error(simResult.error.message);
|
|
6846
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
6847
|
-
}
|
|
6848
7075
|
if (options.json) {
|
|
6849
|
-
console.log(JSON.stringify(simResult
|
|
7076
|
+
console.log(JSON.stringify(simResult, null, 2));
|
|
6850
7077
|
} else {
|
|
6851
|
-
console.log(formatSimulationResult(simResult
|
|
7078
|
+
console.log(formatSimulationResult(simResult, feature));
|
|
6852
7079
|
}
|
|
6853
7080
|
}
|
|
6854
7081
|
|
|
6855
7082
|
// src/cli/commands/new.ts
|
|
6856
7083
|
init_new();
|
|
6857
7084
|
import path12 from "path";
|
|
6858
|
-
import { promises as fs7 } from "fs";
|
|
6859
7085
|
|
|
6860
7086
|
// src/utils/index.ts
|
|
6861
7087
|
init_fs();
|
|
6862
7088
|
|
|
6863
7089
|
// src/cli/commands/new.ts
|
|
6864
7090
|
init_fs();
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
7091
|
+
init_types();
|
|
7092
|
+
async function getConstitutionVersion(sddPath) {
|
|
7093
|
+
const constitutionPath = path12.join(sddPath, "constitution.md");
|
|
7094
|
+
if (!await fileExists(constitutionPath)) {
|
|
7095
|
+
return void 0;
|
|
7096
|
+
}
|
|
7097
|
+
const constResult = await readFile(constitutionPath);
|
|
7098
|
+
if (!constResult.success) {
|
|
7099
|
+
return void 0;
|
|
7100
|
+
}
|
|
7101
|
+
const parseResult = parseConstitution(constResult.data);
|
|
7102
|
+
if (!parseResult.success) {
|
|
7103
|
+
return void 0;
|
|
7104
|
+
}
|
|
7105
|
+
return parseResult.data.metadata.version;
|
|
7106
|
+
}
|
|
7107
|
+
async function createFeature(sddPath, name, options) {
|
|
7108
|
+
let featureId;
|
|
7109
|
+
let branchName;
|
|
7110
|
+
if (options.numbered) {
|
|
7111
|
+
const numberResult = await getNextFeatureNumber(sddPath, name);
|
|
7112
|
+
if (!numberResult.success) {
|
|
7113
|
+
return failure(new Error(`\uBC88\uD638 \uC0DD\uC131 \uC2E4\uD328: ${numberResult.error.message}`));
|
|
7114
|
+
}
|
|
7115
|
+
featureId = numberResult.data.fullId;
|
|
7116
|
+
branchName = numberResult.data.branchName;
|
|
7117
|
+
} else {
|
|
7118
|
+
featureId = generateFeatureId(name);
|
|
7119
|
+
}
|
|
7120
|
+
const title2 = options.title || name;
|
|
7121
|
+
const description = options.description || `${title2} \uAE30\uB2A5 \uBA85\uC138`;
|
|
7122
|
+
const featurePath = path12.join(sddPath, "specs", featureId);
|
|
7123
|
+
const dirResult = await ensureDir(featurePath);
|
|
7124
|
+
if (!dirResult.success) {
|
|
7125
|
+
return failure(new Error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${featurePath}`));
|
|
7126
|
+
}
|
|
7127
|
+
const constitutionVersion = await getConstitutionVersion(sddPath);
|
|
7128
|
+
const filesCreated = [];
|
|
7129
|
+
const specContent = generateSpec({
|
|
7130
|
+
id: featureId,
|
|
7131
|
+
title: title2,
|
|
7132
|
+
description,
|
|
7133
|
+
constitutionVersion
|
|
7134
|
+
});
|
|
7135
|
+
await writeFile(path12.join(featurePath, "spec.md"), specContent);
|
|
7136
|
+
filesCreated.push("spec.md");
|
|
7137
|
+
if (options.plan || options.all) {
|
|
7138
|
+
const planContent = generatePlan({
|
|
7139
|
+
featureId,
|
|
7140
|
+
featureTitle: title2,
|
|
7141
|
+
overview: description
|
|
7142
|
+
});
|
|
7143
|
+
await writeFile(path12.join(featurePath, "plan.md"), planContent);
|
|
7144
|
+
filesCreated.push("plan.md");
|
|
7145
|
+
}
|
|
7146
|
+
if (options.tasks || options.all) {
|
|
7147
|
+
const tasksContent = generateTasks({
|
|
7148
|
+
featureId,
|
|
7149
|
+
featureTitle: title2,
|
|
7150
|
+
tasks: [
|
|
7151
|
+
{ title: "\uAE30\uBC18 \uAD6C\uC870 \uC124\uC815", priority: "high" },
|
|
7152
|
+
{ title: "\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604", priority: "high" },
|
|
7153
|
+
{ title: "\uD14C\uC2A4\uD2B8 \uC791\uC131", priority: "medium" },
|
|
7154
|
+
{ title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
|
|
7155
|
+
]
|
|
7156
|
+
});
|
|
7157
|
+
await writeFile(path12.join(featurePath, "tasks.md"), tasksContent);
|
|
7158
|
+
filesCreated.push("tasks.md");
|
|
7159
|
+
}
|
|
7160
|
+
if (options.checklist || options.all) {
|
|
7161
|
+
const checklistContent = generateFullChecklistMarkdown();
|
|
7162
|
+
await writeFile(path12.join(featurePath, "checklist.md"), checklistContent);
|
|
7163
|
+
filesCreated.push("checklist.md");
|
|
7164
|
+
}
|
|
7165
|
+
return success({
|
|
7166
|
+
featureId,
|
|
7167
|
+
featurePath,
|
|
7168
|
+
branchName,
|
|
7169
|
+
filesCreated
|
|
7170
|
+
});
|
|
7171
|
+
}
|
|
7172
|
+
async function createPlan(featurePath, featureId, title2) {
|
|
7173
|
+
if (!await fileExists(featurePath)) {
|
|
7174
|
+
return failure(new Error(`\uAE30\uB2A5 '${featureId}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
|
|
7175
|
+
}
|
|
7176
|
+
let featureTitle = title2 || featureId;
|
|
7177
|
+
const specPath = path12.join(featurePath, "spec.md");
|
|
7178
|
+
if (await fileExists(specPath)) {
|
|
7179
|
+
const specResult = await readFile(specPath);
|
|
7180
|
+
if (specResult.success) {
|
|
7181
|
+
const titleMatch = specResult.data.match(/title:\s*"?([^"\n]+)"?/);
|
|
7182
|
+
if (titleMatch) {
|
|
7183
|
+
featureTitle = titleMatch[1];
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
7186
|
+
}
|
|
7187
|
+
const planContent = generatePlan({
|
|
7188
|
+
featureId,
|
|
7189
|
+
featureTitle,
|
|
7190
|
+
overview: `${featureTitle} \uAD6C\uD604 \uACC4\uD68D`
|
|
7191
|
+
});
|
|
7192
|
+
const planPath = path12.join(featurePath, "plan.md");
|
|
7193
|
+
await writeFile(planPath, planContent);
|
|
7194
|
+
return success(planPath);
|
|
7195
|
+
}
|
|
7196
|
+
async function createTasks(featurePath, featureId) {
|
|
7197
|
+
if (!await fileExists(featurePath)) {
|
|
7198
|
+
return failure(new Error(`\uAE30\uB2A5 '${featureId}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
|
|
7199
|
+
}
|
|
7200
|
+
let featureTitle = featureId;
|
|
7201
|
+
const specPath = path12.join(featurePath, "spec.md");
|
|
7202
|
+
if (await fileExists(specPath)) {
|
|
7203
|
+
const specResult = await readFile(specPath);
|
|
7204
|
+
if (specResult.success) {
|
|
7205
|
+
const titleMatch = specResult.data.match(/title:\s*"?([^"\n]+)"?/);
|
|
7206
|
+
if (titleMatch) {
|
|
7207
|
+
featureTitle = titleMatch[1];
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
const tasksContent = generateTasks({
|
|
7212
|
+
featureId,
|
|
7213
|
+
featureTitle,
|
|
7214
|
+
tasks: [
|
|
7215
|
+
{ title: "\uAE30\uBC18 \uAD6C\uC870 \uC124\uC815", priority: "high" },
|
|
7216
|
+
{ title: "\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604", priority: "high" },
|
|
7217
|
+
{ title: "\uD14C\uC2A4\uD2B8 \uC791\uC131", priority: "medium" },
|
|
7218
|
+
{ title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
|
|
7219
|
+
]
|
|
7220
|
+
});
|
|
7221
|
+
const tasksPath = path12.join(featurePath, "tasks.md");
|
|
7222
|
+
await writeFile(tasksPath, tasksContent);
|
|
7223
|
+
return success(tasksPath);
|
|
7224
|
+
}
|
|
7225
|
+
async function createChecklist2(sddPath) {
|
|
7226
|
+
if (!await fileExists(sddPath)) {
|
|
7227
|
+
return failure(new Error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
7228
|
+
}
|
|
7229
|
+
const checklistContent = generateFullChecklistMarkdown();
|
|
7230
|
+
const outputPath = path12.join(sddPath, "checklist.md");
|
|
7231
|
+
await writeFile(outputPath, checklistContent);
|
|
7232
|
+
return success(outputPath);
|
|
7233
|
+
}
|
|
7234
|
+
async function getCounterStatus(sddPath) {
|
|
7235
|
+
const peekResult = await peekNextFeatureNumber(sddPath);
|
|
7236
|
+
if (!peekResult.success) {
|
|
7237
|
+
return failure(new Error(`\uCE74\uC6B4\uD130 \uC870\uD68C \uC2E4\uD328: ${peekResult.error.message}`));
|
|
7238
|
+
}
|
|
7239
|
+
const historyResult = await getFeatureHistory(sddPath);
|
|
7240
|
+
if (!historyResult.success) {
|
|
7241
|
+
return failure(new Error(`\uC774\uB825 \uC870\uD68C \uC2E4\uD328: ${historyResult.error.message}`));
|
|
7242
|
+
}
|
|
7243
|
+
return success({
|
|
7244
|
+
nextNumber: peekResult.data,
|
|
7245
|
+
totalFeatures: historyResult.data.length
|
|
7246
|
+
});
|
|
7247
|
+
}
|
|
7248
|
+
function registerNewCommand(program2) {
|
|
7249
|
+
const newCmd = program2.command("new").description("\uC0C8\uB85C\uC6B4 \uAE30\uB2A5 \uC0DD\uC131").argument("[name]", "\uAE30\uB2A5 \uC774\uB984").option("--title <title>", "\uAE30\uB2A5 \uC81C\uBAA9").option("--description <desc>", "\uAE30\uB2A5 \uC124\uBA85").option("--no-branch", "\uBE0C\uB79C\uCE58 \uC0DD\uC131 \uC548 \uD568").option("--numbered", "\uC790\uB3D9 \uBC88\uD638 \uBD80\uC5EC (feature/001-name \uD615\uC2DD)").option("--plan", "\uACC4\uD68D \uD30C\uC77C\uB3C4 \uD568\uAED8 \uC0DD\uC131").option("--tasks", "\uC791\uC5C5 \uBD84\uD574 \uD30C\uC77C\uB3C4 \uD568\uAED8 \uC0DD\uC131").option("--all", "\uBAA8\uB4E0 \uD30C\uC77C \uC0DD\uC131 (spec, plan, tasks)").option("--checklist", "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uD30C\uC77C \uC0DD\uC131").action(async (name, options) => {
|
|
7250
|
+
await handleNew(name, options);
|
|
7251
|
+
});
|
|
7252
|
+
newCmd.command("plan").description("\uAE30\uB2A5 \uAD6C\uD604 \uACC4\uD68D \uC0DD\uC131").argument("<feature>", "\uAE30\uB2A5 ID").option("--title <title>", "\uACC4\uD68D \uC81C\uBAA9").action(async (feature, opts) => {
|
|
7253
|
+
await handlePlan(feature, opts);
|
|
7254
|
+
});
|
|
6872
7255
|
newCmd.command("tasks").description("\uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131").argument("<feature>", "\uAE30\uB2A5 ID").action(async (feature) => {
|
|
6873
7256
|
await handleTasks(feature);
|
|
6874
7257
|
});
|
|
@@ -6886,193 +7269,88 @@ async function handleNew(name, options) {
|
|
|
6886
7269
|
}
|
|
6887
7270
|
const cwd = process.cwd();
|
|
6888
7271
|
const sddPath = path12.join(cwd, ".sdd");
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
const numberResult = await getNextFeatureNumber(sddPath, name);
|
|
6893
|
-
if (!numberResult.success) {
|
|
6894
|
-
logger_exports.error(`\uBC88\uD638 \uC0DD\uC131 \uC2E4\uD328: ${numberResult.error.message}`);
|
|
6895
|
-
process.exit(1);
|
|
6896
|
-
}
|
|
6897
|
-
featureId = numberResult.data.fullId;
|
|
6898
|
-
branchName = numberResult.data.branchName;
|
|
6899
|
-
logger_exports.info(`\uC790\uB3D9 \uBC88\uD638 \uBD80\uC5EC: #${numberResult.data.number.toString().padStart(3, "0")}`);
|
|
6900
|
-
} else {
|
|
6901
|
-
featureId = generateFeatureId(name);
|
|
7272
|
+
if (!await fileExists(sddPath)) {
|
|
7273
|
+
logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 sdd init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
|
|
7274
|
+
process.exit(1);
|
|
6902
7275
|
}
|
|
6903
|
-
const
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
const constitutionPath = path12.join(sddPath, "constitution.md");
|
|
6914
|
-
if (await fileExists(constitutionPath)) {
|
|
6915
|
-
const constResult = await readFile(constitutionPath);
|
|
6916
|
-
if (constResult.success) {
|
|
6917
|
-
const parseResult = parseConstitution(constResult.data);
|
|
6918
|
-
if (parseResult.success) {
|
|
6919
|
-
constitutionVersion = parseResult.data.metadata.version;
|
|
6920
|
-
}
|
|
6921
|
-
}
|
|
7276
|
+
const result = await createFeature(sddPath, name, options);
|
|
7277
|
+
if (!result.success) {
|
|
7278
|
+
logger_exports.error(`\uAE30\uB2A5 \uC0DD\uC131 \uC2E4\uD328: ${result.error.message}`);
|
|
7279
|
+
process.exit(1);
|
|
7280
|
+
}
|
|
7281
|
+
const { featureId, featurePath, branchName, filesCreated } = result.data;
|
|
7282
|
+
if (options.numbered && branchName) {
|
|
7283
|
+
const numberMatch = branchName.match(/feature\/(\d+)-/);
|
|
7284
|
+
if (numberMatch) {
|
|
7285
|
+
logger_exports.info(`\uC790\uB3D9 \uBC88\uD638 \uBD80\uC5EC: #${numberMatch[1]}`);
|
|
6922
7286
|
}
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
const branchToCreate = branchName || featureId;
|
|
6934
|
-
const result = await createBranch(branchToCreate, { checkout: true, cwd });
|
|
6935
|
-
if (result.success) {
|
|
6936
|
-
logger_exports.info(`\u2705 \uBE0C\uB79C\uCE58 \uC0DD\uC131: ${result.data}`);
|
|
6937
|
-
} else {
|
|
6938
|
-
logger_exports.warn(`\u26A0\uFE0F \uBE0C\uB79C\uCE58 \uC0DD\uC131 \uC2E4\uD328: ${result.error.message}`);
|
|
6939
|
-
}
|
|
7287
|
+
}
|
|
7288
|
+
for (const file of filesCreated) {
|
|
7289
|
+
logger_exports.info(`\u2705 ${file} \uC0DD\uC131: ${featurePath}/${file}`);
|
|
7290
|
+
}
|
|
7291
|
+
if (options.branch !== false) {
|
|
7292
|
+
if (await isGitRepository(cwd)) {
|
|
7293
|
+
const branchToCreate = branchName || featureId;
|
|
7294
|
+
const branchResult = await createBranch(branchToCreate, { checkout: true, cwd });
|
|
7295
|
+
if (branchResult.success) {
|
|
7296
|
+
logger_exports.info(`\u2705 \uBE0C\uB79C\uCE58 \uC0DD\uC131: ${branchResult.data}`);
|
|
6940
7297
|
} else {
|
|
6941
|
-
logger_exports.warn(
|
|
7298
|
+
logger_exports.warn(`\u26A0\uFE0F \uBE0C\uB79C\uCE58 \uC0DD\uC131 \uC2E4\uD328: ${branchResult.error.message}`);
|
|
6942
7299
|
}
|
|
7300
|
+
} else {
|
|
7301
|
+
logger_exports.warn("\u26A0\uFE0F Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBE0C\uB79C\uCE58 \uC0DD\uC131\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.");
|
|
6943
7302
|
}
|
|
6944
|
-
if (options.plan || options.all) {
|
|
6945
|
-
const planContent = generatePlan({
|
|
6946
|
-
featureId,
|
|
6947
|
-
featureTitle: title2,
|
|
6948
|
-
overview: description
|
|
6949
|
-
});
|
|
6950
|
-
await fs7.writeFile(path12.join(featurePath, "plan.md"), planContent, "utf-8");
|
|
6951
|
-
logger_exports.info(`\u2705 \uACC4\uD68D \uC0DD\uC131: ${featurePath}/plan.md`);
|
|
6952
|
-
}
|
|
6953
|
-
if (options.tasks || options.all) {
|
|
6954
|
-
const tasksContent = generateTasks({
|
|
6955
|
-
featureId,
|
|
6956
|
-
featureTitle: title2,
|
|
6957
|
-
tasks: [
|
|
6958
|
-
{ title: "\uAE30\uBC18 \uAD6C\uC870 \uC124\uC815", priority: "high" },
|
|
6959
|
-
{ title: "\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604", priority: "high" },
|
|
6960
|
-
{ title: "\uD14C\uC2A4\uD2B8 \uC791\uC131", priority: "medium" },
|
|
6961
|
-
{ title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
|
|
6962
|
-
]
|
|
6963
|
-
});
|
|
6964
|
-
await fs7.writeFile(path12.join(featurePath, "tasks.md"), tasksContent, "utf-8");
|
|
6965
|
-
logger_exports.info(`\u2705 \uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131: ${featurePath}/tasks.md`);
|
|
6966
|
-
}
|
|
6967
|
-
if (options.checklist || options.all) {
|
|
6968
|
-
const checklistContent = generateFullChecklistMarkdown();
|
|
6969
|
-
await fs7.writeFile(path12.join(featurePath, "checklist.md"), checklistContent, "utf-8");
|
|
6970
|
-
logger_exports.info(`\u2705 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC0DD\uC131: ${featurePath}/checklist.md`);
|
|
6971
|
-
}
|
|
6972
|
-
logger_exports.info("");
|
|
6973
|
-
logger_exports.info(`\u{1F389} \uAE30\uB2A5 '${featureId}' \uC0DD\uC131 \uC644\uB8CC!`);
|
|
6974
|
-
logger_exports.info("");
|
|
6975
|
-
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
6976
|
-
logger_exports.info(` 1. ${featurePath}/spec.md \uD3B8\uC9D1`);
|
|
6977
|
-
if (!(options.plan || options.all)) {
|
|
6978
|
-
logger_exports.info(" 2. sdd new plan " + featureId + " - \uACC4\uD68D \uC791\uC131");
|
|
6979
|
-
}
|
|
6980
|
-
if (!(options.tasks || options.all)) {
|
|
6981
|
-
logger_exports.info(" 3. sdd new tasks " + featureId + " - \uC791\uC5C5 \uBD84\uD574");
|
|
6982
|
-
}
|
|
6983
|
-
logger_exports.info(" 4. sdd validate - \uBA85\uC138 \uAC80\uC99D");
|
|
6984
|
-
} catch (error2) {
|
|
6985
|
-
logger_exports.error(`\uAE30\uB2A5 \uC0DD\uC131 \uC2E4\uD328: ${error2}`);
|
|
6986
|
-
process.exit(1);
|
|
6987
7303
|
}
|
|
7304
|
+
logger_exports.info("");
|
|
7305
|
+
logger_exports.info(`\u{1F389} \uAE30\uB2A5 '${featureId}' \uC0DD\uC131 \uC644\uB8CC!`);
|
|
7306
|
+
logger_exports.info("");
|
|
7307
|
+
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
7308
|
+
logger_exports.info(` 1. ${featurePath}/spec.md \uD3B8\uC9D1`);
|
|
7309
|
+
if (!(options.plan || options.all)) {
|
|
7310
|
+
logger_exports.info(" 2. sdd new plan " + featureId + " - \uACC4\uD68D \uC791\uC131");
|
|
7311
|
+
}
|
|
7312
|
+
if (!(options.tasks || options.all)) {
|
|
7313
|
+
logger_exports.info(" 3. sdd new tasks " + featureId + " - \uC791\uC5C5 \uBD84\uD574");
|
|
7314
|
+
}
|
|
7315
|
+
logger_exports.info(" 4. sdd validate - \uBA85\uC138 \uAC80\uC99D");
|
|
6988
7316
|
}
|
|
6989
7317
|
async function handlePlan(feature, options) {
|
|
6990
7318
|
const cwd = process.cwd();
|
|
6991
7319
|
const featurePath = path12.join(cwd, ".sdd", "specs", feature);
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
process.exit(1);
|
|
6996
|
-
}
|
|
6997
|
-
let title2 = options.title || feature;
|
|
6998
|
-
const specPath = path12.join(featurePath, "spec.md");
|
|
6999
|
-
if (await fileExists(specPath)) {
|
|
7000
|
-
const specContent = await fs7.readFile(specPath, "utf-8");
|
|
7001
|
-
const titleMatch = specContent.match(/title:\s*"?([^"\n]+)"?/);
|
|
7002
|
-
if (titleMatch) {
|
|
7003
|
-
title2 = titleMatch[1];
|
|
7004
|
-
}
|
|
7005
|
-
}
|
|
7006
|
-
const planContent = generatePlan({
|
|
7007
|
-
featureId: feature,
|
|
7008
|
-
featureTitle: title2,
|
|
7009
|
-
overview: `${title2} \uAD6C\uD604 \uACC4\uD68D`
|
|
7010
|
-
});
|
|
7011
|
-
await fs7.writeFile(path12.join(featurePath, "plan.md"), planContent, "utf-8");
|
|
7012
|
-
logger_exports.info(`\u2705 \uACC4\uD68D \uC0DD\uC131: ${featurePath}/plan.md`);
|
|
7013
|
-
logger_exports.info("");
|
|
7014
|
-
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
7015
|
-
logger_exports.info(` 1. ${featurePath}/plan.md \uD3B8\uC9D1`);
|
|
7016
|
-
logger_exports.info(" 2. sdd new tasks " + feature + " - \uC791\uC5C5 \uBD84\uD574");
|
|
7017
|
-
} catch (error2) {
|
|
7018
|
-
logger_exports.error(`\uACC4\uD68D \uC0DD\uC131 \uC2E4\uD328: ${error2}`);
|
|
7320
|
+
const result = await createPlan(featurePath, feature, options.title);
|
|
7321
|
+
if (!result.success) {
|
|
7322
|
+
logger_exports.error(result.error.message);
|
|
7019
7323
|
process.exit(1);
|
|
7020
7324
|
}
|
|
7325
|
+
logger_exports.info(`\u2705 \uACC4\uD68D \uC0DD\uC131: ${result.data}`);
|
|
7326
|
+
logger_exports.info("");
|
|
7327
|
+
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
7328
|
+
logger_exports.info(` 1. ${featurePath}/plan.md \uD3B8\uC9D1`);
|
|
7329
|
+
logger_exports.info(" 2. sdd new tasks " + feature + " - \uC791\uC5C5 \uBD84\uD574");
|
|
7021
7330
|
}
|
|
7022
7331
|
async function handleTasks(feature) {
|
|
7023
7332
|
const cwd = process.cwd();
|
|
7024
7333
|
const featurePath = path12.join(cwd, ".sdd", "specs", feature);
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
process.exit(1);
|
|
7029
|
-
}
|
|
7030
|
-
let title2 = feature;
|
|
7031
|
-
const specPath = path12.join(featurePath, "spec.md");
|
|
7032
|
-
if (await fileExists(specPath)) {
|
|
7033
|
-
const specContent = await fs7.readFile(specPath, "utf-8");
|
|
7034
|
-
const titleMatch = specContent.match(/title:\s*"?([^"\n]+)"?/);
|
|
7035
|
-
if (titleMatch) {
|
|
7036
|
-
title2 = titleMatch[1];
|
|
7037
|
-
}
|
|
7038
|
-
}
|
|
7039
|
-
const tasksContent = generateTasks({
|
|
7040
|
-
featureId: feature,
|
|
7041
|
-
featureTitle: title2,
|
|
7042
|
-
tasks: [
|
|
7043
|
-
{ title: "\uAE30\uBC18 \uAD6C\uC870 \uC124\uC815", priority: "high" },
|
|
7044
|
-
{ title: "\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604", priority: "high" },
|
|
7045
|
-
{ title: "\uD14C\uC2A4\uD2B8 \uC791\uC131", priority: "medium" },
|
|
7046
|
-
{ title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
|
|
7047
|
-
]
|
|
7048
|
-
});
|
|
7049
|
-
await fs7.writeFile(path12.join(featurePath, "tasks.md"), tasksContent, "utf-8");
|
|
7050
|
-
logger_exports.info(`\u2705 \uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131: ${featurePath}/tasks.md`);
|
|
7051
|
-
logger_exports.info("");
|
|
7052
|
-
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
7053
|
-
logger_exports.info(` 1. ${featurePath}/tasks.md \uD3B8\uC9D1`);
|
|
7054
|
-
logger_exports.info(" 2. \uAC01 \uC791\uC5C5 \uC21C\uCC28\uC801\uC73C\uB85C \uAD6C\uD604");
|
|
7055
|
-
} catch (error2) {
|
|
7056
|
-
logger_exports.error(`\uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131 \uC2E4\uD328: ${error2}`);
|
|
7334
|
+
const result = await createTasks(featurePath, feature);
|
|
7335
|
+
if (!result.success) {
|
|
7336
|
+
logger_exports.error(result.error.message);
|
|
7057
7337
|
process.exit(1);
|
|
7058
7338
|
}
|
|
7339
|
+
logger_exports.info(`\u2705 \uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131: ${result.data}`);
|
|
7340
|
+
logger_exports.info("");
|
|
7341
|
+
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
7342
|
+
logger_exports.info(` 1. ${featurePath}/tasks.md \uD3B8\uC9D1`);
|
|
7343
|
+
logger_exports.info(" 2. \uAC01 \uC791\uC5C5 \uC21C\uCC28\uC801\uC73C\uB85C \uAD6C\uD604");
|
|
7059
7344
|
}
|
|
7060
7345
|
async function handleChecklist() {
|
|
7061
7346
|
const cwd = process.cwd();
|
|
7062
7347
|
const sddPath = path12.join(cwd, ".sdd");
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
process.exit(1);
|
|
7067
|
-
}
|
|
7068
|
-
const checklistContent = generateFullChecklistMarkdown();
|
|
7069
|
-
const outputPath = path12.join(sddPath, "checklist.md");
|
|
7070
|
-
await fs7.writeFile(outputPath, checklistContent, "utf-8");
|
|
7071
|
-
logger_exports.info(`\u2705 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC0DD\uC131: ${outputPath}`);
|
|
7072
|
-
} catch (error2) {
|
|
7073
|
-
logger_exports.error(`\uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC0DD\uC131 \uC2E4\uD328: ${error2}`);
|
|
7348
|
+
const result = await createChecklist2(sddPath);
|
|
7349
|
+
if (!result.success) {
|
|
7350
|
+
logger_exports.error(result.error.message);
|
|
7074
7351
|
process.exit(1);
|
|
7075
7352
|
}
|
|
7353
|
+
logger_exports.info(`\u2705 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC0DD\uC131: ${result.data}`);
|
|
7076
7354
|
}
|
|
7077
7355
|
async function handleCounter(options) {
|
|
7078
7356
|
const cwd = process.cwd();
|
|
@@ -7131,39 +7409,65 @@ async function handleCounter(options) {
|
|
|
7131
7409
|
}
|
|
7132
7410
|
return;
|
|
7133
7411
|
}
|
|
7134
|
-
const
|
|
7135
|
-
|
|
7136
|
-
if (peekResult.success && historyResult.success) {
|
|
7412
|
+
const statusResult = await getCounterStatus(sddPath);
|
|
7413
|
+
if (statusResult.success) {
|
|
7137
7414
|
logger_exports.info("=== \uAE30\uB2A5 \uBC88\uD638 \uCE74\uC6B4\uD130 \uC0C1\uD0DC ===");
|
|
7138
7415
|
logger_exports.info("");
|
|
7139
|
-
logger_exports.info(`\uB2E4\uC74C \uBC88\uD638: #${String(
|
|
7140
|
-
logger_exports.info(`\uC0DD\uC131\uB41C \uAE30\uB2A5 \uC218: ${
|
|
7416
|
+
logger_exports.info(`\uB2E4\uC74C \uBC88\uD638: #${String(statusResult.data.nextNumber).padStart(3, "0")}`);
|
|
7417
|
+
logger_exports.info(`\uC0DD\uC131\uB41C \uAE30\uB2A5 \uC218: ${statusResult.data.totalFeatures}\uAC1C`);
|
|
7141
7418
|
logger_exports.info("");
|
|
7142
7419
|
logger_exports.info("\uC635\uC158:");
|
|
7143
7420
|
logger_exports.info(" --peek \uB2E4\uC74C \uBC88\uD638 \uD655\uC778");
|
|
7144
7421
|
logger_exports.info(" --history \uC0DD\uC131 \uC774\uB825 \uC870\uD68C");
|
|
7145
7422
|
logger_exports.info(" --set <n> \uB2E4\uC74C \uBC88\uD638 \uC124\uC815");
|
|
7146
7423
|
} else {
|
|
7147
|
-
logger_exports.error(
|
|
7424
|
+
logger_exports.error(statusResult.error.message);
|
|
7148
7425
|
process.exit(1);
|
|
7149
7426
|
}
|
|
7150
7427
|
}
|
|
7151
7428
|
|
|
7152
7429
|
// src/cli/commands/status.ts
|
|
7153
7430
|
import path13 from "path";
|
|
7154
|
-
import { promises as
|
|
7431
|
+
import { promises as fs7 } from "fs";
|
|
7155
7432
|
init_fs();
|
|
7156
7433
|
init_spec_generator();
|
|
7157
7434
|
init_task_generator();
|
|
7158
7435
|
init_branch();
|
|
7159
|
-
function
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7436
|
+
async function getFeatureInfo(id, featurePath) {
|
|
7437
|
+
const info2 = {
|
|
7438
|
+
id,
|
|
7439
|
+
title: id,
|
|
7440
|
+
status: "unknown",
|
|
7441
|
+
hasSpec: false,
|
|
7442
|
+
hasPlan: false,
|
|
7443
|
+
hasTasks: false
|
|
7444
|
+
};
|
|
7445
|
+
const specPath = path13.join(featurePath, "spec.md");
|
|
7446
|
+
if (await fileExists(specPath)) {
|
|
7447
|
+
info2.hasSpec = true;
|
|
7448
|
+
const content = await fs7.readFile(specPath, "utf-8");
|
|
7449
|
+
const metadata = parseSpecMetadata(content);
|
|
7450
|
+
if (metadata) {
|
|
7451
|
+
info2.title = metadata.title;
|
|
7452
|
+
info2.status = metadata.status;
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
info2.hasPlan = await fileExists(path13.join(featurePath, "plan.md"));
|
|
7456
|
+
const tasksPath = path13.join(featurePath, "tasks.md");
|
|
7457
|
+
if (await fileExists(tasksPath)) {
|
|
7458
|
+
info2.hasTasks = true;
|
|
7459
|
+
const content = await fs7.readFile(tasksPath, "utf-8");
|
|
7460
|
+
const tasks = parseTasks(content);
|
|
7461
|
+
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
7462
|
+
info2.taskProgress = {
|
|
7463
|
+
completed,
|
|
7464
|
+
total: tasks.length
|
|
7465
|
+
};
|
|
7466
|
+
}
|
|
7467
|
+
return info2;
|
|
7163
7468
|
}
|
|
7164
|
-
async function
|
|
7165
|
-
const
|
|
7166
|
-
const sddPath = path13.join(cwd, ".sdd");
|
|
7469
|
+
async function getProjectStatus(projectPath) {
|
|
7470
|
+
const sddPath = path13.join(projectPath, ".sdd");
|
|
7167
7471
|
const status = {
|
|
7168
7472
|
initialized: false,
|
|
7169
7473
|
hasConstitution: false,
|
|
@@ -7175,13 +7479,7 @@ async function handleStatus(options) {
|
|
|
7175
7479
|
};
|
|
7176
7480
|
status.initialized = await fileExists(sddPath);
|
|
7177
7481
|
if (!status.initialized) {
|
|
7178
|
-
|
|
7179
|
-
console.log(JSON.stringify(status, null, 2));
|
|
7180
|
-
} else {
|
|
7181
|
-
logger_exports.warn("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
|
|
7182
|
-
logger_exports.info("sdd init \uBA85\uB839\uC5B4\uB85C \uCD08\uAE30\uD654\uD558\uC138\uC694.");
|
|
7183
|
-
}
|
|
7184
|
-
return;
|
|
7482
|
+
return status;
|
|
7185
7483
|
}
|
|
7186
7484
|
status.hasConstitution = await fileExists(path13.join(sddPath, "constitution.md"));
|
|
7187
7485
|
status.hasAgents = await fileExists(path13.join(sddPath, "AGENTS.md"));
|
|
@@ -7191,7 +7489,7 @@ async function handleStatus(options) {
|
|
|
7191
7489
|
if (specsResult.success) {
|
|
7192
7490
|
for (const entry of specsResult.data) {
|
|
7193
7491
|
const featurePath = path13.join(specsPath, entry);
|
|
7194
|
-
const stat = await
|
|
7492
|
+
const stat = await fs7.stat(featurePath);
|
|
7195
7493
|
if (stat.isDirectory()) {
|
|
7196
7494
|
const featureInfo = await getFeatureInfo(entry, featurePath);
|
|
7197
7495
|
status.features.push(featureInfo);
|
|
@@ -7207,52 +7505,56 @@ async function handleStatus(options) {
|
|
|
7207
7505
|
if (archiveResult.success) {
|
|
7208
7506
|
status.archivedChanges = archiveResult.data.length;
|
|
7209
7507
|
}
|
|
7210
|
-
const currentBranchResult = await getCurrentBranch(
|
|
7508
|
+
const currentBranchResult = await getCurrentBranch(projectPath);
|
|
7211
7509
|
if (currentBranchResult.success) {
|
|
7212
7510
|
status.currentBranch = currentBranchResult.data;
|
|
7213
7511
|
}
|
|
7214
|
-
const featureBranchesResult = await listFeatureBranches(
|
|
7512
|
+
const featureBranchesResult = await listFeatureBranches(projectPath);
|
|
7215
7513
|
if (featureBranchesResult.success) {
|
|
7216
7514
|
status.featureBranches = featureBranchesResult.data;
|
|
7217
7515
|
}
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7516
|
+
return status;
|
|
7517
|
+
}
|
|
7518
|
+
function getStatusIcon(status) {
|
|
7519
|
+
switch (status) {
|
|
7520
|
+
case "draft":
|
|
7521
|
+
return "\u{1F4DD}";
|
|
7522
|
+
case "specified":
|
|
7523
|
+
return "\u{1F4C4}";
|
|
7524
|
+
case "planned":
|
|
7525
|
+
return "\u{1F4CB}";
|
|
7526
|
+
case "tasked":
|
|
7527
|
+
return "\u270F\uFE0F";
|
|
7528
|
+
case "implementing":
|
|
7529
|
+
return "\u{1F528}";
|
|
7530
|
+
case "completed":
|
|
7531
|
+
return "\u2705";
|
|
7532
|
+
default:
|
|
7533
|
+
return "\u2753";
|
|
7222
7534
|
}
|
|
7223
7535
|
}
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
if (metadata) {
|
|
7239
|
-
info2.title = metadata.title;
|
|
7240
|
-
info2.status = metadata.status;
|
|
7536
|
+
function registerStatusCommand(program2) {
|
|
7537
|
+
program2.command("status").description("SDD \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uC870\uD68C").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").option("--verbose", "\uC0C1\uC138 \uC815\uBCF4 \uCD9C\uB825").action(async (options) => {
|
|
7538
|
+
await handleStatus(options);
|
|
7539
|
+
});
|
|
7540
|
+
}
|
|
7541
|
+
async function handleStatus(options) {
|
|
7542
|
+
const cwd = process.cwd();
|
|
7543
|
+
const status = await getProjectStatus(cwd);
|
|
7544
|
+
if (!status.initialized) {
|
|
7545
|
+
if (options.json) {
|
|
7546
|
+
console.log(JSON.stringify(status, null, 2));
|
|
7547
|
+
} else {
|
|
7548
|
+
logger_exports.warn("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
|
|
7549
|
+
logger_exports.info("sdd init \uBA85\uB839\uC5B4\uB85C \uCD08\uAE30\uD654\uD558\uC138\uC694.");
|
|
7241
7550
|
}
|
|
7551
|
+
return;
|
|
7242
7552
|
}
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
const content = await fs8.readFile(tasksPath, "utf-8");
|
|
7248
|
-
const tasks = parseTasks(content);
|
|
7249
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
7250
|
-
info2.taskProgress = {
|
|
7251
|
-
completed,
|
|
7252
|
-
total: tasks.length
|
|
7253
|
-
};
|
|
7553
|
+
if (options.json) {
|
|
7554
|
+
console.log(JSON.stringify(status, null, 2));
|
|
7555
|
+
} else {
|
|
7556
|
+
printStatus(status, options.verbose);
|
|
7254
7557
|
}
|
|
7255
|
-
return info2;
|
|
7256
7558
|
}
|
|
7257
7559
|
function printStatus(status, verbose) {
|
|
7258
7560
|
console.log("");
|
|
@@ -7331,32 +7633,155 @@ function printStatus(status, verbose) {
|
|
|
7331
7633
|
}
|
|
7332
7634
|
}
|
|
7333
7635
|
}
|
|
7334
|
-
console.log("");
|
|
7335
|
-
}
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
case "
|
|
7345
|
-
return "\
|
|
7346
|
-
case "
|
|
7347
|
-
return "\u{
|
|
7348
|
-
case "
|
|
7349
|
-
return "\
|
|
7350
|
-
|
|
7351
|
-
return "\
|
|
7352
|
-
|
|
7636
|
+
console.log("");
|
|
7637
|
+
}
|
|
7638
|
+
|
|
7639
|
+
// src/cli/commands/list.ts
|
|
7640
|
+
import path14 from "path";
|
|
7641
|
+
import { promises as fs8 } from "fs";
|
|
7642
|
+
init_fs();
|
|
7643
|
+
init_spec_generator();
|
|
7644
|
+
function getListStatusIcon(status) {
|
|
7645
|
+
switch (status) {
|
|
7646
|
+
case "draft":
|
|
7647
|
+
return "\u{1F4DD}";
|
|
7648
|
+
case "specified":
|
|
7649
|
+
return "\u{1F4C4}";
|
|
7650
|
+
case "planned":
|
|
7651
|
+
return "\u{1F4CB}";
|
|
7652
|
+
case "tasked":
|
|
7653
|
+
return "\u270F\uFE0F";
|
|
7654
|
+
case "implementing":
|
|
7655
|
+
return "\u{1F528}";
|
|
7656
|
+
case "completed":
|
|
7657
|
+
return "\u2705";
|
|
7658
|
+
default:
|
|
7659
|
+
return "\u2753";
|
|
7660
|
+
}
|
|
7661
|
+
}
|
|
7662
|
+
async function getFeatureList(projectPath, options = {}) {
|
|
7663
|
+
const specsPath = path14.join(projectPath, ".sdd", "specs");
|
|
7664
|
+
if (!await fileExists(specsPath)) {
|
|
7665
|
+
return [];
|
|
7666
|
+
}
|
|
7667
|
+
const result = await readDir(specsPath);
|
|
7668
|
+
if (!result.success) {
|
|
7669
|
+
return [];
|
|
7670
|
+
}
|
|
7671
|
+
const features = [];
|
|
7672
|
+
for (const entry of result.data) {
|
|
7673
|
+
const featurePath = path14.join(specsPath, entry);
|
|
7674
|
+
const stat = await fs8.stat(featurePath);
|
|
7675
|
+
if (stat.isDirectory()) {
|
|
7676
|
+
const specPath = path14.join(featurePath, "spec.md");
|
|
7677
|
+
if (await fileExists(specPath)) {
|
|
7678
|
+
const content = await fs8.readFile(specPath, "utf-8");
|
|
7679
|
+
const metadata = parseSpecMetadata(content);
|
|
7680
|
+
if (metadata) {
|
|
7681
|
+
if (!options.status || metadata.status === options.status) {
|
|
7682
|
+
features.push({
|
|
7683
|
+
id: entry,
|
|
7684
|
+
title: metadata.title,
|
|
7685
|
+
status: metadata.status
|
|
7686
|
+
});
|
|
7687
|
+
}
|
|
7688
|
+
}
|
|
7689
|
+
}
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
return features;
|
|
7693
|
+
}
|
|
7694
|
+
async function getChangeList(projectPath, options = {}) {
|
|
7695
|
+
const sddPath = path14.join(projectPath, ".sdd");
|
|
7696
|
+
const result = {
|
|
7697
|
+
pending: [],
|
|
7698
|
+
archived: []
|
|
7699
|
+
};
|
|
7700
|
+
if (!await fileExists(sddPath)) {
|
|
7701
|
+
return result;
|
|
7702
|
+
}
|
|
7703
|
+
if (!options.archived) {
|
|
7704
|
+
const pendingResult = await listPendingChanges(sddPath);
|
|
7705
|
+
if (pendingResult.success) {
|
|
7706
|
+
result.pending = pendingResult.data.map((c) => String(c));
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
if (!options.pending) {
|
|
7710
|
+
const archiveResult = await listArchives(sddPath);
|
|
7711
|
+
if (archiveResult.success) {
|
|
7712
|
+
result.archived = archiveResult.data.map((a) => a.id);
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
return result;
|
|
7716
|
+
}
|
|
7717
|
+
async function getSpecFileTree(specsPath) {
|
|
7718
|
+
if (!await fileExists(specsPath)) {
|
|
7719
|
+
return [];
|
|
7720
|
+
}
|
|
7721
|
+
return walkSpecsTree(specsPath);
|
|
7722
|
+
}
|
|
7723
|
+
async function walkSpecsTree(basePath) {
|
|
7724
|
+
const result = await readDir(basePath);
|
|
7725
|
+
if (!result.success) return [];
|
|
7726
|
+
const items = [];
|
|
7727
|
+
for (const entry of result.data) {
|
|
7728
|
+
const fullPath = path14.join(basePath, entry);
|
|
7729
|
+
const stat = await fs8.stat(fullPath);
|
|
7730
|
+
if (stat.isDirectory()) {
|
|
7731
|
+
const children = await walkSpecsTree(fullPath);
|
|
7732
|
+
items.push({
|
|
7733
|
+
path: fullPath,
|
|
7734
|
+
name: entry,
|
|
7735
|
+
isDirectory: true,
|
|
7736
|
+
children
|
|
7737
|
+
});
|
|
7738
|
+
} else if (entry.endsWith(".md")) {
|
|
7739
|
+
items.push({
|
|
7740
|
+
path: fullPath,
|
|
7741
|
+
name: entry,
|
|
7742
|
+
isDirectory: false
|
|
7743
|
+
});
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
return items;
|
|
7747
|
+
}
|
|
7748
|
+
async function getTemplateList(projectPath) {
|
|
7749
|
+
const templatesPath = path14.join(projectPath, ".sdd", "templates");
|
|
7750
|
+
if (!await fileExists(templatesPath)) {
|
|
7751
|
+
return [];
|
|
7752
|
+
}
|
|
7753
|
+
const result = await readDir(templatesPath);
|
|
7754
|
+
if (!result.success) {
|
|
7755
|
+
return [];
|
|
7756
|
+
}
|
|
7757
|
+
return result.data.filter((f) => f.endsWith(".md"));
|
|
7758
|
+
}
|
|
7759
|
+
async function getProjectSummary(projectPath) {
|
|
7760
|
+
const sddPath = path14.join(projectPath, ".sdd");
|
|
7761
|
+
if (!await fileExists(sddPath)) {
|
|
7762
|
+
return null;
|
|
7763
|
+
}
|
|
7764
|
+
const specsPath = path14.join(sddPath, "specs");
|
|
7765
|
+
let featureCount = 0;
|
|
7766
|
+
if (await fileExists(specsPath)) {
|
|
7767
|
+
const result = await readDir(specsPath);
|
|
7768
|
+
if (result.success) {
|
|
7769
|
+
for (const entry of result.data) {
|
|
7770
|
+
const stat = await fs8.stat(path14.join(specsPath, entry));
|
|
7771
|
+
if (stat.isDirectory()) featureCount++;
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7774
|
+
}
|
|
7775
|
+
const pendingResult = await listPendingChanges(sddPath);
|
|
7776
|
+
const pendingChangeCount = pendingResult.success ? pendingResult.data.length : 0;
|
|
7777
|
+
const archiveResult = await listArchives(sddPath);
|
|
7778
|
+
const archivedChangeCount = archiveResult.success ? archiveResult.data.length : 0;
|
|
7779
|
+
return {
|
|
7780
|
+
featureCount,
|
|
7781
|
+
pendingChangeCount,
|
|
7782
|
+
archivedChangeCount
|
|
7783
|
+
};
|
|
7353
7784
|
}
|
|
7354
|
-
|
|
7355
|
-
// src/cli/commands/list.ts
|
|
7356
|
-
import path14 from "path";
|
|
7357
|
-
import { promises as fs9 } from "fs";
|
|
7358
|
-
init_fs();
|
|
7359
|
-
init_spec_generator();
|
|
7360
7785
|
function registerListCommand(program2) {
|
|
7361
7786
|
const listCmd = program2.command("list").alias("ls").description("\uD56D\uBAA9 \uBAA9\uB85D \uC870\uD68C");
|
|
7362
7787
|
listCmd.command("features").alias("f").description("\uAE30\uB2A5 \uBAA9\uB85D \uC870\uD68C").option("--status <status>", "\uC0C1\uD0DC\uBCC4 \uD544\uD130 (draft, specified, planned, etc.)").action(async (options) => {
|
|
@@ -7376,38 +7801,7 @@ function registerListCommand(program2) {
|
|
|
7376
7801
|
});
|
|
7377
7802
|
}
|
|
7378
7803
|
async function listFeatures(options) {
|
|
7379
|
-
const
|
|
7380
|
-
const specsPath = path14.join(cwd, ".sdd", "specs");
|
|
7381
|
-
if (!await fileExists(specsPath)) {
|
|
7382
|
-
logger_exports.warn("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. sdd init\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
7383
|
-
return;
|
|
7384
|
-
}
|
|
7385
|
-
const result = await readDir(specsPath);
|
|
7386
|
-
if (!result.success) {
|
|
7387
|
-
logger_exports.error("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7388
|
-
return;
|
|
7389
|
-
}
|
|
7390
|
-
const features = [];
|
|
7391
|
-
for (const entry of result.data) {
|
|
7392
|
-
const featurePath = path14.join(specsPath, entry);
|
|
7393
|
-
const stat = await fs9.stat(featurePath);
|
|
7394
|
-
if (stat.isDirectory()) {
|
|
7395
|
-
const specPath = path14.join(featurePath, "spec.md");
|
|
7396
|
-
if (await fileExists(specPath)) {
|
|
7397
|
-
const content = await fs9.readFile(specPath, "utf-8");
|
|
7398
|
-
const metadata = parseSpecMetadata(content);
|
|
7399
|
-
if (metadata) {
|
|
7400
|
-
if (!options.status || metadata.status === options.status) {
|
|
7401
|
-
features.push({
|
|
7402
|
-
id: entry,
|
|
7403
|
-
title: metadata.title,
|
|
7404
|
-
status: metadata.status
|
|
7405
|
-
});
|
|
7406
|
-
}
|
|
7407
|
-
}
|
|
7408
|
-
}
|
|
7409
|
-
}
|
|
7410
|
-
}
|
|
7804
|
+
const features = await getFeatureList(process.cwd(), options);
|
|
7411
7805
|
if (features.length === 0) {
|
|
7412
7806
|
logger_exports.info("\uAE30\uB2A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7413
7807
|
return;
|
|
@@ -7416,25 +7810,19 @@ async function listFeatures(options) {
|
|
|
7416
7810
|
console.log("\u{1F4CB} \uAE30\uB2A5 \uBAA9\uB85D");
|
|
7417
7811
|
console.log("\u2500".repeat(50));
|
|
7418
7812
|
for (const f of features) {
|
|
7419
|
-
const statusIcon =
|
|
7813
|
+
const statusIcon = getListStatusIcon(f.status);
|
|
7420
7814
|
console.log(`${statusIcon} ${f.title} (${f.id}) - ${f.status}`);
|
|
7421
7815
|
}
|
|
7422
7816
|
console.log("");
|
|
7423
7817
|
}
|
|
7424
7818
|
async function listChanges(options) {
|
|
7425
|
-
const
|
|
7426
|
-
const sddPath = path14.join(cwd, ".sdd");
|
|
7427
|
-
if (!await fileExists(sddPath)) {
|
|
7428
|
-
logger_exports.warn(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. sdd init\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
7429
|
-
return;
|
|
7430
|
-
}
|
|
7819
|
+
const result = await getChangeList(process.cwd(), options);
|
|
7431
7820
|
console.log("");
|
|
7432
7821
|
if (!options.archived) {
|
|
7433
|
-
|
|
7434
|
-
if (pendingResult.success && pendingResult.data.length > 0) {
|
|
7822
|
+
if (result.pending.length > 0) {
|
|
7435
7823
|
console.log("\u{1F4DD} \uB300\uAE30 \uC911\uC778 \uBCC0\uACBD");
|
|
7436
7824
|
console.log("\u2500".repeat(30));
|
|
7437
|
-
for (const change of
|
|
7825
|
+
for (const change of result.pending) {
|
|
7438
7826
|
console.log(` - ${change}`);
|
|
7439
7827
|
}
|
|
7440
7828
|
console.log("");
|
|
@@ -7443,11 +7831,10 @@ async function listChanges(options) {
|
|
|
7443
7831
|
}
|
|
7444
7832
|
}
|
|
7445
7833
|
if (!options.pending) {
|
|
7446
|
-
|
|
7447
|
-
if (archiveResult.success && archiveResult.data.length > 0) {
|
|
7834
|
+
if (result.archived.length > 0) {
|
|
7448
7835
|
console.log("\u{1F4E6} \uC544\uCE74\uC774\uBE0C\uB41C \uBCC0\uACBD");
|
|
7449
7836
|
console.log("\u2500".repeat(30));
|
|
7450
|
-
for (const archive of
|
|
7837
|
+
for (const archive of result.archived) {
|
|
7451
7838
|
console.log(` - ${archive}`);
|
|
7452
7839
|
}
|
|
7453
7840
|
console.log("");
|
|
@@ -7457,80 +7844,56 @@ async function listChanges(options) {
|
|
|
7457
7844
|
}
|
|
7458
7845
|
}
|
|
7459
7846
|
async function listSpecs() {
|
|
7460
|
-
const
|
|
7461
|
-
const
|
|
7462
|
-
if (
|
|
7847
|
+
const specsPath = path14.join(process.cwd(), ".sdd", "specs");
|
|
7848
|
+
const tree = await getSpecFileTree(specsPath);
|
|
7849
|
+
if (tree.length === 0) {
|
|
7463
7850
|
logger_exports.warn("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7464
7851
|
return;
|
|
7465
7852
|
}
|
|
7466
7853
|
console.log("");
|
|
7467
7854
|
console.log("\u{1F4C4} \uC2A4\uD399 \uD30C\uC77C \uBAA9\uB85D");
|
|
7468
7855
|
console.log("\u2500".repeat(50));
|
|
7469
|
-
|
|
7856
|
+
printSpecTree(tree, "");
|
|
7470
7857
|
console.log("");
|
|
7471
7858
|
}
|
|
7472
|
-
|
|
7473
|
-
const
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
} else if (entry.endsWith(".md")) {
|
|
7482
|
-
console.log(`${prefix}\u{1F4C4} ${entry}`);
|
|
7859
|
+
function printSpecTree(items, prefix) {
|
|
7860
|
+
for (const item of items) {
|
|
7861
|
+
if (item.isDirectory) {
|
|
7862
|
+
console.log(`${prefix}\u{1F4C1} ${item.name}/`);
|
|
7863
|
+
if (item.children) {
|
|
7864
|
+
printSpecTree(item.children, prefix + " ");
|
|
7865
|
+
}
|
|
7866
|
+
} else {
|
|
7867
|
+
console.log(`${prefix}\u{1F4C4} ${item.name}`);
|
|
7483
7868
|
}
|
|
7484
7869
|
}
|
|
7485
7870
|
}
|
|
7486
7871
|
async function listTemplates() {
|
|
7487
|
-
const
|
|
7488
|
-
|
|
7489
|
-
if (!await fileExists(templatesPath)) {
|
|
7872
|
+
const templates = await getTemplateList(process.cwd());
|
|
7873
|
+
if (templates.length === 0) {
|
|
7490
7874
|
logger_exports.warn("\uD15C\uD50C\uB9BF \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7491
7875
|
return;
|
|
7492
7876
|
}
|
|
7493
|
-
const result = await readDir(templatesPath);
|
|
7494
|
-
if (!result.success) {
|
|
7495
|
-
logger_exports.error("\uD15C\uD50C\uB9BF \uB514\uB809\uD1A0\uB9AC\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7496
|
-
return;
|
|
7497
|
-
}
|
|
7498
7877
|
console.log("");
|
|
7499
7878
|
console.log("\u{1F4D1} \uD15C\uD50C\uB9BF \uBAA9\uB85D");
|
|
7500
7879
|
console.log("\u2500".repeat(30));
|
|
7501
|
-
for (const template of
|
|
7880
|
+
for (const template of templates) {
|
|
7502
7881
|
console.log(` - ${template}`);
|
|
7503
7882
|
}
|
|
7504
7883
|
console.log("");
|
|
7505
7884
|
}
|
|
7506
7885
|
async function listSummary() {
|
|
7507
|
-
const
|
|
7508
|
-
|
|
7509
|
-
if (!await fileExists(sddPath)) {
|
|
7886
|
+
const summary = await getProjectSummary(process.cwd());
|
|
7887
|
+
if (!summary) {
|
|
7510
7888
|
logger_exports.warn(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. sdd init\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
7511
7889
|
return;
|
|
7512
7890
|
}
|
|
7513
7891
|
console.log("");
|
|
7514
7892
|
console.log("\u{1F4CA} SDD \uD504\uB85C\uC81D\uD2B8 \uC694\uC57D");
|
|
7515
7893
|
console.log("\u2550".repeat(40));
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
const result = await readDir(specsPath);
|
|
7520
|
-
if (result.success) {
|
|
7521
|
-
for (const entry of result.data) {
|
|
7522
|
-
const stat = await fs9.stat(path14.join(specsPath, entry));
|
|
7523
|
-
if (stat.isDirectory()) featureCount++;
|
|
7524
|
-
}
|
|
7525
|
-
}
|
|
7526
|
-
}
|
|
7527
|
-
console.log(`\u{1F4CB} \uAE30\uB2A5: ${featureCount}\uAC1C`);
|
|
7528
|
-
const pendingResult = await listPendingChanges(sddPath);
|
|
7529
|
-
const pendingCount = pendingResult.success ? pendingResult.data.length : 0;
|
|
7530
|
-
console.log(`\u{1F4DD} \uB300\uAE30 \uC911\uC778 \uBCC0\uACBD: ${pendingCount}\uAC1C`);
|
|
7531
|
-
const archiveResult = await listArchives(sddPath);
|
|
7532
|
-
const archiveCount = archiveResult.success ? archiveResult.data.length : 0;
|
|
7533
|
-
console.log(`\u{1F4E6} \uC544\uCE74\uC774\uBE0C\uB41C \uBCC0\uACBD: ${archiveCount}\uAC1C`);
|
|
7894
|
+
console.log(`\u{1F4CB} \uAE30\uB2A5: ${summary.featureCount}\uAC1C`);
|
|
7895
|
+
console.log(`\u{1F4DD} \uB300\uAE30 \uC911\uC778 \uBCC0\uACBD: ${summary.pendingChangeCount}\uAC1C`);
|
|
7896
|
+
console.log(`\u{1F4E6} \uC544\uCE74\uC774\uBE0C\uB41C \uBCC0\uACBD: ${summary.archivedChangeCount}\uAC1C`);
|
|
7534
7897
|
console.log("");
|
|
7535
7898
|
console.log("\uC0C1\uC138 \uC815\uBCF4:");
|
|
7536
7899
|
console.log(" sdd list features - \uAE30\uB2A5 \uBAA9\uB85D");
|
|
@@ -7539,29 +7902,134 @@ async function listSummary() {
|
|
|
7539
7902
|
console.log(" sdd status - \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC");
|
|
7540
7903
|
console.log("");
|
|
7541
7904
|
}
|
|
7542
|
-
function getStatusIcon2(status) {
|
|
7543
|
-
switch (status) {
|
|
7544
|
-
case "draft":
|
|
7545
|
-
return "\u{1F4DD}";
|
|
7546
|
-
case "specified":
|
|
7547
|
-
return "\u{1F4C4}";
|
|
7548
|
-
case "planned":
|
|
7549
|
-
return "\u{1F4CB}";
|
|
7550
|
-
case "tasked":
|
|
7551
|
-
return "\u270F\uFE0F";
|
|
7552
|
-
case "implementing":
|
|
7553
|
-
return "\u{1F528}";
|
|
7554
|
-
case "completed":
|
|
7555
|
-
return "\u2705";
|
|
7556
|
-
default:
|
|
7557
|
-
return "\u2753";
|
|
7558
|
-
}
|
|
7559
|
-
}
|
|
7560
7905
|
|
|
7561
7906
|
// src/cli/commands/constitution.ts
|
|
7562
7907
|
init_fs();
|
|
7563
7908
|
init_errors();
|
|
7564
7909
|
import path15 from "path";
|
|
7910
|
+
init_types();
|
|
7911
|
+
function determineBumpType(options) {
|
|
7912
|
+
if (options.major) return "major";
|
|
7913
|
+
if (options.minor) return "minor";
|
|
7914
|
+
if (options.patch) return "patch";
|
|
7915
|
+
return null;
|
|
7916
|
+
}
|
|
7917
|
+
async function readConstitution(projectPath) {
|
|
7918
|
+
const constitutionPath = path15.join(projectPath, ".sdd", "constitution.md");
|
|
7919
|
+
if (!await fileExists(constitutionPath)) {
|
|
7920
|
+
return failure(new Error("Constitution\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD558\uC138\uC694."));
|
|
7921
|
+
}
|
|
7922
|
+
const contentResult = await readFile(constitutionPath);
|
|
7923
|
+
if (!contentResult.success) {
|
|
7924
|
+
return failure(new Error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
7925
|
+
}
|
|
7926
|
+
const parseResult = parseConstitution(contentResult.data);
|
|
7927
|
+
if (!parseResult.success) {
|
|
7928
|
+
return failure(new Error(`Constitution \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
|
|
7929
|
+
}
|
|
7930
|
+
return success({
|
|
7931
|
+
content: contentResult.data,
|
|
7932
|
+
parsed: parseResult.data
|
|
7933
|
+
});
|
|
7934
|
+
}
|
|
7935
|
+
function constitutionToJson(constitution) {
|
|
7936
|
+
return {
|
|
7937
|
+
projectName: constitution.projectName,
|
|
7938
|
+
version: constitution.metadata.version,
|
|
7939
|
+
created: constitution.metadata.created,
|
|
7940
|
+
updated: constitution.metadata.updated,
|
|
7941
|
+
principles: constitution.principles,
|
|
7942
|
+
forbidden: constitution.forbidden,
|
|
7943
|
+
techStack: constitution.techStack,
|
|
7944
|
+
qualityStandards: constitution.qualityStandards
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
async function executeBump(projectPath, bumpType, message) {
|
|
7948
|
+
const constitutionPath = path15.join(projectPath, ".sdd", "constitution.md");
|
|
7949
|
+
const changelogPath = path15.join(projectPath, ".sdd", "CHANGELOG.md");
|
|
7950
|
+
const readResult = await readConstitution(projectPath);
|
|
7951
|
+
if (!readResult.success) {
|
|
7952
|
+
return failure(readResult.error);
|
|
7953
|
+
}
|
|
7954
|
+
const { content, parsed } = readResult.data;
|
|
7955
|
+
const currentVersion = parsed.metadata.version;
|
|
7956
|
+
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
7957
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7958
|
+
let updatedContent = content;
|
|
7959
|
+
updatedContent = updatedContent.replace(
|
|
7960
|
+
/^version:\s*.+$/m,
|
|
7961
|
+
`version: ${newVersion}`
|
|
7962
|
+
);
|
|
7963
|
+
if (/^updated:/m.test(updatedContent)) {
|
|
7964
|
+
updatedContent = updatedContent.replace(
|
|
7965
|
+
/^updated:\s*.+$/m,
|
|
7966
|
+
`updated: ${today}`
|
|
7967
|
+
);
|
|
7968
|
+
} else {
|
|
7969
|
+
updatedContent = updatedContent.replace(
|
|
7970
|
+
/^(version:\s*.+)$/m,
|
|
7971
|
+
`$1
|
|
7972
|
+
updated: ${today}`
|
|
7973
|
+
);
|
|
7974
|
+
}
|
|
7975
|
+
await writeFile(constitutionPath, updatedContent);
|
|
7976
|
+
const changeType = bumpType === "major" ? "changed" : bumpType === "minor" ? "added" : "fixed";
|
|
7977
|
+
const changeDescription = message || `Constitution ${bumpType} \uC5C5\uB370\uC774\uD2B8`;
|
|
7978
|
+
const newEntry = createChangelogEntry(
|
|
7979
|
+
currentVersion,
|
|
7980
|
+
bumpType,
|
|
7981
|
+
[{ type: changeType, description: changeDescription }],
|
|
7982
|
+
message
|
|
7983
|
+
);
|
|
7984
|
+
let existingEntries = [];
|
|
7985
|
+
if (await fileExists(changelogPath)) {
|
|
7986
|
+
const changelogContent = await readFile(changelogPath);
|
|
7987
|
+
if (changelogContent.success) {
|
|
7988
|
+
const parsed2 = parseChangelog(changelogContent.data);
|
|
7989
|
+
if (parsed2.success) {
|
|
7990
|
+
existingEntries = parsed2.data;
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
const allEntries = [newEntry, ...existingEntries];
|
|
7995
|
+
const newChangelog = generateChangelog(allEntries);
|
|
7996
|
+
await writeFile(changelogPath, newChangelog);
|
|
7997
|
+
return success({
|
|
7998
|
+
previousVersion: currentVersion,
|
|
7999
|
+
newVersion,
|
|
8000
|
+
changelogPath
|
|
8001
|
+
});
|
|
8002
|
+
}
|
|
8003
|
+
async function executeValidateConstitution(projectPath) {
|
|
8004
|
+
const readResult = await readConstitution(projectPath);
|
|
8005
|
+
if (!readResult.success) {
|
|
8006
|
+
return failure(readResult.error);
|
|
8007
|
+
}
|
|
8008
|
+
const validationResult = validateConstitution(readResult.data.parsed);
|
|
8009
|
+
if (!validationResult.success) {
|
|
8010
|
+
return failure(new Error(`Constitution \uAC80\uC99D \uC2E4\uD328: ${validationResult.error.message}`));
|
|
8011
|
+
}
|
|
8012
|
+
return success(readResult.data.parsed);
|
|
8013
|
+
}
|
|
8014
|
+
async function getHistory(projectPath, count = 10) {
|
|
8015
|
+
const changelogPath = path15.join(projectPath, ".sdd", "CHANGELOG.md");
|
|
8016
|
+
if (!await fileExists(changelogPath)) {
|
|
8017
|
+
return success({ entries: [], totalCount: 0 });
|
|
8018
|
+
}
|
|
8019
|
+
const contentResult = await readFile(changelogPath);
|
|
8020
|
+
if (!contentResult.success) {
|
|
8021
|
+
return failure(new Error("CHANGELOG \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
8022
|
+
}
|
|
8023
|
+
const parseResult = parseChangelog(contentResult.data);
|
|
8024
|
+
if (!parseResult.success) {
|
|
8025
|
+
return failure(new Error(`CHANGELOG \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
|
|
8026
|
+
}
|
|
8027
|
+
const entries = parseResult.data.slice(0, count);
|
|
8028
|
+
return success({
|
|
8029
|
+
entries,
|
|
8030
|
+
totalCount: parseResult.data.length
|
|
8031
|
+
});
|
|
8032
|
+
}
|
|
7565
8033
|
function registerConstitutionCommand(program2) {
|
|
7566
8034
|
const constitution = program2.command("constitution").description("Constitution(\uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59) \uAD00\uB9AC");
|
|
7567
8035
|
constitution.command("show").description("\uD604\uC7AC Constitution \uB0B4\uC6A9 \uD45C\uC2DC").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").action(async (options) => {
|
|
@@ -7610,33 +8078,14 @@ function registerConstitutionCommand(program2) {
|
|
|
7610
8078
|
}
|
|
7611
8079
|
async function runShow(options) {
|
|
7612
8080
|
const cwd = process.cwd();
|
|
7613
|
-
const
|
|
7614
|
-
if (!
|
|
7615
|
-
error(
|
|
7616
|
-
process.exit(ExitCode.FILE_NOT_FOUND);
|
|
7617
|
-
}
|
|
7618
|
-
const contentResult = await readFile(constitutionPath);
|
|
7619
|
-
if (!contentResult.success) {
|
|
7620
|
-
error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
8081
|
+
const result = await readConstitution(cwd);
|
|
8082
|
+
if (!result.success) {
|
|
8083
|
+
error(result.error.message);
|
|
7621
8084
|
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
7622
8085
|
}
|
|
7623
|
-
const
|
|
7624
|
-
if (!parseResult.success) {
|
|
7625
|
-
error(`Constitution \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
|
|
7626
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
7627
|
-
}
|
|
7628
|
-
const constitution = parseResult.data;
|
|
8086
|
+
const constitution = result.data.parsed;
|
|
7629
8087
|
if (options.json) {
|
|
7630
|
-
console.log(JSON.stringify(
|
|
7631
|
-
projectName: constitution.projectName,
|
|
7632
|
-
version: constitution.metadata.version,
|
|
7633
|
-
created: constitution.metadata.created,
|
|
7634
|
-
updated: constitution.metadata.updated,
|
|
7635
|
-
principles: constitution.principles,
|
|
7636
|
-
forbidden: constitution.forbidden,
|
|
7637
|
-
techStack: constitution.techStack,
|
|
7638
|
-
qualityStandards: constitution.qualityStandards
|
|
7639
|
-
}, null, 2));
|
|
8088
|
+
console.log(JSON.stringify(constitutionToJson(constitution), null, 2));
|
|
7640
8089
|
return;
|
|
7641
8090
|
}
|
|
7642
8091
|
info(`Constitution: ${constitution.projectName}`);
|
|
@@ -7675,119 +8124,40 @@ async function runShow(options) {
|
|
|
7675
8124
|
listItem(standard);
|
|
7676
8125
|
}
|
|
7677
8126
|
}
|
|
7678
|
-
}
|
|
7679
|
-
async function runVersion() {
|
|
7680
|
-
const cwd = process.cwd();
|
|
7681
|
-
const
|
|
7682
|
-
if (!
|
|
7683
|
-
error(
|
|
7684
|
-
process.exit(ExitCode.
|
|
7685
|
-
}
|
|
7686
|
-
const contentResult = await readFile(constitutionPath);
|
|
7687
|
-
if (!contentResult.success) {
|
|
7688
|
-
error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7689
|
-
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
7690
|
-
}
|
|
7691
|
-
const parseResult = parseConstitution(contentResult.data);
|
|
7692
|
-
if (!parseResult.success) {
|
|
7693
|
-
error(`Constitution \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
|
|
7694
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
7695
|
-
}
|
|
7696
|
-
console.log(parseResult.data.metadata.version);
|
|
7697
|
-
}
|
|
7698
|
-
async function runBump(options) {
|
|
7699
|
-
const cwd = process.cwd();
|
|
7700
|
-
const constitutionPath = path15.join(cwd, ".sdd", "constitution.md");
|
|
7701
|
-
const changelogPath = path15.join(cwd, ".sdd", "CHANGELOG.md");
|
|
7702
|
-
if (!await fileExists(constitutionPath)) {
|
|
7703
|
-
error("Constitution\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7704
|
-
process.exit(ExitCode.FILE_NOT_FOUND);
|
|
7705
|
-
}
|
|
7706
|
-
let bumpType;
|
|
7707
|
-
if (options.major) {
|
|
7708
|
-
bumpType = "major";
|
|
7709
|
-
} else if (options.minor) {
|
|
7710
|
-
bumpType = "minor";
|
|
7711
|
-
} else if (options.patch) {
|
|
7712
|
-
bumpType = "patch";
|
|
7713
|
-
} else {
|
|
7714
|
-
error("\uBC84\uC804 \uC720\uD615\uC744 \uC9C0\uC815\uD558\uC138\uC694: --major, --minor, \uB610\uB294 --patch");
|
|
7715
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
7716
|
-
}
|
|
7717
|
-
const contentResult = await readFile(constitutionPath);
|
|
7718
|
-
if (!contentResult.success) {
|
|
7719
|
-
error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7720
|
-
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
7721
|
-
}
|
|
7722
|
-
const parseResult = parseConstitution(contentResult.data);
|
|
7723
|
-
if (!parseResult.success) {
|
|
7724
|
-
error(`Constitution \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
|
|
7725
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
7726
|
-
}
|
|
7727
|
-
const currentVersion = parseResult.data.metadata.version;
|
|
7728
|
-
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
7729
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
7730
|
-
let updatedContent = contentResult.data;
|
|
7731
|
-
updatedContent = updatedContent.replace(
|
|
7732
|
-
/^version:\s*.+$/m,
|
|
7733
|
-
`version: ${newVersion}`
|
|
7734
|
-
);
|
|
7735
|
-
if (/^updated:/m.test(updatedContent)) {
|
|
7736
|
-
updatedContent = updatedContent.replace(
|
|
7737
|
-
/^updated:\s*.+$/m,
|
|
7738
|
-
`updated: ${today}`
|
|
7739
|
-
);
|
|
7740
|
-
} else {
|
|
7741
|
-
updatedContent = updatedContent.replace(
|
|
7742
|
-
/^(version:\s*.+)$/m,
|
|
7743
|
-
`$1
|
|
7744
|
-
updated: ${today}`
|
|
7745
|
-
);
|
|
7746
|
-
}
|
|
7747
|
-
await writeFile(constitutionPath, updatedContent);
|
|
7748
|
-
const changeType = bumpType === "major" ? "changed" : bumpType === "minor" ? "added" : "fixed";
|
|
7749
|
-
const changeDescription = options.message || `Constitution ${bumpType} \uC5C5\uB370\uC774\uD2B8`;
|
|
7750
|
-
const newEntry = createChangelogEntry(
|
|
7751
|
-
currentVersion,
|
|
7752
|
-
bumpType,
|
|
7753
|
-
[{ type: changeType, description: changeDescription }],
|
|
7754
|
-
options.message
|
|
7755
|
-
);
|
|
7756
|
-
let existingEntries = [];
|
|
7757
|
-
if (await fileExists(changelogPath)) {
|
|
7758
|
-
const changelogContent = await readFile(changelogPath);
|
|
7759
|
-
if (changelogContent.success) {
|
|
7760
|
-
const parsed = parseChangelog(changelogContent.data);
|
|
7761
|
-
if (parsed.success) {
|
|
7762
|
-
existingEntries = parsed.data;
|
|
7763
|
-
}
|
|
7764
|
-
}
|
|
8127
|
+
}
|
|
8128
|
+
async function runVersion() {
|
|
8129
|
+
const cwd = process.cwd();
|
|
8130
|
+
const result = await readConstitution(cwd);
|
|
8131
|
+
if (!result.success) {
|
|
8132
|
+
error(result.error.message);
|
|
8133
|
+
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
7765
8134
|
}
|
|
7766
|
-
|
|
7767
|
-
const newChangelog = generateChangelog(allEntries);
|
|
7768
|
-
await writeFile(changelogPath, newChangelog);
|
|
7769
|
-
success2(`Constitution \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8: ${currentVersion} \u2192 ${newVersion}`);
|
|
7770
|
-
info(`CHANGELOG \uC5C5\uB370\uC774\uD2B8: ${changelogPath}`);
|
|
8135
|
+
console.log(result.data.parsed.metadata.version);
|
|
7771
8136
|
}
|
|
7772
|
-
async function
|
|
8137
|
+
async function runBump(options) {
|
|
7773
8138
|
const cwd = process.cwd();
|
|
7774
|
-
const
|
|
7775
|
-
if (!
|
|
7776
|
-
|
|
7777
|
-
|
|
8139
|
+
const bumpType = determineBumpType(options);
|
|
8140
|
+
if (!bumpType) {
|
|
8141
|
+
error("\uBC84\uC804 \uC720\uD615\uC744 \uC9C0\uC815\uD558\uC138\uC694: --major, --minor, \uB610\uB294 --patch");
|
|
8142
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
7778
8143
|
}
|
|
7779
|
-
const
|
|
7780
|
-
if (!
|
|
7781
|
-
error(
|
|
8144
|
+
const result = await executeBump(cwd, bumpType, options.message);
|
|
8145
|
+
if (!result.success) {
|
|
8146
|
+
error(result.error.message);
|
|
7782
8147
|
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
7783
8148
|
}
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
8149
|
+
success2(`Constitution \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8: ${result.data.previousVersion} \u2192 ${result.data.newVersion}`);
|
|
8150
|
+
info(`CHANGELOG \uC5C5\uB370\uC774\uD2B8: ${result.data.changelogPath}`);
|
|
8151
|
+
}
|
|
8152
|
+
async function runHistory(options) {
|
|
8153
|
+
const cwd = process.cwd();
|
|
7789
8154
|
const count = parseInt(options.count, 10) || 10;
|
|
7790
|
-
const
|
|
8155
|
+
const result = await getHistory(cwd, count);
|
|
8156
|
+
if (!result.success) {
|
|
8157
|
+
error(result.error.message);
|
|
8158
|
+
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
8159
|
+
}
|
|
8160
|
+
const { entries } = result.data;
|
|
7791
8161
|
if (entries.length === 0) {
|
|
7792
8162
|
info("\uBCC0\uACBD \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7793
8163
|
return;
|
|
@@ -7807,36 +8177,22 @@ async function runHistory(options) {
|
|
|
7807
8177
|
}
|
|
7808
8178
|
async function runValidate2() {
|
|
7809
8179
|
const cwd = process.cwd();
|
|
7810
|
-
const
|
|
7811
|
-
if (!
|
|
7812
|
-
error(
|
|
7813
|
-
process.exit(ExitCode.
|
|
7814
|
-
}
|
|
7815
|
-
const contentResult = await readFile(constitutionPath);
|
|
7816
|
-
if (!contentResult.success) {
|
|
7817
|
-
error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7818
|
-
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
7819
|
-
}
|
|
7820
|
-
const parseResult = parseConstitution(contentResult.data);
|
|
7821
|
-
if (!parseResult.success) {
|
|
7822
|
-
error(`Constitution \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
|
|
7823
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
7824
|
-
}
|
|
7825
|
-
const validationResult = validateConstitution(parseResult.data);
|
|
7826
|
-
if (!validationResult.success) {
|
|
7827
|
-
error(`Constitution \uAC80\uC99D \uC2E4\uD328: ${validationResult.error.message}`);
|
|
7828
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
8180
|
+
const result = await executeValidateConstitution(cwd);
|
|
8181
|
+
if (!result.success) {
|
|
8182
|
+
error(result.error.message);
|
|
8183
|
+
process.exit(ExitCode.VALIDATION_FAILED);
|
|
7829
8184
|
}
|
|
8185
|
+
const constitution = result.data;
|
|
7830
8186
|
success2("Constitution \uAC80\uC99D \uD1B5\uACFC");
|
|
7831
|
-
info(`\uD504\uB85C\uC81D\uD2B8: ${
|
|
7832
|
-
info(`\uBC84\uC804: ${
|
|
7833
|
-
info(`\uC6D0\uCE59 \uC218: ${
|
|
7834
|
-
info(`\uAE08\uC9C0 \uC0AC\uD56D \uC218: ${
|
|
8187
|
+
info(`\uD504\uB85C\uC81D\uD2B8: ${constitution.projectName}`);
|
|
8188
|
+
info(`\uBC84\uC804: ${constitution.metadata.version}`);
|
|
8189
|
+
info(`\uC6D0\uCE59 \uC218: ${constitution.principles.length}`);
|
|
8190
|
+
info(`\uAE08\uC9C0 \uC0AC\uD56D \uC218: ${constitution.forbidden.length}`);
|
|
7835
8191
|
}
|
|
7836
8192
|
|
|
7837
8193
|
// src/cli/commands/start.ts
|
|
7838
8194
|
import path16 from "path";
|
|
7839
|
-
import { promises as
|
|
8195
|
+
import { promises as fs9 } from "fs";
|
|
7840
8196
|
init_errors();
|
|
7841
8197
|
init_fs();
|
|
7842
8198
|
function registerStartCommand(program2) {
|
|
@@ -7850,7 +8206,7 @@ function registerStartCommand(program2) {
|
|
|
7850
8206
|
});
|
|
7851
8207
|
}
|
|
7852
8208
|
async function runStart(options) {
|
|
7853
|
-
const projectStatus = await
|
|
8209
|
+
const projectStatus = await getProjectStatus2();
|
|
7854
8210
|
if (!projectStatus.initialized) {
|
|
7855
8211
|
info("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
|
|
7856
8212
|
newline();
|
|
@@ -7874,7 +8230,7 @@ async function runStart(options) {
|
|
|
7874
8230
|
newline();
|
|
7875
8231
|
displayWorkflowMenu(projectStatus);
|
|
7876
8232
|
}
|
|
7877
|
-
async function
|
|
8233
|
+
async function getProjectStatus2() {
|
|
7878
8234
|
const projectRoot = await findSddRoot();
|
|
7879
8235
|
if (!projectRoot) {
|
|
7880
8236
|
return {
|
|
@@ -7903,7 +8259,7 @@ async function getProjectStatus() {
|
|
|
7903
8259
|
const specs = [];
|
|
7904
8260
|
if (await directoryExists(specsPath)) {
|
|
7905
8261
|
try {
|
|
7906
|
-
const dirs = await
|
|
8262
|
+
const dirs = await fs9.readdir(specsPath);
|
|
7907
8263
|
for (const dir of dirs) {
|
|
7908
8264
|
const specPath = path16.join(specsPath, dir, "spec.md");
|
|
7909
8265
|
if (await fileExists(specPath)) {
|
|
@@ -8140,7 +8496,7 @@ function displayConstitutionGuide() {
|
|
|
8140
8496
|
|
|
8141
8497
|
// src/cli/commands/migrate.ts
|
|
8142
8498
|
import path18 from "path";
|
|
8143
|
-
import { promises as
|
|
8499
|
+
import { promises as fs11 } from "fs";
|
|
8144
8500
|
init_errors();
|
|
8145
8501
|
init_fs();
|
|
8146
8502
|
init_new();
|
|
@@ -8151,7 +8507,7 @@ init_types();
|
|
|
8151
8507
|
init_errors();
|
|
8152
8508
|
init_fs();
|
|
8153
8509
|
import path17 from "path";
|
|
8154
|
-
import
|
|
8510
|
+
import fs10 from "fs/promises";
|
|
8155
8511
|
async function detectExternalTools(projectRoot) {
|
|
8156
8512
|
try {
|
|
8157
8513
|
const results = [];
|
|
@@ -8188,13 +8544,13 @@ async function detectOpenSpec(projectRoot) {
|
|
|
8188
8544
|
}
|
|
8189
8545
|
const specs = [];
|
|
8190
8546
|
if (hasSpecs) {
|
|
8191
|
-
const specDirs = await
|
|
8547
|
+
const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
|
|
8192
8548
|
for (const entry of specDirs) {
|
|
8193
8549
|
if (entry.isDirectory()) {
|
|
8194
8550
|
const specPath = path17.join(specsPath, entry.name);
|
|
8195
8551
|
const specFile = path17.join(specPath, "spec.md");
|
|
8196
8552
|
if (await fileExists(specFile)) {
|
|
8197
|
-
const content = await
|
|
8553
|
+
const content = await fs10.readFile(specFile, "utf-8");
|
|
8198
8554
|
const title2 = extractTitle2(content);
|
|
8199
8555
|
const status = extractFrontmatterField(content, "status");
|
|
8200
8556
|
specs.push({
|
|
@@ -8229,7 +8585,7 @@ async function detectSpecKit(projectRoot) {
|
|
|
8229
8585
|
return null;
|
|
8230
8586
|
}
|
|
8231
8587
|
const specs = [];
|
|
8232
|
-
const specDirs = await
|
|
8588
|
+
const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
|
|
8233
8589
|
for (const entry of specDirs) {
|
|
8234
8590
|
if (entry.isDirectory()) {
|
|
8235
8591
|
const specPath = path17.join(specsPath, entry.name);
|
|
@@ -8243,7 +8599,7 @@ async function detectSpecKit(projectRoot) {
|
|
|
8243
8599
|
let title2;
|
|
8244
8600
|
let status;
|
|
8245
8601
|
if (hasSpec) {
|
|
8246
|
-
const content = await
|
|
8602
|
+
const content = await fs10.readFile(specFile, "utf-8");
|
|
8247
8603
|
title2 = extractTitle2(content);
|
|
8248
8604
|
status = extractFrontmatterField(content, "status");
|
|
8249
8605
|
}
|
|
@@ -8275,13 +8631,13 @@ async function detectSdd(projectRoot) {
|
|
|
8275
8631
|
return null;
|
|
8276
8632
|
}
|
|
8277
8633
|
const specs = [];
|
|
8278
|
-
const specDirs = await
|
|
8634
|
+
const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
|
|
8279
8635
|
for (const entry of specDirs) {
|
|
8280
8636
|
if (entry.isDirectory()) {
|
|
8281
8637
|
const specPath = path17.join(specsPath, entry.name);
|
|
8282
8638
|
const specFile = path17.join(specPath, "spec.md");
|
|
8283
8639
|
if (await fileExists(specFile)) {
|
|
8284
|
-
const content = await
|
|
8640
|
+
const content = await fs10.readFile(specFile, "utf-8");
|
|
8285
8641
|
const title2 = extractTitle2(content);
|
|
8286
8642
|
const status = extractFrontmatterField(content, "status");
|
|
8287
8643
|
specs.push({
|
|
@@ -8311,7 +8667,7 @@ async function migrateFromOpenSpec(sourcePath, targetPath, options = {}) {
|
|
|
8311
8667
|
if (!await directoryExists(specsPath)) {
|
|
8312
8668
|
return failure(new ChangeError("OpenSpec specs \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
8313
8669
|
}
|
|
8314
|
-
const specDirs = await
|
|
8670
|
+
const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
|
|
8315
8671
|
for (const entry of specDirs) {
|
|
8316
8672
|
if (!entry.isDirectory()) continue;
|
|
8317
8673
|
const sourceSpecPath = path17.join(specsPath, entry.name);
|
|
@@ -8324,18 +8680,18 @@ async function migrateFromOpenSpec(sourcePath, targetPath, options = {}) {
|
|
|
8324
8680
|
}
|
|
8325
8681
|
try {
|
|
8326
8682
|
if (!options.dryRun) {
|
|
8327
|
-
await
|
|
8328
|
-
const files = await
|
|
8683
|
+
await fs10.mkdir(targetSpecPath, { recursive: true });
|
|
8684
|
+
const files = await fs10.readdir(sourceSpecPath);
|
|
8329
8685
|
for (const file of files) {
|
|
8330
8686
|
const sourceFile = path17.join(sourceSpecPath, file);
|
|
8331
8687
|
const targetFile = path17.join(targetSpecPath, file);
|
|
8332
|
-
const stat = await
|
|
8688
|
+
const stat = await fs10.stat(sourceFile);
|
|
8333
8689
|
if (stat.isFile()) {
|
|
8334
|
-
let content = await
|
|
8690
|
+
let content = await fs10.readFile(sourceFile, "utf-8");
|
|
8335
8691
|
if (file === "spec.md") {
|
|
8336
8692
|
content = convertOpenSpecToSdd(content, entry.name);
|
|
8337
8693
|
}
|
|
8338
|
-
await
|
|
8694
|
+
await fs10.writeFile(targetFile, content);
|
|
8339
8695
|
}
|
|
8340
8696
|
}
|
|
8341
8697
|
}
|
|
@@ -8365,7 +8721,7 @@ async function migrateFromSpecKit(sourcePath, targetPath, options = {}) {
|
|
|
8365
8721
|
if (!await directoryExists(specsPath)) {
|
|
8366
8722
|
return failure(new ChangeError("Spec Kit specs \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
|
|
8367
8723
|
}
|
|
8368
|
-
const specDirs = await
|
|
8724
|
+
const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
|
|
8369
8725
|
for (const entry of specDirs) {
|
|
8370
8726
|
if (!entry.isDirectory()) continue;
|
|
8371
8727
|
const sourceSpecPath = path17.join(specsPath, entry.name);
|
|
@@ -8378,18 +8734,18 @@ async function migrateFromSpecKit(sourcePath, targetPath, options = {}) {
|
|
|
8378
8734
|
}
|
|
8379
8735
|
try {
|
|
8380
8736
|
if (!options.dryRun) {
|
|
8381
|
-
await
|
|
8382
|
-
const files = await
|
|
8737
|
+
await fs10.mkdir(targetSpecPath, { recursive: true });
|
|
8738
|
+
const files = await fs10.readdir(sourceSpecPath);
|
|
8383
8739
|
for (const file of files) {
|
|
8384
8740
|
const sourceFile = path17.join(sourceSpecPath, file);
|
|
8385
8741
|
const targetFile = path17.join(targetSpecPath, file);
|
|
8386
|
-
const stat = await
|
|
8742
|
+
const stat = await fs10.stat(sourceFile);
|
|
8387
8743
|
if (stat.isFile()) {
|
|
8388
|
-
let content = await
|
|
8744
|
+
let content = await fs10.readFile(sourceFile, "utf-8");
|
|
8389
8745
|
if (file === "spec.md") {
|
|
8390
8746
|
content = convertSpecKitToSdd(content, entry.name);
|
|
8391
8747
|
}
|
|
8392
|
-
await
|
|
8748
|
+
await fs10.writeFile(targetFile, content);
|
|
8393
8749
|
}
|
|
8394
8750
|
}
|
|
8395
8751
|
}
|
|
@@ -8547,7 +8903,7 @@ async function runMigrateDocs(source, options) {
|
|
|
8547
8903
|
const sourcePath = path18.resolve(source);
|
|
8548
8904
|
let files = [];
|
|
8549
8905
|
try {
|
|
8550
|
-
const stat = await
|
|
8906
|
+
const stat = await fs11.stat(sourcePath);
|
|
8551
8907
|
if (stat.isDirectory()) {
|
|
8552
8908
|
files = await collectMarkdownFiles(sourcePath);
|
|
8553
8909
|
} else if (stat.isFile()) {
|
|
@@ -8590,7 +8946,7 @@ async function runMigrateDocs(source, options) {
|
|
|
8590
8946
|
}
|
|
8591
8947
|
async function migrateDocument(filePath, outputDir, dryRun) {
|
|
8592
8948
|
try {
|
|
8593
|
-
const content = await
|
|
8949
|
+
const content = await fs11.readFile(filePath, "utf-8");
|
|
8594
8950
|
const analysis = analyzeDocument(content);
|
|
8595
8951
|
const featureId = generateFeatureId(analysis.title || path18.basename(filePath, ".md"));
|
|
8596
8952
|
const specContent = generateSpec({
|
|
@@ -8658,7 +9014,7 @@ async function runAnalyze(file) {
|
|
|
8658
9014
|
error(`\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${file}`);
|
|
8659
9015
|
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
8660
9016
|
}
|
|
8661
|
-
const content = await
|
|
9017
|
+
const content = await fs11.readFile(filePath, "utf-8");
|
|
8662
9018
|
const analysis = analyzeDocument(content);
|
|
8663
9019
|
info(`\u{1F4CA} \uBB38\uC11C \uBD84\uC11D: ${path18.basename(file)}`);
|
|
8664
9020
|
newline();
|
|
@@ -8719,7 +9075,7 @@ async function runScan(dir, options) {
|
|
|
8719
9075
|
const results = [];
|
|
8720
9076
|
for (const file of files) {
|
|
8721
9077
|
try {
|
|
8722
|
-
const content = await
|
|
9078
|
+
const content = await fs11.readFile(file, "utf-8");
|
|
8723
9079
|
const analysis = analyzeDocument(content);
|
|
8724
9080
|
results.push({ file, analysis });
|
|
8725
9081
|
} catch {
|
|
@@ -8781,7 +9137,7 @@ async function collectMarkdownFiles(dirPath) {
|
|
|
8781
9137
|
async function collectFilesWithExtensions(dirPath, extensions) {
|
|
8782
9138
|
const files = [];
|
|
8783
9139
|
async function scan(dir) {
|
|
8784
|
-
const entries = await
|
|
9140
|
+
const entries = await fs11.readdir(dir, { withFileTypes: true });
|
|
8785
9141
|
for (const entry of entries) {
|
|
8786
9142
|
const fullPath = path18.join(dir, entry.name);
|
|
8787
9143
|
if (entry.isDirectory()) {
|
|
@@ -9294,8 +9650,129 @@ async function runCiCheck(options) {
|
|
|
9294
9650
|
|
|
9295
9651
|
// src/cli/commands/transition.ts
|
|
9296
9652
|
import path20 from "path";
|
|
9653
|
+
import { promises as fs12 } from "fs";
|
|
9297
9654
|
init_errors();
|
|
9298
9655
|
init_fs();
|
|
9656
|
+
init_types();
|
|
9657
|
+
async function getExistingChangeIds(sddPath) {
|
|
9658
|
+
const changesPath = path20.join(sddPath, "changes");
|
|
9659
|
+
try {
|
|
9660
|
+
const dirs = await fs12.readdir(changesPath);
|
|
9661
|
+
return dirs.filter((d) => d.startsWith("CHG-"));
|
|
9662
|
+
} catch {
|
|
9663
|
+
return [];
|
|
9664
|
+
}
|
|
9665
|
+
}
|
|
9666
|
+
async function extractProposalTitle(proposalPath) {
|
|
9667
|
+
if (!await fileExists(proposalPath)) {
|
|
9668
|
+
return "";
|
|
9669
|
+
}
|
|
9670
|
+
const content = await readFile(proposalPath);
|
|
9671
|
+
if (!content.success) {
|
|
9672
|
+
return "";
|
|
9673
|
+
}
|
|
9674
|
+
const titleMatch = content.data.match(/^#\s+(.+)$/m);
|
|
9675
|
+
return titleMatch ? titleMatch[1] : "";
|
|
9676
|
+
}
|
|
9677
|
+
async function transitionNewToChange(sddPath, specId, options) {
|
|
9678
|
+
const specsPath = path20.join(sddPath, "specs");
|
|
9679
|
+
const specPath = path20.join(specsPath, specId, "spec.md");
|
|
9680
|
+
if (!await fileExists(specPath)) {
|
|
9681
|
+
return failure(new Error(`\uC2A4\uD399\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${specId}`));
|
|
9682
|
+
}
|
|
9683
|
+
const existingIds = await getExistingChangeIds(sddPath);
|
|
9684
|
+
const changeId = generateChangeId(existingIds);
|
|
9685
|
+
const changesPath = path20.join(sddPath, "changes");
|
|
9686
|
+
const changePath = path20.join(changesPath, changeId);
|
|
9687
|
+
await ensureDir(changePath);
|
|
9688
|
+
const title2 = options.title || `${specId} \uAE30\uB2A5 \uD655\uC7A5`;
|
|
9689
|
+
const reason = options.reason || "new \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
|
|
9690
|
+
const proposalContent = generateTransitionProposal(specId, title2, reason, "new-to-change");
|
|
9691
|
+
await writeFile(path20.join(changePath, "proposal.md"), proposalContent);
|
|
9692
|
+
const deltaContent = generateDeltaTemplate2(specId);
|
|
9693
|
+
await writeFile(path20.join(changePath, "delta.md"), deltaContent);
|
|
9694
|
+
const tasksContent = generateTasksTemplate2();
|
|
9695
|
+
await writeFile(path20.join(changePath, "tasks.md"), tasksContent);
|
|
9696
|
+
return success({
|
|
9697
|
+
changeId,
|
|
9698
|
+
changePath,
|
|
9699
|
+
filesCreated: ["proposal.md", "delta.md", "tasks.md"]
|
|
9700
|
+
});
|
|
9701
|
+
}
|
|
9702
|
+
async function transitionChangeToNew(sddPath, changeId, options) {
|
|
9703
|
+
const changePath = path20.join(sddPath, "changes", changeId);
|
|
9704
|
+
if (!await directoryExists(changePath)) {
|
|
9705
|
+
return failure(new Error(`\uBCC0\uACBD \uC81C\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${changeId}`));
|
|
9706
|
+
}
|
|
9707
|
+
const proposalPath = path20.join(changePath, "proposal.md");
|
|
9708
|
+
const extractedTitle = await extractProposalTitle(proposalPath);
|
|
9709
|
+
const featureName = options.name || extractedTitle.toLowerCase().replace(/\s+/g, "-") || `feature-from-${changeId}`;
|
|
9710
|
+
const specsPath = path20.join(sddPath, "specs");
|
|
9711
|
+
const newSpecPath = path20.join(specsPath, featureName);
|
|
9712
|
+
if (await directoryExists(newSpecPath)) {
|
|
9713
|
+
return failure(new Error(`\uC2A4\uD399\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${featureName}`));
|
|
9714
|
+
}
|
|
9715
|
+
await ensureDir(newSpecPath);
|
|
9716
|
+
const reason = options.reason || "change \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
|
|
9717
|
+
const specContent = generateTransitionSpec(featureName, extractedTitle || featureName, reason, changeId);
|
|
9718
|
+
await writeFile(path20.join(newSpecPath, "spec.md"), specContent);
|
|
9719
|
+
const planContent = generatePlanTemplate(featureName);
|
|
9720
|
+
await writeFile(path20.join(newSpecPath, "plan.md"), planContent);
|
|
9721
|
+
const tasksContent = generateTasksTemplate2();
|
|
9722
|
+
await writeFile(path20.join(newSpecPath, "tasks.md"), tasksContent);
|
|
9723
|
+
const statusPath = path20.join(changePath, ".status");
|
|
9724
|
+
await writeFile(statusPath, JSON.stringify({
|
|
9725
|
+
status: "transitioned",
|
|
9726
|
+
transitionedTo: featureName,
|
|
9727
|
+
transitionedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9728
|
+
reason
|
|
9729
|
+
}, null, 2));
|
|
9730
|
+
return success({
|
|
9731
|
+
featureName,
|
|
9732
|
+
featurePath: newSpecPath,
|
|
9733
|
+
filesCreated: ["spec.md", "plan.md", "tasks.md"],
|
|
9734
|
+
originalChangeId: changeId
|
|
9735
|
+
});
|
|
9736
|
+
}
|
|
9737
|
+
function getTransitionGuide() {
|
|
9738
|
+
return `=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658 \uAC00\uC774\uB4DC ===
|
|
9739
|
+
|
|
9740
|
+
## new \u2192 change \uC804\uD658
|
|
9741
|
+
|
|
9742
|
+
\uC0AC\uC6A9 \uC2DC\uC810:
|
|
9743
|
+
- \uC0C8 \uAE30\uB2A5 \uC791\uC131 \uC911 \uAE30\uC874 \uC2A4\uD399\uACFC \uC911\uBCF5 \uBC1C\uACAC
|
|
9744
|
+
- \uAE30\uC874 \uAE30\uB2A5 \uD655\uC7A5\uC774 \uB354 \uC801\uC808\uD55C \uACBD\uC6B0
|
|
9745
|
+
- \uC758\uC874\uC131 \uBD84\uC11D \uACB0\uACFC \uAE30\uC874 \uC2A4\uD399 \uC218\uC815 \uD544\uC694
|
|
9746
|
+
|
|
9747
|
+
\uBA85\uB839\uC5B4:
|
|
9748
|
+
sdd transition new-to-change <spec-id>
|
|
9749
|
+
-t, --title <title> : \uBCC0\uACBD \uC81C\uC548 \uC81C\uBAA9
|
|
9750
|
+
-r, --reason <reason>: \uC804\uD658 \uC0AC\uC720
|
|
9751
|
+
|
|
9752
|
+
## change \u2192 new \uC804\uD658
|
|
9753
|
+
|
|
9754
|
+
\uC0AC\uC6A9 \uC2DC\uC810:
|
|
9755
|
+
- \uBCC0\uACBD \uBC94\uC704\uAC00 \uB108\uBB34 \uCEE4\uC11C \uBCC4\uB3C4 \uAE30\uB2A5\uC73C\uB85C \uBD84\uB9AC \uD544\uC694
|
|
9756
|
+
- \uAE30\uC874 \uC2A4\uD399\uACFC \uB3C5\uB9BD\uC801\uC778 \uC0C8 \uAE30\uB2A5\uC73C\uB85C \uBC1C\uC804
|
|
9757
|
+
- \uC601\uD5A5\uB3C4 \uBD84\uC11D \uACB0\uACFC \uBD84\uB9AC\uAC00 \uC548\uC804
|
|
9758
|
+
|
|
9759
|
+
\uBA85\uB839\uC5B4:
|
|
9760
|
+
sdd transition change-to-new <change-id>
|
|
9761
|
+
-n, --name <name> : \uC0C8 \uAE30\uB2A5 \uC774\uB984
|
|
9762
|
+
-r, --reason <reason>: \uC804\uD658 \uC0AC\uC720
|
|
9763
|
+
|
|
9764
|
+
## \uC804\uD658 \uD310\uB2E8 \uAE30\uC900
|
|
9765
|
+
|
|
9766
|
+
new \u2192 change:
|
|
9767
|
+
- \uC601\uD5A5\uBC1B\uB294 \uC2A4\uD399 \uC218 \u2264 3\uAC1C
|
|
9768
|
+
- \uBCC0\uACBD\uC774 \uAE30\uC874 \uAE30\uB2A5\uC758 \uC790\uC5F0\uC2A4\uB7EC\uC6B4 \uD655\uC7A5
|
|
9769
|
+
- \uC0C8 \uC2DC\uB098\uB9AC\uC624 \uCD94\uAC00\uBCF4\uB2E4 \uAE30\uC874 \uC2DC\uB098\uB9AC\uC624 \uC218\uC815 \uC911\uC2EC
|
|
9770
|
+
|
|
9771
|
+
change \u2192 new:
|
|
9772
|
+
- \uC601\uD5A5\uBC1B\uB294 \uC2A4\uD399 \uC218 > 3\uAC1C
|
|
9773
|
+
- \uC0C8\uB85C\uC6B4 \uAC1C\uB150/\uB3C4\uBA54\uC778 \uB3C4\uC785
|
|
9774
|
+
- \uAE30\uC874 \uC2A4\uD399\uACFC \uB3C5\uB9BD\uC801\uC73C\uB85C \uD14C\uC2A4\uD2B8 \uAC00\uB2A5`;
|
|
9775
|
+
}
|
|
9299
9776
|
function registerTransitionCommand(program2) {
|
|
9300
9777
|
const transition = program2.command("transition").description("\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uAC04 \uC804\uD658\uC744 \uC218\uD589\uD569\uB2C8\uB2E4");
|
|
9301
9778
|
transition.command("new-to-change <spec-id>").description("\uC0C8 \uAE30\uB2A5 \uC791\uC5C5\uC744 \uAE30\uC874 \uC2A4\uD399 \uBCC0\uACBD\uC73C\uB85C \uC804\uD658\uD569\uB2C8\uB2E4").option("-t, --title <title>", "\uBCC0\uACBD \uC81C\uC548 \uC81C\uBAA9").option("-r, --reason <reason>", "\uC804\uD658 \uC0AC\uC720").action(async (specId, options) => {
|
|
@@ -9325,32 +9802,16 @@ async function runNewToChange(specId, options) {
|
|
|
9325
9802
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
9326
9803
|
}
|
|
9327
9804
|
const sddPath = path20.join(projectRoot, ".sdd");
|
|
9328
|
-
const specsPath = path20.join(sddPath, "specs");
|
|
9329
|
-
const specPath = path20.join(specsPath, specId, "spec.md");
|
|
9330
|
-
if (!await fileExists(specPath)) {
|
|
9331
|
-
error(`\uC2A4\uD399\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${specId}`);
|
|
9332
|
-
info("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC2A4\uD399 \uBAA9\uB85D\uC740 `sdd list`\uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
9333
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
9334
|
-
}
|
|
9335
9805
|
info("=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658: new \u2192 change ===");
|
|
9336
9806
|
newline();
|
|
9337
9807
|
info(`\uB300\uC0C1 \uC2A4\uD399: ${specId}`);
|
|
9338
|
-
const
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
if (!specContent.success) {
|
|
9343
|
-
error("\uC2A4\uD399 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
9808
|
+
const result = await transitionNewToChange(sddPath, specId, options);
|
|
9809
|
+
if (!result.success) {
|
|
9810
|
+
error(result.error.message);
|
|
9811
|
+
info("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC2A4\uD399 \uBAA9\uB85D\uC740 `sdd list`\uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
9344
9812
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
9345
9813
|
}
|
|
9346
|
-
const
|
|
9347
|
-
const reason = options.reason || "new \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
|
|
9348
|
-
const proposalContent = generateTransitionProposal(specId, title2, reason, "new-to-change");
|
|
9349
|
-
await writeFile(path20.join(changePath, "proposal.md"), proposalContent);
|
|
9350
|
-
const deltaContent = generateDeltaTemplate(specId);
|
|
9351
|
-
await writeFile(path20.join(changePath, "delta.md"), deltaContent);
|
|
9352
|
-
const tasksContent = generateTasksTemplate();
|
|
9353
|
-
await writeFile(path20.join(changePath, "tasks.md"), tasksContent);
|
|
9814
|
+
const { changeId } = result.data;
|
|
9354
9815
|
newline();
|
|
9355
9816
|
success2(`\uC804\uD658 \uC644\uB8CC! \uBCC0\uACBD \uC81C\uC548\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
|
|
9356
9817
|
newline();
|
|
@@ -9373,55 +9834,26 @@ async function runChangeToNew(changeId, options) {
|
|
|
9373
9834
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
9374
9835
|
}
|
|
9375
9836
|
const sddPath = path20.join(projectRoot, ".sdd");
|
|
9376
|
-
const changePath = path20.join(sddPath, "changes", changeId);
|
|
9377
|
-
if (!await directoryExists(changePath)) {
|
|
9378
|
-
error(`\uBCC0\uACBD \uC81C\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${changeId}`);
|
|
9379
|
-
info("\uC9C4\uD589 \uC911\uC778 \uBCC0\uACBD \uBAA9\uB85D\uC740 `sdd change -l`\uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
9380
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
9381
|
-
}
|
|
9382
9837
|
info("=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658: change \u2192 new ===");
|
|
9383
9838
|
newline();
|
|
9384
9839
|
info(`\uC6D0\uBCF8 \uBCC0\uACBD: ${changeId}`);
|
|
9385
|
-
const
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
extractedTitle = titleMatch[1];
|
|
9393
|
-
}
|
|
9840
|
+
const result = await transitionChangeToNew(sddPath, changeId, options);
|
|
9841
|
+
if (!result.success) {
|
|
9842
|
+
error(result.error.message);
|
|
9843
|
+
if (result.error.message.includes("\uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4")) {
|
|
9844
|
+
info("\uC9C4\uD589 \uC911\uC778 \uBCC0\uACBD \uBAA9\uB85D\uC740 `sdd change -l`\uB85C \uD655\uC778\uD558\uC138\uC694.");
|
|
9845
|
+
} else if (result.error.message.includes("\uC774\uBBF8 \uC874\uC7AC")) {
|
|
9846
|
+
info("\uB2E4\uB978 \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694: --name <name>");
|
|
9394
9847
|
}
|
|
9395
|
-
}
|
|
9396
|
-
const featureName = options.name || extractedTitle.toLowerCase().replace(/\s+/g, "-") || `feature-from-${changeId}`;
|
|
9397
|
-
const specsPath = path20.join(sddPath, "specs");
|
|
9398
|
-
const newSpecPath = path20.join(specsPath, featureName);
|
|
9399
|
-
if (await directoryExists(newSpecPath)) {
|
|
9400
|
-
error(`\uC2A4\uD399\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${featureName}`);
|
|
9401
|
-
info("\uB2E4\uB978 \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694: --name <name>");
|
|
9402
9848
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
9403
9849
|
}
|
|
9404
|
-
|
|
9405
|
-
const reason = options.reason || "change \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
|
|
9406
|
-
const specContent = generateTransitionSpec(featureName, extractedTitle || featureName, reason, changeId);
|
|
9407
|
-
await writeFile(path20.join(newSpecPath, "spec.md"), specContent);
|
|
9408
|
-
const planContent = generatePlanTemplate(featureName);
|
|
9409
|
-
await writeFile(path20.join(newSpecPath, "plan.md"), planContent);
|
|
9410
|
-
const tasksContent = generateTasksTemplate();
|
|
9411
|
-
await writeFile(path20.join(newSpecPath, "tasks.md"), tasksContent);
|
|
9412
|
-
const statusPath = path20.join(changePath, ".status");
|
|
9413
|
-
await writeFile(statusPath, JSON.stringify({
|
|
9414
|
-
status: "transitioned",
|
|
9415
|
-
transitionedTo: featureName,
|
|
9416
|
-
transitionedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9417
|
-
reason
|
|
9418
|
-
}, null, 2));
|
|
9850
|
+
const { featureName, originalChangeId } = result.data;
|
|
9419
9851
|
newline();
|
|
9420
9852
|
success2(`\uC804\uD658 \uC644\uB8CC! \uC0C8 \uAE30\uB2A5 \uC2A4\uD399\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
|
|
9421
9853
|
newline();
|
|
9422
9854
|
info(`\uAE30\uB2A5 \uC774\uB984: ${featureName}`);
|
|
9423
9855
|
info(`\uC704\uCE58: .sdd/specs/${featureName}/`);
|
|
9424
|
-
info(`\uC6D0\uBCF8 \uBCC0\uACBD ${
|
|
9856
|
+
info(`\uC6D0\uBCF8 \uBCC0\uACBD ${originalChangeId}\uC740 'transitioned' \uC0C1\uD0DC\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
|
|
9425
9857
|
newline();
|
|
9426
9858
|
info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
9427
9859
|
listItem(`1. .sdd/specs/${featureName}/spec.md \uD3B8\uC9D1`);
|
|
@@ -9433,43 +9865,7 @@ async function runChangeToNew(changeId, options) {
|
|
|
9433
9865
|
listItem("/sdd.new - \uBA85\uC138 \uC791\uC131 \uB3C4\uC6C0");
|
|
9434
9866
|
}
|
|
9435
9867
|
function displayTransitionGuide() {
|
|
9436
|
-
|
|
9437
|
-
newline();
|
|
9438
|
-
info("## new \u2192 change \uC804\uD658");
|
|
9439
|
-
newline();
|
|
9440
|
-
info("\uC0AC\uC6A9 \uC2DC\uC810:");
|
|
9441
|
-
listItem("\uC0C8 \uAE30\uB2A5 \uC791\uC131 \uC911 \uAE30\uC874 \uC2A4\uD399\uACFC \uC911\uBCF5 \uBC1C\uACAC");
|
|
9442
|
-
listItem("\uAE30\uC874 \uAE30\uB2A5 \uD655\uC7A5\uC774 \uB354 \uC801\uC808\uD55C \uACBD\uC6B0");
|
|
9443
|
-
listItem("\uC758\uC874\uC131 \uBD84\uC11D \uACB0\uACFC \uAE30\uC874 \uC2A4\uD399 \uC218\uC815 \uD544\uC694");
|
|
9444
|
-
newline();
|
|
9445
|
-
info("\uBA85\uB839\uC5B4:");
|
|
9446
|
-
listItem("sdd transition new-to-change <spec-id>");
|
|
9447
|
-
listItem(" -t, --title <title> : \uBCC0\uACBD \uC81C\uC548 \uC81C\uBAA9");
|
|
9448
|
-
listItem(" -r, --reason <reason>: \uC804\uD658 \uC0AC\uC720");
|
|
9449
|
-
newline();
|
|
9450
|
-
info("## change \u2192 new \uC804\uD658");
|
|
9451
|
-
newline();
|
|
9452
|
-
info("\uC0AC\uC6A9 \uC2DC\uC810:");
|
|
9453
|
-
listItem("\uBCC0\uACBD \uBC94\uC704\uAC00 \uB108\uBB34 \uCEE4\uC11C \uBCC4\uB3C4 \uAE30\uB2A5\uC73C\uB85C \uBD84\uB9AC \uD544\uC694");
|
|
9454
|
-
listItem("\uAE30\uC874 \uC2A4\uD399\uACFC \uB3C5\uB9BD\uC801\uC778 \uC0C8 \uAE30\uB2A5\uC73C\uB85C \uBC1C\uC804");
|
|
9455
|
-
listItem("\uC601\uD5A5\uB3C4 \uBD84\uC11D \uACB0\uACFC \uBD84\uB9AC\uAC00 \uC548\uC804");
|
|
9456
|
-
newline();
|
|
9457
|
-
info("\uBA85\uB839\uC5B4:");
|
|
9458
|
-
listItem("sdd transition change-to-new <change-id>");
|
|
9459
|
-
listItem(" -n, --name <name> : \uC0C8 \uAE30\uB2A5 \uC774\uB984");
|
|
9460
|
-
listItem(" -r, --reason <reason>: \uC804\uD658 \uC0AC\uC720");
|
|
9461
|
-
newline();
|
|
9462
|
-
info("## \uC804\uD658 \uD310\uB2E8 \uAE30\uC900");
|
|
9463
|
-
newline();
|
|
9464
|
-
info("new \u2192 change:");
|
|
9465
|
-
listItem("\uC601\uD5A5\uBC1B\uB294 \uC2A4\uD399 \uC218 \u2264 3\uAC1C");
|
|
9466
|
-
listItem("\uBCC0\uACBD\uC774 \uAE30\uC874 \uAE30\uB2A5\uC758 \uC790\uC5F0\uC2A4\uB7EC\uC6B4 \uD655\uC7A5");
|
|
9467
|
-
listItem("\uC0C8 \uC2DC\uB098\uB9AC\uC624 \uCD94\uAC00\uBCF4\uB2E4 \uAE30\uC874 \uC2DC\uB098\uB9AC\uC624 \uC218\uC815 \uC911\uC2EC");
|
|
9468
|
-
newline();
|
|
9469
|
-
info("change \u2192 new:");
|
|
9470
|
-
listItem("\uC601\uD5A5\uBC1B\uB294 \uC2A4\uD399 \uC218 > 3\uAC1C");
|
|
9471
|
-
listItem("\uC0C8\uB85C\uC6B4 \uAC1C\uB150/\uB3C4\uBA54\uC778 \uB3C4\uC785");
|
|
9472
|
-
listItem("\uAE30\uC874 \uC2A4\uD399\uACFC \uB3C5\uB9BD\uC801\uC73C\uB85C \uD14C\uC2A4\uD2B8 \uAC00\uB2A5");
|
|
9868
|
+
console.log(getTransitionGuide());
|
|
9473
9869
|
}
|
|
9474
9870
|
function generateTransitionProposal(specId, title2, reason, direction) {
|
|
9475
9871
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -9511,7 +9907,7 @@ transition_reason: "${reason}"
|
|
|
9511
9907
|
<!-- \uCD94\uAC00 \uCC38\uACE0 \uC815\uBCF4\uAC00 \uC788\uB2E4\uBA74 \uC791\uC131\uD558\uC138\uC694 -->
|
|
9512
9908
|
`;
|
|
9513
9909
|
}
|
|
9514
|
-
function
|
|
9910
|
+
function generateDeltaTemplate2(specId) {
|
|
9515
9911
|
return `---
|
|
9516
9912
|
target: ${specId}
|
|
9517
9913
|
---
|
|
@@ -9622,7 +10018,7 @@ status: draft
|
|
|
9622
10018
|
<!-- \uAD6C\uD604 \uC2DC \uACE0\uB824\uD560 \uC704\uD5D8 \uC694\uC18C -->
|
|
9623
10019
|
`;
|
|
9624
10020
|
}
|
|
9625
|
-
function
|
|
10021
|
+
function generateTasksTemplate2() {
|
|
9626
10022
|
return `---
|
|
9627
10023
|
status: pending
|
|
9628
10024
|
---
|
|
@@ -9965,8 +10361,8 @@ function scoreDependencies(spec) {
|
|
|
9965
10361
|
const details = [];
|
|
9966
10362
|
const suggestions = [];
|
|
9967
10363
|
let score = 0;
|
|
9968
|
-
if (spec.
|
|
9969
|
-
const deps = Array.isArray(spec.
|
|
10364
|
+
if (spec.metadata.depends) {
|
|
10365
|
+
const deps = Array.isArray(spec.metadata.depends) ? spec.metadata.depends : [spec.metadata.depends];
|
|
9970
10366
|
if (deps.length > 0 && deps[0] !== null) {
|
|
9971
10367
|
score = maxScore;
|
|
9972
10368
|
details.push(`\uC758\uC874\uC131: ${deps.join(", ")}`);
|
|
@@ -9975,8 +10371,8 @@ function scoreDependencies(spec) {
|
|
|
9975
10371
|
details.push("\uC758\uC874\uC131 \uC5C6\uC74C (\uBA85\uC2DC\uC801 \uC120\uC5B8)");
|
|
9976
10372
|
}
|
|
9977
10373
|
} else {
|
|
9978
|
-
|
|
9979
|
-
|
|
10374
|
+
score = 5;
|
|
10375
|
+
details.push("\uC758\uC874\uC131 \uD544\uB4DC \uC5C6\uC74C (\uC554\uC2DC\uC801 \uC5C6\uC74C)");
|
|
9980
10376
|
}
|
|
9981
10377
|
return {
|
|
9982
10378
|
name: "\uC758\uC874\uC131 \uBA85\uC2DC",
|
|
@@ -10041,9 +10437,9 @@ function scoreConstitution(spec, hasConstitution) {
|
|
|
10041
10437
|
if (!hasConstitution) {
|
|
10042
10438
|
score = maxScore;
|
|
10043
10439
|
details.push("Constitution \uBBF8\uC124\uC815 (\uAC80\uC0AC \uC0DD\uB7B5)");
|
|
10044
|
-
} else if (spec.
|
|
10440
|
+
} else if (spec.metadata.constitution_version) {
|
|
10045
10441
|
score = maxScore;
|
|
10046
|
-
details.push(`Constitution \uBC84\uC804: ${spec.
|
|
10442
|
+
details.push(`Constitution \uBC84\uC804: ${spec.metadata.constitution_version}`);
|
|
10047
10443
|
} else {
|
|
10048
10444
|
details.push("constitution_version \uD544\uB4DC \uC5C6\uC74C");
|
|
10049
10445
|
suggestions.push("frontmatter\uC5D0 constitution_version\uC744 \uCD94\uAC00\uD558\uC138\uC694");
|
|
@@ -10089,20 +10485,20 @@ function scoreMetadata(spec) {
|
|
|
10089
10485
|
let score = 0;
|
|
10090
10486
|
const missingRequired = [];
|
|
10091
10487
|
for (const field of requiredFields) {
|
|
10092
|
-
if (spec.
|
|
10488
|
+
if (spec.metadata[field]) {
|
|
10093
10489
|
score += 2;
|
|
10094
10490
|
} else {
|
|
10095
10491
|
missingRequired.push(field);
|
|
10096
10492
|
}
|
|
10097
10493
|
}
|
|
10098
10494
|
for (const field of optionalFields) {
|
|
10099
|
-
if (spec.
|
|
10495
|
+
if (spec.metadata[field]) {
|
|
10100
10496
|
score += 1;
|
|
10101
10497
|
}
|
|
10102
10498
|
}
|
|
10103
10499
|
score = Math.min(maxScore, score);
|
|
10104
|
-
const presentFields = Object.keys(spec.
|
|
10105
|
-
(k) => spec.
|
|
10500
|
+
const presentFields = Object.keys(spec.metadata).filter(
|
|
10501
|
+
(k) => spec.metadata[k] !== null && spec.metadata[k] !== void 0
|
|
10106
10502
|
);
|
|
10107
10503
|
details.push(`\uBA54\uD0C0\uB370\uC774\uD130 \uD544\uB4DC: ${presentFields.length}\uAC1C`);
|
|
10108
10504
|
if (missingRequired.length > 0) {
|
|
@@ -10149,7 +10545,7 @@ async function analyzeSpecQuality(specPath, sddPath) {
|
|
|
10149
10545
|
const percentage = Math.round(totalScore / maxScore * 100);
|
|
10150
10546
|
const grade = getGrade(percentage);
|
|
10151
10547
|
const topSuggestions = items.filter((item) => item.suggestions.length > 0).sort((a, b) => a.percentage - b.percentage).slice(0, 3).flatMap((item) => item.suggestions);
|
|
10152
|
-
const specId = spec.
|
|
10548
|
+
const specId = spec.metadata.id || path23.basename(path23.dirname(specPath));
|
|
10153
10549
|
const summary = `\uC2A4\uD399 '${specId}'\uC758 \uD488\uC9C8 \uC810\uC218: ${totalScore}/${maxScore} (${percentage}%, \uB4F1\uAE09: ${grade})`;
|
|
10154
10550
|
return success({
|
|
10155
10551
|
specId,
|
|
@@ -10279,6 +10675,36 @@ function formatProjectQualityResult(result) {
|
|
|
10279
10675
|
// src/cli/commands/quality.ts
|
|
10280
10676
|
init_fs();
|
|
10281
10677
|
init_errors();
|
|
10678
|
+
init_types();
|
|
10679
|
+
async function executeQuality(feature, options, projectRoot) {
|
|
10680
|
+
const sddPath = path24.join(projectRoot, ".sdd");
|
|
10681
|
+
const minScore = parseInt(options.minScore || "0", 10);
|
|
10682
|
+
if (options.all || !feature) {
|
|
10683
|
+
const result2 = await analyzeProjectQuality(sddPath);
|
|
10684
|
+
if (!result2.success) {
|
|
10685
|
+
return failure(result2.error);
|
|
10686
|
+
}
|
|
10687
|
+
const formatted2 = options.json ? JSON.stringify(result2.data, null, 2) : formatProjectQualityResult(result2.data);
|
|
10688
|
+
return success({
|
|
10689
|
+
type: "project",
|
|
10690
|
+
data: result2.data,
|
|
10691
|
+
formatted: formatted2,
|
|
10692
|
+
passed: result2.data.averagePercentage >= minScore
|
|
10693
|
+
});
|
|
10694
|
+
}
|
|
10695
|
+
const specPath = path24.join(sddPath, "specs", feature, "spec.md");
|
|
10696
|
+
const result = await analyzeSpecQuality(specPath, sddPath);
|
|
10697
|
+
if (!result.success) {
|
|
10698
|
+
return failure(result.error);
|
|
10699
|
+
}
|
|
10700
|
+
const formatted = options.json ? JSON.stringify(result.data, null, 2) : formatQualityResult(result.data);
|
|
10701
|
+
return success({
|
|
10702
|
+
type: "spec",
|
|
10703
|
+
data: result.data,
|
|
10704
|
+
formatted,
|
|
10705
|
+
passed: result.data.percentage >= minScore
|
|
10706
|
+
});
|
|
10707
|
+
}
|
|
10282
10708
|
function registerQualityCommand(program2) {
|
|
10283
10709
|
program2.command("quality [feature]").description("\uC2A4\uD399 \uD488\uC9C8\uC744 \uBD84\uC11D\uD558\uACE0 \uC810\uC218\uB97C \uC0B0\uCD9C\uD569\uB2C8\uB2E4").option("-a, --all", "\uC804\uCCB4 \uD504\uB85C\uC81D\uD2B8 \uBD84\uC11D").option("--json", "JSON \uD615\uC2DD \uCD9C\uB825").option("--min-score <score>", "\uCD5C\uC18C \uC810\uC218 \uAE30\uC900 (\uC774\uD558 \uC2DC \uC5D0\uB7EC)", "0").action(async (feature, options) => {
|
|
10284
10710
|
try {
|
|
@@ -10295,41 +10721,17 @@ async function runQuality(feature, options) {
|
|
|
10295
10721
|
error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
10296
10722
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
10297
10723
|
}
|
|
10298
|
-
const
|
|
10299
|
-
const minScore = parseInt(options.minScore || "0", 10);
|
|
10300
|
-
if (options.all || !feature) {
|
|
10301
|
-
const result2 = await analyzeProjectQuality(sddPath);
|
|
10302
|
-
if (!result2.success) {
|
|
10303
|
-
error(result2.error.message);
|
|
10304
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
10305
|
-
}
|
|
10306
|
-
if (options.json) {
|
|
10307
|
-
console.log(JSON.stringify(result2.data, null, 2));
|
|
10308
|
-
} else {
|
|
10309
|
-
console.log(formatProjectQualityResult(result2.data));
|
|
10310
|
-
}
|
|
10311
|
-
if (result2.data.averagePercentage < minScore) {
|
|
10312
|
-
newline();
|
|
10313
|
-
error(`\uD488\uC9C8 \uC810\uC218\uAC00 \uCD5C\uC18C \uAE30\uC900(${minScore}%) \uBBF8\uB2EC\uC785\uB2C8\uB2E4.`);
|
|
10314
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
10315
|
-
}
|
|
10316
|
-
return;
|
|
10317
|
-
}
|
|
10318
|
-
const specPath = path24.join(sddPath, "specs", feature, "spec.md");
|
|
10319
|
-
const result = await analyzeSpecQuality(specPath, sddPath);
|
|
10724
|
+
const result = await executeQuality(feature, options, projectRoot);
|
|
10320
10725
|
if (!result.success) {
|
|
10321
10726
|
error(result.error.message);
|
|
10322
10727
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
10323
10728
|
}
|
|
10324
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
console.log(formatQualityResult(result.data));
|
|
10328
|
-
}
|
|
10329
|
-
if (result.data.percentage < minScore) {
|
|
10729
|
+
console.log(result.data.formatted);
|
|
10730
|
+
if (!result.data.passed) {
|
|
10731
|
+
const minScore = parseInt(options.minScore || "0", 10);
|
|
10330
10732
|
newline();
|
|
10331
10733
|
error(`\uD488\uC9C8 \uC810\uC218\uAC00 \uCD5C\uC18C \uAE30\uC900(${minScore}%) \uBBF8\uB2EC\uC785\uB2C8\uB2E4.`);
|
|
10332
|
-
process.exit(ExitCode.
|
|
10734
|
+
process.exit(ExitCode.VALIDATION_FAILED);
|
|
10333
10735
|
}
|
|
10334
10736
|
}
|
|
10335
10737
|
|
|
@@ -10498,7 +10900,7 @@ function renderHtmlReport(data) {
|
|
|
10498
10900
|
<td>${spec.description || "-"}</td>
|
|
10499
10901
|
</tr>
|
|
10500
10902
|
`).join("");
|
|
10501
|
-
const qualityRows = data.quality?.
|
|
10903
|
+
const qualityRows = data.quality?.specResults?.map((q) => `
|
|
10502
10904
|
<tr>
|
|
10503
10905
|
<td>${q.specId}</td>
|
|
10504
10906
|
<td>${q.percentage}%</td>
|
|
@@ -10506,7 +10908,7 @@ function renderHtmlReport(data) {
|
|
|
10506
10908
|
<td>${q.totalScore}/${q.maxScore}</td>
|
|
10507
10909
|
</tr>
|
|
10508
10910
|
`).join("") || "";
|
|
10509
|
-
const validationRows = data.validation?.
|
|
10911
|
+
const validationRows = data.validation?.files?.map((v) => `
|
|
10510
10912
|
<tr>
|
|
10511
10913
|
<td>${v.file}</td>
|
|
10512
10914
|
<td style="color:${v.errors.length > 0 ? "#ef4444" : "#22c55e"};">${v.errors.length > 0 ? "\u274C \uC2E4\uD328" : "\u2705 \uD1B5\uACFC"}</td>
|
|
@@ -10676,11 +11078,11 @@ function renderMarkdownReport(data) {
|
|
|
10676
11078
|
if (data.quality) {
|
|
10677
11079
|
lines.push("## \uD488\uC9C8 \uBD84\uC11D");
|
|
10678
11080
|
lines.push("");
|
|
10679
|
-
lines.push(`\uD3C9\uADE0 \uC810\uC218: **${data.quality.averagePercentage.toFixed(1)}%** (${data.quality.
|
|
11081
|
+
lines.push(`\uD3C9\uADE0 \uC810\uC218: **${data.quality.averagePercentage.toFixed(1)}%** (${data.quality.grade})`);
|
|
10680
11082
|
lines.push("");
|
|
10681
11083
|
lines.push("| \uC2A4\uD399 ID | \uC810\uC218 | \uB4F1\uAE09 |");
|
|
10682
11084
|
lines.push("|---------|------|------|");
|
|
10683
|
-
for (const q of data.quality.
|
|
11085
|
+
for (const q of data.quality.specResults || []) {
|
|
10684
11086
|
lines.push(`| ${q.specId} | ${q.percentage}% | ${q.grade} |`);
|
|
10685
11087
|
}
|
|
10686
11088
|
lines.push("");
|
|
@@ -10688,14 +11090,14 @@ function renderMarkdownReport(data) {
|
|
|
10688
11090
|
if (data.validation) {
|
|
10689
11091
|
lines.push("## \uAC80\uC99D \uACB0\uACFC");
|
|
10690
11092
|
lines.push("");
|
|
10691
|
-
lines.push(`- \
|
|
10692
|
-
lines.push(`- \
|
|
10693
|
-
lines.push(`- \uACBD\uACE0: ${data.validation.
|
|
11093
|
+
lines.push(`- \uD1B5\uACFC: ${data.validation.passed}\uAC1C`);
|
|
11094
|
+
lines.push(`- \uC2E4\uD328: ${data.validation.failed}\uAC1C`);
|
|
11095
|
+
lines.push(`- \uACBD\uACE0: ${data.validation.warnings}\uAC1C`);
|
|
10694
11096
|
lines.push("");
|
|
10695
|
-
if (data.validation.
|
|
11097
|
+
if (data.validation.failed > 0 || data.validation.warnings > 0) {
|
|
10696
11098
|
lines.push("### \uC0C1\uC138 \uACB0\uACFC");
|
|
10697
11099
|
lines.push("");
|
|
10698
|
-
for (const v of data.validation.
|
|
11100
|
+
for (const v of data.validation.files || []) {
|
|
10699
11101
|
if (v.errors.length > 0 || v.warnings.length > 0) {
|
|
10700
11102
|
lines.push(`#### ${v.file}`);
|
|
10701
11103
|
for (const e of v.errors) {
|
|
@@ -10717,6 +11119,41 @@ function renderMarkdownReport(data) {
|
|
|
10717
11119
|
// src/cli/commands/report.ts
|
|
10718
11120
|
init_fs();
|
|
10719
11121
|
init_errors();
|
|
11122
|
+
init_types();
|
|
11123
|
+
function isValidReportFormat(format) {
|
|
11124
|
+
return ["html", "markdown", "json"].includes(format);
|
|
11125
|
+
}
|
|
11126
|
+
function resolveOutputPath(format, output, projectRoot) {
|
|
11127
|
+
if (!output) {
|
|
11128
|
+
const ext = format === "markdown" ? "md" : format;
|
|
11129
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
11130
|
+
return path26.join(projectRoot, `sdd-report-${timestamp}.${ext}`);
|
|
11131
|
+
}
|
|
11132
|
+
return path26.isAbsolute(output) ? output : path26.join(projectRoot, output);
|
|
11133
|
+
}
|
|
11134
|
+
async function executeReport(options, projectRoot) {
|
|
11135
|
+
const sddPath = path26.join(projectRoot, ".sdd");
|
|
11136
|
+
const format = options.format || "html";
|
|
11137
|
+
if (!isValidReportFormat(format)) {
|
|
11138
|
+
return failure(new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD\uC785\uB2C8\uB2E4: ${format}. \uC9C0\uC6D0 \uD615\uC2DD: html, markdown, json`));
|
|
11139
|
+
}
|
|
11140
|
+
const outputPath = resolveOutputPath(format, options.output, projectRoot);
|
|
11141
|
+
const result = await generateReport(sddPath, {
|
|
11142
|
+
format,
|
|
11143
|
+
outputPath,
|
|
11144
|
+
title: options.title,
|
|
11145
|
+
includeQuality: options.quality !== false,
|
|
11146
|
+
includeValidation: options.validation !== false
|
|
11147
|
+
});
|
|
11148
|
+
if (!result.success) {
|
|
11149
|
+
return failure(result.error);
|
|
11150
|
+
}
|
|
11151
|
+
return success({
|
|
11152
|
+
format,
|
|
11153
|
+
outputPath: result.data.outputPath || outputPath,
|
|
11154
|
+
content: result.data.content
|
|
11155
|
+
});
|
|
11156
|
+
}
|
|
10720
11157
|
function registerReportCommand(program2) {
|
|
10721
11158
|
program2.command("report").description("\uC2A4\uD399 \uB9AC\uD3EC\uD2B8\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4").option("-f, --format <format>", "\uCD9C\uB825 \uD615\uC2DD (html, markdown, json)", "html").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C").option("--title <title>", "\uB9AC\uD3EC\uD2B8 \uC81C\uBAA9").option("--no-quality", "\uD488\uC9C8 \uBD84\uC11D \uC81C\uC678").option("--no-validation", "\uAC80\uC99D \uACB0\uACFC \uC81C\uC678").action(async (options) => {
|
|
10722
11159
|
try {
|
|
@@ -10733,33 +11170,13 @@ async function runReport(options) {
|
|
|
10733
11170
|
error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
10734
11171
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
10735
11172
|
}
|
|
10736
|
-
const sddPath = path26.join(projectRoot, ".sdd");
|
|
10737
11173
|
const format = options.format || "html";
|
|
10738
|
-
if (!["html", "markdown", "json"].includes(format)) {
|
|
10739
|
-
error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD\uC785\uB2C8\uB2E4: ${format}`);
|
|
10740
|
-
info("\uC9C0\uC6D0 \uD615\uC2DD: html, markdown, json");
|
|
10741
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
10742
|
-
}
|
|
10743
|
-
let outputPath = options.output;
|
|
10744
|
-
if (!outputPath) {
|
|
10745
|
-
const ext = format === "markdown" ? "md" : format;
|
|
10746
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10747
|
-
outputPath = path26.join(projectRoot, `sdd-report-${timestamp}.${ext}`);
|
|
10748
|
-
} else if (!path26.isAbsolute(outputPath)) {
|
|
10749
|
-
outputPath = path26.join(projectRoot, outputPath);
|
|
10750
|
-
}
|
|
10751
11174
|
info("\u{1F4CA} \uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uC911...");
|
|
10752
11175
|
info(` \uD615\uC2DD: ${format}`);
|
|
10753
11176
|
info(` \uD488\uC9C8 \uBD84\uC11D: ${options.quality !== false ? "\uD3EC\uD568" : "\uC81C\uC678"}`);
|
|
10754
11177
|
info(` \uAC80\uC99D \uACB0\uACFC: ${options.validation !== false ? "\uD3EC\uD568" : "\uC81C\uC678"}`);
|
|
10755
11178
|
newline();
|
|
10756
|
-
const result = await
|
|
10757
|
-
format,
|
|
10758
|
-
outputPath,
|
|
10759
|
-
title: options.title,
|
|
10760
|
-
includeQuality: options.quality !== false,
|
|
10761
|
-
includeValidation: options.validation !== false
|
|
10762
|
-
});
|
|
11179
|
+
const result = await executeReport(options, projectRoot);
|
|
10763
11180
|
if (!result.success) {
|
|
10764
11181
|
error(result.error.message);
|
|
10765
11182
|
process.exit(ExitCode.GENERAL_ERROR);
|