@mycodemap/mycodemap 0.5.0 → 0.5.2-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +77 -9
  3. package/dist/cli/commands/analyze.d.ts +18 -0
  4. package/dist/cli/commands/analyze.d.ts.map +1 -1
  5. package/dist/cli/commands/analyze.js +239 -6
  6. package/dist/cli/commands/analyze.js.map +1 -1
  7. package/dist/cli/commands/check.d.ts +22 -0
  8. package/dist/cli/commands/check.d.ts.map +1 -0
  9. package/dist/cli/commands/check.js +168 -0
  10. package/dist/cli/commands/check.js.map +1 -0
  11. package/dist/cli/commands/ci.d.ts +25 -0
  12. package/dist/cli/commands/ci.d.ts.map +1 -1
  13. package/dist/cli/commands/ci.js +139 -36
  14. package/dist/cli/commands/ci.js.map +1 -1
  15. package/dist/cli/commands/complexity.d.ts.map +1 -1
  16. package/dist/cli/commands/complexity.js +6 -0
  17. package/dist/cli/commands/complexity.js.map +1 -1
  18. package/dist/cli/commands/design.d.ts +5 -0
  19. package/dist/cli/commands/design.d.ts.map +1 -1
  20. package/dist/cli/commands/design.js +6 -0
  21. package/dist/cli/commands/design.js.map +1 -1
  22. package/dist/cli/commands/generate.d.ts +1 -0
  23. package/dist/cli/commands/generate.d.ts.map +1 -1
  24. package/dist/cli/commands/generate.js +121 -8
  25. package/dist/cli/commands/generate.js.map +1 -1
  26. package/dist/cli/commands/history.d.ts +26 -0
  27. package/dist/cli/commands/history.d.ts.map +1 -0
  28. package/dist/cli/commands/history.js +92 -0
  29. package/dist/cli/commands/history.js.map +1 -0
  30. package/dist/cli/commands/mcp.d.ts +13 -0
  31. package/dist/cli/commands/mcp.d.ts.map +1 -0
  32. package/dist/cli/commands/mcp.js +108 -0
  33. package/dist/cli/commands/mcp.js.map +1 -0
  34. package/dist/cli/commands/workflow.d.ts.map +1 -1
  35. package/dist/cli/commands/workflow.js +22 -2
  36. package/dist/cli/commands/workflow.js.map +1 -1
  37. package/dist/cli/config-loader.d.ts.map +1 -1
  38. package/dist/cli/config-loader.js +3 -2
  39. package/dist/cli/config-loader.js.map +1 -1
  40. package/dist/cli/contract-checker.d.ts +33 -0
  41. package/dist/cli/contract-checker.d.ts.map +1 -0
  42. package/dist/cli/contract-checker.js +719 -0
  43. package/dist/cli/contract-checker.js.map +1 -0
  44. package/dist/cli/contract-diff-scope.d.ts +14 -0
  45. package/dist/cli/contract-diff-scope.d.ts.map +1 -0
  46. package/dist/cli/contract-diff-scope.js +127 -0
  47. package/dist/cli/contract-diff-scope.js.map +1 -0
  48. package/dist/cli/contract-gate-thresholds.d.ts +14 -0
  49. package/dist/cli/contract-gate-thresholds.d.ts.map +1 -0
  50. package/dist/cli/contract-gate-thresholds.js +19 -0
  51. package/dist/cli/contract-gate-thresholds.js.map +1 -0
  52. package/dist/cli/design-contract-loader.d.ts.map +1 -1
  53. package/dist/cli/design-contract-loader.js +355 -3
  54. package/dist/cli/design-contract-loader.js.map +1 -1
  55. package/dist/cli/design-scope-resolver.d.ts.map +1 -1
  56. package/dist/cli/design-scope-resolver.js +89 -41
  57. package/dist/cli/design-scope-resolver.js.map +1 -1
  58. package/dist/cli/index.js +18 -6
  59. package/dist/cli/index.js.map +1 -1
  60. package/dist/cli/paths.d.ts.map +1 -1
  61. package/dist/cli/paths.js +30 -7
  62. package/dist/cli/paths.js.map +1 -1
  63. package/dist/core/analyzer.d.ts.map +1 -1
  64. package/dist/core/analyzer.js +16 -0
  65. package/dist/core/analyzer.js.map +1 -1
  66. package/dist/domain/entities/CodeGraph.d.ts +5 -1
  67. package/dist/domain/entities/CodeGraph.d.ts.map +1 -1
  68. package/dist/domain/entities/CodeGraph.js +29 -12
  69. package/dist/domain/entities/CodeGraph.js.map +1 -1
  70. package/dist/domain/entities/Dependency.d.ts +8 -1
  71. package/dist/domain/entities/Dependency.d.ts.map +1 -1
  72. package/dist/domain/entities/Dependency.js +19 -4
  73. package/dist/domain/entities/Dependency.js.map +1 -1
  74. package/dist/domain/entities/Symbol.d.ts +2 -1
  75. package/dist/domain/entities/Symbol.d.ts.map +1 -1
  76. package/dist/domain/entities/Symbol.js +6 -3
  77. package/dist/domain/entities/Symbol.js.map +1 -1
  78. package/dist/infrastructure/storage/StorageFactory.d.ts +1 -0
  79. package/dist/infrastructure/storage/StorageFactory.d.ts.map +1 -1
  80. package/dist/infrastructure/storage/StorageFactory.js +7 -2
  81. package/dist/infrastructure/storage/StorageFactory.js.map +1 -1
  82. package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts +3 -1
  83. package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts.map +1 -1
  84. package/dist/infrastructure/storage/adapters/FileSystemStorage.js +10 -2
  85. package/dist/infrastructure/storage/adapters/FileSystemStorage.js.map +1 -1
  86. package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts +3 -1
  87. package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts.map +1 -1
  88. package/dist/infrastructure/storage/adapters/KuzuDBStorage.js +9 -1
  89. package/dist/infrastructure/storage/adapters/KuzuDBStorage.js.map +1 -1
  90. package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts +3 -1
  91. package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts.map +1 -1
  92. package/dist/infrastructure/storage/adapters/MemoryStorage.js +9 -1
  93. package/dist/infrastructure/storage/adapters/MemoryStorage.js.map +1 -1
  94. package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts +53 -0
  95. package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts.map +1 -0
  96. package/dist/infrastructure/storage/adapters/SQLiteStorage.js +879 -0
  97. package/dist/infrastructure/storage/adapters/SQLiteStorage.js.map +1 -0
  98. package/dist/infrastructure/storage/graph-helpers.d.ts +3 -1
  99. package/dist/infrastructure/storage/graph-helpers.d.ts.map +1 -1
  100. package/dist/infrastructure/storage/graph-helpers.js +90 -0
  101. package/dist/infrastructure/storage/graph-helpers.js.map +1 -1
  102. package/dist/infrastructure/storage/index.d.ts +1 -1
  103. package/dist/infrastructure/storage/index.d.ts.map +1 -1
  104. package/dist/infrastructure/storage/interfaces/StorageBase.d.ts +3 -1
  105. package/dist/infrastructure/storage/interfaces/StorageBase.d.ts.map +1 -1
  106. package/dist/infrastructure/storage/interfaces/StorageBase.js.map +1 -1
  107. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts +27 -0
  108. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts.map +1 -0
  109. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js +246 -0
  110. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js.map +1 -0
  111. package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts +25 -0
  112. package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts.map +1 -0
  113. package/dist/infrastructure/storage/sqlite/perf-thresholds.js +25 -0
  114. package/dist/infrastructure/storage/sqlite/perf-thresholds.js.map +1 -0
  115. package/dist/infrastructure/storage/sqlite/schema.d.ts +4 -0
  116. package/dist/infrastructure/storage/sqlite/schema.d.ts.map +1 -0
  117. package/dist/infrastructure/storage/sqlite/schema.js +111 -0
  118. package/dist/infrastructure/storage/sqlite/schema.js.map +1 -0
  119. package/dist/interface/types/design-check.d.ts +73 -0
  120. package/dist/interface/types/design-check.d.ts.map +1 -0
  121. package/dist/interface/types/design-check.js +4 -0
  122. package/dist/interface/types/design-check.js.map +1 -0
  123. package/dist/interface/types/design-contract.d.ts +56 -1
  124. package/dist/interface/types/design-contract.d.ts.map +1 -1
  125. package/dist/interface/types/history-risk.d.ts +90 -0
  126. package/dist/interface/types/history-risk.d.ts.map +1 -0
  127. package/dist/interface/types/history-risk.js +4 -0
  128. package/dist/interface/types/history-risk.js.map +1 -0
  129. package/dist/interface/types/index.d.ts +17 -2
  130. package/dist/interface/types/index.d.ts.map +1 -1
  131. package/dist/interface/types/storage.d.ts +28 -1
  132. package/dist/interface/types/storage.d.ts.map +1 -1
  133. package/dist/orchestrator/adapters/ast-grep-adapter.d.ts +10 -0
  134. package/dist/orchestrator/adapters/ast-grep-adapter.d.ts.map +1 -1
  135. package/dist/orchestrator/adapters/ast-grep-adapter.js +46 -17
  136. package/dist/orchestrator/adapters/ast-grep-adapter.js.map +1 -1
  137. package/dist/orchestrator/adapters/codemap-adapter.d.ts.map +1 -1
  138. package/dist/orchestrator/adapters/codemap-adapter.js +2 -22
  139. package/dist/orchestrator/adapters/codemap-adapter.js.map +1 -1
  140. package/dist/orchestrator/history-risk-service.d.ts +55 -0
  141. package/dist/orchestrator/history-risk-service.d.ts.map +1 -0
  142. package/dist/orchestrator/history-risk-service.js +680 -0
  143. package/dist/orchestrator/history-risk-service.js.map +1 -0
  144. package/dist/orchestrator/types.d.ts +19 -1
  145. package/dist/orchestrator/types.d.ts.map +1 -1
  146. package/dist/orchestrator/types.js +19 -0
  147. package/dist/orchestrator/types.js.map +1 -1
  148. package/dist/server/mcp/index.d.ts +4 -0
  149. package/dist/server/mcp/index.d.ts.map +1 -0
  150. package/dist/server/mcp/index.js +5 -0
  151. package/dist/server/mcp/index.js.map +1 -0
  152. package/dist/server/mcp/server.d.ts +17 -0
  153. package/dist/server/mcp/server.d.ts.map +1 -0
  154. package/dist/server/mcp/server.js +84 -0
  155. package/dist/server/mcp/server.js.map +1 -0
  156. package/dist/server/mcp/service.d.ts +22 -0
  157. package/dist/server/mcp/service.d.ts.map +1 -0
  158. package/dist/server/mcp/service.js +177 -0
  159. package/dist/server/mcp/service.js.map +1 -0
  160. package/dist/server/mcp/types.d.ts +56 -0
  161. package/dist/server/mcp/types.d.ts.map +1 -0
  162. package/dist/server/mcp/types.js +4 -0
  163. package/dist/server/mcp/types.js.map +1 -0
  164. package/docs/AI_ASSISTANT_SETUP.md +1 -1
  165. package/docs/PUBLISHING.md +41 -12
  166. package/docs/SETUP_GUIDE.md +6 -6
  167. package/docs/ai-guide/COMMANDS.md +98 -4
  168. package/docs/ai-guide/INTEGRATION.md +137 -433
  169. package/docs/ai-guide/OUTPUT.md +476 -6
  170. package/docs/ai-guide/PATTERNS.md +41 -11
  171. package/docs/ai-guide/PROMPTS.md +11 -6
  172. package/docs/backlog.md +177 -0
  173. package/docs/eatdogfood-reports/2026-04-17-eatdogfood-agent-experience.md +231 -0
  174. package/docs/exec-plans/completed/2026-04-17-eatdogfood-codemap-cli.md +103 -0
  175. package/docs/ideation/2026-04-15-executable-architecture-constitution-ideation.md +102 -0
  176. package/docs/product-specs/DESIGN_CONTRACT_TEMPLATE.md +47 -0
  177. package/docs/product-specs/MVP3-ARCHITECTURE-COMPARISON.md +11 -10
  178. package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-PRD.md +10 -10
  179. package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-TECH-PRD.md +17 -12
  180. package/docs/rules/README.md +16 -11
  181. package/docs/rules/architecture-guardrails.md +24 -336
  182. package/docs/rules/code-quality-redlines.md +25 -311
  183. package/docs/rules/engineering-with-codex-openai.md +14 -1
  184. package/docs/rules/pre-release-checklist.md +9 -4
  185. package/docs/rules/validation.md +91 -40
  186. package/mycodemap.config.schema.json +3 -3
  187. package/package.json +7 -2
  188. package/scripts/benchmark-governance-graph.mjs +132 -0
  189. package/scripts/calibrate-contract-gate.mjs +221 -0
  190. package/scripts/capability-report.py +255 -0
  191. package/scripts/pre-release-check.js +2 -2
  192. package/scripts/qa-rule-control.sh +254 -0
  193. package/scripts/report-high-risk-files.mjs +395 -0
  194. package/scripts/rule-context.mjs +155 -0
  195. package/scripts/smoke-sqlite-impact.mjs +85 -0
  196. package/scripts/sync-analyze-docs.js +1 -0
  197. package/scripts/tests/test_capability_report.py +89 -0
  198. package/scripts/tests/test_rule_control_workflow.py +51 -0
  199. package/scripts/tests/test_validate_rules.py +81 -0
  200. package/scripts/validate-ai-docs.js +283 -1
  201. package/scripts/validate-docs.js +249 -42
  202. package/scripts/validate-rules.py +254 -0
  203. package/dist/ai/claude.d.ts +0 -38
  204. package/dist/ai/claude.d.ts.map +0 -1
  205. package/dist/ai/claude.js +0 -169
  206. package/dist/ai/claude.js.map +0 -1
  207. package/dist/ai/codex.d.ts +0 -38
  208. package/dist/ai/codex.d.ts.map +0 -1
  209. package/dist/ai/codex.js +0 -169
  210. package/dist/ai/codex.js.map +0 -1
  211. package/dist/ai/factory.d.ts +0 -48
  212. package/dist/ai/factory.d.ts.map +0 -1
  213. package/dist/ai/factory.js +0 -95
  214. package/dist/ai/factory.js.map +0 -1
  215. package/dist/ai/index.d.ts +0 -12
  216. package/dist/ai/index.d.ts.map +0 -1
  217. package/dist/ai/index.js +0 -29
  218. package/dist/ai/index.js.map +0 -1
  219. package/dist/ai/provider.d.ts +0 -70
  220. package/dist/ai/provider.d.ts.map +0 -1
  221. package/dist/ai/provider.js +0 -31
  222. package/dist/ai/provider.js.map +0 -1
  223. package/dist/ai/subagent-caller.d.ts +0 -90
  224. package/dist/ai/subagent-caller.d.ts.map +0 -1
  225. package/dist/ai/subagent-caller.js +0 -280
  226. package/dist/ai/subagent-caller.js.map +0 -1
  227. package/dist/ai/types.d.ts +0 -70
  228. package/dist/ai/types.d.ts.map +0 -1
  229. package/dist/ai/types.js +0 -5
  230. package/dist/ai/types.js.map +0 -1
  231. package/dist/cli/commands/server.d.ts +0 -9
  232. package/dist/cli/commands/server.d.ts.map +0 -1
  233. package/dist/cli/commands/server.js +0 -65
  234. package/dist/cli/commands/server.js.map +0 -1
  235. package/dist/cli-new/commands/server.d.ts +0 -13
  236. package/dist/cli-new/commands/server.d.ts.map +0 -1
  237. package/dist/cli-new/commands/server.js +0 -90
  238. package/dist/cli-new/commands/server.js.map +0 -1
  239. package/dist/generator/ai-overview.d.ts +0 -51
  240. package/dist/generator/ai-overview.d.ts.map +0 -1
  241. package/dist/generator/ai-overview.js +0 -160
  242. package/dist/generator/ai-overview.js.map +0 -1
  243. package/dist/infrastructure/storage/adapters/Neo4jStorage.d.ts +0 -41
  244. package/dist/infrastructure/storage/adapters/Neo4jStorage.d.ts.map +0 -1
  245. package/dist/infrastructure/storage/adapters/Neo4jStorage.js +0 -162
  246. package/dist/infrastructure/storage/adapters/Neo4jStorage.js.map +0 -1
  247. package/dist/orchestrator/ai-feed-generator.d.ts +0 -210
  248. package/dist/orchestrator/ai-feed-generator.d.ts.map +0 -1
  249. package/dist/orchestrator/ai-feed-generator.js +0 -377
  250. package/dist/orchestrator/ai-feed-generator.js.map +0 -1
  251. package/docs/archive/test-report-symbol-search.md +0 -384
  252. package/docs/archive/test-scenario-4-complexity-analysis.md +0 -460
  253. package/docs/archive/test_report_scenario5.md +0 -615
  254. package/docs/archive/test_scenario_3_impact_analysis_report.md +0 -520
@@ -0,0 +1,719 @@
1
+ // [META] since:2026-04-15 | owner:cli-team | stable:false
2
+ // [WHY] Execute design contract rules against source code and return one canonical check result for CLI and CI consumers
3
+ import { existsSync, readFileSync, statSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { cwd } from 'node:process';
6
+ import { cruise } from 'dependency-cruiser';
7
+ import ts from 'typescript';
8
+ import { GitHistoryService } from '../orchestrator/history-risk-service.js';
9
+ import { analyzeFileComplexity } from '../core/ast-complexity-analyzer.js';
10
+ import { loadDesignContract } from './design-contract-loader.js';
11
+ import { CONFIG_FILE_NEW, CONFIG_FILE_OLD } from './paths.js';
12
+ import { createConfiguredStorage } from './storage-runtime.js';
13
+ const CONTRACT_CHECK_EXCLUDE_PATHS = [
14
+ '(^|/)__tests__(/|$)',
15
+ '\\.(?:test|spec)\\.[cm]?[jt]sx?$',
16
+ ];
17
+ const compilerOptionsCache = new Map();
18
+ const importLocationCache = new Map();
19
+ function createHistoryRiskBlock(signal) {
20
+ return {
21
+ status: signal.diagnostics.status,
22
+ level: signal.risk.level,
23
+ confidence: signal.diagnostics.confidence,
24
+ freshness: signal.diagnostics.freshness,
25
+ score: signal.risk.score,
26
+ factors: signal.risk.riskFactors,
27
+ analyzed_at: signal.diagnostics.analyzedAt,
28
+ };
29
+ }
30
+ function summarizeHistoryEnrichment(result) {
31
+ const unavailableCount = result.files.filter((signal) => signal.risk.level === 'unavailable').length;
32
+ const staleCount = result.files.filter((signal) => signal.diagnostics.freshness === 'stale').length;
33
+ const lowConfidenceCount = result.files.filter((signal) => signal.diagnostics.confidence === 'low' || signal.diagnostics.confidence === 'unavailable').length;
34
+ return {
35
+ status: result.diagnostics.status,
36
+ confidence: result.diagnostics.confidence,
37
+ freshness: result.diagnostics.freshness,
38
+ scope_mode: result.diagnostics.scopeMode,
39
+ enriched_file_count: result.files.length,
40
+ unavailable_count: unavailableCount,
41
+ stale_count: staleCount,
42
+ low_confidence_count: lowConfidenceCount,
43
+ requires_precompute: result.diagnostics.requiresPrecompute,
44
+ };
45
+ }
46
+ function createHistoryWarnings(result) {
47
+ const warnings = [];
48
+ if (result.diagnostics.requiresPrecompute) {
49
+ warnings.push({
50
+ code: 'history-risk-precompute-recommended',
51
+ message: 'history risk 查询范围已收缩,建议预计算或缩小扫描范围',
52
+ details: {
53
+ requested_files: result.diagnostics.requestedFiles,
54
+ analyzed_files: result.diagnostics.analyzedFiles,
55
+ },
56
+ });
57
+ }
58
+ if (result.files.some((signal) => signal.risk.level === 'unavailable')) {
59
+ warnings.push({
60
+ code: 'history-risk-unavailable',
61
+ message: '部分违规文件缺少可用的 history risk 信号',
62
+ details: {
63
+ unavailable_count: result.files.filter((signal) => signal.risk.level === 'unavailable').length,
64
+ },
65
+ });
66
+ }
67
+ if (result.files.some((signal) => signal.diagnostics.freshness === 'stale')) {
68
+ warnings.push({
69
+ code: 'history-risk-stale',
70
+ message: '部分 history risk 信号已过期,需要重新 materialize',
71
+ details: {
72
+ stale_count: result.files.filter((signal) => signal.diagnostics.freshness === 'stale').length,
73
+ },
74
+ });
75
+ }
76
+ return warnings;
77
+ }
78
+ function enrichViolationsWithHistory(violations, historyResult) {
79
+ const historyByFile = new Map(historyResult.files.map((signal) => [normalizePath(signal.file), signal]));
80
+ return violations.map((violation) => {
81
+ const signal = historyByFile.get(normalizePath(violation.location));
82
+ if (!signal) {
83
+ return violation;
84
+ }
85
+ return {
86
+ ...violation,
87
+ risk: createHistoryRiskBlock(signal),
88
+ };
89
+ });
90
+ }
91
+ function normalizePath(filePath) {
92
+ return path.normalize(filePath).replace(/\\/gu, '/');
93
+ }
94
+ function normalizeResolvedModuleKey(filePath) {
95
+ return normalizePath(filePath)
96
+ .replace(/\.(?:d\.)?[cm]?[jt]sx?$/u, '')
97
+ .replace(/\/index$/u, '');
98
+ }
99
+ function createViolationDiagnostic(file, category, source, location) {
100
+ const scope = location?.scope ?? 'file';
101
+ return {
102
+ file,
103
+ line: location?.line,
104
+ column: location?.column,
105
+ endLine: location?.endLine,
106
+ endColumn: location?.endColumn,
107
+ scope,
108
+ source,
109
+ category,
110
+ degraded: scope !== 'line',
111
+ };
112
+ }
113
+ function getDiagnosticCategory(ruleType) {
114
+ if (ruleType === 'module_public_api_only') {
115
+ return 'module_boundary';
116
+ }
117
+ if (ruleType === 'complexity_threshold') {
118
+ return 'complexity';
119
+ }
120
+ return 'dependency';
121
+ }
122
+ function getContractCheckerCompilerOptions(projectRoot) {
123
+ const cachedOptions = compilerOptionsCache.get(projectRoot);
124
+ if (cachedOptions) {
125
+ return cachedOptions;
126
+ }
127
+ const fallbackOptions = {
128
+ allowJs: true,
129
+ module: ts.ModuleKind.NodeNext,
130
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
131
+ target: ts.ScriptTarget.ES2022,
132
+ };
133
+ const configPath = ts.findConfigFile(projectRoot, ts.sys.fileExists);
134
+ if (!configPath) {
135
+ compilerOptionsCache.set(projectRoot, fallbackOptions);
136
+ return fallbackOptions;
137
+ }
138
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
139
+ if (configFile.error) {
140
+ compilerOptionsCache.set(projectRoot, fallbackOptions);
141
+ return fallbackOptions;
142
+ }
143
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath));
144
+ const compilerOptions = {
145
+ ...fallbackOptions,
146
+ ...parsedConfig.options,
147
+ };
148
+ compilerOptionsCache.set(projectRoot, compilerOptions);
149
+ return compilerOptions;
150
+ }
151
+ function getImportLocations(fromAbsolutePath, projectRoot) {
152
+ const cachedLocations = importLocationCache.get(fromAbsolutePath);
153
+ if (cachedLocations) {
154
+ return cachedLocations;
155
+ }
156
+ const sourceText = readFileSync(fromAbsolutePath, 'utf8');
157
+ const sourceFile = ts.createSourceFile(path.basename(fromAbsolutePath), sourceText, ts.ScriptTarget.Latest, true);
158
+ const locations = [];
159
+ const compilerOptions = getContractCheckerCompilerOptions(projectRoot);
160
+ const addLocation = (node) => {
161
+ if (!node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier)) {
162
+ return;
163
+ }
164
+ const rawSpecifier = node.moduleSpecifier.text;
165
+ const resolvedModule = ts.resolveModuleName(rawSpecifier, fromAbsolutePath, compilerOptions, ts.sys).resolvedModule;
166
+ const start = sourceFile.getLineAndCharacterOfPosition(node.moduleSpecifier.getStart(sourceFile));
167
+ const end = sourceFile.getLineAndCharacterOfPosition(node.moduleSpecifier.getEnd());
168
+ locations.push({
169
+ rawSpecifier,
170
+ resolvedPath: resolvedModule && !resolvedModule.isExternalLibraryImport
171
+ ? resolvedModule.resolvedFileName
172
+ : null,
173
+ line: start.line + 1,
174
+ column: start.character + 1,
175
+ endLine: end.line + 1,
176
+ endColumn: end.character + 1,
177
+ });
178
+ };
179
+ ts.forEachChild(sourceFile, (node) => {
180
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
181
+ addLocation(node);
182
+ }
183
+ });
184
+ importLocationCache.set(fromAbsolutePath, locations);
185
+ return locations;
186
+ }
187
+ function findViolationDiagnostic(fromPath, toPath, projectRoot, category, source) {
188
+ const fromAbsolutePath = path.isAbsolute(fromPath) ? fromPath : path.join(projectRoot, fromPath);
189
+ if (!existsSync(fromAbsolutePath)) {
190
+ return createViolationDiagnostic(fromPath, category, source);
191
+ }
192
+ const targetKey = toPath.includes('/')
193
+ ? normalizeResolvedModuleKey(path.isAbsolute(toPath) ? toPath : path.join(projectRoot, toPath))
194
+ : undefined;
195
+ const targetSpecifier = toPath.replace(/^node:/u, '');
196
+ for (const location of getImportLocations(fromAbsolutePath, projectRoot)) {
197
+ const resolvedKey = location.resolvedPath ? normalizeResolvedModuleKey(location.resolvedPath) : undefined;
198
+ const rawSpecifier = location.rawSpecifier.replace(/^node:/u, '');
199
+ if ((targetKey && resolvedKey === targetKey) || rawSpecifier === targetSpecifier) {
200
+ return createViolationDiagnostic(fromPath, category, source, {
201
+ line: location.line,
202
+ column: location.column,
203
+ endLine: location.endLine,
204
+ endColumn: location.endColumn,
205
+ scope: 'line',
206
+ });
207
+ }
208
+ }
209
+ return createViolationDiagnostic(fromPath, category, source);
210
+ }
211
+ function ensureAbsolutePath(inputPath, rootDir) {
212
+ return path.isAbsolute(inputPath) ? inputPath : path.join(rootDir, inputPath);
213
+ }
214
+ function isWithinDirectory(targetPath, directoryPath) {
215
+ const relativePath = path.relative(directoryPath, targetPath);
216
+ return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
217
+ }
218
+ function resolveAgainstPath(againstPath, rootDir) {
219
+ const absolutePath = path.resolve(ensureAbsolutePath(againstPath, rootDir));
220
+ if (!existsSync(absolutePath)) {
221
+ throw new Error(`--against 路径不存在: ${absolutePath}`);
222
+ }
223
+ if (!isWithinDirectory(absolutePath, rootDir)) {
224
+ throw new Error(`--against 必须位于项目根目录内: ${absolutePath}`);
225
+ }
226
+ return absolutePath;
227
+ }
228
+ function findProjectRoot(againstAbsolutePath, rootDir) {
229
+ let currentPath = againstAbsolutePath;
230
+ if (statSync(currentPath).isFile()) {
231
+ currentPath = path.dirname(currentPath);
232
+ }
233
+ while (isWithinDirectory(currentPath, rootDir)) {
234
+ if (existsSync(path.join(currentPath, 'package.json'))) {
235
+ return currentPath;
236
+ }
237
+ if (normalizePath(currentPath) === normalizePath(rootDir)) {
238
+ break;
239
+ }
240
+ const parentPath = path.dirname(currentPath);
241
+ if (parentPath === currentPath) {
242
+ break;
243
+ }
244
+ currentPath = parentPath;
245
+ }
246
+ return rootDir;
247
+ }
248
+ function findHistoryStorageRoot(againstAbsolutePath, rootDir, fallbackRoot) {
249
+ let currentPath = statSync(againstAbsolutePath).isFile()
250
+ ? path.dirname(againstAbsolutePath)
251
+ : againstAbsolutePath;
252
+ while (isWithinDirectory(currentPath, rootDir)) {
253
+ if (existsSync(path.join(currentPath, CONFIG_FILE_NEW)) || existsSync(path.join(currentPath, CONFIG_FILE_OLD))) {
254
+ return currentPath;
255
+ }
256
+ if (normalizePath(currentPath) === normalizePath(rootDir)) {
257
+ break;
258
+ }
259
+ const parentPath = path.dirname(currentPath);
260
+ if (parentPath === currentPath) {
261
+ break;
262
+ }
263
+ currentPath = parentPath;
264
+ }
265
+ return fallbackRoot;
266
+ }
267
+ function toRelativeProjectPath(filePath, projectRoot) {
268
+ if (path.isAbsolute(filePath) && isWithinDirectory(filePath, projectRoot)) {
269
+ return normalizePath(path.relative(projectRoot, filePath));
270
+ }
271
+ return normalizePath(filePath);
272
+ }
273
+ export function resolveContractCheckPaths(againstPath, rootDirInput = cwd()) {
274
+ const rootDir = path.resolve(rootDirInput);
275
+ const againstAbsolutePath = resolveAgainstPath(againstPath, rootDir);
276
+ const projectRoot = findProjectRoot(againstAbsolutePath, rootDir);
277
+ const againstProjectPath = toRelativeProjectPath(againstAbsolutePath, projectRoot);
278
+ const cruiseStartPath = againstProjectPath === '' ? '.' : againstProjectPath;
279
+ return {
280
+ rootDir,
281
+ againstAbsolutePath,
282
+ projectRoot,
283
+ againstProjectPath,
284
+ cruiseStartPath,
285
+ };
286
+ }
287
+ function globToRegexSource(pattern) {
288
+ const normalizedPattern = normalizePath(pattern).replace(/^\.\//u, '');
289
+ let regexSource = '^';
290
+ for (let index = 0; index < normalizedPattern.length; index += 1) {
291
+ const character = normalizedPattern[index];
292
+ const nextCharacter = normalizedPattern[index + 1];
293
+ if (character === '*' && nextCharacter === '*') {
294
+ regexSource += '.*';
295
+ index += 1;
296
+ continue;
297
+ }
298
+ if (character === '*') {
299
+ regexSource += '[^/]*';
300
+ continue;
301
+ }
302
+ if (character === '?') {
303
+ regexSource += '[^/]';
304
+ continue;
305
+ }
306
+ regexSource += character?.replace(/[.+^${}()|[\]\\]/gu, '\\$&') ?? '';
307
+ }
308
+ return `${regexSource}$`;
309
+ }
310
+ function createGlobMatcher(pattern) {
311
+ return new RegExp(globToRegexSource(pattern));
312
+ }
313
+ function createGeneratedRuleName(ruleName, suffix) {
314
+ return suffix ? `${ruleName}::${suffix}` : ruleName;
315
+ }
316
+ export function buildDependencyCruiserRuleSet(rules) {
317
+ const metadataByGeneratedName = new Map();
318
+ const forbiddenRules = [];
319
+ for (const rule of rules) {
320
+ if (rule.type === 'layer_direction') {
321
+ const generatedRuleName = createGeneratedRuleName(rule.name);
322
+ metadataByGeneratedName.set(generatedRuleName, {
323
+ originalName: rule.name,
324
+ ruleType: 'layer_direction',
325
+ severity: rule.severity,
326
+ });
327
+ forbiddenRules.push({
328
+ name: generatedRuleName,
329
+ severity: rule.severity,
330
+ from: { path: globToRegexSource(rule.from) },
331
+ to: { path: globToRegexSource(rule.to) },
332
+ });
333
+ continue;
334
+ }
335
+ if (rule.type === 'forbidden_imports') {
336
+ rule.forbidden.forEach((forbiddenModule, index) => {
337
+ const generatedRuleName = createGeneratedRuleName(rule.name, String(index + 1));
338
+ metadataByGeneratedName.set(generatedRuleName, {
339
+ originalName: rule.name,
340
+ ruleType: 'forbidden_imports',
341
+ severity: rule.severity,
342
+ });
343
+ const normalizedForbidden = forbiddenModule.trim();
344
+ const targetPattern = normalizedForbidden.includes('*')
345
+ ? globToRegexSource(normalizedForbidden)
346
+ : `^(?:${normalizedForbidden.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')}|node:${normalizedForbidden.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')})$`;
347
+ forbiddenRules.push({
348
+ name: generatedRuleName,
349
+ severity: rule.severity,
350
+ from: { path: globToRegexSource(rule.module) },
351
+ to: { path: targetPattern },
352
+ });
353
+ });
354
+ }
355
+ }
356
+ if (forbiddenRules.length === 0) {
357
+ return { metadataByGeneratedName };
358
+ }
359
+ return {
360
+ metadataByGeneratedName,
361
+ ruleSet: {
362
+ forbidden: forbiddenRules,
363
+ },
364
+ };
365
+ }
366
+ function isLocalResolvedDependency(resolvedPath, projectRoot) {
367
+ return !path.isAbsolute(resolvedPath) || isWithinDirectory(resolvedPath, projectRoot);
368
+ }
369
+ function evaluateModulePublicApiOnlyRules(rules, cruiseResult, projectRoot) {
370
+ const violations = [];
371
+ const seenViolations = new Set();
372
+ for (const rule of rules) {
373
+ const moduleMatcher = createGlobMatcher(rule.module);
374
+ const publicApiSuffix = `/${rule.publicApi}`;
375
+ for (const module of cruiseResult.modules) {
376
+ const fromPath = toRelativeProjectPath(module.source, projectRoot);
377
+ const fromIsInsideModule = moduleMatcher.test(fromPath);
378
+ for (const dependency of module.dependencies) {
379
+ if (dependency.coreModule || dependency.couldNotResolve || !dependency.resolved) {
380
+ continue;
381
+ }
382
+ if (!isLocalResolvedDependency(dependency.resolved, projectRoot)) {
383
+ continue;
384
+ }
385
+ const toPath = toRelativeProjectPath(dependency.resolved, projectRoot);
386
+ const toIsInsideModule = moduleMatcher.test(toPath);
387
+ const usesPublicApi = toPath === rule.publicApi || toPath.endsWith(publicApiSuffix);
388
+ if (!toIsInsideModule || fromIsInsideModule || usesPublicApi) {
389
+ continue;
390
+ }
391
+ const violationKey = `${rule.name}:${fromPath}:${toPath}`;
392
+ if (seenViolations.has(violationKey)) {
393
+ continue;
394
+ }
395
+ seenViolations.add(violationKey);
396
+ violations.push({
397
+ rule: rule.name,
398
+ rule_type: 'module_public_api_only',
399
+ severity: rule.severity,
400
+ location: fromPath,
401
+ message: `${fromPath} 只能通过 ${rule.publicApi} 访问 ${rule.module},当前直接依赖了 ${toPath}`,
402
+ dependency_chain: [fromPath, toPath],
403
+ hard_fail: rule.severity === 'error',
404
+ diagnostic: findViolationDiagnostic(fromPath, toPath, projectRoot, 'module_boundary', 'custom-evaluator'),
405
+ });
406
+ }
407
+ }
408
+ }
409
+ return violations;
410
+ }
411
+ function formatComplexityMetric(value) {
412
+ return Number.isInteger(value) ? String(value) : value.toFixed(1);
413
+ }
414
+ function getComplexityThresholdBreaches(rule, metrics) {
415
+ const breaches = [];
416
+ if (rule.maxCyclomatic != null && metrics.cyclomatic > rule.maxCyclomatic) {
417
+ breaches.push(`cyclomatic ${metrics.cyclomatic} > ${rule.maxCyclomatic}`);
418
+ }
419
+ if (rule.maxCognitive != null && metrics.cognitive > rule.maxCognitive) {
420
+ breaches.push(`cognitive ${metrics.cognitive} > ${rule.maxCognitive}`);
421
+ }
422
+ if (rule.minMaintainability != null && metrics.maintainability < rule.minMaintainability) {
423
+ breaches.push(`maintainability ${formatComplexityMetric(metrics.maintainability)} < ${formatComplexityMetric(rule.minMaintainability)}`);
424
+ }
425
+ return breaches;
426
+ }
427
+ function createComplexityThresholdViolation(rule, projectRelativePath, breaches) {
428
+ return {
429
+ rule: rule.name,
430
+ rule_type: 'complexity_threshold',
431
+ severity: rule.severity,
432
+ location: projectRelativePath,
433
+ message: `${projectRelativePath} 复杂度超限,违反规则 ${rule.name}: ${breaches.join('; ')}`,
434
+ dependency_chain: [projectRelativePath],
435
+ hard_fail: rule.severity === 'error',
436
+ diagnostic: createViolationDiagnostic(projectRelativePath, 'complexity', 'custom-evaluator'),
437
+ };
438
+ }
439
+ function evaluateComplexityThresholdRule(rule, scannedFiles, projectFiles, projectRoot) {
440
+ const matcher = createGlobMatcher(rule.module);
441
+ const matchingProjectFiles = projectFiles.filter((filePath) => matcher.test(filePath));
442
+ if (matchingProjectFiles.length === 0) {
443
+ return {
444
+ violations: [],
445
+ warnings: [{
446
+ code: 'complexity-threshold-unavailable',
447
+ message: `规则 ${rule.name} 未匹配到任何项目文件,complexity 结果不可用`,
448
+ details: {
449
+ rule: rule.name,
450
+ matched_files: 0,
451
+ },
452
+ }],
453
+ };
454
+ }
455
+ const matchingScannedFiles = scannedFiles.filter((filePath) => matcher.test(filePath));
456
+ const violations = [];
457
+ const warnings = [];
458
+ for (const projectRelativePath of matchingScannedFiles) {
459
+ try {
460
+ const metrics = analyzeFileComplexity(path.join(projectRoot, projectRelativePath));
461
+ const breaches = getComplexityThresholdBreaches(rule, metrics);
462
+ if (breaches.length > 0) {
463
+ violations.push(createComplexityThresholdViolation(rule, projectRelativePath, breaches));
464
+ }
465
+ }
466
+ catch (error) {
467
+ const message = error instanceof Error ? error.message : String(error);
468
+ warnings.push({
469
+ code: 'complexity-threshold-unavailable',
470
+ message: `规则 ${rule.name} 无法分析 ${projectRelativePath}: ${message}`,
471
+ details: {
472
+ rule: rule.name,
473
+ location: projectRelativePath,
474
+ },
475
+ });
476
+ }
477
+ }
478
+ return { violations, warnings };
479
+ }
480
+ function evaluateComplexityThresholdRules(rules, scannedFiles, projectFiles, projectRoot) {
481
+ return rules.reduce((result, rule) => {
482
+ const evaluation = evaluateComplexityThresholdRule(rule, scannedFiles, projectFiles, projectRoot);
483
+ result.violations.push(...evaluation.violations);
484
+ result.warnings.push(...evaluation.warnings);
485
+ return result;
486
+ }, {
487
+ violations: [],
488
+ warnings: [],
489
+ });
490
+ }
491
+ function isPathWithinAgainstScope(projectRelativePath, againstProjectPath) {
492
+ if (againstProjectPath === '' || againstProjectPath === '.') {
493
+ return true;
494
+ }
495
+ return projectRelativePath === againstProjectPath || projectRelativePath.startsWith(`${againstProjectPath}/`);
496
+ }
497
+ function isBarrelFile(projectRelativePath) {
498
+ return /(?:^|\/)index\.(?:ts|tsx|js|jsx)$/u.test(projectRelativePath);
499
+ }
500
+ function expandDiffScannedFiles(changedFiles, cruiseResult, againstProjectPath) {
501
+ const moduleBySource = new Map(cruiseResult.modules.map((module) => [normalizePath(module.source), module]));
502
+ const scannedFileSet = new Set();
503
+ for (const changedFile of changedFiles) {
504
+ const normalizedChangedFile = normalizePath(changedFile);
505
+ if (!moduleBySource.has(normalizedChangedFile)) {
506
+ continue;
507
+ }
508
+ scannedFileSet.add(normalizedChangedFile);
509
+ const changedModule = moduleBySource.get(normalizedChangedFile);
510
+ for (const dependent of changedModule?.dependents ?? []) {
511
+ const normalizedDependent = normalizePath(dependent);
512
+ if (isPathWithinAgainstScope(normalizedDependent, againstProjectPath)) {
513
+ scannedFileSet.add(normalizedDependent);
514
+ }
515
+ }
516
+ if (isBarrelFile(normalizedChangedFile)) {
517
+ for (const dependent of changedModule?.dependents ?? []) {
518
+ const normalizedDependent = normalizePath(dependent);
519
+ if (isPathWithinAgainstScope(normalizedDependent, againstProjectPath)) {
520
+ scannedFileSet.add(normalizedDependent);
521
+ }
522
+ }
523
+ }
524
+ }
525
+ const scannedFiles = Array.from(scannedFileSet).sort();
526
+ const warnings = scannedFiles.length > changedFiles.length
527
+ ? [{
528
+ code: 'diff-scope-expanded',
529
+ message: `diff scope 从 ${changedFiles.length} 个 changed file 扩展到 ${scannedFiles.length} 个 scanned file`,
530
+ }]
531
+ : [];
532
+ return {
533
+ scannedFiles,
534
+ warnings,
535
+ };
536
+ }
537
+ function filterViolationsByScannedFiles(violations, scannedFiles) {
538
+ const scannedFileSet = new Set(scannedFiles.map((filePath) => normalizePath(filePath)));
539
+ return violations.filter((violation) => scannedFileSet.has(normalizePath(violation.location)));
540
+ }
541
+ function mapDependencyCruiserViolations(violations, metadataByGeneratedName, projectRoot) {
542
+ return violations.flatMap((violation) => {
543
+ const metadata = metadataByGeneratedName.get(violation.rule.name);
544
+ if (!metadata) {
545
+ return [];
546
+ }
547
+ const fromPath = normalizePath(violation.from);
548
+ const toPath = normalizePath(violation.to);
549
+ return [{
550
+ rule: metadata.originalName,
551
+ rule_type: metadata.ruleType,
552
+ severity: metadata.severity,
553
+ location: fromPath,
554
+ message: `${fromPath} 依赖 ${toPath},违反规则 ${metadata.originalName}`,
555
+ dependency_chain: [fromPath, toPath],
556
+ hard_fail: metadata.severity === 'error',
557
+ diagnostic: findViolationDiagnostic(fromPath, toPath, projectRoot, getDiagnosticCategory(metadata.ruleType), 'dependency-cruiser'),
558
+ }];
559
+ });
560
+ }
561
+ function createSummary(violations, scannedFiles, ruleCount) {
562
+ const errorCount = violations.filter((violation) => violation.severity === 'error').length;
563
+ const warnCount = violations.filter((violation) => violation.severity === 'warn').length;
564
+ return {
565
+ total_violations: violations.length,
566
+ error_count: errorCount,
567
+ warn_count: warnCount,
568
+ scanned_file_count: scannedFiles.length,
569
+ rule_count: ruleCount,
570
+ };
571
+ }
572
+ function getCruiseTsConfigOption(projectRoot) {
573
+ const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
574
+ if (!existsSync(tsConfigPath)) {
575
+ return undefined;
576
+ }
577
+ return {
578
+ fileName: tsConfigPath,
579
+ };
580
+ }
581
+ export function hasBlockingContractViolations(result) {
582
+ return result.violations.some((violation) => violation.severity === 'error');
583
+ }
584
+ async function enrichContractCheckResultWithHistory(result, paths, historyRiskService) {
585
+ const violationFiles = Array.from(new Set(result.violations.map((violation) => normalizePath(violation.location))));
586
+ if (violationFiles.length === 0) {
587
+ return {
588
+ violations: [...result.violations],
589
+ warnings: [...result.warnings],
590
+ history: {
591
+ status: 'not_found',
592
+ confidence: 'medium',
593
+ freshness: 'unknown',
594
+ scope_mode: 'full',
595
+ enriched_file_count: 0,
596
+ unavailable_count: 0,
597
+ stale_count: 0,
598
+ low_confidence_count: 0,
599
+ requires_precompute: false,
600
+ },
601
+ };
602
+ }
603
+ const storageRoot = findHistoryStorageRoot(paths.againstAbsolutePath, paths.rootDir, paths.projectRoot);
604
+ const runtimeWarnings = [];
605
+ let loadedStorage = null;
606
+ let closeLoadedStorage;
607
+ try {
608
+ const service = historyRiskService ?? await (async () => {
609
+ loadedStorage = await createConfiguredStorage(storageRoot);
610
+ closeLoadedStorage = () => loadedStorage.storage.close();
611
+ return new GitHistoryService({
612
+ projectRoot: paths.projectRoot,
613
+ storage: loadedStorage.storage,
614
+ });
615
+ })();
616
+ const historyResult = await service.analyzeFiles(violationFiles, {
617
+ persist: true,
618
+ });
619
+ return {
620
+ violations: enrichViolationsWithHistory(result.violations, historyResult),
621
+ warnings: [...result.warnings, ...createHistoryWarnings(historyResult)],
622
+ history: summarizeHistoryEnrichment(historyResult),
623
+ };
624
+ }
625
+ catch (error) {
626
+ const message = error instanceof Error ? error.message : String(error);
627
+ runtimeWarnings.push({
628
+ code: 'history-risk-enrichment-unavailable',
629
+ message: `history risk enrichment 不可用: ${message}`,
630
+ });
631
+ return {
632
+ violations: [...result.violations],
633
+ warnings: [...result.warnings, ...runtimeWarnings],
634
+ history: {
635
+ status: 'unavailable',
636
+ confidence: 'unavailable',
637
+ freshness: 'unknown',
638
+ scope_mode: 'partial',
639
+ enriched_file_count: 0,
640
+ unavailable_count: violationFiles.length,
641
+ stale_count: 0,
642
+ low_confidence_count: violationFiles.length,
643
+ requires_precompute: false,
644
+ },
645
+ };
646
+ }
647
+ finally {
648
+ if (closeLoadedStorage) {
649
+ await closeLoadedStorage();
650
+ }
651
+ }
652
+ }
653
+ export async function runContractCheck(options) {
654
+ const paths = resolveContractCheckPaths(options.againstPath, options.rootDir);
655
+ const contract = await loadDesignContract({
656
+ filePath: options.contractPath,
657
+ rootDir: paths.rootDir,
658
+ });
659
+ if (!contract.ok) {
660
+ throw new Error(`Contract 文件不合法,请先运行 design validate: ${contract.filePath}`);
661
+ }
662
+ const translatedRules = buildDependencyCruiserRuleSet(contract.contract.rules);
663
+ const tsConfig = getCruiseTsConfigOption(paths.projectRoot);
664
+ const dependencyCruiserOutput = await cruise([paths.cruiseStartPath], {
665
+ baseDir: paths.projectRoot,
666
+ validate: Boolean(translatedRules.ruleSet),
667
+ ruleSet: translatedRules.ruleSet,
668
+ tsPreCompilationDeps: 'specify',
669
+ ...(tsConfig ? { tsConfig } : {}),
670
+ exclude: {
671
+ path: CONTRACT_CHECK_EXCLUDE_PATHS,
672
+ },
673
+ doNotFollow: {
674
+ path: 'node_modules',
675
+ },
676
+ }, undefined);
677
+ if (typeof dependencyCruiserOutput.output === 'string') {
678
+ throw new Error('dependency-cruiser 返回了非结构化输出,无法继续 contract check');
679
+ }
680
+ const dependencyCruiserViolations = mapDependencyCruiserViolations(dependencyCruiserOutput.output.summary.violations, translatedRules.metadataByGeneratedName, paths.projectRoot);
681
+ const publicApiViolations = evaluateModulePublicApiOnlyRules(contract.contract.rules.filter((rule) => rule.type === 'module_public_api_only'), dependencyCruiserOutput.output, paths.projectRoot);
682
+ const allScannedFiles = dependencyCruiserOutput.output.modules
683
+ .map((module) => toRelativeProjectPath(module.source, paths.projectRoot))
684
+ .sort();
685
+ const scanMode = options.scanMode ?? 'full';
686
+ const diffScope = scanMode === 'diff'
687
+ ? expandDiffScannedFiles(options.changedFiles ?? [], dependencyCruiserOutput.output, paths.againstProjectPath)
688
+ : { scannedFiles: allScannedFiles, warnings: [] };
689
+ const scannedFiles = diffScope.scannedFiles.length > 0 ? diffScope.scannedFiles : allScannedFiles;
690
+ const complexityEvaluation = evaluateComplexityThresholdRules(contract.contract.rules.filter((rule) => rule.type === 'complexity_threshold'), scannedFiles, allScannedFiles, paths.projectRoot);
691
+ const allViolations = [
692
+ ...dependencyCruiserViolations,
693
+ ...publicApiViolations,
694
+ ...complexityEvaluation.violations,
695
+ ];
696
+ const violations = scanMode === 'diff'
697
+ ? filterViolationsByScannedFiles(allViolations, scannedFiles)
698
+ : allViolations;
699
+ const summary = createSummary(violations, scannedFiles, contract.contract.rules.length);
700
+ const baseResult = {
701
+ passed: summary.error_count === 0,
702
+ scan_mode: scanMode,
703
+ contract_path: contract.filePath,
704
+ against_path: paths.againstProjectPath,
705
+ changed_files: [...(options.changedFiles ?? [])],
706
+ scanned_files: scannedFiles,
707
+ warnings: [...(options.warnings ?? []), ...diffScope.warnings, ...complexityEvaluation.warnings],
708
+ violations,
709
+ summary,
710
+ };
711
+ const historyEnrichment = await enrichContractCheckResultWithHistory(baseResult, paths, options.historyRiskService);
712
+ return {
713
+ ...baseResult,
714
+ violations: historyEnrichment.violations,
715
+ warnings: historyEnrichment.warnings,
716
+ history: historyEnrichment.history,
717
+ };
718
+ }
719
+ //# sourceMappingURL=contract-checker.js.map