@ectplsm/relic 0.5.2 → 0.5.4

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/README.md +28 -5
  2. package/dist/adapters/shells/codex-hook.d.ts +3 -3
  3. package/dist/adapters/shells/codex-hook.js +12 -11
  4. package/dist/adapters/shells/codex-paths.d.ts +11 -0
  5. package/dist/adapters/shells/codex-paths.js +23 -0
  6. package/dist/adapters/shells/codex-shell.js +7 -5
  7. package/dist/interfaces/cli/index.js +9 -0
  8. package/dist/interfaces/mcp/index.js +0 -0
  9. package/package.json +7 -4
  10. package/dist/adapters/shells/copilot-shell.d.ts +0 -14
  11. package/dist/adapters/shells/copilot-shell.js +0 -43
  12. package/dist/adapters/shells/trust-registrar.d.ts +0 -19
  13. package/dist/adapters/shells/trust-registrar.js +0 -141
  14. package/dist/core/usecases/inbox-search.d.ts +0 -20
  15. package/dist/core/usecases/inbox-search.js +0 -46
  16. package/dist/core/usecases/inbox-write.d.ts +0 -27
  17. package/dist/core/usecases/inbox-write.js +0 -72
  18. package/dist/core/usecases/memory-search.d.ts +0 -23
  19. package/dist/core/usecases/memory-search.js +0 -59
  20. package/dist/core/usecases/mikoshi-clone.d.ts +0 -29
  21. package/dist/core/usecases/mikoshi-clone.js +0 -94
  22. package/dist/interfaces/cli/commands/extract.d.ts +0 -2
  23. package/dist/interfaces/cli/commands/extract.js +0 -37
  24. package/dist/interfaces/cli/commands/inject.d.ts +0 -2
  25. package/dist/interfaces/cli/commands/inject.js +0 -34
  26. package/dist/interfaces/cli/commands/sync.d.ts +0 -2
  27. package/dist/interfaces/cli/commands/sync.js +0 -47
  28. package/dist/shared/memory-inbox.d.ts +0 -78
  29. package/dist/shared/memory-inbox.js +0 -168
  30. package/dist/shared/session-recorder.d.ts +0 -47
  31. package/dist/shared/session-recorder.js +0 -112
package/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
  |:---:|:---:|
3
3
 
4
4
  # Relic: AI Persona Injection System
5
- ![NPM Downloads](https://img.shields.io/npm/dt/%40ectplsm%2Frelic)
5
+ [![NPM Version](https://img.shields.io/npm/v/%40ectplsm%2Frelic)](https://www.npmjs.com/package/@ectplsm/relic)
6
+ [![NPM Downloads](https://img.shields.io/npm/dt/%40ectplsm%2Frelic)](https://www.npmjs.com/package/@ectplsm/relic)
7
+ [![License](https://img.shields.io/npm/l/%40ectplsm%2Frelic)](https://github.com/ectplsm/relic/blob/main/LICENCE.md)
6
8
 
7
9
  <img src="assets/relic-hero.svg" alt="PROJECT RELIC" width="720">
8
10
 
@@ -25,12 +27,10 @@ Relic manages AI **Engrams** (memory + personality) and injects them across codi
25
27
 
26
28
  ## Requirements
27
29
 
28
- - Node.js 18 or later
30
+ - Node.js 18.14.1 or later
29
31
 
30
32
  ## Install
31
33
 
32
- <img alt="version badge" src="https://img.shields.io/github/v/release/ectplsm/relic?filter=*.*.*">
33
-
34
34
  ```bash
35
35
  npm install -g @ectplsm/relic
36
36
  ```
@@ -208,6 +208,29 @@ Upcoming milestones that shape where Relic is heading:
208
208
  - **Structured memory decomposition** — break `MEMORY.md` down into episodic, semantic, and procedural memory so the Construct can recall the right kind of knowledge at the right time.
209
209
  - **Memory Wiki on llm-wiki** — build a navigable memory wiki on top of [llm-wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f), turning long-term memory into a browsable, linkable knowledge base instead of flat Markdown.
210
210
 
211
+ ## Development
212
+
213
+ We welcome contributions! To set up the project locally for development:
214
+
215
+ 1. Clone the repository:
216
+ ```bash
217
+ git clone https://github.com/ectplsm/relic.git
218
+ cd relic
219
+ ```
220
+ 2. Install dependencies:
221
+ ```bash
222
+ npm install
223
+ ```
224
+ 3. Available scripts:
225
+ - `npm run build`: Compile TypeScript into the `dist/` directory.
226
+ - `npm run typecheck`: Run the TypeScript compiler without emitting files.
227
+ - `npm run dev:cli`: Run the CLI in development mode (using `tsx`).
228
+ - `npm run dev:mcp`: Run the MCP server in development mode (using `tsx`).
229
+ - `npm run release:prepare -- <version>`: Generate release notes, validate the package, create and push the version commit and annotated tag, then create a draft GitHub Release. Codex generates the notes by default; use `--no-ai-notes`, `--notes-file <path>`, or `--dry-run` when needed.
230
+ - After reviewing the draft, run `gh workflow run release.yml --ref main -f tag=v<version>` to publish the package and GitHub Release. Before the first release, create a protected GitHub Environment named `npm`: restrict deployment branches to `main`, limit required reviewers to trusted release maintainers, and disable administrator bypass. Then configure npm Trusted Publishing for `ectplsm/relic`, workflow `release.yml`, environment `npm`, with direct publishing enabled. No long-lived npm token is used.
231
+
232
+ Before contributing, please read [CLAUDE.md](./CLAUDE.md) for strict coding conventions, parent documents, and PR rules.
233
+
211
234
  ## License
212
235
 
213
- [MIT](./LICENCE.md)
236
+ This project is licensed under the MIT License - see the [LICENCE.md](./LICENCE.md) file for details.
@@ -5,11 +5,11 @@ export declare const CODEX_HOOK_SCRIPT_PATH: string;
5
5
  */
6
6
  export declare function writeCodexHookScript(): void;
7
7
  /**
8
- * Codex CLI の Stop フックを settings.json に登録する。
8
+ * Codex CLI の Stop フックを hooks.json に登録する。
9
9
  * 既にセットアップ済みの場合はスキップ。
10
10
  */
11
- export declare function setupCodexHook(): void;
11
+ export declare function setupCodexHook(cwd?: string): void;
12
12
  /**
13
13
  * Stop フックがセットアップ済みか確認する。
14
14
  */
15
- export declare function isCodexHookSetup(): boolean;
15
+ export declare function isCodexHookSetup(cwd?: string): boolean;
@@ -1,10 +1,10 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
+ import { resolveCodexHome, resolveCodexHooksPath } from "./codex-paths.js";
4
5
  const RELIC_DIR = join(homedir(), ".relic");
5
6
  const HOOKS_DIR = join(RELIC_DIR, "hooks");
6
7
  export const CODEX_HOOK_SCRIPT_PATH = join(HOOKS_DIR, "codex-stop.js");
7
- const CODEX_HOOKS_PATH = join(homedir(), ".codex", "hooks.json");
8
8
  const RELIC_HOOK_COMMAND = `node ${join(HOOKS_DIR, "codex-stop.js")}`;
9
9
  /**
10
10
  * Stop hook スクリプトの内容。
@@ -88,17 +88,17 @@ export function writeCodexHookScript() {
88
88
  writeFileSync(CODEX_HOOK_SCRIPT_PATH, HOOK_SCRIPT, { encoding: "utf-8", mode: 0o755 });
89
89
  }
90
90
  /**
91
- * Codex CLI の Stop フックを settings.json に登録する。
91
+ * Codex CLI の Stop フックを hooks.json に登録する。
92
92
  * 既にセットアップ済みの場合はスキップ。
93
93
  */
94
- export function setupCodexHook() {
95
- // ~/.codex/hooks.json に Stop フックを登録
96
- const codexDir = join(homedir(), ".codex");
94
+ export function setupCodexHook(cwd = process.cwd()) {
95
+ const codexDir = resolveCodexHome(cwd);
96
+ const codexHooksPath = resolveCodexHooksPath(cwd);
97
97
  mkdirSync(codexDir, { recursive: true });
98
98
  let hooksConfig = {};
99
- if (existsSync(CODEX_HOOKS_PATH)) {
99
+ if (existsSync(codexHooksPath)) {
100
100
  try {
101
- hooksConfig = JSON.parse(readFileSync(CODEX_HOOKS_PATH, "utf-8"));
101
+ hooksConfig = JSON.parse(readFileSync(codexHooksPath, "utf-8"));
102
102
  }
103
103
  catch {
104
104
  hooksConfig = {};
@@ -123,18 +123,19 @@ export function setupCodexHook() {
123
123
  },
124
124
  ];
125
125
  hooksConfig.hooks = hooks;
126
- writeFileSync(CODEX_HOOKS_PATH, JSON.stringify(hooksConfig, null, 2), "utf-8");
126
+ writeFileSync(codexHooksPath, JSON.stringify(hooksConfig, null, 2), "utf-8");
127
127
  }
128
128
  /**
129
129
  * Stop フックがセットアップ済みか確認する。
130
130
  */
131
- export function isCodexHookSetup() {
131
+ export function isCodexHookSetup(cwd = process.cwd()) {
132
+ const codexHooksPath = resolveCodexHooksPath(cwd);
132
133
  if (!existsSync(CODEX_HOOK_SCRIPT_PATH))
133
134
  return false;
134
- if (!existsSync(CODEX_HOOKS_PATH))
135
+ if (!existsSync(codexHooksPath))
135
136
  return false;
136
137
  try {
137
- const hooksConfig = JSON.parse(readFileSync(CODEX_HOOKS_PATH, "utf-8"));
138
+ const hooksConfig = JSON.parse(readFileSync(codexHooksPath, "utf-8"));
138
139
  const stopHooks = hooksConfig.hooks?.Stop ?? [];
139
140
  return stopHooks.some((group) => group.hooks?.some((h) => h.command === RELIC_HOOK_COMMAND));
140
141
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Codex の state root を解決する。
3
+ *
4
+ * CODEX_HOME が未設定または空文字の場合は ~/.codex を使う。
5
+ * 相対パスは、実際に Codex を起動する作業ディレクトリを基準に解決する。
6
+ */
7
+ export declare function resolveCodexHome(cwd?: string): string;
8
+ /**
9
+ * Codex の user-level hooks.json の絶対パスを返す。
10
+ */
11
+ export declare function resolveCodexHooksPath(cwd?: string): string;
@@ -0,0 +1,23 @@
1
+ import { homedir } from "node:os";
2
+ import { isAbsolute, join, resolve } from "node:path";
3
+ const DEFAULT_CODEX_HOME = join(homedir(), ".codex");
4
+ /**
5
+ * Codex の state root を解決する。
6
+ *
7
+ * CODEX_HOME が未設定または空文字の場合は ~/.codex を使う。
8
+ * 相対パスは、実際に Codex を起動する作業ディレクトリを基準に解決する。
9
+ */
10
+ export function resolveCodexHome(cwd = process.cwd()) {
11
+ const configuredHome = process.env.CODEX_HOME;
12
+ if (!configuredHome)
13
+ return DEFAULT_CODEX_HOME;
14
+ return isAbsolute(configuredHome)
15
+ ? resolve(configuredHome)
16
+ : resolve(cwd, configuredHome);
17
+ }
18
+ /**
19
+ * Codex の user-level hooks.json の絶対パスを返す。
20
+ */
21
+ export function resolveCodexHooksPath(cwd = process.cwd()) {
22
+ return join(resolveCodexHome(cwd), "hooks.json");
23
+ }
@@ -3,6 +3,7 @@ import { promisify } from "node:util";
3
3
  import { spawnShell } from "./spawn-shell.js";
4
4
  import { wrapWithOverride } from "./override-preamble.js";
5
5
  import { setupCodexHook, isCodexHookSetup, writeCodexHookScript } from "./codex-hook.js";
6
+ import { resolveCodexHooksPath } from "./codex-paths.js";
6
7
  const execAsync = promisify(exec);
7
8
  /**
8
9
  * Codex CLI アダプター
@@ -29,13 +30,14 @@ export class CodexShell {
29
30
  }
30
31
  }
31
32
  async launch(prompt, options) {
33
+ const cwd = options?.cwd ?? process.cwd();
32
34
  // フックスクリプトを毎回最新に更新
33
35
  writeCodexHookScript();
34
36
  // hooks.json への登録は初回のみ
35
- if (!isCodexHookSetup()) {
37
+ if (!isCodexHookSetup(cwd)) {
36
38
  console.log("Setting up Codex CLI Stop hook (first run only)...");
37
- setupCodexHook();
38
- console.log("Hook registered to ~/.codex/hooks.json");
39
+ setupCodexHook(cwd);
40
+ console.log(`Hook registered to ${resolveCodexHooksPath(cwd)}`);
39
41
  console.log();
40
42
  }
41
43
  const args = [];
@@ -43,10 +45,10 @@ export class CodexShell {
43
45
  if (!options?.skipInjection) {
44
46
  args.push("-c", `developer_instructions=${JSON.stringify(wrapWithOverride(prompt))}`);
45
47
  }
46
- args.push("-c", "features.hooks=true", ...(options?.extraArgs ?? []));
48
+ args.push(...(options?.extraArgs ?? []));
47
49
  const env = {};
48
50
  if (options?.engramId)
49
51
  env.RELIC_ENGRAM_ID = options.engramId;
50
- await spawnShell(this.command, args, options?.cwd, Object.keys(env).length > 0 ? env : undefined);
52
+ await spawnShell(this.command, args, cwd, Object.keys(env).length > 0 ? env : undefined);
51
53
  }
52
54
  }
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { resolve, dirname } from "node:path";
5
5
  import { Command } from "commander";
6
+ import updateNotifier from "update-notifier";
6
7
  import { registerInitCommand } from "./commands/init.js";
7
8
  import { registerListCommand } from "./commands/list.js";
8
9
  import { registerShowCommand } from "./commands/show.js";
@@ -16,6 +17,14 @@ import { registerDeleteCommand } from "./commands/delete.js";
16
17
  import { registerMikoshiCommand } from "./commands/mikoshi.js";
17
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
19
  const pkg = JSON.parse(readFileSync(resolve(__dirname, "../../../package.json"), "utf-8"));
20
+ if (process.stdout.isTTY) {
21
+ updateNotifier({
22
+ pkg: {
23
+ name: pkg.name,
24
+ version: pkg.version,
25
+ },
26
+ }).notify({ defer: false });
27
+ }
19
28
  const program = new Command();
20
29
  program
21
30
  .name("relic")
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ectplsm/relic",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "PROJECT RELIC — Engram injection system for AI constructs",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,22 +24,25 @@
24
24
  "dev:cli": "tsx src/interfaces/cli/index.ts",
25
25
  "dev:mcp": "tsx src/interfaces/mcp/index.ts",
26
26
  "typecheck": "tsc --noEmit",
27
- "build": "tsc"
27
+ "build": "tsc",
28
+ "release:prepare": "node scripts/prepare-release.mjs"
28
29
  },
29
30
  "files": [
30
31
  "dist",
31
32
  "templates"
32
33
  ],
33
34
  "engines": {
34
- "node": ">=18"
35
+ "node": ">=18.14.1"
35
36
  },
36
37
  "dependencies": {
37
- "@modelcontextprotocol/sdk": "^1.12.0",
38
+ "@modelcontextprotocol/sdk": "1.29.0",
38
39
  "commander": "^13.0.0",
40
+ "update-notifier": "^7.3.1",
39
41
  "zod": "^3.23.0"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@types/node": "^22.0.0",
45
+ "@types/update-notifier": "^6.0.8",
43
46
  "tsx": "^4.19.0",
44
47
  "typescript": "^5.7.0"
45
48
  }
@@ -1,14 +0,0 @@
1
- import type { ShellLauncher, InjectionMode, ShellLaunchOptions } from "../../core/ports/shell-launcher.js";
2
- /**
3
- * GitHub Copilot CLI アダプター (copilot コマンド)
4
- * --interactive フラグでEngramを初回プロンプトとして注入し、
5
- * インタラクティブモードを継続する。
6
- */
7
- export declare class CopilotShell implements ShellLauncher {
8
- private readonly command;
9
- readonly name = "GitHub Copilot CLI";
10
- readonly injectionMode: InjectionMode;
11
- constructor(command?: string);
12
- isAvailable(): Promise<boolean>;
13
- launch(prompt: string, options?: ShellLaunchOptions): Promise<void>;
14
- }
@@ -1,43 +0,0 @@
1
- import { exec } from "node:child_process";
2
- import { promisify } from "node:util";
3
- import { readFileSync } from "node:fs";
4
- import { spawnShell, writeTempPrompt } from "./spawn-shell.js";
5
- import { wrapWithOverride } from "./override-preamble.js";
6
- const execAsync = promisify(exec);
7
- /**
8
- * GitHub Copilot CLI アダプター (copilot コマンド)
9
- * --interactive フラグでEngramを初回プロンプトとして注入し、
10
- * インタラクティブモードを継続する。
11
- */
12
- export class CopilotShell {
13
- command;
14
- name = "GitHub Copilot CLI";
15
- injectionMode = "user-message";
16
- constructor(command = "copilot") {
17
- this.command = command;
18
- }
19
- async isAvailable() {
20
- try {
21
- await execAsync(`which ${this.command}`);
22
- return true;
23
- }
24
- catch {
25
- return false;
26
- }
27
- }
28
- async launch(prompt, options) {
29
- const tmp = writeTempPrompt(wrapWithOverride(prompt));
30
- try {
31
- const fileContent = readFileSync(tmp.path, "utf-8");
32
- const args = [
33
- "--interactive",
34
- fileContent,
35
- ...(options?.extraArgs ?? []),
36
- ];
37
- await spawnShell(this.command, args, options?.cwd);
38
- }
39
- finally {
40
- tmp.cleanup();
41
- }
42
- }
43
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * TrustRegistrar — 各Shell CLIに ~/.relic/ ディレクトリの信頼設定を登録する
3
- *
4
- * `relic init` 時に一度だけ呼ばれ、以降のセッションで
5
- * inbox.md 等への書き込みが確認なしで通るようになる。
6
- *
7
- * 対応Shell:
8
- * - Claude Code: ~/.claude/settings.json (sandbox.filesystem.allowWrite)
9
- * - Codex CLI: ~/.codex/config.toml ([sandbox] writable_roots)
10
- * - Gemini CLI: ~/.gemini/trustedFolders.json (TRUST_FOLDER)
11
- */
12
- interface TrustResult {
13
- /** 設定を追加したShell名の一覧 */
14
- registered: string[];
15
- /** 既に設定済みでスキップしたShell名の一覧 */
16
- skipped: string[];
17
- }
18
- export declare function registerTrustedFolders(relicEngramsPath: string): Promise<TrustResult>;
19
- export {};
@@ -1,141 +0,0 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { homedir } from "node:os";
4
- import { existsSync } from "node:fs";
5
- export async function registerTrustedFolders(relicEngramsPath) {
6
- const result = { registered: [], skipped: [] };
7
- await Promise.all([
8
- registerClaude(relicEngramsPath, result),
9
- registerCodex(relicEngramsPath, result),
10
- registerGemini(relicEngramsPath, result),
11
- ]);
12
- return result;
13
- }
14
- // ---------------------------------------------------------------------------
15
- // Claude Code — ~/.claude/settings.json
16
- // { "sandbox": { "filesystem": { "allowWrite": ["~/.relic/engrams"] } } }
17
- // ---------------------------------------------------------------------------
18
- async function registerClaude(engramsPath, result) {
19
- const claudeDir = join(homedir(), ".claude");
20
- const configPath = join(claudeDir, "settings.json");
21
- if (!existsSync(claudeDir)) {
22
- return;
23
- }
24
- try {
25
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- let settings = {};
27
- if (existsSync(configPath)) {
28
- const raw = await readFile(configPath, "utf-8");
29
- settings = JSON.parse(raw);
30
- }
31
- // sandbox.filesystem.allowWrite を確保
32
- settings.sandbox ??= {};
33
- settings.sandbox.filesystem ??= {};
34
- settings.sandbox.filesystem.allowWrite ??= [];
35
- const allowWrite = settings.sandbox.filesystem.allowWrite;
36
- // permissions.allow — ツール承認の自動化
37
- settings.permissions ??= {};
38
- settings.permissions.allow ??= [];
39
- const allow = settings.permissions.allow;
40
- const tildeForm = engramsPath.replace(homedir(), "~");
41
- // sandbox: allowWrite
42
- const sandboxNeeded = !allowWrite.includes(engramsPath) && !allowWrite.includes(tildeForm);
43
- if (sandboxNeeded) {
44
- allowWrite.push(tildeForm);
45
- }
46
- // permissions: Edit allow
47
- const editRule = `Edit(${tildeForm}/**)`;
48
- const permNeeded = !allow.includes(editRule);
49
- if (permNeeded) {
50
- allow.push(editRule);
51
- }
52
- if (!sandboxNeeded && !permNeeded) {
53
- result.skipped.push("Claude Code");
54
- return;
55
- }
56
- await writeFile(configPath, JSON.stringify(settings, null, 2), "utf-8");
57
- result.registered.push("Claude Code");
58
- }
59
- catch {
60
- // best effort
61
- }
62
- }
63
- // ---------------------------------------------------------------------------
64
- // Codex CLI — ~/.codex/config.toml
65
- // [sandbox]
66
- // writable_roots = ["~/.relic/engrams"]
67
- // ---------------------------------------------------------------------------
68
- async function registerCodex(engramsPath, result) {
69
- const codexDir = join(homedir(), ".codex");
70
- const configPath = join(codexDir, "config.toml");
71
- if (!existsSync(codexDir)) {
72
- return;
73
- }
74
- try {
75
- let content = "";
76
- if (existsSync(configPath)) {
77
- content = await readFile(configPath, "utf-8");
78
- }
79
- const tildeForm = engramsPath.replace(homedir(), "~");
80
- // 既に writable_roots に含まれているかチェック
81
- if (content.includes(engramsPath) || content.includes(tildeForm)) {
82
- result.skipped.push("Codex CLI");
83
- return;
84
- }
85
- // [sandbox] セクションが存在するか
86
- const sandboxMatch = content.match(/^\[sandbox\]\s*$/m);
87
- if (sandboxMatch) {
88
- // [sandbox] セクション内の writable_roots を探す
89
- const writableMatch = content.match(/^(writable_roots\s*=\s*\[)(.*?)(\])/m);
90
- if (writableMatch) {
91
- // 既存の writable_roots に追加
92
- const existing = writableMatch[2].trim();
93
- const newValue = existing
94
- ? `${existing}, "${tildeForm}"`
95
- : `"${tildeForm}"`;
96
- content = content.replace(/^(writable_roots\s*=\s*\[)(.*?)(\])/m, `$1${newValue}$3`);
97
- }
98
- else {
99
- // [sandbox] セクションに writable_roots を追加
100
- content = content.replace(/^\[sandbox\]\s*$/m, `[sandbox]\nwritable_roots = ["${tildeForm}"]`);
101
- }
102
- }
103
- else {
104
- // [sandbox] セクションごと追加
105
- content =
106
- content.trimEnd() + `\n\n[sandbox]\nwritable_roots = ["${tildeForm}"]\n`;
107
- }
108
- await writeFile(configPath, content, "utf-8");
109
- result.registered.push("Codex CLI");
110
- }
111
- catch {
112
- // best effort
113
- }
114
- }
115
- // ---------------------------------------------------------------------------
116
- // Gemini CLI — ~/.gemini/trustedFolders.json
117
- // { "/path/to/engrams": "TRUST_FOLDER" }
118
- // ---------------------------------------------------------------------------
119
- async function registerGemini(engramsPath, result) {
120
- const configPath = join(homedir(), ".gemini", "trustedFolders.json");
121
- if (!existsSync(join(homedir(), ".gemini"))) {
122
- return;
123
- }
124
- try {
125
- let folders = {};
126
- if (existsSync(configPath)) {
127
- const raw = await readFile(configPath, "utf-8");
128
- folders = JSON.parse(raw);
129
- }
130
- if (folders[engramsPath]) {
131
- result.skipped.push("Gemini CLI");
132
- return;
133
- }
134
- folders[engramsPath] = "TRUST_FOLDER";
135
- await writeFile(configPath, JSON.stringify(folders, null, 2), "utf-8");
136
- result.registered.push("Gemini CLI");
137
- }
138
- catch {
139
- // best effort
140
- }
141
- }
@@ -1,20 +0,0 @@
1
- export interface InboxSearchResult {
2
- /** マッチしたエントリの内容 */
3
- entry: string;
4
- /** エントリのインデックス(新しい順: 0が最新) */
5
- index: number;
6
- }
7
- /**
8
- * InboxSearch — inbox.md をキーワード検索する
9
- *
10
- * inbox.md は全セッションログ + [memory] エントリを含む生データ。
11
- * memory/*.md(蒸留済み)より情報量が多いため、検索先として優れている。
12
- */
13
- export declare class InboxSearch {
14
- private readonly engramsPath;
15
- constructor(engramsPath: string);
16
- search(engramId: string, query: string, limit?: number): Promise<InboxSearchResult[]>;
17
- }
18
- export declare class InboxSearchEngramNotFoundError extends Error {
19
- constructor(id: string);
20
- }
@@ -1,46 +0,0 @@
1
- import { join } from "node:path";
2
- import { existsSync } from "node:fs";
3
- import { readFile } from "node:fs/promises";
4
- const ENTRY_SEPARATOR = /\n---\n/;
5
- /**
6
- * InboxSearch — inbox.md をキーワード検索する
7
- *
8
- * inbox.md は全セッションログ + [memory] エントリを含む生データ。
9
- * memory/*.md(蒸留済み)より情報量が多いため、検索先として優れている。
10
- */
11
- export class InboxSearch {
12
- engramsPath;
13
- constructor(engramsPath) {
14
- this.engramsPath = engramsPath;
15
- }
16
- async search(engramId, query, limit = 5) {
17
- const inboxPath = join(this.engramsPath, engramId, "inbox.md");
18
- if (!existsSync(inboxPath)) {
19
- throw new InboxSearchEngramNotFoundError(engramId);
20
- }
21
- const raw = await readFile(inboxPath, "utf-8");
22
- if (!raw.trim())
23
- return [];
24
- const entries = raw
25
- .split(ENTRY_SEPARATOR)
26
- .map((e) => e.trim())
27
- .filter((e) => e.length > 0)
28
- .reverse(); // 新しい順
29
- const queryLower = query.toLowerCase();
30
- const results = [];
31
- for (let i = 0; i < entries.length; i++) {
32
- if (entries[i].toLowerCase().includes(queryLower)) {
33
- results.push({ entry: entries[i], index: i });
34
- }
35
- if (results.length >= limit)
36
- break;
37
- }
38
- return results;
39
- }
40
- }
41
- export class InboxSearchEngramNotFoundError extends Error {
42
- constructor(id) {
43
- super(`Engram "${id}" inbox not found`);
44
- this.name = "InboxSearchEngramNotFoundError";
45
- }
46
- }
@@ -1,27 +0,0 @@
1
- export interface InboxWriteResult {
2
- engramId: string;
3
- /** inbox.md に追記されたエントリ数 */
4
- totalEntries: number;
5
- /** memory/*.md に永続化されたメモリ数 */
6
- memoriesSaved: number;
7
- /** ログとして記録されたエントリ数 */
8
- logsRecorded: number;
9
- }
10
- /**
11
- * InboxWrite — MCP経由で inbox.md にエントリを追記する
12
- *
13
- * CLI の MemoryInbox (fs.watch) と同じフォーマットで inbox.md に書き込む。
14
- * `[memory]` タグ付きエントリは即座に memory/*.md にも永続化する。
15
- *
16
- * これにより CLI Shell と MCP Desktop で同一の inbox.md フォーマットを共有し、
17
- * セッションログ + メモリ永続化の二重の役割を果たす。
18
- */
19
- export declare class InboxWrite {
20
- private readonly engramsPath;
21
- constructor(engramsPath: string);
22
- execute(engramId: string, content: string): Promise<InboxWriteResult>;
23
- private appendToInbox;
24
- }
25
- export declare class InboxWriteEngramNotFoundError extends Error {
26
- constructor(id: string);
27
- }
@@ -1,72 +0,0 @@
1
- import { join } from "node:path";
2
- import { existsSync } from "node:fs";
3
- import { readFile, writeFile, mkdir } from "node:fs/promises";
4
- import { MemoryWrite } from "./memory-write.js";
5
- const ENTRY_SEPARATOR = /\n---\n/;
6
- const MEMORY_TAG = /^\[memory\]\s*/i;
7
- /**
8
- * InboxWrite — MCP経由で inbox.md にエントリを追記する
9
- *
10
- * CLI の MemoryInbox (fs.watch) と同じフォーマットで inbox.md に書き込む。
11
- * `[memory]` タグ付きエントリは即座に memory/*.md にも永続化する。
12
- *
13
- * これにより CLI Shell と MCP Desktop で同一の inbox.md フォーマットを共有し、
14
- * セッションログ + メモリ永続化の二重の役割を果たす。
15
- */
16
- export class InboxWrite {
17
- engramsPath;
18
- constructor(engramsPath) {
19
- this.engramsPath = engramsPath;
20
- }
21
- async execute(engramId, content) {
22
- const engramDir = join(this.engramsPath, engramId);
23
- if (!existsSync(engramDir)) {
24
- throw new InboxWriteEngramNotFoundError(engramId);
25
- }
26
- // inbox.md に追記
27
- const inboxPath = join(engramDir, "inbox.md");
28
- await this.appendToInbox(inboxPath, content);
29
- // エントリをパースして [memory] タグ付きを永続化
30
- const entries = content
31
- .split(ENTRY_SEPARATOR)
32
- .map((e) => e.trim())
33
- .filter((e) => e.length > 0);
34
- const writer = new MemoryWrite(this.engramsPath);
35
- let memoriesSaved = 0;
36
- let logsRecorded = 0;
37
- for (const entry of entries) {
38
- if (MEMORY_TAG.test(entry)) {
39
- const memContent = entry.replace(MEMORY_TAG, "").trim();
40
- await writer.execute(engramId, memContent);
41
- memoriesSaved++;
42
- }
43
- else {
44
- logsRecorded++;
45
- }
46
- }
47
- return {
48
- engramId,
49
- totalEntries: entries.length,
50
- memoriesSaved,
51
- logsRecorded,
52
- };
53
- }
54
- async appendToInbox(inboxPath, content) {
55
- await mkdir(join(inboxPath, ".."), { recursive: true });
56
- let existing = "";
57
- if (existsSync(inboxPath)) {
58
- existing = await readFile(inboxPath, "utf-8");
59
- }
60
- // 既存内容があれば --- で区切って追記
61
- const separator = existing.length > 0 && !existing.endsWith("\n---\n")
62
- ? "\n---\n"
63
- : "";
64
- await writeFile(inboxPath, existing + separator + content + "\n", "utf-8");
65
- }
66
- }
67
- export class InboxWriteEngramNotFoundError extends Error {
68
- constructor(id) {
69
- super(`Engram "${id}" not found`);
70
- this.name = "InboxWriteEngramNotFoundError";
71
- }
72
- }