artifact-graph 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.4
4
+
5
+ ### Changed
6
+
7
+ - Version-lock audit and refresh Markdown now explains findings in Chinese, labels each item as a
8
+ blocking error or non-blocking warning, and includes concrete review, refresh, cleanup, and staging
9
+ commands.
10
+ - `missing_lock` remains non-blocking by default and is promoted only by
11
+ `--strict-missing-lock`; runner liveness findings are warnings and no longer fail commits on their
12
+ own. Markdown summaries and CLI exit codes now share the same policy decision.
13
+ - `version-lock refresh --all --remove-orphans` is documented in CLI help and remediation output as
14
+ the safe cleanup path after deleting, renaming, or splitting artifacts. Structural orphan locks
15
+ remain blocking until the user explicitly reviews and removes them.
16
+
17
+ ### Fixed
18
+
19
+ - Pre-commit guidance now distinguishes a successful refresh that changed the lock file from a
20
+ validation failure, and gives the exact review, stage, and retry steps.
21
+
3
22
  ## 0.8.3
4
23
 
5
24
  ### Fixed
package/INSTALL.md CHANGED
@@ -90,6 +90,27 @@ npx artifact-graph version-lock audit --root . --strict-missing-lock
90
90
  > Use `version-lock refresh --all` for the initial lock. The `--changed-only --staged` variant is for
91
91
  > pre-commit hooks on existing projects — not for first-time initialization.
92
92
 
93
+ ## Cleaning Up Orphan Locks
94
+
95
+ After deleting or splitting an artifact (or removing a traceability edge), the affected version
96
+ locks become orphans. Refresh retains them by default; clean them up explicitly and review the
97
+ result before staging:
98
+
99
+ ```bash
100
+ # pnpm
101
+ pnpm exec artifact-graph version-lock refresh --all --remove-orphans --format markdown
102
+ git diff artifacts/traceability-version-lock.json # review the removed lock entries
103
+ git add artifacts/traceability-version-lock.json # stage, then commit again
104
+
105
+ # npm
106
+ npx artifact-graph version-lock refresh --all --remove-orphans --format markdown
107
+ git diff artifacts/traceability-version-lock.json
108
+ git add artifacts/traceability-version-lock.json
109
+ ```
110
+
111
+ `version-lock audit` reports structural orphan locks and stale hashes as blocking issues with
112
+ remediation steps; runner liveness findings are non-blocking warnings.
113
+
93
114
  ## Smoke Test
94
115
 
95
116
  Run these commands to confirm the installation is working:
package/README.md CHANGED
@@ -73,6 +73,28 @@ npx artifact-graph version-lock audit --root . --strict-missing-lock
73
73
  having an empty relation list; run `refresh --all` once to establish the complete relation baseline.
74
74
  - Install opt-in Git hooks with `artifact-graph hooks install-git --hook all`.
75
75
 
76
+ ### Cleaning Up Orphan Locks After Deleting or Splitting Artifacts
77
+
78
+ When an artifact, source file, or traceability edge no longer exists — typically after deleting
79
+ or splitting an artifact — its version locks become orphans. `version-lock refresh` retains
80
+ orphan locks by default so cleanup stays an explicit, reviewable decision:
81
+
82
+ ```bash
83
+ # pnpm
84
+ pnpm exec artifact-graph version-lock refresh --all --remove-orphans --format markdown
85
+ git diff artifacts/traceability-version-lock.json # review the removed lock entries
86
+ git add artifacts/traceability-version-lock.json # stage, then commit again
87
+
88
+ # npm
89
+ npx artifact-graph version-lock refresh --all --remove-orphans --format markdown
90
+ git diff artifacts/traceability-version-lock.json
91
+ git add artifacts/traceability-version-lock.json
92
+ ```
93
+
94
+ `version-lock audit` marks structural orphan locks and stale hashes as blocking issues and prints
95
+ these same remediation steps. Runner liveness findings (a test file no longer active in any
96
+ configured runner) are warnings only and do not block.
97
+
76
98
  ## Review Result Protocol
77
99
 
78
100
  The package publishes `schemas/review-result.schema.json`, plus matching TypeScript types and a
package/README.zh-CN.md CHANGED
@@ -69,6 +69,26 @@ npx artifact-graph version-lock audit --root . --strict-missing-lock
69
69
  `artifactRelations` 时视为空数组;首次启用时执行一次 `refresh --all` 建立完整关系基线。
70
70
  - 用 `artifact-graph hooks install-git --hook all` 安装可选 Git hooks。
71
71
 
72
+ ### 制品删除或拆分后的孤立锁清理
73
+
74
+ 当制品、源码文件或追溯边不再存在时(常见于制品删除或拆分之后),对应的版本锁会成为
75
+ 孤立锁。`version-lock refresh` 默认保留孤立锁,让清理始终是一次显式、可审查的操作:
76
+
77
+ ```bash
78
+ # pnpm
79
+ pnpm exec artifact-graph version-lock refresh --all --remove-orphans --format markdown
80
+ git diff artifacts/traceability-version-lock.json # 审查被移除的锁条目
81
+ git add artifacts/traceability-version-lock.json # 暂存后重新提交
82
+
83
+ # npm
84
+ npx artifact-graph version-lock refresh --all --remove-orphans --format markdown
85
+ git diff artifacts/traceability-version-lock.json
86
+ git add artifacts/traceability-version-lock.json
87
+ ```
88
+
89
+ `version-lock audit` 会把结构性孤立锁和陈旧哈希标记为阻断问题,并输出上述解决步骤;
90
+ runner liveness 发现(测试文件不再处于任何已配置 runner 的激活范围)只是警告,不会阻断。
91
+
72
92
  ## Review Result Protocol
73
93
 
74
94
  包内发布了 `schemas/review-result.schema.json`,以及配套的 TypeScript 类型和 `validateReviewResult`
package/dist/cli.js CHANGED
@@ -1521,6 +1521,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1521
1521
  status: "orphan_lock",
1522
1522
  edgeId: entry.edgeId,
1523
1523
  message: `Liveness: ${sourcePath} is not active in any configured runner \u2014 locked verifies edges may reference dead tests`,
1524
+ severity: "warning",
1524
1525
  artifact: entry.artifact,
1525
1526
  source: entry.source
1526
1527
  });
@@ -1650,7 +1651,84 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1650
1651
  fresh,
1651
1652
  totalArtifactRelationLocks: artifactRelationLocks.length,
1652
1653
  artifactRelationFresh,
1653
- issues: sortBy(issues, (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1654
+ issues: sortBy(issues.map(enrichVersionLockIssue), (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1655
+ };
1656
+ }
1657
+ function isLivenessIssue(issue2) {
1658
+ return issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX);
1659
+ }
1660
+ function versionLockIssueSeverity(issue2) {
1661
+ if (issue2.severity) {
1662
+ return issue2.severity;
1663
+ }
1664
+ if (issue2.status === "missing_lock") {
1665
+ return "warning";
1666
+ }
1667
+ if (issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX)) {
1668
+ return "warning";
1669
+ }
1670
+ return "error";
1671
+ }
1672
+ function isVersionLockIssueBlocking(issue2, strictMissingLock) {
1673
+ if (issue2.status === "missing_lock") {
1674
+ return strictMissingLock;
1675
+ }
1676
+ return versionLockIssueSeverity(issue2) === "error";
1677
+ }
1678
+ function versionLockIssueRemediation(issue2) {
1679
+ switch (issue2.status) {
1680
+ case "artifact_changed":
1681
+ return [
1682
+ "\u786E\u8BA4\u8FD9\u6B21\u5236\u54C1\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1683
+ "\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",
1684
+ "\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"
1685
+ ];
1686
+ case "source_changed":
1687
+ return [
1688
+ "\u786E\u8BA4\u8FD9\u6B21\u6E90\u7801/\u6D4B\u8BD5\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1689
+ "\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",
1690
+ "\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"
1691
+ ];
1692
+ case "verified_by_changed":
1693
+ return [
1694
+ "\u786E\u8BA4\u8FD9\u6B21\u9A8C\u8BC1\u6587\u4EF6\u53D8\u66F4\u662F\u6709\u610F\u7684\u3002",
1695
+ "\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",
1696
+ "\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"
1697
+ ];
1698
+ case "missing_lock":
1699
+ return [
1700
+ "\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",
1701
+ "\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",
1702
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002"
1703
+ ];
1704
+ case "orphan_lock":
1705
+ if (isLivenessIssue(issue2)) {
1706
+ return [
1707
+ "\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",
1708
+ "\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",
1709
+ "\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"
1710
+ ];
1711
+ }
1712
+ return [
1713
+ "\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",
1714
+ "\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",
1715
+ "\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",
1716
+ "\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"
1717
+ ];
1718
+ case "target_not_found":
1719
+ return [
1720
+ "\u68C0\u67E5 `--target` \u7684 `type:id` \u662F\u5426\u62FC\u5199\u6B63\u786E\u3002",
1721
+ "\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"
1722
+ ];
1723
+ default:
1724
+ return [];
1725
+ }
1726
+ }
1727
+ function enrichVersionLockIssue(issue2) {
1728
+ return {
1729
+ ...issue2,
1730
+ severity: versionLockIssueSeverity(issue2),
1731
+ remediation: issue2.remediation ?? versionLockIssueRemediation(issue2)
1654
1732
  };
1655
1733
  }
1656
1734
  async function updateVersionLock(root, options) {
@@ -1948,59 +2026,118 @@ async function traceVersion(root, target, lockPath = VERSION_LOCK_PATH) {
1948
2026
  currentEdges: index.edges.filter((edge2) => edge2.from === targetUid || edge2.to === targetUid),
1949
2027
  locks: lock.locks.filter((entry) => `${entry.artifact.type}:${entry.artifact.id}` === targetUid),
1950
2028
  artifactRelations: targetArtifactRelations,
1951
- issues: [...targetIssues, ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
2029
+ issues: [...targetIssues.map(enrichVersionLockIssue), ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
1952
2030
  };
1953
2031
  }
1954
- function renderVersionLockAuditMarkdown(result) {
2032
+ function renderIssueBlockingTag(issue2, strictMissingLock) {
2033
+ return isVersionLockIssueBlocking(issue2, strictMissingLock) ? "\u963B\u65AD" : "\u8B66\u544A";
2034
+ }
2035
+ function renderIssueBlockingExplanation(issue2, strictMissingLock) {
2036
+ if (!isVersionLockIssueBlocking(issue2, strictMissingLock)) {
2037
+ return "\u5426\uFF08\u4EC5\u63D0\u9192\uFF0C\u4E0D\u963B\u65AD\uFF09";
2038
+ }
2039
+ if (issue2.status === "missing_lock" && versionLockIssueSeverity(issue2) !== "error") {
2040
+ return "\u662F\uFF08\u5DF2\u7531 --strict-missing-lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09";
2041
+ }
2042
+ return "\u662F\uFF08\u4F1A\u4F7F\u547D\u4EE4\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF09";
2043
+ }
2044
+ function appendIssueDetails(lines, issues, strictMissingLock) {
2045
+ issues.forEach((issue2, index) => {
2046
+ const label = VERSION_LOCK_STATUS_LABELS[issue2.status] ?? issue2.status;
2047
+ lines.push(`### ${index + 1}. [${renderIssueBlockingTag(issue2, strictMissingLock)}] ${label}\uFF08\`${issue2.status}\`\uFF09`);
2048
+ lines.push("");
2049
+ lines.push(`- \u8FB9: \`${issue2.edgeId}\``);
2050
+ lines.push(`- \u8BE6\u60C5: ${issue2.message}`);
2051
+ lines.push(`- \u662F\u5426\u963B\u65AD: ${renderIssueBlockingExplanation(issue2, strictMissingLock)}`);
2052
+ const remediation = issue2.remediation ?? [];
2053
+ if (remediation.length > 0) {
2054
+ lines.push("- \u89E3\u51B3\u6B65\u9AA4:");
2055
+ remediation.forEach((step, stepIndex) => {
2056
+ lines.push(` ${stepIndex + 1}. ${step}`);
2057
+ });
2058
+ }
2059
+ lines.push("");
2060
+ });
2061
+ }
2062
+ function renderVersionLockAuditMarkdown(result, options = {}) {
2063
+ const strictMissingLock = options.strictMissingLock === true;
2064
+ const blockingCount = result.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2065
+ const nonBlockingCount = result.issues.length - blockingCount;
1955
2066
  const lines = [
1956
- "# Version Lock Audit",
2067
+ "# \u7248\u672C\u9501\u5BA1\u8BA1\uFF08version-lock audit\uFF09",
1957
2068
  "",
1958
- `Root: \`${result.root}\``,
1959
- `Lock: \`${result.lockPath}\``,
1960
- `Locks: ${result.totalLocks} | Fresh: ${result.fresh} | Issues: ${result.issues.length}`,
1961
- `Artifact Relations: ${result.totalArtifactRelationLocks} | Fresh: ${result.artifactRelationFresh}`,
2069
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2070
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2071
+ `- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: ${result.totalLocks}\uFF08\u65B0\u9C9C ${result.fresh}\uFF09`,
2072
+ `- \u5236\u54C1\u5173\u7CFB\u9501: ${result.totalArtifactRelationLocks}\uFF08\u65B0\u9C9C ${result.artifactRelationFresh}\uFF09`,
2073
+ `- \u963B\u65AD\u7B56\u7565: ${strictMissingLock ? "--strict-missing-lock\uFF08missing_lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09" : "\u9ED8\u8BA4\uFF08missing_lock \u4E0D\u963B\u65AD\uFF09"}`,
2074
+ `- \u95EE\u9898: ${result.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
1962
2075
  ""
1963
2076
  ];
1964
2077
  if (result.issues.length === 0) {
1965
- lines.push("No version lock issues.");
2078
+ lines.push("\u672A\u53D1\u73B0\u7248\u672C\u9501\u95EE\u9898\u3002");
1966
2079
  return `${lines.join("\n")}
1967
2080
  `;
1968
2081
  }
1969
- for (const issue2 of result.issues) {
1970
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2082
+ if (blockingCount > 0) {
2083
+ 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`);
2084
+ } else {
2085
+ 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");
1971
2086
  }
2087
+ lines.push("");
2088
+ lines.push("## \u95EE\u9898\u6E05\u5355");
2089
+ lines.push("");
2090
+ appendIssueDetails(lines, result.issues, strictMissingLock);
1972
2091
  return `${lines.join("\n")}
1973
2092
  `;
1974
2093
  }
1975
2094
  function renderVersionLockRefreshMarkdown(result) {
2095
+ const strictMissingLock = true;
2096
+ const blockingCount = result.postAudit.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2097
+ const nonBlockingCount = result.postAudit.issues.length - blockingCount;
1976
2098
  const lines = [
1977
- "# Version Lock Refresh",
2099
+ "# \u7248\u672C\u9501\u5237\u65B0\uFF08version-lock refresh\uFF09",
2100
+ "",
2101
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2102
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2103
+ `- \u6A21\u5F0F: \`${result.mode}\`\uFF08\u53D8\u66F4\u8DEF\u5F84 ${result.changedPaths.length} \u4E2A\uFF0C\u53D7\u5F71\u54CD\u8FB9 ${result.affectedEdges.length} \u6761\uFF09`,
2104
+ `- \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}`,
2105
+ `- \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}`,
2106
+ `- \u963B\u65AD\u7B56\u7565: refresh \u56FA\u5B9A\u6309 --strict-missing-lock \u5224\u5B9A\uFF0Cmissing_lock \u4E5F\u4F1A\u963B\u65AD`,
2107
+ `- \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898: ${result.postAudit.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
2108
+ "",
2109
+ "\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",
1978
2110
  "",
1979
- `Root: \`${result.root}\``,
1980
- `Lock: \`${result.lockPath}\``,
1981
- `Mode: \`${result.mode}\``,
1982
- `Changed paths: ${result.changedPaths.length}`,
1983
- `Affected edges: ${result.affectedEdges.length}`,
1984
- `Added: ${result.addedLocks.length} | Updated: ${result.updatedLocks.length} | Retained orphans: ${result.retainedOrphans.length} | Removed orphans: ${result.removedOrphans.length}`,
1985
- `Artifact Relations \u2014 Added: ${result.addedArtifactRelationLocks.length} | Updated: ${result.updatedArtifactRelationLocks.length} | Retained orphans: ${result.retainedArtifactRelationOrphans.length} | Removed: ${result.removedArtifactRelationLocks.length}`,
1986
- `Post-audit issues: ${result.postAudit.issues.length}`,
2111
+ "1. `git diff artifacts/traceability-version-lock.json`",
2112
+ "2. `git add artifacts/traceability-version-lock.json`",
2113
+ "3. \u91CD\u65B0\u6267\u884C `git commit`",
1987
2114
  ""
1988
2115
  ];
1989
- appendList(lines, "Added Locks", result.addedLocks);
1990
- appendList(lines, "Updated Locks", result.updatedLocks);
1991
- appendList(lines, "Retained Orphans", result.retainedOrphans);
1992
- appendList(lines, "Removed Orphans", result.removedOrphans);
1993
- appendList(lines, "Added Artifact Relation Locks", result.addedArtifactRelationLocks);
1994
- appendList(lines, "Updated Artifact Relation Locks", result.updatedArtifactRelationLocks);
1995
- appendList(lines, "Retained Artifact Relation Orphans", result.retainedArtifactRelationOrphans);
1996
- appendList(lines, "Removed Artifact Relation Locks", result.removedArtifactRelationLocks);
1997
- appendList(lines, "Warnings", result.warnings);
2116
+ appendList(lines, "\u65B0\u589E\u7684\u9501", result.addedLocks);
2117
+ appendList(lines, "\u66F4\u65B0\u7684\u9501", result.updatedLocks);
2118
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u9501", result.retainedOrphans);
2119
+ if (result.retainedOrphans.length > 0) {
2120
+ 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");
2121
+ lines.push("");
2122
+ }
2123
+ appendList(lines, "\u5220\u9664\u7684\u5B64\u7ACB\u9501", result.removedOrphans);
2124
+ appendList(lines, "\u65B0\u589E\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.addedArtifactRelationLocks);
2125
+ appendList(lines, "\u66F4\u65B0\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.updatedArtifactRelationLocks);
2126
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u5236\u54C1\u5173\u7CFB\u9501", result.retainedArtifactRelationOrphans);
2127
+ if (result.retainedArtifactRelationOrphans.length > 0) {
2128
+ 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");
2129
+ lines.push("");
2130
+ }
2131
+ appendList(lines, "\u5220\u9664\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.removedArtifactRelationLocks);
2132
+ appendList(lines, "\u63D0\u9192", result.warnings);
1998
2133
  if (result.postAudit.issues.length > 0) {
1999
- lines.push("## Post-Audit Issues");
2000
- for (const issue2 of result.postAudit.issues) {
2001
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2134
+ if (blockingCount > 0) {
2135
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`);
2136
+ } else {
2137
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u5F53\u524D\u7B56\u7565\u4E0B\u5168\u90E8\u4E0D\u963B\u65AD\uFF09`);
2002
2138
  }
2003
2139
  lines.push("");
2140
+ appendIssueDetails(lines, result.postAudit.issues, strictMissingLock);
2004
2141
  }
2005
2142
  return `${lines.join("\n")}
2006
2143
  `;
@@ -2455,7 +2592,7 @@ async function isFileActiveInRunner(root, filePath, runner) {
2455
2592
  function sortUnique(items) {
2456
2593
  return [...new Set(items)].sort((left, right) => left.localeCompare(right));
2457
2594
  }
2458
- var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
2595
+ var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION, LIVENESS_MESSAGE_PREFIX, VERSION_LOCK_STATUS_LABELS;
2459
2596
  var init_versioned_traceability = __esm({
2460
2597
  "src/versioned-traceability.ts"() {
2461
2598
  "use strict";
@@ -2464,6 +2601,16 @@ var init_versioned_traceability = __esm({
2464
2601
  VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
2465
2602
  VERSION_INDEX_SCHEMA_VERSION = "1.0";
2466
2603
  VERSION_LOCK_SCHEMA_VERSION = "1.0";
2604
+ LIVENESS_MESSAGE_PREFIX = "Liveness:";
2605
+ VERSION_LOCK_STATUS_LABELS = {
2606
+ fresh: "\u65B0\u9C9C",
2607
+ target_not_found: "\u76EE\u6807\u5236\u54C1\u4E0D\u5B58\u5728",
2608
+ artifact_changed: "\u5236\u54C1\u5DF2\u53D8\u5316",
2609
+ source_changed: "\u6E90\u7801/\u6D4B\u8BD5\u5DF2\u53D8\u5316",
2610
+ verified_by_changed: "\u9A8C\u8BC1\u6587\u4EF6\u5DF2\u53D8\u5316",
2611
+ missing_lock: "\u7F3A\u5C11\u7248\u672C\u9501",
2612
+ orphan_lock: "\u5B64\u7ACB\u9501"
2613
+ };
2467
2614
  }
2468
2615
  });
2469
2616
 
@@ -9408,7 +9555,7 @@ async function runCli(argv, io = {}) {
9408
9555
  out(`${JSON.stringify(result, null, 2)}
9409
9556
  `);
9410
9557
  } else {
9411
- out(renderVersionLockAuditMarkdown(result));
9558
+ out(renderVersionLockAuditMarkdown(result, { strictMissingLock: parsed.flags["strict-missing-lock"] === true }));
9412
9559
  }
9413
9560
  return hasBlockingVersionIssues(result.issues, parsed.flags["strict-missing-lock"] === true) && !parsed.flags["warning-only"] ? 1 : 0;
9414
9561
  }
@@ -9925,7 +10072,11 @@ async function runCli(argv, io = {}) {
9925
10072
  }
9926
10073
  }
9927
10074
  function hasBlockingVersionIssues(issues, strictMissingLock) {
9928
- return issues.some((issue2) => issue2.status !== "missing_lock" || strictMissingLock);
10075
+ return issues.some((issue2) => isVersionLockIssueBlocking({
10076
+ status: issue2.status,
10077
+ message: issue2.message ?? "",
10078
+ severity: issue2.severity
10079
+ }, strictMissingLock));
9929
10080
  }
9930
10081
  function isGraphRelevantPath(path) {
9931
10082
  return path === "artifact-graph.config.yaml" || path === VERSION_LOCK_PATH || path.startsWith("artifacts/") || /\.(md|mdx|json|ya?ml|ts|tsx|js|jsx|mts|cts|rs|py|go)$/.test(path);
@@ -9988,6 +10139,12 @@ Commands:
9988
10139
  version-lock update --target <type:id> --source <path> [--verified-by <path,path>] [--lock-path <path>]
9989
10140
  version-lock bootstrap [--force] [--lock-path <path>]
9990
10141
  version-lock refresh (--all | --changed-only (--staged | --worktree | --base <ref>)) [--remove-orphans] [--format json|markdown] [--lock-path <path>]
10142
+ --remove-orphans delete locks whose artifact, source, or traceability edge no longer
10143
+ exists (default: retain them). Typical cleanup after deleting or
10144
+ splitting artifacts:
10145
+ artifact-graph version-lock refresh --all --remove-orphans --format markdown
10146
+ git diff artifacts/traceability-version-lock.json # review
10147
+ git add artifacts/traceability-version-lock.json # stage, then commit
9991
10148
  trace-version --target <type:id> [--format json|markdown] [--warning-only] [--strict-missing-lock] [--lock-path <path>]
9992
10149
  hooks install-git [--hook pre-commit|pre-push|all] [--uninstall]
9993
10150
  next-id <type> --range <name>
package/dist/index.cjs CHANGED
@@ -1563,6 +1563,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1563
1563
  status: "orphan_lock",
1564
1564
  edgeId: entry.edgeId,
1565
1565
  message: `Liveness: ${sourcePath} is not active in any configured runner \u2014 locked verifies edges may reference dead tests`,
1566
+ severity: "warning",
1566
1567
  artifact: entry.artifact,
1567
1568
  source: entry.source
1568
1569
  });
@@ -1692,7 +1693,84 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1692
1693
  fresh,
1693
1694
  totalArtifactRelationLocks: artifactRelationLocks.length,
1694
1695
  artifactRelationFresh,
1695
- issues: sortBy(issues, (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1696
+ issues: sortBy(issues.map(enrichVersionLockIssue), (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1697
+ };
1698
+ }
1699
+ function isLivenessIssue(issue2) {
1700
+ return issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX);
1701
+ }
1702
+ function versionLockIssueSeverity(issue2) {
1703
+ if (issue2.severity) {
1704
+ return issue2.severity;
1705
+ }
1706
+ if (issue2.status === "missing_lock") {
1707
+ return "warning";
1708
+ }
1709
+ if (issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX)) {
1710
+ return "warning";
1711
+ }
1712
+ return "error";
1713
+ }
1714
+ function isVersionLockIssueBlocking(issue2, strictMissingLock) {
1715
+ if (issue2.status === "missing_lock") {
1716
+ return strictMissingLock;
1717
+ }
1718
+ return versionLockIssueSeverity(issue2) === "error";
1719
+ }
1720
+ function versionLockIssueRemediation(issue2) {
1721
+ switch (issue2.status) {
1722
+ case "artifact_changed":
1723
+ return [
1724
+ "\u786E\u8BA4\u8FD9\u6B21\u5236\u54C1\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1725
+ "\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",
1726
+ "\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"
1727
+ ];
1728
+ case "source_changed":
1729
+ return [
1730
+ "\u786E\u8BA4\u8FD9\u6B21\u6E90\u7801/\u6D4B\u8BD5\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1731
+ "\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",
1732
+ "\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"
1733
+ ];
1734
+ case "verified_by_changed":
1735
+ return [
1736
+ "\u786E\u8BA4\u8FD9\u6B21\u9A8C\u8BC1\u6587\u4EF6\u53D8\u66F4\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 "missing_lock":
1741
+ return [
1742
+ "\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",
1743
+ "\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",
1744
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002"
1745
+ ];
1746
+ case "orphan_lock":
1747
+ if (isLivenessIssue(issue2)) {
1748
+ return [
1749
+ "\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",
1750
+ "\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",
1751
+ "\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"
1752
+ ];
1753
+ }
1754
+ return [
1755
+ "\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",
1756
+ "\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",
1757
+ "\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",
1758
+ "\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"
1759
+ ];
1760
+ case "target_not_found":
1761
+ return [
1762
+ "\u68C0\u67E5 `--target` \u7684 `type:id` \u662F\u5426\u62FC\u5199\u6B63\u786E\u3002",
1763
+ "\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"
1764
+ ];
1765
+ default:
1766
+ return [];
1767
+ }
1768
+ }
1769
+ function enrichVersionLockIssue(issue2) {
1770
+ return {
1771
+ ...issue2,
1772
+ severity: versionLockIssueSeverity(issue2),
1773
+ remediation: issue2.remediation ?? versionLockIssueRemediation(issue2)
1696
1774
  };
1697
1775
  }
1698
1776
  async function updateVersionLock(root, options) {
@@ -1990,59 +2068,118 @@ async function traceVersion(root, target, lockPath = VERSION_LOCK_PATH) {
1990
2068
  currentEdges: index.edges.filter((edge2) => edge2.from === targetUid || edge2.to === targetUid),
1991
2069
  locks: lock.locks.filter((entry) => `${entry.artifact.type}:${entry.artifact.id}` === targetUid),
1992
2070
  artifactRelations: targetArtifactRelations,
1993
- issues: [...targetIssues, ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
2071
+ issues: [...targetIssues.map(enrichVersionLockIssue), ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
1994
2072
  };
1995
2073
  }
1996
- function renderVersionLockAuditMarkdown(result) {
2074
+ function renderIssueBlockingTag(issue2, strictMissingLock) {
2075
+ return isVersionLockIssueBlocking(issue2, strictMissingLock) ? "\u963B\u65AD" : "\u8B66\u544A";
2076
+ }
2077
+ function renderIssueBlockingExplanation(issue2, strictMissingLock) {
2078
+ if (!isVersionLockIssueBlocking(issue2, strictMissingLock)) {
2079
+ return "\u5426\uFF08\u4EC5\u63D0\u9192\uFF0C\u4E0D\u963B\u65AD\uFF09";
2080
+ }
2081
+ if (issue2.status === "missing_lock" && versionLockIssueSeverity(issue2) !== "error") {
2082
+ return "\u662F\uFF08\u5DF2\u7531 --strict-missing-lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09";
2083
+ }
2084
+ return "\u662F\uFF08\u4F1A\u4F7F\u547D\u4EE4\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF09";
2085
+ }
2086
+ function appendIssueDetails(lines, issues, strictMissingLock) {
2087
+ issues.forEach((issue2, index) => {
2088
+ const label = VERSION_LOCK_STATUS_LABELS[issue2.status] ?? issue2.status;
2089
+ lines.push(`### ${index + 1}. [${renderIssueBlockingTag(issue2, strictMissingLock)}] ${label}\uFF08\`${issue2.status}\`\uFF09`);
2090
+ lines.push("");
2091
+ lines.push(`- \u8FB9: \`${issue2.edgeId}\``);
2092
+ lines.push(`- \u8BE6\u60C5: ${issue2.message}`);
2093
+ lines.push(`- \u662F\u5426\u963B\u65AD: ${renderIssueBlockingExplanation(issue2, strictMissingLock)}`);
2094
+ const remediation = issue2.remediation ?? [];
2095
+ if (remediation.length > 0) {
2096
+ lines.push("- \u89E3\u51B3\u6B65\u9AA4:");
2097
+ remediation.forEach((step, stepIndex) => {
2098
+ lines.push(` ${stepIndex + 1}. ${step}`);
2099
+ });
2100
+ }
2101
+ lines.push("");
2102
+ });
2103
+ }
2104
+ function renderVersionLockAuditMarkdown(result, options = {}) {
2105
+ const strictMissingLock = options.strictMissingLock === true;
2106
+ const blockingCount = result.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2107
+ const nonBlockingCount = result.issues.length - blockingCount;
1997
2108
  const lines = [
1998
- "# Version Lock Audit",
2109
+ "# \u7248\u672C\u9501\u5BA1\u8BA1\uFF08version-lock audit\uFF09",
1999
2110
  "",
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}`,
2111
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2112
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2113
+ `- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: ${result.totalLocks}\uFF08\u65B0\u9C9C ${result.fresh}\uFF09`,
2114
+ `- \u5236\u54C1\u5173\u7CFB\u9501: ${result.totalArtifactRelationLocks}\uFF08\u65B0\u9C9C ${result.artifactRelationFresh}\uFF09`,
2115
+ `- \u963B\u65AD\u7B56\u7565: ${strictMissingLock ? "--strict-missing-lock\uFF08missing_lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09" : "\u9ED8\u8BA4\uFF08missing_lock \u4E0D\u963B\u65AD\uFF09"}`,
2116
+ `- \u95EE\u9898: ${result.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
2004
2117
  ""
2005
2118
  ];
2006
2119
  if (result.issues.length === 0) {
2007
- lines.push("No version lock issues.");
2120
+ lines.push("\u672A\u53D1\u73B0\u7248\u672C\u9501\u95EE\u9898\u3002");
2008
2121
  return `${lines.join("\n")}
2009
2122
  `;
2010
2123
  }
2011
- for (const issue2 of result.issues) {
2012
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2124
+ if (blockingCount > 0) {
2125
+ 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`);
2126
+ } else {
2127
+ 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
2128
  }
2129
+ lines.push("");
2130
+ lines.push("## \u95EE\u9898\u6E05\u5355");
2131
+ lines.push("");
2132
+ appendIssueDetails(lines, result.issues, strictMissingLock);
2014
2133
  return `${lines.join("\n")}
2015
2134
  `;
2016
2135
  }
2017
2136
  function renderVersionLockRefreshMarkdown(result) {
2137
+ const strictMissingLock = true;
2138
+ const blockingCount = result.postAudit.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2139
+ const nonBlockingCount = result.postAudit.issues.length - blockingCount;
2018
2140
  const lines = [
2019
- "# Version Lock Refresh",
2141
+ "# \u7248\u672C\u9501\u5237\u65B0\uFF08version-lock refresh\uFF09",
2142
+ "",
2143
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2144
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2145
+ `- \u6A21\u5F0F: \`${result.mode}\`\uFF08\u53D8\u66F4\u8DEF\u5F84 ${result.changedPaths.length} \u4E2A\uFF0C\u53D7\u5F71\u54CD\u8FB9 ${result.affectedEdges.length} \u6761\uFF09`,
2146
+ `- \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}`,
2147
+ `- \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}`,
2148
+ `- \u963B\u65AD\u7B56\u7565: refresh \u56FA\u5B9A\u6309 --strict-missing-lock \u5224\u5B9A\uFF0Cmissing_lock \u4E5F\u4F1A\u963B\u65AD`,
2149
+ `- \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898: ${result.postAudit.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
2150
+ "",
2151
+ "\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
2152
  "",
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}`,
2153
+ "1. `git diff artifacts/traceability-version-lock.json`",
2154
+ "2. `git add artifacts/traceability-version-lock.json`",
2155
+ "3. \u91CD\u65B0\u6267\u884C `git commit`",
2029
2156
  ""
2030
2157
  ];
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);
2158
+ appendList(lines, "\u65B0\u589E\u7684\u9501", result.addedLocks);
2159
+ appendList(lines, "\u66F4\u65B0\u7684\u9501", result.updatedLocks);
2160
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u9501", result.retainedOrphans);
2161
+ if (result.retainedOrphans.length > 0) {
2162
+ 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");
2163
+ lines.push("");
2164
+ }
2165
+ appendList(lines, "\u5220\u9664\u7684\u5B64\u7ACB\u9501", result.removedOrphans);
2166
+ appendList(lines, "\u65B0\u589E\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.addedArtifactRelationLocks);
2167
+ appendList(lines, "\u66F4\u65B0\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.updatedArtifactRelationLocks);
2168
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u5236\u54C1\u5173\u7CFB\u9501", result.retainedArtifactRelationOrphans);
2169
+ if (result.retainedArtifactRelationOrphans.length > 0) {
2170
+ 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");
2171
+ lines.push("");
2172
+ }
2173
+ appendList(lines, "\u5220\u9664\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.removedArtifactRelationLocks);
2174
+ appendList(lines, "\u63D0\u9192", result.warnings);
2040
2175
  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}`);
2176
+ if (blockingCount > 0) {
2177
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`);
2178
+ } else {
2179
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u5F53\u524D\u7B56\u7565\u4E0B\u5168\u90E8\u4E0D\u963B\u65AD\uFF09`);
2044
2180
  }
2045
2181
  lines.push("");
2182
+ appendIssueDetails(lines, result.postAudit.issues, strictMissingLock);
2046
2183
  }
2047
2184
  return `${lines.join("\n")}
2048
2185
  `;
@@ -2497,7 +2634,7 @@ async function isFileActiveInRunner(root, filePath, runner) {
2497
2634
  function sortUnique(items) {
2498
2635
  return [...new Set(items)].sort((left, right) => left.localeCompare(right));
2499
2636
  }
2500
- var import_node_crypto, import_node_fs, import_promises2, import_node_path2, VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
2637
+ var import_node_crypto, import_node_fs, import_promises2, import_node_path2, VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION, LIVENESS_MESSAGE_PREFIX, VERSION_LOCK_STATUS_LABELS;
2501
2638
  var init_versioned_traceability = __esm({
2502
2639
  "src/versioned-traceability.ts"() {
2503
2640
  "use strict";
@@ -2510,6 +2647,16 @@ var init_versioned_traceability = __esm({
2510
2647
  VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
2511
2648
  VERSION_INDEX_SCHEMA_VERSION = "1.0";
2512
2649
  VERSION_LOCK_SCHEMA_VERSION = "1.0";
2650
+ LIVENESS_MESSAGE_PREFIX = "Liveness:";
2651
+ VERSION_LOCK_STATUS_LABELS = {
2652
+ fresh: "\u65B0\u9C9C",
2653
+ target_not_found: "\u76EE\u6807\u5236\u54C1\u4E0D\u5B58\u5728",
2654
+ artifact_changed: "\u5236\u54C1\u5DF2\u53D8\u5316",
2655
+ source_changed: "\u6E90\u7801/\u6D4B\u8BD5\u5DF2\u53D8\u5316",
2656
+ verified_by_changed: "\u9A8C\u8BC1\u6587\u4EF6\u5DF2\u53D8\u5316",
2657
+ missing_lock: "\u7F3A\u5C11\u7248\u672C\u9501",
2658
+ orphan_lock: "\u5B64\u7ACB\u9501"
2659
+ };
2513
2660
  }
2514
2661
  });
2515
2662
 
@@ -4351,6 +4498,7 @@ __export(index_exports, {
4351
4498
  isPacketTargetType: () => isPacketTargetType,
4352
4499
  isPacketTargetTypeDynamic: () => isPacketTargetTypeDynamic,
4353
4500
  isTargetArtifactType: () => isTargetArtifactType,
4501
+ isVersionLockIssueBlocking: () => isVersionLockIssueBlocking,
4354
4502
  loadConfig: () => loadConfig,
4355
4503
  loadContract: () => loadContract,
4356
4504
  loadContractCatalog: () => loadContractCatalog,
@@ -4391,6 +4539,7 @@ __export(index_exports, {
4391
4539
  validateScenarioPrdLinkIndex: () => validateScenarioPrdLinkIndex,
4392
4540
  validateScenarioPrdLinks: () => validateScenarioPrdLinks,
4393
4541
  verifyDigest: () => verifyDigest,
4542
+ versionLockIssueSeverity: () => versionLockIssueSeverity,
4394
4543
  writeGraphCache: () => writeGraphCache
4395
4544
  });
4396
4545
  module.exports = __toCommonJS(index_exports);
@@ -8457,6 +8606,7 @@ init_index();
8457
8606
  isPacketTargetType,
8458
8607
  isPacketTargetTypeDynamic,
8459
8608
  isTargetArtifactType,
8609
+ isVersionLockIssueBlocking,
8460
8610
  loadConfig,
8461
8611
  loadContract,
8462
8612
  loadContractCatalog,
@@ -8497,5 +8647,6 @@ init_index();
8497
8647
  validateScenarioPrdLinkIndex,
8498
8648
  validateScenarioPrdLinks,
8499
8649
  verifyDigest,
8650
+ versionLockIssueSeverity,
8500
8651
  writeGraphCache
8501
8652
  });
package/dist/index.d.cts CHANGED
@@ -509,10 +509,20 @@ interface VersionLockFile {
509
509
  locks: VersionLockEntry[];
510
510
  artifactRelations?: ArtifactRelationLock[];
511
511
  }
512
+ type VersionLockIssueSeverity = 'error' | 'warning';
512
513
  interface VersionLockIssue {
513
514
  status: VersionLockStatus;
514
515
  edgeId: string;
515
516
  message: string;
517
+ /**
518
+ * Backward-compatible severity. `error` issues block audit/refresh exit codes;
519
+ * `warning` issues (e.g. runner liveness hints, non-strict missing_lock) never
520
+ * block on their own. Older producers may omit it; consumers should fall back
521
+ * via `versionLockIssueSeverity`.
522
+ */
523
+ severity?: VersionLockIssueSeverity;
524
+ /** Human-readable remediation steps (Chinese), ordered. */
525
+ remediation?: string[];
516
526
  artifact?: VersionLockRef;
517
527
  source?: VersionLockSourceRef | VersionLockRef;
518
528
  currentArtifactHash?: string;
@@ -580,11 +590,42 @@ interface VersionLockRefreshResult {
580
590
  }
581
591
  declare function buildVersionIndex(root: string, graph?: ArtifactGraph): Promise<VersionIndex>;
582
592
  declare function auditVersionLock(root: string, lockPath?: string, graph?: ArtifactGraph, config?: ArtifactSchema): Promise<VersionLockAuditResult>;
593
+ /**
594
+ * Resolve the effective severity of an issue, tolerating issues produced before
595
+ * the severity field existed. Structural orphans and stale hashes are errors;
596
+ * runner liveness hints and missing locks are warnings (missing_lock is only
597
+ * escalated to blocking by the --strict-missing-lock policy, not by severity).
598
+ */
599
+ declare function versionLockIssueSeverity(issue: {
600
+ status: string;
601
+ message: string;
602
+ severity?: VersionLockIssueSeverity;
603
+ }): VersionLockIssueSeverity;
604
+ /**
605
+ * Decide whether an issue blocks the current command under the given policy.
606
+ * This is deliberately separate from the intrinsic `severity`: missing_lock is
607
+ * a warning by nature, but the --strict-missing-lock policy (always on for
608
+ * `version-lock refresh`, opt-in for `audit`/`trace-version`) escalates it to
609
+ * blocking. CLI exit codes and Markdown rendering must both go through this
610
+ * function so they never disagree.
611
+ */
612
+ declare function isVersionLockIssueBlocking(issue: {
613
+ status: string;
614
+ message: string;
615
+ severity?: VersionLockIssueSeverity;
616
+ }, strictMissingLock: boolean): boolean;
583
617
  declare function updateVersionLock(root: string, options: VersionLockUpdateOptions): Promise<VersionLockFile>;
584
618
  declare function bootstrapVersionLock(root: string, options?: VersionLockBootstrapOptions): Promise<VersionLockFile>;
585
619
  declare function refreshVersionLock(root: string, options?: VersionLockRefreshOptions): Promise<VersionLockRefreshResult>;
586
620
  declare function traceVersion(root: string, target: string, lockPath?: string): Promise<TraceVersionResult>;
587
- declare function renderVersionLockAuditMarkdown(result: VersionLockAuditResult): string;
621
+ interface VersionLockAuditMarkdownOptions {
622
+ /**
623
+ * 当前命令的 missing_lock 阻断策略。CLI 必须传入实际的
624
+ * --strict-missing-lock flag,否则 markdown 的“是否阻断”会与退出码不一致。
625
+ */
626
+ strictMissingLock?: boolean;
627
+ }
628
+ declare function renderVersionLockAuditMarkdown(result: VersionLockAuditResult, options?: VersionLockAuditMarkdownOptions): string;
588
629
  declare function renderVersionLockRefreshMarkdown(result: VersionLockRefreshResult): string;
589
630
  declare function renderTraceVersionMarkdown(result: TraceVersionResult): string;
590
631
 
@@ -1456,4 +1497,4 @@ declare function discoverTargets(graph: ArtifactGraph, options?: DiscoverOptions
1456
1497
  declare function resolveArtifactContext(graph: ArtifactGraph, opts: ContextOptions): ContextManifest;
1457
1498
  declare function formatContextMarkdown(manifest: ContextManifest): string;
1458
1499
 
1459
- export { ALWAYS_PRESENT_ITEMS as ALWAYS_PRESENT, type ArtifactChainDoctorReport, type ArtifactEdge, type ArtifactEdgeRule, type ArtifactExtraFieldSchema, type ArtifactGraph, type ArtifactGraphCliCandidate, type ArtifactGraphCliResolution, type ArtifactGraphCliSource, type ArtifactNode, type ArtifactSchema, type ArtifactTarget, type ArtifactTypeMetadata, type ArtifactTypeRole, type ArtifactTypeSchema, BASELINE_CONSTRAINTS, BASELINE_CONSTRAINTS_COUNT, BASELINE_ITEMS_COUNT, type BatchDefinition, CONTRACT_ERROR_CODES, type CanonicalIR, type CollectChangedPathsOptions, type ContextItem, type ContextManifest, type ContextMode, type ContextOptions, type ContextTier, ContractCatalog, type ContractCatalogEntry, type ContractDefinition, ContractError, type ContractErrorCode, type ContractIdentity, ContractRegistry, type ContractRegistryEntry, type ContractSchema, DEFAULT_MAX_CHARS, DEFAULT_SCHEMA, type DiscoverOptions, E2E_NORMALIZER_CONFIG, type E2eCoverageStats, type E2eCoverageThresholds, type E2eRegistry, type E2eRegistryBatch, type E2eRunnerConfig, type E2eWaiver, type Evidence, type EvidenceObject, type ExecutorType, type Finding, type FindingLocation, type FindingSeverity, type FindingStatus, type GitChangeMode, type GitChangeResult, type GitHookName, type HookInstallResult, type ImplementationBlueprintDraft, type ImplementationPacket, type LegacyFieldMapping, type LoadContractOptions, MIN_PROMPT_CHARS, type ManagedHookBlockOptions, type MissingDetail, type NormalizationResult, type NormalizerConfig, type PacketAuditEntry, type PacketAuditSummary, type PacketCategory, type PacketItem, type PacketOmittedItem, type PacketOptions, type PacketPromptError, type PacketPromptOptions, type PacketTarget, type PacketTargetType, type PacketValidationIssue, type PacketValidationResult, type PolicyCompatibilityResult, type PreparedManagedHookBlock, type Producer, type ProjectPolicy, type PromptValidationIssue, type PromptValidationResult, type QueryOptions, type RepairData, type RepairValidation, type ResolveArtifactGraphCliOptions, type ReviewData, type ReviewDecision, type ReviewMetrics, type ReviewOrderStep, type ReviewResult, type ReviewStatus, type RiskChecklistItem, type SchemaValidationResult, TARGET_ARTIFACT_TYPES, type TargetArtifactType, type TraceVersionResult, VALID_PACKET_TARGET_TYPES, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_PATH, VERSION_LOCK_SCHEMA_VERSION, type ValidationError, type ValidationIssue, type VersionEdgeKind, type VersionIndex, type VersionLockAuditResult, type VersionLockBootstrapOptions, type VersionLockEntry, type VersionLockFile, type VersionLockIssue, type VersionLockRef, type VersionLockRefreshOptions, type VersionLockRefreshResult, type VersionLockSourceRef, type VersionLockStatus, type VersionLockUpdateOptions, type VersionSourceKind, type VersionedEdge, type VersionedNode, applyPreparedManagedHookBlocks, assemblePacket, auditPackets, auditVersionLock, bootstrapVersionLock, buildGraph, buildVersionIndex, collectChangedPaths, computeE2eCoverageStats, computeRevisionDigest, discoverAndAuditPackets, discoverTargets, doctorArtifactChain, formatContextMarkdown, generateE2eRegistry, getArtifactTypeMetadata, getTargetArtifactTypes, installManagedHookBlock, isOfficialNamespace, isPacketTargetType, isPacketTargetTypeDynamic, isTargetArtifactType, loadConfig, loadContract, loadContractCatalog, loadContractsFromDirectory, nextId, normalizeE2eLegacyArtifact, normalizeToCanonical, parseTargetSelector, parseTargetsFile, prepareManagedHookBlock, queryGraph, refreshVersionLock, renderDoctorMarkdown, renderMermaid, renderPacketMarkdown, renderPacketPrompt, renderTraceVersionMarkdown, renderVersionLockAuditMarkdown, renderVersionLockRefreshMarkdown, resolveArtifactContext, resolveArtifactGraphCli, resolveArtifactTypeName, resolveCliTarget, resolveGitHookPath, resolveMatrixEdges, scanArtifacts, traceVersion, updateVersionLock, validateContractAgainstSchema, validateExecutableTraceability, validateGraph, validateNamespaceAuthority, validatePacket, validatePacketMarkdown, validatePacketPrompt, validatePolicyCompatibility, validateReviewResult, validateScenarioPrdLinkIndex, validateScenarioPrdLinks, verifyDigest, writeGraphCache };
1500
+ export { ALWAYS_PRESENT_ITEMS as ALWAYS_PRESENT, type ArtifactChainDoctorReport, type ArtifactEdge, type ArtifactEdgeRule, type ArtifactExtraFieldSchema, type ArtifactGraph, type ArtifactGraphCliCandidate, type ArtifactGraphCliResolution, type ArtifactGraphCliSource, type ArtifactNode, type ArtifactSchema, type ArtifactTarget, type ArtifactTypeMetadata, type ArtifactTypeRole, type ArtifactTypeSchema, BASELINE_CONSTRAINTS, BASELINE_CONSTRAINTS_COUNT, BASELINE_ITEMS_COUNT, type BatchDefinition, CONTRACT_ERROR_CODES, type CanonicalIR, type CollectChangedPathsOptions, type ContextItem, type ContextManifest, type ContextMode, type ContextOptions, type ContextTier, ContractCatalog, type ContractCatalogEntry, type ContractDefinition, ContractError, type ContractErrorCode, type ContractIdentity, ContractRegistry, type ContractRegistryEntry, type ContractSchema, DEFAULT_MAX_CHARS, DEFAULT_SCHEMA, type DiscoverOptions, E2E_NORMALIZER_CONFIG, type E2eCoverageStats, type E2eCoverageThresholds, type E2eRegistry, type E2eRegistryBatch, type E2eRunnerConfig, type E2eWaiver, type Evidence, type EvidenceObject, type ExecutorType, type Finding, type FindingLocation, type FindingSeverity, type FindingStatus, type GitChangeMode, type GitChangeResult, type GitHookName, type HookInstallResult, type ImplementationBlueprintDraft, type ImplementationPacket, type LegacyFieldMapping, type LoadContractOptions, MIN_PROMPT_CHARS, type ManagedHookBlockOptions, type MissingDetail, type NormalizationResult, type NormalizerConfig, type PacketAuditEntry, type PacketAuditSummary, type PacketCategory, type PacketItem, type PacketOmittedItem, type PacketOptions, type PacketPromptError, type PacketPromptOptions, type PacketTarget, type PacketTargetType, type PacketValidationIssue, type PacketValidationResult, type PolicyCompatibilityResult, type PreparedManagedHookBlock, type Producer, type ProjectPolicy, type PromptValidationIssue, type PromptValidationResult, type QueryOptions, type RepairData, type RepairValidation, type ResolveArtifactGraphCliOptions, type ReviewData, type ReviewDecision, type ReviewMetrics, type ReviewOrderStep, type ReviewResult, type ReviewStatus, type RiskChecklistItem, type SchemaValidationResult, TARGET_ARTIFACT_TYPES, type TargetArtifactType, type TraceVersionResult, VALID_PACKET_TARGET_TYPES, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_PATH, VERSION_LOCK_SCHEMA_VERSION, type ValidationError, type ValidationIssue, type VersionEdgeKind, type VersionIndex, type VersionLockAuditMarkdownOptions, type VersionLockAuditResult, type VersionLockBootstrapOptions, type VersionLockEntry, type VersionLockFile, type VersionLockIssue, type VersionLockIssueSeverity, type VersionLockRef, type VersionLockRefreshOptions, type VersionLockRefreshResult, type VersionLockSourceRef, type VersionLockStatus, type VersionLockUpdateOptions, type VersionSourceKind, type VersionedEdge, type VersionedNode, applyPreparedManagedHookBlocks, assemblePacket, auditPackets, auditVersionLock, bootstrapVersionLock, buildGraph, buildVersionIndex, collectChangedPaths, computeE2eCoverageStats, computeRevisionDigest, discoverAndAuditPackets, discoverTargets, doctorArtifactChain, formatContextMarkdown, generateE2eRegistry, getArtifactTypeMetadata, getTargetArtifactTypes, installManagedHookBlock, isOfficialNamespace, isPacketTargetType, isPacketTargetTypeDynamic, isTargetArtifactType, isVersionLockIssueBlocking, loadConfig, loadContract, loadContractCatalog, loadContractsFromDirectory, nextId, normalizeE2eLegacyArtifact, normalizeToCanonical, parseTargetSelector, parseTargetsFile, prepareManagedHookBlock, queryGraph, refreshVersionLock, renderDoctorMarkdown, renderMermaid, renderPacketMarkdown, renderPacketPrompt, renderTraceVersionMarkdown, renderVersionLockAuditMarkdown, renderVersionLockRefreshMarkdown, resolveArtifactContext, resolveArtifactGraphCli, resolveArtifactTypeName, resolveCliTarget, resolveGitHookPath, resolveMatrixEdges, scanArtifacts, traceVersion, updateVersionLock, validateContractAgainstSchema, validateExecutableTraceability, validateGraph, validateNamespaceAuthority, validatePacket, validatePacketMarkdown, validatePacketPrompt, validatePolicyCompatibility, validateReviewResult, validateScenarioPrdLinkIndex, validateScenarioPrdLinks, verifyDigest, versionLockIssueSeverity, writeGraphCache };
package/dist/index.d.ts CHANGED
@@ -509,10 +509,20 @@ interface VersionLockFile {
509
509
  locks: VersionLockEntry[];
510
510
  artifactRelations?: ArtifactRelationLock[];
511
511
  }
512
+ type VersionLockIssueSeverity = 'error' | 'warning';
512
513
  interface VersionLockIssue {
513
514
  status: VersionLockStatus;
514
515
  edgeId: string;
515
516
  message: string;
517
+ /**
518
+ * Backward-compatible severity. `error` issues block audit/refresh exit codes;
519
+ * `warning` issues (e.g. runner liveness hints, non-strict missing_lock) never
520
+ * block on their own. Older producers may omit it; consumers should fall back
521
+ * via `versionLockIssueSeverity`.
522
+ */
523
+ severity?: VersionLockIssueSeverity;
524
+ /** Human-readable remediation steps (Chinese), ordered. */
525
+ remediation?: string[];
516
526
  artifact?: VersionLockRef;
517
527
  source?: VersionLockSourceRef | VersionLockRef;
518
528
  currentArtifactHash?: string;
@@ -580,11 +590,42 @@ interface VersionLockRefreshResult {
580
590
  }
581
591
  declare function buildVersionIndex(root: string, graph?: ArtifactGraph): Promise<VersionIndex>;
582
592
  declare function auditVersionLock(root: string, lockPath?: string, graph?: ArtifactGraph, config?: ArtifactSchema): Promise<VersionLockAuditResult>;
593
+ /**
594
+ * Resolve the effective severity of an issue, tolerating issues produced before
595
+ * the severity field existed. Structural orphans and stale hashes are errors;
596
+ * runner liveness hints and missing locks are warnings (missing_lock is only
597
+ * escalated to blocking by the --strict-missing-lock policy, not by severity).
598
+ */
599
+ declare function versionLockIssueSeverity(issue: {
600
+ status: string;
601
+ message: string;
602
+ severity?: VersionLockIssueSeverity;
603
+ }): VersionLockIssueSeverity;
604
+ /**
605
+ * Decide whether an issue blocks the current command under the given policy.
606
+ * This is deliberately separate from the intrinsic `severity`: missing_lock is
607
+ * a warning by nature, but the --strict-missing-lock policy (always on for
608
+ * `version-lock refresh`, opt-in for `audit`/`trace-version`) escalates it to
609
+ * blocking. CLI exit codes and Markdown rendering must both go through this
610
+ * function so they never disagree.
611
+ */
612
+ declare function isVersionLockIssueBlocking(issue: {
613
+ status: string;
614
+ message: string;
615
+ severity?: VersionLockIssueSeverity;
616
+ }, strictMissingLock: boolean): boolean;
583
617
  declare function updateVersionLock(root: string, options: VersionLockUpdateOptions): Promise<VersionLockFile>;
584
618
  declare function bootstrapVersionLock(root: string, options?: VersionLockBootstrapOptions): Promise<VersionLockFile>;
585
619
  declare function refreshVersionLock(root: string, options?: VersionLockRefreshOptions): Promise<VersionLockRefreshResult>;
586
620
  declare function traceVersion(root: string, target: string, lockPath?: string): Promise<TraceVersionResult>;
587
- declare function renderVersionLockAuditMarkdown(result: VersionLockAuditResult): string;
621
+ interface VersionLockAuditMarkdownOptions {
622
+ /**
623
+ * 当前命令的 missing_lock 阻断策略。CLI 必须传入实际的
624
+ * --strict-missing-lock flag,否则 markdown 的“是否阻断”会与退出码不一致。
625
+ */
626
+ strictMissingLock?: boolean;
627
+ }
628
+ declare function renderVersionLockAuditMarkdown(result: VersionLockAuditResult, options?: VersionLockAuditMarkdownOptions): string;
588
629
  declare function renderVersionLockRefreshMarkdown(result: VersionLockRefreshResult): string;
589
630
  declare function renderTraceVersionMarkdown(result: TraceVersionResult): string;
590
631
 
@@ -1456,4 +1497,4 @@ declare function discoverTargets(graph: ArtifactGraph, options?: DiscoverOptions
1456
1497
  declare function resolveArtifactContext(graph: ArtifactGraph, opts: ContextOptions): ContextManifest;
1457
1498
  declare function formatContextMarkdown(manifest: ContextManifest): string;
1458
1499
 
1459
- export { ALWAYS_PRESENT_ITEMS as ALWAYS_PRESENT, type ArtifactChainDoctorReport, type ArtifactEdge, type ArtifactEdgeRule, type ArtifactExtraFieldSchema, type ArtifactGraph, type ArtifactGraphCliCandidate, type ArtifactGraphCliResolution, type ArtifactGraphCliSource, type ArtifactNode, type ArtifactSchema, type ArtifactTarget, type ArtifactTypeMetadata, type ArtifactTypeRole, type ArtifactTypeSchema, BASELINE_CONSTRAINTS, BASELINE_CONSTRAINTS_COUNT, BASELINE_ITEMS_COUNT, type BatchDefinition, CONTRACT_ERROR_CODES, type CanonicalIR, type CollectChangedPathsOptions, type ContextItem, type ContextManifest, type ContextMode, type ContextOptions, type ContextTier, ContractCatalog, type ContractCatalogEntry, type ContractDefinition, ContractError, type ContractErrorCode, type ContractIdentity, ContractRegistry, type ContractRegistryEntry, type ContractSchema, DEFAULT_MAX_CHARS, DEFAULT_SCHEMA, type DiscoverOptions, E2E_NORMALIZER_CONFIG, type E2eCoverageStats, type E2eCoverageThresholds, type E2eRegistry, type E2eRegistryBatch, type E2eRunnerConfig, type E2eWaiver, type Evidence, type EvidenceObject, type ExecutorType, type Finding, type FindingLocation, type FindingSeverity, type FindingStatus, type GitChangeMode, type GitChangeResult, type GitHookName, type HookInstallResult, type ImplementationBlueprintDraft, type ImplementationPacket, type LegacyFieldMapping, type LoadContractOptions, MIN_PROMPT_CHARS, type ManagedHookBlockOptions, type MissingDetail, type NormalizationResult, type NormalizerConfig, type PacketAuditEntry, type PacketAuditSummary, type PacketCategory, type PacketItem, type PacketOmittedItem, type PacketOptions, type PacketPromptError, type PacketPromptOptions, type PacketTarget, type PacketTargetType, type PacketValidationIssue, type PacketValidationResult, type PolicyCompatibilityResult, type PreparedManagedHookBlock, type Producer, type ProjectPolicy, type PromptValidationIssue, type PromptValidationResult, type QueryOptions, type RepairData, type RepairValidation, type ResolveArtifactGraphCliOptions, type ReviewData, type ReviewDecision, type ReviewMetrics, type ReviewOrderStep, type ReviewResult, type ReviewStatus, type RiskChecklistItem, type SchemaValidationResult, TARGET_ARTIFACT_TYPES, type TargetArtifactType, type TraceVersionResult, VALID_PACKET_TARGET_TYPES, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_PATH, VERSION_LOCK_SCHEMA_VERSION, type ValidationError, type ValidationIssue, type VersionEdgeKind, type VersionIndex, type VersionLockAuditResult, type VersionLockBootstrapOptions, type VersionLockEntry, type VersionLockFile, type VersionLockIssue, type VersionLockRef, type VersionLockRefreshOptions, type VersionLockRefreshResult, type VersionLockSourceRef, type VersionLockStatus, type VersionLockUpdateOptions, type VersionSourceKind, type VersionedEdge, type VersionedNode, applyPreparedManagedHookBlocks, assemblePacket, auditPackets, auditVersionLock, bootstrapVersionLock, buildGraph, buildVersionIndex, collectChangedPaths, computeE2eCoverageStats, computeRevisionDigest, discoverAndAuditPackets, discoverTargets, doctorArtifactChain, formatContextMarkdown, generateE2eRegistry, getArtifactTypeMetadata, getTargetArtifactTypes, installManagedHookBlock, isOfficialNamespace, isPacketTargetType, isPacketTargetTypeDynamic, isTargetArtifactType, loadConfig, loadContract, loadContractCatalog, loadContractsFromDirectory, nextId, normalizeE2eLegacyArtifact, normalizeToCanonical, parseTargetSelector, parseTargetsFile, prepareManagedHookBlock, queryGraph, refreshVersionLock, renderDoctorMarkdown, renderMermaid, renderPacketMarkdown, renderPacketPrompt, renderTraceVersionMarkdown, renderVersionLockAuditMarkdown, renderVersionLockRefreshMarkdown, resolveArtifactContext, resolveArtifactGraphCli, resolveArtifactTypeName, resolveCliTarget, resolveGitHookPath, resolveMatrixEdges, scanArtifacts, traceVersion, updateVersionLock, validateContractAgainstSchema, validateExecutableTraceability, validateGraph, validateNamespaceAuthority, validatePacket, validatePacketMarkdown, validatePacketPrompt, validatePolicyCompatibility, validateReviewResult, validateScenarioPrdLinkIndex, validateScenarioPrdLinks, verifyDigest, writeGraphCache };
1500
+ export { ALWAYS_PRESENT_ITEMS as ALWAYS_PRESENT, type ArtifactChainDoctorReport, type ArtifactEdge, type ArtifactEdgeRule, type ArtifactExtraFieldSchema, type ArtifactGraph, type ArtifactGraphCliCandidate, type ArtifactGraphCliResolution, type ArtifactGraphCliSource, type ArtifactNode, type ArtifactSchema, type ArtifactTarget, type ArtifactTypeMetadata, type ArtifactTypeRole, type ArtifactTypeSchema, BASELINE_CONSTRAINTS, BASELINE_CONSTRAINTS_COUNT, BASELINE_ITEMS_COUNT, type BatchDefinition, CONTRACT_ERROR_CODES, type CanonicalIR, type CollectChangedPathsOptions, type ContextItem, type ContextManifest, type ContextMode, type ContextOptions, type ContextTier, ContractCatalog, type ContractCatalogEntry, type ContractDefinition, ContractError, type ContractErrorCode, type ContractIdentity, ContractRegistry, type ContractRegistryEntry, type ContractSchema, DEFAULT_MAX_CHARS, DEFAULT_SCHEMA, type DiscoverOptions, E2E_NORMALIZER_CONFIG, type E2eCoverageStats, type E2eCoverageThresholds, type E2eRegistry, type E2eRegistryBatch, type E2eRunnerConfig, type E2eWaiver, type Evidence, type EvidenceObject, type ExecutorType, type Finding, type FindingLocation, type FindingSeverity, type FindingStatus, type GitChangeMode, type GitChangeResult, type GitHookName, type HookInstallResult, type ImplementationBlueprintDraft, type ImplementationPacket, type LegacyFieldMapping, type LoadContractOptions, MIN_PROMPT_CHARS, type ManagedHookBlockOptions, type MissingDetail, type NormalizationResult, type NormalizerConfig, type PacketAuditEntry, type PacketAuditSummary, type PacketCategory, type PacketItem, type PacketOmittedItem, type PacketOptions, type PacketPromptError, type PacketPromptOptions, type PacketTarget, type PacketTargetType, type PacketValidationIssue, type PacketValidationResult, type PolicyCompatibilityResult, type PreparedManagedHookBlock, type Producer, type ProjectPolicy, type PromptValidationIssue, type PromptValidationResult, type QueryOptions, type RepairData, type RepairValidation, type ResolveArtifactGraphCliOptions, type ReviewData, type ReviewDecision, type ReviewMetrics, type ReviewOrderStep, type ReviewResult, type ReviewStatus, type RiskChecklistItem, type SchemaValidationResult, TARGET_ARTIFACT_TYPES, type TargetArtifactType, type TraceVersionResult, VALID_PACKET_TARGET_TYPES, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_PATH, VERSION_LOCK_SCHEMA_VERSION, type ValidationError, type ValidationIssue, type VersionEdgeKind, type VersionIndex, type VersionLockAuditMarkdownOptions, type VersionLockAuditResult, type VersionLockBootstrapOptions, type VersionLockEntry, type VersionLockFile, type VersionLockIssue, type VersionLockIssueSeverity, type VersionLockRef, type VersionLockRefreshOptions, type VersionLockRefreshResult, type VersionLockSourceRef, type VersionLockStatus, type VersionLockUpdateOptions, type VersionSourceKind, type VersionedEdge, type VersionedNode, applyPreparedManagedHookBlocks, assemblePacket, auditPackets, auditVersionLock, bootstrapVersionLock, buildGraph, buildVersionIndex, collectChangedPaths, computeE2eCoverageStats, computeRevisionDigest, discoverAndAuditPackets, discoverTargets, doctorArtifactChain, formatContextMarkdown, generateE2eRegistry, getArtifactTypeMetadata, getTargetArtifactTypes, installManagedHookBlock, isOfficialNamespace, isPacketTargetType, isPacketTargetTypeDynamic, isTargetArtifactType, isVersionLockIssueBlocking, loadConfig, loadContract, loadContractCatalog, loadContractsFromDirectory, nextId, normalizeE2eLegacyArtifact, normalizeToCanonical, parseTargetSelector, parseTargetsFile, prepareManagedHookBlock, queryGraph, refreshVersionLock, renderDoctorMarkdown, renderMermaid, renderPacketMarkdown, renderPacketPrompt, renderTraceVersionMarkdown, renderVersionLockAuditMarkdown, renderVersionLockRefreshMarkdown, resolveArtifactContext, resolveArtifactGraphCli, resolveArtifactTypeName, resolveCliTarget, resolveGitHookPath, resolveMatrixEdges, scanArtifacts, traceVersion, updateVersionLock, validateContractAgainstSchema, validateExecutableTraceability, validateGraph, validateNamespaceAuthority, validatePacket, validatePacketMarkdown, validatePacketPrompt, validatePolicyCompatibility, validateReviewResult, validateScenarioPrdLinkIndex, validateScenarioPrdLinks, verifyDigest, versionLockIssueSeverity, writeGraphCache };
package/dist/index.js CHANGED
@@ -1540,6 +1540,7 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1540
1540
  status: "orphan_lock",
1541
1541
  edgeId: entry.edgeId,
1542
1542
  message: `Liveness: ${sourcePath} is not active in any configured runner \u2014 locked verifies edges may reference dead tests`,
1543
+ severity: "warning",
1543
1544
  artifact: entry.artifact,
1544
1545
  source: entry.source
1545
1546
  });
@@ -1669,7 +1670,84 @@ async function auditVersionLock(root, lockPath = VERSION_LOCK_PATH, graph, confi
1669
1670
  fresh,
1670
1671
  totalArtifactRelationLocks: artifactRelationLocks.length,
1671
1672
  artifactRelationFresh,
1672
- issues: sortBy(issues, (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1673
+ issues: sortBy(issues.map(enrichVersionLockIssue), (issue2) => `${issue2.status} ${issue2.edgeId} ${issue2.verifiedByPath ?? ""}`)
1674
+ };
1675
+ }
1676
+ function isLivenessIssue(issue2) {
1677
+ return issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX);
1678
+ }
1679
+ function versionLockIssueSeverity(issue2) {
1680
+ if (issue2.severity) {
1681
+ return issue2.severity;
1682
+ }
1683
+ if (issue2.status === "missing_lock") {
1684
+ return "warning";
1685
+ }
1686
+ if (issue2.status === "orphan_lock" && issue2.message.startsWith(LIVENESS_MESSAGE_PREFIX)) {
1687
+ return "warning";
1688
+ }
1689
+ return "error";
1690
+ }
1691
+ function isVersionLockIssueBlocking(issue2, strictMissingLock) {
1692
+ if (issue2.status === "missing_lock") {
1693
+ return strictMissingLock;
1694
+ }
1695
+ return versionLockIssueSeverity(issue2) === "error";
1696
+ }
1697
+ function versionLockIssueRemediation(issue2) {
1698
+ switch (issue2.status) {
1699
+ case "artifact_changed":
1700
+ return [
1701
+ "\u786E\u8BA4\u8FD9\u6B21\u5236\u54C1\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1702
+ "\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",
1703
+ "\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"
1704
+ ];
1705
+ case "source_changed":
1706
+ return [
1707
+ "\u786E\u8BA4\u8FD9\u6B21\u6E90\u7801/\u6D4B\u8BD5\u53D8\u66F4\uFF08\u5185\u5BB9\u6216\u8DEF\u5F84\uFF09\u662F\u6709\u610F\u7684\u3002",
1708
+ "\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",
1709
+ "\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"
1710
+ ];
1711
+ case "verified_by_changed":
1712
+ return [
1713
+ "\u786E\u8BA4\u8FD9\u6B21\u9A8C\u8BC1\u6587\u4EF6\u53D8\u66F4\u662F\u6709\u610F\u7684\u3002",
1714
+ "\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",
1715
+ "\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"
1716
+ ];
1717
+ case "missing_lock":
1718
+ return [
1719
+ "\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",
1720
+ "\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",
1721
+ "\u8FD0\u884C `git diff artifacts/traceability-version-lock.json` \u5BA1\u67E5\u540E `git add artifacts/traceability-version-lock.json` \u6682\u5B58\u3002"
1722
+ ];
1723
+ case "orphan_lock":
1724
+ if (isLivenessIssue(issue2)) {
1725
+ return [
1726
+ "\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",
1727
+ "\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",
1728
+ "\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"
1729
+ ];
1730
+ }
1731
+ return [
1732
+ "\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",
1733
+ "\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",
1734
+ "\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",
1735
+ "\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"
1736
+ ];
1737
+ case "target_not_found":
1738
+ return [
1739
+ "\u68C0\u67E5 `--target` \u7684 `type:id` \u662F\u5426\u62FC\u5199\u6B63\u786E\u3002",
1740
+ "\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"
1741
+ ];
1742
+ default:
1743
+ return [];
1744
+ }
1745
+ }
1746
+ function enrichVersionLockIssue(issue2) {
1747
+ return {
1748
+ ...issue2,
1749
+ severity: versionLockIssueSeverity(issue2),
1750
+ remediation: issue2.remediation ?? versionLockIssueRemediation(issue2)
1673
1751
  };
1674
1752
  }
1675
1753
  async function updateVersionLock(root, options) {
@@ -1967,59 +2045,118 @@ async function traceVersion(root, target, lockPath = VERSION_LOCK_PATH) {
1967
2045
  currentEdges: index.edges.filter((edge2) => edge2.from === targetUid || edge2.to === targetUid),
1968
2046
  locks: lock.locks.filter((entry) => `${entry.artifact.type}:${entry.artifact.id}` === targetUid),
1969
2047
  artifactRelations: targetArtifactRelations,
1970
- issues: [...targetIssues, ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
2048
+ issues: [...targetIssues.map(enrichVersionLockIssue), ...audit.issues.filter((issue2) => `${issue2.artifact?.type}:${issue2.artifact?.id}` === targetUid || issue2.edgeId.includes(`#${targetUid}`))]
1971
2049
  };
1972
2050
  }
1973
- function renderVersionLockAuditMarkdown(result) {
2051
+ function renderIssueBlockingTag(issue2, strictMissingLock) {
2052
+ return isVersionLockIssueBlocking(issue2, strictMissingLock) ? "\u963B\u65AD" : "\u8B66\u544A";
2053
+ }
2054
+ function renderIssueBlockingExplanation(issue2, strictMissingLock) {
2055
+ if (!isVersionLockIssueBlocking(issue2, strictMissingLock)) {
2056
+ return "\u5426\uFF08\u4EC5\u63D0\u9192\uFF0C\u4E0D\u963B\u65AD\uFF09";
2057
+ }
2058
+ if (issue2.status === "missing_lock" && versionLockIssueSeverity(issue2) !== "error") {
2059
+ return "\u662F\uFF08\u5DF2\u7531 --strict-missing-lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09";
2060
+ }
2061
+ return "\u662F\uFF08\u4F1A\u4F7F\u547D\u4EE4\u4EE5\u975E\u96F6\u9000\u51FA\u7801\u7ED3\u675F\uFF09";
2062
+ }
2063
+ function appendIssueDetails(lines, issues, strictMissingLock) {
2064
+ issues.forEach((issue2, index) => {
2065
+ const label = VERSION_LOCK_STATUS_LABELS[issue2.status] ?? issue2.status;
2066
+ lines.push(`### ${index + 1}. [${renderIssueBlockingTag(issue2, strictMissingLock)}] ${label}\uFF08\`${issue2.status}\`\uFF09`);
2067
+ lines.push("");
2068
+ lines.push(`- \u8FB9: \`${issue2.edgeId}\``);
2069
+ lines.push(`- \u8BE6\u60C5: ${issue2.message}`);
2070
+ lines.push(`- \u662F\u5426\u963B\u65AD: ${renderIssueBlockingExplanation(issue2, strictMissingLock)}`);
2071
+ const remediation = issue2.remediation ?? [];
2072
+ if (remediation.length > 0) {
2073
+ lines.push("- \u89E3\u51B3\u6B65\u9AA4:");
2074
+ remediation.forEach((step, stepIndex) => {
2075
+ lines.push(` ${stepIndex + 1}. ${step}`);
2076
+ });
2077
+ }
2078
+ lines.push("");
2079
+ });
2080
+ }
2081
+ function renderVersionLockAuditMarkdown(result, options = {}) {
2082
+ const strictMissingLock = options.strictMissingLock === true;
2083
+ const blockingCount = result.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2084
+ const nonBlockingCount = result.issues.length - blockingCount;
1974
2085
  const lines = [
1975
- "# Version Lock Audit",
2086
+ "# \u7248\u672C\u9501\u5BA1\u8BA1\uFF08version-lock audit\uFF09",
1976
2087
  "",
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}`,
2088
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2089
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2090
+ `- \u5B9E\u73B0/\u9A8C\u8BC1\u9501: ${result.totalLocks}\uFF08\u65B0\u9C9C ${result.fresh}\uFF09`,
2091
+ `- \u5236\u54C1\u5173\u7CFB\u9501: ${result.totalArtifactRelationLocks}\uFF08\u65B0\u9C9C ${result.artifactRelationFresh}\uFF09`,
2092
+ `- \u963B\u65AD\u7B56\u7565: ${strictMissingLock ? "--strict-missing-lock\uFF08missing_lock \u5347\u7EA7\u4E3A\u963B\u65AD\uFF09" : "\u9ED8\u8BA4\uFF08missing_lock \u4E0D\u963B\u65AD\uFF09"}`,
2093
+ `- \u95EE\u9898: ${result.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
1981
2094
  ""
1982
2095
  ];
1983
2096
  if (result.issues.length === 0) {
1984
- lines.push("No version lock issues.");
2097
+ lines.push("\u672A\u53D1\u73B0\u7248\u672C\u9501\u95EE\u9898\u3002");
1985
2098
  return `${lines.join("\n")}
1986
2099
  `;
1987
2100
  }
1988
- for (const issue2 of result.issues) {
1989
- lines.push(`- [${issue2.status}] \`${issue2.edgeId}\` \u2014 ${issue2.message}`);
2101
+ if (blockingCount > 0) {
2102
+ 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`);
2103
+ } else {
2104
+ 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
2105
  }
2106
+ lines.push("");
2107
+ lines.push("## \u95EE\u9898\u6E05\u5355");
2108
+ lines.push("");
2109
+ appendIssueDetails(lines, result.issues, strictMissingLock);
1991
2110
  return `${lines.join("\n")}
1992
2111
  `;
1993
2112
  }
1994
2113
  function renderVersionLockRefreshMarkdown(result) {
2114
+ const strictMissingLock = true;
2115
+ const blockingCount = result.postAudit.issues.filter((issue2) => isVersionLockIssueBlocking(issue2, strictMissingLock)).length;
2116
+ const nonBlockingCount = result.postAudit.issues.length - blockingCount;
1995
2117
  const lines = [
1996
- "# Version Lock Refresh",
2118
+ "# \u7248\u672C\u9501\u5237\u65B0\uFF08version-lock refresh\uFF09",
2119
+ "",
2120
+ `- \u6839\u76EE\u5F55: \`${result.root}\``,
2121
+ `- \u9501\u6587\u4EF6: \`${result.lockPath}\``,
2122
+ `- \u6A21\u5F0F: \`${result.mode}\`\uFF08\u53D8\u66F4\u8DEF\u5F84 ${result.changedPaths.length} \u4E2A\uFF0C\u53D7\u5F71\u54CD\u8FB9 ${result.affectedEdges.length} \u6761\uFF09`,
2123
+ `- \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}`,
2124
+ `- \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}`,
2125
+ `- \u963B\u65AD\u7B56\u7565: refresh \u56FA\u5B9A\u6309 --strict-missing-lock \u5224\u5B9A\uFF0Cmissing_lock \u4E5F\u4F1A\u963B\u65AD`,
2126
+ `- \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898: ${result.postAudit.issues.length}\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`,
2127
+ "",
2128
+ "\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",
1997
2129
  "",
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}`,
2130
+ "1. `git diff artifacts/traceability-version-lock.json`",
2131
+ "2. `git add artifacts/traceability-version-lock.json`",
2132
+ "3. \u91CD\u65B0\u6267\u884C `git commit`",
2006
2133
  ""
2007
2134
  ];
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);
2135
+ appendList(lines, "\u65B0\u589E\u7684\u9501", result.addedLocks);
2136
+ appendList(lines, "\u66F4\u65B0\u7684\u9501", result.updatedLocks);
2137
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u9501", result.retainedOrphans);
2138
+ if (result.retainedOrphans.length > 0) {
2139
+ 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");
2140
+ lines.push("");
2141
+ }
2142
+ appendList(lines, "\u5220\u9664\u7684\u5B64\u7ACB\u9501", result.removedOrphans);
2143
+ appendList(lines, "\u65B0\u589E\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.addedArtifactRelationLocks);
2144
+ appendList(lines, "\u66F4\u65B0\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.updatedArtifactRelationLocks);
2145
+ appendList(lines, "\u4FDD\u7559\u7684\u5B64\u7ACB\u5236\u54C1\u5173\u7CFB\u9501", result.retainedArtifactRelationOrphans);
2146
+ if (result.retainedArtifactRelationOrphans.length > 0) {
2147
+ 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");
2148
+ lines.push("");
2149
+ }
2150
+ appendList(lines, "\u5220\u9664\u7684\u5236\u54C1\u5173\u7CFB\u9501", result.removedArtifactRelationLocks);
2151
+ appendList(lines, "\u63D0\u9192", result.warnings);
2017
2152
  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}`);
2153
+ if (blockingCount > 0) {
2154
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u963B\u65AD ${blockingCount}\uFF0C\u4E0D\u963B\u65AD ${nonBlockingCount}\uFF09`);
2155
+ } else {
2156
+ lines.push(`## \u5237\u65B0\u540E\u5BA1\u8BA1\u95EE\u9898\uFF08\u5F53\u524D\u7B56\u7565\u4E0B\u5168\u90E8\u4E0D\u963B\u65AD\uFF09`);
2021
2157
  }
2022
2158
  lines.push("");
2159
+ appendIssueDetails(lines, result.postAudit.issues, strictMissingLock);
2023
2160
  }
2024
2161
  return `${lines.join("\n")}
2025
2162
  `;
@@ -2474,7 +2611,7 @@ async function isFileActiveInRunner(root, filePath, runner) {
2474
2611
  function sortUnique(items) {
2475
2612
  return [...new Set(items)].sort((left, right) => left.localeCompare(right));
2476
2613
  }
2477
- var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION;
2614
+ var VERSION_LOCK_PATH, VERSION_INDEX_SCHEMA_VERSION, VERSION_LOCK_SCHEMA_VERSION, LIVENESS_MESSAGE_PREFIX, VERSION_LOCK_STATUS_LABELS;
2478
2615
  var init_versioned_traceability = __esm({
2479
2616
  "src/versioned-traceability.ts"() {
2480
2617
  "use strict";
@@ -2483,6 +2620,16 @@ var init_versioned_traceability = __esm({
2483
2620
  VERSION_LOCK_PATH = "artifacts/traceability-version-lock.json";
2484
2621
  VERSION_INDEX_SCHEMA_VERSION = "1.0";
2485
2622
  VERSION_LOCK_SCHEMA_VERSION = "1.0";
2623
+ LIVENESS_MESSAGE_PREFIX = "Liveness:";
2624
+ VERSION_LOCK_STATUS_LABELS = {
2625
+ fresh: "\u65B0\u9C9C",
2626
+ target_not_found: "\u76EE\u6807\u5236\u54C1\u4E0D\u5B58\u5728",
2627
+ artifact_changed: "\u5236\u54C1\u5DF2\u53D8\u5316",
2628
+ source_changed: "\u6E90\u7801/\u6D4B\u8BD5\u5DF2\u53D8\u5316",
2629
+ verified_by_changed: "\u9A8C\u8BC1\u6587\u4EF6\u5DF2\u53D8\u5316",
2630
+ missing_lock: "\u7F3A\u5C11\u7248\u672C\u9501",
2631
+ orphan_lock: "\u5B64\u7ACB\u9501"
2632
+ };
2486
2633
  }
2487
2634
  });
2488
2635
 
@@ -8343,6 +8490,7 @@ export {
8343
8490
  isPacketTargetType,
8344
8491
  isPacketTargetTypeDynamic,
8345
8492
  isTargetArtifactType,
8493
+ isVersionLockIssueBlocking,
8346
8494
  loadConfig,
8347
8495
  loadContract,
8348
8496
  loadContractCatalog,
@@ -8383,5 +8531,6 @@ export {
8383
8531
  validateScenarioPrdLinkIndex,
8384
8532
  validateScenarioPrdLinks,
8385
8533
  verifyDigest,
8534
+ versionLockIssueSeverity,
8386
8535
  writeGraphCache
8387
8536
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artifact-graph",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Git-native Markdown artifact graph scanner and validator",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -37,7 +37,10 @@ else
37
37
  fi
38
38
 
39
39
  if ! git diff --quiet -- artifacts/traceability-version-lock.json; then
40
- echo "artifact-chain-assistant: version lock changed during pre-commit." >&2
41
- echo "Please review and stage artifacts/traceability-version-lock.json, then commit again." >&2
40
+ echo "artifact-chain-assistant: 版本锁刷新成功,但更新后的锁文件尚未进入本次提交。" >&2
41
+ echo "这不是校验失败,只需把新锁纳入提交:" >&2
42
+ echo " 1. 审查锁变更: git diff artifacts/traceability-version-lock.json" >&2
43
+ echo " 2. 暂存锁文件: git add artifacts/traceability-version-lock.json" >&2
44
+ echo " 3. 重新提交: git commit" >&2
42
45
  exit 1
43
46
  fi