@chrisdudek/yg 5.0.0-alpha.1 → 5.0.0-alpha.2
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 +802 -469
- package/dist/grammars/tree-sitter-c.node-types.json +4615 -0
- package/dist/grammars/tree-sitter-c_sharp.node-types.json +7233 -0
- package/dist/grammars/tree-sitter-cpp.node-types.json +7997 -0
- package/dist/grammars/tree-sitter-go.node-types.json +2995 -0
- package/dist/grammars/tree-sitter-java.node-types.json +4586 -0
- package/dist/grammars/tree-sitter-javascript.node-types.json +3622 -0
- package/dist/grammars/tree-sitter-json.node-types.json +183 -0
- package/dist/grammars/tree-sitter-kotlin.node-types.json +3091 -0
- package/dist/grammars/tree-sitter-php_only.node-types.json +6077 -0
- package/dist/grammars/tree-sitter-python.node-types.json +3746 -0
- package/dist/grammars/tree-sitter-ruby.node-types.json +4108 -0
- package/dist/grammars/tree-sitter-rust.node-types.json +5554 -0
- package/dist/grammars/tree-sitter-toml.node-types.json +356 -0
- package/dist/grammars/tree-sitter-tsx.node-types.json +6288 -0
- package/dist/grammars/tree-sitter-typescript.node-types.json +6038 -0
- package/dist/grammars/tree-sitter-yaml.node-types.json +552 -0
- package/dist/structure.d.ts +11 -4
- package/dist/structure.js +219 -89
- package/graph-schemas/yg-aspect.yaml +27 -8
- package/package.json +4 -3
package/dist/structure.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/structure/runner.ts
|
|
2
2
|
import * as fs4 from "fs";
|
|
3
|
-
import * as
|
|
3
|
+
import * as path8 from "path";
|
|
4
4
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
5
5
|
|
|
6
6
|
// src/ast/loader-hook.ts
|
|
@@ -36,12 +36,15 @@ function normalizeMappingPath(p) {
|
|
|
36
36
|
function toPosix(p) {
|
|
37
37
|
return p.replace(/\\/g, "/");
|
|
38
38
|
}
|
|
39
|
+
function toPosixPath(p) {
|
|
40
|
+
return p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41
|
+
}
|
|
39
42
|
|
|
40
43
|
// src/structure/ctx-fs.ts
|
|
41
44
|
var UndeclaredFsReadError = class extends Error {
|
|
42
|
-
constructor(
|
|
43
|
-
super(`structure-aspect-undeclared-fs-read: ${
|
|
44
|
-
this.path =
|
|
45
|
+
constructor(path9) {
|
|
46
|
+
super(`structure-aspect-undeclared-fs-read: ${path9}`);
|
|
47
|
+
this.path = path9;
|
|
45
48
|
this.name = "UndeclaredFsReadError";
|
|
46
49
|
}
|
|
47
50
|
};
|
|
@@ -101,8 +104,8 @@ function createCtxFs(params) {
|
|
|
101
104
|
const p = assertAllowed(raw);
|
|
102
105
|
const abs = path2.resolve(projectRoot, p);
|
|
103
106
|
try {
|
|
104
|
-
const
|
|
105
|
-
return
|
|
107
|
+
const stat2 = fs.statSync(abs);
|
|
108
|
+
return stat2.isDirectory() ? "dir" : stat2.isFile() ? "file" : false;
|
|
106
109
|
} catch {
|
|
107
110
|
return false;
|
|
108
111
|
}
|
|
@@ -127,6 +130,21 @@ function createCtxFs(params) {
|
|
|
127
130
|
// src/structure/ctx-graph.ts
|
|
128
131
|
import * as fs2 from "fs";
|
|
129
132
|
import * as path3 from "path";
|
|
133
|
+
|
|
134
|
+
// src/structure/expand-mapping-sync.ts
|
|
135
|
+
function isPathInMapping(candidate, mapping) {
|
|
136
|
+
const c = normalizeMappingPath(candidate);
|
|
137
|
+
if (c === "") return false;
|
|
138
|
+
for (const raw of mapping) {
|
|
139
|
+
const n = normalizeMappingPath(raw);
|
|
140
|
+
if (n === "") continue;
|
|
141
|
+
if (c === n) return true;
|
|
142
|
+
if (c.startsWith(n + "/")) return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/structure/ctx-graph.ts
|
|
130
148
|
var UndeclaredGraphReadError = class extends Error {
|
|
131
149
|
constructor(nodePath) {
|
|
132
150
|
super(`structure-aspect-undeclared-graph-read: ${nodePath}`);
|
|
@@ -175,8 +193,8 @@ function createCtxGraph(params) {
|
|
|
175
193
|
if (!p) continue;
|
|
176
194
|
const abs = path3.resolve(projectRoot, p);
|
|
177
195
|
try {
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
196
|
+
const stat2 = fs2.statSync(abs);
|
|
197
|
+
if (stat2.isFile()) {
|
|
180
198
|
const content = fs2.readFileSync(abs, "utf8");
|
|
181
199
|
files.push({ path: p, content });
|
|
182
200
|
touchedFiles.push(p);
|
|
@@ -665,6 +683,144 @@ function collectAllowedReadsForAspect(nodePath, graph) {
|
|
|
665
683
|
return allowed;
|
|
666
684
|
}
|
|
667
685
|
|
|
686
|
+
// src/io/hash.ts
|
|
687
|
+
import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
|
|
688
|
+
import path7 from "path";
|
|
689
|
+
import { createHash } from "crypto";
|
|
690
|
+
import { createRequire as createRequire3 } from "module";
|
|
691
|
+
|
|
692
|
+
// src/io/repo-scanner.ts
|
|
693
|
+
import { readFile, readdir } from "fs/promises";
|
|
694
|
+
import { join, relative as relative2, sep } from "path";
|
|
695
|
+
import { createRequire as createRequire2 } from "module";
|
|
696
|
+
|
|
697
|
+
// src/utils/debug-log.ts
|
|
698
|
+
import path6 from "path";
|
|
699
|
+
|
|
700
|
+
// src/io/repo-scanner.ts
|
|
701
|
+
var require2 = createRequire2(import.meta.url);
|
|
702
|
+
var ignoreFactory = require2("ignore");
|
|
703
|
+
|
|
704
|
+
// src/io/hash.ts
|
|
705
|
+
var require3 = createRequire3(import.meta.url);
|
|
706
|
+
var ignoreFactory2 = require3("ignore");
|
|
707
|
+
async function loadRootGitignoreStack2(projectRoot) {
|
|
708
|
+
if (!projectRoot) return [];
|
|
709
|
+
try {
|
|
710
|
+
const content = await readFile2(path7.join(projectRoot, ".gitignore"), "utf-8");
|
|
711
|
+
const matcher = ignoreFactory2();
|
|
712
|
+
matcher.add(content);
|
|
713
|
+
return [{ basePath: projectRoot, matcher }];
|
|
714
|
+
} catch {
|
|
715
|
+
return [];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function isIgnoredByStack2(candidatePath, stack) {
|
|
719
|
+
for (const { basePath, matcher } of stack) {
|
|
720
|
+
const relativePath = toPosix(path7.relative(basePath, candidatePath));
|
|
721
|
+
if (relativePath === "" || relativePath.startsWith("..")) continue;
|
|
722
|
+
if (matcher.ignores(relativePath) || matcher.ignores(relativePath + "/")) return true;
|
|
723
|
+
}
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
function hashString(content) {
|
|
727
|
+
return createHash("sha256").update(content).digest("hex");
|
|
728
|
+
}
|
|
729
|
+
var EMPTY_IDENTITY = { ownSubset: hashString(""), ports: {}, aspects: {} };
|
|
730
|
+
async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
|
|
731
|
+
let stack = options.gitignoreStack ?? [];
|
|
732
|
+
try {
|
|
733
|
+
const localContent = await readFile2(path7.join(directoryPath, ".gitignore"), "utf-8");
|
|
734
|
+
const localMatcher = ignoreFactory2();
|
|
735
|
+
localMatcher.add(localContent);
|
|
736
|
+
stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
const entries = await readdir2(directoryPath, { withFileTypes: true });
|
|
740
|
+
const dirs = [];
|
|
741
|
+
const files = [];
|
|
742
|
+
for (const entry of entries) {
|
|
743
|
+
const absoluteChildPath = path7.join(directoryPath, entry.name);
|
|
744
|
+
if (isIgnoredByStack2(absoluteChildPath, stack)) continue;
|
|
745
|
+
if (entry.isDirectory()) dirs.push(absoluteChildPath);
|
|
746
|
+
else if (entry.isFile()) files.push(absoluteChildPath);
|
|
747
|
+
}
|
|
748
|
+
const [dirResults, fileStats] = await Promise.all([
|
|
749
|
+
Promise.all(dirs.map((d) => collectDirectoryFilePaths(d, rootDirectoryPath, {
|
|
750
|
+
projectRoot: options.projectRoot,
|
|
751
|
+
gitignoreStack: stack
|
|
752
|
+
}))),
|
|
753
|
+
Promise.all(files.map(async (f) => {
|
|
754
|
+
const fileStat = await stat(f);
|
|
755
|
+
return {
|
|
756
|
+
relPath: toPosixPath(path7.relative(rootDirectoryPath, f)),
|
|
757
|
+
absPath: f,
|
|
758
|
+
mtimeMs: fileStat.mtimeMs
|
|
759
|
+
};
|
|
760
|
+
}))
|
|
761
|
+
]);
|
|
762
|
+
const result = [];
|
|
763
|
+
for (const nested of dirResults) result.push(...nested);
|
|
764
|
+
result.push(...fileStats);
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
767
|
+
async function expandMappingPaths(projectRoot, mappingPaths) {
|
|
768
|
+
const gitignoreStack = await loadRootGitignoreStack2(projectRoot);
|
|
769
|
+
const result = [];
|
|
770
|
+
for (const mp of mappingPaths) {
|
|
771
|
+
const absPath = path7.join(projectRoot, mp);
|
|
772
|
+
try {
|
|
773
|
+
const st = await stat(absPath);
|
|
774
|
+
if (st.isDirectory()) {
|
|
775
|
+
const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
|
|
776
|
+
projectRoot,
|
|
777
|
+
gitignoreStack
|
|
778
|
+
});
|
|
779
|
+
for (const entry of dirEntries) {
|
|
780
|
+
result.push(toPosixPath(path7.join(mp, entry.relPath)));
|
|
781
|
+
}
|
|
782
|
+
} else {
|
|
783
|
+
result.push(toPosixPath(mp));
|
|
784
|
+
}
|
|
785
|
+
} catch {
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return result;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/ast/suppress.ts
|
|
793
|
+
import { extname as extname3 } from "path";
|
|
794
|
+
|
|
795
|
+
// src/ast/find-comments.ts
|
|
796
|
+
import { extname as extname2 } from "path";
|
|
797
|
+
function findComments(target) {
|
|
798
|
+
const hasAst = "ast" in target;
|
|
799
|
+
const hasRootNode = "rootNode" in target;
|
|
800
|
+
if (hasAst && hasRootNode) {
|
|
801
|
+
throw new Error("AST_FINDCOMMENTS_AMBIGUOUS_TARGET: pass either ast or rootNode, not both");
|
|
802
|
+
}
|
|
803
|
+
let language = "language" in target ? target.language : void 0;
|
|
804
|
+
if (language === void 0 && "path" in target) {
|
|
805
|
+
language = getLanguageForExtension(extname2(target.path)) ?? void 0;
|
|
806
|
+
}
|
|
807
|
+
if (language === void 0) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
"AST_FINDCOMMENTS_NO_LANGUAGE: pass a SourceFile whose path has a known extension, or an explicit { language }"
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
const def = LANGUAGES[language];
|
|
813
|
+
if (def === void 0) {
|
|
814
|
+
throw new Error(`AST_FINDCOMMENTS_UNKNOWN_LANGUAGE: '${language}' not in registry`);
|
|
815
|
+
}
|
|
816
|
+
const root = hasAst ? target.ast.rootNode : target.rootNode;
|
|
817
|
+
const out = [];
|
|
818
|
+
for (const type of def.commentTypes) {
|
|
819
|
+
out.push(...root.descendantsOfType(type));
|
|
820
|
+
}
|
|
821
|
+
return out;
|
|
822
|
+
}
|
|
823
|
+
|
|
668
824
|
// src/ast/suppress.ts
|
|
669
825
|
var SuppressMarkerError = class extends Error {
|
|
670
826
|
constructor(message, file, line) {
|
|
@@ -711,7 +867,10 @@ function parseMarker(commentText, line, file) {
|
|
|
711
867
|
return null;
|
|
712
868
|
}
|
|
713
869
|
function collectSuppressions(tree, file, totalLines) {
|
|
714
|
-
|
|
870
|
+
if (getLanguageForExtension(extname3(file)) === null) {
|
|
871
|
+
return [];
|
|
872
|
+
}
|
|
873
|
+
const comments = findComments({ path: file, ast: tree });
|
|
715
874
|
const markers = [];
|
|
716
875
|
for (const c of comments) {
|
|
717
876
|
const m = parseMarker(c.text, c.startPosition.row + 1, file);
|
|
@@ -827,64 +986,64 @@ ${data.next}`);
|
|
|
827
986
|
}
|
|
828
987
|
messageData;
|
|
829
988
|
};
|
|
830
|
-
|
|
831
|
-
|
|
989
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
990
|
+
".gif",
|
|
991
|
+
".png",
|
|
992
|
+
".jpg",
|
|
993
|
+
".jpeg",
|
|
994
|
+
".webp",
|
|
995
|
+
".bmp",
|
|
996
|
+
".ico",
|
|
997
|
+
".svgz",
|
|
998
|
+
".woff",
|
|
999
|
+
".woff2",
|
|
1000
|
+
".ttf",
|
|
1001
|
+
".otf",
|
|
1002
|
+
".eot",
|
|
1003
|
+
".zip",
|
|
1004
|
+
".gz",
|
|
1005
|
+
".tgz",
|
|
1006
|
+
".tar",
|
|
1007
|
+
".bz2",
|
|
1008
|
+
".7z",
|
|
1009
|
+
".pdf",
|
|
1010
|
+
".mp4",
|
|
1011
|
+
".mov",
|
|
1012
|
+
".webm",
|
|
1013
|
+
".mp3",
|
|
1014
|
+
".wav",
|
|
1015
|
+
".wasm",
|
|
1016
|
+
".bin"
|
|
1017
|
+
]);
|
|
1018
|
+
async function buildOwnFiles(node, projectRoot, touchedFiles) {
|
|
1019
|
+
const childMappingEntries = [];
|
|
832
1020
|
for (const child of node.children) {
|
|
833
1021
|
for (const raw of child.meta.mapping ?? []) {
|
|
834
1022
|
const p = normalizeMappingPath(raw);
|
|
835
|
-
if (p)
|
|
1023
|
+
if (p) childMappingEntries.push(p);
|
|
836
1024
|
}
|
|
837
1025
|
}
|
|
1026
|
+
const rawMapping = (node.meta.mapping ?? []).map(normalizeMappingPath).filter((p) => p !== "");
|
|
1027
|
+
const expanded = await expandMappingPaths(projectRoot, rawMapping);
|
|
838
1028
|
const result = [];
|
|
839
|
-
for (const
|
|
840
|
-
|
|
841
|
-
if (
|
|
842
|
-
const abs =
|
|
1029
|
+
for (const p of expanded) {
|
|
1030
|
+
if (childMappingEntries.length > 0 && isPathInMapping(p, childMappingEntries)) continue;
|
|
1031
|
+
if (BINARY_EXTENSIONS.has(path8.extname(p).toLowerCase())) continue;
|
|
1032
|
+
const abs = path8.resolve(projectRoot, p);
|
|
1033
|
+
let content;
|
|
843
1034
|
try {
|
|
844
|
-
|
|
845
|
-
if (stat.isFile()) {
|
|
846
|
-
const content = fs4.readFileSync(abs, "utf8");
|
|
847
|
-
result.push({ path: p, content });
|
|
848
|
-
touchedFiles.push(p);
|
|
849
|
-
}
|
|
1035
|
+
content = fs4.readFileSync(abs, "utf8");
|
|
850
1036
|
} catch {
|
|
1037
|
+
continue;
|
|
851
1038
|
}
|
|
1039
|
+
result.push({ path: p, content });
|
|
1040
|
+
touchedFiles.push(p);
|
|
852
1041
|
}
|
|
853
1042
|
return result;
|
|
854
1043
|
}
|
|
855
|
-
function
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
const rel = normalizeMappingPath(raw);
|
|
859
|
-
if (!rel) continue;
|
|
860
|
-
const abs = path6.resolve(projectRoot, rel);
|
|
861
|
-
try {
|
|
862
|
-
const stat = fs4.statSync(abs);
|
|
863
|
-
if (stat.isFile()) {
|
|
864
|
-
out.push(rel);
|
|
865
|
-
} else if (stat.isDirectory()) {
|
|
866
|
-
for (const sub of walkDirSync(abs)) {
|
|
867
|
-
const relSub = path6.relative(projectRoot, sub).split(/[\\/]/).join("/");
|
|
868
|
-
out.push(relSub);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
} catch {
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
return out;
|
|
875
|
-
}
|
|
876
|
-
function* walkDirSync(dir) {
|
|
877
|
-
let entries;
|
|
878
|
-
try {
|
|
879
|
-
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
880
|
-
} catch {
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
for (const e of entries) {
|
|
884
|
-
const child = path6.join(dir, e.name);
|
|
885
|
-
if (e.isDirectory()) yield* walkDirSync(child);
|
|
886
|
-
else if (e.isFile()) yield child;
|
|
887
|
-
}
|
|
1044
|
+
async function enumerateMappedFilesAsync(mappingPaths, projectRoot) {
|
|
1045
|
+
const normalized = mappingPaths.map(normalizeMappingPath).filter((p) => p !== "");
|
|
1046
|
+
return expandMappingPaths(projectRoot, normalized);
|
|
888
1047
|
}
|
|
889
1048
|
async function runStructureAspect(params) {
|
|
890
1049
|
ensureLoaderRegistered();
|
|
@@ -899,8 +1058,8 @@ async function runStructureAspect(params) {
|
|
|
899
1058
|
next: `Pass an existing node path, or add the node to the graph.`
|
|
900
1059
|
});
|
|
901
1060
|
}
|
|
902
|
-
const aspectDirAbs =
|
|
903
|
-
const checkPath =
|
|
1061
|
+
const aspectDirAbs = path8.isAbsolute(aspectDir) ? aspectDir : path8.resolve(projectRoot, aspectDir);
|
|
1062
|
+
const checkPath = path8.join(aspectDirAbs, "check.mjs");
|
|
904
1063
|
let mod;
|
|
905
1064
|
try {
|
|
906
1065
|
mod = await import(pathToFileURL2(checkPath).href);
|
|
@@ -923,7 +1082,7 @@ async function runStructureAspect(params) {
|
|
|
923
1082
|
const ctxFs = createCtxFs({ allowedSet, projectRoot, touchedFiles });
|
|
924
1083
|
const ctxGraph = createCtxGraph({ currentNodePath: nodePath, graph, projectRoot, touchedFiles });
|
|
925
1084
|
const parsers = createCtxParsers({ allowedSet, projectRoot, touchedFiles, astCache });
|
|
926
|
-
const ownFiles = buildOwnFiles(node, projectRoot, touchedFiles);
|
|
1085
|
+
const ownFiles = await buildOwnFiles(node, projectRoot, touchedFiles);
|
|
927
1086
|
await prewarmupAstCache({ astCache, projectRoot, files: ownFiles });
|
|
928
1087
|
const ownFilesEnriched = enrichFilesWithAst(ownFiles, astCache);
|
|
929
1088
|
const ctx = {
|
|
@@ -946,8 +1105,8 @@ async function runStructureAspect(params) {
|
|
|
946
1105
|
for (const rel of node.meta.relations ?? []) {
|
|
947
1106
|
const target = graph.nodes.get(rel.target);
|
|
948
1107
|
if (!target) continue;
|
|
949
|
-
for (const p of
|
|
950
|
-
const abs =
|
|
1108
|
+
for (const p of await enumerateMappedFilesAsync(target.meta.mapping ?? [], projectRoot)) {
|
|
1109
|
+
const abs = path8.resolve(projectRoot, p);
|
|
951
1110
|
try {
|
|
952
1111
|
const content = fs4.readFileSync(abs, "utf8");
|
|
953
1112
|
astInputSet.push({ path: p, content });
|
|
@@ -1090,35 +1249,6 @@ function inFile(file, pattern) {
|
|
|
1090
1249
|
if ("contains" in pattern) return file.path.includes(pattern.contains);
|
|
1091
1250
|
return false;
|
|
1092
1251
|
}
|
|
1093
|
-
|
|
1094
|
-
// src/ast/find-comments.ts
|
|
1095
|
-
import { extname as extname2 } from "path";
|
|
1096
|
-
function findComments(target) {
|
|
1097
|
-
const hasAst = "ast" in target;
|
|
1098
|
-
const hasRootNode = "rootNode" in target;
|
|
1099
|
-
if (hasAst && hasRootNode) {
|
|
1100
|
-
throw new Error("AST_FINDCOMMENTS_AMBIGUOUS_TARGET: pass either ast or rootNode, not both");
|
|
1101
|
-
}
|
|
1102
|
-
let language = "language" in target ? target.language : void 0;
|
|
1103
|
-
if (language === void 0 && "path" in target) {
|
|
1104
|
-
language = getLanguageForExtension(extname2(target.path)) ?? void 0;
|
|
1105
|
-
}
|
|
1106
|
-
if (language === void 0) {
|
|
1107
|
-
throw new Error(
|
|
1108
|
-
"AST_FINDCOMMENTS_NO_LANGUAGE: pass a SourceFile whose path has a known extension, or an explicit { language }"
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
const def = LANGUAGES[language];
|
|
1112
|
-
if (def === void 0) {
|
|
1113
|
-
throw new Error(`AST_FINDCOMMENTS_UNKNOWN_LANGUAGE: '${language}' not in registry`);
|
|
1114
|
-
}
|
|
1115
|
-
const root = hasAst ? target.ast.rootNode : target.rootNode;
|
|
1116
|
-
const out = [];
|
|
1117
|
-
for (const type of def.commentTypes) {
|
|
1118
|
-
out.push(...root.descendantsOfType(type));
|
|
1119
|
-
}
|
|
1120
|
-
return out;
|
|
1121
|
-
}
|
|
1122
1252
|
export {
|
|
1123
1253
|
StructureRunnerError,
|
|
1124
1254
|
closest,
|
|
@@ -15,16 +15,35 @@ name: CrossCuttingRequirementName # required — display name
|
|
|
15
15
|
description: "Short description" # required — shown in yg aspects output and context packages.
|
|
16
16
|
# Validator emits description-missing if absent.
|
|
17
17
|
|
|
18
|
-
reviewer:
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
18
|
+
# reviewer: # OPTIONAL — reviewer kind is inferred from rule-file presence:
|
|
19
|
+
#
|
|
20
|
+
# content.md present → llm
|
|
21
|
+
# check.mjs present → deterministic
|
|
22
|
+
# neither + implies declared → aggregate
|
|
23
|
+
#
|
|
24
|
+
# The reviewer: block is only required when you need to declare
|
|
25
|
+
# reviewer.tier: for an LLM aspect. When present, an explicit
|
|
26
|
+
# reviewer.type must agree with the inferred kind (validator
|
|
27
|
+
# error otherwise).
|
|
28
|
+
#
|
|
29
|
+
# Three kinds:
|
|
30
|
+
# llm — aspect ships content.md; an LLM reads it and
|
|
31
|
+
# judges the code against the rule.
|
|
32
|
+
# deterministic — aspect ships check.mjs; the runner executes it
|
|
33
|
+
# locally with graph-aware ctx (files, fs, graph,
|
|
34
|
+
# parsers). Language-agnostic. No LLM call, zero
|
|
35
|
+
# token cost.
|
|
36
|
+
# aggregate — aspect ships neither rule source but declares
|
|
37
|
+
# implies:. A named bundle — expands its implied
|
|
38
|
+
# aspects onto every node where effective. Has no
|
|
39
|
+
# own reviewer and produces no own verdict. An
|
|
40
|
+
# aspect with neither rule source and no implies:
|
|
41
|
+
# is rejected (aspect-empty).
|
|
42
|
+
# type: llm # optional; must be 'llm', 'deterministic', or 'aggregate' if set.
|
|
43
|
+
# tier: deep # optional, only when type: llm (or inferred llm).
|
|
25
44
|
# If omitted, the aspect uses reviewer.default from yg-config.yaml.
|
|
26
45
|
# If present, must reference a key under reviewer.tiers in the config.
|
|
27
|
-
# Forbidden when type is 'deterministic'.
|
|
46
|
+
# Forbidden when type is 'deterministic' or 'aggregate'.
|
|
28
47
|
|
|
29
48
|
status: enforced # optional — aspect-level default. enum: draft | advisory | enforced.
|
|
30
49
|
# Absent → 'enforced'.
|
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.2",
|
|
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": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"dist/**/*.js",
|
|
11
11
|
"dist/**/*.d.ts",
|
|
12
12
|
"dist/**/*.wasm",
|
|
13
|
+
"dist/**/*.node-types.json",
|
|
13
14
|
"graph-schemas/"
|
|
14
15
|
],
|
|
15
16
|
"engines": {
|
|
@@ -87,7 +88,7 @@
|
|
|
87
88
|
"@tree-sitter-grammars/tree-sitter-yaml": "^0.7.1",
|
|
88
89
|
"@types/node": "^25.9.1",
|
|
89
90
|
"@types/semver": "^7.7.1",
|
|
90
|
-
"@vitest/coverage-v8": "^4.1.
|
|
91
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
91
92
|
"eslint": "^10.4.1",
|
|
92
93
|
"prettier": "^3.8.3",
|
|
93
94
|
"tree-sitter-c": "^0.24.1",
|
|
@@ -105,6 +106,6 @@
|
|
|
105
106
|
"tsup": "^8.5.1",
|
|
106
107
|
"typescript": "^6.0.3",
|
|
107
108
|
"typescript-eslint": "^8.60.0",
|
|
108
|
-
"vitest": "^4.1.
|
|
109
|
+
"vitest": "^4.1.8"
|
|
109
110
|
}
|
|
110
111
|
}
|