@team-semicolon/semo-cli 4.13.0 → 4.15.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/dist/commands/context.js +23 -3
- package/dist/commands/harness.d.ts +8 -0
- package/dist/commands/harness.js +412 -0
- package/dist/commands/incubator.d.ts +10 -0
- package/dist/commands/incubator.js +517 -0
- package/dist/commands/service.js +144 -3
- package/dist/commands/sessions.js +156 -0
- package/dist/commands/skill-sync.d.ts +2 -1
- package/dist/commands/skill-sync.js +88 -23
- package/dist/commands/skill-sync.test.js +78 -45
- package/dist/database.d.ts +2 -1
- package/dist/database.js +109 -27
- package/dist/global-cache.js +73 -30
- package/dist/index.js +576 -522
- package/dist/kb.d.ts +4 -4
- package/dist/kb.js +203 -103
- package/dist/semo-workspace.js +51 -0
- package/dist/service-migrate.d.ts +47 -2
- package/dist/service-migrate.js +188 -13
- package/dist/templates/harness/commit-msg +1 -0
- package/dist/templates/harness/commitlint.config.js +11 -0
- package/dist/templates/harness/eslint.config.mjs +17 -0
- package/dist/templates/harness/pr-quality-gate.yml +19 -0
- package/dist/templates/harness/pre-commit +1 -0
- package/dist/templates/harness/pre-push +1 -0
- package/dist/templates/harness/prettierignore +5 -0
- package/dist/templates/harness/prettierrc.json +7 -0
- package/package.json +8 -4
package/dist/database.js
CHANGED
|
@@ -69,11 +69,11 @@ const env_parser_1 = require("./env-parser");
|
|
|
69
69
|
// 인터랙티브 쉘이 아닌 환경에서 환경변수를 공급한다.
|
|
70
70
|
// 이미 설정된 환경변수는 덮어쓰지 않는다 (env var > file).
|
|
71
71
|
function loadSemoEnv() {
|
|
72
|
-
const envFile = path.join(os.homedir(),
|
|
72
|
+
const envFile = path.join(os.homedir(), '.claude', 'semo', '.env');
|
|
73
73
|
if (!fs.existsSync(envFile))
|
|
74
74
|
return;
|
|
75
75
|
try {
|
|
76
|
-
const creds = (0, env_parser_1.parseEnvContent)(fs.readFileSync(envFile,
|
|
76
|
+
const creds = (0, env_parser_1.parseEnvContent)(fs.readFileSync(envFile, 'utf8'));
|
|
77
77
|
for (const [key, val] of Object.entries(creds)) {
|
|
78
78
|
if (!process.env[key])
|
|
79
79
|
process.env[key] = val;
|
|
@@ -91,20 +91,22 @@ function buildDbConfig() {
|
|
|
91
91
|
if (process.env.DATABASE_URL) {
|
|
92
92
|
return {
|
|
93
93
|
connectionString: process.env.DATABASE_URL,
|
|
94
|
-
ssl: process.env.DATABASE_URL.includes(
|
|
94
|
+
ssl: process.env.DATABASE_URL.includes('sslmode=require')
|
|
95
|
+
? { rejectUnauthorized: false }
|
|
96
|
+
: false,
|
|
95
97
|
connectionTimeoutMillis: 5000,
|
|
96
98
|
idleTimeoutMillis: 30000,
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
if (!process.env.SEMO_DB_HOST && !process.env.DATABASE_URL) {
|
|
100
|
-
throw new Error(
|
|
102
|
+
throw new Error('DB 연결 정보가 없습니다. DATABASE_URL 또는 SEMO_DB_HOST 환경변수를 설정하세요.');
|
|
101
103
|
}
|
|
102
104
|
return {
|
|
103
105
|
host: process.env.SEMO_DB_HOST,
|
|
104
|
-
port: parseInt(process.env.SEMO_DB_PORT ||
|
|
105
|
-
user: process.env.SEMO_DB_USER ||
|
|
106
|
+
port: parseInt(process.env.SEMO_DB_PORT || '5432'),
|
|
107
|
+
user: process.env.SEMO_DB_USER || 'app',
|
|
106
108
|
password: process.env.SEMO_DB_PASSWORD,
|
|
107
|
-
database: process.env.SEMO_DB_NAME ||
|
|
109
|
+
database: process.env.SEMO_DB_NAME || 'appdb',
|
|
108
110
|
ssl: false,
|
|
109
111
|
connectionTimeoutMillis: 5000,
|
|
110
112
|
idleTimeoutMillis: 30000,
|
|
@@ -125,7 +127,7 @@ async function checkDbConnection() {
|
|
|
125
127
|
return dbAvailable;
|
|
126
128
|
try {
|
|
127
129
|
const client = await getPool().connect();
|
|
128
|
-
await client.query(
|
|
130
|
+
await client.query('SELECT 1');
|
|
129
131
|
client.release();
|
|
130
132
|
dbAvailable = true;
|
|
131
133
|
}
|
|
@@ -140,17 +142,95 @@ async function checkDbConnection() {
|
|
|
140
142
|
const FALLBACK_SKILLS = [];
|
|
141
143
|
const FALLBACK_COMMANDS = [];
|
|
142
144
|
const FALLBACK_AGENTS = [
|
|
143
|
-
{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
{
|
|
146
|
+
id: 'agent-semiclaw',
|
|
147
|
+
name: 'semiclaw',
|
|
148
|
+
display_name: 'SemiClaw 🦀',
|
|
149
|
+
content: '',
|
|
150
|
+
package: 'openclaw',
|
|
151
|
+
is_active: true,
|
|
152
|
+
install_order: 1,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'agent-workclaw',
|
|
156
|
+
name: 'workclaw',
|
|
157
|
+
display_name: 'WorkClaw 🛠️',
|
|
158
|
+
content: '',
|
|
159
|
+
package: 'openclaw',
|
|
160
|
+
is_active: true,
|
|
161
|
+
install_order: 2,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'agent-reviewclaw',
|
|
165
|
+
name: 'reviewclaw',
|
|
166
|
+
display_name: 'ReviewClaw 🔍',
|
|
167
|
+
content: '',
|
|
168
|
+
package: 'openclaw',
|
|
169
|
+
is_active: true,
|
|
170
|
+
install_order: 3,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'agent-planclaw',
|
|
174
|
+
name: 'planclaw',
|
|
175
|
+
display_name: 'PlanClaw 🗓️',
|
|
176
|
+
content: '',
|
|
177
|
+
package: 'openclaw',
|
|
178
|
+
is_active: true,
|
|
179
|
+
install_order: 4,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'agent-designclaw',
|
|
183
|
+
name: 'designclaw',
|
|
184
|
+
display_name: 'DesignClaw 🎨',
|
|
185
|
+
content: '',
|
|
186
|
+
package: 'openclaw',
|
|
187
|
+
is_active: true,
|
|
188
|
+
install_order: 5,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'agent-infraclaw',
|
|
192
|
+
name: 'infraclaw',
|
|
193
|
+
display_name: 'InfraClaw 🏗️',
|
|
194
|
+
content: '',
|
|
195
|
+
package: 'openclaw',
|
|
196
|
+
is_active: true,
|
|
197
|
+
install_order: 6,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'agent-growthclaw',
|
|
201
|
+
name: 'growthclaw',
|
|
202
|
+
display_name: 'GrowthClaw 🌱',
|
|
203
|
+
content: '',
|
|
204
|
+
package: 'openclaw',
|
|
205
|
+
is_active: true,
|
|
206
|
+
install_order: 7,
|
|
207
|
+
},
|
|
150
208
|
];
|
|
151
209
|
const FALLBACK_PACKAGES = [
|
|
152
|
-
{
|
|
153
|
-
|
|
210
|
+
{
|
|
211
|
+
id: 'pkg-cli',
|
|
212
|
+
name: 'semo-cli',
|
|
213
|
+
display_name: 'SEMO CLI',
|
|
214
|
+
description: 'CLI 도구 (컨텍스트 동기화, 봇 관리)',
|
|
215
|
+
layer: 'standard',
|
|
216
|
+
package_type: 'standard',
|
|
217
|
+
version: '4.2.0',
|
|
218
|
+
is_active: true,
|
|
219
|
+
is_required: true,
|
|
220
|
+
install_order: 10,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'pkg-dashboard',
|
|
224
|
+
name: 'semo-dashboard',
|
|
225
|
+
display_name: 'SEMO Dashboard',
|
|
226
|
+
description: '봇 상태/KB 대시보드',
|
|
227
|
+
layer: 'standard',
|
|
228
|
+
package_type: 'standard',
|
|
229
|
+
version: '0.1.0',
|
|
230
|
+
is_active: true,
|
|
231
|
+
is_required: false,
|
|
232
|
+
install_order: 30,
|
|
233
|
+
},
|
|
154
234
|
];
|
|
155
235
|
// ============================================================
|
|
156
236
|
// DB 조회 함수
|
|
@@ -162,7 +242,7 @@ const FALLBACK_PACKAGES = [
|
|
|
162
242
|
async function getActiveSkills() {
|
|
163
243
|
const isConnected = await checkDbConnection();
|
|
164
244
|
if (!isConnected) {
|
|
165
|
-
console.warn(
|
|
245
|
+
console.warn('⚠️ DB 연결 실패, 폴백 스킬 목록 사용');
|
|
166
246
|
return FALLBACK_SKILLS.filter((s) => s.is_active);
|
|
167
247
|
}
|
|
168
248
|
try {
|
|
@@ -173,6 +253,7 @@ async function getActiveSkills() {
|
|
|
173
253
|
ARRAY(SELECT jsonb_array_elements_text(metadata->'bot_ids')),
|
|
174
254
|
ARRAY[]::text[]
|
|
175
255
|
) AS bot_ids,
|
|
256
|
+
metadata->'reference_files' AS reference_files,
|
|
176
257
|
category, package, is_active, is_required, install_order, version
|
|
177
258
|
FROM semo.skill_definitions
|
|
178
259
|
WHERE is_active = true AND office_id IS NULL
|
|
@@ -181,7 +262,7 @@ async function getActiveSkills() {
|
|
|
181
262
|
return result.rows;
|
|
182
263
|
}
|
|
183
264
|
catch (error) {
|
|
184
|
-
console.warn(
|
|
265
|
+
console.warn('⚠️ 스킬 조회 실패, 폴백 데이터 사용:', error);
|
|
185
266
|
return FALLBACK_SKILLS.filter((s) => s.is_active);
|
|
186
267
|
}
|
|
187
268
|
}
|
|
@@ -199,7 +280,7 @@ async function getActiveSkillNames() {
|
|
|
199
280
|
async function getActiveSkillsForBot(botId) {
|
|
200
281
|
const isConnected = await checkDbConnection();
|
|
201
282
|
if (!isConnected) {
|
|
202
|
-
console.warn(
|
|
283
|
+
console.warn('⚠️ DB 연결 실패, 폴백 스킬 목록 사용');
|
|
203
284
|
return FALLBACK_SKILLS.filter((s) => s.is_active);
|
|
204
285
|
}
|
|
205
286
|
try {
|
|
@@ -209,6 +290,7 @@ async function getActiveSkillsForBot(botId) {
|
|
|
209
290
|
ARRAY(SELECT jsonb_array_elements_text(sd.metadata->'bot_ids')),
|
|
210
291
|
ARRAY[]::text[]
|
|
211
292
|
) AS bot_ids,
|
|
293
|
+
sd.metadata->'reference_files' AS reference_files,
|
|
212
294
|
sd.category, sd.package, sd.is_active, sd.is_required,
|
|
213
295
|
sd.install_order, sd.version
|
|
214
296
|
FROM semo.skill_definitions sd
|
|
@@ -221,7 +303,7 @@ async function getActiveSkillsForBot(botId) {
|
|
|
221
303
|
return result.rows;
|
|
222
304
|
}
|
|
223
305
|
catch (error) {
|
|
224
|
-
console.warn(
|
|
306
|
+
console.warn('⚠️ 봇 스킬 조회 실패, 폴백 데이터 사용:', error);
|
|
225
307
|
return FALLBACK_SKILLS.filter((s) => s.is_active);
|
|
226
308
|
}
|
|
227
309
|
}
|
|
@@ -232,7 +314,7 @@ async function getActiveSkillsForBot(botId) {
|
|
|
232
314
|
async function getCommands() {
|
|
233
315
|
const isConnected = await checkDbConnection();
|
|
234
316
|
if (!isConnected) {
|
|
235
|
-
console.warn(
|
|
317
|
+
console.warn('⚠️ DB 연결 실패, 폴백 커맨드 목록 사용');
|
|
236
318
|
return FALLBACK_COMMANDS.filter((c) => c.is_active);
|
|
237
319
|
}
|
|
238
320
|
try {
|
|
@@ -246,7 +328,7 @@ async function getCommands() {
|
|
|
246
328
|
return result.rows;
|
|
247
329
|
}
|
|
248
330
|
catch (error) {
|
|
249
|
-
console.warn(
|
|
331
|
+
console.warn('⚠️ 커맨드 조회 실패, 폴백 데이터 사용:', error);
|
|
250
332
|
return FALLBACK_COMMANDS.filter((c) => c.is_active);
|
|
251
333
|
}
|
|
252
334
|
}
|
|
@@ -257,7 +339,7 @@ async function getCommands() {
|
|
|
257
339
|
async function getAgents() {
|
|
258
340
|
const isConnected = await checkDbConnection();
|
|
259
341
|
if (!isConnected) {
|
|
260
|
-
console.warn(
|
|
342
|
+
console.warn('⚠️ DB 연결 실패, 폴백 에이전트 목록 사용');
|
|
261
343
|
return FALLBACK_AGENTS.filter((a) => a.is_active);
|
|
262
344
|
}
|
|
263
345
|
try {
|
|
@@ -273,7 +355,7 @@ async function getAgents() {
|
|
|
273
355
|
return result.rows;
|
|
274
356
|
}
|
|
275
357
|
catch (error) {
|
|
276
|
-
console.warn(
|
|
358
|
+
console.warn('⚠️ 에이전트 조회 실패, 폴백 데이터 사용:', error);
|
|
277
359
|
return FALLBACK_AGENTS.filter((a) => a.is_active);
|
|
278
360
|
}
|
|
279
361
|
}
|
|
@@ -283,7 +365,7 @@ async function getAgents() {
|
|
|
283
365
|
async function getPackages(layer) {
|
|
284
366
|
const isConnected = await checkDbConnection();
|
|
285
367
|
if (!isConnected) {
|
|
286
|
-
console.warn(
|
|
368
|
+
console.warn('⚠️ DB 연결 실패, 폴백 패키지 목록 사용');
|
|
287
369
|
const fallback = FALLBACK_PACKAGES.filter((p) => p.is_active);
|
|
288
370
|
return layer ? fallback.filter((p) => p.layer === layer) : fallback;
|
|
289
371
|
}
|
|
@@ -304,7 +386,7 @@ async function getPackages(layer) {
|
|
|
304
386
|
return result.rows;
|
|
305
387
|
}
|
|
306
388
|
catch (error) {
|
|
307
|
-
console.warn(
|
|
389
|
+
console.warn('⚠️ 패키지 조회 실패, 폴백 데이터 사용:', error);
|
|
308
390
|
const fallback = FALLBACK_PACKAGES.filter((p) => p.is_active);
|
|
309
391
|
return layer ? fallback.filter((p) => p.layer === layer) : fallback;
|
|
310
392
|
}
|
package/dist/global-cache.js
CHANGED
|
@@ -45,7 +45,7 @@ const path = __importStar(require("path"));
|
|
|
45
45
|
const os = __importStar(require("os"));
|
|
46
46
|
const child_process_1 = require("child_process");
|
|
47
47
|
const database_1 = require("./database");
|
|
48
|
-
const isWindows = process.platform ===
|
|
48
|
+
const isWindows = process.platform === 'win32';
|
|
49
49
|
function removeRecursive(targetPath) {
|
|
50
50
|
if (!fs.existsSync(targetPath))
|
|
51
51
|
return;
|
|
@@ -53,10 +53,10 @@ function removeRecursive(targetPath) {
|
|
|
53
53
|
try {
|
|
54
54
|
const stats = fs.lstatSync(targetPath);
|
|
55
55
|
if (stats.isSymbolicLink()) {
|
|
56
|
-
(0, child_process_1.execSync)(`cmd /c "rmdir "${targetPath}""`, { stdio:
|
|
56
|
+
(0, child_process_1.execSync)(`cmd /c "rmdir "${targetPath}""`, { stdio: 'pipe' });
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
|
-
(0, child_process_1.execSync)(`cmd /c "rd /s /q "${targetPath}""`, { stdio:
|
|
59
|
+
(0, child_process_1.execSync)(`cmd /c "rd /s /q "${targetPath}""`, { stdio: 'pipe' });
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
catch {
|
|
@@ -64,7 +64,7 @@ function removeRecursive(targetPath) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
else {
|
|
67
|
-
(0, child_process_1.execSync)(`rm -rf "${targetPath}"`, { stdio:
|
|
67
|
+
(0, child_process_1.execSync)(`rm -rf "${targetPath}"`, { stdio: 'pipe' });
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
@@ -90,9 +90,7 @@ function injectAgentInfo(content, botIds) {
|
|
|
90
90
|
const absDescStart = 4 + descIdx;
|
|
91
91
|
const lineEnd = content.indexOf('\n', absDescStart);
|
|
92
92
|
if (lineEnd !== -1) {
|
|
93
|
-
return (content.slice(0, lineEnd) +
|
|
94
|
-
`\n Agents: ${botIds.join(', ')}` +
|
|
95
|
-
content.slice(lineEnd));
|
|
93
|
+
return (content.slice(0, lineEnd) + `\n Agents: ${botIds.join(', ')}` + content.slice(lineEnd));
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
96
|
}
|
|
@@ -104,7 +102,7 @@ function injectAgentInfo(content, botIds) {
|
|
|
104
102
|
return content + agentLine;
|
|
105
103
|
}
|
|
106
104
|
async function syncGlobalCache(claudeDir) {
|
|
107
|
-
const dir = claudeDir || path.join(os.homedir(),
|
|
105
|
+
const dir = claudeDir || path.join(os.homedir(), '.claude');
|
|
108
106
|
fs.mkdirSync(dir, { recursive: true });
|
|
109
107
|
// 병렬 조회
|
|
110
108
|
const [skills, commands, agents, delegations] = await Promise.all([
|
|
@@ -114,7 +112,7 @@ async function syncGlobalCache(claudeDir) {
|
|
|
114
112
|
(0, database_1.getDelegations)(),
|
|
115
113
|
]);
|
|
116
114
|
// 1. 스킬 설치 (전체 교체)
|
|
117
|
-
const skillsDir = path.join(dir,
|
|
115
|
+
const skillsDir = path.join(dir, 'skills');
|
|
118
116
|
removeRecursive(skillsDir);
|
|
119
117
|
fs.mkdirSync(skillsDir, { recursive: true });
|
|
120
118
|
let skippedSkills = 0;
|
|
@@ -124,13 +122,27 @@ async function syncGlobalCache(claudeDir) {
|
|
|
124
122
|
skippedSkills++;
|
|
125
123
|
continue;
|
|
126
124
|
}
|
|
125
|
+
if (!skill.content)
|
|
126
|
+
continue; // skip skills with null/empty content
|
|
127
127
|
const skillFolder = path.join(skillsDir, skill.name);
|
|
128
128
|
fs.mkdirSync(skillFolder, { recursive: true });
|
|
129
129
|
const finalContent = injectAgentInfo(skill.content, skill.bot_ids);
|
|
130
|
-
fs.writeFileSync(path.join(skillFolder,
|
|
130
|
+
fs.writeFileSync(path.join(skillFolder, 'SKILL.md'), finalContent);
|
|
131
|
+
// Write reference files if present
|
|
132
|
+
if (skill.reference_files &&
|
|
133
|
+
typeof skill.reference_files === 'object' &&
|
|
134
|
+
Object.keys(skill.reference_files).length > 0) {
|
|
135
|
+
const refsDir = path.join(skillFolder, 'references');
|
|
136
|
+
fs.mkdirSync(refsDir, { recursive: true });
|
|
137
|
+
for (const [filename, refContent] of Object.entries(skill.reference_files)) {
|
|
138
|
+
if (typeof refContent === 'string') {
|
|
139
|
+
fs.writeFileSync(path.join(refsDir, filename), refContent);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
131
143
|
}
|
|
132
144
|
// 2. 커맨드 설치 (전체 교체)
|
|
133
|
-
const commandsDir = path.join(dir,
|
|
145
|
+
const commandsDir = path.join(dir, 'commands');
|
|
134
146
|
removeRecursive(commandsDir);
|
|
135
147
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
136
148
|
const commandsByFolder = {};
|
|
@@ -149,9 +161,8 @@ async function syncGlobalCache(claudeDir) {
|
|
|
149
161
|
cmdCount++;
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
|
-
// 3. 에이전트 설치 (
|
|
153
|
-
const agentsDir = path.join(dir,
|
|
154
|
-
removeRecursive(agentsDir);
|
|
164
|
+
// 3. 에이전트 설치 (머지 모드 — 로컬 YAML frontmatter 보존)
|
|
165
|
+
const agentsDir = path.join(dir, 'agents');
|
|
155
166
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
156
167
|
const seenAgentNames = new Set();
|
|
157
168
|
const dedupedAgents = [];
|
|
@@ -162,30 +173,62 @@ async function syncGlobalCache(claudeDir) {
|
|
|
162
173
|
dedupedAgents.push(agent);
|
|
163
174
|
}
|
|
164
175
|
}
|
|
176
|
+
// 기존 로컬 파일의 frontmatter 캐싱 (덮어쓰기 전)
|
|
177
|
+
const existingFrontmatters = new Map();
|
|
178
|
+
for (const folder of fs.readdirSync(agentsDir).filter((f) => !f.startsWith('.'))) {
|
|
179
|
+
const filePath = path.join(agentsDir, folder, `${folder}.md`);
|
|
180
|
+
if (fs.existsSync(filePath)) {
|
|
181
|
+
const existing = fs.readFileSync(filePath, 'utf8');
|
|
182
|
+
const fmMatch = existing.match(/^---\n([\s\S]*?)\n---\n/);
|
|
183
|
+
if (fmMatch) {
|
|
184
|
+
existingFrontmatters.set(folder.toLowerCase(), fmMatch[1]);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// DB에 없는 에이전트 폴더 정리 (orphan 제거)
|
|
189
|
+
const dbAgentNames = new Set(dedupedAgents.map((a) => a.name.toLowerCase()));
|
|
190
|
+
for (const folder of fs.readdirSync(agentsDir).filter((f) => !f.startsWith('.'))) {
|
|
191
|
+
const folderPath = path.join(agentsDir, folder);
|
|
192
|
+
if (!dbAgentNames.has(folder.toLowerCase()) && fs.statSync(folderPath).isDirectory()) {
|
|
193
|
+
removeRecursive(folderPath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
165
196
|
for (const agent of dedupedAgents) {
|
|
166
197
|
const agentFolder = path.join(agentsDir, agent.name);
|
|
167
198
|
fs.mkdirSync(agentFolder, { recursive: true });
|
|
168
|
-
|
|
199
|
+
// DB content에서 frontmatter 제거 (body만 추출)
|
|
200
|
+
let dbBody = agent.content;
|
|
201
|
+
const dbFmMatch = agent.content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
202
|
+
if (dbFmMatch) {
|
|
203
|
+
dbBody = dbFmMatch[1].trim();
|
|
204
|
+
}
|
|
169
205
|
// 위임 매트릭스 주입
|
|
170
206
|
const agentDelegations = delegations.filter((d) => d.from_bot_id === agent.name);
|
|
171
207
|
if (agentDelegations.length > 0) {
|
|
172
208
|
const delegationLines = agentDelegations
|
|
173
|
-
.map((d) => `- → ${d.to_bot_id}: ${d.domains.join(
|
|
174
|
-
.join(
|
|
175
|
-
|
|
209
|
+
.map((d) => `- → ${d.to_bot_id}: ${d.domains.join(', ')} (via ${d.method})`)
|
|
210
|
+
.join('\n');
|
|
211
|
+
dbBody += `\n\n## 위임 매트릭스\n${delegationLines}\n`;
|
|
176
212
|
}
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
// 로컬 frontmatter 보존 (있으면) → DB body와 합침
|
|
214
|
+
const localFm = existingFrontmatters.get(agent.name.toLowerCase());
|
|
215
|
+
let content;
|
|
216
|
+
if (localFm) {
|
|
217
|
+
// 로컬 frontmatter 우선 보존 + DB body 업데이트
|
|
218
|
+
content = `---\n${localFm}\n---\n${dbBody}`;
|
|
219
|
+
}
|
|
220
|
+
else if (agent.metadata && (agent.metadata.model || agent.metadata.description)) {
|
|
221
|
+
// 로컬 frontmatter 없으면 metadata에서 생성
|
|
222
|
+
const fm = ['---'];
|
|
223
|
+
if (agent.metadata.description)
|
|
224
|
+
fm.push(`description: "${agent.metadata.description}"`);
|
|
225
|
+
if (agent.metadata.model)
|
|
226
|
+
fm.push(`model: "${agent.metadata.model}"`);
|
|
227
|
+
fm.push('---', '');
|
|
228
|
+
content = fm.join('\n') + dbBody;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
content = dbBody;
|
|
189
232
|
}
|
|
190
233
|
fs.writeFileSync(path.join(agentFolder, `${agent.name}.md`), content);
|
|
191
234
|
}
|