@chrisdudek/yg 2.4.1 → 2.5.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 +114 -16
- package/dist/bin.js.map +1 -1
- package/dist/templates/default-config.ts +1 -0
- package/dist/templates/rules.ts +2 -2
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -45,6 +45,7 @@ quality:
|
|
|
45
45
|
context_budget:
|
|
46
46
|
warning: 10000
|
|
47
47
|
error: 20000
|
|
48
|
+
own_warning: 5000
|
|
48
49
|
`;
|
|
49
50
|
|
|
50
51
|
// src/templates/platform.ts
|
|
@@ -348,8 +349,8 @@ When reviewing graph quality (triggered by user or quality improvement):
|
|
|
348
349
|
|
|
349
350
|
- **\`yg\` not found** \u2192 inform user: "yg CLI is not installed or not in PATH." Stop.
|
|
350
351
|
- **Unfixable validate errors** \u2192 if not resolved after 3 attempts, stop and report to user. Do not loop.
|
|
351
|
-
- **Budget
|
|
352
|
-
- **
|
|
352
|
+
- **Budget warning (W005/W006)** \u2192 informational. \`yg validate\` shows a breakdown (own/hierarchy/aspects/flows/dependencies). Large inherited context means the system is complex \u2014 this is not a problem to fix, it is reality to acknowledge. Do not delete knowledge from artifacts. Do not attempt to "reduce" inherited context.
|
|
353
|
+
- **Own budget warning (W015)** \u2192 own artifacts are large. Consider splitting this node's responsibilities into child nodes. Redistribute knowledge across children so total knowledge is preserved or increased, never reduced.
|
|
353
354
|
- **Corrupted \`.yggdrasil/\` files** \u2192 report to user. Do not attempt repair.
|
|
354
355
|
- **Incremental sync** \u2192 run \`yg drift-sync\` every 3-5 source files during multi-file tasks. Do not defer to end.`;
|
|
355
356
|
var KNOWLEDGE_BASE = `## KNOWLEDGE BASE
|
|
@@ -1185,7 +1186,7 @@ import { parse as parseYaml3 } from "yaml";
|
|
|
1185
1186
|
var DEFAULT_QUALITY = {
|
|
1186
1187
|
min_artifact_length: 50,
|
|
1187
1188
|
max_direct_relations: 10,
|
|
1188
|
-
context_budget: { warning: 1e4, error: 2e4 }
|
|
1189
|
+
context_budget: { warning: 1e4, error: 2e4, own_warning: void 0 }
|
|
1189
1190
|
};
|
|
1190
1191
|
async function parseConfig(filePath) {
|
|
1191
1192
|
const content = await readFile5(filePath, "utf-8");
|
|
@@ -1250,7 +1251,8 @@ async function parseConfig(filePath) {
|
|
|
1250
1251
|
max_direct_relations: qualityRaw.max_direct_relations ?? DEFAULT_QUALITY.max_direct_relations,
|
|
1251
1252
|
context_budget: {
|
|
1252
1253
|
warning: qualityRaw.context_budget?.warning ?? DEFAULT_QUALITY.context_budget.warning,
|
|
1253
|
-
error: qualityRaw.context_budget?.error ?? DEFAULT_QUALITY.context_budget.error
|
|
1254
|
+
error: qualityRaw.context_budget?.error ?? DEFAULT_QUALITY.context_budget.error,
|
|
1255
|
+
own_warning: qualityRaw.context_budget?.own_warning
|
|
1254
1256
|
}
|
|
1255
1257
|
} : DEFAULT_QUALITY;
|
|
1256
1258
|
if (quality.context_budget.error < quality.context_budget.warning) {
|
|
@@ -1258,6 +1260,9 @@ async function parseConfig(filePath) {
|
|
|
1258
1260
|
`yg-config.yaml: quality.context_budget.error (${quality.context_budget.error}) must be >= warning (${quality.context_budget.warning})`
|
|
1259
1261
|
);
|
|
1260
1262
|
}
|
|
1263
|
+
if (quality.context_budget.own_warning !== void 0 && quality.context_budget.own_warning <= 0) {
|
|
1264
|
+
throw new Error("quality.context_budget.own_warning must be a positive number");
|
|
1265
|
+
}
|
|
1261
1266
|
return {
|
|
1262
1267
|
version,
|
|
1263
1268
|
name: raw.name.trim(),
|
|
@@ -2045,6 +2050,77 @@ function collectAncestors(node) {
|
|
|
2045
2050
|
}
|
|
2046
2051
|
return ancestors;
|
|
2047
2052
|
}
|
|
2053
|
+
function collectDependencyAncestors(target, config, graph) {
|
|
2054
|
+
const ancestors = collectAncestors(target);
|
|
2055
|
+
const structuralFilenames = Object.entries(config.artifacts ?? {}).filter(([, c]) => c.included_in_relations).map(([filename]) => filename);
|
|
2056
|
+
const configArtifactKeys = [...Object.keys(config.artifacts ?? {})];
|
|
2057
|
+
return ancestors.map((ancestor) => {
|
|
2058
|
+
const nodeAspects = (ancestor.meta.aspects ?? []).map((a) => a.aspect);
|
|
2059
|
+
const expanded = expandAspects(nodeAspects, graph.aspects);
|
|
2060
|
+
const filterFilenames = structuralFilenames.length > 0 ? structuralFilenames : configArtifactKeys;
|
|
2061
|
+
const availableFiles = filterFilenames.filter(
|
|
2062
|
+
(f) => ancestor.artifacts.some((a) => a.filename === f)
|
|
2063
|
+
);
|
|
2064
|
+
return {
|
|
2065
|
+
path: ancestor.path,
|
|
2066
|
+
name: ancestor.meta.name,
|
|
2067
|
+
type: ancestor.meta.type,
|
|
2068
|
+
aspects: expanded,
|
|
2069
|
+
artifactFilenames: availableFiles
|
|
2070
|
+
};
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
function computeBudgetBreakdown(pkg2, graph) {
|
|
2074
|
+
let own = 0;
|
|
2075
|
+
let hierarchy = 0;
|
|
2076
|
+
let aspects = 0;
|
|
2077
|
+
let flows = 0;
|
|
2078
|
+
let relational = 0;
|
|
2079
|
+
for (const layer of pkg2.layers) {
|
|
2080
|
+
const tokens = estimateTokens(layer.content);
|
|
2081
|
+
switch (layer.type) {
|
|
2082
|
+
case "global":
|
|
2083
|
+
case "own":
|
|
2084
|
+
own += tokens;
|
|
2085
|
+
break;
|
|
2086
|
+
case "hierarchy":
|
|
2087
|
+
hierarchy += tokens;
|
|
2088
|
+
break;
|
|
2089
|
+
case "aspects":
|
|
2090
|
+
aspects += tokens;
|
|
2091
|
+
break;
|
|
2092
|
+
case "flows":
|
|
2093
|
+
flows += tokens;
|
|
2094
|
+
break;
|
|
2095
|
+
case "relational":
|
|
2096
|
+
relational += tokens;
|
|
2097
|
+
break;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
let depAncestorTokens = 0;
|
|
2101
|
+
const node = graph.nodes.get(pkg2.nodePath);
|
|
2102
|
+
if (node) {
|
|
2103
|
+
const ancestorPaths = new Set(collectAncestors(node).map((a) => a.path));
|
|
2104
|
+
for (const relation of node.meta.relations ?? []) {
|
|
2105
|
+
const target = graph.nodes.get(relation.target);
|
|
2106
|
+
if (!target || ancestorPaths.has(relation.target)) continue;
|
|
2107
|
+
const depAncestors = collectDependencyAncestors(target, graph.config, graph);
|
|
2108
|
+
for (const anc of depAncestors) {
|
|
2109
|
+
const ancNode = graph.nodes.get(anc.path);
|
|
2110
|
+
if (!ancNode) continue;
|
|
2111
|
+
for (const filename of anc.artifactFilenames) {
|
|
2112
|
+
const art = ancNode.artifacts.find((a) => a.filename === filename);
|
|
2113
|
+
if (art) {
|
|
2114
|
+
depAncestorTokens += estimateTokens(art.content);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
const dependencies = relational + depAncestorTokens;
|
|
2121
|
+
const total = own + hierarchy + aspects + flows + dependencies;
|
|
2122
|
+
return { own, hierarchy, aspects, flows, dependencies, total };
|
|
2123
|
+
}
|
|
2048
2124
|
function toContextMapOutput(pkg2, graph) {
|
|
2049
2125
|
const node = graph.nodes.get(pkg2.nodePath);
|
|
2050
2126
|
const config = graph.config;
|
|
@@ -2093,11 +2169,12 @@ function toContextMapOutput(pkg2, graph) {
|
|
|
2093
2169
|
depRefs.push(ref);
|
|
2094
2170
|
}
|
|
2095
2171
|
const registry = buildArtifactRegistry(node, ancestors, depRefs, graph);
|
|
2172
|
+
const breakdown = computeBudgetBreakdown(pkg2, graph);
|
|
2096
2173
|
const warningThreshold = config.quality?.context_budget?.warning ?? 1e4;
|
|
2097
2174
|
const errorThreshold = config.quality?.context_budget?.error ?? 2e4;
|
|
2098
|
-
const budgetStatus =
|
|
2175
|
+
const budgetStatus = breakdown.total >= errorThreshold ? "severe" : breakdown.total >= warningThreshold ? "warning" : "ok";
|
|
2099
2176
|
return {
|
|
2100
|
-
meta: { tokenCount:
|
|
2177
|
+
meta: { tokenCount: breakdown.total, budgetStatus, breakdown },
|
|
2101
2178
|
project: config.name,
|
|
2102
2179
|
node: {
|
|
2103
2180
|
path: pkg2.nodePath,
|
|
@@ -2962,26 +3039,42 @@ async function checkAnchorPresence(graph) {
|
|
|
2962
3039
|
}
|
|
2963
3040
|
async function checkContextBudget(graph) {
|
|
2964
3041
|
const issues = [];
|
|
2965
|
-
const
|
|
2966
|
-
const
|
|
2967
|
-
|
|
3042
|
+
const budget = graph.config.quality?.context_budget ?? { warning: 1e4, error: 2e4 };
|
|
3043
|
+
const warningThreshold = budget.warning;
|
|
3044
|
+
const errorThreshold = budget.error;
|
|
3045
|
+
const ownWarningThreshold = budget.own_warning;
|
|
3046
|
+
for (const [nodePath] of graph.nodes) {
|
|
3047
|
+
const node = graph.nodes.get(nodePath);
|
|
2968
3048
|
if (node.meta.blackbox) continue;
|
|
2969
3049
|
try {
|
|
2970
3050
|
const pkg2 = await buildContext(graph, nodePath);
|
|
2971
|
-
|
|
3051
|
+
const breakdown = computeBudgetBreakdown(pkg2, graph);
|
|
3052
|
+
const breakdownLine = `own: ${breakdown.own.toLocaleString()} (${pct(breakdown.own, breakdown.total)}) | hierarchy: ${breakdown.hierarchy.toLocaleString()} (${pct(breakdown.hierarchy, breakdown.total)}) | aspects: ${breakdown.aspects.toLocaleString()} (${pct(breakdown.aspects, breakdown.total)}) | flows: ${breakdown.flows.toLocaleString()} (${pct(breakdown.flows, breakdown.total)}) | dependencies: ${breakdown.dependencies.toLocaleString()} (${pct(breakdown.dependencies, breakdown.total)})`;
|
|
3053
|
+
if (breakdown.total >= errorThreshold) {
|
|
2972
3054
|
issues.push({
|
|
2973
3055
|
severity: "warning",
|
|
2974
3056
|
code: "W006",
|
|
2975
3057
|
rule: "budget-error",
|
|
2976
|
-
message: `Context is ${
|
|
3058
|
+
message: `Context is ${breakdown.total.toLocaleString()} tokens (error threshold: ${errorThreshold.toLocaleString()}).
|
|
3059
|
+
${breakdownLine}`,
|
|
2977
3060
|
nodePath
|
|
2978
3061
|
});
|
|
2979
|
-
} else if (
|
|
3062
|
+
} else if (breakdown.total >= warningThreshold) {
|
|
2980
3063
|
issues.push({
|
|
2981
3064
|
severity: "warning",
|
|
2982
3065
|
code: "W005",
|
|
2983
3066
|
rule: "budget-warning",
|
|
2984
|
-
message: `Context is ${
|
|
3067
|
+
message: `Context is ${breakdown.total.toLocaleString()} tokens (warning threshold: ${warningThreshold.toLocaleString()}).
|
|
3068
|
+
${breakdownLine}`,
|
|
3069
|
+
nodePath
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
if (ownWarningThreshold !== void 0 && breakdown.own >= ownWarningThreshold) {
|
|
3073
|
+
issues.push({
|
|
3074
|
+
severity: "warning",
|
|
3075
|
+
code: "W015",
|
|
3076
|
+
rule: "own-budget-warning",
|
|
3077
|
+
message: `Own artifacts: ${breakdown.own.toLocaleString()} tokens (threshold: ${ownWarningThreshold.toLocaleString()}). Consider splitting this node's responsibilities into child nodes.`,
|
|
2985
3078
|
nodePath
|
|
2986
3079
|
});
|
|
2987
3080
|
}
|
|
@@ -2990,6 +3083,10 @@ async function checkContextBudget(graph) {
|
|
|
2990
3083
|
}
|
|
2991
3084
|
return issues;
|
|
2992
3085
|
}
|
|
3086
|
+
function pct(value, total) {
|
|
3087
|
+
if (total === 0) return "0%";
|
|
3088
|
+
return `${Math.round(value / total * 100)}%`;
|
|
3089
|
+
}
|
|
2993
3090
|
|
|
2994
3091
|
// src/cli/build-context.ts
|
|
2995
3092
|
function collectRelevantNodePaths(graph, nodePath) {
|
|
@@ -4321,7 +4418,8 @@ async function runSimulation(graph, nodePaths, targetNodePath) {
|
|
|
4321
4418
|
for (const dep of nodePaths) {
|
|
4322
4419
|
try {
|
|
4323
4420
|
const pkg2 = await buildContext(graph, dep);
|
|
4324
|
-
const
|
|
4421
|
+
const breakdown = computeBudgetBreakdown(pkg2, graph);
|
|
4422
|
+
const status = breakdown.total >= budget.error ? "severe" : breakdown.total >= budget.warning ? "warning" : "ok";
|
|
4325
4423
|
let baselineTokens = null;
|
|
4326
4424
|
if (baselineGraph?.nodes.has(dep)) {
|
|
4327
4425
|
try {
|
|
@@ -4335,8 +4433,8 @@ async function runSimulation(graph, nodePaths, targetNodePath) {
|
|
|
4335
4433
|
);
|
|
4336
4434
|
const changedLine = hasDepOnTarget ? ` + Changed dependency interface: ${targetNodePath}
|
|
4337
4435
|
` : "";
|
|
4338
|
-
const budgetLine = baselineTokens !== null ? ` Budget: ${baselineTokens} -> ${
|
|
4339
|
-
` : ` Budget: ${
|
|
4436
|
+
const budgetLine = baselineTokens !== null ? ` Budget: ${baselineTokens} -> ${breakdown.total} tokens (${status})
|
|
4437
|
+
` : ` Budget: ${breakdown.total} tokens (${status})
|
|
4340
4438
|
`;
|
|
4341
4439
|
const driftEntry = driftByNode.get(dep);
|
|
4342
4440
|
const driftLine = driftEntry && driftEntry.status !== "ok" ? ` Mapped files (on-disk): ${driftEntry.status}${driftEntry.details ? ` (${driftEntry.details})` : ""}
|