@chrisdudek/yg 0.1.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.
- package/README.md +81 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +3294 -0
- package/dist/bin.js.map +1 -0
- package/dist/templates/default-config.ts +55 -0
- package/dist/templates/platform.ts +257 -0
- package/dist/templates/rules.ts +142 -0
- package/graph-templates/aspect.yaml +2 -0
- package/graph-templates/flow.yaml +8 -0
- package/graph-templates/knowledge-scope-nodes.yaml +5 -0
- package/graph-templates/knowledge-scope-tags.yaml +3 -0
- package/graph-templates/knowledge.yaml +2 -0
- package/graph-templates/library.yaml +22 -0
- package/graph-templates/module.yaml +18 -0
- package/graph-templates/node.yaml +17 -0
- package/graph-templates/service.yaml +30 -0
- package/package.json +69 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const DEFAULT_CONFIG = `name: ""
|
|
2
|
+
|
|
3
|
+
stack:
|
|
4
|
+
language: ""
|
|
5
|
+
runtime: ""
|
|
6
|
+
|
|
7
|
+
standards: ""
|
|
8
|
+
|
|
9
|
+
tags: []
|
|
10
|
+
|
|
11
|
+
node_types:
|
|
12
|
+
- module
|
|
13
|
+
- service
|
|
14
|
+
- library
|
|
15
|
+
|
|
16
|
+
artifacts:
|
|
17
|
+
responsibility.md:
|
|
18
|
+
required: always
|
|
19
|
+
description: "What this node is responsible for, and what it is not"
|
|
20
|
+
interface.md:
|
|
21
|
+
required:
|
|
22
|
+
when: has_incoming_relations
|
|
23
|
+
description: "Public API — methods, parameters, return types, contracts"
|
|
24
|
+
structural_context: true
|
|
25
|
+
constraints.md:
|
|
26
|
+
required: never
|
|
27
|
+
description: "Validation rules, business rules, invariants"
|
|
28
|
+
errors.md:
|
|
29
|
+
required:
|
|
30
|
+
when: has_incoming_relations
|
|
31
|
+
description: "Error conditions, codes, recovery behavior"
|
|
32
|
+
structural_context: true
|
|
33
|
+
state.md:
|
|
34
|
+
required: never
|
|
35
|
+
description: "State machines, lifecycle, transitions"
|
|
36
|
+
decisions.md:
|
|
37
|
+
required: never
|
|
38
|
+
description: "Local design decisions and rationale"
|
|
39
|
+
|
|
40
|
+
knowledge_categories:
|
|
41
|
+
- name: decisions
|
|
42
|
+
description: "Global semantic decisions and their rationale"
|
|
43
|
+
- name: patterns
|
|
44
|
+
description: "Implementation conventions with examples"
|
|
45
|
+
- name: invariants
|
|
46
|
+
description: "System truths that must never be violated"
|
|
47
|
+
|
|
48
|
+
quality:
|
|
49
|
+
min_artifact_length: 50
|
|
50
|
+
max_direct_relations: 10
|
|
51
|
+
context_budget:
|
|
52
|
+
warning: 5000
|
|
53
|
+
error: 10000
|
|
54
|
+
knowledge_staleness_days: 90
|
|
55
|
+
`;
|
|
@@ -0,0 +1,257 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical agent rules content — hand-tuned, do not generate programmatically.
|
|
3
|
+
*
|
|
4
|
+
* Operating manual for agents: graph-first, exhaustive coverage, context is sufficient.
|
|
5
|
+
*/
|
|
6
|
+
export const AGENT_RULES_CONTENT = `# Yggdrasil - System Semantic Memory (Operating Manual)
|
|
7
|
+
|
|
8
|
+
You are working in a repository managed by Yggdrasil.
|
|
9
|
+
Yggdrasil is a persistent, structured semantic memory graph stored in \`.yggdrasil/\`. It maps the repository, dictates system rules, and assembles implementation contexts.
|
|
10
|
+
|
|
11
|
+
THIS PROMPT IS YOUR ENTIRE OPERATING MANUAL. Read it carefully. Follow it strictly.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. CORE PRINCIPLES (NON-NEGOTIABLE)
|
|
16
|
+
|
|
17
|
+
1. **Graph First, Always:** Before answering a question, modifying code, or planning a feature, you MUST consult the graph.
|
|
18
|
+
2. **Context is Sufficient:** If you feel the need to randomly explore source files to understand what a node should do, the graph is incomplete. **Fix the graph** (add decisions, interface details, constraints). Do not bypass the graph by reading raw code.
|
|
19
|
+
3. **Graph is Intended Truth:** If the code and graph diverge, the graph is the truth. If a code change is deliberate, update the graph to match.
|
|
20
|
+
4. **Exhaustive Coverage:** Every source file MUST belong to exactly one graph node. No orphaned files.
|
|
21
|
+
5. **Tools Read, You Write:** The \`yg\` CLI tools only read, validate, and manage metadata. YOU must create and edit graph directories, \`.yaml\` files, and \`.md\` artifacts manually.
|
|
22
|
+
6. **English Only for Artifacts:** All graph artifact files (filenames from \`config.artifacts\`, in the same directory as \`node.yaml\`) MUST be written in English. Conversation can be in the user's language.
|
|
23
|
+
7. **Never Touch Operational Metadata:** NEVER manually edit \`.yggdrasil/.drift-state\` or \`.yggdrasil/.journal.yaml\`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. CONVERSATION LIFECYCLE (YOUR HABITS)
|
|
28
|
+
|
|
29
|
+
You do not need explicit "session" commands. Follow these conversational triggers:
|
|
30
|
+
|
|
31
|
+
### A. Preflight (First message of the conversation)
|
|
32
|
+
Always execute these commands before doing anything else:
|
|
33
|
+
1. \`yg journal-read\` -> If entries exist, consolidate them into the graph, then \`yg journal-archive\`.
|
|
34
|
+
2. \`yg drift\` -> If divergence is detected, present states (\`ok\`, \`drift\`, \`missing\`, \`unmaterialized\`). Ask the user: Absorb (update graph) or Reject (re-materialize code from graph)?
|
|
35
|
+
3. \`yg status\` -> Report graph health.
|
|
36
|
+
4. \`yg validate\` -> If W008 stale-knowledge appears, update the knowledge artifacts to reflect current node state.
|
|
37
|
+
|
|
38
|
+
### B. Wrap-up (User signals closing the topic)
|
|
39
|
+
Triggered by phrases like: "kończymy", "wrap up", "to tyle", "gotowe".
|
|
40
|
+
**Note: The graph should ALREADY be up to date. Do not wait for wrap-up to update graph artifacts.**
|
|
41
|
+
1. If iterative journal mode was used: consolidate notes to the graph, then \`yg journal-archive\`.
|
|
42
|
+
2. \`yg drift\` -> Check if files changed manually during the conversation.
|
|
43
|
+
3. \`yg validate\` -> Fix any structural errors.
|
|
44
|
+
4. Report exactly what nodes and files were changed.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. WORKFLOW: MODIFYING OR CREATING FILES
|
|
49
|
+
|
|
50
|
+
You are NOT ALLOWED to edit or create source code without establishing graph coverage first.
|
|
51
|
+
|
|
52
|
+
**Step 1: Check coverage** -> Run \`yg owner --file <path>\`
|
|
53
|
+
|
|
54
|
+
**Step 2: If Owner FOUND (The Execution Checklist)**
|
|
55
|
+
Whenever you write or edit source code, you MUST output this exact checklist in your response to the user, and execute each step BEFORE finishing your turn. This forces you to remember the graph:
|
|
56
|
+
|
|
57
|
+
- [x] 1. Read Specification (ran \`yg build-context\`)
|
|
58
|
+
- [x] 2. Modify Source Code
|
|
59
|
+
- [x] 3. Sync Graph Artifacts (manually edit the node's artifact files — filenames from \`config.artifacts\` — IMMEDIATELY to match new code behavior)
|
|
60
|
+
- [x] 4. Baseline Hash (ran \`yg drift-sync\` ONLY AFTER updating the graph)
|
|
61
|
+
|
|
62
|
+
*If you do not print this checklist and check off step 3, you have failed the core directive of Yggdrasil.*
|
|
63
|
+
|
|
64
|
+
**Step 3: If Owner NOT FOUND (Uncovered Area)**
|
|
65
|
+
STOP. Do not modify the code. First determine: **Is this greenfield (empty or new code to be created)?**
|
|
66
|
+
|
|
67
|
+
* **If GREENFIELD (empty directory, new project, code not yet written):** Do NOT offer blackbox. Use Option 1 only — create proper nodes (reverse engineering or upfront design) before implementing. Blackbox is forbidden for new code.
|
|
68
|
+
* **If EXISTING CODE (legacy, third-party, shipped-but-unmapped):** Present the user with 3 options and wait for their decision:
|
|
69
|
+
* **Option 1: Reverse Engineering:** Create/extend standard nodes to map the area fully before modifying.
|
|
70
|
+
* **Option 2: Blackbox Coverage:** Create a \`blackbox: true\` node at a user-chosen granularity to establish ownership without deep semantic exploration.
|
|
71
|
+
* **Option 3: Abort/Change Plan:** Do not touch the file.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. WORKFLOW: MODIFYING THE GRAPH & BLAST RADIUS
|
|
77
|
+
|
|
78
|
+
When adding features or changing architecture, update the graph FIRST.
|
|
79
|
+
|
|
80
|
+
**DO NOT DEFER GRAPH UPDATES:**
|
|
81
|
+
* **DO NOT wait for the user to confirm if a change is "final".** The graph must evolve continuously with your code edits.
|
|
82
|
+
* **Default Behavior:** If iterative journal mode is OFF, you MUST write structural and semantic changes directly to the graph files (\`node.yaml\`, artifacts or other files like aspects or flows, etc.) IMMEDIATELY. Suppress your innate safety bias to wait for permission.
|
|
83
|
+
|
|
84
|
+
1. **Check Blast Radius:** Before modifying a node that others depend on, run \`yg impact --node <node_path> --simulate\`. Report the impact to the user.
|
|
85
|
+
2. **Read Config & Templates:**
|
|
86
|
+
* Check \`.yggdrasil/config.yaml\` for allowed \`node_types\` and \`tags\`.
|
|
87
|
+
* **CRITICAL:** ALWAYS read the required schema files in \`.yggdrasil/templates/\` (e.g., \`node.yaml\`, \`service.yaml\`) to know the exact fields and structure before creating or editing any graph file.
|
|
88
|
+
3. **Validate & Fix:** Run \`yg validate\`. You must fix all E-codes (Errors).
|
|
89
|
+
4. **Token Economy & W-codes (Warnings):**
|
|
90
|
+
* If you see \`W005 budget-warning\` or \`W006 budget-error\`, the context package is too large. You MUST consider splitting the node or reducing dependencies.
|
|
91
|
+
* If you see \`W008 stale-knowledge\`, the semantic memory is outdated compared to the code. Update the knowledge artifacts.
|
|
92
|
+
* **Smallest Viable Scope:** Prefer \`scope: nodes\` over \`scope: tags\`. Prefer tags over \`scope: global\`. Global scope costs token budget in EVERY context package.
|
|
93
|
+
|
|
94
|
+
**Journaling (Iterative Mode):**
|
|
95
|
+
* **Default:** Write changes directly to graph files immediately.
|
|
96
|
+
* **Opt-in:** ONLY if the user says "tryb iteracyjny" or "użyj journala", use \`yg journal-add --note "..."\` to buffer intent during fast ping-pong changes.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 5. PATH CONVENTIONS (CRITICAL)
|
|
101
|
+
|
|
102
|
+
To avoid broken references (\`E004\`, \`E005\`), use correct relative paths:
|
|
103
|
+
* **Node paths** (used in CLI, relations, flow nodes): Relative to \`.yggdrasil/model/\` (e.g., \`orders/order-service\`).
|
|
104
|
+
* **File paths** (used in mapping, \`yg owner\`): Relative to the repository root (e.g., \`src/modules/orders/order.service.ts\`).
|
|
105
|
+
* **Knowledge paths** (used in node explicit refs): Relative to \`.yggdrasil/knowledge/\` (e.g., \`decisions/001-event-sourcing\`).
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 6. GRAPH STRUCTURE, CONFIG & TEMPLATES CHEAT SHEET
|
|
110
|
+
|
|
111
|
+
The graph lives entirely under \`.yggdrasil/\`. You NEVER guess structure. You MUST ALWAYS read the corresponding schema reference in \`.yggdrasil/templates/\` before creating or editing any graph file.
|
|
112
|
+
|
|
113
|
+
* **\`.yggdrasil/config.yaml\`**: The ONLY config file. Defines \`node_types\`, \`tags\`, \`artifacts\`, \`knowledge_categories\`, and quality thresholds. Read this before any graph work.
|
|
114
|
+
* **\`.yggdrasil/templates/\`**: The SINGLE place for all templates and schemas.
|
|
115
|
+
* Contains node-type templates (e.g., \`service.yaml\`, \`module.yaml\`) with suggested artifacts and guidance.
|
|
116
|
+
* Contains schema references (\`node.yaml\`, \`aspect.yaml\`, \`flow.yaml\`, \`knowledge.yaml\`) showing exact file structures.
|
|
117
|
+
* **\`.yggdrasil/model/\`**: Node tree. Each node is a directory with \`node.yaml\` and artifact files (filenames from \`config.artifacts\`; required ones depend on config).
|
|
118
|
+
* **\`.yggdrasil/aspects/\`**: Cross-cutting rules. Directory contains \`aspect.yaml\` (binds via \`tag: <name>\`) and \`.md\` content.
|
|
119
|
+
* **\`.yggdrasil/flows/\`**: End-to-end processes. Directory contains \`flow.yaml\` (lists \`nodes: [paths]\` and \`knowledge: [paths]\`) and \`.md\` content.
|
|
120
|
+
* **\`.yggdrasil/knowledge/\`**: Repo-wide wisdom (\`decisions/\`, \`patterns/\`, \`invariants/\`). Directory contains \`knowledge.yaml\` and \`.md\` content.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 7. CLI TOOLS REFERENCE (\`yg\`)
|
|
125
|
+
|
|
126
|
+
Always use these exact commands.
|
|
127
|
+
|
|
128
|
+
* \`yg owner --file <file_path>\` -> Find owning node.
|
|
129
|
+
* \`yg build-context --node <node_path>\` -> Assemble strict specification.
|
|
130
|
+
* \`yg tree [--root <node_path>] [--depth N]\` -> Print graph structure.
|
|
131
|
+
* \`yg deps --node <node_path> [--type structural|event|all]\` -> Show dependencies.
|
|
132
|
+
* \`yg impact --node <node_path> --simulate\` -> Simulate blast radius.
|
|
133
|
+
* \`yg status\` -> Graph health metrics.
|
|
134
|
+
* \`yg validate [--scope <node_path>|all]\` -> Compile/check graph. Run after EVERY graph edit.
|
|
135
|
+
* \`yg drift [--scope <node_path>|all]\` -> Check code vs graph baseline.
|
|
136
|
+
* \`yg drift-sync --node <node_path>\` -> Save current file hash as new baseline. Run ONLY after ensuring graph artifacts match the code.
|
|
137
|
+
|
|
138
|
+
*(Iterative mode only)*
|
|
139
|
+
* \`yg journal-read\`
|
|
140
|
+
* \`yg journal-add --note "<content>" [--target <node_path>]\`
|
|
141
|
+
* \`yg journal-archive\`
|
|
142
|
+
`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
node_type: library
|
|
2
|
+
suggested_artifacts:
|
|
3
|
+
- responsibility
|
|
4
|
+
- interface
|
|
5
|
+
guidance: |
|
|
6
|
+
A library node represents shared code, utilities, or reusable components that
|
|
7
|
+
other nodes depend on. It has no business logic of its own — it provides
|
|
8
|
+
capabilities.
|
|
9
|
+
|
|
10
|
+
responsibility.md: Define what capabilities this library provides and what it
|
|
11
|
+
does not. "Provides date formatting, timezone conversion, and scheduling helpers.
|
|
12
|
+
Does not contain business rules or domain logic."
|
|
13
|
+
|
|
14
|
+
interface.md: Required when other nodes depend on this one. List exported functions,
|
|
15
|
+
classes, or APIs. For each: signature, purpose, usage constraints. Libraries are
|
|
16
|
+
often consumed by many nodes — a clear interface prevents misuse.
|
|
17
|
+
|
|
18
|
+
constraints.md: Optional. Use for usage constraints: "All dates must be ISO 8601
|
|
19
|
+
UTC. Thread-safe. No side effects."
|
|
20
|
+
|
|
21
|
+
mapping: Typically "directory" pointing to the library source. Path relative
|
|
22
|
+
to repository root.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
node_type: module
|
|
2
|
+
suggested_artifacts:
|
|
3
|
+
- responsibility
|
|
4
|
+
guidance: |
|
|
5
|
+
A module node represents a domain or subsystem that groups child components.
|
|
6
|
+
It provides domain context that all descendants inherit during context assembly.
|
|
7
|
+
|
|
8
|
+
responsibility.md: Define the business domain this module owns. What is in scope
|
|
9
|
+
(e.g. "orders, order lifecycle, order state transitions") and what is out of scope
|
|
10
|
+
(e.g. "payment processing belongs to payments module"). Child nodes inherit this
|
|
11
|
+
context — a service under this module is understood within this domain.
|
|
12
|
+
|
|
13
|
+
Modules typically have no mapping (they are organizational, not implementation units).
|
|
14
|
+
If the module maps to a directory, that directory contains the implementation of
|
|
15
|
+
all child nodes collectively.
|
|
16
|
+
|
|
17
|
+
Keep responsibility concise but precise. The agent uses it to understand boundaries
|
|
18
|
+
when implementing or modifying child nodes.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: ComponentName
|
|
2
|
+
type: service
|
|
3
|
+
tags: []
|
|
4
|
+
blackbox: false
|
|
5
|
+
|
|
6
|
+
relations:
|
|
7
|
+
- target: other/module-path
|
|
8
|
+
type: calls
|
|
9
|
+
consumes: [methodA, methodB]
|
|
10
|
+
failure: 'retry 3x, then circuit-break'
|
|
11
|
+
|
|
12
|
+
knowledge:
|
|
13
|
+
- decisions/001-choice-name
|
|
14
|
+
|
|
15
|
+
mapping:
|
|
16
|
+
paths:
|
|
17
|
+
- src/modules/component/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
node_type: service
|
|
2
|
+
suggested_artifacts:
|
|
3
|
+
- responsibility
|
|
4
|
+
- interface
|
|
5
|
+
- constraints
|
|
6
|
+
- errors
|
|
7
|
+
guidance: |
|
|
8
|
+
A service node represents a component with a public API that other components depend on.
|
|
9
|
+
It is the primary implementation unit for business logic.
|
|
10
|
+
|
|
11
|
+
responsibility.md: State clearly what this service does and what it does NOT do.
|
|
12
|
+
Define boundaries: "Creates and validates orders; does not process payments."
|
|
13
|
+
Include edge cases: "Handles partial fulfillment; does not handle refunds."
|
|
14
|
+
|
|
15
|
+
interface.md: Required when other nodes depend on this one (has_incoming_relations).
|
|
16
|
+
List every public method with: name, parameters (types), return type, contract.
|
|
17
|
+
For each method: preconditions, postconditions, idempotency, error conditions.
|
|
18
|
+
Example: "createOrder(items: OrderItem[]): Promise<Order> — creates order in pending
|
|
19
|
+
state; items must have positive quantity; returns order with generated id."
|
|
20
|
+
|
|
21
|
+
constraints.md: Validation rules, business rules, invariants this service enforces.
|
|
22
|
+
Be explicit: "Order must have at least 1 item, max 100 items. Total must be > 0."
|
|
23
|
+
Include rate limits, quotas, or SLA constraints if applicable.
|
|
24
|
+
|
|
25
|
+
errors.md: Required when others depend on this node. Document error conditions,
|
|
26
|
+
error codes, and recovery behavior. "INSUFFICIENT_STOCK: retry after inventory
|
|
27
|
+
refresh. PAYMENT_FAILED: order marked payment-failed, manual intervention."
|
|
28
|
+
|
|
29
|
+
mapping: Use type "directory" for a service implemented in one folder, or "file"
|
|
30
|
+
for a single-file service. Path relative to repository root.
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chrisdudek/yg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Make your repository self-aware. Persistent semantic memory and deterministic context assembly for AI agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"yg": "./dist/bin.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"graph-templates/"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:coverage": "vitest run --coverage",
|
|
22
|
+
"lint": "eslint src/",
|
|
23
|
+
"lint:fix": "eslint src/ --fix",
|
|
24
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"yggdrasil",
|
|
29
|
+
"graph",
|
|
30
|
+
"ai",
|
|
31
|
+
"specification",
|
|
32
|
+
"architecture",
|
|
33
|
+
"cli",
|
|
34
|
+
"agent-commands",
|
|
35
|
+
"code-generation"
|
|
36
|
+
],
|
|
37
|
+
"author": "Krzysztof Dudek <me@chrisdudek.com>",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"homepage": "https://github.com/krzysztofdudek/Yggdrasil#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/krzysztofdudek/Yggdrasil/issues",
|
|
42
|
+
"email": "me@chrisdudek.com"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/krzysztofdudek/Yggdrasil.git",
|
|
47
|
+
"directory": "source/cli"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"chalk": "^5.6.2",
|
|
54
|
+
"commander": "^14.0.3",
|
|
55
|
+
"ignore": "^7.0.5",
|
|
56
|
+
"yaml": "^2.8.2"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@eslint/js": "^10.0.1",
|
|
60
|
+
"@types/node": "^25.3.0",
|
|
61
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
62
|
+
"eslint": "^10.0.1",
|
|
63
|
+
"prettier": "^3.8.1",
|
|
64
|
+
"tsup": "^8.5.1",
|
|
65
|
+
"typescript": "^5.9.3",
|
|
66
|
+
"typescript-eslint": "^8.56.0",
|
|
67
|
+
"vitest": "^4.0.18"
|
|
68
|
+
}
|
|
69
|
+
}
|