@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.
@@ -1,86 +0,0 @@
1
- /**
2
- * Memory — read/append operations for MEMORY.md
3
- */
4
-
5
- import { existsSync, readFileSync, writeFileSync } from "fs"
6
- import { join } from "path"
7
- import { MEMORY_FILE } from "./types.ts"
8
-
9
- export interface MemoryEntry {
10
- date: string
11
- content: string
12
- category?: string
13
- }
14
-
15
- /**
16
- * Append a 1-line entry to MEMORY.md under a category.
17
- * Creates category header if it doesn't exist.
18
- */
19
- export function appendMemory(
20
- projectDir: string,
21
- entry: MemoryEntry
22
- ): void {
23
- const filePath = join(projectDir, MEMORY_FILE)
24
- if (!existsSync(filePath)) {
25
- throw new Error(`${MEMORY_FILE} not found. Run 'agent-memory init' first.`)
26
- }
27
-
28
- const content = readFileSync(filePath, "utf-8")
29
- const category = entry.category || "Decisions"
30
- const line = `- ${entry.date}: ${entry.content}`
31
-
32
- // Find category header
33
- const categoryHeader = `## ${category}`
34
- const headerIndex = content.indexOf(categoryHeader)
35
-
36
- let updated: string
37
- if (headerIndex === -1) {
38
- // Append new category at end
39
- updated = content.trimEnd() + `\n\n${categoryHeader}\n${line}\n`
40
- } else {
41
- // Find next ## header or end of file
42
- const afterHeader = headerIndex + categoryHeader.length
43
- const nextHeaderIndex = content.indexOf("\n## ", afterHeader)
44
- const insertAt = nextHeaderIndex === -1 ? content.length : nextHeaderIndex
45
-
46
- // Find last non-empty line in category
47
- const categoryContent = content.slice(afterHeader, insertAt)
48
- const lastLineEnd = afterHeader + categoryContent.trimEnd().length
49
-
50
- updated =
51
- content.slice(0, lastLineEnd) +
52
- "\n" + line +
53
- content.slice(lastLineEnd)
54
- }
55
-
56
- writeFileSync(filePath, updated)
57
- }
58
-
59
- /**
60
- * Read all entries from MEMORY.md, parsed by category.
61
- */
62
- export function readMemory(
63
- projectDir: string
64
- ): Record<string, string[]> {
65
- const filePath = join(projectDir, MEMORY_FILE)
66
- if (!existsSync(filePath)) return {}
67
-
68
- const content = readFileSync(filePath, "utf-8")
69
- const categories: Record<string, string[]> = {}
70
- let currentCategory = "_uncategorized"
71
-
72
- for (const line of content.split("\n")) {
73
- const headerMatch = line.match(/^## (.+)/)
74
- if (headerMatch) {
75
- currentCategory = headerMatch[1]
76
- categories[currentCategory] = categories[currentCategory] || []
77
- continue
78
- }
79
- if (line.startsWith("- ") && currentCategory) {
80
- categories[currentCategory] = categories[currentCategory] || []
81
- categories[currentCategory].push(line.slice(2))
82
- }
83
- }
84
-
85
- return categories
86
- }
@@ -1,124 +0,0 @@
1
- /**
2
- * Scaffold — create .agents/ directory structure in a project.
3
- *
4
- * Idempotent: skips existing files unless force=true.
5
- */
6
-
7
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs"
8
- import { join, resolve, dirname } from "path"
9
- import { fileURLToPath } from "url"
10
- import {
11
- AGENTS_DIR,
12
- SPEC_DIR,
13
- MEMORY_FILE,
14
- TASKS_FILE,
15
- AGENTS_MD,
16
- COPILOT_INSTRUCTIONS,
17
- type AgentMemoryConfig,
18
- } from "./types.ts"
19
-
20
- // Resolve templates dir relative to this file (works in both Bun and Node)
21
- function getTemplatesDir(): string {
22
- // Try import.meta based resolution
23
- try {
24
- const thisDir = dirname(fileURLToPath(import.meta.url))
25
- const candidate = resolve(thisDir, "../../templates")
26
- if (existsSync(candidate)) return candidate
27
- } catch {}
28
- // Fallback: walk up from __dirname if available
29
- const candidate2 = resolve(__dirname, "../../templates")
30
- if (existsSync(candidate2)) return candidate2
31
- throw new Error("Cannot locate templates directory")
32
- }
33
-
34
- const TEMPLATES_DIR = getTemplatesDir()
35
-
36
- export interface ScaffoldResult {
37
- created: string[]
38
- skipped: string[]
39
- }
40
-
41
- function readTemplate(name: string): string {
42
- const templatePath = join(TEMPLATES_DIR, name)
43
- return readFileSync(templatePath, "utf-8")
44
- }
45
-
46
- function applyVars(content: string, vars: Record<string, string>): string {
47
- let result = content
48
- for (const [key, value] of Object.entries(vars)) {
49
- result = result.replaceAll(`{{${key}}}`, value)
50
- }
51
- return result
52
- }
53
-
54
- export function scaffold(
55
- projectDir: string,
56
- config: AgentMemoryConfig = {},
57
- force = false
58
- ): ScaffoldResult {
59
- const result: ScaffoldResult = { created: [], skipped: [] }
60
- const projectName = config.projectName || guessProjectName(projectDir)
61
- const vars = { PROJECT_NAME: projectName }
62
-
63
- // Ensure directories exist
64
- const dirs = [
65
- join(projectDir, AGENTS_DIR),
66
- join(projectDir, SPEC_DIR),
67
- ]
68
- if (config.copilotInstructions !== false) {
69
- dirs.push(join(projectDir, ".github"))
70
- }
71
- for (const dir of dirs) {
72
- if (!existsSync(dir)) {
73
- mkdirSync(dir, { recursive: true })
74
- }
75
- }
76
-
77
- // Files to scaffold
78
- const files: Array<{ target: string; template: string }> = [
79
- { target: AGENTS_MD, template: "AGENTS.md" },
80
- { target: MEMORY_FILE, template: "MEMORY.md" },
81
- { target: TASKS_FILE, template: "TASKS.md" },
82
- ]
83
-
84
- if (config.copilotInstructions !== false) {
85
- files.push({
86
- target: COPILOT_INSTRUCTIONS,
87
- template: "copilot-instructions.md",
88
- })
89
- }
90
-
91
- // Write files
92
- for (const { target, template } of files) {
93
- const targetPath = join(projectDir, target)
94
- if (existsSync(targetPath) && !force) {
95
- result.skipped.push(target)
96
- continue
97
- }
98
- const content = applyVars(readTemplate(template), vars)
99
- writeFileSync(targetPath, content)
100
- result.created.push(target)
101
- }
102
-
103
- // Create .gitkeep in spec/ if empty
104
- const specKeep = join(projectDir, SPEC_DIR, ".gitkeep")
105
- if (!existsSync(specKeep)) {
106
- writeFileSync(specKeep, "")
107
- result.created.push(`${SPEC_DIR}/.gitkeep`)
108
- }
109
-
110
- return result
111
- }
112
-
113
- function guessProjectName(dir: string): string {
114
- // Try package.json
115
- const pkgPath = join(dir, "package.json")
116
- if (existsSync(pkgPath)) {
117
- try {
118
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
119
- if (pkg.name) return pkg.name
120
- } catch {}
121
- }
122
- // Fallback to directory name
123
- return dir.split("/").pop() || "Project"
124
- }
package/src/core/tasks.ts DELETED
@@ -1,77 +0,0 @@
1
- /**
2
- * Tasks — read/update operations for TASKS.md
3
- */
4
-
5
- import { existsSync, readFileSync, writeFileSync } from "fs"
6
- import { join } from "path"
7
- import { TASKS_FILE } from "./types.ts"
8
-
9
- export type TaskStatus = "in_progress" | "up_next" | "completed"
10
-
11
- export interface TaskItem {
12
- content: string
13
- status: TaskStatus
14
- }
15
-
16
- /**
17
- * Read tasks grouped by status section.
18
- */
19
- export function readTasks(projectDir: string): Record<TaskStatus, string[]> {
20
- const filePath = join(projectDir, TASKS_FILE)
21
- const result: Record<TaskStatus, string[]> = {
22
- in_progress: [],
23
- up_next: [],
24
- completed: [],
25
- }
26
-
27
- if (!existsSync(filePath)) return result
28
-
29
- const content = readFileSync(filePath, "utf-8")
30
- let currentSection: TaskStatus | null = null
31
-
32
- for (const line of content.split("\n")) {
33
- if (line.startsWith("## In Progress")) {
34
- currentSection = "in_progress"
35
- continue
36
- }
37
- if (line.startsWith("## Up Next")) {
38
- currentSection = "up_next"
39
- continue
40
- }
41
- if (line.startsWith("## Completed")) {
42
- currentSection = "completed"
43
- continue
44
- }
45
- if (currentSection && line.startsWith("- ")) {
46
- result[currentSection].push(line.slice(2))
47
- }
48
- }
49
-
50
- return result
51
- }
52
-
53
- /**
54
- * Write tasks back to TASKS.md, preserving header structure.
55
- */
56
- export function writeTasks(
57
- projectDir: string,
58
- tasks: Record<TaskStatus, string[]>
59
- ): void {
60
- const filePath = join(projectDir, TASKS_FILE)
61
- const content = `# Current Tasks
62
-
63
- Working memory for cross-session continuity. Update before ending a session.
64
-
65
- ---
66
-
67
- ## In Progress
68
- ${tasks.in_progress.map((t) => `- ${t}`).join("\n") || ""}
69
-
70
- ## Up Next
71
- ${tasks.up_next.map((t) => `- ${t}`).join("\n") || ""}
72
-
73
- ## Completed
74
- ${tasks.completed.map((t) => `- ${t}`).join("\n") || ""}
75
- `
76
- writeFileSync(filePath, content)
77
- }
package/src/core/types.ts DELETED
@@ -1,30 +0,0 @@
1
- /**
2
- * Core constants and types shared across all entry points.
3
- */
4
-
5
- export const AGENTS_DIR = ".agents"
6
- export const SPEC_DIR = ".agents/spec"
7
- export const MEMORY_FILE = ".agents/MEMORY.md"
8
- export const TASKS_FILE = ".agents/TASKS.md"
9
- export const AGENTS_MD = "AGENTS.md"
10
- export const COPILOT_INSTRUCTIONS = ".github/copilot-instructions.md"
11
-
12
- export interface AgentMemoryConfig {
13
- /** Project name used in templates */
14
- projectName?: string
15
- /** Custom spec categories to scaffold */
16
- specFiles?: string[]
17
- /** Whether to create .github/copilot-instructions.md */
18
- copilotInstructions?: boolean
19
- }
20
-
21
- export interface DoctorResult {
22
- ok: boolean
23
- issues: DoctorIssue[]
24
- }
25
-
26
- export interface DoctorIssue {
27
- level: "error" | "warning" | "info"
28
- file: string
29
- message: string
30
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * OpenCode Plugin — Memory Lifecycle
3
- *
4
- * Hooks into session events to manage .agents/ structure:
5
- * - session.created → auto-scaffold if missing, log status
6
- * - session.idle → remind to update TASKS.md
7
- * - tool.execute.after → track file edits
8
- */
9
-
10
- import type { Plugin } from "@opencode-ai/plugin"
11
- import { existsSync, readFileSync } from "fs"
12
- import { resolve } from "path"
13
- import { scaffold } from "../core/scaffold.ts"
14
- import { MEMORY_FILE, TASKS_FILE, AGENTS_MD, SPEC_DIR } from "../core/types.ts"
15
-
16
- export const MemoryLifecyclePlugin: Plugin = async ({ client, directory }) => {
17
- const memoryFile = resolve(directory, MEMORY_FILE)
18
- const tasksFile = resolve(directory, TASKS_FILE)
19
- const agentsFile = resolve(directory, AGENTS_MD)
20
-
21
- let editCount = 0
22
- let specFilesEdited: string[] = []
23
-
24
- return {
25
- event: async ({ event }) => {
26
- if (event.type === "session.created") {
27
- // Auto-scaffold .agents/ if AGENTS.md exists but .agents/ doesn't
28
- const hasAgentsMd = existsSync(agentsFile)
29
- const hasMemory = existsSync(memoryFile)
30
-
31
- if (!hasMemory && hasAgentsMd) {
32
- // Project has AGENTS.md but no .agents/ — scaffold memory files only
33
- try {
34
- const result = scaffold(directory, { copilotInstructions: false })
35
- if (result.created.length > 0) {
36
- await client.tui.showToast({
37
- body: {
38
- message: `Agent memory initialized: ${result.created.join(", ")}`,
39
- variant: "success",
40
- },
41
- })
42
- }
43
- } catch (err) {
44
- await client.app.log({
45
- body: {
46
- service: "agent-memory",
47
- level: "warn",
48
- message: `Auto-scaffold failed: ${err}`,
49
- },
50
- })
51
- }
52
- }
53
-
54
- // Reset session counters
55
- editCount = 0
56
- specFilesEdited = []
57
-
58
- await client.app.log({
59
- body: {
60
- service: "agent-memory",
61
- level: "info",
62
- message: `Memory: ${hasMemory}, Tasks: ${existsSync(tasksFile)}`,
63
- },
64
- })
65
- }
66
-
67
- // Remind about TASKS.md on session idle if significant work was done
68
- if (event.type === "session.idle" && editCount >= 3) {
69
- if (existsSync(tasksFile)) {
70
- const content = readFileSync(tasksFile, "utf-8")
71
- const inProgressSection = content.split("## In Progress")[1]
72
- if (inProgressSection?.trim()) {
73
- await client.tui.showToast({
74
- body: {
75
- message: `${editCount} edits this session. Update .agents/TASKS.md before ending.`,
76
- variant: "info",
77
- },
78
- })
79
- }
80
- }
81
- }
82
- },
83
-
84
- "tool.execute.after": async (input) => {
85
- if (input.tool === "edit" || input.tool === "write") {
86
- editCount++
87
- const filePath = (input as any).args?.filePath || ""
88
- if (filePath.includes(SPEC_DIR)) {
89
- const shortPath = filePath.split(SPEC_DIR + "/").pop() || filePath
90
- if (!specFilesEdited.includes(shortPath)) {
91
- specFilesEdited.push(shortPath)
92
- }
93
- }
94
- }
95
- },
96
- }
97
- }