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/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, path29) {
132
- super(code, formatMessage(code, path29), ExitCode.FILE_SYSTEM_ERROR);
131
+ constructor(code, path36) {
132
+ super(code, formatMessage(code, path36), ExitCode.FILE_SYSTEM_ERROR);
133
133
  this.name = "FileSystemError";
134
- this.path = path29;
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\uC5D0 \uD544\uC694\uD55C \uD658\uACBD\uC744 \uC900\uBE44\uD569\uB2C8\uB2E4.
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
- \uC0C8\uB85C\uC6B4 \uAE30\uB2A5\uC744 \uAD6C\uD604\uD558\uAE30 \uC804\uC5D0 \uD544\uC694\uD55C \uB3C4\uAD6C, \uC758\uC874\uC131, \uC124\uC815\uC744 \uBD84\uC11D\uD558\uACE0 \uC900\uBE44\uD569\uB2C8\uB2E4.
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. \uC2A4\uD399\uC744 \uBD84\uC11D\uD558\uC5EC \uD544\uC694\uD55C \uAE30\uC220\uC744 \uD30C\uC545\uD558\uC138\uC694
2144
- 2. \uD544\uC694\uD55C \uC758\uC874\uC131\uC744 \uB098\uC5F4\uD558\uC138\uC694
2145
- 3. \uD544\uC694\uD55C MCP \uC11C\uBC84\uAC00 \uC788\uB2E4\uBA74 \uD655\uC778\uD558\uC138\uC694
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
- ## \uC900\uBE44 \uCCB4\uD06C\uB9AC\uC2A4\uD2B8
2148
+ ## \uC6CC\uD06C\uD50C\uB85C\uC6B0
2149
2149
 
2150
- ### 1. \uC758\uC874\uC131 \uD655\uC778
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
- # \uD544\uC694\uD55C npm \uD328\uD0A4\uC9C0 \uD655\uC778
2154
- npm list [package-name]
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
- # \uC124\uCE58 \uD544\uC694\uC2DC
2157
- npm install [package-name]
2166
+ # JSON \uCD9C\uB825
2167
+ sdd prepare user-auth --json
2158
2168
  \`\`\`
2159
2169
 
2160
- ### 2. \uD658\uACBD \uC124\uC815
2170
+ ## \uAC10\uC9C0 \uB300\uC0C1
2161
2171
 
2162
- - [ ] \uD658\uACBD \uBCC0\uC218 \uD655\uC778 (.env)
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
- ### 3. MCP \uC11C\uBC84 (\uD544\uC694\uC2DC)
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
- \uD544\uC694\uD55C MCP \uC11C\uBC84 \uBAA9\uB85D:
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
- ### 4. AGENTS.md \uC5C5\uB370\uC774\uD2B8
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
- \uAE30\uB2A5 \uAD6C\uD604\uC5D0 \uD544\uC694\uD55C \uC9C0\uCE68\uC744 AGENTS.md\uC5D0 \uCD94\uAC00:
2191
+ ## \uCD9C\uB825 \uC608\uC2DC
2176
2192
 
2177
- \`\`\`markdown
2178
- ## [\uAE30\uB2A5\uBA85] \uAD6C\uD604 \uC9C0\uCE68
2193
+ \`\`\`
2194
+ === SDD Prepare: user-auth ===
2179
2195
 
2180
- ### \uC0AC\uC6A9 \uAE30\uC220
2181
- - ...
2196
+ \uBD84\uC11D \uB300\uC0C1: 3\uAC1C \uBB38\uC11C, 5\uAC1C \uD0DC\uC2A4\uD06C
2182
2197
 
2183
- ### \uAD6C\uD604 \uADDC\uCE59
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
- ### \uCC38\uACE0 \uC790\uB8CC
2187
- - ...
2188
- \`\`\`
2202
+ --- \uC2A4\uD0AC ---
2203
+ [x] test (\uC874\uC7AC)
2204
+ [ ] gen-api (\uC5C6\uC74C) \u2192 \uC0DD\uC131 \uD544\uC694
2189
2205
 
2190
- ## \uBA85\uB839\uC5B4
2206
+ \uB204\uB77D\uB41C \uB3C4\uAD6C\uB97C \uC0DD\uC131\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/n)
2207
+ \`\`\`
2191
2208
 
2192
- \`\`\`bash
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: fs16 } = await import("fs");
3959
+ const { promises: fs22 } = await import("fs");
3938
3960
  try {
3939
- const entries = await fs16.readdir(dir, { withFileTypes: true });
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, path29) {
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, [...path29, nodeId])) {
6091
+ if (dfs(depId, [...path36, nodeId])) {
6070
6092
  return true;
6071
6093
  }
6072
6094
  } else if (recStack.has(depId)) {
6073
- const cycleStart = path29.indexOf(depId);
6074
- const cycle = cycleStart >= 0 ? [...path29.slice(cycleStart), nodeId, depId] : [nodeId, depId];
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
  }