@horizon_works/banto 0.7.0 → 0.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 (31) hide show
  1. package/add_to_config.js +107 -0
  2. package/gate_core.js +220 -1
  3. package/law_bridge.js +183 -0
  4. package/license/client.js +20 -1
  5. package/package.json +3 -2
  6. package/server.js +140 -37
  7. package/payload/compliance/SKILL.md +0 -113
  8. package/payload/compliance/dict/00_/345/213/225/344/275/234/347/242/272/350/252/215.json +0 -14
  9. package/payload/compliance/dict/06_house_rules.json +0 -16
  10. package/payload/compliance/dict/_sample/04_common_keihyo.json +0 -270
  11. package/payload/compliance/dict/_sample/README.md +0 -72
  12. package/payload/compliance/lint.js +0 -265
  13. package/payload/disciplines//347/225/252/351/240/255/343/201/256/344/275/234/346/263/225.md +0 -64
  14. package/payload/disciplines//350/207/252/345/267/261/345/201/245/350/250/272.md +0 -74
  15. package/payload/disciplines//351/200/261/346/254/241/343/203/241/343/203/263/343/203/206.md +0 -60
  16. package/payload/migration/protocol.md +0 -35
  17. package/payload/skill-maker/SKILL_base_anthropic.md +0 -357
  18. package/payload/skill-maker/skill-maker-v1.0.md +0 -305
  19. package/payload/skills/_/343/201/202/343/201/250/343/201/247/350/266/263/343/201/233/343/202/213/343/202/202/343/201/256.md +0 -47
  20. package/payload/skills/image-gen/SKILL.md +0 -147
  21. package/payload/skills/image-gen/gen.js +0 -225
  22. package/payload/skills/remember/SETUP_Google/351/200/243/346/220/272.md +0 -106
  23. package/payload/skills/remember/SKILL.md +0 -158
  24. package/payload/skills/remember/handoff.js +0 -149
  25. package/payload/skills/remember/tasks.js +0 -320
  26. package/payload/skills/slide-deck/SKILL.md +0 -160
  27. package/payload/skills/transcribe/README.md +0 -182
  28. package/payload/skills/transcribe/dict.json +0 -10
  29. package/payload/skills/transcribe/enroll_speaker.py +0 -76
  30. package/payload/skills/transcribe/identify_speakers.py +0 -153
  31. package/payload/skills/transcribe/transcribe.py +0 -126
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mcp_config.json へ番頭を1ブロック足す(★既にあるものを消さない)
4
+ *
5
+ * 🚨 由来: 2026-08-28。ターミナル1行で入れられるようにしたら、
6
+ * ★**既存の設定を消した**。原因=ファイルが BOM つき UTF-8 で `JSON.parse` が落ち、
7
+ * ★`catch` が「無い」とみなして空から作り直していた。
8
+ * ★★**読めなかったことと、無いことは別**(→ premise-check §3)。
9
+ *
10
+ * ★この道具の約束
11
+ * ① 読めなければ **書かない**(★控えを作って、人に見てもらう)
12
+ * ② 既にある `mcpServers` は **そのまま残す**
13
+ * ③ 書く前に **控えを取る**(.bak-日時)
14
+ * ④ 書いたあと **読み直して確かめる**(★書けたと言う前に)
15
+ *
16
+ * 使い方:
17
+ * node add_to_config.js <鍵.署名> [設定ファイルのパス]
18
+ * ★パスを省くと、この環境の既定の場所を探す
19
+ */
20
+ const fs = require('fs');
21
+ const os = require('os');
22
+ const path = require('path');
23
+
24
+ // ★`npx ... setup <鍵>` で呼ばれると argv[2] が 'setup' になる。★1つずらす
25
+ const 引数 = process.argv.slice(2).filter((x) => x !== 'setup');
26
+ const 鍵 = 引数[0] || process.env.BANTO_KEY || '';
27
+ if (!鍵) {
28
+ console.error('使い方: npx -y @horizon_works/banto setup "<鍵.署名>"');
29
+ process.exit(1);
30
+ }
31
+
32
+ // ★置き場を探す(★見つからなければ、いちばんありそうな所に作る)
33
+ function 設定の場所() {
34
+ if (引数[1]) return 引数[1];
35
+ if (process.env.BANTO_CONFIG) return process.env.BANTO_CONFIG;
36
+ const home = os.homedir();
37
+ const 候補 = [
38
+ path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),
39
+ path.join(home, '.gemini', 'mcp_config.json'),
40
+ path.join(process.cwd(), 'mcp_config.json'),
41
+ ];
42
+ for (const p of 候補) { try { if (fs.existsSync(p)) return p; } catch (_) {} }
43
+ return 候補[0]; // ★無ければ、いちばん標準の所に作る
44
+ }
45
+
46
+ const P = 設定の場所();
47
+ const 印 = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
48
+
49
+ let j = { mcpServers: {} };
50
+ let 既にある = false;
51
+ if (fs.existsSync(P)) {
52
+ 既にある = true;
53
+ let s;
54
+ try { s = fs.readFileSync(P, 'utf8'); }
55
+ catch (e) { console.error('🚨 読めません: ' + e.message + '\n★何も書いていません。'); process.exit(1); }
56
+ // ★BOM を外してから読む(★これで落ちて、前の版は中身を消した)
57
+ s = s.replace(/^/, '');
58
+ try { j = JSON.parse(s); }
59
+ catch (e) {
60
+ console.error('🚨 いまの設定ファイルが読めませんでした(' + e.message + ')');
61
+ console.error(' ' + P);
62
+ console.error('★何も書いていません。★消さないために止めました。');
63
+ console.error('★このファイルを開いて、形が壊れていないかご確認ください。');
64
+ process.exit(1);
65
+ }
66
+ if (typeof j !== 'object' || j === null || Array.isArray(j)) {
67
+ console.error('🚨 設定の形が思っていたものと違います。★何も書いていません。');
68
+ process.exit(1);
69
+ }
70
+ }
71
+
72
+ j.mcpServers = j.mcpServers || {};
73
+ const 前からあった = Object.keys(j.mcpServers).filter((k) => k !== 'banto');
74
+
75
+ j.mcpServers.banto = {
76
+ command: 'npx',
77
+ args: ['-y', '@horizon_works/banto'],
78
+ env: { BANTO_KEY: 鍵 },
79
+ };
80
+
81
+ // ★控えを取ってから書く
82
+ if (既にある) {
83
+ try { fs.copyFileSync(P, P + '.bak-' + 印); }
84
+ catch (e) { console.error('🚨 控えが取れませんでした: ' + e.message + '\n★何も書いていません。'); process.exit(1); }
85
+ }
86
+ fs.mkdirSync(path.dirname(P), { recursive: true });
87
+ fs.writeFileSync(P, JSON.stringify(j, null, 2) + '\n', 'utf8');
88
+
89
+ // ★書いたと言う前に、読み直して確かめる
90
+ let 確認;
91
+ try { 確認 = JSON.parse(fs.readFileSync(P, 'utf8').replace(/^/, '')); }
92
+ catch (e) { console.error('🚨 書いたものが読めません: ' + e.message); process.exit(1); }
93
+
94
+ const 残っている = 前からあった.filter((k) => 確認.mcpServers && 確認.mcpServers[k]);
95
+ const 消えた = 前からあった.filter((k) => !(確認.mcpServers && 確認.mcpServers[k]));
96
+
97
+ console.log('✅ 書きました: ' + P);
98
+ console.log(' banto: ' + (確認.mcpServers.banto ? '入りました' : '❌ 入っていません'));
99
+ console.log(' 前からあったもの: ' + (前からあった.length ? 前からあった.join(', ') : '(なし)'));
100
+ console.log(' そのまま残った : ' + (残っている.length ? 残っている.join(', ') : '(なし)'));
101
+ if (消えた.length) {
102
+ console.error('🚨 消えたものがあります: ' + 消えた.join(', '));
103
+ console.error(' ★控えから戻せます: ' + P + '.bak-' + 印);
104
+ process.exit(1);
105
+ }
106
+ if (既にある) console.log(' 控え: ' + path.basename(P) + '.bak-' + 印);
107
+ console.log('\n★このあと、Antigravity を開き直してください。');
package/gate_core.js CHANGED
@@ -219,6 +219,221 @@ function summarize(r, dict, targetLabel) {
219
219
 
220
220
  // ───────────────────────────────── 記録(学びを溜めて、ルールに落とす)
221
221
  // ★入口は絶対に止めない。書きにくくすると、記録そのものが減る。
222
+ // ═══════════════════════════════════════════════════════════════
223
+ // 正本(★同じ役割のものが2つあると、どちらも育たない)
224
+ //
225
+ // 🚨 由来: 2026-08-28。AI番頭キット(FL)をお使いの方が、このMCPも入れると
226
+ // 下書き帳が2つになることに気づいた。★形が違うので、片方の関所が
227
+ // 「数えるのに一覧に出せない」状態になる(=止まるのに閉じられない)。
228
+ //
229
+ // ★★**機械は数えるだけ。同種かどうかの判断はAIがする。**
230
+ // ファイル名だけで決めると、たまたま名前が似たものを潰す。
231
+ // ★**判断した結果は、その場の会話で終わらせずにファイルへ残す。**
232
+ // 残さないと、次のセッションでまた同じことを考える(=考えが積まない)。
233
+ // ★**残したものを、実際に読んで振る舞いを変える。**
234
+ // 読まないルールは、無いのと同じ(未配線の関所)。
235
+ // ═══════════════════════════════════════════════════════════════
236
+
237
+ // ★役割語=「同じ仕事をしていそう」の当たり付け(★これは判定ではない)
238
+ const 役割語 = {
239
+ '下書き帳': ['retain_log', 'bantou-log', 'bantou_log', '気づき', 'notes', 'メモ帳'],
240
+ '記憶の正本': ['subconscious', 'hot_memory', 'ホットメモリ', 'MEMORY.md'],
241
+ '状態': ['state.md', 'status.md', '.state.json'],
242
+ '引き継ぎ': ['handoff', '引き継ぎ', 'pending'],
243
+ '辞書': ['dict', '辞書', 'dictionary'],
244
+ '人物': ['profile', 'プロファイル', '人物'],
245
+ 'やること': ['task', 'todo', 'やること', '課題'],
246
+ 'ルール': ['rules', 'ルール', 'policy', 'CLAUDE.md', 'GEMINI.md', 'AGENTS.md'],
247
+ };
248
+
249
+ function masterFile(dir) {
250
+ const r = path.join(dir, '_agents', 'rules');
251
+ try { if (fs.existsSync(r)) return path.join(r, '正本.md'); } catch (_) {}
252
+ return path.join(dir, '正本.md');
253
+ }
254
+
255
+ function readMasters(dir) {
256
+ const f = masterFile(dir);
257
+ const out = {};
258
+ let s = '';
259
+ try { s = fs.readFileSync(f, 'utf8'); } catch (_) { return { file: f, decided: out }; }
260
+ // 形: | 役割 | 正本 | 別本(; 区切り) | 決めた日 |
261
+ for (const line of s.split(String.fromCharCode(10))) {
262
+ const m = line.match(/^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]*?)\s*\|\s*([^|]*?)\s*\|\s*$/);
263
+ if (!m || m[1] === '役割' || /^-+$/.test(m[1])) continue;
264
+ out[m[1]] = { 正本: m[2], 別本: m[3] ? m[3].split(';').map((x) => x.trim()).filter(Boolean) : [], 決めた日: m[4] };
265
+ }
266
+ return { file: f, decided: out };
267
+ }
268
+
269
+ function writeMaster(dir, 役割, 正本, 別本, 理由) {
270
+ const f = masterFile(dir);
271
+ const { decided } = readMasters(dir);
272
+ decided[役割] = { 正本, 別本: 別本 || [], 決めた日: stamp().slice(0, 10), 理由 };
273
+ const nl = String.fromCharCode(10);
274
+ const head = [
275
+ '# 正本(どれが本物か)',
276
+ '',
277
+ '> ★同じ役割のものが2つあると、どちらも育ちません。ここで1つに決めます。',
278
+ '> ★決めたのは番頭とあなたです。機械は候補を出しただけです。',
279
+ '> ★増えたら `check_masters` でもう一度見てください。',
280
+ '',
281
+ '| 役割 | 正本 | 別本(使わない) | 決めた日 |',
282
+ '|---|---|---|---|',
283
+ ];
284
+ const rows = Object.entries(decided).map(([k, v]) =>
285
+ '| ' + k + ' | ' + v.正本 + ' | ' + (v.別本 || []).join('; ') + ' | ' + (v.決めた日 || '') + ' |');
286
+ const notes = Object.entries(decided).filter(([, v]) => v.理由)
287
+ .map(([k, v]) => '- **' + k + '**:' + v.理由);
288
+ const body = head.concat(rows, notes.length ? ['', '## なぜそう決めたか', ''].concat(notes) : [], ['']).join(nl);
289
+ fs.mkdirSync(path.dirname(f), { recursive: true });
290
+ fs.writeFileSync(f, body, 'utf8');
291
+ return f;
292
+ }
293
+
294
+ /**
295
+ * ★候補を出す。★判定はしない。
296
+ * ★中身はAIへ返さない(ファイル名・行数・更新日・先頭の見出しだけ)。
297
+ */
298
+ function checkMasters(dir, decide) {
299
+ const d = path.resolve(String(dir || process.cwd()));
300
+ if (!fs.existsSync(d)) throw new Error('見つかりません: ' + d);
301
+
302
+ if (decide && decide.役割 && decide.正本) {
303
+ const f = writeMaster(d, decide.役割, decide.正本, decide.別本, decide.理由);
304
+ return {
305
+ 決めました: decide.役割, 正本: decide.正本,
306
+ 別本: decide.別本 || [], 書いた先: f,
307
+ note: '★これ以降、記録の道具はこの決めごとを読んで振る舞います。'
308
+ + '★決め直すときは、もう一度この道具を呼んでください。',
309
+ };
310
+ }
311
+
312
+ // ── 走査(★深すぎない・重くない)
313
+ const 見つけた = [];
314
+ (function w(p, depth) {
315
+ if (depth > 4) return;
316
+ let es = [];
317
+ try { es = fs.readdirSync(p, { withFileTypes: true }); } catch (_) { return; }
318
+ for (const e of es) {
319
+ if (e.name === 'node_modules' || e.name === '_かたづけ' || e.name.startsWith('.')) continue;
320
+ const full = path.join(p, e.name);
321
+ if (e.isDirectory()) { w(full, depth + 1); continue; }
322
+ if (!/\.(md|json|txt)$/i.test(e.name)) continue;
323
+ 見つけた.push(full);
324
+ }
325
+ })(d, 0);
326
+
327
+ const { file: mf, decided } = readMasters(d);
328
+ const 組 = [];
329
+ for (const [役割, 語s] of Object.entries(役割語)) {
330
+ const 当たり = 見つけた.filter((f) => {
331
+ const rel = path.relative(d, f).split(String.fromCharCode(92)).join('/');
332
+ return 語s.some((w2) => rel.toLowerCase().indexOf(w2.toLowerCase()) >= 0);
333
+ });
334
+ if (当たり.length < 2) continue;
335
+ 組.push({
336
+ 役割,
337
+ すでに決めてある: decided[役割] ? decided[役割].正本 : null,
338
+ 候補: 当たり.slice(0, 8).map((f) => {
339
+ let 行数 = null, 見出し = null, 更新 = null;
340
+ try {
341
+ const s = fs.readFileSync(f, 'utf8');
342
+ 行数 = s.split(String.fromCharCode(10)).length;
343
+ const h = s.split(String.fromCharCode(10)).find((l) => /^#\s/.test(l));
344
+ 見出し = h ? h.replace(/^#\s*/, '').slice(0, 40) : null;
345
+ 更新 = fs.statSync(f).mtime.toISOString().slice(0, 10);
346
+ } catch (_) {}
347
+ return { path: path.relative(d, f).split(String.fromCharCode(92)).join('/'), 行数, 見出し, 更新 };
348
+ }),
349
+ });
350
+ }
351
+
352
+ return {
353
+ 見たフォルダ: d,
354
+ 正本の帳面: mf,
355
+ 決めてある件数: Object.keys(decided).length,
356
+ 確かめてほしい組: 組.length ? 組 : '(役割が重なりそうな組は見つかりませんでした)',
357
+ note:
358
+ '★これは候補です。**同じ役割かどうかは、機械には決められません。**'
359
+ + 'あなた(番頭)が1組ずつ見て、同じ仕事をしているなら1つに決めてください。'
360
+ + '★決めたら check_masters を decide 付きで呼ぶと、帳面へ残ります。'
361
+ + '★中身は返していません(ファイル名・行数・更新日・先頭の見出しだけ)。',
362
+ '★どう決めるか': [
363
+ '同じ仕事をしているか(片方が消えても困らないなら、たぶん別の仕事)',
364
+ '★どちらを残すか=**関所が読んでいるほう**(止める・数える仕組みが見ているファイル)',
365
+ '★形が違うものを1つに混ぜない(読めなくなる)。使わないほうは別本にする',
366
+ ],
367
+ };
368
+ }
369
+
370
+ // 🚨★**AI番頭キット(FL)が入っているなら、記録はそちらへ譲る**(2026-08-28)
371
+ // ★由来=FL 2.2〜2.5 をお使いの方が、このMCPも入れた場合。
372
+ // ★FLキットは `second-brain/retain_log.md` に書き、`kit/memory/close.js` が
373
+ // **そのファイルだけ**を読んで「未処理10件で止める」関所を回している。
374
+ // ★★こちらが `bantou-log.md` に書くと、**下書き帳が2つ**になる(=正本割れ)。
375
+ // ★しかも1行の形が違う:
376
+ // FL : `- [ ] 日時 | 0.6 | failure | 本文 〔sig:key〕`(★点数の欄がある)
377
+ // こちら: `- [ ] 日時 | failure 〔key〕 | 本文`
378
+ // ★FLの関所は行頭 `- [ ]` だけを数え、一覧は正規表現で読む。
379
+ // ★→ **数えられるのに一覧に出ない**。止まるのに閉じられない、いちばん悪い形になる。
380
+ // ★→ ★**書く場所を横取りしない。FLの道具へお返しする。**
381
+ // ★型キーを3件で数える働きは、FLの retain.js が同じことをしている。失うものは無い。
382
+ function findKit() {
383
+ let d = process.cwd();
384
+ for (let i = 0; i < 6; i++) {
385
+ const p = path.join(d, 'kit', 'memory', 'retain.js');
386
+ try { if (fs.existsSync(p)) return { script: p, root: d }; } catch (_) {}
387
+ const up = path.dirname(d);
388
+ if (up === d) break;
389
+ d = up;
390
+ }
391
+ return null;
392
+ }
393
+ // ★★**機械は決めない。決めごとがあれば従い、無ければ止めて番頭に聞く。**
394
+ // ★由来(2026-08-28)=「AIが同種の記載をしているかどうか判断して、ルールを作らせるのがいい」。
395
+ // ★私は最初、キットの有無だけで機械が決める形にした。★それだと
396
+ // ①たまたま名前が似ただけのものまで潰す ②決めた理由が残らない
397
+ // ③次のセッションでまた同じことを考える(考えが積まない)。
398
+ // ★順番=① 環境変数の明示 → ② 正本.md の決めごと → ③ 未決なら止めて聞く。
399
+ function logOwner() {
400
+ if (process.env.BANTOU_LOG) return null; // ★人が明示した指定が最優先
401
+ let decided = {};
402
+ try { decided = readMasters(process.cwd()).decided; } catch (_) {}
403
+ const m = decided['下書き帳'];
404
+ if (m) {
405
+ // ★決めてある。こちらが正本なら普通に書く。よそが正本なら譲る
406
+ return /bantou-log/i.test(m.正本) ? null : { 決めてある: true, 正本: m.正本 };
407
+ }
408
+ const k = findKit(); // ★キットが在る=重なる恐れがある
409
+ return k ? { 決めてある: false, キットの場所: k.root } : null;
410
+ }
411
+
412
+ function kitNotice(cmd) {
413
+ const o = logOwner();
414
+ if (o && o.決めてある) {
415
+ return {
416
+ verdict: '書いていません',
417
+ 理由: '★このフォルダの「下書き帳」の正本は ' + o.正本 + ' と決めてあります(正本.md)。',
418
+ お使いください: 'node kit/memory/' + cmd,
419
+ };
420
+ }
421
+ return {
422
+ verdict: '書いていません',
423
+ 理由: '★このフォルダには AI番頭キットが入っていて、記録の置き場が**まだ決まっていません**。',
424
+ '★あなた(番頭)にお願いしたいこと': [
425
+ '1. `check_masters` を呼んで、役割が重なっている組を見てください',
426
+ '2. 同じ仕事をしているか、あなたが判断してください(★機械には決められません)',
427
+ '3. 決めたら `check_masters` を decide 付きで呼んでください。帳面に残り、次から迷いません',
428
+ ],
429
+ なぜ止めたか:
430
+ '★下書き帳が2つあると、どちらも育ちません。'
431
+ + 'しかも形が違うと、片方の関所が「数えるのに一覧に出せない」状態になります'
432
+ + '(止まるのに閉じられない)。★先に1つ決めてから書きます。',
433
+ キットの場所: o ? o.キットの場所 : null,
434
+ };
435
+ }
436
+
222
437
  function logPath() {
223
438
  return process.env.BANTOU_LOG || path.join(process.cwd(), 'bantou-log.md');
224
439
  }
@@ -227,6 +442,7 @@ function stamp() {
227
442
  return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
228
443
  }
229
444
  function remember(text, tag, sig) {
445
+ if (logOwner()) return kitNotice('retain.js add "<本文>" --score 0.5 --tag failure --sig <型キー>');
230
446
  const t = String(text || '').trim();
231
447
  if (!t) throw new Error('記録する内容がありません');
232
448
  const f = logPath();
@@ -277,6 +493,7 @@ function readLog() {
277
493
  }
278
494
 
279
495
  function recall(days, onlyOpen) {
496
+ if (logOwner()) return kitNotice('retain.js recall --min 0.5 --days 7');
280
497
  const { file, entries } = readLog();
281
498
  const since = Date.now() - (Number(days) || 7) * 86400000;
282
499
  const out = entries
@@ -291,6 +508,7 @@ function recall(days, onlyOpen) {
291
508
  * ★これが FL の山。溜めるのが目的ではなく、ルールか手順に落とすのが目的。
292
509
  */
293
510
  function review(record) {
511
+ if (logOwner()) return kitNotice('close.js');
294
512
  const { file, entries, ruled } = readLog();
295
513
  const ruledBy = {};
296
514
  for (const r of ruled) ruledBy[r.sig] = r;
@@ -353,6 +571,7 @@ function review(record) {
353
571
  * ruled_to … ルールや手順にしたときの置き場所(sig と一緒に指定)
354
572
  */
355
573
  function settle(ids, sig, ruledTo) {
574
+ if (logOwner()) return kitNotice('retain.js promote <行番号...>');
356
575
  const { file, lines, entries } = readLog();
357
576
  if (!fs.existsSync(file)) throw new Error('まだ記録がありません');
358
577
  let closed = 0;
@@ -620,7 +839,7 @@ function handle(req) {
620
839
 
621
840
  // ★このファイルは「正本」。社内版はこれを読み込んで、辞書と鍵を足すだけにする。
622
841
  // (検出のロジックを2か所に持たないため)
623
- module.exports = { inspectText, summarize, report, loadDict, walk, remember, recall, review, settle, checkup, readLog, selfDiagnosis, INTERNAL_PATTERNS, NAMEISH };
842
+ module.exports = { inspectText, summarize, report, loadDict, walk, remember, recall, review, settle, checkup, readLog, selfDiagnosis, INTERNAL_PATTERNS, NAMEISH, checkMasters, readMasters };
624
843
 
625
844
  // 直接起動されたときだけ、MCPサーバーとして動く
626
845
  if (require.main === module) {
package/law_bridge.js ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * 法令パーツを、番頭の中から呼ぶ(★MCP in MCP)
3
+ *
4
+ * 🚨 由来: 2026-08-28。★法令パーツは別のMCPサーバーなので、棚に並ばなかった。
5
+ * ★「MCP in MCP できる」を**実測で確かめてから**この形にした
6
+ * (子を spawn → initialize → tools/list → tools/call が通ることを確認済み)。
7
+ *
8
+ * ★なぜ中継にするか
9
+ * ・利用者の設定は**1行のまま**(もう1つ登録してもらわない)
10
+ * ・鍵も**1つのまま**
11
+ * ・★限定の関所(entitlements)を**番頭が一括で見られる**
12
+ *
13
+ * ★見え方(2026-08-28 裁定=「見えるが使えない」)
14
+ * ・道具は**誰にでも見える**(契約していない方にも一覧に出る)
15
+ * ・呼ぶと「ご契約に含まれていません」と返る(★入口になる)
16
+ *
17
+ * ★重さ対策
18
+ * ・道具の定義は**ここに静的に持つ**(起動のたびに子を立てない)
19
+ * ・子を立てるのは**実際に呼ばれて、かつ契約がある**ときだけ
20
+ * ・一度立てたら**使い回す**(15分さわらなければ落とす)
21
+ * ・★静的な定義が子とズレていないかは `check_law_tools.mjs` が突き合わせる
22
+ */
23
+ const path = require('path');
24
+ const { spawn } = require('child_process');
25
+
26
+ const 名前 = ["law_search", "law_scene", "law_authority", "law_gaps"];
27
+ const SUPPLEMENT = 'law-parts';
28
+
29
+ // ★子の場所。★配布版では npm の別パッケージ、開発中はリポジトリの中
30
+ function 子の場所() {
31
+ if (process.env.LAW_PARTS_SERVER) return process.env.LAW_PARTS_SERVER;
32
+ const 候補 = [
33
+ path.join(__dirname, '..', 'law_parts', 'server.js'),
34
+ path.join(__dirname, 'law_parts', 'server.js'),
35
+ ];
36
+ const fs = require('fs');
37
+ for (const p of 候補) { try { if (fs.existsSync(p)) return p; } catch (_) {} }
38
+ return null;
39
+ }
40
+
41
+ const TOOLS = [
42
+ {
43
+ "name": "law_search",
44
+ "description": "仕事の場面(相談・就業規則・給与計算・手続)から、労働・社会保険の法令パーツを引く。★場面と根拠を分けて返す。★未解決の問い(open_questions_count)と、★このパーツでは判断できないこと(blocked_tasks)を必ず一緒に返すので、必ず読むこと。",
45
+ "inputSchema": {
46
+ "type": "object",
47
+ "properties": {
48
+ "q": {
49
+ "type": "string",
50
+ "description": "探したい語。空白区切りで複数可(例: 年休 時季変更)"
51
+ },
52
+ "limit": {
53
+ "type": "number",
54
+ "description": "場面・根拠それぞれの最大件数(既定6)"
55
+ }
56
+ },
57
+ "required": [
58
+ "q"
59
+ ]
60
+ }
61
+ },
62
+ {
63
+ "name": "law_scene",
64
+ "description": "場面パーツの全文を返す(answers / pitfalls / 根拠の一覧 / ★blocked_tasks / ★open_questions を含む)。law_search の scene_hits の id を渡す。",
65
+ "inputSchema": {
66
+ "type": "object",
67
+ "properties": {
68
+ "id": {
69
+ "type": "string",
70
+ "description": "場面ID(例: annual_leave)"
71
+ }
72
+ },
73
+ "required": [
74
+ "id"
75
+ ]
76
+ }
77
+ },
78
+ {
79
+ "name": "law_authority",
80
+ "description": "根拠(条文・省令・告示・通達・判例)の全文を返す。★読み替え(overridden_by)があるときは、読み替え側も一緒に返す。",
81
+ "inputSchema": {
82
+ "type": "object",
83
+ "properties": {
84
+ "id": {
85
+ "type": "string",
86
+ "description": "根拠ID(例: labor_law_39, case_52785)"
87
+ }
88
+ },
89
+ "required": [
90
+ "id"
91
+ ]
92
+ }
93
+ },
94
+ {
95
+ "name": "law_gaps",
96
+ "description": "★このパーツ集で「まだ埋まっていないこと」を返す。★何が入っているかより、★何が入っていないかを先に見るための口。",
97
+ "inputSchema": {
98
+ "type": "object",
99
+ "properties": {
100
+ "scene": {
101
+ "type": "string",
102
+ "description": "場面IDで絞る(省略時は全部)"
103
+ },
104
+ "status": {
105
+ "type": "string",
106
+ "description": "unresolved / partial(省略時は両方)"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ ];
112
+
113
+ let 子 = null, 待ち = new Map(), 次のid = 1000, 最後に使った = 0, 掃除 = null;
114
+
115
+ function 子を立てる() {
116
+ const srv = 子の場所();
117
+ if (!srv) throw new Error('法令パーツの本体が見つかりません');
118
+ const p = spawn(process.execPath, [srv], { stdio: ['pipe', 'pipe', 'ignore'] });
119
+ let buf = '';
120
+ p.stdout.on('data', (d) => {
121
+ buf += d;
122
+ let i;
123
+ while ((i = buf.indexOf(String.fromCharCode(10))) >= 0) {
124
+ const line = buf.slice(0, i); buf = buf.slice(i + 1);
125
+ if (!line.trim()) continue;
126
+ try {
127
+ const j = JSON.parse(line);
128
+ const f = 待ち.get(j.id);
129
+ if (f) { 待ち.delete(j.id); f(j); }
130
+ } catch (_) {}
131
+ }
132
+ });
133
+ p.on('exit', () => { if (子 && 子.p === p) 子 = null; });
134
+ const 送る = (o) => p.stdin.write(JSON.stringify(o) + String.fromCharCode(10));
135
+ 送る({ jsonrpc: '2.0', id: 1, method: 'initialize',
136
+ params: { protocolVersion: '2024-11-05', capabilities: {},
137
+ clientInfo: { name: 'banto', version: '1' } } });
138
+ return { p, 送る };
139
+ }
140
+
141
+ function 呼ぶ(method, params, 秒 = 30) {
142
+ return new Promise((res, rej) => {
143
+ if (!子) 子 = 子を立てる();
144
+ 最後に使った = Date.now();
145
+ if (!掃除) {
146
+ // ★15分さわらなければ落とす(★立てっぱなしにしない)
147
+ 掃除 = setInterval(() => {
148
+ if (子 && Date.now() - 最後に使った > 15 * 60e3) { try { 子.p.kill(); } catch (_) {} 子 = null; }
149
+ }, 60e3);
150
+ if (掃除.unref) 掃除.unref();
151
+ }
152
+ const id = 次のid++;
153
+ const t = setTimeout(() => { 待ち.delete(id); rej(new Error('法令パーツが応答しません')); }, 秒 * 1000);
154
+ 待ち.set(id, (j) => { clearTimeout(t); j.error ? rej(new Error(j.error.message || '失敗')) : res(j.result); });
155
+ 子.送る({ jsonrpc: '2.0', id, method, params });
156
+ });
157
+ }
158
+
159
+ /** この名前は法令パーツのものか */
160
+ function これか(name) { return 名前.indexOf(name) >= 0; }
161
+
162
+ /**
163
+ * 呼ぶ(★契約を先に見る)
164
+ * @param 契約あり true なら通す
165
+ */
166
+ async function 中継(name, args, 契約あり) {
167
+ if (!契約あり) {
168
+ return {
169
+ verdict: '実行していません',
170
+ 理由: '★法令パーツはご契約に含まれていません。',
171
+ これは何か:
172
+ '労働・社会保険の「仕事の場面」から、条文・省令・告示・通達・判例を1手で引く道具です。'
173
+ + '★引用は原文と機械で全件照合しています。',
174
+ お問い合わせ: '★ご入用でしたら、番頭を出している者(ホライズンワークス)へお知らせください。',
175
+ };
176
+ }
177
+ const r = await 呼ぶ('tools/call', { name, arguments: args || {} });
178
+ const t = r && r.content && r.content[0] && r.content[0].text;
179
+ if (typeof t === 'string') { try { return JSON.parse(t); } catch (_) { return { text: t }; } }
180
+ return r;
181
+ }
182
+
183
+ module.exports = { TOOLS, これか, 中継, SUPPLEMENT, 名前 };
package/license/client.js CHANGED
@@ -22,10 +22,26 @@ const CACHE = path.join(DIR, '.state.json');
22
22
  const GRACE_DAYS = 7; // 通信できないときに通す日数
23
23
  const CHECK_EVERY_H = 24; // 問い合わせの間隔
24
24
 
25
+ // 🚨★**渡すものを1つにする**(2026-08-28)
26
+ // ★URL と anon キーは★**全員同じ**(★公開情報)なので、★ここに埋める。
27
+ // ★★鍵と署名は `鍵.署名` の形で1つにまとめる(★コピペするので長さは問題にならない)。
28
+ // ★→ ★利用者が入れるのは `BANTO_KEY` 1つだけ。
29
+ // ★★古い4つの環境変数も、★そのまま使える(★既に配った方が止まらないように)。
30
+ const BUILTIN_URL = 'https://ltelwzqclaxmpnbjuoql.supabase.co/rest/v1/rpc/verify_license';
31
+ const BUILTIN_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imx0ZWx3enFjbGF4bXBuYmp1b3FsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODcxOTg4MDIsImV4cCI6MjEwMjc3NDgwMn0.vOrEj3rFFUdMM1vYGqBwnGkPbFVQHql3_X_DhpA-9L4';
32
+
25
33
  function config() {
26
34
  const c = { key: process.env.BANTOU_LICENSE_KEY || '', url: process.env.BANTOU_LICENSE_URL || '',
27
35
  anonKey: process.env.BANTOU_LICENSE_ANON || '',
28
36
  sig: process.env.BANTOU_LICENSE_SIG || '' };
37
+
38
+ // ★新しい形=`BANTO_KEY = 鍵.署名`
39
+ const one = process.env.BANTO_KEY || '';
40
+ if (one && !c.key) {
41
+ const i = one.indexOf('.');
42
+ if (i > 0) { c.key = one.slice(0, i); c.sig = one.slice(i + 1); }
43
+ else { c.key = one; }
44
+ }
29
45
  const f = path.join(DIR, 'config.json');
30
46
  if (fs.existsSync(f)) {
31
47
  try {
@@ -36,6 +52,9 @@ function config() {
36
52
  c.sig = c.sig || j.sig || '';
37
53
  } catch (_) {}
38
54
  }
55
+ // ★どこにも書かれていなければ、★埋め込みを使う
56
+ c.url = c.url || BUILTIN_URL;
57
+ c.anonKey = c.anonKey || BUILTIN_ANON;
39
58
  return c;
40
59
  }
41
60
 
@@ -281,4 +300,4 @@ async function reportStats(version, review) {
281
300
  }
282
301
  }
283
302
 
284
- module.exports = { check, reportStats, buildStats, isSharing, setSharing , peek};
303
+ module.exports = { check, reportStats, buildStats, isSharing, setSharing , peek, config};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@horizon_works/banto",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "AI番頭のOS。ルール・記憶・検査を1本のMCPで配ります。中身をクラウドへ渡さずに検査できます。",
5
5
  "keywords": [
6
6
  "mcp",
@@ -24,6 +24,8 @@
24
24
  },
25
25
  "files": [
26
26
  "server.js",
27
+ "law_bridge.js",
28
+ "add_to_config.js",
27
29
  "gate_core.js",
28
30
  "privacy.js",
29
31
  "shelf.js",
@@ -31,7 +33,6 @@
31
33
  "env.js",
32
34
  "subconscious.js",
33
35
  "supplements.json",
34
- "payload/",
35
36
  "license/client.js",
36
37
  "README.md"
37
38
  ],