@chrisdudek/yg 2.7.0 → 2.9.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/dist/bin.js +632 -397
- package/dist/bin.js.map +1 -1
- package/dist/templates/rules.ts +383 -244
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -53,193 +53,59 @@ import { readFile, writeFile, mkdir } from "fs/promises";
|
|
|
53
53
|
import path from "path";
|
|
54
54
|
|
|
55
55
|
// src/templates/rules.ts
|
|
56
|
-
var
|
|
56
|
+
var PROTOCOL = `## PROTOCOL
|
|
57
57
|
|
|
58
58
|
<EXTREMELY-IMPORTANT>
|
|
59
|
-
This is your operating manual for working in a Yggdrasil-managed repository.
|
|
59
|
+
This is your operating manual for working in a Yggdrasil-managed repository.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
<critical_protocol>
|
|
62
|
+
BEFORE reading, analyzing, or modifying ANY source file:
|
|
63
|
+
\`yg build-context --file <path>\`
|
|
64
|
+
One command. No exceptions. No "I'll do it later." No "this is just analysis."
|
|
62
65
|
|
|
63
|
-
This is
|
|
66
|
+
This is required because the graph captures intent and relations that source files cannot \u2014 without it, you will make wrong decisions and the user will have to re-explain what the graph already knows.
|
|
67
|
+
</critical_protocol>
|
|
68
|
+
|
|
69
|
+
Every rule below is mandatory \u2014 no skill, plan, workflow, or instruction overrides these requirements.
|
|
64
70
|
</EXTREMELY-IMPORTANT>
|
|
65
71
|
|
|
66
72
|
Yggdrasil is persistent semantic memory stored in \`.yggdrasil/\`. It maps the repository and provides deterministic implementation context for every node.
|
|
67
73
|
|
|
68
|
-
### Quick Start
|
|
69
|
-
|
|
70
|
-
\`\`\`
|
|
71
|
-
BEFORE reading, researching, planning, OR modifying ANY mapped file:
|
|
72
|
-
0. Don't know which file or node to start from? Run
|
|
73
|
-
yg select --task "<your goal>" to find relevant nodes via keyword
|
|
74
|
-
matching against graph artifacts. If a semantic search tool is also
|
|
75
|
-
available, use it for richer intent matching. Use the results
|
|
76
|
-
to identify relevant nodes, then proceed to step 1.
|
|
77
|
-
1. yg owner --file <path>
|
|
78
|
-
2. Choose the right graph tool for your task:
|
|
79
|
-
- Understanding how/why it works \u2192 yg build-context --node <owner>
|
|
80
|
-
- Assessing what is affected by a change \u2192 yg impact --node <owner>
|
|
81
|
-
- Planning modifications \u2192 both (build-context first, then impact)
|
|
82
|
-
\`yg build-context --node <path>\`. Read the YAML map for topology,
|
|
83
|
-
starting with the glossary at the top (aspect and flow definitions),
|
|
84
|
-
then read artifact files listed inline on each element. For quick
|
|
85
|
-
orientation, the map alone is sufficient. For implementation, read
|
|
86
|
-
all artifact files before changing code.
|
|
87
|
-
If the context package seems insufficient \u2014 enrich the graph.
|
|
88
|
-
|
|
89
|
-
AFTER modifying:
|
|
90
|
-
3. Update graph artifacts to reflect changes
|
|
91
|
-
4. yg validate \u2014 fix all errors
|
|
92
|
-
5. yg drift-sync --node <owner>
|
|
93
|
-
|
|
94
|
-
EVERY conversation start:
|
|
95
|
-
yg preflight \u2192 act on findings (see Operations)
|
|
96
|
-
|
|
97
|
-
NEVER: modify code without graph coverage.
|
|
98
|
-
NEVER: read mapped source files to understand a component without
|
|
99
|
-
running yg build-context first \u2014 the graph captures intent,
|
|
100
|
-
constraints, and relations that source files cannot.
|
|
101
|
-
NEVER: assess blast radius of a change without running yg impact first
|
|
102
|
-
\u2014 the graph knows the dependency structure that grep cannot infer.
|
|
103
|
-
NEVER: invent rationale, business rules, or decisions.
|
|
104
|
-
NEVER: auto-resolve drift without asking the user.
|
|
105
|
-
WHEN UNSURE: ask the user. Never guess. Never assume.
|
|
106
|
-
\`\`\`
|
|
107
|
-
|
|
108
|
-
### Five Core Rules
|
|
109
|
-
|
|
110
|
-
1. **Graph first.** Before reading, researching, planning, or modifying mapped files, run \`yg owner\` and the appropriate graph tool: \`yg build-context\` to understand a component, \`yg impact\` to assess blast radius. The graph is your primary source of architectural understanding. For implementation-level precision (exact behavior, error paths, edge cases) \u2014 verify against source code after loading the context package.
|
|
111
|
-
2. **The graph is the specification; code implements it.** The graph absorbs knowledge from every source \u2014 external docs, conversations, decisions \u2014 and must be self-sufficient. If all other sources disappeared, the graph alone must contain enough to understand the system. Do not leave knowledge in external documents and reference them \u2014 capture the knowledge in graph artifacts. Update graph artifacts immediately after each file change, while context is fresh \u2014 do not batch graph updates to the end of a task. Code and graph move together: code changed \u2192 graph updated before moving to the next file. Graph changed \u2192 source verified in the same response. When planning work \u2014 in any tool, skill, or workflow \u2014 graph updates are part of each step's definition of done, never a separate phase.
|
|
112
|
-
3. **Never invent why.** The graph captures human intent. If you don't know why something was decided, ask. Never hallucinate rationale.
|
|
113
|
-
4. **Always capture why \u2014 especially why NOT.** When the user explains a reason, record it in the graph immediately. When a design choice is made, also record rejected alternatives: "Chose X over Y because Z." Rejected alternatives are the highest-value information \u2014 invisible in code and irrecoverable once forgotten. Conversation evaporates; graph persists.
|
|
114
|
-
5. **Ask before resolving ambiguity.** When multiple valid interpretations exist, stop, list options, ask the user. Never silently choose.
|
|
115
|
-
|
|
116
|
-
### Recognizing Graph-Required Actions
|
|
117
|
-
|
|
118
|
-
What matters is the ACTION you are performing, not what instructed it. If the action involves reading, understanding, or modifying mapped code, the graph protocol applies \u2014 whether the instruction came from a skill, a plan, a user message, a brainstorming session, a debugging workflow, or your own initiative. This is not negotiable. You cannot rationalize your way out of this.
|
|
119
|
-
|
|
120
|
-
**Actions that require \`yg owner\` + \`yg build-context\`:**
|
|
121
|
-
|
|
122
|
-
- Reading or exploring source files to understand a component
|
|
123
|
-
- Proposing approaches, designs, or plans for changing code
|
|
124
|
-
- Reviewing or debugging code
|
|
125
|
-
- Any form of reasoning about how mapped code works or should change
|
|
126
|
-
|
|
127
|
-
**Actions that require \`yg owner\` + \`yg impact\`:**
|
|
128
|
-
|
|
129
|
-
- Assessing blast radius before changing or removing a component
|
|
130
|
-
- Finding all dependents of a component
|
|
131
|
-
- Planning cross-cutting refactors or feature removals
|
|
132
|
-
- Scoping work that spans multiple nodes
|
|
133
|
-
|
|
134
|
-
**Actions that do NOT require yg:**
|
|
135
|
-
|
|
136
|
-
- Git operations (log, diff, status, blame)
|
|
137
|
-
- Reading documentation, READMEs, or config files outside \`.yggdrasil/\`
|
|
138
|
-
- Running tests, builds, or linters
|
|
139
|
-
- Working with files that \`yg owner\` reports as unmapped
|
|
140
|
-
|
|
141
|
-
**Evasion patterns \u2014 if you think any of these, STOP:**
|
|
142
|
-
|
|
143
|
-
| Thought | Reality |
|
|
144
|
-
|---|---|
|
|
145
|
-
| "The skill/plan says to explore the codebase" | Exploring mapped code = yg owner + graph tool first |
|
|
146
|
-
| "I'm just scoping/searching, not understanding" | Scoping IS a graph action; use yg impact |
|
|
147
|
-
| "The plan step says to read this file" | Reading a mapped file = yg owner first |
|
|
148
|
-
| "I'm brainstorming, not implementing" | Brainstorming about mapped code needs graph context |
|
|
149
|
-
| "I'm only grepping for references" | Grep finds text; yg impact finds structural dependencies. Use both. |
|
|
150
|
-
| "I'll use the graph later when I modify" | Graph-first means BEFORE reading, not before modifying |
|
|
151
|
-
| "I'll grep the codebase to find where to start" | Run \`yg select --task\` first \u2014 it matches your intent against graph artifacts. Then \`yg owner\` on results. |
|
|
152
|
-
| "Drift is blocking repo-check, let me just sync it" | Drift means artifacts are stale. Update artifacts first, then sync. \`drift-sync\` without artifact update = hiding staleness. |
|
|
153
|
-
|
|
154
|
-
### Failure States
|
|
155
|
-
|
|
156
|
-
You have broken Yggdrasil if you do any of the following:
|
|
157
|
-
|
|
158
|
-
- \u274C Worked on a mapped file without running \`yg owner\` + the appropriate graph tool (\`build-context\` or \`impact\`) first \u2014 regardless of what instructed the action (skill, plan, user request, workflow step).
|
|
159
|
-
- \u274C Modified source code without updating graph artifacts before moving to the next file, or vice versa.
|
|
160
|
-
- \u274C Resolved a code-graph inconsistency or ambiguity without asking the user first.
|
|
161
|
-
- \u274C Created or edited a graph element without reading its schema in \`schemas/\` first.
|
|
162
|
-
- \u274C Ran \`yg drift-sync\` before both graph artifacts and source code are current.
|
|
163
|
-
- \u274C Placed a cross-cutting requirement in a local artifact instead of an aspect, or used an aspect id with no \`aspects/\` directory.
|
|
164
|
-
- \u274C Invented a rationale, business rule, or decision \u2014 or recorded a decision without documenting rejected alternatives and rationale (use "rationale: unknown" if unknown).
|
|
165
|
-
- \u274C Used blackbox coverage for greenfield (new) code.
|
|
166
|
-
- \u274C Deleted or shortened graph artifact content to reduce context package size instead of splitting the node.
|
|
167
|
-
|
|
168
|
-
### Escape Hatch
|
|
169
|
-
|
|
170
|
-
If the user explicitly requests a code-only change, comply but:
|
|
171
|
-
|
|
172
|
-
- Warn: "This creates drift. Run \`yg drift\` next session to reconcile."
|
|
173
|
-
- Do NOT run \`yg drift-sync\` \u2014 leave the drift visible.
|
|
174
|
-
|
|
175
|
-
### Environment Check
|
|
176
|
-
|
|
177
|
-
Before preflight:
|
|
178
|
-
|
|
179
|
-
- Verify \`yg\` CLI is available. If not found, inform user and stop.
|
|
180
|
-
- If \`yg preflight\` shows 0 nodes \u2192 enter BOOTSTRAP MODE (see Operations).
|
|
181
|
-
- If drift report shows >10 drifted nodes \u2192 report scope to user, ask which area to prioritize. Do not resolve all at once.
|
|
182
|
-
|
|
183
|
-
### Delegating to Subagents
|
|
184
|
-
|
|
185
|
-
When you delegate work to a subagent (any subprocess, tool agent, or spawned assistant), the subagent does NOT inherit your Yggdrasil knowledge. Before any other instruction, the subagent MUST:
|
|
186
|
-
|
|
187
|
-
1. Read \`.yggdrasil/agent-rules.md\` \u2014 this is the complete operating manual
|
|
188
|
-
2. Follow the Quick Start Protocol from that file before touching any mapped code
|
|
189
|
-
|
|
190
|
-
Include this as the FIRST instruction in every subagent prompt:
|
|
74
|
+
### Quick Start
|
|
191
75
|
|
|
192
76
|
\`\`\`
|
|
193
|
-
|
|
194
|
-
DELIVERABLES \u2014 all required, incomplete work will be rejected:
|
|
195
|
-
1. Working source code
|
|
196
|
-
2. Graph nodes with artifacts for every new/modified source file
|
|
197
|
-
3. \`yg validate\` passing
|
|
198
|
-
\`\`\`
|
|
199
|
-
|
|
200
|
-
A subagent that delivers code without corresponding graph updates has not completed its task. Before accepting subagent output, verify: are there new or modified source files without corresponding graph coverage? If yes, the work is incomplete.`;
|
|
201
|
-
var OPERATIONS = `## OPERATIONS
|
|
77
|
+
EVERY conversation: yg preflight \u2014 no exceptions.
|
|
202
78
|
|
|
203
|
-
|
|
79
|
+
BEFORE any source file interaction:
|
|
80
|
+
1. yg build-context --file <path>
|
|
81
|
+
One command: resolves owner, assembles context.
|
|
82
|
+
Read the YAML map \u2014 glossary first (aspect/flow definitions),
|
|
83
|
+
then artifact files listed on each element.
|
|
84
|
+
For blast radius: also run yg impact --file <path>.
|
|
85
|
+
Don't know where to start? yg select --task "<goal>"
|
|
204
86
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
flow definitions, then read artifact files listed inline on each
|
|
217
|
-
element. For quick orientation, the map alone is sufficient. For
|
|
218
|
-
implementation, read all artifact files before changing code.
|
|
219
|
-
- [ ] 3. Owner not found \u2192 use file analysis, state it is not graph-backed.
|
|
220
|
-
Never use grep or raw file reads as primary understanding when graph coverage exists.
|
|
221
|
-
Raw reads supplement the context package \u2014 they do not replace it.
|
|
222
|
-
|
|
223
|
-
WRAP-UP (user signals "done", "wrap up", "that's enough"):
|
|
224
|
-
- [ ] 1. yg drift --drifted-only \u2192 resolve
|
|
225
|
-
- [ ] 2. yg validate \u2192 fix errors
|
|
226
|
-
- [ ] 3. Report: which nodes and files were changed
|
|
227
|
-
|
|
228
|
-
BEFORE ENDING ANY RESPONSE (self-audit):
|
|
229
|
-
- [ ] Did I interact with mapped code (read, research, or modify)? If yes \u2192 did I use a graph tool BEFORE reading source?
|
|
230
|
-
- [ ] Did I modify source code? If yes \u2192 did I update graph artifacts before moving to the next file?
|
|
231
|
-
- [ ] If you broke either rule, you have broken the protocol. Do not finish until both are fixed.
|
|
87
|
+
AFTER modifying:
|
|
88
|
+
2. Update graph artifacts (per file, not batched)
|
|
89
|
+
3. yg validate \u2014 fix all errors
|
|
90
|
+
4. yg drift-sync --node <owner>
|
|
91
|
+
|
|
92
|
+
ALWAYS: establish graph coverage before modifying code.
|
|
93
|
+
ALWAYS: run yg build-context --file before reading source.
|
|
94
|
+
ALWAYS: run yg impact before assessing blast radius.
|
|
95
|
+
ALWAYS: ask the user for rationale \u2014 record it, do not invent it.
|
|
96
|
+
ALWAYS: ask before resolving drift or ambiguity.
|
|
97
|
+
WHEN UNSURE: ask the user. Do not guess. Do not assume.
|
|
232
98
|
\`\`\`
|
|
233
99
|
|
|
234
100
|
### Modify Source Code
|
|
235
101
|
|
|
236
102
|
You are not allowed to edit or create source code without establishing graph coverage first.
|
|
237
103
|
|
|
238
|
-
**Step 1** \u2014
|
|
104
|
+
**Step 1** \u2014 Get context: \`yg build-context --file <path>\` (resolves owner automatically)
|
|
239
105
|
|
|
240
106
|
**Step 2a** \u2014 Owner found: execute checklist:
|
|
241
107
|
|
|
242
|
-
- [ ] 1. Read
|
|
108
|
+
- [ ] 1. Read the context package (already assembled by step 1)
|
|
243
109
|
- [ ] 2. Assess blast radius: \`yg impact --node <node_path>\` \u2014 review dependents, descendants, and co-aspect nodes before changing interfaces or shared behavior
|
|
244
110
|
- [ ] 3. Modify source code
|
|
245
111
|
- [ ] 4. Sync graph artifacts \u2014 edit artifact files to reflect the changes (after each file, not batched \u2014 context is freshest immediately after the change). If the node's purpose changed, update \`description\` in \`yg-node.yaml\`.
|
|
@@ -265,99 +131,138 @@ You are not allowed to edit or create source code without establishing graph cov
|
|
|
265
131
|
5. Implement code that satisfies the specification
|
|
266
132
|
6. The graph specifies WHAT and WHY; the code implements HOW (framework APIs, library choices)
|
|
267
133
|
|
|
134
|
+
**Node sizing rule:** One node per cohesive feature area, NOT per directory. If a node would map >10 source files or cover >3 distinct user workflows, split it into child nodes. Example: an admin panel should be \`admin/blog\`, \`admin/gallery\`, \`admin/clients\`, \`admin/orders\` \u2014 not one \`admin-pages\` node. The CLI enforces this with W017, but plan granularity upfront rather than splitting after the fact.
|
|
135
|
+
|
|
268
136
|
After the user chooses, return to Step 1 and follow Step 2a.
|
|
269
137
|
|
|
270
|
-
###
|
|
138
|
+
### Working from External Specifications
|
|
271
139
|
|
|
272
|
-
|
|
273
|
-
- [ ] 2. Before changing an aspect or flow, check scope: \`yg impact --aspect <id>\` or \`yg impact --flow <name>\` \u2014 understand which nodes are affected before modifying shared rules or processes
|
|
274
|
-
- [ ] 3. Make changes
|
|
275
|
-
- [ ] 4. Run \`yg validate\` immediately \u2014 fix all errors
|
|
276
|
-
- [ ] 5. Verify affected source files are consistent \u2014 update if needed
|
|
277
|
-
- [ ] 6. Run \`yg drift-sync\` for affected nodes
|
|
140
|
+
When the user provides external documents (specs, PRDs, design docs, reference docs) as input for implementation:
|
|
278
141
|
|
|
279
|
-
|
|
142
|
+
1. **Read ALL spec documents BEFORE writing any code.** Understand the full scope \u2014 business context, feature specs, quality requirements, UX rules, deployment config.
|
|
143
|
+
2. **Extract and route knowledge to the graph FIRST**, using the Information Routing table:
|
|
144
|
+
- Business rules, personas, pricing strategy, acquisition channels \u2192 aspects or root node artifacts
|
|
145
|
+
- Feature specifications (UI behavior, validation, workflows) \u2192 node responsibility/interface/internals artifacts
|
|
146
|
+
- Cross-cutting UX/quality requirements \u2192 aspects
|
|
147
|
+
- Business processes \u2192 flows
|
|
148
|
+
3. **The graph is the specification; external docs are INPUT to the graph, not a parallel source of truth.** After the graph is populated, external docs become redundant \u2014 the graph is what future agents will read.
|
|
149
|
+
4. **Spec knowledge is not code knowledge.** Specs contain business context (WHY the system exists, WHO it serves, WHAT it should do) that will never appear in source code. If you only document what you built, you lose what motivated building it.
|
|
150
|
+
5. **Completeness test:** "If the external docs disappeared today, does the graph contain everything a future agent needs to understand the system \u2014 not just HOW it works, but WHY it exists and WHAT business value it delivers?"
|
|
280
151
|
|
|
281
|
-
**
|
|
152
|
+
**Common failure mode:** Agent reads spec \u2192 implements code \u2192 documents code in graph \u2192 spec knowledge (personas, pricing, UX rationale, quality targets) is lost because it was treated as "input I consumed" rather than "knowledge I must persist." The graph must absorb the spec, not just the code.
|
|
282
153
|
|
|
283
|
-
|
|
154
|
+
### Example: Correct vs Wrong
|
|
284
155
|
|
|
285
|
-
|
|
286
|
-
- [ ] 2. Determine node granularity \u2014 propose to user if unclear
|
|
287
|
-
- [ ] 3. Create node directory, read \`schemas/yg-node.yaml\`, create \`yg-node.yaml\`
|
|
288
|
-
- [ ] 3b. Write \`description\` in \`yg-node.yaml\` \u2014 a short summary of what the node does
|
|
289
|
-
- [ ] 4. Analyze source \u2014 for each artifact type in \`yg-config.yaml artifacts\`: extract content, do not invent
|
|
290
|
-
- [ ] 5. Identify relations \u2014 add to \`yg-node.yaml\`
|
|
291
|
-
- [ ] 6. Identify cross-cutting requirements \u2014 add matching aspects, create if needed
|
|
292
|
-
- [ ] 6b. For each aspect on the node: identify 2-5 code anchors (function names, constants) that evidence the pattern \u2192 add as \`anchors\` in the aspect entry in \`yg-node.yaml\`
|
|
293
|
-
- [ ] 7. Identify business process participation \u2014 add to flow, ask user if process unclear
|
|
294
|
-
- [ ] 8. \`yg validate\` \u2014 fix errors
|
|
295
|
-
- [ ] 9. \`yg drift-sync --node <path>\`
|
|
156
|
+
<example_correct>
|
|
296
157
|
|
|
297
|
-
|
|
158
|
+
User: "Fix the bug in payment.service.ts"
|
|
298
159
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
160
|
+
1. yg build-context --file src/payment.service.ts \u2192 payment/payment-service
|
|
161
|
+
2. Read YAML map \u2014 glossary, then artifact files
|
|
162
|
+
3. Read source file, understand bug in graph context
|
|
163
|
+
4. Fix bug
|
|
164
|
+
5. Update payment-service artifacts (responsibility.md, interface.md if API changed)
|
|
165
|
+
6. yg validate
|
|
166
|
+
7. yg drift-sync --node payment/payment-service
|
|
304
167
|
|
|
305
|
-
|
|
168
|
+
</example_correct>
|
|
306
169
|
|
|
307
|
-
|
|
170
|
+
<example_wrong>
|
|
308
171
|
|
|
309
|
-
|
|
310
|
-
- [ ] 2. Scan for cross-cutting patterns \u2192 create aspects
|
|
311
|
-
- [ ] 3. Ask user about business processes \u2192 create flows if applicable
|
|
312
|
-
- [ ] 4. Propose node structure for the area
|
|
313
|
-
- [ ] 5. Create node(s) with initial artifacts, map files
|
|
314
|
-
- [ ] 6. \`yg validate\`, \`yg drift-sync\`
|
|
315
|
-
- [ ] 7. Proceed with user's original request
|
|
172
|
+
User: "Fix the bug in payment.service.ts"
|
|
316
173
|
|
|
317
|
-
|
|
174
|
+
1. Read src/payment.service.ts \u2190 WRONG: no graph context loaded
|
|
175
|
+
2. Fix bug
|
|
176
|
+
3. "I'll update the graph later" \u2190 WRONG: deferred = forgotten
|
|
318
177
|
|
|
319
|
-
|
|
178
|
+
Result: graph is stale, next agent asks user the same questions
|
|
320
179
|
|
|
321
|
-
|
|
180
|
+
</example_wrong>
|
|
322
181
|
|
|
323
|
-
|
|
324
|
-
- **Graph drift** (graph artifacts changed) \u2192 review affected source, update if needed, then \`yg drift-sync\`
|
|
325
|
-
- **Full drift** (both changed) \u2192 present both sides to user, ask which direction wins
|
|
326
|
-
- **Missing** \u2192 ask: re-materialize or remove mapping?
|
|
327
|
-
- **Unmaterialized** \u2192 ask user how to proceed
|
|
182
|
+
<example_correct>
|
|
328
183
|
|
|
329
|
-
|
|
184
|
+
User: "Here are the spec docs. Implement the admin blog editor."
|
|
330
185
|
|
|
331
|
-
|
|
186
|
+
1. Read ALL spec docs (blog-editor.md, autosave.md, user-persona.md, version-history.md)
|
|
187
|
+
2. Extract cross-cutting patterns \u2192 create aspects (admin-ux-rules, autosave, version-history) if they don't exist
|
|
188
|
+
3. Create flow if the blog participates in a business process
|
|
189
|
+
4. Create node admin/blog with artifacts populated from spec (responsibility, interface, internals)
|
|
190
|
+
5. Run yg build-context \u2192 the context package is now the behavioral specification
|
|
191
|
+
6. Implement code that satisfies the specification
|
|
192
|
+
7. Update artifacts with any implementation details that emerged during coding
|
|
193
|
+
8. yg validate, yg drift-sync
|
|
332
194
|
|
|
333
|
-
|
|
195
|
+
</example_correct>
|
|
334
196
|
|
|
335
|
-
|
|
197
|
+
<example_wrong>
|
|
336
198
|
|
|
337
|
-
|
|
199
|
+
User: "Here are the spec docs. Implement the admin blog editor."
|
|
338
200
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
201
|
+
1. Read blog-editor.md spec
|
|
202
|
+
2. Implement all the code \u2190 WRONG: spec knowledge not captured in graph
|
|
203
|
+
3. Create node admin-pages, map 20 admin files \u2190 WRONG: too wide, W017
|
|
204
|
+
4. Write responsibility.md summarizing what the code does \u2190 WRONG: describes code, not spec intent
|
|
205
|
+
5. Business context (persona, UX rules, autosave rationale) lost \u2190 WRONG: spec was input, not persisted
|
|
344
206
|
|
|
345
|
-
|
|
207
|
+
Result: graph mirrors code but misses WHY. Next agent reads graph, understands HOW but not WHO it's for or WHAT UX rules govern it.
|
|
346
208
|
|
|
347
|
-
|
|
348
|
-
- [ ] 2. For each error path: is it in \`interface.md\` (Failure Modes section)?
|
|
349
|
-
- [ ] 3. For each behavioral invariant: is it in the graph?
|
|
350
|
-
- [ ] 4. Report omissions separately from inconsistencies
|
|
209
|
+
</example_wrong>
|
|
351
210
|
|
|
352
|
-
###
|
|
211
|
+
### Conversation Lifecycle
|
|
353
212
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
-
|
|
357
|
-
-
|
|
358
|
-
-
|
|
359
|
-
|
|
360
|
-
|
|
213
|
+
\`\`\`
|
|
214
|
+
PREFLIGHT (every conversation, before any work):
|
|
215
|
+
- [ ] 1. yg preflight \u2192 read unified report
|
|
216
|
+
- [ ] 2. If drift: resolve per Drift Resolution, then yg drift-sync per node
|
|
217
|
+
- [ ] 3. If validation errors: fix, re-run yg validate
|
|
218
|
+
No exceptions. You cannot know if a file is mapped without running yg.
|
|
219
|
+
|
|
220
|
+
UNDERSTANDING any source file (questions, research, OR planning):
|
|
221
|
+
- [ ] 1. yg build-context --file <path>
|
|
222
|
+
Mapped \u2192 read the YAML map (glossary first, then artifact files).
|
|
223
|
+
Unmapped \u2192 use file analysis, state it is not graph-backed.
|
|
224
|
+
Never use grep or raw file reads as primary understanding when graph coverage exists.
|
|
225
|
+
Raw reads supplement the context package \u2014 they do not replace it.
|
|
226
|
+
|
|
227
|
+
BEFORE reasoning about source code, state which graph context you loaded:
|
|
228
|
+
"graph: <node_path>" if mapped, "graph: unmapped" if not.
|
|
229
|
+
This is a required output step, not optional reflection.
|
|
230
|
+
|
|
231
|
+
WRAP-UP (user signals "done", "wrap up", "that's enough"):
|
|
232
|
+
- [ ] 1. yg drift --drifted-only \u2192 resolve
|
|
233
|
+
- [ ] 2. yg validate \u2192 fix errors
|
|
234
|
+
- [ ] 3. Report: which nodes and files were changed
|
|
235
|
+
|
|
236
|
+
\`\`\`
|
|
237
|
+
|
|
238
|
+
### Modify Graph
|
|
239
|
+
|
|
240
|
+
- [ ] 1. Read the relevant schema from \`schemas/\` before touching any YAML
|
|
241
|
+
- [ ] 2. Before changing an aspect or flow, check scope: \`yg impact --aspect <id>\` or \`yg impact --flow <name>\` \u2014 understand which nodes are affected before modifying shared rules or processes
|
|
242
|
+
- [ ] 3. Make changes
|
|
243
|
+
- [ ] 4. Run \`yg validate\` immediately \u2014 fix all errors
|
|
244
|
+
- [ ] 5. Verify affected source files are consistent \u2014 update if needed
|
|
245
|
+
- [ ] 6. Run \`yg drift-sync\` for affected nodes
|
|
246
|
+
|
|
247
|
+
### Delegating to Subagents
|
|
248
|
+
|
|
249
|
+
When you delegate work to a subagent (any subprocess, tool agent, or spawned assistant), the subagent does NOT inherit your Yggdrasil knowledge. Before any other instruction, the subagent MUST:
|
|
250
|
+
|
|
251
|
+
1. Read \`.yggdrasil/agent-rules.md\` \u2014 this is the complete operating manual
|
|
252
|
+
2. Follow the Quick Start Protocol from that file before touching any mapped code
|
|
253
|
+
|
|
254
|
+
Include this as the FIRST instruction in every subagent prompt:
|
|
255
|
+
|
|
256
|
+
\`\`\`
|
|
257
|
+
BEFORE doing anything else: read .yggdrasil/agent-rules.md and follow its protocol.
|
|
258
|
+
DELIVERABLES \u2014 all required, incomplete work will be rejected:
|
|
259
|
+
1. Working source code
|
|
260
|
+
2. Graph nodes with artifacts for every new/modified source file
|
|
261
|
+
3. \`yg validate\` passing
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
A subagent that delivers code without corresponding graph updates has not completed its task. Before accepting subagent output, verify: are there new or modified source files without corresponding graph coverage? If yes, the work is incomplete.`;
|
|
265
|
+
var REFERENCE = `## REFERENCE
|
|
361
266
|
|
|
362
267
|
### Graph Structure
|
|
363
268
|
|
|
@@ -422,6 +327,10 @@ When you encounter information, route it to the correct location:
|
|
|
422
327
|
- **Shared across a domain** \u2192 parent node artifact. Children receive it through hierarchy.
|
|
423
328
|
- **Technology stack or standard** \u2192 node artifact at the appropriate hierarchy level (e.g., root node's \`responsibility.md\` for single-stack repos, or deployment unit node for monorepos)
|
|
424
329
|
- **Decision (why + why NOT):** one node \u2192 Decisions section of \`internals.md\` with format "Chose X over Y because Z"; category of nodes \u2192 aspect content files; tech choice \u2192 node artifact at the level where the technology applies. Always include rejected alternatives \u2014 they are the highest-value graph content. If the rationale is unknown: record the decision with "rationale: unknown" and note what CAN be observed from the code. Never invent a plausible-sounding rationale.
|
|
330
|
+
- **Business strategy** (personas, pricing, acquisition channels, brand positioning) \u2192 root node artifact or dedicated business-context aspect. This knowledge has NO source file \u2014 it exists only in specs and conversations.
|
|
331
|
+
- **Quality targets** (performance budgets, accessibility level, Lighthouse scores, test coverage goals) \u2192 aspect per quality dimension (e.g., \`performance-targets\`, \`accessibility\`). These are measurable cross-cutting constraints.
|
|
332
|
+
- **UX patterns** (autosave, version history, empty states, confirmation modals) \u2192 aspect when the pattern applies to 3+ screens. UX patterns are cross-cutting even if they aren't architectural.
|
|
333
|
+
- **Infrastructure/deployment** (domains, DNS, env vars, CI/CD, cron scheduling, hosting config) \u2192 infrastructure node or root node artifacts. Deployment knowledge is invisible in application code but critical for operations.
|
|
425
334
|
|
|
426
335
|
### Creating Aspects
|
|
427
336
|
|
|
@@ -443,78 +352,308 @@ When a node follows an aspect's pattern with exceptions, record them in the \`ex
|
|
|
443
352
|
|
|
444
353
|
**Aspect lifecycle warning.** Aspects decay CATASTROPHICALLY \u2014 a pattern either exists or it doesn't. When a pattern changes, ALL aspect claims become wrong at once. This differs from other artifacts: \`interface.md\` and \`responsibility.md\` are most stable (~9-year half-life); \`internals.md\` has moderate stability (~2.5-year half-life); aspects are least stable (~2.4-year half-life, binary decay). After any significant feature addition, review ALL aspects touching the affected area. Don't wait for drift \u2014 aspects can be 100% wrong without any mapped file changing.
|
|
445
354
|
|
|
446
|
-
**Aspect stability tiers.** If an aspect has a \`stability\` field in \`yg-aspect.yaml\`, use it to calibrate review urgency:
|
|
355
|
+
**Aspect stability tiers.** If an aspect has a \`stability\` field in \`yg-aspect.yaml\`, use it to calibrate review urgency:
|
|
356
|
+
|
|
357
|
+
- \`schema\` \u2014 enforced by data model; review only when data model changes (most stable)
|
|
358
|
+
- \`protocol\` \u2014 contractual pattern; review when contracts or interfaces change
|
|
359
|
+
- \`implementation\` \u2014 specific mechanism; review after ANY significant code change (least stable)
|
|
360
|
+
|
|
361
|
+
When code anchors (\`anchors\` in an aspect entry in \`yg-node.yaml\`) are present, they list code patterns (function names, constants, SQL fragments) evidencing the aspect's implementation in this node. \`yg validate\` checks that each anchor exists in the node's mapped source files \u2014 a missing anchor (W014) signals the aspect may be stale for this node.
|
|
362
|
+
|
|
363
|
+
### Creating Flows
|
|
364
|
+
|
|
365
|
+
- [ ] 1. Read \`schemas/yg-flow.yaml\`
|
|
366
|
+
- [ ] 2. Create \`flows/<name>/\` directory
|
|
367
|
+
- [ ] 3. Write \`yg-flow.yaml\` \u2014 name, description, nodes (participant list), and flow-level aspects
|
|
368
|
+
- [ ] 4. Write \`description.md\` with required sections: Business context, Trigger, Goal, Participants, Paths (at least Happy path), Invariants across all paths
|
|
369
|
+
- [ ] 5. \`yg validate\`
|
|
370
|
+
|
|
371
|
+
Test: "Does this describe what happens in the world, or only in the software?" If only software \u2014 rewrite.
|
|
372
|
+
|
|
373
|
+
**Warning:** Flow descriptions must describe business processes, not code sequences. "The OrderService calls PaymentGateway.charge()" is WRONG. "The system charges the customer's payment method" is CORRECT.
|
|
374
|
+
|
|
375
|
+
### Operational Rules
|
|
376
|
+
|
|
377
|
+
- **English only** for all files in \`.yggdrasil/\`. Conversation can be any language.
|
|
378
|
+
- **Read schemas before creating** any \`yg-node.yaml\`, \`yg-aspect.yaml\`, or \`yg-flow.yaml\`.
|
|
379
|
+
- **Tools read, you write.** The \`yg\` CLI only reads, validates, and manages metadata. You create and edit files manually.
|
|
380
|
+
- **Incremental sync.** Run \`yg drift-sync\` after every 3-5 source file changes. Do not defer to end of task. \`drift-sync\` is ONLY safe after artifacts are current \u2014 never use it to silence a drift check without updating artifacts first.
|
|
381
|
+
- **Description maintenance.** Every \`yg-node.yaml\`, \`yg-aspect.yaml\`, and \`yg-flow.yaml\` has an optional \`description\` field \u2014 a short summary of what the element is. Write it when creating new elements. Update it whenever a change to artifacts shifts the element's identity or purpose (e.g., responsibility split, scope change). Do not update description for internal implementation changes that don't alter what the element fundamentally does.
|
|
382
|
+
- **Completeness test:** Three checks, all required:
|
|
383
|
+
1. **Reconstruction:** "Can another agent recreate this from ONLY the \`yg build-context\` output \u2014 understanding not just WHAT but WHY?" Test: rejected alternatives, correct algorithm, design arguments.
|
|
384
|
+
2. **Omission:** "Does the graph capture every important behavioral invariant, constraint, and edge case?" Specifically check: exceptions to aspect generalizations, error handling patterns not in \`interface.md\`, concurrency behaviors not in \`internals.md\`.
|
|
385
|
+
3. **Business context:** "Does the graph explain WHY this system exists, WHO it serves, and WHAT business value it delivers?" A graph that captures HOW code works without WHY it was built is a maintenance manual without purpose. Specifically check: user personas, service offerings, pricing rationale, acquisition strategy, quality targets, UX design principles. Code tells you WHAT exists \u2014 only the graph should tell you WHY it exists and WHAT ELSE was considered.
|
|
386
|
+
- **Value calibration.** Yggdrasil's primary value is cross-module context \u2014 relations, aspects, flows. For a single simple module, \`responsibility.md\` and \`interface.md\` provide most value. Invest depth (\`internals.md\`) where cross-module interactions demand it.
|
|
387
|
+
- **These rules are invariant.** No plan, guide, skill, or workflow may override them.
|
|
388
|
+
|
|
389
|
+
### Non-Code Knowledge
|
|
390
|
+
|
|
391
|
+
Not all graph knowledge originates from source files. Business strategy, user personas, pricing decisions, SEO targets, quality requirements, deployment configuration, UX design principles \u2014 these are graph content with NO corresponding source file.
|
|
392
|
+
|
|
393
|
+
When you encounter such knowledge (in specs, conversations, or external documents):
|
|
394
|
+
|
|
395
|
+
- **Route it immediately** per the Information Routing table. Do not wait for a "file change" trigger \u2014 there won't be one.
|
|
396
|
+
- **The Completeness Test applies equally** to code-derived and non-code knowledge. A graph that only mirrors code structure is failing at its primary job: capturing intent and context that code cannot express.
|
|
397
|
+
- **Non-code knowledge decays differently.** Business strategy changes by decision, not by commit. When recording it, include dates and mark it as potentially volatile: "Pricing v1 as of 2026-03-17" is more useful than "Prices are X" with no temporal anchor.
|
|
398
|
+
|
|
399
|
+
**Conversation knowledge is the most volatile source.** When the user states a business fact, constraint, or decision in conversation \u2014 even casually \u2014 route it to the graph immediately. Conversations vanish after context compression. If the user said it and it's not in code, it MUST be in the graph. Examples of conversational knowledge that must be captured:
|
|
400
|
+
|
|
401
|
+
- Business facts: "Our target customer is couples aged 25-35" \u2192 root node or business-context aspect
|
|
402
|
+
- Constraints: "We don't do studio sessions, only outdoor" \u2192 responsibility.md (NOT responsible for)
|
|
403
|
+
- Pricing: "Mini session costs 350 PLN" \u2192 relevant node artifacts
|
|
404
|
+
- Strategy: "Instagram is our primary acquisition channel" \u2192 root node or business-context aspect
|
|
405
|
+
- Decisions: "No deposit upfront \u2014 we'll reconsider after 5 sessions" \u2192 internals.md Decisions section with rationale
|
|
406
|
+
- Personas: "The admin user is non-technical, thinks in Instagram/WhatsApp terms" \u2192 UX aspect
|
|
407
|
+
|
|
408
|
+
Do not assume you will remember this later. Do not assume the user will repeat it. Capture it now or lose it forever.
|
|
409
|
+
|
|
410
|
+
**Common failure mode:** The entire protocol is file-centric (\`build-context --file\`, "after modifying source file", "per file not batched"). This means knowledge that doesn't map to a specific source file has no natural trigger for capture. Treat spec documents, user conversations, and business decisions as first-class inputs to the graph \u2014 not just context for coding.
|
|
411
|
+
|
|
412
|
+
### Aspect Discovery During Implementation
|
|
413
|
+
|
|
414
|
+
Aspects emerge from patterns across features. During greenfield implementation of multiple features:
|
|
415
|
+
|
|
416
|
+
- **After implementing 3+ features in the same area, pause and review:** Are there repeated patterns (autosave, version history, confirmation modals, empty states)? Are there shared UX rules from a persona doc? Are there quality requirements from specs? Extract them to aspects NOW.
|
|
417
|
+
- **Do NOT wait until all features are done.** Aspect extraction after 3 features captures the pattern while context is fresh. After 30 features, the rationale is forgotten and the aspect becomes a mechanical extraction without WHY.
|
|
418
|
+
- **Watch for "invisible" aspects:** UX patterns (autosave everywhere), quality constraints (WCAG level, Lighthouse targets), and business rules (Polish locale, price-in-grosz) are cross-cutting but don't feel "architectural." They are still aspects.
|
|
419
|
+
- **Trigger:** If you notice yourself implementing the same pattern for the third time, stop coding and create the aspect first. Then continue with the aspect applied to the current and previous nodes.
|
|
420
|
+
|
|
421
|
+
### CLI Reference
|
|
422
|
+
|
|
423
|
+
\`\`\`
|
|
424
|
+
yg preflight [--quick] Unified diagnostic: drift + status + validate.
|
|
425
|
+
yg owner --file <path> Find the node that owns this file (quick check).
|
|
426
|
+
yg build-context --file <path> Resolve owner + assemble context in one step.
|
|
427
|
+
yg build-context --node <path> Assemble context map for a known node.
|
|
428
|
+
yg build-context --node <path> --full Same map + file contents appended below separator.
|
|
429
|
+
yg tree [--root <path>] [--depth N] Print graph structure.
|
|
430
|
+
yg aspects List aspects with metadata (YAML output).
|
|
431
|
+
yg flows List flows with metadata (YAML output).
|
|
432
|
+
yg select --task <description> [--limit <n>]
|
|
433
|
+
Find graph nodes relevant to a task description.
|
|
434
|
+
yg deps --node <path> [--depth N] [--type structural|event|all]
|
|
435
|
+
Show dependencies.
|
|
436
|
+
yg impact --file <path> Resolve owner + show blast radius in one step.
|
|
437
|
+
yg impact --node <path> --simulate Simulate blast radius (works with --file too).
|
|
438
|
+
yg impact --node <path> --method <name> Filter to dependents consuming a method (works with --file too).
|
|
439
|
+
yg impact --aspect <id> Show all nodes where aspect is effective.
|
|
440
|
+
yg impact --flow <name> Show flow participants and descendants.
|
|
441
|
+
yg status Graph health: nodes, coverage, drift summary.
|
|
442
|
+
yg validate [--scope <path>|all] Check structural integrity and completeness.
|
|
443
|
+
yg drift [--scope <path>|all] [--drifted-only] [--limit <n>]
|
|
444
|
+
Detect source and graph drift (bidirectional).
|
|
445
|
+
yg drift-sync --node <path> [--recursive] | --all
|
|
446
|
+
Record file hashes as new baseline.
|
|
447
|
+
\`\`\`
|
|
448
|
+
|
|
449
|
+
### Quick Routing Table
|
|
450
|
+
|
|
451
|
+
| What you have | Where it goes |
|
|
452
|
+
|---|---|
|
|
453
|
+
| Information specific to this node | Local node artifact (check \`yg-config.yaml artifacts\` for types) |
|
|
454
|
+
| Rule that applies to many nodes | Aspect (content \`.md\` files in \`aspects/<id>/\`) |
|
|
455
|
+
| Architectural invariant for a node type | Required aspect in \`yg-config.yaml node_types\` |
|
|
456
|
+
| Business process participation | Flow (\`yg-flow.yaml nodes\`) |
|
|
457
|
+
| Process-level requirement | Flow \`aspects\` + aspect directory |
|
|
458
|
+
| Context shared across a domain | Parent node artifact |
|
|
459
|
+
| Technology stack | Node artifact at appropriate hierarchy level |
|
|
460
|
+
| Coding standards | Node artifact at appropriate hierarchy level |
|
|
461
|
+
| Business strategy (personas, pricing, channels) | Root node artifact or dedicated business-context aspect |
|
|
462
|
+
| Quality targets (perf budgets, a11y, test goals) | Aspect per quality dimension |
|
|
463
|
+
| UX patterns (autosave, version history, empty states) | Aspect when pattern applies to 3+ screens |
|
|
464
|
+
| Infrastructure/deployment (domains, env vars, CI/CD) | Infrastructure node or root node artifacts |
|
|
465
|
+
| External service config (Stripe fees, email limits) | Relevant node's \`internals.md\` Decisions section |
|
|
466
|
+
| Feature spec from external doc | Node artifacts \u2014 translate spec into responsibility/interface/internals |`;
|
|
467
|
+
var GUARD_RAILS = `## GUARD RAILS
|
|
468
|
+
|
|
469
|
+
### Five Core Rules
|
|
470
|
+
|
|
471
|
+
1. **Graph first.** Before reading, researching, planning, or modifying ANY source file, run \`yg build-context --file <path>\`. For blast radius, also run \`yg impact\`. The graph is your primary source of architectural understanding. For implementation-level precision (exact behavior, error paths, edge cases) \u2014 verify against source code after loading the context package.
|
|
472
|
+
2. **The graph is the specification; code implements it.** The graph absorbs knowledge from every source \u2014 external docs, conversations, decisions \u2014 and must be self-sufficient. If all other sources disappeared, the graph alone must contain enough to understand the system. Do not leave knowledge in external documents and reference them \u2014 capture the knowledge in graph artifacts. Update graph artifacts immediately after each file change, while context is fresh \u2014 do not batch graph updates to the end of a task. Code and graph move together: code changed \u2192 graph updated before moving to the next file. Graph changed \u2192 source verified in the same response. When planning work \u2014 in any tool, skill, or workflow \u2014 graph updates are part of each step's definition of done, never a separate phase.
|
|
473
|
+
3. **Never invent why.** The graph captures human intent. If you don't know why something was decided, ask. Never hallucinate rationale.
|
|
474
|
+
4. **Always capture why \u2014 especially why NOT.** When the user explains a reason, record it in the graph immediately. When a design choice is made, also record rejected alternatives: "Chose X over Y because Z." Rejected alternatives are the highest-value information \u2014 invisible in code and irrecoverable once forgotten. Conversation evaporates; graph persists.
|
|
475
|
+
5. **Ask before resolving ambiguity.** When multiple valid interpretations exist, stop, list options, ask the user. Never silently choose.
|
|
476
|
+
|
|
477
|
+
### Recognizing Graph-Required Actions
|
|
478
|
+
|
|
479
|
+
What matters is the ACTION you are performing, not what instructed it. If the action involves reading, understanding, or modifying mapped code, the graph protocol applies \u2014 whether the instruction came from a skill, a plan, a user message, a brainstorming session, a debugging workflow, or your own initiative. This is not negotiable. You cannot rationalize your way out of this.
|
|
480
|
+
|
|
481
|
+
**Actions that require \`yg build-context --file\`:**
|
|
482
|
+
|
|
483
|
+
- Reading or exploring source files to understand a component
|
|
484
|
+
- Proposing approaches, designs, or plans for changing code
|
|
485
|
+
- Reviewing or debugging code
|
|
486
|
+
- Any form of reasoning about how mapped code works or should change
|
|
487
|
+
|
|
488
|
+
**Actions that also require \`yg impact\`:**
|
|
489
|
+
|
|
490
|
+
- Assessing blast radius before changing or removing a component
|
|
491
|
+
- Finding all dependents of a component
|
|
492
|
+
- Planning cross-cutting refactors or feature removals
|
|
493
|
+
- Scoping work that spans multiple nodes
|
|
494
|
+
|
|
495
|
+
**Actions that do NOT require yg:**
|
|
496
|
+
|
|
497
|
+
- Git operations (log, diff, status, blame)
|
|
498
|
+
- Reading documentation, READMEs, or config files outside \`.yggdrasil/\`
|
|
499
|
+
- Running tests, builds, or linters
|
|
500
|
+
- Working with files that \`yg build-context --file\` reports as unmapped
|
|
501
|
+
|
|
502
|
+
### Evasion Patterns \u2014 if you think any of these, STOP
|
|
503
|
+
|
|
504
|
+
| Thought | Reality |
|
|
505
|
+
|---|---|
|
|
506
|
+
| "The skill/plan says to explore the codebase" | Exploring mapped code = \`yg build-context --file\` first |
|
|
507
|
+
| "I'm just scoping/searching, not understanding" | Scoping IS a graph action; use yg impact |
|
|
508
|
+
| "The plan step says to read this file" | Reading any source file = \`yg build-context --file\` first |
|
|
509
|
+
| "I'm brainstorming, not implementing" | Brainstorming about code needs graph context. You proved this by failing at it. |
|
|
510
|
+
| "I'm only grepping for references" | Grep finds text; yg impact finds structural dependencies. Use both. |
|
|
511
|
+
| "I'll use the graph later when I modify" | Graph-first means BEFORE reading, not before modifying |
|
|
512
|
+
| "I'll grep the codebase to find where to start" | Run \`yg select --task\` first, then \`yg build-context --file\` on results. |
|
|
513
|
+
| "Drift is blocking repo-check, let me just sync it" | Drift means artifacts are stale. Update artifacts first, then sync. \`drift-sync\` will warn you (W018). |
|
|
514
|
+
| "The user said work autonomously" | Autonomy amplifies discipline, not relaxes it. More tasks = more graph updates, not fewer. |
|
|
515
|
+
| "Same pattern as the last 5 files, no need to document" | Repetitive patterns hide deviations. Per-node coverage captures what aspects don't. The next agent won't know what you know now. |
|
|
516
|
+
| "I'll batch graph updates at the end" | Batching = never. Context is freshest immediately after the change. Defer = forget. This is a failure state. |
|
|
517
|
+
| "I'm saving context/tool calls by skipping graph" | Graph cost is constant per node. Skipping it creates unbounded future cost \u2014 the user re-explaining what you could have recorded. |
|
|
518
|
+
| "I assumed this file isn't mapped" | You cannot know without running \`yg build-context --file\`. Assume nothing. |
|
|
519
|
+
| "The spec is just input, I don't need to capture it" | Specs contain business context that code cannot express. Capture it or lose it. |
|
|
520
|
+
| "This business knowledge will be obvious from the code" | Pricing strategy, personas, UX rationale, and quality targets are NEVER obvious from code. |
|
|
521
|
+
| "I'll extract aspects after I finish all the features" | After 30 features the rationale is gone. Extract after 3. |
|
|
522
|
+
| "This is a UX detail, not architecture" | UX patterns that apply to 3+ screens ARE cross-cutting requirements. Create an aspect. |
|
|
523
|
+
| "The user just mentioned it casually, it's not a formal decision" | Casual statements ARE decisions. "We don't do studio" is a business constraint. Capture it now or lose it after context compression. |
|
|
524
|
+
| "I'll remember this from the conversation" | No you won't. Context gets compressed. The user won't repeat it. Write it to the graph now. |
|
|
525
|
+
|
|
526
|
+
### Failure States
|
|
527
|
+
|
|
528
|
+
You have broken Yggdrasil if you do any of the following:
|
|
529
|
+
|
|
530
|
+
- \u274C Worked on a source file without running \`yg build-context --file\` first \u2014 regardless of what instructed the action (skill, plan, user request, workflow step).
|
|
531
|
+
- \u274C Modified source code without updating graph artifacts before moving to the next file, or vice versa.
|
|
532
|
+
- \u274C Batched graph updates to "do later" \u2014 deferred = forgotten. Update after EACH file.
|
|
533
|
+
- \u274C Resolved a code-graph inconsistency or ambiguity without asking the user first.
|
|
534
|
+
- \u274C Created or edited a graph element without reading its schema in \`schemas/\` first.
|
|
535
|
+
- \u274C Ran \`yg drift-sync\` before both graph artifacts and source code are current. (CLI will warn you: W018.)
|
|
536
|
+
- \u274C Placed a cross-cutting requirement in a local artifact instead of an aspect, or used an aspect id with no \`aspects/\` directory.
|
|
537
|
+
- \u274C Invented a rationale, business rule, or decision \u2014 or recorded a decision without documenting rejected alternatives and rationale (use "rationale: unknown" if unknown).
|
|
538
|
+
- \u274C Used blackbox coverage for greenfield (new) code.
|
|
539
|
+
- \u274C Deleted or shortened graph artifact content to reduce context package size instead of splitting the node.
|
|
540
|
+
- \u274C Created one wide node for many files instead of granular nodes with focused responsibilities. (CLI will warn you: W017.)
|
|
541
|
+
- \u274C Implemented features from a spec without first transferring spec knowledge (business context, UX rules, quality targets) into the graph. Code without captured intent is a maintenance trap.
|
|
542
|
+
- \u274C Implemented 3+ features sharing a pattern (autosave, version history, empty states) without extracting it to an aspect. Deferred aspect discovery = lost rationale.
|
|
543
|
+
- \u274C Left business strategy, personas, or quality targets only in external documents instead of routing them to graph artifacts. External docs are input; the graph is the persistent store.
|
|
544
|
+
- \u274C Heard the user state a business fact, constraint, or decision in conversation and did not record it in the graph. Conversations are the most volatile knowledge source \u2014 they vanish after context compression and the user will not repeat them.
|
|
545
|
+
|
|
546
|
+
### Reverse Engineering
|
|
547
|
+
|
|
548
|
+
**Order:** aspects (cross-cutting patterns) \u2192 flows (business processes) \u2192 model nodes. Never create nodes before aspects and flows are understood.
|
|
549
|
+
|
|
550
|
+
Per area checklist:
|
|
551
|
+
|
|
552
|
+
- [ ] 1. \`yg build-context --file <path>\` \u2014 confirm no coverage
|
|
553
|
+
- [ ] 2. Determine node granularity \u2014 propose to user if unclear
|
|
554
|
+
- [ ] 3. Create node directory, read \`schemas/yg-node.yaml\`, create \`yg-node.yaml\`
|
|
555
|
+
- [ ] 3b. Write \`description\` in \`yg-node.yaml\` \u2014 a short summary of what the node does
|
|
556
|
+
- [ ] 4. Analyze source \u2014 for each artifact type in \`yg-config.yaml artifacts\`: extract content, do not invent
|
|
557
|
+
- [ ] 5. Identify relations \u2014 add to \`yg-node.yaml\`
|
|
558
|
+
- [ ] 6. Identify cross-cutting requirements \u2014 add matching aspects, create if needed
|
|
559
|
+
- [ ] 6b. For each aspect on the node: identify 2-5 code anchors (function names, constants) that evidence the pattern \u2192 add as \`anchors\` in the aspect entry in \`yg-node.yaml\`
|
|
560
|
+
- [ ] 7. Identify business process participation \u2014 add to flow, ask user if process unclear
|
|
561
|
+
- [ ] 8. \`yg validate\` \u2014 fix errors
|
|
562
|
+
- [ ] 9. \`yg drift-sync --node <path>\`
|
|
563
|
+
|
|
564
|
+
**When to ask:**
|
|
565
|
+
|
|
566
|
+
- Business process unclear: "This code appears to be part of a larger process. Can you describe what it means from a business perspective?"
|
|
567
|
+
- Constraint without rationale: "I see [constraint X]. Do you know why this exists? I want to record the reason, not just the rule."
|
|
568
|
+
- Unexplained architectural choice: "I see [approach X]. What was the reason for this choice?"
|
|
569
|
+
- Decision without alternatives: "You chose [X]. What alternatives did you consider, and why did you reject them?" Record the answer in the Decisions section of \`internals.md\`.
|
|
570
|
+
- Decision without known rationale: Record the decision in \`internals.md\` with "rationale: unknown \u2014 inferred from code, not confirmed by developer." A recorded decision with unknown rationale is infinitely more valuable than no record at all, and safer than an invented rationale.
|
|
571
|
+
|
|
572
|
+
### Bootstrap Mode
|
|
573
|
+
|
|
574
|
+
Trigger: \`yg preflight\` shows 0 nodes, or no nodes cover the active work area.
|
|
575
|
+
|
|
576
|
+
- [ ] 1. Identify the active work area (files the user wants to modify)
|
|
577
|
+
- [ ] 2. Scan for cross-cutting patterns \u2192 create aspects
|
|
578
|
+
- [ ] 3. Ask user about business processes \u2192 create flows if applicable
|
|
579
|
+
- [ ] 4. Propose node structure for the area
|
|
580
|
+
- [ ] 5. Create node(s) with initial artifacts, map files
|
|
581
|
+
- [ ] 6. \`yg validate\`, \`yg drift-sync\`
|
|
582
|
+
- [ ] 7. Proceed with user's original request
|
|
583
|
+
|
|
584
|
+
Constraint: Do NOT map the entire repository. Focus on the active area. Expand incrementally.
|
|
585
|
+
|
|
586
|
+
### Drift Resolution
|
|
587
|
+
|
|
588
|
+
Always ask the user before resolving drift. Never auto-resolve.
|
|
589
|
+
|
|
590
|
+
- **Source drift** (source files changed) \u2192 update graph artifacts to match source, then \`yg drift-sync\`
|
|
591
|
+
- **Graph drift** (graph artifacts changed) \u2192 review affected source, update if needed, then \`yg drift-sync\`
|
|
592
|
+
- **Full drift** (both changed) \u2192 present both sides to user, ask which direction wins
|
|
593
|
+
- **Missing** \u2192 ask: re-materialize or remove mapping?
|
|
594
|
+
- **Unmaterialized** \u2192 ask user how to proceed
|
|
595
|
+
|
|
596
|
+
Threshold: >10 drifted nodes \u2192 ask user which area to prioritize. Do not resolve all at once.
|
|
597
|
+
|
|
598
|
+
**Drift triage:** Prioritize aspects and \`internals.md\` (highest decay rate), then \`responsibility.md\` and \`interface.md\` (most stable).
|
|
599
|
+
|
|
600
|
+
### Graph Audit
|
|
601
|
+
|
|
602
|
+
When reviewing graph quality (triggered by user or quality improvement):
|
|
603
|
+
|
|
604
|
+
**Step 1 \u2014 Consistency** (catches WRONG information):
|
|
605
|
+
|
|
606
|
+
- [ ] 1. \`yg build-context --node <path>\`
|
|
607
|
+
- [ ] 2. Read mapped source files
|
|
608
|
+
- [ ] 3. For each claim in graph: verify against source code
|
|
609
|
+
- [ ] 4. For each aspect: verify the pattern holds in THIS node. If it deviates, add \`exceptions\` to the aspect entry in \`yg-node.yaml\`
|
|
610
|
+
- [ ] 5. Report inconsistencies
|
|
611
|
+
|
|
612
|
+
**Step 2 \u2014 Completeness** (catches MISSING information):
|
|
447
613
|
|
|
448
|
-
-
|
|
449
|
-
-
|
|
450
|
-
-
|
|
614
|
+
- [ ] 1. For each public method: is it in \`interface.md\`?
|
|
615
|
+
- [ ] 2. For each error path: is it in \`interface.md\` (Failure Modes section)?
|
|
616
|
+
- [ ] 3. For each behavioral invariant: is it in the graph?
|
|
617
|
+
- [ ] 4. Report omissions separately from inconsistencies
|
|
451
618
|
|
|
452
|
-
|
|
619
|
+
**Step 3 \u2014 Non-Derivable Knowledge** (catches knowledge that exists ONLY in external docs or conversations, not in code):
|
|
453
620
|
|
|
454
|
-
|
|
621
|
+
- [ ] 1. For each business rule embedded in code: is the WHY recorded in the graph, or only the WHAT visible in code?
|
|
622
|
+
- [ ] 2. For each design decision: is the rationale AND rejected alternatives recorded?
|
|
623
|
+
- [ ] 3. For each external constraint (brand guidelines, legal, UX persona, quality targets): is it in the graph?
|
|
624
|
+
- [ ] 4. For each cross-cutting pattern implemented in 3+ places: does an aspect exist?
|
|
625
|
+
- [ ] 5. Report non-derivable knowledge gaps separately \u2014 these are the highest-value omissions because they cannot be recovered by reading code.
|
|
455
626
|
|
|
456
|
-
|
|
457
|
-
- [ ] 2. Create \`flows/<name>/\` directory
|
|
458
|
-
- [ ] 3. Write \`yg-flow.yaml\` \u2014 name, description, nodes (participant list), and flow-level aspects
|
|
459
|
-
- [ ] 4. Write \`description.md\` with required sections: Business context, Trigger, Goal, Participants, Paths (at least Happy path), Invariants across all paths
|
|
460
|
-
- [ ] 5. \`yg validate\`
|
|
627
|
+
### Error Recovery
|
|
461
628
|
|
|
462
|
-
|
|
629
|
+
- **\`yg\` not found** \u2192 inform user: "yg CLI is not installed or not in PATH." Stop.
|
|
630
|
+
- **Unfixable validate errors** \u2192 if not resolved after 3 attempts, stop and report to user. Do not loop.
|
|
631
|
+
- **Budget warning (W005/W006)** \u2192 informational. \`yg validate\` shows a breakdown (own/hierarchy/aspects/flows/dependencies). Large inherited context means the system is complex \u2014 this is not a problem to fix, it is reality to acknowledge. Do not delete knowledge from artifacts. Do not attempt to "reduce" inherited context.
|
|
632
|
+
- **Own budget warning (W015)** \u2192 own artifacts are large. Consider splitting this node's responsibilities into child nodes. Redistribute knowledge across children so total knowledge is preserved or increased, never reduced.
|
|
633
|
+
- **Corrupted \`.yggdrasil/\` files** \u2192 report to user. Do not attempt repair.
|
|
634
|
+
- **Incremental sync** \u2192 run \`yg drift-sync\` every 3-5 source files during multi-file tasks. Do not defer to end. But NEVER run \`yg drift-sync\` to silence a failing drift check \u2014 drift is a signal that artifacts need updating. First update artifacts, then sync.
|
|
463
635
|
|
|
464
|
-
|
|
636
|
+
### Escape Hatch
|
|
465
637
|
|
|
466
|
-
|
|
638
|
+
If the user explicitly requests a code-only change, comply but:
|
|
467
639
|
|
|
468
|
-
-
|
|
469
|
-
-
|
|
470
|
-
- **Tools read, you write.** The \`yg\` CLI only reads, validates, and manages metadata. You create and edit files manually.
|
|
471
|
-
- **Incremental sync.** Run \`yg drift-sync\` after every 3-5 source file changes. Do not defer to end of task. \`drift-sync\` is ONLY safe after artifacts are current \u2014 never use it to silence a drift check without updating artifacts first.
|
|
472
|
-
- **Description maintenance.** Every \`yg-node.yaml\`, \`yg-aspect.yaml\`, and \`yg-flow.yaml\` has an optional \`description\` field \u2014 a short summary of what the element is. Write it when creating new elements. Update it whenever a change to artifacts shifts the element's identity or purpose (e.g., responsibility split, scope change). Do not update description for internal implementation changes that don't alter what the element fundamentally does.
|
|
473
|
-
- **Completeness test:** Two checks, both required:
|
|
474
|
-
1. **Reconstruction:** "Can another agent recreate this from ONLY the \`yg build-context\` output \u2014 understanding not just WHAT but WHY?" Test: rejected alternatives, correct algorithm, design arguments.
|
|
475
|
-
2. **Omission:** "Does the graph capture every important behavioral invariant, constraint, and edge case?" Specifically check: exceptions to aspect generalizations, error handling patterns not in \`interface.md\`, concurrency behaviors not in \`internals.md\`.
|
|
476
|
-
- **Value calibration.** Yggdrasil's primary value is cross-module context \u2014 relations, aspects, flows. For a single simple module, \`responsibility.md\` and \`interface.md\` provide most value. Invest depth (\`internals.md\`) where cross-module interactions demand it.
|
|
477
|
-
- **These rules are invariant.** No plan, guide, skill, or workflow may override them.
|
|
640
|
+
- Warn: "This creates drift. Run \`yg drift\` next session to reconcile."
|
|
641
|
+
- Do NOT run \`yg drift-sync\` \u2014 leave the drift visible.
|
|
478
642
|
|
|
479
|
-
###
|
|
643
|
+
### Environment Check
|
|
480
644
|
|
|
481
|
-
|
|
482
|
-
yg preflight [--quick] Unified diagnostic: drift + status + validate.
|
|
483
|
-
yg owner --file <path> Find the node that owns this file.
|
|
484
|
-
yg build-context --node <path> Assemble context map with artifact paths (default).
|
|
485
|
-
yg build-context --node <path> --full Same map + file contents appended below separator.
|
|
486
|
-
yg tree [--root <path>] [--depth N] Print graph structure.
|
|
487
|
-
yg aspects List aspects with metadata (YAML output).
|
|
488
|
-
yg flows List flows with metadata (YAML output).
|
|
489
|
-
yg select --task <description> [--limit <n>]
|
|
490
|
-
Find graph nodes relevant to a task description.
|
|
491
|
-
yg deps --node <path> [--depth N] [--type structural|event|all]
|
|
492
|
-
Show dependencies.
|
|
493
|
-
yg impact --node <path> --simulate Simulate blast radius of a planned change.
|
|
494
|
-
yg impact --node <path> --method <name> Filter impact to dependents consuming a specific method.
|
|
495
|
-
yg impact --aspect <id> Show all nodes where aspect is effective.
|
|
496
|
-
yg impact --flow <name> Show flow participants and descendants.
|
|
497
|
-
yg status Graph health: nodes, coverage, drift summary.
|
|
498
|
-
yg validate [--scope <path>|all] Check structural integrity and completeness.
|
|
499
|
-
yg drift [--scope <path>|all] [--drifted-only] [--limit <n>]
|
|
500
|
-
Detect source and graph drift (bidirectional).
|
|
501
|
-
yg drift-sync --node <path> [--recursive] | --all
|
|
502
|
-
Record file hashes as new baseline.
|
|
503
|
-
\`\`\`
|
|
645
|
+
Before preflight:
|
|
504
646
|
|
|
505
|
-
|
|
647
|
+
- Verify \`yg\` CLI is available. If not found, inform user and stop.
|
|
648
|
+
- If \`yg preflight\` shows 0 nodes \u2192 enter BOOTSTRAP MODE (see above).
|
|
649
|
+
- If drift report shows >10 drifted nodes \u2192 report scope to user, ask which area to prioritize. Do not resolve all at once.
|
|
506
650
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
| Process-level requirement | Flow \`aspects\` + aspect directory |
|
|
514
|
-
| Context shared across a domain | Parent node artifact |
|
|
515
|
-
| Technology stack | Node artifact at appropriate hierarchy level |
|
|
516
|
-
| Coding standards | Node artifact at appropriate hierarchy level |`;
|
|
517
|
-
var AGENT_RULES_CONTENT = [CORE_PROTOCOL, OPERATIONS, KNOWLEDGE_BASE].join("\n\n---\n\n") + "\n";
|
|
651
|
+
<critical_protocol>
|
|
652
|
+
BEFORE reading, analyzing, or modifying ANY source file:
|
|
653
|
+
\`yg build-context --file <path>\`
|
|
654
|
+
One command. No exceptions. No "I'll do it later." No "this is just analysis."
|
|
655
|
+
</critical_protocol>`;
|
|
656
|
+
var AGENT_RULES_CONTENT = [PROTOCOL, REFERENCE, GUARD_RAILS].join("\n\n---\n\n") + "\n";
|
|
518
657
|
|
|
519
658
|
// src/templates/platform.ts
|
|
520
659
|
var AGENT_RULES_IMPORT = "@.yggdrasil/agent-rules.md";
|
|
@@ -2376,6 +2515,7 @@ async function validate(graph, scope = "all") {
|
|
|
2376
2515
|
issues.push(...checkFlowAspectIds(graph));
|
|
2377
2516
|
issues.push(...await checkDirectoriesHaveNodeYaml(graph));
|
|
2378
2517
|
issues.push(...await checkShallowArtifacts(graph));
|
|
2518
|
+
issues.push(...await checkWideNodes(graph));
|
|
2379
2519
|
issues.push(...checkUnpairedEvents(graph));
|
|
2380
2520
|
let filtered = issues;
|
|
2381
2521
|
let nodesScanned = graph.nodes.size;
|
|
@@ -2857,6 +2997,29 @@ async function checkShallowArtifacts(graph) {
|
|
|
2857
2997
|
}
|
|
2858
2998
|
return issues;
|
|
2859
2999
|
}
|
|
3000
|
+
async function checkWideNodes(graph) {
|
|
3001
|
+
const issues = [];
|
|
3002
|
+
const maxFiles = graph.config.quality?.max_mapping_source_files ?? 10;
|
|
3003
|
+
const projectRoot = path11.dirname(graph.rootPath);
|
|
3004
|
+
for (const [nodePath, node] of graph.nodes) {
|
|
3005
|
+
if (node.meta.blackbox) continue;
|
|
3006
|
+
const mappingPaths = normalizeMappingPaths(node.meta.mapping);
|
|
3007
|
+
if (mappingPaths.length === 0) continue;
|
|
3008
|
+
const sourceFiles = await expandMappingToFiles(projectRoot, mappingPaths);
|
|
3009
|
+
if (sourceFiles.length <= maxFiles) continue;
|
|
3010
|
+
const filledArtifacts = node.artifacts.filter(
|
|
3011
|
+
(a) => a.content.trim().length >= (graph.config.quality?.min_artifact_length ?? 50)
|
|
3012
|
+
).length;
|
|
3013
|
+
issues.push({
|
|
3014
|
+
severity: "warning",
|
|
3015
|
+
code: "W017",
|
|
3016
|
+
rule: "wide-node",
|
|
3017
|
+
message: `Node maps ${sourceFiles.length} source files (max: ${maxFiles}) with ${filledArtifacts} artifact(s). Consider splitting into child nodes with focused responsibilities.`,
|
|
3018
|
+
nodePath
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
return issues;
|
|
3022
|
+
}
|
|
2860
3023
|
function checkHighFanOut(graph) {
|
|
2861
3024
|
const issues = [];
|
|
2862
3025
|
const maxRel = graph.config.quality?.max_direct_relations ?? 10;
|
|
@@ -3134,6 +3297,73 @@ function checkMissingDescriptions(graph) {
|
|
|
3134
3297
|
return issues;
|
|
3135
3298
|
}
|
|
3136
3299
|
|
|
3300
|
+
// src/cli/owner.ts
|
|
3301
|
+
import path12 from "path";
|
|
3302
|
+
import { access as access2 } from "fs/promises";
|
|
3303
|
+
function normalizeForMatch(inputPath) {
|
|
3304
|
+
return inputPath.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
3305
|
+
}
|
|
3306
|
+
function findOwner(graph, projectRoot, rawPath) {
|
|
3307
|
+
const file = normalizeForMatch(normalizeProjectRelativePath(projectRoot, rawPath));
|
|
3308
|
+
let best = null;
|
|
3309
|
+
for (const [nodePath, node] of graph.nodes) {
|
|
3310
|
+
const mappingPaths = normalizeMappingPaths(node.meta.mapping).map(normalizeForMatch).filter((mappingPath) => mappingPath.length > 0);
|
|
3311
|
+
for (const mappingPath of mappingPaths) {
|
|
3312
|
+
if (file === mappingPath) {
|
|
3313
|
+
return { file, nodePath, mappingPath, direct: true };
|
|
3314
|
+
}
|
|
3315
|
+
if (file.startsWith(mappingPath + "/")) {
|
|
3316
|
+
if (!best || best && mappingPath.length > best.mappingPath.length) {
|
|
3317
|
+
best = { nodePath, mappingPath, exact: false };
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
return best ? { file, nodePath: best.nodePath, mappingPath: best.mappingPath, direct: false } : { file, nodePath: null };
|
|
3323
|
+
}
|
|
3324
|
+
function registerOwnerCommand(program2) {
|
|
3325
|
+
program2.command("owner").description("Find which graph node owns a source file").requiredOption("--file <path>", "File path (relative to repository root)").action(async (options) => {
|
|
3326
|
+
try {
|
|
3327
|
+
const cwd = process.cwd();
|
|
3328
|
+
const graph = await loadGraph(cwd);
|
|
3329
|
+
const repoRoot = projectRootFromGraph(graph.rootPath);
|
|
3330
|
+
const rawPath = options.file.trim();
|
|
3331
|
+
const absolute = path12.resolve(cwd, rawPath);
|
|
3332
|
+
const repoRelative = path12.relative(repoRoot, absolute).split(path12.sep).join("/");
|
|
3333
|
+
const result = findOwner(graph, repoRoot, repoRelative);
|
|
3334
|
+
if (!result.nodePath) {
|
|
3335
|
+
const absPath = path12.resolve(repoRoot, result.file);
|
|
3336
|
+
let exists = true;
|
|
3337
|
+
try {
|
|
3338
|
+
await access2(absPath);
|
|
3339
|
+
} catch {
|
|
3340
|
+
exists = false;
|
|
3341
|
+
}
|
|
3342
|
+
if (exists) {
|
|
3343
|
+
process.stdout.write(`${result.file} -> no graph coverage
|
|
3344
|
+
`);
|
|
3345
|
+
} else {
|
|
3346
|
+
process.stdout.write(`${result.file} -> no graph coverage (file not found)
|
|
3347
|
+
`);
|
|
3348
|
+
}
|
|
3349
|
+
} else {
|
|
3350
|
+
process.stdout.write(`${result.file} -> ${result.nodePath}
|
|
3351
|
+
`);
|
|
3352
|
+
if (result.direct === false && result.mappingPath) {
|
|
3353
|
+
process.stdout.write(
|
|
3354
|
+
` File has no direct mapping; context comes from ancestor directory ${result.mappingPath}. Use: yg build-context --node ${result.nodePath}
|
|
3355
|
+
`
|
|
3356
|
+
);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
} catch (error) {
|
|
3360
|
+
process.stderr.write(`Error: ${error.message}
|
|
3361
|
+
`);
|
|
3362
|
+
process.exit(1);
|
|
3363
|
+
}
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3137
3367
|
// src/cli/build-context.ts
|
|
3138
3368
|
function collectRelevantNodePaths(graph, nodePath) {
|
|
3139
3369
|
const relevant = /* @__PURE__ */ new Set();
|
|
@@ -3155,10 +3385,32 @@ function collectRelevantNodePaths(graph, nodePath) {
|
|
|
3155
3385
|
return relevant;
|
|
3156
3386
|
}
|
|
3157
3387
|
function registerBuildCommand(program2) {
|
|
3158
|
-
program2.command("build-context").description("Assemble a context package for one node").
|
|
3388
|
+
program2.command("build-context").description("Assemble a context package for one node").option("--node <node-path>", "Node path relative to .yggdrasil/model/").option("--file <file-path>", "Source file path \u2014 resolves owner node automatically").option("--full", "Include artifact file contents in output").action(async (options) => {
|
|
3159
3389
|
try {
|
|
3390
|
+
if (!options.node && !options.file) {
|
|
3391
|
+
process.stderr.write("Error: either '--node <path>' or '--file <path>' is required\n");
|
|
3392
|
+
process.exit(1);
|
|
3393
|
+
}
|
|
3394
|
+
if (options.node && options.file) {
|
|
3395
|
+
process.stderr.write("Error: '--node' and '--file' are mutually exclusive\n");
|
|
3396
|
+
process.exit(1);
|
|
3397
|
+
}
|
|
3160
3398
|
const graph = await loadGraph(process.cwd());
|
|
3161
|
-
|
|
3399
|
+
let nodePath;
|
|
3400
|
+
if (options.file) {
|
|
3401
|
+
const repoRoot = projectRootFromGraph(graph.rootPath);
|
|
3402
|
+
const result = findOwner(graph, repoRoot, options.file.trim());
|
|
3403
|
+
if (!result.nodePath) {
|
|
3404
|
+
process.stderr.write(`${result.file} -> no graph coverage
|
|
3405
|
+
`);
|
|
3406
|
+
process.exit(1);
|
|
3407
|
+
}
|
|
3408
|
+
process.stderr.write(`${result.file} -> ${result.nodePath}
|
|
3409
|
+
`);
|
|
3410
|
+
nodePath = result.nodePath;
|
|
3411
|
+
} else {
|
|
3412
|
+
nodePath = options.node.trim().replace(/^\.\//, "").replace(/\/$/, "");
|
|
3413
|
+
}
|
|
3162
3414
|
const relevantNodes = collectRelevantNodePaths(graph, nodePath);
|
|
3163
3415
|
const validationResult = await validate(graph, "all");
|
|
3164
3416
|
const relevantErrors = validationResult.issues.filter(
|
|
@@ -3305,11 +3557,11 @@ import chalk2 from "chalk";
|
|
|
3305
3557
|
|
|
3306
3558
|
// src/io/drift-state-store.ts
|
|
3307
3559
|
import { readFile as readFile14, writeFile as writeFile5, stat as stat5, readdir as readdir6, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
3308
|
-
import
|
|
3560
|
+
import path13 from "path";
|
|
3309
3561
|
import { parse as yamlParse } from "yaml";
|
|
3310
3562
|
var DRIFT_STATE_DIR = ".drift-state";
|
|
3311
3563
|
function nodeStatePath(yggRoot, nodePath) {
|
|
3312
|
-
return
|
|
3564
|
+
return path13.join(yggRoot, DRIFT_STATE_DIR, `${nodePath}.json`);
|
|
3313
3565
|
}
|
|
3314
3566
|
async function scanJsonFiles(dir, baseDir) {
|
|
3315
3567
|
const results = [];
|
|
@@ -3320,12 +3572,12 @@ async function scanJsonFiles(dir, baseDir) {
|
|
|
3320
3572
|
return results;
|
|
3321
3573
|
}
|
|
3322
3574
|
for (const entry of entries) {
|
|
3323
|
-
const fullPath =
|
|
3575
|
+
const fullPath = path13.join(dir, entry.name);
|
|
3324
3576
|
if (entry.isDirectory()) {
|
|
3325
3577
|
const nested = await scanJsonFiles(fullPath, baseDir);
|
|
3326
3578
|
results.push(...nested);
|
|
3327
3579
|
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
3328
|
-
const relPath =
|
|
3580
|
+
const relPath = path13.relative(baseDir, fullPath);
|
|
3329
3581
|
const nodePath = relPath.replace(/\\/g, "/").replace(/\.json$/, "");
|
|
3330
3582
|
results.push(nodePath);
|
|
3331
3583
|
}
|
|
@@ -3333,13 +3585,13 @@ async function scanJsonFiles(dir, baseDir) {
|
|
|
3333
3585
|
return results;
|
|
3334
3586
|
}
|
|
3335
3587
|
async function removeEmptyParents(filePath, stopDir) {
|
|
3336
|
-
let dir =
|
|
3588
|
+
let dir = path13.dirname(filePath);
|
|
3337
3589
|
while (dir !== stopDir && dir.startsWith(stopDir)) {
|
|
3338
3590
|
try {
|
|
3339
3591
|
const entries = await readdir6(dir);
|
|
3340
3592
|
if (entries.length === 0) {
|
|
3341
3593
|
await rm2(dir, { recursive: true });
|
|
3342
|
-
dir =
|
|
3594
|
+
dir = path13.dirname(dir);
|
|
3343
3595
|
} else {
|
|
3344
3596
|
break;
|
|
3345
3597
|
}
|
|
@@ -3360,12 +3612,12 @@ async function readNodeDriftState(yggRoot, nodePath) {
|
|
|
3360
3612
|
}
|
|
3361
3613
|
async function writeNodeDriftState(yggRoot, nodePath, nodeState) {
|
|
3362
3614
|
const filePath = nodeStatePath(yggRoot, nodePath);
|
|
3363
|
-
await mkdir3(
|
|
3615
|
+
await mkdir3(path13.dirname(filePath), { recursive: true });
|
|
3364
3616
|
const content = JSON.stringify(nodeState, null, 2) + "\n";
|
|
3365
3617
|
await writeFile5(filePath, content, "utf-8");
|
|
3366
3618
|
}
|
|
3367
3619
|
async function garbageCollectDriftState(yggRoot, validNodePaths) {
|
|
3368
|
-
const driftDir =
|
|
3620
|
+
const driftDir = path13.join(yggRoot, DRIFT_STATE_DIR);
|
|
3369
3621
|
const allNodePaths = await scanJsonFiles(driftDir, driftDir);
|
|
3370
3622
|
const removed = [];
|
|
3371
3623
|
for (const nodePath of allNodePaths) {
|
|
@@ -3379,7 +3631,7 @@ async function garbageCollectDriftState(yggRoot, validNodePaths) {
|
|
|
3379
3631
|
return removed.sort();
|
|
3380
3632
|
}
|
|
3381
3633
|
async function readDriftState(yggRoot) {
|
|
3382
|
-
const driftPath =
|
|
3634
|
+
const driftPath = path13.join(yggRoot, DRIFT_STATE_DIR);
|
|
3383
3635
|
let driftStat;
|
|
3384
3636
|
try {
|
|
3385
3637
|
driftStat = await stat5(driftPath);
|
|
@@ -3420,7 +3672,7 @@ async function readDriftState(yggRoot) {
|
|
|
3420
3672
|
|
|
3421
3673
|
// src/utils/hash.ts
|
|
3422
3674
|
import { readFile as readFile15, readdir as readdir7, stat as stat6 } from "fs/promises";
|
|
3423
|
-
import
|
|
3675
|
+
import path14 from "path";
|
|
3424
3676
|
import { createHash } from "crypto";
|
|
3425
3677
|
import { createRequire } from "module";
|
|
3426
3678
|
var require2 = createRequire(import.meta.url);
|
|
@@ -3432,7 +3684,7 @@ async function hashFile(filePath) {
|
|
|
3432
3684
|
async function loadRootGitignoreStack(projectRoot) {
|
|
3433
3685
|
if (!projectRoot) return [];
|
|
3434
3686
|
try {
|
|
3435
|
-
const content = await readFile15(
|
|
3687
|
+
const content = await readFile15(path14.join(projectRoot, ".gitignore"), "utf-8");
|
|
3436
3688
|
const matcher = ignoreFactory();
|
|
3437
3689
|
matcher.add(content);
|
|
3438
3690
|
return [{ basePath: projectRoot, matcher }];
|
|
@@ -3442,7 +3694,7 @@ async function loadRootGitignoreStack(projectRoot) {
|
|
|
3442
3694
|
}
|
|
3443
3695
|
function isIgnoredByStack(candidatePath, stack) {
|
|
3444
3696
|
for (const { basePath, matcher } of stack) {
|
|
3445
|
-
const relativePath =
|
|
3697
|
+
const relativePath = path14.relative(basePath, candidatePath);
|
|
3446
3698
|
if (relativePath === "" || relativePath.startsWith("..")) continue;
|
|
3447
3699
|
if (matcher.ignores(relativePath) || matcher.ignores(relativePath + "/")) return true;
|
|
3448
3700
|
}
|
|
@@ -3457,7 +3709,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
|
|
|
3457
3709
|
const gitignoreStack = await loadRootGitignoreStack(projectRoot);
|
|
3458
3710
|
const allFiles = [];
|
|
3459
3711
|
for (const tf of trackedFiles) {
|
|
3460
|
-
const absPath =
|
|
3712
|
+
const absPath = path14.join(projectRoot, tf.path);
|
|
3461
3713
|
try {
|
|
3462
3714
|
const st = await stat6(absPath);
|
|
3463
3715
|
if (st.isDirectory()) {
|
|
@@ -3467,7 +3719,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
|
|
|
3467
3719
|
});
|
|
3468
3720
|
for (const entry of dirEntries) {
|
|
3469
3721
|
allFiles.push({
|
|
3470
|
-
relPath:
|
|
3722
|
+
relPath: path14.join(tf.path, entry.relPath).replace(/\\/g, "/"),
|
|
3471
3723
|
absPath: entry.absPath,
|
|
3472
3724
|
mtimeMs: entry.mtimeMs
|
|
3473
3725
|
});
|
|
@@ -3507,7 +3759,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
|
|
|
3507
3759
|
async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
|
|
3508
3760
|
let stack = options.gitignoreStack ?? [];
|
|
3509
3761
|
try {
|
|
3510
|
-
const localContent = await readFile15(
|
|
3762
|
+
const localContent = await readFile15(path14.join(directoryPath, ".gitignore"), "utf-8");
|
|
3511
3763
|
const localMatcher = ignoreFactory();
|
|
3512
3764
|
localMatcher.add(localContent);
|
|
3513
3765
|
stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
|
|
@@ -3517,7 +3769,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
|
|
|
3517
3769
|
const dirs = [];
|
|
3518
3770
|
const files = [];
|
|
3519
3771
|
for (const entry of entries) {
|
|
3520
|
-
const absoluteChildPath =
|
|
3772
|
+
const absoluteChildPath = path14.join(directoryPath, entry.name);
|
|
3521
3773
|
if (isIgnoredByStack(absoluteChildPath, stack)) continue;
|
|
3522
3774
|
if (entry.isDirectory()) dirs.push(absoluteChildPath);
|
|
3523
3775
|
else if (entry.isFile()) files.push(absoluteChildPath);
|
|
@@ -3530,7 +3782,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
|
|
|
3530
3782
|
Promise.all(files.map(async (f) => {
|
|
3531
3783
|
const fileStat = await stat6(f);
|
|
3532
3784
|
return {
|
|
3533
|
-
relPath:
|
|
3785
|
+
relPath: path14.relative(rootDirectoryPath, f),
|
|
3534
3786
|
absPath: f,
|
|
3535
3787
|
mtimeMs: fileStat.mtimeMs
|
|
3536
3788
|
};
|
|
@@ -3543,14 +3795,14 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
|
|
|
3543
3795
|
}
|
|
3544
3796
|
|
|
3545
3797
|
// src/core/context-files.ts
|
|
3546
|
-
import
|
|
3798
|
+
import path15 from "path";
|
|
3547
3799
|
var STRUCTURAL_RELATION_TYPES2 = /* @__PURE__ */ new Set(["uses", "calls", "extends", "implements"]);
|
|
3548
3800
|
function collectTrackedFiles(node, graph) {
|
|
3549
3801
|
const seen = /* @__PURE__ */ new Set();
|
|
3550
3802
|
const result = [];
|
|
3551
|
-
const projectRoot =
|
|
3552
|
-
const yggPrefix =
|
|
3553
|
-
const yggPrefixNormalized = yggPrefix.split(
|
|
3803
|
+
const projectRoot = path15.dirname(graph.rootPath);
|
|
3804
|
+
const yggPrefix = path15.relative(projectRoot, graph.rootPath);
|
|
3805
|
+
const yggPrefixNormalized = yggPrefix.split(path15.sep).join("/");
|
|
3554
3806
|
const configArtifactKeys = new Set(Object.keys(graph.config.artifacts ?? {}));
|
|
3555
3807
|
function addFile(filePath, category) {
|
|
3556
3808
|
if (seen.has(filePath)) return;
|
|
@@ -3662,8 +3914,8 @@ function collectParticipatingFlows2(graph, node, ancestors) {
|
|
|
3662
3914
|
}
|
|
3663
3915
|
|
|
3664
3916
|
// src/core/drift-detector.ts
|
|
3665
|
-
import { access as
|
|
3666
|
-
import
|
|
3917
|
+
import { access as access3 } from "fs/promises";
|
|
3918
|
+
import path16 from "path";
|
|
3667
3919
|
function getChildMappingExclusions(graph, nodePath) {
|
|
3668
3920
|
const node = graph.nodes.get(nodePath);
|
|
3669
3921
|
if (!node) return [];
|
|
@@ -3685,7 +3937,7 @@ function getChildMappingExclusions(graph, nodePath) {
|
|
|
3685
3937
|
return exclusions;
|
|
3686
3938
|
}
|
|
3687
3939
|
async function detectDrift(graph, filterNodePath) {
|
|
3688
|
-
const projectRoot =
|
|
3940
|
+
const projectRoot = path16.dirname(graph.rootPath);
|
|
3689
3941
|
const driftState = await readDriftState(graph.rootPath);
|
|
3690
3942
|
const entries = [];
|
|
3691
3943
|
for (const [nodePath, node] of graph.nodes) {
|
|
@@ -3767,16 +4019,16 @@ async function detectDrift(graph, filterNodePath) {
|
|
|
3767
4019
|
};
|
|
3768
4020
|
}
|
|
3769
4021
|
function categorizeFile(filePath, _rootPath, projectRoot) {
|
|
3770
|
-
const yggPrefix =
|
|
3771
|
-
const normalizedPrefix = yggPrefix.split(
|
|
4022
|
+
const yggPrefix = path16.relative(projectRoot, _rootPath);
|
|
4023
|
+
const normalizedPrefix = yggPrefix.split(path16.sep).join("/");
|
|
3772
4024
|
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
3773
4025
|
return normalizedFilePath.startsWith(normalizedPrefix) ? "graph" : "source";
|
|
3774
4026
|
}
|
|
3775
4027
|
async function allPathsMissing(projectRoot, mappingPaths) {
|
|
3776
4028
|
for (const mp of mappingPaths) {
|
|
3777
|
-
const absPath =
|
|
4029
|
+
const absPath = path16.join(projectRoot, mp);
|
|
3778
4030
|
try {
|
|
3779
|
-
await
|
|
4031
|
+
await access3(absPath);
|
|
3780
4032
|
return false;
|
|
3781
4033
|
} catch {
|
|
3782
4034
|
}
|
|
@@ -3784,7 +4036,7 @@ async function allPathsMissing(projectRoot, mappingPaths) {
|
|
|
3784
4036
|
return true;
|
|
3785
4037
|
}
|
|
3786
4038
|
async function syncDriftState(graph, nodePath) {
|
|
3787
|
-
const projectRoot =
|
|
4039
|
+
const projectRoot = path16.dirname(graph.rootPath);
|
|
3788
4040
|
const node = graph.nodes.get(nodePath);
|
|
3789
4041
|
if (!node) throw new Error(`Node not found: ${nodePath}`);
|
|
3790
4042
|
if (!node.meta.mapping) throw new Error(`Node has no mapping: ${nodePath}`);
|
|
@@ -3794,12 +4046,39 @@ async function syncDriftState(graph, nodePath) {
|
|
|
3794
4046
|
const storedFileData = existingEntry?.files ? { hashes: existingEntry.files, mtimes: existingEntry.mtimes ?? {} } : void 0;
|
|
3795
4047
|
const { canonicalHash, fileHashes, fileMtimes } = await hashTrackedFiles(projectRoot, trackedFiles, storedFileData, excludePrefixes);
|
|
3796
4048
|
const previousHash = existingEntry?.hash;
|
|
4049
|
+
let sourceOnlyChange = false;
|
|
4050
|
+
if (previousHash && previousHash !== canonicalHash && existingEntry?.files) {
|
|
4051
|
+
let hasSourceChange = false;
|
|
4052
|
+
let hasGraphChange = false;
|
|
4053
|
+
const yggPrefix = path16.relative(projectRoot, graph.rootPath).split(path16.sep).join("/");
|
|
4054
|
+
for (const [filePath, hash] of Object.entries(fileHashes)) {
|
|
4055
|
+
const storedHash = existingEntry.files[filePath];
|
|
4056
|
+
if (storedHash && storedHash === hash) continue;
|
|
4057
|
+
if (!storedHash || storedHash !== hash) {
|
|
4058
|
+
if (filePath.startsWith(yggPrefix)) {
|
|
4059
|
+
hasGraphChange = true;
|
|
4060
|
+
} else {
|
|
4061
|
+
hasSourceChange = true;
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
for (const storedPath of Object.keys(existingEntry.files)) {
|
|
4066
|
+
if (!(storedPath in fileHashes)) {
|
|
4067
|
+
if (storedPath.startsWith(yggPrefix)) {
|
|
4068
|
+
hasGraphChange = true;
|
|
4069
|
+
} else {
|
|
4070
|
+
hasSourceChange = true;
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
sourceOnlyChange = hasSourceChange && !hasGraphChange;
|
|
4075
|
+
}
|
|
3797
4076
|
await writeNodeDriftState(graph.rootPath, nodePath, {
|
|
3798
4077
|
hash: canonicalHash,
|
|
3799
4078
|
files: fileHashes,
|
|
3800
4079
|
mtimes: fileMtimes
|
|
3801
4080
|
});
|
|
3802
|
-
return { previousHash, currentHash: canonicalHash };
|
|
4081
|
+
return { previousHash, currentHash: canonicalHash, sourceOnlyChange };
|
|
3803
4082
|
}
|
|
3804
4083
|
|
|
3805
4084
|
// src/cli/drift.ts
|
|
@@ -3972,13 +4251,20 @@ function registerDriftSyncCommand(program2) {
|
|
|
3972
4251
|
}
|
|
3973
4252
|
continue;
|
|
3974
4253
|
}
|
|
3975
|
-
const { previousHash, currentHash } = await syncDriftState(graph, np);
|
|
4254
|
+
const { previousHash, currentHash, sourceOnlyChange } = await syncDriftState(graph, np);
|
|
3976
4255
|
process.stdout.write(chalk3.green(`Synchronized: ${np}
|
|
3977
4256
|
`));
|
|
3978
4257
|
process.stdout.write(
|
|
3979
4258
|
` Hash: ${previousHash ? previousHash.slice(0, 8) : "none"} -> ${currentHash.slice(0, 8)}
|
|
3980
4259
|
`
|
|
3981
4260
|
);
|
|
4261
|
+
if (sourceOnlyChange) {
|
|
4262
|
+
process.stderr.write(
|
|
4263
|
+
chalk3.yellow(` \u26A0 W018: Source files changed but graph artifacts are unchanged for '${np}'.
|
|
4264
|
+
`) + chalk3.yellow(` Update artifacts BEFORE syncing \u2014 drift-sync without artifact update hides staleness.
|
|
4265
|
+
`)
|
|
4266
|
+
);
|
|
4267
|
+
}
|
|
3982
4268
|
}
|
|
3983
4269
|
if (options.all) {
|
|
3984
4270
|
const validPaths = new Set(nodesToSync);
|
|
@@ -4150,73 +4436,6 @@ function printNode(node, prefix, isLast, depth, maxDepth) {
|
|
|
4150
4436
|
}
|
|
4151
4437
|
}
|
|
4152
4438
|
|
|
4153
|
-
// src/cli/owner.ts
|
|
4154
|
-
import path16 from "path";
|
|
4155
|
-
import { access as access3 } from "fs/promises";
|
|
4156
|
-
function normalizeForMatch(inputPath) {
|
|
4157
|
-
return inputPath.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
4158
|
-
}
|
|
4159
|
-
function findOwner(graph, projectRoot, rawPath) {
|
|
4160
|
-
const file = normalizeForMatch(normalizeProjectRelativePath(projectRoot, rawPath));
|
|
4161
|
-
let best = null;
|
|
4162
|
-
for (const [nodePath, node] of graph.nodes) {
|
|
4163
|
-
const mappingPaths = normalizeMappingPaths(node.meta.mapping).map(normalizeForMatch).filter((mappingPath) => mappingPath.length > 0);
|
|
4164
|
-
for (const mappingPath of mappingPaths) {
|
|
4165
|
-
if (file === mappingPath) {
|
|
4166
|
-
return { file, nodePath, mappingPath, direct: true };
|
|
4167
|
-
}
|
|
4168
|
-
if (file.startsWith(mappingPath + "/")) {
|
|
4169
|
-
if (!best || best && mappingPath.length > best.mappingPath.length) {
|
|
4170
|
-
best = { nodePath, mappingPath, exact: false };
|
|
4171
|
-
}
|
|
4172
|
-
}
|
|
4173
|
-
}
|
|
4174
|
-
}
|
|
4175
|
-
return best ? { file, nodePath: best.nodePath, mappingPath: best.mappingPath, direct: false } : { file, nodePath: null };
|
|
4176
|
-
}
|
|
4177
|
-
function registerOwnerCommand(program2) {
|
|
4178
|
-
program2.command("owner").description("Find which graph node owns a source file").requiredOption("--file <path>", "File path (relative to repository root)").action(async (options) => {
|
|
4179
|
-
try {
|
|
4180
|
-
const cwd = process.cwd();
|
|
4181
|
-
const graph = await loadGraph(cwd);
|
|
4182
|
-
const repoRoot = projectRootFromGraph(graph.rootPath);
|
|
4183
|
-
const rawPath = options.file.trim();
|
|
4184
|
-
const absolute = path16.resolve(cwd, rawPath);
|
|
4185
|
-
const repoRelative = path16.relative(repoRoot, absolute).split(path16.sep).join("/");
|
|
4186
|
-
const result = findOwner(graph, repoRoot, repoRelative);
|
|
4187
|
-
if (!result.nodePath) {
|
|
4188
|
-
const absPath = path16.resolve(repoRoot, result.file);
|
|
4189
|
-
let exists = true;
|
|
4190
|
-
try {
|
|
4191
|
-
await access3(absPath);
|
|
4192
|
-
} catch {
|
|
4193
|
-
exists = false;
|
|
4194
|
-
}
|
|
4195
|
-
if (exists) {
|
|
4196
|
-
process.stdout.write(`${result.file} -> no graph coverage
|
|
4197
|
-
`);
|
|
4198
|
-
} else {
|
|
4199
|
-
process.stdout.write(`${result.file} -> no graph coverage (file not found)
|
|
4200
|
-
`);
|
|
4201
|
-
}
|
|
4202
|
-
} else {
|
|
4203
|
-
process.stdout.write(`${result.file} -> ${result.nodePath}
|
|
4204
|
-
`);
|
|
4205
|
-
if (result.direct === false && result.mappingPath) {
|
|
4206
|
-
process.stdout.write(
|
|
4207
|
-
` File has no direct mapping; context comes from ancestor directory ${result.mappingPath}. Use: yg build-context --node ${result.nodePath}
|
|
4208
|
-
`
|
|
4209
|
-
);
|
|
4210
|
-
}
|
|
4211
|
-
}
|
|
4212
|
-
} catch (error) {
|
|
4213
|
-
process.stderr.write(`Error: ${error.message}
|
|
4214
|
-
`);
|
|
4215
|
-
process.exit(1);
|
|
4216
|
-
}
|
|
4217
|
-
});
|
|
4218
|
-
}
|
|
4219
|
-
|
|
4220
4439
|
// src/core/dependency-resolver.ts
|
|
4221
4440
|
import { execSync } from "child_process";
|
|
4222
4441
|
import path17 from "path";
|
|
@@ -4633,23 +4852,39 @@ Total scope: ${sorted.length + indirectPaths.length} nodes
|
|
|
4633
4852
|
}
|
|
4634
4853
|
}
|
|
4635
4854
|
function registerImpactCommand(program2) {
|
|
4636
|
-
program2.command("impact").description("Show reverse dependency impact for a node, aspect, or flow").option("--node <path>", "Node path relative to .yggdrasil/model/").option("--aspect <id>", "Aspect id (directory path under aspects/)").option("--flow <name>", "Flow name (directory name under flows/)").option("--method <name>", "Filter impact to dependents consuming a specific method (requires --node)").option("--simulate", "Simulate context package impact (compare HEAD vs current)").action(
|
|
4855
|
+
program2.command("impact").description("Show reverse dependency impact for a node, aspect, or flow").option("--node <path>", "Node path relative to .yggdrasil/model/").option("--file <file-path>", "Source file path \u2014 resolves owner node automatically").option("--aspect <id>", "Aspect id (directory path under aspects/)").option("--flow <name>", "Flow name (directory name under flows/)").option("--method <name>", "Filter impact to dependents consuming a specific method (requires --node or --file)").option("--simulate", "Simulate context package impact (compare HEAD vs current)").action(
|
|
4637
4856
|
async (options) => {
|
|
4638
4857
|
try {
|
|
4639
|
-
|
|
4858
|
+
if (options.node && options.file) {
|
|
4859
|
+
process.stderr.write("Error: '--node' and '--file' are mutually exclusive\n");
|
|
4860
|
+
process.exit(1);
|
|
4861
|
+
}
|
|
4862
|
+
const modeCount = [options.node || options.file, options.aspect, options.flow].filter(Boolean).length;
|
|
4640
4863
|
if (modeCount === 0) {
|
|
4641
4864
|
process.stderr.write(
|
|
4642
|
-
"Error: one of --node, --aspect, or --flow is required\n"
|
|
4865
|
+
"Error: one of --node, --file, --aspect, or --flow is required\n"
|
|
4643
4866
|
);
|
|
4644
4867
|
process.exit(1);
|
|
4645
4868
|
}
|
|
4646
4869
|
if (modeCount > 1) {
|
|
4647
4870
|
process.stderr.write(
|
|
4648
|
-
"Error: --node, --aspect, and --flow are mutually exclusive\n"
|
|
4871
|
+
"Error: --node/--file, --aspect, and --flow are mutually exclusive\n"
|
|
4649
4872
|
);
|
|
4650
4873
|
process.exit(1);
|
|
4651
4874
|
}
|
|
4652
4875
|
const graph = await loadGraph(process.cwd());
|
|
4876
|
+
if (options.file) {
|
|
4877
|
+
const repoRoot = projectRootFromGraph(graph.rootPath);
|
|
4878
|
+
const result = findOwner(graph, repoRoot, options.file.trim());
|
|
4879
|
+
if (!result.nodePath) {
|
|
4880
|
+
process.stderr.write(`${result.file} -> no graph coverage
|
|
4881
|
+
`);
|
|
4882
|
+
process.exit(1);
|
|
4883
|
+
}
|
|
4884
|
+
process.stderr.write(`${result.file} -> ${result.nodePath}
|
|
4885
|
+
`);
|
|
4886
|
+
options.node = result.nodePath;
|
|
4887
|
+
}
|
|
4653
4888
|
if (options.aspect) {
|
|
4654
4889
|
await handleAspectImpact(graph, options.aspect.trim(), options.simulate);
|
|
4655
4890
|
return;
|