oc-tweaks 0.2.0 → 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.
- package/dist/index.js +112 -106
- 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: 记忆助手 -
|
|
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.
|
|
94
|
-
3.
|
|
95
|
-
4.
|
|
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
|
-
-
|
|
99
|
-
-
|
|
100
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
242
|
+
fileContents
|
|
229
243
|
}));
|
|
230
244
|
}),
|
|
231
245
|
"experimental.session.compacting": safeHook("auto-memory:compacting", async (_input, output) => {
|
|
@@ -235,47 +249,39 @@ var autoMemoryPlugin = async ({ directory }) => {
|
|
|
235
249
|
await ensureAutoMemoryInfra(home, projectMemoryDir);
|
|
236
250
|
output.context.push(`## \uD83D\uDCBE Memory 保存提示 (Compaction Phase)
|
|
237
251
|
|
|
238
|
-
|
|
252
|
+
回顾本轮对话,判断是否有值得跨会话持久保存的信息。
|
|
253
|
+
|
|
254
|
+
### 应该保存的(至少一项命中即标记)
|
|
255
|
+
- 用户明确要求记住的偏好、决策或约定
|
|
256
|
+
- 用户纠正了你的行为(隐含偏好)
|
|
257
|
+
- 架构决策、设计约束、技术选型及其理由
|
|
258
|
+
- 跨会话有价值的模式或约定(问自己:明天从头开始,这个信息有帮助吗?)
|
|
259
|
+
- 反复出现问题的根因与解决方案
|
|
260
|
+
- 用户的工作流、工具链、沟通风格偏好
|
|
261
|
+
|
|
262
|
+
### 不应该保存的
|
|
263
|
+
- 仅本次对话有用的临时细节(具体报错、一次性调试命令)
|
|
264
|
+
- AGENTS.md / CLAUDE.md 中已有的内容(不得重复)
|
|
265
|
+
- 未验证的猜测或中间结论
|
|
266
|
+
- 机密信息(密码、API key、token)
|
|
267
|
+
|
|
268
|
+
### 标记格式
|
|
269
|
+
|
|
270
|
+
有值得保存的内容时,在摘要中标记:
|
|
239
271
|
|
|
240
272
|
\`\`\`
|
|
241
273
|
[MEMORY: 文件名.md]
|
|
242
|
-
|
|
274
|
+
简洁的 bullet points,保持原意不扩写
|
|
243
275
|
\`\`\`
|
|
244
276
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
}
|
|
277
|
+
确实没有值得保存的,标记 \`[MEMORY: none]\`。
|
|
278
|
+
|
|
279
|
+
### Memory 路径
|
|
280
|
+
- 全局:\`${globalMemoryDir}/\`
|
|
281
|
+
- 项目:\`${projectMemoryDir}/\`
|
|
282
|
+
|
|
283
|
+
后续对话中应根据标记调用内置 Read/Edit/Write 工具写入对应 memory 文件。`);
|
|
284
|
+
})
|
|
279
285
|
};
|
|
280
286
|
};
|
|
281
287
|
// src/plugins/background-subagent.ts
|