@team-semicolon/semo-cli 4.1.0 → 4.1.3

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.
@@ -330,6 +330,11 @@ function registerBotsCommands(program) {
330
330
  }
331
331
  }
332
332
  await client.query("COMMIT");
333
+ // session_count를 bot_sessions 실제 집계로 갱신
334
+ await client.query(`UPDATE semo.bot_status bs
335
+ SET session_count = (
336
+ SELECT COUNT(*) FROM semo.bot_sessions WHERE bot_id = bs.bot_id
337
+ )`);
333
338
  spinner.succeed(`bots sync 완료: ${upserted}개 봇 업서트`);
334
339
  if (errors.length > 0) {
335
340
  errors.forEach(e => console.log(chalk_1.default.red(` ❌ ${e}`)));
@@ -0,0 +1,16 @@
1
+ /**
2
+ * semo sessions — 세션 추적
3
+ *
4
+ * Claude Code 훅(SessionStart / Stop)에서 stdin으로 전달되는 JSON을 파싱해
5
+ * semo.bot_sessions 테이블에 upsert합니다.
6
+ *
7
+ * Claude Code hook stdin 구조:
8
+ * SessionStart: { session_id, transcript_path, cwd, hook_event_name }
9
+ * Stop: { session_id, transcript_path, hook_event_name }
10
+ *
11
+ * 사용법 (settings.json hooks):
12
+ * SessionStart → semo sessions push --bot-id workclaw --event start
13
+ * Stop → semo sessions push --bot-id workclaw --event stop
14
+ */
15
+ import { Command } from "commander";
16
+ export declare function registerSessionsCommands(program: Command): void;
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ /**
3
+ * semo sessions — 세션 추적
4
+ *
5
+ * Claude Code 훅(SessionStart / Stop)에서 stdin으로 전달되는 JSON을 파싱해
6
+ * semo.bot_sessions 테이블에 upsert합니다.
7
+ *
8
+ * Claude Code hook stdin 구조:
9
+ * SessionStart: { session_id, transcript_path, cwd, hook_event_name }
10
+ * Stop: { session_id, transcript_path, hook_event_name }
11
+ *
12
+ * 사용법 (settings.json hooks):
13
+ * SessionStart → semo sessions push --bot-id workclaw --event start
14
+ * Stop → semo sessions push --bot-id workclaw --event stop
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.registerSessionsCommands = registerSessionsCommands;
54
+ const chalk_1 = __importDefault(require("chalk"));
55
+ const fs = __importStar(require("fs"));
56
+ const path = __importStar(require("path"));
57
+ const readline = __importStar(require("readline"));
58
+ const child_process_1 = require("child_process");
59
+ const database_1 = require("../database");
60
+ async function readStdin() {
61
+ // stdin이 TTY면 hook에서 호출된 게 아님 → 빈 객체 반환
62
+ if (process.stdin.isTTY)
63
+ return {};
64
+ return new Promise((resolve) => {
65
+ let raw = "";
66
+ process.stdin.setEncoding("utf-8");
67
+ process.stdin.on("data", (chunk) => (raw += chunk));
68
+ process.stdin.on("end", () => {
69
+ try {
70
+ resolve(JSON.parse(raw));
71
+ }
72
+ catch {
73
+ resolve({});
74
+ }
75
+ });
76
+ // 500ms 타임아웃 — stdin이 오지 않으면 그냥 진행
77
+ setTimeout(() => resolve({}), 500);
78
+ });
79
+ }
80
+ // ─── 현재 git 브랜치 (label용) ───────────────────────────────────────────────
81
+ function getGitBranch(cwd) {
82
+ try {
83
+ const dir = cwd || process.cwd();
84
+ return (0, child_process_1.execSync)("git rev-parse --abbrev-ref HEAD", {
85
+ cwd: dir,
86
+ stdio: ["ignore", "pipe", "ignore"],
87
+ timeout: 2000,
88
+ })
89
+ .toString()
90
+ .trim();
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ // ─── transcript.jsonl 메시지 수 카운트 ───────────────────────────────────────
97
+ async function countMessages(transcriptPath) {
98
+ if (!transcriptPath || !fs.existsSync(transcriptPath))
99
+ return 0;
100
+ return new Promise((resolve) => {
101
+ let count = 0;
102
+ const rl = readline.createInterface({
103
+ input: fs.createReadStream(transcriptPath),
104
+ crlfDelay: Infinity,
105
+ });
106
+ rl.on("line", (line) => {
107
+ if (!line.trim())
108
+ return;
109
+ try {
110
+ const obj = JSON.parse(line);
111
+ // role이 있는 메시지(user/assistant)만 카운트
112
+ if (obj.role === "user" || obj.role === "assistant")
113
+ count++;
114
+ }
115
+ catch {
116
+ // invalid line skip
117
+ }
118
+ });
119
+ rl.on("close", () => resolve(count));
120
+ rl.on("error", () => resolve(0));
121
+ });
122
+ }
123
+ // ─── Command registration ─────────────────────────────────────────────────────
124
+ function registerSessionsCommands(program) {
125
+ const sessionsCmd = program
126
+ .command("sessions")
127
+ .description("세션 추적 (Claude Code 훅 연동)");
128
+ // ── semo sessions push ───────────────────────────────────────────────────────
129
+ sessionsCmd
130
+ .command("push")
131
+ .description("현재 세션을 semo.bot_sessions에 기록 (훅에서 호출)")
132
+ .requiredOption("--bot-id <id>", "봇 ID (e.g. workclaw)")
133
+ .option("--event <type>", "이벤트 종류 (start|stop|heartbeat)", "heartbeat")
134
+ .option("--label <text>", "세션 라벨 (미지정 시 git 브랜치 자동 감지)")
135
+ .option("--kind <kind>", "세션 종류 (main|isolated)", "main")
136
+ .action(async (options) => {
137
+ const botId = options.botId;
138
+ const event = options.event;
139
+ // stdin에서 Claude Code hook JSON 읽기
140
+ const hook = await readStdin();
141
+ const sessionKey = hook.session_id ||
142
+ process.env.CLAUDE_SESSION_ID ||
143
+ `${botId}-${Date.now()}`;
144
+ const branch = getGitBranch(hook.cwd);
145
+ const label = options.label ||
146
+ branch ||
147
+ path.basename(hook.cwd || process.cwd());
148
+ const messageCount = event === "stop" && hook.transcript_path
149
+ ? await countMessages(hook.transcript_path)
150
+ : undefined;
151
+ const connected = await (0, database_1.isDbConnected)();
152
+ if (!connected) {
153
+ // 훅에서 호출 시 조용히 실패 (봇 세션에 영향 주지 않도록)
154
+ await (0, database_1.closeConnection)();
155
+ process.exit(0);
156
+ }
157
+ try {
158
+ const pool = (0, database_1.getPool)();
159
+ const client = await pool.connect();
160
+ if (event === "start") {
161
+ await client.query(`INSERT INTO semo.bot_sessions
162
+ (bot_id, session_key, label, kind, chat_type, last_activity, message_count, synced_at)
163
+ VALUES ($1, $2, $3, $4, 'claude-code', NOW(), 0, NOW())
164
+ ON CONFLICT (bot_id, session_key) DO UPDATE SET
165
+ label = EXCLUDED.label,
166
+ last_activity = NOW(),
167
+ synced_at = NOW()`, [botId, sessionKey, label, options.kind]);
168
+ // bot_status.session_count 갱신
169
+ await client.query(`UPDATE semo.bot_status
170
+ SET session_count = (
171
+ SELECT COUNT(*) FROM semo.bot_sessions WHERE bot_id = $1
172
+ ),
173
+ synced_at = NOW()
174
+ WHERE bot_id = $1`, [botId]);
175
+ }
176
+ else if (event === "stop") {
177
+ await client.query(`UPDATE semo.bot_sessions
178
+ SET last_activity = NOW(),
179
+ message_count = COALESCE($1, message_count),
180
+ synced_at = NOW()
181
+ WHERE bot_id = $2 AND session_key = $3`, [messageCount ?? null, botId, sessionKey]);
182
+ }
183
+ else {
184
+ // heartbeat — 마지막 활동 시간 + 메시지 수 갱신
185
+ await client.query(`INSERT INTO semo.bot_sessions
186
+ (bot_id, session_key, label, kind, chat_type, last_activity, message_count, synced_at)
187
+ VALUES ($1, $2, $3, $4, 'claude-code', NOW(), COALESCE($5, 0), NOW())
188
+ ON CONFLICT (bot_id, session_key) DO UPDATE SET
189
+ last_activity = NOW(),
190
+ message_count = COALESCE(EXCLUDED.message_count, semo.bot_sessions.message_count),
191
+ synced_at = NOW()`, [botId, sessionKey, label, options.kind, messageCount ?? null]);
192
+ // bot_status.session_count 갱신
193
+ await client.query(`UPDATE semo.bot_status
194
+ SET session_count = (
195
+ SELECT COUNT(*) FROM semo.bot_sessions WHERE bot_id = $1
196
+ ),
197
+ synced_at = NOW()
198
+ WHERE bot_id = $1`, [botId]);
199
+ }
200
+ client.release();
201
+ console.log(chalk_1.default.green(`✔ sessions push [${event}] ${botId}/${sessionKey.slice(0, 8)}`));
202
+ }
203
+ catch (err) {
204
+ // 훅에서 호출 시 조용히 실패
205
+ console.error(chalk_1.default.red(`sessions push 실패: ${err}`));
206
+ process.exit(0);
207
+ }
208
+ finally {
209
+ await (0, database_1.closeConnection)();
210
+ }
211
+ });
212
+ // ── semo sessions list ───────────────────────────────────────────────────────
213
+ sessionsCmd
214
+ .command("list")
215
+ .description("bot_sessions 테이블 조회")
216
+ .option("--bot-id <id>", "특정 봇만")
217
+ .option("--limit <n>", "최대 조회 수", "20")
218
+ .option("--format <type>", "출력 형식 (table|json)", "table")
219
+ .action(async (options) => {
220
+ const connected = await (0, database_1.isDbConnected)();
221
+ if (!connected) {
222
+ console.log(chalk_1.default.red("❌ DB 연결 실패"));
223
+ await (0, database_1.closeConnection)();
224
+ process.exit(1);
225
+ }
226
+ try {
227
+ const pool = (0, database_1.getPool)();
228
+ const client = await pool.connect();
229
+ const params = [];
230
+ let where = "";
231
+ if (options.botId) {
232
+ where = "WHERE bot_id = $1";
233
+ params.push(options.botId);
234
+ }
235
+ params.push(parseInt(options.limit));
236
+ const limitIdx = params.length;
237
+ const result = await client.query(`SELECT bot_id, session_key, label, kind, chat_type,
238
+ last_activity::text, message_count
239
+ FROM semo.bot_sessions
240
+ ${where}
241
+ ORDER BY last_activity DESC NULLS LAST
242
+ LIMIT $${limitIdx}`, params);
243
+ client.release();
244
+ if (options.format === "json") {
245
+ console.log(JSON.stringify(result.rows, null, 2));
246
+ }
247
+ else {
248
+ console.log(chalk_1.default.cyan.bold("\n📋 세션 목록\n"));
249
+ if (result.rows.length === 0) {
250
+ console.log(chalk_1.default.yellow(" 세션 없음"));
251
+ }
252
+ else {
253
+ for (const s of result.rows) {
254
+ const ts = s.last_activity
255
+ ? new Date(s.last_activity).toLocaleString("ko-KR")
256
+ : "-";
257
+ console.log(chalk_1.default.cyan(` ${s.bot_id.padEnd(14)}`) +
258
+ chalk_1.default.white(`${(s.label || s.session_key).padEnd(30)}`) +
259
+ chalk_1.default.gray(`${ts} ${s.message_count}msg`));
260
+ }
261
+ }
262
+ console.log();
263
+ }
264
+ }
265
+ catch (err) {
266
+ console.log(chalk_1.default.red(`❌ 조회 실패: ${err}`));
267
+ process.exit(1);
268
+ }
269
+ finally {
270
+ await (0, database_1.closeConnection)();
271
+ }
272
+ });
273
+ }
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ const database_1 = require("./database");
60
60
  const context_1 = require("./commands/context");
61
61
  const bots_1 = require("./commands/bots");
62
62
  const get_1 = require("./commands/get");
63
+ const sessions_1 = require("./commands/sessions");
63
64
  const PACKAGE_NAME = "@team-semicolon/semo-cli";
64
65
  // package.json에서 버전 동적 로드
65
66
  function getCliVersion() {
@@ -675,6 +676,11 @@ async function confirmOverwrite(itemName, itemPath) {
675
676
  if (!fs.existsSync(itemPath)) {
676
677
  return true;
677
678
  }
679
+ // 비인터랙티브 환경(CI, 파이프) — 덮어쓰지 않고 기존 파일 유지
680
+ if (!process.stdin.isTTY) {
681
+ console.log(chalk_1.default.gray(` → ${itemName} 이미 존재 (비인터랙티브 모드: 건너뜀)`));
682
+ return false;
683
+ }
678
684
  const { shouldOverwrite } = await inquirer_1.default.prompt([
679
685
  {
680
686
  type: "confirm",
@@ -775,7 +781,6 @@ program
775
781
  .option("--credentials-gist <gistId>", "Private GitHub Gist에서 팀 DB 접속정보 자동 가져오기")
776
782
  .action(async (options) => {
777
783
  console.log(chalk_1.default.cyan.bold("\n🚀 SEMO 설치 시작\n"));
778
- console.log(chalk_1.default.gray("Gemini 하이브리드 전략: White Box + Black Box\n"));
779
784
  const cwd = process.cwd();
780
785
  // 0.1. 버전 비교
781
786
  await showVersionComparison(cwd);
@@ -809,6 +814,8 @@ program
809
814
  fs.mkdirSync(claudeDir, { recursive: true });
810
815
  console.log(chalk_1.default.green("\n✓ .claude/ 디렉토리 생성됨"));
811
816
  }
817
+ // 2.5. ~/.semo.env DB 접속 설정 (자동 감지 → Gist → 프롬프트) — DB 연결 전에 먼저
818
+ await setupSemoEnv(options.credentialsGist);
812
819
  // 3. Standard 설치 (semo-core + semo-skills)
813
820
  await setupStandard(cwd, options.force);
814
821
  // 4. MCP 설정
@@ -821,10 +828,8 @@ program
821
828
  if (options.gitignore !== false) {
822
829
  updateGitignore(cwd);
823
830
  }
824
- // 7. Hooks 설치 (대화 로깅)
831
+ // 7. Hooks 설치
825
832
  await setupHooks(cwd, false);
826
- // 7.5. ~/.semo.env DB 접속 설정 (자동 감지 → Gist → 프롬프트)
827
- await setupSemoEnv(options.credentialsGist);
828
833
  // 8. CLAUDE.md 생성
829
834
  await setupClaudeMd(cwd, [], options.force);
830
835
  // 9. 설치 검증
@@ -844,11 +849,11 @@ program
844
849
  console.log(chalk_1.default.yellow.bold("\n⚠️ SEMO 설치 완료 (일부 문제 발견)\n"));
845
850
  }
846
851
  console.log(chalk_1.default.cyan("설치된 구성:"));
847
- console.log(chalk_1.default.gray(" [Standard]"));
848
- console.log(chalk_1.default.gray(" semo-core (원칙, 오케스트레이터)"));
849
- console.log(chalk_1.default.gray(" semo-skills (13개 통합 스킬)"));
850
- console.log(chalk_1.default.gray(" semo-agents (14개 페르소나 Agent)"));
851
- console.log(chalk_1.default.gray(" semo-scripts (자동화 스크립트)"));
852
+ console.log(chalk_1.default.gray(" ✓ .claude/skills/ (DB 기반 스킬)"));
853
+ console.log(chalk_1.default.gray(" .claude/agents/ (DB 기반 에이전트)"));
854
+ console.log(chalk_1.default.gray(" .claude/commands/ (슬래시 커맨드)"));
855
+ console.log(chalk_1.default.gray(" .claude/memory/ (컨텍스트 동기화)"));
856
+ console.log(chalk_1.default.gray(" ~/.claude/settings.local.json ( 등록)"));
852
857
  console.log(chalk_1.default.cyan("\n다음 단계:"));
853
858
  console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기 (SessionStart 훅이 자동 sync)"));
854
859
  console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
@@ -928,8 +933,6 @@ async function setupStandard(cwd, force) {
928
933
  }
929
934
  console.log(chalk_1.default.green(` ✓ agents 설치 완료 (${agents.length}개)`));
930
935
  spinner.succeed("Standard 설치 완료 (DB 기반)");
931
- // CLAUDE.md 생성
932
- await generateClaudeMd(cwd);
933
936
  }
934
937
  catch (error) {
935
938
  spinner.fail("Standard 설치 실패");
@@ -1628,54 +1631,11 @@ semo-system/
1628
1631
  async function setupHooks(cwd, isUpdate = false) {
1629
1632
  const action = isUpdate ? "업데이트" : "설치";
1630
1633
  console.log(chalk_1.default.cyan(`\n🪝 Claude Code Hooks ${action}`));
1631
- console.log(chalk_1.default.gray(" 전체 대화 로깅 시스템\n"));
1632
- const hooksDir = path.join(cwd, "semo-system", "semo-hooks");
1633
- // semo-hooks 디렉토리 확인
1634
- if (!fs.existsSync(hooksDir)) {
1635
- console.log(chalk_1.default.yellow(" ⚠ semo-hooks 디렉토리 없음 (건너뜀)"));
1636
- return;
1637
- }
1638
- // 1. npm install
1639
- console.log(chalk_1.default.gray(" → 의존성 설치 중..."));
1640
- try {
1641
- (0, child_process_1.execSync)("npm install", {
1642
- cwd: hooksDir,
1643
- stdio: ["pipe", "pipe", "pipe"],
1644
- });
1645
- }
1646
- catch {
1647
- console.log(chalk_1.default.yellow(" ⚠ npm install 실패 (건너뜀)"));
1648
- return;
1649
- }
1650
- // 2. 빌드
1651
- console.log(chalk_1.default.gray(" → 빌드 중..."));
1652
- try {
1653
- (0, child_process_1.execSync)("npm run build", {
1654
- cwd: hooksDir,
1655
- stdio: ["pipe", "pipe", "pipe"],
1656
- });
1657
- }
1658
- catch {
1659
- console.log(chalk_1.default.yellow(" ⚠ 빌드 실패 (건너뜀)"));
1660
- return;
1661
- }
1662
- // 3. settings.local.json 설정
1663
1634
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
1664
1635
  const settingsPath = path.join(homeDir, ".claude", "settings.local.json");
1665
- const hooksCmd = `node ${path.join(hooksDir, "dist", "index.js")}`;
1666
- // hooks 설정 객체
1636
+ // Core 훅: semo context sync/push — semo-hooks 유무와 무관하게 항상 등록
1667
1637
  const hooksConfig = {
1668
1638
  SessionStart: [
1669
- {
1670
- matcher: "",
1671
- hooks: [
1672
- {
1673
- type: "command",
1674
- command: `${hooksCmd} session-start`,
1675
- timeout: 10,
1676
- },
1677
- ],
1678
- },
1679
1639
  {
1680
1640
  matcher: "",
1681
1641
  hooks: [
@@ -1687,29 +1647,7 @@ async function setupHooks(cwd, isUpdate = false) {
1687
1647
  ],
1688
1648
  },
1689
1649
  ],
1690
- UserPromptSubmit: [
1691
- {
1692
- matcher: "",
1693
- hooks: [
1694
- {
1695
- type: "command",
1696
- command: `${hooksCmd} user-prompt`,
1697
- timeout: 5,
1698
- },
1699
- ],
1700
- },
1701
- ],
1702
1650
  Stop: [
1703
- {
1704
- matcher: "",
1705
- hooks: [
1706
- {
1707
- type: "command",
1708
- command: `${hooksCmd} stop`,
1709
- timeout: 10,
1710
- },
1711
- ],
1712
- },
1713
1651
  {
1714
1652
  matcher: "",
1715
1653
  hooks: [
@@ -1721,19 +1659,37 @@ async function setupHooks(cwd, isUpdate = false) {
1721
1659
  ],
1722
1660
  },
1723
1661
  ],
1724
- SessionEnd: [
1725
- {
1726
- matcher: "",
1727
- hooks: [
1728
- {
1729
- type: "command",
1730
- command: `${hooksCmd} session-end`,
1731
- timeout: 10,
1732
- },
1733
- ],
1734
- },
1735
- ],
1736
1662
  };
1663
+ // semo-hooks 빌드 (선택적 — semo-system 레포에서만 동작)
1664
+ const hooksDir = path.join(cwd, "semo-system", "semo-hooks");
1665
+ if (fs.existsSync(hooksDir)) {
1666
+ let hooksBuilt = false;
1667
+ try {
1668
+ (0, child_process_1.execSync)("npm install", { cwd: hooksDir, stdio: ["pipe", "pipe", "pipe"] });
1669
+ (0, child_process_1.execSync)("npm run build", { cwd: hooksDir, stdio: ["pipe", "pipe", "pipe"] });
1670
+ hooksBuilt = true;
1671
+ }
1672
+ catch {
1673
+ console.log(chalk_1.default.yellow(" ⚠ semo-hooks 빌드 실패 (core 훅만 등록)"));
1674
+ }
1675
+ if (hooksBuilt) {
1676
+ const hooksCmd = `node ${path.join(hooksDir, "dist", "index.js")}`;
1677
+ hooksConfig.SessionStart.unshift({
1678
+ matcher: "",
1679
+ hooks: [{ type: "command", command: `${hooksCmd} session-start`, timeout: 10 }],
1680
+ });
1681
+ hooksConfig.UserPromptSubmit = [
1682
+ { matcher: "", hooks: [{ type: "command", command: `${hooksCmd} user-prompt`, timeout: 5 }] },
1683
+ ];
1684
+ hooksConfig.Stop.unshift({
1685
+ matcher: "",
1686
+ hooks: [{ type: "command", command: `${hooksCmd} stop`, timeout: 10 }],
1687
+ });
1688
+ hooksConfig.SessionEnd = [
1689
+ { matcher: "", hooks: [{ type: "command", command: `${hooksCmd} session-end`, timeout: 10 }] },
1690
+ ];
1691
+ }
1692
+ }
1737
1693
  // 기존 설정 로드 또는 새로 생성
1738
1694
  let existingSettings = {};
1739
1695
  const claudeConfigDir = path.join(homeDir, ".claude");
@@ -1934,133 +1890,132 @@ async function setupClaudeMd(cwd, _extensions, force) {
1934
1890
  return;
1935
1891
  }
1936
1892
  }
1937
- const orchestratorRefSection = `**반드시 읽어야 할 파일**: \`semo-system/semo-core/agents/orchestrator/orchestrator.md\`
1938
-
1939
- 파일에서 라우팅 테이블, 의도 분류, 메시지 포맷을 확인하세요.`;
1940
- const claudeMdContent = `# SEMO Project Configuration
1893
+ const projectName = path.basename(cwd);
1894
+ const installDate = new Date().toISOString().split("T")[0];
1895
+ const claudeMdContent = `# ${projectName} Claude Configuration
1941
1896
 
1942
- > SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
1897
+ > SEMO v${VERSION} 설치됨 (${installDate})
1943
1898
 
1944
1899
  ---
1945
1900
 
1946
- ## 🔴 MANDATORY: Memory Context (항시 참조)
1947
-
1948
- > **⚠️ 세션 시작 시 반드시 \`.claude/memory/\` 폴더의 파일들을 먼저 읽으세요. 예외 없음.**
1901
+ ## SEMO란?
1949
1902
 
1950
- ### 필수 참조 파일
1903
+ **SEMO (Semicolon Orchestrate)** 는 OpenClaw 봇팀과 로컬 Claude Code 세션이
1904
+ **팀 Core DB를 단일 진실 공급원(Single Source of Truth)으로 공유**하는 컨텍스트 동기화 시스템이다.
1951
1905
 
1952
1906
  \`\`\`
1953
- .claude/memory/
1954
- ├── context.md # 프로젝트 상태, 기술 스택, 진행 중 작업
1955
- ├── decisions.md # 아키텍처 결정 기록 (ADR)
1956
- ├── projects.md # GitHub Projects 설정
1957
- └── rules/ # 프로젝트별 커스텀 규칙
1907
+ 로컬 Claude Code 세션
1908
+ semo context sync / push
1909
+ Core DB (PostgreSQL, semo 스키마)
1910
+ 세션 시작/종료 훅
1911
+ OpenClaw 봇팀 (7개 봇)
1912
+ workclaw · reviewclaw · planclaw · designclaw
1913
+ infraclaw · growthclaw · semiclaw
1958
1914
  \`\`\`
1959
1915
 
1960
- **이 파일들은 세션의 컨텍스트를 유지하는 장기 기억입니다. 세션마다 반드시 읽고 시작하세요.**
1916
+ **이 CLAUDE.md가 설치된 프로젝트는 OpenClaw 봇팀의 컨텍스트를 실시간으로 공유받는다.**
1961
1917
 
1962
1918
  ---
1963
1919
 
1964
- ## 🔴 MANDATORY: Orchestrator-First Execution
1920
+ ## 자동 동기화
1965
1921
 
1966
- > **⚠️ 규칙은 모든 사용자 요청에 적용됩니다. 예외 없음.**
1922
+ 세션 시작/종료 Core DB와 자동 동기화됩니다.
1967
1923
 
1968
- ### 실행 흐름 (필수)
1924
+ | 시점 | 동작 |
1925
+ |------|------|
1926
+ | 세션 시작 | \`semo context sync\` → \`.claude/memory/\` 최신화 |
1927
+ | 세션 종료 | \`semo context push\` → \`decisions.md\` 변경분 DB 저장 |
1928
+
1929
+ ---
1930
+
1931
+ ## Memory Context
1932
+
1933
+ \`.claude/memory/\` 파일들은 **팀 Core DB (\`semo\` 스키마)에서 자동으로 채워집니다**.
1934
+ 직접 편집하지 마세요 — 세션 시작 시 덮어씌워집니다.
1935
+
1936
+ | 파일 | DB 소스 | 방향 |
1937
+ |------|---------|------|
1938
+ | \`team.md\` | \`kb WHERE domain='team'\` | DB → 로컬 (읽기 전용) |
1939
+ | \`projects.md\` | \`kb WHERE domain='project'\` | DB → 로컬 (읽기 전용) |
1940
+ | \`decisions.md\` | \`kb WHERE domain='decision'\` | **양방향** (편집 가능, Stop 시 DB 저장) |
1941
+ | \`infra.md\` | \`kb WHERE domain='infra'\` | DB → 로컬 (읽기 전용) |
1942
+ | \`process.md\` | \`kb WHERE domain='process'\` | DB → 로컬 (읽기 전용) |
1943
+ | \`bots.md\` | \`semo.bot_status\` | DB → 로컬 (봇 상태) |
1944
+ | \`ontology.md\` | \`semo.ontology\` | DB → 로컬 (읽기 전용) |
1945
+
1946
+ **decisions.md 만 편집 가능합니다.** 아키텍처 결정(ADR)을 여기에 기록하세요.
1947
+
1948
+ ---
1949
+
1950
+ ## 설치된 구성
1969
1951
 
1970
1952
  \`\`\`
1971
- 1. 사용자 요청 수신
1972
- 2. Orchestrator가 의도 분석 후 적절한 Agent/Skill 라우팅
1973
- 3. Agent/Skill이 작업 수행
1974
- 4. 실행 결과 반환
1953
+ .claude/
1954
+ ├── CLAUDE.md # 파일
1955
+ ├── settings.json # MCP 서버 설정 + SessionStart/Stop
1956
+ ├── memory/ # Core DB → 로컬 자동 동기화 컨텍스트
1957
+ ├── skills/ # SEMO 스킬 (semo-system/semo-skills/ 링크)
1958
+ ├── agents/ # SEMO 에이전트 (semo-system/meta/agents/ 링크)
1959
+ └── commands/SEMO # 슬래시 커맨드 (semo-system/semo-core/commands/)
1975
1960
  \`\`\`
1976
1961
 
1977
- ### Orchestrator 참조
1962
+ ---
1978
1963
 
1979
- ${orchestratorRefSection}
1964
+ ## 프로젝트 규칙 (팀이 채워야 함)
1980
1965
 
1981
- ---
1966
+ > 아래 섹션은 이 프로젝트 고유의 규칙을 기록하세요.
1967
+ > 팀 공통 규칙은 \`memory/process.md\`에 있습니다.
1982
1968
 
1983
- ## 🔴 NON-NEGOTIABLE RULES
1969
+ ### 기술 스택
1984
1970
 
1985
- ### 1. Orchestrator-First Policy
1971
+ <!-- 예: Next.js 14, PostgreSQL, TypeScript strict mode -->
1986
1972
 
1987
- > **모든 요청은 반드시 Orchestrator를 통해 라우팅됩니다. 직접 처리 금지.**
1973
+ ### 브랜치 전략
1988
1974
 
1989
- **직접 처리 금지 항목**:
1990
- - 코드 작성/수정 → \`implementation-master\` 또는 \`coder\` 스킬
1991
- - Git 커밋/푸시 → \`git-workflow\` 스킬
1992
- - 품질 검증 → \`quality-master\` 또는 \`verify\` 스킬
1993
- - 명세 작성 → \`spec-master\`
1994
- - 일반 작업 → Orchestrator 분석 후 라우팅
1975
+ <!-- 예: main(prod) / dev(staging) / feat/* -->
1995
1976
 
1996
- ### 2. Pre-Commit Quality Gate
1977
+ ### 코딩 컨벤션
1997
1978
 
1998
- > **코드 변경이 포함된 커밋 반드시 Quality Gate를 통과해야 합니다.**
1979
+ <!-- 예: ESLint airbnb, 함수형 컴포넌트 필수, any 금지 -->
1999
1980
 
2000
- \`\`\`bash
2001
- # 필수 검증 순서
2002
- npm run lint # 1. ESLint 검사
2003
- npx tsc --noEmit # 2. TypeScript 타입 체크
2004
- npm run build # 3. 빌드 검증 (Next.js/TypeScript 프로젝트)
2005
- \`\`\`
1981
+ ### 아키텍처 특이사항
2006
1982
 
2007
- **차단 항목**:
2008
- - \`--no-verify\` 플래그 사용 금지
2009
- - Quality Gate 우회 시도 거부
2010
- - "그냥 커밋해줘", "빌드 생략해줘" 등 거부
1983
+ <!-- 예: DB 직접 접근 금지 — 반드시 API route 통해야 함 -->
2011
1984
 
2012
1985
  ---
2013
1986
 
2014
- ## 설치된 구성
2015
-
2016
- ### Standard (필수)
2017
- - **semo-core**: 원칙, 오케스트레이터, 공통 커맨드
2018
- - **semo-skills**: 13개 통합 스킬
2019
- - 행동: coder, tester, planner, deployer, writer
2020
- - 운영: memory, notify-slack, feedback, version-updater, semo-help, semo-architecture-checker, circuit-breaker, list-bugs
1987
+ ## Quality Gate
2021
1988
 
2022
- ## 구조
1989
+ 코드 변경 커밋 전 필수:
2023
1990
 
2024
- \`\`\`
2025
- .claude/
2026
- ├── settings.json # MCP 서버 설정 (Black Box)
2027
- ├── memory/ # Context Mesh (장기 기억)
2028
- │ ├── context.md # 프로젝트 상태
2029
- │ ├── decisions.md # 아키텍처 결정
2030
- │ └── rules/ # 프로젝트별 규칙
2031
- ├── agents → semo-system/semo-core/agents
2032
- ├── skills → semo-system/semo-skills
2033
- └── commands/SEMO → semo-system/semo-core/commands/SEMO
2034
-
2035
- semo-system/ # White Box (읽기 전용)
2036
- ├── semo-core/ # Layer 0: 원칙, 오케스트레이션
2037
- └── semo-skills/ # Layer 1: 통합 스킬
1991
+ \`\`\`bash
1992
+ npm run lint # ESLint
1993
+ npx tsc --noEmit # TypeScript
1994
+ npm run build # 빌드 검증
2038
1995
  \`\`\`
2039
1996
 
2040
- ## 사용 가능한 커맨드
1997
+ \`--no-verify\` 사용 금지.
1998
+
1999
+ ---
2000
+
2001
+ ## 슬래시 커맨드
2041
2002
 
2042
2003
  | 커맨드 | 설명 |
2043
2004
  |--------|------|
2044
2005
  | \`/SEMO:help\` | 도움말 |
2045
2006
  | \`/SEMO:feedback\` | 피드백 제출 |
2046
- | \`/SEMO:update\` | SEMO 업데이트 |
2047
- | \`/SEMO:onboarding\` | 온보딩 가이드 |
2048
- | \`/SEMO:dry-run {프롬프트}\` | 명령 검증 (라우팅 시뮬레이션) |
2049
-
2050
- ## Context Mesh 사용
2051
-
2052
- SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
2007
+ | \`/SEMO:health\` | 환경 헬스체크 |
2053
2008
 
2054
- - **context.md**: 프로젝트 상태, 진행 중인 작업
2055
- - **decisions.md**: 아키텍처 결정 기록 (ADR)
2056
- - **rules/**: 프로젝트별 커스텀 규칙
2057
-
2058
- memory 스킬이 자동으로 이 파일들을 관리합니다.
2009
+ ---
2059
2010
 
2060
- ## References
2011
+ ## 복구 명령어
2061
2012
 
2062
- - [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
2063
- - [SEMO Skills](semo-system/semo-skills/)
2013
+ \`\`\`bash
2014
+ semo doctor # 환경 진단 (DB 연결, 설치 상태)
2015
+ semo config db # DB URL 재설정
2016
+ semo context sync # memory/ 수동 최신화
2017
+ semo bots status # 봇 상태 조회
2018
+ \`\`\`
2064
2019
  `;
2065
2020
  fs.writeFileSync(claudeMdPath, claudeMdContent);
2066
2021
  console.log(chalk_1.default.green("✓ .claude/CLAUDE.md 생성됨"));
@@ -3001,6 +2956,7 @@ ontoCmd
3001
2956
  (0, context_1.registerContextCommands)(program);
3002
2957
  (0, bots_1.registerBotsCommands)(program);
3003
2958
  (0, get_1.registerGetCommands)(program);
2959
+ (0, sessions_1.registerSessionsCommands)(program);
3004
2960
  // === semo skills — DB 시딩 ===
3005
2961
  /**
3006
2962
  * SKILL.md frontmatter 파싱 (YAML 파서 없이 regex 기반)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "4.1.0",
3
+ "version": "4.1.3",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {