@chrisdudek/yg 4.0.1 → 4.0.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.
Files changed (2) hide show
  1. package/dist/bin.js +183 -205
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -2346,6 +2346,10 @@ function normalizeProjectRelativePath(projectRoot, rawPath) {
2346
2346
  function projectRootFromGraph(yggRootPath) {
2347
2347
  return path10.dirname(yggRootPath);
2348
2348
  }
2349
+ function resolveFileArg(cwd, repoRoot, rawPath) {
2350
+ const absolute = path10.resolve(cwd, rawPath.trim());
2351
+ return path10.relative(repoRoot, absolute).replace(/\\/g, "/").replace(/\/+$/, "");
2352
+ }
2349
2353
 
2350
2354
  // src/core/graph-loader.ts
2351
2355
  function toModelPath(absolutePath, modelDir) {
@@ -2910,8 +2914,160 @@ function formatFileContext(data) {
2910
2914
  }
2911
2915
 
2912
2916
  // src/core/validator.ts
2913
- import { readdir as readdir5, stat as stat4 } from "fs/promises";
2917
+ import { readdir as readdir6 } from "fs/promises";
2918
+ import path14 from "path";
2919
+
2920
+ // src/utils/hash.ts
2921
+ import { readFile as readFile14, readdir as readdir5, stat as stat4 } from "fs/promises";
2914
2922
  import path13 from "path";
2923
+ import { createHash } from "crypto";
2924
+ import { createRequire } from "module";
2925
+ var require2 = createRequire(import.meta.url);
2926
+ var ignoreFactory = require2("ignore");
2927
+ async function hashFile(filePath) {
2928
+ const content = await readFile14(filePath);
2929
+ return createHash("sha256").update(content).digest("hex");
2930
+ }
2931
+ async function loadRootGitignoreStack(projectRoot) {
2932
+ if (!projectRoot) return [];
2933
+ try {
2934
+ const content = await readFile14(path13.join(projectRoot, ".gitignore"), "utf-8");
2935
+ const matcher = ignoreFactory();
2936
+ matcher.add(content);
2937
+ return [{ basePath: projectRoot, matcher }];
2938
+ } catch {
2939
+ return [];
2940
+ }
2941
+ }
2942
+ function isIgnoredByStack(candidatePath, stack) {
2943
+ for (const { basePath, matcher } of stack) {
2944
+ const relativePath = path13.relative(basePath, candidatePath);
2945
+ if (relativePath === "" || relativePath.startsWith("..")) continue;
2946
+ if (matcher.ignores(relativePath) || matcher.ignores(relativePath + "/")) return true;
2947
+ }
2948
+ return false;
2949
+ }
2950
+ function hashString(content) {
2951
+ return createHash("sha256").update(content).digest("hex");
2952
+ }
2953
+ async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, excludePrefixes) {
2954
+ const fileHashes = {};
2955
+ const fileMtimes = {};
2956
+ const gitignoreStack = await loadRootGitignoreStack(projectRoot);
2957
+ const allFiles = [];
2958
+ for (const tf of trackedFiles) {
2959
+ if (tf.syntheticHash) {
2960
+ fileHashes[tf.path] = tf.syntheticHash;
2961
+ continue;
2962
+ }
2963
+ const absPath = path13.join(projectRoot, tf.path);
2964
+ try {
2965
+ const st = await stat4(absPath);
2966
+ if (st.isDirectory()) {
2967
+ const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
2968
+ projectRoot,
2969
+ gitignoreStack
2970
+ });
2971
+ for (const entry of dirEntries) {
2972
+ allFiles.push({
2973
+ relPath: path13.join(tf.path, entry.relPath).replace(/\\/g, "/").replace(/\/+$/, ""),
2974
+ absPath: entry.absPath,
2975
+ mtimeMs: entry.mtimeMs
2976
+ });
2977
+ }
2978
+ } else {
2979
+ allFiles.push({ relPath: tf.path, absPath, mtimeMs: st.mtimeMs });
2980
+ }
2981
+ } catch {
2982
+ continue;
2983
+ }
2984
+ }
2985
+ const filtered = excludePrefixes?.length ? allFiles.filter((entry) => !excludePrefixes.some((prefix) => entry.relPath === prefix || entry.relPath.startsWith(prefix + "/"))) : allFiles;
2986
+ const dirty = [];
2987
+ for (const entry of filtered) {
2988
+ const storedMtime = storedFileData?.mtimes[entry.relPath];
2989
+ const storedHash = storedFileData?.hashes[entry.relPath];
2990
+ if (storedMtime !== void 0 && storedHash !== void 0 && entry.mtimeMs === storedMtime) {
2991
+ fileHashes[entry.relPath] = storedHash;
2992
+ } else {
2993
+ dirty.push(entry);
2994
+ }
2995
+ fileMtimes[entry.relPath] = entry.mtimeMs;
2996
+ }
2997
+ const BATCH_SIZE = 256;
2998
+ for (let i = 0; i < dirty.length; i += BATCH_SIZE) {
2999
+ const batch = dirty.slice(i, i + BATCH_SIZE);
3000
+ const hashes = await Promise.all(batch.map((e) => hashFile(e.absPath)));
3001
+ for (let j = 0; j < batch.length; j++) {
3002
+ fileHashes[batch[j].relPath] = hashes[j];
3003
+ }
3004
+ }
3005
+ const sorted = Object.entries(fileHashes).sort(([a], [b]) => a.localeCompare(b));
3006
+ const digest = sorted.map(([p2, h]) => `${p2}:${h}`).join("\n");
3007
+ const canonicalHash = hashString(digest);
3008
+ return { canonicalHash, fileHashes, fileMtimes };
3009
+ }
3010
+ async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
3011
+ let stack = options.gitignoreStack ?? [];
3012
+ try {
3013
+ const localContent = await readFile14(path13.join(directoryPath, ".gitignore"), "utf-8");
3014
+ const localMatcher = ignoreFactory();
3015
+ localMatcher.add(localContent);
3016
+ stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
3017
+ } catch {
3018
+ }
3019
+ const entries = await readdir5(directoryPath, { withFileTypes: true });
3020
+ const dirs = [];
3021
+ const files = [];
3022
+ for (const entry of entries) {
3023
+ const absoluteChildPath = path13.join(directoryPath, entry.name);
3024
+ if (isIgnoredByStack(absoluteChildPath, stack)) continue;
3025
+ if (entry.isDirectory()) dirs.push(absoluteChildPath);
3026
+ else if (entry.isFile()) files.push(absoluteChildPath);
3027
+ }
3028
+ const [dirResults, fileStats] = await Promise.all([
3029
+ Promise.all(dirs.map((d) => collectDirectoryFilePaths(d, rootDirectoryPath, {
3030
+ projectRoot: options.projectRoot,
3031
+ gitignoreStack: stack
3032
+ }))),
3033
+ Promise.all(files.map(async (f) => {
3034
+ const fileStat = await stat4(f);
3035
+ return {
3036
+ relPath: path13.relative(rootDirectoryPath, f).replace(/\\/g, "/").replace(/\/+$/, ""),
3037
+ absPath: f,
3038
+ mtimeMs: fileStat.mtimeMs
3039
+ };
3040
+ }))
3041
+ ]);
3042
+ const result = [];
3043
+ for (const nested of dirResults) result.push(...nested);
3044
+ result.push(...fileStats);
3045
+ return result;
3046
+ }
3047
+ async function expandMappingPaths(projectRoot, mappingPaths) {
3048
+ const gitignoreStack = await loadRootGitignoreStack(projectRoot);
3049
+ const result = [];
3050
+ for (const mp of mappingPaths) {
3051
+ const absPath = path13.join(projectRoot, mp);
3052
+ try {
3053
+ const st = await stat4(absPath);
3054
+ if (st.isDirectory()) {
3055
+ const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
3056
+ projectRoot,
3057
+ gitignoreStack
3058
+ });
3059
+ for (const entry of dirEntries) {
3060
+ result.push(path13.join(mp, entry.relPath).replace(/\\/g, "/").replace(/\/+$/, ""));
3061
+ }
3062
+ } else {
3063
+ result.push(mp);
3064
+ }
3065
+ } catch {
3066
+ continue;
3067
+ }
3068
+ }
3069
+ return result;
3070
+ }
2915
3071
 
2916
3072
  // src/formatters/message-builder.ts
2917
3073
  function buildIssueMessage(msg) {
@@ -2972,10 +3128,11 @@ async function validate(graph, scope = "all") {
2972
3128
  issues.push(...checkOrphanedAspects(graph));
2973
3129
  let filtered = issues;
2974
3130
  let nodesScanned = graph.nodes.size;
2975
- if (scope !== "all" && scope.trim()) {
2976
- if (!graph.nodes.has(scope)) {
3131
+ const normalizedScope = scope.trim().replace(/\\/g, "/").replace(/\/+$/, "");
3132
+ if (normalizedScope !== "all" && normalizedScope) {
3133
+ if (!graph.nodes.has(normalizedScope)) {
2977
3134
  const parseError = (graph.nodeParseErrors ?? []).find(
2978
- (e) => e.nodePath === scope || scope.startsWith(e.nodePath + "/")
3135
+ (e) => e.nodePath === normalizedScope || normalizedScope.startsWith(e.nodePath + "/")
2979
3136
  );
2980
3137
  if (parseError) {
2981
3138
  return {
@@ -2990,13 +3147,13 @@ async function validate(graph, scope = "all") {
2990
3147
  };
2991
3148
  }
2992
3149
  return {
2993
- issues: [{ severity: "error", rule: "invalid-scope", message: buildIssueMessage({ what: `Node not found: ${scope}`, why: "Validation scope references a node that does not exist in the graph.", next: "Check the node path and try again." }) }],
3150
+ issues: [{ severity: "error", rule: "invalid-scope", message: buildIssueMessage({ what: `Node not found: ${normalizedScope}`, why: "Validation scope references a node that does not exist in the graph.", next: "Check the node path and try again." }) }],
2994
3151
  nodesScanned: 0
2995
3152
  };
2996
3153
  }
2997
- const scopePrefix = scope + "/";
2998
- filtered = issues.filter((i) => !i.nodePath || i.nodePath === scope || i.nodePath.startsWith(scopePrefix));
2999
- nodesScanned = [...graph.nodes.keys()].filter((p2) => p2 === scope || p2.startsWith(scopePrefix)).length;
3154
+ const scopePrefix = normalizedScope + "/";
3155
+ filtered = issues.filter((i) => !i.nodePath || i.nodePath === normalizedScope || i.nodePath.startsWith(scopePrefix));
3156
+ nodesScanned = [...graph.nodes.keys()].filter((p2) => p2 === normalizedScope || p2.startsWith(scopePrefix)).length;
3000
3157
  }
3001
3158
  return { issues: filtered, nodesScanned };
3002
3159
  }
@@ -3313,12 +3470,12 @@ function checkMappingOverlap(graph) {
3313
3470
  }
3314
3471
  async function checkMappingPathsExist(graph) {
3315
3472
  const issues = [];
3316
- const projectRoot = path13.dirname(graph.rootPath);
3473
+ const projectRoot = path14.dirname(graph.rootPath);
3317
3474
  const { access: access3 } = await import("fs/promises");
3318
3475
  for (const [nodePath, node] of graph.nodes) {
3319
3476
  const mappingPaths = normalizeMappingPaths(node.meta.mapping);
3320
3477
  for (const mp of mappingPaths) {
3321
- const absPath = path13.join(projectRoot, mp);
3478
+ const absPath = path14.join(projectRoot, mp);
3322
3479
  try {
3323
3480
  await access3(absPath);
3324
3481
  } catch {
@@ -3362,13 +3519,13 @@ function checkBrokenFlowRefs(graph) {
3362
3519
  async function checkWideNodes(graph) {
3363
3520
  const issues = [];
3364
3521
  const maxFiles = graph.config.quality?.max_mapping_source_files ?? 10;
3365
- const projectRoot = path13.dirname(graph.rootPath);
3522
+ const projectRoot = path14.dirname(graph.rootPath);
3366
3523
  for (const [nodePath, node] of graph.nodes) {
3367
3524
  const effectiveAspects = computeEffectiveAspects(node, graph);
3368
3525
  if (effectiveAspects.size === 0) continue;
3369
3526
  const mappingPaths = normalizeMappingPaths(node.meta.mapping);
3370
3527
  if (mappingPaths.length === 0) continue;
3371
- const sourceFiles = await expandMappingToFiles(projectRoot, mappingPaths);
3528
+ const sourceFiles = await expandMappingPaths(projectRoot, mappingPaths);
3372
3529
  if (sourceFiles.length <= maxFiles) continue;
3373
3530
  issues.push({
3374
3531
  severity: "warning",
@@ -3483,9 +3640,9 @@ function checkSchemas(graph) {
3483
3640
  }
3484
3641
  async function checkDirectoriesHaveNodeYaml(graph) {
3485
3642
  const issues = [];
3486
- const modelDir = path13.join(graph.rootPath, "model");
3643
+ const modelDir = path14.join(graph.rootPath, "model");
3487
3644
  async function scanDir(dirPath, segments) {
3488
- const entries = (await readdir5(dirPath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
3645
+ const entries = (await readdir6(dirPath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
3489
3646
  const hasNodeYaml = entries.some((e) => e.isFile() && e.name === "yg-node.yaml");
3490
3647
  const hasFiles = entries.some((e) => e.isFile());
3491
3648
  const graphPath = segments.join("/");
@@ -3507,47 +3664,20 @@ async function checkDirectoriesHaveNodeYaml(graph) {
3507
3664
  for (const entry of entries) {
3508
3665
  if (!entry.isDirectory()) continue;
3509
3666
  if (entry.name.startsWith(".")) continue;
3510
- await scanDir(path13.join(dirPath, entry.name), [...segments, entry.name]);
3667
+ await scanDir(path14.join(dirPath, entry.name), [...segments, entry.name]);
3511
3668
  }
3512
3669
  }
3513
3670
  try {
3514
- const rootEntries = (await readdir5(modelDir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
3671
+ const rootEntries = (await readdir6(modelDir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
3515
3672
  for (const entry of rootEntries) {
3516
3673
  if (!entry.isDirectory()) continue;
3517
3674
  if (entry.name.startsWith(".")) continue;
3518
- await scanDir(path13.join(modelDir, entry.name), [entry.name]);
3675
+ await scanDir(path14.join(modelDir, entry.name), [entry.name]);
3519
3676
  }
3520
3677
  } catch {
3521
3678
  }
3522
3679
  return issues;
3523
3680
  }
3524
- async function expandMappingToFiles(projectRoot, mappingPaths) {
3525
- const files = [];
3526
- async function collectFiles(absPath) {
3527
- try {
3528
- const s = await stat4(absPath);
3529
- if (s.isFile()) {
3530
- files.push(absPath);
3531
- } else if (s.isDirectory()) {
3532
- const entries = await readdir5(absPath, { withFileTypes: true });
3533
- for (const entry of entries) {
3534
- if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
3535
- const entryPath = path13.join(absPath, entry.name);
3536
- if (entry.isFile()) {
3537
- files.push(entryPath);
3538
- } else if (entry.isDirectory()) {
3539
- await collectFiles(entryPath);
3540
- }
3541
- }
3542
- }
3543
- } catch {
3544
- }
3545
- }
3546
- for (const mp of mappingPaths) {
3547
- await collectFiles(path13.join(projectRoot, mp));
3548
- }
3549
- return files;
3550
- }
3551
3681
  function checkMissingDescriptions(graph) {
3552
3682
  const issues = [];
3553
3683
  for (const [nodePath, node] of graph.nodes) {
@@ -3815,7 +3945,7 @@ function checkOrphanedAspects(graph) {
3815
3945
  }
3816
3946
 
3817
3947
  // src/cli/owner.ts
3818
- import path14 from "path";
3948
+ import path15 from "path";
3819
3949
  import { access } from "fs/promises";
3820
3950
  import chalk2 from "chalk";
3821
3951
  function normalizeForMatch(inputPath) {
@@ -3846,12 +3976,10 @@ function registerOwnerCommand(program2) {
3846
3976
  const graph = await loadGraph(cwd);
3847
3977
  initDebugLog(graph.rootPath, graph.config.debug ?? false);
3848
3978
  const repoRoot = projectRootFromGraph(graph.rootPath);
3849
- const rawPath = options.file.trim();
3850
- const absolute = path14.resolve(cwd, rawPath);
3851
- const repoRelative = path14.relative(repoRoot, absolute).replace(/\\/g, "/").replace(/\/+$/, "");
3979
+ const repoRelative = resolveFileArg(cwd, repoRoot, options.file);
3852
3980
  const result = findOwner(graph, repoRoot, repoRelative);
3853
3981
  if (!result.nodePath) {
3854
- const absPath = path14.resolve(repoRoot, result.file);
3982
+ const absPath = path15.resolve(repoRoot, result.file);
3855
3983
  let exists = true;
3856
3984
  try {
3857
3985
  await access(absPath);
@@ -3891,158 +4019,6 @@ function registerOwnerCommand(program2) {
3891
4019
  });
3892
4020
  }
3893
4021
 
3894
- // src/utils/hash.ts
3895
- import { readFile as readFile14, readdir as readdir6, stat as stat5 } from "fs/promises";
3896
- import path15 from "path";
3897
- import { createHash } from "crypto";
3898
- import { createRequire } from "module";
3899
- var require2 = createRequire(import.meta.url);
3900
- var ignoreFactory = require2("ignore");
3901
- async function hashFile(filePath) {
3902
- const content = await readFile14(filePath);
3903
- return createHash("sha256").update(content).digest("hex");
3904
- }
3905
- async function loadRootGitignoreStack(projectRoot) {
3906
- if (!projectRoot) return [];
3907
- try {
3908
- const content = await readFile14(path15.join(projectRoot, ".gitignore"), "utf-8");
3909
- const matcher = ignoreFactory();
3910
- matcher.add(content);
3911
- return [{ basePath: projectRoot, matcher }];
3912
- } catch {
3913
- return [];
3914
- }
3915
- }
3916
- function isIgnoredByStack(candidatePath, stack) {
3917
- for (const { basePath, matcher } of stack) {
3918
- const relativePath = path15.relative(basePath, candidatePath);
3919
- if (relativePath === "" || relativePath.startsWith("..")) continue;
3920
- if (matcher.ignores(relativePath) || matcher.ignores(relativePath + "/")) return true;
3921
- }
3922
- return false;
3923
- }
3924
- function hashString(content) {
3925
- return createHash("sha256").update(content).digest("hex");
3926
- }
3927
- async function hashTrackedFiles(projectRoot, trackedFiles, storedFileData, excludePrefixes) {
3928
- const fileHashes = {};
3929
- const fileMtimes = {};
3930
- const gitignoreStack = await loadRootGitignoreStack(projectRoot);
3931
- const allFiles = [];
3932
- for (const tf of trackedFiles) {
3933
- if (tf.syntheticHash) {
3934
- fileHashes[tf.path] = tf.syntheticHash;
3935
- continue;
3936
- }
3937
- const absPath = path15.join(projectRoot, tf.path);
3938
- try {
3939
- const st = await stat5(absPath);
3940
- if (st.isDirectory()) {
3941
- const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
3942
- projectRoot,
3943
- gitignoreStack
3944
- });
3945
- for (const entry of dirEntries) {
3946
- allFiles.push({
3947
- relPath: path15.join(tf.path, entry.relPath).replace(/\\/g, "/").replace(/\/+$/, ""),
3948
- absPath: entry.absPath,
3949
- mtimeMs: entry.mtimeMs
3950
- });
3951
- }
3952
- } else {
3953
- allFiles.push({ relPath: tf.path, absPath, mtimeMs: st.mtimeMs });
3954
- }
3955
- } catch {
3956
- continue;
3957
- }
3958
- }
3959
- const filtered = excludePrefixes?.length ? allFiles.filter((entry) => !excludePrefixes.some((prefix) => entry.relPath === prefix || entry.relPath.startsWith(prefix + "/"))) : allFiles;
3960
- const dirty = [];
3961
- for (const entry of filtered) {
3962
- const storedMtime = storedFileData?.mtimes[entry.relPath];
3963
- const storedHash = storedFileData?.hashes[entry.relPath];
3964
- if (storedMtime !== void 0 && storedHash !== void 0 && entry.mtimeMs === storedMtime) {
3965
- fileHashes[entry.relPath] = storedHash;
3966
- } else {
3967
- dirty.push(entry);
3968
- }
3969
- fileMtimes[entry.relPath] = entry.mtimeMs;
3970
- }
3971
- const BATCH_SIZE = 256;
3972
- for (let i = 0; i < dirty.length; i += BATCH_SIZE) {
3973
- const batch = dirty.slice(i, i + BATCH_SIZE);
3974
- const hashes = await Promise.all(batch.map((e) => hashFile(e.absPath)));
3975
- for (let j = 0; j < batch.length; j++) {
3976
- fileHashes[batch[j].relPath] = hashes[j];
3977
- }
3978
- }
3979
- const sorted = Object.entries(fileHashes).sort(([a], [b]) => a.localeCompare(b));
3980
- const digest = sorted.map(([p2, h]) => `${p2}:${h}`).join("\n");
3981
- const canonicalHash = hashString(digest);
3982
- return { canonicalHash, fileHashes, fileMtimes };
3983
- }
3984
- async function collectDirectoryFilePaths(directoryPath, rootDirectoryPath, options) {
3985
- let stack = options.gitignoreStack ?? [];
3986
- try {
3987
- const localContent = await readFile14(path15.join(directoryPath, ".gitignore"), "utf-8");
3988
- const localMatcher = ignoreFactory();
3989
- localMatcher.add(localContent);
3990
- stack = [...stack, { basePath: directoryPath, matcher: localMatcher }];
3991
- } catch {
3992
- }
3993
- const entries = await readdir6(directoryPath, { withFileTypes: true });
3994
- const dirs = [];
3995
- const files = [];
3996
- for (const entry of entries) {
3997
- const absoluteChildPath = path15.join(directoryPath, entry.name);
3998
- if (isIgnoredByStack(absoluteChildPath, stack)) continue;
3999
- if (entry.isDirectory()) dirs.push(absoluteChildPath);
4000
- else if (entry.isFile()) files.push(absoluteChildPath);
4001
- }
4002
- const [dirResults, fileStats] = await Promise.all([
4003
- Promise.all(dirs.map((d) => collectDirectoryFilePaths(d, rootDirectoryPath, {
4004
- projectRoot: options.projectRoot,
4005
- gitignoreStack: stack
4006
- }))),
4007
- Promise.all(files.map(async (f) => {
4008
- const fileStat = await stat5(f);
4009
- return {
4010
- relPath: path15.relative(rootDirectoryPath, f).replace(/\\/g, "/").replace(/\/+$/, ""),
4011
- absPath: f,
4012
- mtimeMs: fileStat.mtimeMs
4013
- };
4014
- }))
4015
- ]);
4016
- const result = [];
4017
- for (const nested of dirResults) result.push(...nested);
4018
- result.push(...fileStats);
4019
- return result;
4020
- }
4021
- async function expandMappingPaths(projectRoot, mappingPaths) {
4022
- const gitignoreStack = await loadRootGitignoreStack(projectRoot);
4023
- const result = [];
4024
- for (const mp of mappingPaths) {
4025
- const absPath = path15.join(projectRoot, mp);
4026
- try {
4027
- const st = await stat5(absPath);
4028
- if (st.isDirectory()) {
4029
- const dirEntries = await collectDirectoryFilePaths(absPath, absPath, {
4030
- projectRoot,
4031
- gitignoreStack
4032
- });
4033
- for (const entry of dirEntries) {
4034
- result.push(path15.join(mp, entry.relPath).replace(/\\/g, "/").replace(/\/+$/, ""));
4035
- }
4036
- } else {
4037
- result.push(mp);
4038
- }
4039
- } catch {
4040
- continue;
4041
- }
4042
- }
4043
- return result;
4044
- }
4045
-
4046
4022
  // src/cli/build-context.ts
4047
4023
  function findCandidateNodes(graph, unmappedFile) {
4048
4024
  const dir = unmappedFile.replace(/\/[^/]+$/, "");
@@ -4108,7 +4084,8 @@ function registerBuildCommand(program2) {
4108
4084
  let resolvedFilePath;
4109
4085
  if (options.file) {
4110
4086
  const repoRoot = projectRootFromGraph(graph.rootPath);
4111
- const result = findOwner(graph, repoRoot, options.file.trim());
4087
+ const repoRelative = resolveFileArg(process.cwd(), repoRoot, options.file);
4088
+ const result = findOwner(graph, repoRoot, repoRelative);
4112
4089
  if (!result.nodePath) {
4113
4090
  const candidates = findCandidateNodes(graph, result.file);
4114
4091
  if (candidates.length > 0) {
@@ -4201,7 +4178,7 @@ import chalk4 from "chalk";
4201
4178
  import path20 from "path";
4202
4179
 
4203
4180
  // src/io/drift-state-store.ts
4204
- import { readFile as readFile15, writeFile as writeFile5, stat as stat6, readdir as readdir7, mkdir as mkdir3, rm as rm2 } from "fs/promises";
4181
+ import { readFile as readFile15, writeFile as writeFile5, stat as stat5, readdir as readdir7, mkdir as mkdir3, rm as rm2 } from "fs/promises";
4205
4182
  import path16 from "path";
4206
4183
  var DRIFT_STATE_DIR = ".drift-state";
4207
4184
  function nodeStatePath(yggRoot, nodePath) {
@@ -4281,7 +4258,7 @@ async function readDriftState(yggRoot) {
4281
4258
  const driftPath = path16.join(yggRoot, DRIFT_STATE_DIR);
4282
4259
  let driftStat;
4283
4260
  try {
4284
- driftStat = await stat6(driftPath);
4261
+ driftStat = await stat5(driftPath);
4285
4262
  } catch (err) {
4286
4263
  debugWrite(`[drift-state-store] readDriftState stat: ${err.message}`);
4287
4264
  return {};
@@ -6238,7 +6215,8 @@ function registerImpactCommand(program2) {
6238
6215
  initDebugLog(graph.rootPath, graph.config.debug ?? false);
6239
6216
  if (options.file) {
6240
6217
  const repoRoot = projectRootFromGraph(graph.rootPath);
6241
- const result = findOwner(graph, repoRoot, options.file.trim());
6218
+ const repoRelative = resolveFileArg(process.cwd(), repoRoot, options.file);
6219
+ const result = findOwner(graph, repoRoot, repoRelative);
6242
6220
  if (!result.nodePath) {
6243
6221
  process.stderr.write(chalk6.red(`${result.file} -> no graph coverage
6244
6222
  `));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrisdudek/yg",
3
- "version": "4.0.1",
3
+ "version": "4.0.2",
4
4
  "description": "Continuous architecture enforcement for AI-assisted development. Aspects, review, enforcement.",
5
5
  "type": "module",
6
6
  "bin": {