@team-semicolon/semo-cli 4.9.0 → 4.11.0
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/database.js +1 -4
- package/dist/env-parser.d.ts +1 -1
- package/dist/env-parser.js +1 -1
- package/dist/index.js +154 -31
- package/dist/kb.d.ts +22 -0
- package/dist/kb.js +76 -0
- package/dist/slack-notify.d.ts +1 -1
- package/dist/slack-notify.js +1 -1
- package/package.json +1 -1
package/dist/database.js
CHANGED
|
@@ -68,11 +68,8 @@ const env_parser_1 = require("./env-parser");
|
|
|
68
68
|
// ~/.claude/semo/.env 자동 로드 — LaunchAgent / Claude Code 앱 / cron 등
|
|
69
69
|
// 인터랙티브 쉘이 아닌 환경에서 환경변수를 공급한다.
|
|
70
70
|
// 이미 설정된 환경변수는 덮어쓰지 않는다 (env var > file).
|
|
71
|
-
// v4.5.0: ~/.semo.env → ~/.claude/semo/.env 이전. 구 경로 폴백 유지.
|
|
72
71
|
function loadSemoEnv() {
|
|
73
|
-
const
|
|
74
|
-
const legacyEnvFile = path.join(os.homedir(), ".semo.env");
|
|
75
|
-
const envFile = fs.existsSync(newEnvFile) ? newEnvFile : legacyEnvFile;
|
|
72
|
+
const envFile = path.join(os.homedir(), ".claude", "semo", ".env");
|
|
76
73
|
if (!fs.existsSync(envFile))
|
|
77
74
|
return;
|
|
78
75
|
try {
|
package/dist/env-parser.d.ts
CHANGED
package/dist/env-parser.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseEnvContent = parseEnvContent;
|
|
4
4
|
/**
|
|
5
5
|
* 공용 KEY=VALUE 파서
|
|
6
|
-
* ~/.semo
|
|
6
|
+
* ~/.claude/semo/.env 파일과 GitHub Gist 콘텐츠 모두 이 함수로 파싱한다.
|
|
7
7
|
*/
|
|
8
8
|
function parseEnvContent(content) {
|
|
9
9
|
const result = {};
|
package/dist/index.js
CHANGED
|
@@ -331,8 +331,7 @@ async function showToolsStatus() {
|
|
|
331
331
|
// === 글로벌 설정 체크 ===
|
|
332
332
|
function isGlobalSetupDone() {
|
|
333
333
|
const home = os.homedir();
|
|
334
|
-
const hasEnv = fs.existsSync(path.join(home, ".claude", "semo", ".env"))
|
|
335
|
-
fs.existsSync(path.join(home, ".semo.env")); // 하위 호환
|
|
334
|
+
const hasEnv = fs.existsSync(path.join(home, ".claude", "semo", ".env"));
|
|
336
335
|
const hasSetup = fs.existsSync(path.join(home, ".claude", "semo", "SOUL.md")) ||
|
|
337
336
|
fs.existsSync(path.join(home, ".claude", "skills")); // 하위 호환
|
|
338
337
|
return hasEnv && hasSetup;
|
|
@@ -494,9 +493,7 @@ const BASE_MCP_SERVERS = [
|
|
|
494
493
|
},
|
|
495
494
|
];
|
|
496
495
|
// === ~/.claude/semo/.env 설정 (자동 감지 → Gist → 프롬프트) ===
|
|
497
|
-
// v4.5.0: ~/.semo.env → ~/.claude/semo/.env 이전
|
|
498
496
|
const SEMO_ENV_PATH = path.join(os.homedir(), ".claude", "semo", ".env");
|
|
499
|
-
const LEGACY_ENV_PATH = path.join(os.homedir(), ".semo.env");
|
|
500
497
|
const SEMO_CREDENTIALS = [
|
|
501
498
|
{
|
|
502
499
|
key: "DATABASE_URL",
|
|
@@ -544,27 +541,9 @@ function writeSemoEnvFile(creds) {
|
|
|
544
541
|
}
|
|
545
542
|
lines.push("");
|
|
546
543
|
fs.writeFileSync(SEMO_ENV_PATH, lines.join("\n"), { mode: 0o600 });
|
|
547
|
-
// 하위 호환 심링크: ~/.semo.env → ~/.claude/semo/.env
|
|
548
|
-
try {
|
|
549
|
-
if (fs.existsSync(LEGACY_ENV_PATH)) {
|
|
550
|
-
const stat = fs.lstatSync(LEGACY_ENV_PATH);
|
|
551
|
-
if (!stat.isSymbolicLink()) {
|
|
552
|
-
// 기존 실파일은 백업 후 심링크로 교체
|
|
553
|
-
fs.renameSync(LEGACY_ENV_PATH, LEGACY_ENV_PATH + ".bak");
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
fs.unlinkSync(LEGACY_ENV_PATH);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
fs.symlinkSync(SEMO_ENV_PATH, LEGACY_ENV_PATH);
|
|
560
|
-
}
|
|
561
|
-
catch {
|
|
562
|
-
// 심링크 실패 시 무시 — 새 경로가 원본
|
|
563
|
-
}
|
|
564
544
|
}
|
|
565
545
|
function readSemoEnvCreds() {
|
|
566
|
-
|
|
567
|
-
const envFile = fs.existsSync(SEMO_ENV_PATH) ? SEMO_ENV_PATH : LEGACY_ENV_PATH;
|
|
546
|
+
const envFile = SEMO_ENV_PATH;
|
|
568
547
|
if (!fs.existsSync(envFile))
|
|
569
548
|
return {};
|
|
570
549
|
try {
|
|
@@ -740,7 +719,7 @@ async function buildKbFirstBlock() {
|
|
|
740
719
|
const label = s.desc || s.scheme_key;
|
|
741
720
|
domainGuide += `- 서비스 ${label} → \`domain: {서비스명}\`, key: \`${s.scheme_key}\`\n`;
|
|
742
721
|
}
|
|
743
|
-
domainGuide += `- 서비스 KPI → \`domain: {서비스명}\`, key: \`kpi/
|
|
722
|
+
domainGuide += `- 서비스 KPI → \`domain: {서비스명}\`, key: \`kpi/{YYYY-MM-DD}\` (최신: 가장 최근 날짜)\n`;
|
|
744
723
|
domainGuide += `- 서비스 마일스톤 → \`domain: {서비스명}\`, key: \`milestone/{slug}\`\n`;
|
|
745
724
|
domainGuide += `\n**정확한 경로를 모를 때:**\n`;
|
|
746
725
|
domainGuide += `1. \`semo kb search "검색어"\` → 결과의 \`[domain] key/sub_key\` 경로 확인\n`;
|
|
@@ -853,7 +832,7 @@ async function setupHooks(isUpdate = false) {
|
|
|
853
832
|
hooks: [
|
|
854
833
|
{
|
|
855
834
|
type: "command",
|
|
856
|
-
command: ". ~/.claude/semo/.env 2>/dev/null
|
|
835
|
+
command: ". ~/.claude/semo/.env 2>/dev/null; semo context sync 2>/dev/null || true",
|
|
857
836
|
timeout: 30,
|
|
858
837
|
},
|
|
859
838
|
],
|
|
@@ -865,7 +844,7 @@ async function setupHooks(isUpdate = false) {
|
|
|
865
844
|
hooks: [
|
|
866
845
|
{
|
|
867
846
|
type: "command",
|
|
868
|
-
command: ". ~/.claude/semo/.env 2>/dev/null
|
|
847
|
+
command: ". ~/.claude/semo/.env 2>/dev/null; semo context push 2>/dev/null || true",
|
|
869
848
|
timeout: 30,
|
|
870
849
|
},
|
|
871
850
|
],
|
|
@@ -1119,9 +1098,9 @@ npm run build # 빌드 검증
|
|
|
1119
1098
|
|
|
1120
1099
|
---
|
|
1121
1100
|
|
|
1122
|
-
## 환경변수 (\`~/.semo
|
|
1101
|
+
## 환경변수 (\`~/.claude/semo/.env\`)
|
|
1123
1102
|
|
|
1124
|
-
SEMO는 \`~/.semo
|
|
1103
|
+
SEMO는 \`~/.claude/semo/.env\` 파일에서 팀 공통 환경변수를 로드합니다.
|
|
1125
1104
|
SessionStart 훅과 OpenClaw 게이트웨이 래퍼에서 자동 source됩니다.
|
|
1126
1105
|
|
|
1127
1106
|
| 변수 | 용도 | 필수 |
|
|
@@ -1130,7 +1109,7 @@ SessionStart 훅과 OpenClaw 게이트웨이 래퍼에서 자동 source됩니다
|
|
|
1130
1109
|
| \`OPENAI_API_KEY\` | KB 임베딩용 (text-embedding-3-small) | 선택 |
|
|
1131
1110
|
| \`SLACK_WEBHOOK\` | Slack 알림 | 선택 |
|
|
1132
1111
|
|
|
1133
|
-
키 갱신이 필요하면 \`~/.semo
|
|
1112
|
+
키 갱신이 필요하면 \`~/.claude/semo/.env\`를 직접 편집하거나 \`semo onboarding -f\`를 실행하세요.
|
|
1134
1113
|
|
|
1135
1114
|
---
|
|
1136
1115
|
|
|
@@ -1754,7 +1733,7 @@ kbCmd
|
|
|
1754
1733
|
kbCmd
|
|
1755
1734
|
.command("ontology")
|
|
1756
1735
|
.description("온톨로지 조회 — 도메인/타입/스키마/라우팅 테이블")
|
|
1757
|
-
.option("--action <type>", "동작 (list|show|services|types|instances|schema|routing-table|register|add-key|remove-key)", "list")
|
|
1736
|
+
.option("--action <type>", "동작 (list|show|services|types|instances|schema|routing-table|register|unregister|create-type|add-key|remove-key)", "list")
|
|
1758
1737
|
.option("--domain <name>", "action=show|register 시 도메인")
|
|
1759
1738
|
.option("--type <name>", "action=schema|register|add-key|remove-key 시 타입 키")
|
|
1760
1739
|
.option("--key <name>", "action=add-key|remove-key 시 스키마 키")
|
|
@@ -1765,6 +1744,8 @@ kbCmd
|
|
|
1765
1744
|
.option("--service <name>", "action=register 시 서비스 그룹")
|
|
1766
1745
|
.option("--tags <tags>", "action=register 시 태그 (쉼표 구분)")
|
|
1767
1746
|
.option("--no-init", "action=register 시 필수 KB entry 자동 생성 건너뛰기")
|
|
1747
|
+
.option("--force", "action=unregister 시 잔존 KB 항목도 모두 삭제")
|
|
1748
|
+
.option("--yes", "action=unregister 시 확인 프롬프트 건너뛰기")
|
|
1768
1749
|
.option("--format <type>", "출력 형식 (json|table)", "table")
|
|
1769
1750
|
.action(async (options) => {
|
|
1770
1751
|
try {
|
|
@@ -1949,6 +1930,24 @@ kbCmd
|
|
|
1949
1930
|
}
|
|
1950
1931
|
}
|
|
1951
1932
|
}
|
|
1933
|
+
else if (action === "create-type") {
|
|
1934
|
+
if (!options.type) {
|
|
1935
|
+
console.log(chalk_1.default.red("--type 옵션이 필요합니다. (예: --type project)"));
|
|
1936
|
+
process.exit(1);
|
|
1937
|
+
}
|
|
1938
|
+
const result = await (0, kb_1.ontoCreateType)(pool, {
|
|
1939
|
+
type_key: options.type,
|
|
1940
|
+
description: options.description,
|
|
1941
|
+
});
|
|
1942
|
+
if (result.success) {
|
|
1943
|
+
console.log(chalk_1.default.green(`\n✅ 온톨로지 타입 '${options.type}' 생성 완료`));
|
|
1944
|
+
console.log(chalk_1.default.gray(`\n 다음 단계: semo kb ontology --action add-key --type ${options.type} --key <key> --key-type singleton|collection\n`));
|
|
1945
|
+
}
|
|
1946
|
+
else {
|
|
1947
|
+
console.log(chalk_1.default.red(`\n❌ 타입 생성 실패: ${result.error}\n`));
|
|
1948
|
+
process.exit(1);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1952
1951
|
else if (action === "add-key") {
|
|
1953
1952
|
if (!options.type) {
|
|
1954
1953
|
console.log(chalk_1.default.red("--type 옵션이 필요합니다. (예: --type service)"));
|
|
@@ -1992,8 +1991,64 @@ kbCmd
|
|
|
1992
1991
|
process.exit(1);
|
|
1993
1992
|
}
|
|
1994
1993
|
}
|
|
1994
|
+
else if (action === "unregister") {
|
|
1995
|
+
if (!options.domain) {
|
|
1996
|
+
console.log(chalk_1.default.red("--domain 옵션이 필요합니다."));
|
|
1997
|
+
process.exit(1);
|
|
1998
|
+
}
|
|
1999
|
+
// 도메인 정보 조회
|
|
2000
|
+
const domainInfo = await (0, kb_1.ontoShow)(pool, options.domain);
|
|
2001
|
+
if (!domainInfo) {
|
|
2002
|
+
console.log(chalk_1.default.red(`\n❌ 도메인 '${options.domain}'은(는) 존재하지 않습니다.\n`));
|
|
2003
|
+
process.exit(1);
|
|
2004
|
+
}
|
|
2005
|
+
// KB 엔트리 수 확인
|
|
2006
|
+
const countRes = await pool.query("SELECT COUNT(*)::int AS cnt FROM semo.knowledge_base WHERE domain = $1", [options.domain]);
|
|
2007
|
+
const kbCount = countRes.rows[0].cnt;
|
|
2008
|
+
// 확인 프롬프트
|
|
2009
|
+
if (!options.yes) {
|
|
2010
|
+
const typeStr = domainInfo.entity_type ? ` [${domainInfo.entity_type}]` : "";
|
|
2011
|
+
const svcStr = domainInfo.service && domainInfo.service !== "_global" ? ` (${domainInfo.service})` : "";
|
|
2012
|
+
if (kbCount > 0 && options.force) {
|
|
2013
|
+
console.log(chalk_1.default.yellow(`\n⚠️ 도메인 '${options.domain}'에 KB 항목 ${kbCount}건이 남아있습니다.`));
|
|
2014
|
+
console.log(chalk_1.default.yellow(` --force 옵션으로 모두 삭제됩니다.\n`));
|
|
2015
|
+
}
|
|
2016
|
+
else {
|
|
2017
|
+
console.log(chalk_1.default.cyan(`\n📐 삭제 대상 도메인: ${options.domain}${typeStr}${svcStr}`));
|
|
2018
|
+
console.log(chalk_1.default.gray(` KB 항목: ${kbCount}건\n`));
|
|
2019
|
+
}
|
|
2020
|
+
const { createInterface } = await import("readline");
|
|
2021
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2022
|
+
const answer = await new Promise((resolve) => {
|
|
2023
|
+
rl.question(chalk_1.default.yellow(" 정말 삭제하시겠습니까? (y/N): "), resolve);
|
|
2024
|
+
});
|
|
2025
|
+
rl.close();
|
|
2026
|
+
if (answer.toLowerCase() !== "y") {
|
|
2027
|
+
console.log(chalk_1.default.gray(" 취소됨.\n"));
|
|
2028
|
+
await (0, database_1.closeConnection)();
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
const result = await (0, kb_1.ontoUnregister)(pool, options.domain, !!options.force);
|
|
2033
|
+
if (options.format === "json") {
|
|
2034
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2035
|
+
}
|
|
2036
|
+
else {
|
|
2037
|
+
if (result.success) {
|
|
2038
|
+
console.log(chalk_1.default.green(`\n✅ 도메인 '${options.domain}' 삭제 완료`));
|
|
2039
|
+
if (result.deleted_entries && result.deleted_entries > 0) {
|
|
2040
|
+
console.log(chalk_1.default.gray(` KB 항목 ${result.deleted_entries}건 함께 삭제됨`));
|
|
2041
|
+
}
|
|
2042
|
+
console.log();
|
|
2043
|
+
}
|
|
2044
|
+
else {
|
|
2045
|
+
console.log(chalk_1.default.red(`\n❌ 삭제 실패: ${result.error}\n`));
|
|
2046
|
+
process.exit(1);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
1995
2050
|
else {
|
|
1996
|
-
console.log(chalk_1.default.red(`알 수 없는 action: '${action}'. 사용 가능: list, show, services, types, instances, schema, routing-table, register, add-key, remove-key`));
|
|
2051
|
+
console.log(chalk_1.default.red(`알 수 없는 action: '${action}'. 사용 가능: list, show, services, types, instances, schema, routing-table, register, create-type, add-key, remove-key, unregister`));
|
|
1997
2052
|
process.exit(1);
|
|
1998
2053
|
}
|
|
1999
2054
|
await (0, database_1.closeConnection)();
|
|
@@ -2221,6 +2276,74 @@ ontoCmd
|
|
|
2221
2276
|
process.exit(1);
|
|
2222
2277
|
}
|
|
2223
2278
|
});
|
|
2279
|
+
ontoCmd
|
|
2280
|
+
.command("unregister <domain>")
|
|
2281
|
+
.description("온톨로지 도메인 삭제 (KB 데이터 포함)")
|
|
2282
|
+
.option("--force", "잔존 KB 항목이 있어도 모두 삭제 후 도메인 제거")
|
|
2283
|
+
.option("--yes", "확인 프롬프트 건너뛰기")
|
|
2284
|
+
.option("--format <type>", "출력 형식 (json|table)", "table")
|
|
2285
|
+
.action(async (domain, options) => {
|
|
2286
|
+
try {
|
|
2287
|
+
const pool = (0, database_1.getPool)();
|
|
2288
|
+
// 도메인 정보 조회
|
|
2289
|
+
const domainInfo = await (0, kb_1.ontoShow)(pool, domain);
|
|
2290
|
+
if (!domainInfo) {
|
|
2291
|
+
console.log(chalk_1.default.red(`\n❌ 도메인 '${domain}'은(는) 존재하지 않습니다.\n`));
|
|
2292
|
+
await (0, database_1.closeConnection)();
|
|
2293
|
+
process.exit(1);
|
|
2294
|
+
}
|
|
2295
|
+
// KB 엔트리 수 확인
|
|
2296
|
+
const countRes = await pool.query("SELECT COUNT(*)::int AS cnt FROM semo.knowledge_base WHERE domain = $1", [domain]);
|
|
2297
|
+
const kbCount = countRes.rows[0].cnt;
|
|
2298
|
+
// 확인 프롬프트
|
|
2299
|
+
if (!options.yes) {
|
|
2300
|
+
const typeStr = domainInfo.entity_type ? ` [${domainInfo.entity_type}]` : "";
|
|
2301
|
+
const svcStr = domainInfo.service && domainInfo.service !== "_global" ? ` (${domainInfo.service})` : "";
|
|
2302
|
+
if (kbCount > 0 && options.force) {
|
|
2303
|
+
console.log(chalk_1.default.yellow(`\n⚠️ 도메인 '${domain}'에 KB 항목 ${kbCount}건이 남아있습니다.`));
|
|
2304
|
+
console.log(chalk_1.default.yellow(` --force 옵션으로 모두 삭제됩니다.\n`));
|
|
2305
|
+
}
|
|
2306
|
+
else {
|
|
2307
|
+
console.log(chalk_1.default.cyan(`\n📐 삭제 대상 도메인: ${domain}${typeStr}${svcStr}`));
|
|
2308
|
+
console.log(chalk_1.default.gray(` KB 항목: ${kbCount}건\n`));
|
|
2309
|
+
}
|
|
2310
|
+
const { createInterface } = await import("readline");
|
|
2311
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2312
|
+
const answer = await new Promise((resolve) => {
|
|
2313
|
+
rl.question(chalk_1.default.yellow(" 정말 삭제하시겠습니까? (y/N): "), resolve);
|
|
2314
|
+
});
|
|
2315
|
+
rl.close();
|
|
2316
|
+
if (answer.toLowerCase() !== "y") {
|
|
2317
|
+
console.log(chalk_1.default.gray(" 취소됨.\n"));
|
|
2318
|
+
await (0, database_1.closeConnection)();
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
const result = await (0, kb_1.ontoUnregister)(pool, domain, !!options.force);
|
|
2323
|
+
if (options.format === "json") {
|
|
2324
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2325
|
+
}
|
|
2326
|
+
else {
|
|
2327
|
+
if (result.success) {
|
|
2328
|
+
console.log(chalk_1.default.green(`\n✅ 도메인 '${domain}' 삭제 완료`));
|
|
2329
|
+
if (result.deleted_entries && result.deleted_entries > 0) {
|
|
2330
|
+
console.log(chalk_1.default.gray(` KB 항목 ${result.deleted_entries}건 함께 삭제됨`));
|
|
2331
|
+
}
|
|
2332
|
+
console.log();
|
|
2333
|
+
}
|
|
2334
|
+
else {
|
|
2335
|
+
console.log(chalk_1.default.red(`\n❌ 삭제 실패: ${result.error}\n`));
|
|
2336
|
+
process.exit(1);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
await (0, database_1.closeConnection)();
|
|
2340
|
+
}
|
|
2341
|
+
catch (err) {
|
|
2342
|
+
console.error(chalk_1.default.red(`삭제 실패: ${err}`));
|
|
2343
|
+
await (0, database_1.closeConnection)();
|
|
2344
|
+
process.exit(1);
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2224
2347
|
// === 신규 v4 커맨드 그룹 등록 ===
|
|
2225
2348
|
(0, context_1.registerContextCommands)(program);
|
|
2226
2349
|
(0, bots_1.registerBotsCommands)(program);
|
package/dist/kb.d.ts
CHANGED
|
@@ -232,6 +232,17 @@ export interface OntoRegisterResult {
|
|
|
232
232
|
* 4. If init_required (default true), create KB entries for required keys in kb_type_schema
|
|
233
233
|
*/
|
|
234
234
|
export declare function ontoRegister(pool: Pool, opts: OntoRegisterOptions): Promise<OntoRegisterResult>;
|
|
235
|
+
export interface OntoUnregisterResult {
|
|
236
|
+
success: boolean;
|
|
237
|
+
deleted_entries?: number;
|
|
238
|
+
error?: string;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Unregister an ontology domain.
|
|
242
|
+
* If force=false and KB entries exist, returns error with count.
|
|
243
|
+
* If force=true, deletes all KB entries in a transaction, then removes the domain.
|
|
244
|
+
*/
|
|
245
|
+
export declare function ontoUnregister(pool: Pool, domain: string, force: boolean): Promise<OntoUnregisterResult>;
|
|
235
246
|
/**
|
|
236
247
|
* Add a key to a type schema
|
|
237
248
|
*/
|
|
@@ -246,6 +257,17 @@ export declare function ontoAddKey(pool: Pool, opts: {
|
|
|
246
257
|
success: boolean;
|
|
247
258
|
error?: string;
|
|
248
259
|
}>;
|
|
260
|
+
/**
|
|
261
|
+
* Create a new ontology type
|
|
262
|
+
*/
|
|
263
|
+
export declare function ontoCreateType(pool: Pool, opts: {
|
|
264
|
+
type_key: string;
|
|
265
|
+
description?: string;
|
|
266
|
+
schema?: Record<string, unknown>;
|
|
267
|
+
}): Promise<{
|
|
268
|
+
success: boolean;
|
|
269
|
+
error?: string;
|
|
270
|
+
}>;
|
|
249
271
|
/**
|
|
250
272
|
* Remove a key from a type schema
|
|
251
273
|
*/
|
package/dist/kb.js
CHANGED
|
@@ -62,7 +62,9 @@ exports.ontoRoutingTable = ontoRoutingTable;
|
|
|
62
62
|
exports.ontoListServices = ontoListServices;
|
|
63
63
|
exports.ontoListInstances = ontoListInstances;
|
|
64
64
|
exports.ontoRegister = ontoRegister;
|
|
65
|
+
exports.ontoUnregister = ontoUnregister;
|
|
65
66
|
exports.ontoAddKey = ontoAddKey;
|
|
67
|
+
exports.ontoCreateType = ontoCreateType;
|
|
66
68
|
exports.ontoRemoveKey = ontoRemoveKey;
|
|
67
69
|
exports.ontoPullToLocal = ontoPullToLocal;
|
|
68
70
|
const fs = __importStar(require("fs"));
|
|
@@ -1010,6 +1012,50 @@ async function ontoRegister(pool, opts) {
|
|
|
1010
1012
|
client.release();
|
|
1011
1013
|
}
|
|
1012
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Unregister an ontology domain.
|
|
1017
|
+
* If force=false and KB entries exist, returns error with count.
|
|
1018
|
+
* If force=true, deletes all KB entries in a transaction, then removes the domain.
|
|
1019
|
+
*/
|
|
1020
|
+
async function ontoUnregister(pool, domain, force) {
|
|
1021
|
+
const client = await pool.connect();
|
|
1022
|
+
try {
|
|
1023
|
+
// 1. Check domain exists
|
|
1024
|
+
const domainCheck = await client.query("SELECT domain, entity_type, service FROM semo.ontology WHERE domain = $1", [domain]);
|
|
1025
|
+
if (domainCheck.rows.length === 0) {
|
|
1026
|
+
return { success: false, error: `도메인 '${domain}'은(는) 존재하지 않습니다.` };
|
|
1027
|
+
}
|
|
1028
|
+
// 2. Count KB entries
|
|
1029
|
+
const countResult = await client.query("SELECT COUNT(*)::int AS cnt FROM semo.knowledge_base WHERE domain = $1", [domain]);
|
|
1030
|
+
const entryCount = countResult.rows[0].cnt;
|
|
1031
|
+
// 3. If entries exist and no force → error
|
|
1032
|
+
if (entryCount > 0 && !force) {
|
|
1033
|
+
return {
|
|
1034
|
+
success: false,
|
|
1035
|
+
error: `도메인 '${domain}'에 KB 항목 ${entryCount}건이 남아있습니다. --force 옵션으로 모두 삭제 후 제거할 수 있습니다.`,
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
// 4. Transaction: delete KB entries (if any) → delete ontology
|
|
1039
|
+
await client.query("BEGIN");
|
|
1040
|
+
try {
|
|
1041
|
+
let deletedEntries = 0;
|
|
1042
|
+
if (entryCount > 0) {
|
|
1043
|
+
const delResult = await client.query("DELETE FROM semo.knowledge_base WHERE domain = $1", [domain]);
|
|
1044
|
+
deletedEntries = delResult.rowCount ?? 0;
|
|
1045
|
+
}
|
|
1046
|
+
await client.query("DELETE FROM semo.ontology WHERE domain = $1", [domain]);
|
|
1047
|
+
await client.query("COMMIT");
|
|
1048
|
+
return { success: true, deleted_entries: deletedEntries };
|
|
1049
|
+
}
|
|
1050
|
+
catch (err) {
|
|
1051
|
+
await client.query("ROLLBACK");
|
|
1052
|
+
return { success: false, error: String(err) };
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
finally {
|
|
1056
|
+
client.release();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1013
1059
|
/**
|
|
1014
1060
|
* Add a key to a type schema
|
|
1015
1061
|
*/
|
|
@@ -1057,6 +1103,36 @@ async function ontoAddKey(pool, opts) {
|
|
|
1057
1103
|
client.release();
|
|
1058
1104
|
}
|
|
1059
1105
|
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Create a new ontology type
|
|
1108
|
+
*/
|
|
1109
|
+
async function ontoCreateType(pool, opts) {
|
|
1110
|
+
// kebab-case 검증
|
|
1111
|
+
if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(opts.type_key)) {
|
|
1112
|
+
return { success: false, error: `타입 키 '${opts.type_key}'이(가) 유효하지 않습니다. kebab-case 소문자만 사용 가능 (예: my-type)` };
|
|
1113
|
+
}
|
|
1114
|
+
const client = await pool.connect();
|
|
1115
|
+
try {
|
|
1116
|
+
// 중복 확인
|
|
1117
|
+
const dupCheck = await client.query("SELECT type_key FROM semo.ontology_types WHERE type_key = $1", [opts.type_key]);
|
|
1118
|
+
if (dupCheck.rows.length > 0) {
|
|
1119
|
+
return { success: false, error: `타입 '${opts.type_key}'은(는) 이미 존재합니다.` };
|
|
1120
|
+
}
|
|
1121
|
+
await client.query(`INSERT INTO semo.ontology_types (type_key, schema, description)
|
|
1122
|
+
VALUES ($1, $2, $3)`, [
|
|
1123
|
+
opts.type_key,
|
|
1124
|
+
JSON.stringify(opts.schema || {}),
|
|
1125
|
+
opts.description || opts.type_key,
|
|
1126
|
+
]);
|
|
1127
|
+
return { success: true };
|
|
1128
|
+
}
|
|
1129
|
+
catch (err) {
|
|
1130
|
+
return { success: false, error: String(err) };
|
|
1131
|
+
}
|
|
1132
|
+
finally {
|
|
1133
|
+
client.release();
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1060
1136
|
/**
|
|
1061
1137
|
* Remove a key from a type schema
|
|
1062
1138
|
*/
|
package/dist/slack-notify.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Slack 알림 유틸리티
|
|
3
3
|
*
|
|
4
4
|
* SLACK_WEBHOOK 환경변수에서 URL을 읽어 알림 전송.
|
|
5
|
-
* ~/.semo
|
|
5
|
+
* ~/.claude/semo/.env에서 자동 로드됨 (database.ts loadSemoEnv).
|
|
6
6
|
*/
|
|
7
7
|
export declare function sendSlackNotification(message: string, webhookUrl?: string): Promise<boolean>;
|
|
8
8
|
export declare function formatTestFailureMessage(suiteId: string, runId: string, pass: number, fail: number, warn: number, failedLabels: string[]): string;
|
package/dist/slack-notify.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Slack 알림 유틸리티
|
|
4
4
|
*
|
|
5
5
|
* SLACK_WEBHOOK 환경변수에서 URL을 읽어 알림 전송.
|
|
6
|
-
* ~/.semo
|
|
6
|
+
* ~/.claude/semo/.env에서 자동 로드됨 (database.ts loadSemoEnv).
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.sendSlackNotification = sendSlackNotification;
|