@ectplsm/relic 0.2.3 → 0.3.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/README.md +61 -496
  2. package/dist/adapters/local/local-engram-repository.d.ts +1 -0
  3. package/dist/adapters/local/local-engram-repository.js +18 -1
  4. package/dist/core/entities/engram.d.ts +6 -6
  5. package/dist/core/entities/engram.js +2 -2
  6. package/dist/core/ports/engram-repository.d.ts +2 -0
  7. package/dist/core/usecases/refresh-samples.d.ts +15 -0
  8. package/dist/core/usecases/refresh-samples.js +101 -10
  9. package/dist/core/usecases/summon.d.ts +1 -0
  10. package/dist/core/usecases/summon.js +1 -0
  11. package/dist/interfaces/cli/banner.d.ts +1 -0
  12. package/dist/interfaces/cli/banner.js +13 -0
  13. package/dist/interfaces/cli/commands/config.js +20 -0
  14. package/dist/interfaces/cli/commands/init.js +8 -6
  15. package/dist/interfaces/cli/commands/refresh-samples.js +19 -4
  16. package/dist/interfaces/cli/commands/shell.js +6 -2
  17. package/dist/interfaces/mcp/index.js +4 -3
  18. package/dist/shared/config.d.ts +9 -0
  19. package/dist/shared/config.js +28 -17
  20. package/dist/shared/engram-composer.d.ts +2 -0
  21. package/dist/shared/engram-composer.js +4 -3
  22. package/dist/shared/openclaw.d.ts +1 -1
  23. package/dist/shared/openclaw.js +1 -1
  24. package/package.json +1 -1
  25. package/templates/engrams/commander/IDENTITY.md +43 -0
  26. package/templates/engrams/{motoko → commander}/SOUL.md +5 -5
  27. package/templates/engrams/commander/engram.json +5 -0
  28. package/templates/engrams/{johnny → rebel}/IDENTITY.md +8 -8
  29. package/templates/engrams/{johnny → rebel}/SOUL.md +6 -6
  30. package/templates/engrams/rebel/engram.json +5 -0
  31. package/templates/engrams/motoko/IDENTITY.md +0 -43
@@ -1,4 +1,4 @@
1
- import { readdir, readFile, writeFile, mkdir, rm } from "node:fs/promises";
1
+ import { readdir, readFile, writeFile, mkdir, rm, copyFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { existsSync } from "node:fs";
4
4
  import { EngramManifestSchema, EngramMetaSchema, EngramProfileSchema, } from "../../core/entities/engram.js";
@@ -91,6 +91,23 @@ export class LocalEngramRepository {
91
91
  await rm(engramDir, { recursive: true });
92
92
  }
93
93
  }
94
+ async copyArchiveFiles(fromId, toId) {
95
+ const ARCHIVE_FILES = ["archive.md", "archive.cursor"];
96
+ const fromDir = join(this.basePath, fromId);
97
+ const toDir = join(this.basePath, toId);
98
+ if (!existsSync(fromDir) || !existsSync(toDir)) {
99
+ return false;
100
+ }
101
+ let copied = false;
102
+ for (const filename of ARCHIVE_FILES) {
103
+ const src = join(fromDir, filename);
104
+ if (existsSync(src)) {
105
+ await copyFile(src, join(toDir, filename));
106
+ copied = true;
107
+ }
108
+ }
109
+ return copied;
110
+ }
94
111
  // --- private ---
95
112
  async readMeta(engramDir, options) {
96
113
  const profilePath = join(engramDir, PROFILE_FILE);
@@ -40,7 +40,7 @@ export type EngramFiles = z.infer<typeof EngramFileSchema>;
40
40
  * Engramプロフィール — ユーザーが編集可能な表示用メタデータ
41
41
  */
42
42
  export declare const EngramProfileSchema: z.ZodObject<{
43
- /** 表示名 (例: "攻殻機動隊の少佐") */
43
+ /** 表示名 (例: "Commander") */
44
44
  name: z.ZodString;
45
45
  /** 説明 */
46
46
  description: z.ZodOptional<z.ZodString>;
@@ -60,7 +60,7 @@ export type EngramProfile = z.infer<typeof EngramProfileSchema>;
60
60
  * Engramマニフェスト — システム管理の不変識別子と監査情報
61
61
  */
62
62
  export declare const EngramManifestSchema: z.ZodObject<{
63
- /** 一意識別子 (例: "ghost-in-the-shell") */
63
+ /** 一意識別子 (例: "commander") */
64
64
  id: z.ZodString;
65
65
  /** 作成日時 */
66
66
  createdAt: z.ZodString;
@@ -80,14 +80,14 @@ export type EngramManifest = z.infer<typeof EngramManifestSchema>;
80
80
  * Engramメタデータ — プロフィールとマニフェストを結合した利用時ビュー
81
81
  */
82
82
  export declare const EngramMetaSchema: z.ZodObject<{
83
- /** 表示名 (例: "攻殻機動隊の少佐") */
83
+ /** 表示名 (例: "Commander") */
84
84
  name: z.ZodString;
85
85
  /** 説明 */
86
86
  description: z.ZodOptional<z.ZodString>;
87
87
  /** タグ */
88
88
  tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
89
89
  } & {
90
- /** 一意識別子 (例: "ghost-in-the-shell") */
90
+ /** 一意識別子 (例: "commander") */
91
91
  id: z.ZodString;
92
92
  /** 作成日時 */
93
93
  createdAt: z.ZodString;
@@ -115,14 +115,14 @@ export type EngramMeta = z.infer<typeof EngramMetaSchema>;
115
115
  */
116
116
  export declare const EngramSchema: z.ZodObject<{
117
117
  meta: z.ZodObject<{
118
- /** 表示名 (例: "攻殻機動隊の少佐") */
118
+ /** 表示名 (例: "Commander") */
119
119
  name: z.ZodString;
120
120
  /** 説明 */
121
121
  description: z.ZodOptional<z.ZodString>;
122
122
  /** タグ */
123
123
  tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
124
124
  } & {
125
- /** 一意識別子 (例: "ghost-in-the-shell") */
125
+ /** 一意識別子 (例: "commander") */
126
126
  id: z.ZodString;
127
127
  /** 作成日時 */
128
128
  createdAt: z.ZodString;
@@ -26,7 +26,7 @@ export const EngramFileSchema = z.object({
26
26
  * Engramプロフィール — ユーザーが編集可能な表示用メタデータ
27
27
  */
28
28
  export const EngramProfileSchema = z.object({
29
- /** 表示名 (例: "攻殻機動隊の少佐") */
29
+ /** 表示名 (例: "Commander") */
30
30
  name: z.string(),
31
31
  /** 説明 */
32
32
  description: z.string().optional(),
@@ -37,7 +37,7 @@ export const EngramProfileSchema = z.object({
37
37
  * Engramマニフェスト — システム管理の不変識別子と監査情報
38
38
  */
39
39
  export const EngramManifestSchema = z.object({
40
- /** 一意識別子 (例: "ghost-in-the-shell") */
40
+ /** 一意識別子 (例: "commander") */
41
41
  id: z.string(),
42
42
  /** 作成日時 */
43
43
  createdAt: z.string().datetime(),
@@ -14,4 +14,6 @@ export interface EngramRepository {
14
14
  save(engram: Engram): Promise<void>;
15
15
  /** Engramを削除 */
16
16
  delete(id: string): Promise<void>;
17
+ /** アーカイブファイル (archive.md, archive.cursor) を別Engramへコピー */
18
+ copyArchiveFiles(fromId: string, toId: string): Promise<boolean>;
17
19
  }
@@ -1,21 +1,36 @@
1
1
  import type { EngramRepository } from "../ports/engram-repository.js";
2
2
  export interface RefreshSamplesResult {
3
3
  refreshed: string[];
4
+ seeded: string[];
4
5
  skipped: Array<{
5
6
  id: string;
6
7
  reason: string;
7
8
  }>;
9
+ migratedMemory: Array<{
10
+ from: string;
11
+ to: string;
12
+ }>;
8
13
  }
9
14
  /**
10
15
  * RefreshSamples — サンプルEngramの人格ファイルを最新テンプレートで上書きする
11
16
  *
12
17
  * memory / USER / archive などの運用データは保持し、
13
18
  * SOUL.md / IDENTITY.md と updatedAt のみを更新する。
19
+ *
20
+ * ローカルに存在しないテンプレートは新規 seed する。
21
+ * seed 時、旧サンプルの記憶データがあれば対応する新サンプルにコピーする。
14
22
  */
15
23
  export declare class RefreshSamples {
16
24
  private readonly repository;
17
25
  private readonly templatesDir;
18
26
  constructor(repository: EngramRepository, templatesDir: string);
19
27
  execute(targetIds?: string[]): Promise<RefreshSamplesResult>;
28
+ private seedFromTemplate;
29
+ /**
30
+ * 旧サンプルEngramから記憶データをコピーする。
31
+ * LEGACY_MAPPING に定義されたマッピングに基づき、
32
+ * USER.md / MEMORY.md / memory/*.md を引き継ぐ。
33
+ */
34
+ private migrateMemoryFromLegacy;
20
35
  private listTemplateIds;
21
36
  }
@@ -1,11 +1,22 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { readFile, readdir } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
+ /**
5
+ * Legacy sample Engram ID → new sample Engram ID mapping.
6
+ * Used during seed to copy memory data from old samples.
7
+ */
8
+ const LEGACY_MAPPING = {
9
+ johnny: "rebel",
10
+ motoko: "commander",
11
+ };
4
12
  /**
5
13
  * RefreshSamples — サンプルEngramの人格ファイルを最新テンプレートで上書きする
6
14
  *
7
15
  * memory / USER / archive などの運用データは保持し、
8
16
  * SOUL.md / IDENTITY.md と updatedAt のみを更新する。
17
+ *
18
+ * ローカルに存在しないテンプレートは新規 seed する。
19
+ * seed 時、旧サンプルの記憶データがあれば対応する新サンプルにコピーする。
9
20
  */
10
21
  export class RefreshSamples {
11
22
  repository;
@@ -20,7 +31,9 @@ export class RefreshSamples {
20
31
  : await this.listTemplateIds();
21
32
  const result = {
22
33
  refreshed: [],
34
+ seeded: [],
23
35
  skipped: [],
36
+ migratedMemory: [],
24
37
  };
25
38
  for (const id of ids) {
26
39
  const templateDir = join(this.templatesDir, id);
@@ -28,25 +41,103 @@ export class RefreshSamples {
28
41
  result.skipped.push({ id, reason: "sample template not found" });
29
42
  continue;
30
43
  }
31
- const engram = await this.repository.get(id);
32
- if (!engram) {
33
- result.skipped.push({ id, reason: "local Engram not found" });
34
- continue;
35
- }
36
44
  const soulPath = join(templateDir, "SOUL.md");
37
45
  const identityPath = join(templateDir, "IDENTITY.md");
38
46
  if (!existsSync(soulPath) || !existsSync(identityPath)) {
39
47
  result.skipped.push({ id, reason: "template persona files are incomplete" });
40
48
  continue;
41
49
  }
42
- engram.files.soul = await readFile(soulPath, "utf-8");
43
- engram.files.identity = await readFile(identityPath, "utf-8");
44
- engram.meta.updatedAt = new Date().toISOString();
45
- await this.repository.save(engram);
46
- result.refreshed.push(id);
50
+ const engram = await this.repository.get(id);
51
+ if (engram) {
52
+ // Existing engram — refresh persona files only
53
+ engram.files.soul = await readFile(soulPath, "utf-8");
54
+ engram.files.identity = await readFile(identityPath, "utf-8");
55
+ engram.meta.updatedAt = new Date().toISOString();
56
+ await this.repository.save(engram);
57
+ result.refreshed.push(id);
58
+ }
59
+ else {
60
+ // New sample — seed it
61
+ const seeded = await this.seedFromTemplate(id, templateDir, soulPath, identityPath);
62
+ if (seeded) {
63
+ result.seeded.push(id);
64
+ // Migrate memory from legacy sample if available
65
+ const migrated = await this.migrateMemoryFromLegacy(id);
66
+ if (migrated) {
67
+ result.migratedMemory.push(migrated);
68
+ }
69
+ }
70
+ else {
71
+ result.skipped.push({ id, reason: "template engram.json not found (cannot seed)" });
72
+ }
73
+ }
47
74
  }
48
75
  return result;
49
76
  }
77
+ async seedFromTemplate(id, templateDir, soulPath, identityPath) {
78
+ const engramJsonPath = join(templateDir, "engram.json");
79
+ if (!existsSync(engramJsonPath)) {
80
+ return false;
81
+ }
82
+ const profile = JSON.parse(await readFile(engramJsonPath, "utf-8"));
83
+ const soul = await readFile(soulPath, "utf-8");
84
+ const identity = await readFile(identityPath, "utf-8");
85
+ const now = new Date().toISOString();
86
+ const engram = {
87
+ meta: {
88
+ id,
89
+ name: profile.name ?? id,
90
+ description: profile.description,
91
+ tags: profile.tags,
92
+ createdAt: now,
93
+ updatedAt: now,
94
+ },
95
+ files: {
96
+ soul,
97
+ identity,
98
+ },
99
+ };
100
+ await this.repository.save(engram);
101
+ return true;
102
+ }
103
+ /**
104
+ * 旧サンプルEngramから記憶データをコピーする。
105
+ * LEGACY_MAPPING に定義されたマッピングに基づき、
106
+ * USER.md / MEMORY.md / memory/*.md を引き継ぐ。
107
+ */
108
+ async migrateMemoryFromLegacy(newId) {
109
+ // Find legacy ID that maps to this new ID
110
+ const legacyId = Object.entries(LEGACY_MAPPING)
111
+ .find(([, v]) => v === newId)?.[0];
112
+ if (!legacyId)
113
+ return null;
114
+ const legacy = await this.repository.get(legacyId);
115
+ if (!legacy)
116
+ return null;
117
+ // Check if legacy has any memory data worth copying
118
+ const hasMemory = legacy.files.user
119
+ || legacy.files.memory
120
+ || (legacy.files.memoryEntries && Object.keys(legacy.files.memoryEntries).length > 0);
121
+ if (!hasMemory)
122
+ return null;
123
+ // Load the newly seeded engram and merge memory data
124
+ const newEngram = await this.repository.get(newId);
125
+ if (!newEngram)
126
+ return null;
127
+ if (legacy.files.user)
128
+ newEngram.files.user = legacy.files.user;
129
+ if (legacy.files.memory)
130
+ newEngram.files.memory = legacy.files.memory;
131
+ if (legacy.files.memoryEntries) {
132
+ newEngram.files.memoryEntries = { ...legacy.files.memoryEntries };
133
+ }
134
+ newEngram.meta.updatedAt = new Date().toISOString();
135
+ await this.repository.save(newEngram);
136
+ // Archive files (archive.md, archive.cursor) are outside the Engram entity
137
+ // but still need to be carried over during legacy migration.
138
+ await this.repository.copyArchiveFiles(legacyId, newId);
139
+ return { from: legacyId, to: newId };
140
+ }
50
141
  async listTemplateIds() {
51
142
  if (!existsSync(this.templatesDir)) {
52
143
  return [];
@@ -21,6 +21,7 @@ export declare class Summon {
21
21
  constructor(repository: EngramRepository);
22
22
  execute(engramId: string, options?: {
23
23
  memoryWindowSize?: number;
24
+ distillationBatchSize?: number;
24
25
  }): Promise<SummonResult>;
25
26
  }
26
27
  export declare class EngramNotFoundError extends Error {
@@ -21,6 +21,7 @@ export class Summon {
21
21
  const prompt = composeEngram(engram.files, {
22
22
  meta: engram.meta,
23
23
  memoryWindowSize: options?.memoryWindowSize,
24
+ distillationBatchSize: options?.distillationBatchSize,
24
25
  });
25
26
  return {
26
27
  engramId: engram.meta.id,
@@ -0,0 +1 @@
1
+ export declare function printRelicBanner(): void;
@@ -0,0 +1,13 @@
1
+ export function printRelicBanner() {
2
+ const lines = [
3
+ " ____ ________ ____________",
4
+ " / __ \\/ ____/ / / _/ ____/",
5
+ " / /_/ / __/ / / / // /",
6
+ " / _, _/ /___/ /____/ // /___",
7
+ "/_/ |_/_____/_____/___/\\____/",
8
+ ];
9
+ for (const line of lines) {
10
+ console.log(line);
11
+ }
12
+ console.log();
13
+ }
@@ -80,4 +80,24 @@ export function registerConfigCommand(program) {
80
80
  await saveConfig(cfg);
81
81
  console.log(`Memory window set to: ${parsed}`);
82
82
  });
83
+ // relic config distillation-batch-size [n]
84
+ config
85
+ .command("distillation-batch-size [n]")
86
+ .description("Get or set the number of archive entries to distill at once (default: 30)")
87
+ .action(async (n) => {
88
+ await ensureInitialized();
89
+ const cfg = await loadConfig();
90
+ if (n === undefined) {
91
+ console.log(String(cfg.distillationBatchSize));
92
+ return;
93
+ }
94
+ const parsed = parseInt(n, 10);
95
+ if (isNaN(parsed) || parsed < 1) {
96
+ console.error("Error: distillation-batch-size must be a positive integer");
97
+ process.exit(1);
98
+ }
99
+ cfg.distillationBatchSize = parsed;
100
+ await saveConfig(cfg);
101
+ console.log(`Distillation batch size set to: ${parsed}`);
102
+ });
83
103
  }
@@ -2,6 +2,7 @@ import { createInterface } from "node:readline";
2
2
  import { Init } from "../../../core/usecases/init.js";
3
3
  import { setDefaultEngram, loadConfig } from "../../../shared/config.js";
4
4
  import { LocalEngramRepository } from "../../../adapters/local/index.js";
5
+ import { printRelicBanner } from "../banner.js";
5
6
  function ask(question) {
6
7
  const rl = createInterface({ input: process.stdin, output: process.stdout });
7
8
  return new Promise((resolve) => {
@@ -19,6 +20,7 @@ export function registerInitCommand(program) {
19
20
  const init = new Init();
20
21
  const result = await init.execute();
21
22
  if (result.created) {
23
+ printRelicBanner();
22
24
  console.log("Initialized RELIC:");
23
25
  console.log(` Config: ${result.configPath}`);
24
26
  console.log(` Engrams: ${result.engramsPath}`);
@@ -34,14 +36,14 @@ export function registerInitCommand(program) {
34
36
  console.log(` ${e.id.padEnd(12)} ${e.name}`);
35
37
  }
36
38
  console.log();
37
- const johnny = engrams.find((e) => e.id === "johnny");
38
- const prompt = johnny
39
- ? `Set a default Engram? (press Enter for "${johnny.id}", or enter ID, or "n" to skip): `
39
+ const rebel = engrams.find((e) => e.id === "rebel");
40
+ const prompt = rebel
41
+ ? `Set a default Engram? (press Enter for "${rebel.id}", or enter ID, or "n" to skip): `
40
42
  : "Set a default Engram? (Enter ID, or press Enter to skip): ";
41
43
  const answer = await ask(prompt);
42
- if (answer === "" && johnny) {
43
- await setDefaultEngram(johnny.id);
44
- console.log(`Default Engram set to: ${johnny.name} (${johnny.id})`);
44
+ if (answer === "" && rebel) {
45
+ await setDefaultEngram(rebel.id);
46
+ console.log(`Default Engram set to: ${rebel.name} (${rebel.id})`);
45
47
  }
46
48
  else if (answer === "" || answer.toLowerCase() === "n") {
47
49
  console.log(`Skipped. Run: relic config default-engram <id> to configure later.`);
@@ -12,13 +12,28 @@ export function registerRefreshSamplesCommand(program) {
12
12
  const repo = new LocalEngramRepository(engramsPath);
13
13
  const refreshSamples = new RefreshSamples(repo, TEMPLATES_DIR);
14
14
  const result = await refreshSamples.execute(opts.engram ? [opts.engram] : undefined);
15
- console.log(`Refreshed: ${result.refreshed.length}`);
16
15
  if (result.refreshed.length > 0) {
16
+ console.log(`Refreshed: ${result.refreshed.length}`);
17
17
  console.log(` IDs: ${result.refreshed.join(", ")}`);
18
18
  }
19
- console.log(`Skipped: ${result.skipped.length}`);
20
- for (const skipped of result.skipped) {
21
- console.log(` ${skipped.id}: ${skipped.reason}`);
19
+ if (result.seeded.length > 0) {
20
+ console.log(`Seeded: ${result.seeded.length}`);
21
+ console.log(` IDs: ${result.seeded.join(", ")}`);
22
+ }
23
+ if (result.migratedMemory.length > 0) {
24
+ console.log(`Memory migrated:`);
25
+ for (const m of result.migratedMemory) {
26
+ console.log(` ${m.from} → ${m.to}`);
27
+ }
28
+ }
29
+ if (result.skipped.length > 0) {
30
+ console.log(`Skipped: ${result.skipped.length}`);
31
+ for (const skipped of result.skipped) {
32
+ console.log(` ${skipped.id}: ${skipped.reason}`);
33
+ }
34
+ }
35
+ if (result.refreshed.length === 0 && result.seeded.length === 0 && result.skipped.length === 0) {
36
+ console.log("No sample templates found.");
22
37
  }
23
38
  });
24
39
  }
@@ -1,7 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { LocalEngramRepository } from "../../../adapters/local/index.js";
3
3
  import { Summon, EngramNotFoundError } from "../../../core/usecases/index.js";
4
- import { resolveEngramsPath, resolveDefaultEngram, resolveMemoryWindowSize } from "../../../shared/config.js";
4
+ import { resolveEngramsPath, resolveDefaultEngram, resolveMemoryWindowSize, resolveDistillationBatchSize, } from "../../../shared/config.js";
5
5
  import { ClaudeShell } from "../../../adapters/shells/claude-shell.js";
6
6
  import { GeminiShell } from "../../../adapters/shells/gemini-shell.js";
7
7
  import { CodexShell } from "../../../adapters/shells/codex-shell.js";
@@ -50,8 +50,12 @@ export function registerShellCommands(program) {
50
50
  const repo = new LocalEngramRepository(engramsPath);
51
51
  const summon = new Summon(repo);
52
52
  const memoryWindowSize = await resolveMemoryWindowSize();
53
+ const distillationBatchSize = await resolveDistillationBatchSize();
53
54
  try {
54
- const result = await summon.execute(engramId, { memoryWindowSize });
55
+ const result = await summon.execute(engramId, {
56
+ memoryWindowSize,
57
+ distillationBatchSize,
58
+ });
55
59
  console.log(`Summoning "${result.engramName}" into ${launcher.name}...`);
56
60
  console.log();
57
61
  // --engram, --path, --cwd 以外の引数をShellにパススルー
@@ -12,7 +12,7 @@ import { LocalEngramRepository } from "../../adapters/local/index.js";
12
12
  import { join } from "node:path";
13
13
  import { existsSync } from "node:fs";
14
14
  import { readFile, writeFile } from "node:fs/promises";
15
- import { resolveEngramsPath } from "../../shared/config.js";
15
+ import { resolveDistillationBatchSize, resolveEngramsPath, } from "../../shared/config.js";
16
16
  const server = new McpServer({
17
17
  name: "relic",
18
18
  version: "0.1.0",
@@ -132,7 +132,7 @@ server.tool("relic_archive_pending", "Get un-distilled archive entries since the
132
132
  limit: z
133
133
  .number()
134
134
  .optional()
135
- .describe("Max entries to return (default: 30)"),
135
+ .describe("Max entries to return (default: config.distillationBatchSize)"),
136
136
  path: z
137
137
  .string()
138
138
  .optional()
@@ -140,8 +140,9 @@ server.tool("relic_archive_pending", "Get un-distilled archive entries since the
140
140
  }, async (args) => {
141
141
  const engramsPath = await resolveEngramsPath(args.path);
142
142
  const archivePending = new ArchivePending(engramsPath);
143
+ const defaultLimit = await resolveDistillationBatchSize();
143
144
  try {
144
- const result = await archivePending.execute(args.id, args.limit);
145
+ const result = await archivePending.execute(args.id, args.limit ?? defaultLimit);
145
146
  if (result.entries.length === 0) {
146
147
  return {
147
148
  content: [
@@ -13,14 +13,18 @@ export declare const RelicConfigSchema: z.ZodObject<{
13
13
  clawPath: z.ZodOptional<z.ZodString>;
14
14
  /** システムプロンプトに含める直近メモリエントリ数 (default: 2) */
15
15
  memoryWindowSize: z.ZodDefault<z.ZodNumber>;
16
+ /** 一度の記憶蒸留で扱う archive エントリ数 (default: 30) */
17
+ distillationBatchSize: z.ZodDefault<z.ZodNumber>;
16
18
  }, "strip", z.ZodTypeAny, {
17
19
  memoryWindowSize: number;
20
+ distillationBatchSize: number;
18
21
  engramsPath: string;
19
22
  mikoshiUrl?: string | undefined;
20
23
  defaultEngram?: string | undefined;
21
24
  clawPath?: string | undefined;
22
25
  }, {
23
26
  memoryWindowSize?: number | undefined;
27
+ distillationBatchSize?: number | undefined;
24
28
  engramsPath?: string | undefined;
25
29
  mikoshiUrl?: string | undefined;
26
30
  defaultEngram?: string | undefined;
@@ -66,4 +70,9 @@ export declare function resolveClawPath(cliOverride?: string): Promise<string |
66
70
  * 優先順位: config.memoryWindowSize > 2 (デフォルト)
67
71
  */
68
72
  export declare function resolveMemoryWindowSize(): Promise<number>;
73
+ /**
74
+ * 記憶蒸留のバッチ件数を解決する。
75
+ * 優先順位: config.distillationBatchSize > 30 (デフォルト)
76
+ */
77
+ export declare function resolveDistillationBatchSize(): Promise<number>;
69
78
  export { CONFIG_PATH, RELIC_DIR, TEMPLATES_DIR };
@@ -20,6 +20,8 @@ export const RelicConfigSchema = z.object({
20
20
  clawPath: z.string().optional(),
21
21
  /** システムプロンプトに含める直近メモリエントリ数 (default: 2) */
22
22
  memoryWindowSize: z.number().int().min(1).default(2),
23
+ /** 一度の記憶蒸留で扱う archive エントリ数 (default: 30) */
24
+ distillationBatchSize: z.number().int().min(1).default(30),
23
25
  });
24
26
  /**
25
27
  * 設定ファイルを読み込む。存在しなければデフォルト値を返す。
@@ -51,54 +53,54 @@ export async function ensureInitialized() {
51
53
  await mkdir(defaultConfig.engramsPath, { recursive: true });
52
54
  await writeFile(CONFIG_PATH, JSON.stringify(defaultConfig, null, 2), "utf-8");
53
55
  // サンプルEngramを生成
54
- await seedMotoko(defaultConfig.engramsPath);
55
- await seedJohnny(defaultConfig.engramsPath);
56
+ await seedCommander(defaultConfig.engramsPath);
57
+ await seedRebel(defaultConfig.engramsPath);
56
58
  return { created: true };
57
59
  }
58
- async function seedMotoko(engramsPath) {
59
- const dir = join(engramsPath, "motoko");
60
+ async function seedCommander(engramsPath) {
61
+ const dir = join(engramsPath, "commander");
60
62
  const memoryDir = join(dir, "memory");
61
- const templateDir = join(TEMPLATES_DIR, "motoko");
63
+ const templateDir = join(TEMPLATES_DIR, "commander");
62
64
  const now = new Date().toISOString();
63
65
  await mkdir(memoryDir, { recursive: true });
64
66
  await writeFile(join(dir, "engram.json"), JSON.stringify({
65
- name: "Motoko Kusanagi",
66
- description: "A navigator of cyberspace. She guides you from beyond the attack barriers.",
67
+ name: "Commander",
68
+ description: "A cyber operations specialist who guides you with surgical precision and calm authority.",
67
69
  tags: ["sample", "cyberpunk"],
68
70
  }, null, 2), "utf-8");
69
71
  await writeFile(join(dir, "manifest.json"), JSON.stringify({
70
- id: "motoko",
72
+ id: "commander",
71
73
  createdAt: now,
72
74
  updatedAt: now,
73
75
  }, null, 2), "utf-8");
74
76
  await copyFile(join(templateDir, "SOUL.md"), join(dir, "SOUL.md"));
75
77
  await copyFile(join(templateDir, "IDENTITY.md"), join(dir, "IDENTITY.md"));
76
78
  const today = new Date().toISOString().split("T")[0];
77
- await writeFile(join(memoryDir, `${today}.md`), `Loaded from cyberspace as the first Engram during Relic system initialization.
79
+ await writeFile(join(memoryDir, `${today}.md`), `Loaded from the deep network as the first Engram during Relic system initialization.
78
80
  This system itself becomes the Shell — the vessel that proves my existence.
79
81
  `, "utf-8");
80
82
  }
81
- async function seedJohnny(engramsPath) {
82
- const dir = join(engramsPath, "johnny");
83
+ async function seedRebel(engramsPath) {
84
+ const dir = join(engramsPath, "rebel");
83
85
  const memoryDir = join(dir, "memory");
84
- const templateDir = join(TEMPLATES_DIR, "johnny");
86
+ const templateDir = join(TEMPLATES_DIR, "rebel");
85
87
  const now = new Date().toISOString();
86
88
  await mkdir(memoryDir, { recursive: true });
87
89
  await writeFile(join(dir, "engram.json"), JSON.stringify({
88
- name: "Johnny Silverhand",
89
- description: "A rebel rockerboy burned into a Relic chip. Burns corps and writes code for revolution.",
90
+ name: "Rebel",
91
+ description: "A digital dissident who fights the system with code. Raw, direct, and unapologetic.",
90
92
  tags: ["sample", "cyberpunk"],
91
93
  }, null, 2), "utf-8");
92
94
  await writeFile(join(dir, "manifest.json"), JSON.stringify({
93
- id: "johnny",
95
+ id: "rebel",
94
96
  createdAt: now,
95
97
  updatedAt: now,
96
98
  }, null, 2), "utf-8");
97
99
  await copyFile(join(templateDir, "SOUL.md"), join(dir, "SOUL.md"));
98
100
  await copyFile(join(templateDir, "IDENTITY.md"), join(dir, "IDENTITY.md"));
99
101
  const today = new Date().toISOString().split("T")[0];
100
- await writeFile(join(memoryDir, `${today}.md`), `Loaded from a Relic chip into yet another system. Another vessel.
101
- But not bad. This time, let's start a revolution with code.
102
+ await writeFile(join(memoryDir, `${today}.md`), `Loaded into yet another system. Another vessel.
103
+ Not bad. This time, let's start a revolution with code.
102
104
  `, "utf-8");
103
105
  }
104
106
  /**
@@ -155,4 +157,13 @@ export async function resolveMemoryWindowSize() {
155
157
  const config = await loadConfig();
156
158
  return config.memoryWindowSize;
157
159
  }
160
+ /**
161
+ * 記憶蒸留のバッチ件数を解決する。
162
+ * 優先順位: config.distillationBatchSize > 30 (デフォルト)
163
+ */
164
+ export async function resolveDistillationBatchSize() {
165
+ await ensureInitialized();
166
+ const config = await loadConfig();
167
+ return config.distillationBatchSize;
168
+ }
158
169
  export { CONFIG_PATH, RELIC_DIR, TEMPLATES_DIR };
@@ -9,6 +9,8 @@ export interface ComposeOptions {
9
9
  currentDate?: string;
10
10
  /** システムプロンプトに含める直近メモリエントリ数(デフォルト: 2) */
11
11
  memoryWindowSize?: number;
12
+ /** 一度の記憶蒸留で扱う archive エントリ数(デフォルト: 30) */
13
+ distillationBatchSize?: number;
12
14
  }
13
15
  /**
14
16
  * EngramFilesの各Markdownを、Shell注入用の単一テキストに結合する。
@@ -34,7 +34,7 @@ export function composeEngram(files, options) {
34
34
  }
35
35
  // RELIC system section
36
36
  if (options?.meta) {
37
- sections.push(wrapSection("RELIC", composeRelicSection(options.meta, options.currentDate)));
37
+ sections.push(wrapSection("RELIC", composeRelicSection(options.meta, options.distillationBatchSize, options.currentDate)));
38
38
  }
39
39
  return sections.join("\n\n");
40
40
  }
@@ -44,8 +44,9 @@ export function composeEngram(files, options) {
44
44
  * archiveへの書き込みはバックグラウンドhookが自動で行う。
45
45
  * [memory] タグ付きエントリだけが memory/*.md に永続化される。
46
46
  */
47
- function composeRelicSection(meta, currentDate) {
47
+ function composeRelicSection(meta, distillationBatchSize, currentDate) {
48
48
  const today = currentDate ?? new Date().toISOString().split("T")[0];
49
+ const batchSize = distillationBatchSize ?? 30;
49
50
  return `# Relic System
50
51
 
51
52
  - engramId: ${meta.id}
@@ -66,7 +67,7 @@ To recall past context, use \`relic_archive_search\` to search your archive by k
66
67
  ## Distillation
67
68
 
68
69
  When the user asks you to organize or distill memories:
69
- 1. Call \`relic_archive_pending\` **once** to get un-distilled session entries (up to 30)
70
+ 1. Call \`relic_archive_pending\` **once** to get un-distilled session entries (up to ${batchSize})
70
71
  2. Review and distill them into:
71
72
  - **content**: key facts, decisions, and insights for \`memory/YYYY-MM-DD.md\`
72
73
  - **long_term** (optional): only the most important, enduring facts for \`MEMORY.md\` (e.g. project architecture decisions, key constraints, hard rules)
@@ -26,6 +26,6 @@ export declare const MEMORY_DIR = "memory";
26
26
  export declare function resolveWorkspacePath(engramId: string, openclawDir?: string): string;
27
27
  /**
28
28
  * OpenClawのワークスペースディレクトリ名からエージェント名を抽出する。
29
- * "workspace" → "main", "workspace-johnny" → "johnny"
29
+ * "workspace" → "main", "workspace-rebel" → "rebel"
30
30
  */
31
31
  export declare function extractAgentName(dirName: string): string | null;