genetics-mcp 2.5.0 → 2.6.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.
Binary file
package/bin/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "aarch64-apple-darwin": "088b89bdac75d16ed88e832471e46c0221c068a0caf888960b629a29e77f6eb2",
3
- "aarch64-unknown-linux-musl": "4c2d40a9b9f09610c9de57fac3c14d385983d5c83d8adbd359d57a2458ba345f",
4
- "x86_64-apple-darwin": "726ce268009952176e62887fef0f8dd97b7ee2c084d46a7af66c4f467697cec5",
5
- "x86_64-unknown-linux-musl": "ec9c9a3105dbbaaaad284a82544268e77ed9295591430789d9a95761eaffc89e"
2
+ "aarch64-apple-darwin": "c0f7ca318d5e4e040b6118e835cbad3e467597d61e94d150c555ed050b5d1f57",
3
+ "aarch64-unknown-linux-musl": "5588db11a4aad41a65609d4cf14f389d106fb8b74023aa745ae4765738243006",
4
+ "x86_64-apple-darwin": "857d738a752776813e9ee52e972e93d046502aa8364ec9fe2d1ece8a0d770914",
5
+ "x86_64-unknown-linux-musl": "f01bf90ab7014764487773b9ac91ad8532ac6a727eedd6ff50151c3e6eab127e"
6
6
  }
Binary file
Binary file
package/genetics.mjs CHANGED
@@ -21,7 +21,7 @@
21
21
  * 의존성 0 — MCP 는 stdio 위의 JSON-RPC 2.0 이라 표준 라이브러리로 충분하다.
22
22
  */
23
23
 
24
- import { spawnSync } from 'node:child_process';
24
+ import { spawn, spawnSync } from 'node:child_process';
25
25
  import crypto from 'node:crypto';
26
26
  import fs from 'node:fs';
27
27
  import os from 'node:os';
@@ -1302,6 +1302,26 @@ function transcriptOf(uuid) {
1302
1302
  }
1303
1303
 
1304
1304
  /** 지금 심박을 보낼 세션들. 이 창의 세션 + 최근에 움직인 사본. */
1305
+ /**
1306
+ * 지금 이 대화의 **출처** — `{session_uuid, head_id}`. 모르면 빈 객체.
1307
+ *
1308
+ * 인용 표(020)는 「이 대화의 **이 턴**이 그 사례의 근거다」를 적는다. 그런데 그 둘을
1309
+ * 도구 인자로 두면 **모델이 지어낸다.** 그래서 여기서 로컬 상태로 채운다 —
1310
+ * 세션 id 는 하네스가 준 환경변수, 턴은 전사의 마지막 발화 id 다.
1311
+ *
1312
+ * ⚠ **모르면 빈 객체를 준다.** 서버는 둘 다 있을 때만 적는다(`citation_ok`).
1313
+ * 지어낸 출처 한 줄이 뒤에서 회수 보호의 근거가 되면, 걷어야 할 것을 안 걷게 된다.
1314
+ */
1315
+ function myCitation() {
1316
+ const uuid = process.env.CLAUDE_CODE_SESSION_ID || '';
1317
+ if (!/^[0-9a-f-]{36}$/i.test(uuid)) return {};
1318
+ const f = transcriptOf(uuid);
1319
+ if (!f) return {};
1320
+ const head = transcriptHead(f);
1321
+ if (!head?.id) return {};
1322
+ return { session_uuid: uuid, head_id: head.id };
1323
+ }
1324
+
1305
1325
  function beatTargets() {
1306
1326
  const idx = indexRead();
1307
1327
  const mine = process.env.CLAUDE_CODE_SESSION_ID || '';
@@ -1484,6 +1504,45 @@ async function beatTick() {
1484
1504
  }
1485
1505
  }
1486
1506
 
1507
+ /*
1508
+ * ── 훅 없이 관측한다 (2026-08-23) ──────────────────────────────────────
1509
+ *
1510
+ * **Codex 는 훅을 신뢰하기 전엔 안 돌린다**(`Hooks need review`), 그리고 그 화면이
1511
+ * ChatGPT.app 경로에서는 안 뜬다(실측). 그래서 codex 세션은 훅으로 관측을 영영 못 올린다 —
1512
+ * 그런데 이 프로젝트가 시작된 사고가 정확히 **옆 codex 가 배포한 것을 내가 몰랐던 일**이다.
1513
+ *
1514
+ * MCP 는 codex 밑에서도 돈다(실측: MCP PID 13409, 부모가 codex 45855). 훅도 신뢰도
1515
+ * 안 지나는 자리다. 그래서 여기서 세포를 띄워 이 저장소의 최근 전사를 훑게 한다.
1516
+ *
1517
+ * ⚠ **심박에 얹지 않고 따로 돈다.** `beatTick()` 은 올린 세션이 없으면 일찍 빠져나가는데
1518
+ * (`if (!recs.length) return`), codex 는 대개 그 상태다. 얹었으면 영영 안 돌았을 것이다.
1519
+ *
1520
+ * 비용: 저장소를 아는 걸음으로 바꿔 **1,436ms → 320ms**(실측). 그리고 세포가 저장소마다
1521
+ * 자물쇠를 잡으므로, MCP 가 세션 수만큼 떠 있어도 **한 번만** 일한다.
1522
+ */
1523
+ const OBSERVE_EVERY_MS = 20_000;
1524
+ let observeTimer = null;
1525
+
1526
+ function observeTick() {
1527
+ const { bin } = cellCheck();
1528
+ if (!bin) return; // 세포가 없으면 할 수 있는 것이 없다
1529
+ try {
1530
+ // 떼어낸 자식이다 — MCP 는 도구 응답의 임계 경로에 있고, 이 일은 그것을 기다릴 이유가 없다.
1531
+ // stdio 를 전부 막는다: MCP 의 stdout 은 **프로토콜**이라 한 글자만 흘려도 세션이 깨진다.
1532
+ const c = spawn(bin, ['observe', '--root', process.cwd(), '--sync', '--quiet'],
1533
+ { stdio: 'ignore', detached: true });
1534
+ c.unref?.();
1535
+ c.on?.('error', () => {});
1536
+ } catch { /* 최선만 다한다 */ }
1537
+ }
1538
+
1539
+ function startObserve() {
1540
+ if (cfg.GENETICS_NO_BEAT) return; // 심박을 끄는 사람은 이것도 안 원한다
1541
+ observeTick();
1542
+ observeTimer = setInterval(observeTick, OBSERVE_EVERY_MS);
1543
+ observeTimer.unref?.(); // 창을 닫으면 노드가 남지 않게
1544
+ }
1545
+
1487
1546
  function startBeat() {
1488
1547
  if (cfg.GENETICS_NO_BEAT) return; // 심박을 원치 않는 사람의 탈출구
1489
1548
  const cred = credential();
@@ -2254,6 +2313,9 @@ async function callTool(name, args = {}) {
2254
2313
  const r = await api('POST', '/api/wiki/case?tenant=' + encodeURIComponent(args.tenant), {
2255
2314
  title: args.title, symptom: args.symptom, cause: args.cause || '', fix: args.fix,
2256
2315
  evidence: args.evidence || '', tags: args.tags || [],
2316
+ // ★ **출처는 우리가 채운다 — 모델에게 안 묻는다.** 스키마에 노출하면 지어낸다
2317
+ // (DESIGN-session-as-vcs.md:136-141). 모르면 안 보내고, 서버는 안 적는다.
2318
+ ...myCitation(),
2257
2319
  });
2258
2320
  return `${r.note} — ${r.tenant} · ${r.path}\n`
2259
2321
  + '다음에 같은 증상을 만나면 search_cases 로 이 장을 먼저 찾는다.';
@@ -2729,6 +2791,7 @@ if (IS_MAIN) {
2729
2791
  * 꺼진 것이지 고장이 아니다(그림 자격이 없을 때와 같은 태도).
2730
2792
  */
2731
2793
  startBeat();
2794
+ startObserve(); // 훅이 안 걸리는 도구(Codex)도 관측을 올린다 — 위 머리말 참조
2732
2795
  process.stdin.setEncoding('utf8');
2733
2796
  process.stdin.on('data', (chunk) => { buf += chunk; pump(); });
2734
2797
  process.stdin.on('end', () => { pump(true); stopBeat().catch(() => {}); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genetics-mcp",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "문서 포털 발행·세션 이식·사례 기억 MCP — 데이터를 주면 규격 문서가 나온다 (짝인 스킬 동봉)",
5
5
  "author": "주식회사 LIM (LIM Inc.)",
6
6
  "license": "SEE LICENSE IN LICENSE",