sdd-tool 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -130,10 +130,10 @@ var init_base = __esm({
130
130
  };
131
131
  FileSystemError = class extends SddError {
132
132
  path;
133
- constructor(code, path42) {
134
- super(code, formatMessage(code, path42), ExitCode.FILE_SYSTEM_ERROR);
133
+ constructor(code, path44) {
134
+ super(code, formatMessage(code, path44), ExitCode.FILE_SYSTEM_ERROR);
135
135
  this.name = "FileSystemError";
136
- this.path = path42;
136
+ this.path = path44;
137
137
  }
138
138
  };
139
139
  ValidationError = class extends SddError {
@@ -1216,7 +1216,7 @@ var init_checklist = __esm({
1216
1216
  });
1217
1217
 
1218
1218
  // src/core/new/counter.ts
1219
- import path11 from "path";
1219
+ import path13 from "path";
1220
1220
  function getDefaultCounterData() {
1221
1221
  return {
1222
1222
  nextFeatureNumber: 1,
@@ -1225,7 +1225,7 @@ function getDefaultCounterData() {
1225
1225
  };
1226
1226
  }
1227
1227
  function getCounterPath(sddPath) {
1228
- return path11.join(sddPath, "counter.json");
1228
+ return path13.join(sddPath, "counter.json");
1229
1229
  }
1230
1230
  async function readCounter(sddPath) {
1231
1231
  const counterPath = getCounterPath(sddPath);
@@ -1413,7 +1413,8 @@ import { createRequire } from "module";
1413
1413
  // src/cli/commands/init.ts
1414
1414
  init_fs();
1415
1415
  init_errors();
1416
- import path2 from "path";
1416
+ import path4 from "path";
1417
+ import readline from "readline";
1417
1418
 
1418
1419
  // src/utils/logger.ts
1419
1420
  var logger_exports = {};
@@ -1624,9 +1625,47 @@ function generateClaudeCommands() {
1624
1625
 
1625
1626
  ## \uC9C0\uC2DC\uC0AC\uD56D
1626
1627
 
1627
- 1. \`sdd start\` \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694
1628
- 2. \uC81C\uC2DC\uB418\uB294 \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uBA54\uB274\uC5D0\uC11C \uC801\uC808\uD55C \uC791\uC5C5\uC744 \uC120\uD0DD\uD558\uC138\uC694
1629
- 3. \uAC01 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC758 \uC548\uB0B4\uC5D0 \uB530\uB77C \uC9C4\uD589\uD558\uC138\uC694
1628
+ 1. \uBA3C\uC800 \uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870\uB97C \uBD84\uC11D\uD558\uC138\uC694:
1629
+ - .sdd/ \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC \uC5EC\uBD80 \uD655\uC778
1630
+ - .git/ \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC \uC5EC\uBD80 \uD655\uC778
1631
+ - .git/hooks/ \uB514\uB809\uD1A0\uB9AC\uC758 SDD \uD6C5 \uC124\uCE58 \uC5EC\uBD80 \uD655\uC778
1632
+ - .github/workflows/ \uB514\uB809\uD1A0\uB9AC\uC758 SDD \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC874\uC7AC \uC5EC\uBD80 \uD655\uC778
1633
+
1634
+ 2. \uBD84\uC11D \uACB0\uACFC\uC5D0 \uB530\uB77C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC548\uB0B4\uD558\uC138\uC694:
1635
+ - SDD \uBBF8\uCD08\uAE30\uD654: \`sdd init\` \uC2E4\uD589 \uAD8C\uC7A5
1636
+ - Git Hooks \uBBF8\uC124\uCE58: Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815 \uAD8C\uC7A5
1637
+ - CI/CD \uBBF8\uC124\uC815: GitHub Actions \uC124\uC815 \uAD8C\uC7A5
1638
+
1639
+ 3. \uC124\uC815\uC774 \uD544\uC694\uD55C \uACBD\uC6B0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C **\uC9C8\uBB38**\uD558\uACE0 **\uC2B9\uC778**\uC744 \uBC1B\uC740 \uD6C4 \uC2E4\uD589\uD558\uC138\uC694
1640
+
1641
+ ## Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815 \uD655\uC778
1642
+
1643
+ \uB2E4\uC74C \uBA85\uB839\uC5B4\uB85C \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4:
1644
+
1645
+ \`\`\`bash
1646
+ # \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uD655\uC778
1647
+ sdd status
1648
+
1649
+ # Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815
1650
+ sdd git setup
1651
+
1652
+ # CI/CD \uC124\uC815
1653
+ sdd cicd setup github
1654
+ \`\`\`
1655
+
1656
+ ### \uC124\uC815 \uC81C\uC548 \uC2DC\uB098\uB9AC\uC624
1657
+
1658
+ 1. **Git Hooks\uAC00 \uC5C6\uB294 \uACBD\uC6B0**:
1659
+ "Git Hooks\uAC00 \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uCEE4\uBC0B/\uD478\uC2DC \uC2DC \uC790\uB3D9 \uC2A4\uD399 \uAC80\uC99D\uC744 \uD65C\uC131\uD654\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?"
1660
+ \u2192 \uC2B9\uC778 \uC2DC: \`sdd git hooks install\` \uC2E4\uD589
1661
+
1662
+ 2. **\uCEE4\uBC0B \uD15C\uD50C\uB9BF\uC774 \uC5C6\uB294 \uACBD\uC6B0**:
1663
+ "\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF\uC744 \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC77C\uAD00\uB41C \uCEE4\uBC0B \uD615\uC2DD\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
1664
+ \u2192 \uC2B9\uC778 \uC2DC: \`sdd git template install\` \uC2E4\uD589
1665
+
1666
+ 3. **GitHub Actions\uAC00 \uC5C6\uB294 \uACBD\uC6B0**:
1667
+ "GitHub Actions CI/CD\uB97C \uC124\uC815\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? PR \uC2DC \uC790\uB3D9\uC73C\uB85C \uC2A4\uD399\uC744 \uAC80\uC99D\uD569\uB2C8\uB2E4."
1668
+ \u2192 \uC2B9\uC778 \uC2DC: \`sdd cicd setup github\` \uC2E4\uD589
1630
1669
 
1631
1670
  ## \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC6CC\uD06C\uD50C\uB85C\uC6B0
1632
1671
 
@@ -1635,6 +1674,7 @@ function generateClaudeCommands() {
1635
1674
  - **validate**: \uBA85\uC138 \uAC80\uC99D
1636
1675
  - **status**: \uC0C1\uD0DC \uD655\uC778
1637
1676
  - **constitution**: Constitution \uAD00\uB9AC
1677
+ - **git-setup**: Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815
1638
1678
 
1639
1679
  ## \uBA85\uB839\uC5B4
1640
1680
 
@@ -1654,9 +1694,26 @@ sdd start --workflow validate
1654
1694
  ## \uD504\uB85C\uC81D\uD2B8 \uBBF8\uCD08\uAE30\uD654 \uC2DC
1655
1695
 
1656
1696
  \uD504\uB85C\uC81D\uD2B8\uAC00 \uCD08\uAE30\uD654\uB418\uC9C0 \uC54A\uC740 \uACBD\uC6B0:
1657
- 1. \`sdd init\`\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD558\uC138\uC694
1697
+ 1. \`sdd init\`\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654\uD558\uC138\uC694 (Git/CI-CD \uC124\uC815 \uD3EC\uD568)
1658
1698
  2. \`/sdd.constitution\`\uC73C\uB85C \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59\uC744 \uC815\uC758\uD558\uC138\uC694
1659
1699
  3. \`/sdd.new\`\uB85C \uCCAB \uAE30\uB2A5 \uBA85\uC138\uB97C \uC791\uC131\uD558\uC138\uC694
1700
+
1701
+ ## Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0C1\uC138
1702
+
1703
+ ### \uC124\uCE58\uB418\uB294 Git Hooks
1704
+
1705
+ | \uD6C5 | \uC2DC\uC810 | \uAE30\uB2A5 |
1706
+ |----|------|------|
1707
+ | pre-commit | \uCEE4\uBC0B \uC804 | \uBCC0\uACBD\uB41C \uC2A4\uD399 \uAC80\uC99D |
1708
+ | commit-msg | \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uC791\uC131 \uD6C4 | \uBA54\uC2DC\uC9C0 \uD615\uC2DD \uAC80\uC99D |
1709
+ | pre-push | \uD478\uC2DC \uC804 | \uC804\uCCB4 \uC2A4\uD399 \uAC80\uC99D |
1710
+
1711
+ ### \uC0DD\uC131\uB418\uB294 GitHub Actions
1712
+
1713
+ | \uC6CC\uD06C\uD50C\uB85C\uC6B0 | \uAE30\uB2A5 |
1714
+ |-----------|------|
1715
+ | sdd-validate.yml | PR/\uD478\uC2DC \uC2DC \uC2A4\uD399 \uC790\uB3D9 \uAC80\uC99D |
1716
+ | sdd-labeler.yml | PR\uC5D0 \uB3C4\uBA54\uC778\uBCC4 \uB77C\uBCA8 \uC790\uB3D9 \uCD94\uAC00 |
1660
1717
  `
1661
1718
  },
1662
1719
  {
@@ -3028,6 +3085,468 @@ sdd export user-auth --no-toc
3028
3085
 
3029
3086
  // src/cli/commands/init.ts
3030
3087
  init_types();
3088
+
3089
+ // src/utils/project-analyzer.ts
3090
+ init_fs();
3091
+ import path2 from "path";
3092
+ async function analyzeProject(projectPath) {
3093
+ const [
3094
+ isGitRepo,
3095
+ hasPreCommitHook,
3096
+ hasCommitMsgHook,
3097
+ hasPrePushHook,
3098
+ hasGitMessageTemplate,
3099
+ hasGitHubWorkflows,
3100
+ hasGitLabCI,
3101
+ isSddProject,
3102
+ isNodeProject,
3103
+ isTypeScriptProject,
3104
+ hasSddValidateWorkflow,
3105
+ hasSddLabelerWorkflow
3106
+ ] = await Promise.all([
3107
+ directoryExists(path2.join(projectPath, ".git")),
3108
+ fileExists(path2.join(projectPath, ".git", "hooks", "pre-commit")),
3109
+ fileExists(path2.join(projectPath, ".git", "hooks", "commit-msg")),
3110
+ fileExists(path2.join(projectPath, ".git", "hooks", "pre-push")),
3111
+ fileExists(path2.join(projectPath, ".gitmessage")),
3112
+ directoryExists(path2.join(projectPath, ".github", "workflows")),
3113
+ fileExists(path2.join(projectPath, ".gitlab-ci.yml")).then(
3114
+ (exists) => exists || fileExists(path2.join(projectPath, ".gitlab-ci-sdd.yml"))
3115
+ ),
3116
+ directoryExists(path2.join(projectPath, ".sdd")),
3117
+ fileExists(path2.join(projectPath, "package.json")),
3118
+ fileExists(path2.join(projectPath, "tsconfig.json")),
3119
+ fileExists(path2.join(projectPath, ".github", "workflows", "sdd-validate.yml")),
3120
+ fileExists(path2.join(projectPath, ".github", "workflows", "sdd-labeler.yml"))
3121
+ ]);
3122
+ return {
3123
+ isGitRepo,
3124
+ hasGitHooks: hasPreCommitHook && hasCommitMsgHook && hasPrePushHook,
3125
+ hasGitMessageTemplate,
3126
+ hasGitHubActions: hasGitHubWorkflows,
3127
+ hasGitLabCI,
3128
+ isSddProject,
3129
+ isNodeProject,
3130
+ isTypeScriptProject,
3131
+ hasSddValidateWorkflow,
3132
+ hasSddLabelerWorkflow
3133
+ };
3134
+ }
3135
+ function generateSuggestions(analysis) {
3136
+ const suggestions = {
3137
+ suggestGitHooks: false,
3138
+ suggestGitTemplate: false,
3139
+ suggestGitHubActions: false,
3140
+ suggestGitLabCI: false,
3141
+ reasons: []
3142
+ };
3143
+ if (!analysis.isGitRepo) {
3144
+ suggestions.reasons.push("Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. git init \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
3145
+ return suggestions;
3146
+ }
3147
+ if (!analysis.hasGitHooks) {
3148
+ suggestions.suggestGitHooks = true;
3149
+ suggestions.reasons.push("Git Hooks\uAC00 \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uCEE4\uBC0B/\uD478\uC2DC \uC2DC \uC790\uB3D9 \uAC80\uC99D\uC744 \uD65C\uC131\uD654\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
3150
+ }
3151
+ if (!analysis.hasGitMessageTemplate) {
3152
+ suggestions.suggestGitTemplate = true;
3153
+ suggestions.reasons.push("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC77C\uAD00\uB41C \uCEE4\uBC0B \uD615\uC2DD\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
3154
+ }
3155
+ if (analysis.hasGitHubActions && !analysis.hasSddValidateWorkflow) {
3156
+ suggestions.suggestGitHubActions = true;
3157
+ suggestions.reasons.push(".github/workflows\uAC00 \uC874\uC7AC\uD558\uC9C0\uB9CC SDD \uAC80\uC99D \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
3158
+ } else if (!analysis.hasGitHubActions && !analysis.hasGitLabCI) {
3159
+ suggestions.suggestGitHubActions = true;
3160
+ suggestions.reasons.push("CI/CD \uC124\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. PR \uC2DC \uC790\uB3D9 \uC2A4\uD399 \uAC80\uC99D\uC744 \uD65C\uC131\uD654\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
3161
+ }
3162
+ if (!analysis.hasGitHubActions && !analysis.hasGitLabCI) {
3163
+ suggestions.suggestGitLabCI = true;
3164
+ }
3165
+ return suggestions;
3166
+ }
3167
+ function formatAnalysis(analysis) {
3168
+ const lines = [];
3169
+ lines.push("=== \uD504\uB85C\uC81D\uD2B8 \uBD84\uC11D \uACB0\uACFC ===");
3170
+ lines.push("");
3171
+ lines.push("\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uD0C0\uC785:");
3172
+ if (analysis.isNodeProject) {
3173
+ lines.push(` ${analysis.isTypeScriptProject ? "TypeScript" : "JavaScript"} (Node.js)`);
3174
+ } else {
3175
+ lines.push(" (\uAC10\uC9C0\uB418\uC9C0 \uC54A\uC74C)");
3176
+ }
3177
+ lines.push("");
3178
+ lines.push("\u{1F527} Git \uC0C1\uD0DC:");
3179
+ lines.push(` \uC800\uC7A5\uC18C: ${analysis.isGitRepo ? "\u2705 \uCD08\uAE30\uD654\uB428" : "\u274C \uBBF8\uCD08\uAE30\uD654"}`);
3180
+ if (analysis.isGitRepo) {
3181
+ lines.push(` Hooks: ${analysis.hasGitHooks ? "\u2705 \uC124\uCE58\uB428" : "\u274C \uBBF8\uC124\uCE58"}`);
3182
+ lines.push(` \uCEE4\uBC0B \uD15C\uD50C\uB9BF: ${analysis.hasGitMessageTemplate ? "\u2705 \uC874\uC7AC" : "\u274C \uC5C6\uC74C"}`);
3183
+ }
3184
+ lines.push("");
3185
+ lines.push("\u{1F680} CI/CD \uC0C1\uD0DC:");
3186
+ if (analysis.hasGitHubActions) {
3187
+ lines.push(` GitHub Actions: \u2705 \uC124\uC815\uB428`);
3188
+ lines.push(` - SDD Validate: ${analysis.hasSddValidateWorkflow ? "\u2705" : "\u274C"}`);
3189
+ lines.push(` - SDD Labeler: ${analysis.hasSddLabelerWorkflow ? "\u2705" : "\u274C"}`);
3190
+ } else {
3191
+ lines.push(" GitHub Actions: \u274C \uBBF8\uC124\uC815");
3192
+ }
3193
+ lines.push(` GitLab CI: ${analysis.hasGitLabCI ? "\u2705 \uC124\uC815\uB428" : "\u274C \uBBF8\uC124\uC815"}`);
3194
+ lines.push("");
3195
+ lines.push("\u{1F4CB} SDD \uC0C1\uD0DC:");
3196
+ lines.push(` \uCD08\uAE30\uD654: ${analysis.isSddProject ? "\u2705 \uC644\uB8CC" : "\u274C \uBBF8\uCD08\uAE30\uD654"}`);
3197
+ return lines.join("\n");
3198
+ }
3199
+
3200
+ // src/cli/commands/git.ts
3201
+ init_fs();
3202
+ init_errors();
3203
+ import path3 from "path";
3204
+ import fs2 from "fs/promises";
3205
+ init_types();
3206
+ function generatePreCommitHook() {
3207
+ return `#!/bin/sh
3208
+ # SDD pre-commit hook
3209
+ # \uBCC0\uACBD\uB41C \uC2A4\uD399 \uD30C\uC77C\uC744 \uAC80\uC99D\uD569\uB2C8\uB2E4
3210
+
3211
+ # \uC0C9\uC0C1 \uC815\uC758
3212
+ RED='\\033[0;31m'
3213
+ GREEN='\\033[0;32m'
3214
+ YELLOW='\\033[1;33m'
3215
+ NC='\\033[0m' # No Color
3216
+
3217
+ # \uBCC0\uACBD\uB41C \uC2A4\uD399 \uD30C\uC77C \uD655\uC778
3218
+ CHANGED_SPECS=$(git diff --cached --name-only | grep "^\\.sdd/specs/")
3219
+
3220
+ if [ -n "$CHANGED_SPECS" ]; then
3221
+ echo "\${YELLOW}\u{1F50D} \uC2A4\uD399 \uAC80\uC99D \uC911...\${NC}"
3222
+
3223
+ # sdd validate \uC2E4\uD589
3224
+ if command -v sdd &> /dev/null; then
3225
+ sdd validate --ci
3226
+ if [ $? -ne 0 ]; then
3227
+ echo "\${RED}\u274C \uC2A4\uD399 \uAC80\uC99D \uC2E4\uD328. \uCEE4\uBC0B\uC774 \uCDE8\uC18C\uB429\uB2C8\uB2E4.\${NC}"
3228
+ echo "\uC624\uB958\uB97C \uC218\uC815\uD558\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."
3229
+ exit 1
3230
+ fi
3231
+ echo "\${GREEN}\u2705 \uC2A4\uD399 \uAC80\uC99D \uD1B5\uACFC\${NC}"
3232
+ else
3233
+ echo "\${YELLOW}\u26A0\uFE0F sdd \uBA85\uB839\uC5B4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC80\uC99D\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.\${NC}"
3234
+ fi
3235
+ fi
3236
+
3237
+ exit 0
3238
+ `;
3239
+ }
3240
+ function generateCommitMsgHook() {
3241
+ return `#!/bin/sh
3242
+ # SDD commit-msg hook
3243
+ # \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD615\uC2DD\uC744 \uAC80\uC99D\uD569\uB2C8\uB2E4
3244
+
3245
+ # \uC0C9\uC0C1 \uC815\uC758
3246
+ RED='\\033[0;31m'
3247
+ GREEN='\\033[0;32m'
3248
+ NC='\\033[0m'
3249
+
3250
+ COMMIT_MSG_FILE=$1
3251
+ COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
3252
+
3253
+ # \uBE48 \uC904\uACFC \uC8FC\uC11D \uC81C\uAC70
3254
+ COMMIT_MSG_CLEAN=$(echo "$COMMIT_MSG" | grep -v "^#" | grep -v "^$" | head -1)
3255
+
3256
+ # \uC2A4\uD399 \uCEE4\uBC0B \uD328\uD134
3257
+ SPEC_PATTERN="^(spec|spec-update|spec-status|plan|tasks|constitution|sdd-config)(\\(.+\\))?: .+"
3258
+
3259
+ # \uC77C\uBC18 \uCEE4\uBC0B \uD328\uD134 (Conventional Commits)
3260
+ GENERAL_PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\\(.+\\))?: .+"
3261
+
3262
+ # \uBA38\uC9C0 \uCEE4\uBC0B \uD328\uD134
3263
+ MERGE_PATTERN="^Merge "
3264
+
3265
+ # \uB9AC\uBC84\uD2B8 \uCEE4\uBC0B \uD328\uD134
3266
+ REVERT_PATTERN="^Revert "
3267
+
3268
+ # \uD328\uD134 \uAC80\uC0AC
3269
+ if echo "$COMMIT_MSG_CLEAN" | grep -qE "$SPEC_PATTERN"; then
3270
+ echo "\${GREEN}\u2705 \uC2A4\uD399 \uCEE4\uBC0B \uD615\uC2DD \uD655\uC778\uB428\${NC}"
3271
+ exit 0
3272
+ elif echo "$COMMIT_MSG_CLEAN" | grep -qE "$GENERAL_PATTERN"; then
3273
+ echo "\${GREEN}\u2705 Conventional Commit \uD615\uC2DD \uD655\uC778\uB428\${NC}"
3274
+ exit 0
3275
+ elif echo "$COMMIT_MSG_CLEAN" | grep -qE "$MERGE_PATTERN"; then
3276
+ exit 0
3277
+ elif echo "$COMMIT_MSG_CLEAN" | grep -qE "$REVERT_PATTERN"; then
3278
+ exit 0
3279
+ else
3280
+ echo "\${RED}\u274C \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD615\uC2DD \uC624\uB958\${NC}"
3281
+ echo ""
3282
+ echo "\uC62C\uBC14\uB978 \uD615\uC2DD:"
3283
+ echo " \uC2A4\uD399 \uCEE4\uBC0B: spec(<scope>): <message>"
3284
+ echo " \uC77C\uBC18 \uCEE4\uBC0B: feat(<scope>): <message>"
3285
+ echo ""
3286
+ echo "\uC2A4\uD399 \uD0C0\uC785: spec, spec-update, spec-status, plan, tasks, constitution, sdd-config"
3287
+ echo "\uC77C\uBC18 \uD0C0\uC785: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
3288
+ echo ""
3289
+ echo "\uC790\uC138\uD55C \uB0B4\uC6A9: docs/guide/commit-convention.md"
3290
+ exit 1
3291
+ fi
3292
+ `;
3293
+ }
3294
+ function generatePrePushHook() {
3295
+ return `#!/bin/sh
3296
+ # SDD pre-push hook
3297
+ # \uD478\uC2DC \uC804 \uC804\uCCB4 \uAC80\uC99D\uC744 \uC218\uD589\uD569\uB2C8\uB2E4
3298
+
3299
+ # \uC0C9\uC0C1 \uC815\uC758
3300
+ RED='\\033[0;31m'
3301
+ GREEN='\\033[0;32m'
3302
+ YELLOW='\\033[1;33m'
3303
+ NC='\\033[0m'
3304
+
3305
+ echo "\${YELLOW}\u{1F50D} \uD478\uC2DC \uC804 \uAC80\uC99D \uC911...\${NC}"
3306
+
3307
+ # sdd \uBA85\uB839\uC5B4 \uD655\uC778
3308
+ if ! command -v sdd &> /dev/null; then
3309
+ echo "\${YELLOW}\u26A0\uFE0F sdd \uBA85\uB839\uC5B4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC80\uC99D\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.\${NC}"
3310
+ exit 0
3311
+ fi
3312
+
3313
+ # \uC804\uCCB4 \uC2A4\uD399 \uAC80\uC99D
3314
+ echo "\uC2A4\uD399 \uAC80\uC99D \uC911..."
3315
+ sdd validate --ci
3316
+ if [ $? -ne 0 ]; then
3317
+ echo "\${RED}\u274C \uC2A4\uD399 \uAC80\uC99D \uC2E4\uD328. \uD478\uC2DC\uAC00 \uCDE8\uC18C\uB429\uB2C8\uB2E4.\${NC}"
3318
+ exit 1
3319
+ fi
3320
+
3321
+ # Constitution \uC815\uD569\uC131 \uD655\uC778
3322
+ echo "Constitution \uC815\uD569\uC131 \uD655\uC778 \uC911..."
3323
+ sdd validate --constitution --ci 2>/dev/null
3324
+ if [ $? -ne 0 ]; then
3325
+ echo "\${YELLOW}\u26A0\uFE0F Constitution \uAC80\uC99D \uACBD\uACE0 (\uACC4\uC18D \uC9C4\uD589)\${NC}"
3326
+ fi
3327
+
3328
+ echo "\${GREEN}\u2705 \uAC80\uC99D \uC644\uB8CC\${NC}"
3329
+ exit 0
3330
+ `;
3331
+ }
3332
+ function generateGitMessageTemplate() {
3333
+ return `# <type>(<scope>): <subject>
3334
+ # |<---- 50\uC790 \uC774\uB0B4\uB85C \uC791\uC131\uD558\uC138\uC694 ---->|
3335
+ #
3336
+ # \uC2A4\uD399 \uD0C0\uC785: spec, spec-update, spec-status, plan, tasks, constitution, sdd-config
3337
+ # \uC77C\uBC18 \uD0C0\uC785: feat, fix, docs, style, refactor, test, chore
3338
+ #
3339
+ # \uC2A4\uCF54\uD504 \uC608\uC2DC:
3340
+ # spec(auth): ... - \uB3C4\uBA54\uC778 \uC804\uCCB4
3341
+ # spec(auth/user-login): ... - \uD2B9\uC815 \uC2A4\uD399
3342
+ # spec(auth,billing): ... - \uB2E4\uC911 \uB3C4\uBA54\uC778
3343
+ # constitution: ... - \uC2A4\uCF54\uD504 \uC5C6\uC74C
3344
+
3345
+ # \uBCF8\uBB38 (\uC120\uD0DD\uC0AC\uD56D, 72\uC790 \uC904\uBC14\uAFC8)
3346
+ # |<---- 72\uC790 \uC774\uB0B4\uB85C \uC791\uC131\uD558\uC138\uC694 ---->|
3347
+
3348
+ # Footer (\uC120\uD0DD\uC0AC\uD56D)
3349
+ # Refs: #\uC774\uC288\uBC88\uD638
3350
+ # Breaking-Spec: \uC601\uD5A5\uBC1B\uB294-\uC2A4\uD399
3351
+ # Depends-On: \uC758\uC874-\uC2A4\uD399
3352
+ # Reviewed-By: @\uB9AC\uBDF0\uC5B4
3353
+ `;
3354
+ }
3355
+ async function installHooks(projectPath, options = {}) {
3356
+ const gitPath = path3.join(projectPath, ".git");
3357
+ const hooksPath = path3.join(gitPath, "hooks");
3358
+ if (!await directoryExists(gitPath)) {
3359
+ return failure(new Error("Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. \uBA3C\uC800 git init\uC744 \uC2E4\uD589\uD558\uC138\uC694."));
3360
+ }
3361
+ await ensureDir(hooksPath);
3362
+ const hooks = [
3363
+ { name: "pre-commit", content: generatePreCommitHook() },
3364
+ { name: "commit-msg", content: generateCommitMsgHook() },
3365
+ { name: "pre-push", content: generatePrePushHook() }
3366
+ ];
3367
+ const result = {
3368
+ installed: [],
3369
+ skipped: [],
3370
+ backedUp: []
3371
+ };
3372
+ for (const hook of hooks) {
3373
+ const hookPath = path3.join(hooksPath, hook.name);
3374
+ const backupPath = path3.join(hooksPath, `${hook.name}.backup`);
3375
+ if (await fileExists(hookPath)) {
3376
+ if (!options.force) {
3377
+ result.skipped.push(hook.name);
3378
+ continue;
3379
+ }
3380
+ try {
3381
+ const existingContent = await fs2.readFile(hookPath, "utf-8");
3382
+ await fs2.writeFile(backupPath, existingContent);
3383
+ result.backedUp.push(hook.name);
3384
+ } catch {
3385
+ }
3386
+ }
3387
+ await fs2.writeFile(hookPath, hook.content, { mode: 493 });
3388
+ result.installed.push(hook.name);
3389
+ }
3390
+ return success(result);
3391
+ }
3392
+ async function uninstallHooks(projectPath) {
3393
+ const gitPath = path3.join(projectPath, ".git");
3394
+ const hooksPath = path3.join(gitPath, "hooks");
3395
+ if (!await directoryExists(gitPath)) {
3396
+ return failure(new Error("Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4."));
3397
+ }
3398
+ const hookNames = ["pre-commit", "commit-msg", "pre-push"];
3399
+ const removed = [];
3400
+ for (const name of hookNames) {
3401
+ const hookPath = path3.join(hooksPath, name);
3402
+ const backupPath = path3.join(hooksPath, `${name}.backup`);
3403
+ try {
3404
+ if (await fileExists(hookPath)) {
3405
+ await fs2.unlink(hookPath);
3406
+ removed.push(name);
3407
+ }
3408
+ if (await fileExists(backupPath)) {
3409
+ await fs2.rename(backupPath, hookPath);
3410
+ }
3411
+ } catch {
3412
+ }
3413
+ }
3414
+ return success(removed);
3415
+ }
3416
+ async function installTemplate(projectPath) {
3417
+ const templatePath = path3.join(projectPath, ".gitmessage");
3418
+ await writeFile(templatePath, generateGitMessageTemplate());
3419
+ let configured = false;
3420
+ try {
3421
+ const { exec: exec3 } = await import("child_process");
3422
+ const { promisify: promisify3 } = await import("util");
3423
+ const execAsync3 = promisify3(exec3);
3424
+ await execAsync3(`git config commit.template .gitmessage`, { cwd: projectPath });
3425
+ configured = true;
3426
+ } catch {
3427
+ }
3428
+ return success({
3429
+ installed: [".gitmessage"],
3430
+ configured
3431
+ });
3432
+ }
3433
+ async function setupGit(projectPath, options = {}) {
3434
+ const hooksResult = await installHooks(projectPath, options);
3435
+ if (!hooksResult.success) {
3436
+ return failure(hooksResult.error);
3437
+ }
3438
+ const templateResult = await installTemplate(projectPath);
3439
+ if (!templateResult.success) {
3440
+ return failure(templateResult.error);
3441
+ }
3442
+ return success({
3443
+ hooks: hooksResult.data,
3444
+ template: templateResult.data
3445
+ });
3446
+ }
3447
+ function registerGitCommand(program2) {
3448
+ const git = program2.command("git").description("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815 (hooks, templates)");
3449
+ git.command("hooks").description("Git hooks \uAD00\uB9AC").argument("<action>", "install \uB610\uB294 uninstall").option("-f, --force", "\uAE30\uC874 \uD6C5 \uB36E\uC5B4\uC4F0\uAE30").action(async (action, options) => {
3450
+ try {
3451
+ const cwd = process.cwd();
3452
+ if (action === "install") {
3453
+ info("Git hooks\uB97C \uC124\uCE58\uD569\uB2C8\uB2E4...");
3454
+ const result = await installHooks(cwd, options);
3455
+ if (!result.success) {
3456
+ error(result.error.message);
3457
+ process.exit(ExitCode.GENERAL_ERROR);
3458
+ }
3459
+ const { installed, skipped, backedUp } = result.data;
3460
+ if (installed.length > 0) {
3461
+ success2(`\uC124\uCE58\uB428: ${installed.join(", ")}`);
3462
+ }
3463
+ if (backedUp.length > 0) {
3464
+ info(`\uBC31\uC5C5\uB428: ${backedUp.join(", ")}`);
3465
+ }
3466
+ if (skipped.length > 0) {
3467
+ warn(`\uAC74\uB108\uB700 (\uC774\uBBF8 \uC874\uC7AC): ${skipped.join(", ")}`);
3468
+ info("\uB36E\uC5B4\uC4F0\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694.");
3469
+ }
3470
+ newline();
3471
+ info("\uC124\uCE58\uB41C \uD6C5:");
3472
+ listItem("pre-commit: \uC2A4\uD399 \uAC80\uC99D");
3473
+ listItem("commit-msg: \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD615\uC2DD \uAC80\uC99D");
3474
+ listItem("pre-push: \uD478\uC2DC \uC804 \uC804\uCCB4 \uAC80\uC99D");
3475
+ } else if (action === "uninstall") {
3476
+ info("Git hooks\uB97C \uC81C\uAC70\uD569\uB2C8\uB2E4...");
3477
+ const result = await uninstallHooks(cwd);
3478
+ if (!result.success) {
3479
+ error(result.error.message);
3480
+ process.exit(ExitCode.GENERAL_ERROR);
3481
+ }
3482
+ if (result.data.length > 0) {
3483
+ success2(`\uC81C\uAC70\uB428: ${result.data.join(", ")}`);
3484
+ } else {
3485
+ info("\uC81C\uAC70\uD560 \uD6C5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
3486
+ }
3487
+ } else {
3488
+ error(`\uC54C \uC218 \uC5C6\uB294 \uC561\uC158: ${action}`);
3489
+ info("\uC0AC\uC6A9\uBC95: sdd git hooks install|uninstall");
3490
+ process.exit(ExitCode.GENERAL_ERROR);
3491
+ }
3492
+ } catch (error2) {
3493
+ error(error2 instanceof Error ? error2.message : String(error2));
3494
+ process.exit(ExitCode.GENERAL_ERROR);
3495
+ }
3496
+ });
3497
+ git.command("template").description("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF \uC124\uCE58").argument("<action>", "install").action(async (action) => {
3498
+ try {
3499
+ if (action !== "install") {
3500
+ error(`\uC54C \uC218 \uC5C6\uB294 \uC561\uC158: ${action}`);
3501
+ info("\uC0AC\uC6A9\uBC95: sdd git template install");
3502
+ process.exit(ExitCode.GENERAL_ERROR);
3503
+ }
3504
+ const cwd = process.cwd();
3505
+ info("\uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF\uC744 \uC124\uCE58\uD569\uB2C8\uB2E4...");
3506
+ const result = await installTemplate(cwd);
3507
+ if (!result.success) {
3508
+ error(result.error.message);
3509
+ process.exit(ExitCode.GENERAL_ERROR);
3510
+ }
3511
+ success2(".gitmessage \uD30C\uC77C\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
3512
+ if (result.data.configured) {
3513
+ success2("git config commit.template\uC774 \uC124\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
3514
+ } else {
3515
+ warn("git config \uC124\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
3516
+ info("\uC218\uB3D9 \uC124\uC815: git config commit.template .gitmessage");
3517
+ }
3518
+ } catch (error2) {
3519
+ error(error2 instanceof Error ? error2.message : String(error2));
3520
+ process.exit(ExitCode.GENERAL_ERROR);
3521
+ }
3522
+ });
3523
+ git.command("setup").description("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uCCB4 \uC124\uC815 (hooks + template)").option("-f, --force", "\uAE30\uC874 \uC124\uC815 \uB36E\uC5B4\uC4F0\uAE30").action(async (options) => {
3524
+ try {
3525
+ const cwd = process.cwd();
3526
+ info("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uC124\uC815\uD569\uB2C8\uB2E4...");
3527
+ const result = await setupGit(cwd, options);
3528
+ if (!result.success) {
3529
+ error(result.error.message);
3530
+ process.exit(ExitCode.GENERAL_ERROR);
3531
+ }
3532
+ newline();
3533
+ success2("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uC815 \uC644\uB8CC!");
3534
+ newline();
3535
+ info("\uC124\uCE58\uB41C \uAD6C\uC131:");
3536
+ listItem("Git Hooks: pre-commit, commit-msg, pre-push");
3537
+ listItem(".gitmessage: \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD15C\uD50C\uB9BF");
3538
+ newline();
3539
+ info("\uB2E4\uC74C \uB2E8\uACC4:");
3540
+ listItem("spec/domain/feature \uD615\uC2DD\uC73C\uB85C \uBE0C\uB79C\uCE58 \uC0DD\uC131");
3541
+ listItem("\uCEE4\uBC0B \uC2DC \uC790\uB3D9\uC73C\uB85C \uD615\uC2DD \uAC80\uC99D");
3542
+ } catch (error2) {
3543
+ error(error2 instanceof Error ? error2.message : String(error2));
3544
+ process.exit(ExitCode.GENERAL_ERROR);
3545
+ }
3546
+ });
3547
+ }
3548
+
3549
+ // src/cli/commands/init.ts
3031
3550
  function getInitDirectories() {
3032
3551
  return [
3033
3552
  ".sdd",
@@ -3109,8 +3628,8 @@ depends: null
3109
3628
  `;
3110
3629
  }
3111
3630
  async function executeInit(projectPath, options) {
3112
- const sddPath = path2.join(projectPath, ".sdd");
3113
- const claudePath = path2.join(projectPath, ".claude");
3631
+ const sddPath = path4.join(projectPath, ".sdd");
3632
+ const claudePath = path4.join(projectPath, ".claude");
3114
3633
  if (await directoryExists(sddPath)) {
3115
3634
  if (!options.force) {
3116
3635
  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."));
@@ -3119,19 +3638,19 @@ async function executeInit(projectPath, options) {
3119
3638
  const directories = getInitDirectories();
3120
3639
  const createdDirs = [];
3121
3640
  for (const dir of directories) {
3122
- const result = await ensureDir(path2.join(projectPath, dir));
3641
+ const result = await ensureDir(path4.join(projectPath, dir));
3123
3642
  if (!result.success) {
3124
3643
  return failure(new Error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${dir}`));
3125
3644
  }
3126
3645
  createdDirs.push(dir);
3127
3646
  }
3128
3647
  const createdFiles = [];
3129
- const projectName = path2.basename(projectPath);
3648
+ const projectName = path4.basename(projectPath);
3130
3649
  const constitutionContent = generateConstitutionContent(projectName);
3131
- await writeFile(path2.join(sddPath, "constitution.md"), constitutionContent);
3650
+ await writeFile(path4.join(sddPath, "constitution.md"), constitutionContent);
3132
3651
  createdFiles.push(".sdd/constitution.md");
3133
3652
  const agentsContent = generateAgentsMd({ projectName });
3134
- await writeFile(path2.join(sddPath, "AGENTS.md"), agentsContent);
3653
+ await writeFile(path4.join(sddPath, "AGENTS.md"), agentsContent);
3135
3654
  createdFiles.push(".sdd/AGENTS.md");
3136
3655
  const templateFiles = await createTemplateFiles(projectPath);
3137
3656
  createdFiles.push(...templateFiles);
@@ -3145,24 +3664,24 @@ async function executeInit(projectPath, options) {
3145
3664
  });
3146
3665
  }
3147
3666
  async function createTemplateFiles(projectPath) {
3148
- const templatesPath = path2.join(projectPath, ".sdd", "templates");
3667
+ const templatesPath = path4.join(projectPath, ".sdd", "templates");
3149
3668
  const files = [];
3150
- await writeFile(path2.join(templatesPath, "spec.md"), generateSpecTemplate());
3669
+ await writeFile(path4.join(templatesPath, "spec.md"), generateSpecTemplate());
3151
3670
  files.push(".sdd/templates/spec.md");
3152
- await writeFile(path2.join(templatesPath, "proposal.md"), generateProposalTemplate());
3671
+ await writeFile(path4.join(templatesPath, "proposal.md"), generateProposalTemplate());
3153
3672
  files.push(".sdd/templates/proposal.md");
3154
- await writeFile(path2.join(templatesPath, "delta.md"), generateDeltaTemplate());
3673
+ await writeFile(path4.join(templatesPath, "delta.md"), generateDeltaTemplate());
3155
3674
  files.push(".sdd/templates/delta.md");
3156
- await writeFile(path2.join(templatesPath, "tasks.md"), generateTasksTemplate());
3675
+ await writeFile(path4.join(templatesPath, "tasks.md"), generateTasksTemplate());
3157
3676
  files.push(".sdd/templates/tasks.md");
3158
3677
  return files;
3159
3678
  }
3160
3679
  async function createCommandFiles(projectPath) {
3161
- const commandsPath = path2.join(projectPath, ".claude", "commands");
3680
+ const commandsPath = path4.join(projectPath, ".claude", "commands");
3162
3681
  const files = [];
3163
3682
  const commands = generateClaudeCommands();
3164
3683
  for (const cmd of commands) {
3165
- await writeFile(path2.join(commandsPath, `${cmd.name}.md`), cmd.content);
3684
+ await writeFile(path4.join(commandsPath, `${cmd.name}.md`), cmd.content);
3166
3685
  files.push(`.claude/commands/${cmd.name}.md`);
3167
3686
  }
3168
3687
  return files;
@@ -3321,8 +3840,194 @@ graph LR
3321
3840
  | [US] | \uBD88\uD655\uC2E4/\uAC80\uD1A0 \uD544\uC694 |
3322
3841
  `;
3323
3842
  }
3843
+ async function askYesNo(question) {
3844
+ const rl = readline.createInterface({
3845
+ input: process.stdin,
3846
+ output: process.stdout
3847
+ });
3848
+ return new Promise((resolve) => {
3849
+ rl.question(`${question} (y/n): `, (answer) => {
3850
+ rl.close();
3851
+ const normalized = answer.trim().toLowerCase();
3852
+ resolve(normalized === "y" || normalized === "yes" || normalized === "\uC608");
3853
+ });
3854
+ });
3855
+ }
3856
+ async function promptGitSetup(projectPath, autoApprove) {
3857
+ newline();
3858
+ info("\u{1F50D} \uD504\uB85C\uC81D\uD2B8 \uAD6C\uC870\uB97C \uBD84\uC11D\uD569\uB2C8\uB2E4...");
3859
+ newline();
3860
+ const analysis = await analyzeProject(projectPath);
3861
+ const suggestions = generateSuggestions(analysis);
3862
+ console.log(formatAnalysis(analysis));
3863
+ newline();
3864
+ if (!analysis.isGitRepo) {
3865
+ warn("Git \uC800\uC7A5\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4.");
3866
+ info("Git \uC124\uC815\uC744 \uD65C\uC131\uD654\uD558\uB824\uBA74:");
3867
+ listItem("git init");
3868
+ listItem("sdd git setup");
3869
+ return;
3870
+ }
3871
+ if (!suggestions.suggestGitHooks && !suggestions.suggestGitTemplate && !suggestions.suggestGitHubActions) {
3872
+ success2("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0\uAC00 \uC774\uBBF8 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4!");
3873
+ return;
3874
+ }
3875
+ info("\u{1F4CB} \uAD8C\uC7A5 \uC124\uC815:");
3876
+ if (suggestions.suggestGitHooks) {
3877
+ listItem("Git Hooks: \uCEE4\uBC0B/\uD478\uC2DC \uC2DC \uC790\uB3D9 \uC2A4\uD399 \uAC80\uC99D");
3878
+ }
3879
+ if (suggestions.suggestGitTemplate) {
3880
+ listItem("\uCEE4\uBC0B \uD15C\uD50C\uB9BF: \uC77C\uAD00\uB41C \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uD615\uC2DD");
3881
+ }
3882
+ if (suggestions.suggestGitHubActions) {
3883
+ listItem("GitHub Actions: PR \uC2DC \uC790\uB3D9 \uAC80\uC99D \uBC0F \uB77C\uBCA8\uB9C1");
3884
+ }
3885
+ newline();
3886
+ if (suggestions.suggestGitHooks || suggestions.suggestGitTemplate) {
3887
+ const setupGitWorkflow = autoApprove || await askYesNo("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0(Hooks + \uD15C\uD50C\uB9BF)\uB97C \uC124\uCE58\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?");
3888
+ if (setupGitWorkflow) {
3889
+ info("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uC124\uCE58\uD569\uB2C8\uB2E4...");
3890
+ const result = await setupGit(projectPath, { force: false });
3891
+ if (result.success) {
3892
+ success2("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uCE58 \uC644\uB8CC!");
3893
+ if (result.data.hooks.installed.length > 0) {
3894
+ listItem(`Hooks: ${result.data.hooks.installed.join(", ")}`);
3895
+ }
3896
+ if (result.data.template.installed.length > 0) {
3897
+ listItem(`\uD15C\uD50C\uB9BF: ${result.data.template.installed.join(", ")}`);
3898
+ }
3899
+ } else {
3900
+ warn("Git \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC124\uCE58 \uC2E4\uD328: " + result.error.message);
3901
+ }
3902
+ newline();
3903
+ }
3904
+ }
3905
+ if (suggestions.suggestGitHubActions) {
3906
+ const setupCicd = autoApprove || await askYesNo("GitHub Actions CI/CD\uB97C \uC124\uC815\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?");
3907
+ if (setupCicd) {
3908
+ info("GitHub Actions\uB97C \uC124\uC815\uD569\uB2C8\uB2E4...");
3909
+ try {
3910
+ const workflowDir = path4.join(projectPath, ".github", "workflows");
3911
+ await ensureDir(workflowDir);
3912
+ const validateContent = generateGitHubValidateWorkflow();
3913
+ await writeFile(path4.join(workflowDir, "sdd-validate.yml"), validateContent);
3914
+ success2("sdd-validate.yml \uC0DD\uC131 \uC644\uB8CC");
3915
+ const labelerContent = generateGitHubLabelerWorkflow();
3916
+ await writeFile(path4.join(workflowDir, "sdd-labeler.yml"), labelerContent);
3917
+ success2("sdd-labeler.yml \uC0DD\uC131 \uC644\uB8CC");
3918
+ } catch (error2) {
3919
+ warn("GitHub Actions \uC124\uC815 \uC2E4\uD328: " + (error2 instanceof Error ? error2.message : String(error2)));
3920
+ }
3921
+ newline();
3922
+ }
3923
+ }
3924
+ }
3925
+ function generateGitHubValidateWorkflow() {
3926
+ return `# SDD \uC2A4\uD399 \uAC80\uC99D \uC6CC\uD06C\uD50C\uB85C\uC6B0
3927
+ # \uC774 \uD30C\uC77C\uC740 sdd init\uC73C\uB85C \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.
3928
+
3929
+ name: SDD Validation
3930
+
3931
+ on:
3932
+ push:
3933
+ branches: [main, master, develop]
3934
+ paths:
3935
+ - '.sdd/**'
3936
+ pull_request:
3937
+ branches: [main, master, develop]
3938
+ paths:
3939
+ - '.sdd/**'
3940
+
3941
+ jobs:
3942
+ validate:
3943
+ name: Validate Specs
3944
+ runs-on: ubuntu-latest
3945
+
3946
+ steps:
3947
+ - name: Checkout repository
3948
+ uses: actions/checkout@v4
3949
+
3950
+ - name: Setup Node.js
3951
+ uses: actions/setup-node@v4
3952
+ with:
3953
+ node-version: '20'
3954
+ cache: 'npm'
3955
+
3956
+ - name: Install dependencies
3957
+ run: npm ci
3958
+
3959
+ - name: Install SDD Tool
3960
+ run: npm install -g sdd-tool
3961
+
3962
+ - name: Validate specifications
3963
+ run: sdd validate
3964
+ `;
3965
+ }
3966
+ function generateGitHubLabelerWorkflow() {
3967
+ return `# SDD PR \uB77C\uBCA8\uB7EC \uC6CC\uD06C\uD50C\uB85C\uC6B0
3968
+ # \uBCC0\uACBD\uB41C \uB3C4\uBA54\uC778\uC5D0 \uB530\uB77C \uC790\uB3D9\uC73C\uB85C \uB77C\uBCA8\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4
3969
+
3970
+ name: SDD Labeler
3971
+
3972
+ on:
3973
+ pull_request:
3974
+ types: [opened, synchronize]
3975
+ paths:
3976
+ - '.sdd/**'
3977
+
3978
+ jobs:
3979
+ label:
3980
+ name: Add Labels
3981
+ runs-on: ubuntu-latest
3982
+
3983
+ steps:
3984
+ - name: Checkout
3985
+ uses: actions/checkout@v4
3986
+ with:
3987
+ fetch-depth: 0
3988
+
3989
+ - name: Detect Changes
3990
+ id: changes
3991
+ run: |
3992
+ # \uBCC0\uACBD\uB41C \uB3C4\uBA54\uC778 \uAC10\uC9C0
3993
+ DOMAINS=$(git diff --name-only origin/\${{ github.base_ref }} | \\
3994
+ grep "^\\.sdd/specs/" | \\
3995
+ cut -d'/' -f3 | \\
3996
+ sort -u | \\
3997
+ tr '\\n' ' ')
3998
+ echo "domains=$DOMAINS" >> $GITHUB_OUTPUT
3999
+
4000
+ # Constitution \uBCC0\uACBD \uAC10\uC9C0
4001
+ if git diff --name-only origin/\${{ github.base_ref }} | grep -q "constitution.md"; then
4002
+ echo "constitution=true" >> $GITHUB_OUTPUT
4003
+ else
4004
+ echo "constitution=false" >> $GITHUB_OUTPUT
4005
+ fi
4006
+
4007
+ - name: Apply Labels
4008
+ uses: actions/github-script@v7
4009
+ with:
4010
+ script: |
4011
+ const labels = [];
4012
+ const domains = '\${{ steps.changes.outputs.domains }}'.trim().split(' ').filter(Boolean);
4013
+ labels.push(...domains.map(d => \`spec:\${d}\`));
4014
+
4015
+ if ('\${{ steps.changes.outputs.constitution }}' === 'true') {
4016
+ labels.push('constitution');
4017
+ }
4018
+
4019
+ if (labels.length > 0) {
4020
+ await github.rest.issues.addLabels({
4021
+ issue_number: context.issue.number,
4022
+ owner: context.repo.owner,
4023
+ repo: context.repo.repo,
4024
+ labels: labels,
4025
+ });
4026
+ }
4027
+ `;
4028
+ }
3324
4029
  function registerInitCommand(program2) {
3325
- 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) => {
4030
+ 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").option("--skip-git-setup", "Git/CI-CD \uC124\uC815 \uAC74\uB108\uB6F0\uAE30").option("--auto-approve", "\uBAA8\uB4E0 \uC124\uC815\uC744 \uC790\uB3D9 \uC2B9\uC778").action(async (options) => {
3326
4031
  try {
3327
4032
  await runInit(options);
3328
4033
  } catch (error2) {
@@ -3333,7 +4038,7 @@ function registerInitCommand(program2) {
3333
4038
  }
3334
4039
  async function runInit(options) {
3335
4040
  const cwd = process.cwd();
3336
- if (await directoryExists(path2.join(cwd, ".sdd"))) {
4041
+ if (await directoryExists(path4.join(cwd, ".sdd"))) {
3337
4042
  if (options.force) {
3338
4043
  warn("\uAE30\uC874 .sdd/ \uB514\uB809\uD1A0\uB9AC\uB97C \uB36E\uC5B4\uC501\uB2C8\uB2E4.");
3339
4044
  }
@@ -3367,6 +4072,9 @@ async function runInit(options) {
3367
4072
  listItem("/sdd.validate - \uC2A4\uD399 \uAC80\uC99D");
3368
4073
  listItem("/sdd.status - \uC0C1\uD0DC \uD655\uC778");
3369
4074
  listItem("/sdd.change - \uBCC0\uACBD \uC81C\uC548");
4075
+ if (!options.skipGitSetup) {
4076
+ await promptGitSetup(cwd, options.autoApprove || false);
4077
+ }
3370
4078
  newline();
3371
4079
  info("\uB2E4\uC74C \uB2E8\uACC4:");
3372
4080
  listItem("constitution.md\uB97C \uC218\uC815\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uC6D0\uCE59\uC744 \uC815\uC758\uD558\uC138\uC694");
@@ -3374,11 +4082,11 @@ async function runInit(options) {
3374
4082
  }
3375
4083
 
3376
4084
  // src/cli/commands/validate.ts
3377
- import path4 from "path";
4085
+ import path6 from "path";
3378
4086
  import chalk2 from "chalk";
3379
4087
 
3380
4088
  // src/core/spec/validator.ts
3381
- import path3 from "path";
4089
+ import path5 from "path";
3382
4090
 
3383
4091
  // src/core/spec/parser.ts
3384
4092
  import matter from "gray-matter";
@@ -4058,7 +4766,7 @@ async function validateSpecFile(filePath, options = {}) {
4058
4766
  }
4059
4767
  }
4060
4768
  if (options.checkConstitution && options.sddRoot) {
4061
- const constitutionPath = path3.join(options.sddRoot, ".sdd", "constitution.md");
4769
+ const constitutionPath = path5.join(options.sddRoot, ".sdd", "constitution.md");
4062
4770
  const constReadResult = await readFile(constitutionPath);
4063
4771
  if (constReadResult.success) {
4064
4772
  const constParseResult = parseConstitution(constReadResult.data);
@@ -4120,7 +4828,7 @@ async function validateSpecFile(filePath, options = {}) {
4120
4828
  async function validateLinks(content, filePath, specsRoot) {
4121
4829
  const brokenLinks = [];
4122
4830
  const lines = content.split("\n");
4123
- const fileDir = path3.dirname(filePath);
4831
+ const fileDir = path5.dirname(filePath);
4124
4832
  const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
4125
4833
  const specRefPattern = /(?:`([a-z0-9-]+)`|\[\[([a-z0-9-]+)\]\])/g;
4126
4834
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
@@ -4131,7 +4839,7 @@ async function validateLinks(content, filePath, specsRoot) {
4131
4839
  if (target.startsWith("http://") || target.startsWith("https://") || target.startsWith("#")) {
4132
4840
  continue;
4133
4841
  }
4134
- const targetPath = path3.resolve(fileDir, target);
4842
+ const targetPath = path5.resolve(fileDir, target);
4135
4843
  if (!await fileExists(targetPath)) {
4136
4844
  brokenLinks.push({
4137
4845
  text,
@@ -4150,8 +4858,8 @@ async function validateLinks(content, filePath, specsRoot) {
4150
4858
  if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(specId)) {
4151
4859
  continue;
4152
4860
  }
4153
- const specPath = path3.join(specsRoot, specId);
4154
- const specFilePath = path3.join(specPath, "spec.md");
4861
+ const specPath = path5.join(specsRoot, specId);
4862
+ const specFilePath = path5.join(specPath, "spec.md");
4155
4863
  if (!await directoryExists(specPath) && !await fileExists(specFilePath)) {
4156
4864
  brokenLinks.push({
4157
4865
  text: specId,
@@ -4204,11 +4912,11 @@ async function validateSpecs(targetPath, options = {}) {
4204
4912
  async function findSpecFiles(dirPath) {
4205
4913
  const files = [];
4206
4914
  async function scanDir(dir) {
4207
- const { promises: fs27 } = await import("fs");
4915
+ const { promises: fs28 } = await import("fs");
4208
4916
  try {
4209
- const entries = await fs27.readdir(dir, { withFileTypes: true });
4917
+ const entries = await fs28.readdir(dir, { withFileTypes: true });
4210
4918
  for (const entry of entries) {
4211
- const fullPath = path3.join(dir, entry.name);
4919
+ const fullPath = path5.join(dir, entry.name);
4212
4920
  if (entry.isDirectory()) {
4213
4921
  await scanDir(fullPath);
4214
4922
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -4239,20 +4947,20 @@ async function createValidateContext(targetPath, options, sddRoot) {
4239
4947
  let resolvedPath;
4240
4948
  let specsRoot;
4241
4949
  if (targetPath) {
4242
- resolvedPath = path4.resolve(targetPath);
4950
+ resolvedPath = path6.resolve(targetPath);
4243
4951
  } else {
4244
4952
  if (!sddRoot) {
4245
4953
  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."));
4246
4954
  }
4247
- resolvedPath = path4.join(sddRoot, ".sdd", "specs");
4955
+ resolvedPath = path6.join(sddRoot, ".sdd", "specs");
4248
4956
  }
4249
4957
  if (options.checkLinks && sddRoot) {
4250
- specsRoot = path4.join(sddRoot, ".sdd", "specs");
4958
+ specsRoot = path6.join(sddRoot, ".sdd", "specs");
4251
4959
  }
4252
4960
  const checkConstitution = options.constitution !== false;
4253
4961
  let hasConstitution = false;
4254
4962
  if (checkConstitution && sddRoot) {
4255
- const constitutionPath = path4.join(sddRoot, ".sdd", "constitution.md");
4963
+ const constitutionPath = path6.join(sddRoot, ".sdd", "constitution.md");
4256
4964
  hasConstitution = await fileExists(constitutionPath);
4257
4965
  }
4258
4966
  return success({
@@ -4329,7 +5037,7 @@ async function runValidate(targetPath, options) {
4329
5037
  }
4330
5038
  }
4331
5039
  function printFileResult(result, basePath) {
4332
- const relativePath = path4.relative(basePath, result.file);
5040
+ const relativePath = path6.relative(basePath, result.file);
4333
5041
  if (result.valid) {
4334
5042
  console.log(chalk2.green("\u2713") + " " + relativePath);
4335
5043
  } else {
@@ -4957,8 +5665,8 @@ async function runPrompt(command, options) {
4957
5665
  }
4958
5666
 
4959
5667
  // src/cli/commands/change.ts
4960
- import path6 from "path";
4961
- import { promises as fs3 } from "fs";
5668
+ import path8 from "path";
5669
+ import { promises as fs4 } from "fs";
4962
5670
 
4963
5671
  // src/core/change/schemas.ts
4964
5672
  import { z as z3 } from "zod";
@@ -5372,32 +6080,32 @@ function validateDelta(content) {
5372
6080
  init_types();
5373
6081
  init_errors();
5374
6082
  init_fs();
5375
- import path5 from "path";
5376
- import { promises as fs2 } from "fs";
6083
+ import path7 from "path";
6084
+ import { promises as fs3 } from "fs";
5377
6085
  async function archiveChange(sddPath, changeId) {
5378
6086
  try {
5379
- const changesPath = path5.join(sddPath, "changes");
5380
- const archivePath = path5.join(sddPath, "archive");
5381
- const sourceDir = path5.join(changesPath, changeId);
6087
+ const changesPath = path7.join(sddPath, "changes");
6088
+ const archivePath = path7.join(sddPath, "archive");
6089
+ const sourceDir = path7.join(changesPath, changeId);
5382
6090
  if (!await directoryExists(sourceDir)) {
5383
6091
  return failure(new ChangeError(`\uBCC0\uACBD \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${changeId}`));
5384
6092
  }
5385
6093
  const today = /* @__PURE__ */ new Date();
5386
6094
  const yearMonth = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, "0")}`;
5387
- const archiveMonthDir = path5.join(archivePath, yearMonth);
6095
+ const archiveMonthDir = path7.join(archivePath, yearMonth);
5388
6096
  await ensureDir(archiveMonthDir);
5389
6097
  const datePrefix = today.toISOString().split("T")[0];
5390
- const archiveDir = path5.join(archiveMonthDir, `${datePrefix}-${changeId}`);
6098
+ const archiveDir = path7.join(archiveMonthDir, `${datePrefix}-${changeId}`);
5391
6099
  const copyResult = await copyDir(sourceDir, archiveDir);
5392
6100
  if (!copyResult.success) {
5393
6101
  return failure(new ChangeError(`\uC544\uCE74\uC774\uBE0C \uBCF5\uC0AC \uC2E4\uD328: ${copyResult.error?.message}`));
5394
6102
  }
5395
- const proposalPath = path5.join(archiveDir, "proposal.md");
6103
+ const proposalPath = path7.join(archiveDir, "proposal.md");
5396
6104
  try {
5397
- const proposalContent = await fs2.readFile(proposalPath, "utf-8");
6105
+ const proposalContent = await fs3.readFile(proposalPath, "utf-8");
5398
6106
  const updateResult = updateProposalStatus(proposalContent, "archived");
5399
6107
  if (updateResult.success) {
5400
- await fs2.writeFile(proposalPath, updateResult.data);
6108
+ await fs3.writeFile(proposalPath, updateResult.data);
5401
6109
  }
5402
6110
  } catch {
5403
6111
  }
@@ -5421,20 +6129,20 @@ async function archiveChange(sddPath, changeId) {
5421
6129
  }
5422
6130
  async function listArchives(sddPath) {
5423
6131
  try {
5424
- const archivePath = path5.join(sddPath, "archive");
6132
+ const archivePath = path7.join(sddPath, "archive");
5425
6133
  if (!await directoryExists(archivePath)) {
5426
6134
  return success([]);
5427
6135
  }
5428
6136
  const archives = [];
5429
- const months = await fs2.readdir(archivePath);
6137
+ const months = await fs3.readdir(archivePath);
5430
6138
  for (const month of months) {
5431
- const monthPath = path5.join(archivePath, month);
5432
- const stat = await fs2.stat(monthPath);
6139
+ const monthPath = path7.join(archivePath, month);
6140
+ const stat = await fs3.stat(monthPath);
5433
6141
  if (!stat.isDirectory()) continue;
5434
- const changes = await fs2.readdir(monthPath);
6142
+ const changes = await fs3.readdir(monthPath);
5435
6143
  for (const change of changes) {
5436
- const changePath = path5.join(monthPath, change);
5437
- const changeStat = await fs2.stat(changePath);
6144
+ const changePath = path7.join(monthPath, change);
6145
+ const changeStat = await fs3.stat(changePath);
5438
6146
  if (!changeStat.isDirectory()) continue;
5439
6147
  const idMatch = change.match(/\d{4}-\d{2}-\d{2}-(CHG-\d+)/);
5440
6148
  const id = idMatch ? idMatch[1] : change;
@@ -5442,8 +6150,8 @@ async function listArchives(sddPath) {
5442
6150
  const archivedAt = dateMatch ? dateMatch[1] : month;
5443
6151
  let title2;
5444
6152
  try {
5445
- const proposalPath = path5.join(changePath, "proposal.md");
5446
- const proposalContent = await fs2.readFile(proposalPath, "utf-8");
6153
+ const proposalPath = path7.join(changePath, "proposal.md");
6154
+ const proposalContent = await fs3.readFile(proposalPath, "utf-8");
5447
6155
  const parseResult = parseProposal(proposalContent);
5448
6156
  if (parseResult.success) {
5449
6157
  title2 = parseResult.data.title;
@@ -5470,22 +6178,22 @@ async function listArchives(sddPath) {
5470
6178
  }
5471
6179
  async function listPendingChanges(sddPath) {
5472
6180
  try {
5473
- const changesPath = path5.join(sddPath, "changes");
6181
+ const changesPath = path7.join(sddPath, "changes");
5474
6182
  if (!await directoryExists(changesPath)) {
5475
6183
  return success([]);
5476
6184
  }
5477
6185
  const changes = [];
5478
- const dirs = await fs2.readdir(changesPath);
6186
+ const dirs = await fs3.readdir(changesPath);
5479
6187
  for (const dir of dirs) {
5480
- const changePath = path5.join(changesPath, dir);
5481
- const stat = await fs2.stat(changePath);
6188
+ const changePath = path7.join(changesPath, dir);
6189
+ const stat = await fs3.stat(changePath);
5482
6190
  if (!stat.isDirectory()) continue;
5483
6191
  let status = "draft";
5484
6192
  let title2;
5485
6193
  let createdAt;
5486
6194
  try {
5487
- const proposalPath = path5.join(changePath, "proposal.md");
5488
- const proposalContent = await fs2.readFile(proposalPath, "utf-8");
6195
+ const proposalPath = path7.join(changePath, "proposal.md");
6196
+ const proposalContent = await fs3.readFile(proposalPath, "utf-8");
5489
6197
  const parseResult = parseProposal(proposalContent);
5490
6198
  if (parseResult.success) {
5491
6199
  status = parseResult.data.metadata.status;
@@ -5529,7 +6237,7 @@ async function getChangeListItems(sddPath) {
5529
6237
  })));
5530
6238
  }
5531
6239
  async function getChangeInfo(changePath) {
5532
- const proposalPath = path6.join(changePath, "proposal.md");
6240
+ const proposalPath = path8.join(changePath, "proposal.md");
5533
6241
  if (!await fileExists(proposalPath)) {
5534
6242
  return failure(new Error("proposal.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5535
6243
  }
@@ -5550,31 +6258,31 @@ async function getChangeInfo(changePath) {
5550
6258
  });
5551
6259
  }
5552
6260
  async function createChange(sddPath, options) {
5553
- const changesPath = path6.join(sddPath, "changes");
6261
+ const changesPath = path8.join(sddPath, "changes");
5554
6262
  await ensureDir(changesPath);
5555
6263
  const existingIds = [];
5556
6264
  try {
5557
- const dirs = await fs3.readdir(changesPath);
6265
+ const dirs = await fs4.readdir(changesPath);
5558
6266
  existingIds.push(...dirs.filter((d) => d.startsWith("CHG-")));
5559
6267
  } catch {
5560
6268
  }
5561
6269
  const newId = generateChangeId(existingIds);
5562
6270
  const title2 = options.title || "\uC0C8 \uBCC0\uACBD \uC81C\uC548";
5563
6271
  const affectedSpecs = options.spec ? [options.spec] : [];
5564
- const changePath = path6.join(changesPath, newId);
6272
+ const changePath = path8.join(changesPath, newId);
5565
6273
  await ensureDir(changePath);
5566
6274
  const proposal = generateProposal({
5567
6275
  id: newId,
5568
6276
  title: title2,
5569
6277
  affectedSpecs
5570
6278
  });
5571
- const proposalPath = path6.join(changePath, "proposal.md");
6279
+ const proposalPath = path8.join(changePath, "proposal.md");
5572
6280
  await writeFile(proposalPath, proposal);
5573
6281
  const delta = generateDelta({
5574
6282
  proposalId: newId,
5575
6283
  title: title2
5576
6284
  });
5577
- const deltaPath = path6.join(changePath, "delta.md");
6285
+ const deltaPath = path8.join(changePath, "delta.md");
5578
6286
  await writeFile(deltaPath, delta);
5579
6287
  return success({
5580
6288
  id: newId,
@@ -5583,7 +6291,7 @@ async function createChange(sddPath, options) {
5583
6291
  });
5584
6292
  }
5585
6293
  async function applyChange(changePath) {
5586
- const proposalPath = path6.join(changePath, "proposal.md");
6294
+ const proposalPath = path8.join(changePath, "proposal.md");
5587
6295
  if (!await fileExists(proposalPath)) {
5588
6296
  return failure(new Error("proposal.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5589
6297
  }
@@ -5599,7 +6307,7 @@ async function applyChange(changePath) {
5599
6307
  return success(void 0);
5600
6308
  }
5601
6309
  async function getDeltaInfo(changePath) {
5602
- const deltaPath = path6.join(changePath, "delta.md");
6310
+ const deltaPath = path8.join(changePath, "delta.md");
5603
6311
  if (!await fileExists(deltaPath)) {
5604
6312
  return failure(new Error("delta.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
5605
6313
  }
@@ -5623,7 +6331,7 @@ async function validateChange(changePath) {
5623
6331
  deltaValid: false,
5624
6332
  hasDelta: false
5625
6333
  };
5626
- const proposalPath = path6.join(changePath, "proposal.md");
6334
+ const proposalPath = path8.join(changePath, "proposal.md");
5627
6335
  if (await fileExists(proposalPath)) {
5628
6336
  const proposalResult = await readFile(proposalPath);
5629
6337
  if (proposalResult.success) {
@@ -5638,7 +6346,7 @@ async function validateChange(changePath) {
5638
6346
  } else {
5639
6347
  result.proposalError = "proposal.md\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.";
5640
6348
  }
5641
- const deltaPath = path6.join(changePath, "delta.md");
6349
+ const deltaPath = path8.join(changePath, "delta.md");
5642
6350
  if (await fileExists(deltaPath)) {
5643
6351
  result.hasDelta = true;
5644
6352
  const deltaResult = await readFile(deltaPath);
@@ -5707,7 +6415,7 @@ async function runChange(id, options) {
5707
6415
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
5708
6416
  process.exit(ExitCode.GENERAL_ERROR);
5709
6417
  }
5710
- const sddPath = path6.join(projectRoot, ".sdd");
6418
+ const sddPath = path8.join(projectRoot, ".sdd");
5711
6419
  if (options.list) {
5712
6420
  const result = await getChangeListItems(sddPath);
5713
6421
  if (!result.success) {
@@ -5727,7 +6435,7 @@ async function runChange(id, options) {
5727
6435
  return;
5728
6436
  }
5729
6437
  if (id) {
5730
- const changePath = path6.join(sddPath, "changes", id);
6438
+ const changePath = path8.join(sddPath, "changes", id);
5731
6439
  if (!await directoryExists(changePath)) {
5732
6440
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
5733
6441
  process.exit(ExitCode.GENERAL_ERROR);
@@ -5769,8 +6477,8 @@ async function runApply(id) {
5769
6477
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5770
6478
  process.exit(ExitCode.GENERAL_ERROR);
5771
6479
  }
5772
- const sddPath = path6.join(projectRoot, ".sdd");
5773
- const changePath = path6.join(sddPath, "changes", id);
6480
+ const sddPath = path8.join(projectRoot, ".sdd");
6481
+ const changePath = path8.join(sddPath, "changes", id);
5774
6482
  if (!await directoryExists(changePath)) {
5775
6483
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
5776
6484
  process.exit(ExitCode.GENERAL_ERROR);
@@ -5792,7 +6500,7 @@ async function runArchive(id) {
5792
6500
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5793
6501
  process.exit(ExitCode.GENERAL_ERROR);
5794
6502
  }
5795
- const sddPath = path6.join(projectRoot, ".sdd");
6503
+ const sddPath = path8.join(projectRoot, ".sdd");
5796
6504
  const result = await archiveChange(sddPath, id);
5797
6505
  if (!result.success) {
5798
6506
  error(result.error.message);
@@ -5807,8 +6515,8 @@ async function runDiff(id) {
5807
6515
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5808
6516
  process.exit(ExitCode.GENERAL_ERROR);
5809
6517
  }
5810
- const sddPath = path6.join(projectRoot, ".sdd");
5811
- const changePath = path6.join(sddPath, "changes", id);
6518
+ const sddPath = path8.join(projectRoot, ".sdd");
6519
+ const changePath = path8.join(sddPath, "changes", id);
5812
6520
  if (!await directoryExists(changePath)) {
5813
6521
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
5814
6522
  process.exit(ExitCode.GENERAL_ERROR);
@@ -5860,8 +6568,8 @@ async function runValidateChange(id) {
5860
6568
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5861
6569
  process.exit(ExitCode.GENERAL_ERROR);
5862
6570
  }
5863
- const sddPath = path6.join(projectRoot, ".sdd");
5864
- const changePath = path6.join(sddPath, "changes", id);
6571
+ const sddPath = path8.join(projectRoot, ".sdd");
6572
+ const changePath = path8.join(sddPath, "changes", id);
5865
6573
  if (!await directoryExists(changePath)) {
5866
6574
  error(`\uBCC0\uACBD\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${id}`);
5867
6575
  process.exit(ExitCode.GENERAL_ERROR);
@@ -5909,7 +6617,7 @@ async function runValidateChange(id) {
5909
6617
  }
5910
6618
 
5911
6619
  // src/cli/commands/impact.ts
5912
- import path10 from "path";
6620
+ import path12 from "path";
5913
6621
 
5914
6622
  // src/core/impact/schemas.ts
5915
6623
  import { z as z6 } from "zod";
@@ -5948,8 +6656,8 @@ function getImpactLevel(score) {
5948
6656
  init_types();
5949
6657
  init_errors();
5950
6658
  init_fs();
5951
- import { promises as fs4 } from "fs";
5952
- import path7 from "path";
6659
+ import { promises as fs5 } from "fs";
6660
+ import path9 from "path";
5953
6661
  import matter5 from "gray-matter";
5954
6662
  async function buildDependencyGraph(specsPath) {
5955
6663
  try {
@@ -5962,8 +6670,8 @@ async function buildDependencyGraph(specsPath) {
5962
6670
  }
5963
6671
  const specFiles = await collectSpecFiles(specsPath);
5964
6672
  for (const filePath of specFiles) {
5965
- const content = await fs4.readFile(filePath, "utf-8");
5966
- const relativePath = path7.relative(specsPath, filePath);
6673
+ const content = await fs5.readFile(filePath, "utf-8");
6674
+ const relativePath = path9.relative(specsPath, filePath);
5967
6675
  const specId = getSpecId(relativePath);
5968
6676
  const node = {
5969
6677
  id: specId,
@@ -5987,7 +6695,7 @@ async function buildDependencyGraph(specsPath) {
5987
6695
  }
5988
6696
  }
5989
6697
  }
5990
- const references = extractReferences(content, specFiles.map((f) => getSpecId(path7.relative(specsPath, f))));
6698
+ const references = extractReferences(content, specFiles.map((f) => getSpecId(path9.relative(specsPath, f))));
5991
6699
  for (const ref of references) {
5992
6700
  if (ref !== specId && !node.dependsOn.includes(ref)) {
5993
6701
  node.dependsOn.push(ref);
@@ -6018,9 +6726,9 @@ async function buildDependencyGraph(specsPath) {
6018
6726
  }
6019
6727
  async function collectSpecFiles(dirPath) {
6020
6728
  const files = [];
6021
- const entries = await fs4.readdir(dirPath, { withFileTypes: true });
6729
+ const entries = await fs5.readdir(dirPath, { withFileTypes: true });
6022
6730
  for (const entry of entries) {
6023
- const fullPath = path7.join(dirPath, entry.name);
6731
+ const fullPath = path9.join(dirPath, entry.name);
6024
6732
  if (entry.isDirectory()) {
6025
6733
  files.push(...await collectSpecFiles(fullPath));
6026
6734
  } else if (entry.name.endsWith(".md") && entry.name !== "AGENTS.md") {
@@ -6084,13 +6792,13 @@ function sanitizeId(id) {
6084
6792
  }
6085
6793
 
6086
6794
  // src/core/impact/analyzer.ts
6087
- import path8 from "path";
6795
+ import path10 from "path";
6088
6796
  init_types();
6089
6797
  init_errors();
6090
6798
  init_fs();
6091
6799
  async function analyzeImpact(sddPath, targetSpec) {
6092
6800
  try {
6093
- const specsPath = path8.join(sddPath, "specs");
6801
+ const specsPath = path10.join(sddPath, "specs");
6094
6802
  if (!await directoryExists(specsPath)) {
6095
6803
  return failure(new ChangeError("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
6096
6804
  }
@@ -6286,7 +6994,7 @@ function formatImpactResult(result) {
6286
6994
  }
6287
6995
  async function generateImpactReport(sddPath) {
6288
6996
  try {
6289
- const specsPath = path8.join(sddPath, "specs");
6997
+ const specsPath = path10.join(sddPath, "specs");
6290
6998
  if (!await directoryExists(specsPath)) {
6291
6999
  return failure(new ChangeError("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
6292
7000
  }
@@ -6330,19 +7038,19 @@ function detectCircularDependencies(graph) {
6330
7038
  const cycles = [];
6331
7039
  const visited = /* @__PURE__ */ new Set();
6332
7040
  const recStack = /* @__PURE__ */ new Set();
6333
- function dfs(nodeId, path42) {
7041
+ function dfs(nodeId, path44) {
6334
7042
  visited.add(nodeId);
6335
7043
  recStack.add(nodeId);
6336
7044
  const node = graph.nodes.get(nodeId);
6337
7045
  if (!node) return false;
6338
7046
  for (const depId of node.dependsOn) {
6339
7047
  if (!visited.has(depId)) {
6340
- if (dfs(depId, [...path42, nodeId])) {
7048
+ if (dfs(depId, [...path44, nodeId])) {
6341
7049
  return true;
6342
7050
  }
6343
7051
  } else if (recStack.has(depId)) {
6344
- const cycleStart = path42.indexOf(depId);
6345
- const cycle = cycleStart >= 0 ? [...path42.slice(cycleStart), nodeId, depId] : [nodeId, depId];
7052
+ const cycleStart = path44.indexOf(depId);
7053
+ const cycle = cycleStart >= 0 ? [...path44.slice(cycleStart), nodeId, depId] : [nodeId, depId];
6346
7054
  cycles.push({
6347
7055
  cycle,
6348
7056
  description: `\uC21C\uD658 \uC758\uC874\uC131: ${cycle.join(" \u2192 ")}`
@@ -6388,8 +7096,8 @@ function generateReportSummary(totalSpecs, totalEdges, orphanCount, circularCoun
6388
7096
  }
6389
7097
  async function analyzeChangeImpact(sddPath, changeId) {
6390
7098
  try {
6391
- const changePath = path8.join(sddPath, "changes", changeId);
6392
- const proposalPath = path8.join(changePath, "proposal.md");
7099
+ const changePath = path10.join(sddPath, "changes", changeId);
7100
+ const proposalPath = path10.join(changePath, "proposal.md");
6393
7101
  if (!await fileExists(proposalPath)) {
6394
7102
  return failure(new ChangeError(`\uBCC0\uACBD \uC81C\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${changeId}`));
6395
7103
  }
@@ -6402,7 +7110,7 @@ async function analyzeChangeImpact(sddPath, changeId) {
6402
7110
  return failure(new ChangeError(`\uC81C\uC548\uC11C \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
6403
7111
  }
6404
7112
  const proposal = parseResult.data;
6405
- const specsPath = path8.join(sddPath, "specs");
7113
+ const specsPath = path10.join(sddPath, "specs");
6406
7114
  const graphResult = await buildDependencyGraph(specsPath);
6407
7115
  if (!graphResult.success) {
6408
7116
  return failure(graphResult.error);
@@ -6501,7 +7209,7 @@ function formatImpactReport(report) {
6501
7209
  }
6502
7210
 
6503
7211
  // src/core/impact/simulator.ts
6504
- import { promises as fs5 } from "fs";
7212
+ import { promises as fs6 } from "fs";
6505
7213
  init_types();
6506
7214
  init_errors();
6507
7215
  init_fs();
@@ -6510,7 +7218,7 @@ async function parseDeltaFromProposal(proposalPath) {
6510
7218
  if (!await fileExists(proposalPath)) {
6511
7219
  return failure(new ChangeError(`\uC81C\uC548\uC11C \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${proposalPath}`));
6512
7220
  }
6513
- const content = await fs5.readFile(proposalPath, "utf-8");
7221
+ const content = await fs6.readFile(proposalPath, "utf-8");
6514
7222
  const deltas = [];
6515
7223
  const addedSection = content.match(/##\s*ADDED([\s\S]*?)(?=##\s*(?:MODIFIED|REMOVED)|---|\n$)/i);
6516
7224
  if (addedSection) {
@@ -6907,8 +7615,8 @@ function formatSimulationResult(result, targetSpec) {
6907
7615
  init_types();
6908
7616
  init_errors();
6909
7617
  init_fs();
6910
- import path9 from "path";
6911
- import { promises as fs6 } from "fs";
7618
+ import path11 from "path";
7619
+ import { promises as fs7 } from "fs";
6912
7620
  var SPEC_REFERENCE_PATTERNS = [
6913
7621
  /\/\/\s*spec:\s*([\w-]+)/gi,
6914
7622
  // // spec: feature-id
@@ -6930,7 +7638,7 @@ async function analyzeCodeImpact(projectRoot, sddPath, targetSpec) {
6930
7638
  );
6931
7639
  const directFiles = links.map((link) => ({
6932
7640
  path: link.filePath,
6933
- relativePath: path9.relative(projectRoot, link.filePath),
7641
+ relativePath: path11.relative(projectRoot, link.filePath),
6934
7642
  impactLevel: getLinkImpactLevel(link.confidence),
6935
7643
  impactType: "direct",
6936
7644
  reason: getLinkReason(link.linkType),
@@ -6969,7 +7677,7 @@ async function analyzeCodeImpact(projectRoot, sddPath, targetSpec) {
6969
7677
  }
6970
7678
  }
6971
7679
  async function loadCodeMappingConfig(sddPath) {
6972
- const configPath = path9.join(sddPath, "code-mapping.json");
7680
+ const configPath = path11.join(sddPath, "code-mapping.json");
6973
7681
  if (!await fileExists(configPath)) {
6974
7682
  return null;
6975
7683
  }
@@ -7000,15 +7708,15 @@ async function scanCodeFiles(projectRoot, maxDepth = 10) {
7000
7708
  async function scan(dir, depth) {
7001
7709
  if (depth > maxDepth) return;
7002
7710
  try {
7003
- const entries = await fs6.readdir(dir, { withFileTypes: true });
7711
+ const entries = await fs7.readdir(dir, { withFileTypes: true });
7004
7712
  for (const entry of entries) {
7005
- const fullPath = path9.join(dir, entry.name);
7713
+ const fullPath = path11.join(dir, entry.name);
7006
7714
  if (entry.isDirectory()) {
7007
7715
  if (!excludeDirs.has(entry.name) && !entry.name.startsWith(".")) {
7008
7716
  await scan(fullPath, depth + 1);
7009
7717
  }
7010
7718
  } else if (entry.isFile()) {
7011
- const ext = path9.extname(entry.name).toLowerCase();
7719
+ const ext = path11.extname(entry.name).toLowerCase();
7012
7720
  if (isCodeFile(ext)) {
7013
7721
  const codeFile = await analyzeCodeFile(fullPath, projectRoot);
7014
7722
  if (codeFile) {
@@ -7038,14 +7746,14 @@ function isCodeFile(ext) {
7038
7746
  }
7039
7747
  async function analyzeCodeFile(filePath, projectRoot) {
7040
7748
  try {
7041
- const content = await fs6.readFile(filePath, "utf-8");
7042
- const ext = path9.extname(filePath).toLowerCase();
7749
+ const content = await fs7.readFile(filePath, "utf-8");
7750
+ const ext = path11.extname(filePath).toLowerCase();
7043
7751
  const specReferences = extractSpecReferences(content);
7044
7752
  const imports = extractImports(content);
7045
7753
  const exports = extractExports(content);
7046
7754
  return {
7047
7755
  path: filePath,
7048
- relativePath: path9.relative(projectRoot, filePath),
7756
+ relativePath: path11.relative(projectRoot, filePath),
7049
7757
  type: ext === ".ts" || ext === ".tsx" ? "typescript" : ext === ".js" || ext === ".jsx" ? "javascript" : "other",
7050
7758
  specReferences,
7051
7759
  imports,
@@ -7145,8 +7853,8 @@ async function findSpecLinks(targetSpec, codeFiles, mappingConfig) {
7145
7853
  }
7146
7854
  }
7147
7855
  for (const file of codeFiles) {
7148
- const fileName = path9.basename(file.path, path9.extname(file.path)).toLowerCase();
7149
- const dirName = path9.basename(path9.dirname(file.path)).toLowerCase();
7856
+ const fileName = path11.basename(file.path, path11.extname(file.path)).toLowerCase();
7857
+ const dirName = path11.basename(path11.dirname(file.path)).toLowerCase();
7150
7858
  if (fileName === normalizedSpec || fileName === normalizedSpec.replace(/-/g, "")) {
7151
7859
  if (!links.some((l) => l.filePath === file.path)) {
7152
7860
  links.push({
@@ -7184,10 +7892,10 @@ async function findIndirectImpact(projectRoot, directFilePaths, allFiles) {
7184
7892
  visited.add(file.path);
7185
7893
  indirectFiles.push({
7186
7894
  path: file.path,
7187
- relativePath: path9.relative(projectRoot, file.path),
7895
+ relativePath: path11.relative(projectRoot, file.path),
7188
7896
  impactLevel: "medium",
7189
7897
  impactType: "indirect",
7190
- reason: `${path9.basename(directPath)}\uB97C import\uD568`
7898
+ reason: `${path11.basename(directPath)}\uB97C import\uD568`
7191
7899
  });
7192
7900
  }
7193
7901
  }
@@ -7200,8 +7908,8 @@ function resolveImport(fromFile, importPath) {
7200
7908
  if (!importPath.startsWith(".")) {
7201
7909
  return importPath;
7202
7910
  }
7203
- const dir = path9.dirname(fromFile);
7204
- return path9.resolve(dir, importPath);
7911
+ const dir = path11.dirname(fromFile);
7912
+ return path11.resolve(dir, importPath);
7205
7913
  }
7206
7914
  function isImportMatch(resolvedImport, targetFile) {
7207
7915
  const importWithoutExt = resolvedImport.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
@@ -7332,14 +8040,14 @@ init_fs();
7332
8040
  init_errors();
7333
8041
  init_types();
7334
8042
  function resolveProposalPath(proposalPath, sddPath) {
7335
- if (path10.isAbsolute(proposalPath)) {
8043
+ if (path12.isAbsolute(proposalPath)) {
7336
8044
  return proposalPath;
7337
8045
  }
7338
- const changesPath = path10.join(sddPath, "changes", proposalPath);
8046
+ const changesPath = path12.join(sddPath, "changes", proposalPath);
7339
8047
  if (proposalPath.endsWith(".md")) {
7340
8048
  return changesPath;
7341
8049
  }
7342
- return path10.join(changesPath, "proposal.md");
8050
+ return path12.join(changesPath, "proposal.md");
7343
8051
  }
7344
8052
  async function executeImpactAnalysis(sddPath, feature) {
7345
8053
  const result = await analyzeImpact(sddPath, feature);
@@ -7472,8 +8180,8 @@ async function runImpact(feature, options) {
7472
8180
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
7473
8181
  process.exit(ExitCode.GENERAL_ERROR);
7474
8182
  }
7475
- const sddPath = path10.join(projectRoot, ".sdd");
7476
- const specsPath = path10.join(sddPath, "specs");
8183
+ const sddPath = path12.join(projectRoot, ".sdd");
8184
+ const specsPath = path12.join(sddPath, "specs");
7477
8185
  if (options.graph) {
7478
8186
  const graphResult = await executeGraphAnalysis(specsPath, feature, options.json);
7479
8187
  if (!graphResult.success) {
@@ -7540,7 +8248,7 @@ async function runImpactReport(options) {
7540
8248
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
7541
8249
  process.exit(ExitCode.GENERAL_ERROR);
7542
8250
  }
7543
- const sddPath = path10.join(projectRoot, ".sdd");
8251
+ const sddPath = path12.join(projectRoot, ".sdd");
7544
8252
  const result = await executeImpactReport(sddPath);
7545
8253
  if (!result.success) {
7546
8254
  error(result.error.message);
@@ -7558,7 +8266,7 @@ async function runChangeImpact(changeId, options) {
7558
8266
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
7559
8267
  process.exit(ExitCode.GENERAL_ERROR);
7560
8268
  }
7561
- const sddPath = path10.join(projectRoot, ".sdd");
8269
+ const sddPath = path12.join(projectRoot, ".sdd");
7562
8270
  const result = await executeChangeImpact(sddPath, changeId);
7563
8271
  if (!result.success) {
7564
8272
  error(result.error.message);
@@ -7576,8 +8284,8 @@ async function runSimulate(feature, proposalPath, options) {
7576
8284
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
7577
8285
  process.exit(ExitCode.GENERAL_ERROR);
7578
8286
  }
7579
- const sddPath = path10.join(projectRoot, ".sdd");
7580
- const specsPath = path10.join(sddPath, "specs");
8287
+ const sddPath = path12.join(projectRoot, ".sdd");
8288
+ const specsPath = path12.join(sddPath, "specs");
7581
8289
  const fullProposalPath = resolveProposalPath(proposalPath, sddPath);
7582
8290
  info(`\u{1F4CA} What-if \uC2DC\uBBAC\uB808\uC774\uC158`);
7583
8291
  info(`\uB300\uC0C1 \uC2A4\uD399: ${feature}`);
@@ -7609,7 +8317,7 @@ async function runSimulate(feature, proposalPath, options) {
7609
8317
 
7610
8318
  // src/cli/commands/new.ts
7611
8319
  init_new();
7612
- import path12 from "path";
8320
+ import path14 from "path";
7613
8321
 
7614
8322
  // src/utils/index.ts
7615
8323
  init_fs();
@@ -7618,7 +8326,7 @@ init_fs();
7618
8326
  init_fs();
7619
8327
  init_types();
7620
8328
  async function getConstitutionVersion(sddPath) {
7621
- const constitutionPath = path12.join(sddPath, "constitution.md");
8329
+ const constitutionPath = path14.join(sddPath, "constitution.md");
7622
8330
  if (!await fileExists(constitutionPath)) {
7623
8331
  return void 0;
7624
8332
  }
@@ -7647,7 +8355,7 @@ async function createFeature(sddPath, name, options) {
7647
8355
  }
7648
8356
  const title2 = options.title || name;
7649
8357
  const description = options.description || `${title2} \uAE30\uB2A5 \uBA85\uC138`;
7650
- const featurePath = path12.join(sddPath, "specs", featureId);
8358
+ const featurePath = path14.join(sddPath, "specs", featureId);
7651
8359
  const dirResult = await ensureDir(featurePath);
7652
8360
  if (!dirResult.success) {
7653
8361
  return failure(new Error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${featurePath}`));
@@ -7660,7 +8368,7 @@ async function createFeature(sddPath, name, options) {
7660
8368
  description,
7661
8369
  constitutionVersion
7662
8370
  });
7663
- await writeFile(path12.join(featurePath, "spec.md"), specContent);
8371
+ await writeFile(path14.join(featurePath, "spec.md"), specContent);
7664
8372
  filesCreated.push("spec.md");
7665
8373
  if (options.plan || options.all) {
7666
8374
  const planContent = generatePlan({
@@ -7668,7 +8376,7 @@ async function createFeature(sddPath, name, options) {
7668
8376
  featureTitle: title2,
7669
8377
  overview: description
7670
8378
  });
7671
- await writeFile(path12.join(featurePath, "plan.md"), planContent);
8379
+ await writeFile(path14.join(featurePath, "plan.md"), planContent);
7672
8380
  filesCreated.push("plan.md");
7673
8381
  }
7674
8382
  if (options.tasks || options.all) {
@@ -7682,12 +8390,12 @@ async function createFeature(sddPath, name, options) {
7682
8390
  { title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
7683
8391
  ]
7684
8392
  });
7685
- await writeFile(path12.join(featurePath, "tasks.md"), tasksContent);
8393
+ await writeFile(path14.join(featurePath, "tasks.md"), tasksContent);
7686
8394
  filesCreated.push("tasks.md");
7687
8395
  }
7688
8396
  if (options.checklist || options.all) {
7689
8397
  const checklistContent = generateFullChecklistMarkdown();
7690
- await writeFile(path12.join(featurePath, "checklist.md"), checklistContent);
8398
+ await writeFile(path14.join(featurePath, "checklist.md"), checklistContent);
7691
8399
  filesCreated.push("checklist.md");
7692
8400
  }
7693
8401
  return success({
@@ -7702,7 +8410,7 @@ async function createPlan(featurePath, featureId, title2) {
7702
8410
  return failure(new Error(`\uAE30\uB2A5 '${featureId}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
7703
8411
  }
7704
8412
  let featureTitle = title2 || featureId;
7705
- const specPath = path12.join(featurePath, "spec.md");
8413
+ const specPath = path14.join(featurePath, "spec.md");
7706
8414
  if (await fileExists(specPath)) {
7707
8415
  const specResult = await readFile(specPath);
7708
8416
  if (specResult.success) {
@@ -7717,7 +8425,7 @@ async function createPlan(featurePath, featureId, title2) {
7717
8425
  featureTitle,
7718
8426
  overview: `${featureTitle} \uAD6C\uD604 \uACC4\uD68D`
7719
8427
  });
7720
- const planPath = path12.join(featurePath, "plan.md");
8428
+ const planPath = path14.join(featurePath, "plan.md");
7721
8429
  await writeFile(planPath, planContent);
7722
8430
  return success(planPath);
7723
8431
  }
@@ -7726,7 +8434,7 @@ async function createTasks(featurePath, featureId) {
7726
8434
  return failure(new Error(`\uAE30\uB2A5 '${featureId}'\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
7727
8435
  }
7728
8436
  let featureTitle = featureId;
7729
- const specPath = path12.join(featurePath, "spec.md");
8437
+ const specPath = path14.join(featurePath, "spec.md");
7730
8438
  if (await fileExists(specPath)) {
7731
8439
  const specResult = await readFile(specPath);
7732
8440
  if (specResult.success) {
@@ -7746,7 +8454,7 @@ async function createTasks(featurePath, featureId) {
7746
8454
  { title: "\uBB38\uC11C \uC5C5\uB370\uC774\uD2B8", priority: "low" }
7747
8455
  ]
7748
8456
  });
7749
- const tasksPath = path12.join(featurePath, "tasks.md");
8457
+ const tasksPath = path14.join(featurePath, "tasks.md");
7750
8458
  await writeFile(tasksPath, tasksContent);
7751
8459
  return success(tasksPath);
7752
8460
  }
@@ -7755,7 +8463,7 @@ async function createChecklist2(sddPath) {
7755
8463
  return failure(new Error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
7756
8464
  }
7757
8465
  const checklistContent = generateFullChecklistMarkdown();
7758
- const outputPath = path12.join(sddPath, "checklist.md");
8466
+ const outputPath = path14.join(sddPath, "checklist.md");
7759
8467
  await writeFile(outputPath, checklistContent);
7760
8468
  return success(outputPath);
7761
8469
  }
@@ -7796,7 +8504,7 @@ async function handleNew(name, options) {
7796
8504
  process.exit(1);
7797
8505
  }
7798
8506
  const cwd = process.cwd();
7799
- const sddPath = path12.join(cwd, ".sdd");
8507
+ const sddPath = path14.join(cwd, ".sdd");
7800
8508
  if (!await fileExists(sddPath)) {
7801
8509
  logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 sdd init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
7802
8510
  process.exit(1);
@@ -7844,7 +8552,7 @@ async function handleNew(name, options) {
7844
8552
  }
7845
8553
  async function handlePlan(feature, options) {
7846
8554
  const cwd = process.cwd();
7847
- const featurePath = path12.join(cwd, ".sdd", "specs", feature);
8555
+ const featurePath = path14.join(cwd, ".sdd", "specs", feature);
7848
8556
  const result = await createPlan(featurePath, feature, options.title);
7849
8557
  if (!result.success) {
7850
8558
  logger_exports.error(result.error.message);
@@ -7858,7 +8566,7 @@ async function handlePlan(feature, options) {
7858
8566
  }
7859
8567
  async function handleTasks(feature) {
7860
8568
  const cwd = process.cwd();
7861
- const featurePath = path12.join(cwd, ".sdd", "specs", feature);
8569
+ const featurePath = path14.join(cwd, ".sdd", "specs", feature);
7862
8570
  const result = await createTasks(featurePath, feature);
7863
8571
  if (!result.success) {
7864
8572
  logger_exports.error(result.error.message);
@@ -7872,7 +8580,7 @@ async function handleTasks(feature) {
7872
8580
  }
7873
8581
  async function handleChecklist() {
7874
8582
  const cwd = process.cwd();
7875
- const sddPath = path12.join(cwd, ".sdd");
8583
+ const sddPath = path14.join(cwd, ".sdd");
7876
8584
  const result = await createChecklist2(sddPath);
7877
8585
  if (!result.success) {
7878
8586
  logger_exports.error(result.error.message);
@@ -7882,7 +8590,7 @@ async function handleChecklist() {
7882
8590
  }
7883
8591
  async function handleCounter(options) {
7884
8592
  const cwd = process.cwd();
7885
- const sddPath = path12.join(cwd, ".sdd");
8593
+ const sddPath = path14.join(cwd, ".sdd");
7886
8594
  if (!await fileExists(sddPath)) {
7887
8595
  logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 sdd init\uC744 \uC2E4\uD589\uD574\uC8FC\uC138\uC694.");
7888
8596
  process.exit(1);
@@ -7955,8 +8663,8 @@ async function handleCounter(options) {
7955
8663
  }
7956
8664
 
7957
8665
  // src/cli/commands/status.ts
7958
- import path13 from "path";
7959
- import { promises as fs7 } from "fs";
8666
+ import path15 from "path";
8667
+ import { promises as fs8 } from "fs";
7960
8668
  init_fs();
7961
8669
  init_spec_generator();
7962
8670
  init_task_generator();
@@ -7970,21 +8678,21 @@ async function getFeatureInfo(id, featurePath) {
7970
8678
  hasPlan: false,
7971
8679
  hasTasks: false
7972
8680
  };
7973
- const specPath = path13.join(featurePath, "spec.md");
8681
+ const specPath = path15.join(featurePath, "spec.md");
7974
8682
  if (await fileExists(specPath)) {
7975
8683
  info2.hasSpec = true;
7976
- const content = await fs7.readFile(specPath, "utf-8");
8684
+ const content = await fs8.readFile(specPath, "utf-8");
7977
8685
  const metadata = parseSpecMetadata(content);
7978
8686
  if (metadata) {
7979
8687
  info2.title = metadata.title;
7980
8688
  info2.status = metadata.status;
7981
8689
  }
7982
8690
  }
7983
- info2.hasPlan = await fileExists(path13.join(featurePath, "plan.md"));
7984
- const tasksPath = path13.join(featurePath, "tasks.md");
8691
+ info2.hasPlan = await fileExists(path15.join(featurePath, "plan.md"));
8692
+ const tasksPath = path15.join(featurePath, "tasks.md");
7985
8693
  if (await fileExists(tasksPath)) {
7986
8694
  info2.hasTasks = true;
7987
- const content = await fs7.readFile(tasksPath, "utf-8");
8695
+ const content = await fs8.readFile(tasksPath, "utf-8");
7988
8696
  const tasks = parseTasks(content);
7989
8697
  const completed = tasks.filter((t) => t.status === "completed").length;
7990
8698
  info2.taskProgress = {
@@ -7995,7 +8703,7 @@ async function getFeatureInfo(id, featurePath) {
7995
8703
  return info2;
7996
8704
  }
7997
8705
  async function getProjectStatus(projectPath) {
7998
- const sddPath = path13.join(projectPath, ".sdd");
8706
+ const sddPath = path15.join(projectPath, ".sdd");
7999
8707
  const status = {
8000
8708
  initialized: false,
8001
8709
  hasConstitution: false,
@@ -8009,15 +8717,15 @@ async function getProjectStatus(projectPath) {
8009
8717
  if (!status.initialized) {
8010
8718
  return status;
8011
8719
  }
8012
- status.hasConstitution = await fileExists(path13.join(sddPath, "constitution.md"));
8013
- status.hasAgents = await fileExists(path13.join(sddPath, "AGENTS.md"));
8014
- const specsPath = path13.join(sddPath, "specs");
8720
+ status.hasConstitution = await fileExists(path15.join(sddPath, "constitution.md"));
8721
+ status.hasAgents = await fileExists(path15.join(sddPath, "AGENTS.md"));
8722
+ const specsPath = path15.join(sddPath, "specs");
8015
8723
  if (await fileExists(specsPath)) {
8016
8724
  const specsResult = await readDir(specsPath);
8017
8725
  if (specsResult.success) {
8018
8726
  for (const entry of specsResult.data) {
8019
- const featurePath = path13.join(specsPath, entry);
8020
- const stat = await fs7.stat(featurePath);
8727
+ const featurePath = path15.join(specsPath, entry);
8728
+ const stat = await fs8.stat(featurePath);
8021
8729
  if (stat.isDirectory()) {
8022
8730
  const featureInfo = await getFeatureInfo(entry, featurePath);
8023
8731
  status.features.push(featureInfo);
@@ -8165,8 +8873,8 @@ function printStatus(status, verbose) {
8165
8873
  }
8166
8874
 
8167
8875
  // src/cli/commands/list.ts
8168
- import path14 from "path";
8169
- import { promises as fs8 } from "fs";
8876
+ import path16 from "path";
8877
+ import { promises as fs9 } from "fs";
8170
8878
  init_fs();
8171
8879
  init_spec_generator();
8172
8880
  function getListStatusIcon(status) {
@@ -8188,7 +8896,7 @@ function getListStatusIcon(status) {
8188
8896
  }
8189
8897
  }
8190
8898
  async function getFeatureList(projectPath, options = {}) {
8191
- const specsPath = path14.join(projectPath, ".sdd", "specs");
8899
+ const specsPath = path16.join(projectPath, ".sdd", "specs");
8192
8900
  if (!await fileExists(specsPath)) {
8193
8901
  return [];
8194
8902
  }
@@ -8198,12 +8906,12 @@ async function getFeatureList(projectPath, options = {}) {
8198
8906
  }
8199
8907
  const features = [];
8200
8908
  for (const entry of result.data) {
8201
- const featurePath = path14.join(specsPath, entry);
8202
- const stat = await fs8.stat(featurePath);
8909
+ const featurePath = path16.join(specsPath, entry);
8910
+ const stat = await fs9.stat(featurePath);
8203
8911
  if (stat.isDirectory()) {
8204
- const specPath = path14.join(featurePath, "spec.md");
8912
+ const specPath = path16.join(featurePath, "spec.md");
8205
8913
  if (await fileExists(specPath)) {
8206
- const content = await fs8.readFile(specPath, "utf-8");
8914
+ const content = await fs9.readFile(specPath, "utf-8");
8207
8915
  const metadata = parseSpecMetadata(content);
8208
8916
  if (metadata) {
8209
8917
  if (!options.status || metadata.status === options.status) {
@@ -8220,7 +8928,7 @@ async function getFeatureList(projectPath, options = {}) {
8220
8928
  return features;
8221
8929
  }
8222
8930
  async function getChangeList(projectPath, options = {}) {
8223
- const sddPath = path14.join(projectPath, ".sdd");
8931
+ const sddPath = path16.join(projectPath, ".sdd");
8224
8932
  const result = {
8225
8933
  pending: [],
8226
8934
  archived: []
@@ -8253,8 +8961,8 @@ async function walkSpecsTree(basePath) {
8253
8961
  if (!result.success) return [];
8254
8962
  const items = [];
8255
8963
  for (const entry of result.data) {
8256
- const fullPath = path14.join(basePath, entry);
8257
- const stat = await fs8.stat(fullPath);
8964
+ const fullPath = path16.join(basePath, entry);
8965
+ const stat = await fs9.stat(fullPath);
8258
8966
  if (stat.isDirectory()) {
8259
8967
  const children = await walkSpecsTree(fullPath);
8260
8968
  items.push({
@@ -8274,7 +8982,7 @@ async function walkSpecsTree(basePath) {
8274
8982
  return items;
8275
8983
  }
8276
8984
  async function getTemplateList(projectPath) {
8277
- const templatesPath = path14.join(projectPath, ".sdd", "templates");
8985
+ const templatesPath = path16.join(projectPath, ".sdd", "templates");
8278
8986
  if (!await fileExists(templatesPath)) {
8279
8987
  return [];
8280
8988
  }
@@ -8285,17 +8993,17 @@ async function getTemplateList(projectPath) {
8285
8993
  return result.data.filter((f) => f.endsWith(".md"));
8286
8994
  }
8287
8995
  async function getProjectSummary(projectPath) {
8288
- const sddPath = path14.join(projectPath, ".sdd");
8996
+ const sddPath = path16.join(projectPath, ".sdd");
8289
8997
  if (!await fileExists(sddPath)) {
8290
8998
  return null;
8291
8999
  }
8292
- const specsPath = path14.join(sddPath, "specs");
9000
+ const specsPath = path16.join(sddPath, "specs");
8293
9001
  let featureCount = 0;
8294
9002
  if (await fileExists(specsPath)) {
8295
9003
  const result = await readDir(specsPath);
8296
9004
  if (result.success) {
8297
9005
  for (const entry of result.data) {
8298
- const stat = await fs8.stat(path14.join(specsPath, entry));
9006
+ const stat = await fs9.stat(path16.join(specsPath, entry));
8299
9007
  if (stat.isDirectory()) featureCount++;
8300
9008
  }
8301
9009
  }
@@ -8372,7 +9080,7 @@ async function listChanges(options) {
8372
9080
  }
8373
9081
  }
8374
9082
  async function listSpecs() {
8375
- const specsPath = path14.join(process.cwd(), ".sdd", "specs");
9083
+ const specsPath = path16.join(process.cwd(), ".sdd", "specs");
8376
9084
  const tree = await getSpecFileTree(specsPath);
8377
9085
  if (tree.length === 0) {
8378
9086
  logger_exports.warn("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
@@ -8434,7 +9142,7 @@ async function listSummary() {
8434
9142
  // src/cli/commands/constitution.ts
8435
9143
  init_fs();
8436
9144
  init_errors();
8437
- import path15 from "path";
9145
+ import path17 from "path";
8438
9146
  init_types();
8439
9147
  function determineBumpType(options) {
8440
9148
  if (options.major) return "major";
@@ -8443,7 +9151,7 @@ function determineBumpType(options) {
8443
9151
  return null;
8444
9152
  }
8445
9153
  async function readConstitution(projectPath) {
8446
- const constitutionPath = path15.join(projectPath, ".sdd", "constitution.md");
9154
+ const constitutionPath = path17.join(projectPath, ".sdd", "constitution.md");
8447
9155
  if (!await fileExists(constitutionPath)) {
8448
9156
  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."));
8449
9157
  }
@@ -8473,8 +9181,8 @@ function constitutionToJson(constitution) {
8473
9181
  };
8474
9182
  }
8475
9183
  async function executeBump(projectPath, bumpType, message) {
8476
- const constitutionPath = path15.join(projectPath, ".sdd", "constitution.md");
8477
- const changelogPath = path15.join(projectPath, ".sdd", "CHANGELOG.md");
9184
+ const constitutionPath = path17.join(projectPath, ".sdd", "constitution.md");
9185
+ const changelogPath = path17.join(projectPath, ".sdd", "CHANGELOG.md");
8478
9186
  const readResult = await readConstitution(projectPath);
8479
9187
  if (!readResult.success) {
8480
9188
  return failure(readResult.error);
@@ -8540,7 +9248,7 @@ async function executeValidateConstitution(projectPath) {
8540
9248
  return success(readResult.data.parsed);
8541
9249
  }
8542
9250
  async function getHistory(projectPath, count = 10) {
8543
- const changelogPath = path15.join(projectPath, ".sdd", "CHANGELOG.md");
9251
+ const changelogPath = path17.join(projectPath, ".sdd", "CHANGELOG.md");
8544
9252
  if (!await fileExists(changelogPath)) {
8545
9253
  return success({ entries: [], totalCount: 0 });
8546
9254
  }
@@ -8719,8 +9427,8 @@ async function runValidate2() {
8719
9427
  }
8720
9428
 
8721
9429
  // src/cli/commands/start.ts
8722
- import path16 from "path";
8723
- import { promises as fs9 } from "fs";
9430
+ import path18 from "path";
9431
+ import { promises as fs10 } from "fs";
8724
9432
  init_errors();
8725
9433
  init_fs();
8726
9434
  function registerStartCommand(program2) {
@@ -8769,11 +9477,11 @@ async function getProjectStatus2() {
8769
9477
  specs: []
8770
9478
  };
8771
9479
  }
8772
- const sddPath = path16.join(projectRoot, ".sdd");
8773
- const specsPath = path16.join(sddPath, "specs");
9480
+ const sddPath = path18.join(projectRoot, ".sdd");
9481
+ const specsPath = path18.join(sddPath, "specs");
8774
9482
  let hasConstitution = false;
8775
9483
  let constitutionVersion;
8776
- const constitutionPath = path16.join(sddPath, "constitution.md");
9484
+ const constitutionPath = path18.join(sddPath, "constitution.md");
8777
9485
  if (await fileExists(constitutionPath)) {
8778
9486
  hasConstitution = true;
8779
9487
  const content = await readFile(constitutionPath);
@@ -8787,9 +9495,9 @@ async function getProjectStatus2() {
8787
9495
  const specs = [];
8788
9496
  if (await directoryExists(specsPath)) {
8789
9497
  try {
8790
- const dirs = await fs9.readdir(specsPath);
9498
+ const dirs = await fs10.readdir(specsPath);
8791
9499
  for (const dir of dirs) {
8792
- const specPath = path16.join(specsPath, dir, "spec.md");
9500
+ const specPath = path18.join(specsPath, dir, "spec.md");
8793
9501
  if (await fileExists(specPath)) {
8794
9502
  specs.push(dir);
8795
9503
  }
@@ -9023,8 +9731,8 @@ function displayConstitutionGuide() {
9023
9731
  }
9024
9732
 
9025
9733
  // src/cli/commands/migrate.ts
9026
- import path18 from "path";
9027
- import { promises as fs11 } from "fs";
9734
+ import path20 from "path";
9735
+ import { promises as fs12 } from "fs";
9028
9736
  init_errors();
9029
9737
  init_fs();
9030
9738
  init_new();
@@ -9034,8 +9742,8 @@ init_schemas();
9034
9742
  init_types();
9035
9743
  init_errors();
9036
9744
  init_fs();
9037
- import path17 from "path";
9038
- import fs10 from "fs/promises";
9745
+ import path19 from "path";
9746
+ import fs11 from "fs/promises";
9039
9747
  async function detectExternalTools(projectRoot) {
9040
9748
  try {
9041
9749
  const results = [];
@@ -9057,13 +9765,13 @@ async function detectExternalTools(projectRoot) {
9057
9765
  }
9058
9766
  }
9059
9767
  async function detectOpenSpec(projectRoot) {
9060
- const openspecPath = path17.join(projectRoot, "openspec");
9768
+ const openspecPath = path19.join(projectRoot, "openspec");
9061
9769
  if (!await directoryExists(openspecPath)) {
9062
9770
  return null;
9063
9771
  }
9064
- const specsPath = path17.join(openspecPath, "specs");
9065
- const changesPath = path17.join(openspecPath, "changes");
9066
- const agentsPath = path17.join(openspecPath, "AGENTS.md");
9772
+ const specsPath = path19.join(openspecPath, "specs");
9773
+ const changesPath = path19.join(openspecPath, "changes");
9774
+ const agentsPath = path19.join(openspecPath, "AGENTS.md");
9067
9775
  const hasAgents = await fileExists(agentsPath);
9068
9776
  const hasSpecs = await directoryExists(specsPath);
9069
9777
  const hasChanges = await directoryExists(changesPath);
@@ -9072,13 +9780,13 @@ async function detectOpenSpec(projectRoot) {
9072
9780
  }
9073
9781
  const specs = [];
9074
9782
  if (hasSpecs) {
9075
- const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
9783
+ const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
9076
9784
  for (const entry of specDirs) {
9077
9785
  if (entry.isDirectory()) {
9078
- const specPath = path17.join(specsPath, entry.name);
9079
- const specFile = path17.join(specPath, "spec.md");
9786
+ const specPath = path19.join(specsPath, entry.name);
9787
+ const specFile = path19.join(specPath, "spec.md");
9080
9788
  if (await fileExists(specFile)) {
9081
- const content = await fs10.readFile(specFile, "utf-8");
9789
+ const content = await fs11.readFile(specFile, "utf-8");
9082
9790
  const title2 = extractTitle2(content);
9083
9791
  const status = extractFrontmatterField(content, "status");
9084
9792
  specs.push({
@@ -9100,26 +9808,26 @@ async function detectOpenSpec(projectRoot) {
9100
9808
  };
9101
9809
  }
9102
9810
  async function detectSpecKit(projectRoot) {
9103
- const specifyPath = path17.join(projectRoot, ".specify");
9811
+ const specifyPath = path19.join(projectRoot, ".specify");
9104
9812
  if (!await directoryExists(specifyPath)) {
9105
9813
  return null;
9106
9814
  }
9107
- const specsPath = path17.join(specifyPath, "specs");
9108
- const memoryPath = path17.join(projectRoot, "memory");
9109
- const constitutionPath = path17.join(memoryPath, "constitution.md");
9815
+ const specsPath = path19.join(specifyPath, "specs");
9816
+ const memoryPath = path19.join(projectRoot, "memory");
9817
+ const constitutionPath = path19.join(memoryPath, "constitution.md");
9110
9818
  const hasSpecs = await directoryExists(specsPath);
9111
9819
  const hasConstitution = await fileExists(constitutionPath);
9112
9820
  if (!hasSpecs) {
9113
9821
  return null;
9114
9822
  }
9115
9823
  const specs = [];
9116
- const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
9824
+ const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
9117
9825
  for (const entry of specDirs) {
9118
9826
  if (entry.isDirectory()) {
9119
- const specPath = path17.join(specsPath, entry.name);
9120
- const specFile = path17.join(specPath, "spec.md");
9121
- const planFile = path17.join(specPath, "plan.md");
9122
- const tasksFile = path17.join(specPath, "tasks.md");
9827
+ const specPath = path19.join(specsPath, entry.name);
9828
+ const specFile = path19.join(specPath, "spec.md");
9829
+ const planFile = path19.join(specPath, "plan.md");
9830
+ const tasksFile = path19.join(specPath, "tasks.md");
9123
9831
  const hasSpec = await fileExists(specFile);
9124
9832
  const hasPlan = await fileExists(planFile);
9125
9833
  const hasTasks = await fileExists(tasksFile);
@@ -9127,7 +9835,7 @@ async function detectSpecKit(projectRoot) {
9127
9835
  let title2;
9128
9836
  let status;
9129
9837
  if (hasSpec) {
9130
- const content = await fs10.readFile(specFile, "utf-8");
9838
+ const content = await fs11.readFile(specFile, "utf-8");
9131
9839
  title2 = extractTitle2(content);
9132
9840
  status = extractFrontmatterField(content, "status");
9133
9841
  }
@@ -9149,23 +9857,23 @@ async function detectSpecKit(projectRoot) {
9149
9857
  };
9150
9858
  }
9151
9859
  async function detectSdd(projectRoot) {
9152
- const sddPath = path17.join(projectRoot, ".sdd");
9860
+ const sddPath = path19.join(projectRoot, ".sdd");
9153
9861
  if (!await directoryExists(sddPath)) {
9154
9862
  return null;
9155
9863
  }
9156
- const specsPath = path17.join(sddPath, "specs");
9157
- const configPath = path17.join(sddPath, "config.yaml");
9864
+ const specsPath = path19.join(sddPath, "specs");
9865
+ const configPath = path19.join(sddPath, "config.yaml");
9158
9866
  if (!await directoryExists(specsPath)) {
9159
9867
  return null;
9160
9868
  }
9161
9869
  const specs = [];
9162
- const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
9870
+ const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
9163
9871
  for (const entry of specDirs) {
9164
9872
  if (entry.isDirectory()) {
9165
- const specPath = path17.join(specsPath, entry.name);
9166
- const specFile = path17.join(specPath, "spec.md");
9873
+ const specPath = path19.join(specsPath, entry.name);
9874
+ const specFile = path19.join(specPath, "spec.md");
9167
9875
  if (await fileExists(specFile)) {
9168
- const content = await fs10.readFile(specFile, "utf-8");
9876
+ const content = await fs11.readFile(specFile, "utf-8");
9169
9877
  const title2 = extractTitle2(content);
9170
9878
  const status = extractFrontmatterField(content, "status");
9171
9879
  specs.push({
@@ -9187,19 +9895,19 @@ async function detectSdd(projectRoot) {
9187
9895
  }
9188
9896
  async function migrateFromOpenSpec(sourcePath, targetPath, options = {}) {
9189
9897
  try {
9190
- const specsPath = path17.join(sourcePath, "specs");
9191
- const targetSpecsPath = path17.join(targetPath, "specs");
9898
+ const specsPath = path19.join(sourcePath, "specs");
9899
+ const targetSpecsPath = path19.join(targetPath, "specs");
9192
9900
  let specsCreated = 0;
9193
9901
  let specsSkipped = 0;
9194
9902
  const errors = [];
9195
9903
  if (!await directoryExists(specsPath)) {
9196
9904
  return failure(new ChangeError("OpenSpec specs \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
9197
9905
  }
9198
- const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
9906
+ const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
9199
9907
  for (const entry of specDirs) {
9200
9908
  if (!entry.isDirectory()) continue;
9201
- const sourceSpecPath = path17.join(specsPath, entry.name);
9202
- const targetSpecPath = path17.join(targetSpecsPath, entry.name);
9909
+ const sourceSpecPath = path19.join(specsPath, entry.name);
9910
+ const targetSpecPath = path19.join(targetSpecsPath, entry.name);
9203
9911
  if (await directoryExists(targetSpecPath)) {
9204
9912
  if (!options.overwrite) {
9205
9913
  specsSkipped++;
@@ -9208,18 +9916,18 @@ async function migrateFromOpenSpec(sourcePath, targetPath, options = {}) {
9208
9916
  }
9209
9917
  try {
9210
9918
  if (!options.dryRun) {
9211
- await fs10.mkdir(targetSpecPath, { recursive: true });
9212
- const files = await fs10.readdir(sourceSpecPath);
9919
+ await fs11.mkdir(targetSpecPath, { recursive: true });
9920
+ const files = await fs11.readdir(sourceSpecPath);
9213
9921
  for (const file of files) {
9214
- const sourceFile = path17.join(sourceSpecPath, file);
9215
- const targetFile = path17.join(targetSpecPath, file);
9216
- const stat = await fs10.stat(sourceFile);
9922
+ const sourceFile = path19.join(sourceSpecPath, file);
9923
+ const targetFile = path19.join(targetSpecPath, file);
9924
+ const stat = await fs11.stat(sourceFile);
9217
9925
  if (stat.isFile()) {
9218
- let content = await fs10.readFile(sourceFile, "utf-8");
9926
+ let content = await fs11.readFile(sourceFile, "utf-8");
9219
9927
  if (file === "spec.md") {
9220
9928
  content = convertOpenSpecToSdd(content, entry.name);
9221
9929
  }
9222
- await fs10.writeFile(targetFile, content);
9930
+ await fs11.writeFile(targetFile, content);
9223
9931
  }
9224
9932
  }
9225
9933
  }
@@ -9241,19 +9949,19 @@ async function migrateFromOpenSpec(sourcePath, targetPath, options = {}) {
9241
9949
  }
9242
9950
  async function migrateFromSpecKit(sourcePath, targetPath, options = {}) {
9243
9951
  try {
9244
- const specsPath = path17.join(sourcePath, "specs");
9245
- const targetSpecsPath = path17.join(targetPath, "specs");
9952
+ const specsPath = path19.join(sourcePath, "specs");
9953
+ const targetSpecsPath = path19.join(targetPath, "specs");
9246
9954
  let specsCreated = 0;
9247
9955
  let specsSkipped = 0;
9248
9956
  const errors = [];
9249
9957
  if (!await directoryExists(specsPath)) {
9250
9958
  return failure(new ChangeError("Spec Kit specs \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
9251
9959
  }
9252
- const specDirs = await fs10.readdir(specsPath, { withFileTypes: true });
9960
+ const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
9253
9961
  for (const entry of specDirs) {
9254
9962
  if (!entry.isDirectory()) continue;
9255
- const sourceSpecPath = path17.join(specsPath, entry.name);
9256
- const targetSpecPath = path17.join(targetSpecsPath, entry.name);
9963
+ const sourceSpecPath = path19.join(specsPath, entry.name);
9964
+ const targetSpecPath = path19.join(targetSpecsPath, entry.name);
9257
9965
  if (await directoryExists(targetSpecPath)) {
9258
9966
  if (!options.overwrite) {
9259
9967
  specsSkipped++;
@@ -9262,18 +9970,18 @@ async function migrateFromSpecKit(sourcePath, targetPath, options = {}) {
9262
9970
  }
9263
9971
  try {
9264
9972
  if (!options.dryRun) {
9265
- await fs10.mkdir(targetSpecPath, { recursive: true });
9266
- const files = await fs10.readdir(sourceSpecPath);
9973
+ await fs11.mkdir(targetSpecPath, { recursive: true });
9974
+ const files = await fs11.readdir(sourceSpecPath);
9267
9975
  for (const file of files) {
9268
- const sourceFile = path17.join(sourceSpecPath, file);
9269
- const targetFile = path17.join(targetSpecPath, file);
9270
- const stat = await fs10.stat(sourceFile);
9976
+ const sourceFile = path19.join(sourceSpecPath, file);
9977
+ const targetFile = path19.join(targetSpecPath, file);
9978
+ const stat = await fs11.stat(sourceFile);
9271
9979
  if (stat.isFile()) {
9272
- let content = await fs10.readFile(sourceFile, "utf-8");
9980
+ let content = await fs11.readFile(sourceFile, "utf-8");
9273
9981
  if (file === "spec.md") {
9274
9982
  content = convertSpecKitToSdd(content, entry.name);
9275
9983
  }
9276
- await fs10.writeFile(targetFile, content);
9984
+ await fs11.writeFile(targetFile, content);
9277
9985
  }
9278
9986
  }
9279
9987
  }
@@ -9428,10 +10136,10 @@ async function runMigrateDocs(source, options) {
9428
10136
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --output \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uAC70\uB098 sdd init\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
9429
10137
  process.exit(ExitCode.GENERAL_ERROR);
9430
10138
  }
9431
- const sourcePath = path18.resolve(source);
10139
+ const sourcePath = path20.resolve(source);
9432
10140
  let files = [];
9433
10141
  try {
9434
- const stat = await fs11.stat(sourcePath);
10142
+ const stat = await fs12.stat(sourcePath);
9435
10143
  if (stat.isDirectory()) {
9436
10144
  files = await collectMarkdownFiles(sourcePath);
9437
10145
  } else if (stat.isFile()) {
@@ -9447,7 +10155,7 @@ async function runMigrateDocs(source, options) {
9447
10155
  }
9448
10156
  info(`${files.length}\uAC1C \uD30C\uC77C \uBC1C\uACAC`);
9449
10157
  newline();
9450
- const outputDir = options.output ? path18.resolve(options.output) : path18.join(projectRoot, ".sdd", "specs");
10158
+ const outputDir = options.output ? path20.resolve(options.output) : path20.join(projectRoot, ".sdd", "specs");
9451
10159
  const summary = {
9452
10160
  total: files.length,
9453
10161
  succeeded: 0,
@@ -9459,10 +10167,10 @@ async function runMigrateDocs(source, options) {
9459
10167
  summary.results.push(result);
9460
10168
  if (result.success) {
9461
10169
  summary.succeeded++;
9462
- info(`\u2705 ${path18.basename(file)} \u2192 ${result.target}`);
10170
+ info(`\u2705 ${path20.basename(file)} \u2192 ${result.target}`);
9463
10171
  } else {
9464
10172
  summary.failed++;
9465
- error(`\u274C ${path18.basename(file)}: ${result.error}`);
10173
+ error(`\u274C ${path20.basename(file)}: ${result.error}`);
9466
10174
  }
9467
10175
  }
9468
10176
  newline();
@@ -9474,25 +10182,25 @@ async function runMigrateDocs(source, options) {
9474
10182
  }
9475
10183
  async function migrateDocument(filePath, outputDir, dryRun) {
9476
10184
  try {
9477
- const content = await fs11.readFile(filePath, "utf-8");
10185
+ const content = await fs12.readFile(filePath, "utf-8");
9478
10186
  const analysis = analyzeDocument(content);
9479
- const featureId = generateFeatureId(analysis.title || path18.basename(filePath, ".md"));
10187
+ const featureId = generateFeatureId(analysis.title || path20.basename(filePath, ".md"));
9480
10188
  const specContent = generateSpec({
9481
10189
  id: featureId,
9482
- title: analysis.title || path18.basename(filePath, ".md"),
10190
+ title: analysis.title || path20.basename(filePath, ".md"),
9483
10191
  description: analysis.description || "",
9484
10192
  requirements: analysis.requirements,
9485
10193
  scenarios: analysis.scenarios
9486
10194
  });
9487
- const targetDir = path18.join(outputDir, featureId);
9488
- const targetPath = path18.join(targetDir, "spec.md");
10195
+ const targetDir = path20.join(outputDir, featureId);
10196
+ const targetPath = path20.join(targetDir, "spec.md");
9489
10197
  if (!dryRun) {
9490
10198
  await ensureDir(targetDir);
9491
10199
  await writeFile(targetPath, specContent);
9492
10200
  }
9493
10201
  return {
9494
10202
  source: filePath,
9495
- target: path18.relative(process.cwd(), targetPath),
10203
+ target: path20.relative(process.cwd(), targetPath),
9496
10204
  success: true
9497
10205
  };
9498
10206
  } catch (error2) {
@@ -9537,14 +10245,14 @@ function analyzeDocument(content) {
9537
10245
  };
9538
10246
  }
9539
10247
  async function runAnalyze(file) {
9540
- const filePath = path18.resolve(file);
10248
+ const filePath = path20.resolve(file);
9541
10249
  if (!await fileExists(filePath)) {
9542
10250
  error(`\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${file}`);
9543
10251
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
9544
10252
  }
9545
- const content = await fs11.readFile(filePath, "utf-8");
10253
+ const content = await fs12.readFile(filePath, "utf-8");
9546
10254
  const analysis = analyzeDocument(content);
9547
- info(`\u{1F4CA} \uBB38\uC11C \uBD84\uC11D: ${path18.basename(file)}`);
10255
+ info(`\u{1F4CA} \uBB38\uC11C \uBD84\uC11D: ${path20.basename(file)}`);
9548
10256
  newline();
9549
10257
  info(`\uC81C\uBAA9: ${analysis.title || "(\uC5C6\uC74C)"}`);
9550
10258
  info(`\uC124\uBA85: ${analysis.description || "(\uC5C6\uC74C)"}`);
@@ -9587,7 +10295,7 @@ async function runAnalyze(file) {
9587
10295
  }
9588
10296
  }
9589
10297
  async function runScan(dir, options) {
9590
- const dirPath = path18.resolve(dir);
10298
+ const dirPath = path20.resolve(dir);
9591
10299
  if (!await directoryExists(dirPath)) {
9592
10300
  error(`\uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
9593
10301
  process.exit(ExitCode.FILE_SYSTEM_ERROR);
@@ -9603,7 +10311,7 @@ async function runScan(dir, options) {
9603
10311
  const results = [];
9604
10312
  for (const file of files) {
9605
10313
  try {
9606
- const content = await fs11.readFile(file, "utf-8");
10314
+ const content = await fs12.readFile(file, "utf-8");
9607
10315
  const analysis = analyzeDocument(content);
9608
10316
  results.push({ file, analysis });
9609
10317
  } catch {
@@ -9618,7 +10326,7 @@ async function runScan(dir, options) {
9618
10326
  const partial = [];
9619
10327
  const notReady = [];
9620
10328
  for (const { file, analysis } of results) {
9621
- const relativePath = path18.relative(process.cwd(), file);
10329
+ const relativePath = path20.relative(process.cwd(), file);
9622
10330
  if (analysis.hasRfc2119 && analysis.hasScenarios) {
9623
10331
  ready.push(relativePath);
9624
10332
  } else if (analysis.hasRfc2119 || analysis.hasScenarios || analysis.requirements.length > 0) {
@@ -9665,15 +10373,15 @@ async function collectMarkdownFiles(dirPath) {
9665
10373
  async function collectFilesWithExtensions(dirPath, extensions) {
9666
10374
  const files = [];
9667
10375
  async function scan(dir) {
9668
- const entries = await fs11.readdir(dir, { withFileTypes: true });
10376
+ const entries = await fs12.readdir(dir, { withFileTypes: true });
9669
10377
  for (const entry of entries) {
9670
- const fullPath = path18.join(dir, entry.name);
10378
+ const fullPath = path20.join(dir, entry.name);
9671
10379
  if (entry.isDirectory()) {
9672
10380
  if (!["node_modules", ".git", ".sdd", "dist", "build"].includes(entry.name)) {
9673
10381
  await scan(fullPath);
9674
10382
  }
9675
10383
  } else if (entry.isFile()) {
9676
- const ext = path18.extname(entry.name).toLowerCase();
10384
+ const ext = path20.extname(entry.name).toLowerCase();
9677
10385
  if (extensions.some((e) => e.toLowerCase() === ext)) {
9678
10386
  if (!["agents.md", "readme.md", "changelog.md", "license.md"].includes(entry.name.toLowerCase())) {
9679
10387
  files.push(fullPath);
@@ -9686,7 +10394,7 @@ async function collectFilesWithExtensions(dirPath, extensions) {
9686
10394
  return files;
9687
10395
  }
9688
10396
  async function runDetect(options) {
9689
- const projectRoot = options.path ? path18.resolve(options.path) : process.cwd();
10397
+ const projectRoot = options.path ? path20.resolve(options.path) : process.cwd();
9690
10398
  info("\u{1F50D} \uC678\uBD80 SDD \uB3C4\uAD6C \uAC10\uC9C0 \uC911...");
9691
10399
  info(` \uACBD\uB85C: ${projectRoot}`);
9692
10400
  newline();
@@ -9740,7 +10448,7 @@ async function runMigrateOpenSpec(source, options) {
9740
10448
  }
9741
10449
  let sourcePath;
9742
10450
  if (source) {
9743
- sourcePath = path18.resolve(source);
10451
+ sourcePath = path20.resolve(source);
9744
10452
  } else {
9745
10453
  const detectResult = await detectExternalTools(projectRoot);
9746
10454
  if (!detectResult.success) {
@@ -9754,7 +10462,7 @@ async function runMigrateOpenSpec(source, options) {
9754
10462
  }
9755
10463
  sourcePath = openspec.path;
9756
10464
  }
9757
- const sddPath = path18.join(projectRoot, ".sdd");
10465
+ const sddPath = path20.join(projectRoot, ".sdd");
9758
10466
  info("\u{1F504} OpenSpec\uC5D0\uC11C \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC911...");
9759
10467
  info(` \uC18C\uC2A4: ${sourcePath}`);
9760
10468
  info(` \uB300\uC0C1: ${sddPath}`);
@@ -9794,7 +10502,7 @@ async function runMigrateSpecKit(source, options) {
9794
10502
  }
9795
10503
  let sourcePath;
9796
10504
  if (source) {
9797
- sourcePath = path18.resolve(source);
10505
+ sourcePath = path20.resolve(source);
9798
10506
  } else {
9799
10507
  const detectResult = await detectExternalTools(projectRoot);
9800
10508
  if (!detectResult.success) {
@@ -9808,7 +10516,7 @@ async function runMigrateSpecKit(source, options) {
9808
10516
  }
9809
10517
  sourcePath = speckit.path;
9810
10518
  }
9811
- const sddPath = path18.join(projectRoot, ".sdd");
10519
+ const sddPath = path20.join(projectRoot, ".sdd");
9812
10520
  info("\u{1F504} Spec Kit\uC5D0\uC11C \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC911...");
9813
10521
  info(` \uC18C\uC2A4: ${sourcePath}`);
9814
10522
  info(` \uB300\uC0C1: ${sddPath}`);
@@ -9878,7 +10586,7 @@ function getConfidenceLabel(confidence) {
9878
10586
  }
9879
10587
 
9880
10588
  // src/cli/commands/cicd.ts
9881
- import path19 from "path";
10589
+ import path21 from "path";
9882
10590
  init_errors();
9883
10591
  init_fs();
9884
10592
  function registerCicdCommand(program2) {
@@ -9930,16 +10638,20 @@ async function runSetup(platform, options) {
9930
10638
  listItem("PR/MR \uC0DD\uC131 \uC2DC \uC790\uB3D9\uC73C\uB85C \uC2A4\uD399 \uAC80\uC99D\uC774 \uC2E4\uD589\uB429\uB2C8\uB2E4");
9931
10639
  }
9932
10640
  async function setupGitHubActions(projectRoot, strict) {
9933
- const workflowDir = path19.join(projectRoot, ".github", "workflows");
10641
+ const workflowDir = path21.join(projectRoot, ".github", "workflows");
9934
10642
  await ensureDir(workflowDir);
9935
- const workflowContent = generateGitHubWorkflow(strict);
9936
- const workflowPath = path19.join(workflowDir, "sdd-validate.yml");
9937
- await writeFile(workflowPath, workflowContent);
10643
+ const validateContent = generateGitHubWorkflow(strict);
10644
+ const validatePath = path21.join(workflowDir, "sdd-validate.yml");
10645
+ await writeFile(validatePath, validateContent);
9938
10646
  info(`\u2705 GitHub Actions \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC0DD\uC131: .github/workflows/sdd-validate.yml`);
10647
+ const labelerContent = generateGitHubLabeler();
10648
+ const labelerPath = path21.join(workflowDir, "sdd-labeler.yml");
10649
+ await writeFile(labelerPath, labelerContent);
10650
+ info(`\u2705 GitHub Actions \uB77C\uBCA8\uB7EC \uC0DD\uC131: .github/workflows/sdd-labeler.yml`);
9939
10651
  }
9940
10652
  async function setupGitLabCI(projectRoot, strict) {
9941
10653
  const ciContent = generateGitLabCI(strict);
9942
- const ciPath = path19.join(projectRoot, ".gitlab-ci-sdd.yml");
10654
+ const ciPath = path21.join(projectRoot, ".gitlab-ci-sdd.yml");
9943
10655
  await writeFile(ciPath, ciContent);
9944
10656
  info(`\u2705 GitLab CI \uAD6C\uC131 \uC0DD\uC131: .gitlab-ci-sdd.yml`);
9945
10657
  info(" (\uAE30\uC874 .gitlab-ci.yml\uC5D0 include\uD558\uAC70\uB098 \uBCD1\uD569\uD558\uC138\uC694)");
@@ -9998,6 +10710,69 @@ jobs:
9998
10710
  path: impact-report.json
9999
10711
  `;
10000
10712
  }
10713
+ function generateGitHubLabeler() {
10714
+ return `# SDD PR \uB77C\uBCA8\uB7EC \uC6CC\uD06C\uD50C\uB85C\uC6B0
10715
+ # \uBCC0\uACBD\uB41C \uB3C4\uBA54\uC778\uC5D0 \uB530\uB77C \uC790\uB3D9\uC73C\uB85C \uB77C\uBCA8\uC744 \uCD94\uAC00\uD569\uB2C8\uB2E4
10716
+
10717
+ name: SDD Labeler
10718
+
10719
+ on:
10720
+ pull_request:
10721
+ types: [opened, synchronize]
10722
+ paths:
10723
+ - '.sdd/**'
10724
+
10725
+ jobs:
10726
+ label:
10727
+ name: Add Labels
10728
+ runs-on: ubuntu-latest
10729
+
10730
+ steps:
10731
+ - name: Checkout
10732
+ uses: actions/checkout@v4
10733
+ with:
10734
+ fetch-depth: 0
10735
+
10736
+ - name: Detect Changes
10737
+ id: changes
10738
+ run: |
10739
+ # \uBCC0\uACBD\uB41C \uB3C4\uBA54\uC778 \uAC10\uC9C0
10740
+ DOMAINS=$(git diff --name-only origin/\${{ github.base_ref }} | \\
10741
+ grep "^\\.sdd/specs/" | \\
10742
+ cut -d'/' -f3 | \\
10743
+ sort -u | \\
10744
+ tr '\\n' ' ')
10745
+ echo "domains=$DOMAINS" >> $GITHUB_OUTPUT
10746
+
10747
+ # Constitution \uBCC0\uACBD \uAC10\uC9C0
10748
+ if git diff --name-only origin/\${{ github.base_ref }} | grep -q "constitution.md"; then
10749
+ echo "constitution=true" >> $GITHUB_OUTPUT
10750
+ else
10751
+ echo "constitution=false" >> $GITHUB_OUTPUT
10752
+ fi
10753
+
10754
+ - name: Apply Labels
10755
+ uses: actions/github-script@v7
10756
+ with:
10757
+ script: |
10758
+ const labels = [];
10759
+ const domains = '\${{ steps.changes.outputs.domains }}'.trim().split(' ').filter(Boolean);
10760
+ labels.push(...domains.map(d => \`spec:\${d}\`));
10761
+
10762
+ if ('\${{ steps.changes.outputs.constitution }}' === 'true') {
10763
+ labels.push('constitution');
10764
+ }
10765
+
10766
+ if (labels.length > 0) {
10767
+ await github.rest.issues.addLabels({
10768
+ issue_number: context.issue.number,
10769
+ owner: context.repo.owner,
10770
+ repo: context.repo.repo,
10771
+ labels: labels,
10772
+ });
10773
+ }
10774
+ `;
10775
+ }
10001
10776
  function generateGitLabCI(strict) {
10002
10777
  const strictFlag = strict ? " --strict" : "";
10003
10778
  return `# SDD \uC2A4\uD399 \uAC80\uC99D \uD30C\uC774\uD504\uB77C\uC778
@@ -10035,7 +10810,7 @@ async function runHooksSetup(type, options) {
10035
10810
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
10036
10811
  process.exit(ExitCode.GENERAL_ERROR);
10037
10812
  }
10038
- const hooksDir = path19.join(projectRoot, ".husky");
10813
+ const hooksDir = path21.join(projectRoot, ".husky");
10039
10814
  if (options.install) {
10040
10815
  info("husky \uC124\uCE58 \uBC29\uBC95:");
10041
10816
  newline();
@@ -10049,7 +10824,7 @@ async function runHooksSetup(type, options) {
10049
10824
  const hooks = type ? [type] : ["pre-commit", "pre-push"];
10050
10825
  for (const hook of hooks) {
10051
10826
  const hookContent = generateHookScript(hook);
10052
- const hookPath = path19.join(hooksDir, hook);
10827
+ const hookPath = path21.join(hooksDir, hook);
10053
10828
  await writeFile(hookPath, hookContent);
10054
10829
  info(`\u2705 ${hook} \uD6C5 \uC0DD\uC131: .husky/${hook}`);
10055
10830
  }
@@ -10131,7 +10906,7 @@ async function runCiCheck(options) {
10131
10906
  let hasErrors = false;
10132
10907
  let hasWarnings = false;
10133
10908
  info("1. Constitution \uAC80\uC99D...");
10134
- const constitutionPath = path19.join(projectRoot, ".sdd", "constitution.md");
10909
+ const constitutionPath = path21.join(projectRoot, ".sdd", "constitution.md");
10135
10910
  if (await fileExists(constitutionPath)) {
10136
10911
  info(" \u2705 constitution.md \uC874\uC7AC");
10137
10912
  } else {
@@ -10139,7 +10914,7 @@ async function runCiCheck(options) {
10139
10914
  hasWarnings = true;
10140
10915
  }
10141
10916
  info("2. \uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC \uD655\uC778...");
10142
- const specsPath = path19.join(projectRoot, ".sdd", "specs");
10917
+ const specsPath = path21.join(projectRoot, ".sdd", "specs");
10143
10918
  if (await directoryExists(specsPath)) {
10144
10919
  info(" \u2705 specs/ \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC");
10145
10920
  } else {
@@ -10149,7 +10924,7 @@ async function runCiCheck(options) {
10149
10924
  info("3. \uAE30\uBCF8 \uAD6C\uC870 \uD655\uC778...");
10150
10925
  const requiredDirs = ["changes", "archive", "templates"];
10151
10926
  for (const dir of requiredDirs) {
10152
- const dirPath = path19.join(projectRoot, ".sdd", dir);
10927
+ const dirPath = path21.join(projectRoot, ".sdd", dir);
10153
10928
  if (await directoryExists(dirPath)) {
10154
10929
  info(` \u2705 ${dir}/ \uC874\uC7AC`);
10155
10930
  } else {
@@ -10177,15 +10952,15 @@ async function runCiCheck(options) {
10177
10952
  }
10178
10953
 
10179
10954
  // src/cli/commands/transition.ts
10180
- import path20 from "path";
10181
- import { promises as fs12 } from "fs";
10955
+ import path22 from "path";
10956
+ import { promises as fs13 } from "fs";
10182
10957
  init_errors();
10183
10958
  init_fs();
10184
10959
  init_types();
10185
10960
  async function getExistingChangeIds(sddPath) {
10186
- const changesPath = path20.join(sddPath, "changes");
10961
+ const changesPath = path22.join(sddPath, "changes");
10187
10962
  try {
10188
- const dirs = await fs12.readdir(changesPath);
10963
+ const dirs = await fs13.readdir(changesPath);
10189
10964
  return dirs.filter((d) => d.startsWith("CHG-"));
10190
10965
  } catch {
10191
10966
  return [];
@@ -10203,24 +10978,24 @@ async function extractProposalTitle(proposalPath) {
10203
10978
  return titleMatch ? titleMatch[1] : "";
10204
10979
  }
10205
10980
  async function transitionNewToChange(sddPath, specId, options) {
10206
- const specsPath = path20.join(sddPath, "specs");
10207
- const specPath = path20.join(specsPath, specId, "spec.md");
10981
+ const specsPath = path22.join(sddPath, "specs");
10982
+ const specPath = path22.join(specsPath, specId, "spec.md");
10208
10983
  if (!await fileExists(specPath)) {
10209
10984
  return failure(new Error(`\uC2A4\uD399\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${specId}`));
10210
10985
  }
10211
10986
  const existingIds = await getExistingChangeIds(sddPath);
10212
10987
  const changeId = generateChangeId(existingIds);
10213
- const changesPath = path20.join(sddPath, "changes");
10214
- const changePath = path20.join(changesPath, changeId);
10988
+ const changesPath = path22.join(sddPath, "changes");
10989
+ const changePath = path22.join(changesPath, changeId);
10215
10990
  await ensureDir(changePath);
10216
10991
  const title2 = options.title || `${specId} \uAE30\uB2A5 \uD655\uC7A5`;
10217
10992
  const reason = options.reason || "new \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
10218
10993
  const proposalContent = generateTransitionProposal(specId, title2, reason, "new-to-change");
10219
- await writeFile(path20.join(changePath, "proposal.md"), proposalContent);
10994
+ await writeFile(path22.join(changePath, "proposal.md"), proposalContent);
10220
10995
  const deltaContent = generateDeltaTemplate2(specId);
10221
- await writeFile(path20.join(changePath, "delta.md"), deltaContent);
10996
+ await writeFile(path22.join(changePath, "delta.md"), deltaContent);
10222
10997
  const tasksContent = generateTasksTemplate2();
10223
- await writeFile(path20.join(changePath, "tasks.md"), tasksContent);
10998
+ await writeFile(path22.join(changePath, "tasks.md"), tasksContent);
10224
10999
  return success({
10225
11000
  changeId,
10226
11001
  changePath,
@@ -10228,27 +11003,27 @@ async function transitionNewToChange(sddPath, specId, options) {
10228
11003
  });
10229
11004
  }
10230
11005
  async function transitionChangeToNew(sddPath, changeId, options) {
10231
- const changePath = path20.join(sddPath, "changes", changeId);
11006
+ const changePath = path22.join(sddPath, "changes", changeId);
10232
11007
  if (!await directoryExists(changePath)) {
10233
11008
  return failure(new Error(`\uBCC0\uACBD \uC81C\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${changeId}`));
10234
11009
  }
10235
- const proposalPath = path20.join(changePath, "proposal.md");
11010
+ const proposalPath = path22.join(changePath, "proposal.md");
10236
11011
  const extractedTitle = await extractProposalTitle(proposalPath);
10237
11012
  const featureName = options.name || extractedTitle.toLowerCase().replace(/\s+/g, "-") || `feature-from-${changeId}`;
10238
- const specsPath = path20.join(sddPath, "specs");
10239
- const newSpecPath = path20.join(specsPath, featureName);
11013
+ const specsPath = path22.join(sddPath, "specs");
11014
+ const newSpecPath = path22.join(specsPath, featureName);
10240
11015
  if (await directoryExists(newSpecPath)) {
10241
11016
  return failure(new Error(`\uC2A4\uD399\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${featureName}`));
10242
11017
  }
10243
11018
  await ensureDir(newSpecPath);
10244
11019
  const reason = options.reason || "change \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC804\uD658\uB428";
10245
11020
  const specContent = generateTransitionSpec(featureName, extractedTitle || featureName, reason, changeId);
10246
- await writeFile(path20.join(newSpecPath, "spec.md"), specContent);
11021
+ await writeFile(path22.join(newSpecPath, "spec.md"), specContent);
10247
11022
  const planContent = generatePlanTemplate(featureName);
10248
- await writeFile(path20.join(newSpecPath, "plan.md"), planContent);
11023
+ await writeFile(path22.join(newSpecPath, "plan.md"), planContent);
10249
11024
  const tasksContent = generateTasksTemplate2();
10250
- await writeFile(path20.join(newSpecPath, "tasks.md"), tasksContent);
10251
- const statusPath = path20.join(changePath, ".status");
11025
+ await writeFile(path22.join(newSpecPath, "tasks.md"), tasksContent);
11026
+ const statusPath = path22.join(changePath, ".status");
10252
11027
  await writeFile(statusPath, JSON.stringify({
10253
11028
  status: "transitioned",
10254
11029
  transitionedTo: featureName,
@@ -10329,7 +11104,7 @@ async function runNewToChange(specId, options) {
10329
11104
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
10330
11105
  process.exit(ExitCode.GENERAL_ERROR);
10331
11106
  }
10332
- const sddPath = path20.join(projectRoot, ".sdd");
11107
+ const sddPath = path22.join(projectRoot, ".sdd");
10333
11108
  info("=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658: new \u2192 change ===");
10334
11109
  newline();
10335
11110
  info(`\uB300\uC0C1 \uC2A4\uD399: ${specId}`);
@@ -10361,7 +11136,7 @@ async function runChangeToNew(changeId, options) {
10361
11136
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
10362
11137
  process.exit(ExitCode.GENERAL_ERROR);
10363
11138
  }
10364
- const sddPath = path20.join(projectRoot, ".sdd");
11139
+ const sddPath = path22.join(projectRoot, ".sdd");
10365
11140
  info("=== \uC6CC\uD06C\uD50C\uB85C\uC6B0 \uC804\uD658: change \u2192 new ===");
10366
11141
  newline();
10367
11142
  info(`\uC6D0\uBCF8 \uBCC0\uACBD: ${changeId}`);
@@ -10568,11 +11343,11 @@ status: pending
10568
11343
  }
10569
11344
 
10570
11345
  // src/cli/commands/watch.ts
10571
- import path22 from "path";
11346
+ import path24 from "path";
10572
11347
 
10573
11348
  // src/core/watch/watcher.ts
10574
11349
  import chokidar from "chokidar";
10575
- import path21 from "path";
11350
+ import path23 from "path";
10576
11351
  import { EventEmitter } from "events";
10577
11352
  var SpecWatcher = class extends EventEmitter {
10578
11353
  watcher = null;
@@ -10643,7 +11418,7 @@ var SpecWatcher = class extends EventEmitter {
10643
11418
  const event = {
10644
11419
  type,
10645
11420
  path: filePath,
10646
- relativePath: path21.relative(this.specsPath, filePath),
11421
+ relativePath: path23.relative(this.specsPath, filePath),
10647
11422
  timestamp: /* @__PURE__ */ new Date()
10648
11423
  };
10649
11424
  this.pendingEvents.push(event);
@@ -10690,8 +11465,8 @@ async function runWatch(options) {
10690
11465
  error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
10691
11466
  process.exit(ExitCode.GENERAL_ERROR);
10692
11467
  }
10693
- const sddPath = path22.join(projectRoot, ".sdd");
10694
- const specsPath = path22.join(sddPath, "specs");
11468
+ const sddPath = path24.join(projectRoot, ".sdd");
11469
+ const specsPath = path24.join(sddPath, "specs");
10695
11470
  const debounceMs = parseInt(options.debounce || "500", 10);
10696
11471
  info("\u{1F441}\uFE0F Watch \uBAA8\uB4DC \uC2DC\uC791");
10697
11472
  info(` \uACBD\uB85C: ${specsPath}`);
@@ -10781,14 +11556,14 @@ async function runWatch(options) {
10781
11556
  }
10782
11557
 
10783
11558
  // src/cli/commands/quality.ts
10784
- import path24 from "path";
11559
+ import path26 from "path";
10785
11560
 
10786
11561
  // src/core/quality/analyzer.ts
10787
11562
  init_types();
10788
11563
  init_errors();
10789
11564
  init_fs();
10790
- import path23 from "path";
10791
- import { promises as fs13 } from "fs";
11565
+ import path25 from "path";
11566
+ import { promises as fs14 } from "fs";
10792
11567
  function getGrade(percentage) {
10793
11568
  if (percentage >= 90) return "A";
10794
11569
  if (percentage >= 80) return "B";
@@ -11056,7 +11831,7 @@ async function analyzeSpecQuality(specPath, sddPath) {
11056
11831
  return failure(new ChangeError(`\uC2A4\uD399 \uD30C\uC2F1 \uC2E4\uD328: ${parseResult.error.message}`));
11057
11832
  }
11058
11833
  const spec = parseResult.data;
11059
- const constitutionPath = path23.join(sddPath, "constitution.md");
11834
+ const constitutionPath = path25.join(sddPath, "constitution.md");
11060
11835
  const hasConstitution = await fileExists(constitutionPath);
11061
11836
  const items = [
11062
11837
  scoreRfc2119(content),
@@ -11073,7 +11848,7 @@ async function analyzeSpecQuality(specPath, sddPath) {
11073
11848
  const percentage = Math.round(totalScore / maxScore * 100);
11074
11849
  const grade = getGrade(percentage);
11075
11850
  const topSuggestions = items.filter((item) => item.suggestions.length > 0).sort((a, b) => a.percentage - b.percentage).slice(0, 3).flatMap((item) => item.suggestions);
11076
- const specId = spec.metadata.id || path23.basename(path23.dirname(specPath));
11851
+ const specId = spec.metadata.id || path25.basename(path25.dirname(specPath));
11077
11852
  const summary = `\uC2A4\uD399 '${specId}'\uC758 \uD488\uC9C8 \uC810\uC218: ${totalScore}/${maxScore} (${percentage}%, \uB4F1\uAE09: ${grade})`;
11078
11853
  return success({
11079
11854
  specId,
@@ -11096,7 +11871,7 @@ async function analyzeSpecQuality(specPath, sddPath) {
11096
11871
  }
11097
11872
  async function analyzeProjectQuality(sddPath) {
11098
11873
  try {
11099
- const specsPath = path23.join(sddPath, "specs");
11874
+ const specsPath = path25.join(sddPath, "specs");
11100
11875
  if (!await directoryExists(specsPath)) {
11101
11876
  return failure(new ChangeError("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
11102
11877
  }
@@ -11139,9 +11914,9 @@ async function analyzeProjectQuality(sddPath) {
11139
11914
  }
11140
11915
  }
11141
11916
  async function findSpecFiles2(dir, files) {
11142
- const entries = await fs13.readdir(dir, { withFileTypes: true });
11917
+ const entries = await fs14.readdir(dir, { withFileTypes: true });
11143
11918
  for (const entry of entries) {
11144
- const fullPath = path23.join(dir, entry.name);
11919
+ const fullPath = path25.join(dir, entry.name);
11145
11920
  if (entry.isDirectory()) {
11146
11921
  await findSpecFiles2(fullPath, files);
11147
11922
  } else if (entry.name === "spec.md") {
@@ -11205,7 +11980,7 @@ init_fs();
11205
11980
  init_errors();
11206
11981
  init_types();
11207
11982
  async function executeQuality(feature, options, projectRoot) {
11208
- const sddPath = path24.join(projectRoot, ".sdd");
11983
+ const sddPath = path26.join(projectRoot, ".sdd");
11209
11984
  const minScore = parseInt(options.minScore || "0", 10);
11210
11985
  if (options.all || !feature) {
11211
11986
  const result2 = await analyzeProjectQuality(sddPath);
@@ -11220,7 +11995,7 @@ async function executeQuality(feature, options, projectRoot) {
11220
11995
  passed: result2.data.averagePercentage >= minScore
11221
11996
  });
11222
11997
  }
11223
- const specPath = path24.join(sddPath, "specs", feature, "spec.md");
11998
+ const specPath = path26.join(sddPath, "specs", feature, "spec.md");
11224
11999
  const result = await analyzeSpecQuality(specPath, sddPath);
11225
12000
  if (!result.success) {
11226
12001
  return failure(result.error);
@@ -11264,13 +12039,13 @@ async function runQuality(feature, options) {
11264
12039
  }
11265
12040
 
11266
12041
  // src/cli/commands/report.ts
11267
- import path26 from "path";
12042
+ import path28 from "path";
11268
12043
 
11269
12044
  // src/core/report/reporter.ts
11270
12045
  init_types();
11271
12046
  init_errors();
11272
- import path25 from "path";
11273
- import fs14 from "fs/promises";
12047
+ import path27 from "path";
12048
+ import fs15 from "fs/promises";
11274
12049
  init_fs();
11275
12050
  async function loadSpecList(specsPath) {
11276
12051
  try {
@@ -11283,12 +12058,12 @@ async function loadSpecList(specsPath) {
11283
12058
  }
11284
12059
  const specs = [];
11285
12060
  for (const entry of result.data) {
11286
- const featurePath = path25.join(specsPath, entry);
11287
- const stat = await fs14.stat(featurePath);
12061
+ const featurePath = path27.join(specsPath, entry);
12062
+ const stat = await fs15.stat(featurePath);
11288
12063
  if (stat.isDirectory()) {
11289
- const specFile = path25.join(featurePath, "spec.md");
12064
+ const specFile = path27.join(featurePath, "spec.md");
11290
12065
  if (await fileExists(specFile)) {
11291
- const content = await fs14.readFile(specFile, "utf-8");
12066
+ const content = await fs15.readFile(specFile, "utf-8");
11292
12067
  const metadata = parseSpecMetadata2(content);
11293
12068
  specs.push({
11294
12069
  id: entry,
@@ -11326,7 +12101,7 @@ function parseSpecMetadata2(content) {
11326
12101
  }
11327
12102
  async function generateReport(sddPath, options) {
11328
12103
  try {
11329
- const specsPath = path25.join(sddPath, "specs");
12104
+ const specsPath = path27.join(sddPath, "specs");
11330
12105
  const specsResult = await loadSpecList(specsPath);
11331
12106
  if (!specsResult.success) {
11332
12107
  return failure(specsResult.error);
@@ -11379,8 +12154,8 @@ async function generateReport(sddPath, options) {
11379
12154
  return failure(new ChangeError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD\uC785\uB2C8\uB2E4: ${options.format}`));
11380
12155
  }
11381
12156
  if (options.outputPath) {
11382
- await fs14.mkdir(path25.dirname(options.outputPath), { recursive: true });
11383
- await fs14.writeFile(options.outputPath, content, "utf-8");
12157
+ await fs15.mkdir(path27.dirname(options.outputPath), { recursive: true });
12158
+ await fs15.writeFile(options.outputPath, content, "utf-8");
11384
12159
  }
11385
12160
  return success({
11386
12161
  format: options.format,
@@ -11655,12 +12430,12 @@ function resolveOutputPath(format, output, projectRoot) {
11655
12430
  if (!output) {
11656
12431
  const ext = format === "markdown" ? "md" : format;
11657
12432
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
11658
- return path26.join(projectRoot, `sdd-report-${timestamp}.${ext}`);
12433
+ return path28.join(projectRoot, `sdd-report-${timestamp}.${ext}`);
11659
12434
  }
11660
- return path26.isAbsolute(output) ? output : path26.join(projectRoot, output);
12435
+ return path28.isAbsolute(output) ? output : path28.join(projectRoot, output);
11661
12436
  }
11662
12437
  async function executeReport(options, projectRoot) {
11663
- const sddPath = path26.join(projectRoot, ".sdd");
12438
+ const sddPath = path28.join(projectRoot, ".sdd");
11664
12439
  const format = options.format || "html";
11665
12440
  if (!isValidReportFormat(format)) {
11666
12441
  return failure(new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD\uC785\uB2C8\uB2E4: ${format}. \uC9C0\uC6D0 \uD615\uC2DD: html, markdown, json`));
@@ -11718,19 +12493,19 @@ async function runReport(options) {
11718
12493
  }
11719
12494
 
11720
12495
  // src/cli/commands/search.ts
11721
- import path28 from "path";
12496
+ import path30 from "path";
11722
12497
  init_fs();
11723
12498
 
11724
12499
  // src/core/search/searcher.ts
11725
12500
  init_types();
11726
12501
  init_errors();
11727
12502
  init_fs();
11728
- import path27 from "path";
11729
- import { promises as fs15 } from "fs";
12503
+ import path29 from "path";
12504
+ import { promises as fs16 } from "fs";
11730
12505
  async function searchSpecs(sddPath, options = {}) {
11731
12506
  const startTime = Date.now();
11732
12507
  try {
11733
- const specsPath = path27.join(sddPath, "specs");
12508
+ const specsPath = path29.join(sddPath, "specs");
11734
12509
  if (!await directoryExists(specsPath)) {
11735
12510
  return failure(new ChangeError("\uC2A4\uD399 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
11736
12511
  }
@@ -11780,17 +12555,17 @@ async function buildSearchIndex(specsPath) {
11780
12555
  }
11781
12556
  }
11782
12557
  async function collectSpecs(basePath, currentPath, index) {
11783
- const entries = await fs15.readdir(currentPath, { withFileTypes: true });
12558
+ const entries = await fs16.readdir(currentPath, { withFileTypes: true });
11784
12559
  for (const entry of entries) {
11785
- const fullPath = path27.join(currentPath, entry.name);
12560
+ const fullPath = path29.join(currentPath, entry.name);
11786
12561
  if (entry.isDirectory()) {
11787
12562
  await collectSpecs(basePath, fullPath, index);
11788
12563
  } else if (entry.name === "spec.md") {
11789
- const content = await fs15.readFile(fullPath, "utf-8");
11790
- const relativePath = path27.relative(basePath, fullPath);
11791
- const specId = path27.dirname(relativePath);
12564
+ const content = await fs16.readFile(fullPath, "utf-8");
12565
+ const relativePath = path29.relative(basePath, fullPath);
12566
+ const specId = path29.dirname(relativePath);
11792
12567
  const metadata = parseMetadata(content);
11793
- const stat = await fs15.stat(fullPath);
12568
+ const stat = await fs16.stat(fullPath);
11794
12569
  index.push({
11795
12570
  id: specId === "." ? entry.name : specId,
11796
12571
  path: relativePath,
@@ -12088,7 +12863,7 @@ function registerSearchCommand(program2) {
12088
12863
  });
12089
12864
  }
12090
12865
  async function executeSearch(query, options) {
12091
- const sddPath = path28.join(process.cwd(), ".sdd");
12866
+ const sddPath = path30.join(process.cwd(), ".sdd");
12092
12867
  if (!await fileExists(sddPath)) {
12093
12868
  logger_exports.error(".sdd \uB514\uB809\uD1A0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. sdd init\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
12094
12869
  return;
@@ -12108,7 +12883,7 @@ async function executeSearch(query, options) {
12108
12883
  }
12109
12884
 
12110
12885
  // src/cli/commands/prepare.ts
12111
- import path35 from "path";
12886
+ import path37 from "path";
12112
12887
 
12113
12888
  // src/core/prepare/schemas.ts
12114
12889
  import { z as z8 } from "zod";
@@ -12243,19 +13018,19 @@ var DEFAULT_KEYWORD_MAPPINGS = [
12243
13018
  ];
12244
13019
 
12245
13020
  // src/core/prepare/document-analyzer.ts
12246
- import * as fs16 from "fs";
12247
- import * as path29 from "path";
13021
+ import * as fs17 from "fs";
13022
+ import * as path31 from "path";
12248
13023
  var DocumentAnalyzer = class {
12249
13024
  sddDir;
12250
13025
  constructor(projectRoot) {
12251
- this.sddDir = path29.join(projectRoot, ".sdd");
13026
+ this.sddDir = path31.join(projectRoot, ".sdd");
12252
13027
  }
12253
13028
  /**
12254
13029
  * 기능 디렉토리의 모든 문서 분석
12255
13030
  */
12256
13031
  async analyzeFeature(featureName) {
12257
- const featureDir = path29.join(this.sddDir, "specs", featureName);
12258
- if (!fs16.existsSync(featureDir)) {
13032
+ const featureDir = path31.join(this.sddDir, "specs", featureName);
13033
+ if (!fs17.existsSync(featureDir)) {
12259
13034
  throw new Error(`\uAE30\uB2A5 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${featureDir}`);
12260
13035
  }
12261
13036
  const results = [];
@@ -12265,8 +13040,8 @@ var DocumentAnalyzer = class {
12265
13040
  { file: "spec.md", type: "spec" }
12266
13041
  ];
12267
13042
  for (const doc of documents) {
12268
- const filePath = path29.join(featureDir, doc.file);
12269
- if (fs16.existsSync(filePath)) {
13043
+ const filePath = path31.join(featureDir, doc.file);
13044
+ if (fs17.existsSync(filePath)) {
12270
13045
  const analysis = await this.analyzeDocument(filePath, doc.type);
12271
13046
  results.push(analysis);
12272
13047
  }
@@ -12277,7 +13052,7 @@ var DocumentAnalyzer = class {
12277
13052
  * 단일 문서 분석
12278
13053
  */
12279
13054
  async analyzeDocument(filePath, type) {
12280
- const content = fs16.readFileSync(filePath, "utf-8");
13055
+ const content = fs17.readFileSync(filePath, "utf-8");
12281
13056
  const lines = content.split("\n");
12282
13057
  const analyzedLines = [];
12283
13058
  let taskCount = 0;
@@ -12343,7 +13118,7 @@ var DocumentAnalyzer = class {
12343
13118
  for (const line of analysis.lines) {
12344
13119
  for (const keyword of line.keywords) {
12345
13120
  sources.push({
12346
- file: path29.basename(analysis.file),
13121
+ file: path31.basename(analysis.file),
12347
13122
  line: line.lineNumber,
12348
13123
  text: line.content,
12349
13124
  keyword
@@ -12460,19 +13235,19 @@ var ToolDetector = class {
12460
13235
  };
12461
13236
 
12462
13237
  // src/core/prepare/agent-scanner.ts
12463
- import * as fs17 from "fs";
12464
- import * as path30 from "path";
13238
+ import * as fs18 from "fs";
13239
+ import * as path32 from "path";
12465
13240
  import matter6 from "gray-matter";
12466
13241
  var AgentScanner = class {
12467
13242
  agentsDir;
12468
13243
  constructor(projectRoot) {
12469
- this.agentsDir = path30.join(projectRoot, ".claude", "agents");
13244
+ this.agentsDir = path32.join(projectRoot, ".claude", "agents");
12470
13245
  }
12471
13246
  /**
12472
13247
  * 에이전트 디렉토리 존재 여부
12473
13248
  */
12474
13249
  exists() {
12475
- return fs17.existsSync(this.agentsDir);
13250
+ return fs18.existsSync(this.agentsDir);
12476
13251
  }
12477
13252
  /**
12478
13253
  * 모든 에이전트 스캔
@@ -12482,10 +13257,10 @@ var AgentScanner = class {
12482
13257
  return [];
12483
13258
  }
12484
13259
  const agents = [];
12485
- const files = fs17.readdirSync(this.agentsDir);
13260
+ const files = fs18.readdirSync(this.agentsDir);
12486
13261
  for (const file of files) {
12487
13262
  if (!file.endsWith(".md")) continue;
12488
- const filePath = path30.join(this.agentsDir, file);
13263
+ const filePath = path32.join(this.agentsDir, file);
12489
13264
  const agent = await this.scanAgent(filePath);
12490
13265
  if (agent) {
12491
13266
  agents.push(agent);
@@ -12498,7 +13273,7 @@ var AgentScanner = class {
12498
13273
  */
12499
13274
  async scanAgent(filePath) {
12500
13275
  try {
12501
- const content = fs17.readFileSync(filePath, "utf-8");
13276
+ const content = fs18.readFileSync(filePath, "utf-8");
12502
13277
  const { data, content: body } = matter6(content);
12503
13278
  const metadata = this.parseMetadata(data, filePath);
12504
13279
  if (!metadata) {
@@ -12518,15 +13293,15 @@ var AgentScanner = class {
12518
13293
  * 특정 에이전트 존재 확인
12519
13294
  */
12520
13295
  hasAgent(name) {
12521
- const filePath = path30.join(this.agentsDir, `${name}.md`);
12522
- return fs17.existsSync(filePath);
13296
+ const filePath = path32.join(this.agentsDir, `${name}.md`);
13297
+ return fs18.existsSync(filePath);
12523
13298
  }
12524
13299
  /**
12525
13300
  * 특정 에이전트 가져오기
12526
13301
  */
12527
13302
  async getAgent(name) {
12528
- const filePath = path30.join(this.agentsDir, `${name}.md`);
12529
- if (!fs17.existsSync(filePath)) {
13303
+ const filePath = path32.join(this.agentsDir, `${name}.md`);
13304
+ if (!fs18.existsSync(filePath)) {
12530
13305
  return null;
12531
13306
  }
12532
13307
  return this.scanAgent(filePath);
@@ -12543,7 +13318,7 @@ var AgentScanner = class {
12543
13318
  if (result.success) {
12544
13319
  return result.data;
12545
13320
  }
12546
- const fileName = path30.basename(filePath, ".md");
13321
+ const fileName = path32.basename(filePath, ".md");
12547
13322
  const withName = { ...data, name: data.name ?? fileName };
12548
13323
  const retryResult = AgentMetadataSchema.safeParse(withName);
12549
13324
  if (retryResult.success) {
@@ -12564,24 +13339,24 @@ var AgentScanner = class {
12564
13339
  * 에이전트 파일 경로 생성
12565
13340
  */
12566
13341
  getAgentFilePath(name) {
12567
- return path30.join(this.agentsDir, `${name}.md`);
13342
+ return path32.join(this.agentsDir, `${name}.md`);
12568
13343
  }
12569
13344
  };
12570
13345
 
12571
13346
  // src/core/prepare/skill-scanner.ts
12572
- import * as fs18 from "fs";
12573
- import * as path31 from "path";
13347
+ import * as fs19 from "fs";
13348
+ import * as path33 from "path";
12574
13349
  import matter7 from "gray-matter";
12575
13350
  var SkillScanner = class {
12576
13351
  skillsDir;
12577
13352
  constructor(projectRoot) {
12578
- this.skillsDir = path31.join(projectRoot, ".claude", "skills");
13353
+ this.skillsDir = path33.join(projectRoot, ".claude", "skills");
12579
13354
  }
12580
13355
  /**
12581
13356
  * 스킬 디렉토리 존재 여부
12582
13357
  */
12583
13358
  exists() {
12584
- return fs18.existsSync(this.skillsDir);
13359
+ return fs19.existsSync(this.skillsDir);
12585
13360
  }
12586
13361
  /**
12587
13362
  * 모든 스킬 스캔
@@ -12591,10 +13366,10 @@ var SkillScanner = class {
12591
13366
  return [];
12592
13367
  }
12593
13368
  const skills = [];
12594
- const entries = fs18.readdirSync(this.skillsDir, { withFileTypes: true });
13369
+ const entries = fs19.readdirSync(this.skillsDir, { withFileTypes: true });
12595
13370
  for (const entry of entries) {
12596
13371
  if (!entry.isDirectory()) continue;
12597
- const skillDir = path31.join(this.skillsDir, entry.name);
13372
+ const skillDir = path33.join(this.skillsDir, entry.name);
12598
13373
  const skill = await this.scanSkill(skillDir);
12599
13374
  if (skill) {
12600
13375
  skills.push(skill);
@@ -12606,12 +13381,12 @@ var SkillScanner = class {
12606
13381
  * 단일 스킬 디렉토리 스캔
12607
13382
  */
12608
13383
  async scanSkill(skillDir) {
12609
- const skillFile = path31.join(skillDir, "SKILL.md");
12610
- if (!fs18.existsSync(skillFile)) {
13384
+ const skillFile = path33.join(skillDir, "SKILL.md");
13385
+ if (!fs19.existsSync(skillFile)) {
12611
13386
  return null;
12612
13387
  }
12613
13388
  try {
12614
- const content = fs18.readFileSync(skillFile, "utf-8");
13389
+ const content = fs19.readFileSync(skillFile, "utf-8");
12615
13390
  const { data, content: body } = matter7(content);
12616
13391
  const metadata = this.parseMetadata(data, skillDir);
12617
13392
  if (!metadata) {
@@ -12632,16 +13407,16 @@ var SkillScanner = class {
12632
13407
  * 특정 스킬 존재 확인
12633
13408
  */
12634
13409
  hasSkill(name) {
12635
- const skillDir = path31.join(this.skillsDir, name);
12636
- const skillFile = path31.join(skillDir, "SKILL.md");
12637
- return fs18.existsSync(skillFile);
13410
+ const skillDir = path33.join(this.skillsDir, name);
13411
+ const skillFile = path33.join(skillDir, "SKILL.md");
13412
+ return fs19.existsSync(skillFile);
12638
13413
  }
12639
13414
  /**
12640
13415
  * 특정 스킬 가져오기
12641
13416
  */
12642
13417
  async getSkill(name) {
12643
- const skillDir = path31.join(this.skillsDir, name);
12644
- if (!fs18.existsSync(skillDir)) {
13418
+ const skillDir = path33.join(this.skillsDir, name);
13419
+ if (!fs19.existsSync(skillDir)) {
12645
13420
  return null;
12646
13421
  }
12647
13422
  return this.scanSkill(skillDir);
@@ -12658,7 +13433,7 @@ var SkillScanner = class {
12658
13433
  if (result.success) {
12659
13434
  return result.data;
12660
13435
  }
12661
- const dirName = path31.basename(skillDir);
13436
+ const dirName = path33.basename(skillDir);
12662
13437
  const withName = { ...data, name: data.name ?? dirName };
12663
13438
  const retryResult = SkillMetadataSchema.safeParse(withName);
12664
13439
  if (retryResult.success) {
@@ -12679,23 +13454,23 @@ var SkillScanner = class {
12679
13454
  * 스킬 디렉토리 경로 생성
12680
13455
  */
12681
13456
  getSkillDirPath(name) {
12682
- return path31.join(this.skillsDir, name);
13457
+ return path33.join(this.skillsDir, name);
12683
13458
  }
12684
13459
  /**
12685
13460
  * 스킬 파일 경로 생성
12686
13461
  */
12687
13462
  getSkillFilePath(name) {
12688
- return path31.join(this.skillsDir, name, "SKILL.md");
13463
+ return path33.join(this.skillsDir, name, "SKILL.md");
12689
13464
  }
12690
13465
  };
12691
13466
 
12692
13467
  // src/core/prepare/agent-generator.ts
12693
- import * as fs19 from "fs";
12694
- import * as path32 from "path";
13468
+ import * as fs20 from "fs";
13469
+ import * as path34 from "path";
12695
13470
  var AgentGenerator = class {
12696
13471
  agentsDir;
12697
13472
  constructor(projectRoot) {
12698
- this.agentsDir = path32.join(projectRoot, ".claude", "agents");
13473
+ this.agentsDir = path34.join(projectRoot, ".claude", "agents");
12699
13474
  }
12700
13475
  /**
12701
13476
  * 감지된 도구에서 에이전트 초안 생성
@@ -12704,7 +13479,7 @@ var AgentGenerator = class {
12704
13479
  const model = options?.model ?? "sonnet";
12705
13480
  const tools = options?.tools ?? this.getDefaultTools(tool.name);
12706
13481
  const content = this.generateContent(tool, model, tools);
12707
- const filePath = path32.join(this.agentsDir, `${tool.name}.md`);
13482
+ const filePath = path34.join(this.agentsDir, `${tool.name}.md`);
12708
13483
  return {
12709
13484
  name: tool.name,
12710
13485
  filePath,
@@ -12715,10 +13490,10 @@ var AgentGenerator = class {
12715
13490
  * 에이전트 파일 생성
12716
13491
  */
12717
13492
  async writeAgent(agent) {
12718
- if (!fs19.existsSync(this.agentsDir)) {
12719
- fs19.mkdirSync(this.agentsDir, { recursive: true });
13493
+ if (!fs20.existsSync(this.agentsDir)) {
13494
+ fs20.mkdirSync(this.agentsDir, { recursive: true });
12720
13495
  }
12721
- fs19.writeFileSync(agent.filePath, agent.content, "utf-8");
13496
+ fs20.writeFileSync(agent.filePath, agent.content, "utf-8");
12722
13497
  }
12723
13498
  /**
12724
13499
  * 에이전트 콘텐츠 생성
@@ -12817,12 +13592,12 @@ var AgentGenerator = class {
12817
13592
  };
12818
13593
 
12819
13594
  // src/core/prepare/skill-generator.ts
12820
- import * as fs20 from "fs";
12821
- import * as path33 from "path";
13595
+ import * as fs21 from "fs";
13596
+ import * as path35 from "path";
12822
13597
  var SkillGenerator = class {
12823
13598
  skillsDir;
12824
13599
  constructor(projectRoot) {
12825
- this.skillsDir = path33.join(projectRoot, ".claude", "skills");
13600
+ this.skillsDir = path35.join(projectRoot, ".claude", "skills");
12826
13601
  }
12827
13602
  /**
12828
13603
  * 감지된 도구에서 스킬 초안 생성
@@ -12830,8 +13605,8 @@ var SkillGenerator = class {
12830
13605
  generate(tool, options) {
12831
13606
  const allowedTools = options?.allowedTools ?? this.getDefaultTools(tool.name);
12832
13607
  const content = this.generateContent(tool, allowedTools);
12833
- const dirPath = path33.join(this.skillsDir, tool.name);
12834
- const filePath = path33.join(dirPath, "SKILL.md");
13608
+ const dirPath = path35.join(this.skillsDir, tool.name);
13609
+ const filePath = path35.join(dirPath, "SKILL.md");
12835
13610
  return {
12836
13611
  name: tool.name,
12837
13612
  dirPath,
@@ -12843,10 +13618,10 @@ var SkillGenerator = class {
12843
13618
  * 스킬 파일 생성
12844
13619
  */
12845
13620
  async writeSkill(skill) {
12846
- if (!fs20.existsSync(skill.dirPath)) {
12847
- fs20.mkdirSync(skill.dirPath, { recursive: true });
13621
+ if (!fs21.existsSync(skill.dirPath)) {
13622
+ fs21.mkdirSync(skill.dirPath, { recursive: true });
12848
13623
  }
12849
- fs20.writeFileSync(skill.filePath, skill.content, "utf-8");
13624
+ fs21.writeFileSync(skill.filePath, skill.content, "utf-8");
12850
13625
  }
12851
13626
  /**
12852
13627
  * 스킬 콘텐츠 생성
@@ -13001,12 +13776,12 @@ var SkillGenerator = class {
13001
13776
  };
13002
13777
 
13003
13778
  // src/core/prepare/prepare-report.ts
13004
- import * as fs21 from "fs";
13005
- import * as path34 from "path";
13779
+ import * as fs22 from "fs";
13780
+ import * as path36 from "path";
13006
13781
  var PrepareReportGenerator = class {
13007
13782
  sddDir;
13008
13783
  constructor(projectRoot) {
13009
- this.sddDir = path34.join(projectRoot, ".sdd");
13784
+ this.sddDir = path36.join(projectRoot, ".sdd");
13010
13785
  }
13011
13786
  /**
13012
13787
  * 보고서 생성
@@ -13121,13 +13896,13 @@ var PrepareReportGenerator = class {
13121
13896
  * 보고서 파일 저장
13122
13897
  */
13123
13898
  async writeReport(feature, report) {
13124
- const featureDir = path34.join(this.sddDir, "specs", feature);
13125
- if (!fs21.existsSync(featureDir)) {
13126
- fs21.mkdirSync(featureDir, { recursive: true });
13899
+ const featureDir = path36.join(this.sddDir, "specs", feature);
13900
+ if (!fs22.existsSync(featureDir)) {
13901
+ fs22.mkdirSync(featureDir, { recursive: true });
13127
13902
  }
13128
- const filePath = path34.join(featureDir, "prepare.md");
13903
+ const filePath = path36.join(featureDir, "prepare.md");
13129
13904
  const content = this.toMarkdown(report);
13130
- fs21.writeFileSync(filePath, content, "utf-8");
13905
+ fs22.writeFileSync(filePath, content, "utf-8");
13131
13906
  return filePath;
13132
13907
  }
13133
13908
  };
@@ -13265,12 +14040,12 @@ async function runPrepare(feature, options) {
13265
14040
  if (created.agents.length > 0 || created.skills.length > 0) {
13266
14041
  success2("\uC0DD\uC131\uB41C \uD30C\uC77C:");
13267
14042
  for (const file of [...created.agents, ...created.skills]) {
13268
- console.log(` - ${path35.relative(projectRoot, file)}`);
14043
+ console.log(` - ${path37.relative(projectRoot, file)}`);
13269
14044
  }
13270
14045
  newline();
13271
14046
  }
13272
14047
  if (reportPath) {
13273
- info(`\uBCF4\uACE0\uC11C: ${path35.relative(projectRoot, reportPath)}`);
14048
+ info(`\uBCF4\uACE0\uC11C: ${path37.relative(projectRoot, reportPath)}`);
13274
14049
  }
13275
14050
  if (missingAgents.length > 0 || missingSkills.length > 0) {
13276
14051
  if (!options.autoApprove && !options.dryRun) {
@@ -13344,8 +14119,8 @@ var CodeReferenceSchema = z9.object({
13344
14119
  });
13345
14120
 
13346
14121
  // src/core/sync/spec-parser.ts
13347
- import { promises as fs22, statSync } from "fs";
13348
- import path36 from "path";
14122
+ import { promises as fs23, statSync } from "fs";
14123
+ import path38 from "path";
13349
14124
  import matter8 from "gray-matter";
13350
14125
  var REQ_ID_PATTERN = /\b(REQ-\d+)\b/gi;
13351
14126
  var RFC_KEYWORD_PATTERN = /\((SHALL|MUST|SHOULD|MAY|SHALL NOT|MUST NOT)\)/i;
@@ -13353,26 +14128,26 @@ var REQ_HEADER_PATTERN = /^#{2,4}\s*(REQ-\d+):\s*(.+)$/i;
13353
14128
  var SpecParser = class {
13354
14129
  constructor(projectRoot) {
13355
14130
  this.projectRoot = projectRoot;
13356
- this.specsDir = path36.join(projectRoot, ".sdd", "specs");
14131
+ this.specsDir = path38.join(projectRoot, ".sdd", "specs");
13357
14132
  }
13358
14133
  specsDir;
13359
14134
  /**
13360
14135
  * 특정 스펙에서 요구사항 추출
13361
14136
  */
13362
14137
  async parseSpec(specId) {
13363
- const specPath = path36.join(this.specsDir, specId, "spec.md");
14138
+ const specPath = path38.join(this.specsDir, specId, "spec.md");
13364
14139
  try {
13365
- const content = await fs22.readFile(specPath, "utf-8");
14140
+ const content = await fs23.readFile(specPath, "utf-8");
13366
14141
  return this.extractRequirements(content, specId);
13367
14142
  } catch {
13368
- const specDir = path36.join(this.specsDir, specId);
14143
+ const specDir = path38.join(this.specsDir, specId);
13369
14144
  try {
13370
- const files = await fs22.readdir(specDir);
14145
+ const files = await fs23.readdir(specDir);
13371
14146
  const mdFiles = files.filter((f) => f.endsWith(".md"));
13372
14147
  const requirements = [];
13373
14148
  for (const file of mdFiles) {
13374
- const filePath = path36.join(specDir, file);
13375
- const content = await fs22.readFile(filePath, "utf-8");
14149
+ const filePath = path38.join(specDir, file);
14150
+ const content = await fs23.readFile(filePath, "utf-8");
13376
14151
  requirements.push(...this.extractRequirements(content, specId));
13377
14152
  }
13378
14153
  return requirements;
@@ -13398,7 +14173,7 @@ var SpecParser = class {
13398
14173
  */
13399
14174
  async listSpecs() {
13400
14175
  try {
13401
- const entries = await fs22.readdir(this.specsDir, { withFileTypes: true });
14176
+ const entries = await fs23.readdir(this.specsDir, { withFileTypes: true });
13402
14177
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
13403
14178
  } catch {
13404
14179
  return [];
@@ -13475,8 +14250,8 @@ var SpecParser = class {
13475
14250
  };
13476
14251
 
13477
14252
  // src/core/sync/code-scanner.ts
13478
- import { promises as fs23, statSync as statSync2 } from "fs";
13479
- import path37 from "path";
14253
+ import { promises as fs24, statSync as statSync2 } from "fs";
14254
+ import path39 from "path";
13480
14255
  import { glob } from "glob";
13481
14256
  var SPEC_ANNOTATION_PATTERN = /@spec:?\s+(REQ-\d+(?:\s*,\s*REQ-\d+)*)/gi;
13482
14257
  var REQ_ID_PATTERN2 = /REQ-\d+/gi;
@@ -13495,7 +14270,7 @@ var DEFAULT_INCLUDE = ["**/*.ts", "**/*.js", "**/*.tsx", "**/*.jsx"];
13495
14270
  var CodeScanner = class {
13496
14271
  constructor(projectRoot, options = {}) {
13497
14272
  this.projectRoot = projectRoot;
13498
- this.srcDir = options.srcDir || path37.join(projectRoot, "src");
14273
+ this.srcDir = options.srcDir || path39.join(projectRoot, "src");
13499
14274
  this.include = options.include || DEFAULT_INCLUDE;
13500
14275
  this.exclude = options.exclude || DEFAULT_EXCLUDE;
13501
14276
  }
@@ -13519,7 +14294,7 @@ var CodeScanner = class {
13519
14294
  */
13520
14295
  async scanFile(filePath) {
13521
14296
  try {
13522
- const content = await fs23.readFile(filePath, "utf-8");
14297
+ const content = await fs24.readFile(filePath, "utf-8");
13523
14298
  const lines = content.split("\n");
13524
14299
  const references = [];
13525
14300
  for (let i = 0; i < lines.length; i++) {
@@ -13531,7 +14306,7 @@ var CodeScanner = class {
13531
14306
  for (const reqMatch of reqIds) {
13532
14307
  references.push({
13533
14308
  reqId: reqMatch[0].toUpperCase(),
13534
- file: path37.relative(this.projectRoot, filePath),
14309
+ file: path39.relative(this.projectRoot, filePath),
13535
14310
  line: lineNum,
13536
14311
  type: "code",
13537
14312
  context: line.trim()
@@ -13575,8 +14350,8 @@ var CodeScanner = class {
13575
14350
  };
13576
14351
 
13577
14352
  // src/core/sync/test-scanner.ts
13578
- import { promises as fs24 } from "fs";
13579
- import path38 from "path";
14353
+ import { promises as fs25 } from "fs";
14354
+ import path40 from "path";
13580
14355
  import { glob as glob2 } from "glob";
13581
14356
  var TEST_REQ_PATTERN = /(?:it|test|describe)\s*\(\s*['"`]([^'"`]*REQ-\d+[^'"`]*)/gi;
13582
14357
  var REQ_ID_PATTERN3 = /REQ-\d+/gi;
@@ -13616,7 +14391,7 @@ var TestScanner = class {
13616
14391
  */
13617
14392
  async scanFile(filePath) {
13618
14393
  try {
13619
- const content = await fs24.readFile(filePath, "utf-8");
14394
+ const content = await fs25.readFile(filePath, "utf-8");
13620
14395
  const lines = content.split("\n");
13621
14396
  const references = [];
13622
14397
  for (let i = 0; i < lines.length; i++) {
@@ -13629,7 +14404,7 @@ var TestScanner = class {
13629
14404
  for (const reqMatch of reqIds) {
13630
14405
  references.push({
13631
14406
  reqId: reqMatch[0].toUpperCase(),
13632
- file: path38.relative(this.projectRoot, filePath),
14407
+ file: path40.relative(this.projectRoot, filePath),
13633
14408
  line: lineNum,
13634
14409
  type: "test",
13635
14410
  context: testDescription.trim()
@@ -13642,12 +14417,12 @@ var TestScanner = class {
13642
14417
  for (const reqMatch of reqIds) {
13643
14418
  const reqId = reqMatch[0].toUpperCase();
13644
14419
  const exists = references.some(
13645
- (r) => r.reqId === reqId && r.file === path38.relative(this.projectRoot, filePath) && r.line === lineNum
14420
+ (r) => r.reqId === reqId && r.file === path40.relative(this.projectRoot, filePath) && r.line === lineNum
13646
14421
  );
13647
14422
  if (!exists) {
13648
14423
  references.push({
13649
14424
  reqId,
13650
- file: path38.relative(this.projectRoot, filePath),
14425
+ file: path40.relative(this.projectRoot, filePath),
13651
14426
  line: lineNum,
13652
14427
  type: "test",
13653
14428
  context: line.trim()
@@ -14149,7 +14924,7 @@ var DiffOptionsSchema = z10.object({
14149
14924
  // src/core/diff/git-diff.ts
14150
14925
  import { exec as exec2 } from "child_process";
14151
14926
  import { promisify as promisify2 } from "util";
14152
- import path39 from "path";
14927
+ import path41 from "path";
14153
14928
  var execAsync2 = promisify2(exec2);
14154
14929
  var GitDiff = class {
14155
14930
  constructor(projectRoot) {
@@ -14159,8 +14934,8 @@ var GitDiff = class {
14159
14934
  * Git diff 실행하여 변경된 스펙 파일 목록 조회
14160
14935
  */
14161
14936
  async getChangedSpecFiles(options = {}) {
14162
- const specsDir = path39.join(this.projectRoot, ".sdd", "specs");
14163
- const specPath = options.specPath ? path39.join(specsDir, options.specPath) : specsDir;
14937
+ const specsDir = path41.join(this.projectRoot, ".sdd", "specs");
14938
+ const specPath = options.specPath ? path41.join(specsDir, options.specPath) : specsDir;
14164
14939
  const normalizedPath = specPath.replace(/\\/g, "/");
14165
14940
  try {
14166
14941
  let diffCommand;
@@ -14244,9 +15019,9 @@ var GitDiff = class {
14244
15019
  }
14245
15020
  } else {
14246
15021
  try {
14247
- const fs27 = await import("fs/promises");
14248
- const absolutePath = path39.join(this.projectRoot, filePath);
14249
- after = await fs27.readFile(absolutePath, "utf-8");
15022
+ const fs28 = await import("fs/promises");
15023
+ const absolutePath = path41.join(this.projectRoot, filePath);
15024
+ after = await fs28.readFile(absolutePath, "utf-8");
14250
15025
  } catch {
14251
15026
  }
14252
15027
  }
@@ -14897,8 +15672,8 @@ function registerDiffCommand(program2) {
14897
15672
  }
14898
15673
 
14899
15674
  // src/core/export/index.ts
14900
- import { promises as fs26 } from "fs";
14901
- import path41 from "path";
15675
+ import { promises as fs27 } from "fs";
15676
+ import path43 from "path";
14902
15677
 
14903
15678
  // src/core/export/schemas.ts
14904
15679
  import { z as z11 } from "zod";
@@ -14954,8 +15729,8 @@ var ExportResultSchema = z11.object({
14954
15729
  });
14955
15730
 
14956
15731
  // src/core/export/spec-parser.ts
14957
- import { promises as fs25 } from "fs";
14958
- import path40 from "path";
15732
+ import { promises as fs26 } from "fs";
15733
+ import path42 from "path";
14959
15734
  import { glob as glob3 } from "glob";
14960
15735
  function parseMetadata3(content) {
14961
15736
  const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
@@ -15120,7 +15895,7 @@ function parseDescription(content) {
15120
15895
  return match ? match[1].trim() : "";
15121
15896
  }
15122
15897
  async function parseSpecFile(filePath, specId) {
15123
- const content = await fs25.readFile(filePath, "utf-8");
15898
+ const content = await fs26.readFile(filePath, "utf-8");
15124
15899
  const metadata = parseMetadata3(content);
15125
15900
  const body = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "");
15126
15901
  return {
@@ -15139,7 +15914,7 @@ async function parseSpecFile(filePath, specId) {
15139
15914
  };
15140
15915
  }
15141
15916
  async function parseAllSpecs(projectRoot) {
15142
- const specsDir = path40.join(projectRoot, ".sdd", "specs");
15917
+ const specsDir = path42.join(projectRoot, ".sdd", "specs");
15143
15918
  const normalizedDir = specsDir.replace(/\\/g, "/");
15144
15919
  const specFiles = await glob3(`${normalizedDir}/**/spec.md`, {
15145
15920
  absolute: true,
@@ -15147,8 +15922,8 @@ async function parseAllSpecs(projectRoot) {
15147
15922
  });
15148
15923
  const specs = [];
15149
15924
  for (const filePath of specFiles) {
15150
- const specDir = path40.dirname(filePath);
15151
- const specId = path40.basename(specDir);
15925
+ const specDir = path42.dirname(filePath);
15926
+ const specId = path42.basename(specDir);
15152
15927
  try {
15153
15928
  const spec = await parseSpecFile(filePath, specId);
15154
15929
  specs.push(spec);
@@ -15158,7 +15933,7 @@ async function parseAllSpecs(projectRoot) {
15158
15933
  return specs;
15159
15934
  }
15160
15935
  async function parseSpecById(projectRoot, specId) {
15161
- const specPath = path40.join(projectRoot, ".sdd", "specs", specId, "spec.md");
15936
+ const specPath = path42.join(projectRoot, ".sdd", "specs", specId, "spec.md");
15162
15937
  try {
15163
15938
  return await parseSpecFile(specPath, specId);
15164
15939
  } catch {
@@ -15762,7 +16537,7 @@ function generateJson(specs, options = {}) {
15762
16537
  // src/core/export/index.ts
15763
16538
  function getDefaultOutputPath(projectRoot, format, specIds) {
15764
16539
  const baseName = specIds?.length === 1 ? specIds[0] : "specs";
15765
- return path41.join(projectRoot, `${baseName}.${format}`);
16540
+ return path43.join(projectRoot, `${baseName}.${format}`);
15766
16541
  }
15767
16542
  async function executeExport(projectRoot, options = {}) {
15768
16543
  const {
@@ -15823,7 +16598,7 @@ async function executeExport(projectRoot, options = {}) {
15823
16598
  title: specs.length === 1 ? specs[0].title : "SDD \uC2A4\uD399 \uBB38\uC11C"
15824
16599
  });
15825
16600
  const htmlPath = outputPath.replace(/\.pdf$/, ".html");
15826
- await fs26.writeFile(htmlPath, content, "utf-8");
16601
+ await fs27.writeFile(htmlPath, content, "utf-8");
15827
16602
  return {
15828
16603
  success: true,
15829
16604
  outputPath: htmlPath,
@@ -15841,9 +16616,9 @@ async function executeExport(projectRoot, options = {}) {
15841
16616
  error: `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD: ${format}`
15842
16617
  };
15843
16618
  }
15844
- const outputDir = path41.dirname(outputPath);
15845
- await fs26.mkdir(outputDir, { recursive: true });
15846
- await fs26.writeFile(outputPath, content, "utf-8");
16619
+ const outputDir = path43.dirname(outputPath);
16620
+ await fs27.mkdir(outputDir, { recursive: true });
16621
+ await fs27.writeFile(outputPath, content, "utf-8");
15847
16622
  return {
15848
16623
  success: true,
15849
16624
  outputPath,
@@ -15934,6 +16709,7 @@ registerPrepareCommand(program);
15934
16709
  registerSyncCommand(program);
15935
16710
  registerDiffCommand(program);
15936
16711
  registerExportCommand(program);
16712
+ registerGitCommand(program);
15937
16713
  function run() {
15938
16714
  program.parse();
15939
16715
  }