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/bin/sdd.js +1 -1
- package/dist/cli/index.js +2021 -957
- package/dist/cli/index.js.map +1 -1
- package/package.json +76 -76
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,
|
|
132
|
-
super(code, formatMessage(code,
|
|
131
|
+
constructor(code, path29) {
|
|
132
|
+
super(code, formatMessage(code, path29), ExitCode.FILE_SYSTEM_ERROR);
|
|
133
133
|
this.name = "FileSystemError";
|
|
134
|
-
this.path =
|
|
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
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
await runInit(options);
|
|
2510
|
-
} catch (error2) {
|
|
2511
|
-
error(error2 instanceof Error ? error2.message : String(error2));
|
|
2512
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
2513
|
-
}
|
|
2514
|
-
});
|
|
2515
|
-
}
|
|
2516
|
-
async function runInit(options) {
|
|
2517
|
-
const cwd = process.cwd();
|
|
2518
|
-
const sddPath = path2.join(cwd, ".sdd");
|
|
2519
|
-
if (await directoryExists(sddPath)) {
|
|
2520
|
-
if (!options.force) {
|
|
2521
|
-
error(".sdd/ \uB514\uB809\uD1A0\uB9AC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. --force \uC635\uC158\uC73C\uB85C \uB36E\uC5B4\uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
2522
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
2523
|
-
}
|
|
2524
|
-
warn("\uAE30\uC874 .sdd/ \uB514\uB809\uD1A0\uB9AC\uB97C \uB36E\uC5B4\uC501\uB2C8\uB2E4.");
|
|
2525
|
-
}
|
|
2526
|
-
info("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD569\uB2C8\uB2E4...");
|
|
2527
|
-
const directories = [
|
|
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
|
-
|
|
2575
|
-
const projectName = path2.basename(cwd);
|
|
2775
|
+
function generateConstitutionContent(projectName) {
|
|
2576
2776
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2577
|
-
|
|
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
|
-
|
|
2813
|
+
function generateSpecTemplate() {
|
|
2617
2814
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
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:
|
|
3937
|
+
const { promises: fs16 } = await import("fs");
|
|
3635
3938
|
try {
|
|
3636
|
-
const entries = await
|
|
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
|
-
|
|
3665
|
-
|
|
3666
|
-
try {
|
|
3667
|
-
await runValidate(targetPath, options);
|
|
3668
|
-
} catch (error2) {
|
|
3669
|
-
error(error2 instanceof Error ? error2.message : String(error2));
|
|
3670
|
-
process.exit(ExitCode.GENERAL_ERROR);
|
|
3671
|
-
}
|
|
3672
|
-
});
|
|
3673
|
-
}
|
|
3674
|
-
async function runValidate(targetPath, options) {
|
|
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
|
-
|
|
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
|
-
|
|
3697
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
4989
|
-
if (!await directoryExists(
|
|
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
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
info(
|
|
5000
|
-
|
|
5001
|
-
if (parseResult.data.affectedSpecs.length > 0) {
|
|
5002
|
-
info("\uC601\uD5A5 \uC2A4\uD399:");
|
|
5003
|
-
parseResult.data.affectedSpecs.forEach((spec) => listItem(spec, 1));
|
|
5004
|
-
}
|
|
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
|
-
}
|
|
5007
|
-
error(
|
|
5473
|
+
} else {
|
|
5474
|
+
error(infoResult.error.message);
|
|
5008
5475
|
}
|
|
5009
5476
|
return;
|
|
5010
5477
|
}
|
|
5011
|
-
const
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
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 =
|
|
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
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
const updateResult = updateProposalStatus(content, "applied");
|
|
5062
|
-
if (updateResult.success) {
|
|
5063
|
-
await fs3.writeFile(proposalPath, updateResult.data);
|
|
5064
|
-
}
|
|
5065
|
-
} catch {
|
|
5066
|
-
error("proposal.md\uB97C \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
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
|
|
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(
|
|
5547
|
+
error(deltaResult.error.message);
|
|
5110
5548
|
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
5111
5549
|
}
|
|
5112
|
-
const
|
|
5113
|
-
if (!parseResult.success) {
|
|
5114
|
-
error(`Delta \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
|
|
5115
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
5116
|
-
}
|
|
5117
|
-
const delta = parseResult.data;
|
|
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
|
-
|
|
5167
|
-
|
|
5168
|
-
const proposalResult = await readFile(proposalPath);
|
|
5169
|
-
if (proposalResult.success) {
|
|
5170
|
-
const parsed = parseProposal(proposalResult.data);
|
|
5171
|
-
if (parsed.success) {
|
|
5172
|
-
success2(`\u2713 proposal.md \uC720\uD6A8 (${parsed.data.title})`);
|
|
5173
|
-
} else {
|
|
5174
|
-
error(`\u2717 proposal.md \uC624\uB958: ${parsed.error.message}`);
|
|
5175
|
-
hasErrors = true;
|
|
5176
|
-
}
|
|
5177
|
-
}
|
|
5605
|
+
if (result.proposalValid) {
|
|
5606
|
+
success2(`\u2713 proposal.md \uC720\uD6A8 (${result.proposalTitle})`);
|
|
5178
5607
|
} else {
|
|
5179
|
-
error(
|
|
5608
|
+
error(`\u2717 proposal.md \uC624\uB958: ${result.proposalError}`);
|
|
5180
5609
|
hasErrors = true;
|
|
5181
5610
|
}
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
if (validation.valid) {
|
|
5188
|
-
const types = [];
|
|
5189
|
-
if (validation.hasAdded) types.push("ADDED");
|
|
5190
|
-
if (validation.hasModified) types.push("MODIFIED");
|
|
5191
|
-
if (validation.hasRemoved) types.push("REMOVED");
|
|
5192
|
-
success2(`\u2713 delta.md \uC720\uD6A8 (${types.join(", ")})`);
|
|
5193
|
-
for (const warning of validation.warnings) {
|
|
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
|
-
}
|
|
5197
|
-
|
|
5198
|
-
|
|
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.
|
|
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,
|
|
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, [...
|
|
6069
|
+
if (dfs(depId, [...path29, nodeId])) {
|
|
5646
6070
|
return true;
|
|
5647
6071
|
}
|
|
5648
6072
|
} else if (recStack.has(depId)) {
|
|
5649
|
-
const cycleStart =
|
|
5650
|
-
const cycle = cycleStart >= 0 ? [...
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6827
|
-
if (!
|
|
6828
|
-
|
|
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 =
|
|
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
|
|
7333
|
+
console.log(JSON.stringify(simResult, null, 2));
|
|
6850
7334
|
} else {
|
|
6851
|
-
console.log(formatSimulationResult(simResult
|
|
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
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
const numberResult = await getNextFeatureNumber(sddPath, name);
|
|
6893
|
-
if (!numberResult.success) {
|
|
6894
|
-
logger_exports.error(`\uBC88\uD638 \uC0DD\uC131 \uC2E4\uD328: ${numberResult.error.message}`);
|
|
6895
|
-
process.exit(1);
|
|
6896
|
-
}
|
|
6897
|
-
featureId = numberResult.data.fullId;
|
|
6898
|
-
branchName = numberResult.data.branchName;
|
|
6899
|
-
logger_exports.info(`\uC790\uB3D9 \uBC88\uD638 \uBD80\uC5EC: #${numberResult.data.number.toString().padStart(3, "0")}`);
|
|
6900
|
-
} else {
|
|
6901
|
-
featureId = generateFeatureId(name);
|
|
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
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
const constitutionPath = path12.join(sddPath, "constitution.md");
|
|
6914
|
-
if (await fileExists(constitutionPath)) {
|
|
6915
|
-
const constResult = await readFile(constitutionPath);
|
|
6916
|
-
if (constResult.success) {
|
|
6917
|
-
const parseResult = parseConstitution(constResult.data);
|
|
6918
|
-
if (parseResult.success) {
|
|
6919
|
-
constitutionVersion = parseResult.data.metadata.version;
|
|
6920
|
-
}
|
|
6921
|
-
}
|
|
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
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
const branchToCreate = branchName || featureId;
|
|
6934
|
-
const result = await createBranch(branchToCreate, { checkout: true, cwd });
|
|
6935
|
-
if (result.success) {
|
|
6936
|
-
logger_exports.info(`\u2705 \uBE0C\uB79C\uCE58 \uC0DD\uC131: ${result.data}`);
|
|
6937
|
-
} else {
|
|
6938
|
-
logger_exports.warn(`\u26A0\uFE0F \uBE0C\uB79C\uCE58 \uC0DD\uC131 \uC2E4\uD328: ${result.error.message}`);
|
|
6939
|
-
}
|
|
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(
|
|
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
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
process.exit(1);
|
|
6996
|
-
}
|
|
6997
|
-
let title2 = options.title || feature;
|
|
6998
|
-
const specPath = path12.join(featurePath, "spec.md");
|
|
6999
|
-
if (await fileExists(specPath)) {
|
|
7000
|
-
const specContent = await fs7.readFile(specPath, "utf-8");
|
|
7001
|
-
const titleMatch = specContent.match(/title:\s*"?([^"\n]+)"?/);
|
|
7002
|
-
if (titleMatch) {
|
|
7003
|
-
title2 = titleMatch[1];
|
|
7004
|
-
}
|
|
7005
|
-
}
|
|
7006
|
-
const planContent = generatePlan({
|
|
7007
|
-
featureId: feature,
|
|
7008
|
-
featureTitle: title2,
|
|
7009
|
-
overview: `${title2} \uAD6C\uD604 \uACC4\uD68D`
|
|
7010
|
-
});
|
|
7011
|
-
await fs7.writeFile(path12.join(featurePath, "plan.md"), planContent, "utf-8");
|
|
7012
|
-
logger_exports.info(`\u2705 \uACC4\uD68D \uC0DD\uC131: ${featurePath}/plan.md`);
|
|
7013
|
-
logger_exports.info("");
|
|
7014
|
-
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
7015
|
-
logger_exports.info(` 1. ${featurePath}/plan.md \uD3B8\uC9D1`);
|
|
7016
|
-
logger_exports.info(" 2. sdd new tasks " + feature + " - \uC791\uC5C5 \uBD84\uD574");
|
|
7017
|
-
} catch (error2) {
|
|
7018
|
-
logger_exports.error(`\uACC4\uD68D \uC0DD\uC131 \uC2E4\uD328: ${error2}`);
|
|
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
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
process.exit(1);
|
|
7029
|
-
}
|
|
7030
|
-
let title2 = feature;
|
|
7031
|
-
const specPath = path12.join(featurePath, "spec.md");
|
|
7032
|
-
if (await fileExists(specPath)) {
|
|
7033
|
-
const specContent = await fs7.readFile(specPath, "utf-8");
|
|
7034
|
-
const titleMatch = specContent.match(/title:\s*"?([^"\n]+)"?/);
|
|
7035
|
-
if (titleMatch) {
|
|
7036
|
-
title2 = titleMatch[1];
|
|
7037
|
-
}
|
|
7038
|
-
}
|
|
7039
|
-
const tasksContent = generateTasks({
|
|
7040
|
-
featureId: feature,
|
|
7041
|
-
featureTitle: title2,
|
|
7042
|
-
tasks: [
|
|
7043
|
-
{ title: "\uAE30\uBC18 \uAD6C\uC870 \uC124\uC815", priority: "high" },
|
|
7044
|
-
{ title: "\uD575\uC2EC \uAE30\uB2A5 \uAD6C\uD604", priority: "high" },
|
|
7045
|
-
{ title: "\uD14C\uC2A4\uD2B8 \uC791\uC131", priority: "medium" },
|
|
7046
|
-
{ title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
|
|
7047
|
-
]
|
|
7048
|
-
});
|
|
7049
|
-
await fs7.writeFile(path12.join(featurePath, "tasks.md"), tasksContent, "utf-8");
|
|
7050
|
-
logger_exports.info(`\u2705 \uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131: ${featurePath}/tasks.md`);
|
|
7051
|
-
logger_exports.info("");
|
|
7052
|
-
logger_exports.info("\uB2E4\uC74C \uB2E8\uACC4:");
|
|
7053
|
-
logger_exports.info(` 1. ${featurePath}/tasks.md \uD3B8\uC9D1`);
|
|
7054
|
-
logger_exports.info(" 2. \uAC01 \uC791\uC5C5 \uC21C\uCC28\uC801\uC73C\uB85C \uAD6C\uD604");
|
|
7055
|
-
} catch (error2) {
|
|
7056
|
-
logger_exports.error(`\uC791\uC5C5 \uBD84\uD574 \uC0DD\uC131 \uC2E4\uD328: ${error2}`);
|
|
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
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
process.exit(1);
|
|
7067
|
-
}
|
|
7068
|
-
const checklistContent = generateFullChecklistMarkdown();
|
|
7069
|
-
const outputPath = path12.join(sddPath, "checklist.md");
|
|
7070
|
-
await fs7.writeFile(outputPath, checklistContent, "utf-8");
|
|
7071
|
-
logger_exports.info(`\u2705 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC0DD\uC131: ${outputPath}`);
|
|
7072
|
-
} catch (error2) {
|
|
7073
|
-
logger_exports.error(`\uCCB4\uD06C\uB9AC\uC2A4\uD2B8 \uC0DD\uC131 \uC2E4\uD328: ${error2}`);
|
|
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
|
|
7135
|
-
|
|
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(
|
|
7140
|
-
logger_exports.info(`\uC0DD\uC131\uB41C \uAE30\uB2A5 \uC218: ${
|
|
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(
|
|
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
|
|
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
|
|
7160
|
-
|
|
7161
|
-
|
|
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
|
|
7165
|
-
const
|
|
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
|
-
|
|
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
|
|
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(
|
|
7765
|
+
const currentBranchResult = await getCurrentBranch(projectPath);
|
|
7211
7766
|
if (currentBranchResult.success) {
|
|
7212
7767
|
status.currentBranch = currentBranchResult.data;
|
|
7213
7768
|
}
|
|
7214
|
-
const featureBranchesResult = await listFeatureBranches(
|
|
7769
|
+
const featureBranchesResult = await listFeatureBranches(projectPath);
|
|
7215
7770
|
if (featureBranchesResult.success) {
|
|
7216
7771
|
status.featureBranches = featureBranchesResult.data;
|
|
7217
7772
|
}
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
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
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
if (metadata) {
|
|
7239
|
-
info2.title = metadata.title;
|
|
7240
|
-
info2.status = metadata.status;
|
|
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
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
const content = await fs8.readFile(tasksPath, "utf-8");
|
|
7248
|
-
const tasks = parseTasks(content);
|
|
7249
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
7250
|
-
info2.taskProgress = {
|
|
7251
|
-
completed,
|
|
7252
|
-
total: tasks.length
|
|
7253
|
-
};
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7383
|
-
return;
|
|
7922
|
+
return [];
|
|
7384
7923
|
}
|
|
7385
7924
|
const result = await readDir(specsPath);
|
|
7386
7925
|
if (!result.success) {
|
|
7387
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
7461
|
-
const
|
|
7462
|
-
if (
|
|
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
|
-
|
|
8113
|
+
printSpecTree(tree, "");
|
|
7470
8114
|
console.log("");
|
|
7471
8115
|
}
|
|
7472
|
-
|
|
7473
|
-
const
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
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
|
|
7488
|
-
|
|
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
|
|
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
|
|
7508
|
-
|
|
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
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
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
|
-
|
|
7528
|
-
const
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
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
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
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
|
|
7614
|
-
if (!
|
|
7615
|
-
error(
|
|
7616
|
-
process.exit(ExitCode.FILE_NOT_FOUND);
|
|
7617
|
-
}
|
|
7618
|
-
const contentResult = await readFile(constitutionPath);
|
|
7619
|
-
if (!contentResult.success) {
|
|
7620
|
-
error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
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
|
|
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
|
|
7682
|
-
if (!
|
|
7683
|
-
error(
|
|
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
|
-
|
|
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
|
|
7701
|
-
|
|
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
|
|
7718
|
-
if (!
|
|
7719
|
-
error(
|
|
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
|
-
|
|
7723
|
-
|
|
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
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
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
|
|
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
|
|
7811
|
-
if (!
|
|
7812
|
-
error(
|
|
7813
|
-
process.exit(ExitCode.
|
|
7814
|
-
}
|
|
7815
|
-
const contentResult = await readFile(constitutionPath);
|
|
7816
|
-
if (!contentResult.success) {
|
|
7817
|
-
error("Constitution \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7818
|
-
process.exit(ExitCode.FILE_SYSTEM_ERROR);
|
|
7819
|
-
}
|
|
7820
|
-
const parseResult = parseConstitution(contentResult.data);
|
|
7821
|
-
if (!parseResult.success) {
|
|
7822
|
-
error(`Constitution \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`);
|
|
7823
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
7824
|
-
}
|
|
7825
|
-
const validationResult = validateConstitution(parseResult.data);
|
|
7826
|
-
if (!validationResult.success) {
|
|
7827
|
-
error(`Constitution \uAC80\uC99D \uC2E4\uD328: ${validationResult.error.message}`);
|
|
7828
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
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: ${
|
|
7832
|
-
info(`\uBC84\uC804: ${
|
|
7833
|
-
info(`\uC6D0\uCE59 \uC218: ${
|
|
7834
|
-
info(`\uAE08\uC9C0 \uC0AC\uD56D \uC218: ${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8328
|
-
const files = await
|
|
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
|
|
8945
|
+
const stat = await fs10.stat(sourceFile);
|
|
8333
8946
|
if (stat.isFile()) {
|
|
8334
|
-
let content = await
|
|
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
|
|
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
|
|
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
|
|
8382
|
-
const files = await
|
|
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
|
|
8999
|
+
const stat = await fs10.stat(sourceFile);
|
|
8387
9000
|
if (stat.isFile()) {
|
|
8388
|
-
let content = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
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
|
|
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
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
9969
|
-
const deps = Array.isArray(spec.
|
|
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
|
-
|
|
9979
|
-
|
|
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.
|
|
10697
|
+
} else if (spec.metadata.constitution_version) {
|
|
10045
10698
|
score = maxScore;
|
|
10046
|
-
details.push(`Constitution \uBC84\uC804: ${spec.
|
|
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.
|
|
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.
|
|
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.
|
|
10105
|
-
(k) => spec.
|
|
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.
|
|
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
|
|
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
|
-
|
|
10325
|
-
|
|
10326
|
-
|
|
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.
|
|
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?.
|
|
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?.
|
|
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.
|
|
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.
|
|
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(`- \
|
|
10692
|
-
lines.push(`- \
|
|
10693
|
-
lines.push(`- \uACBD\uACE0: ${data.validation.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
}
|