@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.
@@ -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 — kb_upsert MCP 도구로 대체)
6
+ * push: .claude/memory/<domain>.md → DB (deprecated — semo kb upsert로 대체)
7
7
  *
8
- * [v4.2.0] KB→md 파일 생성 제거 — semo-kb MCP 서버로 통일
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
- const skill_sync_1 = require("./skill-sync");
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 제거 — MCP kb_digest로 대체
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-kb MCP 서버)");
191
+ .description("스킬/캐시/크론잡 동기화 (KB는 semo CLI)");
190
192
  // ── semo context sync ──────────────────────────────────────
191
193
  ctxCmd
192
194
  .command("sync")
193
- .description("스킬/에이전트/캐시 동기화 + 크론잡 (KB는 semo-kb MCP 서버 사용)")
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 파일 생성 제거 — MCP kb_search/kb_list/kb_bot_status/kb_ontology로 대체
210
+ // [v4.2.0] KB→md 파일 생성 제거 — semo CLI kb 명령어로 대체
209
211
  // 기존 memory/*.md (team, projects, decisions, infra, process, bots, ontology) 파일은
210
- // semo-kb MCP 서버가 실시간 DB 조회로 대체합니다.
211
- // 1. 스킬 파일 skill_definitions DB 동기화
212
- if (options.skills !== false) {
213
- const semoSystemDir = path.join(process.cwd(), "semo-system");
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는 kb_upsert MCP 도구로 대체 예정입니다."));
294
- console.log(chalk_1.default.yellow(" 봇/세션에서는 semo-kb MCP 서버의 kb_upsert 도구를 직접 사용하세요.\n"));
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
  // 각 도메인별 엔트리 수집
@@ -51,11 +51,11 @@ function registerGetCommands(program) {
51
51
  // ── semo get projects ───────────────────────────────────────
52
52
  getCmd
53
53
  .command("projects")
54
- .description("프로젝트 목록 조회 (KB 기반)")
55
- .option("--active", "활성 프로젝트만 (metadata.status='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)("프로젝트 조회 중...").start();
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
- let entries = await (0, kb_1.kbList)(pool, { domain: "project", limit: 100 });
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
- entries = entries.filter(e => {
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(entries, null, 2));
86
+ console.log(JSON.stringify(rows, null, 2));
77
87
  }
78
88
  else if (options.format === "md") {
79
- for (const e of entries) {
80
- console.log(`\n## ${e.key}\n`);
81
- console.log(e.content);
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(["key", "content", "updated_at"], entries.map(e => [
86
- e.key,
87
- (e.content || "").substring(0, 60),
88
- e.updated_at ? new Date(e.updated_at).toLocaleString("ko-KR") : "-",
89
- ]), "📁 프로젝트 (KB)");
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) {
@@ -148,7 +148,8 @@ async function syncMemories(candidates, state, force, dryRun) {
148
148
  }
149
149
  try {
150
150
  const domain = "memory";
151
- const key = stateKey;
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 = `${key}: ${candidate.content}`;
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
- key,
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 [key, val] of entries) {
277
- const [sourceId, date] = key.split("/");
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;