sdd-tool 0.6.2 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -130
- package/dist/cli/index.js +1243 -47
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -128,10 +128,10 @@ var init_base = __esm({
|
|
|
128
128
|
};
|
|
129
129
|
FileSystemError = class extends SddError {
|
|
130
130
|
path;
|
|
131
|
-
constructor(code,
|
|
132
|
-
super(code, formatMessage(code,
|
|
131
|
+
constructor(code, path36) {
|
|
132
|
+
super(code, formatMessage(code, path36), ExitCode.FILE_SYSTEM_ERROR);
|
|
133
133
|
this.name = "FileSystemError";
|
|
134
|
-
this.path =
|
|
134
|
+
this.path = path36;
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
137
|
ValidationError = class extends SddError {
|
|
@@ -2132,70 +2132,92 @@ erDiagram
|
|
|
2132
2132
|
},
|
|
2133
2133
|
{
|
|
2134
2134
|
name: "sdd.prepare",
|
|
2135
|
-
content: `\uAE30\uB2A5 \uAD6C\uD604\
|
|
2135
|
+
content: `\uAE30\uB2A5 \uAD6C\uD604 \uC804 \uD544\uC694\uD55C \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8\uC640 \uC2A4\uD0AC\uC744 \uC810\uAC80\uD569\uB2C8\uB2E4.
|
|
2136
2136
|
|
|
2137
2137
|
## \uAC1C\uC694
|
|
2138
2138
|
|
|
2139
|
-
\
|
|
2139
|
+
\uC2A4\uD399/\uACC4\uD68D/\uC791\uC5C5 \uBB38\uC11C\uB97C \uBD84\uC11D\uD558\uC5EC \uAD6C\uD604\uC5D0 \uD544\uC694\uD55C Claude Code \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8\uC640 \uC2A4\uD0AC\uC744 \uAC10\uC9C0\uD558\uACE0,
|
|
2140
|
+
\uB204\uB77D\uB41C \uB3C4\uAD6C\uAC00 \uC788\uC73C\uBA74 \uC790\uB3D9\uC73C\uB85C \uC0DD\uC131\uD569\uB2C8\uB2E4.
|
|
2140
2141
|
|
|
2141
2142
|
## \uC9C0\uC2DC\uC0AC\uD56D
|
|
2142
2143
|
|
|
2143
|
-
1.
|
|
2144
|
-
2. \
|
|
2145
|
-
3. \
|
|
2146
|
-
4. AGENTS.md\uC5D0 \uD544\uC694\uD55C \uC9C0\uCE68\uC744 \uCD94\uAC00\uD558\uC138\uC694
|
|
2144
|
+
1. \`sdd prepare <feature-id>\` \uBA85\uB839\uC5B4\uB97C \uC2E4\uD589\uD558\uC138\uC694
|
|
2145
|
+
2. \uAC10\uC9C0\uB41C \uB3C4\uAD6C \uBAA9\uB85D\uACFC \uC874\uC7AC \uC5EC\uBD80\uB97C \uD655\uC778\uD558\uC138\uC694
|
|
2146
|
+
3. \uB204\uB77D\uB41C \uB3C4\uAD6C \uC0DD\uC131 \uC5EC\uBD80\uB97C \uACB0\uC815\uD558\uC138\uC694
|
|
2147
2147
|
|
|
2148
|
-
## \
|
|
2148
|
+
## \uC6CC\uD06C\uD50C\uB85C\uC6B0
|
|
2149
2149
|
|
|
2150
|
-
|
|
2150
|
+
\`\`\`
|
|
2151
|
+
/sdd.new \u2192 /sdd.plan \u2192 /sdd.tasks \u2192 sdd prepare \u2192 /sdd.implement
|
|
2152
|
+
\`\`\`
|
|
2153
|
+
|
|
2154
|
+
## \uBA85\uB839\uC5B4
|
|
2151
2155
|
|
|
2152
2156
|
\`\`\`bash
|
|
2153
|
-
# \
|
|
2154
|
-
|
|
2157
|
+
# \uAE30\uBCF8 \uC0AC\uC6A9 (\uB300\uD654\uD615)
|
|
2158
|
+
sdd prepare user-auth
|
|
2159
|
+
|
|
2160
|
+
# \uBBF8\uB9AC\uBCF4\uAE30 (\uD30C\uC77C \uC0DD\uC131 \uC5C6\uC74C)
|
|
2161
|
+
sdd prepare user-auth --dry-run
|
|
2162
|
+
|
|
2163
|
+
# \uC790\uB3D9 \uC2B9\uC778 (\uB204\uB77D \uB3C4\uAD6C \uC790\uB3D9 \uC0DD\uC131)
|
|
2164
|
+
sdd prepare user-auth --auto-approve
|
|
2155
2165
|
|
|
2156
|
-
#
|
|
2157
|
-
|
|
2166
|
+
# JSON \uCD9C\uB825
|
|
2167
|
+
sdd prepare user-auth --json
|
|
2158
2168
|
\`\`\`
|
|
2159
2169
|
|
|
2160
|
-
|
|
2170
|
+
## \uAC10\uC9C0 \uB300\uC0C1
|
|
2161
2171
|
|
|
2162
|
-
|
|
2163
|
-
- [ ] API \uD0A4 \uC124\uC815
|
|
2164
|
-
- [ ] \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC5F0\uACB0
|
|
2172
|
+
### \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8 (\`.claude/agents/*.md\`)
|
|
2165
2173
|
|
|
2166
|
-
|
|
2174
|
+
| \uC5D0\uC774\uC804\uD2B8 | \uAC10\uC9C0 \uD0A4\uC6CC\uB4DC | \uC124\uBA85 |
|
|
2175
|
+
|----------|-------------|------|
|
|
2176
|
+
| test-runner | \uD14C\uC2A4\uD2B8, test, jest, vitest | \uD14C\uC2A4\uD2B8 \uC2E4\uD589 |
|
|
2177
|
+
| api-scaffold | api, rest, endpoint | API \uC2A4\uCE90\uD3F4\uB529 |
|
|
2178
|
+
| component-gen | component, \uCEF4\uD3EC\uB10C\uD2B8, react | \uCEF4\uD3EC\uB10C\uD2B8 \uC0DD\uC131 |
|
|
2179
|
+
| code-reviewer | review, \uB9AC\uBDF0 | \uCF54\uB4DC \uB9AC\uBDF0 |
|
|
2167
2180
|
|
|
2168
|
-
|
|
2169
|
-
- filesystem: \uD30C\uC77C \uC2DC\uC2A4\uD15C \uC811\uADFC
|
|
2170
|
-
- github: GitHub API \uC5F0\uB3D9
|
|
2171
|
-
- database: \uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC811\uADFC
|
|
2181
|
+
### \uC2A4\uD0AC (\`.claude/skills/<name>/SKILL.md\`)
|
|
2172
2182
|
|
|
2173
|
-
|
|
2183
|
+
| \uC2A4\uD0AC | \uAC10\uC9C0 \uD0A4\uC6CC\uB4DC | \uC124\uBA85 |
|
|
2184
|
+
|------|-------------|------|
|
|
2185
|
+
| test | \uD14C\uC2A4\uD2B8, test | \uD14C\uC2A4\uD2B8 \uC791\uC131 |
|
|
2186
|
+
| gen-api | api, rest | API \uC0DD\uC131 |
|
|
2187
|
+
| gen-component | component | \uCEF4\uD3EC\uB10C\uD2B8 \uC0DD\uC131 |
|
|
2188
|
+
| db-migrate | database, \uB9C8\uC774\uADF8\uB808\uC774\uC158 | DB \uB9C8\uC774\uADF8\uB808\uC774\uC158 |
|
|
2189
|
+
| gen-doc | \uBB38\uC11C, doc | \uBB38\uC11C \uC0DD\uC131 |
|
|
2174
2190
|
|
|
2175
|
-
|
|
2191
|
+
## \uCD9C\uB825 \uC608\uC2DC
|
|
2176
2192
|
|
|
2177
|
-
\`\`\`
|
|
2178
|
-
|
|
2193
|
+
\`\`\`
|
|
2194
|
+
=== SDD Prepare: user-auth ===
|
|
2179
2195
|
|
|
2180
|
-
|
|
2181
|
-
- ...
|
|
2196
|
+
\uBD84\uC11D \uB300\uC0C1: 3\uAC1C \uBB38\uC11C, 5\uAC1C \uD0DC\uC2A4\uD06C
|
|
2182
2197
|
|
|
2183
|
-
|
|
2184
|
-
-
|
|
2198
|
+
--- \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8 ---
|
|
2199
|
+
[x] test-runner (\uC874\uC7AC)
|
|
2200
|
+
[ ] api-scaffold (\uC5C6\uC74C) \u2192 \uC0DD\uC131 \uD544\uC694
|
|
2185
2201
|
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2202
|
+
--- \uC2A4\uD0AC ---
|
|
2203
|
+
[x] test (\uC874\uC7AC)
|
|
2204
|
+
[ ] gen-api (\uC5C6\uC74C) \u2192 \uC0DD\uC131 \uD544\uC694
|
|
2189
2205
|
|
|
2190
|
-
|
|
2206
|
+
\uB204\uB77D\uB41C \uB3C4\uAD6C\uB97C \uC0DD\uC131\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/n)
|
|
2207
|
+
\`\`\`
|
|
2191
2208
|
|
|
2192
|
-
|
|
2193
|
-
# \uD504\uB85C\uC81D\uD2B8 \uC0C1\uD0DC \uD655\uC778
|
|
2194
|
-
sdd status
|
|
2209
|
+
## \uC0DD\uC131 \uD30C\uC77C \uAD6C\uC870
|
|
2195
2210
|
|
|
2196
|
-
# \uC2A4\uD399 \uAC80\uC99D
|
|
2197
|
-
sdd validate
|
|
2198
2211
|
\`\`\`
|
|
2212
|
+
.claude/
|
|
2213
|
+
\u251C\u2500\u2500 agents/
|
|
2214
|
+
\u2502 \u2514\u2500\u2500 api-scaffold.md # \uC5D0\uC774\uC804\uD2B8 \uC815\uC758
|
|
2215
|
+
\u2514\u2500\u2500 skills/
|
|
2216
|
+
\u2514\u2500\u2500 gen-api/
|
|
2217
|
+
\u2514\u2500\u2500 SKILL.md # \uC2A4\uD0AC \uC815\uC758
|
|
2218
|
+
\`\`\`
|
|
2219
|
+
|
|
2220
|
+
\uC644\uB8CC \uD6C4 \`/sdd.implement\`\uB85C \uAD6C\uD604\uC744 \uC2DC\uC791\uD558\uC138\uC694.
|
|
2199
2221
|
`
|
|
2200
2222
|
},
|
|
2201
2223
|
{
|
|
@@ -3934,9 +3956,9 @@ async function validateSpecs(targetPath, options = {}) {
|
|
|
3934
3956
|
async function findSpecFiles(dirPath) {
|
|
3935
3957
|
const files = [];
|
|
3936
3958
|
async function scanDir(dir) {
|
|
3937
|
-
const { promises:
|
|
3959
|
+
const { promises: fs22 } = await import("fs");
|
|
3938
3960
|
try {
|
|
3939
|
-
const entries = await
|
|
3961
|
+
const entries = await fs22.readdir(dir, { withFileTypes: true });
|
|
3940
3962
|
for (const entry of entries) {
|
|
3941
3963
|
const fullPath = path3.join(dir, entry.name);
|
|
3942
3964
|
if (entry.isDirectory()) {
|
|
@@ -6059,19 +6081,19 @@ function detectCircularDependencies(graph) {
|
|
|
6059
6081
|
const cycles = [];
|
|
6060
6082
|
const visited = /* @__PURE__ */ new Set();
|
|
6061
6083
|
const recStack = /* @__PURE__ */ new Set();
|
|
6062
|
-
function dfs(nodeId,
|
|
6084
|
+
function dfs(nodeId, path36) {
|
|
6063
6085
|
visited.add(nodeId);
|
|
6064
6086
|
recStack.add(nodeId);
|
|
6065
6087
|
const node = graph.nodes.get(nodeId);
|
|
6066
6088
|
if (!node) return false;
|
|
6067
6089
|
for (const depId of node.dependsOn) {
|
|
6068
6090
|
if (!visited.has(depId)) {
|
|
6069
|
-
if (dfs(depId, [...
|
|
6091
|
+
if (dfs(depId, [...path36, nodeId])) {
|
|
6070
6092
|
return true;
|
|
6071
6093
|
}
|
|
6072
6094
|
} else if (recStack.has(depId)) {
|
|
6073
|
-
const cycleStart =
|
|
6074
|
-
const cycle = cycleStart >= 0 ? [...
|
|
6095
|
+
const cycleStart = path36.indexOf(depId);
|
|
6096
|
+
const cycle = cycleStart >= 0 ? [...path36.slice(cycleStart), nodeId, depId] : [nodeId, depId];
|
|
6075
6097
|
cycles.push({
|
|
6076
6098
|
cycle,
|
|
6077
6099
|
description: `\uC21C\uD658 \uC758\uC874\uC131: ${cycle.join(" \u2192 ")}`
|
|
@@ -11835,6 +11857,1179 @@ async function executeSearch(query, options) {
|
|
|
11835
11857
|
}
|
|
11836
11858
|
}
|
|
11837
11859
|
|
|
11860
|
+
// src/cli/commands/prepare.ts
|
|
11861
|
+
import path35 from "path";
|
|
11862
|
+
|
|
11863
|
+
// src/core/prepare/schemas.ts
|
|
11864
|
+
import { z as z8 } from "zod";
|
|
11865
|
+
var ToolTypeSchema = z8.enum(["agent", "skill"]);
|
|
11866
|
+
var ToolStatusSchema = z8.enum(["exists", "missing", "outdated"]);
|
|
11867
|
+
var DetectionSourceSchema = z8.object({
|
|
11868
|
+
file: z8.string(),
|
|
11869
|
+
line: z8.number(),
|
|
11870
|
+
text: z8.string(),
|
|
11871
|
+
keyword: z8.string()
|
|
11872
|
+
});
|
|
11873
|
+
var DetectedToolSchema = z8.object({
|
|
11874
|
+
type: ToolTypeSchema,
|
|
11875
|
+
name: z8.string(),
|
|
11876
|
+
description: z8.string(),
|
|
11877
|
+
sources: z8.array(DetectionSourceSchema)
|
|
11878
|
+
});
|
|
11879
|
+
var AgentMetadataSchema = z8.object({
|
|
11880
|
+
name: z8.string(),
|
|
11881
|
+
description: z8.string(),
|
|
11882
|
+
tools: z8.array(z8.string()).optional(),
|
|
11883
|
+
model: z8.string().optional()
|
|
11884
|
+
});
|
|
11885
|
+
var ScannedAgentSchema = z8.object({
|
|
11886
|
+
name: z8.string(),
|
|
11887
|
+
filePath: z8.string(),
|
|
11888
|
+
metadata: AgentMetadataSchema,
|
|
11889
|
+
content: z8.string()
|
|
11890
|
+
});
|
|
11891
|
+
var SkillMetadataSchema = z8.object({
|
|
11892
|
+
name: z8.string(),
|
|
11893
|
+
description: z8.string(),
|
|
11894
|
+
"allowed-tools": z8.array(z8.string()).optional()
|
|
11895
|
+
});
|
|
11896
|
+
var ScannedSkillSchema = z8.object({
|
|
11897
|
+
name: z8.string(),
|
|
11898
|
+
dirPath: z8.string(),
|
|
11899
|
+
filePath: z8.string(),
|
|
11900
|
+
metadata: SkillMetadataSchema,
|
|
11901
|
+
content: z8.string()
|
|
11902
|
+
});
|
|
11903
|
+
var ToolCheckResultSchema = z8.object({
|
|
11904
|
+
tool: DetectedToolSchema,
|
|
11905
|
+
status: ToolStatusSchema,
|
|
11906
|
+
filePath: z8.string().optional(),
|
|
11907
|
+
action: z8.string()
|
|
11908
|
+
});
|
|
11909
|
+
var PrepareReportSchema = z8.object({
|
|
11910
|
+
feature: z8.string(),
|
|
11911
|
+
totalTasks: z8.number(),
|
|
11912
|
+
agents: z8.object({
|
|
11913
|
+
required: z8.number(),
|
|
11914
|
+
existing: z8.number(),
|
|
11915
|
+
missing: z8.number(),
|
|
11916
|
+
checks: z8.array(ToolCheckResultSchema)
|
|
11917
|
+
}),
|
|
11918
|
+
skills: z8.object({
|
|
11919
|
+
required: z8.number(),
|
|
11920
|
+
existing: z8.number(),
|
|
11921
|
+
missing: z8.number(),
|
|
11922
|
+
checks: z8.array(ToolCheckResultSchema)
|
|
11923
|
+
}),
|
|
11924
|
+
proposals: z8.array(z8.object({
|
|
11925
|
+
type: ToolTypeSchema,
|
|
11926
|
+
name: z8.string(),
|
|
11927
|
+
filePath: z8.string(),
|
|
11928
|
+
content: z8.string()
|
|
11929
|
+
})),
|
|
11930
|
+
createdAt: z8.string()
|
|
11931
|
+
});
|
|
11932
|
+
var KeywordMappingSchema = z8.object({
|
|
11933
|
+
keywords: z8.array(z8.string()),
|
|
11934
|
+
agent: z8.string().nullable(),
|
|
11935
|
+
skill: z8.string(),
|
|
11936
|
+
agentDescription: z8.string().optional(),
|
|
11937
|
+
skillDescription: z8.string().optional()
|
|
11938
|
+
});
|
|
11939
|
+
var DEFAULT_KEYWORD_MAPPINGS = [
|
|
11940
|
+
{
|
|
11941
|
+
keywords: ["test", "\uD14C\uC2A4\uD2B8", "vitest", "jest", "\uB2E8\uC704 \uD14C\uC2A4\uD2B8", "unit test"],
|
|
11942
|
+
agent: "test-runner",
|
|
11943
|
+
skill: "test",
|
|
11944
|
+
agentDescription: "\uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uBC0F \uACB0\uACFC \uBD84\uC11D \uC5D0\uC774\uC804\uD2B8",
|
|
11945
|
+
skillDescription: "\uD14C\uC2A4\uD2B8 \uC791\uC131 \uBC0F \uC2E4\uD589 \uC2A4\uD0AC"
|
|
11946
|
+
},
|
|
11947
|
+
{
|
|
11948
|
+
keywords: ["api", "rest", "endpoint", "\uC5D4\uB4DC\uD3EC\uC778\uD2B8", "graphql"],
|
|
11949
|
+
agent: "api-scaffold",
|
|
11950
|
+
skill: "gen-api",
|
|
11951
|
+
agentDescription: "REST API \uBCF4\uC77C\uB7EC\uD50C\uB808\uC774\uD2B8 \uC0DD\uC131 \uC5D0\uC774\uC804\uD2B8",
|
|
11952
|
+
skillDescription: "API \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uC0DD\uC131 \uC2A4\uD0AC"
|
|
11953
|
+
},
|
|
11954
|
+
{
|
|
11955
|
+
keywords: ["component", "\uCEF4\uD3EC\uB10C\uD2B8", "react", "vue", "ui"],
|
|
11956
|
+
agent: "component-gen",
|
|
11957
|
+
skill: "gen-component",
|
|
11958
|
+
agentDescription: "UI \uCEF4\uD3EC\uB10C\uD2B8 \uC0DD\uC131 \uC5D0\uC774\uC804\uD2B8",
|
|
11959
|
+
skillDescription: "\uCEF4\uD3EC\uB10C\uD2B8 \uBCF4\uC77C\uB7EC\uD50C\uB808\uC774\uD2B8 \uC0DD\uC131 \uC2A4\uD0AC"
|
|
11960
|
+
},
|
|
11961
|
+
{
|
|
11962
|
+
keywords: ["db", "database", "prisma", "migration", "\uB9C8\uC774\uADF8\uB808\uC774\uC158", "\uB370\uC774\uD130\uBCA0\uC774\uC2A4"],
|
|
11963
|
+
agent: null,
|
|
11964
|
+
skill: "db-migrate",
|
|
11965
|
+
skillDescription: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC2A4\uD0AC"
|
|
11966
|
+
},
|
|
11967
|
+
{
|
|
11968
|
+
keywords: ["doc", "\uBB38\uC11C", "readme", "jsdoc", "\uC8FC\uC11D", "documentation"],
|
|
11969
|
+
agent: "doc-generator",
|
|
11970
|
+
skill: "doc",
|
|
11971
|
+
agentDescription: "\uBB38\uC11C \uC0DD\uC131 \uC5D0\uC774\uC804\uD2B8",
|
|
11972
|
+
skillDescription: "\uBB38\uC11C\uD654 \uC2A4\uD0AC"
|
|
11973
|
+
},
|
|
11974
|
+
{
|
|
11975
|
+
keywords: ["type", "\uD0C0\uC785", "schema", "zod", "typescript"],
|
|
11976
|
+
agent: null,
|
|
11977
|
+
skill: "gen-types",
|
|
11978
|
+
skillDescription: "\uD0C0\uC785 \uC815\uC758 \uC0DD\uC131 \uC2A4\uD0AC"
|
|
11979
|
+
},
|
|
11980
|
+
{
|
|
11981
|
+
keywords: ["lint", "eslint", "prettier", "\uD3EC\uB9F7", "format"],
|
|
11982
|
+
agent: null,
|
|
11983
|
+
skill: "lint",
|
|
11984
|
+
skillDescription: "\uCF54\uB4DC \uB9B0\uD2B8 \uBC0F \uD3EC\uB9F7 \uC2A4\uD0AC"
|
|
11985
|
+
},
|
|
11986
|
+
{
|
|
11987
|
+
keywords: ["review", "\uB9AC\uBDF0", "\uAC80\uC99D", "code review", "\uCF54\uB4DC \uB9AC\uBDF0"],
|
|
11988
|
+
agent: "code-reviewer",
|
|
11989
|
+
skill: "review",
|
|
11990
|
+
agentDescription: "\uCF54\uB4DC \uB9AC\uBDF0 \uBC0F \uD488\uC9C8 \uAC80\uC99D \uC5D0\uC774\uC804\uD2B8",
|
|
11991
|
+
skillDescription: "\uCF54\uB4DC \uB9AC\uBDF0 \uC2A4\uD0AC"
|
|
11992
|
+
}
|
|
11993
|
+
];
|
|
11994
|
+
|
|
11995
|
+
// src/core/prepare/document-analyzer.ts
|
|
11996
|
+
import * as fs16 from "fs";
|
|
11997
|
+
import * as path29 from "path";
|
|
11998
|
+
var DocumentAnalyzer = class {
|
|
11999
|
+
sddDir;
|
|
12000
|
+
constructor(projectRoot) {
|
|
12001
|
+
this.sddDir = path29.join(projectRoot, ".sdd");
|
|
12002
|
+
}
|
|
12003
|
+
/**
|
|
12004
|
+
* 기능 디렉토리의 모든 문서 분석
|
|
12005
|
+
*/
|
|
12006
|
+
async analyzeFeature(featureName) {
|
|
12007
|
+
const featureDir = path29.join(this.sddDir, "specs", featureName);
|
|
12008
|
+
if (!fs16.existsSync(featureDir)) {
|
|
12009
|
+
throw new Error(`\uAE30\uB2A5 \uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${featureDir}`);
|
|
12010
|
+
}
|
|
12011
|
+
const results = [];
|
|
12012
|
+
const documents = [
|
|
12013
|
+
{ file: "tasks.md", type: "tasks" },
|
|
12014
|
+
{ file: "plan.md", type: "plan" },
|
|
12015
|
+
{ file: "spec.md", type: "spec" }
|
|
12016
|
+
];
|
|
12017
|
+
for (const doc of documents) {
|
|
12018
|
+
const filePath = path29.join(featureDir, doc.file);
|
|
12019
|
+
if (fs16.existsSync(filePath)) {
|
|
12020
|
+
const analysis = await this.analyzeDocument(filePath, doc.type);
|
|
12021
|
+
results.push(analysis);
|
|
12022
|
+
}
|
|
12023
|
+
}
|
|
12024
|
+
return results;
|
|
12025
|
+
}
|
|
12026
|
+
/**
|
|
12027
|
+
* 단일 문서 분석
|
|
12028
|
+
*/
|
|
12029
|
+
async analyzeDocument(filePath, type) {
|
|
12030
|
+
const content = fs16.readFileSync(filePath, "utf-8");
|
|
12031
|
+
const lines = content.split("\n");
|
|
12032
|
+
const analyzedLines = [];
|
|
12033
|
+
let taskCount = 0;
|
|
12034
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12035
|
+
const line = lines[i];
|
|
12036
|
+
const keywords = this.extractKeywords(line);
|
|
12037
|
+
if (/^[\s]*-\s*\[[\sx]\]/.test(line)) {
|
|
12038
|
+
taskCount++;
|
|
12039
|
+
}
|
|
12040
|
+
if (keywords.length > 0) {
|
|
12041
|
+
analyzedLines.push({
|
|
12042
|
+
lineNumber: i + 1,
|
|
12043
|
+
content: line.trim(),
|
|
12044
|
+
keywords
|
|
12045
|
+
});
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
return {
|
|
12049
|
+
file: filePath,
|
|
12050
|
+
type,
|
|
12051
|
+
lines: analyzedLines,
|
|
12052
|
+
taskCount
|
|
12053
|
+
};
|
|
12054
|
+
}
|
|
12055
|
+
/**
|
|
12056
|
+
* 라인에서 키워드 추출
|
|
12057
|
+
*/
|
|
12058
|
+
extractKeywords(line) {
|
|
12059
|
+
const keywords = [];
|
|
12060
|
+
const lowerLine = line.toLowerCase();
|
|
12061
|
+
if (/test|테스트|vitest|jest|단위\s*테스트|unit\s*test/i.test(lowerLine)) {
|
|
12062
|
+
keywords.push("test");
|
|
12063
|
+
}
|
|
12064
|
+
if (/\bapi\b|rest|endpoint|엔드포인트|graphql/i.test(lowerLine)) {
|
|
12065
|
+
keywords.push("api");
|
|
12066
|
+
}
|
|
12067
|
+
if (/component|컴포넌트|\breact\b|\bvue\b|\bui\b/i.test(lowerLine)) {
|
|
12068
|
+
keywords.push("component");
|
|
12069
|
+
}
|
|
12070
|
+
if (/\bdb\b|database|prisma|migration|마이그레이션|데이터베이스/i.test(lowerLine)) {
|
|
12071
|
+
keywords.push("database");
|
|
12072
|
+
}
|
|
12073
|
+
if (/\bdoc\b|문서|readme|jsdoc|주석|documentation/i.test(lowerLine)) {
|
|
12074
|
+
keywords.push("doc");
|
|
12075
|
+
}
|
|
12076
|
+
if (/\btype\b|타입|schema|스키마|\bzod\b|typescript/i.test(lowerLine)) {
|
|
12077
|
+
keywords.push("type");
|
|
12078
|
+
}
|
|
12079
|
+
if (/lint|eslint|prettier|포맷|format/i.test(lowerLine)) {
|
|
12080
|
+
keywords.push("lint");
|
|
12081
|
+
}
|
|
12082
|
+
if (/review|리뷰|검증|code\s*review|코드\s*리뷰/i.test(lowerLine)) {
|
|
12083
|
+
keywords.push("review");
|
|
12084
|
+
}
|
|
12085
|
+
return [...new Set(keywords)];
|
|
12086
|
+
}
|
|
12087
|
+
/**
|
|
12088
|
+
* 분석 결과에서 DetectionSource 배열 생성
|
|
12089
|
+
*/
|
|
12090
|
+
static toDetectionSources(analyses) {
|
|
12091
|
+
const sources = [];
|
|
12092
|
+
for (const analysis of analyses) {
|
|
12093
|
+
for (const line of analysis.lines) {
|
|
12094
|
+
for (const keyword of line.keywords) {
|
|
12095
|
+
sources.push({
|
|
12096
|
+
file: path29.basename(analysis.file),
|
|
12097
|
+
line: line.lineNumber,
|
|
12098
|
+
text: line.content,
|
|
12099
|
+
keyword
|
|
12100
|
+
});
|
|
12101
|
+
}
|
|
12102
|
+
}
|
|
12103
|
+
}
|
|
12104
|
+
return sources;
|
|
12105
|
+
}
|
|
12106
|
+
/**
|
|
12107
|
+
* 전체 태스크 수 계산
|
|
12108
|
+
*/
|
|
12109
|
+
static getTotalTaskCount(analyses) {
|
|
12110
|
+
return analyses.reduce((sum, a) => sum + a.taskCount, 0);
|
|
12111
|
+
}
|
|
12112
|
+
};
|
|
12113
|
+
|
|
12114
|
+
// src/core/prepare/tool-detector.ts
|
|
12115
|
+
var ToolDetector = class {
|
|
12116
|
+
mappings;
|
|
12117
|
+
constructor(customMappings) {
|
|
12118
|
+
this.mappings = customMappings ?? DEFAULT_KEYWORD_MAPPINGS;
|
|
12119
|
+
}
|
|
12120
|
+
/**
|
|
12121
|
+
* 문서 분석 결과에서 필요한 도구 감지
|
|
12122
|
+
*/
|
|
12123
|
+
detect(analyses) {
|
|
12124
|
+
const sources = DocumentAnalyzer.toDetectionSources(analyses);
|
|
12125
|
+
return this.detectFromSources(sources);
|
|
12126
|
+
}
|
|
12127
|
+
/**
|
|
12128
|
+
* DetectionSource 배열에서 도구 감지
|
|
12129
|
+
*/
|
|
12130
|
+
detectFromSources(sources) {
|
|
12131
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
12132
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
12133
|
+
for (const source of sources) {
|
|
12134
|
+
const mapping = this.findMapping(source.keyword);
|
|
12135
|
+
if (!mapping) continue;
|
|
12136
|
+
if (mapping.agent) {
|
|
12137
|
+
const existing2 = agentMap.get(mapping.agent);
|
|
12138
|
+
if (existing2) {
|
|
12139
|
+
existing2.sources.push(source);
|
|
12140
|
+
} else {
|
|
12141
|
+
agentMap.set(mapping.agent, {
|
|
12142
|
+
type: "agent",
|
|
12143
|
+
name: mapping.agent,
|
|
12144
|
+
description: mapping.agentDescription ?? `${mapping.agent} \uC5D0\uC774\uC804\uD2B8`,
|
|
12145
|
+
sources: [source]
|
|
12146
|
+
});
|
|
12147
|
+
}
|
|
12148
|
+
}
|
|
12149
|
+
const existing = skillMap.get(mapping.skill);
|
|
12150
|
+
if (existing) {
|
|
12151
|
+
existing.sources.push(source);
|
|
12152
|
+
} else {
|
|
12153
|
+
skillMap.set(mapping.skill, {
|
|
12154
|
+
type: "skill",
|
|
12155
|
+
name: mapping.skill,
|
|
12156
|
+
description: mapping.skillDescription ?? `${mapping.skill} \uC2A4\uD0AC`,
|
|
12157
|
+
sources: [source]
|
|
12158
|
+
});
|
|
12159
|
+
}
|
|
12160
|
+
}
|
|
12161
|
+
return {
|
|
12162
|
+
agents: Array.from(agentMap.values()),
|
|
12163
|
+
skills: Array.from(skillMap.values()),
|
|
12164
|
+
totalSources: sources.length
|
|
12165
|
+
};
|
|
12166
|
+
}
|
|
12167
|
+
/**
|
|
12168
|
+
* 키워드에 해당하는 매핑 찾기
|
|
12169
|
+
*/
|
|
12170
|
+
findMapping(keyword) {
|
|
12171
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
12172
|
+
for (const mapping of this.mappings) {
|
|
12173
|
+
for (const kw of mapping.keywords) {
|
|
12174
|
+
if (lowerKeyword.includes(kw.toLowerCase()) || kw.toLowerCase().includes(lowerKeyword)) {
|
|
12175
|
+
return mapping;
|
|
12176
|
+
}
|
|
12177
|
+
}
|
|
12178
|
+
}
|
|
12179
|
+
return void 0;
|
|
12180
|
+
}
|
|
12181
|
+
/**
|
|
12182
|
+
* 감지된 도구 요약
|
|
12183
|
+
*/
|
|
12184
|
+
static summarize(result) {
|
|
12185
|
+
const lines = [];
|
|
12186
|
+
lines.push(`## \uAC10\uC9C0\uB41C \uB3C4\uAD6C`);
|
|
12187
|
+
lines.push("");
|
|
12188
|
+
if (result.agents.length > 0) {
|
|
12189
|
+
lines.push(`### \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8 (${result.agents.length}\uAC1C)`);
|
|
12190
|
+
lines.push("");
|
|
12191
|
+
lines.push("| \uC774\uB984 | \uC124\uBA85 | \uAC10\uC9C0 \uD69F\uC218 |");
|
|
12192
|
+
lines.push("|------|------|-----------|");
|
|
12193
|
+
for (const agent of result.agents) {
|
|
12194
|
+
lines.push(`| ${agent.name} | ${agent.description} | ${agent.sources.length} |`);
|
|
12195
|
+
}
|
|
12196
|
+
lines.push("");
|
|
12197
|
+
}
|
|
12198
|
+
if (result.skills.length > 0) {
|
|
12199
|
+
lines.push(`### \uC2A4\uD0AC (${result.skills.length}\uAC1C)`);
|
|
12200
|
+
lines.push("");
|
|
12201
|
+
lines.push("| \uC774\uB984 | \uC124\uBA85 | \uAC10\uC9C0 \uD69F\uC218 |");
|
|
12202
|
+
lines.push("|------|------|-----------|");
|
|
12203
|
+
for (const skill of result.skills) {
|
|
12204
|
+
lines.push(`| ${skill.name} | ${skill.description} | ${skill.sources.length} |`);
|
|
12205
|
+
}
|
|
12206
|
+
lines.push("");
|
|
12207
|
+
}
|
|
12208
|
+
return lines.join("\n");
|
|
12209
|
+
}
|
|
12210
|
+
};
|
|
12211
|
+
|
|
12212
|
+
// src/core/prepare/agent-scanner.ts
|
|
12213
|
+
import * as fs17 from "fs";
|
|
12214
|
+
import * as path30 from "path";
|
|
12215
|
+
import matter6 from "gray-matter";
|
|
12216
|
+
var AgentScanner = class {
|
|
12217
|
+
agentsDir;
|
|
12218
|
+
constructor(projectRoot) {
|
|
12219
|
+
this.agentsDir = path30.join(projectRoot, ".claude", "agents");
|
|
12220
|
+
}
|
|
12221
|
+
/**
|
|
12222
|
+
* 에이전트 디렉토리 존재 여부
|
|
12223
|
+
*/
|
|
12224
|
+
exists() {
|
|
12225
|
+
return fs17.existsSync(this.agentsDir);
|
|
12226
|
+
}
|
|
12227
|
+
/**
|
|
12228
|
+
* 모든 에이전트 스캔
|
|
12229
|
+
*/
|
|
12230
|
+
async scanAll() {
|
|
12231
|
+
if (!this.exists()) {
|
|
12232
|
+
return [];
|
|
12233
|
+
}
|
|
12234
|
+
const agents = [];
|
|
12235
|
+
const files = fs17.readdirSync(this.agentsDir);
|
|
12236
|
+
for (const file of files) {
|
|
12237
|
+
if (!file.endsWith(".md")) continue;
|
|
12238
|
+
const filePath = path30.join(this.agentsDir, file);
|
|
12239
|
+
const agent = await this.scanAgent(filePath);
|
|
12240
|
+
if (agent) {
|
|
12241
|
+
agents.push(agent);
|
|
12242
|
+
}
|
|
12243
|
+
}
|
|
12244
|
+
return agents;
|
|
12245
|
+
}
|
|
12246
|
+
/**
|
|
12247
|
+
* 단일 에이전트 파일 스캔
|
|
12248
|
+
*/
|
|
12249
|
+
async scanAgent(filePath) {
|
|
12250
|
+
try {
|
|
12251
|
+
const content = fs17.readFileSync(filePath, "utf-8");
|
|
12252
|
+
const { data, content: body } = matter6(content);
|
|
12253
|
+
const metadata = this.parseMetadata(data, filePath);
|
|
12254
|
+
if (!metadata) {
|
|
12255
|
+
return null;
|
|
12256
|
+
}
|
|
12257
|
+
return {
|
|
12258
|
+
name: metadata.name,
|
|
12259
|
+
filePath,
|
|
12260
|
+
metadata,
|
|
12261
|
+
content: body.trim()
|
|
12262
|
+
};
|
|
12263
|
+
} catch {
|
|
12264
|
+
return null;
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
/**
|
|
12268
|
+
* 특정 에이전트 존재 확인
|
|
12269
|
+
*/
|
|
12270
|
+
hasAgent(name) {
|
|
12271
|
+
const filePath = path30.join(this.agentsDir, `${name}.md`);
|
|
12272
|
+
return fs17.existsSync(filePath);
|
|
12273
|
+
}
|
|
12274
|
+
/**
|
|
12275
|
+
* 특정 에이전트 가져오기
|
|
12276
|
+
*/
|
|
12277
|
+
async getAgent(name) {
|
|
12278
|
+
const filePath = path30.join(this.agentsDir, `${name}.md`);
|
|
12279
|
+
if (!fs17.existsSync(filePath)) {
|
|
12280
|
+
return null;
|
|
12281
|
+
}
|
|
12282
|
+
return this.scanAgent(filePath);
|
|
12283
|
+
}
|
|
12284
|
+
/**
|
|
12285
|
+
* 메타데이터 파싱
|
|
12286
|
+
*/
|
|
12287
|
+
parseMetadata(data, filePath) {
|
|
12288
|
+
try {
|
|
12289
|
+
if (typeof data.tools === "string") {
|
|
12290
|
+
data.tools = data.tools.split(",").map((t) => t.trim());
|
|
12291
|
+
}
|
|
12292
|
+
const result = AgentMetadataSchema.safeParse(data);
|
|
12293
|
+
if (result.success) {
|
|
12294
|
+
return result.data;
|
|
12295
|
+
}
|
|
12296
|
+
const fileName = path30.basename(filePath, ".md");
|
|
12297
|
+
const withName = { ...data, name: data.name ?? fileName };
|
|
12298
|
+
const retryResult = AgentMetadataSchema.safeParse(withName);
|
|
12299
|
+
if (retryResult.success) {
|
|
12300
|
+
return retryResult.data;
|
|
12301
|
+
}
|
|
12302
|
+
return null;
|
|
12303
|
+
} catch {
|
|
12304
|
+
return null;
|
|
12305
|
+
}
|
|
12306
|
+
}
|
|
12307
|
+
/**
|
|
12308
|
+
* 에이전트 디렉토리 경로
|
|
12309
|
+
*/
|
|
12310
|
+
getAgentsDir() {
|
|
12311
|
+
return this.agentsDir;
|
|
12312
|
+
}
|
|
12313
|
+
/**
|
|
12314
|
+
* 에이전트 파일 경로 생성
|
|
12315
|
+
*/
|
|
12316
|
+
getAgentFilePath(name) {
|
|
12317
|
+
return path30.join(this.agentsDir, `${name}.md`);
|
|
12318
|
+
}
|
|
12319
|
+
};
|
|
12320
|
+
|
|
12321
|
+
// src/core/prepare/skill-scanner.ts
|
|
12322
|
+
import * as fs18 from "fs";
|
|
12323
|
+
import * as path31 from "path";
|
|
12324
|
+
import matter7 from "gray-matter";
|
|
12325
|
+
var SkillScanner = class {
|
|
12326
|
+
skillsDir;
|
|
12327
|
+
constructor(projectRoot) {
|
|
12328
|
+
this.skillsDir = path31.join(projectRoot, ".claude", "skills");
|
|
12329
|
+
}
|
|
12330
|
+
/**
|
|
12331
|
+
* 스킬 디렉토리 존재 여부
|
|
12332
|
+
*/
|
|
12333
|
+
exists() {
|
|
12334
|
+
return fs18.existsSync(this.skillsDir);
|
|
12335
|
+
}
|
|
12336
|
+
/**
|
|
12337
|
+
* 모든 스킬 스캔
|
|
12338
|
+
*/
|
|
12339
|
+
async scanAll() {
|
|
12340
|
+
if (!this.exists()) {
|
|
12341
|
+
return [];
|
|
12342
|
+
}
|
|
12343
|
+
const skills = [];
|
|
12344
|
+
const entries = fs18.readdirSync(this.skillsDir, { withFileTypes: true });
|
|
12345
|
+
for (const entry of entries) {
|
|
12346
|
+
if (!entry.isDirectory()) continue;
|
|
12347
|
+
const skillDir = path31.join(this.skillsDir, entry.name);
|
|
12348
|
+
const skill = await this.scanSkill(skillDir);
|
|
12349
|
+
if (skill) {
|
|
12350
|
+
skills.push(skill);
|
|
12351
|
+
}
|
|
12352
|
+
}
|
|
12353
|
+
return skills;
|
|
12354
|
+
}
|
|
12355
|
+
/**
|
|
12356
|
+
* 단일 스킬 디렉토리 스캔
|
|
12357
|
+
*/
|
|
12358
|
+
async scanSkill(skillDir) {
|
|
12359
|
+
const skillFile = path31.join(skillDir, "SKILL.md");
|
|
12360
|
+
if (!fs18.existsSync(skillFile)) {
|
|
12361
|
+
return null;
|
|
12362
|
+
}
|
|
12363
|
+
try {
|
|
12364
|
+
const content = fs18.readFileSync(skillFile, "utf-8");
|
|
12365
|
+
const { data, content: body } = matter7(content);
|
|
12366
|
+
const metadata = this.parseMetadata(data, skillDir);
|
|
12367
|
+
if (!metadata) {
|
|
12368
|
+
return null;
|
|
12369
|
+
}
|
|
12370
|
+
return {
|
|
12371
|
+
name: metadata.name,
|
|
12372
|
+
dirPath: skillDir,
|
|
12373
|
+
filePath: skillFile,
|
|
12374
|
+
metadata,
|
|
12375
|
+
content: body.trim()
|
|
12376
|
+
};
|
|
12377
|
+
} catch {
|
|
12378
|
+
return null;
|
|
12379
|
+
}
|
|
12380
|
+
}
|
|
12381
|
+
/**
|
|
12382
|
+
* 특정 스킬 존재 확인
|
|
12383
|
+
*/
|
|
12384
|
+
hasSkill(name) {
|
|
12385
|
+
const skillDir = path31.join(this.skillsDir, name);
|
|
12386
|
+
const skillFile = path31.join(skillDir, "SKILL.md");
|
|
12387
|
+
return fs18.existsSync(skillFile);
|
|
12388
|
+
}
|
|
12389
|
+
/**
|
|
12390
|
+
* 특정 스킬 가져오기
|
|
12391
|
+
*/
|
|
12392
|
+
async getSkill(name) {
|
|
12393
|
+
const skillDir = path31.join(this.skillsDir, name);
|
|
12394
|
+
if (!fs18.existsSync(skillDir)) {
|
|
12395
|
+
return null;
|
|
12396
|
+
}
|
|
12397
|
+
return this.scanSkill(skillDir);
|
|
12398
|
+
}
|
|
12399
|
+
/**
|
|
12400
|
+
* 메타데이터 파싱
|
|
12401
|
+
*/
|
|
12402
|
+
parseMetadata(data, skillDir) {
|
|
12403
|
+
try {
|
|
12404
|
+
if (typeof data["allowed-tools"] === "string") {
|
|
12405
|
+
data["allowed-tools"] = data["allowed-tools"].split(",").map((t) => t.trim());
|
|
12406
|
+
}
|
|
12407
|
+
const result = SkillMetadataSchema.safeParse(data);
|
|
12408
|
+
if (result.success) {
|
|
12409
|
+
return result.data;
|
|
12410
|
+
}
|
|
12411
|
+
const dirName = path31.basename(skillDir);
|
|
12412
|
+
const withName = { ...data, name: data.name ?? dirName };
|
|
12413
|
+
const retryResult = SkillMetadataSchema.safeParse(withName);
|
|
12414
|
+
if (retryResult.success) {
|
|
12415
|
+
return retryResult.data;
|
|
12416
|
+
}
|
|
12417
|
+
return null;
|
|
12418
|
+
} catch {
|
|
12419
|
+
return null;
|
|
12420
|
+
}
|
|
12421
|
+
}
|
|
12422
|
+
/**
|
|
12423
|
+
* 스킬 디렉토리 경로
|
|
12424
|
+
*/
|
|
12425
|
+
getSkillsDir() {
|
|
12426
|
+
return this.skillsDir;
|
|
12427
|
+
}
|
|
12428
|
+
/**
|
|
12429
|
+
* 스킬 디렉토리 경로 생성
|
|
12430
|
+
*/
|
|
12431
|
+
getSkillDirPath(name) {
|
|
12432
|
+
return path31.join(this.skillsDir, name);
|
|
12433
|
+
}
|
|
12434
|
+
/**
|
|
12435
|
+
* 스킬 파일 경로 생성
|
|
12436
|
+
*/
|
|
12437
|
+
getSkillFilePath(name) {
|
|
12438
|
+
return path31.join(this.skillsDir, name, "SKILL.md");
|
|
12439
|
+
}
|
|
12440
|
+
};
|
|
12441
|
+
|
|
12442
|
+
// src/core/prepare/agent-generator.ts
|
|
12443
|
+
import * as fs19 from "fs";
|
|
12444
|
+
import * as path32 from "path";
|
|
12445
|
+
var AgentGenerator = class {
|
|
12446
|
+
agentsDir;
|
|
12447
|
+
constructor(projectRoot) {
|
|
12448
|
+
this.agentsDir = path32.join(projectRoot, ".claude", "agents");
|
|
12449
|
+
}
|
|
12450
|
+
/**
|
|
12451
|
+
* 감지된 도구에서 에이전트 초안 생성
|
|
12452
|
+
*/
|
|
12453
|
+
generate(tool, options) {
|
|
12454
|
+
const model = options?.model ?? "sonnet";
|
|
12455
|
+
const tools = options?.tools ?? this.getDefaultTools(tool.name);
|
|
12456
|
+
const content = this.generateContent(tool, model, tools);
|
|
12457
|
+
const filePath = path32.join(this.agentsDir, `${tool.name}.md`);
|
|
12458
|
+
return {
|
|
12459
|
+
name: tool.name,
|
|
12460
|
+
filePath,
|
|
12461
|
+
content
|
|
12462
|
+
};
|
|
12463
|
+
}
|
|
12464
|
+
/**
|
|
12465
|
+
* 에이전트 파일 생성
|
|
12466
|
+
*/
|
|
12467
|
+
async writeAgent(agent) {
|
|
12468
|
+
if (!fs19.existsSync(this.agentsDir)) {
|
|
12469
|
+
fs19.mkdirSync(this.agentsDir, { recursive: true });
|
|
12470
|
+
}
|
|
12471
|
+
fs19.writeFileSync(agent.filePath, agent.content, "utf-8");
|
|
12472
|
+
}
|
|
12473
|
+
/**
|
|
12474
|
+
* 에이전트 콘텐츠 생성
|
|
12475
|
+
*/
|
|
12476
|
+
generateContent(tool, model, tools) {
|
|
12477
|
+
const lines = [];
|
|
12478
|
+
lines.push("---");
|
|
12479
|
+
lines.push(`name: ${tool.name}`);
|
|
12480
|
+
lines.push(`description: ${tool.description}`);
|
|
12481
|
+
lines.push(`tools: ${tools.join(", ")}`);
|
|
12482
|
+
lines.push(`model: ${model}`);
|
|
12483
|
+
lines.push("---");
|
|
12484
|
+
lines.push("");
|
|
12485
|
+
lines.push(`# ${this.formatTitle(tool.name)} Agent`);
|
|
12486
|
+
lines.push("");
|
|
12487
|
+
lines.push(tool.description);
|
|
12488
|
+
lines.push("");
|
|
12489
|
+
lines.push("## Instructions");
|
|
12490
|
+
lines.push("");
|
|
12491
|
+
lines.push(...this.getInstructions(tool.name));
|
|
12492
|
+
lines.push("");
|
|
12493
|
+
if (tool.sources.length > 0) {
|
|
12494
|
+
lines.push("## Detection Sources");
|
|
12495
|
+
lines.push("");
|
|
12496
|
+
lines.push("> \uC774 \uC5D0\uC774\uC804\uD2B8\uB294 \uB2E4\uC74C \uBB38\uC11C\uC5D0\uC11C \uAC10\uC9C0\uB41C \uD0A4\uC6CC\uB4DC\uB97C \uAE30\uBC18\uC73C\uB85C \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
12497
|
+
lines.push("");
|
|
12498
|
+
for (const source of tool.sources.slice(0, 5)) {
|
|
12499
|
+
lines.push(`- ${source.file}:${source.line} - "${source.text.substring(0, 50)}..."`);
|
|
12500
|
+
}
|
|
12501
|
+
lines.push("");
|
|
12502
|
+
}
|
|
12503
|
+
return lines.join("\n");
|
|
12504
|
+
}
|
|
12505
|
+
/**
|
|
12506
|
+
* 에이전트별 기본 도구
|
|
12507
|
+
*/
|
|
12508
|
+
getDefaultTools(name) {
|
|
12509
|
+
const toolMap = {
|
|
12510
|
+
"test-runner": ["Read", "Glob", "Grep", "Bash"],
|
|
12511
|
+
"api-scaffold": ["Read", "Write", "Edit", "Glob", "Grep"],
|
|
12512
|
+
"component-gen": ["Read", "Write", "Edit", "Glob"],
|
|
12513
|
+
"doc-generator": ["Read", "Write", "Edit", "Glob", "Grep"],
|
|
12514
|
+
"code-reviewer": ["Read", "Glob", "Grep", "Bash"]
|
|
12515
|
+
};
|
|
12516
|
+
return toolMap[name] ?? ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
12517
|
+
}
|
|
12518
|
+
/**
|
|
12519
|
+
* 에이전트별 지침
|
|
12520
|
+
*/
|
|
12521
|
+
getInstructions(name) {
|
|
12522
|
+
const instructionMap = {
|
|
12523
|
+
"test-runner": [
|
|
12524
|
+
"1. \uD14C\uC2A4\uD2B8 \uD30C\uC77C \uD0D0\uC0C9 (`*.test.ts`, `*.spec.ts`)",
|
|
12525
|
+
"2. \uD14C\uC2A4\uD2B8 \uC2E4\uD589 (`vitest run` \uB610\uB294 `jest`)",
|
|
12526
|
+
"3. \uC2E4\uD328\uD55C \uD14C\uC2A4\uD2B8 \uBD84\uC11D",
|
|
12527
|
+
"4. \uCEE4\uBC84\uB9AC\uC9C0 \uB9AC\uD3EC\uD2B8 \uC0DD\uC131"
|
|
12528
|
+
],
|
|
12529
|
+
"api-scaffold": [
|
|
12530
|
+
"1. API \uC0AC\uC591 \uBD84\uC11D",
|
|
12531
|
+
"2. \uB77C\uC6B0\uD130/\uCEE8\uD2B8\uB864\uB7EC \uBCF4\uC77C\uB7EC\uD50C\uB808\uC774\uD2B8 \uC0DD\uC131",
|
|
12532
|
+
"3. \uD0C0\uC785 \uC815\uC758 \uC0DD\uC131",
|
|
12533
|
+
"4. \uAE30\uBCF8 \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4 \uC0DD\uC131"
|
|
12534
|
+
],
|
|
12535
|
+
"component-gen": [
|
|
12536
|
+
"1. \uCEF4\uD3EC\uB10C\uD2B8 \uC694\uAD6C\uC0AC\uD56D \uBD84\uC11D",
|
|
12537
|
+
"2. \uCEF4\uD3EC\uB10C\uD2B8 \uD30C\uC77C \uC0DD\uC131",
|
|
12538
|
+
"3. \uC2A4\uD0C0\uC77C \uD30C\uC77C \uC0DD\uC131",
|
|
12539
|
+
"4. \uAE30\uBCF8 \uD14C\uC2A4\uD2B8 \uC0DD\uC131"
|
|
12540
|
+
],
|
|
12541
|
+
"doc-generator": [
|
|
12542
|
+
"1. \uCF54\uB4DC\uBCA0\uC774\uC2A4 \uBD84\uC11D",
|
|
12543
|
+
"2. JSDoc/TSDoc \uC8FC\uC11D \uC0DD\uC131",
|
|
12544
|
+
"3. README \uC5C5\uB370\uC774\uD2B8",
|
|
12545
|
+
"4. API \uBB38\uC11C \uC0DD\uC131"
|
|
12546
|
+
],
|
|
12547
|
+
"code-reviewer": [
|
|
12548
|
+
"1. \uBCC0\uACBD\uB41C \uD30C\uC77C \uBD84\uC11D",
|
|
12549
|
+
"2. \uCF54\uB4DC \uD488\uC9C8 \uAC80\uC0AC",
|
|
12550
|
+
"3. \uBCF4\uC548 \uCDE8\uC57D\uC810 \uAC80\uC0AC",
|
|
12551
|
+
"4. \uAC1C\uC120 \uC81C\uC548 \uC791\uC131"
|
|
12552
|
+
]
|
|
12553
|
+
};
|
|
12554
|
+
return instructionMap[name] ?? [
|
|
12555
|
+
"1. \uC791\uC5C5 \uC694\uAD6C\uC0AC\uD56D \uBD84\uC11D",
|
|
12556
|
+
"2. \uD544\uC694\uD55C \uD30C\uC77C \uD0D0\uC0C9",
|
|
12557
|
+
"3. \uC791\uC5C5 \uC218\uD589",
|
|
12558
|
+
"4. \uACB0\uACFC \uAC80\uC99D"
|
|
12559
|
+
];
|
|
12560
|
+
}
|
|
12561
|
+
/**
|
|
12562
|
+
* 이름 포맷팅 (kebab-case → Title Case)
|
|
12563
|
+
*/
|
|
12564
|
+
formatTitle(name) {
|
|
12565
|
+
return name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
12566
|
+
}
|
|
12567
|
+
};
|
|
12568
|
+
|
|
12569
|
+
// src/core/prepare/skill-generator.ts
|
|
12570
|
+
import * as fs20 from "fs";
|
|
12571
|
+
import * as path33 from "path";
|
|
12572
|
+
var SkillGenerator = class {
|
|
12573
|
+
skillsDir;
|
|
12574
|
+
constructor(projectRoot) {
|
|
12575
|
+
this.skillsDir = path33.join(projectRoot, ".claude", "skills");
|
|
12576
|
+
}
|
|
12577
|
+
/**
|
|
12578
|
+
* 감지된 도구에서 스킬 초안 생성
|
|
12579
|
+
*/
|
|
12580
|
+
generate(tool, options) {
|
|
12581
|
+
const allowedTools = options?.allowedTools ?? this.getDefaultTools(tool.name);
|
|
12582
|
+
const content = this.generateContent(tool, allowedTools);
|
|
12583
|
+
const dirPath = path33.join(this.skillsDir, tool.name);
|
|
12584
|
+
const filePath = path33.join(dirPath, "SKILL.md");
|
|
12585
|
+
return {
|
|
12586
|
+
name: tool.name,
|
|
12587
|
+
dirPath,
|
|
12588
|
+
filePath,
|
|
12589
|
+
content
|
|
12590
|
+
};
|
|
12591
|
+
}
|
|
12592
|
+
/**
|
|
12593
|
+
* 스킬 파일 생성
|
|
12594
|
+
*/
|
|
12595
|
+
async writeSkill(skill) {
|
|
12596
|
+
if (!fs20.existsSync(skill.dirPath)) {
|
|
12597
|
+
fs20.mkdirSync(skill.dirPath, { recursive: true });
|
|
12598
|
+
}
|
|
12599
|
+
fs20.writeFileSync(skill.filePath, skill.content, "utf-8");
|
|
12600
|
+
}
|
|
12601
|
+
/**
|
|
12602
|
+
* 스킬 콘텐츠 생성
|
|
12603
|
+
*/
|
|
12604
|
+
generateContent(tool, allowedTools) {
|
|
12605
|
+
const lines = [];
|
|
12606
|
+
lines.push("---");
|
|
12607
|
+
lines.push(`name: ${tool.name}`);
|
|
12608
|
+
lines.push(`description: ${tool.description}`);
|
|
12609
|
+
lines.push(`allowed-tools: ${allowedTools.join(", ")}`);
|
|
12610
|
+
lines.push("---");
|
|
12611
|
+
lines.push("");
|
|
12612
|
+
lines.push(`# ${this.formatTitle(tool.name)} Skill`);
|
|
12613
|
+
lines.push("");
|
|
12614
|
+
lines.push(tool.description);
|
|
12615
|
+
lines.push("");
|
|
12616
|
+
lines.push("## Instructions");
|
|
12617
|
+
lines.push("");
|
|
12618
|
+
lines.push(...this.getInstructions(tool.name));
|
|
12619
|
+
lines.push("");
|
|
12620
|
+
lines.push("## Examples");
|
|
12621
|
+
lines.push("");
|
|
12622
|
+
lines.push(...this.getExamples(tool.name));
|
|
12623
|
+
lines.push("");
|
|
12624
|
+
if (tool.sources.length > 0) {
|
|
12625
|
+
lines.push("## Detection Sources");
|
|
12626
|
+
lines.push("");
|
|
12627
|
+
lines.push("> \uC774 \uC2A4\uD0AC\uC740 \uB2E4\uC74C \uBB38\uC11C\uC5D0\uC11C \uAC10\uC9C0\uB41C \uD0A4\uC6CC\uB4DC\uB97C \uAE30\uBC18\uC73C\uB85C \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
12628
|
+
lines.push("");
|
|
12629
|
+
for (const source of tool.sources.slice(0, 5)) {
|
|
12630
|
+
lines.push(`- ${source.file}:${source.line} - "${source.text.substring(0, 50)}..."`);
|
|
12631
|
+
}
|
|
12632
|
+
lines.push("");
|
|
12633
|
+
}
|
|
12634
|
+
return lines.join("\n");
|
|
12635
|
+
}
|
|
12636
|
+
/**
|
|
12637
|
+
* 스킬별 기본 도구
|
|
12638
|
+
*/
|
|
12639
|
+
getDefaultTools(name) {
|
|
12640
|
+
const toolMap = {
|
|
12641
|
+
"test": ["Read", "Glob", "Grep", "Bash"],
|
|
12642
|
+
"gen-api": ["Read", "Write", "Edit", "Glob"],
|
|
12643
|
+
"gen-component": ["Read", "Write", "Edit", "Glob"],
|
|
12644
|
+
"db-migrate": ["Read", "Write", "Bash"],
|
|
12645
|
+
"doc": ["Read", "Write", "Edit", "Glob", "Grep"],
|
|
12646
|
+
"gen-types": ["Read", "Write", "Edit", "Glob"],
|
|
12647
|
+
"lint": ["Read", "Bash"],
|
|
12648
|
+
"review": ["Read", "Glob", "Grep"]
|
|
12649
|
+
};
|
|
12650
|
+
return toolMap[name] ?? ["Read", "Write", "Edit", "Glob"];
|
|
12651
|
+
}
|
|
12652
|
+
/**
|
|
12653
|
+
* 스킬별 지침
|
|
12654
|
+
*/
|
|
12655
|
+
getInstructions(name) {
|
|
12656
|
+
const instructionMap = {
|
|
12657
|
+
"test": [
|
|
12658
|
+
"1. \uB300\uC0C1 \uD30C\uC77C \uB610\uB294 \uBAA8\uB4C8 \uBD84\uC11D",
|
|
12659
|
+
"2. \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4 \uC791\uC131",
|
|
12660
|
+
"3. \uD14C\uC2A4\uD2B8 \uC2E4\uD589 \uBC0F \uACB0\uACFC \uD655\uC778",
|
|
12661
|
+
"4. \uCEE4\uBC84\uB9AC\uC9C0 \uAC1C\uC120 \uC81C\uC548"
|
|
12662
|
+
],
|
|
12663
|
+
"gen-api": [
|
|
12664
|
+
"1. API \uC694\uAD6C\uC0AC\uD56D \uBD84\uC11D",
|
|
12665
|
+
"2. \uC5D4\uB4DC\uD3EC\uC778\uD2B8 \uAD6C\uC870 \uC124\uACC4",
|
|
12666
|
+
"3. \uB77C\uC6B0\uD130/\uCEE8\uD2B8\uB864\uB7EC \uCF54\uB4DC \uC0DD\uC131",
|
|
12667
|
+
"4. \uD0C0\uC785 \uC815\uC758 \uC0DD\uC131"
|
|
12668
|
+
],
|
|
12669
|
+
"gen-component": [
|
|
12670
|
+
"1. \uCEF4\uD3EC\uB10C\uD2B8 \uC694\uAD6C\uC0AC\uD56D \uBD84\uC11D",
|
|
12671
|
+
"2. \uCEF4\uD3EC\uB10C\uD2B8 \uAD6C\uC870 \uC124\uACC4",
|
|
12672
|
+
"3. \uCEF4\uD3EC\uB10C\uD2B8 \uCF54\uB4DC \uC0DD\uC131",
|
|
12673
|
+
"4. \uC2A4\uD0C0\uC77C \uBC0F \uD14C\uC2A4\uD2B8 \uC0DD\uC131"
|
|
12674
|
+
],
|
|
12675
|
+
"db-migrate": [
|
|
12676
|
+
"1. \uC2A4\uD0A4\uB9C8 \uBCC0\uACBD\uC0AC\uD56D \uBD84\uC11D",
|
|
12677
|
+
"2. \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uD30C\uC77C \uC0DD\uC131",
|
|
12678
|
+
"3. \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC2E4\uD589",
|
|
12679
|
+
"4. \uB864\uBC31 \uC2A4\uD06C\uB9BD\uD2B8 \uAC80\uC99D"
|
|
12680
|
+
],
|
|
12681
|
+
"doc": [
|
|
12682
|
+
"1. \uCF54\uB4DC\uBCA0\uC774\uC2A4 \uBD84\uC11D",
|
|
12683
|
+
"2. \uBB38\uC11C\uD654 \uB300\uC0C1 \uC2DD\uBCC4",
|
|
12684
|
+
"3. \uBB38\uC11C \uC0DD\uC131 (README, API \uBB38\uC11C \uB4F1)",
|
|
12685
|
+
"4. \uAE30\uC874 \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8"
|
|
12686
|
+
],
|
|
12687
|
+
"gen-types": [
|
|
12688
|
+
"1. \uB370\uC774\uD130 \uAD6C\uC870 \uBD84\uC11D",
|
|
12689
|
+
"2. TypeScript \uD0C0\uC785/\uC778\uD130\uD398\uC774\uC2A4 \uC0DD\uC131",
|
|
12690
|
+
"3. Zod \uC2A4\uD0A4\uB9C8 \uC0DD\uC131 (\uD544\uC694\uC2DC)",
|
|
12691
|
+
"4. \uD0C0\uC785 \uB0B4\uBCF4\uB0B4\uAE30 \uC124\uC815"
|
|
12692
|
+
],
|
|
12693
|
+
"lint": [
|
|
12694
|
+
"1. \uB9B0\uD2B8 \uADDC\uCE59 \uD655\uC778",
|
|
12695
|
+
"2. \uB9B0\uD2B8 \uC624\uB958 \uAC80\uC0AC",
|
|
12696
|
+
"3. \uC790\uB3D9 \uC218\uC815 \uAC00\uB2A5\uD55C \uC624\uB958 \uC218\uC815",
|
|
12697
|
+
"4. \uC218\uB3D9 \uC218\uC815 \uD544\uC694\uD55C \uD56D\uBAA9 \uB9AC\uD3EC\uD2B8"
|
|
12698
|
+
],
|
|
12699
|
+
"review": [
|
|
12700
|
+
"1. \uBCC0\uACBD\uB41C \uCF54\uB4DC \uBD84\uC11D",
|
|
12701
|
+
"2. \uCF54\uB4DC \uD488\uC9C8 \uAC80\uC0AC",
|
|
12702
|
+
"3. \uC7A0\uC7AC\uC801 \uBB38\uC81C\uC810 \uC2DD\uBCC4",
|
|
12703
|
+
"4. \uAC1C\uC120 \uC81C\uC548 \uC791\uC131"
|
|
12704
|
+
]
|
|
12705
|
+
};
|
|
12706
|
+
return instructionMap[name] ?? [
|
|
12707
|
+
"1. \uC694\uAD6C\uC0AC\uD56D \uBD84\uC11D",
|
|
12708
|
+
"2. \uC791\uC5C5 \uC218\uD589",
|
|
12709
|
+
"3. \uACB0\uACFC \uAC80\uC99D",
|
|
12710
|
+
"4. \uB9AC\uD3EC\uD2B8 \uC791\uC131"
|
|
12711
|
+
];
|
|
12712
|
+
}
|
|
12713
|
+
/**
|
|
12714
|
+
* 스킬별 사용 예시
|
|
12715
|
+
*/
|
|
12716
|
+
getExamples(name) {
|
|
12717
|
+
const exampleMap = {
|
|
12718
|
+
"test": [
|
|
12719
|
+
"**\uC0AC\uC6A9\uC790**: UserService\uC5D0 \uB300\uD55C \uD14C\uC2A4\uD2B8\uB97C \uC791\uC131\uD574\uC918",
|
|
12720
|
+
"",
|
|
12721
|
+
"**\uC751\uB2F5**: UserService\uC758 \uC8FC\uC694 \uBA54\uC11C\uB4DC\uC5D0 \uB300\uD55C \uD14C\uC2A4\uD2B8 \uCF00\uC774\uC2A4\uB97C \uC791\uC131\uD558\uACA0\uC2B5\uB2C8\uB2E4..."
|
|
12722
|
+
],
|
|
12723
|
+
"gen-api": [
|
|
12724
|
+
"**\uC0AC\uC6A9\uC790**: \uC0AC\uC6A9\uC790 CRUD API\uB97C \uC0DD\uC131\uD574\uC918",
|
|
12725
|
+
"",
|
|
12726
|
+
"**\uC751\uB2F5**: \uC0AC\uC6A9\uC790 \uAD00\uB9AC\uB97C \uC704\uD55C CRUD API \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB97C \uC0DD\uC131\uD558\uACA0\uC2B5\uB2C8\uB2E4..."
|
|
12727
|
+
],
|
|
12728
|
+
"gen-component": [
|
|
12729
|
+
"**\uC0AC\uC6A9\uC790**: \uBAA8\uB2EC \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uB9CC\uB4E4\uC5B4\uC918",
|
|
12730
|
+
"",
|
|
12731
|
+
"**\uC751\uB2F5**: \uC7AC\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBAA8\uB2EC \uCEF4\uD3EC\uB10C\uD2B8\uB97C \uC0DD\uC131\uD558\uACA0\uC2B5\uB2C8\uB2E4..."
|
|
12732
|
+
],
|
|
12733
|
+
"doc": [
|
|
12734
|
+
"**\uC0AC\uC6A9\uC790**: API \uBB38\uC11C\uB97C \uC5C5\uB370\uC774\uD2B8\uD574\uC918",
|
|
12735
|
+
"",
|
|
12736
|
+
"**\uC751\uB2F5**: \uD604\uC7AC API \uC5D4\uB4DC\uD3EC\uC778\uD2B8\uB97C \uBD84\uC11D\uD558\uC5EC \uBB38\uC11C\uB97C \uC5C5\uB370\uC774\uD2B8\uD558\uACA0\uC2B5\uB2C8\uB2E4..."
|
|
12737
|
+
]
|
|
12738
|
+
};
|
|
12739
|
+
return exampleMap[name] ?? [
|
|
12740
|
+
`**\uC0AC\uC6A9\uC790**: ${this.formatTitle(name)} \uC791\uC5C5\uC744 \uC218\uD589\uD574\uC918`,
|
|
12741
|
+
"",
|
|
12742
|
+
"**\uC751\uB2F5**: \uC694\uCCAD\uD558\uC2E0 \uC791\uC5C5\uC744 \uC218\uD589\uD558\uACA0\uC2B5\uB2C8\uB2E4..."
|
|
12743
|
+
];
|
|
12744
|
+
}
|
|
12745
|
+
/**
|
|
12746
|
+
* 이름 포맷팅 (kebab-case → Title Case)
|
|
12747
|
+
*/
|
|
12748
|
+
formatTitle(name) {
|
|
12749
|
+
return name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
12750
|
+
}
|
|
12751
|
+
};
|
|
12752
|
+
|
|
12753
|
+
// src/core/prepare/prepare-report.ts
|
|
12754
|
+
import * as fs21 from "fs";
|
|
12755
|
+
import * as path34 from "path";
|
|
12756
|
+
var PrepareReportGenerator = class {
|
|
12757
|
+
sddDir;
|
|
12758
|
+
constructor(projectRoot) {
|
|
12759
|
+
this.sddDir = path34.join(projectRoot, ".sdd");
|
|
12760
|
+
}
|
|
12761
|
+
/**
|
|
12762
|
+
* 보고서 생성
|
|
12763
|
+
*/
|
|
12764
|
+
generate(input) {
|
|
12765
|
+
const agentMissing = input.agentChecks.filter((c) => c.status === "missing").length;
|
|
12766
|
+
const agentExisting = input.agentChecks.filter((c) => c.status === "exists").length;
|
|
12767
|
+
const skillMissing = input.skillChecks.filter((c) => c.status === "missing").length;
|
|
12768
|
+
const skillExisting = input.skillChecks.filter((c) => c.status === "exists").length;
|
|
12769
|
+
return {
|
|
12770
|
+
feature: input.feature,
|
|
12771
|
+
totalTasks: input.totalTasks,
|
|
12772
|
+
agents: {
|
|
12773
|
+
required: input.agentChecks.length,
|
|
12774
|
+
existing: agentExisting,
|
|
12775
|
+
missing: agentMissing,
|
|
12776
|
+
checks: input.agentChecks
|
|
12777
|
+
},
|
|
12778
|
+
skills: {
|
|
12779
|
+
required: input.skillChecks.length,
|
|
12780
|
+
existing: skillExisting,
|
|
12781
|
+
missing: skillMissing,
|
|
12782
|
+
checks: input.skillChecks
|
|
12783
|
+
},
|
|
12784
|
+
proposals: [
|
|
12785
|
+
...input.generatedAgents.map((a) => ({
|
|
12786
|
+
type: "agent",
|
|
12787
|
+
name: a.name,
|
|
12788
|
+
filePath: a.filePath,
|
|
12789
|
+
content: a.content
|
|
12790
|
+
})),
|
|
12791
|
+
...input.generatedSkills.map((s) => ({
|
|
12792
|
+
type: "skill",
|
|
12793
|
+
name: s.name,
|
|
12794
|
+
filePath: s.filePath,
|
|
12795
|
+
content: s.content
|
|
12796
|
+
}))
|
|
12797
|
+
],
|
|
12798
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12799
|
+
};
|
|
12800
|
+
}
|
|
12801
|
+
/**
|
|
12802
|
+
* 마크다운 보고서 생성
|
|
12803
|
+
*/
|
|
12804
|
+
toMarkdown(report) {
|
|
12805
|
+
const lines = [];
|
|
12806
|
+
lines.push(`# Prepare: ${report.feature}`);
|
|
12807
|
+
lines.push("");
|
|
12808
|
+
lines.push(`> \uC0DD\uC131\uC77C: ${report.createdAt.split("T")[0]}`);
|
|
12809
|
+
lines.push("");
|
|
12810
|
+
lines.push("## \uBD84\uC11D \uC694\uC57D");
|
|
12811
|
+
lines.push("");
|
|
12812
|
+
lines.push(`- \uCD1D \uC791\uC5C5 \uC218: ${report.totalTasks}\uAC1C`);
|
|
12813
|
+
lines.push(`- \uD544\uC694 \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8: ${report.agents.required}\uAC1C (\uC874\uC7AC: ${report.agents.existing}, \uB204\uB77D: ${report.agents.missing})`);
|
|
12814
|
+
lines.push(`- \uD544\uC694 \uC2A4\uD0AC: ${report.skills.required}\uAC1C (\uC874\uC7AC: ${report.skills.existing}, \uB204\uB77D: ${report.skills.missing})`);
|
|
12815
|
+
lines.push("");
|
|
12816
|
+
if (report.agents.checks.length > 0) {
|
|
12817
|
+
lines.push("## \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8 \uC810\uAC80");
|
|
12818
|
+
lines.push("");
|
|
12819
|
+
lines.push("> \uC704\uCE58: `.claude/agents/`");
|
|
12820
|
+
lines.push("");
|
|
12821
|
+
lines.push("| \uC5D0\uC774\uC804\uD2B8 | \uC124\uBA85 | \uC0C1\uD0DC | \uC870\uCE58 |");
|
|
12822
|
+
lines.push("|----------|------|------|------|");
|
|
12823
|
+
for (const check of report.agents.checks) {
|
|
12824
|
+
const status = check.status === "exists" ? "\u2705 \uC874\uC7AC" : "\u274C \uC5C6\uC74C";
|
|
12825
|
+
lines.push(`| ${check.tool.name} | ${check.tool.description} | ${status} | ${check.action} |`);
|
|
12826
|
+
}
|
|
12827
|
+
lines.push("");
|
|
12828
|
+
}
|
|
12829
|
+
if (report.skills.checks.length > 0) {
|
|
12830
|
+
lines.push("## \uC2A4\uD0AC \uC810\uAC80");
|
|
12831
|
+
lines.push("");
|
|
12832
|
+
lines.push("> \uC704\uCE58: `.claude/skills/<name>/SKILL.md`");
|
|
12833
|
+
lines.push("");
|
|
12834
|
+
lines.push("| \uC2A4\uD0AC | \uC124\uBA85 | \uC0C1\uD0DC | \uC870\uCE58 |");
|
|
12835
|
+
lines.push("|------|------|------|------|");
|
|
12836
|
+
for (const check of report.skills.checks) {
|
|
12837
|
+
const status = check.status === "exists" ? "\u2705 \uC874\uC7AC" : "\u274C \uC5C6\uC74C";
|
|
12838
|
+
lines.push(`| ${check.tool.name} | ${check.tool.description} | ${status} | ${check.action} |`);
|
|
12839
|
+
}
|
|
12840
|
+
lines.push("");
|
|
12841
|
+
}
|
|
12842
|
+
const proposals = report.proposals.filter((p) => p.content);
|
|
12843
|
+
if (proposals.length > 0) {
|
|
12844
|
+
lines.push("## \uCD94\uAC00/\uC218\uC815 \uC81C\uC548");
|
|
12845
|
+
lines.push("");
|
|
12846
|
+
for (const proposal of proposals) {
|
|
12847
|
+
const typeLabel = proposal.type === "agent" ? "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8" : "\uC2A4\uD0AC";
|
|
12848
|
+
lines.push(`### \uC0C8 ${typeLabel}: ${proposal.name}`);
|
|
12849
|
+
lines.push("");
|
|
12850
|
+
lines.push(`**\uD30C\uC77C:** \`${proposal.filePath}\``);
|
|
12851
|
+
lines.push("");
|
|
12852
|
+
lines.push("```markdown");
|
|
12853
|
+
lines.push(proposal.content);
|
|
12854
|
+
lines.push("```");
|
|
12855
|
+
lines.push("");
|
|
12856
|
+
}
|
|
12857
|
+
}
|
|
12858
|
+
const pendingApprovals = report.proposals.filter((p) => p.content);
|
|
12859
|
+
if (pendingApprovals.length > 0) {
|
|
12860
|
+
lines.push("## \uC2B9\uC778 \uB300\uAE30");
|
|
12861
|
+
lines.push("");
|
|
12862
|
+
for (const proposal of pendingApprovals) {
|
|
12863
|
+
const typeLabel = proposal.type === "agent" ? "\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8" : "\uC2A4\uD0AC";
|
|
12864
|
+
lines.push(`- [ ] ${proposal.name} ${typeLabel} \uCD94\uAC00 \u2192 \`${proposal.filePath}\``);
|
|
12865
|
+
}
|
|
12866
|
+
lines.push("");
|
|
12867
|
+
}
|
|
12868
|
+
return lines.join("\n");
|
|
12869
|
+
}
|
|
12870
|
+
/**
|
|
12871
|
+
* 보고서 파일 저장
|
|
12872
|
+
*/
|
|
12873
|
+
async writeReport(feature, report) {
|
|
12874
|
+
const featureDir = path34.join(this.sddDir, "specs", feature);
|
|
12875
|
+
if (!fs21.existsSync(featureDir)) {
|
|
12876
|
+
fs21.mkdirSync(featureDir, { recursive: true });
|
|
12877
|
+
}
|
|
12878
|
+
const filePath = path34.join(featureDir, "prepare.md");
|
|
12879
|
+
const content = this.toMarkdown(report);
|
|
12880
|
+
fs21.writeFileSync(filePath, content, "utf-8");
|
|
12881
|
+
return filePath;
|
|
12882
|
+
}
|
|
12883
|
+
};
|
|
12884
|
+
|
|
12885
|
+
// src/cli/commands/prepare.ts
|
|
12886
|
+
init_fs();
|
|
12887
|
+
init_errors();
|
|
12888
|
+
init_types();
|
|
12889
|
+
async function executePrepare(feature, options, projectRoot) {
|
|
12890
|
+
try {
|
|
12891
|
+
const docAnalyzer = new DocumentAnalyzer(projectRoot);
|
|
12892
|
+
const analyses = await docAnalyzer.analyzeFeature(feature);
|
|
12893
|
+
if (analyses.length === 0) {
|
|
12894
|
+
return failure(new Error(`\uAE30\uB2A5 '${feature}'\uC758 \uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`));
|
|
12895
|
+
}
|
|
12896
|
+
const toolDetector = new ToolDetector();
|
|
12897
|
+
const detected = toolDetector.detect(analyses);
|
|
12898
|
+
const agentScanner = new AgentScanner(projectRoot);
|
|
12899
|
+
const existingAgents = await agentScanner.scanAll();
|
|
12900
|
+
const existingAgentNames = new Set(existingAgents.map((a) => a.name));
|
|
12901
|
+
const skillScanner = new SkillScanner(projectRoot);
|
|
12902
|
+
const existingSkills = await skillScanner.scanAll();
|
|
12903
|
+
const existingSkillNames = new Set(existingSkills.map((s) => s.name));
|
|
12904
|
+
const agentChecks = detected.agents.map((agent) => ({
|
|
12905
|
+
tool: agent,
|
|
12906
|
+
status: existingAgentNames.has(agent.name) ? "exists" : "missing",
|
|
12907
|
+
filePath: agentScanner.getAgentFilePath(agent.name),
|
|
12908
|
+
action: existingAgentNames.has(agent.name) ? "-" : "\uCD94\uAC00 \uD544\uC694"
|
|
12909
|
+
}));
|
|
12910
|
+
const skillChecks = detected.skills.map((skill) => ({
|
|
12911
|
+
tool: skill,
|
|
12912
|
+
status: existingSkillNames.has(skill.name) ? "exists" : "missing",
|
|
12913
|
+
filePath: skillScanner.getSkillFilePath(skill.name),
|
|
12914
|
+
action: existingSkillNames.has(skill.name) ? "-" : "\uCD94\uAC00 \uD544\uC694"
|
|
12915
|
+
}));
|
|
12916
|
+
const agentGenerator = new AgentGenerator(projectRoot);
|
|
12917
|
+
const skillGenerator = new SkillGenerator(projectRoot);
|
|
12918
|
+
const generatedAgents = [];
|
|
12919
|
+
const generatedSkills = [];
|
|
12920
|
+
for (const check of agentChecks) {
|
|
12921
|
+
if (check.status === "missing") {
|
|
12922
|
+
generatedAgents.push(agentGenerator.generate(check.tool));
|
|
12923
|
+
}
|
|
12924
|
+
}
|
|
12925
|
+
for (const check of skillChecks) {
|
|
12926
|
+
if (check.status === "missing") {
|
|
12927
|
+
generatedSkills.push(skillGenerator.generate(check.tool));
|
|
12928
|
+
}
|
|
12929
|
+
}
|
|
12930
|
+
const reportGenerator = new PrepareReportGenerator(projectRoot);
|
|
12931
|
+
const totalTasks = DocumentAnalyzer.getTotalTaskCount(analyses);
|
|
12932
|
+
const report = reportGenerator.generate({
|
|
12933
|
+
feature,
|
|
12934
|
+
totalTasks,
|
|
12935
|
+
agentChecks,
|
|
12936
|
+
skillChecks,
|
|
12937
|
+
generatedAgents,
|
|
12938
|
+
generatedSkills
|
|
12939
|
+
});
|
|
12940
|
+
const result = {
|
|
12941
|
+
report,
|
|
12942
|
+
created: {
|
|
12943
|
+
agents: [],
|
|
12944
|
+
skills: []
|
|
12945
|
+
}
|
|
12946
|
+
};
|
|
12947
|
+
if (!options.dryRun) {
|
|
12948
|
+
result.reportPath = await reportGenerator.writeReport(feature, report);
|
|
12949
|
+
if (options.autoApprove) {
|
|
12950
|
+
for (const agent of generatedAgents) {
|
|
12951
|
+
await agentGenerator.writeAgent(agent);
|
|
12952
|
+
result.created.agents.push(agent.filePath);
|
|
12953
|
+
}
|
|
12954
|
+
for (const skill of generatedSkills) {
|
|
12955
|
+
await skillGenerator.writeSkill(skill);
|
|
12956
|
+
result.created.skills.push(skill.filePath);
|
|
12957
|
+
}
|
|
12958
|
+
}
|
|
12959
|
+
}
|
|
12960
|
+
return success(result);
|
|
12961
|
+
} catch (error2) {
|
|
12962
|
+
return failure(error2 instanceof Error ? error2 : new Error(String(error2)));
|
|
12963
|
+
}
|
|
12964
|
+
}
|
|
12965
|
+
function registerPrepareCommand(program2) {
|
|
12966
|
+
program2.command("prepare <feature>").description("\uAD6C\uD604 \uC804 \uD544\uC694\uD55C \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8\uC640 \uC2A4\uD0AC\uC744 \uC810\uAC80\uD569\uB2C8\uB2E4").option("--dry-run", "\uBCC0\uACBD \uC5C6\uC774 \uBD84\uC11D \uACB0\uACFC\uB9CC \uCD9C\uB825").option("--auto-approve", "\uB204\uB77D\uB41C \uB3C4\uAD6C\uB97C \uC790\uB3D9\uC73C\uB85C \uC0DD\uC131").option("--json", "JSON \uD615\uC2DD \uCD9C\uB825").action(async (feature, options) => {
|
|
12967
|
+
try {
|
|
12968
|
+
await runPrepare(feature, options);
|
|
12969
|
+
} catch (error2) {
|
|
12970
|
+
error(error2 instanceof Error ? error2.message : String(error2));
|
|
12971
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
12972
|
+
}
|
|
12973
|
+
});
|
|
12974
|
+
}
|
|
12975
|
+
async function runPrepare(feature, options) {
|
|
12976
|
+
const projectRoot = await findSddRoot();
|
|
12977
|
+
if (!projectRoot) {
|
|
12978
|
+
error("SDD \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. `sdd init`\uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.");
|
|
12979
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
12980
|
+
}
|
|
12981
|
+
info(`\uAE30\uB2A5 '${feature}' \uC900\uBE44 \uC810\uAC80 \uC911...`);
|
|
12982
|
+
newline();
|
|
12983
|
+
const result = await executePrepare(feature, options, projectRoot);
|
|
12984
|
+
if (!result.success) {
|
|
12985
|
+
error(result.error.message);
|
|
12986
|
+
process.exit(ExitCode.GENERAL_ERROR);
|
|
12987
|
+
}
|
|
12988
|
+
const { report, reportPath, created } = result.data;
|
|
12989
|
+
if (options.json) {
|
|
12990
|
+
console.log(JSON.stringify(report, null, 2));
|
|
12991
|
+
return;
|
|
12992
|
+
}
|
|
12993
|
+
success2("\uBD84\uC11D \uC644\uB8CC");
|
|
12994
|
+
newline();
|
|
12995
|
+
console.log(`\uCD1D \uC791\uC5C5 \uC218: ${report.totalTasks}\uAC1C`);
|
|
12996
|
+
console.log(`\uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8: ${report.agents.required}\uAC1C \uD544\uC694 (\uC874\uC7AC: ${report.agents.existing}, \uB204\uB77D: ${report.agents.missing})`);
|
|
12997
|
+
console.log(`\uC2A4\uD0AC: ${report.skills.required}\uAC1C \uD544\uC694 (\uC874\uC7AC: ${report.skills.existing}, \uB204\uB77D: ${report.skills.missing})`);
|
|
12998
|
+
newline();
|
|
12999
|
+
const missingAgents = report.agents.checks.filter((c) => c.status === "missing");
|
|
13000
|
+
const missingSkills = report.skills.checks.filter((c) => c.status === "missing");
|
|
13001
|
+
if (missingAgents.length > 0) {
|
|
13002
|
+
warn("\uB204\uB77D\uB41C \uC11C\uBE0C\uC5D0\uC774\uC804\uD2B8:");
|
|
13003
|
+
for (const check of missingAgents) {
|
|
13004
|
+
console.log(` - ${check.tool.name}: ${check.tool.description}`);
|
|
13005
|
+
}
|
|
13006
|
+
newline();
|
|
13007
|
+
}
|
|
13008
|
+
if (missingSkills.length > 0) {
|
|
13009
|
+
warn("\uB204\uB77D\uB41C \uC2A4\uD0AC:");
|
|
13010
|
+
for (const check of missingSkills) {
|
|
13011
|
+
console.log(` - ${check.tool.name}: ${check.tool.description}`);
|
|
13012
|
+
}
|
|
13013
|
+
newline();
|
|
13014
|
+
}
|
|
13015
|
+
if (created.agents.length > 0 || created.skills.length > 0) {
|
|
13016
|
+
success2("\uC0DD\uC131\uB41C \uD30C\uC77C:");
|
|
13017
|
+
for (const file of [...created.agents, ...created.skills]) {
|
|
13018
|
+
console.log(` - ${path35.relative(projectRoot, file)}`);
|
|
13019
|
+
}
|
|
13020
|
+
newline();
|
|
13021
|
+
}
|
|
13022
|
+
if (reportPath) {
|
|
13023
|
+
info(`\uBCF4\uACE0\uC11C: ${path35.relative(projectRoot, reportPath)}`);
|
|
13024
|
+
}
|
|
13025
|
+
if (missingAgents.length > 0 || missingSkills.length > 0) {
|
|
13026
|
+
if (!options.autoApprove && !options.dryRun) {
|
|
13027
|
+
newline();
|
|
13028
|
+
info("\uB204\uB77D\uB41C \uB3C4\uAD6C\uB97C \uC790\uB3D9 \uC0DD\uC131\uD558\uB824\uBA74: sdd prepare " + feature + " --auto-approve");
|
|
13029
|
+
}
|
|
13030
|
+
}
|
|
13031
|
+
}
|
|
13032
|
+
|
|
11838
13033
|
// src/cli/index.ts
|
|
11839
13034
|
var require2 = createRequire(import.meta.url);
|
|
11840
13035
|
var pkg = require2("../../package.json");
|
|
@@ -11857,6 +13052,7 @@ registerWatchCommand(program);
|
|
|
11857
13052
|
registerQualityCommand(program);
|
|
11858
13053
|
registerReportCommand(program);
|
|
11859
13054
|
registerSearchCommand(program);
|
|
13055
|
+
registerPrepareCommand(program);
|
|
11860
13056
|
function run() {
|
|
11861
13057
|
program.parse();
|
|
11862
13058
|
}
|