@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 CHANGED
@@ -82,9 +82,18 @@ npx @vuau/agent-memory init
82
82
  npx @vuau/agent-memory init --force # Overwrite existing files
83
83
  npx @vuau/agent-memory init --name "My App" # Custom project name
84
84
  npx @vuau/agent-memory init --no-copilot # Skip copilot-instructions.md
85
+ npx @vuau/agent-memory init --opencode # Wire up OpenCode plugin
85
86
  npx @vuau/agent-memory doctor # Validate structure
86
87
  ```
87
88
 
89
+ #### `--opencode` flag
90
+
91
+ Wires up the OpenCode plugin automatically:
92
+ - Creates/updates `.opencode/package.json` with `@vuau/agent-memory` dependency
93
+ - Creates/updates `opencode.json` with `"plugin": ["@vuau/agent-memory"]`
94
+
95
+ After running, restart OpenCode to activate the plugin.
96
+
88
97
  ## How It Works
89
98
 
90
99
  ### For AI Agents
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/core/scaffold.ts
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
5
+ import { join, resolve, dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
+
8
+ // src/core/types.ts
9
+ var AGENTS_DIR = ".agents";
10
+ var SPEC_DIR = ".agents/spec";
11
+ var MEMORY_FILE = ".agents/MEMORY.md";
12
+ var TASKS_FILE = ".agents/TASKS.md";
13
+ var AGENTS_MD = "AGENTS.md";
14
+ var COPILOT_INSTRUCTIONS = ".github/copilot-instructions.md";
15
+
16
+ // src/core/scaffold.ts
17
+ function getTemplatesDir() {
18
+ try {
19
+ const thisDir = dirname(fileURLToPath(import.meta.url));
20
+ const candidate = resolve(thisDir, "../../templates");
21
+ if (existsSync(candidate)) return candidate;
22
+ } catch {
23
+ }
24
+ const candidate2 = resolve(__dirname, "../../templates");
25
+ if (existsSync(candidate2)) return candidate2;
26
+ throw new Error("Cannot locate templates directory");
27
+ }
28
+ var TEMPLATES_DIR = getTemplatesDir();
29
+ function readTemplate(name) {
30
+ const templatePath = join(TEMPLATES_DIR, name);
31
+ return readFileSync(templatePath, "utf-8");
32
+ }
33
+ function applyVars(content, vars) {
34
+ let result = content;
35
+ for (const [key, value] of Object.entries(vars)) {
36
+ result = result.replaceAll(`{{${key}}}`, value);
37
+ }
38
+ return result;
39
+ }
40
+ function scaffold(projectDir, config = {}, force = false) {
41
+ const result = { created: [], skipped: [] };
42
+ const projectName = config.projectName || guessProjectName(projectDir);
43
+ const vars = { PROJECT_NAME: projectName };
44
+ const dirs = [
45
+ join(projectDir, AGENTS_DIR),
46
+ join(projectDir, SPEC_DIR)
47
+ ];
48
+ if (config.copilotInstructions !== false) {
49
+ dirs.push(join(projectDir, ".github"));
50
+ }
51
+ for (const dir of dirs) {
52
+ if (!existsSync(dir)) {
53
+ mkdirSync(dir, { recursive: true });
54
+ }
55
+ }
56
+ const files = [
57
+ { target: AGENTS_MD, template: "AGENTS.md" },
58
+ { target: MEMORY_FILE, template: "MEMORY.md" },
59
+ { target: TASKS_FILE, template: "TASKS.md" }
60
+ ];
61
+ if (config.copilotInstructions !== false) {
62
+ files.push({
63
+ target: COPILOT_INSTRUCTIONS,
64
+ template: "copilot-instructions.md"
65
+ });
66
+ }
67
+ for (const { target, template } of files) {
68
+ const targetPath = join(projectDir, target);
69
+ if (existsSync(targetPath) && !force) {
70
+ result.skipped.push(target);
71
+ continue;
72
+ }
73
+ const content = applyVars(readTemplate(template), vars);
74
+ writeFileSync(targetPath, content);
75
+ result.created.push(target);
76
+ }
77
+ const specKeep = join(projectDir, SPEC_DIR, ".gitkeep");
78
+ if (!existsSync(specKeep)) {
79
+ writeFileSync(specKeep, "");
80
+ result.created.push(`${SPEC_DIR}/.gitkeep`);
81
+ }
82
+ if (config.opencode) {
83
+ scaffoldOpenCode(projectDir, result, force);
84
+ }
85
+ return result;
86
+ }
87
+ function scaffoldOpenCode(projectDir, result, force) {
88
+ const PACKAGE_NAME = "@vuau/agent-memory";
89
+ const opencodePkgPath = join(projectDir, ".opencode", "package.json");
90
+ const opencodeDir = join(projectDir, ".opencode");
91
+ if (!existsSync(opencodeDir)) {
92
+ mkdirSync(opencodeDir, { recursive: true });
93
+ }
94
+ if (!existsSync(opencodePkgPath)) {
95
+ writeFileSync(
96
+ opencodePkgPath,
97
+ JSON.stringify({ dependencies: { [PACKAGE_NAME]: "latest" } }, null, 2) + "\n"
98
+ );
99
+ result.created.push(".opencode/package.json");
100
+ } else {
101
+ const pkg = JSON.parse(readFileSync(opencodePkgPath, "utf-8"));
102
+ const deps = pkg.dependencies || {};
103
+ if (!deps[PACKAGE_NAME] || force) {
104
+ deps[PACKAGE_NAME] = "latest";
105
+ pkg.dependencies = deps;
106
+ writeFileSync(opencodePkgPath, JSON.stringify(pkg, null, 2) + "\n");
107
+ if (!deps[PACKAGE_NAME]) {
108
+ result.created.push(".opencode/package.json");
109
+ }
110
+ } else {
111
+ result.skipped.push(".opencode/package.json");
112
+ }
113
+ }
114
+ const opencodeJsonPath = join(projectDir, "opencode.json");
115
+ if (!existsSync(opencodeJsonPath)) {
116
+ writeFileSync(
117
+ opencodeJsonPath,
118
+ JSON.stringify({ plugin: [PACKAGE_NAME] }, null, 2) + "\n"
119
+ );
120
+ result.created.push("opencode.json");
121
+ } else {
122
+ const config = JSON.parse(readFileSync(opencodeJsonPath, "utf-8"));
123
+ const plugins = config.plugin || [];
124
+ if (!plugins.includes(PACKAGE_NAME)) {
125
+ config.plugin = [...plugins, PACKAGE_NAME];
126
+ writeFileSync(opencodeJsonPath, JSON.stringify(config, null, 2) + "\n");
127
+ result.created.push("opencode.json (merged plugin)");
128
+ } else {
129
+ result.skipped.push("opencode.json");
130
+ }
131
+ }
132
+ }
133
+ function guessProjectName(dir) {
134
+ const pkgPath = join(dir, "package.json");
135
+ if (existsSync(pkgPath)) {
136
+ try {
137
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
138
+ if (pkg.name) return pkg.name;
139
+ } catch {
140
+ }
141
+ }
142
+ return dir.split("/").pop() || "Project";
143
+ }
144
+
145
+ // src/core/doctor.ts
146
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
147
+ import { join as join2 } from "path";
148
+ function doctor(projectDir) {
149
+ const issues = [];
150
+ const required = [
151
+ { file: AGENTS_MD, desc: "Root router file" },
152
+ { file: MEMORY_FILE, desc: "Long-term memory" },
153
+ { file: TASKS_FILE, desc: "Working memory" }
154
+ ];
155
+ for (const { file, desc } of required) {
156
+ const filePath = join2(projectDir, file);
157
+ if (!existsSync2(filePath)) {
158
+ issues.push({ level: "error", file, message: `Missing ${desc}` });
159
+ }
160
+ }
161
+ for (const dir of [AGENTS_DIR, SPEC_DIR]) {
162
+ if (!existsSync2(join2(projectDir, dir))) {
163
+ issues.push({ level: "error", file: dir, message: "Directory missing" });
164
+ }
165
+ }
166
+ const copilotPath = join2(projectDir, COPILOT_INSTRUCTIONS);
167
+ if (!existsSync2(copilotPath)) {
168
+ issues.push({
169
+ level: "warning",
170
+ file: COPILOT_INSTRUCTIONS,
171
+ message: "Copilot instructions missing \u2014 VSCode/GitHub Copilot won't have context"
172
+ });
173
+ }
174
+ const agentsPath = join2(projectDir, AGENTS_MD);
175
+ if (existsSync2(agentsPath)) {
176
+ const content = readFileSync2(agentsPath, "utf-8");
177
+ if (!content.includes(".agents/")) {
178
+ issues.push({
179
+ level: "warning",
180
+ file: AGENTS_MD,
181
+ message: "No references to .agents/ \u2014 agents may not find memory files"
182
+ });
183
+ }
184
+ if (content.split("\n").length > 150) {
185
+ issues.push({
186
+ level: "warning",
187
+ file: AGENTS_MD,
188
+ message: "Over 150 lines \u2014 consider keeping it concise as a router"
189
+ });
190
+ }
191
+ }
192
+ const memoryPath = join2(projectDir, MEMORY_FILE);
193
+ if (existsSync2(memoryPath)) {
194
+ const lines = readFileSync2(memoryPath, "utf-8").split("\n").length;
195
+ if (lines > 150) {
196
+ issues.push({
197
+ level: "warning",
198
+ file: MEMORY_FILE,
199
+ message: `${lines} lines \u2014 consider compressing or archiving old entries`
200
+ });
201
+ }
202
+ }
203
+ const opencodePkgPath = join2(projectDir, ".opencode", "package.json");
204
+ const opencodeJsonPath = join2(projectDir, "opencode.json");
205
+ const opencodeExists = existsSync2(join2(projectDir, ".opencode"));
206
+ if (opencodeExists) {
207
+ if (!existsSync2(opencodePkgPath)) {
208
+ issues.push({
209
+ level: "warning",
210
+ file: ".opencode/package.json",
211
+ message: "Missing \u2014 run 'agent-memory init --opencode' to wire up the plugin"
212
+ });
213
+ } else {
214
+ try {
215
+ const pkg = JSON.parse(readFileSync2(opencodePkgPath, "utf-8"));
216
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
217
+ if (!deps["@vuau/agent-memory"]) {
218
+ issues.push({
219
+ level: "warning",
220
+ file: ".opencode/package.json",
221
+ message: "@vuau/agent-memory not in dependencies \u2014 run 'agent-memory init --opencode'"
222
+ });
223
+ }
224
+ } catch {
225
+ issues.push({ level: "warning", file: ".opencode/package.json", message: "Invalid JSON" });
226
+ }
227
+ }
228
+ if (!existsSync2(opencodeJsonPath)) {
229
+ issues.push({
230
+ level: "warning",
231
+ file: "opencode.json",
232
+ message: "Missing \u2014 run 'agent-memory init --opencode' to wire up the plugin"
233
+ });
234
+ } else {
235
+ try {
236
+ const config = JSON.parse(readFileSync2(opencodeJsonPath, "utf-8"));
237
+ const plugins = config.plugin || [];
238
+ if (!plugins.includes("@vuau/agent-memory")) {
239
+ issues.push({
240
+ level: "warning",
241
+ file: "opencode.json",
242
+ message: "@vuau/agent-memory not in plugin array \u2014 run 'agent-memory init --opencode'"
243
+ });
244
+ }
245
+ } catch {
246
+ issues.push({ level: "warning", file: "opencode.json", message: "Invalid JSON" });
247
+ }
248
+ }
249
+ }
250
+ return { ok: issues.filter((i) => i.level === "error").length === 0, issues };
251
+ }
252
+
253
+ // bin/cli.ts
254
+ var args = process.argv.slice(2);
255
+ var command = args[0];
256
+ function printUsage() {
257
+ console.log(`
258
+ @vuau/agent-memory \u2014 Structured AI memory for codebases
259
+
260
+ Usage:
261
+ agent-memory init [options] Scaffold .agents/ structure
262
+ agent-memory doctor Validate .agents/ structure
263
+ agent-memory help Show this help
264
+
265
+ Options (init):
266
+ --force Overwrite existing files
267
+ --name <name> Project name (default: from package.json)
268
+ --no-copilot Skip .github/copilot-instructions.md
269
+ --opencode Wire up OpenCode plugin (.opencode/package.json + opencode.json)
270
+ `);
271
+ }
272
+ function runInit() {
273
+ const force = args.includes("--force");
274
+ const noCopilot = args.includes("--no-copilot");
275
+ const opencode = args.includes("--opencode");
276
+ const nameIdx = args.indexOf("--name");
277
+ const projectName = nameIdx !== -1 ? args[nameIdx + 1] : void 0;
278
+ const cwd = process.cwd();
279
+ console.log(`Initializing agent memory in ${cwd}...`);
280
+ const result = scaffold(cwd, {
281
+ projectName,
282
+ copilotInstructions: !noCopilot,
283
+ opencode
284
+ }, force);
285
+ if (result.created.length > 0) {
286
+ console.log("\nCreated:");
287
+ for (const f of result.created) {
288
+ console.log(` \u2713 ${f}`);
289
+ }
290
+ }
291
+ if (result.skipped.length > 0) {
292
+ console.log("\nSkipped (already exist):");
293
+ for (const f of result.skipped) {
294
+ console.log(` - ${f}`);
295
+ }
296
+ }
297
+ if (result.created.length === 0 && result.skipped.length > 0) {
298
+ console.log("\nAll files already exist. Use --force to overwrite.");
299
+ }
300
+ console.log("\nNext steps:");
301
+ console.log(" 1. Edit AGENTS.md \u2014 add your project-specific rules");
302
+ console.log(" 2. Add spec files to .agents/spec/ for detailed documentation");
303
+ if (opencode) {
304
+ console.log(" 3. Restart OpenCode to activate the plugin");
305
+ } else {
306
+ console.log(" 3. For OpenCode: run with --opencode flag to wire up the plugin");
307
+ }
308
+ console.log("");
309
+ }
310
+ function runDoctor() {
311
+ const cwd = process.cwd();
312
+ const result = doctor(cwd);
313
+ if (result.issues.length === 0) {
314
+ console.log("\u2713 All checks passed!");
315
+ return;
316
+ }
317
+ for (const issue of result.issues) {
318
+ const icon = issue.level === "error" ? "\u2717" : issue.level === "warning" ? "\u26A0" : "\u2139";
319
+ console.log(` ${icon} [${issue.level}] ${issue.file}: ${issue.message}`);
320
+ }
321
+ console.log("");
322
+ if (result.ok) {
323
+ console.log("\u26A0 Passed with warnings. Run 'agent-memory init' to fix missing files.");
324
+ } else {
325
+ console.log("\u2717 Failed. Run 'agent-memory init' to create missing files.");
326
+ process.exit(1);
327
+ }
328
+ }
329
+ switch (command) {
330
+ case "init":
331
+ runInit();
332
+ break;
333
+ case "doctor":
334
+ runDoctor();
335
+ break;
336
+ case "help":
337
+ case "--help":
338
+ case "-h":
339
+ case void 0:
340
+ printUsage();
341
+ break;
342
+ default:
343
+ console.error(`Unknown command: ${command}`);
344
+ printUsage();
345
+ process.exit(1);
346
+ }