@ectplsm/relic 0.3.2 → 0.5.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.
Files changed (69) hide show
  1. package/LICENCE.md +1 -1
  2. package/README.md +20 -13
  3. package/dist/adapters/mikoshi/client.d.ts +21 -0
  4. package/dist/adapters/mikoshi/client.js +84 -0
  5. package/dist/core/ports/index.d.ts +1 -0
  6. package/dist/core/ports/mikoshi.d.ts +937 -0
  7. package/dist/core/ports/mikoshi.js +209 -0
  8. package/dist/core/sync/crypto.d.ts +55 -0
  9. package/dist/core/sync/crypto.js +189 -0
  10. package/dist/core/sync/memory-hash.d.ts +35 -0
  11. package/dist/core/sync/memory-hash.js +61 -0
  12. package/dist/core/sync/memory-merge.d.ts +13 -0
  13. package/dist/core/sync/memory-merge.js +84 -0
  14. package/dist/core/sync/normalize.d.ts +10 -0
  15. package/dist/core/sync/normalize.js +21 -0
  16. package/dist/core/sync/persona-hash.d.ts +15 -0
  17. package/dist/core/sync/persona-hash.js +36 -0
  18. package/dist/core/usecases/archive-cursor-update.d.ts +6 -0
  19. package/dist/core/usecases/archive-cursor-update.js +18 -0
  20. package/dist/core/usecases/archive-pending.d.ts +18 -1
  21. package/dist/core/usecases/archive-pending.js +37 -2
  22. package/dist/core/usecases/claw-pull.d.ts +44 -0
  23. package/dist/core/usecases/claw-pull.js +117 -0
  24. package/dist/core/usecases/extract.d.ts +0 -1
  25. package/dist/core/usecases/extract.js +13 -27
  26. package/dist/core/usecases/index.d.ts +11 -3
  27. package/dist/core/usecases/index.js +10 -2
  28. package/dist/core/usecases/inject.d.ts +1 -0
  29. package/dist/core/usecases/inject.js +2 -0
  30. package/dist/core/usecases/memory-write.d.ts +5 -0
  31. package/dist/core/usecases/memory-write.js +26 -16
  32. package/dist/core/usecases/mikoshi-clone.d.ts +29 -0
  33. package/dist/core/usecases/mikoshi-clone.js +94 -0
  34. package/dist/core/usecases/mikoshi-download.d.ts +29 -0
  35. package/dist/core/usecases/mikoshi-download.js +94 -0
  36. package/dist/core/usecases/mikoshi-memory-pull.d.ts +38 -0
  37. package/dist/core/usecases/mikoshi-memory-pull.js +142 -0
  38. package/dist/core/usecases/mikoshi-memory-push.d.ts +34 -0
  39. package/dist/core/usecases/mikoshi-memory-push.js +105 -0
  40. package/dist/core/usecases/mikoshi-memory-sync.d.ts +36 -0
  41. package/dist/core/usecases/mikoshi-memory-sync.js +175 -0
  42. package/dist/core/usecases/mikoshi-pull.d.ts +44 -0
  43. package/dist/core/usecases/mikoshi-pull.js +124 -0
  44. package/dist/core/usecases/mikoshi-push.d.ts +39 -0
  45. package/dist/core/usecases/mikoshi-push.js +127 -0
  46. package/dist/core/usecases/mikoshi-status.d.ts +37 -0
  47. package/dist/core/usecases/mikoshi-status.js +92 -0
  48. package/dist/core/usecases/sync.d.ts +0 -5
  49. package/dist/core/usecases/sync.js +23 -61
  50. package/dist/interfaces/cli/commands/claw.js +134 -85
  51. package/dist/interfaces/cli/commands/config.js +60 -1
  52. package/dist/interfaces/cli/commands/create.js +17 -16
  53. package/dist/interfaces/cli/commands/init.js +11 -10
  54. package/dist/interfaces/cli/commands/list.js +9 -7
  55. package/dist/interfaces/cli/commands/migrate.js +8 -7
  56. package/dist/interfaces/cli/commands/mikoshi.d.ts +2 -0
  57. package/dist/interfaces/cli/commands/mikoshi.js +565 -0
  58. package/dist/interfaces/cli/commands/refresh-samples.js +10 -9
  59. package/dist/interfaces/cli/index.js +2 -0
  60. package/dist/interfaces/cli/output.d.ts +5 -0
  61. package/dist/interfaces/cli/output.js +16 -0
  62. package/dist/interfaces/cli/spinner.d.ts +13 -0
  63. package/dist/interfaces/cli/spinner.js +47 -0
  64. package/dist/interfaces/mcp/index.js +109 -38
  65. package/dist/shared/config.d.ts +25 -5
  66. package/dist/shared/config.js +33 -5
  67. package/dist/shared/engram-composer.d.ts +1 -1
  68. package/dist/shared/engram-composer.js +9 -5
  69. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
2
+ const INTERVAL_MS = 80;
3
+ /**
4
+ * Start a CLI spinner that overwrites the current line.
5
+ *
6
+ * Falls back to a single static line when stdout is not a TTY
7
+ * (e.g. CI, piped output).
8
+ */
9
+ export function startSpinner(message) {
10
+ const isTTY = process.stdout.isTTY ?? false;
11
+ let currentMessage = message;
12
+ if (!isTTY) {
13
+ process.stdout.write(`${message}\n`);
14
+ return {
15
+ update(msg) {
16
+ currentMessage = msg;
17
+ process.stdout.write(`${msg}\n`);
18
+ },
19
+ stop(finalMessage) {
20
+ if (finalMessage) {
21
+ process.stdout.write(`${finalMessage}\n`);
22
+ }
23
+ },
24
+ };
25
+ }
26
+ let frameIndex = 0;
27
+ const render = () => {
28
+ const frame = FRAMES[frameIndex % FRAMES.length];
29
+ process.stdout.write(`\r${frame} ${currentMessage}`);
30
+ frameIndex++;
31
+ };
32
+ render();
33
+ const timer = setInterval(render, INTERVAL_MS);
34
+ return {
35
+ update(msg) {
36
+ currentMessage = msg;
37
+ },
38
+ stop(finalMessage) {
39
+ clearInterval(timer);
40
+ // Clear the spinner line
41
+ process.stdout.write("\r\x1b[2K");
42
+ if (finalMessage) {
43
+ process.stdout.write(`${finalMessage}\n`);
44
+ }
45
+ },
46
+ };
47
+ }
@@ -3,8 +3,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
5
  import { ArchiveSearch, ArchiveSearchEngramNotFoundError, } from "../../core/usecases/archive-search.js";
6
- import { ArchivePending, ArchivePendingEngramNotFoundError, } from "../../core/usecases/archive-pending.js";
7
- import { ArchiveCursorUpdate, } from "../../core/usecases/archive-cursor-update.js";
6
+ import { ArchivePending, ArchiveCursorCorruptedError, ArchivePendingEngramNotFoundError, } from "../../core/usecases/archive-pending.js";
7
+ import { ArchiveCursorUpdate, ArchiveCursorAdvanceOverflowError, } from "../../core/usecases/archive-cursor-update.js";
8
8
  import { MemoryWrite, MemoryWriteEngramNotFoundError, } from "../../core/usecases/memory-write.js";
9
9
  import { CreateEngram, EngramAlreadyExistsError, InvalidEngramIdError, } from "../../core/usecases/create-engram.js";
10
10
  import { DEFAULT_SOUL, DEFAULT_IDENTITY } from "../../shared/templates.js";
@@ -13,6 +13,63 @@ import { join } from "node:path";
13
13
  import { existsSync } from "node:fs";
14
14
  import { readFile, writeFile } from "node:fs/promises";
15
15
  import { resolveDistillationBatchSize, resolveEngramsPath, } from "../../shared/config.js";
16
+ async function appendLongTermAndUserProfile(engramsPath, id, long_term, user_profile) {
17
+ let longTermWritten = false;
18
+ if (long_term) {
19
+ const memoryMdPath = join(engramsPath, id, "MEMORY.md");
20
+ if (existsSync(memoryMdPath)) {
21
+ const existing = await readFile(memoryMdPath, "utf-8");
22
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
23
+ await writeFile(memoryMdPath, existing + separator + long_term + "\n", "utf-8");
24
+ }
25
+ else {
26
+ await writeFile(memoryMdPath, long_term + "\n", "utf-8");
27
+ }
28
+ longTermWritten = true;
29
+ }
30
+ let userProfileWritten = false;
31
+ if (user_profile) {
32
+ const userMdPath = join(engramsPath, id, "USER.md");
33
+ await writeFile(userMdPath, user_profile + "\n", "utf-8");
34
+ userProfileWritten = true;
35
+ }
36
+ return { longTermWritten, userProfileWritten };
37
+ }
38
+ function assertDateCoverage(writes, expectedDates, skippedDates) {
39
+ const writeDates = writes.map((write) => write.date);
40
+ const duplicateWriteDates = findDuplicates(writeDates);
41
+ if (duplicateWriteDates.length > 0) {
42
+ throw new Error(`Duplicate write dates are not allowed: ${duplicateWriteDates.join(", ")}`);
43
+ }
44
+ const duplicateSkippedDates = findDuplicates(skippedDates);
45
+ if (duplicateSkippedDates.length > 0) {
46
+ throw new Error(`Duplicate skipped_dates are not allowed: ${duplicateSkippedDates.join(", ")}`);
47
+ }
48
+ const overlappedDates = writeDates.filter((date) => skippedDates.includes(date));
49
+ if (overlappedDates.length > 0) {
50
+ throw new Error(`Dates cannot appear in both writes and skipped_dates: ${[...new Set(overlappedDates)].join(", ")}`);
51
+ }
52
+ const expectedSet = new Set(expectedDates);
53
+ const coveredDates = [...writeDates, ...skippedDates];
54
+ const unexpectedDates = coveredDates.filter((date) => !expectedSet.has(date));
55
+ if (unexpectedDates.length > 0) {
56
+ throw new Error(`Unexpected dates: ${[...new Set(unexpectedDates)].join(", ")}`);
57
+ }
58
+ const coveredSet = new Set(coveredDates);
59
+ const missingDates = expectedDates.filter((date) => !coveredSet.has(date));
60
+ if (missingDates.length > 0) {
61
+ throw new Error(`Missing dates: ${missingDates.join(", ")}`);
62
+ }
63
+ }
64
+ function findDuplicates(values) {
65
+ const counts = new Map();
66
+ for (const value of values) {
67
+ counts.set(value, (counts.get(value) ?? 0) + 1);
68
+ }
69
+ return [...counts.entries()]
70
+ .filter(([, count]) => count > 1)
71
+ .map(([value]) => value);
72
+ }
16
73
  const server = new McpServer({
17
74
  name: "relic",
18
75
  version: "0.1.0",
@@ -153,16 +210,34 @@ server.tool("relic_archive_pending", "Get un-distilled archive entries since the
153
210
  ],
154
211
  };
155
212
  }
156
- const header = `cursor: ${result.cursor} | total: ${result.total} | returned: ${result.entries.length} | remaining: ${result.remaining}`;
213
+ const dates = [...new Set(result.entries.map((entry) => entry.date).filter(Boolean))];
214
+ const header = [
215
+ `cursor: ${result.cursor}`,
216
+ `total: ${result.total}`,
217
+ `returned: ${result.entries.length}`,
218
+ `remaining: ${result.remaining}`,
219
+ `dates: ${dates.length > 0 ? dates.join(", ") : "unknown"}`,
220
+ ].join(" | ");
157
221
  const body = result.entries
158
- .map((e, i) => `[entry ${result.cursor + i + 1}]\n${e}`)
222
+ .map((entry, i) => {
223
+ const lines = [`[entry ${result.cursor + i + 1}]`];
224
+ if (entry.date) {
225
+ lines.push(`date: ${entry.date}`);
226
+ }
227
+ if (entry.summary) {
228
+ lines.push(`summary: ${entry.summary}`);
229
+ }
230
+ lines.push(entry.raw);
231
+ return lines.join("\n");
232
+ })
159
233
  .join("\n\n---\n\n");
160
234
  return {
161
235
  content: [{ type: "text", text: `${header}\n\n${body}` }],
162
236
  };
163
237
  }
164
238
  catch (err) {
165
- if (err instanceof ArchivePendingEngramNotFoundError) {
239
+ if (err instanceof ArchivePendingEngramNotFoundError ||
240
+ err instanceof ArchiveCursorCorruptedError) {
166
241
  return {
167
242
  content: [{ type: "text", text: err.message }],
168
243
  isError: true,
@@ -174,7 +249,19 @@ server.tool("relic_archive_pending", "Get un-distilled archive entries since the
174
249
  // --- relic_memory_write ---
175
250
  server.tool("relic_memory_write", "Write distilled memory to an Engram's memory file and advance the archive cursor. Call this after reviewing pending archive entries and distilling them into key insights.", {
176
251
  id: z.string().describe("Engram ID"),
177
- content: z.string().describe("Distilled memory content to write to memory/YYYY-MM-DD.md"),
252
+ writes: z
253
+ .array(z.object({
254
+ date: z.string().describe("Archive entry date for this memory file (YYYY-MM-DD)"),
255
+ content: z.string().describe("Distilled memory content for memory/YYYY-MM-DD.md"),
256
+ }))
257
+ .min(0)
258
+ .describe("Distilled memory writes grouped by actual archive dates"),
259
+ expected_dates: z
260
+ .array(z.string())
261
+ .describe("All archive dates that must be explicitly covered by writes or skipped_dates"),
262
+ skipped_dates: z
263
+ .array(z.string())
264
+ .describe("Archive dates intentionally skipped because they produced no durable memory"),
178
265
  count: z
179
266
  .number()
180
267
  .describe("Number of archive entries distilled (from relic_archive_pending returned count)"),
@@ -186,10 +273,6 @@ server.tool("relic_memory_write", "Write distilled memory to an Engram's memory
186
273
  .string()
187
274
  .optional()
188
275
  .describe("User profile updates to write to USER.md (preferences, tendencies, work style — about the human, not the project)"),
189
- date: z
190
- .string()
191
- .optional()
192
- .describe("Date for memory file (YYYY-MM-DD, default: today)"),
193
276
  path: z
194
277
  .string()
195
278
  .optional()
@@ -197,36 +280,21 @@ server.tool("relic_memory_write", "Write distilled memory to an Engram's memory
197
280
  }, async (args) => {
198
281
  const engramsPath = await resolveEngramsPath(args.path);
199
282
  try {
200
- // Step 1: Write daily memory
201
283
  const memoryWrite = new MemoryWrite(engramsPath);
202
- const writeResult = await memoryWrite.execute(args.id, args.content, args.date);
203
- // Step 2: Append to MEMORY.md if long_term is provided
204
- let longTermWritten = false;
205
- if (args.long_term) {
206
- const memoryMdPath = join(engramsPath, args.id, "MEMORY.md");
207
- if (existsSync(memoryMdPath)) {
208
- const existing = await readFile(memoryMdPath, "utf-8");
209
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
210
- await writeFile(memoryMdPath, existing + separator + args.long_term + "\n", "utf-8");
211
- }
212
- else {
213
- await writeFile(memoryMdPath, args.long_term + "\n", "utf-8");
214
- }
215
- longTermWritten = true;
216
- }
217
- // Step 2.5: Write USER.md if user_profile is provided
218
- let userProfileWritten = false;
219
- if (args.user_profile) {
220
- const userMdPath = join(engramsPath, args.id, "USER.md");
221
- await writeFile(userMdPath, args.user_profile + "\n", "utf-8");
222
- userProfileWritten = true;
223
- }
224
- // Step 3: Advance cursor by the number of distilled entries
284
+ assertDateCoverage(args.writes, args.expected_dates, args.skipped_dates);
285
+ const writeResults = await memoryWrite.executeBatch(args.id, args.writes);
286
+ const writeSummary = writeResults.length > 0
287
+ ? writeResults
288
+ .map((result) => `${result.date} (${result.appended ? "appended" : "created"})`)
289
+ .join(", ")
290
+ : "no files";
291
+ const { longTermWritten, userProfileWritten } = await appendLongTermAndUserProfile(engramsPath, args.id, args.long_term, args.user_profile);
225
292
  const cursorUpdate = new ArchiveCursorUpdate(engramsPath);
226
293
  const cursorResult = await cursorUpdate.execute(args.id, args.count);
227
- const parts = [
228
- `Memory written to ${writeResult.date} (${writeResult.appended ? "appended" : "created"}).`,
229
- ];
294
+ const parts = [`Memory written to ${writeSummary}.`];
295
+ if (args.skipped_dates.length > 0) {
296
+ parts.push(`Skipped dates: ${args.skipped_dates.join(", ")}.`);
297
+ }
230
298
  if (longTermWritten) {
231
299
  parts.push("Long-term memory (MEMORY.md) updated.");
232
300
  }
@@ -244,7 +312,10 @@ server.tool("relic_memory_write", "Write distilled memory to an Engram's memory
244
312
  };
245
313
  }
246
314
  catch (err) {
247
- if (err instanceof MemoryWriteEngramNotFoundError) {
315
+ if (err instanceof MemoryWriteEngramNotFoundError ||
316
+ err instanceof ArchiveCursorAdvanceOverflowError ||
317
+ err instanceof ArchiveCursorCorruptedError ||
318
+ err instanceof Error) {
248
319
  return {
249
320
  content: [{ type: "text", text: err.message }],
250
321
  isError: true,
@@ -5,21 +5,27 @@ declare const CONFIG_PATH: string;
5
5
  export declare const RelicConfigSchema: z.ZodObject<{
6
6
  /** Engramデータの格納ディレクトリ */
7
7
  engramsPath: z.ZodDefault<z.ZodString>;
8
- /** Mikoshi APIのベースURL(将来用) */
9
- mikoshiUrl: z.ZodOptional<z.ZodString>;
8
+ /** Mikoshi APIのベースURL */
9
+ mikoshiUrl: z.ZodDefault<z.ZodString>;
10
+ /** Mikoshi APIキー */
11
+ mikoshiApiKey: z.ZodOptional<z.ZodString>;
12
+ /** Mikoshi memory 暗号化パスフレーズ */
13
+ mikoshiPassphrase: z.ZodOptional<z.ZodString>;
10
14
  /** --engram 未指定時に召喚するデフォルトEngram ID */
11
15
  defaultEngram: z.ZodOptional<z.ZodString>;
12
16
  /** claw inject/extract/sync で使うClawディレクトリ (default: ~/.openclaw) */
13
17
  clawPath: z.ZodOptional<z.ZodString>;
14
18
  /** システムプロンプトに含める直近メモリエントリ数 (default: 2) */
15
19
  memoryWindowSize: z.ZodDefault<z.ZodNumber>;
16
- /** 一度の記憶蒸留で扱う archive エントリ数 (default: 30) */
20
+ /** 一度の記憶蒸留で扱う archive エントリ数 (default: 100) */
17
21
  distillationBatchSize: z.ZodDefault<z.ZodNumber>;
18
22
  }, "strip", z.ZodTypeAny, {
19
23
  memoryWindowSize: number;
20
24
  distillationBatchSize: number;
21
25
  engramsPath: string;
22
- mikoshiUrl?: string | undefined;
26
+ mikoshiUrl: string;
27
+ mikoshiApiKey?: string | undefined;
28
+ mikoshiPassphrase?: string | undefined;
23
29
  defaultEngram?: string | undefined;
24
30
  clawPath?: string | undefined;
25
31
  }, {
@@ -27,6 +33,8 @@ export declare const RelicConfigSchema: z.ZodObject<{
27
33
  distillationBatchSize?: number | undefined;
28
34
  engramsPath?: string | undefined;
29
35
  mikoshiUrl?: string | undefined;
36
+ mikoshiApiKey?: string | undefined;
37
+ mikoshiPassphrase?: string | undefined;
30
38
  defaultEngram?: string | undefined;
31
39
  clawPath?: string | undefined;
32
40
  }>;
@@ -72,7 +80,19 @@ export declare function resolveClawPath(cliOverride?: string): Promise<string |
72
80
  export declare function resolveMemoryWindowSize(): Promise<number>;
73
81
  /**
74
82
  * 記憶蒸留のバッチ件数を解決する。
75
- * 優先順位: config.distillationBatchSize > 30 (デフォルト)
83
+ * 優先順位: config.distillationBatchSize > 100 (デフォルト)
76
84
  */
77
85
  export declare function resolveDistillationBatchSize(): Promise<number>;
86
+ /**
87
+ * Mikoshi URLを解決する。
88
+ */
89
+ export declare function resolveMikoshiUrl(): Promise<string>;
90
+ /**
91
+ * Mikoshi APIキーを解決する。未設定時はundefined。
92
+ */
93
+ export declare function resolveMikoshiApiKey(): Promise<string | undefined>;
94
+ /**
95
+ * Mikoshi パスフレーズを解決する。未設定時はundefined。
96
+ */
97
+ export declare function resolveMikoshiPassphrase(): Promise<string | undefined>;
78
98
  export { CONFIG_PATH, RELIC_DIR, TEMPLATES_DIR };
@@ -12,16 +12,20 @@ const CONFIG_PATH = join(RELIC_DIR, "config.json");
12
12
  export const RelicConfigSchema = z.object({
13
13
  /** Engramデータの格納ディレクトリ */
14
14
  engramsPath: z.string().default(join(homedir(), ".relic", "engrams")),
15
- /** Mikoshi APIのベースURL(将来用) */
16
- mikoshiUrl: z.string().optional(),
15
+ /** Mikoshi APIのベースURL */
16
+ mikoshiUrl: z.string().default("https://mikoshi.ectplsm.com"),
17
+ /** Mikoshi APIキー */
18
+ mikoshiApiKey: z.string().optional(),
19
+ /** Mikoshi memory 暗号化パスフレーズ */
20
+ mikoshiPassphrase: z.string().optional(),
17
21
  /** --engram 未指定時に召喚するデフォルトEngram ID */
18
22
  defaultEngram: z.string().optional(),
19
23
  /** claw inject/extract/sync で使うClawディレクトリ (default: ~/.openclaw) */
20
24
  clawPath: z.string().optional(),
21
25
  /** システムプロンプトに含める直近メモリエントリ数 (default: 2) */
22
26
  memoryWindowSize: z.number().int().min(1).default(2),
23
- /** 一度の記憶蒸留で扱う archive エントリ数 (default: 30) */
24
- distillationBatchSize: z.number().int().min(1).default(30),
27
+ /** 一度の記憶蒸留で扱う archive エントリ数 (default: 100) */
28
+ distillationBatchSize: z.number().int().min(1).default(100),
25
29
  });
26
30
  /**
27
31
  * 設定ファイルを読み込む。存在しなければデフォルト値を返す。
@@ -159,11 +163,35 @@ export async function resolveMemoryWindowSize() {
159
163
  }
160
164
  /**
161
165
  * 記憶蒸留のバッチ件数を解決する。
162
- * 優先順位: config.distillationBatchSize > 30 (デフォルト)
166
+ * 優先順位: config.distillationBatchSize > 100 (デフォルト)
163
167
  */
164
168
  export async function resolveDistillationBatchSize() {
165
169
  await ensureInitialized();
166
170
  const config = await loadConfig();
167
171
  return config.distillationBatchSize;
168
172
  }
173
+ /**
174
+ * Mikoshi URLを解決する。
175
+ */
176
+ export async function resolveMikoshiUrl() {
177
+ await ensureInitialized();
178
+ const config = await loadConfig();
179
+ return config.mikoshiUrl;
180
+ }
181
+ /**
182
+ * Mikoshi APIキーを解決する。未設定時はundefined。
183
+ */
184
+ export async function resolveMikoshiApiKey() {
185
+ await ensureInitialized();
186
+ const config = await loadConfig();
187
+ return config.mikoshiApiKey;
188
+ }
189
+ /**
190
+ * Mikoshi パスフレーズを解決する。未設定時はundefined。
191
+ */
192
+ export async function resolveMikoshiPassphrase() {
193
+ await ensureInitialized();
194
+ const config = await loadConfig();
195
+ return config.mikoshiPassphrase;
196
+ }
169
197
  export { CONFIG_PATH, RELIC_DIR, TEMPLATES_DIR };
@@ -9,7 +9,7 @@ export interface ComposeOptions {
9
9
  currentDate?: string;
10
10
  /** システムプロンプトに含める直近メモリエントリ数(デフォルト: 2) */
11
11
  memoryWindowSize?: number;
12
- /** 一度の記憶蒸留で扱う archive エントリ数(デフォルト: 30) */
12
+ /** 一度の記憶蒸留で扱う archive エントリ数(デフォルト: 100) */
13
13
  distillationBatchSize?: number;
14
14
  }
15
15
  /**
@@ -46,7 +46,7 @@ export function composeEngram(files, options) {
46
46
  */
47
47
  function composeRelicSection(meta, distillationBatchSize, currentDate) {
48
48
  const today = currentDate ?? new Date().toISOString().split("T")[0];
49
- const batchSize = distillationBatchSize ?? 30;
49
+ const batchSize = distillationBatchSize ?? 100;
50
50
  return `# Relic System
51
51
 
52
52
  - engramId: ${meta.id}
@@ -68,16 +68,20 @@ To recall past context, use \`relic_archive_search\` to search your archive by k
68
68
 
69
69
  When the user asks you to organize or distill memories:
70
70
  1. Call \`relic_archive_pending\` **once** to get un-distilled session entries (up to ${batchSize})
71
- 2. Review and distill them into:
72
- - **content**: key facts, decisions, and insights for \`memory/YYYY-MM-DD.md\`
71
+ 2. Review the archive entry dates and group the distilled memory by the **actual archive entry date**, not the distillation date
72
+ 3. Distill them into:
73
+ - **writes**: one item per date, each with \`date\` and \`content\`, for \`memory/YYYY-MM-DD.md\`
74
+ - **expected_dates**: every archive date present in the pending batch
75
+ - **skipped_dates**: dates that had pending entries but produced no durable memory worth saving
73
76
  - **long_term** (optional): only the most important, enduring facts for \`MEMORY.md\` (e.g. project architecture decisions, key constraints, hard rules)
74
77
  - **user_profile** (optional): full updated user profile for \`USER.md\` (preferences, tendencies, work style — about the human, not the project)
75
- 3. Call \`relic_memory_write\` with \`content\`, \`count\`, and optionally \`long_term\` and/or \`user_profile\`
76
- 4. If \`remaining > 0\`, inform the user how many entries are still pending — do NOT fetch more automatically
78
+ 4. Call \`relic_memory_write\` with \`writes\`, \`expected_dates\`, \`skipped_dates\`, \`count\`, and optionally \`long_term\` and/or \`user_profile\`
79
+ 5. If \`remaining > 0\`, inform the user how many entries are still pending — do NOT fetch more automatically
77
80
 
78
81
  **Important:**
79
82
  - Write distilled memories in the **same language the user is using** in the current conversation
80
83
  - Do NOT loop or repeat the distillation process — one round per user request
84
+ - Every archive date in the pending batch must appear in exactly one of \`writes.date\` or \`skipped_dates\`
81
85
  - \`long_term\` should be highly selective — only facts that matter across all future sessions (objective facts and rules)
82
86
  - \`user_profile\` is a complete replacement, not an append — write the full profile each time you update it (user tendencies, preferences, work style)
83
87
  - Distilled memories are loaded into your prompt on future sessions`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ectplsm/relic",
3
- "version": "0.3.2",
3
+ "version": "0.5.0",
4
4
  "description": "PROJECT RELIC — Engram injection system for AI constructs",
5
5
  "license": "MIT",
6
6
  "repository": {