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