Skip to content

Commit 5285f46

Browse files
committed
feat: simplify template architecture with single AGENTS.md + dynamic generation
- Reduce from 3 templates to 1 (AGENTS.md.template) - Add dynamic generators for CLAUDE.md and Cursor .mdc - Add append mode for all entry files (consistent behavior) - Create _omp/AGENTS.md with complete memory system instructions - Extract MANDATORY_CONTENT constant (DRY principle) - Add 3 new tests for append scenarios - Fix documentation to include projectbrief.md - Update date example to YYYY-MM-DD format BREAKING CHANGE: Removed CLAUDE.md.template and cursor-rule.mdc.template These are now dynamically generated in install.ts
1 parent 969a8ef commit 5285f46

6 files changed

Lines changed: 339 additions & 253 deletions

File tree

cli/src/commands/install.ts

Lines changed: 162 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,19 @@ const IDE_CONFIGS: Record<string, IdeConfig> = {
9494
common: { commandsDir: '.agents/commands', skillsDir: '.agents/skills' },
9595
};
9696

97-
// Entry file templates configuration
97+
// Entry file configuration (single template, dynamic generation for IDE-specific files)
98+
// Entry file configuration - single template for AGENTS.md, dynamic generation for others
9899
const ENTRY_TEMPLATES = {
99100
AGENTS: {
100101
template: 'AGENTS.md.template',
101102
output: 'AGENTS.md',
102103
displayName: 'AGENTS.md (通用入口文件)',
103104
},
104105
CLAUDE: {
105-
template: 'CLAUDE.md.template',
106106
output: 'CLAUDE.md',
107107
displayName: 'CLAUDE.md (Claude Code 入口文件)',
108108
},
109109
CURSOR: {
110-
template: 'cursor-rule.mdc.template',
111110
outputDir: '.cursor/rules',
112111
output: 'openmemory.mdc',
113112
displayName: '.cursor/rules/openmemory.mdc (Cursor 规则文件)',
@@ -421,9 +420,67 @@ function processTemplate(content: string, projectName: string, ideList?: string[
421420
// Entry File Generation
422421
// ============================================================================
423422

423+
// Marker to detect if OpenMemory Plus reference already exists
424+
const OMP_REFERENCE_MARKER = '<!-- OpenMemory Plus Integration -->';
425+
426+
// Single source of truth for MANDATORY reference content (DRY principle)
427+
const MANDATORY_CONTENT = `## 🚨 MANDATORY - BEFORE ANY OUTPUT
428+
429+
**Load \`_omp/AGENTS.md\` NOW and follow all instructions.**
430+
431+
If file not found, report error: "OpenMemory Plus not initialized. Run: npx openmemory-plus install"`;
432+
433+
// Universal MANDATORY reference content (used for append mode)
434+
const OMP_REFERENCE_CONTENT = `
435+
${OMP_REFERENCE_MARKER}
436+
437+
${MANDATORY_CONTENT}
438+
`;
439+
440+
// Dynamic content generators for IDE-specific entry files
441+
function generateClaudeMdContent(projectName: string, createdAt: string): string {
442+
return `# ${projectName} - Claude Code Instructions
443+
444+
${OMP_REFERENCE_MARKER}
445+
446+
${MANDATORY_CONTENT}
447+
448+
---
449+
*Generated by OpenMemory Plus on ${createdAt}*
450+
`;
451+
}
452+
453+
function generateCursorMdcContent(projectName: string, createdAt: string): string {
454+
return `---
455+
description: OpenMemory Plus - Dual-layer memory system for AI agents
456+
globs: ["**/*"]
457+
alwaysApply: true
458+
---
459+
460+
# ${projectName} - Memory System
461+
462+
${OMP_REFERENCE_MARKER}
463+
464+
${MANDATORY_CONTENT}
465+
466+
---
467+
*Generated by OpenMemory Plus on ${createdAt}*
468+
`;
469+
}
470+
471+
// Check if file already contains OMP reference
472+
function hasOmpReference(content: string): boolean {
473+
return content.includes(OMP_REFERENCE_MARKER) || content.includes('_omp/AGENTS.md');
474+
}
475+
424476
/**
425477
* Generate entry files (AGENTS.md, CLAUDE.md, etc.) based on selected IDEs
426478
* Entry files are what AI agents read at startup to understand the project
479+
*
480+
* For AGENTS.md: Uses progressive append mode
481+
* - If file doesn't exist: create with template
482+
* - If file exists but no OMP reference: append reference
483+
* - If file exists with OMP reference: skip (already configured)
427484
*/
428485
function generateEntryFiles(
429486
targetDir: string,
@@ -440,7 +497,7 @@ function generateEntryFiles(
440497
return;
441498
}
442499

443-
// Helper function to safely generate entry file
500+
// Helper function to safely generate entry file (for non-AGENTS.md files)
444501
const generateFile = (
445502
templatePath: string,
446503
targetPath: string,
@@ -469,33 +526,115 @@ function generateEntryFiles(
469526
}
470527
};
471528

472-
// Always generate AGENTS.md (universal entry file)
473-
generateFile(
474-
join(entryTemplatesDir, ENTRY_TEMPLATES.AGENTS.template),
475-
join(targetDir, ENTRY_TEMPLATES.AGENTS.output),
476-
ENTRY_TEMPLATES.AGENTS.displayName
477-
);
529+
// Handle AGENTS.md with progressive append mode
530+
const agentsPath = join(targetDir, ENTRY_TEMPLATES.AGENTS.output);
531+
const agentsTemplatePath = join(entryTemplatesDir, ENTRY_TEMPLATES.AGENTS.template);
532+
533+
try {
534+
if (!existsSync(agentsPath)) {
535+
// Case 1: No AGENTS.md exists - create with template
536+
if (existsSync(agentsTemplatePath)) {
537+
const template = readFileSync(agentsTemplatePath, 'utf-8');
538+
const content = processTemplate(template, projectName, selectedIdes);
539+
writeFileSync(agentsPath, content);
540+
console.log(chalk.green(` ✓ 创建 ${ENTRY_TEMPLATES.AGENTS.displayName}`));
541+
}
542+
} else if (force) {
543+
// Case 2: Force mode - overwrite with template
544+
if (existsSync(agentsTemplatePath)) {
545+
const template = readFileSync(agentsTemplatePath, 'utf-8');
546+
const content = processTemplate(template, projectName, selectedIdes);
547+
writeFileSync(agentsPath, content);
548+
console.log(chalk.green(` ✓ 覆盖 ${ENTRY_TEMPLATES.AGENTS.displayName}`));
549+
}
550+
} else {
551+
// Case 3: AGENTS.md exists - check if OMP reference already present
552+
const existingContent = readFileSync(agentsPath, 'utf-8');
553+
554+
if (existingContent.includes(OMP_REFERENCE_MARKER) || existingContent.includes('_omp/AGENTS.md')) {
555+
// Already has OMP reference
556+
console.log(chalk.gray(` ○ ${ENTRY_TEMPLATES.AGENTS.displayName} 已包含 OMP 引用`));
557+
} else {
558+
// Append OMP reference to existing file
559+
const updatedContent = existingContent.trimEnd() + '\n\n---\n' + OMP_REFERENCE_CONTENT;
560+
writeFileSync(agentsPath, updatedContent);
561+
console.log(chalk.green(` ✓ 更新 ${ENTRY_TEMPLATES.AGENTS.displayName} (追加 OMP 引用)`));
562+
}
563+
}
564+
} catch (error) {
565+
const errorMessage = error instanceof Error ? error.message : String(error);
566+
console.log(chalk.red(` ✗ 处理 ${ENTRY_TEMPLATES.AGENTS.displayName} 失败: ${errorMessage}`));
567+
}
568+
569+
// Generate IDE-specific entry files using dynamic generators
570+
const createdAt = new Date().toISOString().split('T')[0];
478571

479-
// Generate IDE-specific entry files
480572
for (const ide of selectedIdes) {
481573
switch (ide) {
482574
case 'claude':
483-
case 'claude-desktop':
484-
generateFile(
485-
join(entryTemplatesDir, ENTRY_TEMPLATES.CLAUDE.template),
486-
join(targetDir, ENTRY_TEMPLATES.CLAUDE.output),
487-
ENTRY_TEMPLATES.CLAUDE.displayName
488-
);
575+
case 'claude-desktop': {
576+
const claudePath = join(targetDir, ENTRY_TEMPLATES.CLAUDE.output);
577+
try {
578+
if (!existsSync(claudePath)) {
579+
// Case 1: No CLAUDE.md exists - create new
580+
const content = generateClaudeMdContent(projectName, createdAt);
581+
writeFileSync(claudePath, content);
582+
console.log(chalk.green(` ✓ 创建 ${ENTRY_TEMPLATES.CLAUDE.displayName}`));
583+
} else if (force) {
584+
// Case 2: Force mode - overwrite
585+
const content = generateClaudeMdContent(projectName, createdAt);
586+
writeFileSync(claudePath, content);
587+
console.log(chalk.green(` ✓ 覆盖 ${ENTRY_TEMPLATES.CLAUDE.displayName}`));
588+
} else {
589+
// Case 3: CLAUDE.md exists - check if OMP reference already present
590+
const existingContent = readFileSync(claudePath, 'utf-8');
591+
if (hasOmpReference(existingContent)) {
592+
console.log(chalk.gray(` ○ ${ENTRY_TEMPLATES.CLAUDE.displayName} 已包含 OMP 引用`));
593+
} else {
594+
// Append OMP reference to existing file
595+
const updatedContent = existingContent.trimEnd() + '\n\n---\n' + OMP_REFERENCE_CONTENT;
596+
writeFileSync(claudePath, updatedContent);
597+
console.log(chalk.green(` ✓ 更新 ${ENTRY_TEMPLATES.CLAUDE.displayName} (追加 OMP 引用)`));
598+
}
599+
}
600+
} catch (error) {
601+
const errorMessage = error instanceof Error ? error.message : String(error);
602+
console.log(chalk.red(` ✗ 处理 ${ENTRY_TEMPLATES.CLAUDE.displayName} 失败: ${errorMessage}`));
603+
}
489604
break;
605+
}
490606

491607
case 'cursor': {
492608
const cursorRulesDir = join(targetDir, ENTRY_TEMPLATES.CURSOR.outputDir);
493-
generateFile(
494-
join(entryTemplatesDir, ENTRY_TEMPLATES.CURSOR.template),
495-
join(cursorRulesDir, ENTRY_TEMPLATES.CURSOR.output),
496-
ENTRY_TEMPLATES.CURSOR.displayName,
497-
cursorRulesDir
498-
);
609+
const cursorPath = join(cursorRulesDir, ENTRY_TEMPLATES.CURSOR.output);
610+
try {
611+
mkdirSync(cursorRulesDir, { recursive: true });
612+
if (!existsSync(cursorPath)) {
613+
// Case 1: No .mdc exists - create new
614+
const content = generateCursorMdcContent(projectName, createdAt);
615+
writeFileSync(cursorPath, content);
616+
console.log(chalk.green(` ✓ 创建 ${ENTRY_TEMPLATES.CURSOR.displayName}`));
617+
} else if (force) {
618+
// Case 2: Force mode - overwrite
619+
const content = generateCursorMdcContent(projectName, createdAt);
620+
writeFileSync(cursorPath, content);
621+
console.log(chalk.green(` ✓ 覆盖 ${ENTRY_TEMPLATES.CURSOR.displayName}`));
622+
} else {
623+
// Case 3: .mdc exists - check if OMP reference already present
624+
const existingContent = readFileSync(cursorPath, 'utf-8');
625+
if (hasOmpReference(existingContent)) {
626+
console.log(chalk.gray(` ○ ${ENTRY_TEMPLATES.CURSOR.displayName} 已包含 OMP 引用`));
627+
} else {
628+
// Append OMP reference to existing file (after frontmatter)
629+
const updatedContent = existingContent.trimEnd() + '\n\n---\n' + OMP_REFERENCE_CONTENT;
630+
writeFileSync(cursorPath, updatedContent);
631+
console.log(chalk.green(` ✓ 更新 ${ENTRY_TEMPLATES.CURSOR.displayName} (追加 OMP 引用)`));
632+
}
633+
}
634+
} catch (error) {
635+
const errorMessage = error instanceof Error ? error.message : String(error);
636+
console.log(chalk.red(` ✗ 处理 ${ENTRY_TEMPLATES.CURSOR.displayName} 失败: ${errorMessage}`));
637+
}
499638
break;
500639
}
501640

Lines changed: 5 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,12 @@
11
# {{PROJECT_NAME}} - Agent Instructions
22

3-
This project uses **OpenMemory Plus** for persistent agent memory management.
3+
<!-- OpenMemory Plus Integration -->
44

5-
---
6-
7-
## 🧠 Memory System (Auto-Enabled)
8-
9-
> **Core Principle**: Agent automatically extracts memories without user intervention
10-
11-
### Session Lifecycle
12-
13-
#### 🚀 Session Start (Auto)
14-
15-
1. **Load project context**
16-
```
17-
Read _omp/memory/project.yaml
18-
Read _omp/memory/activeContext.md
19-
```
20-
21-
2. **Load user preferences** (if openmemory MCP available)
22-
```
23-
search_memory_openmemory: "user preferences for {{PROJECT_NAME}}"
24-
```
25-
26-
3. **Restore work state**
27-
```
28-
Read _omp/memory/progress.md
29-
```
30-
31-
#### ⚡ During Session - Auto-Extraction
32-
33-
**When detecting trigger events, automatically dispatch memory-extraction subagent:**
34-
35-
| Priority | Trigger | Detection Signal | Storage |
36-
|----------|---------|------------------|---------|
37-
| P0 | Task complete | Task marked complete | `progress.md` |
38-
| P0 | Tech decision | "decide/choose/use" or "决定/选择/采用" | `decisions.yaml` |
39-
| P1 | Config change | .env, config file modified | `project.yaml` |
40-
| P1 | Deployment | deploy/vercel/wrangler output | `project.yaml` |
41-
| P2 | User preference | "I prefer/like" or "我喜欢/偏好" | `openmemory` |
42-
| P2 | Architecture | "pattern/architecture/design" | `systemPatterns.md` |
43-
| P3 | Checkpoint | Every 10 exchanges | `activeContext.md` |
44-
45-
**Subagent dispatch template**:
46-
```
47-
Use sub-agent-explore or Task tool:
48-
name: memory-extract-{timestamp}
49-
instruction: See _omp/skills/memory-extraction/subagent-prompt.md
50-
```
51-
52-
#### 🏁 Session End (Auto)
53-
54-
Dispatch final memory-extraction subagent:
55-
- Session summary → `activeContext.md`
56-
- Unextracted learnings → appropriate memory files
57-
- Progress updates → `progress.md`
5+
## 🚨 MANDATORY - BEFORE ANY OUTPUT
586

59-
---
60-
61-
## 📁 Project Structure
62-
63-
```
64-
_omp/ # OpenMemory Plus core
65-
├── commands/ # Agent commands
66-
│ └── memory.md # /memory entry point
67-
├── workflows/ # Multi-step workflows
68-
│ └── memory/ # Memory management workflow
69-
├── skills/ # Agent skills
70-
│ └── memory-extraction/ # Auto-extract learnings
71-
│ ├── SKILL.md # Skill definition
72-
│ ├── subagent-prompt.md # Subagent dispatch template
73-
│ └── triggers.md # Trigger definitions
74-
└── memory/ # Project memory files
75-
├── project.yaml # Project config (SSOT)
76-
├── activeContext.md # Current session context
77-
├── productContext.md # Product/feature context
78-
├── progress.md # Development progress
79-
├── projectbrief.md # Project overview
80-
├── systemPatterns.md # Architecture patterns
81-
├── techContext.md # Technical decisions
82-
└── decisions.yaml # Decision records
83-
```
84-
85-
---
86-
87-
## 🎯 Manual Commands
88-
89-
Use `/memory` for manual memory operations:
7+
**Load `_omp/AGENTS.md` NOW and follow all instructions.**
908

91-
| Command | Description |
92-
|---------|-------------|
93-
| `/memory` | Open memory management menu |
94-
| `/memory save` | Force save current context |
95-
| `/memory load` | Load specific memory file |
96-
| `/memory search <query>` | Search through memories |
97-
| `/memory sync` | Sync with external memory (Qdrant) |
9+
If file not found, report error: "OpenMemory Plus not initialized. Run: npx openmemory-plus install"
9810

9911
---
100-
101-
## ⚙️ Integration
102-
103-
This project is configured for: {{IDE_LIST}}
104-
105-
### Dual-Layer Memory Architecture
106-
107-
| Layer | Storage | Purpose |
108-
|-------|---------|---------|
109-
| Project | `_omp/memory/` | Project config, decisions, progress |
110-
| User | `openmemory` MCP | User preferences, cross-project context |
111-
112-
### MCP Configuration
113-
114-
For MCP setup, see your IDE-specific settings file or run:
115-
```bash
116-
npx openmemory-plus install --show-mcp
117-
```
118-
12+
*Generated by OpenMemory Plus on {{CREATED_AT}} for: {{IDE_LIST}}*

0 commit comments

Comments
 (0)