@chrisdudek/yg 3.0.0 → 4.0.1

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,22 +0,0 @@
1
- export const DEFAULT_CONFIG = `version: "3.0.0"
2
-
3
- name: ""
4
-
5
- node_types:
6
- module:
7
- description: "Business logic unit with clear domain responsibility"
8
- service:
9
- description: "Component providing functionality to other nodes"
10
- library:
11
- description: "Shared utility code with no domain knowledge"
12
- infrastructure:
13
- description: "Guards, middleware, interceptors — invisible in call graphs but affect blast radius"
14
-
15
- quality:
16
- min_artifact_length: 50
17
- max_direct_relations: 10
18
- context_budget:
19
- warning: 10000
20
- error: 20000
21
- own_warning: 5000
22
- `;
@@ -1,257 +0,0 @@
1
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { AGENT_RULES_CONTENT } from './rules.js';
4
-
5
- const AGENT_RULES_IMPORT = '@.yggdrasil/agent-rules.md';
6
- const YGGDRASIL_START = '<!-- yggdrasil:start -->';
7
- const YGGDRASIL_END = '<!-- yggdrasil:end -->';
8
- const YGGDRASIL_SECTION = `## Yggdrasil\n\n${AGENT_RULES_CONTENT}`;
9
- const YGGDRASIL_BLOCK = `${YGGDRASIL_START}\n${YGGDRASIL_SECTION}\n${YGGDRASIL_END}`;
10
-
11
- export type Platform =
12
- | 'cursor'
13
- | 'claude-code'
14
- | 'copilot'
15
- | 'cline'
16
- | 'roocode'
17
- | 'codex'
18
- | 'windsurf'
19
- | 'aider'
20
- | 'gemini'
21
- | 'amp'
22
- | 'generic';
23
-
24
- export const PLATFORMS: Platform[] = [
25
- 'cursor',
26
- 'claude-code',
27
- 'copilot',
28
- 'cline',
29
- 'roocode',
30
- 'codex',
31
- 'windsurf',
32
- 'aider',
33
- 'gemini',
34
- 'amp',
35
- 'generic',
36
- ];
37
-
38
- export async function installRulesForPlatform(
39
- projectRoot: string,
40
- platform: Platform,
41
- ): Promise<string> {
42
- const agentRulesPath = path.join(projectRoot, '.yggdrasil', 'agent-rules.md');
43
-
44
- switch (platform) {
45
- case 'cursor':
46
- return installForCursor(projectRoot);
47
- case 'claude-code':
48
- return installForClaudeCode(projectRoot, agentRulesPath);
49
- case 'copilot':
50
- return installForCopilot(projectRoot);
51
- case 'cline':
52
- return installForCline(projectRoot);
53
- case 'roocode':
54
- return installForRooCode(projectRoot);
55
- case 'codex':
56
- return installForCodex(projectRoot);
57
- case 'windsurf':
58
- return installForWindsurf(projectRoot);
59
- case 'aider':
60
- return installForAider(projectRoot, agentRulesPath);
61
- case 'gemini':
62
- return installForGemini(projectRoot, agentRulesPath);
63
- case 'amp':
64
- return installForAmp(projectRoot, agentRulesPath);
65
- case 'generic':
66
- default:
67
- return installForGeneric(projectRoot);
68
- }
69
- }
70
-
71
- async function ensureAgentRules(agentRulesPath: string): Promise<void> {
72
- await mkdir(path.dirname(agentRulesPath), { recursive: true });
73
- await writeFile(agentRulesPath, AGENT_RULES_CONTENT, 'utf-8');
74
- }
75
-
76
- async function installForCursor(projectRoot: string): Promise<string> {
77
- const dir = path.join(projectRoot, '.cursor', 'rules');
78
- await mkdir(dir, { recursive: true });
79
- const filePath = path.join(dir, 'yggdrasil.mdc');
80
- const content = `---
81
- description: Yggdrasil — semantic memory of the repository
82
- alwaysApply: true
83
- ---
84
-
85
- ${AGENT_RULES_CONTENT}`;
86
- await writeFile(filePath, content, 'utf-8');
87
- return filePath;
88
- }
89
-
90
- async function installForClaudeCode(projectRoot: string, agentRulesPath: string): Promise<string> {
91
- await ensureAgentRules(agentRulesPath);
92
- const filePath = path.join(projectRoot, 'CLAUDE.md');
93
- let existing = '';
94
- try {
95
- existing = await readFile(filePath, 'utf-8');
96
- } catch {
97
- /* file doesn't exist */
98
- }
99
- const importLine = AGENT_RULES_IMPORT;
100
- if (existing.includes(importLine)) {
101
- return agentRulesPath;
102
- }
103
- const content = existing.trimEnd() ? `${existing.trimEnd()}\n${importLine}\n` : `${importLine}\n`;
104
- await writeFile(filePath, content, 'utf-8');
105
- return agentRulesPath;
106
- }
107
-
108
- async function installForCopilot(projectRoot: string): Promise<string> {
109
- const dir = path.join(projectRoot, '.github');
110
- await mkdir(dir, { recursive: true });
111
- const filePath = path.join(dir, 'copilot-instructions.md');
112
- let existing = '';
113
- try {
114
- existing = await readFile(filePath, 'utf-8');
115
- } catch {
116
- /* file doesn't exist */
117
- }
118
- let content: string;
119
- if (existing.includes(YGGDRASIL_START) && existing.includes(YGGDRASIL_END)) {
120
- content = existing.replace(
121
- new RegExp(`${escapeRegex(YGGDRASIL_START)}[\\s\\S]*?${escapeRegex(YGGDRASIL_END)}`, 'g'),
122
- YGGDRASIL_BLOCK,
123
- );
124
- } else {
125
- content = existing.trimEnd()
126
- ? `${existing.trimEnd()}\n\n${YGGDRASIL_BLOCK}\n`
127
- : `${YGGDRASIL_BLOCK}\n`;
128
- }
129
- await writeFile(filePath, content, 'utf-8');
130
- return filePath;
131
- }
132
-
133
- async function installForCline(projectRoot: string): Promise<string> {
134
- const dir = path.join(projectRoot, '.clinerules');
135
- await mkdir(dir, { recursive: true });
136
- const filePath = path.join(dir, 'yggdrasil.md');
137
- await writeFile(filePath, AGENT_RULES_CONTENT, 'utf-8');
138
- return filePath;
139
- }
140
-
141
- async function installForRooCode(projectRoot: string): Promise<string> {
142
- const dir = path.join(projectRoot, '.roo', 'rules');
143
- await mkdir(dir, { recursive: true });
144
- const filePath = path.join(dir, 'yggdrasil.md');
145
- await writeFile(filePath, AGENT_RULES_CONTENT, 'utf-8');
146
- return filePath;
147
- }
148
-
149
- async function installForCodex(projectRoot: string): Promise<string> {
150
- const filePath = path.join(projectRoot, 'AGENTS.md');
151
- let existing = '';
152
- try {
153
- existing = await readFile(filePath, 'utf-8');
154
- } catch {
155
- /* file doesn't exist */
156
- }
157
- let content: string;
158
- if (existing.includes(YGGDRASIL_START) && existing.includes(YGGDRASIL_END)) {
159
- content = existing.replace(
160
- new RegExp(`${escapeRegex(YGGDRASIL_START)}[\\s\\S]*?${escapeRegex(YGGDRASIL_END)}`, 'g'),
161
- YGGDRASIL_BLOCK,
162
- );
163
- } else {
164
- content = existing.trimEnd()
165
- ? `${existing.trimEnd()}\n\n${YGGDRASIL_BLOCK}\n`
166
- : `${YGGDRASIL_BLOCK}\n`;
167
- }
168
- await writeFile(filePath, content, 'utf-8');
169
- return filePath;
170
- }
171
-
172
- async function installForWindsurf(projectRoot: string): Promise<string> {
173
- const dir = path.join(projectRoot, '.windsurf', 'rules');
174
- await mkdir(dir, { recursive: true });
175
- const filePath = path.join(dir, 'yggdrasil.md');
176
- await writeFile(filePath, AGENT_RULES_CONTENT, 'utf-8');
177
- return filePath;
178
- }
179
-
180
- async function installForAider(projectRoot: string, agentRulesPath: string): Promise<string> {
181
- await ensureAgentRules(agentRulesPath);
182
- const filePath = path.join(projectRoot, '.aider.conf.yml');
183
- const entry = '.yggdrasil/agent-rules.md';
184
- let existing = '';
185
- try {
186
- existing = await readFile(filePath, 'utf-8');
187
- } catch {
188
- /* file doesn't exist */
189
- }
190
- if (existing.includes(entry)) {
191
- return agentRulesPath;
192
- }
193
- const content = appendAiderReadEntry(existing, entry);
194
- await writeFile(filePath, content, 'utf-8');
195
- return agentRulesPath;
196
- }
197
-
198
- function appendAiderReadEntry(existing: string, entry: string): string {
199
- const newItem = ` - ${entry} # added by yg init\n`;
200
- const readBlock = /^read:\s*\n((?:\s+-\s+[^\n]+\n)*)/m;
201
- const match = existing.match(readBlock);
202
- if (match) {
203
- return existing.replace(match[0], `read:\n${match[1]}${newItem}`);
204
- }
205
- const readEmpty = /^read:\s*$/m;
206
- if (readEmpty.test(existing)) {
207
- return existing.replace(readEmpty, `read:\n${newItem}`);
208
- }
209
- const trimmed = existing.trimEnd();
210
- return trimmed ? `${trimmed}\n\nread:\n${newItem}` : `read:\n${newItem}`;
211
- }
212
-
213
- async function installForGemini(projectRoot: string, agentRulesPath: string): Promise<string> {
214
- await ensureAgentRules(agentRulesPath);
215
- const filePath = path.join(projectRoot, 'GEMINI.md');
216
- let existing = '';
217
- try {
218
- existing = await readFile(filePath, 'utf-8');
219
- } catch {
220
- /* file doesn't exist */
221
- }
222
- const importLine = AGENT_RULES_IMPORT;
223
- if (existing.includes(importLine)) {
224
- return agentRulesPath;
225
- }
226
- const content = existing.trimEnd() ? `${existing.trimEnd()}\n${importLine}\n` : `${importLine}\n`;
227
- await writeFile(filePath, content, 'utf-8');
228
- return agentRulesPath;
229
- }
230
-
231
- async function installForAmp(projectRoot: string, agentRulesPath: string): Promise<string> {
232
- await ensureAgentRules(agentRulesPath);
233
- const filePath = path.join(projectRoot, 'AGENTS.md');
234
- let existing = '';
235
- try {
236
- existing = await readFile(filePath, 'utf-8');
237
- } catch {
238
- /* file doesn't exist */
239
- }
240
- const importLine = AGENT_RULES_IMPORT;
241
- if (existing.includes(importLine)) {
242
- return agentRulesPath;
243
- }
244
- const content = existing.trimEnd() ? `${existing.trimEnd()}\n${importLine}\n` : `${importLine}\n`;
245
- await writeFile(filePath, content, 'utf-8');
246
- return agentRulesPath;
247
- }
248
-
249
- async function installForGeneric(projectRoot: string): Promise<string> {
250
- const filePath = path.join(projectRoot, '.yggdrasil', 'agent-rules.md');
251
- await ensureAgentRules(filePath);
252
- return filePath;
253
- }
254
-
255
- function escapeRegex(s: string): string {
256
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
257
- }