sdd-tool 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -6
- package/bin/sdd.js +1 -1
- package/dist/cli/index.js +656 -9
- 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,6 +2497,263 @@ 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
|
];
|
|
@@ -3677,9 +3934,9 @@ async function validateSpecs(targetPath, options = {}) {
|
|
|
3677
3934
|
async function findSpecFiles(dirPath) {
|
|
3678
3935
|
const files = [];
|
|
3679
3936
|
async function scanDir(dir) {
|
|
3680
|
-
const { promises:
|
|
3937
|
+
const { promises: fs16 } = await import("fs");
|
|
3681
3938
|
try {
|
|
3682
|
-
const entries = await
|
|
3939
|
+
const entries = await fs16.readdir(dir, { withFileTypes: true });
|
|
3683
3940
|
for (const entry of entries) {
|
|
3684
3941
|
const fullPath = path3.join(dir, entry.name);
|
|
3685
3942
|
if (entry.isDirectory()) {
|
|
@@ -5802,19 +6059,19 @@ function detectCircularDependencies(graph) {
|
|
|
5802
6059
|
const cycles = [];
|
|
5803
6060
|
const visited = /* @__PURE__ */ new Set();
|
|
5804
6061
|
const recStack = /* @__PURE__ */ new Set();
|
|
5805
|
-
function dfs(nodeId,
|
|
6062
|
+
function dfs(nodeId, path29) {
|
|
5806
6063
|
visited.add(nodeId);
|
|
5807
6064
|
recStack.add(nodeId);
|
|
5808
6065
|
const node = graph.nodes.get(nodeId);
|
|
5809
6066
|
if (!node) return false;
|
|
5810
6067
|
for (const depId of node.dependsOn) {
|
|
5811
6068
|
if (!visited.has(depId)) {
|
|
5812
|
-
if (dfs(depId, [...
|
|
6069
|
+
if (dfs(depId, [...path29, nodeId])) {
|
|
5813
6070
|
return true;
|
|
5814
6071
|
}
|
|
5815
6072
|
} else if (recStack.has(depId)) {
|
|
5816
|
-
const cycleStart =
|
|
5817
|
-
const cycle = cycleStart >= 0 ? [...
|
|
6073
|
+
const cycleStart = path29.indexOf(depId);
|
|
6074
|
+
const cycle = cycleStart >= 0 ? [...path29.slice(cycleStart), nodeId, depId] : [nodeId, depId];
|
|
5818
6075
|
cycles.push({
|
|
5819
6076
|
cycle,
|
|
5820
6077
|
description: `\uC21C\uD658 \uC758\uC874\uC131: ${cycle.join(" \u2192 ")}`
|
|
@@ -11189,6 +11446,395 @@ async function runReport(options) {
|
|
|
11189
11446
|
}
|
|
11190
11447
|
}
|
|
11191
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
|
+
|
|
11192
11838
|
// src/cli/index.ts
|
|
11193
11839
|
var require2 = createRequire(import.meta.url);
|
|
11194
11840
|
var pkg = require2("../../package.json");
|
|
@@ -11210,6 +11856,7 @@ registerTransitionCommand(program);
|
|
|
11210
11856
|
registerWatchCommand(program);
|
|
11211
11857
|
registerQualityCommand(program);
|
|
11212
11858
|
registerReportCommand(program);
|
|
11859
|
+
registerSearchCommand(program);
|
|
11213
11860
|
function run() {
|
|
11214
11861
|
program.parse();
|
|
11215
11862
|
}
|