@chrisdudek/yg 5.0.0-alpha.2 → 5.0.0-alpha.3
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 +190 -50
- package/dist/structure.d.ts +8 -0
- package/graph-schemas/yg-config.yaml +6 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -570,9 +570,25 @@ async function collectFiles(dir, projectRoot, stack) {
|
|
|
570
570
|
}
|
|
571
571
|
return results;
|
|
572
572
|
}
|
|
573
|
+
function excludeNestedGraphSubtrees(relPaths) {
|
|
574
|
+
const seg = `/${YGGDRASIL_DIRNAME}/`;
|
|
575
|
+
const nestedRoots = /* @__PURE__ */ new Set();
|
|
576
|
+
for (const p2 of relPaths) {
|
|
577
|
+
const idx = p2.indexOf(seg);
|
|
578
|
+
if (idx > 0) nestedRoots.add(p2.slice(0, idx));
|
|
579
|
+
}
|
|
580
|
+
if (nestedRoots.size === 0) return relPaths;
|
|
581
|
+
return relPaths.filter((p2) => {
|
|
582
|
+
for (const root of nestedRoots) {
|
|
583
|
+
if (p2 === root || p2.startsWith(root + "/")) return false;
|
|
584
|
+
}
|
|
585
|
+
return true;
|
|
586
|
+
});
|
|
587
|
+
}
|
|
573
588
|
async function walkRepoFiles(projectRoot) {
|
|
574
589
|
const stack = await loadRootGitignoreStack(projectRoot);
|
|
575
|
-
|
|
590
|
+
const files = await collectFiles(projectRoot, projectRoot, stack);
|
|
591
|
+
return excludeNestedGraphSubtrees(files);
|
|
576
592
|
}
|
|
577
593
|
var require2, ignoreFactory, YGGDRASIL_DIRNAME;
|
|
578
594
|
var init_repo_scanner = __esm({
|
|
@@ -5677,6 +5693,50 @@ var DEFAULT_QUALITY = {
|
|
|
5677
5693
|
max_direct_relations: 10,
|
|
5678
5694
|
max_node_chars: 4e4
|
|
5679
5695
|
};
|
|
5696
|
+
var DEFAULT_COVERAGE = { required: ["/"], excluded: [] };
|
|
5697
|
+
function parseStringArray(raw, field, filename) {
|
|
5698
|
+
if (raw === void 0) return [];
|
|
5699
|
+
if (!Array.isArray(raw) || raw.some((x) => typeof x !== "string")) {
|
|
5700
|
+
throw new ConfigParseError({
|
|
5701
|
+
what: `${filename}: ${field} must be a list of strings (got ${JSON.stringify(raw)}).`,
|
|
5702
|
+
why: "Coverage roots are repo-relative path prefixes; a non-list value cannot be matched against files.",
|
|
5703
|
+
next: `Set ${field} to a YAML list, e.g.
|
|
5704
|
+
${field.split(".").pop()}:
|
|
5705
|
+
- services/`
|
|
5706
|
+
}, "config-invalid");
|
|
5707
|
+
}
|
|
5708
|
+
return raw;
|
|
5709
|
+
}
|
|
5710
|
+
function parseCoverage(raw, filename) {
|
|
5711
|
+
if (raw === void 0) return DEFAULT_COVERAGE;
|
|
5712
|
+
if (typeof raw !== "object" || Array.isArray(raw) || raw === null) {
|
|
5713
|
+
throw new ConfigParseError({
|
|
5714
|
+
what: `${filename}: coverage must be a mapping`,
|
|
5715
|
+
why: "coverage holds the required/excluded root lists",
|
|
5716
|
+
next: 'replace with `coverage: { required: ["/"], excluded: [] }`'
|
|
5717
|
+
}, "config-invalid");
|
|
5718
|
+
}
|
|
5719
|
+
const cov = raw;
|
|
5720
|
+
const required = cov.required === void 0 ? ["/"] : parseStringArray(cov.required, "coverage.required", filename);
|
|
5721
|
+
const excluded = parseStringArray(cov.excluded, "coverage.excluded", filename);
|
|
5722
|
+
if (required.length === 0) {
|
|
5723
|
+
throw new ConfigParseError({
|
|
5724
|
+
what: `${filename}: coverage.required must list at least one root.`,
|
|
5725
|
+
why: "An empty required list silently turns every unmapped file into a non-blocking warning, disabling coverage enforcement.",
|
|
5726
|
+
next: "Omit the coverage block to require the whole repo, or list real roots (e.g. - services/)."
|
|
5727
|
+
}, "config-invalid");
|
|
5728
|
+
}
|
|
5729
|
+
for (const root of [...required, ...excluded]) {
|
|
5730
|
+
if (root.split("/").includes("..")) {
|
|
5731
|
+
throw new ConfigParseError({
|
|
5732
|
+
what: `${filename}: coverage root '${root}' contains a '..' segment.`,
|
|
5733
|
+
why: "'..' is not a valid repo-relative prefix and will never match any git-tracked path, silently mis-scoping coverage enforcement.",
|
|
5734
|
+
next: 'Use a repo-relative path prefix without any ".." segments (e.g. - services/ instead of - services/../other/).'
|
|
5735
|
+
}, "config-invalid");
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
return { required, excluded };
|
|
5739
|
+
}
|
|
5680
5740
|
function parseMaxNodeChars(raw, filename) {
|
|
5681
5741
|
if (raw === void 0) return DEFAULT_QUALITY.max_node_chars ?? 4e4;
|
|
5682
5742
|
if (typeof raw !== "number" || !Number.isInteger(raw) || raw <= 0) {
|
|
@@ -5754,7 +5814,8 @@ async function parseConfig(filePath) {
|
|
|
5754
5814
|
quality,
|
|
5755
5815
|
reviewer,
|
|
5756
5816
|
parallel,
|
|
5757
|
-
debug
|
|
5817
|
+
debug,
|
|
5818
|
+
coverage: parseCoverage(raw.coverage, filename)
|
|
5758
5819
|
};
|
|
5759
5820
|
}
|
|
5760
5821
|
function parseReviewer(raw, filename) {
|
|
@@ -11757,6 +11818,88 @@ var COMPLETENESS_CODES = /* @__PURE__ */ new Set(["description-missing"]);
|
|
|
11757
11818
|
// src/core/check.ts
|
|
11758
11819
|
init_log_format();
|
|
11759
11820
|
init_posix();
|
|
11821
|
+
init_repo_scanner();
|
|
11822
|
+
|
|
11823
|
+
// src/core/check-coverage-tiers.ts
|
|
11824
|
+
init_posix();
|
|
11825
|
+
function normalizeRoot(root) {
|
|
11826
|
+
return toPosixPath(root.trim()).replace(/^\/+/, "").replace(/\/+$/, "").replace(/\/{2,}/g, "/");
|
|
11827
|
+
}
|
|
11828
|
+
function matchesRoot(file, normRoot) {
|
|
11829
|
+
return normRoot === "" || file === normRoot || file.startsWith(normRoot + "/");
|
|
11830
|
+
}
|
|
11831
|
+
function partitionByCoverageTier(uncovered, coverage) {
|
|
11832
|
+
const req = coverage.required.map(normalizeRoot);
|
|
11833
|
+
const exc = coverage.excluded.map(normalizeRoot);
|
|
11834
|
+
const required = [];
|
|
11835
|
+
const middle = [];
|
|
11836
|
+
for (const f of uncovered) {
|
|
11837
|
+
let best = { len: -1, tier: "middle" };
|
|
11838
|
+
for (const r of req) if (matchesRoot(f, r) && r.length > best.len) best = { len: r.length, tier: "required" };
|
|
11839
|
+
for (const r of exc) if (matchesRoot(f, r) && r.length >= best.len) best = { len: r.length, tier: "excluded" };
|
|
11840
|
+
if (best.tier === "required") required.push(f);
|
|
11841
|
+
else if (best.tier === "middle") middle.push(f);
|
|
11842
|
+
}
|
|
11843
|
+
return { required, middle };
|
|
11844
|
+
}
|
|
11845
|
+
function buildCoverageIssue(uncoveredFiles, totalGitFiles) {
|
|
11846
|
+
if (uncoveredFiles.length === 0) return null;
|
|
11847
|
+
const sampleSize = 5;
|
|
11848
|
+
const sample = uncoveredFiles.slice(0, sampleSize);
|
|
11849
|
+
const remaining = uncoveredFiles.length - sample.length;
|
|
11850
|
+
const coveragePct = totalGitFiles > 0 ? (totalGitFiles - uncoveredFiles.length) / totalGitFiles * 100 : 100;
|
|
11851
|
+
let coverageMd;
|
|
11852
|
+
if (uncoveredFiles.length <= sampleSize) {
|
|
11853
|
+
coverageMd = {
|
|
11854
|
+
what: `${uncoveredFiles.length} source file${uncoveredFiles.length === 1 ? "" : "s"} not covered by any node.
|
|
11855
|
+
${sample.map((f) => " " + f).join("\n")}`,
|
|
11856
|
+
why: "Files without graph coverage cannot be modified under the protocol.",
|
|
11857
|
+
next: `Check ownership candidates: yg context --file <path>
|
|
11858
|
+
Then: add to existing node mapping, or create a new node.`
|
|
11859
|
+
};
|
|
11860
|
+
} else {
|
|
11861
|
+
const guidance = coveragePct < 50 ? "Establish coverage: create nodes for active areas first, expand coverage incrementally." : "Add to an existing node mapping, or create a new node.";
|
|
11862
|
+
coverageMd = {
|
|
11863
|
+
what: `${uncoveredFiles.length} source files have no graph coverage.
|
|
11864
|
+
Examples:
|
|
11865
|
+
${sample.map((f) => " " + f).join("\n")}
|
|
11866
|
+
... and ${remaining} more`,
|
|
11867
|
+
why: "Files without graph coverage cannot be modified under the protocol.",
|
|
11868
|
+
next: `${guidance}
|
|
11869
|
+
Check ownership candidates: yg context --file <path>`
|
|
11870
|
+
};
|
|
11871
|
+
}
|
|
11872
|
+
return {
|
|
11873
|
+
severity: "error",
|
|
11874
|
+
code: "unmapped-files",
|
|
11875
|
+
rule: "unmapped-file",
|
|
11876
|
+
messageData: coverageMd,
|
|
11877
|
+
uncoveredFiles,
|
|
11878
|
+
uncoveredCount: uncoveredFiles.length
|
|
11879
|
+
};
|
|
11880
|
+
}
|
|
11881
|
+
function buildCoverageAdvisoryIssue(uncoveredFiles) {
|
|
11882
|
+
if (uncoveredFiles.length === 0) return null;
|
|
11883
|
+
const sample = uncoveredFiles.slice(0, 5);
|
|
11884
|
+
const remaining = uncoveredFiles.length - sample.length;
|
|
11885
|
+
const body = uncoveredFiles.length <= 5 ? sample.map((f) => " " + f).join("\n") : `${sample.map((f) => " " + f).join("\n")}
|
|
11886
|
+
... and ${remaining} more`;
|
|
11887
|
+
return {
|
|
11888
|
+
severity: "warning",
|
|
11889
|
+
code: "uncovered-advisory",
|
|
11890
|
+
rule: "uncovered-advisory",
|
|
11891
|
+
messageData: {
|
|
11892
|
+
what: `${uncoveredFiles.length} tracked file${uncoveredFiles.length === 1 ? "" : "s"} outside any required coverage root.
|
|
11893
|
+
${body}`,
|
|
11894
|
+
why: "Not under a coverage.required root \u2014 visible but non-blocking. Bring an area under graph coverage to enforce it.",
|
|
11895
|
+
next: "Map these files to a node, or add their root to coverage.required to make this an error."
|
|
11896
|
+
},
|
|
11897
|
+
uncoveredFiles,
|
|
11898
|
+
uncoveredCount: uncoveredFiles.length
|
|
11899
|
+
};
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11902
|
+
// src/core/check.ts
|
|
11760
11903
|
async function classifyDrift(graph) {
|
|
11761
11904
|
const projectRoot = path34.dirname(graph.rootPath);
|
|
11762
11905
|
const issues = [];
|
|
@@ -12012,7 +12155,8 @@ function scanUncoveredFiles(graph, gitTrackedFiles) {
|
|
|
12012
12155
|
const projectRoot = path34.dirname(graph.rootPath);
|
|
12013
12156
|
const yggPrefix = toPosixPath(path34.relative(projectRoot, graph.rootPath));
|
|
12014
12157
|
const uncovered = [];
|
|
12015
|
-
|
|
12158
|
+
const tracked = excludeNestedGraphSubtrees(gitTrackedFiles);
|
|
12159
|
+
for (const file of tracked) {
|
|
12016
12160
|
const normalized = toPosixPath(file.trim());
|
|
12017
12161
|
if (normalized.startsWith(yggPrefix + "/") || normalized === yggPrefix) continue;
|
|
12018
12162
|
let covered = false;
|
|
@@ -12029,42 +12173,6 @@ function scanUncoveredFiles(graph, gitTrackedFiles) {
|
|
|
12029
12173
|
}
|
|
12030
12174
|
return uncovered.sort();
|
|
12031
12175
|
}
|
|
12032
|
-
function buildCoverageIssue(uncoveredFiles, totalGitFiles) {
|
|
12033
|
-
if (uncoveredFiles.length === 0) return null;
|
|
12034
|
-
const sampleSize = 5;
|
|
12035
|
-
const sample = uncoveredFiles.slice(0, sampleSize);
|
|
12036
|
-
const remaining = uncoveredFiles.length - sample.length;
|
|
12037
|
-
const coveragePct = totalGitFiles > 0 ? (totalGitFiles - uncoveredFiles.length) / totalGitFiles * 100 : 100;
|
|
12038
|
-
let coverageMd;
|
|
12039
|
-
if (uncoveredFiles.length <= sampleSize) {
|
|
12040
|
-
coverageMd = {
|
|
12041
|
-
what: `${uncoveredFiles.length} source file${uncoveredFiles.length === 1 ? "" : "s"} not covered by any node.
|
|
12042
|
-
${sample.map((f) => " " + f).join("\n")}`,
|
|
12043
|
-
why: "Files without graph coverage cannot be modified under the protocol.",
|
|
12044
|
-
next: `Check ownership candidates: yg context --file <path>
|
|
12045
|
-
Then: add to existing node mapping, or create a new node.`
|
|
12046
|
-
};
|
|
12047
|
-
} else {
|
|
12048
|
-
const guidance = coveragePct < 50 ? "Establish coverage: create nodes for active areas first, expand coverage incrementally." : "Add to an existing node mapping, or create a new node.";
|
|
12049
|
-
coverageMd = {
|
|
12050
|
-
what: `${uncoveredFiles.length} source files have no graph coverage.
|
|
12051
|
-
Examples:
|
|
12052
|
-
${sample.map((f) => " " + f).join("\n")}
|
|
12053
|
-
... and ${remaining} more`,
|
|
12054
|
-
why: "Files without graph coverage cannot be modified under the protocol.",
|
|
12055
|
-
next: `${guidance}
|
|
12056
|
-
Check ownership candidates: yg context --file <path>`
|
|
12057
|
-
};
|
|
12058
|
-
}
|
|
12059
|
-
return {
|
|
12060
|
-
severity: "error",
|
|
12061
|
-
code: "unmapped-files",
|
|
12062
|
-
rule: "unmapped-file",
|
|
12063
|
-
messageData: coverageMd,
|
|
12064
|
-
uncoveredFiles,
|
|
12065
|
-
uncoveredCount: uncoveredFiles.length
|
|
12066
|
-
};
|
|
12067
|
-
}
|
|
12068
12176
|
async function detectOrphanedDriftState(graph) {
|
|
12069
12177
|
const driftState = await readDriftState(graph.rootPath);
|
|
12070
12178
|
const validNodePaths = new Set(graph.nodes.keys());
|
|
@@ -12074,20 +12182,25 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
12074
12182
|
const validation = await validate(graph);
|
|
12075
12183
|
const validationIssues = validation.issues.filter((vi) => vi.code).map((vi) => ({ ...vi, code: vi.code }));
|
|
12076
12184
|
const driftIssues = await classifyDrift(graph);
|
|
12077
|
-
let
|
|
12185
|
+
let coverageIssues = [];
|
|
12078
12186
|
let coveredFiles = 0;
|
|
12079
12187
|
let totalFiles = 0;
|
|
12080
12188
|
if (gitTrackedFiles !== null) {
|
|
12081
12189
|
const projectRoot = path34.dirname(graph.rootPath);
|
|
12082
12190
|
const yggPrefix = toPosixPath(path34.relative(projectRoot, graph.rootPath));
|
|
12083
|
-
const sourceFiles = gitTrackedFiles.filter((f) => {
|
|
12191
|
+
const sourceFiles = excludeNestedGraphSubtrees(gitTrackedFiles).filter((f) => {
|
|
12084
12192
|
const normalized = toPosixPath(f.trim());
|
|
12085
12193
|
return !normalized.startsWith(yggPrefix + "/") && normalized !== yggPrefix;
|
|
12086
12194
|
});
|
|
12087
12195
|
totalFiles = sourceFiles.length;
|
|
12088
12196
|
const uncovered = scanUncoveredFiles(graph, gitTrackedFiles);
|
|
12089
|
-
|
|
12090
|
-
|
|
12197
|
+
const coverage = graph.config.coverage ?? DEFAULT_COVERAGE;
|
|
12198
|
+
const tiers = partitionByCoverageTier(uncovered, coverage);
|
|
12199
|
+
coveredFiles = totalFiles - (tiers.required.length + tiers.middle.length);
|
|
12200
|
+
coverageIssues = [
|
|
12201
|
+
buildCoverageIssue(tiers.required, totalFiles),
|
|
12202
|
+
buildCoverageAdvisoryIssue(tiers.middle)
|
|
12203
|
+
].filter((x) => x !== null);
|
|
12091
12204
|
}
|
|
12092
12205
|
const orphanedPaths = await detectOrphanedDriftState(graph);
|
|
12093
12206
|
await garbageCollectDriftState(
|
|
@@ -12116,7 +12229,7 @@ async function runCheck(graph, gitTrackedFiles) {
|
|
|
12116
12229
|
const allIssues = [
|
|
12117
12230
|
...driftIssues,
|
|
12118
12231
|
...validationIssues,
|
|
12119
|
-
...
|
|
12232
|
+
...coverageIssues,
|
|
12120
12233
|
...orphanWarnings
|
|
12121
12234
|
];
|
|
12122
12235
|
const nodeTypeCounts = /* @__PURE__ */ new Map();
|
|
@@ -14017,10 +14130,16 @@ function renderErrorSection(errors) {
|
|
|
14017
14130
|
}
|
|
14018
14131
|
function renderWarningSection(warnings) {
|
|
14019
14132
|
const lines = [chalk8.yellow(`Warnings (${warnings.length}):`)];
|
|
14020
|
-
|
|
14133
|
+
const coverage = warnings.filter((i) => i.code === "uncovered-advisory");
|
|
14134
|
+
const rest = warnings.filter((i) => i.code !== "uncovered-advisory");
|
|
14135
|
+
for (const issue of sortByNodePath(rest)) {
|
|
14021
14136
|
lines.push("");
|
|
14022
14137
|
renderIssueBlock(issue, lines, "warning");
|
|
14023
14138
|
}
|
|
14139
|
+
for (const issue of coverage) {
|
|
14140
|
+
lines.push("");
|
|
14141
|
+
renderUnmappedBlock(issue, lines, "uncovered");
|
|
14142
|
+
}
|
|
14024
14143
|
return lines.join("\n");
|
|
14025
14144
|
}
|
|
14026
14145
|
function renderIssueBlock(issue, lines, mode) {
|
|
@@ -14038,14 +14157,12 @@ function renderIssueBlock(issue, lines, mode) {
|
|
|
14038
14157
|
lines.push(` Fix: ${md.next}${fixSuffix}`);
|
|
14039
14158
|
}
|
|
14040
14159
|
}
|
|
14041
|
-
function renderUnmappedBlock(issue, lines) {
|
|
14160
|
+
function renderUnmappedBlock(issue, lines, label = "unmapped") {
|
|
14042
14161
|
const md = issue.messageData;
|
|
14043
14162
|
const files = issue.uncoveredFiles ?? [];
|
|
14044
|
-
const whatFirstLine = md.what.split("\n")[0];
|
|
14045
|
-
const countMatch = whatFirstLine.match(/^(\d[\d,]*)/);
|
|
14046
14163
|
const count = issue.uncoveredCount ?? files.length;
|
|
14047
|
-
const countLabel =
|
|
14048
|
-
lines.push(`
|
|
14164
|
+
const countLabel = String(count);
|
|
14165
|
+
lines.push(` ${label} (${countLabel})`);
|
|
14049
14166
|
const shown = files.slice(0, 10);
|
|
14050
14167
|
for (const f of shown) {
|
|
14051
14168
|
lines.push(` ${f}`);
|
|
@@ -17044,6 +17161,11 @@ reviewer:
|
|
|
17044
17161
|
# max_bytes_per_file: 65536 # default: 64 KiB per reference file
|
|
17045
17162
|
# max_total_bytes_per_aspect: 262144 # default: 256 KiB total per aspect
|
|
17046
17163
|
|
|
17164
|
+
coverage: # Optional \u2014 controls which files must be mapped
|
|
17165
|
+
required: # Unmapped files under these roots are a blocking error
|
|
17166
|
+
- "/" # Default: whole repo (previous always-map-everything behavior)
|
|
17167
|
+
excluded: [] # Files under these roots are silently ignored
|
|
17168
|
+
|
|
17047
17169
|
quality:
|
|
17048
17170
|
max_direct_relations: 10 # Max out-edges per node before high-fan-out warning
|
|
17049
17171
|
max_node_chars: 40000 # Per-node character budget (source + aspect refs) before oversized-node error
|
|
@@ -17179,6 +17301,24 @@ the provider's standard \`*_API_KEY\`) as a fallback when not present in
|
|
|
17179
17301
|
\`yg-config.yaml\` itself must never contain credentials. Commit it to the
|
|
17180
17302
|
repository \u2014 it is safe to share.
|
|
17181
17303
|
|
|
17304
|
+
## Coverage config
|
|
17305
|
+
|
|
17306
|
+
\`\`\`yaml
|
|
17307
|
+
coverage:
|
|
17308
|
+
required:
|
|
17309
|
+
- src/ # unmapped files under src/ are a blocking error
|
|
17310
|
+
excluded:
|
|
17311
|
+
- vendor/ # silently ignored
|
|
17312
|
+
\`\`\`
|
|
17313
|
+
|
|
17314
|
+
Controls which git-tracked files must be mapped to a node.
|
|
17315
|
+
|
|
17316
|
+
- \`required\` \u2014 path roots where unmapped files are a blocking \`unmapped-files\` error. Default: \`["/"]\` (whole repo \u2014 the previous always-map-everything behavior).
|
|
17317
|
+
- \`excluded\` \u2014 path roots that are silently ignored. Default: \`[]\`.
|
|
17318
|
+
- Files that match neither a required nor an excluded root produce a non-blocking \`uncovered-advisory\` warning.
|
|
17319
|
+
- Subtrees containing their own nested \`.yggdrasil/\` are auto-skipped by all repo-walking checks (they are governed by their own graph).
|
|
17320
|
+
- Longest-match wins; on a tie between required and excluded, excluded wins.
|
|
17321
|
+
|
|
17182
17322
|
## Quality thresholds
|
|
17183
17323
|
|
|
17184
17324
|
\`\`\`yaml
|
package/dist/structure.d.ts
CHANGED
|
@@ -158,6 +158,12 @@ interface ReviewerConfig {
|
|
|
158
158
|
/** At least one entry required; key is the tier name */
|
|
159
159
|
tiers: Record<string, LlmConfig>;
|
|
160
160
|
}
|
|
161
|
+
interface CoverageConfig {
|
|
162
|
+
/** Roots (POSIX, from repo root; "/" = whole repo) where an uncovered file is an error. */
|
|
163
|
+
required: string[];
|
|
164
|
+
/** Roots where an uncovered file is silent (no issue). */
|
|
165
|
+
excluded: string[];
|
|
166
|
+
}
|
|
161
167
|
interface YggConfig {
|
|
162
168
|
version?: string;
|
|
163
169
|
quality?: QualityConfig;
|
|
@@ -167,6 +173,8 @@ interface YggConfig {
|
|
|
167
173
|
reviewer?: ReviewerConfig;
|
|
168
174
|
parallel?: number;
|
|
169
175
|
debug?: boolean;
|
|
176
|
+
/** Coverage scope. Absent ⇒ DEFAULT_COVERAGE (whole repo required = today's behavior). */
|
|
177
|
+
coverage?: CoverageConfig;
|
|
170
178
|
}
|
|
171
179
|
interface ArchitectureNodeType {
|
|
172
180
|
description: string;
|
|
@@ -14,6 +14,12 @@ parallel: 1 # optional — concurrency limit for batch app
|
|
|
14
14
|
debug: false # optional — when true, appends all command output to .yggdrasil/.debug.log
|
|
15
15
|
# Default: false (off). Log is append-only; rotate or delete manually.
|
|
16
16
|
|
|
17
|
+
coverage: # optional — scopes the unmapped-files gate. Absent = whole repo required (today's behavior).
|
|
18
|
+
required: ["/"] # roots where an uncovered tracked file is an ERROR (blocks). "/" = whole repo.
|
|
19
|
+
excluded: [] # roots where an uncovered file is SILENT (no warning).
|
|
20
|
+
# Files outside required and excluded are a non-blocking WARNING.
|
|
21
|
+
# Subtrees containing their own nested .yggdrasil/ are auto-skipped by every check.
|
|
22
|
+
|
|
17
23
|
reviewer: # required — aspect verification during yg approve
|
|
18
24
|
default: standard # required when more than one tier is configured; optional with exactly one tier.
|
|
19
25
|
# Must reference one of the keys under reviewer.tiers.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrisdudek/yg",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.3",
|
|
4
4
|
"description": "Architecture rules your coding agent can't ignore. Written in Markdown, verified on every change, enforced in the agent's loop — not after on a PR. Works with Claude Code, Cursor, Copilot, Codex, Cline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|