@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 +147 -53
- package/dist/bin.js.map +1 -1
- package/dist/templates/default-config.ts +4 -21
- package/dist/templates/rules.ts +69 -26
- package/package.json +1 -1
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
|
-
|
|
36
|
+
internals.md:
|
|
36
37
|
required: never
|
|
37
|
-
description: "
|
|
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
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|
116
|
-
- \u274C Modified source code without updating graph artifacts in the same response.
|
|
117
|
-
- \u274C
|
|
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
|
|
121
|
-
- \u274C
|
|
122
|
-
- \u274C
|
|
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,
|
|
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 \`
|
|
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.
|
|
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 \`
|
|
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:**
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}
|