sdd-tool 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -128,10 +128,10 @@ var init_base = __esm({
128
128
  };
129
129
  FileSystemError = class extends SddError {
130
130
  path;
131
- constructor(code, path27) {
132
- super(code, formatMessage(code, path27), ExitCode.FILE_SYSTEM_ERROR);
131
+ constructor(code, path29) {
132
+ super(code, formatMessage(code, path29), ExitCode.FILE_SYSTEM_ERROR);
133
133
  this.name = "FileSystemError";
134
- this.path = path27;
134
+ this.path = path29;
135
135
  }
136
136
  };
137
137
  ValidationError = class extends SddError {
@@ -2497,34 +2497,272 @@ sdd transition change-to-new <change-id>
2497
2497
  \`\`\`bash
2498
2498
  sdd transition guide
2499
2499
  \`\`\`
2500
+ `
2501
+ },
2502
+ // CLI 래퍼 커맨드들
2503
+ {
2504
+ name: "sdd.search",
2505
+ content: `---
2506
+ description: SDD \uC2A4\uD399\uC744 \uAC80\uC0C9\uD569\uB2C8\uB2E4
2507
+ allowed-tools: Bash, Read
2508
+ argument-hint: [query] [--status draft] [--phase phase1]
2509
+ ---
2510
+
2511
+ sdd search \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2512
+
2513
+ \`\`\`bash
2514
+ sdd search $ARGUMENTS
2515
+ \`\`\`
2516
+
2517
+ ## \uC635\uC158
2518
+
2519
+ - \`--status <status>\`: \uC0C1\uD0DC \uD544\uD130 (draft, review, approved)
2520
+ - \`--phase <phase>\`: Phase \uD544\uD130
2521
+ - \`--author <name>\`: \uC791\uC131\uC790 \uD544\uD130
2522
+ - \`--tags <tags>\`: \uD0DC\uADF8 \uD544\uD130 (\uCF64\uB9C8 \uAD6C\uBD84)
2523
+ - \`--depends-on <spec>\`: \uC758\uC874\uC131 \uD544\uD130
2524
+ - \`--limit <n>\`: \uACB0\uACFC \uC81C\uD55C
2525
+ - \`--sort-by <field>\`: \uC815\uB82C \uAE30\uC900 (relevance, created, updated, title, status)
2526
+ - \`-r, --regex\`: \uC815\uADDC\uC2DD \uAC80\uC0C9
2527
+ - \`-c, --case-sensitive\`: \uB300\uC18C\uBB38\uC790 \uAD6C\uBD84
2528
+ - \`--json\`: JSON \uCD9C\uB825
2529
+
2530
+ \uAC80\uC0C9 \uACB0\uACFC\uB97C \uBD84\uC11D\uD558\uACE0 \uAD00\uB828 \uC2A4\uD399 \uC815\uBCF4\uB97C \uC694\uC57D\uD574\uC8FC\uC138\uC694.
2531
+ `
2532
+ },
2533
+ {
2534
+ name: "sdd.quality",
2535
+ content: `---
2536
+ description: SDD \uC2A4\uD399 \uD488\uC9C8\uC744 \uBD84\uC11D\uD569\uB2C8\uB2E4
2537
+ allowed-tools: Bash, Read
2538
+ argument-hint: [specId] [--all] [--json]
2539
+ ---
2540
+
2541
+ sdd quality \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2542
+
2543
+ \`\`\`bash
2544
+ sdd quality $ARGUMENTS
2545
+ \`\`\`
2546
+
2547
+ ## \uC635\uC158
2548
+
2549
+ - \`--all\`: \uBAA8\uB4E0 \uC2A4\uD399 \uBD84\uC11D
2550
+ - \`--json\`: JSON \uD615\uC2DD \uCD9C\uB825
2551
+ - \`--min-score <n>\`: \uCD5C\uC18C \uC810\uC218 \uAE30\uC900
2552
+
2553
+ ## \uB4F1\uAE09 \uAE30\uC900
2554
+
2555
+ | \uB4F1\uAE09 | \uC810\uC218 | \uC124\uBA85 |
2556
+ |------|------|------|
2557
+ | A | 90-100 | \uC6B0\uC218 |
2558
+ | B | 80-89 | \uC591\uD638 |
2559
+ | C | 70-79 | \uBCF4\uD1B5 |
2560
+ | D | 60-69 | \uBBF8\uD761 |
2561
+ | F | 0-59 | \uBD80\uC871 |
2562
+
2563
+ \uBD84\uC11D \uACB0\uACFC\uB97C \uBC14\uD0D5\uC73C\uB85C \uD488\uC9C8 \uAC1C\uC120 \uBC29\uC548\uC744 \uC81C\uC548\uD574\uC8FC\uC138\uC694.
2564
+ `
2565
+ },
2566
+ {
2567
+ name: "sdd.report",
2568
+ content: `---
2569
+ description: SDD \uD504\uB85C\uC81D\uD2B8 \uB9AC\uD3EC\uD2B8\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4
2570
+ allowed-tools: Bash, Read, Write
2571
+ argument-hint: [--format html] [-o report.html]
2572
+ ---
2573
+
2574
+ sdd report \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2575
+
2576
+ \`\`\`bash
2577
+ sdd report $ARGUMENTS
2578
+ \`\`\`
2579
+
2580
+ ## \uC635\uC158
2581
+
2582
+ - \`--format <type>\`: \uCD9C\uB825 \uD615\uC2DD (html, markdown, json)
2583
+ - \`-o, --output <path>\`: \uD30C\uC77C\uB85C \uC800\uC7A5
2584
+ - \`--title <title>\`: \uB9AC\uD3EC\uD2B8 \uC81C\uBAA9
2585
+ - \`--no-quality\`: \uD488\uC9C8 \uBD84\uC11D \uC81C\uC678
2586
+ - \`--no-validation\`: \uAC80\uC99D \uACB0\uACFC \uC81C\uC678
2587
+
2588
+ \uB9AC\uD3EC\uD2B8 \uB0B4\uC6A9\uC744 \uC694\uC57D\uD558\uACE0 \uC8FC\uC694 \uC9C0\uD45C\uB97C \uC124\uBA85\uD574\uC8FC\uC138\uC694.
2589
+ `
2590
+ },
2591
+ {
2592
+ name: "sdd.impact",
2593
+ content: `---
2594
+ description: SDD \uC2A4\uD399 \uBCC0\uACBD\uC758 \uC601\uD5A5\uB3C4\uB97C \uBD84\uC11D\uD569\uB2C8\uB2E4
2595
+ allowed-tools: Bash, Read
2596
+ argument-hint: <specId> [--graph] [--code] [--json]
2597
+ ---
2598
+
2599
+ sdd impact \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2600
+
2601
+ \`\`\`bash
2602
+ sdd impact $ARGUMENTS
2603
+ \`\`\`
2604
+
2605
+ ## \uC635\uC158
2606
+
2607
+ - \`--graph\`: Mermaid \uC758\uC874\uC131 \uADF8\uB798\uD504 \uCD9C\uB825
2608
+ - \`--code\`: \uCF54\uB4DC \uC601\uD5A5\uB3C4 \uBD84\uC11D
2609
+ - \`--json\`: JSON \uD615\uC2DD \uCD9C\uB825
2610
+
2611
+ ## \uC11C\uBE0C\uCEE4\uB9E8\uB4DC
2612
+
2613
+ - \`report\`: \uC804\uCCB4 \uD504\uB85C\uC81D\uD2B8 \uC601\uD5A5\uB3C4 \uB9AC\uD3EC\uD2B8
2614
+ - \`change <id>\`: \uBCC0\uACBD \uC81C\uC548\uC758 \uC601\uD5A5\uB3C4
2615
+ - \`simulate --remove <spec>\`: What-if \uC2DC\uBBAC\uB808\uC774\uC158
2616
+
2617
+ \uC601\uD5A5\uBC1B\uB294 \uC2A4\uD399\uACFC \uCF54\uB4DC\uB97C \uBD84\uC11D\uD558\uACE0 \uBCC0\uACBD \uC804\uB7B5\uC744 \uC81C\uC548\uD574\uC8FC\uC138\uC694.
2618
+ `
2619
+ },
2620
+ {
2621
+ name: "sdd.list",
2622
+ content: `---
2623
+ description: SDD \uD56D\uBAA9 \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4
2624
+ allowed-tools: Bash, Read
2625
+ argument-hint: [features|changes|specs|templates] [--status draft]
2626
+ ---
2627
+
2628
+ sdd list \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2629
+
2630
+ \`\`\`bash
2631
+ sdd list $ARGUMENTS
2632
+ \`\`\`
2633
+
2634
+ ## \uC11C\uBE0C\uCEE4\uB9E8\uB4DC
2635
+
2636
+ - (\uC5C6\uC74C): \uD504\uB85C\uC81D\uD2B8 \uC694\uC57D
2637
+ - \`features\` (f): \uAE30\uB2A5 \uBAA9\uB85D
2638
+ - \`changes\` (c): \uBCC0\uACBD \uBAA9\uB85D
2639
+ - \`specs\` (s): \uC2A4\uD399 \uD30C\uC77C \uBAA9\uB85D
2640
+ - \`templates\` (t): \uD15C\uD50C\uB9BF \uBAA9\uB85D
2641
+
2642
+ ## \uC635\uC158
2643
+
2644
+ - \`--status <status>\`: \uC0C1\uD0DC\uBCC4 \uD544\uD130 (features)
2645
+ - \`--pending\`: \uB300\uAE30 \uC911\uB9CC (changes)
2646
+ - \`--archived\`: \uC544\uCE74\uC774\uBE0C\uB9CC (changes)
2647
+
2648
+ \uBAA9\uB85D\uC744 \uBD84\uC11D\uD558\uACE0 \uC694\uC57D\uD574\uC8FC\uC138\uC694.
2649
+ `
2650
+ },
2651
+ {
2652
+ name: "sdd.migrate",
2653
+ content: `---
2654
+ description: \uAE30\uC874 \uD504\uB85C\uC81D\uD2B8\uB97C SDD\uB85C \uB9C8\uC774\uADF8\uB808\uC774\uC158\uD569\uB2C8\uB2E4
2655
+ allowed-tools: Bash, Read
2656
+ argument-hint: [detect|openspec|speckit|docs] [--dry-run]
2657
+ ---
2658
+
2659
+ sdd migrate \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2660
+
2661
+ \`\`\`bash
2662
+ sdd migrate $ARGUMENTS
2663
+ \`\`\`
2664
+
2665
+ ## \uC11C\uBE0C\uCEE4\uB9E8\uB4DC
2666
+
2667
+ - \`detect\`: \uAE30\uC874 \uB3C4\uAD6C \uAC10\uC9C0
2668
+ - \`openspec\`: OpenSpec\uC5D0\uC11C \uB9C8\uC774\uADF8\uB808\uC774\uC158
2669
+ - \`speckit\`: SpecKit\uC5D0\uC11C \uB9C8\uC774\uADF8\uB808\uC774\uC158
2670
+ - \`docs\`: \uB9C8\uD06C\uB2E4\uC6B4 \uBB38\uC11C \uBCC0\uD658
2671
+
2672
+ ## \uC635\uC158
2673
+
2674
+ - \`--dry-run\`: \uBBF8\uB9AC\uBCF4\uAE30
2675
+ - \`--overwrite\`: \uAE30\uC874 \uC2A4\uD399 \uB36E\uC5B4\uC4F0\uAE30
2676
+ - \`--source <path>\`: \uC18C\uC2A4 \uB514\uB809\uD1A0\uB9AC
2677
+
2678
+ \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uACB0\uACFC\uB97C \uD655\uC778\uD558\uACE0 \uD544\uC694\uD55C \uD6C4\uC18D \uC791\uC5C5\uC744 \uC548\uB0B4\uD574\uC8FC\uC138\uC694.
2679
+ `
2680
+ },
2681
+ {
2682
+ name: "sdd.cicd",
2683
+ content: `---
2684
+ description: CI/CD \uD30C\uC774\uD504\uB77C\uC778\uC744 \uC124\uC815\uD569\uB2C8\uB2E4
2685
+ allowed-tools: Bash, Read, Write
2686
+ argument-hint: [setup|hooks|check] [--platform github]
2687
+ ---
2688
+
2689
+ sdd cicd \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2690
+
2691
+ \`\`\`bash
2692
+ sdd cicd $ARGUMENTS
2693
+ \`\`\`
2694
+
2695
+ ## \uC11C\uBE0C\uCEE4\uB9E8\uB4DC
2696
+
2697
+ - \`setup\`: CI/CD \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0DD\uC131
2698
+ - \`hooks\`: Git hooks \uC124\uC815
2699
+ - \`check\`: CI \uAC80\uC99D \uC2E4\uD589
2700
+
2701
+ ## \uC635\uC158
2702
+
2703
+ - \`--platform <type>\`: github, gitlab, all
2704
+
2705
+ \uC0DD\uC131\uB41C CI/CD \uC124\uC815\uC744 \uD655\uC778\uD558\uACE0 \uCD94\uAC00 \uC124\uC815\uC774 \uD544\uC694\uD558\uBA74 \uC548\uB0B4\uD574\uC8FC\uC138\uC694.
2706
+ `
2707
+ },
2708
+ {
2709
+ name: "sdd.watch",
2710
+ content: `---
2711
+ description: \uC2A4\uD399 \uD30C\uC77C \uBCC0\uACBD\uC744 \uAC10\uC2DC\uD569\uB2C8\uB2E4
2712
+ allowed-tools: Bash
2713
+ argument-hint: [--validate] [--path .sdd/specs]
2714
+ ---
2715
+
2716
+ sdd watch \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2717
+
2718
+ \`\`\`bash
2719
+ sdd watch $ARGUMENTS
2720
+ \`\`\`
2721
+
2722
+ ## \uC635\uC158
2723
+
2724
+ - \`--path <dir>\`: \uAC10\uC2DC\uD560 \uB514\uB809\uD1A0\uB9AC
2725
+ - \`--validate\`: \uBCC0\uACBD \uC2DC \uC790\uB3D9 \uAC80\uC99D
2726
+ - \`--quality\`: \uBCC0\uACBD \uC2DC \uD488\uC9C8 \uAC80\uC0AC
2727
+
2728
+ Ctrl+C\uB85C \uC885\uB8CC\uD569\uB2C8\uB2E4.
2729
+ `
2730
+ },
2731
+ {
2732
+ name: "sdd.prompt",
2733
+ content: `---
2734
+ description: SDD \uC791\uC5C5\uC6A9 AI \uD504\uB86C\uD504\uD2B8\uB97C \uCD9C\uB825\uD569\uB2C8\uB2E4
2735
+ allowed-tools: Bash, Read
2736
+ argument-hint: [change|apply|archive|validate] [--list]
2737
+ ---
2738
+
2739
+ sdd prompt \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD569\uB2C8\uB2E4.
2740
+
2741
+ \`\`\`bash
2742
+ sdd prompt $ARGUMENTS
2743
+ \`\`\`
2744
+
2745
+ ## \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB86C\uD504\uD2B8
2746
+
2747
+ - \`change\`: \uBCC0\uACBD \uC81C\uC548 \uC791\uC131
2748
+ - \`apply\`: \uBCC0\uACBD \uC801\uC6A9
2749
+ - \`archive\`: \uBCC0\uACBD \uC544\uCE74\uC774\uBE0C
2750
+ - \`validate\`: \uC2A4\uD399 \uAC80\uC99D
2751
+
2752
+ ## \uC635\uC158
2753
+
2754
+ - \`--list\`: \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD504\uB86C\uD504\uD2B8 \uBAA9\uB85D
2755
+
2756
+ \uD504\uB86C\uD504\uD2B8 \uB0B4\uC6A9\uC744 \uCD9C\uB825\uD558\uACE0 \uC0AC\uC6A9 \uBC29\uBC95\uC744 \uC548\uB0B4\uD574\uC8FC\uC138\uC694.
2500
2757
  `
2501
2758
  }
2502
2759
  ];
2503
2760
  }
2504
2761
 
2505
2762
  // 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 = [
2763
+ init_types();
2764
+ function getInitDirectories() {
2765
+ return [
2528
2766
  ".sdd",
2529
2767
  ".sdd/specs",
2530
2768
  ".sdd/changes",
@@ -2533,48 +2771,10 @@ async function runInit(options) {
2533
2771
  ".claude",
2534
2772
  ".claude/commands"
2535
2773
  ];
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
2774
  }
2574
- async function createDefaultFiles(cwd) {
2575
- const projectName = path2.basename(cwd);
2775
+ function generateConstitutionContent(projectName) {
2576
2776
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2577
- const constitution = `---
2777
+ return `---
2578
2778
  version: 1.0.0
2579
2779
  created: ${today}
2580
2780
  ---
@@ -2609,13 +2809,10 @@ created: ${today}
2609
2809
 
2610
2810
  - \uD14C\uC2A4\uD2B8 \uCEE4\uBC84\uB9AC\uC9C0: 80% \uC774\uC0C1(SHOULD)
2611
2811
  `;
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
2812
  }
2616
- async function copyTemplates(cwd) {
2813
+ function generateSpecTemplate() {
2617
2814
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2618
- const specTemplate = `---
2815
+ return `---
2619
2816
  status: draft
2620
2817
  created: ${today}
2621
2818
  depends: null
@@ -2643,7 +2840,69 @@ depends: null
2643
2840
 
2644
2841
  \uCD94\uAC00 \uC124\uBA85\uC774\uB098 \uC81C\uC57D \uC870\uAC74
2645
2842
  `;
2646
- const proposalTemplate = `---
2843
+ }
2844
+ async function executeInit(projectPath, options) {
2845
+ const sddPath = path2.join(projectPath, ".sdd");
2846
+ const claudePath = path2.join(projectPath, ".claude");
2847
+ if (await directoryExists(sddPath)) {
2848
+ if (!options.force) {
2849
+ 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."));
2850
+ }
2851
+ }
2852
+ const directories = getInitDirectories();
2853
+ const createdDirs = [];
2854
+ for (const dir of directories) {
2855
+ const result = await ensureDir(path2.join(projectPath, dir));
2856
+ if (!result.success) {
2857
+ return failure(new Error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${dir}`));
2858
+ }
2859
+ createdDirs.push(dir);
2860
+ }
2861
+ const createdFiles = [];
2862
+ const projectName = path2.basename(projectPath);
2863
+ const constitutionContent = generateConstitutionContent(projectName);
2864
+ await writeFile(path2.join(sddPath, "constitution.md"), constitutionContent);
2865
+ createdFiles.push(".sdd/constitution.md");
2866
+ const agentsContent = generateAgentsMd({ projectName });
2867
+ await writeFile(path2.join(sddPath, "AGENTS.md"), agentsContent);
2868
+ createdFiles.push(".sdd/AGENTS.md");
2869
+ const templateFiles = await createTemplateFiles(projectPath);
2870
+ createdFiles.push(...templateFiles);
2871
+ const commandFiles = await createCommandFiles(projectPath);
2872
+ createdFiles.push(...commandFiles);
2873
+ return success({
2874
+ sddPath,
2875
+ claudePath,
2876
+ directories: createdDirs,
2877
+ files: createdFiles
2878
+ });
2879
+ }
2880
+ async function createTemplateFiles(projectPath) {
2881
+ const templatesPath = path2.join(projectPath, ".sdd", "templates");
2882
+ const files = [];
2883
+ await writeFile(path2.join(templatesPath, "spec.md"), generateSpecTemplate());
2884
+ files.push(".sdd/templates/spec.md");
2885
+ await writeFile(path2.join(templatesPath, "proposal.md"), generateProposalTemplate());
2886
+ files.push(".sdd/templates/proposal.md");
2887
+ await writeFile(path2.join(templatesPath, "delta.md"), generateDeltaTemplate());
2888
+ files.push(".sdd/templates/delta.md");
2889
+ await writeFile(path2.join(templatesPath, "tasks.md"), generateTasksTemplate());
2890
+ files.push(".sdd/templates/tasks.md");
2891
+ return files;
2892
+ }
2893
+ async function createCommandFiles(projectPath) {
2894
+ const commandsPath = path2.join(projectPath, ".claude", "commands");
2895
+ const files = [];
2896
+ const commands = generateClaudeCommands();
2897
+ for (const cmd of commands) {
2898
+ await writeFile(path2.join(commandsPath, `${cmd.name}.md`), cmd.content);
2899
+ files.push(`.claude/commands/${cmd.name}.md`);
2900
+ }
2901
+ return files;
2902
+ }
2903
+ function generateProposalTemplate() {
2904
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2905
+ return `---
2647
2906
  id: CHG-{{ID}}
2648
2907
  status: draft
2649
2908
  created: ${today}
@@ -2706,7 +2965,10 @@ created: ${today}
2706
2965
  - \uC601\uD5A5\uB3C4: \uB0AE\uC74C/\uC911\uAC04/\uB192\uC74C
2707
2966
  - \uBCF5\uC7A1\uB3C4: \uB0AE\uC74C/\uC911\uAC04/\uB192\uC74C
2708
2967
  `;
2709
- const deltaTemplate = `---
2968
+ }
2969
+ function generateDeltaTemplate() {
2970
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2971
+ return `---
2710
2972
  proposal: CHG-{{ID}}
2711
2973
  created: ${today}
2712
2974
  ---
@@ -2737,7 +2999,10 @@ created: ${today}
2737
2999
 
2738
3000
  (\uC0AD\uC81C\uB418\uB294 \uC2A4\uD399 \uCC38\uC870)
2739
3001
  `;
2740
- const tasksTemplate = `---
3002
+ }
3003
+ function generateTasksTemplate() {
3004
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3005
+ return `---
2741
3006
  spec: {{SPEC_ID}}
2742
3007
  created: ${today}
2743
3008
  ---
@@ -2788,19 +3053,57 @@ graph LR
2788
3053
  | [\u2192T] | \uD14C\uC2A4\uD2B8 \uD544\uC694 |
2789
3054
  | [US] | \uBD88\uD655\uC2E4/\uAC80\uD1A0 \uD544\uC694 |
2790
3055
  `;
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
3056
  }
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
- );
3057
+ function registerInitCommand(program2) {
3058
+ 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) => {
3059
+ try {
3060
+ await runInit(options);
3061
+ } catch (error2) {
3062
+ error(error2 instanceof Error ? error2.message : String(error2));
3063
+ process.exit(ExitCode.GENERAL_ERROR);
3064
+ }
3065
+ });
3066
+ }
3067
+ async function runInit(options) {
3068
+ const cwd = process.cwd();
3069
+ if (await directoryExists(path2.join(cwd, ".sdd"))) {
3070
+ if (options.force) {
3071
+ warn("\uAE30\uC874 .sdd/ \uB514\uB809\uD1A0\uB9AC\uB97C \uB36E\uC5B4\uC501\uB2C8\uB2E4.");
3072
+ }
3073
+ }
3074
+ info("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD569\uB2C8\uB2E4...");
3075
+ const result = await executeInit(cwd, options);
3076
+ if (!result.success) {
3077
+ error(result.error.message);
3078
+ process.exit(ExitCode.GENERAL_ERROR);
2803
3079
  }
3080
+ success2("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
3081
+ newline();
3082
+ info("\uC0DD\uC131\uB41C \uAD6C\uC870:");
3083
+ listItem(".sdd/");
3084
+ listItem("AGENTS.md", 1);
3085
+ listItem("constitution.md", 1);
3086
+ listItem("specs/", 1);
3087
+ listItem("changes/", 1);
3088
+ listItem("archive/", 1);
3089
+ listItem("templates/", 1);
3090
+ listItem(".claude/");
3091
+ listItem("commands/", 1);
3092
+ newline();
3093
+ info("Claude \uC2AC\uB798\uC2DC \uCEE4\uB9E8\uB4DC:");
3094
+ listItem("/sdd.start - \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC2DC\uC791 (\uD1B5\uD569 \uC9C4\uC785\uC810)");
3095
+ listItem("/sdd.constitution - \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59 \uAD00\uB9AC");
3096
+ listItem("/sdd.new - \uC0C8 \uAE30\uB2A5 \uBA85\uC138 \uC791\uC131");
3097
+ listItem("/sdd.plan - \uAD6C\uD604 \uACC4\uD68D \uC791\uC131");
3098
+ listItem("/sdd.tasks - \uC791\uC5C5 \uBD84\uD574");
3099
+ listItem("/sdd.implement - \uAD6C\uD604 \uC9C4\uD589");
3100
+ listItem("/sdd.validate - \uC2A4\uD399 \uAC80\uC99D");
3101
+ listItem("/sdd.status - \uC0C1\uD0DC \uD655\uC778");
3102
+ listItem("/sdd.change - \uBCC0\uACBD \uC81C\uC548");
3103
+ newline();
3104
+ info("\uB2E4\uC74C \uB2E8\uACC4:");
3105
+ listItem("constitution.md\uB97C \uC218\uC815\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59\uC744 \uC815\uC758\uD558\uC138\uC694");
3106
+ listItem("/sdd.new \uB85C \uCCAB \uBC88\uC9F8 \uAE30\uB2A5 \uBA85\uC138\uB97C \uC791\uC131\uD558\uC138\uC694");
2804
3107
  }
2805
3108
 
2806
3109
  // src/cli/commands/validate.ts
@@ -3631,9 +3934,9 @@ async function validateSpecs(targetPath, options = {}) {
3631
3934
  async function findSpecFiles(dirPath) {
3632
3935
  const files = [];
3633
3936
  async function scanDir(dir) {
3634
- const { promises: fs15 } = await import("fs");
3937
+ const { promises: fs16 } = await import("fs");
3635
3938
  try {
3636
- const entries = await fs15.readdir(dir, { withFileTypes: true });
3939
+ const entries = await fs16.readdir(dir, { withFileTypes: true });
3637
3940
  for (const entry of entries) {
3638
3941
  const fullPath = path3.join(dir, entry.name);
3639
3942
  if (entry.isDirectory()) {
@@ -3661,26 +3964,15 @@ async function findSpecFiles(dirPath) {
3661
3964
  // src/cli/commands/validate.ts
3662
3965
  init_errors();
3663
3966
  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) {
3967
+ init_types();
3968
+ async function createValidateContext(targetPath, options, sddRoot) {
3675
3969
  let resolvedPath;
3676
3970
  let specsRoot;
3677
- const sddRoot = await findSddRoot();
3678
3971
  if (targetPath) {
3679
3972
  resolvedPath = path4.resolve(targetPath);
3680
3973
  } else {
3681
3974
  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);
3975
+ 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
3976
  }
3685
3977
  resolvedPath = path4.join(sddRoot, ".sdd", "specs");
3686
3978
  }
@@ -3693,39 +3985,75 @@ async function runValidate(targetPath, options) {
3693
3985
  const constitutionPath = path4.join(sddRoot, ".sdd", "constitution.md");
3694
3986
  hasConstitution = await fileExists(constitutionPath);
3695
3987
  }
3696
- if (!options.quiet) {
3697
- info(`\uAC80\uC99D \uC911: ${resolvedPath}`);
3988
+ return success({
3989
+ resolvedPath,
3990
+ specsRoot,
3991
+ checkConstitution,
3992
+ hasConstitution,
3993
+ sddRoot: sddRoot || void 0
3994
+ });
3995
+ }
3996
+ async function executeValidate(options, context) {
3997
+ const result = await validateSpecs(context.resolvedPath, {
3998
+ strict: options.strict,
3999
+ checkLinks: options.checkLinks,
4000
+ specsRoot: context.specsRoot,
4001
+ checkConstitution: context.checkConstitution && context.hasConstitution,
4002
+ sddRoot: context.sddRoot
4003
+ });
4004
+ if (!result.success) {
4005
+ return failure(result.error);
4006
+ }
4007
+ return success(result.data);
4008
+ }
4009
+ function formatValidateSummary(result, useColors = true) {
4010
+ const { passed, failed, warnings } = result;
4011
+ const passedText = useColors ? chalk2.green(`${passed} passed`) : `${passed} passed`;
4012
+ const failedText = failed > 0 ? useColors ? chalk2.red(`${failed} failed`) : `${failed} failed` : `${failed} failed`;
4013
+ const warningsText = warnings > 0 ? useColors ? chalk2.yellow(`${warnings} warnings`) : `${warnings} warnings` : "";
4014
+ return [passedText, failedText, warningsText].filter(Boolean).join(", ");
4015
+ }
4016
+ function registerValidateCommand(program2) {
4017
+ 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) => {
4018
+ try {
4019
+ await runValidate(targetPath, options);
4020
+ } catch (error2) {
4021
+ error(error2 instanceof Error ? error2.message : String(error2));
4022
+ process.exit(ExitCode.GENERAL_ERROR);
4023
+ }
4024
+ });
4025
+ }
4026
+ async function runValidate(targetPath, options) {
4027
+ const sddRoot = await findSddRoot();
4028
+ const contextResult = await createValidateContext(targetPath, options, sddRoot);
4029
+ if (!contextResult.success) {
4030
+ error(contextResult.error.message);
4031
+ process.exit(ExitCode.GENERAL_ERROR);
4032
+ }
4033
+ const context = contextResult.data;
4034
+ if (!options.quiet) {
4035
+ info(`\uAC80\uC99D \uC911: ${context.resolvedPath}`);
3698
4036
  if (options.checkLinks) {
3699
4037
  info("(\uCC38\uC870 \uB9C1\uD06C \uAC80\uC99D \uD3EC\uD568)");
3700
4038
  }
3701
- if (checkConstitution && hasConstitution) {
4039
+ if (context.checkConstitution && context.hasConstitution) {
3702
4040
  info("(Constitution \uC704\uBC18 \uAC80\uC0AC \uD3EC\uD568)");
3703
4041
  }
3704
4042
  newline();
3705
4043
  }
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
- });
4044
+ const result = await executeValidate(options, context);
3713
4045
  if (!result.success) {
3714
4046
  error(result.error.message);
3715
4047
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
3716
4048
  }
3717
- const { passed, failed, warnings, files } = result.data;
4049
+ const { failed, files } = result.data;
3718
4050
  if (!options.quiet) {
3719
4051
  for (const file of files) {
3720
- printFileResult(file, resolvedPath);
4052
+ printFileResult(file, context.resolvedPath);
3721
4053
  }
3722
4054
  newline();
3723
4055
  }
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}`);
4056
+ console.log(`Result: ${formatValidateSummary(result.data)}`);
3729
4057
  if (failed > 0) {
3730
4058
  process.exit(ExitCode.VALIDATION_FAILED);
3731
4059
  }
@@ -4917,6 +5245,149 @@ async function listPendingChanges(sddPath) {
4917
5245
  // src/cli/commands/change.ts
4918
5246
  init_fs();
4919
5247
  init_errors();
5248
+ init_types();
5249
+ async function getChangeListItems(sddPath) {
5250
+ const result = await listPendingChanges(sddPath);
5251
+ if (!result.success) {
5252
+ return failure(result.error);
5253
+ }
5254
+ return success(result.data.map((change) => ({
5255
+ id: change.id,
5256
+ title: change.title || null,
5257
+ status: change.status
5258
+ })));
5259
+ }
5260
+ async function getChangeInfo(changePath) {
5261
+ const proposalPath = path6.join(changePath, "proposal.md");
5262
+ if (!await fileExists(proposalPath)) {
5263
+ return failure(new Error("proposal.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5264
+ }
5265
+ const contentResult = await readFile(proposalPath);
5266
+ if (!contentResult.success) {
5267
+ return failure(new Error("proposal.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5268
+ }
5269
+ const parseResult = parseProposal(contentResult.data);
5270
+ if (!parseResult.success) {
5271
+ return failure(new Error(`proposal.md \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
5272
+ }
5273
+ return success({
5274
+ id: parseResult.data.metadata.id,
5275
+ title: parseResult.data.title,
5276
+ status: parseResult.data.metadata.status,
5277
+ created: parseResult.data.metadata.created,
5278
+ affectedSpecs: parseResult.data.affectedSpecs
5279
+ });
5280
+ }
5281
+ async function createChange(sddPath, options) {
5282
+ const changesPath = path6.join(sddPath, "changes");
5283
+ await ensureDir(changesPath);
5284
+ const existingIds = [];
5285
+ try {
5286
+ const dirs = await fs3.readdir(changesPath);
5287
+ existingIds.push(...dirs.filter((d) => d.startsWith("CHG-")));
5288
+ } catch {
5289
+ }
5290
+ const newId = generateChangeId(existingIds);
5291
+ const title2 = options.title || "\uC0C8 \uBCC0\uACBD \uC81C\uC548";
5292
+ const affectedSpecs = options.spec ? [options.spec] : [];
5293
+ const changePath = path6.join(changesPath, newId);
5294
+ await ensureDir(changePath);
5295
+ const proposal = generateProposal({
5296
+ id: newId,
5297
+ title: title2,
5298
+ affectedSpecs
5299
+ });
5300
+ const proposalPath = path6.join(changePath, "proposal.md");
5301
+ await writeFile(proposalPath, proposal);
5302
+ const delta = generateDelta({
5303
+ proposalId: newId,
5304
+ title: title2
5305
+ });
5306
+ const deltaPath = path6.join(changePath, "delta.md");
5307
+ await writeFile(deltaPath, delta);
5308
+ return success({
5309
+ id: newId,
5310
+ proposalPath,
5311
+ deltaPath
5312
+ });
5313
+ }
5314
+ async function applyChange(changePath) {
5315
+ const proposalPath = path6.join(changePath, "proposal.md");
5316
+ if (!await fileExists(proposalPath)) {
5317
+ return failure(new Error("proposal.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5318
+ }
5319
+ const contentResult = await readFile(proposalPath);
5320
+ if (!contentResult.success) {
5321
+ return failure(new Error("proposal.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5322
+ }
5323
+ const updateResult = updateProposalStatus(contentResult.data, "applied");
5324
+ if (!updateResult.success) {
5325
+ return failure(new Error("proposal.md\uB97C \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5326
+ }
5327
+ await writeFile(proposalPath, updateResult.data);
5328
+ return success(void 0);
5329
+ }
5330
+ async function getDeltaInfo(changePath) {
5331
+ const deltaPath = path6.join(changePath, "delta.md");
5332
+ if (!await fileExists(deltaPath)) {
5333
+ return failure(new Error("delta.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5334
+ }
5335
+ const deltaResult = await readFile(deltaPath);
5336
+ if (!deltaResult.success) {
5337
+ return failure(new Error("delta.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5338
+ }
5339
+ const parseResult = parseDelta(deltaResult.data);
5340
+ if (!parseResult.success) {
5341
+ return failure(new Error(`Delta \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
5342
+ }
5343
+ return success({
5344
+ added: parseResult.data.added,
5345
+ modified: parseResult.data.modified,
5346
+ removed: parseResult.data.removed
5347
+ });
5348
+ }
5349
+ async function validateChange(changePath) {
5350
+ const result = {
5351
+ proposalValid: false,
5352
+ deltaValid: false,
5353
+ hasDelta: false
5354
+ };
5355
+ const proposalPath = path6.join(changePath, "proposal.md");
5356
+ if (await fileExists(proposalPath)) {
5357
+ const proposalResult = await readFile(proposalPath);
5358
+ if (proposalResult.success) {
5359
+ const parsed = parseProposal(proposalResult.data);
5360
+ if (parsed.success) {
5361
+ result.proposalValid = true;
5362
+ result.proposalTitle = parsed.data.title;
5363
+ } else {
5364
+ result.proposalError = parsed.error.message;
5365
+ }
5366
+ }
5367
+ } else {
5368
+ result.proposalError = "proposal.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.";
5369
+ }
5370
+ const deltaPath = path6.join(changePath, "delta.md");
5371
+ if (await fileExists(deltaPath)) {
5372
+ result.hasDelta = true;
5373
+ const deltaResult = await readFile(deltaPath);
5374
+ if (deltaResult.success) {
5375
+ const validation = validateDelta(deltaResult.data);
5376
+ if (validation.valid) {
5377
+ result.deltaValid = true;
5378
+ const types = [];
5379
+ if (validation.hasAdded) types.push("ADDED");
5380
+ if (validation.hasModified) types.push("MODIFIED");
5381
+ if (validation.hasRemoved) types.push("REMOVED");
5382
+ result.deltaTypes = types;
5383
+ result.deltaWarnings = validation.warnings;
5384
+ } else {
5385
+ result.deltaErrors = validation.errors;
5386
+ }
5387
+ }
5388
+ }
5389
+ return success(result);
5390
+ }
4920
5391
  function registerChangeCommand(program2) {
4921
5392
  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
5393
  try {
@@ -4967,7 +5438,7 @@ async function runChange(id, options) {
4967
5438
  }
4968
5439
  const sddPath = path6.join(projectRoot, ".sdd");
4969
5440
  if (options.list) {
4970
- const result = await listPendingChanges(sddPath);
5441
+ const result = await getChangeListItems(sddPath);
4971
5442
  if (!result.success) {
4972
5443
  error(result.error.message);
4973
5444
  process.exit(ExitCode.GENERAL_ERROR);
@@ -4985,53 +5456,31 @@ async function runChange(id, options) {
4985
5456
  return;
4986
5457
  }
4987
5458
  if (id) {
4988
- const changePath2 = path6.join(sddPath, "changes", id);
4989
- if (!await directoryExists(changePath2)) {
5459
+ const changePath = path6.join(sddPath, "changes", id);
5460
+ if (!await directoryExists(changePath)) {
4990
5461
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
4991
5462
  process.exit(ExitCode.GENERAL_ERROR);
4992
5463
  }
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
- }
5464
+ const infoResult = await getChangeInfo(changePath);
5465
+ if (infoResult.success) {
5466
+ info(`\uBCC0\uACBD \uC81C\uC548: ${infoResult.data.title}`);
5467
+ info(`\uC0C1\uD0DC: ${infoResult.data.status}`);
5468
+ info(`\uC0DD\uC131: ${infoResult.data.created}`);
5469
+ if (infoResult.data.affectedSpecs.length > 0) {
5470
+ info("\uC601\uD5A5 \uC2A4\uD399:");
5471
+ infoResult.data.affectedSpecs.forEach((spec) => listItem(spec, 1));
5005
5472
  }
5006
- } catch {
5007
- error("proposal.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5473
+ } else {
5474
+ error(infoResult.error.message);
5008
5475
  }
5009
5476
  return;
5010
5477
  }
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 {
5478
+ const createResult = await createChange(sddPath, options);
5479
+ if (!createResult.success) {
5480
+ error(createResult.error.message);
5481
+ process.exit(ExitCode.GENERAL_ERROR);
5018
5482
  }
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);
5483
+ const newId = createResult.data.id;
5035
5484
  success2(`\uBCC0\uACBD \uC81C\uC548\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${newId}`);
5036
5485
  newline();
5037
5486
  info("\uC0DD\uC131\uB41C \uD30C\uC77C:");
@@ -5055,15 +5504,9 @@ async function runApply(id) {
5055
5504
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
5056
5505
  process.exit(ExitCode.GENERAL_ERROR);
5057
5506
  }
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.");
5507
+ const result = await applyChange(changePath);
5508
+ if (!result.success) {
5509
+ error(result.error.message);
5067
5510
  process.exit(ExitCode.GENERAL_ERROR);
5068
5511
  }
5069
5512
  success2(`\uBCC0\uACBD\uC774 \uC801\uC6A9 \uC0C1\uD0DC\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${id}`);
@@ -5099,22 +5542,12 @@ async function runDiff(id) {
5099
5542
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
5100
5543
  process.exit(ExitCode.GENERAL_ERROR);
5101
5544
  }
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);
5545
+ const deltaResult = await getDeltaInfo(changePath);
5108
5546
  if (!deltaResult.success) {
5109
- error("delta.md\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5547
+ error(deltaResult.error.message);
5110
5548
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
5111
5549
  }
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;
5550
+ const delta = deltaResult.data;
5118
5551
  info(`\uBCC0\uACBD Diff: ${id}`);
5119
5552
  newline();
5120
5553
  if (delta.added.length > 0 && delta.added[0].content !== "(\uCD94\uAC00\uB418\uB294 \uC2A4\uD399 \uB0B4\uC6A9)") {
@@ -5162,44 +5595,35 @@ async function runValidateChange(id) {
5162
5595
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
5163
5596
  process.exit(ExitCode.GENERAL_ERROR);
5164
5597
  }
5598
+ const validationResult = await validateChange(changePath);
5599
+ if (!validationResult.success) {
5600
+ error(validationResult.error.message);
5601
+ process.exit(ExitCode.GENERAL_ERROR);
5602
+ }
5603
+ const result = validationResult.data;
5165
5604
  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
- }
5605
+ if (result.proposalValid) {
5606
+ success2(`\u2713 proposal.md \uC720\uD6A8 (${result.proposalTitle})`);
5178
5607
  } else {
5179
- error("\u2717 proposal.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
5608
+ error(`\u2717 proposal.md \uC624\uB958: ${result.proposalError}`);
5180
5609
  hasErrors = true;
5181
5610
  }
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) {
5611
+ if (result.hasDelta) {
5612
+ if (result.deltaValid) {
5613
+ success2(`\u2713 delta.md \uC720\uD6A8 (${result.deltaTypes?.join(", ")})`);
5614
+ if (result.deltaWarnings) {
5615
+ for (const warning of result.deltaWarnings) {
5194
5616
  warn(` \u26A0 ${warning}`);
5195
5617
  }
5196
- } else {
5197
- error(`\u2717 delta.md \uC624\uB958:`);
5198
- for (const error2 of validation.errors) {
5618
+ }
5619
+ } else {
5620
+ error(`\u2717 delta.md \uC624\uB958:`);
5621
+ if (result.deltaErrors) {
5622
+ for (const error2 of result.deltaErrors) {
5199
5623
  error(` - ${error2}`);
5200
5624
  }
5201
- hasErrors = true;
5202
5625
  }
5626
+ hasErrors = true;
5203
5627
  }
5204
5628
  } else {
5205
5629
  warn("\u26A0 delta.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
@@ -5207,7 +5631,7 @@ async function runValidateChange(id) {
5207
5631
  newline();
5208
5632
  if (hasErrors) {
5209
5633
  error(`\uAC80\uC99D \uC2E4\uD328: ${id}`);
5210
- process.exit(ExitCode.VALIDATION_ERROR);
5634
+ process.exit(ExitCode.VALIDATION_FAILED);
5211
5635
  } else {
5212
5636
  success2(`\uAC80\uC99D \uD1B5\uACFC: ${id}`);
5213
5637
  }
@@ -5635,19 +6059,19 @@ function detectCircularDependencies(graph) {
5635
6059
  const cycles = [];
5636
6060
  const visited = /* @__PURE__ */ new Set();
5637
6061
  const recStack = /* @__PURE__ */ new Set();
5638
- function dfs(nodeId, path27) {
6062
+ function dfs(nodeId, path29) {
5639
6063
  visited.add(nodeId);
5640
6064
  recStack.add(nodeId);
5641
6065
  const node = graph.nodes.get(nodeId);
5642
6066
  if (!node) return false;
5643
6067
  for (const depId of node.dependsOn) {
5644
6068
  if (!visited.has(depId)) {
5645
- if (dfs(depId, [...path27, nodeId])) {
6069
+ if (dfs(depId, [...path29, nodeId])) {
5646
6070
  return true;
5647
6071
  }
5648
6072
  } else if (recStack.has(depId)) {
5649
- const cycleStart = path27.indexOf(depId);
5650
- const cycle = cycleStart >= 0 ? [...path27.slice(cycleStart), nodeId, depId] : [nodeId, depId];
6073
+ const cycleStart = path29.indexOf(depId);
6074
+ const cycle = cycleStart >= 0 ? [...path29.slice(cycleStart), nodeId, depId] : [nodeId, depId];
5651
6075
  cycles.push({
5652
6076
  cycle,
5653
6077
  description: `\uC21C\uD658 \uC758\uC874\uC131: ${cycle.join(" \u2192 ")}`
@@ -6635,6 +7059,108 @@ function formatCodeImpactResult(result) {
6635
7059
  // src/cli/commands/impact.ts
6636
7060
  init_fs();
6637
7061
  init_errors();
7062
+ init_types();
7063
+ function resolveProposalPath(proposalPath, sddPath) {
7064
+ if (path10.isAbsolute(proposalPath)) {
7065
+ return proposalPath;
7066
+ }
7067
+ const changesPath = path10.join(sddPath, "changes", proposalPath);
7068
+ if (proposalPath.endsWith(".md")) {
7069
+ return changesPath;
7070
+ }
7071
+ return path10.join(changesPath, "proposal.md");
7072
+ }
7073
+ async function executeImpactAnalysis(sddPath, feature) {
7074
+ const result = await analyzeImpact(sddPath, feature);
7075
+ if (!result.success) {
7076
+ return failure(result.error);
7077
+ }
7078
+ return success(result.data);
7079
+ }
7080
+ async function executeGraphAnalysis(specsPath, feature, asJson = false) {
7081
+ const graphResult = await buildDependencyGraph(specsPath);
7082
+ if (!graphResult.success) {
7083
+ return failure(graphResult.error);
7084
+ }
7085
+ const mermaid = generateMermaidGraph(graphResult.data, feature);
7086
+ if (asJson) {
7087
+ return success({
7088
+ mermaid,
7089
+ nodes: Array.from(graphResult.data.nodes.values()),
7090
+ edges: graphResult.data.edges
7091
+ });
7092
+ }
7093
+ return success({ mermaid });
7094
+ }
7095
+ async function executeCodeImpactAnalysis(projectRoot, sddPath, feature) {
7096
+ const result = await analyzeCodeImpact(projectRoot, sddPath, feature);
7097
+ if (!result.success) {
7098
+ return failure(result.error);
7099
+ }
7100
+ return success(result.data);
7101
+ }
7102
+ async function executeImpactReport(sddPath) {
7103
+ const result = await generateImpactReport(sddPath);
7104
+ if (!result.success) {
7105
+ return failure(result.error);
7106
+ }
7107
+ return success(result.data);
7108
+ }
7109
+ async function executeChangeImpact(sddPath, changeId) {
7110
+ const result = await analyzeChangeImpact(sddPath, changeId);
7111
+ if (!result.success) {
7112
+ return failure(result.error);
7113
+ }
7114
+ return success(result.data);
7115
+ }
7116
+ function formatChangeImpactOutput(data) {
7117
+ const lines = [];
7118
+ lines.push(`\u{1F4CA} \uBCC0\uACBD \uC601\uD5A5 \uBD84\uC11D: ${data.changeId}`);
7119
+ if (data.title) {
7120
+ lines.push(`\uC81C\uBAA9: ${data.title}`);
7121
+ }
7122
+ lines.push(`\uC0C1\uD0DC: ${data.status}`);
7123
+ lines.push("");
7124
+ if (data.affectedSpecs.length > 0) {
7125
+ lines.push("\u26A0\uFE0F \uC9C1\uC811 \uC601\uD5A5 \uBC1B\uB294 \uC2A4\uD399:");
7126
+ for (const spec of data.affectedSpecs) {
7127
+ lines.push(` - ${spec.id} - ${spec.reason}`);
7128
+ }
7129
+ lines.push("");
7130
+ }
7131
+ if (data.transitiveAffected.length > 0) {
7132
+ lines.push("\u{1F504} \uAC04\uC811 \uC601\uD5A5 \uBC1B\uB294 \uC2A4\uD399:");
7133
+ for (const spec of data.transitiveAffected) {
7134
+ lines.push(` - ${spec.id} (${spec.reason})`);
7135
+ }
7136
+ lines.push("");
7137
+ }
7138
+ const riskIcon = data.riskLevel === "high" ? "\u{1F534}" : data.riskLevel === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
7139
+ lines.push(`\uCD1D \uC601\uD5A5 \uBC94\uC704: ${data.totalImpact}\uAC1C \uC2A4\uD399 ${riskIcon}`);
7140
+ lines.push("");
7141
+ if (data.recommendations.length > 0) {
7142
+ lines.push("\u{1F4A1} \uAD8C\uC7A5\uC0AC\uD56D:");
7143
+ for (const rec of data.recommendations) {
7144
+ lines.push(` - ${rec}`);
7145
+ }
7146
+ }
7147
+ return lines.join("\n");
7148
+ }
7149
+ async function executeSimulation(specsPath, feature, proposalPath) {
7150
+ const deltaResult = await parseDeltaFromProposal(proposalPath);
7151
+ if (!deltaResult.success) {
7152
+ return failure(deltaResult.error);
7153
+ }
7154
+ const deltas = deltaResult.data;
7155
+ if (deltas.length === 0) {
7156
+ return failure(new Error("\uBCC0\uACBD \uC81C\uC548\uC5D0\uC11C \uB378\uD0C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
7157
+ }
7158
+ const simResult = await runSimulation(specsPath, feature, deltas);
7159
+ if (!simResult.success) {
7160
+ return failure(simResult.error);
7161
+ }
7162
+ return success({ deltas, result: simResult.data });
7163
+ }
6638
7164
  function registerImpactCommand(program2) {
6639
7165
  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
7166
  try {
@@ -6676,25 +7202,25 @@ async function runImpact(feature, options) {
6676
7202
  process.exit(ExitCode.GENERAL_ERROR);
6677
7203
  }
6678
7204
  const sddPath = path10.join(projectRoot, ".sdd");
7205
+ const specsPath = path10.join(sddPath, "specs");
6679
7206
  if (options.graph) {
6680
- const graphResult = await buildDependencyGraph(path10.join(sddPath, "specs"));
7207
+ const graphResult = await executeGraphAnalysis(specsPath, feature, options.json);
6681
7208
  if (!graphResult.success) {
6682
7209
  error(graphResult.error.message);
6683
7210
  process.exit(ExitCode.GENERAL_ERROR);
6684
7211
  }
6685
- const mermaid = generateMermaidGraph(graphResult.data, feature);
6686
7212
  if (options.json) {
6687
7213
  console.log(JSON.stringify({
6688
7214
  format: "mermaid",
6689
- content: mermaid,
6690
- nodes: Array.from(graphResult.data.nodes.values()),
7215
+ content: graphResult.data.mermaid,
7216
+ nodes: graphResult.data.nodes,
6691
7217
  edges: graphResult.data.edges
6692
7218
  }, null, 2));
6693
7219
  } else {
6694
7220
  info("\uC758\uC874\uC131 \uADF8\uB798\uD504 (Mermaid):");
6695
7221
  newline();
6696
7222
  console.log("```mermaid");
6697
- console.log(mermaid);
7223
+ console.log(graphResult.data.mermaid);
6698
7224
  console.log("```");
6699
7225
  }
6700
7226
  return;
@@ -6708,7 +7234,7 @@ async function runImpact(feature, options) {
6708
7234
  }
6709
7235
  info(`\u{1F4BB} \uCF54\uB4DC \uC601\uD5A5\uB3C4 \uBD84\uC11D: ${feature}`);
6710
7236
  newline();
6711
- const codeResult = await analyzeCodeImpact(projectRoot, sddPath, feature);
7237
+ const codeResult = await executeCodeImpactAnalysis(projectRoot, sddPath, feature);
6712
7238
  if (!codeResult.success) {
6713
7239
  error(codeResult.error.message);
6714
7240
  process.exit(ExitCode.GENERAL_ERROR);
@@ -6726,7 +7252,7 @@ async function runImpact(feature, options) {
6726
7252
  info("\uC608\uC2DC: sdd impact auth");
6727
7253
  process.exit(ExitCode.GENERAL_ERROR);
6728
7254
  }
6729
- const result = await analyzeImpact(sddPath, feature);
7255
+ const result = await executeImpactAnalysis(sddPath, feature);
6730
7256
  if (!result.success) {
6731
7257
  error(result.error.message);
6732
7258
  process.exit(ExitCode.GENERAL_ERROR);
@@ -6744,7 +7270,7 @@ async function runImpactReport(options) {
6744
7270
  process.exit(ExitCode.GENERAL_ERROR);
6745
7271
  }
6746
7272
  const sddPath = path10.join(projectRoot, ".sdd");
6747
- const result = await generateImpactReport(sddPath);
7273
+ const result = await executeImpactReport(sddPath);
6748
7274
  if (!result.success) {
6749
7275
  error(result.error.message);
6750
7276
  process.exit(ExitCode.GENERAL_ERROR);
@@ -6762,7 +7288,7 @@ async function runChangeImpact(changeId, options) {
6762
7288
  process.exit(ExitCode.GENERAL_ERROR);
6763
7289
  }
6764
7290
  const sddPath = path10.join(projectRoot, ".sdd");
6765
- const result = await analyzeChangeImpact(sddPath, changeId);
7291
+ const result = await executeChangeImpact(sddPath, changeId);
6766
7292
  if (!result.success) {
6767
7293
  error(result.error.message);
6768
7294
  process.exit(ExitCode.GENERAL_ERROR);
@@ -6770,36 +7296,7 @@ async function runChangeImpact(changeId, options) {
6770
7296
  if (options.json) {
6771
7297
  console.log(JSON.stringify(result.data, null, 2));
6772
7298
  } 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
- }
7299
+ console.log(formatChangeImpactOutput(result.data));
6803
7300
  }
6804
7301
  }
6805
7302
  async function runSimulate(feature, proposalPath, options) {
@@ -6810,58 +7307,201 @@ async function runSimulate(feature, proposalPath, options) {
6810
7307
  }
6811
7308
  const sddPath = path10.join(projectRoot, ".sdd");
6812
7309
  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
- }
7310
+ const fullProposalPath = resolveProposalPath(proposalPath, sddPath);
6822
7311
  info(`\u{1F4CA} What-if \uC2DC\uBBAC\uB808\uC774\uC158`);
6823
7312
  info(`\uB300\uC0C1 \uC2A4\uD399: ${feature}`);
6824
7313
  info(`\uBCC0\uACBD \uC81C\uC548: ${fullProposalPath}`);
6825
7314
  newline();
6826
- const deltaResult = await parseDeltaFromProposal(fullProposalPath);
6827
- if (!deltaResult.success) {
6828
- error(deltaResult.error.message);
7315
+ const result = await executeSimulation(specsPath, feature, fullProposalPath);
7316
+ if (!result.success) {
7317
+ if (result.error.message.includes("\uB378\uD0C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4")) {
7318
+ warn(result.error.message);
7319
+ info("ADDED, MODIFIED, REMOVED \uC139\uC158\uC744 \uD655\uC778\uD558\uC138\uC694.");
7320
+ return;
7321
+ }
7322
+ error(result.error.message);
6829
7323
  process.exit(ExitCode.GENERAL_ERROR);
6830
7324
  }
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
- }
7325
+ const { deltas, result: simResult } = result.data;
6837
7326
  info(`\uAC10\uC9C0\uB41C \uBCC0\uACBD: ${deltas.length}\uAC74`);
6838
7327
  for (const delta of deltas) {
6839
7328
  const icon = delta.type === "ADDED" ? "\u2795" : delta.type === "REMOVED" ? "\u2796" : "\u270F\uFE0F";
6840
7329
  listItem(`${icon} ${delta.type}: ${delta.specId}`, 1);
6841
7330
  }
6842
7331
  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
7332
  if (options.json) {
6849
- console.log(JSON.stringify(simResult.data, null, 2));
7333
+ console.log(JSON.stringify(simResult, null, 2));
6850
7334
  } else {
6851
- console.log(formatSimulationResult(simResult.data, feature));
7335
+ console.log(formatSimulationResult(simResult, feature));
6852
7336
  }
6853
7337
  }
6854
7338
 
6855
7339
  // src/cli/commands/new.ts
6856
7340
  init_new();
6857
7341
  import path12 from "path";
6858
- import { promises as fs7 } from "fs";
6859
7342
 
6860
7343
  // src/utils/index.ts
6861
7344
  init_fs();
6862
7345
 
6863
7346
  // src/cli/commands/new.ts
6864
7347
  init_fs();
7348
+ init_types();
7349
+ async function getConstitutionVersion(sddPath) {
7350
+ const constitutionPath = path12.join(sddPath, "constitution.md");
7351
+ if (!await fileExists(constitutionPath)) {
7352
+ return void 0;
7353
+ }
7354
+ const constResult = await readFile(constitutionPath);
7355
+ if (!constResult.success) {
7356
+ return void 0;
7357
+ }
7358
+ const parseResult = parseConstitution(constResult.data);
7359
+ if (!parseResult.success) {
7360
+ return void 0;
7361
+ }
7362
+ return parseResult.data.metadata.version;
7363
+ }
7364
+ async function createFeature(sddPath, name, options) {
7365
+ let featureId;
7366
+ let branchName;
7367
+ if (options.numbered) {
7368
+ const numberResult = await getNextFeatureNumber(sddPath, name);
7369
+ if (!numberResult.success) {
7370
+ return failure(new Error(`\uBC88\uD638 \uC0DD\uC131 \uC2E4\uD328: ${numberResult.error.message}`));
7371
+ }
7372
+ featureId = numberResult.data.fullId;
7373
+ branchName = numberResult.data.branchName;
7374
+ } else {
7375
+ featureId = generateFeatureId(name);
7376
+ }
7377
+ const title2 = options.title || name;
7378
+ const description = options.description || `${title2} \uAE30\uB2A5 \uBA85\uC138`;
7379
+ const featurePath = path12.join(sddPath, "specs", featureId);
7380
+ const dirResult = await ensureDir(featurePath);
7381
+ if (!dirResult.success) {
7382
+ return failure(new Error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${featurePath}`));
7383
+ }
7384
+ const constitutionVersion = await getConstitutionVersion(sddPath);
7385
+ const filesCreated = [];
7386
+ const specContent = generateSpec({
7387
+ id: featureId,
7388
+ title: title2,
7389
+ description,
7390
+ constitutionVersion
7391
+ });
7392
+ await writeFile(path12.join(featurePath, "spec.md"), specContent);
7393
+ filesCreated.push("spec.md");
7394
+ if (options.plan || options.all) {
7395
+ const planContent = generatePlan({
7396
+ featureId,
7397
+ featureTitle: title2,
7398
+ overview: description
7399
+ });
7400
+ await writeFile(path12.join(featurePath, "plan.md"), planContent);
7401
+ filesCreated.push("plan.md");
7402
+ }
7403
+ if (options.tasks || options.all) {
7404
+ const tasksContent = generateTasks({
7405
+ featureId,
7406
+ featureTitle: title2,
7407
+ tasks: [
7408
+ { title: "\uAE30\uBC18 \uAD6C\uC870 \uC124\uC815", priority: "high" },
7409
+ { title: "\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604", priority: "high" },
7410
+ { title: "\uD14C\uC2A4\uD2B8 \uC791\uC131", priority: "medium" },
7411
+ { title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
7412
+ ]
7413
+ });
7414
+ await writeFile(path12.join(featurePath, "tasks.md"), tasksContent);
7415
+ filesCreated.push("tasks.md");
7416
+ }
7417
+ if (options.checklist || options.all) {
7418
+ const checklistContent = generateFullChecklistMarkdown();
7419
+ await writeFile(path12.join(featurePath, "checklist.md"), checklistContent);
7420
+ filesCreated.push("checklist.md");
7421
+ }
7422
+ return success({
7423
+ featureId,
7424
+ featurePath,
7425
+ branchName,
7426
+ filesCreated
7427
+ });
7428
+ }
7429
+ async function createPlan(featurePath, featureId, title2) {
7430
+ if (!await fileExists(featurePath)) {
7431
+ return failure(new Error(`\uAE30\uB2A5 '${featureId}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
7432
+ }
7433
+ let featureTitle = title2 || featureId;
7434
+ const specPath = path12.join(featurePath, "spec.md");
7435
+ if (await fileExists(specPath)) {
7436
+ const specResult = await readFile(specPath);
7437
+ if (specResult.success) {
7438
+ const titleMatch = specResult.data.match(/title:\s*"?([^"\n]+)"?/);
7439
+ if (titleMatch) {
7440
+ featureTitle = titleMatch[1];
7441
+ }
7442
+ }
7443
+ }
7444
+ const planContent = generatePlan({
7445
+ featureId,
7446
+ featureTitle,
7447
+ overview: `${featureTitle} \uAD6C\uD604 \uACC4\uD68D`
7448
+ });
7449
+ const planPath = path12.join(featurePath, "plan.md");
7450
+ await writeFile(planPath, planContent);
7451
+ return success(planPath);
7452
+ }
7453
+ async function createTasks(featurePath, featureId) {
7454
+ if (!await fileExists(featurePath)) {
7455
+ return failure(new Error(`\uAE30\uB2A5 '${featureId}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
7456
+ }
7457
+ let featureTitle = featureId;
7458
+ const specPath = path12.join(featurePath, "spec.md");
7459
+ if (await fileExists(specPath)) {
7460
+ const specResult = await readFile(specPath);
7461
+ if (specResult.success) {
7462
+ const titleMatch = specResult.data.match(/title:\s*"?([^"\n]+)"?/);
7463
+ if (titleMatch) {
7464
+ featureTitle = titleMatch[1];
7465
+ }
7466
+ }
7467
+ }
7468
+ const tasksContent = generateTasks({
7469
+ featureId,
7470
+ featureTitle,
7471
+ tasks: [
7472
+ { title: "\uAE30\uBC18 \uAD6C\uC870 \uC124\uC815", priority: "high" },
7473
+ { title: "\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604", priority: "high" },
7474
+ { title: "\uD14C\uC2A4\uD2B8 \uC791\uC131", priority: "medium" },
7475
+ { title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
7476
+ ]
7477
+ });
7478
+ const tasksPath = path12.join(featurePath, "tasks.md");
7479
+ await writeFile(tasksPath, tasksContent);
7480
+ return success(tasksPath);
7481
+ }
7482
+ async function createChecklist2(sddPath) {
7483
+ if (!await fileExists(sddPath)) {
7484
+ return failure(new Error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
7485
+ }
7486
+ const checklistContent = generateFullChecklistMarkdown();
7487
+ const outputPath = path12.join(sddPath, "checklist.md");
7488
+ await writeFile(outputPath, checklistContent);
7489
+ return success(outputPath);
7490
+ }
7491
+ async function getCounterStatus(sddPath) {
7492
+ const peekResult = await peekNextFeatureNumber(sddPath);
7493
+ if (!peekResult.success) {
7494
+ return failure(new Error(`\uCE74\uC6B4\uD130 \uC870\uD68C \uC2E4\uD328: ${peekResult.error.message}`));
7495
+ }
7496
+ const historyResult = await getFeatureHistory(sddPath);
7497
+ if (!historyResult.success) {
7498
+ return failure(new Error(`\uC774\uB825 \uC870\uD68C \uC2E4\uD328: ${historyResult.error.message}`));
7499
+ }
7500
+ return success({
7501
+ nextNumber: peekResult.data,
7502
+ totalFeatures: historyResult.data.length
7503
+ });
7504
+ }
6865
7505
  function registerNewCommand(program2) {
6866
7506
  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
7507
  await handleNew(name, options);
@@ -6886,193 +7526,88 @@ async function handleNew(name, options) {
6886
7526
  }
6887
7527
  const cwd = process.cwd();
6888
7528
  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);
7529
+ if (!await fileExists(sddPath)) {
7530
+ logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 sdd init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
7531
+ process.exit(1);
6902
7532
  }
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
- }
7533
+ const result = await createFeature(sddPath, name, options);
7534
+ if (!result.success) {
7535
+ logger_exports.error(`\uAE30\uB2A5 \uC0DD\uC131 \uC2E4\uD328: ${result.error.message}`);
7536
+ process.exit(1);
7537
+ }
7538
+ const { featureId, featurePath, branchName, filesCreated } = result.data;
7539
+ if (options.numbered && branchName) {
7540
+ const numberMatch = branchName.match(/feature\/(\d+)-/);
7541
+ if (numberMatch) {
7542
+ logger_exports.info(`\uC790\uB3D9 \uBC88\uD638 \uBD80\uC5EC: #${numberMatch[1]}`);
6922
7543
  }
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
- }
7544
+ }
7545
+ for (const file of filesCreated) {
7546
+ logger_exports.info(`\u2705 ${file} \uC0DD\uC131: ${featurePath}/${file}`);
7547
+ }
7548
+ if (options.branch !== false) {
7549
+ if (await isGitRepository(cwd)) {
7550
+ const branchToCreate = branchName || featureId;
7551
+ const branchResult = await createBranch(branchToCreate, { checkout: true, cwd });
7552
+ if (branchResult.success) {
7553
+ logger_exports.info(`\u2705 \uBE0C\uB79C\uCE58 \uC0DD\uC131: ${branchResult.data}`);
6940
7554
  } 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.");
7555
+ logger_exports.warn(`\u26A0\uFE0F \uBE0C\uB79C\uCE58 \uC0DD\uC131 \uC2E4\uD328: ${branchResult.error.message}`);
6942
7556
  }
7557
+ } else {
7558
+ 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
7559
  }
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
7560
  }
7561
+ logger_exports.info("");
7562
+ logger_exports.info(`\u{1F389} \uAE30\uB2A5 '${featureId}' \uC0DD\uC131 \uC644\uB8CC!`);
7563
+ logger_exports.info("");
7564
+ logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
7565
+ logger_exports.info(` 1. ${featurePath}/spec.md \uD3B8\uC9D1`);
7566
+ if (!(options.plan || options.all)) {
7567
+ logger_exports.info(" 2. sdd new plan " + featureId + " - \uACC4\uD68D \uC791\uC131");
7568
+ }
7569
+ if (!(options.tasks || options.all)) {
7570
+ logger_exports.info(" 3. sdd new tasks " + featureId + " - \uC791\uC5C5 \uBD84\uD574");
7571
+ }
7572
+ logger_exports.info(" 4. sdd validate - \uBA85\uC138 \uAC80\uC99D");
6988
7573
  }
6989
7574
  async function handlePlan(feature, options) {
6990
7575
  const cwd = process.cwd();
6991
7576
  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}`);
7577
+ const result = await createPlan(featurePath, feature, options.title);
7578
+ if (!result.success) {
7579
+ logger_exports.error(result.error.message);
7019
7580
  process.exit(1);
7020
7581
  }
7582
+ logger_exports.info(`\u2705 \uACC4\uD68D \uC0DD\uC131: ${result.data}`);
7583
+ logger_exports.info("");
7584
+ logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
7585
+ logger_exports.info(` 1. ${featurePath}/plan.md \uD3B8\uC9D1`);
7586
+ logger_exports.info(" 2. sdd new tasks " + feature + " - \uC791\uC5C5 \uBD84\uD574");
7021
7587
  }
7022
7588
  async function handleTasks(feature) {
7023
7589
  const cwd = process.cwd();
7024
7590
  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}`);
7591
+ const result = await createTasks(featurePath, feature);
7592
+ if (!result.success) {
7593
+ logger_exports.error(result.error.message);
7057
7594
  process.exit(1);
7058
7595
  }
7596
+ logger_exports.info(`\u2705 \uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131: ${result.data}`);
7597
+ logger_exports.info("");
7598
+ logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
7599
+ logger_exports.info(` 1. ${featurePath}/tasks.md \uD3B8\uC9D1`);
7600
+ logger_exports.info(" 2. \uAC01 \uC791\uC5C5 \uC21C\uCC28\uC801\uC73C\uB85C \uAD6C\uD604");
7059
7601
  }
7060
7602
  async function handleChecklist() {
7061
7603
  const cwd = process.cwd();
7062
7604
  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}`);
7605
+ const result = await createChecklist2(sddPath);
7606
+ if (!result.success) {
7607
+ logger_exports.error(result.error.message);
7074
7608
  process.exit(1);
7075
7609
  }
7610
+ logger_exports.info(`\u2705 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC0DD\uC131: ${result.data}`);
7076
7611
  }
7077
7612
  async function handleCounter(options) {
7078
7613
  const cwd = process.cwd();
@@ -7131,39 +7666,65 @@ async function handleCounter(options) {
7131
7666
  }
7132
7667
  return;
7133
7668
  }
7134
- const peekResult = await peekNextFeatureNumber(sddPath);
7135
- const historyResult = await getFeatureHistory(sddPath);
7136
- if (peekResult.success && historyResult.success) {
7669
+ const statusResult = await getCounterStatus(sddPath);
7670
+ if (statusResult.success) {
7137
7671
  logger_exports.info("=== \uAE30\uB2A5 \uBC88\uD638 \uCE74\uC6B4\uD130 \uC0C1\uD0DC ===");
7138
7672
  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`);
7673
+ logger_exports.info(`\uB2E4\uC74C \uBC88\uD638: #${String(statusResult.data.nextNumber).padStart(3, "0")}`);
7674
+ logger_exports.info(`\uC0DD\uC131\uB41C \uAE30\uB2A5 \uC218: ${statusResult.data.totalFeatures}\uAC1C`);
7141
7675
  logger_exports.info("");
7142
7676
  logger_exports.info("\uC635\uC158:");
7143
7677
  logger_exports.info(" --peek \uB2E4\uC74C \uBC88\uD638 \uD655\uC778");
7144
7678
  logger_exports.info(" --history \uC0DD\uC131 \uC774\uB825 \uC870\uD68C");
7145
7679
  logger_exports.info(" --set <n> \uB2E4\uC74C \uBC88\uD638 \uC124\uC815");
7146
7680
  } else {
7147
- logger_exports.error("\uCE74\uC6B4\uD130 \uC0C1\uD0DC \uC870\uD68C \uC2E4\uD328");
7681
+ logger_exports.error(statusResult.error.message);
7148
7682
  process.exit(1);
7149
7683
  }
7150
7684
  }
7151
7685
 
7152
7686
  // src/cli/commands/status.ts
7153
7687
  import path13 from "path";
7154
- import { promises as fs8 } from "fs";
7688
+ import { promises as fs7 } from "fs";
7155
7689
  init_fs();
7156
7690
  init_spec_generator();
7157
7691
  init_task_generator();
7158
7692
  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
- });
7693
+ async function getFeatureInfo(id, featurePath) {
7694
+ const info2 = {
7695
+ id,
7696
+ title: id,
7697
+ status: "unknown",
7698
+ hasSpec: false,
7699
+ hasPlan: false,
7700
+ hasTasks: false
7701
+ };
7702
+ const specPath = path13.join(featurePath, "spec.md");
7703
+ if (await fileExists(specPath)) {
7704
+ info2.hasSpec = true;
7705
+ const content = await fs7.readFile(specPath, "utf-8");
7706
+ const metadata = parseSpecMetadata(content);
7707
+ if (metadata) {
7708
+ info2.title = metadata.title;
7709
+ info2.status = metadata.status;
7710
+ }
7711
+ }
7712
+ info2.hasPlan = await fileExists(path13.join(featurePath, "plan.md"));
7713
+ const tasksPath = path13.join(featurePath, "tasks.md");
7714
+ if (await fileExists(tasksPath)) {
7715
+ info2.hasTasks = true;
7716
+ const content = await fs7.readFile(tasksPath, "utf-8");
7717
+ const tasks = parseTasks(content);
7718
+ const completed = tasks.filter((t) => t.status === "completed").length;
7719
+ info2.taskProgress = {
7720
+ completed,
7721
+ total: tasks.length
7722
+ };
7723
+ }
7724
+ return info2;
7163
7725
  }
7164
- async function handleStatus(options) {
7165
- const cwd = process.cwd();
7166
- const sddPath = path13.join(cwd, ".sdd");
7726
+ async function getProjectStatus(projectPath) {
7727
+ const sddPath = path13.join(projectPath, ".sdd");
7167
7728
  const status = {
7168
7729
  initialized: false,
7169
7730
  hasConstitution: false,
@@ -7175,13 +7736,7 @@ async function handleStatus(options) {
7175
7736
  };
7176
7737
  status.initialized = await fileExists(sddPath);
7177
7738
  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;
7739
+ return status;
7185
7740
  }
7186
7741
  status.hasConstitution = await fileExists(path13.join(sddPath, "constitution.md"));
7187
7742
  status.hasAgents = await fileExists(path13.join(sddPath, "AGENTS.md"));
@@ -7191,7 +7746,7 @@ async function handleStatus(options) {
7191
7746
  if (specsResult.success) {
7192
7747
  for (const entry of specsResult.data) {
7193
7748
  const featurePath = path13.join(specsPath, entry);
7194
- const stat = await fs8.stat(featurePath);
7749
+ const stat = await fs7.stat(featurePath);
7195
7750
  if (stat.isDirectory()) {
7196
7751
  const featureInfo = await getFeatureInfo(entry, featurePath);
7197
7752
  status.features.push(featureInfo);
@@ -7207,52 +7762,56 @@ async function handleStatus(options) {
7207
7762
  if (archiveResult.success) {
7208
7763
  status.archivedChanges = archiveResult.data.length;
7209
7764
  }
7210
- const currentBranchResult = await getCurrentBranch(cwd);
7765
+ const currentBranchResult = await getCurrentBranch(projectPath);
7211
7766
  if (currentBranchResult.success) {
7212
7767
  status.currentBranch = currentBranchResult.data;
7213
7768
  }
7214
- const featureBranchesResult = await listFeatureBranches(cwd);
7769
+ const featureBranchesResult = await listFeatureBranches(projectPath);
7215
7770
  if (featureBranchesResult.success) {
7216
7771
  status.featureBranches = featureBranchesResult.data;
7217
7772
  }
7218
- if (options.json) {
7219
- console.log(JSON.stringify(status, null, 2));
7220
- } else {
7221
- printStatus(status, options.verbose);
7773
+ return status;
7774
+ }
7775
+ function getStatusIcon(status) {
7776
+ switch (status) {
7777
+ case "draft":
7778
+ return "\u{1F4DD}";
7779
+ case "specified":
7780
+ return "\u{1F4C4}";
7781
+ case "planned":
7782
+ return "\u{1F4CB}";
7783
+ case "tasked":
7784
+ return "\u270F\uFE0F";
7785
+ case "implementing":
7786
+ return "\u{1F528}";
7787
+ case "completed":
7788
+ return "\u2705";
7789
+ default:
7790
+ return "\u2753";
7222
7791
  }
7223
7792
  }
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;
7793
+ function registerStatusCommand(program2) {
7794
+ 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) => {
7795
+ await handleStatus(options);
7796
+ });
7797
+ }
7798
+ async function handleStatus(options) {
7799
+ const cwd = process.cwd();
7800
+ const status = await getProjectStatus(cwd);
7801
+ if (!status.initialized) {
7802
+ if (options.json) {
7803
+ console.log(JSON.stringify(status, null, 2));
7804
+ } else {
7805
+ logger_exports.warn("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
7806
+ logger_exports.info("sdd init \uBA85\uB839\uC5B4\uB85C \uCD08\uAE30\uD654\uD558\uC138\uC694.");
7241
7807
  }
7808
+ return;
7242
7809
  }
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
- };
7810
+ if (options.json) {
7811
+ console.log(JSON.stringify(status, null, 2));
7812
+ } else {
7813
+ printStatus(status, options.verbose);
7254
7814
  }
7255
- return info2;
7256
7815
  }
7257
7816
  function printStatus(status, verbose) {
7258
7817
  console.log("");
@@ -7333,7 +7892,13 @@ function printStatus(status, verbose) {
7333
7892
  }
7334
7893
  console.log("");
7335
7894
  }
7336
- function getStatusIcon(status) {
7895
+
7896
+ // src/cli/commands/list.ts
7897
+ import path14 from "path";
7898
+ import { promises as fs8 } from "fs";
7899
+ init_fs();
7900
+ init_spec_generator();
7901
+ function getListStatusIcon(status) {
7337
7902
  switch (status) {
7338
7903
  case "draft":
7339
7904
  return "\u{1F4DD}";
@@ -7351,50 +7916,23 @@ function getStatusIcon(status) {
7351
7916
  return "\u2753";
7352
7917
  }
7353
7918
  }
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
- function registerListCommand(program2) {
7361
- const listCmd = program2.command("list").alias("ls").description("\uD56D\uBAA9 \uBAA9\uB85D \uC870\uD68C");
7362
- 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) => {
7363
- await listFeatures(options);
7364
- });
7365
- listCmd.command("changes").alias("c").description("\uBCC0\uACBD \uC81C\uC548 \uBAA9\uB85D \uC870\uD68C").option("--pending", "\uB300\uAE30 \uC911\uC778 \uBCC0\uACBD\uB9CC \uD45C\uC2DC").option("--archived", "\uC544\uCE74\uC774\uBE0C\uB41C \uBCC0\uACBD\uB9CC \uD45C\uC2DC").action(async (options) => {
7366
- await listChanges(options);
7367
- });
7368
- listCmd.command("specs").alias("s").description("\uC2A4\uD399 \uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").action(async () => {
7369
- await listSpecs();
7370
- });
7371
- listCmd.command("templates").alias("t").description("\uD15C\uD50C\uB9BF \uBAA9\uB85D \uC870\uD68C").action(async () => {
7372
- await listTemplates();
7373
- });
7374
- listCmd.action(async () => {
7375
- await listSummary();
7376
- });
7377
- }
7378
- async function listFeatures(options) {
7379
- const cwd = process.cwd();
7380
- const specsPath = path14.join(cwd, ".sdd", "specs");
7919
+ async function getFeatureList(projectPath, options = {}) {
7920
+ const specsPath = path14.join(projectPath, ".sdd", "specs");
7381
7921
  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;
7922
+ return [];
7384
7923
  }
7385
7924
  const result = await readDir(specsPath);
7386
7925
  if (!result.success) {
7387
- logger_exports.error("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
7388
- return;
7926
+ return [];
7389
7927
  }
7390
7928
  const features = [];
7391
7929
  for (const entry of result.data) {
7392
7930
  const featurePath = path14.join(specsPath, entry);
7393
- const stat = await fs9.stat(featurePath);
7931
+ const stat = await fs8.stat(featurePath);
7394
7932
  if (stat.isDirectory()) {
7395
7933
  const specPath = path14.join(featurePath, "spec.md");
7396
7934
  if (await fileExists(specPath)) {
7397
- const content = await fs9.readFile(specPath, "utf-8");
7935
+ const content = await fs8.readFile(specPath, "utf-8");
7398
7936
  const metadata = parseSpecMetadata(content);
7399
7937
  if (metadata) {
7400
7938
  if (!options.status || metadata.status === options.status) {
@@ -7408,6 +7946,119 @@ async function listFeatures(options) {
7408
7946
  }
7409
7947
  }
7410
7948
  }
7949
+ return features;
7950
+ }
7951
+ async function getChangeList(projectPath, options = {}) {
7952
+ const sddPath = path14.join(projectPath, ".sdd");
7953
+ const result = {
7954
+ pending: [],
7955
+ archived: []
7956
+ };
7957
+ if (!await fileExists(sddPath)) {
7958
+ return result;
7959
+ }
7960
+ if (!options.archived) {
7961
+ const pendingResult = await listPendingChanges(sddPath);
7962
+ if (pendingResult.success) {
7963
+ result.pending = pendingResult.data.map((c) => String(c));
7964
+ }
7965
+ }
7966
+ if (!options.pending) {
7967
+ const archiveResult = await listArchives(sddPath);
7968
+ if (archiveResult.success) {
7969
+ result.archived = archiveResult.data.map((a) => a.id);
7970
+ }
7971
+ }
7972
+ return result;
7973
+ }
7974
+ async function getSpecFileTree(specsPath) {
7975
+ if (!await fileExists(specsPath)) {
7976
+ return [];
7977
+ }
7978
+ return walkSpecsTree(specsPath);
7979
+ }
7980
+ async function walkSpecsTree(basePath) {
7981
+ const result = await readDir(basePath);
7982
+ if (!result.success) return [];
7983
+ const items = [];
7984
+ for (const entry of result.data) {
7985
+ const fullPath = path14.join(basePath, entry);
7986
+ const stat = await fs8.stat(fullPath);
7987
+ if (stat.isDirectory()) {
7988
+ const children = await walkSpecsTree(fullPath);
7989
+ items.push({
7990
+ path: fullPath,
7991
+ name: entry,
7992
+ isDirectory: true,
7993
+ children
7994
+ });
7995
+ } else if (entry.endsWith(".md")) {
7996
+ items.push({
7997
+ path: fullPath,
7998
+ name: entry,
7999
+ isDirectory: false
8000
+ });
8001
+ }
8002
+ }
8003
+ return items;
8004
+ }
8005
+ async function getTemplateList(projectPath) {
8006
+ const templatesPath = path14.join(projectPath, ".sdd", "templates");
8007
+ if (!await fileExists(templatesPath)) {
8008
+ return [];
8009
+ }
8010
+ const result = await readDir(templatesPath);
8011
+ if (!result.success) {
8012
+ return [];
8013
+ }
8014
+ return result.data.filter((f) => f.endsWith(".md"));
8015
+ }
8016
+ async function getProjectSummary(projectPath) {
8017
+ const sddPath = path14.join(projectPath, ".sdd");
8018
+ if (!await fileExists(sddPath)) {
8019
+ return null;
8020
+ }
8021
+ const specsPath = path14.join(sddPath, "specs");
8022
+ let featureCount = 0;
8023
+ if (await fileExists(specsPath)) {
8024
+ const result = await readDir(specsPath);
8025
+ if (result.success) {
8026
+ for (const entry of result.data) {
8027
+ const stat = await fs8.stat(path14.join(specsPath, entry));
8028
+ if (stat.isDirectory()) featureCount++;
8029
+ }
8030
+ }
8031
+ }
8032
+ const pendingResult = await listPendingChanges(sddPath);
8033
+ const pendingChangeCount = pendingResult.success ? pendingResult.data.length : 0;
8034
+ const archiveResult = await listArchives(sddPath);
8035
+ const archivedChangeCount = archiveResult.success ? archiveResult.data.length : 0;
8036
+ return {
8037
+ featureCount,
8038
+ pendingChangeCount,
8039
+ archivedChangeCount
8040
+ };
8041
+ }
8042
+ function registerListCommand(program2) {
8043
+ const listCmd = program2.command("list").alias("ls").description("\uD56D\uBAA9 \uBAA9\uB85D \uC870\uD68C");
8044
+ 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) => {
8045
+ await listFeatures(options);
8046
+ });
8047
+ listCmd.command("changes").alias("c").description("\uBCC0\uACBD \uC81C\uC548 \uBAA9\uB85D \uC870\uD68C").option("--pending", "\uB300\uAE30 \uC911\uC778 \uBCC0\uACBD\uB9CC \uD45C\uC2DC").option("--archived", "\uC544\uCE74\uC774\uBE0C\uB41C \uBCC0\uACBD\uB9CC \uD45C\uC2DC").action(async (options) => {
8048
+ await listChanges(options);
8049
+ });
8050
+ listCmd.command("specs").alias("s").description("\uC2A4\uD399 \uD30C\uC77C \uBAA9\uB85D \uC870\uD68C").action(async () => {
8051
+ await listSpecs();
8052
+ });
8053
+ listCmd.command("templates").alias("t").description("\uD15C\uD50C\uB9BF \uBAA9\uB85D \uC870\uD68C").action(async () => {
8054
+ await listTemplates();
8055
+ });
8056
+ listCmd.action(async () => {
8057
+ await listSummary();
8058
+ });
8059
+ }
8060
+ async function listFeatures(options) {
8061
+ const features = await getFeatureList(process.cwd(), options);
7411
8062
  if (features.length === 0) {
7412
8063
  logger_exports.info("\uAE30\uB2A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
7413
8064
  return;
@@ -7416,25 +8067,19 @@ async function listFeatures(options) {
7416
8067
  console.log("\u{1F4CB} \uAE30\uB2A5 \uBAA9\uB85D");
7417
8068
  console.log("\u2500".repeat(50));
7418
8069
  for (const f of features) {
7419
- const statusIcon = getStatusIcon2(f.status);
8070
+ const statusIcon = getListStatusIcon(f.status);
7420
8071
  console.log(`${statusIcon} ${f.title} (${f.id}) - ${f.status}`);
7421
8072
  }
7422
8073
  console.log("");
7423
8074
  }
7424
8075
  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
- }
8076
+ const result = await getChangeList(process.cwd(), options);
7431
8077
  console.log("");
7432
8078
  if (!options.archived) {
7433
- const pendingResult = await listPendingChanges(sddPath);
7434
- if (pendingResult.success && pendingResult.data.length > 0) {
8079
+ if (result.pending.length > 0) {
7435
8080
  console.log("\u{1F4DD} \uB300\uAE30 \uC911\uC778 \uBCC0\uACBD");
7436
8081
  console.log("\u2500".repeat(30));
7437
- for (const change of pendingResult.data) {
8082
+ for (const change of result.pending) {
7438
8083
  console.log(` - ${change}`);
7439
8084
  }
7440
8085
  console.log("");
@@ -7443,11 +8088,10 @@ async function listChanges(options) {
7443
8088
  }
7444
8089
  }
7445
8090
  if (!options.pending) {
7446
- const archiveResult = await listArchives(sddPath);
7447
- if (archiveResult.success && archiveResult.data.length > 0) {
8091
+ if (result.archived.length > 0) {
7448
8092
  console.log("\u{1F4E6} \uC544\uCE74\uC774\uBE0C\uB41C \uBCC0\uACBD");
7449
8093
  console.log("\u2500".repeat(30));
7450
- for (const archive of archiveResult.data) {
8094
+ for (const archive of result.archived) {
7451
8095
  console.log(` - ${archive}`);
7452
8096
  }
7453
8097
  console.log("");
@@ -7457,111 +8101,192 @@ async function listChanges(options) {
7457
8101
  }
7458
8102
  }
7459
8103
  async function listSpecs() {
7460
- const cwd = process.cwd();
7461
- const specsPath = path14.join(cwd, ".sdd", "specs");
7462
- if (!await fileExists(specsPath)) {
8104
+ const specsPath = path14.join(process.cwd(), ".sdd", "specs");
8105
+ const tree = await getSpecFileTree(specsPath);
8106
+ if (tree.length === 0) {
7463
8107
  logger_exports.warn("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
7464
8108
  return;
7465
8109
  }
7466
8110
  console.log("");
7467
8111
  console.log("\u{1F4C4} \uC2A4\uD399 \uD30C\uC77C \uBAA9\uB85D");
7468
8112
  console.log("\u2500".repeat(50));
7469
- await walkSpecs(specsPath, "");
8113
+ printSpecTree(tree, "");
7470
8114
  console.log("");
7471
8115
  }
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}`);
8116
+ function printSpecTree(items, prefix) {
8117
+ for (const item of items) {
8118
+ if (item.isDirectory) {
8119
+ console.log(`${prefix}\u{1F4C1} ${item.name}/`);
8120
+ if (item.children) {
8121
+ printSpecTree(item.children, prefix + " ");
8122
+ }
8123
+ } else {
8124
+ console.log(`${prefix}\u{1F4C4} ${item.name}`);
7483
8125
  }
7484
8126
  }
7485
8127
  }
7486
8128
  async function listTemplates() {
7487
- const cwd = process.cwd();
7488
- const templatesPath = path14.join(cwd, ".sdd", "templates");
7489
- if (!await fileExists(templatesPath)) {
8129
+ const templates = await getTemplateList(process.cwd());
8130
+ if (templates.length === 0) {
7490
8131
  logger_exports.warn("\uD15C\uD50C\uB9BF \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
7491
8132
  return;
7492
8133
  }
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
8134
  console.log("");
7499
8135
  console.log("\u{1F4D1} \uD15C\uD50C\uB9BF \uBAA9\uB85D");
7500
8136
  console.log("\u2500".repeat(30));
7501
- for (const template of result.data.filter((f) => f.endsWith(".md"))) {
8137
+ for (const template of templates) {
7502
8138
  console.log(` - ${template}`);
7503
8139
  }
7504
8140
  console.log("");
7505
8141
  }
7506
8142
  async function listSummary() {
7507
- const cwd = process.cwd();
7508
- const sddPath = path14.join(cwd, ".sdd");
7509
- if (!await fileExists(sddPath)) {
8143
+ const summary = await getProjectSummary(process.cwd());
8144
+ if (!summary) {
7510
8145
  logger_exports.warn(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. sdd init\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
7511
8146
  return;
7512
8147
  }
7513
8148
  console.log("");
7514
8149
  console.log("\u{1F4CA} SDD \uD504\uB85C\uC81D\uD2B8 \uC694\uC57D");
7515
8150
  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++;
8151
+ console.log(`\u{1F4CB} \uAE30\uB2A5: ${summary.featureCount}\uAC1C`);
8152
+ console.log(`\u{1F4DD} \uB300\uAE30 \uC911\uC778 \uBCC0\uACBD: ${summary.pendingChangeCount}\uAC1C`);
8153
+ console.log(`\u{1F4E6} \uC544\uCE74\uC774\uBE0C\uB41C \uBCC0\uACBD: ${summary.archivedChangeCount}\uAC1C`);
8154
+ console.log("");
8155
+ console.log("\uC0C1\uC138 \uC815\uBCF4:");
8156
+ console.log(" sdd list features - \uAE30\uB2A5 \uBAA9\uB85D");
8157
+ console.log(" sdd list changes - \uBCC0\uACBD \uBAA9\uB85D");
8158
+ console.log(" sdd list specs - \uC2A4\uD399 \uD30C\uC77C \uBAA9\uB85D");
8159
+ console.log(" sdd status - \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC");
8160
+ console.log("");
8161
+ }
8162
+
8163
+ // src/cli/commands/constitution.ts
8164
+ init_fs();
8165
+ init_errors();
8166
+ import path15 from "path";
8167
+ init_types();
8168
+ function determineBumpType(options) {
8169
+ if (options.major) return "major";
8170
+ if (options.minor) return "minor";
8171
+ if (options.patch) return "patch";
8172
+ return null;
8173
+ }
8174
+ async function readConstitution(projectPath) {
8175
+ const constitutionPath = path15.join(projectPath, ".sdd", "constitution.md");
8176
+ if (!await fileExists(constitutionPath)) {
8177
+ 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."));
8178
+ }
8179
+ const contentResult = await readFile(constitutionPath);
8180
+ if (!contentResult.success) {
8181
+ return failure(new Error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
8182
+ }
8183
+ const parseResult = parseConstitution(contentResult.data);
8184
+ if (!parseResult.success) {
8185
+ return failure(new Error(`Constitution \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
8186
+ }
8187
+ return success({
8188
+ content: contentResult.data,
8189
+ parsed: parseResult.data
8190
+ });
8191
+ }
8192
+ function constitutionToJson(constitution) {
8193
+ return {
8194
+ projectName: constitution.projectName,
8195
+ version: constitution.metadata.version,
8196
+ created: constitution.metadata.created,
8197
+ updated: constitution.metadata.updated,
8198
+ principles: constitution.principles,
8199
+ forbidden: constitution.forbidden,
8200
+ techStack: constitution.techStack,
8201
+ qualityStandards: constitution.qualityStandards
8202
+ };
8203
+ }
8204
+ async function executeBump(projectPath, bumpType, message) {
8205
+ const constitutionPath = path15.join(projectPath, ".sdd", "constitution.md");
8206
+ const changelogPath = path15.join(projectPath, ".sdd", "CHANGELOG.md");
8207
+ const readResult = await readConstitution(projectPath);
8208
+ if (!readResult.success) {
8209
+ return failure(readResult.error);
8210
+ }
8211
+ const { content, parsed } = readResult.data;
8212
+ const currentVersion = parsed.metadata.version;
8213
+ const newVersion = bumpVersion(currentVersion, bumpType);
8214
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
8215
+ let updatedContent = content;
8216
+ updatedContent = updatedContent.replace(
8217
+ /^version:\s*.+$/m,
8218
+ `version: ${newVersion}`
8219
+ );
8220
+ if (/^updated:/m.test(updatedContent)) {
8221
+ updatedContent = updatedContent.replace(
8222
+ /^updated:\s*.+$/m,
8223
+ `updated: ${today}`
8224
+ );
8225
+ } else {
8226
+ updatedContent = updatedContent.replace(
8227
+ /^(version:\s*.+)$/m,
8228
+ `$1
8229
+ updated: ${today}`
8230
+ );
8231
+ }
8232
+ await writeFile(constitutionPath, updatedContent);
8233
+ const changeType = bumpType === "major" ? "changed" : bumpType === "minor" ? "added" : "fixed";
8234
+ const changeDescription = message || `Constitution ${bumpType} \uC5C5\uB370\uC774\uD2B8`;
8235
+ const newEntry = createChangelogEntry(
8236
+ currentVersion,
8237
+ bumpType,
8238
+ [{ type: changeType, description: changeDescription }],
8239
+ message
8240
+ );
8241
+ let existingEntries = [];
8242
+ if (await fileExists(changelogPath)) {
8243
+ const changelogContent = await readFile(changelogPath);
8244
+ if (changelogContent.success) {
8245
+ const parsed2 = parseChangelog(changelogContent.data);
8246
+ if (parsed2.success) {
8247
+ existingEntries = parsed2.data;
7524
8248
  }
7525
8249
  }
7526
8250
  }
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`);
7534
- console.log("");
7535
- console.log("\uC0C1\uC138 \uC815\uBCF4:");
7536
- console.log(" sdd list features - \uAE30\uB2A5 \uBAA9\uB85D");
7537
- console.log(" sdd list changes - \uBCC0\uACBD \uBAA9\uB85D");
7538
- console.log(" sdd list specs - \uC2A4\uD399 \uD30C\uC77C \uBAA9\uB85D");
7539
- console.log(" sdd status - \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC");
7540
- console.log("");
8251
+ const allEntries = [newEntry, ...existingEntries];
8252
+ const newChangelog = generateChangelog(allEntries);
8253
+ await writeFile(changelogPath, newChangelog);
8254
+ return success({
8255
+ previousVersion: currentVersion,
8256
+ newVersion,
8257
+ changelogPath
8258
+ });
7541
8259
  }
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";
8260
+ async function executeValidateConstitution(projectPath) {
8261
+ const readResult = await readConstitution(projectPath);
8262
+ if (!readResult.success) {
8263
+ return failure(readResult.error);
7558
8264
  }
8265
+ const validationResult = validateConstitution(readResult.data.parsed);
8266
+ if (!validationResult.success) {
8267
+ return failure(new Error(`Constitution \uAC80\uC99D \uC2E4\uD328: ${validationResult.error.message}`));
8268
+ }
8269
+ return success(readResult.data.parsed);
8270
+ }
8271
+ async function getHistory(projectPath, count = 10) {
8272
+ const changelogPath = path15.join(projectPath, ".sdd", "CHANGELOG.md");
8273
+ if (!await fileExists(changelogPath)) {
8274
+ return success({ entries: [], totalCount: 0 });
8275
+ }
8276
+ const contentResult = await readFile(changelogPath);
8277
+ if (!contentResult.success) {
8278
+ return failure(new Error("CHANGELOG \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
8279
+ }
8280
+ const parseResult = parseChangelog(contentResult.data);
8281
+ if (!parseResult.success) {
8282
+ return failure(new Error(`CHANGELOG \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
8283
+ }
8284
+ const entries = parseResult.data.slice(0, count);
8285
+ return success({
8286
+ entries,
8287
+ totalCount: parseResult.data.length
8288
+ });
7559
8289
  }
7560
-
7561
- // src/cli/commands/constitution.ts
7562
- init_fs();
7563
- init_errors();
7564
- import path15 from "path";
7565
8290
  function registerConstitutionCommand(program2) {
7566
8291
  const constitution = program2.command("constitution").description("Constitution(\uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59) \uAD00\uB9AC");
7567
8292
  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 +8335,14 @@ function registerConstitutionCommand(program2) {
7610
8335
  }
7611
8336
  async function runShow(options) {
7612
8337
  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.");
8338
+ const result = await readConstitution(cwd);
8339
+ if (!result.success) {
8340
+ error(result.error.message);
7621
8341
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
7622
8342
  }
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;
8343
+ const constitution = result.data.parsed;
7629
8344
  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));
8345
+ console.log(JSON.stringify(constitutionToJson(constitution), null, 2));
7640
8346
  return;
7641
8347
  }
7642
8348
  info(`Constitution: ${constitution.projectName}`);
@@ -7678,116 +8384,37 @@ async function runShow(options) {
7678
8384
  }
7679
8385
  async function runVersion() {
7680
8386
  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.");
8387
+ const result = await readConstitution(cwd);
8388
+ if (!result.success) {
8389
+ error(result.error.message);
7689
8390
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
7690
8391
  }
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);
8392
+ console.log(result.data.parsed.metadata.version);
7697
8393
  }
7698
8394
  async function runBump(options) {
7699
8395
  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 {
8396
+ const bumpType = determineBumpType(options);
8397
+ if (!bumpType) {
7714
8398
  error("\uBC84\uC804 \uC720\uD615\uC744 \uC9C0\uC815\uD558\uC138\uC694: --major, --minor, \uB610\uB294 --patch");
7715
8399
  process.exit(ExitCode.GENERAL_ERROR);
7716
8400
  }
7717
- const contentResult = await readFile(constitutionPath);
7718
- if (!contentResult.success) {
7719
- error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
8401
+ const result = await executeBump(cwd, bumpType, options.message);
8402
+ if (!result.success) {
8403
+ error(result.error.message);
7720
8404
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
7721
8405
  }
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
- }
7765
- }
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}`);
8406
+ success2(`Constitution \uBC84\uC804 \uC5C5\uB370\uC774\uD2B8: ${result.data.previousVersion} \u2192 ${result.data.newVersion}`);
8407
+ info(`CHANGELOG \uC5C5\uB370\uC774\uD2B8: ${result.data.changelogPath}`);
7771
8408
  }
7772
8409
  async function runHistory(options) {
7773
8410
  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;
7778
- }
7779
- const contentResult = await readFile(changelogPath);
7780
- if (!contentResult.success) {
7781
- error("CHANGELOG \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
8411
+ const count = parseInt(options.count, 10) || 10;
8412
+ const result = await getHistory(cwd, count);
8413
+ if (!result.success) {
8414
+ error(result.error.message);
7782
8415
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
7783
8416
  }
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
- }
7789
- const count = parseInt(options.count, 10) || 10;
7790
- const entries = parseResult.data.slice(0, count);
8417
+ const { entries } = result.data;
7791
8418
  if (entries.length === 0) {
7792
8419
  info("\uBCC0\uACBD \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
7793
8420
  return;
@@ -7807,36 +8434,22 @@ async function runHistory(options) {
7807
8434
  }
7808
8435
  async function runValidate2() {
7809
8436
  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);
8437
+ const result = await executeValidateConstitution(cwd);
8438
+ if (!result.success) {
8439
+ error(result.error.message);
8440
+ process.exit(ExitCode.VALIDATION_FAILED);
7829
8441
  }
8442
+ const constitution = result.data;
7830
8443
  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}`);
8444
+ info(`\uD504\uB85C\uC81D\uD2B8: ${constitution.projectName}`);
8445
+ info(`\uBC84\uC804: ${constitution.metadata.version}`);
8446
+ info(`\uC6D0\uCE59 \uC218: ${constitution.principles.length}`);
8447
+ info(`\uAE08\uC9C0 \uC0AC\uD56D \uC218: ${constitution.forbidden.length}`);
7835
8448
  }
7836
8449
 
7837
8450
  // src/cli/commands/start.ts
7838
8451
  import path16 from "path";
7839
- import { promises as fs10 } from "fs";
8452
+ import { promises as fs9 } from "fs";
7840
8453
  init_errors();
7841
8454
  init_fs();
7842
8455
  function registerStartCommand(program2) {
@@ -7850,7 +8463,7 @@ function registerStartCommand(program2) {
7850
8463
  });
7851
8464
  }
7852
8465
  async function runStart(options) {
7853
- const projectStatus = await getProjectStatus();
8466
+ const projectStatus = await getProjectStatus2();
7854
8467
  if (!projectStatus.initialized) {
7855
8468
  info("SDD \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
7856
8469
  newline();
@@ -7874,7 +8487,7 @@ async function runStart(options) {
7874
8487
  newline();
7875
8488
  displayWorkflowMenu(projectStatus);
7876
8489
  }
7877
- async function getProjectStatus() {
8490
+ async function getProjectStatus2() {
7878
8491
  const projectRoot = await findSddRoot();
7879
8492
  if (!projectRoot) {
7880
8493
  return {
@@ -7903,7 +8516,7 @@ async function getProjectStatus() {
7903
8516
  const specs = [];
7904
8517
  if (await directoryExists(specsPath)) {
7905
8518
  try {
7906
- const dirs = await fs10.readdir(specsPath);
8519
+ const dirs = await fs9.readdir(specsPath);
7907
8520
  for (const dir of dirs) {
7908
8521
  const specPath = path16.join(specsPath, dir, "spec.md");
7909
8522
  if (await fileExists(specPath)) {
@@ -8140,7 +8753,7 @@ function displayConstitutionGuide() {
8140
8753
 
8141
8754
  // src/cli/commands/migrate.ts
8142
8755
  import path18 from "path";
8143
- import { promises as fs12 } from "fs";
8756
+ import { promises as fs11 } from "fs";
8144
8757
  init_errors();
8145
8758
  init_fs();
8146
8759
  init_new();
@@ -8151,7 +8764,7 @@ init_types();
8151
8764
  init_errors();
8152
8765
  init_fs();
8153
8766
  import path17 from "path";
8154
- import fs11 from "fs/promises";
8767
+ import fs10 from "fs/promises";
8155
8768
  async function detectExternalTools(projectRoot) {
8156
8769
  try {
8157
8770
  const results = [];
@@ -8188,13 +8801,13 @@ async function detectOpenSpec(projectRoot) {
8188
8801
  }
8189
8802
  const specs = [];
8190
8803
  if (hasSpecs) {
8191
- const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
8804
+ const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
8192
8805
  for (const entry of specDirs) {
8193
8806
  if (entry.isDirectory()) {
8194
8807
  const specPath = path17.join(specsPath, entry.name);
8195
8808
  const specFile = path17.join(specPath, "spec.md");
8196
8809
  if (await fileExists(specFile)) {
8197
- const content = await fs11.readFile(specFile, "utf-8");
8810
+ const content = await fs10.readFile(specFile, "utf-8");
8198
8811
  const title2 = extractTitle2(content);
8199
8812
  const status = extractFrontmatterField(content, "status");
8200
8813
  specs.push({
@@ -8229,7 +8842,7 @@ async function detectSpecKit(projectRoot) {
8229
8842
  return null;
8230
8843
  }
8231
8844
  const specs = [];
8232
- const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
8845
+ const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
8233
8846
  for (const entry of specDirs) {
8234
8847
  if (entry.isDirectory()) {
8235
8848
  const specPath = path17.join(specsPath, entry.name);
@@ -8243,7 +8856,7 @@ async function detectSpecKit(projectRoot) {
8243
8856
  let title2;
8244
8857
  let status;
8245
8858
  if (hasSpec) {
8246
- const content = await fs11.readFile(specFile, "utf-8");
8859
+ const content = await fs10.readFile(specFile, "utf-8");
8247
8860
  title2 = extractTitle2(content);
8248
8861
  status = extractFrontmatterField(content, "status");
8249
8862
  }
@@ -8275,13 +8888,13 @@ async function detectSdd(projectRoot) {
8275
8888
  return null;
8276
8889
  }
8277
8890
  const specs = [];
8278
- const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
8891
+ const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
8279
8892
  for (const entry of specDirs) {
8280
8893
  if (entry.isDirectory()) {
8281
8894
  const specPath = path17.join(specsPath, entry.name);
8282
8895
  const specFile = path17.join(specPath, "spec.md");
8283
8896
  if (await fileExists(specFile)) {
8284
- const content = await fs11.readFile(specFile, "utf-8");
8897
+ const content = await fs10.readFile(specFile, "utf-8");
8285
8898
  const title2 = extractTitle2(content);
8286
8899
  const status = extractFrontmatterField(content, "status");
8287
8900
  specs.push({
@@ -8311,7 +8924,7 @@ async function migrateFromOpenSpec(sourcePath, targetPath, options = {}) {
8311
8924
  if (!await directoryExists(specsPath)) {
8312
8925
  return failure(new ChangeError("OpenSpec specs \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
8313
8926
  }
8314
- const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
8927
+ const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
8315
8928
  for (const entry of specDirs) {
8316
8929
  if (!entry.isDirectory()) continue;
8317
8930
  const sourceSpecPath = path17.join(specsPath, entry.name);
@@ -8324,18 +8937,18 @@ async function migrateFromOpenSpec(sourcePath, targetPath, options = {}) {
8324
8937
  }
8325
8938
  try {
8326
8939
  if (!options.dryRun) {
8327
- await fs11.mkdir(targetSpecPath, { recursive: true });
8328
- const files = await fs11.readdir(sourceSpecPath);
8940
+ await fs10.mkdir(targetSpecPath, { recursive: true });
8941
+ const files = await fs10.readdir(sourceSpecPath);
8329
8942
  for (const file of files) {
8330
8943
  const sourceFile = path17.join(sourceSpecPath, file);
8331
8944
  const targetFile = path17.join(targetSpecPath, file);
8332
- const stat = await fs11.stat(sourceFile);
8945
+ const stat = await fs10.stat(sourceFile);
8333
8946
  if (stat.isFile()) {
8334
- let content = await fs11.readFile(sourceFile, "utf-8");
8947
+ let content = await fs10.readFile(sourceFile, "utf-8");
8335
8948
  if (file === "spec.md") {
8336
8949
  content = convertOpenSpecToSdd(content, entry.name);
8337
8950
  }
8338
- await fs11.writeFile(targetFile, content);
8951
+ await fs10.writeFile(targetFile, content);
8339
8952
  }
8340
8953
  }
8341
8954
  }
@@ -8365,7 +8978,7 @@ async function migrateFromSpecKit(sourcePath, targetPath, options = {}) {
8365
8978
  if (!await directoryExists(specsPath)) {
8366
8979
  return failure(new ChangeError("Spec Kit specs \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
8367
8980
  }
8368
- const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
8981
+ const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
8369
8982
  for (const entry of specDirs) {
8370
8983
  if (!entry.isDirectory()) continue;
8371
8984
  const sourceSpecPath = path17.join(specsPath, entry.name);
@@ -8378,18 +8991,18 @@ async function migrateFromSpecKit(sourcePath, targetPath, options = {}) {
8378
8991
  }
8379
8992
  try {
8380
8993
  if (!options.dryRun) {
8381
- await fs11.mkdir(targetSpecPath, { recursive: true });
8382
- const files = await fs11.readdir(sourceSpecPath);
8994
+ await fs10.mkdir(targetSpecPath, { recursive: true });
8995
+ const files = await fs10.readdir(sourceSpecPath);
8383
8996
  for (const file of files) {
8384
8997
  const sourceFile = path17.join(sourceSpecPath, file);
8385
8998
  const targetFile = path17.join(targetSpecPath, file);
8386
- const stat = await fs11.stat(sourceFile);
8999
+ const stat = await fs10.stat(sourceFile);
8387
9000
  if (stat.isFile()) {
8388
- let content = await fs11.readFile(sourceFile, "utf-8");
9001
+ let content = await fs10.readFile(sourceFile, "utf-8");
8389
9002
  if (file === "spec.md") {
8390
9003
  content = convertSpecKitToSdd(content, entry.name);
8391
9004
  }
8392
- await fs11.writeFile(targetFile, content);
9005
+ await fs10.writeFile(targetFile, content);
8393
9006
  }
8394
9007
  }
8395
9008
  }
@@ -8547,7 +9160,7 @@ async function runMigrateDocs(source, options) {
8547
9160
  const sourcePath = path18.resolve(source);
8548
9161
  let files = [];
8549
9162
  try {
8550
- const stat = await fs12.stat(sourcePath);
9163
+ const stat = await fs11.stat(sourcePath);
8551
9164
  if (stat.isDirectory()) {
8552
9165
  files = await collectMarkdownFiles(sourcePath);
8553
9166
  } else if (stat.isFile()) {
@@ -8590,7 +9203,7 @@ async function runMigrateDocs(source, options) {
8590
9203
  }
8591
9204
  async function migrateDocument(filePath, outputDir, dryRun) {
8592
9205
  try {
8593
- const content = await fs12.readFile(filePath, "utf-8");
9206
+ const content = await fs11.readFile(filePath, "utf-8");
8594
9207
  const analysis = analyzeDocument(content);
8595
9208
  const featureId = generateFeatureId(analysis.title || path18.basename(filePath, ".md"));
8596
9209
  const specContent = generateSpec({
@@ -8658,7 +9271,7 @@ async function runAnalyze(file) {
8658
9271
  error(`\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${file}`);
8659
9272
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
8660
9273
  }
8661
- const content = await fs12.readFile(filePath, "utf-8");
9274
+ const content = await fs11.readFile(filePath, "utf-8");
8662
9275
  const analysis = analyzeDocument(content);
8663
9276
  info(`\u{1F4CA} \uBB38\uC11C \uBD84\uC11D: ${path18.basename(file)}`);
8664
9277
  newline();
@@ -8719,7 +9332,7 @@ async function runScan(dir, options) {
8719
9332
  const results = [];
8720
9333
  for (const file of files) {
8721
9334
  try {
8722
- const content = await fs12.readFile(file, "utf-8");
9335
+ const content = await fs11.readFile(file, "utf-8");
8723
9336
  const analysis = analyzeDocument(content);
8724
9337
  results.push({ file, analysis });
8725
9338
  } catch {
@@ -8781,7 +9394,7 @@ async function collectMarkdownFiles(dirPath) {
8781
9394
  async function collectFilesWithExtensions(dirPath, extensions) {
8782
9395
  const files = [];
8783
9396
  async function scan(dir) {
8784
- const entries = await fs12.readdir(dir, { withFileTypes: true });
9397
+ const entries = await fs11.readdir(dir, { withFileTypes: true });
8785
9398
  for (const entry of entries) {
8786
9399
  const fullPath = path18.join(dir, entry.name);
8787
9400
  if (entry.isDirectory()) {
@@ -9294,8 +9907,129 @@ async function runCiCheck(options) {
9294
9907
 
9295
9908
  // src/cli/commands/transition.ts
9296
9909
  import path20 from "path";
9910
+ import { promises as fs12 } from "fs";
9297
9911
  init_errors();
9298
9912
  init_fs();
9913
+ init_types();
9914
+ async function getExistingChangeIds(sddPath) {
9915
+ const changesPath = path20.join(sddPath, "changes");
9916
+ try {
9917
+ const dirs = await fs12.readdir(changesPath);
9918
+ return dirs.filter((d) => d.startsWith("CHG-"));
9919
+ } catch {
9920
+ return [];
9921
+ }
9922
+ }
9923
+ async function extractProposalTitle(proposalPath) {
9924
+ if (!await fileExists(proposalPath)) {
9925
+ return "";
9926
+ }
9927
+ const content = await readFile(proposalPath);
9928
+ if (!content.success) {
9929
+ return "";
9930
+ }
9931
+ const titleMatch = content.data.match(/^#\s+(.+)$/m);
9932
+ return titleMatch ? titleMatch[1] : "";
9933
+ }
9934
+ async function transitionNewToChange(sddPath, specId, options) {
9935
+ const specsPath = path20.join(sddPath, "specs");
9936
+ const specPath = path20.join(specsPath, specId, "spec.md");
9937
+ if (!await fileExists(specPath)) {
9938
+ return failure(new Error(`\uC2A4\uD399\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${specId}`));
9939
+ }
9940
+ const existingIds = await getExistingChangeIds(sddPath);
9941
+ const changeId = generateChangeId(existingIds);
9942
+ const changesPath = path20.join(sddPath, "changes");
9943
+ const changePath = path20.join(changesPath, changeId);
9944
+ await ensureDir(changePath);
9945
+ const title2 = options.title || `${specId} \uAE30\uB2A5 \uD655\uC7A5`;
9946
+ const reason = options.reason || "new \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
9947
+ const proposalContent = generateTransitionProposal(specId, title2, reason, "new-to-change");
9948
+ await writeFile(path20.join(changePath, "proposal.md"), proposalContent);
9949
+ const deltaContent = generateDeltaTemplate2(specId);
9950
+ await writeFile(path20.join(changePath, "delta.md"), deltaContent);
9951
+ const tasksContent = generateTasksTemplate2();
9952
+ await writeFile(path20.join(changePath, "tasks.md"), tasksContent);
9953
+ return success({
9954
+ changeId,
9955
+ changePath,
9956
+ filesCreated: ["proposal.md", "delta.md", "tasks.md"]
9957
+ });
9958
+ }
9959
+ async function transitionChangeToNew(sddPath, changeId, options) {
9960
+ const changePath = path20.join(sddPath, "changes", changeId);
9961
+ if (!await directoryExists(changePath)) {
9962
+ return failure(new Error(`\uBCC0\uACBD \uC81C\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${changeId}`));
9963
+ }
9964
+ const proposalPath = path20.join(changePath, "proposal.md");
9965
+ const extractedTitle = await extractProposalTitle(proposalPath);
9966
+ const featureName = options.name || extractedTitle.toLowerCase().replace(/\s+/g, "-") || `feature-from-${changeId}`;
9967
+ const specsPath = path20.join(sddPath, "specs");
9968
+ const newSpecPath = path20.join(specsPath, featureName);
9969
+ if (await directoryExists(newSpecPath)) {
9970
+ return failure(new Error(`\uC2A4\uD399\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${featureName}`));
9971
+ }
9972
+ await ensureDir(newSpecPath);
9973
+ const reason = options.reason || "change \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
9974
+ const specContent = generateTransitionSpec(featureName, extractedTitle || featureName, reason, changeId);
9975
+ await writeFile(path20.join(newSpecPath, "spec.md"), specContent);
9976
+ const planContent = generatePlanTemplate(featureName);
9977
+ await writeFile(path20.join(newSpecPath, "plan.md"), planContent);
9978
+ const tasksContent = generateTasksTemplate2();
9979
+ await writeFile(path20.join(newSpecPath, "tasks.md"), tasksContent);
9980
+ const statusPath = path20.join(changePath, ".status");
9981
+ await writeFile(statusPath, JSON.stringify({
9982
+ status: "transitioned",
9983
+ transitionedTo: featureName,
9984
+ transitionedAt: (/* @__PURE__ */ new Date()).toISOString(),
9985
+ reason
9986
+ }, null, 2));
9987
+ return success({
9988
+ featureName,
9989
+ featurePath: newSpecPath,
9990
+ filesCreated: ["spec.md", "plan.md", "tasks.md"],
9991
+ originalChangeId: changeId
9992
+ });
9993
+ }
9994
+ function getTransitionGuide() {
9995
+ return `=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658 \uAC00\uC774\uB4DC ===
9996
+
9997
+ ## new \u2192 change \uC804\uD658
9998
+
9999
+ \uC0AC\uC6A9 \uC2DC\uC810:
10000
+ - \uC0C8 \uAE30\uB2A5 \uC791\uC131 \uC911 \uAE30\uC874 \uC2A4\uD399\uACFC \uC911\uBCF5 \uBC1C\uACAC
10001
+ - \uAE30\uC874 \uAE30\uB2A5 \uD655\uC7A5\uC774 \uB354 \uC801\uC808\uD55C \uACBD\uC6B0
10002
+ - \uC758\uC874\uC131 \uBD84\uC11D \uACB0\uACFC \uAE30\uC874 \uC2A4\uD399 \uC218\uC815 \uD544\uC694
10003
+
10004
+ \uBA85\uB839\uC5B4:
10005
+ sdd transition new-to-change <spec-id>
10006
+ -t, --title <title> : \uBCC0\uACBD \uC81C\uC548 \uC81C\uBAA9
10007
+ -r, --reason <reason>: \uC804\uD658 \uC0AC\uC720
10008
+
10009
+ ## change \u2192 new \uC804\uD658
10010
+
10011
+ \uC0AC\uC6A9 \uC2DC\uC810:
10012
+ - \uBCC0\uACBD \uBC94\uC704\uAC00 \uB108\uBB34 \uCEE4\uC11C \uBCC4\uB3C4 \uAE30\uB2A5\uC73C\uB85C \uBD84\uB9AC \uD544\uC694
10013
+ - \uAE30\uC874 \uC2A4\uD399\uACFC \uB3C5\uB9BD\uC801\uC778 \uC0C8 \uAE30\uB2A5\uC73C\uB85C \uBC1C\uC804
10014
+ - \uC601\uD5A5\uB3C4 \uBD84\uC11D \uACB0\uACFC \uBD84\uB9AC\uAC00 \uC548\uC804
10015
+
10016
+ \uBA85\uB839\uC5B4:
10017
+ sdd transition change-to-new <change-id>
10018
+ -n, --name <name> : \uC0C8 \uAE30\uB2A5 \uC774\uB984
10019
+ -r, --reason <reason>: \uC804\uD658 \uC0AC\uC720
10020
+
10021
+ ## \uC804\uD658 \uD310\uB2E8 \uAE30\uC900
10022
+
10023
+ new \u2192 change:
10024
+ - \uC601\uD5A5\uBC1B\uB294 \uC2A4\uD399 \uC218 \u2264 3\uAC1C
10025
+ - \uBCC0\uACBD\uC774 \uAE30\uC874 \uAE30\uB2A5\uC758 \uC790\uC5F0\uC2A4\uB7EC\uC6B4 \uD655\uC7A5
10026
+ - \uC0C8 \uC2DC\uB098\uB9AC\uC624 \uCD94\uAC00\uBCF4\uB2E4 \uAE30\uC874 \uC2DC\uB098\uB9AC\uC624 \uC218\uC815 \uC911\uC2EC
10027
+
10028
+ change \u2192 new:
10029
+ - \uC601\uD5A5\uBC1B\uB294 \uC2A4\uD399 \uC218 > 3\uAC1C
10030
+ - \uC0C8\uB85C\uC6B4 \uAC1C\uB150/\uB3C4\uBA54\uC778 \uB3C4\uC785
10031
+ - \uAE30\uC874 \uC2A4\uD399\uACFC \uB3C5\uB9BD\uC801\uC73C\uB85C \uD14C\uC2A4\uD2B8 \uAC00\uB2A5`;
10032
+ }
9299
10033
  function registerTransitionCommand(program2) {
9300
10034
  const transition = program2.command("transition").description("\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uAC04 \uC804\uD658\uC744 \uC218\uD589\uD569\uB2C8\uB2E4");
9301
10035
  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 +10059,16 @@ async function runNewToChange(specId, options) {
9325
10059
  process.exit(ExitCode.GENERAL_ERROR);
9326
10060
  }
9327
10061
  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
10062
  info("=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658: new \u2192 change ===");
9336
10063
  newline();
9337
10064
  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.");
10065
+ const result = await transitionNewToChange(sddPath, specId, options);
10066
+ if (!result.success) {
10067
+ error(result.error.message);
10068
+ info("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC2A4\uD399 \uBAA9\uB85D\uC740 `sdd list`\uB85C \uD655\uC778\uD558\uC138\uC694.");
9344
10069
  process.exit(ExitCode.GENERAL_ERROR);
9345
10070
  }
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);
10071
+ const { changeId } = result.data;
9354
10072
  newline();
9355
10073
  success2(`\uC804\uD658 \uC644\uB8CC! \uBCC0\uACBD \uC81C\uC548\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
9356
10074
  newline();
@@ -9368,60 +10086,31 @@ async function runNewToChange(specId, options) {
9368
10086
  }
9369
10087
  async function runChangeToNew(changeId, options) {
9370
10088
  const projectRoot = await findSddRoot();
9371
- if (!projectRoot) {
9372
- error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
9373
- process.exit(ExitCode.GENERAL_ERROR);
9374
- }
9375
- 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.");
10089
+ if (!projectRoot) {
10090
+ error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
9380
10091
  process.exit(ExitCode.GENERAL_ERROR);
9381
10092
  }
10093
+ const sddPath = path20.join(projectRoot, ".sdd");
9382
10094
  info("=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658: change \u2192 new ===");
9383
10095
  newline();
9384
10096
  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
- }
10097
+ const result = await transitionChangeToNew(sddPath, changeId, options);
10098
+ if (!result.success) {
10099
+ error(result.error.message);
10100
+ if (result.error.message.includes("\uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4")) {
10101
+ info("\uC9C4\uD589 \uC911\uC778 \uBCC0\uACBD \uBAA9\uB85D\uC740 `sdd change -l`\uB85C \uD655\uC778\uD558\uC138\uC694.");
10102
+ } else if (result.error.message.includes("\uC774\uBBF8 \uC874\uC7AC")) {
10103
+ info("\uB2E4\uB978 \uC774\uB984\uC744 \uC9C0\uC815\uD558\uC138\uC694: --name <name>");
9394
10104
  }
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
10105
  process.exit(ExitCode.GENERAL_ERROR);
9403
10106
  }
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));
10107
+ const { featureName, originalChangeId } = result.data;
9419
10108
  newline();
9420
10109
  success2(`\uC804\uD658 \uC644\uB8CC! \uC0C8 \uAE30\uB2A5 \uC2A4\uD399\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
9421
10110
  newline();
9422
10111
  info(`\uAE30\uB2A5 \uC774\uB984: ${featureName}`);
9423
10112
  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.`);
10113
+ info(`\uC6D0\uBCF8 \uBCC0\uACBD ${originalChangeId}\uC740 'transitioned' \uC0C1\uD0DC\uB85C \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
9425
10114
  newline();
9426
10115
  info("\uB2E4\uC74C \uB2E8\uACC4:");
9427
10116
  listItem(`1. .sdd/specs/${featureName}/spec.md \uD3B8\uC9D1`);
@@ -9433,43 +10122,7 @@ async function runChangeToNew(changeId, options) {
9433
10122
  listItem("/sdd.new - \uBA85\uC138 \uC791\uC131 \uB3C4\uC6C0");
9434
10123
  }
9435
10124
  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");
10125
+ console.log(getTransitionGuide());
9473
10126
  }
9474
10127
  function generateTransitionProposal(specId, title2, reason, direction) {
9475
10128
  const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -9511,7 +10164,7 @@ transition_reason: "${reason}"
9511
10164
  <!-- \uCD94\uAC00 \uCC38\uACE0 \uC815\uBCF4\uAC00 \uC788\uB2E4\uBA74 \uC791\uC131\uD558\uC138\uC694 -->
9512
10165
  `;
9513
10166
  }
9514
- function generateDeltaTemplate(specId) {
10167
+ function generateDeltaTemplate2(specId) {
9515
10168
  return `---
9516
10169
  target: ${specId}
9517
10170
  ---
@@ -9622,7 +10275,7 @@ status: draft
9622
10275
  <!-- \uAD6C\uD604 \uC2DC \uACE0\uB824\uD560 \uC704\uD5D8 \uC694\uC18C -->
9623
10276
  `;
9624
10277
  }
9625
- function generateTasksTemplate() {
10278
+ function generateTasksTemplate2() {
9626
10279
  return `---
9627
10280
  status: pending
9628
10281
  ---
@@ -9965,8 +10618,8 @@ function scoreDependencies(spec) {
9965
10618
  const details = [];
9966
10619
  const suggestions = [];
9967
10620
  let score = 0;
9968
- if (spec.frontmatter.depends) {
9969
- const deps = Array.isArray(spec.frontmatter.depends) ? spec.frontmatter.depends : [spec.frontmatter.depends];
10621
+ if (spec.metadata.depends) {
10622
+ const deps = Array.isArray(spec.metadata.depends) ? spec.metadata.depends : [spec.metadata.depends];
9970
10623
  if (deps.length > 0 && deps[0] !== null) {
9971
10624
  score = maxScore;
9972
10625
  details.push(`\uC758\uC874\uC131: ${deps.join(", ")}`);
@@ -9975,8 +10628,8 @@ function scoreDependencies(spec) {
9975
10628
  details.push("\uC758\uC874\uC131 \uC5C6\uC74C (\uBA85\uC2DC\uC801 \uC120\uC5B8)");
9976
10629
  }
9977
10630
  } 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");
10631
+ score = 5;
10632
+ details.push("\uC758\uC874\uC131 \uD544\uB4DC \uC5C6\uC74C (\uC554\uC2DC\uC801 \uC5C6\uC74C)");
9980
10633
  }
9981
10634
  return {
9982
10635
  name: "\uC758\uC874\uC131 \uBA85\uC2DC",
@@ -10041,9 +10694,9 @@ function scoreConstitution(spec, hasConstitution) {
10041
10694
  if (!hasConstitution) {
10042
10695
  score = maxScore;
10043
10696
  details.push("Constitution \uBBF8\uC124\uC815 (\uAC80\uC0AC \uC0DD\uB7B5)");
10044
- } else if (spec.frontmatter.constitution_version) {
10697
+ } else if (spec.metadata.constitution_version) {
10045
10698
  score = maxScore;
10046
- details.push(`Constitution \uBC84\uC804: ${spec.frontmatter.constitution_version}`);
10699
+ details.push(`Constitution \uBC84\uC804: ${spec.metadata.constitution_version}`);
10047
10700
  } else {
10048
10701
  details.push("constitution_version \uD544\uB4DC \uC5C6\uC74C");
10049
10702
  suggestions.push("frontmatter\uC5D0 constitution_version\uC744 \uCD94\uAC00\uD558\uC138\uC694");
@@ -10089,20 +10742,20 @@ function scoreMetadata(spec) {
10089
10742
  let score = 0;
10090
10743
  const missingRequired = [];
10091
10744
  for (const field of requiredFields) {
10092
- if (spec.frontmatter[field]) {
10745
+ if (spec.metadata[field]) {
10093
10746
  score += 2;
10094
10747
  } else {
10095
10748
  missingRequired.push(field);
10096
10749
  }
10097
10750
  }
10098
10751
  for (const field of optionalFields) {
10099
- if (spec.frontmatter[field]) {
10752
+ if (spec.metadata[field]) {
10100
10753
  score += 1;
10101
10754
  }
10102
10755
  }
10103
10756
  score = Math.min(maxScore, score);
10104
- const presentFields = Object.keys(spec.frontmatter).filter(
10105
- (k) => spec.frontmatter[k] !== null && spec.frontmatter[k] !== void 0
10757
+ const presentFields = Object.keys(spec.metadata).filter(
10758
+ (k) => spec.metadata[k] !== null && spec.metadata[k] !== void 0
10106
10759
  );
10107
10760
  details.push(`\uBA54\uD0C0\uB370\uC774\uD130 \uD544\uB4DC: ${presentFields.length}\uAC1C`);
10108
10761
  if (missingRequired.length > 0) {
@@ -10149,7 +10802,7 @@ async function analyzeSpecQuality(specPath, sddPath) {
10149
10802
  const percentage = Math.round(totalScore / maxScore * 100);
10150
10803
  const grade = getGrade(percentage);
10151
10804
  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));
10805
+ const specId = spec.metadata.id || path23.basename(path23.dirname(specPath));
10153
10806
  const summary = `\uC2A4\uD399 '${specId}'\uC758 \uD488\uC9C8 \uC810\uC218: ${totalScore}/${maxScore} (${percentage}%, \uB4F1\uAE09: ${grade})`;
10154
10807
  return success({
10155
10808
  specId,
@@ -10279,6 +10932,36 @@ function formatProjectQualityResult(result) {
10279
10932
  // src/cli/commands/quality.ts
10280
10933
  init_fs();
10281
10934
  init_errors();
10935
+ init_types();
10936
+ async function executeQuality(feature, options, projectRoot) {
10937
+ const sddPath = path24.join(projectRoot, ".sdd");
10938
+ const minScore = parseInt(options.minScore || "0", 10);
10939
+ if (options.all || !feature) {
10940
+ const result2 = await analyzeProjectQuality(sddPath);
10941
+ if (!result2.success) {
10942
+ return failure(result2.error);
10943
+ }
10944
+ const formatted2 = options.json ? JSON.stringify(result2.data, null, 2) : formatProjectQualityResult(result2.data);
10945
+ return success({
10946
+ type: "project",
10947
+ data: result2.data,
10948
+ formatted: formatted2,
10949
+ passed: result2.data.averagePercentage >= minScore
10950
+ });
10951
+ }
10952
+ const specPath = path24.join(sddPath, "specs", feature, "spec.md");
10953
+ const result = await analyzeSpecQuality(specPath, sddPath);
10954
+ if (!result.success) {
10955
+ return failure(result.error);
10956
+ }
10957
+ const formatted = options.json ? JSON.stringify(result.data, null, 2) : formatQualityResult(result.data);
10958
+ return success({
10959
+ type: "spec",
10960
+ data: result.data,
10961
+ formatted,
10962
+ passed: result.data.percentage >= minScore
10963
+ });
10964
+ }
10282
10965
  function registerQualityCommand(program2) {
10283
10966
  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
10967
  try {
@@ -10295,41 +10978,17 @@ async function runQuality(feature, options) {
10295
10978
  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
10979
  process.exit(ExitCode.GENERAL_ERROR);
10297
10980
  }
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);
10981
+ const result = await executeQuality(feature, options, projectRoot);
10320
10982
  if (!result.success) {
10321
10983
  error(result.error.message);
10322
10984
  process.exit(ExitCode.GENERAL_ERROR);
10323
10985
  }
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) {
10986
+ console.log(result.data.formatted);
10987
+ if (!result.data.passed) {
10988
+ const minScore = parseInt(options.minScore || "0", 10);
10330
10989
  newline();
10331
10990
  error(`\uD488\uC9C8 \uC810\uC218\uAC00 \uCD5C\uC18C \uAE30\uC900(${minScore}%) \uBBF8\uB2EC\uC785\uB2C8\uB2E4.`);
10332
- process.exit(ExitCode.VALIDATION_ERROR);
10991
+ process.exit(ExitCode.VALIDATION_FAILED);
10333
10992
  }
10334
10993
  }
10335
10994
 
@@ -10498,7 +11157,7 @@ function renderHtmlReport(data) {
10498
11157
  <td>${spec.description || "-"}</td>
10499
11158
  </tr>
10500
11159
  `).join("");
10501
- const qualityRows = data.quality?.results.map((q) => `
11160
+ const qualityRows = data.quality?.specResults?.map((q) => `
10502
11161
  <tr>
10503
11162
  <td>${q.specId}</td>
10504
11163
  <td>${q.percentage}%</td>
@@ -10506,7 +11165,7 @@ function renderHtmlReport(data) {
10506
11165
  <td>${q.totalScore}/${q.maxScore}</td>
10507
11166
  </tr>
10508
11167
  `).join("") || "";
10509
- const validationRows = data.validation?.results.map((v) => `
11168
+ const validationRows = data.validation?.files?.map((v) => `
10510
11169
  <tr>
10511
11170
  <td>${v.file}</td>
10512
11171
  <td style="color:${v.errors.length > 0 ? "#ef4444" : "#22c55e"};">${v.errors.length > 0 ? "\u274C \uC2E4\uD328" : "\u2705 \uD1B5\uACFC"}</td>
@@ -10676,11 +11335,11 @@ function renderMarkdownReport(data) {
10676
11335
  if (data.quality) {
10677
11336
  lines.push("## \uD488\uC9C8 \uBD84\uC11D");
10678
11337
  lines.push("");
10679
- lines.push(`\uD3C9\uADE0 \uC810\uC218: **${data.quality.averagePercentage.toFixed(1)}%** (${data.quality.averageGrade})`);
11338
+ lines.push(`\uD3C9\uADE0 \uC810\uC218: **${data.quality.averagePercentage.toFixed(1)}%** (${data.quality.grade})`);
10680
11339
  lines.push("");
10681
11340
  lines.push("| \uC2A4\uD399 ID | \uC810\uC218 | \uB4F1\uAE09 |");
10682
11341
  lines.push("|---------|------|------|");
10683
- for (const q of data.quality.results) {
11342
+ for (const q of data.quality.specResults || []) {
10684
11343
  lines.push(`| ${q.specId} | ${q.percentage}% | ${q.grade} |`);
10685
11344
  }
10686
11345
  lines.push("");
@@ -10688,14 +11347,14 @@ function renderMarkdownReport(data) {
10688
11347
  if (data.validation) {
10689
11348
  lines.push("## \uAC80\uC99D \uACB0\uACFC");
10690
11349
  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`);
11350
+ lines.push(`- \uD1B5\uACFC: ${data.validation.passed}\uAC1C`);
11351
+ lines.push(`- \uC2E4\uD328: ${data.validation.failed}\uAC1C`);
11352
+ lines.push(`- \uACBD\uACE0: ${data.validation.warnings}\uAC1C`);
10694
11353
  lines.push("");
10695
- if (data.validation.errorCount > 0 || data.validation.warningCount > 0) {
11354
+ if (data.validation.failed > 0 || data.validation.warnings > 0) {
10696
11355
  lines.push("### \uC0C1\uC138 \uACB0\uACFC");
10697
11356
  lines.push("");
10698
- for (const v of data.validation.results) {
11357
+ for (const v of data.validation.files || []) {
10699
11358
  if (v.errors.length > 0 || v.warnings.length > 0) {
10700
11359
  lines.push(`#### ${v.file}`);
10701
11360
  for (const e of v.errors) {
@@ -10717,6 +11376,41 @@ function renderMarkdownReport(data) {
10717
11376
  // src/cli/commands/report.ts
10718
11377
  init_fs();
10719
11378
  init_errors();
11379
+ init_types();
11380
+ function isValidReportFormat(format) {
11381
+ return ["html", "markdown", "json"].includes(format);
11382
+ }
11383
+ function resolveOutputPath(format, output, projectRoot) {
11384
+ if (!output) {
11385
+ const ext = format === "markdown" ? "md" : format;
11386
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
11387
+ return path26.join(projectRoot, `sdd-report-${timestamp}.${ext}`);
11388
+ }
11389
+ return path26.isAbsolute(output) ? output : path26.join(projectRoot, output);
11390
+ }
11391
+ async function executeReport(options, projectRoot) {
11392
+ const sddPath = path26.join(projectRoot, ".sdd");
11393
+ const format = options.format || "html";
11394
+ if (!isValidReportFormat(format)) {
11395
+ return failure(new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD\uC785\uB2C8\uB2E4: ${format}. \uC9C0\uC6D0 \uD615\uC2DD: html, markdown, json`));
11396
+ }
11397
+ const outputPath = resolveOutputPath(format, options.output, projectRoot);
11398
+ const result = await generateReport(sddPath, {
11399
+ format,
11400
+ outputPath,
11401
+ title: options.title,
11402
+ includeQuality: options.quality !== false,
11403
+ includeValidation: options.validation !== false
11404
+ });
11405
+ if (!result.success) {
11406
+ return failure(result.error);
11407
+ }
11408
+ return success({
11409
+ format,
11410
+ outputPath: result.data.outputPath || outputPath,
11411
+ content: result.data.content
11412
+ });
11413
+ }
10720
11414
  function registerReportCommand(program2) {
10721
11415
  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
11416
  try {
@@ -10733,33 +11427,13 @@ async function runReport(options) {
10733
11427
  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
11428
  process.exit(ExitCode.GENERAL_ERROR);
10735
11429
  }
10736
- const sddPath = path26.join(projectRoot, ".sdd");
10737
11430
  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
11431
  info("\u{1F4CA} \uB9AC\uD3EC\uD2B8 \uC0DD\uC131 \uC911...");
10752
11432
  info(` \uD615\uC2DD: ${format}`);
10753
11433
  info(` \uD488\uC9C8 \uBD84\uC11D: ${options.quality !== false ? "\uD3EC\uD568" : "\uC81C\uC678"}`);
10754
11434
  info(` \uAC80\uC99D \uACB0\uACFC: ${options.validation !== false ? "\uD3EC\uD568" : "\uC81C\uC678"}`);
10755
11435
  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
- });
11436
+ const result = await executeReport(options, projectRoot);
10763
11437
  if (!result.success) {
10764
11438
  error(result.error.message);
10765
11439
  process.exit(ExitCode.GENERAL_ERROR);
@@ -10772,6 +11446,395 @@ async function runReport(options) {
10772
11446
  }
10773
11447
  }
10774
11448
 
11449
+ // src/cli/commands/search.ts
11450
+ import path28 from "path";
11451
+ init_fs();
11452
+
11453
+ // src/core/search/searcher.ts
11454
+ init_types();
11455
+ init_errors();
11456
+ init_fs();
11457
+ import path27 from "path";
11458
+ import { promises as fs15 } from "fs";
11459
+ async function searchSpecs(sddPath, options = {}) {
11460
+ const startTime = Date.now();
11461
+ try {
11462
+ const specsPath = path27.join(sddPath, "specs");
11463
+ if (!await directoryExists(specsPath)) {
11464
+ return failure(new ChangeError("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
11465
+ }
11466
+ const indexResult = await buildSearchIndex(specsPath);
11467
+ if (!indexResult.success) {
11468
+ return failure(indexResult.error);
11469
+ }
11470
+ const index = indexResult.data;
11471
+ let results = filterByOptions(index, options);
11472
+ if (options.query) {
11473
+ results = searchByQuery(results, options.query, options);
11474
+ } else {
11475
+ results = results.map((item) => ({ ...item, score: 100, matches: [] }));
11476
+ }
11477
+ results = sortResults(results, options);
11478
+ if (options.limit && options.limit > 0) {
11479
+ results = results.slice(0, options.limit);
11480
+ }
11481
+ const duration = Date.now() - startTime;
11482
+ return success({
11483
+ query: options.query || "*",
11484
+ options,
11485
+ totalCount: results.length,
11486
+ items: results,
11487
+ duration
11488
+ });
11489
+ } catch (error2) {
11490
+ return failure(
11491
+ new ChangeError(
11492
+ `\uAC80\uC0C9 \uC2E4\uD328: ${error2 instanceof Error ? error2.message : String(error2)}`
11493
+ )
11494
+ );
11495
+ }
11496
+ }
11497
+ async function buildSearchIndex(specsPath) {
11498
+ try {
11499
+ const index = [];
11500
+ await collectSpecs(specsPath, specsPath, index);
11501
+ return success(index);
11502
+ } catch (error2) {
11503
+ return failure(
11504
+ new ChangeError(
11505
+ `\uC778\uB371\uC2A4 \uAD6C\uCD95 \uC2E4\uD328: ${error2 instanceof Error ? error2.message : String(error2)}`
11506
+ )
11507
+ );
11508
+ }
11509
+ }
11510
+ async function collectSpecs(basePath, currentPath, index) {
11511
+ const entries = await fs15.readdir(currentPath, { withFileTypes: true });
11512
+ for (const entry of entries) {
11513
+ const fullPath = path27.join(currentPath, entry.name);
11514
+ if (entry.isDirectory()) {
11515
+ await collectSpecs(basePath, fullPath, index);
11516
+ } else if (entry.name === "spec.md") {
11517
+ const content = await fs15.readFile(fullPath, "utf-8");
11518
+ const relativePath = path27.relative(basePath, fullPath);
11519
+ const specId = path27.dirname(relativePath);
11520
+ const metadata = parseMetadata(content);
11521
+ const stat = await fs15.stat(fullPath);
11522
+ index.push({
11523
+ id: specId === "." ? entry.name : specId,
11524
+ path: relativePath,
11525
+ title: metadata.title || specId,
11526
+ content,
11527
+ status: metadata.status || "unknown",
11528
+ phase: metadata.phase || "unknown",
11529
+ author: metadata.author || "",
11530
+ created: metadata.created || "",
11531
+ updated: metadata.updated || stat.mtime.toISOString().split("T")[0],
11532
+ depends: parseDependencies(metadata.depends),
11533
+ tags: parseTags(metadata.tags)
11534
+ });
11535
+ }
11536
+ }
11537
+ }
11538
+ function parseMetadata(content) {
11539
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
11540
+ if (!match) return {};
11541
+ const metadata = {};
11542
+ const lines = match[1].split("\n");
11543
+ for (const line of lines) {
11544
+ const kvMatch = line.match(/^(\w+):\s*(.*)$/);
11545
+ if (kvMatch) {
11546
+ let value = kvMatch[2].trim();
11547
+ if (value.startsWith('"') && value.endsWith('"')) {
11548
+ value = value.slice(1, -1);
11549
+ }
11550
+ if (value === "null" || value === "~") {
11551
+ value = null;
11552
+ }
11553
+ metadata[kvMatch[1]] = value;
11554
+ }
11555
+ }
11556
+ return metadata;
11557
+ }
11558
+ function parseDependencies(depends) {
11559
+ if (!depends) return [];
11560
+ if (Array.isArray(depends)) return depends.filter(Boolean);
11561
+ if (typeof depends === "string" && depends !== "null") {
11562
+ return depends.split(",").map((d) => d.trim()).filter(Boolean);
11563
+ }
11564
+ return [];
11565
+ }
11566
+ function parseTags(tags) {
11567
+ if (!tags) return [];
11568
+ if (Array.isArray(tags)) return tags.filter(Boolean);
11569
+ if (typeof tags === "string") {
11570
+ return tags.split(",").map((t) => t.trim()).filter(Boolean);
11571
+ }
11572
+ return [];
11573
+ }
11574
+ function filterByOptions(index, options) {
11575
+ return index.filter((item) => {
11576
+ if (options.status) {
11577
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
11578
+ if (!statuses.includes(item.status)) return false;
11579
+ }
11580
+ if (options.phase) {
11581
+ const phases = Array.isArray(options.phase) ? options.phase : [options.phase];
11582
+ if (!phases.includes(item.phase)) return false;
11583
+ }
11584
+ if (options.author) {
11585
+ if (!item.author.toLowerCase().includes(options.author.toLowerCase())) {
11586
+ return false;
11587
+ }
11588
+ }
11589
+ if (options.createdAfter && item.created) {
11590
+ if (item.created < options.createdAfter) return false;
11591
+ }
11592
+ if (options.createdBefore && item.created) {
11593
+ if (item.created > options.createdBefore) return false;
11594
+ }
11595
+ if (options.updatedAfter && item.updated) {
11596
+ if (item.updated < options.updatedAfter) return false;
11597
+ }
11598
+ if (options.updatedBefore && item.updated) {
11599
+ if (item.updated > options.updatedBefore) return false;
11600
+ }
11601
+ if (options.dependsOn) {
11602
+ if (!item.depends.includes(options.dependsOn)) return false;
11603
+ }
11604
+ if (options.tags && options.tags.length > 0) {
11605
+ const hasTag = options.tags.some(
11606
+ (tag) => item.tags.some((t) => t.toLowerCase() === tag.toLowerCase())
11607
+ );
11608
+ if (!hasTag) return false;
11609
+ }
11610
+ return true;
11611
+ });
11612
+ }
11613
+ function searchByQuery(items, query, options) {
11614
+ const results = [];
11615
+ let pattern;
11616
+ try {
11617
+ if (options.regex) {
11618
+ pattern = new RegExp(query, options.caseSensitive ? "g" : "gi");
11619
+ } else {
11620
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11621
+ pattern = new RegExp(escaped, options.caseSensitive ? "g" : "gi");
11622
+ }
11623
+ } catch {
11624
+ const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11625
+ pattern = new RegExp(escaped, "gi");
11626
+ }
11627
+ for (const item of items) {
11628
+ const matches = [];
11629
+ let score = 0;
11630
+ if (pattern.test(item.title)) {
11631
+ score += 50;
11632
+ pattern.lastIndex = 0;
11633
+ }
11634
+ if (pattern.test(item.id)) {
11635
+ score += 30;
11636
+ pattern.lastIndex = 0;
11637
+ }
11638
+ const lines = item.content.split("\n");
11639
+ for (let i = 0; i < lines.length; i++) {
11640
+ const line = lines[i];
11641
+ pattern.lastIndex = 0;
11642
+ if (pattern.test(line)) {
11643
+ score += 10;
11644
+ const highlighted = line.replace(
11645
+ pattern,
11646
+ (match) => `**${match}**`
11647
+ );
11648
+ matches.push({
11649
+ line: i + 1,
11650
+ content: highlighted.trim(),
11651
+ original: line.trim()
11652
+ });
11653
+ }
11654
+ }
11655
+ if (score > 0) {
11656
+ results.push({
11657
+ id: item.id,
11658
+ path: item.path,
11659
+ title: item.title,
11660
+ status: item.status,
11661
+ phase: item.phase,
11662
+ author: item.author || void 0,
11663
+ created: item.created || void 0,
11664
+ updated: item.updated || void 0,
11665
+ depends: item.depends.length > 0 ? item.depends : void 0,
11666
+ tags: item.tags.length > 0 ? item.tags : void 0,
11667
+ score: Math.min(100, score),
11668
+ matches: matches.slice(0, 5)
11669
+ // 최대 5개 매칭 컨텍스트
11670
+ });
11671
+ }
11672
+ }
11673
+ return results;
11674
+ }
11675
+ function sortResults(results, options) {
11676
+ const sortBy = options.sortBy || "relevance";
11677
+ const sortOrder = options.sortOrder || "desc";
11678
+ return [...results].sort((a, b) => {
11679
+ let comparison = 0;
11680
+ switch (sortBy) {
11681
+ case "relevance":
11682
+ comparison = a.score - b.score;
11683
+ break;
11684
+ case "created":
11685
+ comparison = (a.created || "").localeCompare(b.created || "");
11686
+ break;
11687
+ case "updated":
11688
+ comparison = (a.updated || "").localeCompare(b.updated || "");
11689
+ break;
11690
+ case "title":
11691
+ comparison = (a.title || "").localeCompare(b.title || "");
11692
+ break;
11693
+ case "status":
11694
+ comparison = (a.status || "").localeCompare(b.status || "");
11695
+ break;
11696
+ default:
11697
+ comparison = a.score - b.score;
11698
+ }
11699
+ return sortOrder === "asc" ? comparison : -comparison;
11700
+ });
11701
+ }
11702
+ function formatSearchResult(result) {
11703
+ const lines = [];
11704
+ lines.push(`\u{1F50D} \uAC80\uC0C9 \uACB0\uACFC: "${result.query}"`);
11705
+ lines.push(` ${result.totalCount}\uAC1C \uBC1C\uACAC (${result.duration}ms)`);
11706
+ lines.push("");
11707
+ if (result.items.length === 0) {
11708
+ lines.push(" \uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
11709
+ return lines.join("\n");
11710
+ }
11711
+ for (const item of result.items) {
11712
+ const statusIcon = getStatusIcon2(item.status);
11713
+ const scoreBar = getScoreBar(item.score);
11714
+ lines.push(`${statusIcon} ${item.id}`);
11715
+ if (item.title && item.title !== item.id) {
11716
+ lines.push(` \uC81C\uBAA9: ${item.title}`);
11717
+ }
11718
+ lines.push(` \uC0C1\uD0DC: ${item.status || "unknown"} | Phase: ${item.phase || "unknown"} | \uC810\uC218: ${scoreBar}`);
11719
+ if (item.matches && item.matches.length > 0) {
11720
+ lines.push(" \uB9E4\uCE6D:");
11721
+ for (const match of item.matches.slice(0, 3)) {
11722
+ const truncated = match.content.length > 60 ? match.content.slice(0, 60) + "..." : match.content;
11723
+ lines.push(` L${match.line}: ${truncated}`);
11724
+ }
11725
+ }
11726
+ lines.push("");
11727
+ }
11728
+ return lines.join("\n");
11729
+ }
11730
+ function getStatusIcon2(status) {
11731
+ switch (status) {
11732
+ case "draft":
11733
+ return "\u{1F4DD}";
11734
+ case "review":
11735
+ return "\u{1F440}";
11736
+ case "approved":
11737
+ return "\u2705";
11738
+ case "implemented":
11739
+ return "\u{1F680}";
11740
+ case "deprecated":
11741
+ return "\u26A0\uFE0F";
11742
+ default:
11743
+ return "\u{1F4C4}";
11744
+ }
11745
+ }
11746
+ function getScoreBar(score) {
11747
+ const filled = Math.round(score / 10);
11748
+ const empty = 10 - filled;
11749
+ return "\u2588".repeat(filled) + "\u2591".repeat(empty) + ` ${score}%`;
11750
+ }
11751
+ function formatSearchResultJson(result) {
11752
+ return JSON.stringify(result, null, 2);
11753
+ }
11754
+
11755
+ // src/cli/commands/search.ts
11756
+ function parseSearchCliOptions(query, options) {
11757
+ const searchOptions = {};
11758
+ if (query) {
11759
+ searchOptions.query = query;
11760
+ }
11761
+ if (options.status) {
11762
+ searchOptions.status = options.status.includes(",") ? options.status.split(",").map((s) => s.trim()) : options.status;
11763
+ }
11764
+ if (options.phase) {
11765
+ searchOptions.phase = options.phase.includes(",") ? options.phase.split(",").map((p) => p.trim()) : options.phase;
11766
+ }
11767
+ if (options.author) {
11768
+ searchOptions.author = options.author;
11769
+ }
11770
+ if (options.tags) {
11771
+ searchOptions.tags = options.tags.split(",").map((t) => t.trim());
11772
+ }
11773
+ if (options.createdAfter) {
11774
+ searchOptions.createdAfter = options.createdAfter;
11775
+ }
11776
+ if (options.createdBefore) {
11777
+ searchOptions.createdBefore = options.createdBefore;
11778
+ }
11779
+ if (options.updatedAfter) {
11780
+ searchOptions.updatedAfter = options.updatedAfter;
11781
+ }
11782
+ if (options.updatedBefore) {
11783
+ searchOptions.updatedBefore = options.updatedBefore;
11784
+ }
11785
+ if (options.dependsOn) {
11786
+ searchOptions.dependsOn = options.dependsOn;
11787
+ }
11788
+ if (options.limit) {
11789
+ const limit = parseInt(options.limit, 10);
11790
+ if (!isNaN(limit) && limit > 0) {
11791
+ searchOptions.limit = limit;
11792
+ }
11793
+ }
11794
+ if (options.sortBy) {
11795
+ const validSortBy = ["relevance", "created", "updated", "title", "status"];
11796
+ if (validSortBy.includes(options.sortBy)) {
11797
+ searchOptions.sortBy = options.sortBy;
11798
+ }
11799
+ }
11800
+ if (options.sortOrder) {
11801
+ if (options.sortOrder === "asc" || options.sortOrder === "desc") {
11802
+ searchOptions.sortOrder = options.sortOrder;
11803
+ }
11804
+ }
11805
+ if (options.regex) {
11806
+ searchOptions.regex = true;
11807
+ }
11808
+ if (options.caseSensitive) {
11809
+ searchOptions.caseSensitive = true;
11810
+ }
11811
+ return searchOptions;
11812
+ }
11813
+ function registerSearchCommand(program2) {
11814
+ program2.command("search [query]").description("\uC2A4\uD399 \uAC80\uC0C9").option("--status <status>", "\uC0C1\uD0DC \uD544\uD130 (\uCF64\uB9C8\uB85C \uAD6C\uBD84)").option("--phase <phase>", "Phase \uD544\uD130 (\uCF64\uB9C8\uB85C \uAD6C\uBD84)").option("--author <author>", "\uC791\uC131\uC790 \uD544\uD130").option("--tags <tags>", "\uD0DC\uADF8 \uD544\uD130 (\uCF64\uB9C8\uB85C \uAD6C\uBD84)").option("--created-after <date>", "\uC0DD\uC131\uC77C \uC774\uD6C4 (YYYY-MM-DD)").option("--created-before <date>", "\uC0DD\uC131\uC77C \uC774\uC804 (YYYY-MM-DD)").option("--updated-after <date>", "\uC218\uC815\uC77C \uC774\uD6C4 (YYYY-MM-DD)").option("--updated-before <date>", "\uC218\uC815\uC77C \uC774\uC804 (YYYY-MM-DD)").option("--depends-on <specId>", "\uC758\uC874\uC131 \uD544\uD130").option("--limit <n>", "\uACB0\uACFC \uC81C\uD55C \uC218").option("--sort-by <field>", "\uC815\uB82C \uAE30\uC900 (relevance, created, updated, title, status)").option("--sort-order <order>", "\uC815\uB82C \uBC29\uD5A5 (asc, desc)").option("-r, --regex", "\uC815\uADDC\uC2DD \uAC80\uC0C9").option("-c, --case-sensitive", "\uB300\uC18C\uBB38\uC790 \uAD6C\uBD84").option("--json", "JSON \uD615\uC2DD\uC73C\uB85C \uCD9C\uB825").action(async (query, options) => {
11815
+ await executeSearch(query, options);
11816
+ });
11817
+ }
11818
+ async function executeSearch(query, options) {
11819
+ const sddPath = path28.join(process.cwd(), ".sdd");
11820
+ if (!await fileExists(sddPath)) {
11821
+ logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. sdd init\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
11822
+ return;
11823
+ }
11824
+ const searchOptions = parseSearchCliOptions(query, options);
11825
+ const result = await searchSpecs(sddPath, searchOptions);
11826
+ if (!result.success) {
11827
+ logger_exports.error(`\uAC80\uC0C9 \uC2E4\uD328: ${result.error.message}`);
11828
+ return;
11829
+ }
11830
+ if (options.json) {
11831
+ console.log(formatSearchResultJson(result.data));
11832
+ } else {
11833
+ console.log("");
11834
+ console.log(formatSearchResult(result.data));
11835
+ }
11836
+ }
11837
+
10775
11838
  // src/cli/index.ts
10776
11839
  var require2 = createRequire(import.meta.url);
10777
11840
  var pkg = require2("../../package.json");
@@ -10793,6 +11856,7 @@ registerTransitionCommand(program);
10793
11856
  registerWatchCommand(program);
10794
11857
  registerQualityCommand(program);
10795
11858
  registerReportCommand(program);
11859
+ registerSearchCommand(program);
10796
11860
  function run() {
10797
11861
  program.parse();
10798
11862
  }