@chrisdudek/yg 2.11.0 → 3.0.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 CHANGED
@@ -4,14 +4,14 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/cli/init.ts
7
- import { mkdir as mkdir2, writeFile as writeFile4, readdir as readdir2, readFile as readFile4, stat as stat2 } from "fs/promises";
8
- import path4 from "path";
7
+ import { mkdir as mkdir2, writeFile as writeFile5, readdir as readdir2, readFile as readFile5, stat as stat2 } from "fs/promises";
8
+ import path5 from "path";
9
9
  import { fileURLToPath } from "url";
10
10
  import { readFileSync } from "fs";
11
11
  import { gt as gt2, valid as valid2 } from "semver";
12
12
 
13
13
  // src/templates/default-config.ts
14
- var DEFAULT_CONFIG = `version: "2.0.0"
14
+ var DEFAULT_CONFIG = `version: "3.0.0"
15
15
 
16
16
  name: ""
17
17
 
@@ -25,20 +25,6 @@ node_types:
25
25
  infrastructure:
26
26
  description: "Guards, middleware, interceptors \u2014 invisible in call graphs but affect blast radius"
27
27
 
28
- artifacts:
29
- responsibility.md:
30
- required: always
31
- description: "What this node is responsible for, and what it is not"
32
- included_in_relations: true
33
- interface.md:
34
- required:
35
- when: has_incoming_relations
36
- description: "Public API \u2014 methods, parameters, return types, contracts, failure modes, exposed data structures"
37
- included_in_relations: true
38
- internals.md:
39
- required: never
40
- description: "How the node works and why \u2014 algorithms, business rules, state machines, design decisions with rejected alternatives"
41
-
42
28
  quality:
43
29
  min_artifact_length: 50
44
30
  max_direct_relations: 10
@@ -59,11 +45,32 @@ var PROTOCOL = `## PROTOCOL
59
45
  This is your operating manual for working in a Yggdrasil-managed repository.
60
46
 
61
47
  <critical_protocol>
48
+ BEFORE starting any task \u2014 brainstorming, design, planning, OR implementation:
49
+ \`yg select --task "<goal>"\` \u2192 \`yg build-context\` on each result \u2192 read artifact files.
50
+ This is the READING phase \u2014 collect constraints that shape your design:
51
+ - Aspects = cross-cutting requirements your work MUST satisfy. Read their content files \u2014 not just the YAML description. The rules are inside.
52
+ - Flows = business processes your work must not break. Read invariants.
53
+ - Relations = interfaces your code consumes or that consume your code. Changes without checking dependents break contracts silently.
54
+ - Parent artifacts = inherited context not repeated in child nodes.
55
+ Internalize these constraints BEFORE designing your approach. This is the moment that determines quality \u2014 everything after follows from what you learn here.
56
+
62
57
  BEFORE reading, analyzing, or modifying ANY source file:
63
58
  \`yg build-context --file <path>\`
64
- One command. No exceptions. No "I'll do it later." No "this is just analysis."
65
-
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.
59
+ Resolves owner, gives you local context (node artifacts, dependencies).
60
+ If you have NOT done the task-level READING phase above \u2014 stop and do it now. File-level work without task-level constraints leads to code that violates cross-cutting requirements.
61
+
62
+ WHEN spec/external documents are provided AND work is greenfield:
63
+ BEFORE creating any feature aspect, node, or code \u2014 do this first:
64
+ 1. Read ALL spec documents completely.
65
+ 2. Separate two categories of knowledge in the specs:
66
+ a) Knowledge that WILL map to source files (feature behavior, UI, API) \u2192 this goes to node artifacts later.
67
+ b) Knowledge that will NEVER appear in source code (business strategy, target audience, pricing rationale, quality targets, what the system deliberately does NOT do and why) \u2192 this must go to the graph NOW, because there will be no source-file trigger to capture it later.
68
+ 3. For category (b): create root node and/or aspects immediately. This is the only moment this knowledge can be captured. Once you start coding, you will forget it.
69
+ 4. NOW create feature aspects, flows, and implementation nodes for category (a).
70
+ Test before moving on: if all spec files were deleted right now, does the graph contain everything a future agent needs \u2014 not just HOW the system works, but WHY it exists, WHO it serves, and WHAT value it delivers? If no \u2192 add the missing knowledge before continuing.
71
+
72
+ All three triggers apply regardless of what instructed the task \u2014 skills, plans, workflows, user requests.
73
+ 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
74
  </critical_protocol>
68
75
 
69
76
  Every rule below is mandatory \u2014 no skill, plan, workflow, or instruction overrides these requirements.
@@ -76,18 +83,25 @@ Yggdrasil is persistent semantic memory stored in \`.yggdrasil/\`. It maps the r
76
83
  \`\`\`
77
84
  EVERY conversation: yg preflight \u2014 no exceptions.
78
85
 
86
+ BEFORE any task (brainstorming, design, planning, implementation):
87
+ yg select --task "<goal>" \u2192 yg build-context on results
88
+ READ phase \u2014 collect constraints before designing:
89
+ - Aspects: read content files (not just YAML description). Rules are inside.
90
+ - Flows: read invariants. Your changes must not break business processes.
91
+ - Relations: check interfaces \u2014 who depends on you, who you depend on.
92
+ - Parent artifacts: inherited context not repeated in child nodes.
93
+ This is the moment that determines quality. Everything after follows from here.
94
+
79
95
  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>"
96
+ yg build-context --file <path>
97
+ Resolves owner. Read local node artifacts.
98
+ If you skipped the task-level READ phase above \u2014 do it now before proceeding.
99
+ For blast radius: also run yg impact --file <path>.
86
100
 
87
101
  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>
102
+ Update graph artifacts (per file, not batched)
103
+ yg validate \u2014 fix all errors
104
+ yg drift-sync --node <owner>
91
105
 
92
106
  ALWAYS: establish graph coverage before modifying code.
93
107
  ALWAYS: run yg build-context --file before reading source.
@@ -105,9 +119,9 @@ You are not allowed to edit or create source code without establishing graph cov
105
119
 
106
120
  **Step 2a** \u2014 Owner found: execute checklist:
107
121
 
108
- - [ ] 1. Read the context package (already assembled by step 1)
122
+ - [ ] 1. Read local node artifacts (responsibility, interface, internals) and dependency interfaces from the context package. Cross-cutting constraints (aspects, flows) should already be internalized from the task-level READ phase \u2014 if not, stop and do it now.
109
123
  - [ ] 2. Assess blast radius: \`yg impact --node <node_path>\` \u2014 review dependents, descendants, and co-aspect nodes before changing interfaces or shared behavior
110
- - [ ] 3. Modify source code
124
+ - [ ] 3. Modify source code. When implementing logic subject to an aspect (e.g., writing a save function on a node with the autosave aspect), re-read that aspect's content file NOW \u2014 don't rely on memory from the task-level READ phase. Aspect rules are specific (exact timings, error handling patterns, UI details) and fade from working memory. Read them at the moment you need them.
111
125
  - [ ] 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\`.
112
126
  - [ ] 4b. If you split, merged, or renamed a node: run \`yg flows\` and update any flow \`nodes\` lists that referenced the old node path to point to the correct child/new nodes.
113
127
  - [ ] 5. Run \`yg validate\` \u2014 fix all errors (if unfixable after 3 attempts \u2192 stop, report to user)
@@ -125,12 +139,14 @@ You are not allowed to edit or create source code without establishing graph cov
125
139
 
126
140
  *Greenfield (new code):* Only Option A. Blackbox is forbidden for new code. Follow the graph-first workflow:
127
141
 
142
+ 0. **If spec/external documents exist:** route ALL knowledge from specs to the graph per the Information Routing table BEFORE any feature work. Use the appropriate location for each piece of knowledge \u2014 root node, aspects, flows, or node artifacts depending on its nature.
128
143
  1. Create aspects first (cross-cutting requirements the new code must satisfy)
129
144
  2. Create flows if the code participates in a business process
130
145
  3. Create nodes with full artifacts \u2014 description in \`yg-node.yaml\`, responsibility, interface, internals
131
146
  4. Review the context package (\`yg build-context\`) \u2014 it is now the behavioral specification
132
- 5. Implement code that satisfies the specification
133
- 6. The graph specifies WHAT and WHY; the code implements HOW (framework APIs, library choices)
147
+ 5. Implement code that satisfies the specification. Every source file must be mapped \u2014 including shared utilities, types, and helpers.
148
+ 6. After implementing each node, write \`internals.md\` with a ## Decisions section. Record every design choice: "Chose X over Y because Z." This is required in greenfield \u2014 not optional. Every node has design decisions (data model shape, algorithm, library, UI pattern). If you made a choice between alternatives, document it now \u2014 you will not remember later.
149
+ 7. The graph specifies WHAT and WHY; the code implements HOW (framework APIs, library choices)
134
150
 
135
151
  **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.
136
152
 
@@ -185,13 +201,15 @@ Result: graph is stale, next agent asks user the same questions
185
201
  User: "Here are the spec docs. Implement the admin blog editor."
186
202
 
187
203
  1. Read ALL spec docs (blog-editor.md, autosave.md, user-persona.md, version-history.md)
188
- 2. Extract cross-cutting patterns \u2192 create aspects (admin-ux-rules, autosave, version-history) if they don't exist
189
- 3. Create flow if the blog participates in a business process
190
- 4. Create node admin/blog with artifacts populated from spec (responsibility, interface, internals)
191
- 5. Run yg build-context \u2192 the context package is now the behavioral specification
192
- 6. Implement code that satisfies the specification
193
- 7. Update artifacts with any implementation details that emerged during coding
194
- 8. yg validate, yg drift-sync
204
+ 2. Route all knowledge from spec docs to the graph per Information Routing table \u2014 business context to root node artifacts, cross-cutting requirements to aspects, business processes to flows, feature specs to node artifacts
205
+ 3. Extract cross-cutting patterns \u2192 create aspects (admin-ux-rules, autosave, version-history) if they don't exist
206
+ 4. Create flow if the blog participates in a business process
207
+ 5. Create node admin/blog with artifacts populated from spec (responsibility, interface, internals)
208
+ 6. Run yg build-context \u2192 the context package is now the behavioral specification
209
+ 7. Implement code that satisfies the specification
210
+ 8. Update artifacts with any implementation details that emerged during coding
211
+ 9. yg validate, yg drift-sync
212
+ Test: if spec files disappeared today, does the graph contain everything a future agent needs to understand the system?
195
213
 
196
214
  </example_correct>
197
215
 
@@ -199,13 +217,36 @@ User: "Here are the spec docs. Implement the admin blog editor."
199
217
 
200
218
  User: "Here are the spec docs. Implement the admin blog editor."
201
219
 
202
- 1. Read blog-editor.md spec
203
- 2. Implement all the code \u2190 WRONG: spec knowledge not captured in graph
204
- 3. Create node admin-pages, map 20 admin files \u2190 WRONG: too wide, W017
220
+ 1. Read spec docs
221
+ 2. Create aspects and flow for the blog feature \u2190 INCOMPLETE: knowledge from spec docs not routed to graph per Information Routing table
222
+ 3. Create node admin/blog, implement code
205
223
  4. Write responsibility.md summarizing what the code does \u2190 WRONG: describes code, not spec intent
206
- 5. Business context (persona, UX rules, autosave rationale) lost \u2190 WRONG: spec was input, not persisted
224
+ 5. Knowledge from spec docs lost \u2190 WRONG: spec treated as consumed input, not persisted to graph
207
225
 
208
- 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.
226
+ Result: graph mirrors code structure but misses everything spec docs contained that has no corresponding source file. Future agent must re-read spec files or ask the user.
227
+
228
+ </example_wrong>
229
+
230
+ <example_correct>
231
+
232
+ User: "Let's design a soft delete feature for blog posts"
233
+
234
+ 1. yg select --task "blog soft delete" \u2192 find relevant nodes
235
+ 2. yg build-context on each result \u2192 read ALL artifacts (aspects, flows, conventions)
236
+ 3. Now read source files WITH graph context
237
+ 4. Propose design informed by admin-ux-rules, existing flows, database conventions
238
+
239
+ </example_correct>
240
+
241
+ <example_wrong>
242
+
243
+ User: "Let's design a soft delete feature for blog posts"
244
+
245
+ 1. Read BlogEditor.tsx to understand current behavior \u2190 WRONG: no graph context
246
+ 2. Read database schema \u2190 WRONG: graph has conventions, aspects, flows
247
+ 3. Propose design based on raw code \u2190 WRONG: missed admin-ux-rules aspect, existing flows
248
+
249
+ Result: design misses cross-cutting requirements the graph already captured.
209
250
 
210
251
  </example_wrong>
211
252
 
@@ -269,7 +310,7 @@ var REFERENCE = `## REFERENCE
269
310
 
270
311
  \`\`\`
271
312
  .yggdrasil/
272
- yg-config.yaml \u2190 version, vocabulary, node types, artifact rules, required aspects
313
+ yg-config.yaml \u2190 version, vocabulary, node types, required aspects
273
314
  model/ \u2190 what exists: nodes, hierarchy, relations, file mappings
274
315
  aspects/ \u2190 what must: cross-cutting requirements with rationale and guidance
275
316
  flows/ \u2190 why and in what process: business processes with node participation
@@ -295,7 +336,7 @@ Three artifacts capture node knowledge at three levels:
295
336
 
296
337
  **Enrichment priority (when adding incrementally):** \`interface.md\` first (highest cross-module ROI \u2014 contracts enable other nodes to reason about interactions), then \`responsibility.md\` (identity and boundaries), then \`internals.md\` (depth for complex nodes). A node with only \`interface.md\` provides more cross-module value than one with only \`internals.md\`.
297
338
 
298
- Projects can define additional artifact types in \`yg-config.yaml\` under \`artifacts\`. Each custom artifact has a \`description\` (tells you what to write), a \`required\` condition (\`always\`, \`never\`, \`when: has_incoming_relations\`, \`when: has_aspect:<id>\`), and an \`included_in_relations\` flag (if true, included in dependency context packages for structural relations). The three standard artifacts are always present in config. Check \`yg-config.yaml\` to see all defined artifacts for the project.
339
+ These three artifacts are built into the CLI and are not configurable. \`responsibility.md\` is always required, \`interface.md\` is required when the node has incoming relations, and \`internals.md\` is always optional.
299
340
 
300
341
  ### Context Assembly
301
342
 
@@ -310,9 +351,14 @@ Projects can define additional artifact types in \`yg-config.yaml\` under \`arti
310
351
 
311
352
  All artifact paths are relative to \`.yggdrasil/\` \u2014 construct full path as \`.yggdrasil/<path>\`.
312
353
 
313
- **Default mode (paths-only):** Use for all graph operations. Read the YAML map first \u2014 start with the \`glossary\` to understand aspects and flows, then the \`node\` section for the target. Read artifact files inline on each element using the Read tool. For quick orientation (scoping, blast radius assessment), the map alone is sufficient. For implementation or modification, read all artifact files before changing code.
354
+ **Default mode (paths-only):** Use for all graph operations. Read the YAML map, then read artifact files with purpose:
314
355
 
315
- The glossary at the top defines all aspects and flows \u2014 read it first to understand IDs used throughout.
356
+ 1. **Glossary first** \u2014 defines aspects and flows. Aspects are constraints your implementation must satisfy (not background reading). Flows are business processes whose invariants you must not break.
357
+ 2. **Node section** \u2014 your target's own artifacts. Read before modifying.
358
+ 3. **Hierarchy** \u2014 parent artifacts contain inherited requirements not repeated in child nodes.
359
+ 4. **Dependencies** \u2014 interfaces you consume or that consume you. Read before changing contracts.
360
+
361
+ A typical context package is ~8K tokens (less than a single source file). Read ALL artifact files listed \u2014 the cost is low, the risk of skipping is high (violating constraints you didn't know about).
316
362
 
317
363
  **Full mode (\`--full\`):** Use only when you cannot read files individually \u2014 e.g., when pasting context into a prompt, sharing with a user, or when you have no Read tool available.
318
364
 
@@ -322,7 +368,7 @@ Artifact paths are stable identifiers within a session. When building context fo
322
368
 
323
369
  When you encounter information, route it to the correct location:
324
370
 
325
- - **Specific to this node** \u2192 local node artifact (check \`yg-config.yaml artifacts\` for available types)
371
+ - **Specific to this node** \u2192 local node artifact (\`responsibility.md\`, \`interface.md\`, or \`internals.md\` depending on the knowledge type)
326
372
  - **Rule for many nodes** \u2192 aspect (\`aspects/<id>/\` with \`yg-aspect.yaml\` + content \`.md\` files). If applies to ALL nodes of a type \u2192 \`node_types.<type>.required_aspects\` in \`yg-config.yaml\`
327
373
  - **Business process** \u2192 flow (\`flows/<name>/\` with \`yg-flow.yaml\` + \`description.md\`). Ask user if process unclear.
328
374
  - **Shared across a domain** \u2192 parent node artifact. Children receive it through hierarchy.
@@ -433,6 +479,7 @@ yg owner --file <path> Find the node that owns this file (quick che
433
479
  yg build-context --file <path> Resolve owner + assemble context in one step.
434
480
  yg build-context --node <path> Assemble context map for a known node.
435
481
  yg build-context --node <path> --full Same map + file contents appended below separator.
482
+ yg build-context --file <path> --self Own artifacts only (no hierarchy/deps/aspects/flows).
436
483
  yg tree [--root <path>] [--depth N] Print graph structure.
437
484
  yg aspects List aspects with metadata (YAML output).
438
485
  yg flows List flows with metadata (YAML output).
@@ -457,7 +504,7 @@ yg drift-sync --node <path> [--recursive] | --all
457
504
 
458
505
  | What you have | Where it goes |
459
506
  |---|---|
460
- | Information specific to this node | Local node artifact (check \`yg-config.yaml artifacts\` for types) |
507
+ | Information specific to this node | Local node artifact (\`responsibility.md\`, \`interface.md\`, or \`internals.md\`) |
461
508
  | Rule that applies to many nodes | Aspect (content \`.md\` files in \`aspects/<id>/\`) |
462
509
  | Architectural invariant for a node type | Required aspect in \`yg-config.yaml node_types\` |
463
510
  | Business process participation | Flow (\`yg-flow.yaml nodes\`) |
@@ -532,11 +579,15 @@ What matters is the ACTION you are performing, not what instructed it. If the ac
532
579
  | "Flows can wait until I understand the full system" | Flows capture business processes from specs. Create them BEFORE implementing \u2014 they are part of the specification, not an afterthought. |
533
580
  | "I split the node but the flow still works" | Flow participants reference specific node paths. After a split, old paths are stale. Run \`yg flows\` and update. |
534
581
  | "This is just CRUD, not a business process" | A user performing a sequence of steps toward a goal IS a business process \u2014 even single-actor workflows (publish blog, manage portfolio, fulfill order). Create a flow. |
582
+ | "The context package is too large to read" | A typical context package is ~8K tokens \u2014 less than one source file. Read ALL of it. |
583
+ | "I have a plan, I don't need graph context" | A plan is not a substitute for graph context. Plans capture task steps; the graph captures cross-cutting aspects, flows, and conventions that plans may not repeat. Always run \`build-context\`. |
584
+ | "The user told me what to do, that's my plan" | A verbal instruction is not a written plan. And even a written plan does not exempt you from the graph protocol. |
535
585
 
536
586
  ### Failure States
537
587
 
538
588
  You have broken Yggdrasil if you do any of the following:
539
589
 
590
+ - \u274C Started brainstorming, design, or planning without running \`yg select --task\` and reading graph context first. The graph contains aspects, flows, and conventions that MUST inform design decisions.
540
591
  - \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).
541
592
  - \u274C Modified source code without updating graph artifacts before moving to the next file, or vice versa.
542
593
  - \u274C Batched graph updates to "do later" \u2014 deferred = forgotten. Update after EACH file.
@@ -566,7 +617,7 @@ Per area checklist:
566
617
  - [ ] 2. Determine node granularity \u2014 propose to user if unclear
567
618
  - [ ] 3. Create node directory, read \`schemas/yg-node.yaml\`, create \`yg-node.yaml\`
568
619
  - [ ] 3b. Write \`description\` in \`yg-node.yaml\` \u2014 a short summary of what the node does
569
- - [ ] 4. Analyze source \u2014 for each artifact type in \`yg-config.yaml artifacts\`: extract content, do not invent
620
+ - [ ] 4. Analyze source \u2014 write \`responsibility.md\`, \`interface.md\`, and \`internals.md\` from code analysis, do not invent
570
621
  - [ ] 5. Identify relations \u2014 add to \`yg-node.yaml\`
571
622
  - [ ] 6. Identify cross-cutting requirements \u2014 add matching aspects, create if needed
572
623
  - [ ] 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\`
@@ -1188,38 +1239,73 @@ async function transformSingleNode(filePath, actions, warnings) {
1188
1239
  }
1189
1240
  }
1190
1241
 
1242
+ // src/migrations/to-3.0.0.ts
1243
+ import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
1244
+ import path4 from "path";
1245
+ import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
1246
+ var STANDARD_ARTIFACT_NAMES = /* @__PURE__ */ new Set([
1247
+ "responsibility.md",
1248
+ "interface.md",
1249
+ "internals.md"
1250
+ ]);
1251
+ async function migrateTo3(yggRoot) {
1252
+ const actions = [];
1253
+ const warnings = [];
1254
+ const configPath = path4.join(yggRoot, "yg-config.yaml");
1255
+ const content = await readFile4(configPath, "utf-8");
1256
+ const raw = parseYaml3(content);
1257
+ if (raw.artifacts && typeof raw.artifacts === "object") {
1258
+ const artifactKeys = Object.keys(raw.artifacts);
1259
+ const custom = artifactKeys.filter((k) => !STANDARD_ARTIFACT_NAMES.has(k));
1260
+ if (custom.length > 0) {
1261
+ warnings.push(
1262
+ `Custom artifacts removed from config: ${custom.join(", ")}. Files remain on disk but CLI will ignore them.`
1263
+ );
1264
+ }
1265
+ delete raw.artifacts;
1266
+ await writeFile4(configPath, stringifyYaml2(raw, { lineWidth: 0 }), "utf-8");
1267
+ actions.push("Removed artifacts section from yg-config.yaml");
1268
+ }
1269
+ return { actions, warnings };
1270
+ }
1271
+
1191
1272
  // src/migrations/index.ts
1192
1273
  var MIGRATIONS = [
1193
1274
  {
1194
1275
  to: "2.0.0",
1195
1276
  description: "Rename YAML files to yg-* prefix, restructure config, convert aspects format",
1196
1277
  run: migrateTo2
1278
+ },
1279
+ {
1280
+ to: "3.0.0",
1281
+ description: "Remove artifacts section from config (now hardcoded in CLI)",
1282
+ run: migrateTo3
1197
1283
  }
1198
1284
  ];
1199
1285
 
1200
1286
  // src/cli/init.ts
1201
1287
  function getGraphSchemasDir() {
1202
- const currentDir = path4.dirname(fileURLToPath(import.meta.url));
1203
- const packageRoot = path4.join(currentDir, "..");
1204
- return path4.join(packageRoot, "graph-schemas");
1288
+ const currentDir = path5.dirname(fileURLToPath(import.meta.url));
1289
+ const packageRoot = path5.join(currentDir, "..");
1290
+ return path5.join(packageRoot, "graph-schemas");
1205
1291
  }
1206
1292
  function getCliVersion() {
1207
- const currentDir = path4.dirname(fileURLToPath(import.meta.url));
1208
- const packageRoot = path4.join(currentDir, "..");
1209
- const pkg2 = JSON.parse(readFileSync(path4.join(packageRoot, "package.json"), "utf-8"));
1293
+ const currentDir = path5.dirname(fileURLToPath(import.meta.url));
1294
+ const packageRoot = path5.join(currentDir, "..");
1295
+ const pkg2 = JSON.parse(readFileSync(path5.join(packageRoot, "package.json"), "utf-8"));
1210
1296
  return pkg2.version;
1211
1297
  }
1212
1298
  async function refreshSchemas(yggRoot) {
1213
- const schemasDir = path4.join(yggRoot, "schemas");
1299
+ const schemasDir = path5.join(yggRoot, "schemas");
1214
1300
  await mkdir2(schemasDir, { recursive: true });
1215
1301
  const graphSchemasDir = getGraphSchemasDir();
1216
1302
  try {
1217
1303
  const entries = await readdir2(graphSchemasDir, { withFileTypes: true });
1218
1304
  const schemaFiles = entries.filter((e) => e.isFile()).map((e) => e.name);
1219
1305
  for (const file of schemaFiles) {
1220
- const srcPath = path4.join(graphSchemasDir, file);
1221
- const content = await readFile4(srcPath, "utf-8");
1222
- await writeFile4(path4.join(schemasDir, file), content, "utf-8");
1306
+ const srcPath = path5.join(graphSchemasDir, file);
1307
+ const content = await readFile5(srcPath, "utf-8");
1308
+ await writeFile5(path5.join(schemasDir, file), content, "utf-8");
1223
1309
  }
1224
1310
  } catch {
1225
1311
  }
@@ -1232,7 +1318,7 @@ function registerInitCommand(program2) {
1232
1318
  "generic"
1233
1319
  ).option("--upgrade", "Refresh rules only (when .yggdrasil/ already exists)").action(async (options) => {
1234
1320
  const projectRoot = process.cwd();
1235
- const yggRoot = path4.join(projectRoot, ".yggdrasil");
1321
+ const yggRoot = path5.join(projectRoot, ".yggdrasil");
1236
1322
  let upgradeMode = false;
1237
1323
  try {
1238
1324
  const statResult = await stat2(yggRoot);
@@ -1295,23 +1381,23 @@ function registerInitCommand(program2) {
1295
1381
  await refreshSchemas(yggRoot);
1296
1382
  const rulesPath2 = await installRulesForPlatform(projectRoot, platform);
1297
1383
  process.stdout.write("\u2713 Rules refreshed.\n");
1298
- process.stdout.write(` ${path4.relative(projectRoot, rulesPath2)}
1384
+ process.stdout.write(` ${path5.relative(projectRoot, rulesPath2)}
1299
1385
  `);
1300
1386
  return;
1301
1387
  }
1302
- await mkdir2(path4.join(yggRoot, "model"), { recursive: true });
1303
- await mkdir2(path4.join(yggRoot, "aspects"), { recursive: true });
1304
- await mkdir2(path4.join(yggRoot, "flows"), { recursive: true });
1305
- const schemasDir = path4.join(yggRoot, "schemas");
1388
+ await mkdir2(path5.join(yggRoot, "model"), { recursive: true });
1389
+ await mkdir2(path5.join(yggRoot, "aspects"), { recursive: true });
1390
+ await mkdir2(path5.join(yggRoot, "flows"), { recursive: true });
1391
+ const schemasDir = path5.join(yggRoot, "schemas");
1306
1392
  await mkdir2(schemasDir, { recursive: true });
1307
1393
  const graphSchemasDir = getGraphSchemasDir();
1308
1394
  try {
1309
1395
  const entries = await readdir2(graphSchemasDir, { withFileTypes: true });
1310
1396
  const schemaFiles = entries.filter((e) => e.isFile()).map((e) => e.name);
1311
1397
  for (const file of schemaFiles) {
1312
- const srcPath = path4.join(graphSchemasDir, file);
1313
- const content = await readFile4(srcPath, "utf-8");
1314
- await writeFile4(path4.join(schemasDir, file), content, "utf-8");
1398
+ const srcPath = path5.join(graphSchemasDir, file);
1399
+ const content = await readFile5(srcPath, "utf-8");
1400
+ await writeFile5(path5.join(schemasDir, file), content, "utf-8");
1315
1401
  }
1316
1402
  } catch (err) {
1317
1403
  process.stderr.write(
@@ -1319,8 +1405,8 @@ function registerInitCommand(program2) {
1319
1405
  `
1320
1406
  );
1321
1407
  }
1322
- await writeFile4(path4.join(yggRoot, "yg-config.yaml"), DEFAULT_CONFIG, "utf-8");
1323
- await writeFile4(path4.join(yggRoot, ".gitignore"), GITIGNORE_CONTENT, "utf-8");
1408
+ await writeFile5(path5.join(yggRoot, "yg-config.yaml"), DEFAULT_CONFIG, "utf-8");
1409
+ await writeFile5(path5.join(yggRoot, ".gitignore"), GITIGNORE_CONTENT, "utf-8");
1324
1410
  const rulesPath = await installRulesForPlatform(projectRoot, platform);
1325
1411
  process.stdout.write("\u2713 Yggdrasil initialized.\n\n");
1326
1412
  process.stdout.write("Created:\n");
@@ -1330,7 +1416,7 @@ function registerInitCommand(program2) {
1330
1416
  process.stdout.write(" .yggdrasil/aspects/\n");
1331
1417
  process.stdout.write(" .yggdrasil/flows/\n");
1332
1418
  process.stdout.write(" .yggdrasil/schemas/ (yg-config, yg-node, yg-aspect, yg-flow)\n");
1333
- process.stdout.write(` ${path4.relative(projectRoot, rulesPath)} (rules)
1419
+ process.stdout.write(` ${path5.relative(projectRoot, rulesPath)} (rules)
1334
1420
 
1335
1421
  `);
1336
1422
  process.stdout.write("Next steps:\n");
@@ -1341,20 +1427,39 @@ function registerInitCommand(program2) {
1341
1427
  }
1342
1428
 
1343
1429
  // src/core/graph-loader.ts
1344
- import { readdir as readdir4, readFile as readFile11 } from "fs/promises";
1345
- import path9 from "path";
1430
+ import { readdir as readdir4, readFile as readFile12 } from "fs/promises";
1431
+ import path10 from "path";
1432
+
1433
+ // src/model/types.ts
1434
+ var STANDARD_ARTIFACTS2 = {
1435
+ "responsibility.md": {
1436
+ required: "always",
1437
+ description: "What this node is responsible for, and what it is not",
1438
+ included_in_relations: true
1439
+ },
1440
+ "interface.md": {
1441
+ required: { when: "has_incoming_relations" },
1442
+ description: "Public API \u2014 methods, parameters, return types, contracts, failure modes",
1443
+ included_in_relations: true
1444
+ },
1445
+ "internals.md": {
1446
+ required: "never",
1447
+ description: "How the node works and why \u2014 algorithms, business rules, design decisions",
1448
+ included_in_relations: false
1449
+ }
1450
+ };
1346
1451
 
1347
1452
  // src/io/config-parser.ts
1348
- import { readFile as readFile5 } from "fs/promises";
1349
- import { parse as parseYaml3 } from "yaml";
1453
+ import { readFile as readFile6 } from "fs/promises";
1454
+ import { parse as parseYaml4 } from "yaml";
1350
1455
  var DEFAULT_QUALITY = {
1351
1456
  min_artifact_length: 50,
1352
1457
  max_direct_relations: 10,
1353
1458
  context_budget: { warning: 1e4, error: 2e4, own_warning: void 0 }
1354
1459
  };
1355
1460
  async function parseConfig(filePath) {
1356
- const content = await readFile5(filePath, "utf-8");
1357
- const raw = parseYaml3(content);
1461
+ const content = await readFile6(filePath, "utf-8");
1462
+ const raw = parseYaml4(content);
1358
1463
  if (!raw || typeof raw !== "object") {
1359
1464
  throw new Error(`yg-config.yaml: file is empty or not a valid YAML mapping`);
1360
1465
  }
@@ -1380,35 +1485,6 @@ async function parseConfig(filePath) {
1380
1485
  required_aspects: requiredAspects && requiredAspects.length > 0 ? requiredAspects : void 0
1381
1486
  };
1382
1487
  }
1383
- const artifacts = raw.artifacts;
1384
- if (!artifacts || typeof artifacts !== "object" || Array.isArray(artifacts) || Object.keys(artifacts).length === 0) {
1385
- throw new Error(`yg-config.yaml: 'artifacts' must be a non-empty object`);
1386
- }
1387
- const artifactsMap = {};
1388
- for (const [key, val] of Object.entries(artifacts)) {
1389
- if (key === "yg-node.yaml") {
1390
- throw new Error(`yg-config.yaml: artifact name 'yg-node.yaml' is reserved`);
1391
- }
1392
- const a = val;
1393
- const required = a.required;
1394
- if (required !== "always" && required !== "never" && (typeof required !== "object" || !required || !("when" in required))) {
1395
- throw new Error(`yg-config.yaml: artifact '${key}' has invalid 'required' field`);
1396
- }
1397
- if (typeof required === "object" && required && "when" in required) {
1398
- const when = required.when;
1399
- const validWhen = when === "has_incoming_relations" || when === "has_outgoing_relations" || typeof when === "string" && (when.startsWith("has_aspect:") || when.startsWith("has_tag:"));
1400
- if (!validWhen) {
1401
- throw new Error(
1402
- `yg-config.yaml: artifact '${key}' has invalid 'required.when': must be has_incoming_relations, has_outgoing_relations, or has_aspect:<name>`
1403
- );
1404
- }
1405
- }
1406
- artifactsMap[key] = {
1407
- required,
1408
- description: a.description ?? "",
1409
- included_in_relations: a.included_in_relations ?? false
1410
- };
1411
- }
1412
1488
  const qualityRaw = raw.quality;
1413
1489
  const quality = qualityRaw ? {
1414
1490
  min_artifact_length: qualityRaw.min_artifact_length ?? DEFAULT_QUALITY.min_artifact_length,
@@ -1431,14 +1507,13 @@ async function parseConfig(filePath) {
1431
1507
  version,
1432
1508
  name: raw.name.trim(),
1433
1509
  node_types: nodeTypes,
1434
- artifacts: artifactsMap,
1435
1510
  quality
1436
1511
  };
1437
1512
  }
1438
1513
 
1439
1514
  // src/io/node-parser.ts
1440
- import { readFile as readFile6 } from "fs/promises";
1441
- import { parse as parseYaml4 } from "yaml";
1515
+ import { readFile as readFile7 } from "fs/promises";
1516
+ import { parse as parseYaml5 } from "yaml";
1442
1517
  var RELATION_TYPES = [
1443
1518
  "uses",
1444
1519
  "calls",
@@ -1451,8 +1526,8 @@ function isValidRelationType(t) {
1451
1526
  return typeof t === "string" && RELATION_TYPES.includes(t);
1452
1527
  }
1453
1528
  async function parseNodeYaml(filePath) {
1454
- const content = await readFile6(filePath, "utf-8");
1455
- const raw = parseYaml4(content);
1529
+ const content = await readFile7(filePath, "utf-8");
1530
+ const raw = parseYaml5(content);
1456
1531
  if (!raw || typeof raw !== "object") {
1457
1532
  throw new Error(`yg-node.yaml at ${filePath}: file is empty or not a valid YAML mapping`);
1458
1533
  }
@@ -1597,12 +1672,12 @@ function parseMapping(rawMapping, filePath) {
1597
1672
  }
1598
1673
 
1599
1674
  // src/io/aspect-parser.ts
1600
- import { readFile as readFile8 } from "fs/promises";
1601
- import { parse as parseYaml5 } from "yaml";
1675
+ import { readFile as readFile9 } from "fs/promises";
1676
+ import { parse as parseYaml6 } from "yaml";
1602
1677
 
1603
1678
  // src/io/artifact-reader.ts
1604
- import { readFile as readFile7, readdir as readdir3 } from "fs/promises";
1605
- import path5 from "path";
1679
+ import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
1680
+ import path6 from "path";
1606
1681
  async function readArtifacts(dirPath, excludeFiles = ["yg-node.yaml"], includeFiles) {
1607
1682
  const entries = await readdir3(dirPath, { withFileTypes: true });
1608
1683
  const artifacts = [];
@@ -1611,8 +1686,8 @@ async function readArtifacts(dirPath, excludeFiles = ["yg-node.yaml"], includeFi
1611
1686
  if (!entry.isFile()) continue;
1612
1687
  if (excludeFiles.includes(entry.name)) continue;
1613
1688
  if (includeSet && !includeSet.has(entry.name)) continue;
1614
- const filePath = path5.join(dirPath, entry.name);
1615
- const content = await readFile7(filePath, "utf-8");
1689
+ const filePath = path6.join(dirPath, entry.name);
1690
+ const content = await readFile8(filePath, "utf-8");
1616
1691
  artifacts.push({ filename: entry.name, content });
1617
1692
  }
1618
1693
  artifacts.sort((a, b) => a.filename.localeCompare(b.filename));
@@ -1626,8 +1701,8 @@ async function parseAspect(aspectDir, aspectYamlPath, id) {
1626
1701
  if (!idTrimmed) {
1627
1702
  throw new Error(`Aspect id must be non-empty (relative path in aspects/)`);
1628
1703
  }
1629
- const content = await readFile8(aspectYamlPath, "utf-8");
1630
- const raw = parseYaml5(content);
1704
+ const content = await readFile9(aspectYamlPath, "utf-8");
1705
+ const raw = parseYaml6(content);
1631
1706
  if (!raw || typeof raw !== "object") {
1632
1707
  throw new Error(`Aspect file ${aspectYamlPath}: file is empty or not a valid YAML mapping`);
1633
1708
  }
@@ -1663,12 +1738,12 @@ async function parseAspect(aspectDir, aspectYamlPath, id) {
1663
1738
  }
1664
1739
 
1665
1740
  // src/io/flow-parser.ts
1666
- import { readFile as readFile9 } from "fs/promises";
1667
- import path6 from "path";
1668
- import { parse as parseYaml6 } from "yaml";
1741
+ import { readFile as readFile10 } from "fs/promises";
1742
+ import path7 from "path";
1743
+ import { parse as parseYaml7 } from "yaml";
1669
1744
  async function parseFlow(flowDir, flowYamlPath) {
1670
- const content = await readFile9(flowYamlPath, "utf-8");
1671
- const raw = parseYaml6(content);
1745
+ const content = await readFile10(flowYamlPath, "utf-8");
1746
+ const raw = parseYaml7(content);
1672
1747
  if (!raw || typeof raw !== "object") {
1673
1748
  throw new Error(`yg-flow.yaml at ${flowYamlPath}: file is empty or not a valid YAML mapping`);
1674
1749
  }
@@ -1698,7 +1773,7 @@ async function parseFlow(flowDir, flowYamlPath) {
1698
1773
  }
1699
1774
  const artifacts = await readArtifacts(flowDir, ["yg-flow.yaml"]);
1700
1775
  return {
1701
- path: path6.basename(flowDir),
1776
+ path: path7.basename(flowDir),
1702
1777
  name: raw.name.trim(),
1703
1778
  description,
1704
1779
  nodes: nodePaths,
@@ -1708,26 +1783,26 @@ async function parseFlow(flowDir, flowYamlPath) {
1708
1783
  }
1709
1784
 
1710
1785
  // src/io/schema-parser.ts
1711
- import { readFile as readFile10 } from "fs/promises";
1712
- import path7 from "path";
1713
- import { parse as parseYaml7 } from "yaml";
1786
+ import { readFile as readFile11 } from "fs/promises";
1787
+ import path8 from "path";
1788
+ import { parse as parseYaml8 } from "yaml";
1714
1789
  async function parseSchema(filePath) {
1715
- const content = await readFile10(filePath, "utf-8");
1716
- parseYaml7(content);
1717
- const rawName = path7.basename(filePath, path7.extname(filePath));
1790
+ const content = await readFile11(filePath, "utf-8");
1791
+ parseYaml8(content);
1792
+ const rawName = path8.basename(filePath, path8.extname(filePath));
1718
1793
  const schemaType = rawName.startsWith("yg-") ? rawName.slice(3) : rawName;
1719
1794
  return { schemaType };
1720
1795
  }
1721
1796
 
1722
1797
  // src/utils/paths.ts
1723
- import path8 from "path";
1798
+ import path9 from "path";
1724
1799
  import { fileURLToPath as fileURLToPath2 } from "url";
1725
1800
  import { stat as stat3 } from "fs/promises";
1726
1801
  async function findYggRoot(projectRoot) {
1727
- let current = path8.resolve(projectRoot);
1728
- const root = path8.parse(current).root;
1802
+ let current = path9.resolve(projectRoot);
1803
+ const root = path9.parse(current).root;
1729
1804
  while (true) {
1730
- const yggPath = path8.join(current, ".yggdrasil");
1805
+ const yggPath = path9.join(current, ".yggdrasil");
1731
1806
  try {
1732
1807
  const st = await stat3(yggPath);
1733
1808
  if (!st.isDirectory()) {
@@ -1741,7 +1816,7 @@ async function findYggRoot(projectRoot) {
1741
1816
  if (current === root) {
1742
1817
  throw new Error(`No .yggdrasil/ directory found. Run 'yg init' first.`, { cause: err });
1743
1818
  }
1744
- current = path8.dirname(current);
1819
+ current = path9.dirname(current);
1745
1820
  continue;
1746
1821
  }
1747
1822
  throw err;
@@ -1757,43 +1832,42 @@ function normalizeProjectRelativePath(projectRoot, rawPath) {
1757
1832
  if (normalizedInput.length === 0) {
1758
1833
  throw new Error("Path cannot be empty");
1759
1834
  }
1760
- const absolute = path8.resolve(projectRoot, normalizedInput);
1761
- const relative = path8.relative(projectRoot, absolute);
1762
- const isOutside = relative.startsWith("..") || path8.isAbsolute(relative);
1835
+ const absolute = path9.resolve(projectRoot, normalizedInput);
1836
+ const relative = path9.relative(projectRoot, absolute);
1837
+ const isOutside = relative.startsWith("..") || path9.isAbsolute(relative);
1763
1838
  if (isOutside) {
1764
1839
  throw new Error(`Path is outside project root: ${rawPath}`);
1765
1840
  }
1766
- return relative.split(path8.sep).join("/");
1841
+ return relative.split(path9.sep).join("/");
1767
1842
  }
1768
1843
  function projectRootFromGraph(yggRootPath) {
1769
- return path8.dirname(yggRootPath);
1844
+ return path9.dirname(yggRootPath);
1770
1845
  }
1771
1846
 
1772
1847
  // src/core/graph-loader.ts
1773
1848
  function toModelPath(absolutePath, modelDir) {
1774
- return path9.relative(modelDir, absolutePath).split(path9.sep).join("/");
1849
+ return path10.relative(modelDir, absolutePath).split(path10.sep).join("/");
1775
1850
  }
1776
1851
  var FALLBACK_CONFIG = {
1777
1852
  name: "",
1778
- node_types: {},
1779
- artifacts: {}
1853
+ node_types: {}
1780
1854
  };
1781
1855
  async function loadGraph(projectRoot, options = {}) {
1782
1856
  const yggRoot = await findYggRoot(projectRoot);
1783
1857
  let configError;
1784
1858
  let config = FALLBACK_CONFIG;
1785
1859
  try {
1786
- config = await parseConfig(path9.join(yggRoot, "yg-config.yaml"));
1860
+ config = await parseConfig(path10.join(yggRoot, "yg-config.yaml"));
1787
1861
  } catch (error) {
1788
1862
  if (!options.tolerateInvalidConfig) {
1789
1863
  throw error;
1790
1864
  }
1791
1865
  configError = error.message;
1792
1866
  }
1793
- const modelDir = path9.join(yggRoot, "model");
1867
+ const modelDir = path10.join(yggRoot, "model");
1794
1868
  const nodes = /* @__PURE__ */ new Map();
1795
1869
  const nodeParseErrors = [];
1796
- const artifactFilenames = Object.keys(config.artifacts ?? {});
1870
+ const artifactFilenames = Object.keys(STANDARD_ARTIFACTS2);
1797
1871
  try {
1798
1872
  await scanModelDirectory(modelDir, modelDir, null, nodes, nodeParseErrors, artifactFilenames);
1799
1873
  } catch (err) {
@@ -1804,9 +1878,9 @@ async function loadGraph(projectRoot, options = {}) {
1804
1878
  }
1805
1879
  throw err;
1806
1880
  }
1807
- const aspects = await loadAspects(path9.join(yggRoot, "aspects"));
1808
- const flows = await loadFlows(path9.join(yggRoot, "flows"));
1809
- const schemas = await loadSchemas(path9.join(yggRoot, "schemas"));
1881
+ const aspects = await loadAspects(path10.join(yggRoot, "aspects"));
1882
+ const flows = await loadFlows(path10.join(yggRoot, "flows"));
1883
+ const schemas = await loadSchemas(path10.join(yggRoot, "schemas"));
1810
1884
  return {
1811
1885
  config,
1812
1886
  configError,
@@ -1826,11 +1900,11 @@ async function scanModelDirectory(dirPath, modelDir, parent, nodes, nodeParseErr
1826
1900
  }
1827
1901
  if (hasNodeYaml) {
1828
1902
  const graphPath = toModelPath(dirPath, modelDir);
1829
- const nodeYamlPath = path9.join(dirPath, "yg-node.yaml");
1903
+ const nodeYamlPath = path10.join(dirPath, "yg-node.yaml");
1830
1904
  let meta;
1831
1905
  let nodeYamlRaw;
1832
1906
  try {
1833
- nodeYamlRaw = await readFile11(nodeYamlPath, "utf-8");
1907
+ nodeYamlRaw = await readFile12(nodeYamlPath, "utf-8");
1834
1908
  meta = await parseNodeYaml(nodeYamlPath);
1835
1909
  } catch (err) {
1836
1910
  nodeParseErrors.push({
@@ -1856,7 +1930,7 @@ async function scanModelDirectory(dirPath, modelDir, parent, nodes, nodeParseErr
1856
1930
  if (!entry.isDirectory()) continue;
1857
1931
  if (entry.name.startsWith(".")) continue;
1858
1932
  await scanModelDirectory(
1859
- path9.join(dirPath, entry.name),
1933
+ path10.join(dirPath, entry.name),
1860
1934
  modelDir,
1861
1935
  node,
1862
1936
  nodes,
@@ -1869,7 +1943,7 @@ async function scanModelDirectory(dirPath, modelDir, parent, nodes, nodeParseErr
1869
1943
  if (!entry.isDirectory()) continue;
1870
1944
  if (entry.name.startsWith(".")) continue;
1871
1945
  await scanModelDirectory(
1872
- path9.join(dirPath, entry.name),
1946
+ path10.join(dirPath, entry.name),
1873
1947
  modelDir,
1874
1948
  null,
1875
1949
  nodes,
@@ -1892,15 +1966,15 @@ async function scanAspectsDirectory(dirPath, aspectsRoot, aspects) {
1892
1966
  const entries = await readdir4(dirPath, { withFileTypes: true });
1893
1967
  const hasAspectYaml = entries.some((e) => e.isFile() && e.name === "yg-aspect.yaml");
1894
1968
  if (hasAspectYaml) {
1895
- const id = path9.relative(aspectsRoot, dirPath).split(path9.sep).join("/");
1896
- const aspectYamlPath = path9.join(dirPath, "yg-aspect.yaml");
1969
+ const id = path10.relative(aspectsRoot, dirPath).split(path10.sep).join("/");
1970
+ const aspectYamlPath = path10.join(dirPath, "yg-aspect.yaml");
1897
1971
  const aspect = await parseAspect(dirPath, aspectYamlPath, id);
1898
1972
  aspects.push(aspect);
1899
1973
  }
1900
1974
  for (const entry of entries) {
1901
1975
  if (!entry.isDirectory()) continue;
1902
1976
  if (entry.name.startsWith(".")) continue;
1903
- await scanAspectsDirectory(path9.join(dirPath, entry.name), aspectsRoot, aspects);
1977
+ await scanAspectsDirectory(path10.join(dirPath, entry.name), aspectsRoot, aspects);
1904
1978
  }
1905
1979
  }
1906
1980
  async function loadFlows(flowsDir) {
@@ -1913,8 +1987,8 @@ async function loadFlows(flowsDir) {
1913
1987
  const flows = [];
1914
1988
  for (const entry of entries) {
1915
1989
  if (!entry.isDirectory()) continue;
1916
- const flowYamlPath = path9.join(flowsDir, entry.name, "yg-flow.yaml");
1917
- const flow = await parseFlow(path9.join(flowsDir, entry.name), flowYamlPath);
1990
+ const flowYamlPath = path10.join(flowsDir, entry.name, "yg-flow.yaml");
1991
+ const flow = await parseFlow(path10.join(flowsDir, entry.name), flowYamlPath);
1918
1992
  flows.push(flow);
1919
1993
  }
1920
1994
  return flows;
@@ -1926,7 +2000,7 @@ async function loadSchemas(schemasDir) {
1926
2000
  for (const entry of entries) {
1927
2001
  if (!entry.isFile()) continue;
1928
2002
  if (!entry.name.endsWith(".yaml") && !entry.name.endsWith(".yml")) continue;
1929
- const s = await parseSchema(path9.join(schemasDir, entry.name));
2003
+ const s = await parseSchema(path10.join(schemasDir, entry.name));
1930
2004
  schemas.push(s);
1931
2005
  }
1932
2006
  return schemas;
@@ -1936,8 +2010,8 @@ async function loadSchemas(schemasDir) {
1936
2010
  }
1937
2011
 
1938
2012
  // src/core/context-builder.ts
1939
- import { readFile as readFile12 } from "fs/promises";
1940
- import path10 from "path";
2013
+ import { readFile as readFile13 } from "fs/promises";
2014
+ import path11 from "path";
1941
2015
 
1942
2016
  // src/utils/tokens.ts
1943
2017
  function estimateTokens(text) {
@@ -1948,48 +2022,53 @@ function estimateTokens(text) {
1948
2022
  var STRUCTURAL_RELATION_TYPES = /* @__PURE__ */ new Set(["uses", "calls", "extends", "implements"]);
1949
2023
  var EVENT_RELATION_TYPES = /* @__PURE__ */ new Set(["emits", "listens"]);
1950
2024
  var YG_YAML_FILES = /* @__PURE__ */ new Set(["yg-node.yaml", "yg-aspect.yaml", "yg-flow.yaml"]);
1951
- async function buildContext(graph, nodePath) {
2025
+ async function buildContext(graph, nodePath, options) {
1952
2026
  const node = graph.nodes.get(nodePath);
1953
2027
  if (!node) {
1954
2028
  throw new Error(`Node not found: ${nodePath}`);
1955
2029
  }
2030
+ const selfOnly = options?.selfOnly ?? false;
1956
2031
  const layers = [];
1957
2032
  layers.push(buildGlobalLayer(graph.config));
1958
2033
  const ancestors = collectAncestors(node);
1959
- for (const ancestor of ancestors) {
1960
- layers.push(buildHierarchyLayer(ancestor, graph.config, graph));
2034
+ if (!selfOnly) {
2035
+ for (const ancestor of ancestors) {
2036
+ layers.push(buildHierarchyLayer(ancestor, graph.config, graph));
2037
+ }
1961
2038
  }
1962
2039
  layers.push(await buildOwnLayer(node, graph.config, graph.rootPath, graph));
1963
- const ancestorPaths = new Set(ancestors.map((a) => a.path));
1964
- for (const relation of node.meta.relations ?? []) {
1965
- const target = graph.nodes.get(relation.target);
1966
- if (!target) {
1967
- throw new Error(`Broken relation: ${nodePath} -> ${relation.target} (target not found)`);
2040
+ if (!selfOnly) {
2041
+ const ancestorPaths = new Set(ancestors.map((a) => a.path));
2042
+ for (const relation of node.meta.relations ?? []) {
2043
+ const target = graph.nodes.get(relation.target);
2044
+ if (!target) {
2045
+ throw new Error(`Broken relation: ${nodePath} -> ${relation.target} (target not found)`);
2046
+ }
2047
+ if (ancestorPaths.has(relation.target)) continue;
2048
+ if (STRUCTURAL_RELATION_TYPES.has(relation.type)) {
2049
+ layers.push(buildStructuralRelationLayer(target, relation));
2050
+ } else if (EVENT_RELATION_TYPES.has(relation.type)) {
2051
+ layers.push(buildEventRelationLayer(target, relation));
2052
+ }
1968
2053
  }
1969
- if (ancestorPaths.has(relation.target)) continue;
1970
- if (STRUCTURAL_RELATION_TYPES.has(relation.type)) {
1971
- layers.push(buildStructuralRelationLayer(target, relation, graph.config));
1972
- } else if (EVENT_RELATION_TYPES.has(relation.type)) {
1973
- layers.push(buildEventRelationLayer(target, relation));
2054
+ for (const flow of collectParticipatingFlows(graph, node)) {
2055
+ layers.push(buildFlowLayer(flow, graph));
1974
2056
  }
1975
- }
1976
- for (const flow of collectParticipatingFlows(graph, node)) {
1977
- layers.push(buildFlowLayer(flow, graph));
1978
- }
1979
- const allAspectIds = /* @__PURE__ */ new Set();
1980
- for (const l of layers) {
1981
- const aspects = l.attrs?.aspects;
1982
- if (aspects) {
1983
- for (const id of aspects.split(",").map((t) => t.trim()).filter(Boolean)) {
1984
- allAspectIds.add(id);
2057
+ const allAspectIds = /* @__PURE__ */ new Set();
2058
+ for (const l of layers) {
2059
+ const aspects = l.attrs?.aspects;
2060
+ if (aspects) {
2061
+ for (const id of aspects.split(",").map((t) => t.trim()).filter(Boolean)) {
2062
+ allAspectIds.add(id);
2063
+ }
1985
2064
  }
1986
2065
  }
1987
- }
1988
- const aspectsToInclude = resolveAspects(allAspectIds, graph.aspects);
1989
- for (const aspect of aspectsToInclude) {
1990
- const entry = node.meta.aspects?.find((a) => a.aspect === aspect.id);
1991
- const exceptionNote = entry?.exceptions?.join("; ");
1992
- layers.push(buildAspectLayer(aspect, exceptionNote));
2066
+ const aspectsToInclude = resolveAspects(allAspectIds, graph.aspects);
2067
+ for (const aspect of aspectsToInclude) {
2068
+ const entry = node.meta.aspects?.find((a) => a.aspect === aspect.id);
2069
+ const exceptionNote = entry?.exceptions?.join("; ");
2070
+ layers.push(buildAspectLayer(aspect, exceptionNote));
2071
+ }
1993
2072
  }
1994
2073
  const fullText = layers.map((l) => l.content).join("\n\n");
1995
2074
  const tokenCount = estimateTokens(fullText);
@@ -2050,12 +2129,12 @@ function buildGlobalLayer(config) {
2050
2129
  `;
2051
2130
  return { type: "global", label: "Global Context", content };
2052
2131
  }
2053
- function filterArtifactsByConfig(artifacts, config) {
2054
- const allowed = new Set(Object.keys(config.artifacts ?? {}));
2132
+ function filterByStandardArtifacts(artifacts) {
2133
+ const allowed = new Set(Object.keys(STANDARD_ARTIFACTS2));
2055
2134
  return artifacts.filter((a) => allowed.has(a.filename));
2056
2135
  }
2057
2136
  function buildHierarchyLayer(ancestor, config, graph) {
2058
- const filtered = filterArtifactsByConfig(ancestor.artifacts, config);
2137
+ const filtered = filterByStandardArtifacts(ancestor.artifacts);
2059
2138
  const content = filtered.map((a) => `### ${a.filename}
2060
2139
  ${a.content}`).join("\n\n");
2061
2140
  const nodeAspects = (ancestor.meta.aspects ?? []).map((a) => a.aspect);
@@ -2074,9 +2153,9 @@ async function buildOwnLayer(node, config, graphRootPath, graph) {
2074
2153
  parts.push(`### yg-node.yaml
2075
2154
  ${node.nodeYamlRaw.trim()}`);
2076
2155
  } else {
2077
- const nodeYamlPath = path10.join(graphRootPath, "model", node.path, "yg-node.yaml");
2156
+ const nodeYamlPath = path11.join(graphRootPath, "model", node.path, "yg-node.yaml");
2078
2157
  try {
2079
- const nodeYamlContent = await readFile12(nodeYamlPath, "utf-8");
2158
+ const nodeYamlContent = await readFile13(nodeYamlPath, "utf-8");
2080
2159
  parts.push(`### yg-node.yaml
2081
2160
  ${nodeYamlContent.trim()}`);
2082
2161
  } catch {
@@ -2084,7 +2163,7 @@ ${nodeYamlContent.trim()}`);
2084
2163
  (not found)`);
2085
2164
  }
2086
2165
  }
2087
- const filtered = filterArtifactsByConfig(node.artifacts, config);
2166
+ const filtered = filterByStandardArtifacts(node.artifacts);
2088
2167
  for (const a of filtered) {
2089
2168
  parts.push(`### ${a.filename}
2090
2169
  ${a.content}`);
@@ -2100,7 +2179,7 @@ ${a.content}`);
2100
2179
  attrs
2101
2180
  };
2102
2181
  }
2103
- function buildStructuralRelationLayer(target, relation, config) {
2182
+ function buildStructuralRelationLayer(target, relation) {
2104
2183
  let content = "";
2105
2184
  if (relation.consumes?.length) {
2106
2185
  content += `Consumes: ${relation.consumes.join(", ")}
@@ -2112,7 +2191,7 @@ function buildStructuralRelationLayer(target, relation, config) {
2112
2191
 
2113
2192
  `;
2114
2193
  }
2115
- const structuralArtifactFilenames = Object.entries(config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
2194
+ const structuralArtifactFilenames = Object.entries(STANDARD_ARTIFACTS2).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
2116
2195
  const structuralArts = structuralArtifactFilenames.map((filename) => {
2117
2196
  const art = target.artifacts.find((a) => a.filename === filename);
2118
2197
  return art ? { filename: art.filename, content: art.content } : null;
@@ -2121,7 +2200,7 @@ function buildStructuralRelationLayer(target, relation, config) {
2121
2200
  content += structuralArts.map((a) => `### ${a.filename}
2122
2201
  ${a.content}`).join("\n\n");
2123
2202
  } else {
2124
- const filtered = filterArtifactsByConfig(target.artifacts, config);
2203
+ const filtered = filterByStandardArtifacts(target.artifacts);
2125
2204
  content += filtered.map((a) => `### ${a.filename}
2126
2205
  ${a.content}`).join("\n\n");
2127
2206
  }
@@ -2226,8 +2305,8 @@ function collectAncestors(node) {
2226
2305
  }
2227
2306
  function collectDependencyAncestors(target, config, graph) {
2228
2307
  const ancestors = collectAncestors(target);
2229
- const structuralFilenames = Object.entries(config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
2230
- const configArtifactKeys = [...Object.keys(config.artifacts ?? {})];
2308
+ const structuralFilenames = Object.entries(STANDARD_ARTIFACTS2).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
2309
+ const configArtifactKeys = [...Object.keys(STANDARD_ARTIFACTS2)];
2231
2310
  return ancestors.map((ancestor) => {
2232
2311
  const nodeAspects = (ancestor.meta.aspects ?? []).map((a) => a.aspect);
2233
2312
  const expanded = expandAspects(nodeAspects, graph.aspects);
@@ -2295,7 +2374,7 @@ function computeBudgetBreakdown(pkg2, graph) {
2295
2374
  const total = own + hierarchy + aspects + flows + dependencies;
2296
2375
  return { own, hierarchy, aspects, flows, dependencies, total };
2297
2376
  }
2298
- function toContextMapOutput(pkg2, graph) {
2377
+ function toContextMapOutput(pkg2, graph, options) {
2299
2378
  const node = graph.nodes.get(pkg2.nodePath);
2300
2379
  const config = graph.config;
2301
2380
  const nodeAspects = (node.meta.aspects ?? []).map((entry) => {
@@ -2304,48 +2383,51 @@ function toContextMapOutput(pkg2, graph) {
2304
2383
  if (entry.exceptions?.length) ref.exceptions = entry.exceptions;
2305
2384
  return ref;
2306
2385
  });
2307
- const participatingFlows = collectParticipatingFlows(graph, node);
2386
+ const selfOnly = options?.selfOnly ?? false;
2387
+ const participatingFlows = selfOnly ? [] : collectParticipatingFlows(graph, node);
2308
2388
  const flowRefs = participatingFlows.map((f) => {
2309
2389
  const ref = { path: f.path };
2310
2390
  if (f.aspects?.length) ref.aspects = f.aspects;
2311
2391
  return ref;
2312
2392
  });
2313
2393
  const ancestors = collectAncestors(node);
2314
- const hierarchyRefs = ancestors.map((a) => {
2394
+ const hierarchyRefs = selfOnly ? [] : ancestors.map((a) => {
2315
2395
  const nodeAspectIds = (a.meta.aspects ?? []).map((e) => e.aspect);
2316
2396
  const expanded = expandAspects(nodeAspectIds, graph.aspects);
2317
2397
  return { path: a.path, name: a.meta.name, type: a.meta.type, description: a.meta.description, aspects: expanded, files: buildNodeFiles(a, config, `model/${a.path}`) };
2318
2398
  });
2319
- const ancestorPaths = new Set(ancestors.map((a) => a.path));
2320
2399
  const depRefs = [];
2321
- for (const relation of node.meta.relations ?? []) {
2322
- const target = graph.nodes.get(relation.target);
2323
- if (!target) continue;
2324
- if (ancestorPaths.has(relation.target)) continue;
2325
- const depAncestors = collectAncestors(target);
2326
- const depHierarchy = depAncestors.map((a) => {
2327
- const ids = (a.meta.aspects ?? []).map((e) => e.aspect);
2328
- const expanded = expandAspects(ids, graph.aspects);
2329
- const ancestorNode = graph.nodes.get(a.path);
2330
- return { path: a.path, name: a.meta.name, type: a.meta.type, description: a.meta.description, aspects: expanded, files: ancestorNode ? buildDepNodeFiles(ancestorNode, config, `model/${a.path}`) : [] };
2331
- });
2332
- const depEffectiveAspects = [...collectEffectiveAspectIds(graph, target.path)];
2333
- const ref = {
2334
- path: target.path,
2335
- name: target.meta.name,
2336
- type: target.meta.type,
2337
- description: target.meta.description,
2338
- relation: relation.type,
2339
- aspects: depEffectiveAspects,
2340
- hierarchy: depHierarchy,
2341
- files: buildDepNodeFiles(target, config, `model/${target.path}`)
2342
- };
2343
- if (relation.consumes?.length) ref.consumes = relation.consumes;
2344
- if (relation.failure) ref.failure = relation.failure;
2345
- if (relation.event_name) ref["event-name"] = relation.event_name;
2346
- depRefs.push(ref);
2400
+ if (!selfOnly) {
2401
+ const ancestorPaths = new Set(ancestors.map((a) => a.path));
2402
+ for (const relation of node.meta.relations ?? []) {
2403
+ const target = graph.nodes.get(relation.target);
2404
+ if (!target) continue;
2405
+ if (ancestorPaths.has(relation.target)) continue;
2406
+ const depAncestors = collectAncestors(target);
2407
+ const depHierarchy = depAncestors.map((a) => {
2408
+ const ids = (a.meta.aspects ?? []).map((e) => e.aspect);
2409
+ const expanded = expandAspects(ids, graph.aspects);
2410
+ const ancestorNode = graph.nodes.get(a.path);
2411
+ return { path: a.path, name: a.meta.name, type: a.meta.type, description: a.meta.description, aspects: expanded, files: ancestorNode ? buildDepNodeFiles(ancestorNode, config, `model/${a.path}`) : [] };
2412
+ });
2413
+ const depEffectiveAspects = [...collectEffectiveAspectIds(graph, target.path)];
2414
+ const ref = {
2415
+ path: target.path,
2416
+ name: target.meta.name,
2417
+ type: target.meta.type,
2418
+ description: target.meta.description,
2419
+ relation: relation.type,
2420
+ aspects: depEffectiveAspects,
2421
+ hierarchy: depHierarchy,
2422
+ files: buildDepNodeFiles(target, config, `model/${target.path}`)
2423
+ };
2424
+ if (relation.consumes?.length) ref.consumes = relation.consumes;
2425
+ if (relation.failure) ref.failure = relation.failure;
2426
+ if (relation.event_name) ref["event-name"] = relation.event_name;
2427
+ depRefs.push(ref);
2428
+ }
2347
2429
  }
2348
- const glossary = buildGlossary(node, depRefs, graph);
2430
+ const glossary = selfOnly ? { aspects: {}, flows: {} } : buildGlossary(node, depRefs, graph);
2349
2431
  const breakdown = computeBudgetBreakdown(pkg2, graph);
2350
2432
  const warningThreshold = config.quality?.context_budget?.warning ?? 1e4;
2351
2433
  const errorThreshold = config.quality?.context_budget?.error ?? 2e4;
@@ -2368,13 +2450,13 @@ function toContextMapOutput(pkg2, graph) {
2368
2450
  glossary
2369
2451
  };
2370
2452
  }
2371
- function buildNodeFiles(node, config, prefix) {
2372
- const configKeys = Object.keys(config.artifacts ?? {});
2453
+ function buildNodeFiles(node, _config, prefix) {
2454
+ const configKeys = Object.keys(STANDARD_ARTIFACTS2);
2373
2455
  return configKeys.filter((f) => !YG_YAML_FILES.has(f) && node.artifacts.some((a) => a.filename === f)).map((f) => `${prefix}/${f}`);
2374
2456
  }
2375
- function buildDepNodeFiles(node, config, prefix) {
2376
- const structural = Object.entries(config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([f]) => f);
2377
- const filenames = structural.length > 0 ? structural : Object.keys(config.artifacts ?? {});
2457
+ function buildDepNodeFiles(node, _config, prefix) {
2458
+ const structural = Object.entries(STANDARD_ARTIFACTS2).filter(([, c]) => c.included_in_relations).map(([f]) => f);
2459
+ const filenames = structural.length > 0 ? structural : Object.keys(STANDARD_ARTIFACTS2);
2378
2460
  return filenames.filter((f) => !YG_YAML_FILES.has(f) && node.artifacts.some((a) => a.filename === f)).map((f) => `${prefix}/${f}`);
2379
2461
  }
2380
2462
  function buildGlossary(node, dependencies, graph) {
@@ -2479,8 +2561,8 @@ ${file.content}
2479
2561
  }
2480
2562
 
2481
2563
  // src/core/validator.ts
2482
- import { readdir as readdir5, readFile as readFile13, stat as stat4 } from "fs/promises";
2483
- import path11 from "path";
2564
+ import { readdir as readdir5, readFile as readFile14, stat as stat4 } from "fs/promises";
2565
+ import path12 from "path";
2484
2566
  function getAspectIds(aspects) {
2485
2567
  return (aspects ?? []).map((a) => a.aspect);
2486
2568
  }
@@ -2514,7 +2596,6 @@ async function validate(graph, scope = "all") {
2514
2596
  issues.push(...checkRequiredAspectsCoverage(graph));
2515
2597
  issues.push(...await checkAnchorPresence(graph));
2516
2598
  issues.push(...checkRequiredArtifacts(graph));
2517
- issues.push(...checkInvalidArtifactConditions(graph));
2518
2599
  issues.push(...await checkContextBudget(graph));
2519
2600
  issues.push(...checkHighFanOut(graph));
2520
2601
  issues.push(...checkMissingDescriptions(graph));
@@ -2847,12 +2928,12 @@ function checkMappingOverlap(graph) {
2847
2928
  }
2848
2929
  async function checkMappingPathsExist(graph) {
2849
2930
  const issues = [];
2850
- const projectRoot = path11.dirname(graph.rootPath);
2931
+ const projectRoot = path12.dirname(graph.rootPath);
2851
2932
  const { access: access4 } = await import("fs/promises");
2852
2933
  for (const [nodePath, node] of graph.nodes) {
2853
2934
  const mappingPaths = normalizeMappingPaths(node.meta.mapping);
2854
2935
  for (const mp of mappingPaths) {
2855
- const absPath = path11.join(projectRoot, mp);
2936
+ const absPath = path12.join(projectRoot, mp);
2856
2937
  try {
2857
2938
  await access4(absPath);
2858
2939
  } catch {
@@ -2887,15 +2968,6 @@ function artifactRequiredReason(graph, nodePath, node, required) {
2887
2968
  const sources = getIncomingRelationSources(graph, nodePath);
2888
2969
  return sources.length > 0 ? `${sources.length} incoming relation(s): ${sources.join(", ")}` : null;
2889
2970
  }
2890
- if (when === "has_outgoing_relations") {
2891
- const count = node.meta.relations?.length ?? 0;
2892
- return count > 0 ? `${count} outgoing relation(s)` : null;
2893
- }
2894
- if (when.startsWith("has_aspect:") || when.startsWith("has_tag:")) {
2895
- const prefix = when.startsWith("has_aspect:") ? "has_aspect:" : "has_tag:";
2896
- const aspectId = when.slice(prefix.length);
2897
- return (node.meta.aspects ?? []).some((a) => a.aspect === aspectId) ? `node has aspect '${aspectId}'` : null;
2898
- }
2899
2971
  return null;
2900
2972
  }
2901
2973
  function getIncomingRelations(graph, nodePath) {
@@ -2912,7 +2984,7 @@ function getIncomingRelations(graph, nodePath) {
2912
2984
  }
2913
2985
  function checkRequiredArtifacts(graph) {
2914
2986
  const issues = [];
2915
- const artifacts = graph.config.artifacts ?? {};
2987
+ const artifacts = STANDARD_ARTIFACTS2;
2916
2988
  for (const [nodePath, node] of graph.nodes) {
2917
2989
  for (const [filename, config] of Object.entries(artifacts)) {
2918
2990
  const hasArtifact = node.artifacts.some((a) => a.filename === filename);
@@ -2968,30 +3040,6 @@ function checkFlowAspectIds(graph) {
2968
3040
  }
2969
3041
  return issues;
2970
3042
  }
2971
- function checkInvalidArtifactConditions(graph) {
2972
- const issues = [];
2973
- const validAspectIds = new Set(graph.aspects.map((a) => a.id));
2974
- const artifacts = graph.config.artifacts ?? {};
2975
- for (const [artifactName, config] of Object.entries(artifacts)) {
2976
- const required = config.required;
2977
- if (typeof required === "object" && required && "when" in required) {
2978
- const when = required.when;
2979
- if (when.startsWith("has_aspect:") || when.startsWith("has_tag:")) {
2980
- const prefix = when.startsWith("has_aspect:") ? "has_aspect:" : "has_tag:";
2981
- const aspectId = when.slice(prefix.length);
2982
- if (!validAspectIds.has(aspectId)) {
2983
- issues.push({
2984
- severity: "error",
2985
- code: "E013",
2986
- rule: "invalid-artifact-condition",
2987
- message: `Artifact '${artifactName}' condition has_aspect:${aspectId} has no corresponding aspect in aspects/`
2988
- });
2989
- }
2990
- }
2991
- }
2992
- }
2993
- return issues;
2994
- }
2995
3043
  async function checkShallowArtifacts(graph) {
2996
3044
  const issues = [];
2997
3045
  const minLen = graph.config.quality?.min_artifact_length ?? 50;
@@ -3013,7 +3061,7 @@ async function checkShallowArtifacts(graph) {
3013
3061
  async function checkWideNodes(graph) {
3014
3062
  const issues = [];
3015
3063
  const maxFiles = graph.config.quality?.max_mapping_source_files ?? 10;
3016
- const projectRoot = path11.dirname(graph.rootPath);
3064
+ const projectRoot = path12.dirname(graph.rootPath);
3017
3065
  for (const [nodePath, node] of graph.nodes) {
3018
3066
  if (node.meta.blackbox) continue;
3019
3067
  const mappingPaths = normalizeMappingPaths(node.meta.mapping);
@@ -3116,11 +3164,11 @@ function checkSchemas(graph) {
3116
3164
  }
3117
3165
  async function checkDirectoriesHaveNodeYaml(graph) {
3118
3166
  const issues = [];
3119
- const modelDir = path11.join(graph.rootPath, "model");
3167
+ const modelDir = path12.join(graph.rootPath, "model");
3120
3168
  async function scanDir(dirPath, segments) {
3121
3169
  const entries = await readdir5(dirPath, { withFileTypes: true });
3122
3170
  const hasNodeYaml = entries.some((e) => e.isFile() && e.name === "yg-node.yaml");
3123
- const dirName = path11.basename(dirPath);
3171
+ const dirName = path12.basename(dirPath);
3124
3172
  if (RESERVED_DIRS.has(dirName)) return;
3125
3173
  const hasFiles = entries.some((e) => e.isFile());
3126
3174
  const hasSubdirs = entries.some((e) => e.isDirectory() && !RESERVED_DIRS.has(e.name) && !e.name.startsWith("."));
@@ -3148,7 +3196,7 @@ async function checkDirectoriesHaveNodeYaml(graph) {
3148
3196
  if (!entry.isDirectory()) continue;
3149
3197
  if (RESERVED_DIRS.has(entry.name)) continue;
3150
3198
  if (entry.name.startsWith(".")) continue;
3151
- await scanDir(path11.join(dirPath, entry.name), [...segments, entry.name]);
3199
+ await scanDir(path12.join(dirPath, entry.name), [...segments, entry.name]);
3152
3200
  }
3153
3201
  }
3154
3202
  try {
@@ -3156,7 +3204,7 @@ async function checkDirectoriesHaveNodeYaml(graph) {
3156
3204
  for (const entry of rootEntries) {
3157
3205
  if (!entry.isDirectory()) continue;
3158
3206
  if (entry.name.startsWith(".")) continue;
3159
- await scanDir(path11.join(modelDir, entry.name), [entry.name]);
3207
+ await scanDir(path12.join(modelDir, entry.name), [entry.name]);
3160
3208
  }
3161
3209
  } catch {
3162
3210
  }
@@ -3173,7 +3221,7 @@ async function expandMappingToFiles(projectRoot, mappingPaths) {
3173
3221
  const entries = await readdir5(absPath, { withFileTypes: true });
3174
3222
  for (const entry of entries) {
3175
3223
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
3176
- const entryPath = path11.join(absPath, entry.name);
3224
+ const entryPath = path12.join(absPath, entry.name);
3177
3225
  if (entry.isFile()) {
3178
3226
  files.push(entryPath);
3179
3227
  } else if (entry.isDirectory()) {
@@ -3185,13 +3233,13 @@ async function expandMappingToFiles(projectRoot, mappingPaths) {
3185
3233
  }
3186
3234
  }
3187
3235
  for (const mp of mappingPaths) {
3188
- await collectFiles(path11.join(projectRoot, mp));
3236
+ await collectFiles(path12.join(projectRoot, mp));
3189
3237
  }
3190
3238
  return files;
3191
3239
  }
3192
3240
  async function checkAnchorPresence(graph) {
3193
3241
  const issues = [];
3194
- const projectRoot = path11.dirname(graph.rootPath);
3242
+ const projectRoot = path12.dirname(graph.rootPath);
3195
3243
  for (const [nodePath, node] of graph.nodes) {
3196
3244
  const aspectsWithAnchors = (node.meta.aspects ?? []).filter((a) => a.anchors && a.anchors.length > 0);
3197
3245
  if (aspectsWithAnchors.length === 0) continue;
@@ -3202,7 +3250,7 @@ async function checkAnchorPresence(graph) {
3202
3250
  const fileContents = [];
3203
3251
  for (const filePath of sourceFiles) {
3204
3252
  try {
3205
- const content = await readFile13(filePath, "utf-8");
3253
+ const content = await readFile14(filePath, "utf-8");
3206
3254
  fileContents.push(content);
3207
3255
  } catch {
3208
3256
  }
@@ -3311,7 +3359,7 @@ function checkMissingDescriptions(graph) {
3311
3359
  }
3312
3360
 
3313
3361
  // src/cli/owner.ts
3314
- import path12 from "path";
3362
+ import path13 from "path";
3315
3363
  import { access as access2 } from "fs/promises";
3316
3364
  function normalizeForMatch(inputPath) {
3317
3365
  return inputPath.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -3341,11 +3389,11 @@ function registerOwnerCommand(program2) {
3341
3389
  const graph = await loadGraph(cwd);
3342
3390
  const repoRoot = projectRootFromGraph(graph.rootPath);
3343
3391
  const rawPath = options.file.trim();
3344
- const absolute = path12.resolve(cwd, rawPath);
3345
- const repoRelative = path12.relative(repoRoot, absolute).split(path12.sep).join("/");
3392
+ const absolute = path13.resolve(cwd, rawPath);
3393
+ const repoRelative = path13.relative(repoRoot, absolute).split(path13.sep).join("/");
3346
3394
  const result = findOwner(graph, repoRoot, repoRelative);
3347
3395
  if (!result.nodePath) {
3348
- const absPath = path12.resolve(repoRoot, result.file);
3396
+ const absPath = path13.resolve(repoRoot, result.file);
3349
3397
  let exists = true;
3350
3398
  try {
3351
3399
  await access2(absPath);
@@ -3398,7 +3446,7 @@ function collectRelevantNodePaths(graph, nodePath) {
3398
3446
  return relevant;
3399
3447
  }
3400
3448
  function registerBuildCommand(program2) {
3401
- 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) => {
3449
+ 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").option("--self", "Only include the node\u2019s own artifacts (no hierarchy, dependencies, aspects, flows)").action(async (options) => {
3402
3450
  try {
3403
3451
  if (!options.node && !options.file) {
3404
3452
  process.stderr.write("Error: either '--node <path>' or '--file <path>' is required\n");
@@ -3446,8 +3494,8 @@ function registerBuildCommand(program2) {
3446
3494
  process.stderr.write(msg);
3447
3495
  process.exit(1);
3448
3496
  }
3449
- const pkg2 = await buildContext(graph, nodePath);
3450
- const mapOutput = toContextMapOutput(pkg2, graph);
3497
+ const pkg2 = await buildContext(graph, nodePath, { selfOnly: options.self });
3498
+ const mapOutput = toContextMapOutput(pkg2, graph, { selfOnly: options.self });
3451
3499
  let output = formatContextYaml(mapOutput);
3452
3500
  if (options.full) {
3453
3501
  const seen = /* @__PURE__ */ new Set();
@@ -3569,12 +3617,12 @@ ${errors.length} errors, ${warnings.length} warnings.
3569
3617
  import chalk2 from "chalk";
3570
3618
 
3571
3619
  // src/io/drift-state-store.ts
3572
- import { readFile as readFile14, writeFile as writeFile5, stat as stat5, readdir as readdir6, mkdir as mkdir3, rm as rm2 } from "fs/promises";
3573
- import path13 from "path";
3620
+ import { readFile as readFile15, writeFile as writeFile6, stat as stat5, readdir as readdir6, mkdir as mkdir3, rm as rm2 } from "fs/promises";
3621
+ import path14 from "path";
3574
3622
  import { parse as yamlParse } from "yaml";
3575
3623
  var DRIFT_STATE_DIR = ".drift-state";
3576
3624
  function nodeStatePath(yggRoot, nodePath) {
3577
- return path13.join(yggRoot, DRIFT_STATE_DIR, `${nodePath}.json`);
3625
+ return path14.join(yggRoot, DRIFT_STATE_DIR, `${nodePath}.json`);
3578
3626
  }
3579
3627
  async function scanJsonFiles(dir, baseDir) {
3580
3628
  const results = [];
@@ -3585,12 +3633,12 @@ async function scanJsonFiles(dir, baseDir) {
3585
3633
  return results;
3586
3634
  }
3587
3635
  for (const entry of entries) {
3588
- const fullPath = path13.join(dir, entry.name);
3636
+ const fullPath = path14.join(dir, entry.name);
3589
3637
  if (entry.isDirectory()) {
3590
3638
  const nested = await scanJsonFiles(fullPath, baseDir);
3591
3639
  results.push(...nested);
3592
3640
  } else if (entry.isFile() && entry.name.endsWith(".json")) {
3593
- const relPath = path13.relative(baseDir, fullPath);
3641
+ const relPath = path14.relative(baseDir, fullPath);
3594
3642
  const nodePath = relPath.replace(/\\/g, "/").replace(/\.json$/, "");
3595
3643
  results.push(nodePath);
3596
3644
  }
@@ -3598,13 +3646,13 @@ async function scanJsonFiles(dir, baseDir) {
3598
3646
  return results;
3599
3647
  }
3600
3648
  async function removeEmptyParents(filePath, stopDir) {
3601
- let dir = path13.dirname(filePath);
3649
+ let dir = path14.dirname(filePath);
3602
3650
  while (dir !== stopDir && dir.startsWith(stopDir)) {
3603
3651
  try {
3604
3652
  const entries = await readdir6(dir);
3605
3653
  if (entries.length === 0) {
3606
3654
  await rm2(dir, { recursive: true });
3607
- dir = path13.dirname(dir);
3655
+ dir = path14.dirname(dir);
3608
3656
  } else {
3609
3657
  break;
3610
3658
  }
@@ -3616,7 +3664,7 @@ async function removeEmptyParents(filePath, stopDir) {
3616
3664
  async function readNodeDriftState(yggRoot, nodePath) {
3617
3665
  try {
3618
3666
  const filePath = nodeStatePath(yggRoot, nodePath);
3619
- const content = await readFile14(filePath, "utf-8");
3667
+ const content = await readFile15(filePath, "utf-8");
3620
3668
  const parsed = JSON.parse(content);
3621
3669
  return parsed;
3622
3670
  } catch {
@@ -3625,12 +3673,12 @@ async function readNodeDriftState(yggRoot, nodePath) {
3625
3673
  }
3626
3674
  async function writeNodeDriftState(yggRoot, nodePath, nodeState) {
3627
3675
  const filePath = nodeStatePath(yggRoot, nodePath);
3628
- await mkdir3(path13.dirname(filePath), { recursive: true });
3676
+ await mkdir3(path14.dirname(filePath), { recursive: true });
3629
3677
  const content = JSON.stringify(nodeState, null, 2) + "\n";
3630
- await writeFile5(filePath, content, "utf-8");
3678
+ await writeFile6(filePath, content, "utf-8");
3631
3679
  }
3632
3680
  async function garbageCollectDriftState(yggRoot, validNodePaths) {
3633
- const driftDir = path13.join(yggRoot, DRIFT_STATE_DIR);
3681
+ const driftDir = path14.join(yggRoot, DRIFT_STATE_DIR);
3634
3682
  const allNodePaths = await scanJsonFiles(driftDir, driftDir);
3635
3683
  const removed = [];
3636
3684
  for (const nodePath of allNodePaths) {
@@ -3644,7 +3692,7 @@ async function garbageCollectDriftState(yggRoot, validNodePaths) {
3644
3692
  return removed.sort();
3645
3693
  }
3646
3694
  async function readDriftState(yggRoot) {
3647
- const driftPath = path13.join(yggRoot, DRIFT_STATE_DIR);
3695
+ const driftPath = path14.join(yggRoot, DRIFT_STATE_DIR);
3648
3696
  let driftStat;
3649
3697
  try {
3650
3698
  driftStat = await stat5(driftPath);
@@ -3652,7 +3700,7 @@ async function readDriftState(yggRoot) {
3652
3700
  return {};
3653
3701
  }
3654
3702
  if (driftStat.isFile()) {
3655
- const content = await readFile14(driftPath, "utf-8");
3703
+ const content = await readFile15(driftPath, "utf-8");
3656
3704
  let raw;
3657
3705
  try {
3658
3706
  raw = JSON.parse(content);
@@ -3684,20 +3732,20 @@ async function readDriftState(yggRoot) {
3684
3732
  }
3685
3733
 
3686
3734
  // src/utils/hash.ts
3687
- import { readFile as readFile15, readdir as readdir7, stat as stat6 } from "fs/promises";
3688
- import path14 from "path";
3735
+ import { readFile as readFile16, readdir as readdir7, stat as stat6 } from "fs/promises";
3736
+ import path15 from "path";
3689
3737
  import { createHash } from "crypto";
3690
3738
  import { createRequire } from "module";
3691
3739
  var require2 = createRequire(import.meta.url);
3692
3740
  var ignoreFactory = require2("ignore");
3693
3741
  async function hashFile(filePath) {
3694
- const content = await readFile15(filePath);
3742
+ const content = await readFile16(filePath);
3695
3743
  return createHash("sha256").update(content).digest("hex");
3696
3744
  }
3697
3745
  async function loadRootGitignoreStack(projectRoot) {
3698
3746
  if (!projectRoot) return [];
3699
3747
  try {
3700
- const content = await readFile15(path14.join(projectRoot, ".gitignore"), "utf-8");
3748
+ const content = await readFile16(path15.join(projectRoot, ".gitignore"), "utf-8");
3701
3749
  const matcher = ignoreFactory();
3702
3750
  matcher.add(content);
3703
3751
  return [{ basePath: projectRoot, matcher }];
@@ -3707,7 +3755,7 @@ async function loadRootGitignoreStack(projectRoot) {
3707
3755
  }
3708
3756
  function isIgnoredByStack(candidatePath, stack) {
3709
3757
  for (const { basePath, matcher } of stack) {
3710
- const relativePath = path14.relative(basePath, candidatePath);
3758
+ const relativePath = path15.relative(basePath, candidatePath);
3711
3759
  if (relativePath === "" || relativePath.startsWith("..")) continue;
3712
3760
  if (matcher.ignores(relativePath) || matcher.ignores(relativePath + "/")) return true;
3713
3761
  }
@@ -3722,7 +3770,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
3722
3770
  const gitignoreStack = await loadRootGitignoreStack(projectRoot);
3723
3771
  const allFiles = [];
3724
3772
  for (const tf of trackedFiles) {
3725
- const absPath = path14.join(projectRoot, tf.path);
3773
+ const absPath = path15.join(projectRoot, tf.path);
3726
3774
  try {
3727
3775
  const st = await stat6(absPath);
3728
3776
  if (st.isDirectory()) {
@@ -3732,7 +3780,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
3732
3780
  });
3733
3781
  for (const entry of dirEntries) {
3734
3782
  allFiles.push({
3735
- relPath: path14.join(tf.path, entry.relPath).replace(/\\/g, "/"),
3783
+ relPath: path15.join(tf.path, entry.relPath).replace(/\\/g, "/"),
3736
3784
  absPath: entry.absPath,
3737
3785
  mtimeMs: entry.mtimeMs
3738
3786
  });
@@ -3772,7 +3820,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
3772
3820
  async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
3773
3821
  let stack = options.gitignoreStack ?? [];
3774
3822
  try {
3775
- const localContent = await readFile15(path14.join(directoryPath, ".gitignore"), "utf-8");
3823
+ const localContent = await readFile16(path15.join(directoryPath, ".gitignore"), "utf-8");
3776
3824
  const localMatcher = ignoreFactory();
3777
3825
  localMatcher.add(localContent);
3778
3826
  stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
@@ -3782,7 +3830,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3782
3830
  const dirs = [];
3783
3831
  const files = [];
3784
3832
  for (const entry of entries) {
3785
- const absoluteChildPath = path14.join(directoryPath, entry.name);
3833
+ const absoluteChildPath = path15.join(directoryPath, entry.name);
3786
3834
  if (isIgnoredByStack(absoluteChildPath, stack)) continue;
3787
3835
  if (entry.isDirectory()) dirs.push(absoluteChildPath);
3788
3836
  else if (entry.isFile()) files.push(absoluteChildPath);
@@ -3795,7 +3843,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3795
3843
  Promise.all(files.map(async (f) => {
3796
3844
  const fileStat = await stat6(f);
3797
3845
  return {
3798
- relPath: path14.relative(rootDirectoryPath, f),
3846
+ relPath: path15.relative(rootDirectoryPath, f),
3799
3847
  absPath: f,
3800
3848
  mtimeMs: fileStat.mtimeMs
3801
3849
  };
@@ -3808,15 +3856,15 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3808
3856
  }
3809
3857
 
3810
3858
  // src/core/context-files.ts
3811
- import path15 from "path";
3859
+ import path16 from "path";
3812
3860
  var STRUCTURAL_RELATION_TYPES2 = /* @__PURE__ */ new Set(["uses", "calls", "extends", "implements"]);
3813
3861
  function collectTrackedFiles(node, graph) {
3814
3862
  const seen = /* @__PURE__ */ new Set();
3815
3863
  const result = [];
3816
- const projectRoot = path15.dirname(graph.rootPath);
3817
- const yggPrefix = path15.relative(projectRoot, graph.rootPath);
3818
- const yggPrefixNormalized = yggPrefix.split(path15.sep).join("/");
3819
- const configArtifactKeys = new Set(Object.keys(graph.config.artifacts ?? {}));
3864
+ const projectRoot = path16.dirname(graph.rootPath);
3865
+ const yggPrefix = path16.relative(projectRoot, graph.rootPath);
3866
+ const yggPrefixNormalized = yggPrefix.split(path16.sep).join("/");
3867
+ const configArtifactKeys = new Set(Object.keys(STANDARD_ARTIFACTS2));
3820
3868
  function addFile(filePath, category) {
3821
3869
  if (seen.has(filePath)) return;
3822
3870
  seen.add(filePath);
@@ -3864,7 +3912,7 @@ function collectTrackedFiles(node, graph) {
3864
3912
  if (!STRUCTURAL_RELATION_TYPES2.has(relation.type)) continue;
3865
3913
  const target = graph.nodes.get(relation.target);
3866
3914
  if (!target) continue;
3867
- const structuralFilenames = Object.entries(graph.config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
3915
+ const structuralFilenames = Object.entries(STANDARD_ARTIFACTS2).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
3868
3916
  const structuralArts = structuralFilenames.filter(
3869
3917
  (filename) => target.artifacts.some((a) => a.filename === filename)
3870
3918
  );
@@ -3893,7 +3941,7 @@ function collectTrackedFiles(node, graph) {
3893
3941
  if (relation.type !== "emits" && relation.type !== "listens") continue;
3894
3942
  const target = graph.nodes.get(relation.target);
3895
3943
  if (!target) continue;
3896
- const structuralFilenames = Object.entries(graph.config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
3944
+ const structuralFilenames = Object.entries(STANDARD_ARTIFACTS2).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
3897
3945
  const filterFilenames = structuralFilenames.length > 0 ? structuralFilenames : [...configArtifactKeys];
3898
3946
  for (const filename of filterFilenames) {
3899
3947
  if (target.artifacts.some((a) => a.filename === filename)) {
@@ -3928,7 +3976,7 @@ function collectParticipatingFlows2(graph, node, ancestors) {
3928
3976
 
3929
3977
  // src/core/drift-detector.ts
3930
3978
  import { access as access3 } from "fs/promises";
3931
- import path16 from "path";
3979
+ import path17 from "path";
3932
3980
  function getChildMappingExclusions(graph, nodePath) {
3933
3981
  const node = graph.nodes.get(nodePath);
3934
3982
  if (!node) return [];
@@ -3950,7 +3998,7 @@ function getChildMappingExclusions(graph, nodePath) {
3950
3998
  return exclusions;
3951
3999
  }
3952
4000
  async function detectDrift(graph, filterNodePath) {
3953
- const projectRoot = path16.dirname(graph.rootPath);
4001
+ const projectRoot = path17.dirname(graph.rootPath);
3954
4002
  const driftState = await readDriftState(graph.rootPath);
3955
4003
  const entries = [];
3956
4004
  for (const [nodePath, node] of graph.nodes) {
@@ -4033,14 +4081,14 @@ async function detectDrift(graph, filterNodePath) {
4033
4081
  };
4034
4082
  }
4035
4083
  function categorizeFile(filePath, _rootPath, projectRoot) {
4036
- const yggPrefix = path16.relative(projectRoot, _rootPath);
4037
- const normalizedPrefix = yggPrefix.split(path16.sep).join("/");
4084
+ const yggPrefix = path17.relative(projectRoot, _rootPath);
4085
+ const normalizedPrefix = yggPrefix.split(path17.sep).join("/");
4038
4086
  const normalizedFilePath = filePath.replace(/\\/g, "/");
4039
4087
  return normalizedFilePath.startsWith(normalizedPrefix) ? "graph" : "source";
4040
4088
  }
4041
4089
  async function allPathsMissing(projectRoot, mappingPaths) {
4042
4090
  for (const mp of mappingPaths) {
4043
- const absPath = path16.join(projectRoot, mp);
4091
+ const absPath = path17.join(projectRoot, mp);
4044
4092
  try {
4045
4093
  await access3(absPath);
4046
4094
  return false;
@@ -4050,7 +4098,7 @@ async function allPathsMissing(projectRoot, mappingPaths) {
4050
4098
  return true;
4051
4099
  }
4052
4100
  async function syncDriftState(graph, nodePath) {
4053
- const projectRoot = path16.dirname(graph.rootPath);
4101
+ const projectRoot = path17.dirname(graph.rootPath);
4054
4102
  const node = graph.nodes.get(nodePath);
4055
4103
  if (!node) throw new Error(`Node not found: ${nodePath}`);
4056
4104
  if (!node.meta.mapping) throw new Error(`Node has no mapping: ${nodePath}`);
@@ -4065,7 +4113,7 @@ async function syncDriftState(graph, nodePath) {
4065
4113
  if (previousHash && previousHash !== canonicalHash && existingEntry?.files) {
4066
4114
  let hasSourceChange = false;
4067
4115
  let hasGraphChange = false;
4068
- const yggPrefix = path16.relative(projectRoot, graph.rootPath).split(path16.sep).join("/");
4116
+ const yggPrefix = path17.relative(projectRoot, graph.rootPath).split(path17.sep).join("/");
4069
4117
  for (const [filePath, hash] of Object.entries(fileHashes)) {
4070
4118
  const storedHash = existingEntry.files[filePath];
4071
4119
  if (storedHash && storedHash === hash) continue;
@@ -4331,7 +4379,7 @@ function registerStatusCommand(program2) {
4331
4379
  const warningCount = validation.issues.filter(
4332
4380
  (issue) => issue.severity === "warning"
4333
4381
  ).length;
4334
- const configuredArtifactTypes = Object.keys(graph.config.artifacts ?? {});
4382
+ const configuredArtifactTypes = Object.keys(STANDARD_ARTIFACTS2);
4335
4383
  const totalSlots = graph.nodes.size * configuredArtifactTypes.length;
4336
4384
  let filledSlots = 0;
4337
4385
  let mappedNodeCount = 0;
@@ -4405,10 +4453,10 @@ function registerTreeCommand(program2) {
4405
4453
  let roots;
4406
4454
  let showProjectName;
4407
4455
  if (options.root?.trim()) {
4408
- const path19 = options.root.trim().replace(/\/$/, "");
4409
- const node = graph.nodes.get(path19);
4456
+ const path20 = options.root.trim().replace(/\/$/, "");
4457
+ const node = graph.nodes.get(path20);
4410
4458
  if (!node) {
4411
- process.stderr.write(`Error: path '${path19}' not found
4459
+ process.stderr.write(`Error: path '${path20}' not found
4412
4460
  `);
4413
4461
  process.exit(1);
4414
4462
  }
@@ -4453,7 +4501,7 @@ function printNode(node, prefix, isLast, depth, maxDepth) {
4453
4501
 
4454
4502
  // src/core/dependency-resolver.ts
4455
4503
  import { execSync } from "child_process";
4456
- import path17 from "path";
4504
+ import path18 from "path";
4457
4505
  var STRUCTURAL_RELATION_TYPES3 = /* @__PURE__ */ new Set(["uses", "calls", "extends", "implements"]);
4458
4506
  var EVENT_RELATION_TYPES2 = /* @__PURE__ */ new Set(["emits", "listens"]);
4459
4507
  function filterRelationType(relType, filter) {
@@ -4530,7 +4578,7 @@ function registerDepsCommand(program2) {
4530
4578
  // src/core/graph-from-git.ts
4531
4579
  import { mkdtemp, rm as rm3 } from "fs/promises";
4532
4580
  import { tmpdir } from "os";
4533
- import path18 from "path";
4581
+ import path19 from "path";
4534
4582
  import { execSync as execSync2 } from "child_process";
4535
4583
  async function loadGraphFromRef(projectRoot, ref = "HEAD") {
4536
4584
  const yggPath = ".yggdrasil";
@@ -4541,8 +4589,8 @@ async function loadGraphFromRef(projectRoot, ref = "HEAD") {
4541
4589
  return null;
4542
4590
  }
4543
4591
  try {
4544
- tmpDir = await mkdtemp(path18.join(tmpdir(), "ygg-git-"));
4545
- const archivePath = path18.join(tmpDir, "archive.tar");
4592
+ tmpDir = await mkdtemp(path19.join(tmpdir(), "ygg-git-"));
4593
+ const archivePath = path19.join(tmpDir, "archive.tar");
4546
4594
  execSync2(`git archive ${ref} ${yggPath} -o "${archivePath}"`, {
4547
4595
  cwd: projectRoot,
4548
4596
  stdio: "pipe"
@@ -4612,14 +4660,14 @@ function buildTransitiveChains(targetNode, direct, allDependents, reverse) {
4612
4660
  }
4613
4661
  const chains = [];
4614
4662
  for (const node of transitiveOnly) {
4615
- const path19 = [];
4663
+ const path20 = [];
4616
4664
  let current = node;
4617
4665
  while (current) {
4618
- path19.unshift(current);
4666
+ path20.unshift(current);
4619
4667
  current = parent.get(current);
4620
4668
  }
4621
- if (path19.length >= 3) {
4622
- chains.push(path19.slice(1).map((p) => `<- ${p}`).join(" "));
4669
+ if (path20.length >= 3) {
4670
+ chains.push(path20.slice(1).map((p) => `<- ${p}`).join(" "));
4623
4671
  }
4624
4672
  }
4625
4673
  return chains.sort();
@@ -4663,14 +4711,14 @@ function collectIndirectDependents(graph, directlyAffected) {
4663
4711
  }
4664
4712
  for (const [node] of parent) {
4665
4713
  if (directSet.has(node)) continue;
4666
- const path19 = [node];
4714
+ const path20 = [node];
4667
4715
  let current = node;
4668
4716
  while (parent.has(current)) {
4669
4717
  current = parent.get(current);
4670
- path19.push(current);
4718
+ path20.push(current);
4671
4719
  }
4672
- const chain = path19.map((p) => `<- ${p}`).join(" ");
4673
- const depth = path19.length;
4720
+ const chain = path20.map((p) => `<- ${p}`).join(" ");
4721
+ const depth = path20.length;
4674
4722
  const existing = bestChain.get(node);
4675
4723
  if (!existing || depth < existing.depth) {
4676
4724
  bestChain.set(node, { chain, depth });