@team-semicolon/semo-cli 4.3.0 → 4.4.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/audit.d.ts +12 -4
- package/dist/commands/audit.js +219 -27
- package/dist/commands/bots.js +60 -37
- package/dist/commands/context.d.ts +2 -2
- package/dist/commands/context.js +15 -52
- package/dist/commands/get.js +31 -17
- package/dist/commands/memory.js +10 -8
- package/dist/commands/skill-sync.js +1 -1
- package/dist/commands/test.d.ts +11 -0
- package/dist/commands/test.js +520 -0
- package/dist/database.js +2 -2
- package/dist/index.js +335 -104
- package/dist/kb.d.ts +69 -0
- package/dist/kb.js +265 -16
- package/dist/slack-notify.d.ts +8 -0
- package/dist/slack-notify.js +45 -0
- package/dist/test-runners/workspace-audit.d.ts +17 -0
- package/dist/test-runners/workspace-audit.js +366 -0
- package/package.json +1 -1
package/dist/commands/context.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* semo context — 스킬/캐시/크론잡 동기화
|
|
4
4
|
*
|
|
5
5
|
* sync: DB → 글로벌 캐시 (skills/commands/agents) + 스킬 DB 동기화 + 크론잡
|
|
6
|
-
* push: .claude/memory/<domain>.md → DB (deprecated —
|
|
6
|
+
* push: .claude/memory/<domain>.md → DB (deprecated — semo kb upsert로 대체)
|
|
7
7
|
*
|
|
8
|
-
* [v4.2.0] KB→md 파일 생성 제거 — semo
|
|
8
|
+
* [v4.2.0] KB→md 파일 생성 제거 — semo CLI kb 명령어로 통일
|
|
9
9
|
*/
|
|
10
10
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
11
|
if (k2 === undefined) k2 = k;
|
|
@@ -53,7 +53,7 @@ const path = __importStar(require("path"));
|
|
|
53
53
|
const os = __importStar(require("os"));
|
|
54
54
|
const database_1 = require("../database");
|
|
55
55
|
const kb_1 = require("../kb");
|
|
56
|
-
|
|
56
|
+
// [v4.4.0] syncSkillsToDB 제거 — semo-system/ 폐기됨, 스킬 SoT는 DB 직접 관리
|
|
57
57
|
const global_cache_1 = require("../global-cache");
|
|
58
58
|
// ============================================================
|
|
59
59
|
// Memory file mapping
|
|
@@ -68,7 +68,9 @@ function resolveMemoryDir(outDir) {
|
|
|
68
68
|
}
|
|
69
69
|
return path.join(require("os").homedir(), MEMORY_DIR);
|
|
70
70
|
}
|
|
71
|
+
/** @deprecated context push uses legacy flat domains — prefer semo kb upsert */
|
|
71
72
|
const KB_DOMAIN_MAP = {
|
|
73
|
+
semicolon: "semicolon.md",
|
|
72
74
|
team: "team.md",
|
|
73
75
|
project: "projects.md",
|
|
74
76
|
decision: "decisions.md",
|
|
@@ -179,18 +181,18 @@ function parseMarkdownSections(content, domain) {
|
|
|
179
181
|
}
|
|
180
182
|
return entries;
|
|
181
183
|
}
|
|
182
|
-
// [v4.2.0] digestToMarkdown 제거 —
|
|
184
|
+
// [v4.2.0] digestToMarkdown 제거 — semo CLI로 대체
|
|
183
185
|
// ============================================================
|
|
184
186
|
// Commands
|
|
185
187
|
// ============================================================
|
|
186
188
|
function registerContextCommands(program) {
|
|
187
189
|
const ctxCmd = program
|
|
188
190
|
.command("context")
|
|
189
|
-
.description("스킬/캐시/크론잡 동기화 (KB는 semo
|
|
191
|
+
.description("스킬/캐시/크론잡 동기화 (KB는 semo CLI)");
|
|
190
192
|
// ── semo context sync ──────────────────────────────────────
|
|
191
193
|
ctxCmd
|
|
192
194
|
.command("sync")
|
|
193
|
-
.description("스킬/에이전트/캐시 동기화 + 크론잡 (KB는 semo
|
|
195
|
+
.description("스킬/에이전트/캐시 동기화 + 크론잡 (KB는 semo CLI 사용)")
|
|
194
196
|
.option("--no-skills", "스킬 파일 → DB 동기화 건너뜀")
|
|
195
197
|
.option("--out-dir <path>", "캐시 파일 출력 경로 (기본: .claude/memory/)")
|
|
196
198
|
.option("--no-global-cache", "글로벌 캐시(skills/commands/agents) 동기화 건너뜀")
|
|
@@ -205,30 +207,12 @@ function registerContextCommands(program) {
|
|
|
205
207
|
const pool = (0, database_1.getPool)();
|
|
206
208
|
const memDir = ensureMemoryDir(resolveMemoryDir(options.outDir));
|
|
207
209
|
try {
|
|
208
|
-
// [v4.2.0] KB→md 파일 생성 제거 —
|
|
210
|
+
// [v4.2.0] KB→md 파일 생성 제거 — semo CLI kb 명령어로 대체
|
|
209
211
|
// 기존 memory/*.md (team, projects, decisions, infra, process, bots, ontology) 파일은
|
|
210
|
-
// semo
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (fs.existsSync(semoSystemDir)) {
|
|
215
|
-
spinner.text = "skills 동기화...";
|
|
216
|
-
const client = await pool.connect();
|
|
217
|
-
try {
|
|
218
|
-
await client.query("BEGIN");
|
|
219
|
-
await (0, skill_sync_1.syncSkillsToDB)(client, semoSystemDir);
|
|
220
|
-
await client.query("COMMIT");
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
await client.query("ROLLBACK").catch(() => { });
|
|
224
|
-
// 스킬 동기화 실패는 무시 — context sync의 핵심은 memory/ 파일
|
|
225
|
-
}
|
|
226
|
-
finally {
|
|
227
|
-
client.release();
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// 2. DB → 글로벌 캐시 (skills/commands/agents → ~/.claude/)
|
|
212
|
+
// semo CLI가 실시간 DB 조회로 대체합니다.
|
|
213
|
+
// [v4.4.0] semo-system/ → DB 스킬 동기화 제거 (semo-system 폐기됨)
|
|
214
|
+
// 스킬 SoT는 DB(skill_definitions). 수정은 직접 DB UPDATE 또는 마이그레이션.
|
|
215
|
+
// DB → 글로벌 캐시 (skills/commands/agents → ~/.claude/)
|
|
232
216
|
if (options.globalCache !== false) {
|
|
233
217
|
spinner.text = "글로벌 캐시 동기화 (skills/commands/agents)...";
|
|
234
218
|
try {
|
|
@@ -251,27 +235,6 @@ function registerContextCommands(program) {
|
|
|
251
235
|
catch {
|
|
252
236
|
// 크론잡 동기화 실패는 비치명적
|
|
253
237
|
}
|
|
254
|
-
// [v4.2.0] KB Digest 제거 — MCP kb_digest로 대체
|
|
255
|
-
// 4. MCP 서버 자동 등록 (.claude/settings.json)
|
|
256
|
-
try {
|
|
257
|
-
const semoRoot = process.cwd();
|
|
258
|
-
const settingsPath = path.join(memDir, "..", "settings.json");
|
|
259
|
-
if (fs.existsSync(settingsPath)) {
|
|
260
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
261
|
-
if (!settings.mcpServers?.["semo-kb"]) {
|
|
262
|
-
settings.mcpServers = settings.mcpServers || {};
|
|
263
|
-
settings.mcpServers["semo-kb"] = {
|
|
264
|
-
command: "node",
|
|
265
|
-
args: [path.join(semoRoot, "packages/mcp-kb/dist/index.js")],
|
|
266
|
-
};
|
|
267
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
268
|
-
console.log(chalk_1.default.green(" ✓ semo-kb MCP 서버 자동 등록"));
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
// MCP 자동 등록 실패는 비치명적
|
|
274
|
-
}
|
|
275
238
|
spinner.succeed("context sync 완료 — 스킬/캐시/크론잡 동기화");
|
|
276
239
|
console.log(chalk_1.default.gray(` 저장 위치: ${memDir}`));
|
|
277
240
|
}
|
|
@@ -290,8 +253,8 @@ function registerContextCommands(program) {
|
|
|
290
253
|
.option("--dry-run", "실제 push 없이 변경사항만 미리보기")
|
|
291
254
|
.option("--out-dir <path>", "메모리 파일 경로 (기본: .claude/memory/). OpenClaw 봇 workspace 지원용")
|
|
292
255
|
.action(async (options) => {
|
|
293
|
-
console.log(chalk_1.default.yellow("⚠️ [deprecated] context push는
|
|
294
|
-
console.log(chalk_1.default.yellow(" 봇/세션에서는 semo
|
|
256
|
+
console.log(chalk_1.default.yellow("⚠️ [deprecated] context push는 semo kb upsert로 대체 예정입니다."));
|
|
257
|
+
console.log(chalk_1.default.yellow(" 봇/세션에서는 semo kb upsert 명령어를 직접 사용하세요.\n"));
|
|
295
258
|
const domains = options.domain.split(",").map((d) => d.trim()).filter(Boolean);
|
|
296
259
|
const memDir = resolveMemoryDir(options.outDir);
|
|
297
260
|
// 각 도메인별 엔트리 수집
|
package/dist/commands/get.js
CHANGED
|
@@ -51,11 +51,11 @@ function registerGetCommands(program) {
|
|
|
51
51
|
// ── semo get projects ───────────────────────────────────────
|
|
52
52
|
getCmd
|
|
53
53
|
.command("projects")
|
|
54
|
-
.description("
|
|
55
|
-
.option("--active", "활성
|
|
54
|
+
.description("서비스 인스턴스 목록 조회 (온톨로지 기반)")
|
|
55
|
+
.option("--active", "활성 서비스만 (status='active')")
|
|
56
56
|
.option("--format <type>", "출력 형식 (table|json|md)", "table")
|
|
57
57
|
.action(async (options) => {
|
|
58
|
-
const spinner = (0, ora_1.default)("
|
|
58
|
+
const spinner = (0, ora_1.default)("서비스 인스턴스 조회 중...").start();
|
|
59
59
|
const connected = await (0, database_1.isDbConnected)();
|
|
60
60
|
if (!connected) {
|
|
61
61
|
spinner.fail("DB 연결 실패");
|
|
@@ -64,29 +64,43 @@ function registerGetCommands(program) {
|
|
|
64
64
|
}
|
|
65
65
|
try {
|
|
66
66
|
const pool = (0, database_1.getPool)();
|
|
67
|
-
|
|
67
|
+
const client = await pool.connect();
|
|
68
|
+
// Query service instances from ontology + their status from KB
|
|
69
|
+
const result = await client.query(`
|
|
70
|
+
SELECT o.domain, o.description,
|
|
71
|
+
ks.content as status,
|
|
72
|
+
(SELECT COUNT(*)::int FROM semo.knowledge_base k WHERE k.domain = o.domain) as entry_count,
|
|
73
|
+
(SELECT k2.content FROM semo.knowledge_base k2 WHERE k2.domain = o.domain AND k2.key = 'po' LIMIT 1) as po
|
|
74
|
+
FROM semo.ontology o
|
|
75
|
+
LEFT JOIN semo.knowledge_base ks ON ks.domain = o.domain AND ks.key = 'status'
|
|
76
|
+
WHERE o.entity_type = 'service'
|
|
77
|
+
ORDER BY o.domain
|
|
78
|
+
`);
|
|
79
|
+
client.release();
|
|
80
|
+
let rows = result.rows;
|
|
68
81
|
if (options.active) {
|
|
69
|
-
|
|
70
|
-
const meta = e.metadata;
|
|
71
|
-
return meta?.status === "active";
|
|
72
|
-
});
|
|
82
|
+
rows = rows.filter((r) => r.status === "active");
|
|
73
83
|
}
|
|
74
84
|
spinner.stop();
|
|
75
85
|
if (options.format === "json") {
|
|
76
|
-
console.log(JSON.stringify(
|
|
86
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
77
87
|
}
|
|
78
88
|
else if (options.format === "md") {
|
|
79
|
-
for (const
|
|
80
|
-
console.log(`\n## ${
|
|
81
|
-
console.log(
|
|
89
|
+
for (const r of rows) {
|
|
90
|
+
console.log(`\n## ${r.domain}\n`);
|
|
91
|
+
console.log(`상태: ${r.status || "-"} | 담당: ${r.po || "-"}`);
|
|
92
|
+
if (r.description)
|
|
93
|
+
console.log(r.description);
|
|
82
94
|
}
|
|
83
95
|
}
|
|
84
96
|
else {
|
|
85
|
-
printTable(["
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
printTable(["service", "status", "po", "entries", "description"], rows.map((r) => [
|
|
98
|
+
r.domain,
|
|
99
|
+
r.status || "-",
|
|
100
|
+
r.po || "-",
|
|
101
|
+
String(r.entry_count || 0),
|
|
102
|
+
(r.description || "").substring(0, 40),
|
|
103
|
+
]), "📁 서비스 인스턴스");
|
|
90
104
|
}
|
|
91
105
|
}
|
|
92
106
|
catch (err) {
|
package/dist/commands/memory.js
CHANGED
|
@@ -148,7 +148,8 @@ async function syncMemories(candidates, state, force, dryRun) {
|
|
|
148
148
|
}
|
|
149
149
|
try {
|
|
150
150
|
const domain = "memory";
|
|
151
|
-
const
|
|
151
|
+
const flatKey = "memory";
|
|
152
|
+
const subKey = stateKey; // sourceId/date
|
|
152
153
|
const metadata = {
|
|
153
154
|
source_type: candidate.sourceType,
|
|
154
155
|
source_id: candidate.sourceId,
|
|
@@ -158,19 +159,20 @@ async function syncMemories(candidates, state, force, dryRun) {
|
|
|
158
159
|
synced_at: new Date().toISOString(),
|
|
159
160
|
};
|
|
160
161
|
// Generate embedding
|
|
161
|
-
const text =
|
|
162
|
+
const text = `memory/${stateKey}: ${candidate.content}`;
|
|
162
163
|
const embedding = await (0, kb_1.generateEmbedding)(text);
|
|
163
164
|
const embeddingStr = embedding ? `[${embedding.join(",")}]` : null;
|
|
164
165
|
const client = await pool.connect();
|
|
165
166
|
try {
|
|
166
|
-
await client.query(`INSERT INTO semo.knowledge_base (domain, key, content, metadata, created_by, embedding)
|
|
167
|
-
VALUES ($1, $2, $3, $4, $5, $6::vector)
|
|
168
|
-
ON CONFLICT (domain, key) DO UPDATE SET
|
|
167
|
+
await client.query(`INSERT INTO semo.knowledge_base (domain, key, sub_key, content, metadata, created_by, embedding)
|
|
168
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7::vector)
|
|
169
|
+
ON CONFLICT (domain, key, sub_key) DO UPDATE SET
|
|
169
170
|
content = EXCLUDED.content,
|
|
170
171
|
metadata = EXCLUDED.metadata,
|
|
171
172
|
embedding = EXCLUDED.embedding`, [
|
|
172
173
|
domain,
|
|
173
|
-
|
|
174
|
+
flatKey,
|
|
175
|
+
subKey,
|
|
174
176
|
candidate.content,
|
|
175
177
|
JSON.stringify(metadata),
|
|
176
178
|
"semo-memory-sync",
|
|
@@ -273,8 +275,8 @@ function registerMemoryCommands(program) {
|
|
|
273
275
|
}
|
|
274
276
|
// Group by source
|
|
275
277
|
const grouped = {};
|
|
276
|
-
for (const [
|
|
277
|
-
const [sourceId, date] =
|
|
278
|
+
for (const [stKey, val] of entries) {
|
|
279
|
+
const [sourceId, date] = stKey.split("/");
|
|
278
280
|
if (specificBot && sourceId !== specificBot)
|
|
279
281
|
continue;
|
|
280
282
|
if (!grouped[sourceId])
|
|
@@ -88,7 +88,7 @@ function scanSkills(semoSystemDir) {
|
|
|
88
88
|
async function syncSkillsToDB(client, semoSystemDir) {
|
|
89
89
|
const skills = scanSkills(semoSystemDir);
|
|
90
90
|
for (const skill of skills) {
|
|
91
|
-
await client.query(`INSERT INTO skill_definitions (name, prompt, package, metadata, is_active, office_id)
|
|
91
|
+
await client.query(`INSERT INTO semo.skill_definitions (name, prompt, package, metadata, is_active, office_id)
|
|
92
92
|
VALUES ($1, $2, $3, $4, true, NULL)
|
|
93
93
|
ON CONFLICT (name, office_id) DO UPDATE SET
|
|
94
94
|
prompt = EXCLUDED.prompt,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* semo test — 테스트 관리
|
|
3
|
+
*
|
|
4
|
+
* semo test list — 등록된 스위트 + 최근 실행 상태
|
|
5
|
+
* semo test run [suite] — 스위트 실행 + DB 기록
|
|
6
|
+
* semo test run --all — 전체 스위트 실행
|
|
7
|
+
* semo test run --notify — 실패 시 Slack 알림
|
|
8
|
+
* semo test history [suite] — 실행 이력
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
export declare function registerTestCommands(program: Command): void;
|