Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions internal/setup/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,18 @@ func agentAdapters() []agentAdapter {
},
{
slug: "cursor",
description: "Cursor β€” MCP registration in ~/.cursor/mcp.json plus an always-applied .mdc rule",
description: "Cursor β€” MCP registration in ~/.cursor/mcp.json plus an informational Memory Protocol file to paste as a User Rule",
mcpPath: cursorMCPPath,
mcpFormat: mcpServersObject,
instructions: []instrSurface{
{path: cursorRulesPath, style: wholeFile, body: cursorRulesBody()},
{path: cursorMemoryProtocolPath, style: wholeFile, body: memoryProtocolMarkdown},
},
postInstall: []string{
"Restart Cursor so MCP config is reloaded",
"Verify ~/.cursor/mcp.json includes mcpServers.engram",
"Verify ~/.cursor/rules/engram.mdc exists (always-applied rule)",
"NOTE: Cursor does NOT read global rule files from the filesystem β€” .mdc files outside a project are silently ignored",
"Open ~/.cursor/engram-memory-protocol.md and copy its contents",
"In Cursor, open Settings β†’ Rules β†’ User Rules and paste the copied contents",
},
},
{
Expand Down Expand Up @@ -161,13 +163,6 @@ func agentAdapters() []agentAdapter {
}
}

// cursorRulesBody wraps the Memory Protocol in the YAML frontmatter Cursor needs
// for an always-applied rule (alwaysApply:true ignores globs and attaches the rule
// regardless of context).
func cursorRulesBody() string {
return "---\ndescription: Engram persistent memory protocol\nalwaysApply: true\n---\n\n" + memoryProtocolMarkdown
}

// vscodeInstructionsBody wraps the Memory Protocol in the frontmatter VS Code
// Copilot uses for a user-level instructions file that applies to every file.
func vscodeInstructionsBody() string {
Expand Down Expand Up @@ -237,9 +232,14 @@ func cursorMCPPath() string {
return filepath.Join(home, ".cursor", "mcp.json")
}

func cursorRulesPath() string {
// cursorMemoryProtocolPath returns the path to the informational Memory Protocol
// file for Cursor. Cursor does not read global rule files from the filesystem;
// .mdc files with alwaysApply outside a project are silently ignored. This file
// is intended to be opened by the user and its contents pasted into
// Settings β†’ Rules β†’ User Rules inside Cursor.
func cursorMemoryProtocolPath() string {
home, _ := userHome()
return filepath.Join(home, ".cursor", "rules", "engram.mdc")
return filepath.Join(home, ".cursor", "engram-memory-protocol.md")
}

// ─── VS Code (Copilot) paths ─────────────────────────────────────────────────
Expand Down
34 changes: 28 additions & 6 deletions internal/setup/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func declarativeAgents() []declarativeAgent {
{"windsurf", windsurfMCPPath, "mcpServers", mcpServersObject, windsurfRulesPath, markerBlock},
{"qwen", qwenSettingsPath, "mcpServers", mcpServersObject, qwenContextPath, markerBlock},
{"kiro", kiroMCPPath, "mcpServers", mcpServersObject, kiroSteeringPath, markerBlock},
{"cursor", cursorMCPPath, "mcpServers", mcpServersObject, cursorRulesPath, wholeFile},
{"cursor", cursorMCPPath, "mcpServers", mcpServersObject, cursorMemoryProtocolPath, wholeFile},
{"vscode-copilot", vscodeMCPPath, "servers", serversObject, vscodePromptPath, wholeFile},
{"kilocode", kilocodeConfigPath, "mcp", opencodeObject, kilocodeAgentsPath, markerBlock},
}
Expand Down Expand Up @@ -180,16 +180,38 @@ func TestInstallDeclarativeAgentsRegisterMCPAndInstructions(t *testing.T) {
}
}

func TestCursorAndVSCodeInstructionsCarryFrontmatter(t *testing.T) {
stubRegistryEnv(t)
func TestCursorMemoryProtocolHasNoFrontmatter(t *testing.T) {
home := stubRegistryEnv(t)

if _, err := Install("cursor"); err != nil {
t.Fatalf("Install(cursor): %v", err)
}
cursorRaw, _ := os.ReadFile(cursorRulesPath())
if !strings.Contains(string(cursorRaw), "alwaysApply: true") {
t.Errorf("cursor .mdc missing alwaysApply frontmatter")

// The old .mdc path must NOT exist β€” we no longer write a global rule file.
oldMDCPath := filepath.Join(home, ".cursor", "rules", "engram.mdc")
if _, err := os.Stat(oldMDCPath); err == nil {
t.Errorf("cursor: ~/.cursor/rules/engram.mdc should not be written (Cursor ignores global rule files)")
}
Comment on lines +191 to +194

// The new informational file must exist, contain the protocol, and have no YAML frontmatter.
protocolRaw, err := os.ReadFile(cursorMemoryProtocolPath())
if err != nil {
t.Fatalf("cursor: memory protocol file not written at %s: %v", cursorMemoryProtocolPath(), err)
}
content := string(protocolRaw)
if !strings.Contains(content, "Engram Persistent Memory") {
t.Errorf("cursor: memory protocol file missing expected content")
}
if strings.Contains(content, "alwaysApply") {
t.Errorf("cursor: memory protocol file must not contain alwaysApply frontmatter")
}
if strings.HasPrefix(content, "---") {
t.Errorf("cursor: memory protocol file must not start with YAML frontmatter")
}
}

func TestVSCodeInstructionsCarryFrontmatter(t *testing.T) {
stubRegistryEnv(t)

if _, err := Install("vscode-copilot"); err != nil {
t.Fatalf("Install(vscode-copilot): %v", err)
Expand Down
Loading