@vuau/agent-memory 0.1.0 → 0.2.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 +9 -0
- package/dist/bin/cli.js +346 -0
- package/dist/index.js +444 -0
- package/package.json +14 -8
- package/bin/cli.ts +0 -111
- package/index.ts +0 -11
- package/src/core/doctor.ts +0 -86
- package/src/core/index.ts +0 -5
- package/src/core/memory.ts +0 -86
- package/src/core/scaffold.ts +0 -124
- package/src/core/tasks.ts +0 -77
- package/src/core/types.ts +0 -30
- package/src/opencode/plugin.ts +0 -97
package/dist/index.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
// src/opencode/plugin.ts
|
|
2
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
3
|
+
import { resolve as resolve2 } from "path";
|
|
4
|
+
|
|
5
|
+
// src/core/scaffold.ts
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
7
|
+
import { join, resolve, dirname } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
|
|
10
|
+
// src/core/types.ts
|
|
11
|
+
var AGENTS_DIR = ".agents";
|
|
12
|
+
var SPEC_DIR = ".agents/spec";
|
|
13
|
+
var MEMORY_FILE = ".agents/MEMORY.md";
|
|
14
|
+
var TASKS_FILE = ".agents/TASKS.md";
|
|
15
|
+
var AGENTS_MD = "AGENTS.md";
|
|
16
|
+
var COPILOT_INSTRUCTIONS = ".github/copilot-instructions.md";
|
|
17
|
+
|
|
18
|
+
// src/core/scaffold.ts
|
|
19
|
+
function getTemplatesDir() {
|
|
20
|
+
try {
|
|
21
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const candidate = resolve(thisDir, "../../templates");
|
|
23
|
+
if (existsSync(candidate)) return candidate;
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
const candidate2 = resolve(__dirname, "../../templates");
|
|
27
|
+
if (existsSync(candidate2)) return candidate2;
|
|
28
|
+
throw new Error("Cannot locate templates directory");
|
|
29
|
+
}
|
|
30
|
+
var TEMPLATES_DIR = getTemplatesDir();
|
|
31
|
+
function readTemplate(name) {
|
|
32
|
+
const templatePath = join(TEMPLATES_DIR, name);
|
|
33
|
+
return readFileSync(templatePath, "utf-8");
|
|
34
|
+
}
|
|
35
|
+
function applyVars(content, vars) {
|
|
36
|
+
let result = content;
|
|
37
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
38
|
+
result = result.replaceAll(`{{${key}}}`, value);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
function scaffold(projectDir, config = {}, force = false) {
|
|
43
|
+
const result = { created: [], skipped: [] };
|
|
44
|
+
const projectName = config.projectName || guessProjectName(projectDir);
|
|
45
|
+
const vars = { PROJECT_NAME: projectName };
|
|
46
|
+
const dirs = [
|
|
47
|
+
join(projectDir, AGENTS_DIR),
|
|
48
|
+
join(projectDir, SPEC_DIR)
|
|
49
|
+
];
|
|
50
|
+
if (config.copilotInstructions !== false) {
|
|
51
|
+
dirs.push(join(projectDir, ".github"));
|
|
52
|
+
}
|
|
53
|
+
for (const dir of dirs) {
|
|
54
|
+
if (!existsSync(dir)) {
|
|
55
|
+
mkdirSync(dir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const files = [
|
|
59
|
+
{ target: AGENTS_MD, template: "AGENTS.md" },
|
|
60
|
+
{ target: MEMORY_FILE, template: "MEMORY.md" },
|
|
61
|
+
{ target: TASKS_FILE, template: "TASKS.md" }
|
|
62
|
+
];
|
|
63
|
+
if (config.copilotInstructions !== false) {
|
|
64
|
+
files.push({
|
|
65
|
+
target: COPILOT_INSTRUCTIONS,
|
|
66
|
+
template: "copilot-instructions.md"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
for (const { target, template } of files) {
|
|
70
|
+
const targetPath = join(projectDir, target);
|
|
71
|
+
if (existsSync(targetPath) && !force) {
|
|
72
|
+
result.skipped.push(target);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const content = applyVars(readTemplate(template), vars);
|
|
76
|
+
writeFileSync(targetPath, content);
|
|
77
|
+
result.created.push(target);
|
|
78
|
+
}
|
|
79
|
+
const specKeep = join(projectDir, SPEC_DIR, ".gitkeep");
|
|
80
|
+
if (!existsSync(specKeep)) {
|
|
81
|
+
writeFileSync(specKeep, "");
|
|
82
|
+
result.created.push(`${SPEC_DIR}/.gitkeep`);
|
|
83
|
+
}
|
|
84
|
+
if (config.opencode) {
|
|
85
|
+
scaffoldOpenCode(projectDir, result, force);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
function scaffoldOpenCode(projectDir, result, force) {
|
|
90
|
+
const PACKAGE_NAME = "@vuau/agent-memory";
|
|
91
|
+
const opencodePkgPath = join(projectDir, ".opencode", "package.json");
|
|
92
|
+
const opencodeDir = join(projectDir, ".opencode");
|
|
93
|
+
if (!existsSync(opencodeDir)) {
|
|
94
|
+
mkdirSync(opencodeDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
if (!existsSync(opencodePkgPath)) {
|
|
97
|
+
writeFileSync(
|
|
98
|
+
opencodePkgPath,
|
|
99
|
+
JSON.stringify({ dependencies: { [PACKAGE_NAME]: "latest" } }, null, 2) + "\n"
|
|
100
|
+
);
|
|
101
|
+
result.created.push(".opencode/package.json");
|
|
102
|
+
} else {
|
|
103
|
+
const pkg = JSON.parse(readFileSync(opencodePkgPath, "utf-8"));
|
|
104
|
+
const deps = pkg.dependencies || {};
|
|
105
|
+
if (!deps[PACKAGE_NAME] || force) {
|
|
106
|
+
deps[PACKAGE_NAME] = "latest";
|
|
107
|
+
pkg.dependencies = deps;
|
|
108
|
+
writeFileSync(opencodePkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
109
|
+
if (!deps[PACKAGE_NAME]) {
|
|
110
|
+
result.created.push(".opencode/package.json");
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
result.skipped.push(".opencode/package.json");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const opencodeJsonPath = join(projectDir, "opencode.json");
|
|
117
|
+
if (!existsSync(opencodeJsonPath)) {
|
|
118
|
+
writeFileSync(
|
|
119
|
+
opencodeJsonPath,
|
|
120
|
+
JSON.stringify({ plugin: [PACKAGE_NAME] }, null, 2) + "\n"
|
|
121
|
+
);
|
|
122
|
+
result.created.push("opencode.json");
|
|
123
|
+
} else {
|
|
124
|
+
const config = JSON.parse(readFileSync(opencodeJsonPath, "utf-8"));
|
|
125
|
+
const plugins = config.plugin || [];
|
|
126
|
+
if (!plugins.includes(PACKAGE_NAME)) {
|
|
127
|
+
config.plugin = [...plugins, PACKAGE_NAME];
|
|
128
|
+
writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
129
|
+
result.created.push("opencode.json (merged plugin)");
|
|
130
|
+
} else {
|
|
131
|
+
result.skipped.push("opencode.json");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function guessProjectName(dir) {
|
|
136
|
+
const pkgPath = join(dir, "package.json");
|
|
137
|
+
if (existsSync(pkgPath)) {
|
|
138
|
+
try {
|
|
139
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
140
|
+
if (pkg.name) return pkg.name;
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return dir.split("/").pop() || "Project";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/opencode/plugin.ts
|
|
148
|
+
var MemoryLifecyclePlugin = async ({ client, directory }) => {
|
|
149
|
+
const memoryFile = resolve2(directory, MEMORY_FILE);
|
|
150
|
+
const tasksFile = resolve2(directory, TASKS_FILE);
|
|
151
|
+
const agentsFile = resolve2(directory, AGENTS_MD);
|
|
152
|
+
let editCount = 0;
|
|
153
|
+
let specFilesEdited = [];
|
|
154
|
+
return {
|
|
155
|
+
event: async ({ event }) => {
|
|
156
|
+
if (event.type === "session.created") {
|
|
157
|
+
const hasAgentsMd = existsSync2(agentsFile);
|
|
158
|
+
const hasMemory = existsSync2(memoryFile);
|
|
159
|
+
if (!hasMemory && hasAgentsMd) {
|
|
160
|
+
try {
|
|
161
|
+
const result = scaffold(directory, { copilotInstructions: false });
|
|
162
|
+
if (result.created.length > 0) {
|
|
163
|
+
await client.tui.showToast({
|
|
164
|
+
body: {
|
|
165
|
+
message: `Agent memory initialized: ${result.created.join(", ")}`,
|
|
166
|
+
variant: "success"
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
await client.app.log({
|
|
172
|
+
body: {
|
|
173
|
+
service: "agent-memory",
|
|
174
|
+
level: "warn",
|
|
175
|
+
message: `Auto-scaffold failed: ${err}`
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
editCount = 0;
|
|
181
|
+
specFilesEdited = [];
|
|
182
|
+
await client.app.log({
|
|
183
|
+
body: {
|
|
184
|
+
service: "agent-memory",
|
|
185
|
+
level: "info",
|
|
186
|
+
message: `Memory: ${hasMemory}, Tasks: ${existsSync2(tasksFile)}`
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (event.type === "session.idle" && editCount >= 3) {
|
|
191
|
+
if (existsSync2(tasksFile)) {
|
|
192
|
+
const content = readFileSync2(tasksFile, "utf-8");
|
|
193
|
+
const inProgressSection = content.split("## In Progress")[1];
|
|
194
|
+
if (inProgressSection?.trim()) {
|
|
195
|
+
await client.tui.showToast({
|
|
196
|
+
body: {
|
|
197
|
+
message: `${editCount} edits this session. Update .agents/TASKS.md before ending.`,
|
|
198
|
+
variant: "info"
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"tool.execute.after": async (input) => {
|
|
206
|
+
if (input.tool === "edit" || input.tool === "write") {
|
|
207
|
+
editCount++;
|
|
208
|
+
const filePath = input.args?.filePath || "";
|
|
209
|
+
if (filePath.includes(SPEC_DIR)) {
|
|
210
|
+
const shortPath = filePath.split(SPEC_DIR + "/").pop() || filePath;
|
|
211
|
+
if (!specFilesEdited.includes(shortPath)) {
|
|
212
|
+
specFilesEdited.push(shortPath);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/core/memory.ts
|
|
221
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
222
|
+
import { join as join2 } from "path";
|
|
223
|
+
function appendMemory(projectDir, entry) {
|
|
224
|
+
const filePath = join2(projectDir, MEMORY_FILE);
|
|
225
|
+
if (!existsSync3(filePath)) {
|
|
226
|
+
throw new Error(`${MEMORY_FILE} not found. Run 'agent-memory init' first.`);
|
|
227
|
+
}
|
|
228
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
229
|
+
const category = entry.category || "Decisions";
|
|
230
|
+
const line = `- ${entry.date}: ${entry.content}`;
|
|
231
|
+
const categoryHeader = `## ${category}`;
|
|
232
|
+
const headerIndex = content.indexOf(categoryHeader);
|
|
233
|
+
let updated;
|
|
234
|
+
if (headerIndex === -1) {
|
|
235
|
+
updated = content.trimEnd() + `
|
|
236
|
+
|
|
237
|
+
${categoryHeader}
|
|
238
|
+
${line}
|
|
239
|
+
`;
|
|
240
|
+
} else {
|
|
241
|
+
const afterHeader = headerIndex + categoryHeader.length;
|
|
242
|
+
const nextHeaderIndex = content.indexOf("\n## ", afterHeader);
|
|
243
|
+
const insertAt = nextHeaderIndex === -1 ? content.length : nextHeaderIndex;
|
|
244
|
+
const categoryContent = content.slice(afterHeader, insertAt);
|
|
245
|
+
const lastLineEnd = afterHeader + categoryContent.trimEnd().length;
|
|
246
|
+
updated = content.slice(0, lastLineEnd) + "\n" + line + content.slice(lastLineEnd);
|
|
247
|
+
}
|
|
248
|
+
writeFileSync2(filePath, updated);
|
|
249
|
+
}
|
|
250
|
+
function readMemory(projectDir) {
|
|
251
|
+
const filePath = join2(projectDir, MEMORY_FILE);
|
|
252
|
+
if (!existsSync3(filePath)) return {};
|
|
253
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
254
|
+
const categories = {};
|
|
255
|
+
let currentCategory = "_uncategorized";
|
|
256
|
+
for (const line of content.split("\n")) {
|
|
257
|
+
const headerMatch = line.match(/^## (.+)/);
|
|
258
|
+
if (headerMatch) {
|
|
259
|
+
currentCategory = headerMatch[1];
|
|
260
|
+
categories[currentCategory] = categories[currentCategory] || [];
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (line.startsWith("- ") && currentCategory) {
|
|
264
|
+
categories[currentCategory] = categories[currentCategory] || [];
|
|
265
|
+
categories[currentCategory].push(line.slice(2));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return categories;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/core/tasks.ts
|
|
272
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
273
|
+
import { join as join3 } from "path";
|
|
274
|
+
function readTasks(projectDir) {
|
|
275
|
+
const filePath = join3(projectDir, TASKS_FILE);
|
|
276
|
+
const result = {
|
|
277
|
+
in_progress: [],
|
|
278
|
+
up_next: [],
|
|
279
|
+
completed: []
|
|
280
|
+
};
|
|
281
|
+
if (!existsSync4(filePath)) return result;
|
|
282
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
283
|
+
let currentSection = null;
|
|
284
|
+
for (const line of content.split("\n")) {
|
|
285
|
+
if (line.startsWith("## In Progress")) {
|
|
286
|
+
currentSection = "in_progress";
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (line.startsWith("## Up Next")) {
|
|
290
|
+
currentSection = "up_next";
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (line.startsWith("## Completed")) {
|
|
294
|
+
currentSection = "completed";
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (currentSection && line.startsWith("- ")) {
|
|
298
|
+
result[currentSection].push(line.slice(2));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
function writeTasks(projectDir, tasks) {
|
|
304
|
+
const filePath = join3(projectDir, TASKS_FILE);
|
|
305
|
+
const content = `# Current Tasks
|
|
306
|
+
|
|
307
|
+
Working memory for cross-session continuity. Update before ending a session.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## In Progress
|
|
312
|
+
${tasks.in_progress.map((t) => `- ${t}`).join("\n") || ""}
|
|
313
|
+
|
|
314
|
+
## Up Next
|
|
315
|
+
${tasks.up_next.map((t) => `- ${t}`).join("\n") || ""}
|
|
316
|
+
|
|
317
|
+
## Completed
|
|
318
|
+
${tasks.completed.map((t) => `- ${t}`).join("\n") || ""}
|
|
319
|
+
`;
|
|
320
|
+
writeFileSync3(filePath, content);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/core/doctor.ts
|
|
324
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
325
|
+
import { join as join4 } from "path";
|
|
326
|
+
function doctor(projectDir) {
|
|
327
|
+
const issues = [];
|
|
328
|
+
const required = [
|
|
329
|
+
{ file: AGENTS_MD, desc: "Root router file" },
|
|
330
|
+
{ file: MEMORY_FILE, desc: "Long-term memory" },
|
|
331
|
+
{ file: TASKS_FILE, desc: "Working memory" }
|
|
332
|
+
];
|
|
333
|
+
for (const { file, desc } of required) {
|
|
334
|
+
const filePath = join4(projectDir, file);
|
|
335
|
+
if (!existsSync5(filePath)) {
|
|
336
|
+
issues.push({ level: "error", file, message: `Missing ${desc}` });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const dir of [AGENTS_DIR, SPEC_DIR]) {
|
|
340
|
+
if (!existsSync5(join4(projectDir, dir))) {
|
|
341
|
+
issues.push({ level: "error", file: dir, message: "Directory missing" });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const copilotPath = join4(projectDir, COPILOT_INSTRUCTIONS);
|
|
345
|
+
if (!existsSync5(copilotPath)) {
|
|
346
|
+
issues.push({
|
|
347
|
+
level: "warning",
|
|
348
|
+
file: COPILOT_INSTRUCTIONS,
|
|
349
|
+
message: "Copilot instructions missing \u2014 VSCode/GitHub Copilot won't have context"
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const agentsPath = join4(projectDir, AGENTS_MD);
|
|
353
|
+
if (existsSync5(agentsPath)) {
|
|
354
|
+
const content = readFileSync5(agentsPath, "utf-8");
|
|
355
|
+
if (!content.includes(".agents/")) {
|
|
356
|
+
issues.push({
|
|
357
|
+
level: "warning",
|
|
358
|
+
file: AGENTS_MD,
|
|
359
|
+
message: "No references to .agents/ \u2014 agents may not find memory files"
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (content.split("\n").length > 150) {
|
|
363
|
+
issues.push({
|
|
364
|
+
level: "warning",
|
|
365
|
+
file: AGENTS_MD,
|
|
366
|
+
message: "Over 150 lines \u2014 consider keeping it concise as a router"
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const memoryPath = join4(projectDir, MEMORY_FILE);
|
|
371
|
+
if (existsSync5(memoryPath)) {
|
|
372
|
+
const lines = readFileSync5(memoryPath, "utf-8").split("\n").length;
|
|
373
|
+
if (lines > 150) {
|
|
374
|
+
issues.push({
|
|
375
|
+
level: "warning",
|
|
376
|
+
file: MEMORY_FILE,
|
|
377
|
+
message: `${lines} lines \u2014 consider compressing or archiving old entries`
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const opencodePkgPath = join4(projectDir, ".opencode", "package.json");
|
|
382
|
+
const opencodeJsonPath = join4(projectDir, "opencode.json");
|
|
383
|
+
const opencodeExists = existsSync5(join4(projectDir, ".opencode"));
|
|
384
|
+
if (opencodeExists) {
|
|
385
|
+
if (!existsSync5(opencodePkgPath)) {
|
|
386
|
+
issues.push({
|
|
387
|
+
level: "warning",
|
|
388
|
+
file: ".opencode/package.json",
|
|
389
|
+
message: "Missing \u2014 run 'agent-memory init --opencode' to wire up the plugin"
|
|
390
|
+
});
|
|
391
|
+
} else {
|
|
392
|
+
try {
|
|
393
|
+
const pkg = JSON.parse(readFileSync5(opencodePkgPath, "utf-8"));
|
|
394
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
395
|
+
if (!deps["@vuau/agent-memory"]) {
|
|
396
|
+
issues.push({
|
|
397
|
+
level: "warning",
|
|
398
|
+
file: ".opencode/package.json",
|
|
399
|
+
message: "@vuau/agent-memory not in dependencies \u2014 run 'agent-memory init --opencode'"
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
} catch {
|
|
403
|
+
issues.push({ level: "warning", file: ".opencode/package.json", message: "Invalid JSON" });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!existsSync5(opencodeJsonPath)) {
|
|
407
|
+
issues.push({
|
|
408
|
+
level: "warning",
|
|
409
|
+
file: "opencode.json",
|
|
410
|
+
message: "Missing \u2014 run 'agent-memory init --opencode' to wire up the plugin"
|
|
411
|
+
});
|
|
412
|
+
} else {
|
|
413
|
+
try {
|
|
414
|
+
const config = JSON.parse(readFileSync5(opencodeJsonPath, "utf-8"));
|
|
415
|
+
const plugins = config.plugin || [];
|
|
416
|
+
if (!plugins.includes("@vuau/agent-memory")) {
|
|
417
|
+
issues.push({
|
|
418
|
+
level: "warning",
|
|
419
|
+
file: "opencode.json",
|
|
420
|
+
message: "@vuau/agent-memory not in plugin array \u2014 run 'agent-memory init --opencode'"
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
} catch {
|
|
424
|
+
issues.push({ level: "warning", file: "opencode.json", message: "Invalid JSON" });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return { ok: issues.filter((i) => i.level === "error").length === 0, issues };
|
|
429
|
+
}
|
|
430
|
+
export {
|
|
431
|
+
AGENTS_DIR,
|
|
432
|
+
AGENTS_MD,
|
|
433
|
+
MemoryLifecyclePlugin as AgentMemoryPlugin,
|
|
434
|
+
COPILOT_INSTRUCTIONS,
|
|
435
|
+
MEMORY_FILE,
|
|
436
|
+
SPEC_DIR,
|
|
437
|
+
TASKS_FILE,
|
|
438
|
+
appendMemory,
|
|
439
|
+
doctor,
|
|
440
|
+
readMemory,
|
|
441
|
+
readTasks,
|
|
442
|
+
scaffold,
|
|
443
|
+
writeTasks
|
|
444
|
+
};
|
package/package.json
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vuau/agent-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Structured AI memory for codebases — OpenCode plugin + scaffolding CLI",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./index.
|
|
8
|
+
".": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"agent-memory": "
|
|
11
|
+
"agent-memory": "dist/bin/cli.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"
|
|
15
|
-
"src/",
|
|
14
|
+
"dist/",
|
|
16
15
|
"templates/",
|
|
17
|
-
"bin/",
|
|
18
16
|
"docs/",
|
|
19
17
|
"README.md"
|
|
20
18
|
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
},
|
|
21
27
|
"keywords": [
|
|
22
28
|
"opencode",
|
|
23
29
|
"opencode-plugin",
|
|
@@ -32,7 +38,7 @@
|
|
|
32
38
|
"license": "MIT",
|
|
33
39
|
"repository": {
|
|
34
40
|
"type": "git",
|
|
35
|
-
"url": "https://github.com/vuau/agent-memory"
|
|
41
|
+
"url": "git+https://github.com/vuau/agent-memory.git"
|
|
36
42
|
},
|
|
37
43
|
"peerDependencies": {
|
|
38
44
|
"@opencode-ai/plugin": ">=1.0.0"
|
package/bin/cli.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node --experimental-strip-types --no-warnings
|
|
2
|
-
/**
|
|
3
|
-
* CLI entry point for @vuau/agent-memory
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* npx @vuau/agent-memory init [--force] [--name <project-name>]
|
|
7
|
-
* npx @vuau/agent-memory doctor
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { scaffold } from "../src/core/scaffold.ts"
|
|
11
|
-
import { doctor } from "../src/core/doctor.ts"
|
|
12
|
-
|
|
13
|
-
const args = process.argv.slice(2)
|
|
14
|
-
const command = args[0]
|
|
15
|
-
|
|
16
|
-
function printUsage() {
|
|
17
|
-
console.log(`
|
|
18
|
-
@vuau/agent-memory — Structured AI memory for codebases
|
|
19
|
-
|
|
20
|
-
Usage:
|
|
21
|
-
agent-memory init [options] Scaffold .agents/ structure
|
|
22
|
-
agent-memory doctor Validate .agents/ structure
|
|
23
|
-
agent-memory help Show this help
|
|
24
|
-
|
|
25
|
-
Options (init):
|
|
26
|
-
--force Overwrite existing files
|
|
27
|
-
--name <name> Project name (default: from package.json)
|
|
28
|
-
--no-copilot Skip .github/copilot-instructions.md
|
|
29
|
-
`)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function runInit() {
|
|
33
|
-
const force = args.includes("--force")
|
|
34
|
-
const noCopilot = args.includes("--no-copilot")
|
|
35
|
-
const nameIdx = args.indexOf("--name")
|
|
36
|
-
const projectName = nameIdx !== -1 ? args[nameIdx + 1] : undefined
|
|
37
|
-
|
|
38
|
-
const cwd = process.cwd()
|
|
39
|
-
console.log(`Initializing agent memory in ${cwd}...`)
|
|
40
|
-
|
|
41
|
-
const result = scaffold(cwd, {
|
|
42
|
-
projectName,
|
|
43
|
-
copilotInstructions: !noCopilot,
|
|
44
|
-
}, force)
|
|
45
|
-
|
|
46
|
-
if (result.created.length > 0) {
|
|
47
|
-
console.log("\nCreated:")
|
|
48
|
-
for (const f of result.created) {
|
|
49
|
-
console.log(` ✓ ${f}`)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (result.skipped.length > 0) {
|
|
54
|
-
console.log("\nSkipped (already exist):")
|
|
55
|
-
for (const f of result.skipped) {
|
|
56
|
-
console.log(` - ${f}`)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (result.created.length === 0 && result.skipped.length > 0) {
|
|
61
|
-
console.log("\nAll files already exist. Use --force to overwrite.")
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
console.log("\nNext steps:")
|
|
65
|
-
console.log(" 1. Edit AGENTS.md — add your project-specific rules")
|
|
66
|
-
console.log(" 2. Add spec files to .agents/spec/ for detailed documentation")
|
|
67
|
-
console.log(" 3. For OpenCode: add to opencode.json → { \"plugin\": [\"@vuau/agent-memory\"] }")
|
|
68
|
-
console.log("")
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function runDoctor() {
|
|
72
|
-
const cwd = process.cwd()
|
|
73
|
-
const result = doctor(cwd)
|
|
74
|
-
|
|
75
|
-
if (result.issues.length === 0) {
|
|
76
|
-
console.log("✓ All checks passed!")
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const issue of result.issues) {
|
|
81
|
-
const icon = issue.level === "error" ? "✗" : issue.level === "warning" ? "⚠" : "ℹ"
|
|
82
|
-
console.log(` ${icon} [${issue.level}] ${issue.file}: ${issue.message}`)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
console.log("")
|
|
86
|
-
if (result.ok) {
|
|
87
|
-
console.log("⚠ Passed with warnings. Run 'agent-memory init' to fix missing files.")
|
|
88
|
-
} else {
|
|
89
|
-
console.log("✗ Failed. Run 'agent-memory init' to create missing files.")
|
|
90
|
-
process.exit(1)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
switch (command) {
|
|
95
|
-
case "init":
|
|
96
|
-
runInit()
|
|
97
|
-
break
|
|
98
|
-
case "doctor":
|
|
99
|
-
runDoctor()
|
|
100
|
-
break
|
|
101
|
-
case "help":
|
|
102
|
-
case "--help":
|
|
103
|
-
case "-h":
|
|
104
|
-
case undefined:
|
|
105
|
-
printUsage()
|
|
106
|
-
break
|
|
107
|
-
default:
|
|
108
|
-
console.error(`Unknown command: ${command}`)
|
|
109
|
-
printUsage()
|
|
110
|
-
process.exit(1)
|
|
111
|
-
}
|
package/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vuau/agent-memory
|
|
3
|
-
*
|
|
4
|
-
* Structured AI memory for codebases.
|
|
5
|
-
* OpenCode plugin entry point.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export { MemoryLifecyclePlugin as AgentMemoryPlugin } from "./src/opencode/plugin.ts"
|
|
9
|
-
|
|
10
|
-
// Re-export core for programmatic use
|
|
11
|
-
export * from "./src/core/index.ts"
|
package/src/core/doctor.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Doctor — validate .agents/ structure integrity.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { existsSync, readFileSync } from "fs"
|
|
6
|
-
import { join } from "path"
|
|
7
|
-
import {
|
|
8
|
-
AGENTS_DIR,
|
|
9
|
-
SPEC_DIR,
|
|
10
|
-
MEMORY_FILE,
|
|
11
|
-
TASKS_FILE,
|
|
12
|
-
AGENTS_MD,
|
|
13
|
-
COPILOT_INSTRUCTIONS,
|
|
14
|
-
type DoctorResult,
|
|
15
|
-
type DoctorIssue,
|
|
16
|
-
} from "./types.ts"
|
|
17
|
-
|
|
18
|
-
export function doctor(projectDir: string): DoctorResult {
|
|
19
|
-
const issues: DoctorIssue[] = []
|
|
20
|
-
|
|
21
|
-
// Check required files
|
|
22
|
-
const required = [
|
|
23
|
-
{ file: AGENTS_MD, desc: "Root router file" },
|
|
24
|
-
{ file: MEMORY_FILE, desc: "Long-term memory" },
|
|
25
|
-
{ file: TASKS_FILE, desc: "Working memory" },
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
for (const { file, desc } of required) {
|
|
29
|
-
const filePath = join(projectDir, file)
|
|
30
|
-
if (!existsSync(filePath)) {
|
|
31
|
-
issues.push({ level: "error", file, message: `Missing ${desc}` })
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Check directories
|
|
36
|
-
for (const dir of [AGENTS_DIR, SPEC_DIR]) {
|
|
37
|
-
if (!existsSync(join(projectDir, dir))) {
|
|
38
|
-
issues.push({ level: "error", file: dir, message: "Directory missing" })
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Check optional files
|
|
43
|
-
const copilotPath = join(projectDir, COPILOT_INSTRUCTIONS)
|
|
44
|
-
if (!existsSync(copilotPath)) {
|
|
45
|
-
issues.push({
|
|
46
|
-
level: "warning",
|
|
47
|
-
file: COPILOT_INSTRUCTIONS,
|
|
48
|
-
message: "Copilot instructions missing — VSCode/GitHub Copilot won't have context",
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Validate AGENTS.md has documentation map
|
|
53
|
-
const agentsPath = join(projectDir, AGENTS_MD)
|
|
54
|
-
if (existsSync(agentsPath)) {
|
|
55
|
-
const content = readFileSync(agentsPath, "utf-8")
|
|
56
|
-
if (!content.includes(".agents/")) {
|
|
57
|
-
issues.push({
|
|
58
|
-
level: "warning",
|
|
59
|
-
file: AGENTS_MD,
|
|
60
|
-
message: "No references to .agents/ — agents may not find memory files",
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
if (content.split("\n").length > 150) {
|
|
64
|
-
issues.push({
|
|
65
|
-
level: "warning",
|
|
66
|
-
file: AGENTS_MD,
|
|
67
|
-
message: "Over 150 lines — consider keeping it concise as a router",
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Validate MEMORY.md line count
|
|
73
|
-
const memoryPath = join(projectDir, MEMORY_FILE)
|
|
74
|
-
if (existsSync(memoryPath)) {
|
|
75
|
-
const lines = readFileSync(memoryPath, "utf-8").split("\n").length
|
|
76
|
-
if (lines > 150) {
|
|
77
|
-
issues.push({
|
|
78
|
-
level: "warning",
|
|
79
|
-
file: MEMORY_FILE,
|
|
80
|
-
message: `${lines} lines — consider compressing or archiving old entries`,
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return { ok: issues.filter((i) => i.level === "error").length === 0, issues }
|
|
86
|
-
}
|
package/src/core/index.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { scaffold, type ScaffoldResult } from "./scaffold.ts"
|
|
2
|
-
export { appendMemory, readMemory, type MemoryEntry } from "./memory.ts"
|
|
3
|
-
export { readTasks, writeTasks, type TaskItem, type TaskStatus } from "./tasks.ts"
|
|
4
|
-
export { doctor } from "./doctor.ts"
|
|
5
|
-
export * from "./types.ts"
|