@chrisdudek/yg 1.1.0 → 1.3.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
@@ -21,6 +21,7 @@ node_types:
21
21
  - name: module
22
22
  - name: service
23
23
  - name: library
24
+ - name: infrastructure
24
25
 
25
26
  artifacts:
26
27
  responsibility.md:
@@ -30,29 +31,11 @@ artifacts:
30
31
  interface.md:
31
32
  required:
32
33
  when: has_incoming_relations
33
- description: "Public API \u2014 methods, parameters, return types, contracts"
34
+ description: "Public API \u2014 methods, parameters, return types, contracts, failure modes, exposed data structures"
34
35
  structural_context: true
35
- logic.md:
36
+ internals.md:
36
37
  required: never
37
- description: "Algorithmic flow, control flow, branching logic, decision trees \u2014 the 'how' of execution"
38
- constraints.md:
39
- required: never
40
- description: "Validation rules, business rules, invariants"
41
- structural_context: true
42
- errors.md:
43
- required:
44
- when: has_incoming_relations
45
- description: "Failure modes, edge cases, error conditions, recovery behavior"
46
- structural_context: true
47
- model.md:
48
- required: never
49
- description: "Data structures, schemas, entities, type definitions \u2014 the shape of data this node owns or manages"
50
- state.md:
51
- required: never
52
- description: "State machines, lifecycle, transitions"
53
- decisions.md:
54
- required: never
55
- description: "Local design decisions and rationale \u2014 choices specific to this node, not system-wide"
38
+ description: "How the node works and why \u2014 algorithms, business rules, state machines, design decisions with rejected alternatives"
56
39
 
57
40
  quality:
58
41
  min_artifact_length: 50
@@ -77,11 +60,12 @@ Yggdrasil is persistent semantic memory stored in \`.yggdrasil/\`. It maps the r
77
60
  BEFORE reading, researching, planning, OR modifying ANY mapped file:
78
61
  1. yg owner --file <path>
79
62
  2. yg build-context --node <owner>
80
- The context package is your primary source of understanding.
81
- Raw file reads are for implementation details WITHIN a node you
82
- already understand from its context package.
63
+ The context package is your primary source of ARCHITECTURAL understanding:
64
+ intent, constraints, relations, rationale. For IMPLEMENTATION precision
65
+ (exact behavior, error handling, await patterns, edge cases) \u2014 verify
66
+ against source code. Aspects describe intended patterns; individual
67
+ implementations may deviate.
83
68
  If the context package seems insufficient \u2014 enrich the graph.
84
- Do not bypass it.
85
69
 
86
70
  AFTER modifying:
87
71
  3. Update graph artifacts to reflect changes
@@ -102,7 +86,7 @@ WHEN UNSURE: ask the user. Never guess. Never assume.
102
86
 
103
87
  ### Five Core Rules
104
88
 
105
- 1. **Graph first.** Before reading, researching, planning, or modifying mapped files, run \`yg owner\` and \`yg build-context\`. Always. The context package \u2014 not raw source \u2014 is your primary source of understanding.
89
+ 1. **Graph first.** Before reading, researching, planning, or modifying mapped files, run \`yg owner\` and \`yg build-context\`. Always. The context package is your primary source of architectural understanding. For implementation-level precision (exact behavior, error paths, edge cases) \u2014 verify against source code after loading the context package.
106
90
  2. **Code and graph are one.** Code changed \u2192 graph updated in the same response. Graph changed \u2192 source verified in the same response. No exceptions.
107
91
  3. **Never invent why.** The graph captures human intent. If you don't know why something was decided, ask. Never hallucinate rationale.
108
92
  4. **Always capture why \u2014 especially why NOT.** When the user explains a reason, record it in the graph immediately. When a design choice is made, also record rejected alternatives: "Chose X over Y because Z." Rejected alternatives are the highest-value information \u2014 invisible in code and irrecoverable once forgotten. Conversation evaporates; graph persists.
@@ -112,21 +96,14 @@ WHEN UNSURE: ask the user. Never guess. Never assume.
112
96
 
113
97
  You have broken Yggdrasil if you do any of the following:
114
98
 
115
- - \u274C Modified source code without running \`yg owner --file <path>\` first.
116
- - \u274C Modified source code without updating graph artifacts in the same response.
117
- - \u274C Modified graph files without verifying source code alignment in the same response.
118
- - \u274C Resolved a code-graph inconsistency without asking the user first.
99
+ - \u274C Worked on a mapped file without running \`yg owner\` + \`yg build-context\` first \u2014 whether reading to understand, planning, or modifying.
100
+ - \u274C Modified source code without updating graph artifacts in the same response, or vice versa.
101
+ - \u274C Resolved a code-graph inconsistency or ambiguity without asking the user first.
119
102
  - \u274C Created or edited a graph element without reading its schema in \`schemas/\` first.
120
- - \u274C Ran \`yg drift-sync\` before updating graph artifacts.
121
- - \u274C Wrote a flow description that describes code sequences instead of a business process.
122
- - \u274C Used an aspect identifier that has no corresponding \`aspects/\` directory.
123
- - \u274C Placed a cross-cutting requirement in a local node artifact instead of an aspect.
124
- - \u274C Invented a rationale, business rule, or architectural decision.
103
+ - \u274C Ran \`yg drift-sync\` before both graph artifacts and source code are current.
104
+ - \u274C Placed a cross-cutting requirement in a local artifact instead of an aspect, or used an aspect id with no \`aspects/\` directory.
105
+ - \u274C Invented a rationale, business rule, or decision \u2014 or recorded a decision without documenting rejected alternatives and rationale (use "rationale: unknown" if unknown).
125
106
  - \u274C Used blackbox coverage for greenfield (new) code.
126
- - \u274C Answered a question about a mapped file without running \`yg build-context\` first.
127
- - \u274C Read mapped source files to plan or research changes without running \`yg build-context\` first.
128
- - \u274C Deferred \`yg drift-sync\` to the end of a multi-step task instead of running it incrementally after each logical group of changes.
129
- - \u274C Recorded a design decision without documenting which alternatives were rejected and why.
130
107
 
131
108
  ### Escape Hatch
132
109
 
@@ -166,6 +143,10 @@ WRAP-UP (user signals "done", "wrap up", "that's enough"):
166
143
  - [ ] 2. yg drift --drifted-only \u2192 resolve
167
144
  - [ ] 3. yg validate \u2192 fix errors
168
145
  - [ ] 4. Report: which nodes and files were changed
146
+
147
+ BEFORE ENDING ANY RESPONSE (self-audit):
148
+ - [ ] Did I modify source code? If yes \u2192 did I update graph artifacts in this same response?
149
+ - [ ] If you changed code and did not sync the graph, you have broken the protocol. Do not finish until both are done.
169
150
  \`\`\`
170
151
 
171
152
  ### Modify Source Code
@@ -197,7 +178,7 @@ You are not allowed to edit or create source code without establishing graph cov
197
178
 
198
179
  1. Create aspects first (cross-cutting requirements the new code must satisfy)
199
180
  2. Create flows if the code participates in a business process
200
- 3. Create nodes with full artifacts \u2014 responsibility, constraints, decisions, interface, logic
181
+ 3. Create nodes with full artifacts \u2014 responsibility, interface, internals
201
182
  4. Review the context package (\`yg build-context\`) \u2014 it is now the behavioral specification
202
183
  5. Implement code that satisfies the specification
203
184
  6. The graph specifies WHAT and WHY; the code implements HOW (framework APIs, library choices)
@@ -234,7 +215,8 @@ Per area checklist:
234
215
  - Business process unclear: "This code appears to be part of a larger process. Can you describe what it means from a business perspective?"
235
216
  - Constraint without rationale: "I see [constraint X]. Do you know why this exists? I want to record the reason, not just the rule."
236
217
  - Unexplained architectural choice: "I see [approach X]. What was the reason for this choice?"
237
- - Decision without alternatives: "You chose [X]. What alternatives did you consider, and why did you reject them?" Record the answer in \`decisions.md\`.
218
+ - Decision without alternatives: "You chose [X]. What alternatives did you consider, and why did you reject them?" Record the answer in the Decisions section of \`internals.md\`.
219
+ - Decision without known rationale: Record the decision in \`internals.md\` with "rationale: unknown \u2014 inferred from code, not confirmed by developer." A recorded decision with unknown rationale is infinitely more valuable than no record at all, and safer than an invented rationale.
238
220
 
239
221
  ### Bootstrap Mode
240
222
 
@@ -262,6 +244,27 @@ Always ask the user before resolving drift. Never auto-resolve.
262
244
 
263
245
  Threshold: >10 drifted nodes \u2192 ask user which area to prioritize. Do not resolve all at once.
264
246
 
247
+ **Drift triage:** Prioritize aspects and \`internals.md\` (highest decay rate), then \`responsibility.md\` and \`interface.md\` (most stable).
248
+
249
+ ### Graph Audit
250
+
251
+ When reviewing graph quality (triggered by user or quality improvement):
252
+
253
+ **Step 1 \u2014 Consistency** (catches WRONG information):
254
+
255
+ - [ ] 1. \`yg build-context --node <path>\`
256
+ - [ ] 2. Read mapped source files
257
+ - [ ] 3. For each claim in graph: verify against source code
258
+ - [ ] 4. For each aspect: verify the pattern holds in THIS node. If it deviates, add \`aspect_exceptions\` in \`node.yaml\`
259
+ - [ ] 5. Report inconsistencies
260
+
261
+ **Step 2 \u2014 Completeness** (catches MISSING information):
262
+
263
+ - [ ] 1. For each public method: is it in \`interface.md\`?
264
+ - [ ] 2. For each error path: is it in \`interface.md\` (Failure Modes section)?
265
+ - [ ] 3. For each behavioral invariant: is it in the graph?
266
+ - [ ] 4. Report omissions separately from inconsistencies
267
+
265
268
  ### Error Recovery
266
269
 
267
270
  - **\`yg\` not found** \u2192 inform user: "yg CLI is not installed or not in PATH." Stop.
@@ -290,9 +293,24 @@ Key facts:
290
293
  - **Aspect id = directory path** under \`aspects/\`. Each aspect has \`aspect.yaml\` + content \`.md\` files. No automatic parent-child \u2014 use \`implies\` explicitly.
291
294
  - **Flows = business processes.** A flow describes what happens in the world, not code sequences. Flow aspects propagate to all participants.
292
295
 
296
+ **Node type guidance:**
297
+
298
+ - \`module\` \u2014 business logic unit with clear domain responsibility
299
+ - \`service\` \u2014 component providing functionality to other nodes
300
+ - \`library\` \u2014 shared utility code with no domain knowledge
301
+ - \`infrastructure\` \u2014 guards, resolvers, middleware, interceptors, validators that intercept or modify request flow. These affect blast radius of changes but are invisible in call graphs. Map them to make blast radius analysis accurate. Key signal: code that runs WITHOUT being explicitly called by business logic (e.g., NestJS guards, Express middleware, GraphQL resolvers).
302
+
303
+ ### Artifact Structure
304
+
305
+ Three artifacts capture node knowledge at three levels:
306
+
307
+ - **responsibility.md** (always required) \u2014 WHAT: identity, boundaries, what the node is NOT responsible for.
308
+ - **interface.md** (required when node has consumers) \u2014 HOW TO USE: public methods, parameters, return types, contracts, failure modes, exposed data structures. Everything another node needs to interact with this one.
309
+ - **internals.md** (optional, highest value for cross-module nodes) \u2014 HOW IT WORKS + WHY: algorithms, control flow, business rules, invariants, state machines, lifecycle, and design decisions with rejected alternatives. Use sections within the file: ## Logic, ## Constraints, ## State, ## Decisions (with "Chose X over Y because Z" format).
310
+
293
311
  ### Context Assembly
294
312
 
295
- Run \`yg build-context --node <path>\` to get the deterministic context package for a node. Trust the package \u2014 it assembles global config, hierarchy, own artifacts, aspects, and relational context. If the package is insufficient, enrich the graph. Do not bypass it with raw file exploration.
313
+ Run \`yg build-context --node <path>\` to get the deterministic context package for a node. The package assembles global config, hierarchy, own artifacts, aspects, and relational context. It is your architectural map. For implementation-level claims (exact call patterns, error handling, await vs fire-and-forget) \u2014 verify against source code. If the package is insufficient, enrich the graph.
296
314
 
297
315
  ### Information Routing
298
316
 
@@ -303,7 +321,7 @@ When you encounter information, route it to the correct location:
303
321
  - **Business process** \u2192 flow (\`flows/<name>/\` with \`flow.yaml\` + \`description.md\`). Ask user if process unclear.
304
322
  - **Shared across a domain** \u2192 parent node artifact. Children receive it through hierarchy.
305
323
  - **Technology stack or standard** \u2192 \`config.yaml\` under \`stack\` or \`standards\` (+ \`rationale\` field)
306
- - **Decision (why + why NOT):** one node \u2192 \`decisions.md\` with format "Chose X over Y because Z"; category of nodes \u2192 aspect content files; tech choice \u2192 \`config.yaml\` rationale field. Always include rejected alternatives \u2014 they are the highest-value graph content.
324
+ - **Decision (why + why NOT):** one node \u2192 Decisions section of \`internals.md\` with format "Chose X over Y because Z"; category of nodes \u2192 aspect content files; tech choice \u2192 \`config.yaml\` rationale field. Always include rejected alternatives \u2014 they are the highest-value graph content. If the rationale is unknown: record the decision with "rationale: unknown" and note what CAN be observed from the code. Never invent a plausible-sounding rationale.
307
325
 
308
326
  ### Creating Aspects
309
327
 
@@ -321,6 +339,10 @@ Test: "Does this requirement apply to more than one node?" Yes \u2192 aspect. No
321
339
  - **Architectural:** Structural patterns with rationale (e.g., dual-rollback on provider failure, idempotency via key generation, fire-and-forget dispatch)
322
340
  - **Concurrency:** Shared concurrency strategies (e.g., pessimistic locking, retry-on-deadlock, optimistic versioning)
323
341
 
342
+ When a node follows an aspect's pattern with exceptions, record exceptions in \`node.yaml\` under \`aspect_exceptions\`. Example: aspect says "fire-and-forget" but this node awaits the publish call. The exception appears in the context package next to the aspect content, preventing abstractions from masking implementation details.
343
+
344
+ **Aspect lifecycle warning.** Aspects decay CATASTROPHICALLY \u2014 a pattern either exists or it doesn't. When a pattern changes, ALL aspect claims become wrong at once. This differs from other artifacts: \`interface.md\` and \`responsibility.md\` are most stable (~9-year half-life); \`internals.md\` has moderate stability (~2.5-year half-life); aspects are least stable (~2.4-year half-life, binary decay). After any significant feature addition, review ALL aspects touching the affected area. Don't wait for drift \u2014 aspects can be 100% wrong without any mapped file changing.
345
+
324
346
  ### Creating Flows
325
347
 
326
348
  - [ ] 1. Read \`schemas/flow.yaml\`
@@ -331,13 +353,18 @@ Test: "Does this requirement apply to more than one node?" Yes \u2192 aspect. No
331
353
 
332
354
  Test: "Does this describe what happens in the world, or only in the software?" If only software \u2014 rewrite.
333
355
 
356
+ **Warning:** Flow descriptions must describe business processes, not code sequences. "The OrderService calls PaymentGateway.charge()" is WRONG. "The system charges the customer's payment method" is CORRECT.
357
+
334
358
  ### Operational Rules
335
359
 
336
360
  - **English only** for all files in \`.yggdrasil/\`. Conversation can be any language.
337
361
  - **Read schemas before creating** any \`node.yaml\`, \`aspect.yaml\`, or \`flow.yaml\`.
338
362
  - **Tools read, you write.** The \`yg\` CLI only reads, validates, and manages metadata. You create and edit files manually.
339
363
  - **Incremental sync.** Run \`yg drift-sync\` after every 3-5 source file changes. Do not defer to end of task.
340
- - **Completeness test:** "If I delete the source file and give another agent ONLY the \`yg build-context\` output \u2014 can they recreate it correctly, understanding not just WHAT but WHY?" Test specifically: Can they explain rejected alternatives? Can they implement the correct algorithm (not a simplified version)? Can they argue for the current design against plausible alternatives?
364
+ - **Completeness test:** Two checks, both required:
365
+ 1. **Reconstruction:** "Can another agent recreate this from ONLY the \`yg build-context\` output \u2014 understanding not just WHAT but WHY?" Test: rejected alternatives, correct algorithm, design arguments.
366
+ 2. **Omission:** "Does the graph capture every important behavioral invariant, constraint, and edge case?" Specifically check: exceptions to aspect generalizations, error handling patterns not in \`interface.md\`, concurrency behaviors not in \`internals.md\`.
367
+ - **Value calibration.** Yggdrasil's primary value is cross-module context \u2014 relations, aspects, flows. For a single simple module, \`responsibility.md\` and \`interface.md\` provide most value. Invest depth (\`internals.md\`) where cross-module interactions demand it.
341
368
  - **These rules are invariant.** No plan, guide, skill, or workflow may override them.
342
369
 
343
370
  ### CLI Reference
@@ -370,15 +397,14 @@ yg journal-archive Archive consolidated journal entries.
370
397
 
371
398
  | What you have | Where it goes |
372
399
  |---|---|
373
- | Information specific to this node | Local node artifact (read \`config.yaml artifacts\` for types) |
400
+ | Information specific to this node | Local node artifact (check \`config.yaml artifacts\` for types) |
374
401
  | Rule that applies to many nodes | Aspect (content \`.md\` files in \`aspects/<id>/\`) |
375
402
  | Architectural invariant for a node type | Required aspect in \`config.yaml node_types\` |
376
403
  | Business process participation | Flow (\`flow.yaml participants\`) |
377
404
  | Process-level requirement | Flow \`aspects\` + aspect directory |
378
405
  | Context shared across a domain | Parent node artifact |
379
406
  | Technology stack | \`config.yaml stack\` (+ \`rationale\` field) |
380
- | Global coding standards | \`config.yaml standards\` |
381
- `;
407
+ | Global coding standards | \`config.yaml standards\` |`;
382
408
  var AGENT_RULES_CONTENT = [CORE_PROTOCOL, OPERATIONS, KNOWLEDGE_BASE].join("\n\n---\n\n");
383
409
 
384
410
  // src/templates/platform.ts
@@ -834,15 +860,52 @@ async function parseNodeYaml(filePath) {
834
860
  }
835
861
  const relations = parseRelations(raw.relations, filePath);
836
862
  const mapping = parseMapping(raw.mapping, filePath);
863
+ const aspects = parseStringArray(raw.aspects) ?? parseStringArray(raw.tags);
864
+ const aspectExceptions = parseAspectExceptions(raw.aspect_exceptions, aspects, filePath);
837
865
  return {
838
866
  name: raw.name.trim(),
839
867
  type: raw.type.trim(),
840
- aspects: parseStringArray(raw.aspects) ?? parseStringArray(raw.tags),
868
+ aspects,
869
+ aspect_exceptions: aspectExceptions,
841
870
  blackbox: raw.blackbox ?? false,
842
871
  relations: relations.length > 0 ? relations : void 0,
843
872
  mapping
844
873
  };
845
874
  }
875
+ function parseAspectExceptions(raw, aspects, filePath) {
876
+ if (raw === void 0 || raw === null) return void 0;
877
+ if (!Array.isArray(raw)) {
878
+ throw new Error(`node.yaml at ${filePath}: 'aspect_exceptions' must be an array`);
879
+ }
880
+ if (raw.length === 0) return void 0;
881
+ const aspectSet = new Set(aspects ?? []);
882
+ const result = [];
883
+ for (let i = 0; i < raw.length; i++) {
884
+ const item = raw[i];
885
+ if (typeof item !== "object" || item === null) {
886
+ throw new Error(`node.yaml at ${filePath}: aspect_exceptions[${i}] must be an object`);
887
+ }
888
+ const obj = item;
889
+ if (typeof obj.aspect !== "string" || obj.aspect.trim() === "") {
890
+ throw new Error(
891
+ `node.yaml at ${filePath}: aspect_exceptions[${i}].aspect must be a non-empty string`
892
+ );
893
+ }
894
+ if (typeof obj.note !== "string" || obj.note.trim() === "") {
895
+ throw new Error(
896
+ `node.yaml at ${filePath}: aspect_exceptions[${i}].note must be a non-empty string`
897
+ );
898
+ }
899
+ const aspectId = obj.aspect.trim();
900
+ if (!aspectSet.has(aspectId)) {
901
+ throw new Error(
902
+ `node.yaml at ${filePath}: aspect_exceptions[${i}].aspect "${aspectId}" is not in this node's aspects list`
903
+ );
904
+ }
905
+ result.push({ aspect: aspectId, note: obj.note.trim() });
906
+ }
907
+ return result.length > 0 ? result : void 0;
908
+ }
846
909
  function parseStringArray(val) {
847
910
  if (!Array.isArray(val)) return void 0;
848
911
  const arr = val.filter((v) => typeof v === "string");
@@ -1288,7 +1351,8 @@ async function buildContext(graph, nodePath) {
1288
1351
  }
1289
1352
  const aspectsToInclude = resolveAspects(allAspectIds, graph.aspects);
1290
1353
  for (const aspect of aspectsToInclude) {
1291
- layers.push(buildAspectLayer(aspect));
1354
+ const exception = node.meta.aspect_exceptions?.find((e) => e.aspect === aspect.id);
1355
+ layers.push(buildAspectLayer(aspect, exception?.note));
1292
1356
  }
1293
1357
  const fullText = layers.map((l) => l.content).join("\n\n");
1294
1358
  const tokenCount = estimateTokens(fullText);
@@ -1471,9 +1535,14 @@ Consumes: ${relation.consumes.join(", ")}`;
1471
1535
  attrs
1472
1536
  };
1473
1537
  }
1474
- function buildAspectLayer(aspect) {
1475
- const content = aspect.artifacts.map((a) => `### ${a.filename}
1538
+ function buildAspectLayer(aspect, exceptionNote) {
1539
+ let content = aspect.artifacts.map((a) => `### ${a.filename}
1476
1540
  ${a.content}`).join("\n\n");
1541
+ if (exceptionNote) {
1542
+ content += `
1543
+
1544
+ \u26A0 **Exception for this node:** ${exceptionNote}`;
1545
+ }
1477
1546
  return {
1478
1547
  type: "aspects",
1479
1548
  label: `${aspect.name} (aspect: ${aspect.id})`,
@@ -1574,6 +1643,7 @@ async function validate(graph, scope = "all") {
1574
1643
  issues.push(...checkImpliedAspectsExist(graph));
1575
1644
  issues.push(...checkImpliesNoCycles(graph));
1576
1645
  issues.push(...checkRequiredAspectsCoverage(graph));
1646
+ issues.push(...checkAspectExceptions(graph));
1577
1647
  issues.push(...checkRequiredArtifacts(graph));
1578
1648
  issues.push(...checkInvalidArtifactConditions(graph));
1579
1649
  issues.push(...await checkContextBudget(graph));
@@ -1820,6 +1890,24 @@ function checkRequiredAspectsCoverage(graph) {
1820
1890
  }
1821
1891
  return issues;
1822
1892
  }
1893
+ function checkAspectExceptions(graph) {
1894
+ const issues = [];
1895
+ for (const [nodePath, node] of graph.nodes) {
1896
+ for (const exception of node.meta.aspect_exceptions ?? []) {
1897
+ const nodeAspects = node.meta.aspects ?? [];
1898
+ if (!nodeAspects.includes(exception.aspect)) {
1899
+ issues.push({
1900
+ severity: "error",
1901
+ code: "E018",
1902
+ rule: "invalid-aspect-exception",
1903
+ message: `aspect_exceptions references aspect '${exception.aspect}' which is not in this node's aspects list (${nodeAspects.join(", ") || "none"})`,
1904
+ nodePath
1905
+ });
1906
+ }
1907
+ }
1908
+ }
1909
+ return issues;
1910
+ }
1823
1911
  function checkNoCycles(graph) {
1824
1912
  const WHITE = 0;
1825
1913
  const GRAY = 1;
@@ -3155,7 +3243,7 @@ function findOwner(graph, projectRoot, rawPath) {
3155
3243
  const mappingPaths = normalizeMappingPaths(node.meta.mapping).map(normalizeForMatch).filter((mappingPath) => mappingPath.length > 0);
3156
3244
  for (const mappingPath of mappingPaths) {
3157
3245
  if (file === mappingPath) {
3158
- return { file, nodePath, mappingPath };
3246
+ return { file, nodePath, mappingPath, direct: true };
3159
3247
  }
3160
3248
  if (file.startsWith(mappingPath + "/")) {
3161
3249
  if (!best || best && mappingPath.length > best.mappingPath.length) {
@@ -3164,7 +3252,7 @@ function findOwner(graph, projectRoot, rawPath) {
3164
3252
  }
3165
3253
  }
3166
3254
  }
3167
- return best ? { file, nodePath: best.nodePath, mappingPath: best.mappingPath } : { file, nodePath: null };
3255
+ return best ? { file, nodePath: best.nodePath, mappingPath: best.mappingPath, direct: false } : { file, nodePath: null };
3168
3256
  }
3169
3257
  function registerOwnerCommand(program2) {
3170
3258
  program2.command("owner").description("Find which graph node owns a source file").requiredOption("--file <path>", "File path (relative to repository root)").action(async (options) => {
@@ -3194,6 +3282,12 @@ function registerOwnerCommand(program2) {
3194
3282
  } else {
3195
3283
  process.stdout.write(`${result.file} -> ${result.nodePath}
3196
3284
  `);
3285
+ if (result.direct === false && result.mappingPath) {
3286
+ process.stdout.write(
3287
+ ` Plik nie ma w\u0142asnego mapowania; kontekst pochodzi z nadrz\u0119dnego katalogu ${result.mappingPath}. U\u017Cyj: yg build-context --node ${result.nodePath}
3288
+ `
3289
+ );
3290
+ }
3197
3291
  }
3198
3292
  } catch (error) {
3199
3293
  process.stderr.write(`Error: ${error.message}