oc-tweaks 0.2.0 → 0.3.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 +86 -104
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  // src/plugins/auto-memory.ts
2
- import { tool } from "@opencode-ai/plugin";
3
2
  import { mkdir as mkdir2, readdir } from "node:fs/promises";
4
3
 
5
4
  // src/utils/logger.ts
@@ -85,36 +84,34 @@ async function loadOcTweaksConfig() {
85
84
  var TRIGGER_WORDS_CN = ["记住", "保存偏好", "记录一下", "记到memory", "别忘了"];
86
85
  var TRIGGER_WORDS_EN = ["remember", "save to memory", "note this down", "don't forget", "record"];
87
86
  var REMEMBER_COMMAND_CONTENT = `---
88
- description: 记忆助手 - 从当前会话提取关键信息并写入 memory 文件
87
+ description: 记忆助手 - 将关键信息写入 memory 文件
89
88
  ---
90
89
 
91
- 当用户希望你记住偏好、决策或长期有价值的信息时:
90
+ 当用户希望你记住偏好、决策或长期有价值的信息时,
91
+ 直接使用 Write 或 Edit 工具操作 memory 文件。
92
+
93
+ ## 保存位置
94
+ - 全局 memory:\`~/.config/opencode/memory/\`
95
+ - 项目 memory:\`{project}/.opencode/memory/\`
96
+
97
+ ## 保存步骤
92
98
  1. 提取要保存的信息(保持原意,不扩写)
93
- 2. 推断 category(如 preferences / decisions / setup / notes)
94
- 3. 推断 scope(global project)
95
- 4. 调用 remember tool 执行写入
99
+ 2. 确定文件分类(如 preferences.md、decisions.md、setup.md、notes.md
100
+ 3. 确定 scope(全局 vs 项目级)
101
+ 4. 使用 Read 工具检查目标文件是否已存在,读取现有内容
102
+ 5. 使用 Edit 工具追加新内容(若文件存在),或用 Write 创建新文件
96
103
 
97
- 参数:
98
- - content: 要保存的内容
99
- - category: 目标文件分类(不带 .md)
100
- - scope: global | project
104
+ ## 格式规范
105
+ - 使用 markdown bullet points
106
+ - 保持简洁,不扩写
107
+ - 不存临时信息(只存跨会话有价值的内容)
108
+ - 不重复 AGENTS.md / CLAUDE.md 中已有的内容
101
109
 
102
110
  如有参数,则优先围绕参数提取重点:$ARGUMENTS
103
111
  `;
104
112
  function getHome2() {
105
113
  return Bun.env?.HOME ?? process.env.HOME ?? "";
106
114
  }
107
- function sanitizeCategory(raw) {
108
- if (!raw || !raw.trim())
109
- return "notes";
110
- const normalized = raw.trim().toLowerCase().replace(/\.md$/i, "").replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
111
- return normalized || "notes";
112
- }
113
- function resolveScope(raw) {
114
- if (!raw)
115
- return "global";
116
- return raw.trim().toLowerCase() === "project" ? "project" : "global";
117
- }
118
115
  async function listMarkdownFiles(path) {
119
116
  try {
120
117
  const entries = await readdir(path);
@@ -123,23 +120,17 @@ async function listMarkdownFiles(path) {
123
120
  return [];
124
121
  }
125
122
  }
126
- async function readPreferences(path) {
127
- try {
128
- const file = Bun.file(path);
129
- if (!await file.exists())
130
- return "(尚无 preferences.md)";
131
- const content = await file.text();
132
- return content.trim() || "(preferences.md 为空)";
133
- } catch {
134
- return "(读取 preferences.md 失败)";
135
- }
136
- }
137
123
  async function ensureRememberCommand(home) {
138
124
  const commandDir = `${home}/.config/opencode/commands`;
139
125
  const commandPath = `${commandDir}/remember.md`;
140
126
  const commandFile = Bun.file(commandPath);
141
- if (await commandFile.exists())
142
- return;
127
+ if (await commandFile.exists()) {
128
+ try {
129
+ const existing = await commandFile.text();
130
+ if (existing.trim() === REMEMBER_COMMAND_CONTENT.trim())
131
+ return;
132
+ } catch {}
133
+ }
143
134
  await mkdir2(commandDir, { recursive: true });
144
135
  await Bun.write(commandPath, REMEMBER_COMMAND_CONTENT);
145
136
  }
@@ -148,56 +139,68 @@ async function ensureAutoMemoryInfra(home, projectMemoryDir) {
148
139
  await mkdir2(projectMemoryDir, { recursive: true });
149
140
  await ensureRememberCommand(home);
150
141
  }
151
- async function appendMemoryRecord(filePath, content) {
152
- const file = Bun.file(filePath);
153
- let previous = "";
154
- try {
155
- if (await file.exists()) {
156
- previous = await file.text();
157
- }
158
- } catch {}
159
- const prefix = previous.length > 0 && !previous.endsWith(`
160
- `) ? `
161
- ` : "";
162
- const record = `[${new Date().toISOString()}]
163
- ${content.trim()}
164
-
165
- `;
166
- await Bun.write(filePath, `${previous}${prefix}${record}`);
167
- }
168
142
  function buildMemoryGuide(params) {
169
143
  const globalList = params.globalFiles.length > 0 ? params.globalFiles.map((name) => `- \`${name}\``).join(`
170
144
  `) : "- (暂无全局 memory 文件)";
171
145
  const projectList = params.projectFiles.length > 0 ? params.projectFiles.map((name) => `- \`${name}\``).join(`
172
146
  `) : "- (暂无项目级 memory 文件)";
147
+ const injectedContents = params.fileContents.size > 0 ? Array.from(params.fileContents.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([path, content]) => `Contents of ${path}:
148
+ ${content}`).join(`
149
+
150
+ `) : "(暂无可注入的 memory 内容)";
173
151
  return `## \uD83E\uDDE0 Memory 系统指引
174
152
 
153
+ Memory 是 AGENTS.md / CLAUDE.md 的**补充**,用于存储跨会话有价值的信息。
154
+ 不要将 AGENTS.md / CLAUDE.md 中已有的内容重复写入 memory。
155
+
175
156
  可用记忆层:
176
157
  1. 全局 memory:\`${params.globalMemoryDir}\`
177
158
  2. 项目 memory:\`${params.projectMemoryDir}\`
178
159
 
179
- ### 当前文件
160
+ ### 何时保存 memory
161
+
162
+ **你必须(MUST)保存 memory 当:**
163
+ - 用户明确要求记住(触发词:${TRIGGER_WORDS_CN.join("、")} / ${TRIGGER_WORDS_EN.join(", ")})
164
+ - 用户纠正了你的行为或表达了明确偏好
165
+
166
+ **建议保存 memory 当:**
167
+ - 发现了跨会话有用的模式或约定(想想:如果明天从头开始,这个信息有帮助吗?)
168
+ - 用户描述了目标或背景("我在做..."、"我们在迁移到...")
169
+ - 找到了可能再次出现的问题的解决方案
170
+ - 用户的工作流、工具、沟通风格偏好
171
+
172
+ ### 不要保存
173
+
174
+ - 临时的当前任务细节(只在本次对话有用的信息)
175
+ - AGENTS.md 或 CLAUDE.md 中已有的内容(不得重复或矛盾)
176
+ - 可能不完整或未验证的结论(先查证再记录)
177
+ - 机密信息(密码、API key 等)
178
+
179
+ ### 如何保存
180
+
181
+ 直接使用你的内置 Write 或 Edit 工具操作 memory 文件:
182
+ - 全局 memory:\`${params.globalMemoryDir}/\`
183
+ - 项目 memory:\`${params.projectMemoryDir}/\`
184
+
185
+ 文件按主题分类(如 preferences.md、decisions.md、setup.md、notes.md)。
186
+ 写入时保持简洁,用 markdown bullet points,保持原意不扩写。
187
+
188
+ ### 如何更新已有 memory
189
+
190
+ - 更新已有文件时,使用 Edit 工具追加或修改特定段落,不要用 Write 整体覆盖
191
+ - 内容要具体、信息密集(包含文件路径、函数名、具体命令等)
192
+ - 当某个 memory 文件内容过长时,精简旧条目而不是无限追加
193
+ - 更新时保持已有内容的结构完整,不要破坏其他条目
194
+
195
+ ### 当前 Memory 文件
180
196
  **全局**
181
197
  ${globalList}
182
198
 
183
199
  **项目级**
184
200
  ${projectList}
185
201
 
186
- ### 触发词(优先调用 remember tool)
187
- - 中文:${TRIGGER_WORDS_CN.join("、")}
188
- - English: ${TRIGGER_WORDS_EN.join(", ")}
189
-
190
- 命中触发词后:
191
- 1. 提取要保存的信息
192
- 2. 判断 scope(global / project)
193
- 3. 判断 category(例如 preferences / decisions / setup / notes)
194
- 4. 调用 \`remember\` tool 写入
195
-
196
202
  ### 用户核心 Preferences
197
- \`\`\`markdown
198
- ${params.preferencesContent}
199
- \`\`\`
200
- `;
203
+ ${injectedContents}`;
201
204
  }
202
205
  var autoMemoryPlugin = async ({ directory }) => {
203
206
  const home = getHome2();
@@ -215,17 +218,28 @@ var autoMemoryPlugin = async ({ directory }) => {
215
218
  if (!config || config.autoMemory?.enabled !== true)
216
219
  return;
217
220
  await ensureAutoMemoryInfra(home, projectMemoryDir);
218
- const [globalFiles, projectFiles, preferencesContent] = await Promise.all([
221
+ const [globalFiles, projectFiles] = await Promise.all([
219
222
  listMarkdownFiles(globalMemoryDir),
220
- listMarkdownFiles(projectMemoryDir),
221
- readPreferences(`${globalMemoryDir}/preferences.md`)
223
+ listMarkdownFiles(projectMemoryDir)
222
224
  ]);
225
+ const fileContents = new Map;
226
+ const allPaths = [
227
+ ...globalFiles.map((name) => ({ dir: globalMemoryDir, name })),
228
+ ...projectFiles.map((name) => ({ dir: projectMemoryDir, name }))
229
+ ];
230
+ await Promise.all(allPaths.map(async ({ dir, name }) => {
231
+ try {
232
+ const content = await Bun.file(`${dir}/${name}`).text();
233
+ if (content.trim())
234
+ fileContents.set(`${dir}/${name}`, content.trim());
235
+ } catch {}
236
+ }));
223
237
  output.system.push(buildMemoryGuide({
224
238
  globalMemoryDir,
225
239
  projectMemoryDir,
226
240
  globalFiles,
227
241
  projectFiles,
228
- preferencesContent
242
+ fileContents
229
243
  }));
230
244
  }),
231
245
  "experimental.session.compacting": safeHook("auto-memory:compacting", async (_input, output) => {
@@ -242,40 +256,8 @@ var autoMemoryPlugin = async ({ directory }) => {
242
256
  这里写要保存的内容
243
257
  \`\`\`
244
258
 
245
- 后续对话中应根据该标记调用 write/edit remember tool 写入对应 memory 文件。`);
246
- }),
247
- tool: {
248
- remember: tool({
249
- description: "Save important session facts into global/project memory markdown files",
250
- args: {
251
- content: tool.schema.string(),
252
- category: tool.schema.string().optional(),
253
- scope: tool.schema.string().optional()
254
- },
255
- async execute(args, context) {
256
- try {
257
- const config = await loadOcTweaksConfig();
258
- if (!config || config.autoMemory?.enabled !== true) {
259
- return "autoMemory is disabled in oc-tweaks config";
260
- }
261
- const scope = resolveScope(args.scope);
262
- const category = sanitizeCategory(args.category);
263
- const runtimeHome = getHome2();
264
- await ensureAutoMemoryInfra(runtimeHome, `${context.directory}/.opencode/memory`);
265
- const targetDir = scope === "project" ? `${context.directory}/.opencode/memory` : `${runtimeHome}/.config/opencode/memory`;
266
- await mkdir2(targetDir, { recursive: true });
267
- const targetPath = `${targetDir}/${category}.md`;
268
- await appendMemoryRecord(targetPath, args.content);
269
- return `Saved to ${targetPath}
270
-
271
- Content preview: ${args.content.slice(0, 150)}${args.content.length > 150 ? "..." : ""}`;
272
- } catch (error) {
273
- const message = error instanceof Error ? error.message : String(error);
274
- return `Failed to save memory: ${message}`;
275
- }
276
- }
277
- })
278
- }
259
+ 后续对话中应根据该标记调用内置 Read/Edit/Write 工具写入对应 memory 文件。`);
260
+ })
279
261
  };
280
262
  };
281
263
  // src/plugins/background-subagent.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-tweaks",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/index.js"