makecc 0.2.14 → 0.2.15
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.
|
@@ -67,6 +67,8 @@ export class NodeSyncService {
|
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* 엣지 연결 시 관계 업데이트
|
|
70
|
+
* - source의 downstream에 target 추가
|
|
71
|
+
* - target의 upstream에 source 추가
|
|
70
72
|
*/
|
|
71
73
|
async syncEdge(edge, nodes) {
|
|
72
74
|
try {
|
|
@@ -75,25 +77,68 @@ export class NodeSyncService {
|
|
|
75
77
|
if (!sourceNode || !targetNode) {
|
|
76
78
|
return { success: false, error: 'Source or target node not found' };
|
|
77
79
|
}
|
|
78
|
-
|
|
80
|
+
const sourceId = this.getNodeIdentifier(sourceNode);
|
|
81
|
+
const targetId = this.getNodeIdentifier(targetNode);
|
|
82
|
+
// 서브에이전트 → 스킬 연결
|
|
79
83
|
if (sourceNode.type === 'subagent' && targetNode.type === 'skill') {
|
|
84
|
+
// 에이전트의 skills 필드 업데이트
|
|
80
85
|
const skills = sourceNode.skills || [];
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
skills.push(skillId);
|
|
86
|
+
if (!skills.includes(targetId)) {
|
|
87
|
+
skills.push(targetId);
|
|
84
88
|
sourceNode.skills = skills;
|
|
85
89
|
await this.syncSubagentNode(sourceNode);
|
|
86
90
|
}
|
|
91
|
+
// 스킬의 upstream 필드 업데이트
|
|
92
|
+
const upstream = targetNode.upstream || [];
|
|
93
|
+
if (!upstream.includes(sourceId)) {
|
|
94
|
+
upstream.push(sourceId);
|
|
95
|
+
targetNode.upstream = upstream;
|
|
96
|
+
await this.syncSkillNode(targetNode);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 스킬 → 스킬 연결
|
|
100
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'skill') {
|
|
101
|
+
// source의 downstream 업데이트
|
|
102
|
+
const downstream = sourceNode.downstream || [];
|
|
103
|
+
if (!downstream.includes(targetId)) {
|
|
104
|
+
downstream.push(targetId);
|
|
105
|
+
sourceNode.downstream = downstream;
|
|
106
|
+
await this.syncSkillNode(sourceNode);
|
|
107
|
+
}
|
|
108
|
+
// target의 upstream 업데이트
|
|
109
|
+
const upstream = targetNode.upstream || [];
|
|
110
|
+
if (!upstream.includes(sourceId)) {
|
|
111
|
+
upstream.push(sourceId);
|
|
112
|
+
targetNode.upstream = upstream;
|
|
113
|
+
await this.syncSkillNode(targetNode);
|
|
114
|
+
}
|
|
87
115
|
}
|
|
88
|
-
// 스킬 → 서브에이전트
|
|
116
|
+
// 스킬 → 서브에이전트 연결
|
|
89
117
|
if (sourceNode.type === 'skill' && targetNode.type === 'subagent') {
|
|
118
|
+
// 에이전트의 upstream skills 필드 업데이트
|
|
90
119
|
const skills = targetNode.skills || [];
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
skills.push(skillId);
|
|
120
|
+
if (!skills.includes(sourceId)) {
|
|
121
|
+
skills.push(sourceId);
|
|
94
122
|
targetNode.skills = skills;
|
|
95
123
|
await this.syncSubagentNode(targetNode);
|
|
96
124
|
}
|
|
125
|
+
// 스킬의 downstream 필드 업데이트
|
|
126
|
+
const downstream = sourceNode.downstream || [];
|
|
127
|
+
if (!downstream.includes(targetId)) {
|
|
128
|
+
downstream.push(targetId);
|
|
129
|
+
sourceNode.downstream = downstream;
|
|
130
|
+
await this.syncSkillNode(sourceNode);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// 서브에이전트 → 서브에이전트 연결
|
|
134
|
+
if (sourceNode.type === 'subagent' && targetNode.type === 'subagent') {
|
|
135
|
+
// source의 downstream agents
|
|
136
|
+
const sourceDownstream = sourceNode.skills || [];
|
|
137
|
+
if (!sourceDownstream.includes(targetId)) {
|
|
138
|
+
sourceDownstream.push(targetId);
|
|
139
|
+
sourceNode.skills = sourceDownstream;
|
|
140
|
+
await this.syncSubagentNode(sourceNode);
|
|
141
|
+
}
|
|
97
142
|
}
|
|
98
143
|
return { success: true };
|
|
99
144
|
}
|
|
@@ -102,6 +147,15 @@ export class NodeSyncService {
|
|
|
102
147
|
return { success: false, error: errorMessage };
|
|
103
148
|
}
|
|
104
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* 노드의 식별자 반환 (skillId 또는 kebab-case label)
|
|
152
|
+
*/
|
|
153
|
+
getNodeIdentifier(node) {
|
|
154
|
+
if (node.type === 'skill') {
|
|
155
|
+
return node.skillId || this.toKebabCase(node.label);
|
|
156
|
+
}
|
|
157
|
+
return this.toKebabCase(node.label);
|
|
158
|
+
}
|
|
105
159
|
/**
|
|
106
160
|
* 엣지 삭제 시 관계 업데이트
|
|
107
161
|
*/
|
|
@@ -112,16 +166,35 @@ export class NodeSyncService {
|
|
|
112
166
|
if (!sourceNode || !targetNode) {
|
|
113
167
|
return { success: true }; // 노드가 없으면 무시
|
|
114
168
|
}
|
|
115
|
-
|
|
169
|
+
const sourceId = this.getNodeIdentifier(sourceNode);
|
|
170
|
+
const targetId = this.getNodeIdentifier(targetNode);
|
|
171
|
+
// 서브에이전트 → 스킬 연결 해제
|
|
116
172
|
if (sourceNode.type === 'subagent' && targetNode.type === 'skill') {
|
|
117
|
-
|
|
118
|
-
sourceNode.skills = (sourceNode.skills || []).filter(s => s !==
|
|
173
|
+
// 에이전트에서 스킬 제거
|
|
174
|
+
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
119
175
|
await this.syncSubagentNode(sourceNode);
|
|
176
|
+
// 스킬에서 upstream 제거
|
|
177
|
+
targetNode.upstream = (targetNode.upstream || []).filter(s => s !== sourceId);
|
|
178
|
+
await this.syncSkillNode(targetNode);
|
|
179
|
+
}
|
|
180
|
+
// 스킬 → 스킬 연결 해제
|
|
181
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'skill') {
|
|
182
|
+
sourceNode.downstream = (sourceNode.downstream || []).filter(s => s !== targetId);
|
|
183
|
+
await this.syncSkillNode(sourceNode);
|
|
184
|
+
targetNode.upstream = (targetNode.upstream || []).filter(s => s !== sourceId);
|
|
185
|
+
await this.syncSkillNode(targetNode);
|
|
120
186
|
}
|
|
187
|
+
// 스킬 → 서브에이전트 연결 해제
|
|
121
188
|
if (sourceNode.type === 'skill' && targetNode.type === 'subagent') {
|
|
122
|
-
|
|
123
|
-
targetNode.skills = (targetNode.skills || []).filter(s => s !== skillId);
|
|
189
|
+
targetNode.skills = (targetNode.skills || []).filter(s => s !== sourceId);
|
|
124
190
|
await this.syncSubagentNode(targetNode);
|
|
191
|
+
sourceNode.downstream = (sourceNode.downstream || []).filter(s => s !== targetId);
|
|
192
|
+
await this.syncSkillNode(sourceNode);
|
|
193
|
+
}
|
|
194
|
+
// 서브에이전트 → 서브에이전트 연결 해제
|
|
195
|
+
if (sourceNode.type === 'subagent' && targetNode.type === 'subagent') {
|
|
196
|
+
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
197
|
+
await this.syncSubagentNode(sourceNode);
|
|
125
198
|
}
|
|
126
199
|
return { success: true };
|
|
127
200
|
}
|
|
@@ -136,21 +209,35 @@ export class NodeSyncService {
|
|
|
136
209
|
const skillPath = path.join(this.projectRoot, '.claude', 'skills', skillId);
|
|
137
210
|
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
138
211
|
await fs.mkdir(skillPath, { recursive: true });
|
|
212
|
+
// Frontmatter 구성
|
|
213
|
+
const frontmatter = {
|
|
214
|
+
name: skillId,
|
|
215
|
+
description: node.description || node.label,
|
|
216
|
+
};
|
|
217
|
+
// upstream 연결 추가
|
|
218
|
+
if (node.upstream && node.upstream.length > 0) {
|
|
219
|
+
frontmatter.upstream = node.upstream.join(', ');
|
|
220
|
+
}
|
|
221
|
+
// downstream 연결 추가
|
|
222
|
+
if (node.downstream && node.downstream.length > 0) {
|
|
223
|
+
frontmatter.downstream = node.downstream.join(', ');
|
|
224
|
+
}
|
|
139
225
|
// 기존 파일이 있으면 읽어서 업데이트, 없으면 새로 생성
|
|
140
226
|
let content = '';
|
|
141
227
|
try {
|
|
142
228
|
content = await fs.readFile(skillMdPath, 'utf-8');
|
|
143
229
|
// frontmatter 업데이트
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
});
|
|
230
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
231
|
+
content = this.updateFrontmatter(content, { [key]: value });
|
|
232
|
+
}
|
|
148
233
|
}
|
|
149
234
|
catch {
|
|
150
235
|
// 새로 생성
|
|
236
|
+
const frontmatterStr = Object.entries(frontmatter)
|
|
237
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
238
|
+
.join('\n');
|
|
151
239
|
content = `---
|
|
152
|
-
|
|
153
|
-
description: ${node.description || node.label}
|
|
240
|
+
${frontmatterStr}
|
|
154
241
|
---
|
|
155
242
|
|
|
156
243
|
# ${node.label}
|
package/package.json
CHANGED
|
@@ -9,10 +9,12 @@ interface NodeData {
|
|
|
9
9
|
// Skill specific
|
|
10
10
|
skillId?: string;
|
|
11
11
|
skillPath?: string;
|
|
12
|
+
upstream?: string[]; // Connected upstream agents/skills
|
|
13
|
+
downstream?: string[]; // Connected downstream agents/skills
|
|
12
14
|
// Subagent specific
|
|
13
15
|
tools?: string[];
|
|
14
16
|
model?: string;
|
|
15
|
-
skills?: string[]; // Connected skills
|
|
17
|
+
skills?: string[]; // Connected downstream skills
|
|
16
18
|
systemPrompt?: string;
|
|
17
19
|
// Command specific
|
|
18
20
|
commandName?: string;
|
|
@@ -99,6 +101,8 @@ export class NodeSyncService {
|
|
|
99
101
|
|
|
100
102
|
/**
|
|
101
103
|
* 엣지 연결 시 관계 업데이트
|
|
104
|
+
* - source의 downstream에 target 추가
|
|
105
|
+
* - target의 upstream에 source 추가
|
|
102
106
|
*/
|
|
103
107
|
async syncEdge(edge: EdgeData, nodes: NodeData[]): Promise<{ success: boolean; error?: string }> {
|
|
104
108
|
try {
|
|
@@ -109,26 +113,75 @@ export class NodeSyncService {
|
|
|
109
113
|
return { success: false, error: 'Source or target node not found' };
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
const sourceId = this.getNodeIdentifier(sourceNode);
|
|
117
|
+
const targetId = this.getNodeIdentifier(targetNode);
|
|
118
|
+
|
|
119
|
+
// 서브에이전트 → 스킬 연결
|
|
113
120
|
if (sourceNode.type === 'subagent' && targetNode.type === 'skill') {
|
|
121
|
+
// 에이전트의 skills 필드 업데이트
|
|
114
122
|
const skills = sourceNode.skills || [];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
skills.push(skillId);
|
|
123
|
+
if (!skills.includes(targetId)) {
|
|
124
|
+
skills.push(targetId);
|
|
118
125
|
sourceNode.skills = skills;
|
|
119
126
|
await this.syncSubagentNode(sourceNode);
|
|
120
127
|
}
|
|
128
|
+
|
|
129
|
+
// 스킬의 upstream 필드 업데이트
|
|
130
|
+
const upstream = targetNode.upstream || [];
|
|
131
|
+
if (!upstream.includes(sourceId)) {
|
|
132
|
+
upstream.push(sourceId);
|
|
133
|
+
targetNode.upstream = upstream;
|
|
134
|
+
await this.syncSkillNode(targetNode);
|
|
135
|
+
}
|
|
121
136
|
}
|
|
122
137
|
|
|
123
|
-
// 스킬 →
|
|
138
|
+
// 스킬 → 스킬 연결
|
|
139
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'skill') {
|
|
140
|
+
// source의 downstream 업데이트
|
|
141
|
+
const downstream = sourceNode.downstream || [];
|
|
142
|
+
if (!downstream.includes(targetId)) {
|
|
143
|
+
downstream.push(targetId);
|
|
144
|
+
sourceNode.downstream = downstream;
|
|
145
|
+
await this.syncSkillNode(sourceNode);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// target의 upstream 업데이트
|
|
149
|
+
const upstream = targetNode.upstream || [];
|
|
150
|
+
if (!upstream.includes(sourceId)) {
|
|
151
|
+
upstream.push(sourceId);
|
|
152
|
+
targetNode.upstream = upstream;
|
|
153
|
+
await this.syncSkillNode(targetNode);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 스킬 → 서브에이전트 연결
|
|
124
158
|
if (sourceNode.type === 'skill' && targetNode.type === 'subagent') {
|
|
159
|
+
// 에이전트의 upstream skills 필드 업데이트
|
|
125
160
|
const skills = targetNode.skills || [];
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
skills.push(skillId);
|
|
161
|
+
if (!skills.includes(sourceId)) {
|
|
162
|
+
skills.push(sourceId);
|
|
129
163
|
targetNode.skills = skills;
|
|
130
164
|
await this.syncSubagentNode(targetNode);
|
|
131
165
|
}
|
|
166
|
+
|
|
167
|
+
// 스킬의 downstream 필드 업데이트
|
|
168
|
+
const downstream = sourceNode.downstream || [];
|
|
169
|
+
if (!downstream.includes(targetId)) {
|
|
170
|
+
downstream.push(targetId);
|
|
171
|
+
sourceNode.downstream = downstream;
|
|
172
|
+
await this.syncSkillNode(sourceNode);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 서브에이전트 → 서브에이전트 연결
|
|
177
|
+
if (sourceNode.type === 'subagent' && targetNode.type === 'subagent') {
|
|
178
|
+
// source의 downstream agents
|
|
179
|
+
const sourceDownstream = sourceNode.skills || [];
|
|
180
|
+
if (!sourceDownstream.includes(targetId)) {
|
|
181
|
+
sourceDownstream.push(targetId);
|
|
182
|
+
sourceNode.skills = sourceDownstream;
|
|
183
|
+
await this.syncSubagentNode(sourceNode);
|
|
184
|
+
}
|
|
132
185
|
}
|
|
133
186
|
|
|
134
187
|
return { success: true };
|
|
@@ -138,6 +191,16 @@ export class NodeSyncService {
|
|
|
138
191
|
}
|
|
139
192
|
}
|
|
140
193
|
|
|
194
|
+
/**
|
|
195
|
+
* 노드의 식별자 반환 (skillId 또는 kebab-case label)
|
|
196
|
+
*/
|
|
197
|
+
private getNodeIdentifier(node: NodeData): string {
|
|
198
|
+
if (node.type === 'skill') {
|
|
199
|
+
return node.skillId || this.toKebabCase(node.label);
|
|
200
|
+
}
|
|
201
|
+
return this.toKebabCase(node.label);
|
|
202
|
+
}
|
|
203
|
+
|
|
141
204
|
/**
|
|
142
205
|
* 엣지 삭제 시 관계 업데이트
|
|
143
206
|
*/
|
|
@@ -150,17 +213,42 @@ export class NodeSyncService {
|
|
|
150
213
|
return { success: true }; // 노드가 없으면 무시
|
|
151
214
|
}
|
|
152
215
|
|
|
153
|
-
|
|
216
|
+
const sourceId = this.getNodeIdentifier(sourceNode);
|
|
217
|
+
const targetId = this.getNodeIdentifier(targetNode);
|
|
218
|
+
|
|
219
|
+
// 서브에이전트 → 스킬 연결 해제
|
|
154
220
|
if (sourceNode.type === 'subagent' && targetNode.type === 'skill') {
|
|
155
|
-
|
|
156
|
-
sourceNode.skills = (sourceNode.skills || []).filter(s => s !==
|
|
221
|
+
// 에이전트에서 스킬 제거
|
|
222
|
+
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
157
223
|
await this.syncSubagentNode(sourceNode);
|
|
224
|
+
|
|
225
|
+
// 스킬에서 upstream 제거
|
|
226
|
+
targetNode.upstream = (targetNode.upstream || []).filter(s => s !== sourceId);
|
|
227
|
+
await this.syncSkillNode(targetNode);
|
|
158
228
|
}
|
|
159
229
|
|
|
230
|
+
// 스킬 → 스킬 연결 해제
|
|
231
|
+
if (sourceNode.type === 'skill' && targetNode.type === 'skill') {
|
|
232
|
+
sourceNode.downstream = (sourceNode.downstream || []).filter(s => s !== targetId);
|
|
233
|
+
await this.syncSkillNode(sourceNode);
|
|
234
|
+
|
|
235
|
+
targetNode.upstream = (targetNode.upstream || []).filter(s => s !== sourceId);
|
|
236
|
+
await this.syncSkillNode(targetNode);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 스킬 → 서브에이전트 연결 해제
|
|
160
240
|
if (sourceNode.type === 'skill' && targetNode.type === 'subagent') {
|
|
161
|
-
|
|
162
|
-
targetNode.skills = (targetNode.skills || []).filter(s => s !== skillId);
|
|
241
|
+
targetNode.skills = (targetNode.skills || []).filter(s => s !== sourceId);
|
|
163
242
|
await this.syncSubagentNode(targetNode);
|
|
243
|
+
|
|
244
|
+
sourceNode.downstream = (sourceNode.downstream || []).filter(s => s !== targetId);
|
|
245
|
+
await this.syncSkillNode(sourceNode);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 서브에이전트 → 서브에이전트 연결 해제
|
|
249
|
+
if (sourceNode.type === 'subagent' && targetNode.type === 'subagent') {
|
|
250
|
+
sourceNode.skills = (sourceNode.skills || []).filter(s => s !== targetId);
|
|
251
|
+
await this.syncSubagentNode(sourceNode);
|
|
164
252
|
}
|
|
165
253
|
|
|
166
254
|
return { success: true };
|
|
@@ -179,20 +267,38 @@ export class NodeSyncService {
|
|
|
179
267
|
|
|
180
268
|
await fs.mkdir(skillPath, { recursive: true });
|
|
181
269
|
|
|
270
|
+
// Frontmatter 구성
|
|
271
|
+
const frontmatter: Record<string, string> = {
|
|
272
|
+
name: skillId,
|
|
273
|
+
description: node.description || node.label,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// upstream 연결 추가
|
|
277
|
+
if (node.upstream && node.upstream.length > 0) {
|
|
278
|
+
frontmatter.upstream = node.upstream.join(', ');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// downstream 연결 추가
|
|
282
|
+
if (node.downstream && node.downstream.length > 0) {
|
|
283
|
+
frontmatter.downstream = node.downstream.join(', ');
|
|
284
|
+
}
|
|
285
|
+
|
|
182
286
|
// 기존 파일이 있으면 읽어서 업데이트, 없으면 새로 생성
|
|
183
287
|
let content = '';
|
|
184
288
|
try {
|
|
185
289
|
content = await fs.readFile(skillMdPath, 'utf-8');
|
|
186
290
|
// frontmatter 업데이트
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
291
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
292
|
+
content = this.updateFrontmatter(content, { [key]: value });
|
|
293
|
+
}
|
|
191
294
|
} catch {
|
|
192
295
|
// 새로 생성
|
|
296
|
+
const frontmatterStr = Object.entries(frontmatter)
|
|
297
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
298
|
+
.join('\n');
|
|
299
|
+
|
|
193
300
|
content = `---
|
|
194
|
-
|
|
195
|
-
description: ${node.description || node.label}
|
|
301
|
+
${frontmatterStr}
|
|
196
302
|
---
|
|
197
303
|
|
|
198
304
|
# ${node.label}
|