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.cjs CHANGED
@@ -362,6 +362,41 @@ var init_glob_matcher = __esm({
362
362
  }
363
363
  });
364
364
 
365
+ // src/file-walker.ts
366
+ async function walkFiles(root, current = root, readDirectory = defaultReadDirectory) {
367
+ let entries;
368
+ try {
369
+ entries = await readDirectory(current, { withFileTypes: true });
370
+ } catch (error) {
371
+ if (current !== root && error.code === "ENOENT") {
372
+ return [];
373
+ }
374
+ throw error;
375
+ }
376
+ const files = [];
377
+ for (const entry of entries) {
378
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
379
+ continue;
380
+ }
381
+ const fullPath = (0, import_node_path.join)(current, entry.name);
382
+ if (entry.isDirectory()) {
383
+ files.push(...await walkFiles(root, fullPath, readDirectory));
384
+ } else {
385
+ files.push((0, import_node_path.relative)(root, fullPath).split("\\").join("/"));
386
+ }
387
+ }
388
+ return files;
389
+ }
390
+ var import_promises, import_node_path, defaultReadDirectory;
391
+ var init_file_walker = __esm({
392
+ "src/file-walker.ts"() {
393
+ "use strict";
394
+ import_promises = require("fs/promises");
395
+ import_node_path = require("path");
396
+ defaultReadDirectory = import_promises.readdir;
397
+ }
398
+ });
399
+
365
400
  // src/target-selector.ts
366
401
  function parseTargetSelector(value) {
367
402
  const separator = value.indexOf(":");
@@ -965,14 +1000,14 @@ async function auditSingleTarget(target, graph, options) {
965
1000
  const fmt = options.format ?? "markdown";
966
1001
  const ext = fmt === "json" ? "json" : "md";
967
1002
  const filename = `${target.type}-${target.id}.packet.${ext}`;
968
- const outPath = (0, import_node_path.join)(options.outDir, filename);
1003
+ const outPath = (0, import_node_path2.join)(options.outDir, filename);
969
1004
  let content;
970
1005
  if (fmt === "json") {
971
1006
  content = JSON.stringify(packet, null, 2) + "\n";
972
1007
  } else {
973
1008
  content = renderPacketMarkdown(packet);
974
1009
  }
975
- await (0, import_promises.writeFile)(outPath, content, "utf-8");
1010
+ await (0, import_promises2.writeFile)(outPath, content, "utf-8");
976
1011
  entry.outputPath = outPath;
977
1012
  }
978
1013
  } catch (error) {
@@ -993,7 +1028,7 @@ async function auditPackets(root, targets, options, graph) {
993
1028
  }
994
1029
  }
995
1030
  if (options.outDir) {
996
- await (0, import_promises.mkdir)(options.outDir, { recursive: true });
1031
+ await (0, import_promises2.mkdir)(options.outDir, { recursive: true });
997
1032
  }
998
1033
  const sampleSet = options.sampleTargets ? new Set(options.sampleTargets) : null;
999
1034
  const sampleOutputPaths = [];
@@ -1045,8 +1080,8 @@ async function auditPackets(root, targets, options, graph) {
1045
1080
  ...isCompact ? { summaryDetail: "compact", countsByType } : {}
1046
1081
  };
1047
1082
  if (options.outDir) {
1048
- const summaryPath = (0, import_node_path.join)(options.outDir, "summary.json");
1049
- await (0, import_promises.writeFile)(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
1083
+ const summaryPath = (0, import_node_path2.join)(options.outDir, "summary.json");
1084
+ await (0, import_promises2.writeFile)(summaryPath, JSON.stringify(summary, null, 2) + "\n", "utf-8");
1050
1085
  }
1051
1086
  return summary;
1052
1087
  }
@@ -1072,14 +1107,14 @@ async function discoverAndAuditPackets(root, options) {
1072
1107
  universalBaseline: effectiveBaseline
1073
1108
  }, graph);
1074
1109
  }
1075
- var import_promises, import_node_path, VALID_TYPES, VALID_TYPES_LABEL;
1110
+ var import_promises2, import_node_path2, VALID_TYPES, VALID_TYPES_LABEL;
1076
1111
  var init_packet_audit = __esm({
1077
1112
  "src/packet-audit.ts"() {
1078
1113
  "use strict";
1079
1114
  init_index();
1080
1115
  init_packet_validator();
1081
- import_promises = require("fs/promises");
1082
- import_node_path = require("path");
1116
+ import_promises2 = require("fs/promises");
1117
+ import_node_path2 = require("path");
1083
1118
  VALID_TYPES = new Set(VALID_PACKET_TARGET_TYPES);
1084
1119
  VALID_TYPES_LABEL = VALID_PACKET_TARGET_TYPES.join(", ");
1085
1120
  }
@@ -1551,7 +1586,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1551
1586
  for (const entry of lock.locks) {
1552
1587
  if (entry.kind !== "verifies") continue;
1553
1588
  const sourcePath = entry.source.path;
1554
- const fullSourcePath = (0, import_node_path2.join)(root, sourcePath);
1589
+ const fullSourcePath = (0, import_node_path3.join)(root, sourcePath);
1555
1590
  if (!(0, import_node_fs.existsSync)(fullSourcePath)) continue;
1556
1591
  let liveness = livenessCache.get(sourcePath);
1557
1592
  if (liveness === void 0) {
@@ -1563,6 +1598,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1563
1598
  status: "orphan_lock",
1564
1599
  edgeId: entry.edgeId,
1565
1600
  message: `Liveness: ${sourcePath} is not active in any configured runner \u2014 locked verifies edges may reference dead tests`,
1601
+ severity: "warning",
1566
1602
  artifact: entry.artifact,
1567
1603
  source: entry.source
1568
1604
  });
@@ -1645,14 +1681,14 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1645
1681
  currentArtifactHash: targetNode.contentHash
1646
1682
  });
1647
1683
  }
1648
- if (!(0, import_node_fs.existsSync)((0, import_node_path2.join)(root, sourceNode.path))) {
1684
+ if (!(0, import_node_fs.existsSync)((0, import_node_path3.join)(root, sourceNode.path))) {
1649
1685
  entryIssues.push({
1650
1686
  status: "orphan_lock",
1651
1687
  edgeId: relEdgeId,
1652
1688
  message: `Artifact relation source file ${sourceNode.path} no longer exists`
1653
1689
  });
1654
1690
  }
1655
- if (!(0, import_node_fs.existsSync)((0, import_node_path2.join)(root, targetNode.path))) {
1691
+ if (!(0, import_node_fs.existsSync)((0, import_node_path3.join)(root, targetNode.path))) {
1656
1692
  entryIssues.push({
1657
1693
  status: "orphan_lock",
1658
1694
  edgeId: relEdgeId,
@@ -1692,7 +1728,84 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1692
1728
  fresh,
1693
1729
  totalArtifactRelationLocks: artifactRelationLocks.length,
1694
1730
  artifactRelationFresh,
1695
- issues: sortBy(issues, (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1731
+ issues: sortBy(issues.map(enrichVersionLockIssue), (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1732
+ };
1733
+ }
1734
+ function isLivenessIssue(issue2) {
1735
+ return issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX);
1736
+ }
1737
+ function versionLockIssueSeverity(issue2) {
1738
+ if (issue2.severity) {
1739
+ return issue2.severity;
1740
+ }
1741
+ if (issue2.status === "missing_lock") {
1742
+ return "warning";
1743
+ }
1744
+ if (issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX)) {
1745
+ return "warning";
1746
+ }
1747
+ return "error";
1748
+ }
1749
+ function isVersionLockIssueBlocking(issue2, strictMissingLock) {
1750
+ if (issue2.status === "missing_lock") {
1751
+ return strictMissingLock;
1752
+ }
1753
+ return versionLockIssueSeverity(issue2) === "error";
1754
+ }
1755
+ function versionLockIssueRemediation(issue2) {
1756
+ switch (issue2.status) {
1757
+ case "artifact_changed":
1758
+ return [
1759
+ "\u786E\u8BA4\u8FD9\u6B21\u5236\u54C1\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1760
+ "\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",
1761
+ "\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"
1762
+ ];
1763
+ case "source_changed":
1764
+ return [
1765
+ "\u786E\u8BA4\u8FD9\u6B21\u6E90\u7801/\u6D4B\u8BD5\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1766
+ "\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",
1767
+ "\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"
1768
+ ];
1769
+ case "verified_by_changed":
1770
+ return [
1771
+ "\u786E\u8BA4\u8FD9\u6B21\u9A8C\u8BC1\u6587\u4EF6\u53D8\u66F4\u662F\u6709\u610F\u7684\u3002",
1772
+ "\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",
1773
+ "\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"
1774
+ ];
1775
+ case "missing_lock":
1776
+ return [
1777
+ "\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",
1778
+ "\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",
1779
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002"
1780
+ ];
1781
+ case "orphan_lock":
1782
+ if (isLivenessIssue(issue2)) {
1783
+ return [
1784
+ "\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",
1785
+ "\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",
1786
+ "\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"
1787
+ ];
1788
+ }
1789
+ return [
1790
+ "\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",
1791
+ "\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",
1792
+ "\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",
1793
+ "\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"
1794
+ ];
1795
+ case "target_not_found":
1796
+ return [
1797
+ "\u68C0\u67E5 `--target` \u7684 `type:id` \u662F\u5426\u62FC\u5199\u6B63\u786E\u3002",
1798
+ "\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"
1799
+ ];
1800
+ default:
1801
+ return [];
1802
+ }
1803
+ }
1804
+ function enrichVersionLockIssue(issue2) {
1805
+ return {
1806
+ ...issue2,
1807
+ severity: versionLockIssueSeverity(issue2),
1808
+ remediation: issue2.remediation ?? versionLockIssueRemediation(issue2)
1696
1809
  };
1697
1810
  }
1698
1811
  async function updateVersionLock(root, options) {
@@ -1990,59 +2103,118 @@ async function traceVersion(root, target, lockPath = VERSION_LOCK_PATH) {
1990
2103
  currentEdges: index.edges.filter((edge2) => edge2.from === targetUid || edge2.to === targetUid),
1991
2104
  locks: lock.locks.filter((entry) => `${entry.artifact.type}:${entry.artifact.id}` === targetUid),
1992
2105
  artifactRelations: targetArtifactRelations,
1993
- issues: [...targetIssues, ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
2106
+ issues: [...targetIssues.map(enrichVersionLockIssue), ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
1994
2107
  };
1995
2108
  }
1996
- function renderVersionLockAuditMarkdown(result) {
2109
+ function renderIssueBlockingTag(issue2, strictMissingLock) {
2110
+ return isVersionLockIssueBlocking(issue2, strictMissingLock) ? "\u963B\u65AD" : "\u8B66\u544A";
2111
+ }
2112
+ function renderIssueBlockingExplanation(issue2, strictMissingLock) {
2113
+ if (!isVersionLockIssueBlocking(issue2, strictMissingLock)) {
2114
+ return "\u5426\uFF08\u4EC5\u63D0\u9192\uFF0C\u4E0D\u963B\u65AD\uFF09";
2115
+ }
2116
+ if (issue2.status === "missing_lock" && versionLockIssueSeverity(issue2) !== "error") {
2117
+ return "\u662F\uFF08\u5DF2\u7531 --strict-missing-lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09";
2118
+ }
2119
+ return "\u662F\uFF08\u4F1A\u4F7F\u547D\u4EE4\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF09";
2120
+ }
2121
+ function appendIssueDetails(lines, issues, strictMissingLock) {
2122
+ issues.forEach((issue2, index) => {
2123
+ const label = VERSION_LOCK_STATUS_LABELS[issue2.status] ?? issue2.status;
2124
+ lines.push(`### ${index + 1}. [${renderIssueBlockingTag(issue2, strictMissingLock)}] ${label}\uFF08\`${issue2.status}\`\uFF09`);
2125
+ lines.push("");
2126
+ lines.push(`- \u8FB9: \`${issue2.edgeId}\``);
2127
+ lines.push(`- \u8BE6\u60C5: ${issue2.message}`);
2128
+ lines.push(`- \u662F\u5426\u963B\u65AD: ${renderIssueBlockingExplanation(issue2, strictMissingLock)}`);
2129
+ const remediation = issue2.remediation ?? [];
2130
+ if (remediation.length > 0) {
2131
+ lines.push("- \u89E3\u51B3\u6B65\u9AA4:");
2132
+ remediation.forEach((step, stepIndex) => {
2133
+ lines.push(` ${stepIndex + 1}. ${step}`);
2134
+ });
2135
+ }
2136
+ lines.push("");
2137
+ });
2138
+ }
2139
+ function renderVersionLockAuditMarkdown(result, options = {}) {
2140
+ const strictMissingLock = options.strictMissingLock === true;
2141
+ const blockingCount = result.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2142
+ const nonBlockingCount = result.issues.length - blockingCount;
1997
2143
  const lines = [
1998
- "# Version Lock Audit",
2144
+ "# \u7248\u672C\u9501\u5BA1\u8BA1\uFF08version-lock audit\uFF09",
1999
2145
  "",
2000
- `Root: \`${result.root}\``,
2001
- `Lock: \`${result.lockPath}\``,
2002
- `Locks: ${result.totalLocks} | Fresh: ${result.fresh} | Issues: ${result.issues.length}`,
2003
- `Artifact Relations: ${result.totalArtifactRelationLocks} | Fresh: ${result.artifactRelationFresh}`,
2146
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2147
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2148
+ `- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: ${result.totalLocks}\uFF08\u65B0\u9C9C ${result.fresh}\uFF09`,
2149
+ `- \u5236\u54C1\u5173\u7CFB\u9501: ${result.totalArtifactRelationLocks}\uFF08\u65B0\u9C9C ${result.artifactRelationFresh}\uFF09`,
2150
+ `- \u963B\u65AD\u7B56\u7565: ${strictMissingLock ? "--strict-missing-lock\uFF08missing_lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09" : "\u9ED8\u8BA4\uFF08missing_lock \u4E0D\u963B\u65AD\uFF09"}`,
2151
+ `- \u95EE\u9898: ${result.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
2004
2152
  ""
2005
2153
  ];
2006
2154
  if (result.issues.length === 0) {
2007
- lines.push("No version lock issues.");
2155
+ lines.push("\u672A\u53D1\u73B0\u7248\u672C\u9501\u95EE\u9898\u3002");
2008
2156
  return `${lines.join("\n")}
2009
2157
  `;
2010
2158
  }
2011
- for (const issue2 of result.issues) {
2012
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2159
+ if (blockingCount > 0) {
2160
+ 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`);
2161
+ } else {
2162
+ 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");
2013
2163
  }
2164
+ lines.push("");
2165
+ lines.push("## \u95EE\u9898\u6E05\u5355");
2166
+ lines.push("");
2167
+ appendIssueDetails(lines, result.issues, strictMissingLock);
2014
2168
  return `${lines.join("\n")}
2015
2169
  `;
2016
2170
  }
2017
2171
  function renderVersionLockRefreshMarkdown(result) {
2172
+ const strictMissingLock = true;
2173
+ const blockingCount = result.postAudit.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2174
+ const nonBlockingCount = result.postAudit.issues.length - blockingCount;
2018
2175
  const lines = [
2019
- "# Version Lock Refresh",
2176
+ "# \u7248\u672C\u9501\u5237\u65B0\uFF08version-lock refresh\uFF09",
2177
+ "",
2178
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2179
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2180
+ `- \u6A21\u5F0F: \`${result.mode}\`\uFF08\u53D8\u66F4\u8DEF\u5F84 ${result.changedPaths.length} \u4E2A\uFF0C\u53D7\u5F71\u54CD\u8FB9 ${result.affectedEdges.length} \u6761\uFF09`,
2181
+ `- \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}`,
2182
+ `- \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}`,
2183
+ `- \u963B\u65AD\u7B56\u7565: refresh \u56FA\u5B9A\u6309 --strict-missing-lock \u5224\u5B9A\uFF0Cmissing_lock \u4E5F\u4F1A\u963B\u65AD`,
2184
+ `- \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898: ${result.postAudit.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
2185
+ "",
2186
+ "\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",
2020
2187
  "",
2021
- `Root: \`${result.root}\``,
2022
- `Lock: \`${result.lockPath}\``,
2023
- `Mode: \`${result.mode}\``,
2024
- `Changed paths: ${result.changedPaths.length}`,
2025
- `Affected edges: ${result.affectedEdges.length}`,
2026
- `Added: ${result.addedLocks.length} | Updated: ${result.updatedLocks.length} | Retained orphans: ${result.retainedOrphans.length} | Removed orphans: ${result.removedOrphans.length}`,
2027
- `Artifact Relations \u2014 Added: ${result.addedArtifactRelationLocks.length} | Updated: ${result.updatedArtifactRelationLocks.length} | Retained orphans: ${result.retainedArtifactRelationOrphans.length} | Removed: ${result.removedArtifactRelationLocks.length}`,
2028
- `Post-audit issues: ${result.postAudit.issues.length}`,
2188
+ "1. `git diff artifacts/traceability-version-lock.json`",
2189
+ "2. `git add artifacts/traceability-version-lock.json`",
2190
+ "3. \u91CD\u65B0\u6267\u884C `git commit`",
2029
2191
  ""
2030
2192
  ];
2031
- appendList(lines, "Added Locks", result.addedLocks);
2032
- appendList(lines, "Updated Locks", result.updatedLocks);
2033
- appendList(lines, "Retained Orphans", result.retainedOrphans);
2034
- appendList(lines, "Removed Orphans", result.removedOrphans);
2035
- appendList(lines, "Added Artifact Relation Locks", result.addedArtifactRelationLocks);
2036
- appendList(lines, "Updated Artifact Relation Locks", result.updatedArtifactRelationLocks);
2037
- appendList(lines, "Retained Artifact Relation Orphans", result.retainedArtifactRelationOrphans);
2038
- appendList(lines, "Removed Artifact Relation Locks", result.removedArtifactRelationLocks);
2039
- appendList(lines, "Warnings", result.warnings);
2193
+ appendList(lines, "\u65B0\u589E\u7684\u9501", result.addedLocks);
2194
+ appendList(lines, "\u66F4\u65B0\u7684\u9501", result.updatedLocks);
2195
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u9501", result.retainedOrphans);
2196
+ if (result.retainedOrphans.length > 0) {
2197
+ 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");
2198
+ lines.push("");
2199
+ }
2200
+ appendList(lines, "\u5220\u9664\u7684\u5B64\u7ACB\u9501", result.removedOrphans);
2201
+ appendList(lines, "\u65B0\u589E\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.addedArtifactRelationLocks);
2202
+ appendList(lines, "\u66F4\u65B0\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.updatedArtifactRelationLocks);
2203
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u5236\u54C1\u5173\u7CFB\u9501", result.retainedArtifactRelationOrphans);
2204
+ if (result.retainedArtifactRelationOrphans.length > 0) {
2205
+ 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");
2206
+ lines.push("");
2207
+ }
2208
+ appendList(lines, "\u5220\u9664\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.removedArtifactRelationLocks);
2209
+ appendList(lines, "\u63D0\u9192", result.warnings);
2040
2210
  if (result.postAudit.issues.length > 0) {
2041
- lines.push("## Post-Audit Issues");
2042
- for (const issue2 of result.postAudit.issues) {
2043
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2211
+ if (blockingCount > 0) {
2212
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`);
2213
+ } else {
2214
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u5F53\u524D\u7B56\u7565\u4E0B\u5168\u90E8\u4E0D\u963B\u65AD\uFF09`);
2044
2215
  }
2045
2216
  lines.push("");
2217
+ appendIssueDetails(lines, result.postAudit.issues, strictMissingLock);
2046
2218
  }
2047
2219
  return `${lines.join("\n")}
2048
2220
  `;
@@ -2089,7 +2261,7 @@ function renderTraceVersionMarkdown(result) {
2089
2261
  async function readVersionLock(root, lockPath) {
2090
2262
  const safeLockPath = normalizeRelativePath(root, lockPath);
2091
2263
  try {
2092
- const raw = await (0, import_promises2.readFile)((0, import_node_path2.join)(root, safeLockPath), "utf-8");
2264
+ const raw = await (0, import_promises3.readFile)((0, import_node_path3.join)(root, safeLockPath), "utf-8");
2093
2265
  let parsed;
2094
2266
  try {
2095
2267
  parsed = JSON.parse(raw);
@@ -2248,9 +2420,9 @@ function requireSafeRelativePath(value, path) {
2248
2420
  }
2249
2421
  async function writeVersionLock(root, lockPath, lock) {
2250
2422
  const safeLockPath = normalizeRelativePath(root, lockPath);
2251
- const fullPath = (0, import_node_path2.join)(root, safeLockPath);
2252
- await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(fullPath), { recursive: true });
2253
- await (0, import_promises2.writeFile)(fullPath, `${JSON.stringify(lock, null, 2)}
2423
+ const fullPath = (0, import_node_path3.join)(root, safeLockPath);
2424
+ await (0, import_promises3.mkdir)((0, import_node_path3.dirname)(fullPath), { recursive: true });
2425
+ await (0, import_promises3.writeFile)(fullPath, `${JSON.stringify(lock, null, 2)}
2254
2426
  `);
2255
2427
  }
2256
2428
  function implementationEdges(index) {
@@ -2431,14 +2603,14 @@ async function hashRelativePath(root, path, cache) {
2431
2603
  const normalized = normalizeRelativePath(root, path);
2432
2604
  const cached = cache.get(normalized);
2433
2605
  if (cached) return cached;
2434
- const content = await (0, import_promises2.readFile)((0, import_node_path2.join)(root, normalized));
2606
+ const content = await (0, import_promises3.readFile)((0, import_node_path3.join)(root, normalized));
2435
2607
  const hash = `sha256:${(0, import_node_crypto.createHash)("sha256").update(content).digest("hex")}`;
2436
2608
  cache.set(normalized, hash);
2437
2609
  return hash;
2438
2610
  }
2439
2611
  function normalizeRelativePath(root, path) {
2440
2612
  const normalized = path.replace(/\\/g, "/");
2441
- const relativePath = normalized.startsWith("/") ? (0, import_node_path2.relative)(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
2613
+ const relativePath = normalized.startsWith("/") ? (0, import_node_path3.relative)(root, normalized).replace(/\\/g, "/") : normalized.replace(/^\.\//, "");
2442
2614
  if (relativePath === ".." || relativePath.startsWith("../")) {
2443
2615
  throw new Error(`Path is outside root: ${path}`);
2444
2616
  }
@@ -2454,10 +2626,10 @@ async function getTestFileRunnerLiveness(root, filePath, config) {
2454
2626
  if (!/e2e/i.test(filePath) && !/\.e2e\./i.test(filePath)) {
2455
2627
  return "active";
2456
2628
  }
2457
- const fullSourcePath = (0, import_node_path2.join)(root, filePath);
2629
+ const fullSourcePath = (0, import_node_path3.join)(root, filePath);
2458
2630
  if (!(0, import_node_fs.existsSync)(fullSourcePath)) return "inactive";
2459
2631
  try {
2460
- const content = await (0, import_promises2.readFile)(fullSourcePath, "utf-8");
2632
+ const content = await (0, import_promises3.readFile)(fullSourcePath, "utf-8");
2461
2633
  return /\/\/!?\s*@(?:e2e_test|tc)\s+/.test(content) ? "active" : "inactive";
2462
2634
  } catch {
2463
2635
  return "inactive";
@@ -2497,19 +2669,29 @@ async function isFileActiveInRunner(root, filePath, runner) {
2497
2669
  function sortUnique(items) {
2498
2670
  return [...new Set(items)].sort((left, right) => left.localeCompare(right));
2499
2671
  }
2500
- var import_node_crypto, import_node_fs, import_promises2, import_node_path2, VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
2672
+ var import_node_crypto, import_node_fs, import_promises3, import_node_path3, VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION, LIVENESS_MESSAGE_PREFIX, VERSION_LOCK_STATUS_LABELS;
2501
2673
  var init_versioned_traceability = __esm({
2502
2674
  "src/versioned-traceability.ts"() {
2503
2675
  "use strict";
2504
2676
  import_node_crypto = require("crypto");
2505
2677
  import_node_fs = require("fs");
2506
- import_promises2 = require("fs/promises");
2507
- import_node_path2 = require("path");
2678
+ import_promises3 = require("fs/promises");
2679
+ import_node_path3 = require("path");
2508
2680
  init_index();
2509
2681
  init_glob_matcher();
2510
2682
  VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
2511
2683
  VERSION_INDEX_SCHEMA_VERSION = "1.0";
2512
2684
  VERSION_LOCK_SCHEMA_VERSION = "1.0";
2685
+ LIVENESS_MESSAGE_PREFIX = "Liveness:";
2686
+ VERSION_LOCK_STATUS_LABELS = {
2687
+ fresh: "\u65B0\u9C9C",
2688
+ target_not_found: "\u76EE\u6807\u5236\u54C1\u4E0D\u5B58\u5728",
2689
+ artifact_changed: "\u5236\u54C1\u5DF2\u53D8\u5316",
2690
+ source_changed: "\u6E90\u7801/\u6D4B\u8BD5\u5DF2\u53D8\u5316",
2691
+ verified_by_changed: "\u9A8C\u8BC1\u6587\u4EF6\u5DF2\u53D8\u5316",
2692
+ missing_lock: "\u7F3A\u5C11\u7248\u672C\u9501",
2693
+ orphan_lock: "\u5B64\u7ACB\u9501"
2694
+ };
2513
2695
  }
2514
2696
  });
2515
2697
 
@@ -2520,7 +2702,7 @@ async function resolveArtifactGraphCli(root, options = {}) {
2520
2702
  const candidates = [
2521
2703
  {
2522
2704
  source: "node_modules",
2523
- path: (0, import_node_path3.join)(root, "node_modules/.bin/artifact-graph"),
2705
+ path: (0, import_node_path4.join)(root, "node_modules/.bin/artifact-graph"),
2524
2706
  exists: false
2525
2707
  },
2526
2708
  {
@@ -2553,8 +2735,8 @@ async function resolveArtifactGraphCli(root, options = {}) {
2553
2735
  }
2554
2736
  async function doctorArtifactChain(root, options = {}) {
2555
2737
  const cli = await resolveArtifactGraphCli(root, options);
2556
- const configPath = (0, import_node_path3.join)(root, "artifact-graph.config.yaml");
2557
- const lockPath = (0, import_node_path3.join)(root, VERSION_LOCK_PATH);
2738
+ const configPath = (0, import_node_path4.join)(root, "artifact-graph.config.yaml");
2739
+ const lockPath = (0, import_node_path4.join)(root, VERSION_LOCK_PATH);
2558
2740
  const supportedCommands = cli.path ? await detectSupportedCommands(cli.path) : [];
2559
2741
  const nodeCompatible = isNodeCompatible(process.versions.node);
2560
2742
  const warnings = [
@@ -2627,7 +2809,7 @@ ${maybe.stderr ?? ""}`;
2627
2809
  }
2628
2810
  async function pathExists(path) {
2629
2811
  try {
2630
- await (0, import_promises3.access)(path, import_node_fs2.constants.R_OK);
2812
+ await (0, import_promises4.access)(path, import_node_fs2.constants.R_OK);
2631
2813
  return true;
2632
2814
  } catch {
2633
2815
  return false;
@@ -2643,20 +2825,20 @@ async function findCommandOnPath(command) {
2643
2825
  }
2644
2826
  }
2645
2827
  function resolveCandidatePath(root, candidatePath) {
2646
- return (0, import_node_path3.isAbsolute)(candidatePath) ? candidatePath : (0, import_node_path3.resolve)(root, candidatePath);
2828
+ return (0, import_node_path4.isAbsolute)(candidatePath) ? candidatePath : (0, import_node_path4.resolve)(root, candidatePath);
2647
2829
  }
2648
2830
  function isNodeCompatible(version) {
2649
2831
  const major = Number(version.split(".")[0]);
2650
2832
  return Number.isFinite(major) && major >= 22;
2651
2833
  }
2652
- var import_node_child_process, import_node_fs2, import_promises3, import_node_path3, import_node_util, execFileAsync, KNOWN_COMMANDS;
2834
+ var import_node_child_process, import_node_fs2, import_promises4, import_node_path4, import_node_util, execFileAsync, KNOWN_COMMANDS;
2653
2835
  var init_cli_resolver = __esm({
2654
2836
  "src/cli-resolver.ts"() {
2655
2837
  "use strict";
2656
2838
  import_node_child_process = require("child_process");
2657
2839
  import_node_fs2 = require("fs");
2658
- import_promises3 = require("fs/promises");
2659
- import_node_path3 = require("path");
2840
+ import_promises4 = require("fs/promises");
2841
+ import_node_path4 = require("path");
2660
2842
  import_node_util = require("util");
2661
2843
  init_versioned_traceability();
2662
2844
  execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
@@ -2767,14 +2949,14 @@ async function resolveGitHookPath(root, hookName) {
2767
2949
  ]);
2768
2950
  const value = stdout.trim();
2769
2951
  if (!value) throw new Error(`Git returned an empty hook path for ${hookName}`);
2770
- return (0, import_node_path4.isAbsolute)(value) ? (0, import_node_path4.resolve)(value) : (0, import_node_path4.resolve)(root, value);
2952
+ return (0, import_node_path5.isAbsolute)(value) ? (0, import_node_path5.resolve)(value) : (0, import_node_path5.resolve)(root, value);
2771
2953
  }
2772
- var import_node_child_process3, import_node_path4, import_node_util3, execFileAsync3;
2954
+ var import_node_child_process3, import_node_path5, import_node_util3, execFileAsync3;
2773
2955
  var init_git_hook_path = __esm({
2774
2956
  "src/git-hook-path.ts"() {
2775
2957
  "use strict";
2776
2958
  import_node_child_process3 = require("child_process");
2777
- import_node_path4 = require("path");
2959
+ import_node_path5 = require("path");
2778
2960
  import_node_util3 = require("util");
2779
2961
  execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
2780
2962
  }
@@ -2997,7 +3179,7 @@ async function readHookSnapshot(hookPath) {
2997
3179
  for (let attempt = 0; attempt < 3; attempt += 1) {
2998
3180
  let metadata;
2999
3181
  try {
3000
- metadata = await (0, import_promises4.lstat)(hookPath, { bigint: true });
3182
+ metadata = await (0, import_promises5.lstat)(hookPath, { bigint: true });
3001
3183
  } catch (error) {
3002
3184
  if (error.code === "ENOENT") {
3003
3185
  return { kind: "missing", bytes: Buffer.alloc(0) };
@@ -3013,7 +3195,7 @@ async function readHookSnapshot(hookPath) {
3013
3195
  size: metadata.size,
3014
3196
  mtimeNs: metadata.mtimeNs,
3015
3197
  mode: metadata.mode,
3016
- linkTarget: await (0, import_promises4.readlink)(hookPath)
3198
+ linkTarget: await (0, import_promises5.readlink)(hookPath)
3017
3199
  };
3018
3200
  }
3019
3201
  if (!metadata.isFile()) {
@@ -3029,7 +3211,7 @@ async function readHookSnapshot(hookPath) {
3029
3211
  }
3030
3212
  let handle;
3031
3213
  try {
3032
- handle = await (0, import_promises4.open)(hookPath, import_node_fs3.constants.O_RDONLY | (import_node_fs3.constants.O_NOFOLLOW ?? 0));
3214
+ handle = await (0, import_promises5.open)(hookPath, import_node_fs3.constants.O_RDONLY | (import_node_fs3.constants.O_NOFOLLOW ?? 0));
3033
3215
  const opened = await handle.stat({ bigint: true });
3034
3216
  if (opened.dev !== metadata.dev || opened.ino !== metadata.ino) {
3035
3217
  await handle.close();
@@ -3087,15 +3269,15 @@ async function removeHookAtomically(hookPath, snapshot) {
3087
3269
  throw new ConcurrentHookModificationError(hookPath);
3088
3270
  }
3089
3271
  if (current.kind !== "missing") {
3090
- await (0, import_promises4.unlink)(hookPath);
3272
+ await (0, import_promises5.unlink)(hookPath);
3091
3273
  }
3092
3274
  }
3093
3275
  async function writeHookAtomically(hookPath, content, snapshot, mode) {
3094
- await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(hookPath), { recursive: true });
3095
- const temporaryPath = (0, import_node_path5.join)((0, import_node_path5.dirname)(hookPath), `.${(0, import_node_path5.basename)(hookPath)}.${(0, import_node_crypto2.randomUUID)()}.tmp`);
3276
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(hookPath), { recursive: true });
3277
+ const temporaryPath = (0, import_node_path6.join)((0, import_node_path6.dirname)(hookPath), `.${(0, import_node_path6.basename)(hookPath)}.${(0, import_node_crypto2.randomUUID)()}.tmp`);
3096
3278
  let temporaryExists = false;
3097
3279
  try {
3098
- const temporary = await (0, import_promises4.open)(temporaryPath, "wx", mode);
3280
+ const temporary = await (0, import_promises5.open)(temporaryPath, "wx", mode);
3099
3281
  temporaryExists = true;
3100
3282
  try {
3101
3283
  await temporary.writeFile(content);
@@ -3108,11 +3290,11 @@ async function writeHookAtomically(hookPath, content, snapshot, mode) {
3108
3290
  if (!sameHookSnapshot(snapshot, current)) {
3109
3291
  throw new ConcurrentHookModificationError(hookPath);
3110
3292
  }
3111
- await (0, import_promises4.rename)(temporaryPath, hookPath);
3293
+ await (0, import_promises5.rename)(temporaryPath, hookPath);
3112
3294
  temporaryExists = false;
3113
3295
  } finally {
3114
3296
  if (temporaryExists) {
3115
- await (0, import_promises4.unlink)(temporaryPath).catch((error) => {
3297
+ await (0, import_promises5.unlink)(temporaryPath).catch((error) => {
3116
3298
  if (error.code !== "ENOENT") {
3117
3299
  throw error;
3118
3300
  }
@@ -3138,14 +3320,14 @@ function findManagedRange(content, begin, end) {
3138
3320
  end: endLine < 0 ? content.length : endLine + 1
3139
3321
  };
3140
3322
  }
3141
- var import_node_fs3, import_node_crypto2, import_promises4, import_node_path5, DEFAULT_MARKER_ID, MANAGED_STATE_PREFIX, UnsupportedHookInterpreterError, SymlinkHookUnsupportedError, UnsupportedHookTypeError, InvalidManagedHookStateError, ConcurrentHookModificationError, HookTransactionRollbackError;
3323
+ var import_node_fs3, import_node_crypto2, import_promises5, import_node_path6, DEFAULT_MARKER_ID, MANAGED_STATE_PREFIX, UnsupportedHookInterpreterError, SymlinkHookUnsupportedError, UnsupportedHookTypeError, InvalidManagedHookStateError, ConcurrentHookModificationError, HookTransactionRollbackError;
3142
3324
  var init_hook_installer = __esm({
3143
3325
  "src/hook-installer.ts"() {
3144
3326
  "use strict";
3145
3327
  import_node_fs3 = require("fs");
3146
3328
  import_node_crypto2 = require("crypto");
3147
- import_promises4 = require("fs/promises");
3148
- import_node_path5 = require("path");
3329
+ import_promises5 = require("fs/promises");
3330
+ import_node_path6 = require("path");
3149
3331
  DEFAULT_MARKER_ID = "artifact-chain-assistant";
3150
3332
  MANAGED_STATE_PREFIX = "# artifact-chain-assistant managed state v1:";
3151
3333
  UnsupportedHookInterpreterError = class extends Error {
@@ -3812,7 +3994,7 @@ function validatePolicyCompatibility(policy, baseContract) {
3812
3994
  };
3813
3995
  }
3814
3996
  async function loadContract(contractPath, options) {
3815
- const rawContent = await (0, import_promises5.readFile)(contractPath, "utf-8");
3997
+ const rawContent = await (0, import_promises6.readFile)(contractPath, "utf-8");
3816
3998
  let schema;
3817
3999
  try {
3818
4000
  schema = JSON.parse(rawContent);
@@ -3860,10 +4042,10 @@ async function loadContract(contractPath, options) {
3860
4042
  }
3861
4043
  async function loadContractsFromDirectory(contractsDir, options) {
3862
4044
  const contracts = [];
3863
- const entries = await (0, import_promises5.readdir)(contractsDir, { withFileTypes: true });
4045
+ const entries = await (0, import_promises6.readdir)(contractsDir, { withFileTypes: true });
3864
4046
  for (const entry of entries) {
3865
4047
  if (entry.isDirectory()) {
3866
- const schemaPath = (0, import_node_path6.join)(contractsDir, entry.name, "schema.json");
4048
+ const schemaPath = (0, import_node_path7.join)(contractsDir, entry.name, "schema.json");
3867
4049
  const contract = await loadContract(schemaPath, options);
3868
4050
  contracts.push(contract);
3869
4051
  }
@@ -3978,13 +4160,13 @@ async function loadContractCatalog(contractsDir, options) {
3978
4160
  }
3979
4161
  return catalog;
3980
4162
  }
3981
- var import_node_crypto3, import_promises5, import_node_path6, import_ajv, CONTRACT_ERROR_CODES, ContractError, OFFICIAL_AUTHORITY, OFFICIAL_NAMESPACE_PATTERN, ContractRegistry, _validatorCache, E2E_NORMALIZER_CONFIG, ContractCatalog;
4163
+ var import_node_crypto3, import_promises6, import_node_path7, import_ajv, CONTRACT_ERROR_CODES, ContractError, OFFICIAL_AUTHORITY, OFFICIAL_NAMESPACE_PATTERN, ContractRegistry, _validatorCache, E2E_NORMALIZER_CONFIG, ContractCatalog;
3982
4164
  var init_contract_kernel = __esm({
3983
4165
  "src/contract-kernel.ts"() {
3984
4166
  "use strict";
3985
4167
  import_node_crypto3 = require("crypto");
3986
- import_promises5 = require("fs/promises");
3987
- import_node_path6 = require("path");
4168
+ import_promises6 = require("fs/promises");
4169
+ import_node_path7 = require("path");
3988
4170
  import_ajv = __toESM(require("ajv"), 1);
3989
4171
  CONTRACT_ERROR_CODES = {
3990
4172
  /** Contract identity not found */
@@ -4337,11 +4519,11 @@ __export(index_exports, {
4337
4519
  collectChangedPaths: () => collectChangedPaths,
4338
4520
  computeE2eCoverageStats: () => computeE2eCoverageStats,
4339
4521
  computeRevisionDigest: () => computeRevisionDigest,
4340
- dirname: () => import_node_path7.dirname,
4522
+ dirname: () => import_node_path8.dirname,
4341
4523
  discoverAndAuditPackets: () => discoverAndAuditPackets,
4342
4524
  discoverTargets: () => discoverTargets,
4343
4525
  doctorArtifactChain: () => doctorArtifactChain,
4344
- extname: () => import_node_path7.extname,
4526
+ extname: () => import_node_path8.extname,
4345
4527
  formatContextMarkdown: () => formatContextMarkdown,
4346
4528
  generateE2eRegistry: () => generateE2eRegistry,
4347
4529
  getArtifactTypeMetadata: () => getArtifactTypeMetadata,
@@ -4351,10 +4533,12 @@ __export(index_exports, {
4351
4533
  isPacketTargetType: () => isPacketTargetType,
4352
4534
  isPacketTargetTypeDynamic: () => isPacketTargetTypeDynamic,
4353
4535
  isTargetArtifactType: () => isTargetArtifactType,
4536
+ isVersionLockIssueBlocking: () => isVersionLockIssueBlocking,
4354
4537
  loadConfig: () => loadConfig,
4355
4538
  loadContract: () => loadContract,
4356
4539
  loadContractCatalog: () => loadContractCatalog,
4357
4540
  loadContractsFromDirectory: () => loadContractsFromDirectory,
4541
+ matchesConfiguredArtifactPath: () => matchesConfiguredArtifactPath,
4358
4542
  nextId: () => nextId,
4359
4543
  normalizeE2eLegacyArtifact: () => normalizeE2eLegacyArtifact,
4360
4544
  normalizeToCanonical: () => normalizeToCanonical,
@@ -4391,6 +4575,7 @@ __export(index_exports, {
4391
4575
  validateScenarioPrdLinkIndex: () => validateScenarioPrdLinkIndex,
4392
4576
  validateScenarioPrdLinks: () => validateScenarioPrdLinks,
4393
4577
  verifyDigest: () => verifyDigest,
4578
+ versionLockIssueSeverity: () => versionLockIssueSeverity,
4394
4579
  writeGraphCache: () => writeGraphCache
4395
4580
  });
4396
4581
  module.exports = __toCommonJS(index_exports);
@@ -4435,10 +4620,10 @@ function resolveArtifactTypeName(schema, token) {
4435
4620
  return void 0;
4436
4621
  }
4437
4622
  async function loadConfig(root) {
4438
- const configPath = (0, import_node_path7.join)(root, "artifact-graph.config.yaml");
4623
+ const configPath = (0, import_node_path8.join)(root, "artifact-graph.config.yaml");
4439
4624
  let parsed = {};
4440
4625
  try {
4441
- const raw = await (0, import_promises6.readFile)(configPath, "utf-8");
4626
+ const raw = await (0, import_promises7.readFile)(configPath, "utf-8");
4442
4627
  parsed = import_js_yaml.default.load(raw) ?? {};
4443
4628
  } catch (error) {
4444
4629
  if (error.code !== "ENOENT") {
@@ -4528,7 +4713,7 @@ function validateE2eConfig(e2e) {
4528
4713
  if (typeof runner.root !== "string" || !runner.root.trim()) {
4529
4714
  throw new Error(`Invalid e2e.runners[${runner.name}].root: must be a non-empty string.`);
4530
4715
  }
4531
- if ((0, import_node_path7.isAbsolute)(runner.root)) {
4716
+ if ((0, import_node_path8.isAbsolute)(runner.root)) {
4532
4717
  throw new Error(`Invalid e2e.runners[${runner.name}].root: "${runner.root}" must not be an absolute path.`);
4533
4718
  }
4534
4719
  if (runner.root.replace(/\\/g, "/").split("/").includes("..")) {
@@ -4612,14 +4797,14 @@ async function scanArtifacts(root, schema) {
4612
4797
  continue;
4613
4798
  }
4614
4799
  scannedFiles.set(file, type);
4615
- const raw = await (0, import_promises6.readFile)((0, import_node_path7.join)(root, file), "utf-8");
4800
+ const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(root, file), "utf-8");
4616
4801
  const parsed = parseFile(type, file, raw, config);
4617
4802
  nodes.push(...parsed.nodes);
4618
4803
  edges.push(...parsed.edges);
4619
4804
  scanDiagnostics.push(...parsed.diagnostics);
4620
4805
  }
4621
4806
  }
4622
- const absoluteRoot = (0, import_node_path7.isAbsolute)(root) ? root : (0, import_node_path7.resolve)(root);
4807
+ const absoluteRoot = (0, import_node_path8.isAbsolute)(root) ? root : (0, import_node_path8.resolve)(root);
4623
4808
  const graph = buildGraph(nodes, edges, scanDiagnostics, absoluteRoot);
4624
4809
  return resolveMatrixEdges(graph);
4625
4810
  }
@@ -4924,7 +5109,7 @@ async function validateScenarioPrdLinkIndex(root, graph) {
4924
5109
  const indexPath = "artifacts/prd/feature-index.md";
4925
5110
  let raw = "";
4926
5111
  try {
4927
- raw = await (0, import_promises6.readFile)((0, import_node_path7.join)(root, indexPath), "utf-8");
5112
+ raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(root, indexPath), "utf-8");
4928
5113
  } catch (error) {
4929
5114
  if (error.code === "ENOENT") {
4930
5115
  return [];
@@ -5114,11 +5299,11 @@ function nextId(graph, schema, type, rangeName) {
5114
5299
  throw new Error(`ID range ${type}.${rangeName} is exhausted`);
5115
5300
  }
5116
5301
  async function writeGraphCache(root, graph) {
5117
- const cacheDir = (0, import_node_path7.join)(root, ".artifact-graph");
5118
- await (0, import_promises6.mkdir)(cacheDir, { recursive: true });
5119
- await (0, import_promises6.writeFile)((0, import_node_path7.join)(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5302
+ const cacheDir = (0, import_node_path8.join)(root, ".artifact-graph");
5303
+ await (0, import_promises7.mkdir)(cacheDir, { recursive: true });
5304
+ await (0, import_promises7.writeFile)((0, import_node_path8.join)(cacheDir, "index.json"), `${JSON.stringify(graph, null, 2)}
5120
5305
  `);
5121
- const db = new import_better_sqlite3.default((0, import_node_path7.join)(cacheDir, "graph.sqlite"));
5306
+ const db = new import_better_sqlite3.default((0, import_node_path8.join)(cacheDir, "graph.sqlite"));
5122
5307
  try {
5123
5308
  db.exec(`
5124
5309
  DROP TABLE IF EXISTS nodes;
@@ -5229,7 +5414,7 @@ function parseFile(type, path, raw, schema = DEFAULT_SCHEMA) {
5229
5414
  }
5230
5415
  function parseGenericMarkdown(type, path, raw, schema) {
5231
5416
  const diagnostics = [];
5232
- const ext = (0, import_node_path7.extname)(path).toLowerCase();
5417
+ const ext = (0, import_node_path8.extname)(path).toLowerCase();
5233
5418
  if (ext !== ".md" && ext !== ".markdown") {
5234
5419
  diagnostics.push(issue(
5235
5420
  "UNSUPPORTED_FORMAT",
@@ -5456,7 +5641,7 @@ function parseDecisions(path, raw) {
5456
5641
  }
5457
5642
  function isTestFile(filePath) {
5458
5643
  const normalized = filePath.replace(/\\/g, "/");
5459
- const name = (0, import_node_path7.basename)(normalized);
5644
+ const name = (0, import_node_path8.basename)(normalized);
5460
5645
  if (/\.(test|spec)\.[^.]+$/.test(name)) return true;
5461
5646
  if (/(^|\/)(tests|test|__tests__)\//.test(normalized)) return true;
5462
5647
  if (/\w+Tests?\.java$/.test(name)) return true;
@@ -5747,7 +5932,7 @@ function parseE2eTest(path, raw) {
5747
5932
  });
5748
5933
  const nodes = [];
5749
5934
  const edges = [];
5750
- const batch = String(data.test_batch ?? (0, import_node_path7.basename)(path, (0, import_node_path7.extname)(path))).trim();
5935
+ const batch = String(data.test_batch ?? (0, import_node_path8.basename)(path, (0, import_node_path8.extname)(path))).trim();
5751
5936
  const frontmatterScenarios = toArray(data.related_scenarios).map((value) => String(value).trim()).filter(Boolean);
5752
5937
  const scopeFeatures = extractCodes(String(data.scope ?? ""), "feature");
5753
5938
  const frontmatterFeatures = [.../* @__PURE__ */ new Set([...Object.keys(asRecord(data.ac_coverage)), ...scopeFeatures])];
@@ -5820,7 +6005,7 @@ function parseE2eRegistry(path, raw) {
5820
6005
  data = { parseError: error.message };
5821
6006
  }
5822
6007
  return {
5823
- nodes: [{ type: "e2e_registry", code: (0, import_node_path7.basename)(path, (0, import_node_path7.extname)(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
6008
+ nodes: [{ type: "e2e_registry", code: (0, import_node_path8.basename)(path, (0, import_node_path8.extname)(path)), title: "E2E Test Registry", path, line: 1, attrs: data }],
5824
6009
  edges: []
5825
6010
  };
5826
6011
  }
@@ -6801,10 +6986,10 @@ function validateE2eRegistry(graph) {
6801
6986
  async function validateExecutableTraceability(root, config) {
6802
6987
  const issues = [];
6803
6988
  const schema = config ?? await loadConfig(root);
6804
- const e2eDir = (0, import_node_path7.join)(root, "artifacts", "tests", "e2e");
6989
+ const e2eDir = (0, import_node_path8.join)(root, "artifacts", "tests", "e2e");
6805
6990
  let e2eFiles;
6806
6991
  try {
6807
- e2eFiles = (await (0, import_promises6.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => (0, import_node_path7.join)(e2eDir, name));
6992
+ e2eFiles = (await (0, import_promises7.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => (0, import_node_path8.join)(e2eDir, name));
6808
6993
  } catch {
6809
6994
  return [];
6810
6995
  }
@@ -6813,11 +6998,11 @@ async function validateExecutableTraceability(root, config) {
6813
6998
  const tcKeyToFields = /* @__PURE__ */ new Map();
6814
6999
  const mdBatches = /* @__PURE__ */ new Set();
6815
7000
  for (const filePath of e2eFiles) {
6816
- const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
6817
- const relPath = (0, import_node_path7.relative)(root, filePath).split("\\").join("/");
7001
+ const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
7002
+ const relPath = (0, import_node_path8.relative)(root, filePath).split("\\").join("/");
6818
7003
  const parsed = (0, import_gray_matter.default)(raw);
6819
7004
  const data = parsed.data;
6820
- const batch = String(data.test_batch ?? (0, import_node_path7.basename)(filePath, (0, import_node_path7.extname)(filePath))).trim();
7005
+ const batch = String(data.test_batch ?? (0, import_node_path8.basename)(filePath, (0, import_node_path8.extname)(filePath))).trim();
6821
7006
  const lines = raw.split(/\r?\n/);
6822
7007
  const tcStarts = [];
6823
7008
  lines.forEach((line, index) => {
@@ -6842,7 +7027,7 @@ async function validateExecutableTraceability(root, config) {
6842
7027
  }
6843
7028
  }
6844
7029
  }
6845
- const allFiles = await walk(root);
7030
+ const allFiles = await walkFiles(root);
6846
7031
  const specFiles = /* @__PURE__ */ new Set();
6847
7032
  const configuredRunners = schema.e2e?.runners ?? [];
6848
7033
  if (configuredRunners.length > 0) {
@@ -6862,23 +7047,25 @@ async function validateExecutableTraceability(root, config) {
6862
7047
  const tcAnnotationRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+?)\s+\[(\w+)\]/;
6863
7048
  const tcAnnotationNoLevelRegex = /\/\/!?\s*@(?:e2e_test|tc)\s+(\S+)/;
6864
7049
  for (const specFile of specFiles) {
6865
- const fullSpecPath = (0, import_node_path7.join)(root, specFile);
7050
+ const fullSpecPath = (0, import_node_path8.join)(root, specFile);
6866
7051
  let content;
6867
7052
  try {
6868
- content = await (0, import_promises6.readFile)(fullSpecPath, "utf-8");
7053
+ content = await (0, import_promises7.readFile)(fullSpecPath, "utf-8");
6869
7054
  } catch {
6870
7055
  continue;
6871
7056
  }
6872
7057
  const level = detectTestLevel(specFile, content);
6873
7058
  const specLines = content.split(/\r?\n/);
6874
- for (let lineIndex = 0; lineIndex < specLines.length; lineIndex += 1) {
6875
- const line = specLines[lineIndex];
6876
- let match = tcAnnotationRegex.exec(line);
7059
+ const lineComments = scanCodeComments(content).filter((comment) => comment.kind === "line" && comment.standalone);
7060
+ for (const comment of lineComments) {
7061
+ const lineIndex = comment.lineNumber - 1;
7062
+ const commentText = comment.text.replace(/^!/, "");
7063
+ let match = tcAnnotationRegex.exec(`//${commentText}`);
6877
7064
  let annotatedLevel = "";
6878
7065
  if (match) {
6879
7066
  annotatedLevel = match[2];
6880
7067
  } else {
6881
- match = tcAnnotationNoLevelRegex.exec(line);
7068
+ match = tcAnnotationNoLevelRegex.exec(`//${commentText}`);
6882
7069
  }
6883
7070
  if (!match) {
6884
7071
  continue;
@@ -6905,7 +7092,7 @@ async function validateExecutableTraceability(root, config) {
6905
7092
  level: effectiveLevel,
6906
7093
  file: specFile,
6907
7094
  testName,
6908
- line: lineIndex + 1
7095
+ line: comment.lineNumber
6909
7096
  };
6910
7097
  const key = `${batch}:${tcId}`;
6911
7098
  const existing = refToSource.get(key) ?? [];
@@ -6942,7 +7129,7 @@ async function validateExecutableTraceability(root, config) {
6942
7129
  if (entry.testId) {
6943
7130
  let content;
6944
7131
  try {
6945
- content = await (0, import_promises6.readFile)((0, import_node_path7.join)(root, normalizedRefFile), "utf-8");
7132
+ content = await (0, import_promises7.readFile)((0, import_node_path8.join)(root, normalizedRefFile), "utf-8");
6946
7133
  } catch {
6947
7134
  continue;
6948
7135
  }
@@ -7119,16 +7306,16 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7119
7306
  let withExecutableRef = 0;
7120
7307
  const statusBreakdown = {};
7121
7308
  const chainTypeBreakdown = {};
7122
- const e2eDir = (0, import_node_path7.join)(root, "artifacts", "tests", "e2e");
7309
+ const e2eDir = (0, import_node_path8.join)(root, "artifacts", "tests", "e2e");
7123
7310
  const tcFieldsMap = /* @__PURE__ */ new Map();
7124
7311
  let e2eFiles;
7125
7312
  try {
7126
- e2eFiles = (await (0, import_promises6.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => (0, import_node_path7.join)(e2eDir, name));
7313
+ e2eFiles = (await (0, import_promises7.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).map((name) => (0, import_node_path8.join)(e2eDir, name));
7127
7314
  } catch {
7128
7315
  e2eFiles = [];
7129
7316
  }
7130
7317
  for (const filePath of e2eFiles) {
7131
- const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
7318
+ const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
7132
7319
  const lines = raw.split(/\r?\n/);
7133
7320
  const tcStarts = [];
7134
7321
  lines.forEach((line, index) => {
@@ -7138,7 +7325,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7138
7325
  }
7139
7326
  });
7140
7327
  const parsed = (0, import_gray_matter.default)(raw);
7141
- const batch = String(parsed.data.test_batch ?? (0, import_node_path7.basename)(filePath, (0, import_node_path7.extname)(filePath))).trim();
7328
+ const batch = String(parsed.data.test_batch ?? (0, import_node_path8.basename)(filePath, (0, import_node_path8.extname)(filePath))).trim();
7142
7329
  for (let i = 0; i < tcStarts.length; i++) {
7143
7330
  const start = tcStarts[i];
7144
7331
  const end = tcStarts[i + 1]?.index ?? lines.length;
@@ -7190,7 +7377,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7190
7377
  }
7191
7378
  }
7192
7379
  const runners = (await loadConfig(root)).e2e?.runners ?? [];
7193
- const allProjectFiles = await walk(root);
7380
+ const allProjectFiles = await walkFiles(root);
7194
7381
  for (const node of e2eNodes) {
7195
7382
  const fields = tcFieldsMap.get(node.code) ?? asRecord(node.attrs?.tcFields);
7196
7383
  const status = String(fields["status"] ?? "").trim().toLowerCase();
@@ -7201,7 +7388,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7201
7388
  let hasActiveE2eRef = false;
7202
7389
  for (const entry of parseExecutableRefLines(execRef)) {
7203
7390
  const normalized = resolveExecutableRefFile(entry.file, allProjectFiles);
7204
- if (!normalized || !(0, import_node_fs4.existsSync)((0, import_node_path7.join)(root, normalized))) continue;
7391
+ if (!normalized || !(0, import_node_fs4.existsSync)((0, import_node_path8.join)(root, normalized))) continue;
7205
7392
  const accepting = await getAcceptingRunners(root, normalized, runners);
7206
7393
  if (accepting.some((runner) => runner.kind === "e2e")) {
7207
7394
  hasActiveE2eRef = true;
@@ -7260,7 +7447,7 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7260
7447
  const acCoverageRateByFeature = {};
7261
7448
  const featureAcMap = /* @__PURE__ */ new Map();
7262
7449
  for (const node of featureNodes) {
7263
- const acs = parseAcceptanceCriteria(await (0, import_promises6.readFile)((0, import_node_path7.join)(root, node.path), "utf-8"));
7450
+ const acs = parseAcceptanceCriteria(await (0, import_promises7.readFile)((0, import_node_path8.join)(root, node.path), "utf-8"));
7264
7451
  featureAcMap.set(node.code, new Set(acs));
7265
7452
  }
7266
7453
  const coveredAcByFeature = /* @__PURE__ */ new Map();
@@ -7302,10 +7489,10 @@ async function computeE2eCoverageStats(graph, root, thresholds = {}) {
7302
7489
  };
7303
7490
  }
7304
7491
  async function generateE2eRegistry(root, opts) {
7305
- const e2eDir = (0, import_node_path7.join)(root, "artifacts", "tests", "e2e");
7492
+ const e2eDir = (0, import_node_path8.join)(root, "artifacts", "tests", "e2e");
7306
7493
  let files;
7307
7494
  try {
7308
- files = (await (0, import_promises6.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
7495
+ files = (await (0, import_promises7.readdir)(e2eDir)).filter((name) => /^test-.*\.md$/.test(name)).sort();
7309
7496
  } catch {
7310
7497
  return {
7311
7498
  registry_version: "1.0",
@@ -7318,11 +7505,11 @@ async function generateE2eRegistry(root, opts) {
7318
7505
  const batches = [];
7319
7506
  let totalTestCases = 0;
7320
7507
  for (const file of files) {
7321
- const filePath = (0, import_node_path7.join)(e2eDir, file);
7322
- const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
7508
+ const filePath = (0, import_node_path8.join)(e2eDir, file);
7509
+ const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
7323
7510
  const parsed = (0, import_gray_matter.default)(raw);
7324
7511
  const data = parsed.data;
7325
- const batch = String(data.test_batch ?? (0, import_node_path7.basename)(file, (0, import_node_path7.extname)(file))).trim();
7512
+ const batch = String(data.test_batch ?? (0, import_node_path8.basename)(file, (0, import_node_path8.extname)(file))).trim();
7326
7513
  const relPath = `artifacts/tests/e2e/${file}`;
7327
7514
  const scope = String(data.scope ?? "").trim();
7328
7515
  const acCoverage = normalizeAcCoverageForRegistry(data.ac_coverage);
@@ -7446,10 +7633,10 @@ async function validatePartialRustEvidence(tcFields, tcKey, root, allFiles) {
7446
7633
  if (!normalizedPath) {
7447
7634
  return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
7448
7635
  }
7449
- const fullPath = (0, import_node_path7.join)(root, normalizedPath);
7636
+ const fullPath = (0, import_node_path8.join)(root, normalizedPath);
7450
7637
  let content;
7451
7638
  try {
7452
- content = await (0, import_promises6.readFile)(fullPath, "utf-8");
7639
+ content = await (0, import_promises7.readFile)(fullPath, "utf-8");
7453
7640
  } catch {
7454
7641
  return { hasValidPartialRust: false, detail: `partial_rust file not found: ${ref.file}` };
7455
7642
  }
@@ -7483,7 +7670,7 @@ function detectTestLevel(specFile, content) {
7483
7670
  }
7484
7671
  function resolveExecutableRefFile(refFile, allFiles) {
7485
7672
  const normalized = refFile.replace(/\\/g, "/").replace(/^\.\//, "");
7486
- if (!normalized || (0, import_node_path7.isAbsolute)(refFile) || normalized.split("/").includes("..")) {
7673
+ if (!normalized || (0, import_node_path8.isAbsolute)(refFile) || normalized.split("/").includes("..")) {
7487
7674
  return void 0;
7488
7675
  }
7489
7676
  if (allFiles.includes(normalized)) {
@@ -7558,9 +7745,9 @@ function escapeRegExp(value) {
7558
7745
  }
7559
7746
  async function hasMarkdownTc(tcKey, e2eDir) {
7560
7747
  const [batch, tcId] = tcKey.split(":");
7561
- const filePath = (0, import_node_path7.join)(e2eDir, `${batch}.md`);
7748
+ const filePath = (0, import_node_path8.join)(e2eDir, `${batch}.md`);
7562
7749
  try {
7563
- const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
7750
+ const raw = await (0, import_promises7.readFile)(filePath, "utf-8");
7564
7751
  const tcRegex = new RegExp(`^#{2,3}\\s+${escapeRegExp(tcId)}\\s*[:\uFF1A]?`, "m");
7565
7752
  return tcRegex.test(raw);
7566
7753
  } catch {
@@ -7568,7 +7755,7 @@ async function hasMarkdownTc(tcKey, e2eDir) {
7568
7755
  }
7569
7756
  }
7570
7757
  async function findFiles(root, patterns) {
7571
- const all = await walk(root);
7758
+ const all = await walkFiles(root);
7572
7759
  const matched = /* @__PURE__ */ new Set();
7573
7760
  for (const pattern of patterns) {
7574
7761
  for (const file of all) {
@@ -7579,21 +7766,9 @@ async function findFiles(root, patterns) {
7579
7766
  }
7580
7767
  return [...matched].sort();
7581
7768
  }
7582
- async function walk(root, current = root) {
7583
- const entries = await (0, import_promises6.readdir)(current, { withFileTypes: true });
7584
- const files = [];
7585
- for (const entry of entries) {
7586
- if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".artifact-graph") {
7587
- continue;
7588
- }
7589
- const fullPath = (0, import_node_path7.join)(current, entry.name);
7590
- if (entry.isDirectory()) {
7591
- files.push(...await walk(root, fullPath));
7592
- } else {
7593
- files.push((0, import_node_path7.relative)(root, fullPath).split("\\").join("/"));
7594
- }
7595
- }
7596
- return files;
7769
+ function matchesConfiguredArtifactPath(path, schema) {
7770
+ const normalizedPath = path.replace(/\\/g, "/").replace(/^\.\//, "");
7771
+ return Object.values(schema.types).some((definition) => definition.paths.some((pattern) => matchesPattern(normalizedPath, pattern)));
7597
7772
  }
7598
7773
  function matchesPattern(file, pattern) {
7599
7774
  if (!pattern.includes("*")) {
@@ -7805,7 +7980,7 @@ function normalizeDesignCode(value) {
7805
7980
  if (!raw) {
7806
7981
  return "";
7807
7982
  }
7808
- return (0, import_node_path7.basename)(raw, (0, import_node_path7.extname)(raw));
7983
+ return (0, import_node_path8.basename)(raw, (0, import_node_path8.extname)(raw));
7809
7984
  }
7810
7985
  function toUid(type, code) {
7811
7986
  return `${type}:${code}`;
@@ -8070,7 +8245,7 @@ function resolveArtifactContext(graph, opts) {
8070
8245
  }
8071
8246
  if (root) {
8072
8247
  for (const ap of ALWAYS_PRESENT_ITEMS) {
8073
- const fullPath = (0, import_node_path7.join)(root, ap.path);
8248
+ const fullPath = (0, import_node_path8.join)(root, ap.path);
8074
8249
  let stat;
8075
8250
  try {
8076
8251
  stat = (0, import_node_fs4.statSync)(fullPath);
@@ -8300,18 +8475,19 @@ function formatContextMarkdown(manifest) {
8300
8475
  }
8301
8476
  return lines.join("\n");
8302
8477
  }
8303
- var import_better_sqlite3, import_gray_matter, import_js_yaml, import_node_fs4, import_promises6, import_node_path7, TARGET_ARTIFACT_TYPES, NON_TARGET_ROLES, DEFAULT_SCHEMA, VALID_TC_STATUSES, VALID_CHAIN_TYPES, DEPRECATED_CHAIN_TYPE_ALIASES, CONTEXT_CATEGORIES, TIER_ORDER;
8478
+ var import_better_sqlite3, import_gray_matter, import_js_yaml, import_node_fs4, import_promises7, import_node_path8, TARGET_ARTIFACT_TYPES, NON_TARGET_ROLES, DEFAULT_SCHEMA, VALID_TC_STATUSES, VALID_CHAIN_TYPES, DEPRECATED_CHAIN_TYPE_ALIASES, CONTEXT_CATEGORIES, TIER_ORDER;
8304
8479
  var init_index = __esm({
8305
8480
  "src/index.ts"() {
8306
8481
  import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
8307
8482
  import_gray_matter = __toESM(require("gray-matter"), 1);
8308
8483
  import_js_yaml = __toESM(require("js-yaml"), 1);
8309
8484
  import_node_fs4 = require("fs");
8310
- import_promises6 = require("fs/promises");
8311
- import_node_path7 = require("path");
8485
+ import_promises7 = require("fs/promises");
8486
+ import_node_path8 = require("path");
8312
8487
  init_packet_constants();
8313
8488
  init_packet_validator();
8314
8489
  init_glob_matcher();
8490
+ init_file_walker();
8315
8491
  init_packet_constants();
8316
8492
  init_target_selector();
8317
8493
  init_packet_assembler();
@@ -8457,10 +8633,12 @@ init_index();
8457
8633
  isPacketTargetType,
8458
8634
  isPacketTargetTypeDynamic,
8459
8635
  isTargetArtifactType,
8636
+ isVersionLockIssueBlocking,
8460
8637
  loadConfig,
8461
8638
  loadContract,
8462
8639
  loadContractCatalog,
8463
8640
  loadContractsFromDirectory,
8641
+ matchesConfiguredArtifactPath,
8464
8642
  nextId,
8465
8643
  normalizeE2eLegacyArtifact,
8466
8644
  normalizeToCanonical,
@@ -8497,5 +8675,6 @@ init_index();
8497
8675
  validateScenarioPrdLinkIndex,
8498
8676
  validateScenarioPrdLinks,
8499
8677
  verifyDigest,
8678
+ versionLockIssueSeverity,
8500
8679
  writeGraphCache
8501
8680
  });