oc-tweaks 0.10.0 → 0.11.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 (2) hide show
  1. package/dist/index.js +82 -40
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -63,10 +63,12 @@ async function loadJsonConfig(path, defaults) {
63
63
  var DEFAULT_CONFIG = {
64
64
  compaction: {},
65
65
  autoMemory: {
66
+ enabled: false,
66
67
  autoWrite: "notify",
67
68
  maxBytesPerFile: 32768,
68
69
  maxWritesPerSession: 5,
69
- summaryTokenBudget: 4000
70
+ summaryTokenBudget: 4000,
71
+ maxDiffLines: 500
70
72
  },
71
73
  backgroundSubagent: {},
72
74
  leaderboard: {},
@@ -148,6 +150,75 @@ function buildSystemInjection(entries, opts) {
148
150
  `);
149
151
  }
150
152
 
153
+ // src/plugins/auto-memory/recall.ts
154
+ import { readFile } from "node:fs/promises";
155
+
156
+ // src/plugins/auto-memory/sanitize.ts
157
+ function wrapAsUntrusted(id, body) {
158
+ return `<untrusted_memory id="${id}">
159
+ ${body}
160
+ </untrusted_memory>`;
161
+ }
162
+
163
+ // src/plugins/auto-memory/recall.ts
164
+ var DEFAULT_MAX_BYTES_PER_FILE = 32768;
165
+ var TRUNCATION_MARKER = "<!-- truncated -->";
166
+ var NO_MATCH_SENTINEL_ID = "__none__";
167
+ var NO_MATCH_SENTINEL_CONTENT = "<!-- recall:no-match -->";
168
+ function truncateBytes(body, maxBytes) {
169
+ const buf = Buffer.from(body, "utf8");
170
+ if (buf.byteLength <= maxBytes)
171
+ return body;
172
+ return buf.subarray(0, maxBytes).toString("utf8") + ` ${TRUNCATION_MARKER}`;
173
+ }
174
+ function fireOnHit(id, cb) {
175
+ if (!cb)
176
+ return;
177
+ try {
178
+ const r = cb(id);
179
+ if (r && typeof r.catch === "function") {
180
+ r.catch((err) => {
181
+ console.error("[recall] onHit (async) failed:", err);
182
+ });
183
+ }
184
+ } catch (err) {
185
+ console.error("[recall] onHit (sync) failed:", err);
186
+ }
187
+ }
188
+ async function recallMemory(query, registry, opts = {}) {
189
+ const maxBytes = opts.maxBytesPerFile ?? DEFAULT_MAX_BYTES_PER_FILE;
190
+ if (query.length === 0) {
191
+ return [{ id: NO_MATCH_SENTINEL_ID, content: NO_MATCH_SENTINEL_CONTENT }];
192
+ }
193
+ const typeCandidates = opts.filterType ? registry.filter((e) => e.meta.type === opts.filterType) : registry;
194
+ const candidates = opts.filterTags?.length ? typeCandidates.filter((entry) => {
195
+ const tags = entry.meta.tags;
196
+ return !tags || tags.some((tag) => opts.filterTags?.includes(tag));
197
+ }) : typeCandidates;
198
+ const hits = [];
199
+ for (const entry of candidates) {
200
+ let body;
201
+ try {
202
+ body = await readFile(entry.absPath, "utf8");
203
+ } catch {
204
+ continue;
205
+ }
206
+ if (!body.includes(query))
207
+ continue;
208
+ const truncated = truncateBytes(body, maxBytes);
209
+ const id = entry.meta.id || entry.absPath;
210
+ hits.push({
211
+ id,
212
+ content: wrapAsUntrusted(id, truncated)
213
+ });
214
+ fireOnHit(id, opts.onHit);
215
+ }
216
+ if (hits.length === 0) {
217
+ return [{ id: NO_MATCH_SENTINEL_ID, content: NO_MATCH_SENTINEL_CONTENT }];
218
+ }
219
+ return hits;
220
+ }
221
+
151
222
  // src/plugins/auto-memory/registry.ts
152
223
  import { closeSync, openSync, readdirSync, readSync } from "node:fs";
153
224
  import { join } from "node:path";
@@ -455,53 +526,24 @@ var autoMemoryPlugin = async ({ directory }) => {
455
526
  return;
456
527
  await ensureAutoMemoryInfra(home, projectMemoryDir);
457
528
  const entries = scanMemoryRoots(globalMemoryDir, projectMemoryDir);
529
+ const recalled = await recallMemory("", entries, {
530
+ onHit: (id) => {
531
+ process.stderr.write(`T9-onHit: ${id}
532
+ `);
533
+ }
534
+ });
458
535
  const injection = buildMemoryInjection(entries, config.autoMemory.summaryTokenBudget ?? 4000);
536
+ const recallInjection = recalled.map((result) => result.content).join(`
537
+ `);
459
538
  const summaryPathHints = buildSummaryPathHints(entries);
460
539
  output.system.push(buildMemoryGuide({
461
540
  globalMemoryDir,
462
541
  projectMemoryDir,
463
542
  entries,
464
- injection,
543
+ injection: [injection, recallInjection].filter(Boolean).join(`
544
+ `),
465
545
  summaryPathHints
466
546
  }));
467
- }),
468
- "experimental.session.compacting": safeHook("auto-memory:compacting", async (_input, output) => {
469
- const config = await loadOcTweaksConfig();
470
- if (!config || config.autoMemory?.enabled !== true)
471
- return;
472
- await ensureAutoMemoryInfra(home, projectMemoryDir);
473
- output.context.push(`## \uD83D\uDCBE Memory Checkpoint
474
-
475
- 核心问题:**如果明天开一个全新会话,本轮对话中有哪些信息会让你希望已经记录下来?**
476
-
477
- 有 → 标记保存。没有 → 标记 none。
478
-
479
- ### 值得保存
480
- - 用户表达的偏好、纠正、或明确要求记住的内容
481
- - 架构决策、设计约束、技术选型及其理由
482
- - 反复出现问题的根因与解决方案
483
- - 工作流、工具链、沟通风格等跨会话有价值的模式
484
-
485
- ### 不要保存
486
- - 本次对话的临时细节(具体报错、一次性调试步骤)
487
- - AGENTS.md / CLAUDE.md 中已有的内容
488
- - 未验证的猜测
489
- - 机密信息(密码、API key 等)
490
-
491
- 每次 compaction 最多标记 1-2 条,宁缺毋滥。
492
-
493
- 有内容:
494
- \`\`\`
495
- [MEMORY: 文件名.md]
496
- 简洁 bullet points,保持原意
497
- \`\`\`
498
-
499
- 无内容:\`[MEMORY: none]\` 并附一句理由说明为何无需保存
500
-
501
- ### Memory 路径
502
- - 全局:\`${globalMemoryDir}/\`
503
- - 项目:\`${projectMemoryDir}/\`
504
- `);
505
547
  })
506
548
  };
507
549
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-tweaks",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"