@redaksjon/brennpunkt 0.0.3 → 0.1.0

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.
@@ -1,11 +1,662 @@
1
1
  #!/usr/bin/env node
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { existsSync, statSync, readFileSync } from 'node:fs';
6
6
  import { resolve } from 'node:path';
7
7
  import { c as calculateOverallCoverage, p as parseLcov, b as analyzeFile } from '../analyzer.js';
8
8
 
9
+ const SCHEME = "brennpunkt";
10
+ function parseUri(uri) {
11
+ if (!uri.startsWith(`${SCHEME}://`)) {
12
+ throw new Error(`Invalid URI scheme: ${uri}. Expected ${SCHEME}://`);
13
+ }
14
+ const withoutScheme = uri.substring(`${SCHEME}://`.length);
15
+ const [pathPart, queryPart] = withoutScheme.split("?");
16
+ const segments = pathPart.split("/");
17
+ if (segments.length === 0 || !segments[0]) {
18
+ throw new Error(`Invalid URI: ${uri}. No resource type specified.`);
19
+ }
20
+ const resourceType = segments[0];
21
+ const params = parseQueryParams(queryPart);
22
+ switch (resourceType) {
23
+ case "coverage":
24
+ return parseCoverageUri(segments, params);
25
+ case "file":
26
+ return parseFileUri(segments, params);
27
+ case "priorities":
28
+ return parsePrioritiesUri(params);
29
+ case "config":
30
+ return parseConfigUri(segments, params);
31
+ case "quick-wins":
32
+ return parseQuickWinsUri(params);
33
+ default:
34
+ throw new Error(`Unknown resource type: ${resourceType}`);
35
+ }
36
+ }
37
+ function parseQueryParams(queryPart) {
38
+ if (!queryPart) return {};
39
+ const params = {};
40
+ const pairs = queryPart.split("&");
41
+ for (const pair of pairs) {
42
+ const [key, value] = pair.split("=");
43
+ if (key && value !== void 0) {
44
+ params[decodeURIComponent(key)] = decodeURIComponent(value);
45
+ }
46
+ }
47
+ return params;
48
+ }
49
+ function parseCoverageUri(segments, params) {
50
+ const pathSegments = segments.slice(1);
51
+ let projectPath;
52
+ if (pathSegments.length === 0) {
53
+ throw new Error("Coverage URI requires project path");
54
+ }
55
+ if (pathSegments[0] === "") {
56
+ projectPath = "/" + pathSegments.slice(1).join("/");
57
+ } else {
58
+ projectPath = pathSegments.join("/");
59
+ }
60
+ if (!projectPath || projectPath === "/") {
61
+ throw new Error("Coverage URI requires project path");
62
+ }
63
+ return {
64
+ scheme: SCHEME,
65
+ resourceType: "coverage",
66
+ projectPath: decodeURIComponent(projectPath),
67
+ params
68
+ };
69
+ }
70
+ function parseFileUri(segments, params) {
71
+ if (segments.length < 3) {
72
+ throw new Error("File URI requires project path and file path");
73
+ }
74
+ const projectPath = decodeURIComponent(segments[1]);
75
+ const filePath = segments.slice(2).join("/");
76
+ return {
77
+ scheme: SCHEME,
78
+ resourceType: "file",
79
+ projectPath: decodeURIComponent(projectPath),
80
+ filePath: decodeURIComponent(filePath),
81
+ params
82
+ };
83
+ }
84
+ function parsePrioritiesUri(params) {
85
+ const projectPath = params.project;
86
+ if (!projectPath) {
87
+ throw new Error("Priorities URI requires project parameter");
88
+ }
89
+ return {
90
+ scheme: SCHEME,
91
+ resourceType: "priorities",
92
+ projectPath,
93
+ params
94
+ };
95
+ }
96
+ function parseConfigUri(segments, params) {
97
+ const pathSegments = segments.slice(1);
98
+ let projectPath;
99
+ if (pathSegments.length === 0) {
100
+ throw new Error("Config URI requires project path");
101
+ }
102
+ if (pathSegments[0] === "") {
103
+ projectPath = "/" + pathSegments.slice(1).join("/");
104
+ } else {
105
+ projectPath = pathSegments.join("/");
106
+ }
107
+ if (!projectPath || projectPath === "/") {
108
+ throw new Error("Config URI requires project path");
109
+ }
110
+ return {
111
+ scheme: SCHEME,
112
+ resourceType: "config",
113
+ projectPath: decodeURIComponent(projectPath),
114
+ params
115
+ };
116
+ }
117
+ function parseQuickWinsUri(params) {
118
+ const projectPath = params.project;
119
+ if (!projectPath) {
120
+ throw new Error("Quick wins URI requires project parameter");
121
+ }
122
+ return {
123
+ scheme: SCHEME,
124
+ resourceType: "quick-wins",
125
+ projectPath,
126
+ params
127
+ };
128
+ }
129
+ function buildCoverageUri(projectPath) {
130
+ return `${SCHEME}://coverage/${encodeURIComponent(projectPath).replace(/%2F/g, "/")}`;
131
+ }
132
+ function buildFileUri(projectPath, filePath) {
133
+ const encodedProject = encodeURIComponent(projectPath);
134
+ const encodedFile = encodeURIComponent(filePath).replace(/%2F/g, "/");
135
+ return `${SCHEME}://file/${encodedProject}/${encodedFile}`;
136
+ }
137
+ function buildPrioritiesUri(projectPath, options) {
138
+ const params = new URLSearchParams();
139
+ params.set("project", projectPath);
140
+ if (options?.top) params.set("top", String(options.top));
141
+ if (options?.minLines) params.set("minLines", String(options.minLines));
142
+ return `${SCHEME}://priorities?${params.toString()}`;
143
+ }
144
+ function buildConfigUri(projectPath) {
145
+ return `${SCHEME}://config/${encodeURIComponent(projectPath).replace(/%2F/g, "/")}`;
146
+ }
147
+ function buildQuickWinsUri(projectPath, maxLines) {
148
+ const params = new URLSearchParams();
149
+ params.set("project", projectPath);
150
+ if (maxLines) params.set("maxLines", String(maxLines));
151
+ return `${SCHEME}://quick-wins?${params.toString()}`;
152
+ }
153
+
154
+ const resourceTemplates = [
155
+ {
156
+ uriTemplate: "brennpunkt://coverage/{projectPath}",
157
+ name: "Coverage Report",
158
+ description: "Full coverage data for a project",
159
+ mimeType: "application/json"
160
+ },
161
+ {
162
+ uriTemplate: "brennpunkt://file/{projectPath}/{filePath}",
163
+ name: "File Coverage",
164
+ description: "Detailed coverage for a specific file",
165
+ mimeType: "application/json"
166
+ },
167
+ {
168
+ uriTemplate: "brennpunkt://priorities?project={projectPath}",
169
+ name: "Priority List",
170
+ description: "Files ranked by testing priority",
171
+ mimeType: "application/json"
172
+ },
173
+ {
174
+ uriTemplate: "brennpunkt://config/{projectPath}",
175
+ name: "Project Configuration",
176
+ description: "Brennpunkt configuration for a project",
177
+ mimeType: "application/json"
178
+ },
179
+ {
180
+ uriTemplate: "brennpunkt://quick-wins?project={projectPath}",
181
+ name: "Quick Wins",
182
+ description: "Small files with high impact potential",
183
+ mimeType: "application/json"
184
+ }
185
+ ];
186
+ async function handleListResources() {
187
+ return {
188
+ resources: [],
189
+ resourceTemplates
190
+ };
191
+ }
192
+ async function handleReadResource(uri) {
193
+ const parsed = parseUri(uri);
194
+ switch (parsed.resourceType) {
195
+ case "coverage":
196
+ return readCoverageResource(parsed.projectPath);
197
+ case "file":
198
+ return readFileResource(parsed.projectPath, parsed.filePath);
199
+ case "priorities":
200
+ return readPrioritiesResource(parsed.projectPath, parsed.params);
201
+ case "config":
202
+ return readConfigResource(parsed.projectPath);
203
+ case "quick-wins":
204
+ return readQuickWinsResource(parsed.projectPath, parsed.params);
205
+ default:
206
+ throw new Error(`Unknown resource type: ${parsed.resourceType}`);
207
+ }
208
+ }
209
+ async function readCoverageResource(projectPath) {
210
+ const config = loadProjectConfig(projectPath);
211
+ const files = loadCoverage(projectPath, config);
212
+ const overall = calculateOverallCoverage(files);
213
+ const uri = buildCoverageUri(resolve(projectPath));
214
+ const data = {
215
+ projectPath: resolve(projectPath),
216
+ configUsed: existsSync(resolve(projectPath, "brennpunkt.yaml")) ? "brennpunkt.yaml" : "defaults",
217
+ overall: {
218
+ lines: overall.lines.coverage,
219
+ functions: overall.functions.coverage,
220
+ branches: overall.branches.coverage,
221
+ fileCount: overall.fileCount
222
+ },
223
+ files: files.map((f) => ({
224
+ file: f.file,
225
+ lines: {
226
+ hit: f.linesHit,
227
+ found: f.linesFound,
228
+ coverage: f.linesFound > 0 ? f.linesHit / f.linesFound * 100 : 100
229
+ },
230
+ functions: {
231
+ hit: f.functionsHit,
232
+ found: f.functionsFound,
233
+ coverage: f.functionsFound > 0 ? f.functionsHit / f.functionsFound * 100 : 100
234
+ },
235
+ branches: {
236
+ hit: f.branchesHit,
237
+ found: f.branchesFound,
238
+ coverage: f.branchesFound > 0 ? f.branchesHit / f.branchesFound * 100 : 100
239
+ }
240
+ }))
241
+ };
242
+ return {
243
+ uri,
244
+ mimeType: "application/json",
245
+ text: JSON.stringify(data, null, 2)
246
+ };
247
+ }
248
+ async function readFileResource(projectPath, filePath) {
249
+ const config = loadProjectConfig(projectPath);
250
+ const files = loadCoverage(projectPath, config);
251
+ const file = files.find(
252
+ (f) => f.file === filePath || f.file.endsWith(filePath) || filePath.endsWith(f.file)
253
+ );
254
+ if (!file) {
255
+ throw new Error(`File not found in coverage data: ${filePath}`);
256
+ }
257
+ const lineCov = file.linesFound > 0 ? file.linesHit / file.linesFound * 100 : 100;
258
+ const funcCov = file.functionsFound > 0 ? file.functionsHit / file.functionsFound * 100 : 100;
259
+ const branchCov = file.branchesFound > 0 ? file.branchesHit / file.branchesFound * 100 : 100;
260
+ const uri = buildFileUri(resolve(projectPath), filePath);
261
+ const data = {
262
+ projectPath: resolve(projectPath),
263
+ configUsed: existsSync(resolve(projectPath, "brennpunkt.yaml")) ? "brennpunkt.yaml" : "defaults",
264
+ file: file.file,
265
+ coverage: {
266
+ lines: {
267
+ covered: file.linesHit,
268
+ total: file.linesFound,
269
+ percentage: Math.round(lineCov * 10) / 10
270
+ },
271
+ functions: {
272
+ covered: file.functionsHit,
273
+ total: file.functionsFound,
274
+ percentage: Math.round(funcCov * 10) / 10
275
+ },
276
+ branches: {
277
+ covered: file.branchesHit,
278
+ total: file.branchesFound,
279
+ percentage: Math.round(branchCov * 10) / 10
280
+ }
281
+ },
282
+ uncovered: {
283
+ lines: file.linesFound - file.linesHit,
284
+ functions: file.functionsFound - file.functionsHit,
285
+ branches: file.branchesFound - file.branchesHit
286
+ },
287
+ suggestedFocus: generateSuggestedFocus(file)
288
+ };
289
+ return {
290
+ uri,
291
+ mimeType: "application/json",
292
+ text: JSON.stringify(data, null, 2)
293
+ };
294
+ }
295
+ async function readPrioritiesResource(projectPath, params) {
296
+ const config = loadProjectConfig(projectPath);
297
+ const files = loadCoverage(projectPath, config);
298
+ const top = params.top ? parseInt(params.top, 10) : config.top ?? 5;
299
+ const minLines = params.minLines ? parseInt(params.minLines, 10) : config.minLines ?? 10;
300
+ const weights = config.weights ?? DEFAULT_WEIGHTS;
301
+ const priorities = getPriorities(files, top, minLines, weights);
302
+ const overall = calculateOverallCoverage(files);
303
+ const uri = buildPrioritiesUri(resolve(projectPath), { top, minLines });
304
+ const data = {
305
+ projectPath: resolve(projectPath),
306
+ configUsed: existsSync(resolve(projectPath, "brennpunkt.yaml")) ? "brennpunkt.yaml" : "defaults",
307
+ overall: {
308
+ lines: overall.lines.coverage,
309
+ functions: overall.functions.coverage,
310
+ branches: overall.branches.coverage,
311
+ fileCount: overall.fileCount
312
+ },
313
+ priorities
314
+ };
315
+ return {
316
+ uri,
317
+ mimeType: "application/json",
318
+ text: JSON.stringify(data, null, 2)
319
+ };
320
+ }
321
+ async function readConfigResource(projectPath) {
322
+ const config = loadProjectConfig(projectPath);
323
+ const configPath = resolve(projectPath, "brennpunkt.yaml");
324
+ const hasConfig = existsSync(configPath);
325
+ const uri = buildConfigUri(resolve(projectPath));
326
+ const data = {
327
+ projectPath: resolve(projectPath),
328
+ configPath,
329
+ hasConfig,
330
+ config: {
331
+ coveragePath: config.coveragePath ?? "auto-detect",
332
+ weights: config.weights ?? DEFAULT_WEIGHTS,
333
+ minLines: config.minLines ?? 10,
334
+ top: config.top ?? 5
335
+ },
336
+ source: hasConfig ? "brennpunkt.yaml" : "defaults"
337
+ };
338
+ return {
339
+ uri,
340
+ mimeType: "application/json",
341
+ text: JSON.stringify(data, null, 2)
342
+ };
343
+ }
344
+ async function readQuickWinsResource(projectPath, params) {
345
+ const config = loadProjectConfig(projectPath);
346
+ const files = loadCoverage(projectPath, config);
347
+ const overall = calculateOverallCoverage(files);
348
+ const minLines = config.minLines ?? 10;
349
+ const maxLines = params.maxLines ? parseInt(params.maxLines, 10) : 100;
350
+ const quickWins = files.filter((f) => f.linesFound >= minLines && f.linesFound <= maxLines).map((f) => {
351
+ const uncoveredLines = f.linesFound - f.linesHit;
352
+ const potentialImpact = overall.lines.found > 0 ? uncoveredLines / overall.lines.found * 100 : 0;
353
+ const currentCoverage = f.linesFound > 0 ? f.linesHit / f.linesFound * 100 : 100;
354
+ return {
355
+ file: f.file,
356
+ linesTotal: f.linesFound,
357
+ uncoveredLines,
358
+ currentCoverage: Math.round(currentCoverage * 10) / 10,
359
+ potentialImpact: Math.round(potentialImpact * 100) / 100
360
+ };
361
+ }).filter((f) => f.uncoveredLines > 0).sort((a, b) => b.potentialImpact - a.potentialImpact).slice(0, 10);
362
+ const uri = buildQuickWinsUri(resolve(projectPath), maxLines);
363
+ const data = {
364
+ projectPath: resolve(projectPath),
365
+ configUsed: existsSync(resolve(projectPath, "brennpunkt.yaml")) ? "brennpunkt.yaml" : "defaults",
366
+ criteria: {
367
+ minLines,
368
+ maxLines
369
+ },
370
+ overall: {
371
+ lines: overall.lines.coverage
372
+ },
373
+ quickWins
374
+ };
375
+ return {
376
+ uri,
377
+ mimeType: "application/json",
378
+ text: JSON.stringify(data, null, 2)
379
+ };
380
+ }
381
+
382
+ const prompts = [
383
+ {
384
+ name: "improve_coverage",
385
+ description: "Complete workflow to improve test coverage to a target percentage. Analyzes current state, identifies gaps, estimates effort, and provides actionable plan.",
386
+ arguments: [
387
+ {
388
+ name: "projectPath",
389
+ description: "Absolute path to the project",
390
+ required: true
391
+ },
392
+ {
393
+ name: "targetPercentage",
394
+ description: "Target coverage percentage (default: 90)",
395
+ required: false
396
+ },
397
+ {
398
+ name: "focusMetric",
399
+ description: "Metric to focus on: lines, branches, or functions (default: lines)",
400
+ required: false
401
+ }
402
+ ]
403
+ },
404
+ {
405
+ name: "analyze_gaps",
406
+ description: "Understand why coverage is below target. Identifies patterns, problematic modules, and root causes.",
407
+ arguments: [
408
+ {
409
+ name: "projectPath",
410
+ description: "Absolute path to the project",
411
+ required: true
412
+ },
413
+ {
414
+ name: "targetPercentage",
415
+ description: "Target coverage to compare against (default: 90)",
416
+ required: false
417
+ }
418
+ ]
419
+ },
420
+ {
421
+ name: "quick_wins_workflow",
422
+ description: "Find fast paths to coverage improvement. Identifies small files with high impact, estimates effort and total improvement.",
423
+ arguments: [
424
+ {
425
+ name: "projectPath",
426
+ description: "Absolute path to the project",
427
+ required: true
428
+ },
429
+ {
430
+ name: "timeConstraint",
431
+ description: "Time available: quick (5 files), moderate (10 files), thorough (20 files)",
432
+ required: false
433
+ }
434
+ ]
435
+ },
436
+ {
437
+ name: "coverage_review",
438
+ description: "Code review focused on coverage gaps. Analyzes specific files, identifies untested functions/branches, suggests test cases.",
439
+ arguments: [
440
+ {
441
+ name: "projectPath",
442
+ description: "Absolute path to the project",
443
+ required: true
444
+ },
445
+ {
446
+ name: "filePattern",
447
+ description: "Filter to specific files (optional)",
448
+ required: false
449
+ }
450
+ ]
451
+ }
452
+ ];
453
+ async function handleListPrompts() {
454
+ return { prompts };
455
+ }
456
+ async function handleGetPrompt(name, args) {
457
+ const prompt = prompts.find((p) => p.name === name);
458
+ if (!prompt) {
459
+ throw new Error(`Unknown prompt: ${name}`);
460
+ }
461
+ for (const arg of prompt.arguments || []) {
462
+ if (arg.required && !args[arg.name]) {
463
+ throw new Error(`Missing required argument: ${arg.name}`);
464
+ }
465
+ }
466
+ switch (name) {
467
+ case "improve_coverage":
468
+ return generateImproveCoveragePrompt(args);
469
+ case "analyze_gaps":
470
+ return generateAnalyzeGapsPrompt(args);
471
+ case "quick_wins_workflow":
472
+ return generateQuickWinsWorkflowPrompt(args);
473
+ case "coverage_review":
474
+ return generateCoverageReviewPrompt(args);
475
+ default:
476
+ throw new Error(`Prompt handler not implemented: ${name}`);
477
+ }
478
+ }
479
+ async function generateImproveCoveragePrompt(args) {
480
+ const projectPath = args.projectPath;
481
+ const target = parseFloat(args.targetPercentage || "90");
482
+ const metric = args.focusMetric || "lines";
483
+ const messages = [];
484
+ messages.push({
485
+ role: "user",
486
+ content: {
487
+ type: "text",
488
+ text: `I want to improve test coverage for ${projectPath} to ${target}% ${metric} coverage.`
489
+ }
490
+ });
491
+ let assistantText = `I'll help you improve test coverage to ${target}%.
492
+
493
+ `;
494
+ assistantText += `**Workflow:**
495
+ `;
496
+ assistantText += `1. Check current coverage: \`brennpunkt_coverage_summary\` with projectPath="${projectPath}"
497
+ `;
498
+ assistantText += `2. Get priorities: \`brennpunkt_get_priorities\` to identify top files
499
+ `;
500
+ assistantText += `3. Estimate impact: \`brennpunkt_estimate_impact\` to verify we'll hit ${target}%
501
+ `;
502
+ assistantText += `4. Write tests for highest priority files
503
+
504
+ `;
505
+ assistantText += `**Resources available:**
506
+ `;
507
+ assistantText += `- Coverage data: ${buildCoverageUri(projectPath)}
508
+ `;
509
+ assistantText += `- Priorities: ${buildPrioritiesUri(projectPath, { top: 5 })}
510
+
511
+ `;
512
+ assistantText += `Let me start by checking your current coverage...`;
513
+ messages.push({
514
+ role: "assistant",
515
+ content: {
516
+ type: "text",
517
+ text: assistantText
518
+ }
519
+ });
520
+ return { messages };
521
+ }
522
+ async function generateAnalyzeGapsPrompt(args) {
523
+ const projectPath = args.projectPath;
524
+ const target = parseFloat(args.targetPercentage || "90");
525
+ const messages = [];
526
+ messages.push({
527
+ role: "user",
528
+ content: {
529
+ type: "text",
530
+ text: `Analyze coverage gaps in ${projectPath}. Why is coverage below ${target}%?`
531
+ }
532
+ });
533
+ let assistantText = `I'll analyze coverage gaps to understand the patterns.
534
+
535
+ `;
536
+ assistantText += `**Analysis Plan:**
537
+ `;
538
+ assistantText += `1. Get current coverage summary: \`brennpunkt_coverage_summary\`
539
+ `;
540
+ assistantText += `2. Examine top priority files: \`brennpunkt_get_priorities\` with top=10
541
+ `;
542
+ assistantText += `3. Look for patterns:
543
+ `;
544
+ assistantText += ` - Are gaps in specific modules?
545
+ `;
546
+ assistantText += ` - Common issue (branches, functions, lines)?
547
+ `;
548
+ assistantText += ` - Error handling missing?
549
+ `;
550
+ assistantText += ` - Edge cases untested?
551
+
552
+ `;
553
+ assistantText += `**Resources:**
554
+ `;
555
+ assistantText += `- Full coverage: ${buildCoverageUri(projectPath)}
556
+ `;
557
+ assistantText += `- Priority analysis: ${buildPrioritiesUri(projectPath, { top: 10 })}
558
+
559
+ `;
560
+ assistantText += `Let me examine the coverage data...`;
561
+ messages.push({
562
+ role: "assistant",
563
+ content: {
564
+ type: "text",
565
+ text: assistantText
566
+ }
567
+ });
568
+ return { messages };
569
+ }
570
+ async function generateQuickWinsWorkflowPrompt(args) {
571
+ const projectPath = args.projectPath;
572
+ const constraint = args.timeConstraint || "moderate";
573
+ const fileCount = constraint === "quick" ? 5 : constraint === "thorough" ? 20 : 10;
574
+ const messages = [];
575
+ messages.push({
576
+ role: "user",
577
+ content: {
578
+ type: "text",
579
+ text: `Find quick wins to improve coverage in ${projectPath}. I have ${constraint} time available.`
580
+ }
581
+ });
582
+ let assistantText = `I'll find ${fileCount} quick wins - small files with high impact.
583
+
584
+ `;
585
+ assistantText += `**Strategy:**
586
+ `;
587
+ assistantText += `1. Get quick wins: Use resource ${buildQuickWinsUri(projectPath, 100)}
588
+ `;
589
+ assistantText += `2. Estimate total impact: \`brennpunkt_estimate_impact\` with identified files
590
+ `;
591
+ assistantText += `3. Present in priority order with effort estimates
592
+ `;
593
+ assistantText += `4. Suggest test approaches for each
594
+
595
+ `;
596
+ assistantText += `**Criteria:**
597
+ `;
598
+ assistantText += `- Small files (<100 lines)
599
+ `;
600
+ assistantText += `- High impact potential
601
+ `;
602
+ assistantText += `- Currently have coverage gaps
603
+
604
+ `;
605
+ assistantText += `Let me identify the quick wins...`;
606
+ messages.push({
607
+ role: "assistant",
608
+ content: {
609
+ type: "text",
610
+ text: assistantText
611
+ }
612
+ });
613
+ return { messages };
614
+ }
615
+ async function generateCoverageReviewPrompt(args) {
616
+ const projectPath = args.projectPath;
617
+ const pattern = args.filePattern || "highest priority files";
618
+ const messages = [];
619
+ messages.push({
620
+ role: "user",
621
+ content: {
622
+ type: "text",
623
+ text: `Review coverage for ${pattern} in ${projectPath}. What tests should I write?`
624
+ }
625
+ });
626
+ let assistantText = `I'll perform a detailed coverage review.
627
+
628
+ `;
629
+ assistantText += `**Review Process:**
630
+ `;
631
+ assistantText += `1. Get priorities: \`brennpunkt_get_priorities\` to identify files
632
+ `;
633
+ assistantText += `2. For each file:
634
+ `;
635
+ assistantText += ` - Get details: \`brennpunkt_get_file_coverage\`
636
+ `;
637
+ assistantText += ` - Identify untested functions/branches
638
+ `;
639
+ assistantText += ` - Suggest specific test cases
640
+ `;
641
+ assistantText += `3. Prioritize suggestions by impact
642
+
643
+ `;
644
+ assistantText += `**Resources:**
645
+ `;
646
+ assistantText += `- Priorities: ${buildPrioritiesUri(projectPath, { top: 5 })}
647
+
648
+ `;
649
+ assistantText += `Let me start the review...`;
650
+ messages.push({
651
+ role: "assistant",
652
+ content: {
653
+ type: "text",
654
+ text: assistantText
655
+ }
656
+ });
657
+ return { messages };
658
+ }
659
+
9
660
  const COVERAGE_SEARCH_PATHS = [
10
661
  "coverage/lcov.info",
11
662
  ".coverage/lcov.info",
@@ -430,13 +1081,20 @@ async function main() {
430
1081
  const server = new Server(
431
1082
  {
432
1083
  name: "brennpunkt",
433
- version: "0.0.1",
1084
+ version: "0.1.0",
434
1085
  // Server-level description for AI tools discovering this MCP server
435
1086
  description: "Test coverage priority analyzer. Reads lcov.info coverage data (produced by Jest, Vitest, Mocha, c8, NYC, Karma, Playwright, or any lcov-compatible tool) and ranks files by testing priority. Use this to identify WHERE to focus testing efforts for maximum coverage impact WITHOUT running tests. Provides actionable insights: priority scores, coverage gaps, and specific suggestions for each file."
436
1087
  },
437
1088
  {
438
1089
  capabilities: {
439
- tools: {}
1090
+ tools: {},
1091
+ resources: {
1092
+ subscribe: false,
1093
+ listChanged: false
1094
+ },
1095
+ prompts: {
1096
+ listChanged: false
1097
+ }
440
1098
  }
441
1099
  }
442
1100
  );
@@ -477,8 +1135,35 @@ async function main() {
477
1135
  };
478
1136
  }
479
1137
  });
1138
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
1139
+ return handleListResources();
1140
+ });
1141
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1142
+ const { uri } = request.params;
1143
+ try {
1144
+ const contents = await handleReadResource(uri);
1145
+ return { contents: [contents] };
1146
+ } catch (error) {
1147
+ const message = error instanceof Error ? error.message : String(error);
1148
+ throw new Error(`Failed to read resource ${uri}: ${message}`);
1149
+ }
1150
+ });
1151
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
1152
+ return handleListPrompts();
1153
+ });
1154
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1155
+ const { name, arguments: args } = request.params;
1156
+ try {
1157
+ return handleGetPrompt(name, args || {});
1158
+ } catch (error) {
1159
+ const message = error instanceof Error ? error.message : String(error);
1160
+ throw new Error(`Failed to get prompt ${name}: ${message}`);
1161
+ }
1162
+ });
480
1163
  const transport = new StdioServerTransport();
481
1164
  await server.connect(transport);
482
1165
  }
483
1166
  main().catch(console.error);
1167
+
1168
+ export { DEFAULT_CONFIG, DEFAULT_WEIGHTS, generateSuggestedFocus, getPriorities, loadCoverage, loadProjectConfig, validateProjectPath };
484
1169
  //# sourceMappingURL=server.js.map