memory-bank-skill 5.0.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.
- package/README.md +179 -0
- package/dist/cli.js +438 -0
- package/package.json +41 -0
- package/plugin/memory-bank.ts +886 -0
- package/skill/memory-bank/README.md +108 -0
- package/skill/memory-bank/SKILL.md +338 -0
- package/skill/memory-bank/references/advanced-rules.md +189 -0
- package/skill/memory-bank/references/schema.md +112 -0
- package/skill/memory-bank/references/templates.md +225 -0
- package/templates/active.md +20 -0
- package/templates/brief.md +17 -0
- package/templates/docs/architecture.md +32 -0
- package/templates/docs/modules/module-template.md +34 -0
- package/templates/docs/specs/spec-template.md +47 -0
- package/templates/learnings/bugs/YYYY-MM-DD-template.md +27 -0
- package/templates/learnings/integrations/YYYY-MM-DD-template.md +28 -0
- package/templates/learnings/performance/YYYY-MM-DD-template.md +25 -0
- package/templates/patterns.md +11 -0
- package/templates/progress.md +18 -0
- package/templates/requirements/REQ-000-template.md +20 -0
- package/templates/tech.md +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Memory Bank Skill
|
|
2
|
+
|
|
3
|
+
> 项目记忆系统 - 让 AI 助手在每次对话中都能快速理解项目上下文
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 什么是 Memory Bank
|
|
8
|
+
|
|
9
|
+
Memory Bank 是一个 **OpenCode 技能(Skill)**,用于解决 AI 对话的 **上下文丢失** 问题。
|
|
10
|
+
|
|
11
|
+
每次开始新对话时,AI 助手都会"失忆":
|
|
12
|
+
- 不记得项目用了什么技术栈
|
|
13
|
+
- 不记得之前做了什么决策
|
|
14
|
+
- 不记得当前在做什么任务
|
|
15
|
+
|
|
16
|
+
Memory Bank 通过结构化 Markdown 文件持久化项目上下文,实现:
|
|
17
|
+
- **零初始化**:不需要手动 init,随项目推进自动创建
|
|
18
|
+
- **智能检索**:基于 AI 语义理解,自动加载相关上下文
|
|
19
|
+
- **自动写入**:工作过程中自动记录重要发现和决策
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
### 一键安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bunx memory-bank-skill install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
然后 **重启 OpenCode**,完成!
|
|
32
|
+
|
|
33
|
+
### 验证安装
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bunx memory-bank-skill doctor
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 安装做了什么?
|
|
40
|
+
|
|
41
|
+
| 操作 | 目标路径 |
|
|
42
|
+
|------|----------|
|
|
43
|
+
| 复制 Skill 文件 | `~/.config/opencode/skill/memory-bank/` |
|
|
44
|
+
| 复制 Plugin 文件 | `~/.config/opencode/plugin/memory-bank.ts` |
|
|
45
|
+
| 配置 opencode.json | 添加 `permission.skill` 和插件注册 |
|
|
46
|
+
| 安装依赖 | `~/.config/opencode/node_modules/` |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 快速开始
|
|
51
|
+
|
|
52
|
+
**不需要手动初始化。** Memory Bank 会在你开始工作时自动检测和创建:
|
|
53
|
+
|
|
54
|
+
| 场景 | AI 行为 |
|
|
55
|
+
|------|---------|
|
|
56
|
+
| **已有代码库** | 扫描 package.json/README 等,自动生成 brief.md + tech.md |
|
|
57
|
+
| **新项目** | 不创建任何文件,等你开始工作后按需创建 |
|
|
58
|
+
| **已有 Memory Bank** | 直接读取 brief.md + active.md,恢复上下文 |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 文件结构
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
memory-bank/
|
|
66
|
+
├── _index.md # 索引文件(AI 用于智能检索)
|
|
67
|
+
├── brief.md # 项目概述(稳定)
|
|
68
|
+
├── tech.md # 技术栈 + 环境 + 命令
|
|
69
|
+
├── active.md # 当前焦点 + 下一步 + 阻塞项
|
|
70
|
+
├── progress.md # 完成状态
|
|
71
|
+
├── patterns.md # 技术决策 + 代码约定
|
|
72
|
+
│
|
|
73
|
+
├── requirements/ # 需求池
|
|
74
|
+
│ └── REQ-{ID}-{slug}.md
|
|
75
|
+
│
|
|
76
|
+
├── docs/ # 技术文档
|
|
77
|
+
│ ├── architecture.md
|
|
78
|
+
│ └── modules/
|
|
79
|
+
│
|
|
80
|
+
└── learnings/ # 经验沉淀
|
|
81
|
+
├── bugs/
|
|
82
|
+
├── performance/
|
|
83
|
+
└── integrations/
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 插件功能
|
|
89
|
+
|
|
90
|
+
Memory Bank 包含一个 OpenCode 插件,提供两个核心功能:
|
|
91
|
+
|
|
92
|
+
### 1. 自动读取
|
|
93
|
+
|
|
94
|
+
每次 LLM 调用前,自动将 Memory Bank 内容注入 system prompt:
|
|
95
|
+
- 读取 `brief.md` + `active.md` + `_index.md`
|
|
96
|
+
- 文件缓存 + mtime 检测,只有变更才重新读取
|
|
97
|
+
- 12,000 字符上限,超出自动截断
|
|
98
|
+
|
|
99
|
+
### 2. 自动提醒更新
|
|
100
|
+
|
|
101
|
+
AI 尝试停止时,检测是否需要更新 Memory Bank:
|
|
102
|
+
- 检测文件修改(代码/配置/文档)
|
|
103
|
+
- 检测用户消息关键词(新需求、bug、决策等)
|
|
104
|
+
- 提醒初始化或更新
|
|
105
|
+
|
|
106
|
+
**逃逸阀**:
|
|
107
|
+
- 回复"无需更新"或"已检查" → 本次会话不再提醒
|
|
108
|
+
- 回复"跳过初始化" → 本次会话不再提醒初始化
|
|
109
|
+
- 环境变量 `MEMORY_BANK_DISABLED=1` → 完全禁用
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 常见问题
|
|
114
|
+
|
|
115
|
+
### Skill 没有被识别?
|
|
116
|
+
|
|
117
|
+
确认 `~/.config/opencode/opencode.json` 中包含:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"permission": {
|
|
122
|
+
"skill": "allow"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 插件没有加载?
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# 检查安装状态
|
|
131
|
+
bunx memory-bank-skill doctor
|
|
132
|
+
|
|
133
|
+
# 重新安装
|
|
134
|
+
bunx memory-bank-skill install
|
|
135
|
+
|
|
136
|
+
# 安装依赖
|
|
137
|
+
cd ~/.config/opencode && bun install
|
|
138
|
+
|
|
139
|
+
# 重启 OpenCode
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 验证插件加载
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
MEMORY_BANK_DEBUG=1 opencode --print-logs
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
启动时应该看到:
|
|
149
|
+
```
|
|
150
|
+
service=memory-bank Plugin initialized (unified) {"projectRoot":"..."}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 安全提示
|
|
156
|
+
|
|
157
|
+
**不要在 Memory Bank 中存储敏感信息**:
|
|
158
|
+
- API 密钥
|
|
159
|
+
- 数据库密码
|
|
160
|
+
- 私钥文件
|
|
161
|
+
- 任何凭证
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 文件位置
|
|
166
|
+
|
|
167
|
+
| 文件 | 路径 |
|
|
168
|
+
|------|------|
|
|
169
|
+
| Skill 主文件 | `~/.config/opencode/skill/memory-bank/SKILL.md` |
|
|
170
|
+
| 文件模板 | `~/.config/opencode/skill/memory-bank/references/templates.md` |
|
|
171
|
+
| 高级规则 | `~/.config/opencode/skill/memory-bank/references/advanced-rules.md` |
|
|
172
|
+
| 插件 | `~/.config/opencode/plugin/memory-bank.ts` |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 版本
|
|
177
|
+
|
|
178
|
+
- **版本**: 5.0.0
|
|
179
|
+
- **主要更新**: 切换到 OpenCode 原生 skill 路径(`~/.config/opencode/skill/`),移除 Claude 兼容层
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __require = import.meta.require;
|
|
20
|
+
|
|
21
|
+
// src/cli.ts
|
|
22
|
+
import { promises as fs } from "fs";
|
|
23
|
+
import { homedir } from "os";
|
|
24
|
+
import { join, dirname } from "path";
|
|
25
|
+
import { createHash } from "crypto";
|
|
26
|
+
import { fileURLToPath } from "url";
|
|
27
|
+
var VERSION = "5.0.0";
|
|
28
|
+
var colors = {
|
|
29
|
+
reset: "\x1B[0m",
|
|
30
|
+
bold: "\x1B[1m",
|
|
31
|
+
dim: "\x1B[2m",
|
|
32
|
+
green: "\x1B[32m",
|
|
33
|
+
yellow: "\x1B[33m",
|
|
34
|
+
red: "\x1B[31m",
|
|
35
|
+
cyan: "\x1B[36m"
|
|
36
|
+
};
|
|
37
|
+
function log(msg) {
|
|
38
|
+
console.log(msg);
|
|
39
|
+
}
|
|
40
|
+
function logStep(num, total, msg) {
|
|
41
|
+
log(`${colors.cyan}[${num}/${total}]${colors.reset} ${msg}`);
|
|
42
|
+
}
|
|
43
|
+
function logDetail(msg) {
|
|
44
|
+
log(` ${colors.dim}\u2192${colors.reset} ${msg}`);
|
|
45
|
+
}
|
|
46
|
+
function logSuccess(msg) {
|
|
47
|
+
log(`${colors.green}\u2713${colors.reset} ${msg}`);
|
|
48
|
+
}
|
|
49
|
+
function logError(msg) {
|
|
50
|
+
log(`${colors.red}\u2717${colors.reset} ${msg}`);
|
|
51
|
+
}
|
|
52
|
+
async function exists(path) {
|
|
53
|
+
try {
|
|
54
|
+
await fs.access(path);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function ensureDir(dir) {
|
|
61
|
+
await fs.mkdir(dir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
function sha256(content) {
|
|
64
|
+
return createHash("sha256").update(content).digest("hex");
|
|
65
|
+
}
|
|
66
|
+
function getPackageRoot() {
|
|
67
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
68
|
+
const __dirname2 = dirname(__filename2);
|
|
69
|
+
if (__dirname2.endsWith("/dist") || __dirname2.endsWith("\\dist")) {
|
|
70
|
+
return dirname(__dirname2);
|
|
71
|
+
}
|
|
72
|
+
if (__dirname2.endsWith("/src") || __dirname2.endsWith("\\src")) {
|
|
73
|
+
return dirname(__dirname2);
|
|
74
|
+
}
|
|
75
|
+
return __dirname2;
|
|
76
|
+
}
|
|
77
|
+
async function atomicWriteFile(targetPath, content, undoStack) {
|
|
78
|
+
const tmpPath = `${targetPath}.tmp`;
|
|
79
|
+
await ensureDir(dirname(targetPath));
|
|
80
|
+
if (await exists(targetPath)) {
|
|
81
|
+
const backupPath = `${targetPath}.backup`;
|
|
82
|
+
const original = await fs.readFile(targetPath);
|
|
83
|
+
await fs.writeFile(backupPath, original);
|
|
84
|
+
undoStack.push({ type: "restore", path: targetPath, backupPath });
|
|
85
|
+
} else {
|
|
86
|
+
undoStack.push({ type: "remove", path: targetPath });
|
|
87
|
+
}
|
|
88
|
+
await fs.writeFile(tmpPath, content);
|
|
89
|
+
await fs.rename(tmpPath, targetPath);
|
|
90
|
+
}
|
|
91
|
+
async function atomicCopyDir(srcDir, destDir, undoStack, manifestFiles) {
|
|
92
|
+
const dirExisted = await exists(destDir);
|
|
93
|
+
if (!dirExisted) {
|
|
94
|
+
undoStack.push({ type: "remove-dir", path: destDir });
|
|
95
|
+
}
|
|
96
|
+
await ensureDir(destDir);
|
|
97
|
+
const entries = await fs.readdir(srcDir, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const srcPath = join(srcDir, entry.name);
|
|
100
|
+
const destPath = join(destDir, entry.name);
|
|
101
|
+
if (entry.isDirectory()) {
|
|
102
|
+
await atomicCopyDir(srcPath, destPath, undoStack, manifestFiles);
|
|
103
|
+
} else if (entry.isFile()) {
|
|
104
|
+
const content = await fs.readFile(srcPath);
|
|
105
|
+
await atomicWriteFile(destPath, content, undoStack);
|
|
106
|
+
manifestFiles.push({ path: destPath, sha256: sha256(content) });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function rollback(undoStack) {
|
|
111
|
+
log(`
|
|
112
|
+
${colors.yellow}Rolling back...${colors.reset}`);
|
|
113
|
+
for (let i = undoStack.length - 1;i >= 0; i--) {
|
|
114
|
+
const action = undoStack[i];
|
|
115
|
+
try {
|
|
116
|
+
switch (action.type) {
|
|
117
|
+
case "restore":
|
|
118
|
+
if (action.backupPath && await exists(action.backupPath)) {
|
|
119
|
+
await fs.rename(action.backupPath, action.path);
|
|
120
|
+
logDetail(`Restored ${action.path}`);
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
case "remove":
|
|
124
|
+
if (await exists(action.path)) {
|
|
125
|
+
await fs.unlink(action.path);
|
|
126
|
+
logDetail(`Removed ${action.path}`);
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
case "remove-dir":
|
|
130
|
+
if (await exists(action.path)) {
|
|
131
|
+
await fs.rm(action.path, { recursive: true });
|
|
132
|
+
logDetail(`Removed directory ${action.path}`);
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(` Rollback failed for ${action.path}: ${err}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function cleanupBackups(undoStack) {
|
|
142
|
+
for (const action of undoStack) {
|
|
143
|
+
if (action.type === "restore" && action.backupPath) {
|
|
144
|
+
try {
|
|
145
|
+
if (await exists(action.backupPath)) {
|
|
146
|
+
await fs.unlink(action.backupPath);
|
|
147
|
+
}
|
|
148
|
+
} catch {}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function installSkillFiles(packageRoot, undoStack, manifestFiles) {
|
|
153
|
+
const srcDir = join(packageRoot, "skill", "memory-bank");
|
|
154
|
+
const destDir = join(homedir(), ".config", "opencode", "skill", "memory-bank");
|
|
155
|
+
if (!await exists(srcDir)) {
|
|
156
|
+
throw new Error(`Skill source not found: ${srcDir}`);
|
|
157
|
+
}
|
|
158
|
+
const existed = await exists(destDir);
|
|
159
|
+
await atomicCopyDir(srcDir, destDir, undoStack, manifestFiles);
|
|
160
|
+
return {
|
|
161
|
+
step: "Installing skill files",
|
|
162
|
+
status: existed ? "updated" : "created",
|
|
163
|
+
details: destDir
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function installPluginFile(packageRoot, undoStack, manifestFiles) {
|
|
167
|
+
const srcPath = join(packageRoot, "plugin", "memory-bank.ts");
|
|
168
|
+
const destPath = join(homedir(), ".config", "opencode", "plugin", "memory-bank.ts");
|
|
169
|
+
if (!await exists(srcPath)) {
|
|
170
|
+
throw new Error(`Plugin source not found: ${srcPath}`);
|
|
171
|
+
}
|
|
172
|
+
const existed = await exists(destPath);
|
|
173
|
+
const content = await fs.readFile(srcPath);
|
|
174
|
+
await atomicWriteFile(destPath, content, undoStack);
|
|
175
|
+
manifestFiles.push({ path: destPath, sha256: sha256(content) });
|
|
176
|
+
return {
|
|
177
|
+
step: "Installing plugin",
|
|
178
|
+
status: existed ? "updated" : "created",
|
|
179
|
+
details: destPath
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function configureOpencodeJson(undoStack) {
|
|
183
|
+
const configPath = join(homedir(), ".config", "opencode", "opencode.json");
|
|
184
|
+
const pluginPath = join(homedir(), ".config", "opencode", "plugin", "memory-bank.ts");
|
|
185
|
+
const pluginUrl = `file://${pluginPath}`;
|
|
186
|
+
let config = {};
|
|
187
|
+
let existed = false;
|
|
188
|
+
let modified = false;
|
|
189
|
+
const changes = [];
|
|
190
|
+
if (await exists(configPath)) {
|
|
191
|
+
existed = true;
|
|
192
|
+
try {
|
|
193
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
194
|
+
config = JSON.parse(content);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
throw new Error(`Failed to parse ${configPath}: ${err}
|
|
197
|
+
|
|
198
|
+
` + `Please fix the JSON manually, or add this to your config:
|
|
199
|
+
|
|
200
|
+
` + JSON.stringify({
|
|
201
|
+
permission: { skill: "allow" },
|
|
202
|
+
plugin: [pluginUrl]
|
|
203
|
+
}, null, 2));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (!config.permission) {
|
|
207
|
+
config.permission = {};
|
|
208
|
+
}
|
|
209
|
+
if (config.permission.skill !== "allow") {
|
|
210
|
+
config.permission.skill = "allow";
|
|
211
|
+
changes.push('Added permission.skill = "allow"');
|
|
212
|
+
modified = true;
|
|
213
|
+
}
|
|
214
|
+
if (!Array.isArray(config.plugin)) {
|
|
215
|
+
config.plugin = [];
|
|
216
|
+
}
|
|
217
|
+
if (!config.plugin.includes(pluginUrl)) {
|
|
218
|
+
config.plugin.push(pluginUrl);
|
|
219
|
+
changes.push("Added plugin entry");
|
|
220
|
+
modified = true;
|
|
221
|
+
}
|
|
222
|
+
if (modified) {
|
|
223
|
+
const newContent = JSON.stringify(config, null, 2) + `
|
|
224
|
+
`;
|
|
225
|
+
await atomicWriteFile(configPath, newContent, undoStack);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
step: "Configuring opencode.json",
|
|
229
|
+
status: modified ? existed ? "updated" : "created" : "already-configured",
|
|
230
|
+
details: changes.join(", ") || "Already configured"
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async function ensurePluginDependencies(undoStack) {
|
|
234
|
+
const packageJsonPath = join(homedir(), ".config", "opencode", "package.json");
|
|
235
|
+
let pkg = { dependencies: {} };
|
|
236
|
+
let existed = false;
|
|
237
|
+
let modified = false;
|
|
238
|
+
if (await exists(packageJsonPath)) {
|
|
239
|
+
existed = true;
|
|
240
|
+
try {
|
|
241
|
+
const content = await fs.readFile(packageJsonPath, "utf-8");
|
|
242
|
+
pkg = JSON.parse(content);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
throw new Error(`Failed to parse ${packageJsonPath}: ${err}
|
|
245
|
+
|
|
246
|
+
` + `Please fix the JSON manually, or add this to your config:
|
|
247
|
+
|
|
248
|
+
` + JSON.stringify({
|
|
249
|
+
dependencies: {
|
|
250
|
+
"@opencode-ai/plugin": "^1.1.14"
|
|
251
|
+
}
|
|
252
|
+
}, null, 2));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (!pkg.dependencies) {
|
|
256
|
+
pkg.dependencies = {};
|
|
257
|
+
}
|
|
258
|
+
if (!pkg.dependencies["@opencode-ai/plugin"]) {
|
|
259
|
+
pkg.dependencies["@opencode-ai/plugin"] = "^1.1.14";
|
|
260
|
+
modified = true;
|
|
261
|
+
}
|
|
262
|
+
if (modified) {
|
|
263
|
+
const newContent = JSON.stringify(pkg, null, 2) + `
|
|
264
|
+
`;
|
|
265
|
+
await atomicWriteFile(packageJsonPath, newContent, undoStack);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
step: "Ensuring plugin dependencies",
|
|
269
|
+
status: modified ? existed ? "updated" : "created" : "already-configured",
|
|
270
|
+
details: modified ? "Added @opencode-ai/plugin" : "Already present"
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
async function writeManifest(manifestFiles, undoStack) {
|
|
274
|
+
const manifestPath = join(homedir(), ".config", "opencode", "skill", "memory-bank", ".manifest.json");
|
|
275
|
+
const manifest = {
|
|
276
|
+
version: VERSION,
|
|
277
|
+
installedAt: new Date().toISOString(),
|
|
278
|
+
files: manifestFiles
|
|
279
|
+
};
|
|
280
|
+
await atomicWriteFile(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
281
|
+
`, undoStack);
|
|
282
|
+
}
|
|
283
|
+
async function runBunInstall() {
|
|
284
|
+
const opencodeDir = join(homedir(), ".config", "opencode");
|
|
285
|
+
const nodeModulesPath = join(opencodeDir, "node_modules", "@opencode-ai", "plugin");
|
|
286
|
+
if (await exists(nodeModulesPath)) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const { execSync } = await import("child_process");
|
|
291
|
+
execSync("bun install", {
|
|
292
|
+
cwd: opencodeDir,
|
|
293
|
+
stdio: "pipe",
|
|
294
|
+
timeout: 60000
|
|
295
|
+
});
|
|
296
|
+
return true;
|
|
297
|
+
} catch (err) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async function install() {
|
|
302
|
+
log(`
|
|
303
|
+
${colors.bold}Memory Bank Skill Installer v${VERSION}${colors.reset}
|
|
304
|
+
`);
|
|
305
|
+
const packageRoot = getPackageRoot();
|
|
306
|
+
const undoStack = [];
|
|
307
|
+
const manifestFiles = [];
|
|
308
|
+
const results = [];
|
|
309
|
+
try {
|
|
310
|
+
logStep(1, 5, "Installing skill files...");
|
|
311
|
+
const r1 = await installSkillFiles(packageRoot, undoStack, manifestFiles);
|
|
312
|
+
logDetail(r1.details || "");
|
|
313
|
+
results.push(r1);
|
|
314
|
+
logStep(2, 5, "Installing plugin...");
|
|
315
|
+
const r2 = await installPluginFile(packageRoot, undoStack, manifestFiles);
|
|
316
|
+
logDetail(r2.details || "");
|
|
317
|
+
results.push(r2);
|
|
318
|
+
logStep(3, 5, "Configuring opencode.json...");
|
|
319
|
+
const r3 = await configureOpencodeJson(undoStack);
|
|
320
|
+
logDetail(r3.details || "");
|
|
321
|
+
results.push(r3);
|
|
322
|
+
logStep(4, 5, "Ensuring plugin dependencies...");
|
|
323
|
+
const r4 = await ensurePluginDependencies(undoStack);
|
|
324
|
+
logDetail(r4.details || "");
|
|
325
|
+
results.push(r4);
|
|
326
|
+
logStep(5, 5, "Installing dependencies...");
|
|
327
|
+
const bunSuccess = await runBunInstall();
|
|
328
|
+
if (bunSuccess) {
|
|
329
|
+
logDetail("Dependencies ready");
|
|
330
|
+
} else {
|
|
331
|
+
logDetail(`${colors.yellow}Run manually: cd ~/.config/opencode && bun install${colors.reset}`);
|
|
332
|
+
}
|
|
333
|
+
await writeManifest(manifestFiles, undoStack);
|
|
334
|
+
await cleanupBackups(undoStack);
|
|
335
|
+
const allSkipped = results.every((r) => r.status === "already-configured" || r.status === "skipped");
|
|
336
|
+
log("");
|
|
337
|
+
if (allSkipped && bunSuccess) {
|
|
338
|
+
logSuccess(`Already installed (v${VERSION})`);
|
|
339
|
+
} else {
|
|
340
|
+
logSuccess("Installation complete!");
|
|
341
|
+
}
|
|
342
|
+
log("");
|
|
343
|
+
log(`${colors.bold}Next step:${colors.reset} Restart OpenCode`);
|
|
344
|
+
log("");
|
|
345
|
+
} catch (err) {
|
|
346
|
+
await rollback(undoStack);
|
|
347
|
+
log("");
|
|
348
|
+
logError(`Installation failed: ${err}`);
|
|
349
|
+
log("");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function doctor() {
|
|
354
|
+
log(`
|
|
355
|
+
${colors.bold}Memory Bank Skill Doctor v${VERSION}${colors.reset}
|
|
356
|
+
`);
|
|
357
|
+
const checks = [
|
|
358
|
+
{
|
|
359
|
+
name: "Skill files",
|
|
360
|
+
path: join(homedir(), ".config", "opencode", "skill", "memory-bank", "SKILL.md")
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
name: "Plugin file",
|
|
364
|
+
path: join(homedir(), ".config", "opencode", "plugin", "memory-bank.ts")
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "OpenCode config",
|
|
368
|
+
path: join(homedir(), ".config", "opencode", "opencode.json")
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "Plugin dependencies",
|
|
372
|
+
path: join(homedir(), ".config", "opencode", "package.json")
|
|
373
|
+
}
|
|
374
|
+
];
|
|
375
|
+
let allOk = true;
|
|
376
|
+
for (const check of checks) {
|
|
377
|
+
const ok = await exists(check.path);
|
|
378
|
+
if (ok) {
|
|
379
|
+
log(`${colors.green}\u2713${colors.reset} ${check.name}`);
|
|
380
|
+
logDetail(check.path);
|
|
381
|
+
} else {
|
|
382
|
+
log(`${colors.red}\u2717${colors.reset} ${check.name}`);
|
|
383
|
+
logDetail(`Missing: ${check.path}`);
|
|
384
|
+
allOk = false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const manifestPath = join(homedir(), ".config", "opencode", "skill", "memory-bank", ".manifest.json");
|
|
388
|
+
if (await exists(manifestPath)) {
|
|
389
|
+
try {
|
|
390
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
|
|
391
|
+
log("");
|
|
392
|
+
log(`${colors.dim}Installed version: ${manifest.version}${colors.reset}`);
|
|
393
|
+
log(`${colors.dim}Installed at: ${manifest.installedAt}${colors.reset}`);
|
|
394
|
+
} catch {}
|
|
395
|
+
}
|
|
396
|
+
log("");
|
|
397
|
+
if (allOk) {
|
|
398
|
+
logSuccess("All checks passed!");
|
|
399
|
+
} else {
|
|
400
|
+
logError("Some checks failed. Run 'bunx memory-bank-skill install' to fix.");
|
|
401
|
+
}
|
|
402
|
+
log("");
|
|
403
|
+
process.exit(allOk ? 0 : 1);
|
|
404
|
+
}
|
|
405
|
+
function showHelp() {
|
|
406
|
+
log(`
|
|
407
|
+
${colors.bold}Memory Bank Skill v${VERSION}${colors.reset}
|
|
408
|
+
|
|
409
|
+
Usage:
|
|
410
|
+
bunx memory-bank-skill <command>
|
|
411
|
+
|
|
412
|
+
Commands:
|
|
413
|
+
install Install Memory Bank skill and plugin
|
|
414
|
+
doctor Check installation status
|
|
415
|
+
|
|
416
|
+
Examples:
|
|
417
|
+
bunx memory-bank-skill install
|
|
418
|
+
bunx memory-bank-skill doctor
|
|
419
|
+
`);
|
|
420
|
+
}
|
|
421
|
+
var command = process.argv[2];
|
|
422
|
+
switch (command) {
|
|
423
|
+
case "install":
|
|
424
|
+
install();
|
|
425
|
+
break;
|
|
426
|
+
case "doctor":
|
|
427
|
+
doctor();
|
|
428
|
+
break;
|
|
429
|
+
case "--help":
|
|
430
|
+
case "-h":
|
|
431
|
+
case undefined:
|
|
432
|
+
showHelp();
|
|
433
|
+
break;
|
|
434
|
+
default:
|
|
435
|
+
log(`Unknown command: ${command}`);
|
|
436
|
+
showHelp();
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "memory-bank-skill",
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"description": "Memory Bank - 项目记忆系统,让 AI 助手在每次对话中都能快速理解项目上下文",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"memory-bank-skill": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"plugin/",
|
|
12
|
+
"skill/",
|
|
13
|
+
"templates/"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "bun build src/cli.ts --outdir dist --target bun",
|
|
17
|
+
"prepublishOnly": "bun run build",
|
|
18
|
+
"test:install": "bun run build && bun ./dist/cli.js install",
|
|
19
|
+
"test:doctor": "bun run build && bun ./dist/cli.js doctor"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"opencode",
|
|
23
|
+
"plugin",
|
|
24
|
+
"memory-bank",
|
|
25
|
+
"ai",
|
|
26
|
+
"context",
|
|
27
|
+
"claude"
|
|
28
|
+
],
|
|
29
|
+
"author": "",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/user/memory-bank-skill.git"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"registry": "https://registry.npmjs.org/"
|
|
40
|
+
}
|
|
41
|
+
}
|