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/LICENSE +21 -21
- package/README.md +490 -471
- package/bin/sdd.js +2 -2
- package/dist/cli/index.js +1219 -443
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +79 -81
- package/templates/agents.md +62 -62
- package/templates/constitution.md +43 -43
- package/templates/git/.gitattributes.sdd +39 -0
- package/templates/git/.gitignore.sdd +44 -0
- package/templates/git/.gitmessage +20 -0
- package/templates/git/commitlint.config.js +122 -0
- package/templates/github/branch-protection.md +194 -0
- package/templates/github/workflows/sdd-labeler.yml +125 -0
- package/templates/github/workflows/sdd-validate.yml +85 -0
- package/templates/proposal.md +71 -71
- package/templates/spec.md +27 -27
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,
|
|
134
|
-
super(code, formatMessage(code,
|
|
133
|
+
constructor(code, path44) {
|
|
134
|
+
super(code, formatMessage(code, path44), ExitCode.FILE_SYSTEM_ERROR);
|
|
135
135
|
this.name = "FileSystemError";
|
|
136
|
-
this.path =
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1628
|
-
|
|
1629
|
-
|
|
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 =
|
|
3113
|
-
const claudePath =
|
|
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(
|
|
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 =
|
|
3648
|
+
const projectName = path4.basename(projectPath);
|
|
3130
3649
|
const constitutionContent = generateConstitutionContent(projectName);
|
|
3131
|
-
await writeFile(
|
|
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(
|
|
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 =
|
|
3667
|
+
const templatesPath = path4.join(projectPath, ".sdd", "templates");
|
|
3149
3668
|
const files = [];
|
|
3150
|
-
await writeFile(
|
|
3669
|
+
await writeFile(path4.join(templatesPath, "spec.md"), generateSpecTemplate());
|
|
3151
3670
|
files.push(".sdd/templates/spec.md");
|
|
3152
|
-
await writeFile(
|
|
3671
|
+
await writeFile(path4.join(templatesPath, "proposal.md"), generateProposalTemplate());
|
|
3153
3672
|
files.push(".sdd/templates/proposal.md");
|
|
3154
|
-
await writeFile(
|
|
3673
|
+
await writeFile(path4.join(templatesPath, "delta.md"), generateDeltaTemplate());
|
|
3155
3674
|
files.push(".sdd/templates/delta.md");
|
|
3156
|
-
await writeFile(
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
4085
|
+
import path6 from "path";
|
|
3378
4086
|
import chalk2 from "chalk";
|
|
3379
4087
|
|
|
3380
4088
|
// src/core/spec/validator.ts
|
|
3381
|
-
import
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4154
|
-
const specFilePath =
|
|
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:
|
|
4915
|
+
const { promises: fs28 } = await import("fs");
|
|
4208
4916
|
try {
|
|
4209
|
-
const entries = await
|
|
4917
|
+
const entries = await fs28.readdir(dir, { withFileTypes: true });
|
|
4210
4918
|
for (const entry of entries) {
|
|
4211
|
-
const fullPath =
|
|
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 =
|
|
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 =
|
|
4955
|
+
resolvedPath = path6.join(sddRoot, ".sdd", "specs");
|
|
4248
4956
|
}
|
|
4249
4957
|
if (options.checkLinks && sddRoot) {
|
|
4250
|
-
specsRoot =
|
|
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 =
|
|
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 =
|
|
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
|
|
4961
|
-
import { promises as
|
|
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
|
|
5376
|
-
import { promises as
|
|
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 =
|
|
5380
|
-
const archivePath =
|
|
5381
|
-
const sourceDir =
|
|
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 =
|
|
6095
|
+
const archiveMonthDir = path7.join(archivePath, yearMonth);
|
|
5388
6096
|
await ensureDir(archiveMonthDir);
|
|
5389
6097
|
const datePrefix = today.toISOString().split("T")[0];
|
|
5390
|
-
const archiveDir =
|
|
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 =
|
|
6103
|
+
const proposalPath = path7.join(archiveDir, "proposal.md");
|
|
5396
6104
|
try {
|
|
5397
|
-
const proposalContent = await
|
|
6105
|
+
const proposalContent = await fs3.readFile(proposalPath, "utf-8");
|
|
5398
6106
|
const updateResult = updateProposalStatus(proposalContent, "archived");
|
|
5399
6107
|
if (updateResult.success) {
|
|
5400
|
-
await
|
|
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 =
|
|
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
|
|
6137
|
+
const months = await fs3.readdir(archivePath);
|
|
5430
6138
|
for (const month of months) {
|
|
5431
|
-
const monthPath =
|
|
5432
|
-
const stat = await
|
|
6139
|
+
const monthPath = path7.join(archivePath, month);
|
|
6140
|
+
const stat = await fs3.stat(monthPath);
|
|
5433
6141
|
if (!stat.isDirectory()) continue;
|
|
5434
|
-
const changes = await
|
|
6142
|
+
const changes = await fs3.readdir(monthPath);
|
|
5435
6143
|
for (const change of changes) {
|
|
5436
|
-
const changePath =
|
|
5437
|
-
const changeStat = await
|
|
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 =
|
|
5446
|
-
const proposalContent = await
|
|
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 =
|
|
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
|
|
6186
|
+
const dirs = await fs3.readdir(changesPath);
|
|
5479
6187
|
for (const dir of dirs) {
|
|
5480
|
-
const changePath =
|
|
5481
|
-
const stat = await
|
|
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 =
|
|
5488
|
-
const proposalContent = await
|
|
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 =
|
|
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 =
|
|
6261
|
+
const changesPath = path8.join(sddPath, "changes");
|
|
5554
6262
|
await ensureDir(changesPath);
|
|
5555
6263
|
const existingIds = [];
|
|
5556
6264
|
try {
|
|
5557
|
-
const dirs = await
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5773
|
-
const changePath =
|
|
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 =
|
|
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 =
|
|
5811
|
-
const changePath =
|
|
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 =
|
|
5864
|
-
const changePath =
|
|
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
|
|
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
|
|
5952
|
-
import
|
|
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
|
|
5966
|
-
const relativePath =
|
|
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(
|
|
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
|
|
6729
|
+
const entries = await fs5.readdir(dirPath, { withFileTypes: true });
|
|
6022
6730
|
for (const entry of entries) {
|
|
6023
|
-
const fullPath =
|
|
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
|
|
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 =
|
|
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 =
|
|
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,
|
|
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, [...
|
|
7048
|
+
if (dfs(depId, [...path44, nodeId])) {
|
|
6341
7049
|
return true;
|
|
6342
7050
|
}
|
|
6343
7051
|
} else if (recStack.has(depId)) {
|
|
6344
|
-
const cycleStart =
|
|
6345
|
-
const cycle = cycleStart >= 0 ? [...
|
|
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 =
|
|
6392
|
-
const proposalPath =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
6911
|
-
import { promises as
|
|
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:
|
|
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 =
|
|
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
|
|
7711
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
7004
7712
|
for (const entry of entries) {
|
|
7005
|
-
const fullPath =
|
|
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 =
|
|
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
|
|
7042
|
-
const ext =
|
|
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:
|
|
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 =
|
|
7149
|
-
const dirName =
|
|
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:
|
|
7895
|
+
relativePath: path11.relative(projectRoot, file.path),
|
|
7188
7896
|
impactLevel: "medium",
|
|
7189
7897
|
impactType: "indirect",
|
|
7190
|
-
reason: `${
|
|
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 =
|
|
7204
|
-
return
|
|
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 (
|
|
8043
|
+
if (path12.isAbsolute(proposalPath)) {
|
|
7336
8044
|
return proposalPath;
|
|
7337
8045
|
}
|
|
7338
|
-
const changesPath =
|
|
8046
|
+
const changesPath = path12.join(sddPath, "changes", proposalPath);
|
|
7339
8047
|
if (proposalPath.endsWith(".md")) {
|
|
7340
8048
|
return changesPath;
|
|
7341
8049
|
}
|
|
7342
|
-
return
|
|
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 =
|
|
7476
|
-
const specsPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
7580
|
-
const specsPath =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
7959
|
-
import { promises as
|
|
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 =
|
|
8681
|
+
const specPath = path15.join(featurePath, "spec.md");
|
|
7974
8682
|
if (await fileExists(specPath)) {
|
|
7975
8683
|
info2.hasSpec = true;
|
|
7976
|
-
const content = await
|
|
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(
|
|
7984
|
-
const tasksPath =
|
|
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
|
|
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 =
|
|
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(
|
|
8013
|
-
status.hasAgents = await fileExists(
|
|
8014
|
-
const specsPath =
|
|
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 =
|
|
8020
|
-
const stat = await
|
|
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
|
|
8169
|
-
import { promises as
|
|
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 =
|
|
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 =
|
|
8202
|
-
const stat = await
|
|
8909
|
+
const featurePath = path16.join(specsPath, entry);
|
|
8910
|
+
const stat = await fs9.stat(featurePath);
|
|
8203
8911
|
if (stat.isDirectory()) {
|
|
8204
|
-
const specPath =
|
|
8912
|
+
const specPath = path16.join(featurePath, "spec.md");
|
|
8205
8913
|
if (await fileExists(specPath)) {
|
|
8206
|
-
const content = await
|
|
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 =
|
|
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 =
|
|
8257
|
-
const stat = await
|
|
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 =
|
|
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 =
|
|
8996
|
+
const sddPath = path16.join(projectPath, ".sdd");
|
|
8289
8997
|
if (!await fileExists(sddPath)) {
|
|
8290
8998
|
return null;
|
|
8291
8999
|
}
|
|
8292
|
-
const specsPath =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
8477
|
-
const changelogPath =
|
|
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 =
|
|
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
|
|
8723
|
-
import { promises as
|
|
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 =
|
|
8773
|
-
const specsPath =
|
|
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 =
|
|
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
|
|
9498
|
+
const dirs = await fs10.readdir(specsPath);
|
|
8791
9499
|
for (const dir of dirs) {
|
|
8792
|
-
const specPath =
|
|
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
|
|
9027
|
-
import { promises as
|
|
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
|
|
9038
|
-
import
|
|
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 =
|
|
9768
|
+
const openspecPath = path19.join(projectRoot, "openspec");
|
|
9061
9769
|
if (!await directoryExists(openspecPath)) {
|
|
9062
9770
|
return null;
|
|
9063
9771
|
}
|
|
9064
|
-
const specsPath =
|
|
9065
|
-
const changesPath =
|
|
9066
|
-
const agentsPath =
|
|
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
|
|
9783
|
+
const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
|
|
9076
9784
|
for (const entry of specDirs) {
|
|
9077
9785
|
if (entry.isDirectory()) {
|
|
9078
|
-
const specPath =
|
|
9079
|
-
const specFile =
|
|
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
|
|
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 =
|
|
9811
|
+
const specifyPath = path19.join(projectRoot, ".specify");
|
|
9104
9812
|
if (!await directoryExists(specifyPath)) {
|
|
9105
9813
|
return null;
|
|
9106
9814
|
}
|
|
9107
|
-
const specsPath =
|
|
9108
|
-
const memoryPath =
|
|
9109
|
-
const constitutionPath =
|
|
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
|
|
9824
|
+
const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
|
|
9117
9825
|
for (const entry of specDirs) {
|
|
9118
9826
|
if (entry.isDirectory()) {
|
|
9119
|
-
const specPath =
|
|
9120
|
-
const specFile =
|
|
9121
|
-
const planFile =
|
|
9122
|
-
const tasksFile =
|
|
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
|
|
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 =
|
|
9860
|
+
const sddPath = path19.join(projectRoot, ".sdd");
|
|
9153
9861
|
if (!await directoryExists(sddPath)) {
|
|
9154
9862
|
return null;
|
|
9155
9863
|
}
|
|
9156
|
-
const specsPath =
|
|
9157
|
-
const configPath =
|
|
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
|
|
9870
|
+
const specDirs = await fs11.readdir(specsPath, { withFileTypes: true });
|
|
9163
9871
|
for (const entry of specDirs) {
|
|
9164
9872
|
if (entry.isDirectory()) {
|
|
9165
|
-
const specPath =
|
|
9166
|
-
const specFile =
|
|
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
|
|
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 =
|
|
9191
|
-
const targetSpecsPath =
|
|
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
|
|
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 =
|
|
9202
|
-
const targetSpecPath =
|
|
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
|
|
9212
|
-
const files = await
|
|
9919
|
+
await fs11.mkdir(targetSpecPath, { recursive: true });
|
|
9920
|
+
const files = await fs11.readdir(sourceSpecPath);
|
|
9213
9921
|
for (const file of files) {
|
|
9214
|
-
const sourceFile =
|
|
9215
|
-
const targetFile =
|
|
9216
|
-
const stat = await
|
|
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
|
|
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
|
|
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 =
|
|
9245
|
-
const targetSpecsPath =
|
|
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
|
|
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 =
|
|
9256
|
-
const targetSpecPath =
|
|
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
|
|
9266
|
-
const files = await
|
|
9973
|
+
await fs11.mkdir(targetSpecPath, { recursive: true });
|
|
9974
|
+
const files = await fs11.readdir(sourceSpecPath);
|
|
9267
9975
|
for (const file of files) {
|
|
9268
|
-
const sourceFile =
|
|
9269
|
-
const targetFile =
|
|
9270
|
-
const stat = await
|
|
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
|
|
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
|
|
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 =
|
|
10139
|
+
const sourcePath = path20.resolve(source);
|
|
9432
10140
|
let files = [];
|
|
9433
10141
|
try {
|
|
9434
|
-
const stat = await
|
|
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 ?
|
|
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 ${
|
|
10170
|
+
info(`\u2705 ${path20.basename(file)} \u2192 ${result.target}`);
|
|
9463
10171
|
} else {
|
|
9464
10172
|
summary.failed++;
|
|
9465
|
-
error(`\u274C ${
|
|
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
|
|
10185
|
+
const content = await fs12.readFile(filePath, "utf-8");
|
|
9478
10186
|
const analysis = analyzeDocument(content);
|
|
9479
|
-
const featureId = generateFeatureId(analysis.title ||
|
|
10187
|
+
const featureId = generateFeatureId(analysis.title || path20.basename(filePath, ".md"));
|
|
9480
10188
|
const specContent = generateSpec({
|
|
9481
10189
|
id: featureId,
|
|
9482
|
-
title: analysis.title ||
|
|
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 =
|
|
9488
|
-
const targetPath =
|
|
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:
|
|
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 =
|
|
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
|
|
10253
|
+
const content = await fs12.readFile(filePath, "utf-8");
|
|
9546
10254
|
const analysis = analyzeDocument(content);
|
|
9547
|
-
info(`\u{1F4CA} \uBB38\uC11C \uBD84\uC11D: ${
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
10376
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
9669
10377
|
for (const entry of entries) {
|
|
9670
|
-
const fullPath =
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
10641
|
+
const workflowDir = path21.join(projectRoot, ".github", "workflows");
|
|
9934
10642
|
await ensureDir(workflowDir);
|
|
9935
|
-
const
|
|
9936
|
-
const
|
|
9937
|
-
await writeFile(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
10181
|
-
import { promises as
|
|
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 =
|
|
10961
|
+
const changesPath = path22.join(sddPath, "changes");
|
|
10187
10962
|
try {
|
|
10188
|
-
const dirs = await
|
|
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 =
|
|
10207
|
-
const specPath =
|
|
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 =
|
|
10214
|
-
const changePath =
|
|
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(
|
|
10994
|
+
await writeFile(path22.join(changePath, "proposal.md"), proposalContent);
|
|
10220
10995
|
const deltaContent = generateDeltaTemplate2(specId);
|
|
10221
|
-
await writeFile(
|
|
10996
|
+
await writeFile(path22.join(changePath, "delta.md"), deltaContent);
|
|
10222
10997
|
const tasksContent = generateTasksTemplate2();
|
|
10223
|
-
await writeFile(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
10239
|
-
const newSpecPath =
|
|
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(
|
|
11021
|
+
await writeFile(path22.join(newSpecPath, "spec.md"), specContent);
|
|
10247
11022
|
const planContent = generatePlanTemplate(featureName);
|
|
10248
|
-
await writeFile(
|
|
11023
|
+
await writeFile(path22.join(newSpecPath, "plan.md"), planContent);
|
|
10249
11024
|
const tasksContent = generateTasksTemplate2();
|
|
10250
|
-
await writeFile(
|
|
10251
|
-
const statusPath =
|
|
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 =
|
|
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 =
|
|
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
|
|
11346
|
+
import path24 from "path";
|
|
10572
11347
|
|
|
10573
11348
|
// src/core/watch/watcher.ts
|
|
10574
11349
|
import chokidar from "chokidar";
|
|
10575
|
-
import
|
|
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:
|
|
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 =
|
|
10694
|
-
const specsPath =
|
|
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
|
|
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
|
|
10791
|
-
import { promises as
|
|
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 =
|
|
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 ||
|
|
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 =
|
|
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
|
|
11917
|
+
const entries = await fs14.readdir(dir, { withFileTypes: true });
|
|
11143
11918
|
for (const entry of entries) {
|
|
11144
|
-
const fullPath =
|
|
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 =
|
|
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 =
|
|
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
|
|
12042
|
+
import path28 from "path";
|
|
11268
12043
|
|
|
11269
12044
|
// src/core/report/reporter.ts
|
|
11270
12045
|
init_types();
|
|
11271
12046
|
init_errors();
|
|
11272
|
-
import
|
|
11273
|
-
import
|
|
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 =
|
|
11287
|
-
const stat = await
|
|
12061
|
+
const featurePath = path27.join(specsPath, entry);
|
|
12062
|
+
const stat = await fs15.stat(featurePath);
|
|
11288
12063
|
if (stat.isDirectory()) {
|
|
11289
|
-
const specFile =
|
|
12064
|
+
const specFile = path27.join(featurePath, "spec.md");
|
|
11290
12065
|
if (await fileExists(specFile)) {
|
|
11291
|
-
const content = await
|
|
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 =
|
|
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
|
|
11383
|
-
await
|
|
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
|
|
12433
|
+
return path28.join(projectRoot, `sdd-report-${timestamp}.${ext}`);
|
|
11659
12434
|
}
|
|
11660
|
-
return
|
|
12435
|
+
return path28.isAbsolute(output) ? output : path28.join(projectRoot, output);
|
|
11661
12436
|
}
|
|
11662
12437
|
async function executeReport(options, projectRoot) {
|
|
11663
|
-
const sddPath =
|
|
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
|
|
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
|
|
11729
|
-
import { promises as
|
|
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 =
|
|
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
|
|
12558
|
+
const entries = await fs16.readdir(currentPath, { withFileTypes: true });
|
|
11784
12559
|
for (const entry of entries) {
|
|
11785
|
-
const fullPath =
|
|
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
|
|
11790
|
-
const relativePath =
|
|
11791
|
-
const specId =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
12247
|
-
import * as
|
|
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 =
|
|
13026
|
+
this.sddDir = path31.join(projectRoot, ".sdd");
|
|
12252
13027
|
}
|
|
12253
13028
|
/**
|
|
12254
13029
|
* 기능 디렉토리의 모든 문서 분석
|
|
12255
13030
|
*/
|
|
12256
13031
|
async analyzeFeature(featureName) {
|
|
12257
|
-
const featureDir =
|
|
12258
|
-
if (!
|
|
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 =
|
|
12269
|
-
if (
|
|
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 =
|
|
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:
|
|
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
|
|
12464
|
-
import * as
|
|
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 =
|
|
13244
|
+
this.agentsDir = path32.join(projectRoot, ".claude", "agents");
|
|
12470
13245
|
}
|
|
12471
13246
|
/**
|
|
12472
13247
|
* 에이전트 디렉토리 존재 여부
|
|
12473
13248
|
*/
|
|
12474
13249
|
exists() {
|
|
12475
|
-
return
|
|
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 =
|
|
13260
|
+
const files = fs18.readdirSync(this.agentsDir);
|
|
12486
13261
|
for (const file of files) {
|
|
12487
13262
|
if (!file.endsWith(".md")) continue;
|
|
12488
|
-
const filePath =
|
|
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 =
|
|
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 =
|
|
12522
|
-
return
|
|
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 =
|
|
12529
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
12573
|
-
import * as
|
|
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 =
|
|
13353
|
+
this.skillsDir = path33.join(projectRoot, ".claude", "skills");
|
|
12579
13354
|
}
|
|
12580
13355
|
/**
|
|
12581
13356
|
* 스킬 디렉토리 존재 여부
|
|
12582
13357
|
*/
|
|
12583
13358
|
exists() {
|
|
12584
|
-
return
|
|
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 =
|
|
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 =
|
|
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 =
|
|
12610
|
-
if (!
|
|
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 =
|
|
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 =
|
|
12636
|
-
const skillFile =
|
|
12637
|
-
return
|
|
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 =
|
|
12644
|
-
if (!
|
|
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 =
|
|
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
|
|
13457
|
+
return path33.join(this.skillsDir, name);
|
|
12683
13458
|
}
|
|
12684
13459
|
/**
|
|
12685
13460
|
* 스킬 파일 경로 생성
|
|
12686
13461
|
*/
|
|
12687
13462
|
getSkillFilePath(name) {
|
|
12688
|
-
return
|
|
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
|
|
12694
|
-
import * as
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
12719
|
-
|
|
13493
|
+
if (!fs20.existsSync(this.agentsDir)) {
|
|
13494
|
+
fs20.mkdirSync(this.agentsDir, { recursive: true });
|
|
12720
13495
|
}
|
|
12721
|
-
|
|
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
|
|
12821
|
-
import * as
|
|
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 =
|
|
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 =
|
|
12834
|
-
const filePath =
|
|
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 (!
|
|
12847
|
-
|
|
13621
|
+
if (!fs21.existsSync(skill.dirPath)) {
|
|
13622
|
+
fs21.mkdirSync(skill.dirPath, { recursive: true });
|
|
12848
13623
|
}
|
|
12849
|
-
|
|
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
|
|
13005
|
-
import * as
|
|
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 =
|
|
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 =
|
|
13125
|
-
if (!
|
|
13126
|
-
|
|
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 =
|
|
13903
|
+
const filePath = path36.join(featureDir, "prepare.md");
|
|
13129
13904
|
const content = this.toMarkdown(report);
|
|
13130
|
-
|
|
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(` - ${
|
|
14043
|
+
console.log(` - ${path37.relative(projectRoot, file)}`);
|
|
13269
14044
|
}
|
|
13270
14045
|
newline();
|
|
13271
14046
|
}
|
|
13272
14047
|
if (reportPath) {
|
|
13273
|
-
info(`\uBCF4\uACE0\uC11C: ${
|
|
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
|
|
13348
|
-
import
|
|
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 =
|
|
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 =
|
|
14138
|
+
const specPath = path38.join(this.specsDir, specId, "spec.md");
|
|
13364
14139
|
try {
|
|
13365
|
-
const content = await
|
|
14140
|
+
const content = await fs23.readFile(specPath, "utf-8");
|
|
13366
14141
|
return this.extractRequirements(content, specId);
|
|
13367
14142
|
} catch {
|
|
13368
|
-
const specDir =
|
|
14143
|
+
const specDir = path38.join(this.specsDir, specId);
|
|
13369
14144
|
try {
|
|
13370
|
-
const files = await
|
|
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 =
|
|
13375
|
-
const content = await
|
|
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
|
|
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
|
|
13479
|
-
import
|
|
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 ||
|
|
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
|
|
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:
|
|
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
|
|
13579
|
-
import
|
|
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
|
|
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:
|
|
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 ===
|
|
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:
|
|
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
|
|
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 =
|
|
14163
|
-
const specPath = options.specPath ?
|
|
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
|
|
14248
|
-
const absolutePath =
|
|
14249
|
-
after = await
|
|
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
|
|
14901
|
-
import
|
|
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
|
|
14958
|
-
import
|
|
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
|
|
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 =
|
|
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 =
|
|
15151
|
-
const specId =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
15845
|
-
await
|
|
15846
|
-
await
|
|
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
|
}
|