artifact-graph 0.8.3 → 0.8.5

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/index.js CHANGED
@@ -335,6 +335,41 @@ var init_glob_matcher = __esm({
335
335
  }
336
336
  });
337
337
 
338
+ // src/file-walker.ts
339
+ import { readdir } from "fs/promises";
340
+ import { join, relative } from "path";
341
+ async function walkFiles(root, current = root, readDirectory = defaultReadDirectory) {
342
+ let entries;
343
+ try {
344
+ entries = await readDirectory(current, { withFileTypes: true });
345
+ } catch (error) {
346
+ if (current !== root && error.code === "ENOENT") {
347
+ return [];
348
+ }
349
+ throw error;
350
+ }
351
+ const files = [];
352
+ for (const entry of entries) {
353
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
354
+ continue;
355
+ }
356
+ const fullPath = join(current, entry.name);
357
+ if (entry.isDirectory()) {
358
+ files.push(...await walkFiles(root, fullPath, readDirectory));
359
+ } else {
360
+ files.push(relative(root, fullPath).split("\\").join("/"));
361
+ }
362
+ }
363
+ return files;
364
+ }
365
+ var defaultReadDirectory;
366
+ var init_file_walker = __esm({
367
+ "src/file-walker.ts"() {
368
+ "use strict";
369
+ defaultReadDirectory = readdir;
370
+ }
371
+ });
372
+
338
373
  // src/target-selector.ts
339
374
  function parseTargetSelector(value) {
340
375
  const separator = value.indexOf(":");
@@ -849,7 +884,7 @@ var init_packet_assembler = __esm({
849
884
 
850
885
  // src/packet-audit.ts
851
886
  import { mkdir, writeFile } from "fs/promises";
852
- import { join } from "path";
887
+ import { join as join2 } from "path";
853
888
  function parseTargetsFile(content, schema) {
854
889
  const validTypes = schema ? new Set(getTargetArtifactTypes(schema)) : VALID_TYPES;
855
890
  const validTypesLabel = [...validTypes].join(", ");
@@ -940,7 +975,7 @@ async function auditSingleTarget(target, graph, options) {
940
975
  const fmt = options.format ?? "markdown";
941
976
  const ext = fmt === "json" ? "json" : "md";
942
977
  const filename = `${target.type}-${target.id}.packet.${ext}`;
943
- const outPath = join(options.outDir, filename);
978
+ const outPath = join2(options.outDir, filename);
944
979
  let content;
945
980
  if (fmt === "json") {
946
981
  content = JSON.stringify(packet, null, 2) + "\n";
@@ -1020,7 +1055,7 @@ async function auditPackets(root, targets, options, graph) {
1020
1055
  ...isCompact ? { summaryDetail: "compact", countsByType } : {}
1021
1056
  };
1022
1057
  if (options.outDir) {
1023
- const summaryPath = join(options.outDir, "summary.json");
1058
+ const summaryPath = join2(options.outDir, "summary.json");
1024
1059
  await writeFile(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
1025
1060
  }
1026
1061
  return summary;
@@ -1392,7 +1427,7 @@ var init_packet_prompt_validator = __esm({
1392
1427
  import { createHash } from "crypto";
1393
1428
  import { existsSync } from "fs";
1394
1429
  import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
1395
- import { dirname, join as join2, relative } from "path";
1430
+ import { dirname, join as join3, relative as relative2 } from "path";
1396
1431
  async function buildVersionIndex(root, graph) {
1397
1432
  const scannedGraph = graph ?? await scanArtifacts(root);
1398
1433
  const hashCache = /* @__PURE__ */ new Map();
@@ -1528,7 +1563,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1528
1563
  for (const entry of lock.locks) {
1529
1564
  if (entry.kind !== "verifies") continue;
1530
1565
  const sourcePath = entry.source.path;
1531
- const fullSourcePath = join2(root, sourcePath);
1566
+ const fullSourcePath = join3(root, sourcePath);
1532
1567
  if (!existsSync(fullSourcePath)) continue;
1533
1568
  let liveness = livenessCache.get(sourcePath);
1534
1569
  if (liveness === void 0) {
@@ -1540,6 +1575,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1540
1575
  status: "orphan_lock",
1541
1576
  edgeId: entry.edgeId,
1542
1577
  message: `Liveness: ${sourcePath} is not active in any configured runner \u2014 locked verifies edges may reference dead tests`,
1578
+ severity: "warning",
1543
1579
  artifact: entry.artifact,
1544
1580
  source: entry.source
1545
1581
  });
@@ -1622,14 +1658,14 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1622
1658
  currentArtifactHash: targetNode.contentHash
1623
1659
  });
1624
1660
  }
1625
- if (!existsSync(join2(root, sourceNode.path))) {
1661
+ if (!existsSync(join3(root, sourceNode.path))) {
1626
1662
  entryIssues.push({
1627
1663
  status: "orphan_lock",
1628
1664
  edgeId: relEdgeId,
1629
1665
  message: `Artifact relation source file ${sourceNode.path} no longer exists`
1630
1666
  });
1631
1667
  }
1632
- if (!existsSync(join2(root, targetNode.path))) {
1668
+ if (!existsSync(join3(root, targetNode.path))) {
1633
1669
  entryIssues.push({
1634
1670
  status: "orphan_lock",
1635
1671
  edgeId: relEdgeId,
@@ -1669,7 +1705,84 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1669
1705
  fresh,
1670
1706
  totalArtifactRelationLocks: artifactRelationLocks.length,
1671
1707
  artifactRelationFresh,
1672
- issues: sortBy(issues, (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1708
+ issues: sortBy(issues.map(enrichVersionLockIssue), (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1709
+ };
1710
+ }
1711
+ function isLivenessIssue(issue2) {
1712
+ return issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX);
1713
+ }
1714
+ function versionLockIssueSeverity(issue2) {
1715
+ if (issue2.severity) {
1716
+ return issue2.severity;
1717
+ }
1718
+ if (issue2.status === "missing_lock") {
1719
+ return "warning";
1720
+ }
1721
+ if (issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX)) {
1722
+ return "warning";
1723
+ }
1724
+ return "error";
1725
+ }
1726
+ function isVersionLockIssueBlocking(issue2, strictMissingLock) {
1727
+ if (issue2.status === "missing_lock") {
1728
+ return strictMissingLock;
1729
+ }
1730
+ return versionLockIssueSeverity(issue2) === "error";
1731
+ }
1732
+ function versionLockIssueRemediation(issue2) {
1733
+ switch (issue2.status) {
1734
+ case "artifact_changed":
1735
+ return [
1736
+ "\u786E\u8BA4\u8FD9\u6B21\u5236\u54C1\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1737
+ "\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown`\uFF08\u65E5\u5E38\u5F00\u53D1\uFF09\u6216 `artifact-graph version-lock refresh --changed-only --staged --format markdown`\uFF08\u63D0\u4EA4\u524D\uFF09\u5237\u65B0\u53D7\u5F71\u54CD\u7684\u9501\u3002",
1738
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u9501\u53D8\u66F4\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u5E76\u91CD\u65B0\u63D0\u4EA4\u3002"
1739
+ ];
1740
+ case "source_changed":
1741
+ return [
1742
+ "\u786E\u8BA4\u8FD9\u6B21\u6E90\u7801/\u6D4B\u8BD5\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1743
+ "\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown`\uFF08\u65E5\u5E38\u5F00\u53D1\uFF09\u6216 `artifact-graph version-lock refresh --changed-only --staged --format markdown`\uFF08\u63D0\u4EA4\u524D\uFF09\u5237\u65B0\u53D7\u5F71\u54CD\u7684\u9501\u3002",
1744
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u9501\u53D8\u66F4\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u5E76\u91CD\u65B0\u63D0\u4EA4\u3002"
1745
+ ];
1746
+ case "verified_by_changed":
1747
+ return [
1748
+ "\u786E\u8BA4\u8FD9\u6B21\u9A8C\u8BC1\u6587\u4EF6\u53D8\u66F4\u662F\u6709\u610F\u7684\u3002",
1749
+ "\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown`\uFF08\u65E5\u5E38\u5F00\u53D1\uFF09\u6216 `artifact-graph version-lock refresh --changed-only --staged --format markdown`\uFF08\u63D0\u4EA4\u524D\uFF09\u5237\u65B0\u53D7\u5F71\u54CD\u7684\u9501\u3002",
1750
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u9501\u53D8\u66F4\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u5E76\u91CD\u65B0\u63D0\u4EA4\u3002"
1751
+ ];
1752
+ case "missing_lock":
1753
+ return [
1754
+ "\u65B0\u589E\u8FFD\u6EAF\u8FB9\u8FD8\u6CA1\u6709\u5BF9\u5E94\u7248\u672C\u9501\uFF1B\u9ED8\u8BA4\u4E0D\u963B\u65AD\uFF0C`--strict-missing-lock` \u4E0B\u4F1A\u963B\u65AD\u3002",
1755
+ "\u8FD0\u884C `artifact-graph version-lock refresh --changed-only --worktree --format markdown` \u4E3A\u53D7\u5F71\u54CD\u8FB9\u8865\u9501\uFF1B\u9996\u6B21\u5EFA\u7ACB\u57FA\u7EBF\u6216\u914D\u7F6E\u53D8\u66F4\u65F6\u7528 `artifact-graph version-lock refresh --all --format markdown`\u3002",
1756
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002"
1757
+ ];
1758
+ case "orphan_lock":
1759
+ if (isLivenessIssue(issue2)) {
1760
+ return [
1761
+ "\u8FD9\u662F liveness \u8B66\u544A\uFF0C\u4E0D\u662F\u6821\u9A8C\u5931\u8D25\uFF1A\u9501\u4ECD\u7136\u4FDD\u7559\uFF0C\u9ED8\u8BA4\u4E0D\u963B\u65AD\u3002",
1762
+ "\u5982\u679C\u6D4B\u8BD5\u5DF2\u5E9F\u5F03\uFF1A\u5220\u9664\u8BE5\u6587\u4EF6\u6216\u79FB\u9664\u5176\u4E2D\u7684\u8FFD\u6EAF\u6CE8\u91CA\uFF0C\u7136\u540E\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\u5BF9\u5E94\u7684\u9501\u3002",
1763
+ "\u5982\u679C\u6D4B\u8BD5\u4ECD\u7136\u6709\u6548\uFF1A\u628A\u5B83\u52A0\u5165 artifact-graph.config.yaml \u4E2D\u67D0\u4E2A e2e runner \u7684 include\uFF0C\u6216\u68C0\u67E5 exclude/testIgnore \u662F\u5426\u8BEF\u4F24\u3002"
1764
+ ];
1765
+ }
1766
+ return [
1767
+ "\u9501\u5F15\u7528\u7684\u5236\u54C1\u3001\u6E90\u7801\u6216\u8FFD\u6EAF\u8FB9\u5728\u5F53\u524D\u56FE\u4E2D\u5DF2\u4E0D\u5B58\u5728\uFF0C\u5E38\u89C1\u4E8E\u5236\u54C1\u5220\u9664\u6216\u62C6\u5206\u4E4B\u540E\u3002",
1768
+ "\u786E\u8BA4\u5220\u9664\u662F\u6709\u610F\u7684\u540E\uFF0C\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\u5B64\u7ACB\u9501\u3002",
1769
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u88AB\u79FB\u9664\u7684\u9501\u6761\u76EE\uFF0C\u786E\u8BA4\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002",
1770
+ "\u5982\u679C\u5220\u9664\u662F\u8BEF\u64CD\u4F5C\uFF0C\u5148\u6062\u590D\u5BF9\u5E94\u5236\u54C1\u6216\u6E90\u7801\u6587\u4EF6\uFF0C\u518D\u8FD0\u884C\u4E0D\u5E26 `--remove-orphans` \u7684 refresh \u5237\u65B0\u54C8\u5E0C\u3002"
1771
+ ];
1772
+ case "target_not_found":
1773
+ return [
1774
+ "\u68C0\u67E5 `--target` \u7684 `type:id` \u662F\u5426\u62FC\u5199\u6B63\u786E\u3002",
1775
+ "\u5982\u679C\u8BE5\u5236\u54C1\u5DF2\u88AB\u5220\u9664\u6216\u6539\u540D\uFF0C\u6539\u7528\u5F53\u524D\u5B58\u5728\u7684\u5236\u54C1\u6807\u8BC6\u91CD\u8BD5\u3002"
1776
+ ];
1777
+ default:
1778
+ return [];
1779
+ }
1780
+ }
1781
+ function enrichVersionLockIssue(issue2) {
1782
+ return {
1783
+ ...issue2,
1784
+ severity: versionLockIssueSeverity(issue2),
1785
+ remediation: issue2.remediation ?? versionLockIssueRemediation(issue2)
1673
1786
  };
1674
1787
  }
1675
1788
  async function updateVersionLock(root, options) {
@@ -1967,59 +2080,118 @@ async function traceVersion(root, target, lockPath = VERSION_LOCK_PATH) {
1967
2080
  currentEdges: index.edges.filter((edge2) => edge2.from === targetUid || edge2.to === targetUid),
1968
2081
  locks: lock.locks.filter((entry) => `${entry.artifact.type}:${entry.artifact.id}` === targetUid),
1969
2082
  artifactRelations: targetArtifactRelations,
1970
- issues: [...targetIssues, ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
2083
+ issues: [...targetIssues.map(enrichVersionLockIssue), ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
1971
2084
  };
1972
2085
  }
1973
- function renderVersionLockAuditMarkdown(result) {
2086
+ function renderIssueBlockingTag(issue2, strictMissingLock) {
2087
+ return isVersionLockIssueBlocking(issue2, strictMissingLock) ? "\u963B\u65AD" : "\u8B66\u544A";
2088
+ }
2089
+ function renderIssueBlockingExplanation(issue2, strictMissingLock) {
2090
+ if (!isVersionLockIssueBlocking(issue2, strictMissingLock)) {
2091
+ return "\u5426\uFF08\u4EC5\u63D0\u9192\uFF0C\u4E0D\u963B\u65AD\uFF09";
2092
+ }
2093
+ if (issue2.status === "missing_lock" && versionLockIssueSeverity(issue2) !== "error") {
2094
+ return "\u662F\uFF08\u5DF2\u7531 --strict-missing-lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09";
2095
+ }
2096
+ return "\u662F\uFF08\u4F1A\u4F7F\u547D\u4EE4\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF09";
2097
+ }
2098
+ function appendIssueDetails(lines, issues, strictMissingLock) {
2099
+ issues.forEach((issue2, index) => {
2100
+ const label = VERSION_LOCK_STATUS_LABELS[issue2.status] ?? issue2.status;
2101
+ lines.push(`### ${index + 1}. [${renderIssueBlockingTag(issue2, strictMissingLock)}] ${label}\uFF08\`${issue2.status}\`\uFF09`);
2102
+ lines.push("");
2103
+ lines.push(`- \u8FB9: \`${issue2.edgeId}\``);
2104
+ lines.push(`- \u8BE6\u60C5: ${issue2.message}`);
2105
+ lines.push(`- \u662F\u5426\u963B\u65AD: ${renderIssueBlockingExplanation(issue2, strictMissingLock)}`);
2106
+ const remediation = issue2.remediation ?? [];
2107
+ if (remediation.length > 0) {
2108
+ lines.push("- \u89E3\u51B3\u6B65\u9AA4:");
2109
+ remediation.forEach((step, stepIndex) => {
2110
+ lines.push(` ${stepIndex + 1}. ${step}`);
2111
+ });
2112
+ }
2113
+ lines.push("");
2114
+ });
2115
+ }
2116
+ function renderVersionLockAuditMarkdown(result, options = {}) {
2117
+ const strictMissingLock = options.strictMissingLock === true;
2118
+ const blockingCount = result.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2119
+ const nonBlockingCount = result.issues.length - blockingCount;
1974
2120
  const lines = [
1975
- "# Version Lock Audit",
2121
+ "# \u7248\u672C\u9501\u5BA1\u8BA1\uFF08version-lock audit\uFF09",
1976
2122
  "",
1977
- `Root: \`${result.root}\``,
1978
- `Lock: \`${result.lockPath}\``,
1979
- `Locks: ${result.totalLocks} | Fresh: ${result.fresh} | Issues: ${result.issues.length}`,
1980
- `Artifact Relations: ${result.totalArtifactRelationLocks} | Fresh: ${result.artifactRelationFresh}`,
2123
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2124
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2125
+ `- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: ${result.totalLocks}\uFF08\u65B0\u9C9C ${result.fresh}\uFF09`,
2126
+ `- \u5236\u54C1\u5173\u7CFB\u9501: ${result.totalArtifactRelationLocks}\uFF08\u65B0\u9C9C ${result.artifactRelationFresh}\uFF09`,
2127
+ `- \u963B\u65AD\u7B56\u7565: ${strictMissingLock ? "--strict-missing-lock\uFF08missing_lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09" : "\u9ED8\u8BA4\uFF08missing_lock \u4E0D\u963B\u65AD\uFF09"}`,
2128
+ `- \u95EE\u9898: ${result.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
1981
2129
  ""
1982
2130
  ];
1983
2131
  if (result.issues.length === 0) {
1984
- lines.push("No version lock issues.");
2132
+ lines.push("\u672A\u53D1\u73B0\u7248\u672C\u9501\u95EE\u9898\u3002");
1985
2133
  return `${lines.join("\n")}
1986
2134
  `;
1987
2135
  }
1988
- for (const issue2 of result.issues) {
1989
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2136
+ if (blockingCount > 0) {
2137
+ lines.push(`\u5F53\u524D\u7B56\u7565\u4E0B\u5B58\u5728 ${blockingCount} \u4E2A\u963B\u65AD\u95EE\u9898\uFF0C\u547D\u4EE4\u5C06\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF1B\u8BF7\u6309\u4E0B\u65B9\u6B65\u9AA4\u9010\u9879\u5904\u7406\u3002`);
2138
+ } else {
2139
+ lines.push("\u5F53\u524D\u7B56\u7565\u4E0B\u6CA1\u6709\u963B\u65AD\u95EE\u9898\uFF0C\u4EE5\u4E0B\u4EC5\u4E3A\u63D0\u9192\uFF0C\u547D\u4EE4\u4EE5\u96F6\u9000\u51FA\u7801\u7ED3\u675F\u3002");
1990
2140
  }
2141
+ lines.push("");
2142
+ lines.push("## \u95EE\u9898\u6E05\u5355");
2143
+ lines.push("");
2144
+ appendIssueDetails(lines, result.issues, strictMissingLock);
1991
2145
  return `${lines.join("\n")}
1992
2146
  `;
1993
2147
  }
1994
2148
  function renderVersionLockRefreshMarkdown(result) {
2149
+ const strictMissingLock = true;
2150
+ const blockingCount = result.postAudit.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2151
+ const nonBlockingCount = result.postAudit.issues.length - blockingCount;
1995
2152
  const lines = [
1996
- "# Version Lock Refresh",
2153
+ "# \u7248\u672C\u9501\u5237\u65B0\uFF08version-lock refresh\uFF09",
2154
+ "",
2155
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2156
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2157
+ `- \u6A21\u5F0F: \`${result.mode}\`\uFF08\u53D8\u66F4\u8DEF\u5F84 ${result.changedPaths.length} \u4E2A\uFF0C\u53D7\u5F71\u54CD\u8FB9 ${result.affectedEdges.length} \u6761\uFF09`,
2158
+ `- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: \u65B0\u589E ${result.addedLocks.length} | \u66F4\u65B0 ${result.updatedLocks.length} | \u4FDD\u7559\u5B64\u7ACB ${result.retainedOrphans.length} | \u5220\u9664\u5B64\u7ACB ${result.removedOrphans.length}`,
2159
+ `- \u5236\u54C1\u5173\u7CFB\u9501: \u65B0\u589E ${result.addedArtifactRelationLocks.length} | \u66F4\u65B0 ${result.updatedArtifactRelationLocks.length} | \u4FDD\u7559\u5B64\u7ACB ${result.retainedArtifactRelationOrphans.length} | \u5220\u9664 ${result.removedArtifactRelationLocks.length}`,
2160
+ `- \u963B\u65AD\u7B56\u7565: refresh \u56FA\u5B9A\u6309 --strict-missing-lock \u5224\u5B9A\uFF0Cmissing_lock \u4E5F\u4F1A\u963B\u65AD`,
2161
+ `- \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898: ${result.postAudit.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
1997
2162
  "",
1998
- `Root: \`${result.root}\``,
1999
- `Lock: \`${result.lockPath}\``,
2000
- `Mode: \`${result.mode}\``,
2001
- `Changed paths: ${result.changedPaths.length}`,
2002
- `Affected edges: ${result.affectedEdges.length}`,
2003
- `Added: ${result.addedLocks.length} | Updated: ${result.updatedLocks.length} | Retained orphans: ${result.retainedOrphans.length} | Removed orphans: ${result.removedOrphans.length}`,
2004
- `Artifact Relations \u2014 Added: ${result.addedArtifactRelationLocks.length} | Updated: ${result.updatedArtifactRelationLocks.length} | Retained orphans: ${result.retainedArtifactRelationOrphans.length} | Removed: ${result.removedArtifactRelationLocks.length}`,
2005
- `Post-audit issues: ${result.postAudit.issues.length}`,
2163
+ "\u9501\u6587\u4EF6\u5DF2\u5199\u5165\u3002\u82E5\u9501\u6587\u4EF6\u76F8\u5BF9\u6682\u5B58\u533A\u6709\u53D8\u5316\uFF08\u4F8B\u5982 pre-commit \u573A\u666F\uFF09\uFF0C\u8BF7\u5148\u5BA1\u67E5\u5E76\u6682\u5B58\uFF1A",
2164
+ "",
2165
+ "1. `git diff artifacts/traceability-version-lock.json`",
2166
+ "2. `git add artifacts/traceability-version-lock.json`",
2167
+ "3. \u91CD\u65B0\u6267\u884C `git commit`",
2006
2168
  ""
2007
2169
  ];
2008
- appendList(lines, "Added Locks", result.addedLocks);
2009
- appendList(lines, "Updated Locks", result.updatedLocks);
2010
- appendList(lines, "Retained Orphans", result.retainedOrphans);
2011
- appendList(lines, "Removed Orphans", result.removedOrphans);
2012
- appendList(lines, "Added Artifact Relation Locks", result.addedArtifactRelationLocks);
2013
- appendList(lines, "Updated Artifact Relation Locks", result.updatedArtifactRelationLocks);
2014
- appendList(lines, "Retained Artifact Relation Orphans", result.retainedArtifactRelationOrphans);
2015
- appendList(lines, "Removed Artifact Relation Locks", result.removedArtifactRelationLocks);
2016
- appendList(lines, "Warnings", result.warnings);
2170
+ appendList(lines, "\u65B0\u589E\u7684\u9501", result.addedLocks);
2171
+ appendList(lines, "\u66F4\u65B0\u7684\u9501", result.updatedLocks);
2172
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u9501", result.retainedOrphans);
2173
+ if (result.retainedOrphans.length > 0) {
2174
+ lines.push("> \u4EE5\u4E0A\u662F\u5DF2\u5220\u9664/\u62C6\u5206\u5236\u54C1\u6216\u5931\u6548\u8FFD\u6EAF\u8FB9\u9057\u7559\u7684\u9501\u3002\u786E\u8BA4\u4E0D\u518D\u9700\u8981\u540E\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\uFF0C\u5BA1\u67E5 `git diff artifacts/traceability-version-lock.json` \u540E\u518D\u6682\u5B58\u3002");
2175
+ lines.push("");
2176
+ }
2177
+ appendList(lines, "\u5220\u9664\u7684\u5B64\u7ACB\u9501", result.removedOrphans);
2178
+ appendList(lines, "\u65B0\u589E\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.addedArtifactRelationLocks);
2179
+ appendList(lines, "\u66F4\u65B0\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.updatedArtifactRelationLocks);
2180
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u5236\u54C1\u5173\u7CFB\u9501", result.retainedArtifactRelationOrphans);
2181
+ if (result.retainedArtifactRelationOrphans.length > 0) {
2182
+ lines.push("> \u4EE5\u4E0A\u662F\u5931\u6548\u5236\u54C1\u5173\u7CFB\u9057\u7559\u7684\u9501\u3002\u786E\u8BA4\u540E\u8FD0\u884C `artifact-graph version-lock refresh --all --remove-orphans --format markdown` \u6E05\u7406\uFF0C\u5BA1\u67E5\u9501 diff \u540E\u518D\u6682\u5B58\u3002");
2183
+ lines.push("");
2184
+ }
2185
+ appendList(lines, "\u5220\u9664\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.removedArtifactRelationLocks);
2186
+ appendList(lines, "\u63D0\u9192", result.warnings);
2017
2187
  if (result.postAudit.issues.length > 0) {
2018
- lines.push("## Post-Audit Issues");
2019
- for (const issue2 of result.postAudit.issues) {
2020
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2188
+ if (blockingCount > 0) {
2189
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`);
2190
+ } else {
2191
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u5F53\u524D\u7B56\u7565\u4E0B\u5168\u90E8\u4E0D\u963B\u65AD\uFF09`);
2021
2192
  }
2022
2193
  lines.push("");
2194
+ appendIssueDetails(lines, result.postAudit.issues, strictMissingLock);
2023
2195
  }
2024
2196
  return `${lines.join("\n")}
2025
2197
  `;
@@ -2066,7 +2238,7 @@ function renderTraceVersionMarkdown(result) {
2066
2238
  async function readVersionLock(root, lockPath) {
2067
2239
  const safeLockPath = normalizeRelativePath(root, lockPath);
2068
2240
  try {
2069
- const raw = await readFile(join2(root, safeLockPath), "utf-8");
2241
+ const raw = await readFile(join3(root, safeLockPath), "utf-8");
2070
2242
  let parsed;
2071
2243
  try {
2072
2244
  parsed = JSON.parse(raw);
@@ -2225,7 +2397,7 @@ function requireSafeRelativePath(value, path) {
2225
2397
  }
2226
2398
  async function writeVersionLock(root, lockPath, lock) {
2227
2399
  const safeLockPath = normalizeRelativePath(root, lockPath);
2228
- const fullPath = join2(root, safeLockPath);
2400
+ const fullPath = join3(root, safeLockPath);
2229
2401
  await mkdir2(dirname(fullPath), { recursive: true });
2230
2402
  await writeFile2(fullPath, `${JSON.stringify(lock, null, 2)}
2231
2403
  `);
@@ -2408,14 +2580,14 @@ async function hashRelativePath(root, path, cache) {
2408
2580
  const normalized = normalizeRelativePath(root, path);
2409
2581
  const cached = cache.get(normalized);
2410
2582
  if (cached) return cached;
2411
- const content = await readFile(join2(root, normalized));
2583
+ const content = await readFile(join3(root, normalized));
2412
2584
  const hash = `sha256:${createHash("sha256").update(content).digest("hex")}`;
2413
2585
  cache.set(normalized, hash);
2414
2586
  return hash;
2415
2587
  }
2416
2588
  function normalizeRelativePath(root, path) {
2417
2589
  const normalized = path.replace(/\\/g, "/");
2418
- const relativePath = normalized.startsWith("/") ? relative(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
2590
+ const relativePath = normalized.startsWith("/") ? relative2(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
2419
2591
  if (relativePath === ".." || relativePath.startsWith("../")) {
2420
2592
  throw new Error(`Path is outside root: ${path}`);
2421
2593
  }
@@ -2431,7 +2603,7 @@ async function getTestFileRunnerLiveness(root, filePath, config) {
2431
2603
  if (!/e2e/i.test(filePath) && !/\.e2e\./i.test(filePath)) {
2432
2604
  return "active";
2433
2605
  }
2434
- const fullSourcePath = join2(root, filePath);
2606
+ const fullSourcePath = join3(root, filePath);
2435
2607
  if (!existsSync(fullSourcePath)) return "inactive";
2436
2608
  try {
2437
2609
  const content = await readFile(fullSourcePath, "utf-8");
@@ -2474,7 +2646,7 @@ async function isFileActiveInRunner(root, filePath, runner) {
2474
2646
  function sortUnique(items) {
2475
2647
  return [...new Set(items)].sort((left, right) => left.localeCompare(right));
2476
2648
  }
2477
- var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
2649
+ var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION, LIVENESS_MESSAGE_PREFIX, VERSION_LOCK_STATUS_LABELS;
2478
2650
  var init_versioned_traceability = __esm({
2479
2651
  "src/versioned-traceability.ts"() {
2480
2652
  "use strict";
@@ -2483,6 +2655,16 @@ var init_versioned_traceability = __esm({
2483
2655
  VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
2484
2656
  VERSION_INDEX_SCHEMA_VERSION = "1.0";
2485
2657
  VERSION_LOCK_SCHEMA_VERSION = "1.0";
2658
+ LIVENESS_MESSAGE_PREFIX = "Liveness:";
2659
+ VERSION_LOCK_STATUS_LABELS = {
2660
+ fresh: "\u65B0\u9C9C",
2661
+ target_not_found: "\u76EE\u6807\u5236\u54C1\u4E0D\u5B58\u5728",
2662
+ artifact_changed: "\u5236\u54C1\u5DF2\u53D8\u5316",
2663
+ source_changed: "\u6E90\u7801/\u6D4B\u8BD5\u5DF2\u53D8\u5316",
2664
+ verified_by_changed: "\u9A8C\u8BC1\u6587\u4EF6\u5DF2\u53D8\u5316",
2665
+ missing_lock: "\u7F3A\u5C11\u7248\u672C\u9501",
2666
+ orphan_lock: "\u5B64\u7ACB\u9501"
2667
+ };
2486
2668
  }
2487
2669
  });
2488
2670
 
@@ -2490,7 +2672,7 @@ var init_versioned_traceability = __esm({
2490
2672
  import { execFile } from "child_process";
2491
2673
  import { constants } from "fs";
2492
2674
  import { access } from "fs/promises";
2493
- import { isAbsolute, join as join3, resolve } from "path";
2675
+ import { isAbsolute, join as join4, resolve } from "path";
2494
2676
  import { promisify } from "util";
2495
2677
  async function resolveArtifactGraphCli(root, options = {}) {
2496
2678
  const pathCli = await findCommandOnPath("artifact-graph");
@@ -2498,7 +2680,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
2498
2680
  const candidates = [
2499
2681
  {
2500
2682
  source: "node_modules",
2501
- path: join3(root, "node_modules/.bin/artifact-graph"),
2683
+ path: join4(root, "node_modules/.bin/artifact-graph"),
2502
2684
  exists: false
2503
2685
  },
2504
2686
  {
@@ -2531,8 +2713,8 @@ async function resolveArtifactGraphCli(root, options = {}) {
2531
2713
  }
2532
2714
  async function doctorArtifactChain(root, options = {}) {
2533
2715
  const cli = await resolveArtifactGraphCli(root, options);
2534
- const configPath = join3(root, "artifact-graph.config.yaml");
2535
- const lockPath = join3(root, VERSION_LOCK_PATH);
2716
+ const configPath = join4(root, "artifact-graph.config.yaml");
2717
+ const lockPath = join4(root, VERSION_LOCK_PATH);
2536
2718
  const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
2537
2719
  const nodeCompatible = isNodeCompatible(process.versions.node);
2538
2720
  const warnings = [
@@ -2757,7 +2939,7 @@ var init_git_hook_path = __esm({
2757
2939
  import { constants as constants2 } from "fs";
2758
2940
  import { randomUUID } from "crypto";
2759
2941
  import { lstat, mkdir as mkdir3, open, readlink, rename, unlink } from "fs/promises";
2760
- import { basename, dirname as dirname2, join as join4 } from "path";
2942
+ import { basename, dirname as dirname2, join as join5 } from "path";
2761
2943
  function detectHookInterpreter(content) {
2762
2944
  if (content.trim().length === 0) {
2763
2945
  return "empty";
@@ -3069,7 +3251,7 @@ async function removeHookAtomically(hookPath, snapshot) {
3069
3251
  }
3070
3252
  async function writeHookAtomically(hookPath, content, snapshot, mode) {
3071
3253
  await mkdir3(dirname2(hookPath), { recursive: true });
3072
- const temporaryPath = join4(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
3254
+ const temporaryPath = join5(dirname2(hookPath), `.${basename(hookPath)}.${randomUUID()}.tmp`);
3073
3255
  let temporaryExists = false;
3074
3256
  try {
3075
3257
  const temporary = await open(temporaryPath, "wx", mode);
@@ -3507,8 +3689,8 @@ var init_review_result_validator = __esm({
3507
3689
 
3508
3690
  // src/contract-kernel.ts
3509
3691
  import { createHash as createHash2 } from "crypto";
3510
- import { readFile as readFile2, readdir } from "fs/promises";
3511
- import { join as join5 } from "path";
3692
+ import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
3693
+ import { join as join6 } from "path";
3512
3694
  import _AjvModule from "ajv";
3513
3695
  function isOfficialNamespace(namespace) {
3514
3696
  return OFFICIAL_NAMESPACE_PATTERN.test(namespace);
@@ -3837,10 +4019,10 @@ async function loadContract(contractPath, options) {
3837
4019
  }
3838
4020
  async function loadContractsFromDirectory(contractsDir, options) {
3839
4021
  const contracts = [];
3840
- const entries = await readdir(contractsDir, { withFileTypes: true });
4022
+ const entries = await readdir2(contractsDir, { withFileTypes: true });
3841
4023
  for (const entry of entries) {
3842
4024
  if (entry.isDirectory()) {
3843
- const schemaPath = join5(contractsDir, entry.name, "schema.json");
4025
+ const schemaPath = join6(contractsDir, entry.name, "schema.json");
3844
4026
  const contract = await loadContract(schemaPath, options);
3845
4027
  contracts.push(contract);
3846
4028
  }
@@ -4285,8 +4467,8 @@ import Database from "better-sqlite3";
4285
4467
  import matter from "gray-matter";
4286
4468
  import yaml from "js-yaml";
4287
4469
  import { accessSync, constants as fsConstants, existsSync as existsSync2, statSync } from "fs";
4288
- import { mkdir as mkdir4, readFile as readFile3, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
4289
- import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve3 } from "path";
4470
+ import { mkdir as mkdir4, readFile as readFile3, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
4471
+ import { basename as basename3, dirname as dirname3, extname, isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3 } from "path";
4290
4472
  function isTargetArtifactType(type) {
4291
4473
  return isPacketTargetType(type);
4292
4474
  }
@@ -4328,7 +4510,7 @@ function resolveArtifactTypeName(schema, token) {
4328
4510
  return void 0;
4329
4511
  }
4330
4512
  async function loadConfig(root) {
4331
- const configPath = join6(root, "artifact-graph.config.yaml");
4513
+ const configPath = join7(root, "artifact-graph.config.yaml");
4332
4514
  let parsed = {};
4333
4515
  try {
4334
4516
  const raw = await readFile3(configPath, "utf-8");
@@ -4505,7 +4687,7 @@ async function scanArtifacts(root, schema) {
4505
4687
  continue;
4506
4688
  }
4507
4689
  scannedFiles.set(file, type);
4508
- const raw = await readFile3(join6(root, file), "utf-8");
4690
+ const raw = await readFile3(join7(root, file), "utf-8");
4509
4691
  const parsed = parseFile(type, file, raw, config);
4510
4692
  nodes.push(...parsed.nodes);
4511
4693
  edges.push(...parsed.edges);
@@ -4817,7 +4999,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
4817
4999
  const indexPath = "artifacts/prd/feature-index.md";
4818
5000
  let raw = "";
4819
5001
  try {
4820
- raw = await readFile3(join6(root, indexPath), "utf-8");
5002
+ raw = await readFile3(join7(root, indexPath), "utf-8");
4821
5003
  } catch (error) {
4822
5004
  if (error.code === "ENOENT") {
4823
5005
  return [];
@@ -5007,11 +5189,11 @@ function nextId(graph, schema, type, rangeName) {
5007
5189
  throw new Error(`ID range ${type}.${rangeName} is exhausted`);
5008
5190
  }
5009
5191
  async function writeGraphCache(root, graph) {
5010
- const cacheDir = join6(root, ".artifact-graph");
5192
+ const cacheDir = join7(root, ".artifact-graph");
5011
5193
  await mkdir4(cacheDir, { recursive: true });
5012
- await writeFile3(join6(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5194
+ await writeFile3(join7(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5013
5195
  `);
5014
- const db = new Database(join6(cacheDir, "graph.sqlite"));
5196
+ const db = new Database(join7(cacheDir, "graph.sqlite"));
5015
5197
  try {
5016
5198
  db.exec(`
5017
5199
  DROP TABLE IF EXISTS nodes;
@@ -6694,10 +6876,10 @@ function validateE2eRegistry(graph) {
6694
6876
  async function validateExecutableTraceability(root, config) {
6695
6877
  const issues = [];
6696
6878
  const schema = config ?? await loadConfig(root);
6697
- const e2eDir = join6(root, "artifacts", "tests", "e2e");
6879
+ const e2eDir = join7(root, "artifacts", "tests", "e2e");
6698
6880
  let e2eFiles;
6699
6881
  try {
6700
- e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
6882
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
6701
6883
  } catch {
6702
6884
  return [];
6703
6885
  }
@@ -6707,7 +6889,7 @@ async function validateExecutableTraceability(root, config) {
6707
6889
  const mdBatches = /* @__PURE__ */ new Set();
6708
6890
  for (const filePath of e2eFiles) {
6709
6891
  const raw = await readFile3(filePath, "utf-8");
6710
- const relPath = relative2(root, filePath).split("\\").join("/");
6892
+ const relPath = relative3(root, filePath).split("\\").join("/");
6711
6893
  const parsed = matter(raw);
6712
6894
  const data = parsed.data;
6713
6895
  const batch = String(data.test_batch ?? basename3(filePath, extname(filePath))).trim();
@@ -6735,7 +6917,7 @@ async function validateExecutableTraceability(root, config) {
6735
6917
  }
6736
6918
  }
6737
6919
  }
6738
- const allFiles = await walk(root);
6920
+ const allFiles = await walkFiles(root);
6739
6921
  const specFiles = /* @__PURE__ */ new Set();
6740
6922
  const configuredRunners = schema.e2e?.runners ?? [];
6741
6923
  if (configuredRunners.length > 0) {
@@ -6755,7 +6937,7 @@ async function validateExecutableTraceability(root, config) {
6755
6937
  const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
6756
6938
  const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
6757
6939
  for (const specFile of specFiles) {
6758
- const fullSpecPath = join6(root, specFile);
6940
+ const fullSpecPath = join7(root, specFile);
6759
6941
  let content;
6760
6942
  try {
6761
6943
  content = await readFile3(fullSpecPath, "utf-8");
@@ -6764,14 +6946,16 @@ async function validateExecutableTraceability(root, config) {
6764
6946
  }
6765
6947
  const level = detectTestLevel(specFile, content);
6766
6948
  const specLines = content.split(/\r?\n/);
6767
- for (let lineIndex = 0; lineIndex < specLines.length; lineIndex += 1) {
6768
- const line = specLines[lineIndex];
6769
- let match = tcAnnotationRegex.exec(line);
6949
+ const lineComments = scanCodeComments(content).filter((comment) => comment.kind === "line" && comment.standalone);
6950
+ for (const comment of lineComments) {
6951
+ const lineIndex = comment.lineNumber - 1;
6952
+ const commentText = comment.text.replace(/^!/, "");
6953
+ let match = tcAnnotationRegex.exec(`//${commentText}`);
6770
6954
  let annotatedLevel = "";
6771
6955
  if (match) {
6772
6956
  annotatedLevel = match[2];
6773
6957
  } else {
6774
- match = tcAnnotationNoLevelRegex.exec(line);
6958
+ match = tcAnnotationNoLevelRegex.exec(`//${commentText}`);
6775
6959
  }
6776
6960
  if (!match) {
6777
6961
  continue;
@@ -6798,7 +6982,7 @@ async function validateExecutableTraceability(root, config) {
6798
6982
  level: effectiveLevel,
6799
6983
  file: specFile,
6800
6984
  testName,
6801
- line: lineIndex + 1
6985
+ line: comment.lineNumber
6802
6986
  };
6803
6987
  const key = `${batch}:${tcId}`;
6804
6988
  const existing = refToSource.get(key) ?? [];
@@ -6835,7 +7019,7 @@ async function validateExecutableTraceability(root, config) {
6835
7019
  if (entry.testId) {
6836
7020
  let content;
6837
7021
  try {
6838
- content = await readFile3(join6(root, normalizedRefFile), "utf-8");
7022
+ content = await readFile3(join7(root, normalizedRefFile), "utf-8");
6839
7023
  } catch {
6840
7024
  continue;
6841
7025
  }
@@ -7012,11 +7196,11 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7012
7196
  let withExecutableRef = 0;
7013
7197
  const statusBreakdown = {};
7014
7198
  const chainTypeBreakdown = {};
7015
- const e2eDir = join6(root, "artifacts", "tests", "e2e");
7199
+ const e2eDir = join7(root, "artifacts", "tests", "e2e");
7016
7200
  const tcFieldsMap = /* @__PURE__ */ new Map();
7017
7201
  let e2eFiles;
7018
7202
  try {
7019
- e2eFiles = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join6(e2eDir, name));
7203
+ e2eFiles = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => join7(e2eDir, name));
7020
7204
  } catch {
7021
7205
  e2eFiles = [];
7022
7206
  }
@@ -7083,7 +7267,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7083
7267
  }
7084
7268
  }
7085
7269
  const runners = (await loadConfig(root)).e2e?.runners ?? [];
7086
- const allProjectFiles = await walk(root);
7270
+ const allProjectFiles = await walkFiles(root);
7087
7271
  for (const node of e2eNodes) {
7088
7272
  const fields = tcFieldsMap.get(node.code) ?? asRecord(node.attrs?.tcFields);
7089
7273
  const status = String(fields["status"] ?? "").trim().toLowerCase();
@@ -7094,7 +7278,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7094
7278
  let hasActiveE2eRef = false;
7095
7279
  for (const entry of parseExecutableRefLines(execRef)) {
7096
7280
  const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
7097
- if (!normalized || !existsSync2(join6(root, normalized))) continue;
7281
+ if (!normalized || !existsSync2(join7(root, normalized))) continue;
7098
7282
  const accepting = await getAcceptingRunners(root, normalized, runners);
7099
7283
  if (accepting.some((runner) => runner.kind === "e2e")) {
7100
7284
  hasActiveE2eRef = true;
@@ -7153,7 +7337,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7153
7337
  const acCoverageRateByFeature = {};
7154
7338
  const featureAcMap = /* @__PURE__ */ new Map();
7155
7339
  for (const node of featureNodes) {
7156
- const acs = parseAcceptanceCriteria(await readFile3(join6(root, node.path), "utf-8"));
7340
+ const acs = parseAcceptanceCriteria(await readFile3(join7(root, node.path), "utf-8"));
7157
7341
  featureAcMap.set(node.code, new Set(acs));
7158
7342
  }
7159
7343
  const coveredAcByFeature = /* @__PURE__ */ new Map();
@@ -7195,10 +7379,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7195
7379
  };
7196
7380
  }
7197
7381
  async function generateE2eRegistry(root, opts) {
7198
- const e2eDir = join6(root, "artifacts", "tests", "e2e");
7382
+ const e2eDir = join7(root, "artifacts", "tests", "e2e");
7199
7383
  let files;
7200
7384
  try {
7201
- files = (await readdir2(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
7385
+ files = (await readdir3(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
7202
7386
  } catch {
7203
7387
  return {
7204
7388
  registry_version: "1.0",
@@ -7211,7 +7395,7 @@ async function generateE2eRegistry(root, opts) {
7211
7395
  const batches = [];
7212
7396
  let totalTestCases = 0;
7213
7397
  for (const file of files) {
7214
- const filePath = join6(e2eDir, file);
7398
+ const filePath = join7(e2eDir, file);
7215
7399
  const raw = await readFile3(filePath, "utf-8");
7216
7400
  const parsed = matter(raw);
7217
7401
  const data = parsed.data;
@@ -7339,7 +7523,7 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
7339
7523
  if (!normalizedPath) {
7340
7524
  return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
7341
7525
  }
7342
- const fullPath = join6(root, normalizedPath);
7526
+ const fullPath = join7(root, normalizedPath);
7343
7527
  let content;
7344
7528
  try {
7345
7529
  content = await readFile3(fullPath, "utf-8");
@@ -7451,7 +7635,7 @@ function escapeRegExp(value) {
7451
7635
  }
7452
7636
  async function hasMarkdownTc(tcKey, e2eDir) {
7453
7637
  const [batch, tcId] = tcKey.split(":");
7454
- const filePath = join6(e2eDir, `${batch}.md`);
7638
+ const filePath = join7(e2eDir, `${batch}.md`);
7455
7639
  try {
7456
7640
  const raw = await readFile3(filePath, "utf-8");
7457
7641
  const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
@@ -7461,7 +7645,7 @@ async function hasMarkdownTc(tcKey, e2eDir) {
7461
7645
  }
7462
7646
  }
7463
7647
  async function findFiles(root, patterns) {
7464
- const all = await walk(root);
7648
+ const all = await walkFiles(root);
7465
7649
  const matched = /* @__PURE__ */ new Set();
7466
7650
  for (const pattern of patterns) {
7467
7651
  for (const file of all) {
@@ -7472,21 +7656,9 @@ async function findFiles(root, patterns) {
7472
7656
  }
7473
7657
  return [...matched].sort();
7474
7658
  }
7475
- async function walk(root, current = root) {
7476
- const entries = await readdir2(current, { withFileTypes: true });
7477
- const files = [];
7478
- for (const entry of entries) {
7479
- if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
7480
- continue;
7481
- }
7482
- const fullPath = join6(current, entry.name);
7483
- if (entry.isDirectory()) {
7484
- files.push(...await walk(root, fullPath));
7485
- } else {
7486
- files.push(relative2(root, fullPath).split("\\").join("/"));
7487
- }
7488
- }
7489
- return files;
7659
+ function matchesConfiguredArtifactPath(path, schema) {
7660
+ const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
7661
+ return Object.values(schema.types).some((definition) => definition.paths.some((pattern) => matchesPattern(normalizedPath, pattern)));
7490
7662
  }
7491
7663
  function matchesPattern(file, pattern) {
7492
7664
  if (!pattern.includes("*")) {
@@ -7963,7 +8135,7 @@ function resolveArtifactContext(graph, opts) {
7963
8135
  }
7964
8136
  if (root) {
7965
8137
  for (const ap of ALWAYS_PRESENT_ITEMS) {
7966
- const fullPath = join6(root, ap.path);
8138
+ const fullPath = join7(root, ap.path);
7967
8139
  let stat;
7968
8140
  try {
7969
8141
  stat = statSync(fullPath);
@@ -8199,6 +8371,7 @@ var init_index = __esm({
8199
8371
  init_packet_constants();
8200
8372
  init_packet_validator();
8201
8373
  init_glob_matcher();
8374
+ init_file_walker();
8202
8375
  init_packet_constants();
8203
8376
  init_target_selector();
8204
8377
  init_packet_assembler();
@@ -8343,10 +8516,12 @@ export {
8343
8516
  isPacketTargetType,
8344
8517
  isPacketTargetTypeDynamic,
8345
8518
  isTargetArtifactType,
8519
+ isVersionLockIssueBlocking,
8346
8520
  loadConfig,
8347
8521
  loadContract,
8348
8522
  loadContractCatalog,
8349
8523
  loadContractsFromDirectory,
8524
+ matchesConfiguredArtifactPath,
8350
8525
  nextId,
8351
8526
  normalizeE2eLegacyArtifact,
8352
8527
  normalizeToCanonical,
@@ -8383,5 +8558,6 @@ export {
8383
8558
  validateScenarioPrdLinkIndex,
8384
8559
  validateScenarioPrdLinks,
8385
8560
  verifyDigest,
8561
+ versionLockIssueSeverity,
8386
8562
  writeGraphCache
8387
8563
  };