@mycodemap/mycodemap 0.4.2 → 0.5.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 (288) hide show
  1. package/CHANGELOG.md +105 -3
  2. package/README.md +192 -53
  3. package/dist/ai/claude.d.ts +38 -0
  4. package/dist/ai/claude.d.ts.map +1 -0
  5. package/dist/ai/claude.js +169 -0
  6. package/dist/ai/claude.js.map +1 -0
  7. package/dist/ai/codex.d.ts +38 -0
  8. package/dist/ai/codex.d.ts.map +1 -0
  9. package/dist/ai/codex.js +169 -0
  10. package/dist/ai/codex.js.map +1 -0
  11. package/dist/ai/factory.d.ts +48 -0
  12. package/dist/ai/factory.d.ts.map +1 -0
  13. package/dist/ai/factory.js +95 -0
  14. package/dist/ai/factory.js.map +1 -0
  15. package/dist/ai/index.d.ts +12 -0
  16. package/dist/ai/index.d.ts.map +1 -0
  17. package/dist/ai/index.js +29 -0
  18. package/dist/ai/index.js.map +1 -0
  19. package/dist/ai/provider.d.ts +70 -0
  20. package/dist/ai/provider.d.ts.map +1 -0
  21. package/dist/ai/provider.js +31 -0
  22. package/dist/ai/provider.js.map +1 -0
  23. package/dist/ai/subagent-caller.d.ts +90 -0
  24. package/dist/ai/subagent-caller.d.ts.map +1 -0
  25. package/dist/ai/subagent-caller.js +280 -0
  26. package/dist/ai/subagent-caller.js.map +1 -0
  27. package/dist/ai/types.d.ts +70 -0
  28. package/dist/ai/types.d.ts.map +1 -0
  29. package/dist/ai/types.js +5 -0
  30. package/dist/ai/types.js.map +1 -0
  31. package/dist/cli/commands/analyze.d.ts +18 -0
  32. package/dist/cli/commands/analyze.d.ts.map +1 -1
  33. package/dist/cli/commands/analyze.js +239 -6
  34. package/dist/cli/commands/analyze.js.map +1 -1
  35. package/dist/cli/commands/check.d.ts +22 -0
  36. package/dist/cli/commands/check.d.ts.map +1 -0
  37. package/dist/cli/commands/check.js +168 -0
  38. package/dist/cli/commands/check.js.map +1 -0
  39. package/dist/cli/commands/ci.d.ts +25 -0
  40. package/dist/cli/commands/ci.d.ts.map +1 -1
  41. package/dist/cli/commands/ci.js +139 -36
  42. package/dist/cli/commands/ci.js.map +1 -1
  43. package/dist/cli/commands/complexity.d.ts.map +1 -1
  44. package/dist/cli/commands/complexity.js +6 -0
  45. package/dist/cli/commands/complexity.js.map +1 -1
  46. package/dist/cli/commands/design.d.ts +52 -0
  47. package/dist/cli/commands/design.d.ts.map +1 -0
  48. package/dist/cli/commands/design.js +274 -0
  49. package/dist/cli/commands/design.js.map +1 -0
  50. package/dist/cli/commands/generate.d.ts +1 -0
  51. package/dist/cli/commands/generate.d.ts.map +1 -1
  52. package/dist/cli/commands/generate.js +121 -8
  53. package/dist/cli/commands/generate.js.map +1 -1
  54. package/dist/cli/commands/history.d.ts +26 -0
  55. package/dist/cli/commands/history.d.ts.map +1 -0
  56. package/dist/cli/commands/history.js +92 -0
  57. package/dist/cli/commands/history.js.map +1 -0
  58. package/dist/cli/commands/mcp.d.ts +13 -0
  59. package/dist/cli/commands/mcp.d.ts.map +1 -0
  60. package/dist/cli/commands/mcp.js +108 -0
  61. package/dist/cli/commands/mcp.js.map +1 -0
  62. package/dist/cli/commands/server.d.ts +9 -0
  63. package/dist/cli/commands/server.d.ts.map +1 -0
  64. package/dist/cli/commands/server.js +65 -0
  65. package/dist/cli/commands/server.js.map +1 -0
  66. package/dist/cli/commands/ship/pipeline.d.ts.map +1 -1
  67. package/dist/cli/commands/ship/pipeline.js +8 -1
  68. package/dist/cli/commands/ship/pipeline.js.map +1 -1
  69. package/dist/cli/commands/ship/publisher.d.ts +9 -1
  70. package/dist/cli/commands/ship/publisher.d.ts.map +1 -1
  71. package/dist/cli/commands/ship/publisher.js +149 -6
  72. package/dist/cli/commands/ship/publisher.js.map +1 -1
  73. package/dist/cli/commands/workflow.d.ts.map +1 -1
  74. package/dist/cli/commands/workflow.js +22 -2
  75. package/dist/cli/commands/workflow.js.map +1 -1
  76. package/dist/cli/config-loader.d.ts.map +1 -1
  77. package/dist/cli/config-loader.js +3 -2
  78. package/dist/cli/config-loader.js.map +1 -1
  79. package/dist/cli/contract-checker.d.ts +33 -0
  80. package/dist/cli/contract-checker.d.ts.map +1 -0
  81. package/dist/cli/contract-checker.js +719 -0
  82. package/dist/cli/contract-checker.js.map +1 -0
  83. package/dist/cli/contract-diff-scope.d.ts +14 -0
  84. package/dist/cli/contract-diff-scope.d.ts.map +1 -0
  85. package/dist/cli/contract-diff-scope.js +127 -0
  86. package/dist/cli/contract-diff-scope.js.map +1 -0
  87. package/dist/cli/contract-gate-thresholds.d.ts +14 -0
  88. package/dist/cli/contract-gate-thresholds.d.ts.map +1 -0
  89. package/dist/cli/contract-gate-thresholds.js +19 -0
  90. package/dist/cli/contract-gate-thresholds.js.map +1 -0
  91. package/dist/cli/design-contract-loader.d.ts +15 -0
  92. package/dist/cli/design-contract-loader.d.ts.map +1 -0
  93. package/dist/cli/design-contract-loader.js +527 -0
  94. package/dist/cli/design-contract-loader.js.map +1 -0
  95. package/dist/cli/design-contract-schema.d.ts +11 -0
  96. package/dist/cli/design-contract-schema.d.ts.map +1 -0
  97. package/dist/cli/design-contract-schema.js +75 -0
  98. package/dist/cli/design-contract-schema.js.map +1 -0
  99. package/dist/cli/design-handoff-builder.d.ts +15 -0
  100. package/dist/cli/design-handoff-builder.d.ts.map +1 -0
  101. package/dist/cli/design-handoff-builder.js +345 -0
  102. package/dist/cli/design-handoff-builder.js.map +1 -0
  103. package/dist/cli/design-scope-resolver.d.ts +8 -0
  104. package/dist/cli/design-scope-resolver.d.ts.map +1 -0
  105. package/dist/cli/design-scope-resolver.js +760 -0
  106. package/dist/cli/design-scope-resolver.js.map +1 -0
  107. package/dist/cli/design-verification-builder.d.ts +8 -0
  108. package/dist/cli/design-verification-builder.d.ts.map +1 -0
  109. package/dist/cli/design-verification-builder.js +369 -0
  110. package/dist/cli/design-verification-builder.js.map +1 -0
  111. package/dist/cli/index.js +20 -6
  112. package/dist/cli/index.js.map +1 -1
  113. package/dist/cli/paths.d.ts.map +1 -1
  114. package/dist/cli/paths.js +30 -7
  115. package/dist/cli/paths.js.map +1 -1
  116. package/dist/cli-new/commands/server.d.ts +13 -0
  117. package/dist/cli-new/commands/server.d.ts.map +1 -0
  118. package/dist/cli-new/commands/server.js +90 -0
  119. package/dist/cli-new/commands/server.js.map +1 -0
  120. package/dist/core/analyzer.d.ts.map +1 -1
  121. package/dist/core/analyzer.js +16 -0
  122. package/dist/core/analyzer.js.map +1 -1
  123. package/dist/domain/entities/CodeGraph.d.ts +5 -1
  124. package/dist/domain/entities/CodeGraph.d.ts.map +1 -1
  125. package/dist/domain/entities/CodeGraph.js +29 -12
  126. package/dist/domain/entities/CodeGraph.js.map +1 -1
  127. package/dist/domain/entities/Dependency.d.ts +8 -1
  128. package/dist/domain/entities/Dependency.d.ts.map +1 -1
  129. package/dist/domain/entities/Dependency.js +19 -4
  130. package/dist/domain/entities/Dependency.js.map +1 -1
  131. package/dist/domain/entities/Symbol.d.ts +2 -1
  132. package/dist/domain/entities/Symbol.d.ts.map +1 -1
  133. package/dist/domain/entities/Symbol.js +6 -3
  134. package/dist/domain/entities/Symbol.js.map +1 -1
  135. package/dist/generator/ai-overview.d.ts +51 -0
  136. package/dist/generator/ai-overview.d.ts.map +1 -0
  137. package/dist/generator/ai-overview.js +160 -0
  138. package/dist/generator/ai-overview.js.map +1 -0
  139. package/dist/infrastructure/storage/StorageFactory.d.ts +13 -5
  140. package/dist/infrastructure/storage/StorageFactory.d.ts.map +1 -1
  141. package/dist/infrastructure/storage/StorageFactory.js +62 -16
  142. package/dist/infrastructure/storage/StorageFactory.js.map +1 -1
  143. package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts +3 -1
  144. package/dist/infrastructure/storage/adapters/FileSystemStorage.d.ts.map +1 -1
  145. package/dist/infrastructure/storage/adapters/FileSystemStorage.js +10 -2
  146. package/dist/infrastructure/storage/adapters/FileSystemStorage.js.map +1 -1
  147. package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts +3 -1
  148. package/dist/infrastructure/storage/adapters/KuzuDBStorage.d.ts.map +1 -1
  149. package/dist/infrastructure/storage/adapters/KuzuDBStorage.js +9 -1
  150. package/dist/infrastructure/storage/adapters/KuzuDBStorage.js.map +1 -1
  151. package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts +3 -1
  152. package/dist/infrastructure/storage/adapters/MemoryStorage.d.ts.map +1 -1
  153. package/dist/infrastructure/storage/adapters/MemoryStorage.js +9 -1
  154. package/dist/infrastructure/storage/adapters/MemoryStorage.js.map +1 -1
  155. package/dist/infrastructure/storage/adapters/Neo4jStorage.d.ts +41 -0
  156. package/dist/infrastructure/storage/adapters/Neo4jStorage.d.ts.map +1 -0
  157. package/dist/infrastructure/storage/adapters/Neo4jStorage.js +162 -0
  158. package/dist/infrastructure/storage/adapters/Neo4jStorage.js.map +1 -0
  159. package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts +53 -0
  160. package/dist/infrastructure/storage/adapters/SQLiteStorage.d.ts.map +1 -0
  161. package/dist/infrastructure/storage/adapters/SQLiteStorage.js +879 -0
  162. package/dist/infrastructure/storage/adapters/SQLiteStorage.js.map +1 -0
  163. package/dist/infrastructure/storage/graph-helpers.d.ts +3 -1
  164. package/dist/infrastructure/storage/graph-helpers.d.ts.map +1 -1
  165. package/dist/infrastructure/storage/graph-helpers.js +90 -0
  166. package/dist/infrastructure/storage/graph-helpers.js.map +1 -1
  167. package/dist/infrastructure/storage/index.d.ts +1 -1
  168. package/dist/infrastructure/storage/index.d.ts.map +1 -1
  169. package/dist/infrastructure/storage/interfaces/StorageBase.d.ts +3 -1
  170. package/dist/infrastructure/storage/interfaces/StorageBase.d.ts.map +1 -1
  171. package/dist/infrastructure/storage/interfaces/StorageBase.js.map +1 -1
  172. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts +27 -0
  173. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.d.ts.map +1 -0
  174. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js +246 -0
  175. package/dist/infrastructure/storage/sqlite/GovernanceGraphCache.js.map +1 -0
  176. package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts +25 -0
  177. package/dist/infrastructure/storage/sqlite/perf-thresholds.d.ts.map +1 -0
  178. package/dist/infrastructure/storage/sqlite/perf-thresholds.js +25 -0
  179. package/dist/infrastructure/storage/sqlite/perf-thresholds.js.map +1 -0
  180. package/dist/infrastructure/storage/sqlite/schema.d.ts +4 -0
  181. package/dist/infrastructure/storage/sqlite/schema.d.ts.map +1 -0
  182. package/dist/infrastructure/storage/sqlite/schema.js +111 -0
  183. package/dist/infrastructure/storage/sqlite/schema.js.map +1 -0
  184. package/dist/interface/types/design-check.d.ts +73 -0
  185. package/dist/interface/types/design-check.d.ts.map +1 -0
  186. package/dist/interface/types/design-check.js +4 -0
  187. package/dist/interface/types/design-check.js.map +1 -0
  188. package/dist/interface/types/design-contract.d.ts +123 -0
  189. package/dist/interface/types/design-contract.d.ts.map +1 -0
  190. package/dist/interface/types/design-contract.js +7 -0
  191. package/dist/interface/types/design-contract.js.map +1 -0
  192. package/dist/interface/types/design-handoff.d.ts +68 -0
  193. package/dist/interface/types/design-handoff.d.ts.map +1 -0
  194. package/dist/interface/types/design-handoff.js +4 -0
  195. package/dist/interface/types/design-handoff.js.map +1 -0
  196. package/dist/interface/types/design-mapping.d.ts +51 -0
  197. package/dist/interface/types/design-mapping.d.ts.map +1 -0
  198. package/dist/interface/types/design-mapping.js +4 -0
  199. package/dist/interface/types/design-mapping.js.map +1 -0
  200. package/dist/interface/types/design-verification.d.ts +49 -0
  201. package/dist/interface/types/design-verification.d.ts.map +1 -0
  202. package/dist/interface/types/design-verification.js +4 -0
  203. package/dist/interface/types/design-verification.js.map +1 -0
  204. package/dist/interface/types/history-risk.d.ts +90 -0
  205. package/dist/interface/types/history-risk.d.ts.map +1 -0
  206. package/dist/interface/types/history-risk.js +4 -0
  207. package/dist/interface/types/history-risk.js.map +1 -0
  208. package/dist/interface/types/index.d.ts +20 -1
  209. package/dist/interface/types/index.d.ts.map +1 -1
  210. package/dist/interface/types/storage.d.ts +28 -1
  211. package/dist/interface/types/storage.d.ts.map +1 -1
  212. package/dist/orchestrator/adapters/ast-grep-adapter.d.ts +10 -0
  213. package/dist/orchestrator/adapters/ast-grep-adapter.d.ts.map +1 -1
  214. package/dist/orchestrator/adapters/ast-grep-adapter.js +46 -17
  215. package/dist/orchestrator/adapters/ast-grep-adapter.js.map +1 -1
  216. package/dist/orchestrator/adapters/codemap-adapter.d.ts.map +1 -1
  217. package/dist/orchestrator/adapters/codemap-adapter.js +2 -22
  218. package/dist/orchestrator/adapters/codemap-adapter.js.map +1 -1
  219. package/dist/orchestrator/ai-feed-generator.d.ts +210 -0
  220. package/dist/orchestrator/ai-feed-generator.d.ts.map +1 -0
  221. package/dist/orchestrator/ai-feed-generator.js +377 -0
  222. package/dist/orchestrator/ai-feed-generator.js.map +1 -0
  223. package/dist/orchestrator/history-risk-service.d.ts +55 -0
  224. package/dist/orchestrator/history-risk-service.d.ts.map +1 -0
  225. package/dist/orchestrator/history-risk-service.js +680 -0
  226. package/dist/orchestrator/history-risk-service.js.map +1 -0
  227. package/dist/orchestrator/types.d.ts +19 -1
  228. package/dist/orchestrator/types.d.ts.map +1 -1
  229. package/dist/orchestrator/types.js +19 -0
  230. package/dist/orchestrator/types.js.map +1 -1
  231. package/dist/server/mcp/index.d.ts +4 -0
  232. package/dist/server/mcp/index.d.ts.map +1 -0
  233. package/dist/server/mcp/index.js +5 -0
  234. package/dist/server/mcp/index.js.map +1 -0
  235. package/dist/server/mcp/server.d.ts +17 -0
  236. package/dist/server/mcp/server.d.ts.map +1 -0
  237. package/dist/server/mcp/server.js +84 -0
  238. package/dist/server/mcp/server.js.map +1 -0
  239. package/dist/server/mcp/service.d.ts +22 -0
  240. package/dist/server/mcp/service.d.ts.map +1 -0
  241. package/dist/server/mcp/service.js +177 -0
  242. package/dist/server/mcp/service.js.map +1 -0
  243. package/dist/server/mcp/types.d.ts +56 -0
  244. package/dist/server/mcp/types.d.ts.map +1 -0
  245. package/dist/server/mcp/types.js +4 -0
  246. package/dist/server/mcp/types.js.map +1 -0
  247. package/docs/AI_ASSISTANT_SETUP.md +1 -1
  248. package/docs/SETUP_GUIDE.md +6 -6
  249. package/docs/ai-guide/COMMANDS.md +171 -4
  250. package/docs/ai-guide/INTEGRATION.md +137 -433
  251. package/docs/ai-guide/OUTPUT.md +890 -5
  252. package/docs/ai-guide/PATTERNS.md +54 -14
  253. package/docs/ai-guide/PROMPTS.md +17 -6
  254. package/docs/archive/test-report-symbol-search.md +384 -0
  255. package/docs/archive/test-scenario-4-complexity-analysis.md +460 -0
  256. package/docs/archive/test_report_scenario5.md +615 -0
  257. package/docs/archive/test_scenario_3_impact_analysis_report.md +520 -0
  258. package/docs/backlog.md +177 -0
  259. package/docs/eatdogfood-reports/2026-04-17-eatdogfood-agent-experience.md +231 -0
  260. package/docs/exec-plans/completed/2026-04-17-eatdogfood-codemap-cli.md +103 -0
  261. package/docs/ideation/2026-04-15-executable-architecture-constitution-ideation.md +102 -0
  262. package/docs/product-specs/DESIGN_CONTRACT_TEMPLATE.md +126 -0
  263. package/docs/product-specs/MVP3-ARCHITECTURE-COMPARISON.md +11 -10
  264. package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-PRD.md +10 -10
  265. package/docs/product-specs/MVP3-ARCHITECTURE-REDESIGN-TECH-PRD.md +17 -12
  266. package/docs/product-specs/README.md +2 -1
  267. package/docs/rules/README.md +16 -11
  268. package/docs/rules/architecture-guardrails.md +24 -336
  269. package/docs/rules/code-quality-redlines.md +25 -311
  270. package/docs/rules/engineering-with-codex-openai.md +20 -3
  271. package/docs/rules/validation.md +90 -37
  272. package/mycodemap.config.schema.json +3 -3
  273. package/package.json +7 -2
  274. package/scripts/benchmark-governance-graph.mjs +132 -0
  275. package/scripts/calibrate-contract-gate.mjs +221 -0
  276. package/scripts/capability-report.py +255 -0
  277. package/scripts/experiments/arcadedb-http-smoke.mjs +90 -0
  278. package/scripts/qa-rule-control.sh +254 -0
  279. package/scripts/report-high-risk-files.mjs +395 -0
  280. package/scripts/rule-context.mjs +155 -0
  281. package/scripts/smoke-sqlite-impact.mjs +85 -0
  282. package/scripts/sync-analyze-docs.js +1 -0
  283. package/scripts/tests/test_capability_report.py +89 -0
  284. package/scripts/tests/test_rule_control_workflow.py +51 -0
  285. package/scripts/tests/test_validate_rules.py +81 -0
  286. package/scripts/validate-ai-docs.js +283 -1
  287. package/scripts/validate-docs.js +479 -25
  288. package/scripts/validate-rules.py +254 -0
@@ -0,0 +1,680 @@
1
+ // [META] since:2026-04-15 | owner:architecture-team | stable:false
2
+ // [WHY] Canonical history/risk service that materializes git signals once and degrades safely when history is missing
3
+ import path from 'node:path';
4
+ import { GitAnalyzer, TAG_WEIGHTS } from './git-analyzer.js';
5
+ import { WorkflowGitAnalyzer } from './workflow/git-analyzer.js';
6
+ const DEFAULT_MAX_COMMITS = 20;
7
+ const DEFAULT_MAX_FILES_PER_REQUEST = 5;
8
+ const DEFAULT_PRECOMPUTE_THRESHOLD = 25;
9
+ const DEFAULT_STALE_AFTER_HOURS = 24;
10
+ const DEFAULT_EXPIRED_AFTER_HOURS = 24 * 7;
11
+ const DEFAULT_IMPACT_DEPTH = 2;
12
+ function isHistoryRiskStorageAdapter(storage) {
13
+ const candidate = storage;
14
+ return typeof candidate.saveHistoryRiskSnapshot === 'function'
15
+ && typeof candidate.loadLatestFileHistorySignal === 'function'
16
+ && typeof candidate.loadLatestSymbolHistoryResult === 'function';
17
+ }
18
+ function uniqueValues(values) {
19
+ return [...new Set(values.map(value => value.trim()).filter(value => value.length > 0))];
20
+ }
21
+ function normalizeProjectRelativePath(filePath, projectRoot) {
22
+ const normalizedPath = filePath.replace(/\\/g, '/');
23
+ if (!path.isAbsolute(filePath)) {
24
+ return normalizedPath;
25
+ }
26
+ const relativePath = path.relative(projectRoot, filePath).replace(/\\/g, '/');
27
+ return relativePath.length > 0 ? relativePath : normalizedPath;
28
+ }
29
+ function toIsoString(value) {
30
+ if (value instanceof Date) {
31
+ return value.toISOString();
32
+ }
33
+ if (typeof value === 'string' && value.length > 0) {
34
+ const parsed = new Date(value);
35
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
36
+ }
37
+ return null;
38
+ }
39
+ function createUnavailableRiskScore(reason) {
40
+ return {
41
+ level: 'unavailable',
42
+ score: null,
43
+ gravity: null,
44
+ heat: null,
45
+ impact: null,
46
+ riskFactors: reason ? [reason] : [],
47
+ };
48
+ }
49
+ function downgradeConfidence(confidence) {
50
+ if (confidence === 'high') {
51
+ return 'medium';
52
+ }
53
+ if (confidence === 'medium') {
54
+ return 'low';
55
+ }
56
+ return confidence;
57
+ }
58
+ function pickHigherConfidence(left, right) {
59
+ const rank = {
60
+ unavailable: 0,
61
+ low: 1,
62
+ medium: 2,
63
+ high: 3,
64
+ };
65
+ return rank[left] >= rank[right] ? left : right;
66
+ }
67
+ function calculateFreshness(analyzedAt, now, staleAfterHours, expiredAfterHours) {
68
+ if (!analyzedAt) {
69
+ return 'unknown';
70
+ }
71
+ const parsed = new Date(analyzedAt);
72
+ if (Number.isNaN(parsed.getTime())) {
73
+ return 'unknown';
74
+ }
75
+ const ageHours = (now.getTime() - parsed.getTime()) / (1000 * 60 * 60);
76
+ if (ageHours <= staleAfterHours) {
77
+ return 'fresh';
78
+ }
79
+ if (ageHours <= expiredAfterHours) {
80
+ return 'stale';
81
+ }
82
+ return 'expired';
83
+ }
84
+ function refreshDiagnostics(diagnostics, now, staleAfterHours, expiredAfterHours) {
85
+ const freshness = calculateFreshness(diagnostics.analyzedAt, now, staleAfterHours, expiredAfterHours);
86
+ let confidence = diagnostics.confidence;
87
+ const reasons = [...diagnostics.reasons];
88
+ if (freshness === 'stale') {
89
+ confidence = downgradeConfidence(confidence);
90
+ reasons.push('materialized history is stale');
91
+ }
92
+ else if (freshness === 'expired') {
93
+ confidence = 'low';
94
+ reasons.push('materialized history is expired');
95
+ }
96
+ return {
97
+ ...diagnostics,
98
+ freshness,
99
+ confidence,
100
+ source: diagnostics.source === 'unavailable' ? 'unavailable' : 'sqlite-cache',
101
+ reasons: uniqueValues(reasons),
102
+ };
103
+ }
104
+ function mapWorkflowHeat(heat) {
105
+ return {
106
+ freq30d: heat?.freq30d ?? 0,
107
+ lastType: (heat?.lastType ?? 'NEW'),
108
+ lastDate: toIsoString(heat?.lastDate),
109
+ stability: heat?.stability ?? true,
110
+ };
111
+ }
112
+ function hasHistoryEvidence(heat, timeline) {
113
+ return timeline.length > 0 || heat.freq30d > 0 || heat.lastDate !== null;
114
+ }
115
+ function createDiagnostics(params) {
116
+ return {
117
+ status: params.status,
118
+ confidence: params.confidence,
119
+ freshness: params.analyzedAt ? 'fresh' : 'unknown',
120
+ source: params.source,
121
+ reasons: uniqueValues(params.reasons),
122
+ analyzedAt: params.analyzedAt,
123
+ scopeMode: params.scopeMode,
124
+ requestedFiles: params.requestedFiles,
125
+ analyzedFiles: params.analyzedFiles,
126
+ requiresPrecompute: params.requiresPrecompute ?? false,
127
+ };
128
+ }
129
+ function createUnavailableFileSignal(file, reason, analyzedAt, source, status = 'unavailable') {
130
+ return {
131
+ file,
132
+ risk: createUnavailableRiskScore(reason),
133
+ timeline: [],
134
+ diagnostics: createDiagnostics({
135
+ status,
136
+ confidence: status === 'not_found' ? 'low' : 'unavailable',
137
+ source,
138
+ reasons: [reason],
139
+ analyzedAt,
140
+ scopeMode: 'partial',
141
+ requestedFiles: 1,
142
+ analyzedFiles: 0,
143
+ }),
144
+ };
145
+ }
146
+ function createUnavailableSymbolResult(query, reason, status = 'unavailable', candidates = []) {
147
+ return {
148
+ query,
149
+ candidates,
150
+ symbol: null,
151
+ files: [],
152
+ timeline: [],
153
+ risk: createUnavailableRiskScore(reason),
154
+ diagnostics: createDiagnostics({
155
+ status,
156
+ confidence: status === 'not_found' || status === 'ambiguous' ? 'low' : 'unavailable',
157
+ source: status === 'unavailable' ? 'unavailable' : 'git-live',
158
+ reasons: [reason],
159
+ analyzedAt: null,
160
+ scopeMode: 'partial',
161
+ requestedFiles: 0,
162
+ analyzedFiles: 0,
163
+ }),
164
+ };
165
+ }
166
+ function createSyntheticWorkflowContext(task) {
167
+ const now = new Date();
168
+ return {
169
+ id: 'history-risk-service',
170
+ task,
171
+ currentPhase: 'link',
172
+ phaseStatus: 'running',
173
+ artifacts: new Map(),
174
+ cachedResults: {},
175
+ userConfirmed: new Set(),
176
+ startedAt: now,
177
+ updatedAt: now,
178
+ };
179
+ }
180
+ function normalizeTimeline(commits, source) {
181
+ return commits.map((commit) => {
182
+ const tagType = commit.tag?.type ?? 'UNKNOWN';
183
+ return {
184
+ hash: commit.hash,
185
+ message: commit.message,
186
+ date: toIsoString(commit.date) ?? new Date(0).toISOString(),
187
+ author: commit.author,
188
+ files: commit.files,
189
+ tagType,
190
+ tagScope: commit.tag?.scope ?? 'general',
191
+ subject: commit.tag?.subject ?? commit.message,
192
+ riskWeight: TAG_WEIGHTS[tagType] ?? TAG_WEIGHTS.UNKNOWN,
193
+ source,
194
+ };
195
+ });
196
+ }
197
+ function filterCommitsForFile(commits, file) {
198
+ return commits.filter((commit) => commit.files.length === 0 || commit.files.includes(file));
199
+ }
200
+ function filterCommitsForSymbol(commits, candidate) {
201
+ return commits.filter((commit) => commit.files.includes(candidate.file) || commit.message.includes(candidate.name));
202
+ }
203
+ function buildCandidate(symbol, query) {
204
+ return {
205
+ symbolId: symbol.id,
206
+ moduleId: symbol.moduleId,
207
+ name: symbol.name,
208
+ kind: symbol.kind,
209
+ file: symbol.location.file,
210
+ line: symbol.location.line,
211
+ exactNameMatch: symbol.name === query,
212
+ };
213
+ }
214
+ export class GitHistoryService {
215
+ projectRoot;
216
+ storage;
217
+ gitAnalyzer;
218
+ workflowGitAnalyzer;
219
+ now;
220
+ maxCommits;
221
+ maxFilesPerRequest;
222
+ precomputeThreshold;
223
+ staleAfterHours;
224
+ expiredAfterHours;
225
+ impactDepth;
226
+ constructor(options) {
227
+ this.projectRoot = options.projectRoot;
228
+ this.storage = options.storage;
229
+ this.gitAnalyzer = options.gitAnalyzer ?? new GitAnalyzer();
230
+ this.workflowGitAnalyzer = options.workflowGitAnalyzer ?? new WorkflowGitAnalyzer(options.projectRoot);
231
+ this.now = options.now ?? (() => new Date());
232
+ this.maxCommits = options.maxCommits ?? DEFAULT_MAX_COMMITS;
233
+ this.maxFilesPerRequest = options.maxFilesPerRequest ?? DEFAULT_MAX_FILES_PER_REQUEST;
234
+ this.precomputeThreshold = options.precomputeThreshold ?? DEFAULT_PRECOMPUTE_THRESHOLD;
235
+ this.staleAfterHours = options.staleAfterHours ?? DEFAULT_STALE_AFTER_HOURS;
236
+ this.expiredAfterHours = options.expiredAfterHours ?? DEFAULT_EXPIRED_AFTER_HOURS;
237
+ this.impactDepth = options.impactDepth ?? DEFAULT_IMPACT_DEPTH;
238
+ }
239
+ async analyzeFiles(files, options = {}) {
240
+ const requestedFiles = uniqueValues(files);
241
+ if (requestedFiles.length === 0) {
242
+ return {
243
+ requestedFiles: [],
244
+ files: [],
245
+ aggregatedRisk: createUnavailableRiskScore('no target files provided'),
246
+ diagnostics: createDiagnostics({
247
+ status: 'unavailable',
248
+ confidence: 'unavailable',
249
+ source: 'unavailable',
250
+ reasons: ['no target files provided'],
251
+ analyzedAt: null,
252
+ scopeMode: 'partial',
253
+ requestedFiles: 0,
254
+ analyzedFiles: 0,
255
+ }),
256
+ };
257
+ }
258
+ const selection = await this.selectFilesForAnalysis(requestedFiles, options.maxFiles ?? this.maxFilesPerRequest);
259
+ const storageAdapter = this.getHistoryRiskStorageAdapter();
260
+ const analyzedAt = this.now().toISOString();
261
+ const gitAvailable = await this.gitAnalyzer.isGitRepository(this.projectRoot);
262
+ if (!gitAvailable) {
263
+ return this.loadFileAnalysisFromCache(requestedFiles, selection, storageAdapter);
264
+ }
265
+ const workflowAnalysis = await this.workflowGitAnalyzer.analyzeForPhase('link', selection.selectedFiles, createSyntheticWorkflowContext('history-risk:file'));
266
+ const heatMap = this.buildHeatMap(workflowAnalysis);
267
+ const commits = await this.gitAnalyzer.findRelatedCommits([], selection.selectedFiles, {
268
+ maxCommits: options.maxCommits ?? this.maxCommits,
269
+ projectRoot: this.projectRoot,
270
+ });
271
+ const fileSignals = selection.selectedFiles.map((file) => {
272
+ const timeline = normalizeTimeline(filterCommitsForFile(commits, file), 'file');
273
+ const heat = heatMap.get(file) ?? mapWorkflowHeat();
274
+ const metrics = selection.metricsByFile.get(file) ?? {
275
+ file,
276
+ moduleIds: [],
277
+ dependencyIds: [],
278
+ dependentIds: [],
279
+ gravity: 0,
280
+ impact: 0,
281
+ };
282
+ if (!hasHistoryEvidence(heat, timeline)) {
283
+ return {
284
+ file,
285
+ risk: createUnavailableRiskScore('no git history evidence for file'),
286
+ timeline: [],
287
+ diagnostics: createDiagnostics({
288
+ status: 'not_found',
289
+ confidence: selection.scopeReduced || selection.requiresPrecompute ? 'low' : 'medium',
290
+ source: 'git-live',
291
+ reasons: ['no git history evidence for file'],
292
+ analyzedAt,
293
+ scopeMode: selection.scopeMode,
294
+ requestedFiles: 1,
295
+ analyzedFiles: 1,
296
+ requiresPrecompute: selection.requiresPrecompute,
297
+ }),
298
+ };
299
+ }
300
+ let confidence = timeline.length > 0 && heat.lastDate ? 'high' : 'medium';
301
+ if (selection.scopeReduced || selection.requiresPrecompute) {
302
+ confidence = downgradeConfidence(confidence);
303
+ }
304
+ return {
305
+ file,
306
+ risk: this.calculateRiskFromFeed(file, commits, heat, metrics),
307
+ timeline,
308
+ diagnostics: createDiagnostics({
309
+ status: 'ok',
310
+ confidence,
311
+ source: 'git-live',
312
+ reasons: ['git history materialized from live repository'],
313
+ analyzedAt,
314
+ scopeMode: selection.scopeMode,
315
+ requestedFiles: 1,
316
+ analyzedFiles: 1,
317
+ requiresPrecompute: selection.requiresPrecompute,
318
+ }),
319
+ };
320
+ });
321
+ let snapshotId;
322
+ if (storageAdapter && options.persist !== false) {
323
+ const snapshot = await storageAdapter.saveHistoryRiskSnapshot({
324
+ recordedAt: analyzedAt,
325
+ source: 'git-live',
326
+ fileSignals,
327
+ });
328
+ snapshotId = snapshot.snapshotId;
329
+ }
330
+ const availableSignals = fileSignals.filter((signal) => signal.risk.score !== null);
331
+ const aggregatedRisk = availableSignals.length > 0
332
+ ? this.calculateAggregatedLiveRisk(availableSignals, commits, selection.metricsByFile, heatMap)
333
+ : createUnavailableRiskScore('no file produced usable history evidence');
334
+ const overallConfidence = this.calculateOverallConfidence(fileSignals.map((signal) => signal.diagnostics.confidence), selection.scopeReduced || selection.requiresPrecompute);
335
+ return {
336
+ requestedFiles,
337
+ files: fileSignals,
338
+ aggregatedRisk,
339
+ diagnostics: createDiagnostics({
340
+ status: availableSignals.length > 0 ? 'ok' : 'not_found',
341
+ confidence: overallConfidence,
342
+ source: 'git-live',
343
+ reasons: [...selection.reasons, 'canonical history risk service completed live materialization'],
344
+ analyzedAt,
345
+ scopeMode: selection.scopeMode,
346
+ requestedFiles: requestedFiles.length,
347
+ analyzedFiles: selection.selectedFiles.length,
348
+ requiresPrecompute: selection.requiresPrecompute,
349
+ }),
350
+ snapshotId,
351
+ };
352
+ }
353
+ async resolveSymbolCandidates(query) {
354
+ const symbols = await this.storage.findSymbolByName(query);
355
+ return symbols
356
+ .map((symbol) => buildCandidate(symbol, query))
357
+ .sort((left, right) => {
358
+ if (left.exactNameMatch !== right.exactNameMatch) {
359
+ return left.exactNameMatch ? -1 : 1;
360
+ }
361
+ if (left.name.length !== right.name.length) {
362
+ return left.name.length - right.name.length;
363
+ }
364
+ return left.file.localeCompare(right.file);
365
+ });
366
+ }
367
+ async analyzeSymbol(query, options = {}) {
368
+ const trimmedQuery = query.trim();
369
+ if (trimmedQuery.length === 0) {
370
+ return createUnavailableSymbolResult(query, 'symbol query cannot be empty');
371
+ }
372
+ const directSymbol = await this.storage.findSymbolById(trimmedQuery);
373
+ const candidates = directSymbol
374
+ ? [buildCandidate(directSymbol, directSymbol.name)]
375
+ : await this.resolveSymbolCandidates(trimmedQuery);
376
+ if (candidates.length === 0) {
377
+ return createUnavailableSymbolResult(trimmedQuery, 'symbol not found in storage', 'not_found');
378
+ }
379
+ const exactCandidates = directSymbol
380
+ ? candidates
381
+ : candidates.filter((candidate) => candidate.exactNameMatch);
382
+ const isAmbiguous = !directSymbol
383
+ && ((exactCandidates.length > 1) || (exactCandidates.length === 0 && candidates.length > 1));
384
+ if (isAmbiguous) {
385
+ return createUnavailableSymbolResult(trimmedQuery, 'symbol query resolved to multiple candidates', 'ambiguous', candidates);
386
+ }
387
+ const selected = directSymbol
388
+ ? candidates[0]
389
+ : (exactCandidates[0] ?? candidates[0] ?? null);
390
+ if (!selected) {
391
+ return createUnavailableSymbolResult(trimmedQuery, 'failed to resolve target symbol', 'not_found');
392
+ }
393
+ const storageAdapter = this.getHistoryRiskStorageAdapter();
394
+ const analyzedAt = this.now().toISOString();
395
+ const gitAvailable = await this.gitAnalyzer.isGitRepository(this.projectRoot);
396
+ if (!gitAvailable) {
397
+ if (storageAdapter) {
398
+ const cached = await storageAdapter.loadLatestSymbolHistoryResult(selected.symbolId, trimmedQuery);
399
+ if (cached.diagnostics.status !== 'unavailable') {
400
+ return {
401
+ ...cached,
402
+ candidates: cached.candidates.length > 0 ? cached.candidates : candidates,
403
+ diagnostics: refreshDiagnostics(cached.diagnostics, this.now(), this.staleAfterHours, this.expiredAfterHours),
404
+ };
405
+ }
406
+ }
407
+ return createUnavailableSymbolResult(trimmedQuery, 'git repository unavailable and no materialized symbol history found');
408
+ }
409
+ const workflowAnalysis = await this.workflowGitAnalyzer.analyzeForPhase('link', [selected.file], createSyntheticWorkflowContext(`history-risk:symbol:${trimmedQuery}`));
410
+ const heat = this.buildHeatMap(workflowAnalysis).get(selected.file) ?? mapWorkflowHeat();
411
+ const commits = await this.gitAnalyzer.findRelatedCommits([selected.name], [selected.file], {
412
+ maxCommits: options.maxCommits ?? this.maxCommits,
413
+ projectRoot: this.projectRoot,
414
+ });
415
+ const timeline = normalizeTimeline(filterCommitsForSymbol(commits, selected), 'symbol');
416
+ if (!hasHistoryEvidence(heat, timeline)) {
417
+ return {
418
+ query: trimmedQuery,
419
+ candidates,
420
+ symbol: selected,
421
+ files: [selected.file],
422
+ timeline: [],
423
+ risk: createUnavailableRiskScore('no git history evidence for symbol'),
424
+ diagnostics: createDiagnostics({
425
+ status: 'not_found',
426
+ confidence: 'low',
427
+ source: 'git-live',
428
+ reasons: ['no git history evidence for symbol'],
429
+ analyzedAt,
430
+ scopeMode: 'full',
431
+ requestedFiles: 1,
432
+ analyzedFiles: 1,
433
+ }),
434
+ };
435
+ }
436
+ const metrics = await this.resolveSymbolMetrics(selected);
437
+ const risk = this.calculateRiskFromFeed(selected.file, commits, heat, {
438
+ file: selected.file,
439
+ moduleIds: [selected.moduleId],
440
+ dependencyIds: metrics.dependencyIds,
441
+ dependentIds: metrics.dependentIds,
442
+ gravity: metrics.gravity,
443
+ impact: metrics.impact,
444
+ });
445
+ const result = {
446
+ query: trimmedQuery,
447
+ candidates,
448
+ symbol: selected,
449
+ files: [selected.file],
450
+ timeline,
451
+ risk,
452
+ diagnostics: createDiagnostics({
453
+ status: 'ok',
454
+ confidence: selected.exactNameMatch ? 'high' : 'medium',
455
+ source: 'git-live',
456
+ reasons: ['symbol history resolved from live git signals'],
457
+ analyzedAt,
458
+ scopeMode: 'full',
459
+ requestedFiles: 1,
460
+ analyzedFiles: 1,
461
+ }),
462
+ };
463
+ if (storageAdapter && options.persist !== false) {
464
+ const snapshot = await storageAdapter.saveHistoryRiskSnapshot({
465
+ recordedAt: analyzedAt,
466
+ source: 'git-live',
467
+ symbolSignals: [result],
468
+ });
469
+ result.snapshotId = snapshot.snapshotId;
470
+ }
471
+ return result;
472
+ }
473
+ async loadFileAnalysisFromCache(requestedFiles, selection, storageAdapter) {
474
+ if (!storageAdapter) {
475
+ return {
476
+ requestedFiles,
477
+ files: selection.selectedFiles.map((file) => createUnavailableFileSignal(file, 'git repository unavailable and storage does not support history materialization', null, 'unavailable')),
478
+ aggregatedRisk: createUnavailableRiskScore('git repository unavailable and no cache adapter present'),
479
+ diagnostics: createDiagnostics({
480
+ status: 'unavailable',
481
+ confidence: 'unavailable',
482
+ source: 'unavailable',
483
+ reasons: [...selection.reasons, 'git repository unavailable and no cache adapter present'],
484
+ analyzedAt: null,
485
+ scopeMode: selection.scopeMode,
486
+ requestedFiles: requestedFiles.length,
487
+ analyzedFiles: 0,
488
+ requiresPrecompute: selection.requiresPrecompute,
489
+ }),
490
+ };
491
+ }
492
+ const cachedSignals = await Promise.all(selection.selectedFiles.map(async (file) => {
493
+ const signal = await storageAdapter.loadLatestFileHistorySignal(file);
494
+ if (!signal) {
495
+ return createUnavailableFileSignal(file, 'no materialized history snapshot found for file', null, 'unavailable');
496
+ }
497
+ return {
498
+ ...signal,
499
+ diagnostics: refreshDiagnostics(signal.diagnostics, this.now(), this.staleAfterHours, this.expiredAfterHours),
500
+ };
501
+ }));
502
+ const availableSignals = cachedSignals.filter((signal) => signal.risk.score !== null);
503
+ const bestConfidence = cachedSignals.reduce((current, signal) => pickHigherConfidence(current, signal.diagnostics.confidence), 'unavailable');
504
+ return {
505
+ requestedFiles,
506
+ files: cachedSignals,
507
+ aggregatedRisk: this.combinePersistedRisk(cachedSignals),
508
+ diagnostics: createDiagnostics({
509
+ status: availableSignals.length > 0 ? 'ok' : 'unavailable',
510
+ confidence: selection.scopeReduced || selection.requiresPrecompute
511
+ ? downgradeConfidence(bestConfidence)
512
+ : bestConfidence,
513
+ source: availableSignals.length > 0 ? 'sqlite-cache' : 'unavailable',
514
+ reasons: [...selection.reasons, 'served materialized history from SQLite cache'],
515
+ analyzedAt: availableSignals[0]?.diagnostics.analyzedAt ?? null,
516
+ scopeMode: selection.scopeMode,
517
+ requestedFiles: requestedFiles.length,
518
+ analyzedFiles: availableSignals.length,
519
+ requiresPrecompute: selection.requiresPrecompute,
520
+ }),
521
+ };
522
+ }
523
+ calculateAggregatedLiveRisk(fileSignals, commits, metricsByFile, heatMap) {
524
+ const targetFiles = fileSignals.map((signal) => signal.file);
525
+ const feedData = fileSignals.map((signal) => {
526
+ const metrics = metricsByFile.get(signal.file) ?? {
527
+ file: signal.file,
528
+ moduleIds: [],
529
+ dependencyIds: [],
530
+ dependentIds: [],
531
+ gravity: 0,
532
+ impact: 0,
533
+ };
534
+ const heat = heatMap.get(signal.file) ?? mapWorkflowHeat();
535
+ return this.createFeed(signal.file, heat, metrics);
536
+ });
537
+ return this.mapAnalyzerRisk(this.gitAnalyzer.calculateRiskScore(targetFiles, commits, feedData), heatMap.get(targetFiles[0] ?? ''));
538
+ }
539
+ calculateRiskFromFeed(file, commits, heat, metrics) {
540
+ const risk = this.gitAnalyzer.calculateRiskScore([file], commits, [this.createFeed(file, heat, metrics)]);
541
+ return this.mapAnalyzerRisk(risk, heat);
542
+ }
543
+ mapAnalyzerRisk(risk, heat) {
544
+ return {
545
+ level: risk.level,
546
+ score: risk.score,
547
+ gravity: risk.gravity,
548
+ heat: heat ?? {
549
+ freq30d: risk.heat.freq30d,
550
+ lastType: risk.heat.lastType,
551
+ lastDate: risk.heat.lastDate,
552
+ stability: risk.heat.stability,
553
+ },
554
+ impact: risk.impact,
555
+ riskFactors: [...risk.riskFactors],
556
+ };
557
+ }
558
+ createFeed(file, heat, metrics) {
559
+ return {
560
+ file,
561
+ gravity: metrics.gravity,
562
+ heat: {
563
+ freq30d: heat.freq30d,
564
+ lastType: heat.lastType,
565
+ lastDate: heat.lastDate,
566
+ stability: heat.stability,
567
+ },
568
+ meta: {
569
+ stable: heat.stability,
570
+ },
571
+ deps: metrics.dependencyIds,
572
+ dependents: metrics.dependentIds,
573
+ };
574
+ }
575
+ buildHeatMap(result) {
576
+ const heatMap = new Map();
577
+ for (const heat of result.fileHeat ?? []) {
578
+ heatMap.set(heat.file, mapWorkflowHeat(heat));
579
+ }
580
+ return heatMap;
581
+ }
582
+ async selectFilesForAnalysis(requestedFiles, maxFiles) {
583
+ const metricsEntries = await Promise.all(requestedFiles.map((file) => this.resolveFileMetrics(file)));
584
+ metricsEntries.sort((left, right) => {
585
+ const leftScore = left.gravity + left.impact;
586
+ const rightScore = right.gravity + right.impact;
587
+ if (leftScore !== rightScore) {
588
+ return rightScore - leftScore;
589
+ }
590
+ return left.file.localeCompare(right.file);
591
+ });
592
+ const selectedEntries = metricsEntries.slice(0, Math.max(1, maxFiles));
593
+ const metricsByFile = new Map(metricsEntries.map((entry) => [entry.file, entry]));
594
+ const scopeReduced = selectedEntries.length < requestedFiles.length;
595
+ const requiresPrecompute = requestedFiles.length > this.precomputeThreshold;
596
+ const reasons = [];
597
+ if (scopeReduced) {
598
+ reasons.push(`scope shrunk to top ${selectedEntries.length} files out of ${requestedFiles.length}`);
599
+ }
600
+ if (requiresPrecompute) {
601
+ reasons.push('request exceeds live git budget; precompute is recommended');
602
+ }
603
+ return {
604
+ selectedFiles: selectedEntries.map((entry) => entry.file),
605
+ metricsByFile,
606
+ scopeReduced,
607
+ scopeMode: scopeReduced ? 'top-files-only' : 'full',
608
+ requiresPrecompute,
609
+ reasons,
610
+ };
611
+ }
612
+ async resolveFileMetrics(file) {
613
+ const normalizedFile = normalizeProjectRelativePath(file, this.projectRoot);
614
+ const modules = (await this.storage.findModulesByPath(file))
615
+ .filter((module) => normalizeProjectRelativePath(module.path, this.projectRoot) === normalizedFile);
616
+ const dependencyIds = new Set();
617
+ const dependentIds = new Set();
618
+ for (const module of modules) {
619
+ for (const dependency of await this.storage.findDependencies(module.id)) {
620
+ dependencyIds.add(dependency.targetId);
621
+ }
622
+ for (const dependency of await this.storage.findDependents(module.id)) {
623
+ dependentIds.add(dependency.sourceId);
624
+ }
625
+ const impact = await this.storage.calculateImpact(module.id, this.impactDepth);
626
+ for (const impactedModule of impact.affectedModules) {
627
+ dependentIds.add(impactedModule.id);
628
+ }
629
+ }
630
+ return {
631
+ file,
632
+ moduleIds: modules.map((module) => module.id),
633
+ dependencyIds: [...dependencyIds],
634
+ dependentIds: [...dependentIds],
635
+ gravity: dependencyIds.size + dependentIds.size,
636
+ impact: dependentIds.size,
637
+ };
638
+ }
639
+ async resolveSymbolMetrics(candidate) {
640
+ const fileMetrics = await this.resolveFileMetrics(candidate.file);
641
+ const dependencyIds = new Set(fileMetrics.dependencyIds);
642
+ const dependentIds = new Set(fileMetrics.dependentIds);
643
+ for (const callee of await this.storage.findCallees(candidate.symbolId)) {
644
+ dependencyIds.add(callee.id);
645
+ }
646
+ for (const caller of await this.storage.findCallers(candidate.symbolId)) {
647
+ dependentIds.add(caller.id);
648
+ }
649
+ return {
650
+ dependencyIds: [...dependencyIds],
651
+ dependentIds: [...dependentIds],
652
+ gravity: dependencyIds.size + dependentIds.size,
653
+ impact: dependentIds.size,
654
+ };
655
+ }
656
+ calculateOverallConfidence(confidences, requiresDowngrade) {
657
+ const best = confidences.reduce((current, confidence) => pickHigherConfidence(current, confidence), 'unavailable');
658
+ return requiresDowngrade ? downgradeConfidence(best) : best;
659
+ }
660
+ combinePersistedRisk(signals) {
661
+ const availableSignals = signals.filter((signal) => signal.risk.score !== null);
662
+ if (availableSignals.length === 0) {
663
+ return createUnavailableRiskScore('no persisted history risk available');
664
+ }
665
+ const topSignal = availableSignals.reduce((current, signal) => ((signal.risk.score ?? -1) > (current.risk.score ?? -1) ? signal : current));
666
+ const riskFactors = uniqueValues(availableSignals.flatMap((signal) => signal.risk.riskFactors));
667
+ return {
668
+ level: topSignal.risk.level,
669
+ score: Math.max(...availableSignals.map((signal) => signal.risk.score ?? 0)),
670
+ gravity: Math.max(...availableSignals.map((signal) => signal.risk.gravity ?? 0)),
671
+ heat: topSignal.risk.heat,
672
+ impact: Math.max(...availableSignals.map((signal) => signal.risk.impact ?? 0)),
673
+ riskFactors,
674
+ };
675
+ }
676
+ getHistoryRiskStorageAdapter() {
677
+ return isHistoryRiskStorageAdapter(this.storage) ? this.storage : null;
678
+ }
679
+ }
680
+ //# sourceMappingURL=history-risk-service.js.map