genetics-mcp 2.8.0 → 2.8.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.
Files changed (2) hide show
  1. package/genetics.mjs +40 -16
  2. package/package.json +1 -1
package/genetics.mjs CHANGED
@@ -1537,7 +1537,36 @@ function beatWell() {
1537
1537
  /* 자동 올리기 — **서버 사본이 낡았을 때만.** 사용자 결정(2026-08-20)이라 기본이 켜짐이고,
1538
1538
  * 끄는 길을 함께 둔다(`GENETICS_NO_AUTO_PUSH=1`). 이 값은 「내부 대화가 서버에 쌓인다」를
1539
1539
  * 뜻하므로, 끄는 길이 없으면 결정이 아니라 함정이 된다. */
1540
- const AUTO_PUSH = !cfg.GENETICS_NO_AUTO_PUSH;
1540
+ /**
1541
+ * 이 텍스트와 환경변수로 **자동 올리기가 켜졌는지**. 순수 함수 — 시험이 걷는다.
1542
+ *
1543
+ * ⚠ 위 `cfg` 와 **같은 우선순위**: 환경변수가 파일을 이긴다. 반대로 짰다가 실제 사고를
1544
+ * 냈다(2026-08-12) — 오버라이드가 안 먹는 설정은 안전한 기본값이 아니라 함정이다.
1545
+ */
1546
+ function autoPushFromText(text, env) {
1547
+ const K = 'GENETICS_NO_AUTO_PUSH';
1548
+ if (env && Object.prototype.hasOwnProperty.call(env, K)) return !env[K];
1549
+ let v = '';
1550
+ for (const line of String(text || '').split('\n')) {
1551
+ const m = line.match(/^\s*([A-Z_]+)\s*=\s*(.*)\s*$/);
1552
+ if (m && m[1] === K) v = m[2];
1553
+ }
1554
+ return !v;
1555
+ }
1556
+
1557
+ /**
1558
+ * **스위치를 매번 다시 읽는다.** 예전에는 시작할 때 한 번만 읽어서, 켠 사람이 같은
1559
+ * 「꺼져 있습니다」를 다시 보고 「안 켜졌다」고 읽었다 — 2026-08-24 내가 그 함정에 빠졌다.
1560
+ *
1561
+ * 안내문으로 덮는 것보다 **켜면 켜지는 것**이 맞다. 파일은 200바이트 남짓이고 심박은
1562
+ * 20초에 한 번이라 비용이 없다. 자격은 그대로 시작할 때 값을 쓴다 — 여기서 다시 읽는 것은
1563
+ * **사람이 쥐는 스위치 하나**뿐이다.
1564
+ */
1565
+ function autoPushOn() {
1566
+ let text = '';
1567
+ try { text = fs.readFileSync(ENV_FILE, 'utf8'); } catch { /* 없으면 env 로만 */ }
1568
+ return autoPushFromText(text, process.env);
1569
+ }
1541
1570
 
1542
1571
  /*
1543
1572
  * ⚠⚠ **꺼져 있으면 말한다.** 전에는 `if (!AUTO_PUSH) return;` 한 줄로 조용히 빠졌다.
@@ -1550,26 +1579,21 @@ const AUTO_PUSH = !cfg.GENETICS_NO_AUTO_PUSH;
1550
1579
  * 이 저장소는 세포를 필수로 만들며 규칙을 세웠다: **「없으면 막지 말고 알려주면서 유도한다」.**
1551
1580
  * 같은 규칙이 여기에도 걸린다. 끄는 것은 사람의 선택이지만, **끈 줄 모르는 것은 선택이 아니다.**
1552
1581
  */
1553
- let autoPushSaid = false;
1582
+ let autoPushSaid = null; // 마지막으로 말한 상태 — 스위치가 바뀌면 다시 말한다
1554
1583
  function autoPushSilence() {
1555
- if (autoPushSaid) return;
1556
- autoPushSaid = true;
1584
+ if (autoPushSaid === false) return;
1585
+ autoPushSaid = false;
1557
1586
  // stdout 은 프로토콜이다 — 사람이 보는 자리(stderr)로만 말한다.
1558
1587
  process.stderr.write(
1559
1588
  'genetics: 대화 본문 자동 올리기가 꺼져 있다(GENETICS_NO_AUTO_PUSH). '
1560
1589
  + '이 기계의 세션은 서버에 안 쌓이고, 그래서 두뇌가 그 대화로 팀 기억을 못 굽는다.\n'
1561
- /*
1562
- * ⚠ **설정은 시작할 때 한 번만 읽는다**(위 `cfg`). 파일을 고쳐도 돌고 있는 이 프로세스는
1563
- * 옛 값을 든 채다 — 그래서 켠 사람이 같은 문장을 다시 보고 「안 켜졌다」고 읽는다.
1564
- * 실측 2026-08-24: 내가 그 함정에 빠졌다. 「끈 줄 모르는 것은 선택이 아니다」와 같은 이유로
1565
- * **켠 줄 모르는 것도 선택이 아니다.**
1566
- */
1567
- + ' (설정을 방금 바꿨다면 이 창을 다시 열어야 반영된다 — 시작할 때 한 번만 읽는다.)\n');
1590
+ // 스위치는 심박마다 다시 읽는다 — 켜면 다음 심박(20초)에 스스로 켜진다.
1591
+ + ' (지금 켜면 다음 심박에 스스로 반영된다 — 창을 다시 열 필요 없다.)\n');
1568
1592
  }
1569
1593
  const autoPushAt = new Map();
1570
1594
 
1571
1595
  async function autoPushIfAhead(reply, recs) {
1572
- if (!AUTO_PUSH) { autoPushSilence(); return; }
1596
+ if (!autoPushOn()) { autoPushSilence(); return; }
1573
1597
  if (!reply) return;
1574
1598
  const now = Date.now();
1575
1599
  for (const v of reply) {
@@ -1606,7 +1630,7 @@ async function beatTick() {
1606
1630
  beatRunning = true;
1607
1631
  try {
1608
1632
  // 새 세션을 같은 폴더의 앞 세션과 같은 소속처로 등록한다(색인만 — 올리기는 아래가 한다).
1609
- if (AUTO_PUSH) { try { indexAdoptCurrent(); } catch { /* 등록 실패가 심박을 멈추면 안 된다 */ } }
1633
+ if (autoPushOn()) { try { indexAdoptCurrent(); } catch { /* 등록 실패가 심박을 멈추면 안 된다 */ } }
1610
1634
  const recs = beatTargets();
1611
1635
  if (!recs.length) return;
1612
1636
  // **안 바뀌었으면 안 보낸다.** 이 설계가 실제로 버는 것은 바이트가 아니라 이것이다.
@@ -2365,11 +2389,11 @@ async function callTool(name, args = {}) {
2365
2389
  * 지식에 없지」를 사람이 짐작해야 한다(2026-08-23 실측: 하루가 통째로 비었다).
2366
2390
  * **사실만 적는다** — 켜라고 하지 않는다. 끄는 것은 사람의 선택이다.
2367
2391
  */
2368
- const pushOff = AUTO_PUSH ? '' :
2392
+ const pushOff = autoPushOn() ? '' :
2369
2393
  '\n대화 본문 자동 올리기는 꺼져 있습니다(GENETICS_NO_AUTO_PUSH) — '
2370
2394
  + '이 기계의 세션은 서버에 안 쌓이고, 두뇌가 그 대화로 팀 기억을 굽지 않습니다.'
2371
- // ⚠ 설정은 **시작할 때 한 번** 읽는다. 방금 켠 사람이 이 문장을 또 보고 「안 켜졌다」고 읽는다.
2372
- + '\n (방금 설정을 바꿨다면 이 창을 다시 열어야 반영됩니다 — 시작할 때 한 번만 읽습니다.)';
2395
+ // 스위치는 심박마다 다시 읽는다 — 창을 다시 열 필요가 없다.
2396
+ + '\n (지금 켜시면 다음 심박에 스스로 반영됩니다 — 창을 다시 열 필요 없습니다.)';
2373
2397
  return `${me.user.name}(${me.user.email}) · ${me.user.role} 으로 붙어 있습니다.\n`
2374
2398
  + `기계: ${me.device_label || '(이름 없음)'} · 만료 ${c.expires_at}${alsoKey}${pushOff}`;
2375
2399
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genetics-mcp",
3
- "version": "2.8.0",
3
+ "version": "2.8.1",
4
4
  "description": "문서 포털 발행·세션 이식·사례 기억 MCP — 데이터를 주면 규격 문서가 나온다 (짝인 스킬 동봉)",
5
5
  "author": "주식회사 LIM (LIM Inc.)",
6
6
  "license": "SEE LICENSE IN LICENSE",