api-tests-coverage 1.0.24 → 1.0.26

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 (103) hide show
  1. package/dist/dashboard/dist/assets/_basePickBy-BHjg34fk.js +1 -0
  2. package/dist/dashboard/dist/assets/_basePickBy-DIphIltc.js +1 -0
  3. package/dist/dashboard/dist/assets/_baseUniq-D57u2_9m.js +1 -0
  4. package/dist/dashboard/dist/assets/_baseUniq-DxJYHd7T.js +1 -0
  5. package/dist/dashboard/dist/assets/arc-DQosMxPM.js +1 -0
  6. package/dist/dashboard/dist/assets/arc-DcXkmNi0.js +1 -0
  7. package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-CNbtIqHR.js +36 -0
  8. package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-ChMY32ql.js +36 -0
  9. package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-DVhWtRxG.js +122 -0
  10. package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-NgdJaQvK.js +122 -0
  11. package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-B6esYq70.js +10 -0
  12. package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-ChTe70Dn.js +10 -0
  13. package/dist/dashboard/dist/assets/channel-B3Mj1BTw.js +1 -0
  14. package/dist/dashboard/dist/assets/channel-Df6s6dhy.js +1 -0
  15. package/dist/dashboard/dist/assets/chunk-4BX2VUAB-B7Pkx3C3.js +1 -0
  16. package/dist/dashboard/dist/assets/chunk-4BX2VUAB-BS3-4dfL.js +1 -0
  17. package/dist/dashboard/dist/assets/chunk-55IACEB6-8ClDkPsD.js +1 -0
  18. package/dist/dashboard/dist/assets/chunk-55IACEB6-BCczdImM.js +1 -0
  19. package/dist/dashboard/dist/assets/chunk-B4BG7PRW--cjprmFF.js +165 -0
  20. package/dist/dashboard/dist/assets/chunk-B4BG7PRW-D6Mi4ccz.js +165 -0
  21. package/dist/dashboard/dist/assets/chunk-DI55MBZ5-B0tOisd5.js +220 -0
  22. package/dist/dashboard/dist/assets/chunk-DI55MBZ5-D9bxNSnS.js +220 -0
  23. package/dist/dashboard/dist/assets/chunk-FMBD7UC4-CSek7h3u.js +15 -0
  24. package/dist/dashboard/dist/assets/chunk-FMBD7UC4-RSShKwSG.js +15 -0
  25. package/dist/dashboard/dist/assets/chunk-QN33PNHL-BRCzcTtl.js +1 -0
  26. package/dist/dashboard/dist/assets/chunk-QN33PNHL-DFyjAoyD.js +1 -0
  27. package/dist/dashboard/dist/assets/chunk-QZHKN3VN-ARq4habW.js +1 -0
  28. package/dist/dashboard/dist/assets/chunk-QZHKN3VN-TFdw1-iS.js +1 -0
  29. package/dist/dashboard/dist/assets/chunk-TZMSLE5B-CWotsEVz.js +1 -0
  30. package/dist/dashboard/dist/assets/chunk-TZMSLE5B-DrmzpdLp.js +1 -0
  31. package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-Dr8j2BkV.js +1 -0
  32. package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-cvlgQ4cC.js +1 -0
  33. package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-Dr8j2BkV.js +1 -0
  34. package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-cvlgQ4cC.js +1 -0
  35. package/dist/dashboard/dist/assets/clone-D-A0zWrx.js +1 -0
  36. package/dist/dashboard/dist/assets/clone-DRiM0_7k.js +1 -0
  37. package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-L06bC_vI.js +1 -0
  38. package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-_dXvVagP.js +1 -0
  39. package/dist/dashboard/dist/assets/dagre-6UL2VRFP-BfhkcdcZ.js +4 -0
  40. package/dist/dashboard/dist/assets/dagre-6UL2VRFP-LQJxsDjp.js +4 -0
  41. package/dist/dashboard/dist/assets/diagram-PSM6KHXK-Bguvtjhb.js +24 -0
  42. package/dist/dashboard/dist/assets/diagram-PSM6KHXK-C8bgfsC2.js +24 -0
  43. package/dist/dashboard/dist/assets/diagram-QEK2KX5R-CDM-bAUc.js +43 -0
  44. package/dist/dashboard/dist/assets/diagram-QEK2KX5R-SPnyk4NX.js +43 -0
  45. package/dist/dashboard/dist/assets/diagram-S2PKOQOG-Cv8CAseP.js +24 -0
  46. package/dist/dashboard/dist/assets/diagram-S2PKOQOG-DNQuKOCA.js +24 -0
  47. package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-CgAEujxC.js +60 -0
  48. package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-DHMIYnca.js +60 -0
  49. package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-B-9A_TD6.js +162 -0
  50. package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-C8juupCT.js +162 -0
  51. package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-BzXOAiOm.js +267 -0
  52. package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-DDmcIEO0.js +267 -0
  53. package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-6Rn0oWgA.js +65 -0
  54. package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-D4YFQ0Qf.js +65 -0
  55. package/dist/dashboard/dist/assets/graph-DI2MOSai.js +1 -0
  56. package/dist/dashboard/dist/assets/graph-VO6A5Zyb.js +1 -0
  57. package/dist/dashboard/dist/assets/index-BD_Ue7zI.js +777 -0
  58. package/dist/dashboard/dist/assets/index-BQfUzgMV.js +778 -0
  59. package/dist/dashboard/dist/assets/index-Bpho1Ov5.css +1 -0
  60. package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-BEOgUULT.js +2 -0
  61. package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-BULYGXV8.js +2 -0
  62. package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-CBFUW_L2.js +139 -0
  63. package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-CpGd67rs.js +139 -0
  64. package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-BXpodEnf.js +89 -0
  65. package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-CXjvcKGc.js +89 -0
  66. package/dist/dashboard/dist/assets/layout-Cpj8l95P.js +1 -0
  67. package/dist/dashboard/dist/assets/layout-D5qgY_UX.js +1 -0
  68. package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-CKZrc1IF.js +68 -0
  69. package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-_3DZbNEl.js +68 -0
  70. package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-B--OM1Gs.js +30 -0
  71. package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-uIOJq-u0.js +30 -0
  72. package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-CDx0v76p.js +7 -0
  73. package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-D5g_wTRC.js +7 -0
  74. package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-BLpGY-Om.js +64 -0
  75. package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-CbvZ1a-7.js +64 -0
  76. package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-D-fji9s3.js +10 -0
  77. package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-E0klRQfk.js +10 -0
  78. package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-Byy1IdkL.js +145 -0
  79. package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-CWB1Ub2x.js +145 -0
  80. package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-J-c1KNJ7.js +1 -0
  81. package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC-x3lHxmNY.js +1 -0
  82. package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-D0gUM6SR.js +1 -0
  83. package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-DRL2jF9p.js +1 -0
  84. package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-D490JqJU.js +61 -0
  85. package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-LOxOovzx.js +61 -0
  86. package/dist/dashboard/dist/assets/treemap-GDKQZRPO-C5Nk6dQh.js +162 -0
  87. package/dist/dashboard/dist/assets/treemap-GDKQZRPO-C6DntuKu.js +162 -0
  88. package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-BKisDUaz.js +7 -0
  89. package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-CYsIKi3H.js +7 -0
  90. package/dist/dashboard/dist/index.html +2 -2
  91. package/dist/src/analyzeHelpers.d.ts +60 -0
  92. package/dist/src/analyzeHelpers.d.ts.map +1 -0
  93. package/dist/src/analyzeHelpers.js +670 -0
  94. package/dist/src/config/defaultConfig.js +1 -1
  95. package/dist/src/index.js +553 -615
  96. package/dist/src/reporting.d.ts +10 -0
  97. package/dist/src/reporting.d.ts.map +1 -1
  98. package/dist/src/reporting.js +3 -0
  99. package/dist/src/summary/evaluateMetrics.js +3 -2
  100. package/dist/src/unitAnalysis.d.ts +132 -1
  101. package/dist/src/unitAnalysis.d.ts.map +1 -1
  102. package/dist/src/unitAnalysis.js +1139 -185
  103. package/package.json +1 -1
@@ -0,0 +1,670 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.requiredNaReason = requiredNaReason;
37
+ exports.buildNaCoverageResult = buildNaCoverageResult;
38
+ exports.runMetricSafe = runMetricSafe;
39
+ exports.enrichResultsWithStatuses = enrichResultsWithStatuses;
40
+ exports.renderAnalyzeSummaryTable = renderAnalyzeSummaryTable;
41
+ exports.detectOpenApiSpec = detectOpenApiSpec;
42
+ exports.writeMetricReport = writeMetricReport;
43
+ exports.writeArchitectureArtifacts = writeArchitectureArtifacts;
44
+ exports.detailPathForMetric = detailPathForMetric;
45
+ exports.metricTitle = metricTitle;
46
+ exports.sortResultsInKnownOrder = sortResultsInKnownOrder;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const http = __importStar(require("http"));
50
+ const https = __importStar(require("https"));
51
+ const evaluateMetrics_1 = require("./summary/evaluateMetrics");
52
+ const summaryTypes_1 = require("./summary/summaryTypes");
53
+ const FILE_SPEC_CANDIDATES = [
54
+ 'openapi.yaml',
55
+ 'openapi.json',
56
+ path.join('docs', 'openapi.yaml'),
57
+ path.join('docs', 'openapi.json'),
58
+ path.join('src', 'main', 'resources', 'openapi.yaml'),
59
+ path.join('src', 'main', 'resources', 'openapi.json'),
60
+ path.join('src', 'main', 'resources', 'static', 'openapi.yaml'),
61
+ path.join('src', 'main', 'resources', 'static', 'openapi.json'),
62
+ ];
63
+ const URL_SPEC_CANDIDATES = [
64
+ 'http://localhost:8080/v3/api-docs',
65
+ 'http://localhost:8080/swagger.json',
66
+ ];
67
+ /**
68
+ * Returns the appropriate security N/A reason based on whether a spec was found.
69
+ * When a spec is present but yields no controls, the message should explain that
70
+ * the spec lacks security schemes/requirements rather than implying the spec is missing.
71
+ *
72
+ * @param context Optional reason-building context.
73
+ * @param context.specDetected Whether analyze already detected an OpenAPI spec.
74
+ * @returns A user-facing N/A reason string for the security metric.
75
+ */
76
+ function securityNaReason({ specDetected = false }) {
77
+ if (specDetected) {
78
+ return 'No security schemes or requirements found in the OpenAPI spec.';
79
+ }
80
+ return 'No OpenAPI spec found. Add openapi.yaml to enable security control analysis.';
81
+ }
82
+ const REQUIRED_NA_REASONS = {
83
+ endpoint: ({ filesScanned = 0 }) => `No routes detected in ${filesScanned} source files. Ensure source files are in the scan root.`,
84
+ parameter: ({ routesScanned = 0 }) => `No parameters detected in ${routesScanned} routes. Add openapi.yaml for full parameter analysis.`,
85
+ business: ({ filesScanned = 0 }) => `No business rules detected in ${filesScanned} service files.`,
86
+ integration: ({ filesScanned = 0 }) => `No multi-step integration flows detected in ${filesScanned} test files.`,
87
+ error: () => 'No error scenarios detected. Add OpenAPI spec with 4xx/5xx responses.',
88
+ security: securityNaReason,
89
+ performance: ({ filesScanned = 0 }) => `No SLA annotations or load test results detected in ${filesScanned} scanned files.`,
90
+ resilience: ({ filesScanned = 0 }) => `No resilience patterns detected (circuit breakers, retries, timeouts) in ${filesScanned} files.`,
91
+ unit: ({ filesScanned = 0 }) => `No unit test cases detected in ${filesScanned} scanned test files.`,
92
+ event: ({ filesScanned = 0 }) => `No Kafka/RabbitMQ/SQS/SNS patterns detected in ${filesScanned} scanned source files.`,
93
+ compatibility: () => 'No previous API spec found. Provide --spec-old to enable compatibility checking.',
94
+ };
95
+ const METRIC_TITLES = {
96
+ endpoint: 'Endpoint Coverage',
97
+ parameter: 'Parameter Coverage',
98
+ business: 'Business Coverage',
99
+ integration: 'Integration Coverage',
100
+ error: 'Error Coverage',
101
+ security: 'Security Coverage',
102
+ performance: 'Performance Coverage',
103
+ resilience: 'Resilience Coverage',
104
+ compatibility: 'Compatibility Coverage',
105
+ event: 'Streaming Coverage',
106
+ unit: 'Unit Analysis',
107
+ };
108
+ const METRIC_REPORT_FILENAMES = {
109
+ endpoint: 'endpoint-coverage.json',
110
+ parameter: 'parameter-coverage.json',
111
+ business: 'business-coverage.json',
112
+ integration: 'integration-coverage.json',
113
+ error: 'error-coverage.json',
114
+ security: 'security-coverage.json',
115
+ performance: 'performance-coverage.json',
116
+ resilience: 'resilience-coverage.json',
117
+ compatibility: 'compatibility-coverage.json',
118
+ event: 'event-coverage.json',
119
+ unit: 'unit-coverage.json',
120
+ };
121
+ function requiredNaReason(type, context = {}) {
122
+ var _a, _b;
123
+ return (_b = (_a = REQUIRED_NA_REASONS[type]) === null || _a === void 0 ? void 0 : _a.call(REQUIRED_NA_REASONS, context)) !== null && _b !== void 0 ? _b : `No ${type} items detected during analysis.`;
124
+ }
125
+ function buildNaCoverageResult(type, reason, scanMeta, details = { items: [] }) {
126
+ return {
127
+ type,
128
+ totalItems: 0,
129
+ coveredItems: 0,
130
+ coveragePercent: 0,
131
+ status: 'N/A',
132
+ reason,
133
+ scanMeta,
134
+ details,
135
+ };
136
+ }
137
+ async function runMetricSafe(name, fallbackScanMeta, fn) {
138
+ var _a, _b;
139
+ try {
140
+ const result = await fn();
141
+ return {
142
+ ...result,
143
+ status: result.totalItems === 0 ? 'N/A' : result.status,
144
+ reason: result.totalItems === 0 ? ((_a = result.reason) !== null && _a !== void 0 ? _a : requiredNaReason(name)) : result.reason,
145
+ scanMeta: (_b = result.scanMeta) !== null && _b !== void 0 ? _b : fallbackScanMeta,
146
+ };
147
+ }
148
+ catch (err) {
149
+ const message = err instanceof Error ? err.message : String(err);
150
+ return buildNaCoverageResult(name, `Metric execution error: ${message}`, {
151
+ ...fallbackScanMeta,
152
+ detectionMethod: 'failed',
153
+ });
154
+ }
155
+ }
156
+ function enrichResultsWithStatuses(results, thresholds, qualityGate) {
157
+ const evaluated = (0, evaluateMetrics_1.evaluateMetrics)(results, thresholds, qualityGate);
158
+ const resultByType = new Map(results.map((result) => [result.type, result]));
159
+ return evaluated
160
+ .filter((metric) => resultByType.has(metric.category))
161
+ .map((metric) => {
162
+ var _a;
163
+ const result = resultByType.get(metric.category);
164
+ return {
165
+ ...result,
166
+ status: metric.status,
167
+ reason: (_a = result.reason) !== null && _a !== void 0 ? _a : metric.reason,
168
+ };
169
+ });
170
+ }
171
+ function renderAnalyzeSummaryTable(input) {
172
+ const evaluated = (0, evaluateMetrics_1.evaluateMetrics)(input.results, input.thresholds, input.qualityGate);
173
+ const lines = [
174
+ '=== API Test Coverage Analysis Complete ===',
175
+ `Project: ${input.rootDir}`,
176
+ `Scanned: ${input.serviceFiles} service files, ${input.testFiles} test files`,
177
+ `Duration: ${(input.durationMs / 1000).toFixed(1)}s`,
178
+ '',
179
+ ' Metric Items Covered % Status',
180
+ ' ─────────────────────────────────────────────────────────────────────────────',
181
+ ];
182
+ for (const metric of evaluated) {
183
+ const percentCell = metric.totalItems === 0 ? '-' : `${Math.round(metric.coveragePercent)}%`;
184
+ const statusCell = metric.status === 'PASS'
185
+ ? '✓ PASS'
186
+ : metric.status === 'FAIL'
187
+ ? '✗ FAIL'
188
+ : metric.status === 'SKIPPED'
189
+ ? '· SKIPPED'
190
+ : '· N/A';
191
+ const inlineReason = metric.status === 'N/A' ? ` ${metric.reason}` : '';
192
+ lines.push(` ${metric.category.padEnd(13)} ${String(metric.totalItems).padStart(7)} ${String(metric.coveredItems).padStart(9)} ${percentCell.padStart(6)} ${statusCell}${inlineReason}`);
193
+ }
194
+ return lines.join('\n');
195
+ }
196
+ async function detectOpenApiSpec(rootDir, reportsDir, explicitSpecPath) {
197
+ if (explicitSpecPath) {
198
+ const abs = path.resolve(explicitSpecPath);
199
+ if (fs.existsSync(abs))
200
+ return abs;
201
+ }
202
+ for (const candidate of FILE_SPEC_CANDIDATES) {
203
+ const abs = path.resolve(rootDir, candidate);
204
+ if (fs.existsSync(abs))
205
+ return abs;
206
+ }
207
+ for (const url of URL_SPEC_CANDIDATES) {
208
+ const content = await fetchUrlText(url);
209
+ if (!content)
210
+ continue;
211
+ fs.mkdirSync(reportsDir, { recursive: true });
212
+ const extension = url.endsWith('.json') || url.includes('api-docs') ? 'json' : 'yaml';
213
+ const targetPath = path.join(reportsDir, `auto-detected-openapi.${extension}`);
214
+ fs.writeFileSync(targetPath, content, 'utf-8');
215
+ return targetPath;
216
+ }
217
+ return undefined;
218
+ }
219
+ function writeMetricReport(result, reportsDir) {
220
+ const fileName = METRIC_REPORT_FILENAMES[result.type];
221
+ if (!fileName)
222
+ return;
223
+ const payload = buildMetricReportPayload(result);
224
+ const outPath = path.join(reportsDir, fileName);
225
+ fs.mkdirSync(reportsDir, { recursive: true });
226
+ fs.writeFileSync(outPath, JSON.stringify(payload, null, 2), 'utf-8');
227
+ if (result.type === 'unit') {
228
+ fs.writeFileSync(path.join(reportsDir, 'unit-analysis.json'), JSON.stringify(payload, null, 2), 'utf-8');
229
+ }
230
+ }
231
+ function writeArchitectureArtifacts(input) {
232
+ const generatedAt = new Date().toISOString();
233
+ const current = buildCurrentArchitecture({
234
+ generatedAt,
235
+ rootDir: input.rootDir,
236
+ languages: input.languages,
237
+ frameworks: input.frameworks,
238
+ serviceFiles: input.serviceFiles,
239
+ testFiles: input.testFiles,
240
+ hasSpec: Boolean(input.specPath),
241
+ results: input.results,
242
+ });
243
+ const suggested = buildSuggestedArchitecture({
244
+ generatedAt,
245
+ rootDir: input.rootDir,
246
+ results: input.results,
247
+ });
248
+ const markdown = buildArchitectureMarkdown(current, suggested, input.results);
249
+ const enriched = buildEnrichedArchitecturePayload(current, suggested, input.results);
250
+ fs.mkdirSync(input.reportsDir, { recursive: true });
251
+ fs.writeFileSync(path.join(input.reportsDir, 'architecture-current.json'), JSON.stringify({
252
+ ...current,
253
+ ...enriched,
254
+ }, null, 2), 'utf-8');
255
+ fs.writeFileSync(path.join(input.reportsDir, 'architecture-suggested.json'), JSON.stringify(suggested, null, 2), 'utf-8');
256
+ fs.writeFileSync(path.join(input.reportsDir, 'architecture.md'), markdown, 'utf-8');
257
+ }
258
+ function detailPathForMetric(type) {
259
+ switch (type) {
260
+ case 'endpoint':
261
+ return '/endpoints';
262
+ case 'parameter':
263
+ return '/parameters';
264
+ case 'business':
265
+ return '/business-rules';
266
+ case 'integration':
267
+ return '/integration-flows';
268
+ case 'error':
269
+ return '/error-handling';
270
+ case 'security':
271
+ return '/security';
272
+ case 'performance':
273
+ case 'resilience':
274
+ return '/performance';
275
+ case 'compatibility':
276
+ return '/compatibility';
277
+ case 'event':
278
+ return '/streaming';
279
+ case 'unit':
280
+ return '/unit';
281
+ default:
282
+ return '/';
283
+ }
284
+ }
285
+ function metricTitle(type) {
286
+ var _a;
287
+ return (_a = METRIC_TITLES[type]) !== null && _a !== void 0 ? _a : type;
288
+ }
289
+ function sortResultsInKnownOrder(results) {
290
+ const resultByType = new Map(results.map((result) => [result.type, result]));
291
+ const ordered = summaryTypes_1.KNOWN_METRIC_TYPES
292
+ .map((type) => resultByType.get(type))
293
+ .filter((result) => result !== undefined);
294
+ const extras = results.filter((result) => !summaryTypes_1.KNOWN_METRIC_TYPES.includes(result.type));
295
+ return [...ordered, ...extras];
296
+ }
297
+ function buildCurrentArchitecture(input) {
298
+ const nodes = [
299
+ {
300
+ id: 'project',
301
+ label: path.basename(input.rootDir) || 'project-root',
302
+ kind: 'artifact',
303
+ description: `Languages: ${input.languages.join(', ') || 'unknown'}; frameworks: ${input.frameworks.join(', ') || 'unknown'}`,
304
+ },
305
+ {
306
+ id: 'source',
307
+ label: `${input.serviceFiles} source files`,
308
+ kind: 'artifact',
309
+ description: 'Discovered service and implementation files',
310
+ },
311
+ {
312
+ id: 'tests',
313
+ label: `${input.testFiles} test files`,
314
+ kind: 'artifact',
315
+ description: 'Discovered unit, integration, and API tests',
316
+ },
317
+ {
318
+ id: 'spec',
319
+ label: input.hasSpec ? 'OpenAPI spec detected' : 'OpenAPI spec missing',
320
+ kind: 'artifact',
321
+ },
322
+ {
323
+ id: 'analyze',
324
+ label: 'analyze command',
325
+ kind: 'analysis',
326
+ description: 'Runs all metrics and writes reports/',
327
+ },
328
+ {
329
+ id: 'reports',
330
+ label: 'reports/',
331
+ kind: 'report',
332
+ description: 'Coverage, intelligence, summary, and architecture outputs',
333
+ },
334
+ ...buildMetricArchitectureNodes(input.results, 'current'),
335
+ ];
336
+ const edges = [
337
+ { from: 'project', to: 'source', label: 'discover' },
338
+ { from: 'project', to: 'tests', label: 'discover' },
339
+ { from: 'project', to: 'spec', label: 'detect' },
340
+ { from: 'source', to: 'analyze', label: 'scan' },
341
+ { from: 'tests', to: 'analyze', label: 'correlate' },
342
+ { from: 'spec', to: 'analyze', label: 'enrich' },
343
+ { from: 'analyze', to: 'reports', label: 'write' },
344
+ ...buildMetricArchitectureEdges(input.results, 'current'),
345
+ ];
346
+ return {
347
+ generatedAt: input.generatedAt,
348
+ projectRoot: input.rootDir,
349
+ nodes,
350
+ edges,
351
+ };
352
+ }
353
+ function buildSuggestedArchitecture(input) {
354
+ const prioritizedGaps = prioritizeArchitectureGaps(input.results);
355
+ const actionSteps = prioritizedGaps.map((gap, index) => `${index + 1}. ${gap.step}`);
356
+ const recommendations = prioritizedGaps.slice(0, 6).map((gap, index) => ({
357
+ id: `recommendation-${index + 1}`,
358
+ label: gap.label,
359
+ kind: 'recommendation',
360
+ description: gap.description,
361
+ }));
362
+ const nodes = [
363
+ {
364
+ id: 'current',
365
+ label: 'Current scan inputs',
366
+ kind: 'artifact',
367
+ description: 'Discovered source, tests, and optional specs',
368
+ },
369
+ {
370
+ id: 'suggested',
371
+ label: 'Suggested improvements',
372
+ kind: 'analysis',
373
+ description: 'Artifacts that would activate currently N/A metrics',
374
+ },
375
+ ...recommendations,
376
+ ];
377
+ const edges = recommendations.map((node) => ({
378
+ from: 'current',
379
+ to: node.id,
380
+ label: 'activate',
381
+ }));
382
+ if (recommendations.length > 0) {
383
+ edges.push({ from: 'suggested', to: recommendations[0].id, label: 'prioritize' });
384
+ }
385
+ return {
386
+ generatedAt: input.generatedAt,
387
+ projectRoot: input.rootDir,
388
+ nodes,
389
+ edges,
390
+ actionSteps,
391
+ };
392
+ }
393
+ function buildMetricReportPayload(result) {
394
+ var _a, _b, _c, _d, _e, _f;
395
+ const payload = {
396
+ generatedAt: new Date().toISOString(),
397
+ ...result,
398
+ };
399
+ if (result.type !== 'unit' || !result.details || typeof result.details !== 'object') {
400
+ return payload;
401
+ }
402
+ const details = result.details;
403
+ const ast = isRecord(details.ast) ? details.ast : undefined;
404
+ const dynamic = isRecord(details.dynamic) ? details.dynamic : undefined;
405
+ const coverage = isRecord(details.coverage) ? details.coverage : undefined;
406
+ const mutation = isRecord(details.mutation) ? details.mutation : undefined;
407
+ const smellItems = isRecord(details.testSmells) && Array.isArray(details.testSmells.items)
408
+ ? details.testSmells.items
409
+ : Array.isArray(details.smells)
410
+ ? details.smells
411
+ : [];
412
+ const slowTests = dynamic && Array.isArray(dynamic.slowTests)
413
+ ? dynamic.slowTests
414
+ : Array.isArray(details.slowTests)
415
+ ? details.slowTests
416
+ : [];
417
+ const lowCoverageFiles = Array.isArray(details.lowCoverageFiles)
418
+ ? details.lowCoverageFiles
419
+ : coverage && Array.isArray(coverage.lowCoverageFiles)
420
+ ? coverage.lowCoverageFiles
421
+ : [];
422
+ return {
423
+ ...payload,
424
+ astScore: asNumber(ast === null || ast === void 0 ? void 0 : ast.qualityScore),
425
+ weightedScore: asNumber(details.weightedScore),
426
+ enrichedByRuntime: Boolean(details.enrichedByRuntime),
427
+ enrichedByMutation: Boolean(details.enrichedByMutation),
428
+ lineCoverage: (_a = asNumber(details.lineCoverage)) !== null && _a !== void 0 ? _a : asNumber(coverage === null || coverage === void 0 ? void 0 : coverage.lineCoverage),
429
+ branchCoverage: (_b = asNumber(details.branchCoverage)) !== null && _b !== void 0 ? _b : asNumber(coverage === null || coverage === void 0 ? void 0 : coverage.branchCoverage),
430
+ methodCoverage: (_c = asNumber(details.methodCoverage)) !== null && _c !== void 0 ? _c : asNumber(coverage === null || coverage === void 0 ? void 0 : coverage.methodCoverage),
431
+ mutationScore: (_d = asNumber(details.mutationScore)) !== null && _d !== void 0 ? _d : asNumber(mutation === null || mutation === void 0 ? void 0 : mutation.mutationScore),
432
+ runtimeReportPath: (_e = asString(details.runtimeReportPath)) !== null && _e !== void 0 ? _e : asString(coverage === null || coverage === void 0 ? void 0 : coverage.reportPath),
433
+ smells: smellItems,
434
+ disabledTests: Array.isArray(ast === null || ast === void 0 ? void 0 : ast.disabledTests) ? ast.disabledTests : [],
435
+ slowTests,
436
+ lowCoverageFiles,
437
+ filesScanned: (_f = asNumber(details.filesScanned)) !== null && _f !== void 0 ? _f : asNumber(ast === null || ast === void 0 ? void 0 : ast.filesScanned),
438
+ };
439
+ }
440
+ function buildEnrichedArchitecturePayload(current, suggested, results) {
441
+ const prioritized = prioritizeArchitectureGaps(results);
442
+ const gaps = prioritized.map((gap, index) => {
443
+ const priority = architecturePriorityLabel(gap.priority);
444
+ const metricSlug = slugify(gap.label);
445
+ return {
446
+ id: `${metricSlug}-gap-${index + 1}`,
447
+ type: 'coverage-gap',
448
+ node: gap.label,
449
+ priority,
450
+ description: gap.description,
451
+ riskScore: Math.max(1, Math.min(100, gap.priority)),
452
+ };
453
+ });
454
+ const actionSteps = prioritized.map((gap, index) => {
455
+ var _a, _b;
456
+ const priority = architecturePriorityLabel(gap.priority);
457
+ const gapId = (_b = (_a = gaps[index]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : `gap-${index + 1}`;
458
+ return {
459
+ id: `step-${index + 1}`,
460
+ priority,
461
+ title: `${gap.label} follow-up`,
462
+ description: gap.step,
463
+ why: gap.description,
464
+ riskScore: Math.max(1, Math.min(100, gap.priority)),
465
+ codeSnippet: buildArchitectureCodeSnippet(gap.label, priority),
466
+ copilotPrompt: buildArchitectureCopilotPrompt(gap.label, gap.step, gap.description),
467
+ relatedGapIds: [gapId],
468
+ };
469
+ });
470
+ return {
471
+ mermaidCurrent: ['graph TD', ...renderMermaidGraph(current)].join('\n'),
472
+ mermaidSuggested: ['graph TD', ...renderMermaidGraph(suggested)].join('\n'),
473
+ gaps,
474
+ actionSteps,
475
+ };
476
+ }
477
+ function buildArchitectureMarkdown(current, suggested, results) {
478
+ const actionSteps = prioritizeArchitectureGaps(results)
479
+ .map((gap, index) => `${index + 1}. ${gap.step}`);
480
+ return [
481
+ '# Architecture Analysis',
482
+ '',
483
+ '## Current State',
484
+ '',
485
+ '```mermaid',
486
+ 'graph TD',
487
+ ...renderMermaidGraph(current),
488
+ '```',
489
+ '',
490
+ '## Suggested State',
491
+ '',
492
+ '```mermaid',
493
+ 'graph TD',
494
+ ...renderMermaidGraph(suggested),
495
+ '```',
496
+ '',
497
+ '## Action Steps',
498
+ '',
499
+ ...(actionSteps.length > 0 ? actionSteps : ['1. No architecture activation steps required.']),
500
+ '',
501
+ ].join('\n');
502
+ }
503
+ function buildMetricArchitectureNodes(results, mode) {
504
+ return sortResultsInKnownOrder(results)
505
+ .filter((result) => result.totalItems > 0 || result.reason)
506
+ .map((result) => {
507
+ const gapCount = Math.max(0, result.totalItems - result.coveredItems);
508
+ const labelPrefix = mode === 'current' ? metricTitle(result.type) : `${metricTitle(result.type)} target`;
509
+ return {
510
+ id: `${mode}-${result.type}`,
511
+ label: buildMetricArchitectureLabel(result, labelPrefix),
512
+ kind: result.totalItems === 0 || result.coveragePercent < 80 ? 'recommendation' : 'analysis',
513
+ description: result.totalItems === 0
514
+ ? result.reason
515
+ : buildMetricArchitectureDescription(result, gapCount),
516
+ };
517
+ });
518
+ }
519
+ function buildMetricArchitectureEdges(results, mode) {
520
+ return sortResultsInKnownOrder(results)
521
+ .filter((result) => result.totalItems > 0 || result.reason)
522
+ .map((result) => ({
523
+ from: mode === 'current' ? 'analyze' : 'suggested',
524
+ to: `${mode}-${result.type}`,
525
+ label: result.totalItems === 0 ? 'missing input' : 'coverage signal',
526
+ }));
527
+ }
528
+ function prioritizeArchitectureGaps(results) {
529
+ return sortResultsInKnownOrder(results)
530
+ .map((result) => {
531
+ var _a;
532
+ const uncovered = Math.max(0, result.totalItems - result.coveredItems);
533
+ const priority = result.totalItems === 0
534
+ ? 100
535
+ : uncovered > 0
536
+ ? Math.round((uncovered / Math.max(1, result.totalItems)) * 100)
537
+ : 0;
538
+ return {
539
+ label: metricTitle(result.type),
540
+ description: result.totalItems === 0
541
+ ? (_a = result.reason) !== null && _a !== void 0 ? _a : 'Add supporting inputs and rerun analyze.'
542
+ : buildGapPriorityDescription(result, uncovered),
543
+ step: buildArchitectureActionStep(result, uncovered),
544
+ priority,
545
+ };
546
+ })
547
+ .filter((gap) => gap.priority > 0)
548
+ .sort((a, b) => b.priority - a.priority || a.label.localeCompare(b.label));
549
+ }
550
+ function buildMetricArchitectureLabel(result, labelPrefix) {
551
+ if (result.type === 'unit' && result.totalItems > 0) {
552
+ return `${labelPrefix}: ${Math.round(result.coveragePercent)}% (${result.coveredItems}/${result.totalItems})`;
553
+ }
554
+ return `${labelPrefix}: ${result.coveredItems}/${result.totalItems}`;
555
+ }
556
+ function buildMetricArchitectureDescription(result, gapCount) {
557
+ if (gapCount > 0) {
558
+ return result.type === 'unit'
559
+ ? `${gapCount} weighted-quality ${gapCount === 1 ? 'gap remains' : 'gaps remain'} (${Math.round(result.coveragePercent)}% quality score). See ${detailPathForMetric(result.type)}`
560
+ : `${gapCount} gaps remain (${Math.round(result.coveragePercent)}% coverage). See ${detailPathForMetric(result.type)}`;
561
+ }
562
+ return result.type === 'unit'
563
+ ? `Healthy quality score (${Math.round(result.coveragePercent)}%).`
564
+ : `Healthy coverage (${Math.round(result.coveragePercent)}%).`;
565
+ }
566
+ function buildGapPriorityDescription(result, uncovered) {
567
+ return result.type === 'unit'
568
+ ? `${uncovered} weighted-quality ${uncovered === 1 ? 'gap' : 'gaps'} of ${result.totalItems} (${Math.round(result.coveragePercent)}% quality score).`
569
+ : `${uncovered} uncovered of ${result.totalItems} (${Math.round(result.coveragePercent)}% coverage).`;
570
+ }
571
+ function buildArchitectureActionStep(result, uncovered) {
572
+ var _a;
573
+ if (result.totalItems === 0) {
574
+ return `${metricTitle(result.type)}: ${(_a = result.reason) !== null && _a !== void 0 ? _a : 'Add supporting inputs and rerun analyze.'}`;
575
+ }
576
+ return `${metricTitle(result.type)}: close ${uncovered} remaining gaps and re-run analyze (${detailPathForMetric(result.type)}).`;
577
+ }
578
+ function buildArchitectureCodeSnippet(label, priority) {
579
+ return [
580
+ `# ${label}`,
581
+ `priority: ${priority}`,
582
+ 'action: rerun analyze after closing the reported gap',
583
+ ].join('\n');
584
+ }
585
+ function buildArchitectureCopilotPrompt(label, step, why) {
586
+ return [
587
+ `Improve ${label} in this repository.`,
588
+ `Action: ${step}`,
589
+ `Reason: ${why}`,
590
+ 'Update the relevant implementation or tests, then rerun the analyzer.',
591
+ ].join(' ');
592
+ }
593
+ function architecturePriorityLabel(priority) {
594
+ if (priority >= 75)
595
+ return 'P0';
596
+ if (priority >= 40)
597
+ return 'P1';
598
+ return 'P2';
599
+ }
600
+ function slugify(value) {
601
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
602
+ }
603
+ function escapeMermaidId(value) {
604
+ const sanitized = value.replace(/[^a-zA-Z0-9_]/g, '_');
605
+ return /^[A-Za-z]/.test(sanitized) ? sanitized : `node_${sanitized}`;
606
+ }
607
+ function escapeMermaidText(value) {
608
+ return value
609
+ .replace(/["`\[\]\n\r]/g, ' ')
610
+ .replace(/\s+/g, ' ')
611
+ .trim();
612
+ }
613
+ async function fetchUrlText(url) {
614
+ if (!isAllowedSpecUrl(url)) {
615
+ return undefined;
616
+ }
617
+ const transport = url.startsWith('https:') ? https : http;
618
+ return new Promise((resolve) => {
619
+ const request = transport.get(url, { timeout: 1500 }, (response) => {
620
+ var _a;
621
+ if (((_a = response.statusCode) !== null && _a !== void 0 ? _a : 500) >= 400) {
622
+ response.resume();
623
+ resolve(undefined);
624
+ return;
625
+ }
626
+ let body = '';
627
+ response.setEncoding('utf-8');
628
+ response.on('data', (chunk) => {
629
+ body += chunk;
630
+ });
631
+ response.on('end', () => resolve(body));
632
+ });
633
+ request.on('timeout', () => {
634
+ request.destroy();
635
+ resolve(undefined);
636
+ });
637
+ request.on('error', () => resolve(undefined));
638
+ });
639
+ }
640
+ function renderMermaidGraph(snapshot) {
641
+ const aliasById = new Map();
642
+ snapshot.nodes.forEach((node, index) => {
643
+ aliasById.set(node.id, `${escapeMermaidId(node.id)}_${index}`);
644
+ });
645
+ return [
646
+ ...snapshot.nodes.map((node) => ` ${aliasById.get(node.id)}["${escapeMermaidText(node.label)}"]`),
647
+ ...snapshot.edges
648
+ .filter((edge) => aliasById.has(edge.from) && aliasById.has(edge.to))
649
+ .map((edge) => { var _a; return ` ${aliasById.get(edge.from)} -->|${escapeMermaidText((_a = edge.label) !== null && _a !== void 0 ? _a : '')}| ${aliasById.get(edge.to)}`; }),
650
+ ];
651
+ }
652
+ function isAllowedSpecUrl(candidate) {
653
+ try {
654
+ const parsed = new URL(candidate);
655
+ return (parsed.protocol === 'http:' || parsed.protocol === 'https:')
656
+ && ['localhost', '127.0.0.1'].includes(parsed.hostname);
657
+ }
658
+ catch {
659
+ return false;
660
+ }
661
+ }
662
+ function isRecord(value) {
663
+ return typeof value === 'object' && value !== null;
664
+ }
665
+ function asNumber(value) {
666
+ return typeof value === 'number' ? value : undefined;
667
+ }
668
+ function asString(value) {
669
+ return typeof value === 'string' ? value : undefined;
670
+ }
@@ -138,7 +138,7 @@ exports.DEFAULT_CONFIG = {
138
138
  },
139
139
  },
140
140
  mutationTesting: {
141
- enabled: false,
141
+ enabled: true,
142
142
  tool: 'auto',
143
143
  threshold: 70,
144
144
  scope: 'changed-files',