artifact-graph 0.8.4 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Changed
6
+
7
+ - Version synchronized with `artifact-chain-assistant@0.9.0` (suite lockstep release). No runtime
8
+ behavior changes.
9
+
10
+ ## 0.8.5
11
+
12
+ ### Fixed
13
+
14
+ - E2E trace validation now accepts only real standalone line comments. Annotation-shaped text in
15
+ source strings and template literals is ignored, eliminating false `E2E-TRACE-002` findings from
16
+ test fixtures.
17
+ - Staged version-lock refresh now blocks only for unstaged files matched by the project's configured
18
+ artifact paths (plus the graph config and lock file). Unrelated Markdown or source files no longer
19
+ prevent a valid split commit, while genuine graph-relevant divergence remains fail-closed.
20
+
3
21
  ## 0.8.4
4
22
 
5
23
  ### Changed
package/dist/cli.js CHANGED
@@ -316,6 +316,41 @@ var init_glob_matcher = __esm({
316
316
  }
317
317
  });
318
318
 
319
+ // src/file-walker.ts
320
+ import { readdir } from "fs/promises";
321
+ import { join, relative } from "path";
322
+ async function walkFiles(root, current = root, readDirectory = defaultReadDirectory) {
323
+ let entries;
324
+ try {
325
+ entries = await readDirectory(current, { withFileTypes: true });
326
+ } catch (error) {
327
+ if (current !== root && error.code === "ENOENT") {
328
+ return [];
329
+ }
330
+ throw error;
331
+ }
332
+ const files = [];
333
+ for (const entry of entries) {
334
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
335
+ continue;
336
+ }
337
+ const fullPath = join(current, entry.name);
338
+ if (entry.isDirectory()) {
339
+ files.push(...await walkFiles(root, fullPath, readDirectory));
340
+ } else {
341
+ files.push(relative(root, fullPath).split("\\").join("/"));
342
+ }
343
+ }
344
+ return files;
345
+ }
346
+ var defaultReadDirectory;
347
+ var init_file_walker = __esm({
348
+ "src/file-walker.ts"() {
349
+ "use strict";
350
+ defaultReadDirectory = readdir;
351
+ }
352
+ });
353
+
319
354
  // src/target-selector.ts
320
355
  function parseTargetSelector(value) {
321
356
  const separator = value.indexOf(":");
@@ -830,7 +865,7 @@ var init_packet_assembler = __esm({
830
865
 
831
866
  // src/packet-audit.ts
832
867
  import { mkdir, writeFile } from "fs/promises";
833
- import { join } from "path";
868
+ import { join as join2 } from "path";
834
869
  function parseTargetsFile(content, schema) {
835
870
  const validTypes = schema ? new Set(getTargetArtifactTypes(schema)) : VALID_TYPES;
836
871
  const validTypesLabel = [...validTypes].join(", ");
@@ -921,7 +956,7 @@ async function auditSingleTarget(target, graph, options) {
921
956
  const fmt = options.format ?? "markdown";
922
957
  const ext = fmt === "json" ? "json" : "md";
923
958
  const filename = `${target.type}-${target.id}.packet.${ext}`;
924
- const outPath = join(options.outDir, filename);
959
+ const outPath = join2(options.outDir, filename);
925
960
  let content;
926
961
  if (fmt === "json") {
927
962
  content = JSON.stringify(packet, null, 2) + "\n";
@@ -1001,7 +1036,7 @@ async function auditPackets(root, targets, options, graph) {
1001
1036
  ...isCompact ? { summaryDetail: "compact", countsByType } : {}
1002
1037
  };
1003
1038
  if (options.outDir) {
1004
- const summaryPath = join(options.outDir, "summary.json");
1039
+ const summaryPath = join2(options.outDir, "summary.json");
1005
1040
  await writeFile(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
1006
1041
  }
1007
1042
  return summary;
@@ -1373,7 +1408,7 @@ var init_packet_prompt_validator = __esm({
1373
1408
  import { createHash } from "crypto";
1374
1409
  import { existsSync } from "fs";
1375
1410
  import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
1376
- import { dirname, join as join2, relative } from "path";
1411
+ import { dirname, join as join3, relative as relative2 } from "path";
1377
1412
  async function buildVersionIndex(root, graph) {
1378
1413
  const scannedGraph = graph ?? await scanArtifacts(root);
1379
1414
  const hashCache = /* @__PURE__ */ new Map();
@@ -1509,7 +1544,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1509
1544
  for (const entry of lock.locks) {
1510
1545
  if (entry.kind !== "verifies") continue;
1511
1546
  const sourcePath = entry.source.path;
1512
- const fullSourcePath = join2(root, sourcePath);
1547
+ const fullSourcePath = join3(root, sourcePath);
1513
1548
  if (!existsSync(fullSourcePath)) continue;
1514
1549
  let liveness = livenessCache.get(sourcePath);
1515
1550
  if (liveness === void 0) {
@@ -1604,14 +1639,14 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1604
1639
  currentArtifactHash: targetNode.contentHash
1605
1640
  });
1606
1641
  }
1607
- if (!existsSync(join2(root, sourceNode.path))) {
1642
+ if (!existsSync(join3(root, sourceNode.path))) {
1608
1643
  entryIssues.push({
1609
1644
  status: "orphan_lock",
1610
1645
  edgeId: relEdgeId,
1611
1646
  message: `Artifact relation source file ${sourceNode.path} no longer exists`
1612
1647
  });
1613
1648
  }
1614
- if (!existsSync(join2(root, targetNode.path))) {
1649
+ if (!existsSync(join3(root, targetNode.path))) {
1615
1650
  entryIssues.push({
1616
1651
  status: "orphan_lock",
1617
1652
  edgeId: relEdgeId,
@@ -2184,7 +2219,7 @@ function renderTraceVersionMarkdown(result) {
2184
2219
  async function readVersionLock(root, lockPath) {
2185
2220
  const safeLockPath = normalizeRelativePath(root, lockPath);
2186
2221
  try {
2187
- const raw = await readFile(join2(root, safeLockPath), "utf-8");
2222
+ const raw = await readFile(join3(root, safeLockPath), "utf-8");
2188
2223
  let parsed;
2189
2224
  try {
2190
2225
  parsed = JSON.parse(raw);
@@ -2343,7 +2378,7 @@ function requireSafeRelativePath(value, path) {
2343
2378
  }
2344
2379
  async function writeVersionLock(root, lockPath, lock) {
2345
2380
  const safeLockPath = normalizeRelativePath(root, lockPath);
2346
- const fullPath = join2(root, safeLockPath);
2381
+ const fullPath = join3(root, safeLockPath);
2347
2382
  await mkdir2(dirname(fullPath), { recursive: true });
2348
2383
  await writeFile2(fullPath, `${JSON.stringify(lock, null, 2)}
2349
2384
  `);
@@ -2526,14 +2561,14 @@ async function hashRelativePath(root, path, cache) {
2526
2561
  const normalized = normalizeRelativePath(root, path);
2527
2562
  const cached = cache.get(normalized);
2528
2563
  if (cached) return cached;
2529
- const content = await readFile(join2(root, normalized));
2564
+ const content = await readFile(join3(root, normalized));
2530
2565
  const hash = `sha256:${createHash("sha256").update(content).digest("hex")}`;
2531
2566
  cache.set(normalized, hash);
2532
2567
  return hash;
2533
2568
  }
2534
2569
  function normalizeRelativePath(root, path) {
2535
2570
  const normalized = path.replace(/\\/g, "/");
2536
- const relativePath = normalized.startsWith("/") ? relative(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
2571
+ const relativePath = normalized.startsWith("/") ? relative2(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
2537
2572
  if (relativePath === ".." || relativePath.startsWith("../")) {
2538
2573
  throw new Error(`Path is outside root: ${path}`);
2539
2574
  }
@@ -2549,7 +2584,7 @@ async function getTestFileRunnerLiveness(root, filePath, config) {
2549
2584
  if (!/e2e/i.test(filePath) && !/\.e2e\./i.test(filePath)) {
2550
2585
  return "active";
2551
2586
  }
2552
- const fullSourcePath = join2(root, filePath);
2587
+ const fullSourcePath = join3(root, filePath);
2553
2588
  if (!existsSync(fullSourcePath)) return "inactive";
2554
2589
  try {
2555
2590
  const content = await readFile(fullSourcePath, "utf-8");
@@ -2618,7 +2653,7 @@ var init_versioned_traceability = __esm({
2618
2653
  import { execFile } from "child_process";
2619
2654
  import { constants } from "fs";
2620
2655
  import { access } from "fs/promises";
2621
- import { isAbsolute, join as join3, resolve } from "path";
2656
+ import { isAbsolute, join as join4, resolve } from "path";
2622
2657
  import { promisify } from "util";
2623
2658
  async function resolveArtifactGraphCli(root, options = {}) {
2624
2659
  const pathCli = await findCommandOnPath("artifact-graph");
@@ -2626,7 +2661,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
2626
2661
  const candidates = [
2627
2662
  {
2628
2663
  source: "node_modules",
2629
- path: join3(root, "node_modules/.bin/artifact-graph"),
2664
+ path: join4(root, "node_modules/.bin/artifact-graph"),
2630
2665
  exists: false
2631
2666
  },
2632
2667
  {
@@ -2659,8 +2694,8 @@ async function resolveArtifactGraphCli(root, options = {}) {
2659
2694
  }
2660
2695
  async function doctorArtifactChain(root, options = {}) {
2661
2696
  const cli = await resolveArtifactGraphCli(root, options);
2662
- const configPath = join3(root, "artifact-graph.config.yaml");
2663
- const lockPath = join3(root, VERSION_LOCK_PATH);
2697
+ const configPath = join4(root, "artifact-graph.config.yaml");
2698
+ const lockPath = join4(root, VERSION_LOCK_PATH);
2664
2699
  const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
2665
2700
  const nodeCompatible = isNodeCompatible(process.versions.node);
2666
2701
  const warnings = [
@@ -2885,7 +2920,7 @@ var init_git_hook_path = __esm({
2885
2920
  import { constants as constants2 } from "fs";
2886
2921
  import { randomUUID } from "crypto";
2887
2922
  import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
2888
- import { basename, dirname as dirname2, join as join4 } from "path";
2923
+ import { basename, dirname as dirname2, join as join5 } from "path";
2889
2924
  function detectHookInterpreter(content) {
2890
2925
  if (content.trim().length === 0) {
2891
2926
  return "empty";
@@ -3193,7 +3228,7 @@ async function removeHookAtomically(hookPath, snapshot) {
3193
3228
  }
3194
3229
  async function writeHookAtomically(hookPath, content, snapshot, mode) {
3195
3230
  await mkdir3(dirname2(hookPath), { recursive: true });
3196
- const temporaryPath = join4(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
3231
+ const temporaryPath = join5(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
3197
3232
  let temporaryExists = false;
3198
3233
  try {
3199
3234
  const temporary = await open(temporaryPath, "wx", mode);
@@ -3658,8 +3693,8 @@ __export(contract_kernel_exports, {
3658
3693
  verifyDigest: () => verifyDigest
3659
3694
  });
3660
3695
  import { createHash as createHash2 } from "crypto";
3661
- import { readFile as readFile2, readdir } from "fs/promises";
3662
- import { join as join5 } from "path";
3696
+ import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
3697
+ import { join as join6 } from "path";
3663
3698
  import _AjvModule from "ajv";
3664
3699
  function isOfficialNamespace(namespace) {
3665
3700
  return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
@@ -4069,10 +4104,10 @@ async function loadContract(contractPath, options) {
4069
4104
  }
4070
4105
  async function loadContractsFromDirectory(contractsDir, options) {
4071
4106
  const contracts = [];
4072
- const entries = await readdir(contractsDir, { withFileTypes: true });
4107
+ const entries = await readdir2(contractsDir, { withFileTypes: true });
4073
4108
  for (const entry of entries) {
4074
4109
  if (entry.isDirectory()) {
4075
- const schemaPath = join5(contractsDir, entry.name, "schema.json");
4110
+ const schemaPath = join6(contractsDir, entry.name, "schema.json");
4076
4111
  const contract = await loadContract(schemaPath, options);
4077
4112
  contracts.push(contract);
4078
4113
  }
@@ -4564,8 +4599,8 @@ import Database from "better-sqlite3";
4564
4599
  import matter from "gray-matter";
4565
4600
  import yaml from "js-yaml";
4566
4601
  import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
4567
- import { mkdir as mkdir4, readFile as readFile3, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
4568
- import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve3 } from "path";
4602
+ import { mkdir as mkdir4, readFile as readFile3, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
4603
+ import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3 } from "path";
4569
4604
  function isTargetArtifactType(type) {
4570
4605
  return isPacketTargetType(type);
4571
4606
  }
@@ -4607,7 +4642,7 @@ function resolveArtifactTypeName(schema, token) {
4607
4642
  return void 0;
4608
4643
  }
4609
4644
  async function loadConfig(root) {
4610
- const configPath = join6(root, "artifact-graph.config.yaml");
4645
+ const configPath = join7(root, "artifact-graph.config.yaml");
4611
4646
  let parsed = {};
4612
4647
  try {
4613
4648
  const raw = await readFile3(configPath, "utf-8");
@@ -4784,7 +4819,7 @@ async function scanArtifacts(root, schema) {
4784
4819
  continue;
4785
4820
  }
4786
4821
  scannedFiles.set(file, type);
4787
- const raw = await readFile3(join6(root, file), "utf-8");
4822
+ const raw = await readFile3(join7(root, file), "utf-8");
4788
4823
  const parsed = parseFile(type, file, raw, config);
4789
4824
  nodes.push(...parsed.nodes);
4790
4825
  edges.push(...parsed.edges);
@@ -5096,7 +5131,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
5096
5131
  const indexPath = "artifacts/prd/feature-index.md";
5097
5132
  let raw = "";
5098
5133
  try {
5099
- raw = await readFile3(join6(root, indexPath), "utf-8");
5134
+ raw = await readFile3(join7(root, indexPath), "utf-8");
5100
5135
  } catch (error) {
5101
5136
  if (error.code === "ENOENT") {
5102
5137
  return [];
@@ -5286,11 +5321,11 @@ function nextId(graph, schema, type, rangeName) {
5286
5321
  throw new Error(`ID range ${type}.${rangeName} is exhausted`);
5287
5322
  }
5288
5323
  async function writeGraphCache(root, graph) {
5289
- const cacheDir = join6(root, ".artifact-graph");
5324
+ const cacheDir = join7(root, ".artifact-graph");
5290
5325
  await mkdir4(cacheDir, { recursive: true });
5291
- await writeFile3(join6(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5326
+ await writeFile3(join7(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5292
5327
  `);
5293
- const db = new Database(join6(cacheDir, "graph.sqlite"));
5328
+ const db = new Database(join7(cacheDir, "graph.sqlite"));
5294
5329
  try {
5295
5330
  db.exec(`
5296
5331
  DROP TABLE IF EXISTS nodes;
@@ -6973,10 +7008,10 @@ function validateE2eRegistry(graph) {
6973
7008
  async function validateExecutableTraceability(root, config) {
6974
7009
  const issues = [];
6975
7010
  const schema = config ?? await loadConfig(root);
6976
- const e2eDir = join6(root, "artifacts", "tests", "e2e");
7011
+ const e2eDir = join7(root, "artifacts", "tests", "e2e");
6977
7012
  let e2eFiles;
6978
7013
  try {
6979
- e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
7014
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
6980
7015
  } catch {
6981
7016
  return [];
6982
7017
  }
@@ -6986,7 +7021,7 @@ async function validateExecutableTraceability(root, config) {
6986
7021
  const mdBatches = /* @__PURE__ */ new Set();
6987
7022
  for (const filePath of e2eFiles) {
6988
7023
  const raw = await readFile3(filePath, "utf-8");
6989
- const relPath = relative2(root, filePath).split("\\").join("/");
7024
+ const relPath = relative3(root, filePath).split("\\").join("/");
6990
7025
  const parsed = matter(raw);
6991
7026
  const data = parsed.data;
6992
7027
  const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
@@ -7014,7 +7049,7 @@ async function validateExecutableTraceability(root, config) {
7014
7049
  }
7015
7050
  }
7016
7051
  }
7017
- const allFiles = await walk(root);
7052
+ const allFiles = await walkFiles(root);
7018
7053
  const specFiles = /* @__PURE__ */ new Set();
7019
7054
  const configuredRunners = schema.e2e?.runners ?? [];
7020
7055
  if (configuredRunners.length > 0) {
@@ -7034,7 +7069,7 @@ async function validateExecutableTraceability(root, config) {
7034
7069
  const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
7035
7070
  const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
7036
7071
  for (const specFile of specFiles) {
7037
- const fullSpecPath = join6(root, specFile);
7072
+ const fullSpecPath = join7(root, specFile);
7038
7073
  let content;
7039
7074
  try {
7040
7075
  content = await readFile3(fullSpecPath, "utf-8");
@@ -7043,14 +7078,16 @@ async function validateExecutableTraceability(root, config) {
7043
7078
  }
7044
7079
  const level = detectTestLevel(specFile, content);
7045
7080
  const specLines = content.split(/\r?\n/);
7046
- for (let lineIndex = 0; lineIndex < specLines.length; lineIndex += 1) {
7047
- const line = specLines[lineIndex];
7048
- let match = tcAnnotationRegex.exec(line);
7081
+ const lineComments = scanCodeComments(content).filter((comment) => comment.kind === "line" && comment.standalone);
7082
+ for (const comment of lineComments) {
7083
+ const lineIndex = comment.lineNumber - 1;
7084
+ const commentText = comment.text.replace(/^!/, "");
7085
+ let match = tcAnnotationRegex.exec(`//${commentText}`);
7049
7086
  let annotatedLevel = "";
7050
7087
  if (match) {
7051
7088
  annotatedLevel = match[2];
7052
7089
  } else {
7053
- match = tcAnnotationNoLevelRegex.exec(line);
7090
+ match = tcAnnotationNoLevelRegex.exec(`//${commentText}`);
7054
7091
  }
7055
7092
  if (!match) {
7056
7093
  continue;
@@ -7077,7 +7114,7 @@ async function validateExecutableTraceability(root, config) {
7077
7114
  level: effectiveLevel,
7078
7115
  file: specFile,
7079
7116
  testName,
7080
- line: lineIndex + 1
7117
+ line: comment.lineNumber
7081
7118
  };
7082
7119
  const key = `${batch}:${tcId}`;
7083
7120
  const existing = refToSource.get(key) ?? [];
@@ -7114,7 +7151,7 @@ async function validateExecutableTraceability(root, config) {
7114
7151
  if (entry.testId) {
7115
7152
  let content;
7116
7153
  try {
7117
- content = await readFile3(join6(root, normalizedRefFile), "utf-8");
7154
+ content = await readFile3(join7(root, normalizedRefFile), "utf-8");
7118
7155
  } catch {
7119
7156
  continue;
7120
7157
  }
@@ -7291,11 +7328,11 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7291
7328
  let withExecutableRef = 0;
7292
7329
  const statusBreakdown = {};
7293
7330
  const chainTypeBreakdown = {};
7294
- const e2eDir = join6(root, "artifacts", "tests", "e2e");
7331
+ const e2eDir = join7(root, "artifacts", "tests", "e2e");
7295
7332
  const tcFieldsMap = /* @__PURE__ */ new Map();
7296
7333
  let e2eFiles;
7297
7334
  try {
7298
- e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
7335
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
7299
7336
  } catch {
7300
7337
  e2eFiles = [];
7301
7338
  }
@@ -7362,7 +7399,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7362
7399
  }
7363
7400
  }
7364
7401
  const runners = (await loadConfig(root)).e2e?.runners ?? [];
7365
- const allProjectFiles = await walk(root);
7402
+ const allProjectFiles = await walkFiles(root);
7366
7403
  for (const node of e2eNodes) {
7367
7404
  const fields = tcFieldsMap.get(node.code) ?? asRecord(node.attrs?.tcFields);
7368
7405
  const status = String(fields["status"] ?? "").trim().toLowerCase();
@@ -7373,7 +7410,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7373
7410
  let hasActiveE2eRef = false;
7374
7411
  for (const entry of parseExecutableRefLines(execRef)) {
7375
7412
  const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
7376
- if (!normalized || !existsSync2(join6(root, normalized))) continue;
7413
+ if (!normalized || !existsSync2(join7(root, normalized))) continue;
7377
7414
  const accepting = await getAcceptingRunners(root, normalized, runners);
7378
7415
  if (accepting.some((runner) => runner.kind === "e2e")) {
7379
7416
  hasActiveE2eRef = true;
@@ -7432,7 +7469,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7432
7469
  const acCoverageRateByFeature = {};
7433
7470
  const featureAcMap = /* @__PURE__ */ new Map();
7434
7471
  for (const node of featureNodes) {
7435
- const acs = parseAcceptanceCriteria(await readFile3(join6(root, node.path), "utf-8"));
7472
+ const acs = parseAcceptanceCriteria(await readFile3(join7(root, node.path), "utf-8"));
7436
7473
  featureAcMap.set(node.code, new Set(acs));
7437
7474
  }
7438
7475
  const coveredAcByFeature = /* @__PURE__ */ new Map();
@@ -7474,10 +7511,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7474
7511
  };
7475
7512
  }
7476
7513
  async function generateE2eRegistry(root, opts) {
7477
- const e2eDir = join6(root, "artifacts", "tests", "e2e");
7514
+ const e2eDir = join7(root, "artifacts", "tests", "e2e");
7478
7515
  let files;
7479
7516
  try {
7480
- files = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
7517
+ files = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
7481
7518
  } catch {
7482
7519
  return {
7483
7520
  registry_version: "1.0",
@@ -7490,7 +7527,7 @@ async function generateE2eRegistry(root, opts) {
7490
7527
  const batches = [];
7491
7528
  let totalTestCases = 0;
7492
7529
  for (const file of files) {
7493
- const filePath = join6(e2eDir, file);
7530
+ const filePath = join7(e2eDir, file);
7494
7531
  const raw = await readFile3(filePath, "utf-8");
7495
7532
  const parsed = matter(raw);
7496
7533
  const data = parsed.data;
@@ -7618,7 +7655,7 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
7618
7655
  if (!normalizedPath) {
7619
7656
  return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
7620
7657
  }
7621
- const fullPath = join6(root, normalizedPath);
7658
+ const fullPath = join7(root, normalizedPath);
7622
7659
  let content;
7623
7660
  try {
7624
7661
  content = await readFile3(fullPath, "utf-8");
@@ -7730,7 +7767,7 @@ function escapeRegExp(value) {
7730
7767
  }
7731
7768
  async function hasMarkdownTc(tcKey, e2eDir) {
7732
7769
  const [batch, tcId] = tcKey.split(":");
7733
- const filePath = join6(e2eDir, `${batch}.md`);
7770
+ const filePath = join7(e2eDir, `${batch}.md`);
7734
7771
  try {
7735
7772
  const raw = await readFile3(filePath, "utf-8");
7736
7773
  const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
@@ -7740,7 +7777,7 @@ async function hasMarkdownTc(tcKey, e2eDir) {
7740
7777
  }
7741
7778
  }
7742
7779
  async function findFiles(root, patterns) {
7743
- const all = await walk(root);
7780
+ const all = await walkFiles(root);
7744
7781
  const matched = /* @__PURE__ */ new Set();
7745
7782
  for (const pattern of patterns) {
7746
7783
  for (const file of all) {
@@ -7751,21 +7788,9 @@ async function findFiles(root, patterns) {
7751
7788
  }
7752
7789
  return [...matched].sort();
7753
7790
  }
7754
- async function walk(root, current = root) {
7755
- const entries = await readdir2(current, { withFileTypes: true });
7756
- const files = [];
7757
- for (const entry of entries) {
7758
- if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
7759
- continue;
7760
- }
7761
- const fullPath = join6(current, entry.name);
7762
- if (entry.isDirectory()) {
7763
- files.push(...await walk(root, fullPath));
7764
- } else {
7765
- files.push(relative2(root, fullPath).split("\\").join("/"));
7766
- }
7767
- }
7768
- return files;
7791
+ function matchesConfiguredArtifactPath(path, schema) {
7792
+ const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
7793
+ return Object.values(schema.types).some((definition) => definition.paths.some((pattern) => matchesPattern(normalizedPath, pattern)));
7769
7794
  }
7770
7795
  function matchesPattern(file, pattern) {
7771
7796
  if (!pattern.includes("*")) {
@@ -8242,7 +8267,7 @@ function resolveArtifactContext(graph, opts) {
8242
8267
  }
8243
8268
  if (root) {
8244
8269
  for (const ap of ALWAYS_PRESENT_ITEMS) {
8245
- const fullPath = join6(root, ap.path);
8270
+ const fullPath = join7(root, ap.path);
8246
8271
  let stat;
8247
8272
  try {
8248
8273
  stat = statSync(fullPath);
@@ -8479,6 +8504,7 @@ var init_index = __esm({
8479
8504
  init_packet_constants();
8480
8505
  init_packet_validator();
8481
8506
  init_glob_matcher();
8507
+ init_file_walker();
8482
8508
  init_packet_constants();
8483
8509
  init_target_selector();
8484
8510
  init_packet_assembler();
@@ -8583,7 +8609,7 @@ var init_index = __esm({
8583
8609
 
8584
8610
  // src/packet-prompt-audit.ts
8585
8611
  import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
8586
- import { join as join7 } from "path";
8612
+ import { join as join8 } from "path";
8587
8613
  function promptFilename(target) {
8588
8614
  return `prompt-${target.type}-${target.id}.md`;
8589
8615
  }
@@ -8635,7 +8661,7 @@ async function auditSinglePromptTarget(target, graph, options) {
8635
8661
  }
8636
8662
  if (options.outDir) {
8637
8663
  const filename = promptFilename(target);
8638
- const outPath = join7(options.outDir, filename);
8664
+ const outPath = join8(options.outDir, filename);
8639
8665
  await writeFile4(outPath, prompt, "utf-8");
8640
8666
  entry.outputPath = outPath;
8641
8667
  }
@@ -8741,9 +8767,9 @@ async function auditPromptBatch(root, targets, options, graph) {
8741
8767
  };
8742
8768
  if (options.outDir) {
8743
8769
  await mkdir5(options.outDir, { recursive: true });
8744
- const jsonPath = join7(options.outDir, "prompt-audit-summary.json");
8770
+ const jsonPath = join8(options.outDir, "prompt-audit-summary.json");
8745
8771
  await writeFile4(jsonPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
8746
- const mdPath = join7(options.outDir, "prompt-audit-summary.md");
8772
+ const mdPath = join8(options.outDir, "prompt-audit-summary.md");
8747
8773
  await writeFile4(mdPath, renderPromptAuditSummaryMarkdown(summary), "utf-8");
8748
8774
  }
8749
8775
  return summary;
@@ -8785,7 +8811,7 @@ __export(cli_exports, {
8785
8811
  import yaml2 from "js-yaml";
8786
8812
  import { realpathSync } from "fs";
8787
8813
  import { access as access2, mkdir as mkdir6, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
8788
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join8 } from "path";
8814
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join9 } from "path";
8789
8815
  import { fileURLToPath } from "url";
8790
8816
  async function runCli(argv, io = {}) {
8791
8817
  const parsed = parseArgs(argv);
@@ -8802,7 +8828,7 @@ async function runCli(argv, io = {}) {
8802
8828
  switch (parsed.command) {
8803
8829
  case "init": {
8804
8830
  await initConfig(root);
8805
- out(`Created ${join8(root, "artifact-graph.config.yaml")}
8831
+ out(`Created ${join9(root, "artifact-graph.config.yaml")}
8806
8832
  `);
8807
8833
  return 0;
8808
8834
  }
@@ -9631,7 +9657,8 @@ async function runCli(argv, io = {}) {
9631
9657
  err("Stage or stash the unstaged changes before running changed-only staged refresh.\n");
9632
9658
  return 1;
9633
9659
  }
9634
- const unstagedGraphPaths = changeResult.unstagedPaths.filter(isGraphRelevantPath);
9660
+ const schema = await loadConfig(root);
9661
+ const unstagedGraphPaths = changeResult.unstagedPaths.filter((path) => isGraphRelevantPath(path, schema));
9635
9662
  if (unstagedGraphPaths.length > 0) {
9636
9663
  err("Cannot refresh staged version locks because graph-relevant unstaged changes may affect working-tree hashes:\n");
9637
9664
  for (const conflictPath of unstagedGraphPaths) {
@@ -9740,7 +9767,7 @@ async function runCli(argv, io = {}) {
9740
9767
  err("Usage: artifact-graph validate-review-result --file <path> [--format json]\n");
9741
9768
  return 1;
9742
9769
  }
9743
- const resolvedPath = isAbsolute4(filePath) ? filePath : join8(root, filePath);
9770
+ const resolvedPath = isAbsolute4(filePath) ? filePath : join9(root, filePath);
9744
9771
  let content;
9745
9772
  try {
9746
9773
  content = await readFile4(resolvedPath, "utf-8");
@@ -9778,7 +9805,7 @@ async function runCli(argv, io = {}) {
9778
9805
  const deterministic = checkMode || parsed.flags.deterministic === true;
9779
9806
  const registry = await generateE2eRegistry(root, { deterministic });
9780
9807
  const output = JSON.stringify(registry, null, 2) + "\n";
9781
- const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out : join8(root, "artifacts/tests/e2e/e2e-test-registry.json");
9808
+ const outPath = typeof parsed.flags.out === "string" ? parsed.flags.out : join9(root, "artifacts/tests/e2e/e2e-test-registry.json");
9782
9809
  if (checkMode) {
9783
9810
  let existing = "";
9784
9811
  try {
@@ -9815,7 +9842,7 @@ async function runCli(argv, io = {}) {
9815
9842
  return 1;
9816
9843
  }
9817
9844
  const packageDir = dirname4(fileURLToPath(import.meta.url));
9818
- const contractsDir = typeof parsed.flags["contracts-dir"] === "string" ? parsed.flags["contracts-dir"] : join8(packageDir, "..", "contracts");
9845
+ const contractsDir = typeof parsed.flags["contracts-dir"] === "string" ? parsed.flags["contracts-dir"] : join9(packageDir, "..", "contracts");
9819
9846
  const revisionDigest = typeof parsed.flags["revision-digest"] === "string" ? parsed.flags["revision-digest"] : void 0;
9820
9847
  async function resolveContract(contractId) {
9821
9848
  const catalog = await loadContractCatalog(contractsDir);
@@ -10078,11 +10105,11 @@ function hasBlockingVersionIssues(issues, strictMissingLock) {
10078
10105
  severity: issue2.severity
10079
10106
  }, strictMissingLock));
10080
10107
  }
10081
- function isGraphRelevantPath(path) {
10082
- return path === "artifact-graph.config.yaml" || path === VERSION_LOCK_PATH || path.startsWith("artifacts/") || /\.(md|mdx|json|ya?ml|ts|tsx|js|jsx|mts|cts|rs|py|go)$/.test(path);
10108
+ function isGraphRelevantPath(path, schema) {
10109
+ return path === "artifact-graph.config.yaml" || path === VERSION_LOCK_PATH || matchesConfiguredArtifactPath(path, schema);
10083
10110
  }
10084
10111
  async function initConfig(root) {
10085
- const configPath = join8(root, "artifact-graph.config.yaml");
10112
+ const configPath = join9(root, "artifact-graph.config.yaml");
10086
10113
  try {
10087
10114
  await access2(configPath);
10088
10115
  throw new Error(`Config already exists: ${configPath}`);