@chrisdudek/yg 2.12.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
@@ -60,14 +46,30 @@ This is your operating manual for working in a Yggdrasil-managed repository.
60
46
 
61
47
  <critical_protocol>
62
48
  BEFORE starting any task \u2014 brainstorming, design, planning, OR implementation:
63
- \`yg select --task "<goal>"\` \u2192 \`yg build-context\` on each result \u2192 read ALL artifact files
64
- This is how you learn WHAT the system already knows before proposing anything.
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.
65
56
 
66
57
  BEFORE reading, analyzing, or modifying ANY source file:
67
58
  \`yg build-context --file <path>\`
68
- One command. No exceptions. No "I'll do it later." No "this is just analysis."
69
-
70
- Both triggers apply regardless of what instructed the task \u2014 skills, plans, workflows, user requests.
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.
71
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.
72
74
  </critical_protocol>
73
75
 
@@ -82,21 +84,24 @@ Yggdrasil is persistent semantic memory stored in \`.yggdrasil/\`. It maps the r
82
84
  EVERY conversation: yg preflight \u2014 no exceptions.
83
85
 
84
86
  BEFORE any task (brainstorming, design, planning, implementation):
85
- yg select --task "<goal>" \u2192 yg build-context on results \u2192 read ALL artifacts
86
- The graph is your primary knowledge source. Raw file reads supplement it, never replace it.
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.
87
94
 
88
95
  BEFORE any source file interaction:
89
- 1. yg build-context --file <path>
90
- One command: resolves owner, assembles context.
91
- Read the YAML map \u2014 glossary first (aspect/flow definitions),
92
- then artifact files listed on each element.
93
- For blast radius: also run yg impact --file <path>.
94
- 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>.
95
100
 
96
101
  AFTER modifying:
97
- 2. Update graph artifacts (per file, not batched)
98
- 3. yg validate \u2014 fix all errors
99
- 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>
100
105
 
101
106
  ALWAYS: establish graph coverage before modifying code.
102
107
  ALWAYS: run yg build-context --file before reading source.
@@ -114,9 +119,9 @@ You are not allowed to edit or create source code without establishing graph cov
114
119
 
115
120
  **Step 2a** \u2014 Owner found: execute checklist:
116
121
 
117
- - [ ] 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.
118
123
  - [ ] 2. Assess blast radius: \`yg impact --node <node_path>\` \u2014 review dependents, descendants, and co-aspect nodes before changing interfaces or shared behavior
119
- - [ ] 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.
120
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\`.
121
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.
122
127
  - [ ] 5. Run \`yg validate\` \u2014 fix all errors (if unfixable after 3 attempts \u2192 stop, report to user)
@@ -134,12 +139,14 @@ You are not allowed to edit or create source code without establishing graph cov
134
139
 
135
140
  *Greenfield (new code):* Only Option A. Blackbox is forbidden for new code. Follow the graph-first workflow:
136
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.
137
143
  1. Create aspects first (cross-cutting requirements the new code must satisfy)
138
144
  2. Create flows if the code participates in a business process
139
145
  3. Create nodes with full artifacts \u2014 description in \`yg-node.yaml\`, responsibility, interface, internals
140
146
  4. Review the context package (\`yg build-context\`) \u2014 it is now the behavioral specification
141
- 5. Implement code that satisfies the specification
142
- 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)
143
150
 
144
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.
145
152
 
@@ -194,13 +201,15 @@ Result: graph is stale, next agent asks user the same questions
194
201
  User: "Here are the spec docs. Implement the admin blog editor."
195
202
 
196
203
  1. Read ALL spec docs (blog-editor.md, autosave.md, user-persona.md, version-history.md)
197
- 2. Extract cross-cutting patterns \u2192 create aspects (admin-ux-rules, autosave, version-history) if they don't exist
198
- 3. Create flow if the blog participates in a business process
199
- 4. Create node admin/blog with artifacts populated from spec (responsibility, interface, internals)
200
- 5. Run yg build-context \u2192 the context package is now the behavioral specification
201
- 6. Implement code that satisfies the specification
202
- 7. Update artifacts with any implementation details that emerged during coding
203
- 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?
204
213
 
205
214
  </example_correct>
206
215
 
@@ -208,13 +217,13 @@ User: "Here are the spec docs. Implement the admin blog editor."
208
217
 
209
218
  User: "Here are the spec docs. Implement the admin blog editor."
210
219
 
211
- 1. Read blog-editor.md spec
212
- 2. Implement all the code \u2190 WRONG: spec knowledge not captured in graph
213
- 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
214
223
  4. Write responsibility.md summarizing what the code does \u2190 WRONG: describes code, not spec intent
215
- 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
216
225
 
217
- 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.
218
227
 
219
228
  </example_wrong>
220
229
 
@@ -301,7 +310,7 @@ var REFERENCE = `## REFERENCE
301
310
 
302
311
  \`\`\`
303
312
  .yggdrasil/
304
- yg-config.yaml \u2190 version, vocabulary, node types, artifact rules, required aspects
313
+ yg-config.yaml \u2190 version, vocabulary, node types, required aspects
305
314
  model/ \u2190 what exists: nodes, hierarchy, relations, file mappings
306
315
  aspects/ \u2190 what must: cross-cutting requirements with rationale and guidance
307
316
  flows/ \u2190 why and in what process: business processes with node participation
@@ -327,7 +336,7 @@ Three artifacts capture node knowledge at three levels:
327
336
 
328
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\`.
329
338
 
330
- 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.
331
340
 
332
341
  ### Context Assembly
333
342
 
@@ -342,9 +351,14 @@ Projects can define additional artifact types in \`yg-config.yaml\` under \`arti
342
351
 
343
352
  All artifact paths are relative to \`.yggdrasil/\` \u2014 construct full path as \`.yggdrasil/<path>\`.
344
353
 
345
- **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:
355
+
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.
346
360
 
347
- The glossary at the top defines all aspects and flows \u2014 read it first to understand IDs used throughout.
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).
348
362
 
349
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.
350
364
 
@@ -354,7 +368,7 @@ Artifact paths are stable identifiers within a session. When building context fo
354
368
 
355
369
  When you encounter information, route it to the correct location:
356
370
 
357
- - **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)
358
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\`
359
373
  - **Business process** \u2192 flow (\`flows/<name>/\` with \`yg-flow.yaml\` + \`description.md\`). Ask user if process unclear.
360
374
  - **Shared across a domain** \u2192 parent node artifact. Children receive it through hierarchy.
@@ -465,6 +479,7 @@ yg owner --file <path> Find the node that owns this file (quick che
465
479
  yg build-context --file <path> Resolve owner + assemble context in one step.
466
480
  yg build-context --node <path> Assemble context map for a known node.
467
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).
468
483
  yg tree [--root <path>] [--depth N] Print graph structure.
469
484
  yg aspects List aspects with metadata (YAML output).
470
485
  yg flows List flows with metadata (YAML output).
@@ -489,7 +504,7 @@ yg drift-sync --node <path> [--recursive] | --all
489
504
 
490
505
  | What you have | Where it goes |
491
506
  |---|---|
492
- | 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\`) |
493
508
  | Rule that applies to many nodes | Aspect (content \`.md\` files in \`aspects/<id>/\`) |
494
509
  | Architectural invariant for a node type | Required aspect in \`yg-config.yaml node_types\` |
495
510
  | Business process participation | Flow (\`yg-flow.yaml nodes\`) |
@@ -564,11 +579,15 @@ What matters is the ACTION you are performing, not what instructed it. If the ac
564
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. |
565
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. |
566
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. |
567
585
 
568
586
  ### Failure States
569
587
 
570
588
  You have broken Yggdrasil if you do any of the following:
571
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.
572
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).
573
592
  - \u274C Modified source code without updating graph artifacts before moving to the next file, or vice versa.
574
593
  - \u274C Batched graph updates to "do later" \u2014 deferred = forgotten. Update after EACH file.
@@ -598,7 +617,7 @@ Per area checklist:
598
617
  - [ ] 2. Determine node granularity \u2014 propose to user if unclear
599
618
  - [ ] 3. Create node directory, read \`schemas/yg-node.yaml\`, create \`yg-node.yaml\`
600
619
  - [ ] 3b. Write \`description\` in \`yg-node.yaml\` \u2014 a short summary of what the node does
601
- - [ ] 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
602
621
  - [ ] 5. Identify relations \u2014 add to \`yg-node.yaml\`
603
622
  - [ ] 6. Identify cross-cutting requirements \u2014 add matching aspects, create if needed
604
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\`
@@ -1220,38 +1239,73 @@ async function transformSingleNode(filePath, actions, warnings) {
1220
1239
  }
1221
1240
  }
1222
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
+
1223
1272
  // src/migrations/index.ts
1224
1273
  var MIGRATIONS = [
1225
1274
  {
1226
1275
  to: "2.0.0",
1227
1276
  description: "Rename YAML files to yg-* prefix, restructure config, convert aspects format",
1228
1277
  run: migrateTo2
1278
+ },
1279
+ {
1280
+ to: "3.0.0",
1281
+ description: "Remove artifacts section from config (now hardcoded in CLI)",
1282
+ run: migrateTo3
1229
1283
  }
1230
1284
  ];
1231
1285
 
1232
1286
  // src/cli/init.ts
1233
1287
  function getGraphSchemasDir() {
1234
- const currentDir = path4.dirname(fileURLToPath(import.meta.url));
1235
- const packageRoot = path4.join(currentDir, "..");
1236
- 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");
1237
1291
  }
1238
1292
  function getCliVersion() {
1239
- const currentDir = path4.dirname(fileURLToPath(import.meta.url));
1240
- const packageRoot = path4.join(currentDir, "..");
1241
- 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"));
1242
1296
  return pkg2.version;
1243
1297
  }
1244
1298
  async function refreshSchemas(yggRoot) {
1245
- const schemasDir = path4.join(yggRoot, "schemas");
1299
+ const schemasDir = path5.join(yggRoot, "schemas");
1246
1300
  await mkdir2(schemasDir, { recursive: true });
1247
1301
  const graphSchemasDir = getGraphSchemasDir();
1248
1302
  try {
1249
1303
  const entries = await readdir2(graphSchemasDir, { withFileTypes: true });
1250
1304
  const schemaFiles = entries.filter((e) => e.isFile()).map((e) => e.name);
1251
1305
  for (const file of schemaFiles) {
1252
- const srcPath = path4.join(graphSchemasDir, file);
1253
- const content = await readFile4(srcPath, "utf-8");
1254
- 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");
1255
1309
  }
1256
1310
  } catch {
1257
1311
  }
@@ -1264,7 +1318,7 @@ function registerInitCommand(program2) {
1264
1318
  "generic"
1265
1319
  ).option("--upgrade", "Refresh rules only (when .yggdrasil/ already exists)").action(async (options) => {
1266
1320
  const projectRoot = process.cwd();
1267
- const yggRoot = path4.join(projectRoot, ".yggdrasil");
1321
+ const yggRoot = path5.join(projectRoot, ".yggdrasil");
1268
1322
  let upgradeMode = false;
1269
1323
  try {
1270
1324
  const statResult = await stat2(yggRoot);
@@ -1327,23 +1381,23 @@ function registerInitCommand(program2) {
1327
1381
  await refreshSchemas(yggRoot);
1328
1382
  const rulesPath2 = await installRulesForPlatform(projectRoot, platform);
1329
1383
  process.stdout.write("\u2713 Rules refreshed.\n");
1330
- process.stdout.write(` ${path4.relative(projectRoot, rulesPath2)}
1384
+ process.stdout.write(` ${path5.relative(projectRoot, rulesPath2)}
1331
1385
  `);
1332
1386
  return;
1333
1387
  }
1334
- await mkdir2(path4.join(yggRoot, "model"), { recursive: true });
1335
- await mkdir2(path4.join(yggRoot, "aspects"), { recursive: true });
1336
- await mkdir2(path4.join(yggRoot, "flows"), { recursive: true });
1337
- 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");
1338
1392
  await mkdir2(schemasDir, { recursive: true });
1339
1393
  const graphSchemasDir = getGraphSchemasDir();
1340
1394
  try {
1341
1395
  const entries = await readdir2(graphSchemasDir, { withFileTypes: true });
1342
1396
  const schemaFiles = entries.filter((e) => e.isFile()).map((e) => e.name);
1343
1397
  for (const file of schemaFiles) {
1344
- const srcPath = path4.join(graphSchemasDir, file);
1345
- const content = await readFile4(srcPath, "utf-8");
1346
- 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");
1347
1401
  }
1348
1402
  } catch (err) {
1349
1403
  process.stderr.write(
@@ -1351,8 +1405,8 @@ function registerInitCommand(program2) {
1351
1405
  `
1352
1406
  );
1353
1407
  }
1354
- await writeFile4(path4.join(yggRoot, "yg-config.yaml"), DEFAULT_CONFIG, "utf-8");
1355
- 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");
1356
1410
  const rulesPath = await installRulesForPlatform(projectRoot, platform);
1357
1411
  process.stdout.write("\u2713 Yggdrasil initialized.\n\n");
1358
1412
  process.stdout.write("Created:\n");
@@ -1362,7 +1416,7 @@ function registerInitCommand(program2) {
1362
1416
  process.stdout.write(" .yggdrasil/aspects/\n");
1363
1417
  process.stdout.write(" .yggdrasil/flows/\n");
1364
1418
  process.stdout.write(" .yggdrasil/schemas/ (yg-config, yg-node, yg-aspect, yg-flow)\n");
1365
- process.stdout.write(` ${path4.relative(projectRoot, rulesPath)} (rules)
1419
+ process.stdout.write(` ${path5.relative(projectRoot, rulesPath)} (rules)
1366
1420
 
1367
1421
  `);
1368
1422
  process.stdout.write("Next steps:\n");
@@ -1373,20 +1427,39 @@ function registerInitCommand(program2) {
1373
1427
  }
1374
1428
 
1375
1429
  // src/core/graph-loader.ts
1376
- import { readdir as readdir4, readFile as readFile11 } from "fs/promises";
1377
- 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
+ };
1378
1451
 
1379
1452
  // src/io/config-parser.ts
1380
- import { readFile as readFile5 } from "fs/promises";
1381
- import { parse as parseYaml3 } from "yaml";
1453
+ import { readFile as readFile6 } from "fs/promises";
1454
+ import { parse as parseYaml4 } from "yaml";
1382
1455
  var DEFAULT_QUALITY = {
1383
1456
  min_artifact_length: 50,
1384
1457
  max_direct_relations: 10,
1385
1458
  context_budget: { warning: 1e4, error: 2e4, own_warning: void 0 }
1386
1459
  };
1387
1460
  async function parseConfig(filePath) {
1388
- const content = await readFile5(filePath, "utf-8");
1389
- const raw = parseYaml3(content);
1461
+ const content = await readFile6(filePath, "utf-8");
1462
+ const raw = parseYaml4(content);
1390
1463
  if (!raw || typeof raw !== "object") {
1391
1464
  throw new Error(`yg-config.yaml: file is empty or not a valid YAML mapping`);
1392
1465
  }
@@ -1412,35 +1485,6 @@ async function parseConfig(filePath) {
1412
1485
  required_aspects: requiredAspects && requiredAspects.length > 0 ? requiredAspects : void 0
1413
1486
  };
1414
1487
  }
1415
- const artifacts = raw.artifacts;
1416
- if (!artifacts || typeof artifacts !== "object" || Array.isArray(artifacts) || Object.keys(artifacts).length === 0) {
1417
- throw new Error(`yg-config.yaml: 'artifacts' must be a non-empty object`);
1418
- }
1419
- const artifactsMap = {};
1420
- for (const [key, val] of Object.entries(artifacts)) {
1421
- if (key === "yg-node.yaml") {
1422
- throw new Error(`yg-config.yaml: artifact name 'yg-node.yaml' is reserved`);
1423
- }
1424
- const a = val;
1425
- const required = a.required;
1426
- if (required !== "always" && required !== "never" && (typeof required !== "object" || !required || !("when" in required))) {
1427
- throw new Error(`yg-config.yaml: artifact '${key}' has invalid 'required' field`);
1428
- }
1429
- if (typeof required === "object" && required && "when" in required) {
1430
- const when = required.when;
1431
- const validWhen = when === "has_incoming_relations" || when === "has_outgoing_relations" || typeof when === "string" && (when.startsWith("has_aspect:") || when.startsWith("has_tag:"));
1432
- if (!validWhen) {
1433
- throw new Error(
1434
- `yg-config.yaml: artifact '${key}' has invalid 'required.when': must be has_incoming_relations, has_outgoing_relations, or has_aspect:<name>`
1435
- );
1436
- }
1437
- }
1438
- artifactsMap[key] = {
1439
- required,
1440
- description: a.description ?? "",
1441
- included_in_relations: a.included_in_relations ?? false
1442
- };
1443
- }
1444
1488
  const qualityRaw = raw.quality;
1445
1489
  const quality = qualityRaw ? {
1446
1490
  min_artifact_length: qualityRaw.min_artifact_length ?? DEFAULT_QUALITY.min_artifact_length,
@@ -1463,14 +1507,13 @@ async function parseConfig(filePath) {
1463
1507
  version,
1464
1508
  name: raw.name.trim(),
1465
1509
  node_types: nodeTypes,
1466
- artifacts: artifactsMap,
1467
1510
  quality
1468
1511
  };
1469
1512
  }
1470
1513
 
1471
1514
  // src/io/node-parser.ts
1472
- import { readFile as readFile6 } from "fs/promises";
1473
- import { parse as parseYaml4 } from "yaml";
1515
+ import { readFile as readFile7 } from "fs/promises";
1516
+ import { parse as parseYaml5 } from "yaml";
1474
1517
  var RELATION_TYPES = [
1475
1518
  "uses",
1476
1519
  "calls",
@@ -1483,8 +1526,8 @@ function isValidRelationType(t) {
1483
1526
  return typeof t === "string" && RELATION_TYPES.includes(t);
1484
1527
  }
1485
1528
  async function parseNodeYaml(filePath) {
1486
- const content = await readFile6(filePath, "utf-8");
1487
- const raw = parseYaml4(content);
1529
+ const content = await readFile7(filePath, "utf-8");
1530
+ const raw = parseYaml5(content);
1488
1531
  if (!raw || typeof raw !== "object") {
1489
1532
  throw new Error(`yg-node.yaml at ${filePath}: file is empty or not a valid YAML mapping`);
1490
1533
  }
@@ -1629,12 +1672,12 @@ function parseMapping(rawMapping, filePath) {
1629
1672
  }
1630
1673
 
1631
1674
  // src/io/aspect-parser.ts
1632
- import { readFile as readFile8 } from "fs/promises";
1633
- import { parse as parseYaml5 } from "yaml";
1675
+ import { readFile as readFile9 } from "fs/promises";
1676
+ import { parse as parseYaml6 } from "yaml";
1634
1677
 
1635
1678
  // src/io/artifact-reader.ts
1636
- import { readFile as readFile7, readdir as readdir3 } from "fs/promises";
1637
- import path5 from "path";
1679
+ import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
1680
+ import path6 from "path";
1638
1681
  async function readArtifacts(dirPath, excludeFiles = ["yg-node.yaml"], includeFiles) {
1639
1682
  const entries = await readdir3(dirPath, { withFileTypes: true });
1640
1683
  const artifacts = [];
@@ -1643,8 +1686,8 @@ async function readArtifacts(dirPath, excludeFiles = ["yg-node.yaml"], includeFi
1643
1686
  if (!entry.isFile()) continue;
1644
1687
  if (excludeFiles.includes(entry.name)) continue;
1645
1688
  if (includeSet && !includeSet.has(entry.name)) continue;
1646
- const filePath = path5.join(dirPath, entry.name);
1647
- const content = await readFile7(filePath, "utf-8");
1689
+ const filePath = path6.join(dirPath, entry.name);
1690
+ const content = await readFile8(filePath, "utf-8");
1648
1691
  artifacts.push({ filename: entry.name, content });
1649
1692
  }
1650
1693
  artifacts.sort((a, b) => a.filename.localeCompare(b.filename));
@@ -1658,8 +1701,8 @@ async function parseAspect(aspectDir, aspectYamlPath, id) {
1658
1701
  if (!idTrimmed) {
1659
1702
  throw new Error(`Aspect id must be non-empty (relative path in aspects/)`);
1660
1703
  }
1661
- const content = await readFile8(aspectYamlPath, "utf-8");
1662
- const raw = parseYaml5(content);
1704
+ const content = await readFile9(aspectYamlPath, "utf-8");
1705
+ const raw = parseYaml6(content);
1663
1706
  if (!raw || typeof raw !== "object") {
1664
1707
  throw new Error(`Aspect file ${aspectYamlPath}: file is empty or not a valid YAML mapping`);
1665
1708
  }
@@ -1695,12 +1738,12 @@ async function parseAspect(aspectDir, aspectYamlPath, id) {
1695
1738
  }
1696
1739
 
1697
1740
  // src/io/flow-parser.ts
1698
- import { readFile as readFile9 } from "fs/promises";
1699
- import path6 from "path";
1700
- 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";
1701
1744
  async function parseFlow(flowDir, flowYamlPath) {
1702
- const content = await readFile9(flowYamlPath, "utf-8");
1703
- const raw = parseYaml6(content);
1745
+ const content = await readFile10(flowYamlPath, "utf-8");
1746
+ const raw = parseYaml7(content);
1704
1747
  if (!raw || typeof raw !== "object") {
1705
1748
  throw new Error(`yg-flow.yaml at ${flowYamlPath}: file is empty or not a valid YAML mapping`);
1706
1749
  }
@@ -1730,7 +1773,7 @@ async function parseFlow(flowDir, flowYamlPath) {
1730
1773
  }
1731
1774
  const artifacts = await readArtifacts(flowDir, ["yg-flow.yaml"]);
1732
1775
  return {
1733
- path: path6.basename(flowDir),
1776
+ path: path7.basename(flowDir),
1734
1777
  name: raw.name.trim(),
1735
1778
  description,
1736
1779
  nodes: nodePaths,
@@ -1740,26 +1783,26 @@ async function parseFlow(flowDir, flowYamlPath) {
1740
1783
  }
1741
1784
 
1742
1785
  // src/io/schema-parser.ts
1743
- import { readFile as readFile10 } from "fs/promises";
1744
- import path7 from "path";
1745
- 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";
1746
1789
  async function parseSchema(filePath) {
1747
- const content = await readFile10(filePath, "utf-8");
1748
- parseYaml7(content);
1749
- 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));
1750
1793
  const schemaType = rawName.startsWith("yg-") ? rawName.slice(3) : rawName;
1751
1794
  return { schemaType };
1752
1795
  }
1753
1796
 
1754
1797
  // src/utils/paths.ts
1755
- import path8 from "path";
1798
+ import path9 from "path";
1756
1799
  import { fileURLToPath as fileURLToPath2 } from "url";
1757
1800
  import { stat as stat3 } from "fs/promises";
1758
1801
  async function findYggRoot(projectRoot) {
1759
- let current = path8.resolve(projectRoot);
1760
- const root = path8.parse(current).root;
1802
+ let current = path9.resolve(projectRoot);
1803
+ const root = path9.parse(current).root;
1761
1804
  while (true) {
1762
- const yggPath = path8.join(current, ".yggdrasil");
1805
+ const yggPath = path9.join(current, ".yggdrasil");
1763
1806
  try {
1764
1807
  const st = await stat3(yggPath);
1765
1808
  if (!st.isDirectory()) {
@@ -1773,7 +1816,7 @@ async function findYggRoot(projectRoot) {
1773
1816
  if (current === root) {
1774
1817
  throw new Error(`No .yggdrasil/ directory found. Run 'yg init' first.`, { cause: err });
1775
1818
  }
1776
- current = path8.dirname(current);
1819
+ current = path9.dirname(current);
1777
1820
  continue;
1778
1821
  }
1779
1822
  throw err;
@@ -1789,43 +1832,42 @@ function normalizeProjectRelativePath(projectRoot, rawPath) {
1789
1832
  if (normalizedInput.length === 0) {
1790
1833
  throw new Error("Path cannot be empty");
1791
1834
  }
1792
- const absolute = path8.resolve(projectRoot, normalizedInput);
1793
- const relative = path8.relative(projectRoot, absolute);
1794
- 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);
1795
1838
  if (isOutside) {
1796
1839
  throw new Error(`Path is outside project root: ${rawPath}`);
1797
1840
  }
1798
- return relative.split(path8.sep).join("/");
1841
+ return relative.split(path9.sep).join("/");
1799
1842
  }
1800
1843
  function projectRootFromGraph(yggRootPath) {
1801
- return path8.dirname(yggRootPath);
1844
+ return path9.dirname(yggRootPath);
1802
1845
  }
1803
1846
 
1804
1847
  // src/core/graph-loader.ts
1805
1848
  function toModelPath(absolutePath, modelDir) {
1806
- return path9.relative(modelDir, absolutePath).split(path9.sep).join("/");
1849
+ return path10.relative(modelDir, absolutePath).split(path10.sep).join("/");
1807
1850
  }
1808
1851
  var FALLBACK_CONFIG = {
1809
1852
  name: "",
1810
- node_types: {},
1811
- artifacts: {}
1853
+ node_types: {}
1812
1854
  };
1813
1855
  async function loadGraph(projectRoot, options = {}) {
1814
1856
  const yggRoot = await findYggRoot(projectRoot);
1815
1857
  let configError;
1816
1858
  let config = FALLBACK_CONFIG;
1817
1859
  try {
1818
- config = await parseConfig(path9.join(yggRoot, "yg-config.yaml"));
1860
+ config = await parseConfig(path10.join(yggRoot, "yg-config.yaml"));
1819
1861
  } catch (error) {
1820
1862
  if (!options.tolerateInvalidConfig) {
1821
1863
  throw error;
1822
1864
  }
1823
1865
  configError = error.message;
1824
1866
  }
1825
- const modelDir = path9.join(yggRoot, "model");
1867
+ const modelDir = path10.join(yggRoot, "model");
1826
1868
  const nodes = /* @__PURE__ */ new Map();
1827
1869
  const nodeParseErrors = [];
1828
- const artifactFilenames = Object.keys(config.artifacts ?? {});
1870
+ const artifactFilenames = Object.keys(STANDARD_ARTIFACTS2);
1829
1871
  try {
1830
1872
  await scanModelDirectory(modelDir, modelDir, null, nodes, nodeParseErrors, artifactFilenames);
1831
1873
  } catch (err) {
@@ -1836,9 +1878,9 @@ async function loadGraph(projectRoot, options = {}) {
1836
1878
  }
1837
1879
  throw err;
1838
1880
  }
1839
- const aspects = await loadAspects(path9.join(yggRoot, "aspects"));
1840
- const flows = await loadFlows(path9.join(yggRoot, "flows"));
1841
- 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"));
1842
1884
  return {
1843
1885
  config,
1844
1886
  configError,
@@ -1858,11 +1900,11 @@ async function scanModelDirectory(dirPath, modelDir, parent, nodes, nodeParseErr
1858
1900
  }
1859
1901
  if (hasNodeYaml) {
1860
1902
  const graphPath = toModelPath(dirPath, modelDir);
1861
- const nodeYamlPath = path9.join(dirPath, "yg-node.yaml");
1903
+ const nodeYamlPath = path10.join(dirPath, "yg-node.yaml");
1862
1904
  let meta;
1863
1905
  let nodeYamlRaw;
1864
1906
  try {
1865
- nodeYamlRaw = await readFile11(nodeYamlPath, "utf-8");
1907
+ nodeYamlRaw = await readFile12(nodeYamlPath, "utf-8");
1866
1908
  meta = await parseNodeYaml(nodeYamlPath);
1867
1909
  } catch (err) {
1868
1910
  nodeParseErrors.push({
@@ -1888,7 +1930,7 @@ async function scanModelDirectory(dirPath, modelDir, parent, nodes, nodeParseErr
1888
1930
  if (!entry.isDirectory()) continue;
1889
1931
  if (entry.name.startsWith(".")) continue;
1890
1932
  await scanModelDirectory(
1891
- path9.join(dirPath, entry.name),
1933
+ path10.join(dirPath, entry.name),
1892
1934
  modelDir,
1893
1935
  node,
1894
1936
  nodes,
@@ -1901,7 +1943,7 @@ async function scanModelDirectory(dirPath, modelDir, parent, nodes, nodeParseErr
1901
1943
  if (!entry.isDirectory()) continue;
1902
1944
  if (entry.name.startsWith(".")) continue;
1903
1945
  await scanModelDirectory(
1904
- path9.join(dirPath, entry.name),
1946
+ path10.join(dirPath, entry.name),
1905
1947
  modelDir,
1906
1948
  null,
1907
1949
  nodes,
@@ -1924,15 +1966,15 @@ async function scanAspectsDirectory(dirPath, aspectsRoot, aspects) {
1924
1966
  const entries = await readdir4(dirPath, { withFileTypes: true });
1925
1967
  const hasAspectYaml = entries.some((e) => e.isFile() && e.name === "yg-aspect.yaml");
1926
1968
  if (hasAspectYaml) {
1927
- const id = path9.relative(aspectsRoot, dirPath).split(path9.sep).join("/");
1928
- 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");
1929
1971
  const aspect = await parseAspect(dirPath, aspectYamlPath, id);
1930
1972
  aspects.push(aspect);
1931
1973
  }
1932
1974
  for (const entry of entries) {
1933
1975
  if (!entry.isDirectory()) continue;
1934
1976
  if (entry.name.startsWith(".")) continue;
1935
- await scanAspectsDirectory(path9.join(dirPath, entry.name), aspectsRoot, aspects);
1977
+ await scanAspectsDirectory(path10.join(dirPath, entry.name), aspectsRoot, aspects);
1936
1978
  }
1937
1979
  }
1938
1980
  async function loadFlows(flowsDir) {
@@ -1945,8 +1987,8 @@ async function loadFlows(flowsDir) {
1945
1987
  const flows = [];
1946
1988
  for (const entry of entries) {
1947
1989
  if (!entry.isDirectory()) continue;
1948
- const flowYamlPath = path9.join(flowsDir, entry.name, "yg-flow.yaml");
1949
- 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);
1950
1992
  flows.push(flow);
1951
1993
  }
1952
1994
  return flows;
@@ -1958,7 +2000,7 @@ async function loadSchemas(schemasDir) {
1958
2000
  for (const entry of entries) {
1959
2001
  if (!entry.isFile()) continue;
1960
2002
  if (!entry.name.endsWith(".yaml") && !entry.name.endsWith(".yml")) continue;
1961
- const s = await parseSchema(path9.join(schemasDir, entry.name));
2003
+ const s = await parseSchema(path10.join(schemasDir, entry.name));
1962
2004
  schemas.push(s);
1963
2005
  }
1964
2006
  return schemas;
@@ -1968,8 +2010,8 @@ async function loadSchemas(schemasDir) {
1968
2010
  }
1969
2011
 
1970
2012
  // src/core/context-builder.ts
1971
- import { readFile as readFile12 } from "fs/promises";
1972
- import path10 from "path";
2013
+ import { readFile as readFile13 } from "fs/promises";
2014
+ import path11 from "path";
1973
2015
 
1974
2016
  // src/utils/tokens.ts
1975
2017
  function estimateTokens(text) {
@@ -1980,48 +2022,53 @@ function estimateTokens(text) {
1980
2022
  var STRUCTURAL_RELATION_TYPES = /* @__PURE__ */ new Set(["uses", "calls", "extends", "implements"]);
1981
2023
  var EVENT_RELATION_TYPES = /* @__PURE__ */ new Set(["emits", "listens"]);
1982
2024
  var YG_YAML_FILES = /* @__PURE__ */ new Set(["yg-node.yaml", "yg-aspect.yaml", "yg-flow.yaml"]);
1983
- async function buildContext(graph, nodePath) {
2025
+ async function buildContext(graph, nodePath, options) {
1984
2026
  const node = graph.nodes.get(nodePath);
1985
2027
  if (!node) {
1986
2028
  throw new Error(`Node not found: ${nodePath}`);
1987
2029
  }
2030
+ const selfOnly = options?.selfOnly ?? false;
1988
2031
  const layers = [];
1989
2032
  layers.push(buildGlobalLayer(graph.config));
1990
2033
  const ancestors = collectAncestors(node);
1991
- for (const ancestor of ancestors) {
1992
- 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
+ }
1993
2038
  }
1994
2039
  layers.push(await buildOwnLayer(node, graph.config, graph.rootPath, graph));
1995
- const ancestorPaths = new Set(ancestors.map((a) => a.path));
1996
- for (const relation of node.meta.relations ?? []) {
1997
- const target = graph.nodes.get(relation.target);
1998
- if (!target) {
1999
- 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
+ }
2000
2053
  }
2001
- if (ancestorPaths.has(relation.target)) continue;
2002
- if (STRUCTURAL_RELATION_TYPES.has(relation.type)) {
2003
- layers.push(buildStructuralRelationLayer(target, relation, graph.config));
2004
- } else if (EVENT_RELATION_TYPES.has(relation.type)) {
2005
- layers.push(buildEventRelationLayer(target, relation));
2054
+ for (const flow of collectParticipatingFlows(graph, node)) {
2055
+ layers.push(buildFlowLayer(flow, graph));
2006
2056
  }
2007
- }
2008
- for (const flow of collectParticipatingFlows(graph, node)) {
2009
- layers.push(buildFlowLayer(flow, graph));
2010
- }
2011
- const allAspectIds = /* @__PURE__ */ new Set();
2012
- for (const l of layers) {
2013
- const aspects = l.attrs?.aspects;
2014
- if (aspects) {
2015
- for (const id of aspects.split(",").map((t) => t.trim()).filter(Boolean)) {
2016
- 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
+ }
2017
2064
  }
2018
2065
  }
2019
- }
2020
- const aspectsToInclude = resolveAspects(allAspectIds, graph.aspects);
2021
- for (const aspect of aspectsToInclude) {
2022
- const entry = node.meta.aspects?.find((a) => a.aspect === aspect.id);
2023
- const exceptionNote = entry?.exceptions?.join("; ");
2024
- 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
+ }
2025
2072
  }
2026
2073
  const fullText = layers.map((l) => l.content).join("\n\n");
2027
2074
  const tokenCount = estimateTokens(fullText);
@@ -2082,12 +2129,12 @@ function buildGlobalLayer(config) {
2082
2129
  `;
2083
2130
  return { type: "global", label: "Global Context", content };
2084
2131
  }
2085
- function filterArtifactsByConfig(artifacts, config) {
2086
- const allowed = new Set(Object.keys(config.artifacts ?? {}));
2132
+ function filterByStandardArtifacts(artifacts) {
2133
+ const allowed = new Set(Object.keys(STANDARD_ARTIFACTS2));
2087
2134
  return artifacts.filter((a) => allowed.has(a.filename));
2088
2135
  }
2089
2136
  function buildHierarchyLayer(ancestor, config, graph) {
2090
- const filtered = filterArtifactsByConfig(ancestor.artifacts, config);
2137
+ const filtered = filterByStandardArtifacts(ancestor.artifacts);
2091
2138
  const content = filtered.map((a) => `### ${a.filename}
2092
2139
  ${a.content}`).join("\n\n");
2093
2140
  const nodeAspects = (ancestor.meta.aspects ?? []).map((a) => a.aspect);
@@ -2106,9 +2153,9 @@ async function buildOwnLayer(node, config, graphRootPath, graph) {
2106
2153
  parts.push(`### yg-node.yaml
2107
2154
  ${node.nodeYamlRaw.trim()}`);
2108
2155
  } else {
2109
- const nodeYamlPath = path10.join(graphRootPath, "model", node.path, "yg-node.yaml");
2156
+ const nodeYamlPath = path11.join(graphRootPath, "model", node.path, "yg-node.yaml");
2110
2157
  try {
2111
- const nodeYamlContent = await readFile12(nodeYamlPath, "utf-8");
2158
+ const nodeYamlContent = await readFile13(nodeYamlPath, "utf-8");
2112
2159
  parts.push(`### yg-node.yaml
2113
2160
  ${nodeYamlContent.trim()}`);
2114
2161
  } catch {
@@ -2116,7 +2163,7 @@ ${nodeYamlContent.trim()}`);
2116
2163
  (not found)`);
2117
2164
  }
2118
2165
  }
2119
- const filtered = filterArtifactsByConfig(node.artifacts, config);
2166
+ const filtered = filterByStandardArtifacts(node.artifacts);
2120
2167
  for (const a of filtered) {
2121
2168
  parts.push(`### ${a.filename}
2122
2169
  ${a.content}`);
@@ -2132,7 +2179,7 @@ ${a.content}`);
2132
2179
  attrs
2133
2180
  };
2134
2181
  }
2135
- function buildStructuralRelationLayer(target, relation, config) {
2182
+ function buildStructuralRelationLayer(target, relation) {
2136
2183
  let content = "";
2137
2184
  if (relation.consumes?.length) {
2138
2185
  content += `Consumes: ${relation.consumes.join(", ")}
@@ -2144,7 +2191,7 @@ function buildStructuralRelationLayer(target, relation, config) {
2144
2191
 
2145
2192
  `;
2146
2193
  }
2147
- 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);
2148
2195
  const structuralArts = structuralArtifactFilenames.map((filename) => {
2149
2196
  const art = target.artifacts.find((a) => a.filename === filename);
2150
2197
  return art ? { filename: art.filename, content: art.content } : null;
@@ -2153,7 +2200,7 @@ function buildStructuralRelationLayer(target, relation, config) {
2153
2200
  content += structuralArts.map((a) => `### ${a.filename}
2154
2201
  ${a.content}`).join("\n\n");
2155
2202
  } else {
2156
- const filtered = filterArtifactsByConfig(target.artifacts, config);
2203
+ const filtered = filterByStandardArtifacts(target.artifacts);
2157
2204
  content += filtered.map((a) => `### ${a.filename}
2158
2205
  ${a.content}`).join("\n\n");
2159
2206
  }
@@ -2258,8 +2305,8 @@ function collectAncestors(node) {
2258
2305
  }
2259
2306
  function collectDependencyAncestors(target, config, graph) {
2260
2307
  const ancestors = collectAncestors(target);
2261
- const structuralFilenames = Object.entries(config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
2262
- 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)];
2263
2310
  return ancestors.map((ancestor) => {
2264
2311
  const nodeAspects = (ancestor.meta.aspects ?? []).map((a) => a.aspect);
2265
2312
  const expanded = expandAspects(nodeAspects, graph.aspects);
@@ -2327,7 +2374,7 @@ function computeBudgetBreakdown(pkg2, graph) {
2327
2374
  const total = own + hierarchy + aspects + flows + dependencies;
2328
2375
  return { own, hierarchy, aspects, flows, dependencies, total };
2329
2376
  }
2330
- function toContextMapOutput(pkg2, graph) {
2377
+ function toContextMapOutput(pkg2, graph, options) {
2331
2378
  const node = graph.nodes.get(pkg2.nodePath);
2332
2379
  const config = graph.config;
2333
2380
  const nodeAspects = (node.meta.aspects ?? []).map((entry) => {
@@ -2336,48 +2383,51 @@ function toContextMapOutput(pkg2, graph) {
2336
2383
  if (entry.exceptions?.length) ref.exceptions = entry.exceptions;
2337
2384
  return ref;
2338
2385
  });
2339
- const participatingFlows = collectParticipatingFlows(graph, node);
2386
+ const selfOnly = options?.selfOnly ?? false;
2387
+ const participatingFlows = selfOnly ? [] : collectParticipatingFlows(graph, node);
2340
2388
  const flowRefs = participatingFlows.map((f) => {
2341
2389
  const ref = { path: f.path };
2342
2390
  if (f.aspects?.length) ref.aspects = f.aspects;
2343
2391
  return ref;
2344
2392
  });
2345
2393
  const ancestors = collectAncestors(node);
2346
- const hierarchyRefs = ancestors.map((a) => {
2394
+ const hierarchyRefs = selfOnly ? [] : ancestors.map((a) => {
2347
2395
  const nodeAspectIds = (a.meta.aspects ?? []).map((e) => e.aspect);
2348
2396
  const expanded = expandAspects(nodeAspectIds, graph.aspects);
2349
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}`) };
2350
2398
  });
2351
- const ancestorPaths = new Set(ancestors.map((a) => a.path));
2352
2399
  const depRefs = [];
2353
- for (const relation of node.meta.relations ?? []) {
2354
- const target = graph.nodes.get(relation.target);
2355
- if (!target) continue;
2356
- if (ancestorPaths.has(relation.target)) continue;
2357
- const depAncestors = collectAncestors(target);
2358
- const depHierarchy = depAncestors.map((a) => {
2359
- const ids = (a.meta.aspects ?? []).map((e) => e.aspect);
2360
- const expanded = expandAspects(ids, graph.aspects);
2361
- const ancestorNode = graph.nodes.get(a.path);
2362
- 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}`) : [] };
2363
- });
2364
- const depEffectiveAspects = [...collectEffectiveAspectIds(graph, target.path)];
2365
- const ref = {
2366
- path: target.path,
2367
- name: target.meta.name,
2368
- type: target.meta.type,
2369
- description: target.meta.description,
2370
- relation: relation.type,
2371
- aspects: depEffectiveAspects,
2372
- hierarchy: depHierarchy,
2373
- files: buildDepNodeFiles(target, config, `model/${target.path}`)
2374
- };
2375
- if (relation.consumes?.length) ref.consumes = relation.consumes;
2376
- if (relation.failure) ref.failure = relation.failure;
2377
- if (relation.event_name) ref["event-name"] = relation.event_name;
2378
- 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
+ }
2379
2429
  }
2380
- const glossary = buildGlossary(node, depRefs, graph);
2430
+ const glossary = selfOnly ? { aspects: {}, flows: {} } : buildGlossary(node, depRefs, graph);
2381
2431
  const breakdown = computeBudgetBreakdown(pkg2, graph);
2382
2432
  const warningThreshold = config.quality?.context_budget?.warning ?? 1e4;
2383
2433
  const errorThreshold = config.quality?.context_budget?.error ?? 2e4;
@@ -2400,13 +2450,13 @@ function toContextMapOutput(pkg2, graph) {
2400
2450
  glossary
2401
2451
  };
2402
2452
  }
2403
- function buildNodeFiles(node, config, prefix) {
2404
- const configKeys = Object.keys(config.artifacts ?? {});
2453
+ function buildNodeFiles(node, _config, prefix) {
2454
+ const configKeys = Object.keys(STANDARD_ARTIFACTS2);
2405
2455
  return configKeys.filter((f) => !YG_YAML_FILES.has(f) && node.artifacts.some((a) => a.filename === f)).map((f) => `${prefix}/${f}`);
2406
2456
  }
2407
- function buildDepNodeFiles(node, config, prefix) {
2408
- const structural = Object.entries(config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([f]) => f);
2409
- 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);
2410
2460
  return filenames.filter((f) => !YG_YAML_FILES.has(f) && node.artifacts.some((a) => a.filename === f)).map((f) => `${prefix}/${f}`);
2411
2461
  }
2412
2462
  function buildGlossary(node, dependencies, graph) {
@@ -2511,8 +2561,8 @@ ${file.content}
2511
2561
  }
2512
2562
 
2513
2563
  // src/core/validator.ts
2514
- import { readdir as readdir5, readFile as readFile13, stat as stat4 } from "fs/promises";
2515
- import path11 from "path";
2564
+ import { readdir as readdir5, readFile as readFile14, stat as stat4 } from "fs/promises";
2565
+ import path12 from "path";
2516
2566
  function getAspectIds(aspects) {
2517
2567
  return (aspects ?? []).map((a) => a.aspect);
2518
2568
  }
@@ -2546,7 +2596,6 @@ async function validate(graph, scope = "all") {
2546
2596
  issues.push(...checkRequiredAspectsCoverage(graph));
2547
2597
  issues.push(...await checkAnchorPresence(graph));
2548
2598
  issues.push(...checkRequiredArtifacts(graph));
2549
- issues.push(...checkInvalidArtifactConditions(graph));
2550
2599
  issues.push(...await checkContextBudget(graph));
2551
2600
  issues.push(...checkHighFanOut(graph));
2552
2601
  issues.push(...checkMissingDescriptions(graph));
@@ -2879,12 +2928,12 @@ function checkMappingOverlap(graph) {
2879
2928
  }
2880
2929
  async function checkMappingPathsExist(graph) {
2881
2930
  const issues = [];
2882
- const projectRoot = path11.dirname(graph.rootPath);
2931
+ const projectRoot = path12.dirname(graph.rootPath);
2883
2932
  const { access: access4 } = await import("fs/promises");
2884
2933
  for (const [nodePath, node] of graph.nodes) {
2885
2934
  const mappingPaths = normalizeMappingPaths(node.meta.mapping);
2886
2935
  for (const mp of mappingPaths) {
2887
- const absPath = path11.join(projectRoot, mp);
2936
+ const absPath = path12.join(projectRoot, mp);
2888
2937
  try {
2889
2938
  await access4(absPath);
2890
2939
  } catch {
@@ -2919,15 +2968,6 @@ function artifactRequiredReason(graph, nodePath, node, required) {
2919
2968
  const sources = getIncomingRelationSources(graph, nodePath);
2920
2969
  return sources.length > 0 ? `${sources.length} incoming relation(s): ${sources.join(", ")}` : null;
2921
2970
  }
2922
- if (when === "has_outgoing_relations") {
2923
- const count = node.meta.relations?.length ?? 0;
2924
- return count > 0 ? `${count} outgoing relation(s)` : null;
2925
- }
2926
- if (when.startsWith("has_aspect:") || when.startsWith("has_tag:")) {
2927
- const prefix = when.startsWith("has_aspect:") ? "has_aspect:" : "has_tag:";
2928
- const aspectId = when.slice(prefix.length);
2929
- return (node.meta.aspects ?? []).some((a) => a.aspect === aspectId) ? `node has aspect '${aspectId}'` : null;
2930
- }
2931
2971
  return null;
2932
2972
  }
2933
2973
  function getIncomingRelations(graph, nodePath) {
@@ -2944,7 +2984,7 @@ function getIncomingRelations(graph, nodePath) {
2944
2984
  }
2945
2985
  function checkRequiredArtifacts(graph) {
2946
2986
  const issues = [];
2947
- const artifacts = graph.config.artifacts ?? {};
2987
+ const artifacts = STANDARD_ARTIFACTS2;
2948
2988
  for (const [nodePath, node] of graph.nodes) {
2949
2989
  for (const [filename, config] of Object.entries(artifacts)) {
2950
2990
  const hasArtifact = node.artifacts.some((a) => a.filename === filename);
@@ -3000,30 +3040,6 @@ function checkFlowAspectIds(graph) {
3000
3040
  }
3001
3041
  return issues;
3002
3042
  }
3003
- function checkInvalidArtifactConditions(graph) {
3004
- const issues = [];
3005
- const validAspectIds = new Set(graph.aspects.map((a) => a.id));
3006
- const artifacts = graph.config.artifacts ?? {};
3007
- for (const [artifactName, config] of Object.entries(artifacts)) {
3008
- const required = config.required;
3009
- if (typeof required === "object" && required && "when" in required) {
3010
- const when = required.when;
3011
- if (when.startsWith("has_aspect:") || when.startsWith("has_tag:")) {
3012
- const prefix = when.startsWith("has_aspect:") ? "has_aspect:" : "has_tag:";
3013
- const aspectId = when.slice(prefix.length);
3014
- if (!validAspectIds.has(aspectId)) {
3015
- issues.push({
3016
- severity: "error",
3017
- code: "E013",
3018
- rule: "invalid-artifact-condition",
3019
- message: `Artifact '${artifactName}' condition has_aspect:${aspectId} has no corresponding aspect in aspects/`
3020
- });
3021
- }
3022
- }
3023
- }
3024
- }
3025
- return issues;
3026
- }
3027
3043
  async function checkShallowArtifacts(graph) {
3028
3044
  const issues = [];
3029
3045
  const minLen = graph.config.quality?.min_artifact_length ?? 50;
@@ -3045,7 +3061,7 @@ async function checkShallowArtifacts(graph) {
3045
3061
  async function checkWideNodes(graph) {
3046
3062
  const issues = [];
3047
3063
  const maxFiles = graph.config.quality?.max_mapping_source_files ?? 10;
3048
- const projectRoot = path11.dirname(graph.rootPath);
3064
+ const projectRoot = path12.dirname(graph.rootPath);
3049
3065
  for (const [nodePath, node] of graph.nodes) {
3050
3066
  if (node.meta.blackbox) continue;
3051
3067
  const mappingPaths = normalizeMappingPaths(node.meta.mapping);
@@ -3148,11 +3164,11 @@ function checkSchemas(graph) {
3148
3164
  }
3149
3165
  async function checkDirectoriesHaveNodeYaml(graph) {
3150
3166
  const issues = [];
3151
- const modelDir = path11.join(graph.rootPath, "model");
3167
+ const modelDir = path12.join(graph.rootPath, "model");
3152
3168
  async function scanDir(dirPath, segments) {
3153
3169
  const entries = await readdir5(dirPath, { withFileTypes: true });
3154
3170
  const hasNodeYaml = entries.some((e) => e.isFile() && e.name === "yg-node.yaml");
3155
- const dirName = path11.basename(dirPath);
3171
+ const dirName = path12.basename(dirPath);
3156
3172
  if (RESERVED_DIRS.has(dirName)) return;
3157
3173
  const hasFiles = entries.some((e) => e.isFile());
3158
3174
  const hasSubdirs = entries.some((e) => e.isDirectory() && !RESERVED_DIRS.has(e.name) && !e.name.startsWith("."));
@@ -3180,7 +3196,7 @@ async function checkDirectoriesHaveNodeYaml(graph) {
3180
3196
  if (!entry.isDirectory()) continue;
3181
3197
  if (RESERVED_DIRS.has(entry.name)) continue;
3182
3198
  if (entry.name.startsWith(".")) continue;
3183
- await scanDir(path11.join(dirPath, entry.name), [...segments, entry.name]);
3199
+ await scanDir(path12.join(dirPath, entry.name), [...segments, entry.name]);
3184
3200
  }
3185
3201
  }
3186
3202
  try {
@@ -3188,7 +3204,7 @@ async function checkDirectoriesHaveNodeYaml(graph) {
3188
3204
  for (const entry of rootEntries) {
3189
3205
  if (!entry.isDirectory()) continue;
3190
3206
  if (entry.name.startsWith(".")) continue;
3191
- await scanDir(path11.join(modelDir, entry.name), [entry.name]);
3207
+ await scanDir(path12.join(modelDir, entry.name), [entry.name]);
3192
3208
  }
3193
3209
  } catch {
3194
3210
  }
@@ -3205,7 +3221,7 @@ async function expandMappingToFiles(projectRoot, mappingPaths) {
3205
3221
  const entries = await readdir5(absPath, { withFileTypes: true });
3206
3222
  for (const entry of entries) {
3207
3223
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
3208
- const entryPath = path11.join(absPath, entry.name);
3224
+ const entryPath = path12.join(absPath, entry.name);
3209
3225
  if (entry.isFile()) {
3210
3226
  files.push(entryPath);
3211
3227
  } else if (entry.isDirectory()) {
@@ -3217,13 +3233,13 @@ async function expandMappingToFiles(projectRoot, mappingPaths) {
3217
3233
  }
3218
3234
  }
3219
3235
  for (const mp of mappingPaths) {
3220
- await collectFiles(path11.join(projectRoot, mp));
3236
+ await collectFiles(path12.join(projectRoot, mp));
3221
3237
  }
3222
3238
  return files;
3223
3239
  }
3224
3240
  async function checkAnchorPresence(graph) {
3225
3241
  const issues = [];
3226
- const projectRoot = path11.dirname(graph.rootPath);
3242
+ const projectRoot = path12.dirname(graph.rootPath);
3227
3243
  for (const [nodePath, node] of graph.nodes) {
3228
3244
  const aspectsWithAnchors = (node.meta.aspects ?? []).filter((a) => a.anchors && a.anchors.length > 0);
3229
3245
  if (aspectsWithAnchors.length === 0) continue;
@@ -3234,7 +3250,7 @@ async function checkAnchorPresence(graph) {
3234
3250
  const fileContents = [];
3235
3251
  for (const filePath of sourceFiles) {
3236
3252
  try {
3237
- const content = await readFile13(filePath, "utf-8");
3253
+ const content = await readFile14(filePath, "utf-8");
3238
3254
  fileContents.push(content);
3239
3255
  } catch {
3240
3256
  }
@@ -3343,7 +3359,7 @@ function checkMissingDescriptions(graph) {
3343
3359
  }
3344
3360
 
3345
3361
  // src/cli/owner.ts
3346
- import path12 from "path";
3362
+ import path13 from "path";
3347
3363
  import { access as access2 } from "fs/promises";
3348
3364
  function normalizeForMatch(inputPath) {
3349
3365
  return inputPath.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -3373,11 +3389,11 @@ function registerOwnerCommand(program2) {
3373
3389
  const graph = await loadGraph(cwd);
3374
3390
  const repoRoot = projectRootFromGraph(graph.rootPath);
3375
3391
  const rawPath = options.file.trim();
3376
- const absolute = path12.resolve(cwd, rawPath);
3377
- 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("/");
3378
3394
  const result = findOwner(graph, repoRoot, repoRelative);
3379
3395
  if (!result.nodePath) {
3380
- const absPath = path12.resolve(repoRoot, result.file);
3396
+ const absPath = path13.resolve(repoRoot, result.file);
3381
3397
  let exists = true;
3382
3398
  try {
3383
3399
  await access2(absPath);
@@ -3430,7 +3446,7 @@ function collectRelevantNodePaths(graph, nodePath) {
3430
3446
  return relevant;
3431
3447
  }
3432
3448
  function registerBuildCommand(program2) {
3433
- 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) => {
3434
3450
  try {
3435
3451
  if (!options.node && !options.file) {
3436
3452
  process.stderr.write("Error: either '--node <path>' or '--file <path>' is required\n");
@@ -3478,8 +3494,8 @@ function registerBuildCommand(program2) {
3478
3494
  process.stderr.write(msg);
3479
3495
  process.exit(1);
3480
3496
  }
3481
- const pkg2 = await buildContext(graph, nodePath);
3482
- const mapOutput = toContextMapOutput(pkg2, graph);
3497
+ const pkg2 = await buildContext(graph, nodePath, { selfOnly: options.self });
3498
+ const mapOutput = toContextMapOutput(pkg2, graph, { selfOnly: options.self });
3483
3499
  let output = formatContextYaml(mapOutput);
3484
3500
  if (options.full) {
3485
3501
  const seen = /* @__PURE__ */ new Set();
@@ -3601,12 +3617,12 @@ ${errors.length} errors, ${warnings.length} warnings.
3601
3617
  import chalk2 from "chalk";
3602
3618
 
3603
3619
  // src/io/drift-state-store.ts
3604
- import { readFile as readFile14, writeFile as writeFile5, stat as stat5, readdir as readdir6, mkdir as mkdir3, rm as rm2 } from "fs/promises";
3605
- 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";
3606
3622
  import { parse as yamlParse } from "yaml";
3607
3623
  var DRIFT_STATE_DIR = ".drift-state";
3608
3624
  function nodeStatePath(yggRoot, nodePath) {
3609
- return path13.join(yggRoot, DRIFT_STATE_DIR, `${nodePath}.json`);
3625
+ return path14.join(yggRoot, DRIFT_STATE_DIR, `${nodePath}.json`);
3610
3626
  }
3611
3627
  async function scanJsonFiles(dir, baseDir) {
3612
3628
  const results = [];
@@ -3617,12 +3633,12 @@ async function scanJsonFiles(dir, baseDir) {
3617
3633
  return results;
3618
3634
  }
3619
3635
  for (const entry of entries) {
3620
- const fullPath = path13.join(dir, entry.name);
3636
+ const fullPath = path14.join(dir, entry.name);
3621
3637
  if (entry.isDirectory()) {
3622
3638
  const nested = await scanJsonFiles(fullPath, baseDir);
3623
3639
  results.push(...nested);
3624
3640
  } else if (entry.isFile() && entry.name.endsWith(".json")) {
3625
- const relPath = path13.relative(baseDir, fullPath);
3641
+ const relPath = path14.relative(baseDir, fullPath);
3626
3642
  const nodePath = relPath.replace(/\\/g, "/").replace(/\.json$/, "");
3627
3643
  results.push(nodePath);
3628
3644
  }
@@ -3630,13 +3646,13 @@ async function scanJsonFiles(dir, baseDir) {
3630
3646
  return results;
3631
3647
  }
3632
3648
  async function removeEmptyParents(filePath, stopDir) {
3633
- let dir = path13.dirname(filePath);
3649
+ let dir = path14.dirname(filePath);
3634
3650
  while (dir !== stopDir && dir.startsWith(stopDir)) {
3635
3651
  try {
3636
3652
  const entries = await readdir6(dir);
3637
3653
  if (entries.length === 0) {
3638
3654
  await rm2(dir, { recursive: true });
3639
- dir = path13.dirname(dir);
3655
+ dir = path14.dirname(dir);
3640
3656
  } else {
3641
3657
  break;
3642
3658
  }
@@ -3648,7 +3664,7 @@ async function removeEmptyParents(filePath, stopDir) {
3648
3664
  async function readNodeDriftState(yggRoot, nodePath) {
3649
3665
  try {
3650
3666
  const filePath = nodeStatePath(yggRoot, nodePath);
3651
- const content = await readFile14(filePath, "utf-8");
3667
+ const content = await readFile15(filePath, "utf-8");
3652
3668
  const parsed = JSON.parse(content);
3653
3669
  return parsed;
3654
3670
  } catch {
@@ -3657,12 +3673,12 @@ async function readNodeDriftState(yggRoot, nodePath) {
3657
3673
  }
3658
3674
  async function writeNodeDriftState(yggRoot, nodePath, nodeState) {
3659
3675
  const filePath = nodeStatePath(yggRoot, nodePath);
3660
- await mkdir3(path13.dirname(filePath), { recursive: true });
3676
+ await mkdir3(path14.dirname(filePath), { recursive: true });
3661
3677
  const content = JSON.stringify(nodeState, null, 2) + "\n";
3662
- await writeFile5(filePath, content, "utf-8");
3678
+ await writeFile6(filePath, content, "utf-8");
3663
3679
  }
3664
3680
  async function garbageCollectDriftState(yggRoot, validNodePaths) {
3665
- const driftDir = path13.join(yggRoot, DRIFT_STATE_DIR);
3681
+ const driftDir = path14.join(yggRoot, DRIFT_STATE_DIR);
3666
3682
  const allNodePaths = await scanJsonFiles(driftDir, driftDir);
3667
3683
  const removed = [];
3668
3684
  for (const nodePath of allNodePaths) {
@@ -3676,7 +3692,7 @@ async function garbageCollectDriftState(yggRoot, validNodePaths) {
3676
3692
  return removed.sort();
3677
3693
  }
3678
3694
  async function readDriftState(yggRoot) {
3679
- const driftPath = path13.join(yggRoot, DRIFT_STATE_DIR);
3695
+ const driftPath = path14.join(yggRoot, DRIFT_STATE_DIR);
3680
3696
  let driftStat;
3681
3697
  try {
3682
3698
  driftStat = await stat5(driftPath);
@@ -3684,7 +3700,7 @@ async function readDriftState(yggRoot) {
3684
3700
  return {};
3685
3701
  }
3686
3702
  if (driftStat.isFile()) {
3687
- const content = await readFile14(driftPath, "utf-8");
3703
+ const content = await readFile15(driftPath, "utf-8");
3688
3704
  let raw;
3689
3705
  try {
3690
3706
  raw = JSON.parse(content);
@@ -3716,20 +3732,20 @@ async function readDriftState(yggRoot) {
3716
3732
  }
3717
3733
 
3718
3734
  // src/utils/hash.ts
3719
- import { readFile as readFile15, readdir as readdir7, stat as stat6 } from "fs/promises";
3720
- import path14 from "path";
3735
+ import { readFile as readFile16, readdir as readdir7, stat as stat6 } from "fs/promises";
3736
+ import path15 from "path";
3721
3737
  import { createHash } from "crypto";
3722
3738
  import { createRequire } from "module";
3723
3739
  var require2 = createRequire(import.meta.url);
3724
3740
  var ignoreFactory = require2("ignore");
3725
3741
  async function hashFile(filePath) {
3726
- const content = await readFile15(filePath);
3742
+ const content = await readFile16(filePath);
3727
3743
  return createHash("sha256").update(content).digest("hex");
3728
3744
  }
3729
3745
  async function loadRootGitignoreStack(projectRoot) {
3730
3746
  if (!projectRoot) return [];
3731
3747
  try {
3732
- const content = await readFile15(path14.join(projectRoot, ".gitignore"), "utf-8");
3748
+ const content = await readFile16(path15.join(projectRoot, ".gitignore"), "utf-8");
3733
3749
  const matcher = ignoreFactory();
3734
3750
  matcher.add(content);
3735
3751
  return [{ basePath: projectRoot, matcher }];
@@ -3739,7 +3755,7 @@ async function loadRootGitignoreStack(projectRoot) {
3739
3755
  }
3740
3756
  function isIgnoredByStack(candidatePath, stack) {
3741
3757
  for (const { basePath, matcher } of stack) {
3742
- const relativePath = path14.relative(basePath, candidatePath);
3758
+ const relativePath = path15.relative(basePath, candidatePath);
3743
3759
  if (relativePath === "" || relativePath.startsWith("..")) continue;
3744
3760
  if (matcher.ignores(relativePath) || matcher.ignores(relativePath + "/")) return true;
3745
3761
  }
@@ -3754,7 +3770,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
3754
3770
  const gitignoreStack = await loadRootGitignoreStack(projectRoot);
3755
3771
  const allFiles = [];
3756
3772
  for (const tf of trackedFiles) {
3757
- const absPath = path14.join(projectRoot, tf.path);
3773
+ const absPath = path15.join(projectRoot, tf.path);
3758
3774
  try {
3759
3775
  const st = await stat6(absPath);
3760
3776
  if (st.isDirectory()) {
@@ -3764,7 +3780,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
3764
3780
  });
3765
3781
  for (const entry of dirEntries) {
3766
3782
  allFiles.push({
3767
- relPath: path14.join(tf.path, entry.relPath).replace(/\\/g, "/"),
3783
+ relPath: path15.join(tf.path, entry.relPath).replace(/\\/g, "/"),
3768
3784
  absPath: entry.absPath,
3769
3785
  mtimeMs: entry.mtimeMs
3770
3786
  });
@@ -3804,7 +3820,7 @@ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, exclu
3804
3820
  async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
3805
3821
  let stack = options.gitignoreStack ?? [];
3806
3822
  try {
3807
- const localContent = await readFile15(path14.join(directoryPath, ".gitignore"), "utf-8");
3823
+ const localContent = await readFile16(path15.join(directoryPath, ".gitignore"), "utf-8");
3808
3824
  const localMatcher = ignoreFactory();
3809
3825
  localMatcher.add(localContent);
3810
3826
  stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
@@ -3814,7 +3830,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3814
3830
  const dirs = [];
3815
3831
  const files = [];
3816
3832
  for (const entry of entries) {
3817
- const absoluteChildPath = path14.join(directoryPath, entry.name);
3833
+ const absoluteChildPath = path15.join(directoryPath, entry.name);
3818
3834
  if (isIgnoredByStack(absoluteChildPath, stack)) continue;
3819
3835
  if (entry.isDirectory()) dirs.push(absoluteChildPath);
3820
3836
  else if (entry.isFile()) files.push(absoluteChildPath);
@@ -3827,7 +3843,7 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3827
3843
  Promise.all(files.map(async (f) => {
3828
3844
  const fileStat = await stat6(f);
3829
3845
  return {
3830
- relPath: path14.relative(rootDirectoryPath, f),
3846
+ relPath: path15.relative(rootDirectoryPath, f),
3831
3847
  absPath: f,
3832
3848
  mtimeMs: fileStat.mtimeMs
3833
3849
  };
@@ -3840,15 +3856,15 @@ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, optio
3840
3856
  }
3841
3857
 
3842
3858
  // src/core/context-files.ts
3843
- import path15 from "path";
3859
+ import path16 from "path";
3844
3860
  var STRUCTURAL_RELATION_TYPES2 = /* @__PURE__ */ new Set(["uses", "calls", "extends", "implements"]);
3845
3861
  function collectTrackedFiles(node, graph) {
3846
3862
  const seen = /* @__PURE__ */ new Set();
3847
3863
  const result = [];
3848
- const projectRoot = path15.dirname(graph.rootPath);
3849
- const yggPrefix = path15.relative(projectRoot, graph.rootPath);
3850
- const yggPrefixNormalized = yggPrefix.split(path15.sep).join("/");
3851
- 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));
3852
3868
  function addFile(filePath, category) {
3853
3869
  if (seen.has(filePath)) return;
3854
3870
  seen.add(filePath);
@@ -3896,7 +3912,7 @@ function collectTrackedFiles(node, graph) {
3896
3912
  if (!STRUCTURAL_RELATION_TYPES2.has(relation.type)) continue;
3897
3913
  const target = graph.nodes.get(relation.target);
3898
3914
  if (!target) continue;
3899
- 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);
3900
3916
  const structuralArts = structuralFilenames.filter(
3901
3917
  (filename) => target.artifacts.some((a) => a.filename === filename)
3902
3918
  );
@@ -3925,7 +3941,7 @@ function collectTrackedFiles(node, graph) {
3925
3941
  if (relation.type !== "emits" && relation.type !== "listens") continue;
3926
3942
  const target = graph.nodes.get(relation.target);
3927
3943
  if (!target) continue;
3928
- 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);
3929
3945
  const filterFilenames = structuralFilenames.length > 0 ? structuralFilenames : [...configArtifactKeys];
3930
3946
  for (const filename of filterFilenames) {
3931
3947
  if (target.artifacts.some((a) => a.filename === filename)) {
@@ -3960,7 +3976,7 @@ function collectParticipatingFlows2(graph, node, ancestors) {
3960
3976
 
3961
3977
  // src/core/drift-detector.ts
3962
3978
  import { access as access3 } from "fs/promises";
3963
- import path16 from "path";
3979
+ import path17 from "path";
3964
3980
  function getChildMappingExclusions(graph, nodePath) {
3965
3981
  const node = graph.nodes.get(nodePath);
3966
3982
  if (!node) return [];
@@ -3982,7 +3998,7 @@ function getChildMappingExclusions(graph, nodePath) {
3982
3998
  return exclusions;
3983
3999
  }
3984
4000
  async function detectDrift(graph, filterNodePath) {
3985
- const projectRoot = path16.dirname(graph.rootPath);
4001
+ const projectRoot = path17.dirname(graph.rootPath);
3986
4002
  const driftState = await readDriftState(graph.rootPath);
3987
4003
  const entries = [];
3988
4004
  for (const [nodePath, node] of graph.nodes) {
@@ -4065,14 +4081,14 @@ async function detectDrift(graph, filterNodePath) {
4065
4081
  };
4066
4082
  }
4067
4083
  function categorizeFile(filePath, _rootPath, projectRoot) {
4068
- const yggPrefix = path16.relative(projectRoot, _rootPath);
4069
- const normalizedPrefix = yggPrefix.split(path16.sep).join("/");
4084
+ const yggPrefix = path17.relative(projectRoot, _rootPath);
4085
+ const normalizedPrefix = yggPrefix.split(path17.sep).join("/");
4070
4086
  const normalizedFilePath = filePath.replace(/\\/g, "/");
4071
4087
  return normalizedFilePath.startsWith(normalizedPrefix) ? "graph" : "source";
4072
4088
  }
4073
4089
  async function allPathsMissing(projectRoot, mappingPaths) {
4074
4090
  for (const mp of mappingPaths) {
4075
- const absPath = path16.join(projectRoot, mp);
4091
+ const absPath = path17.join(projectRoot, mp);
4076
4092
  try {
4077
4093
  await access3(absPath);
4078
4094
  return false;
@@ -4082,7 +4098,7 @@ async function allPathsMissing(projectRoot, mappingPaths) {
4082
4098
  return true;
4083
4099
  }
4084
4100
  async function syncDriftState(graph, nodePath) {
4085
- const projectRoot = path16.dirname(graph.rootPath);
4101
+ const projectRoot = path17.dirname(graph.rootPath);
4086
4102
  const node = graph.nodes.get(nodePath);
4087
4103
  if (!node) throw new Error(`Node not found: ${nodePath}`);
4088
4104
  if (!node.meta.mapping) throw new Error(`Node has no mapping: ${nodePath}`);
@@ -4097,7 +4113,7 @@ async function syncDriftState(graph, nodePath) {
4097
4113
  if (previousHash && previousHash !== canonicalHash && existingEntry?.files) {
4098
4114
  let hasSourceChange = false;
4099
4115
  let hasGraphChange = false;
4100
- const yggPrefix = path16.relative(projectRoot, graph.rootPath).split(path16.sep).join("/");
4116
+ const yggPrefix = path17.relative(projectRoot, graph.rootPath).split(path17.sep).join("/");
4101
4117
  for (const [filePath, hash] of Object.entries(fileHashes)) {
4102
4118
  const storedHash = existingEntry.files[filePath];
4103
4119
  if (storedHash && storedHash === hash) continue;
@@ -4363,7 +4379,7 @@ function registerStatusCommand(program2) {
4363
4379
  const warningCount = validation.issues.filter(
4364
4380
  (issue) => issue.severity === "warning"
4365
4381
  ).length;
4366
- const configuredArtifactTypes = Object.keys(graph.config.artifacts ?? {});
4382
+ const configuredArtifactTypes = Object.keys(STANDARD_ARTIFACTS2);
4367
4383
  const totalSlots = graph.nodes.size * configuredArtifactTypes.length;
4368
4384
  let filledSlots = 0;
4369
4385
  let mappedNodeCount = 0;
@@ -4437,10 +4453,10 @@ function registerTreeCommand(program2) {
4437
4453
  let roots;
4438
4454
  let showProjectName;
4439
4455
  if (options.root?.trim()) {
4440
- const path19 = options.root.trim().replace(/\/$/, "");
4441
- const node = graph.nodes.get(path19);
4456
+ const path20 = options.root.trim().replace(/\/$/, "");
4457
+ const node = graph.nodes.get(path20);
4442
4458
  if (!node) {
4443
- process.stderr.write(`Error: path '${path19}' not found
4459
+ process.stderr.write(`Error: path '${path20}' not found
4444
4460
  `);
4445
4461
  process.exit(1);
4446
4462
  }
@@ -4485,7 +4501,7 @@ function printNode(node, prefix, isLast, depth, maxDepth) {
4485
4501
 
4486
4502
  // src/core/dependency-resolver.ts
4487
4503
  import { execSync } from "child_process";
4488
- import path17 from "path";
4504
+ import path18 from "path";
4489
4505
  var STRUCTURAL_RELATION_TYPES3 = /* @__PURE__ */ new Set(["uses", "calls", "extends", "implements"]);
4490
4506
  var EVENT_RELATION_TYPES2 = /* @__PURE__ */ new Set(["emits", "listens"]);
4491
4507
  function filterRelationType(relType, filter) {
@@ -4562,7 +4578,7 @@ function registerDepsCommand(program2) {
4562
4578
  // src/core/graph-from-git.ts
4563
4579
  import { mkdtemp, rm as rm3 } from "fs/promises";
4564
4580
  import { tmpdir } from "os";
4565
- import path18 from "path";
4581
+ import path19 from "path";
4566
4582
  import { execSync as execSync2 } from "child_process";
4567
4583
  async function loadGraphFromRef(projectRoot, ref = "HEAD") {
4568
4584
  const yggPath = ".yggdrasil";
@@ -4573,8 +4589,8 @@ async function loadGraphFromRef(projectRoot, ref = "HEAD") {
4573
4589
  return null;
4574
4590
  }
4575
4591
  try {
4576
- tmpDir = await mkdtemp(path18.join(tmpdir(), "ygg-git-"));
4577
- const archivePath = path18.join(tmpDir, "archive.tar");
4592
+ tmpDir = await mkdtemp(path19.join(tmpdir(), "ygg-git-"));
4593
+ const archivePath = path19.join(tmpDir, "archive.tar");
4578
4594
  execSync2(`git archive ${ref} ${yggPath} -o "${archivePath}"`, {
4579
4595
  cwd: projectRoot,
4580
4596
  stdio: "pipe"
@@ -4644,14 +4660,14 @@ function buildTransitiveChains(targetNode, direct, allDependents, reverse) {
4644
4660
  }
4645
4661
  const chains = [];
4646
4662
  for (const node of transitiveOnly) {
4647
- const path19 = [];
4663
+ const path20 = [];
4648
4664
  let current = node;
4649
4665
  while (current) {
4650
- path19.unshift(current);
4666
+ path20.unshift(current);
4651
4667
  current = parent.get(current);
4652
4668
  }
4653
- if (path19.length >= 3) {
4654
- chains.push(path19.slice(1).map((p) => `<- ${p}`).join(" "));
4669
+ if (path20.length >= 3) {
4670
+ chains.push(path20.slice(1).map((p) => `<- ${p}`).join(" "));
4655
4671
  }
4656
4672
  }
4657
4673
  return chains.sort();
@@ -4695,14 +4711,14 @@ function collectIndirectDependents(graph, directlyAffected) {
4695
4711
  }
4696
4712
  for (const [node] of parent) {
4697
4713
  if (directSet.has(node)) continue;
4698
- const path19 = [node];
4714
+ const path20 = [node];
4699
4715
  let current = node;
4700
4716
  while (parent.has(current)) {
4701
4717
  current = parent.get(current);
4702
- path19.push(current);
4718
+ path20.push(current);
4703
4719
  }
4704
- const chain = path19.map((p) => `<- ${p}`).join(" ");
4705
- const depth = path19.length;
4720
+ const chain = path20.map((p) => `<- ${p}`).join(" ");
4721
+ const depth = path20.length;
4706
4722
  const existing = bestChain.get(node);
4707
4723
  if (!existing || depth < existing.depth) {
4708
4724
  bestChain.set(node, { chain, depth });