-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathproject_cursor.go
More file actions
131 lines (113 loc) · 3.5 KB
/
project_cursor.go
File metadata and controls
131 lines (113 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package main
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
)
// CursorProjector generates Cursor IDE platform files.
type CursorProjector struct{}
func (p *CursorProjector) Name() string { return "cursor" }
func (p *CursorProjector) OutputPaths(rules, skills, agents []string, hasMCP bool) []string {
paths := []string{"AGENTS.md", ".cursor/hooks.json"}
if hasMCP {
paths = append(paths, ".cursor/mcp.json")
}
for _, n := range rules {
paths = append(paths, ".cursor/rules/"+n+".mdc")
}
for _, n := range skills {
paths = append(paths, ".cursor/skills/"+n+"/", ".cursor/skills/"+n+"/SKILL.md")
}
for _, n := range agents {
paths = append(paths, ".cursor/agents/"+n+".md")
}
return paths
}
func (p *CursorProjector) Project(root string, ms *MergedSet) error {
cursorDir := filepath.Join(root, ".cursor")
// Rules → .cursor/rules/<name>.mdc
for _, name := range sortedKeys(ms.Rules) {
rule := ms.Rules[name]
if err := p.writeRule(root, name, rule); err != nil {
return err
}
}
// Skills → .cursor/skills/<name>/SKILL.md
if err := projectSkills(cursorDir, ms); err != nil {
return err
}
// Agents → .cursor/agents/<name>.md
if err := projectAgents(cursorDir, ms); err != nil {
return err
}
// .cursor/hooks.json
if err := p.writeHooks(root); err != nil {
return err
}
// AGENTS.md at root
if err := writeAGENTSMD(root, ms); err != nil {
return err
}
// .cursor/mcp.json — merge bundle MCP servers with existing user config
if len(ms.MCP) > 0 {
if err := writeMCPConfig(filepath.Join(root, ".cursor", "mcp.json"), ms.MCP); err != nil {
return err
}
}
return nil
}
func (p *CursorProjector) writeRule(root, name string, rule *AgenticFile) error {
var sb strings.Builder
sb.WriteString("---\n")
if rule.Description != "" {
sb.WriteString(fmt.Sprintf("description: %s\n", rule.Description))
}
if len(rule.Globs) > 0 {
sb.WriteString("globs:\n")
for _, p := range rule.Globs {
sb.WriteString(fmt.Sprintf(" - %s\n", p))
}
} else {
sb.WriteString("alwaysApply: true\n")
}
sb.WriteString("---\n\n")
sb.WriteString(rule.Body)
sb.WriteString("\n")
path := filepath.Join(root, ".cursor", "rules", name+".mdc")
return writeFile(path, []byte(sb.String()))
}
// cursorHooksConfig matches .cursor/hooks.json format.
type cursorHooksConfig struct {
Version int `json:"version"`
Hooks map[string][]cursorHook `json:"hooks"`
}
type cursorHook struct {
Command string `json:"command"`
Type string `json:"type"`
Matcher string `json:"matcher,omitempty"`
}
func (p *CursorProjector) writeHooks(root string) error {
loreHook := func(cmd string) []cursorHook {
return []cursorHook{{Command: cmd, Type: "command"}}
}
cfg := cursorHooksConfig{
Version: 1,
Hooks: map[string][]cursorHook{
"preToolUse": loreHook("lore hook pre-tool-use"),
"postToolUse": loreHook("lore hook post-tool-use"),
"beforeSubmitPrompt": loreHook("lore hook prompt-submit"),
"sessionStart": loreHook("lore hook session-start"),
"stop": loreHook("lore hook stop"),
"preCompact": loreHook("lore hook pre-compact"),
"sessionEnd": loreHook("lore hook session-end"),
"subagentStart": loreHook("lore hook subagent-start"),
"subagentStop": loreHook("lore hook subagent-stop"),
},
}
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return fmt.Errorf("marshal cursor hooks.json: %w", err)
}
return writeFile(filepath.Join(root, ".cursor", "hooks.json"), append(data, '\n'))
}