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 CHANGED
@@ -2503,28 +2503,9 @@ sdd transition guide
2503
2503
  }
2504
2504
 
2505
2505
  // src/cli/commands/init.ts
2506
- function registerInitCommand(program2) {
2507
- 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) => {
2508
- try {
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
- async function createDefaultFiles(cwd) {
2575
- const projectName = path2.basename(cwd);
2518
+ function generateConstitutionContent(projectName) {
2576
2519
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2577
- const constitution = `---
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
- async function copyTemplates(cwd) {
2556
+ function generateSpecTemplate() {
2617
2557
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2618
- const specTemplate = `---
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
- const proposalTemplate = `---
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
- const deltaTemplate = `---
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
- const tasksTemplate = `---
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
- async function createClaudeCommands(cwd) {
2797
- const commands = generateClaudeCommands();
2798
- for (const cmd of commands) {
2799
- await writeFile(
2800
- path2.join(cwd, ".claude", "commands", `${cmd.name}.md`),
2801
- cmd.content
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
- function registerValidateCommand(program2) {
3665
- 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) => {
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
- error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
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 validateSpecs(resolvedPath, {
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 { passed, failed, warnings, files } = result.data;
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
- const passedText = chalk2.green(`${passed} passed`);
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
- function registerChangeCommand(program2) {
4921
- 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) => {
4922
- try {
4923
- await runChange(id, options);
4924
- } catch (error2) {
4925
- error(error2 instanceof Error ? error2.message : String(error2));
4926
- process.exit(ExitCode.GENERAL_ERROR);
4927
- }
4928
- });
4929
- change.command("apply <id>").description("\uBCC0\uACBD \uC81C\uC548\uC744 \uC2A4\uD399\uC5D0 \uC801\uC6A9\uD569\uB2C8\uB2E4").action(async (id) => {
4930
- try {
4931
- await runApply(id);
4932
- } catch (error2) {
4933
- error(error2 instanceof Error ? error2.message : String(error2));
4934
- process.exit(ExitCode.GENERAL_ERROR);
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
- change.command("archive <id>").description("\uC644\uB8CC\uB41C \uBCC0\uACBD\uC744 \uC544\uCE74\uC774\uBE0C\uD569\uB2C8\uB2E4").action(async (id) => {
4938
- try {
4939
- await runArchive(id);
4940
- } catch (error2) {
4941
- error(error2 instanceof Error ? error2.message : String(error2));
4942
- process.exit(ExitCode.GENERAL_ERROR);
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
- change.command("diff <id>").description("\uBCC0\uACBD \uC81C\uC548\uC758 diff\uB97C \uD45C\uC2DC\uD569\uB2C8\uB2E4").action(async (id) => {
4946
- try {
4947
- await runDiff(id);
4948
- } catch (error2) {
4949
- error(error2 instanceof Error ? error2.message : String(error2));
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
- change.command("validate <id>").description("\uBCC0\uACBD \uC81C\uC548\uC758 \uC720\uD6A8\uC131\uC744 \uAC80\uC99D\uD569\uB2C8\uB2E4").action(async (id) => {
4954
- try {
4955
- await runValidateChange(id);
4956
- } catch (error2) {
4957
- error(error2 instanceof Error ? error2.message : String(error2));
4958
- process.exit(ExitCode.GENERAL_ERROR);
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 runChange(id, options) {
4963
- const projectRoot = await findSddRoot();
4964
- if (!projectRoot) {
4965
- error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
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 sddPath = path6.join(projectRoot, ".sdd");
4969
- if (options.list) {
4970
- const result = await listPendingChanges(sddPath);
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 changePath2 = path6.join(sddPath, "changes", id);
4989
- if (!await directoryExists(changePath2)) {
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 proposalPath = path6.join(changePath2, "proposal.md");
4994
- try {
4995
- const content = await fs3.readFile(proposalPath, "utf-8");
4996
- const parseResult = parseProposal(content);
4997
- if (parseResult.success) {
4998
- info(`\uBCC0\uACBD \uC81C\uC548: ${parseResult.data.title}`);
4999
- info(`\uC0C1\uD0DC: ${parseResult.data.metadata.status}`);
5000
- info(`\uC0DD\uC131: ${parseResult.data.metadata.created}`);
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
- } catch {
5007
- error("proposal.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5216
+ } else {
5217
+ error(infoResult.error.message);
5008
5218
  }
5009
5219
  return;
5010
5220
  }
5011
- const changesPath = path6.join(sddPath, "changes");
5012
- await ensureDir(changesPath);
5013
- const existingIds = [];
5014
- try {
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 = generateChangeId(existingIds);
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 proposalPath = path6.join(changePath, "proposal.md");
5059
- try {
5060
- const content = await fs3.readFile(proposalPath, "utf-8");
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 deltaPath = path6.join(changePath, "delta.md");
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("delta.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5290
+ error(deltaResult.error.message);
5110
5291
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
5111
5292
  }
5112
- const parseResult = parseDelta(deltaResult.data);
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
- const proposalPath = path6.join(changePath, "proposal.md");
5167
- if (await fileExists(proposalPath)) {
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("\u2717 proposal.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
5351
+ error(`\u2717 proposal.md \uC624\uB958: ${result.proposalError}`);
5180
5352
  hasErrors = true;
5181
5353
  }
5182
- const deltaPath = path6.join(changePath, "delta.md");
5183
- if (await fileExists(deltaPath)) {
5184
- const deltaResult = await readFile(deltaPath);
5185
- if (deltaResult.success) {
5186
- const validation = validateDelta(deltaResult.data);
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
- } else {
5197
- error(`\u2717 delta.md \uC624\uB958:`);
5198
- for (const error2 of validation.errors) {
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.VALIDATION_ERROR);
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 buildDependencyGraph(path10.join(sddPath, "specs"));
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: Array.from(graphResult.data.nodes.values()),
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 analyzeCodeImpact(projectRoot, sddPath, feature);
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 analyzeImpact(sddPath, feature);
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 generateImpactReport(sddPath);
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 analyzeChangeImpact(sddPath, changeId);
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
- const data = result.data;
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
- let fullProposalPath = proposalPath;
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 deltaResult = await parseDeltaFromProposal(fullProposalPath);
6827
- if (!deltaResult.success) {
6828
- error(deltaResult.error.message);
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 = deltaResult.data;
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.data, null, 2));
7076
+ console.log(JSON.stringify(simResult, null, 2));
6850
7077
  } else {
6851
- console.log(formatSimulationResult(simResult.data, feature));
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
- function registerNewCommand(program2) {
6866
- 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) => {
6867
- await handleNew(name, options);
6868
- });
6869
- 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) => {
6870
- await handlePlan(feature, opts);
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
- let featureId;
6890
- let branchName;
6891
- if (options.numbered) {
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 title2 = options.title || name;
6904
- const description = options.description || `${title2} \uAE30\uB2A5 \uBA85\uC138`;
6905
- const featurePath = path12.join(sddPath, "specs", featureId);
6906
- try {
6907
- if (!await fileExists(sddPath)) {
6908
- logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 sdd init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
6909
- process.exit(1);
6910
- }
6911
- await ensureDir(featurePath);
6912
- let constitutionVersion;
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
- const specContent = generateSpec({
6924
- id: featureId,
6925
- title: title2,
6926
- description,
6927
- constitutionVersion
6928
- });
6929
- await fs7.writeFile(path12.join(featurePath, "spec.md"), specContent, "utf-8");
6930
- logger_exports.info(`\u2705 \uBA85\uC138 \uC0DD\uC131: ${featurePath}/spec.md`);
6931
- if (options.branch !== false) {
6932
- if (await isGitRepository(cwd)) {
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("\u26A0\uFE0F Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBE0C\uB79C\uCE58 \uC0DD\uC131\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.");
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
- try {
6993
- if (!await fileExists(featurePath)) {
6994
- logger_exports.error(`\uAE30\uB2A5 '${feature}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`);
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
- try {
7026
- if (!await fileExists(featurePath)) {
7027
- logger_exports.error(`\uAE30\uB2A5 '${feature}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`);
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
- try {
7064
- if (!await fileExists(sddPath)) {
7065
- logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 sdd init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
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 peekResult = await peekNextFeatureNumber(sddPath);
7135
- const historyResult = await getFeatureHistory(sddPath);
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(peekResult.data).padStart(3, "0")}`);
7140
- logger_exports.info(`\uC0DD\uC131\uB41C \uAE30\uB2A5 \uC218: ${historyResult.data.length}\uAC1C`);
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("\uCE74\uC6B4\uD130 \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328");
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 fs8 } from "fs";
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 registerStatusCommand(program2) {
7160
- 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) => {
7161
- await handleStatus(options);
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 handleStatus(options) {
7165
- const cwd = process.cwd();
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
- if (options.json) {
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 fs8.stat(featurePath);
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(cwd);
7508
+ const currentBranchResult = await getCurrentBranch(projectPath);
7211
7509
  if (currentBranchResult.success) {
7212
7510
  status.currentBranch = currentBranchResult.data;
7213
7511
  }
7214
- const featureBranchesResult = await listFeatureBranches(cwd);
7512
+ const featureBranchesResult = await listFeatureBranches(projectPath);
7215
7513
  if (featureBranchesResult.success) {
7216
7514
  status.featureBranches = featureBranchesResult.data;
7217
7515
  }
7218
- if (options.json) {
7219
- console.log(JSON.stringify(status, null, 2));
7220
- } else {
7221
- printStatus(status, options.verbose);
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
- async function getFeatureInfo(id, featurePath) {
7225
- const info2 = {
7226
- id,
7227
- title: id,
7228
- status: "unknown",
7229
- hasSpec: false,
7230
- hasPlan: false,
7231
- hasTasks: false
7232
- };
7233
- const specPath = path13.join(featurePath, "spec.md");
7234
- if (await fileExists(specPath)) {
7235
- info2.hasSpec = true;
7236
- const content = await fs8.readFile(specPath, "utf-8");
7237
- const metadata = parseSpecMetadata(content);
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
- info2.hasPlan = await fileExists(path13.join(featurePath, "plan.md"));
7244
- const tasksPath = path13.join(featurePath, "tasks.md");
7245
- if (await fileExists(tasksPath)) {
7246
- info2.hasTasks = true;
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
- function getStatusIcon(status) {
7337
- switch (status) {
7338
- case "draft":
7339
- return "\u{1F4DD}";
7340
- case "specified":
7341
- return "\u{1F4C4}";
7342
- case "planned":
7343
- return "\u{1F4CB}";
7344
- case "tasked":
7345
- return "\u270F\uFE0F";
7346
- case "implementing":
7347
- return "\u{1F528}";
7348
- case "completed":
7349
- return "\u2705";
7350
- default:
7351
- return "\u2753";
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 cwd = process.cwd();
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 = getStatusIcon2(f.status);
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 cwd = process.cwd();
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
- const pendingResult = await listPendingChanges(sddPath);
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 pendingResult.data) {
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
- const archiveResult = await listArchives(sddPath);
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 archiveResult.data) {
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 cwd = process.cwd();
7461
- const specsPath = path14.join(cwd, ".sdd", "specs");
7462
- if (!await fileExists(specsPath)) {
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
- await walkSpecs(specsPath, "");
7856
+ printSpecTree(tree, "");
7470
7857
  console.log("");
7471
7858
  }
7472
- async function walkSpecs(basePath, prefix) {
7473
- const result = await readDir(basePath);
7474
- if (!result.success) return;
7475
- for (const entry of result.data) {
7476
- const fullPath = path14.join(basePath, entry);
7477
- const stat = await fs9.stat(fullPath);
7478
- if (stat.isDirectory()) {
7479
- console.log(`${prefix}\u{1F4C1} ${entry}/`);
7480
- await walkSpecs(fullPath, prefix + " ");
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 cwd = process.cwd();
7488
- const templatesPath = path14.join(cwd, ".sdd", "templates");
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 result.data.filter((f) => f.endsWith(".md"))) {
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 cwd = process.cwd();
7508
- const sddPath = path14.join(cwd, ".sdd");
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
- const specsPath = path14.join(sddPath, "specs");
7517
- let featureCount = 0;
7518
- if (await fileExists(specsPath)) {
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 constitutionPath = path15.join(cwd, ".sdd", "constitution.md");
7614
- if (!await fileExists(constitutionPath)) {
7615
- error("Constitution\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD558\uC138\uC694.");
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 parseResult = parseConstitution(contentResult.data);
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 constitutionPath = path15.join(cwd, ".sdd", "constitution.md");
7682
- if (!await fileExists(constitutionPath)) {
7683
- error("Constitution\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
7684
- process.exit(ExitCode.FILE_NOT_FOUND);
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
- const allEntries = [newEntry, ...existingEntries];
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 runHistory(options) {
8137
+ async function runBump(options) {
7773
8138
  const cwd = process.cwd();
7774
- const changelogPath = path15.join(cwd, ".sdd", "CHANGELOG.md");
7775
- if (!await fileExists(changelogPath)) {
7776
- warn("CHANGELOG\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd constitution bump`\uB85C \uBC84\uC804\uC744 \uC5C5\uB370\uC774\uD2B8\uD558\uBA74 \uC0DD\uC131\uB429\uB2C8\uB2E4.");
7777
- return;
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 contentResult = await readFile(changelogPath);
7780
- if (!contentResult.success) {
7781
- error("CHANGELOG \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
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
- const parseResult = parseChangelog(contentResult.data);
7785
- if (!parseResult.success) {
7786
- error(`CHANGELOG \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
7787
- process.exit(ExitCode.VALIDATION_ERROR);
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 entries = parseResult.data.slice(0, count);
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 constitutionPath = path15.join(cwd, ".sdd", "constitution.md");
7811
- if (!await fileExists(constitutionPath)) {
7812
- error("Constitution\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
7813
- process.exit(ExitCode.FILE_NOT_FOUND);
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: ${parseResult.data.projectName}`);
7832
- info(`\uBC84\uC804: ${parseResult.data.metadata.version}`);
7833
- info(`\uC6D0\uCE59 \uC218: ${parseResult.data.principles.length}`);
7834
- info(`\uAE08\uC9C0 \uC0AC\uD56D \uC218: ${parseResult.data.forbidden.length}`);
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 fs10 } from "fs";
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 getProjectStatus();
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 getProjectStatus() {
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 fs10.readdir(specsPath);
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 fs12 } from "fs";
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 fs11 from "fs/promises";
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 fs11.readdir(specsPath, { withFileTypes: true });
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 fs11.readFile(specFile, "utf-8");
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 fs11.readdir(specsPath, { withFileTypes: true });
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 fs11.readFile(specFile, "utf-8");
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 fs11.readdir(specsPath, { withFileTypes: true });
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 fs11.readFile(specFile, "utf-8");
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 fs11.readdir(specsPath, { withFileTypes: true });
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 fs11.mkdir(targetSpecPath, { recursive: true });
8328
- const files = await fs11.readdir(sourceSpecPath);
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 fs11.stat(sourceFile);
8688
+ const stat = await fs10.stat(sourceFile);
8333
8689
  if (stat.isFile()) {
8334
- let content = await fs11.readFile(sourceFile, "utf-8");
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 fs11.writeFile(targetFile, content);
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 fs11.readdir(specsPath, { withFileTypes: true });
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 fs11.mkdir(targetSpecPath, { recursive: true });
8382
- const files = await fs11.readdir(sourceSpecPath);
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 fs11.stat(sourceFile);
8742
+ const stat = await fs10.stat(sourceFile);
8387
8743
  if (stat.isFile()) {
8388
- let content = await fs11.readFile(sourceFile, "utf-8");
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 fs11.writeFile(targetFile, content);
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 fs12.stat(sourcePath);
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 fs12.readFile(filePath, "utf-8");
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 fs12.readFile(filePath, "utf-8");
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 fs12.readFile(file, "utf-8");
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 fs12.readdir(dir, { withFileTypes: true });
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 changeId = generateChangeId();
9339
- const changePath = path20.join(sddPath, "changes", changeId);
9340
- await ensureDir(changePath);
9341
- const specContent = await readFile(specPath);
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 title2 = options.title || `${specId} \uAE30\uB2A5 \uD655\uC7A5`;
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 proposalPath = path20.join(changePath, "proposal.md");
9386
- let extractedTitle = "";
9387
- if (await fileExists(proposalPath)) {
9388
- const proposalContent = await readFile(proposalPath);
9389
- if (proposalContent.success) {
9390
- const titleMatch = proposalContent.data.match(/^#\s+(.+)$/m);
9391
- if (titleMatch) {
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
- await ensureDir(newSpecPath);
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 ${changeId}\uC740 'transitioned' \uC0C1\uD0DC\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
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
- info("=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658 \uAC00\uC774\uB4DC ===");
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 generateDeltaTemplate(specId) {
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 generateTasksTemplate() {
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.frontmatter.depends) {
9969
- const deps = Array.isArray(spec.frontmatter.depends) ? spec.frontmatter.depends : [spec.frontmatter.depends];
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
- details.push("\uC758\uC874\uC131 \uD544\uB4DC\uAC00 \uC5C6\uC74C");
9979
- suggestions.push("frontmatter\uC5D0 depends \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694");
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.frontmatter.constitution_version) {
10440
+ } else if (spec.metadata.constitution_version) {
10045
10441
  score = maxScore;
10046
- details.push(`Constitution \uBC84\uC804: ${spec.frontmatter.constitution_version}`);
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.frontmatter[field]) {
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.frontmatter[field]) {
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.frontmatter).filter(
10105
- (k) => spec.frontmatter[k] !== null && spec.frontmatter[k] !== void 0
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.frontmatter.id || path23.basename(path23.dirname(specPath));
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 sddPath = path24.join(projectRoot, ".sdd");
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
- if (options.json) {
10325
- console.log(JSON.stringify(result.data, null, 2));
10326
- } else {
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.VALIDATION_ERROR);
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?.results.map((q) => `
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?.results.map((v) => `
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.averageGrade})`);
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.results) {
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(`- \uAC80\uC99D\uB41C \uC2A4\uD399: ${data.validation.validCount}\uAC1C`);
10692
- lines.push(`- \uC5D0\uB7EC: ${data.validation.errorCount}\uAC1C`);
10693
- lines.push(`- \uACBD\uACE0: ${data.validation.warningCount}\uAC1C`);
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.errorCount > 0 || data.validation.warningCount > 0) {
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.results) {
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 generateReport(sddPath, {
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);