awesome-slash 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +54 -0
- package/.claude-plugin/plugin.json +11 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +261 -0
- package/LICENSE +21 -0
- package/README.md +363 -0
- package/SECURITY.md +101 -0
- package/adapters/README.md +256 -0
- package/adapters/codex/README.md +272 -0
- package/adapters/codex/install.sh +179 -0
- package/adapters/opencode/README.md +301 -0
- package/adapters/opencode/install.sh +223 -0
- package/lib/patterns/review-patterns.js +511 -0
- package/lib/patterns/slop-patterns.js +647 -0
- package/lib/platform/detect-platform.js +535 -0
- package/lib/platform/verify-tools.js +235 -0
- package/lib/state/workflow-state.js +635 -0
- package/lib/state/workflow-state.schema.json +282 -0
- package/lib/utils/context-optimizer.js +227 -0
- package/mcp-server/index.js +303 -0
- package/mcp-server/package.json +23 -0
- package/package.json +63 -0
- package/plugins/deslop-around/.claude-plugin/plugin.json +20 -0
- package/plugins/deslop-around/commands/deslop-around.md +220 -0
- package/plugins/deslop-around/lib/patterns/review-patterns.js +511 -0
- package/plugins/deslop-around/lib/patterns/slop-patterns.js +641 -0
- package/plugins/deslop-around/lib/platform/detect-platform.js +514 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +235 -0
- package/plugins/deslop-around/lib/state/workflow-state.js +635 -0
- package/plugins/deslop-around/lib/state/workflow-state.schema.json +282 -0
- package/plugins/deslop-around/lib/utils/context-optimizer.js +222 -0
- package/plugins/next-task/.claude-plugin/plugin.json +24 -0
- package/plugins/next-task/agents/ci-fixer.md +236 -0
- package/plugins/next-task/agents/ci-monitor.md +291 -0
- package/plugins/next-task/agents/delivery-validator.md +451 -0
- package/plugins/next-task/agents/deslop-work.md +272 -0
- package/plugins/next-task/agents/docs-updater.md +506 -0
- package/plugins/next-task/agents/exploration-agent.md +277 -0
- package/plugins/next-task/agents/implementation-agent.md +427 -0
- package/plugins/next-task/agents/planning-agent.md +236 -0
- package/plugins/next-task/agents/policy-selector.md +248 -0
- package/plugins/next-task/agents/review-orchestrator.md +521 -0
- package/plugins/next-task/agents/simple-fixer.md +136 -0
- package/plugins/next-task/agents/task-discoverer.md +357 -0
- package/plugins/next-task/agents/test-coverage-checker.md +447 -0
- package/plugins/next-task/agents/worktree-manager.md +419 -0
- package/plugins/next-task/commands/delivery-approval.md +331 -0
- package/plugins/next-task/commands/next-task.md +627 -0
- package/plugins/next-task/commands/update-docs-around.md +418 -0
- package/plugins/next-task/hooks/hooks.json +14 -0
- package/plugins/next-task/lib/patterns/review-patterns.js +511 -0
- package/plugins/next-task/lib/patterns/slop-patterns.js +641 -0
- package/plugins/next-task/lib/platform/detect-platform.js +514 -0
- package/plugins/next-task/lib/platform/verify-tools.js +235 -0
- package/plugins/next-task/lib/state/tasks-registry.schema.json +85 -0
- package/plugins/next-task/lib/state/workflow-state.js +635 -0
- package/plugins/next-task/lib/state/workflow-state.schema.json +282 -0
- package/plugins/next-task/lib/state/worktree-status.schema.json +219 -0
- package/plugins/next-task/lib/utils/context-optimizer.js +222 -0
- package/plugins/project-review/.claude-plugin/plugin.json +20 -0
- package/plugins/project-review/commands/project-review-agents.md +286 -0
- package/plugins/project-review/commands/project-review-github.md +142 -0
- package/plugins/project-review/commands/project-review.md +273 -0
- package/plugins/project-review/lib/patterns/review-patterns.js +511 -0
- package/plugins/project-review/lib/patterns/slop-patterns.js +641 -0
- package/plugins/project-review/lib/platform/detect-platform.js +514 -0
- package/plugins/project-review/lib/platform/verify-tools.js +235 -0
- package/plugins/project-review/lib/state/workflow-state.js +635 -0
- package/plugins/project-review/lib/state/workflow-state.schema.json +282 -0
- package/plugins/project-review/lib/utils/context-optimizer.js +222 -0
- package/plugins/reality-check/.claude-plugin/plugin.json +23 -0
- package/plugins/reality-check/README.md +156 -0
- package/plugins/reality-check/agents/code-explorer.md +353 -0
- package/plugins/reality-check/agents/doc-analyzer.md +337 -0
- package/plugins/reality-check/agents/issue-scanner.md +231 -0
- package/plugins/reality-check/agents/plan-synthesizer.md +479 -0
- package/plugins/reality-check/commands/scan.md +242 -0
- package/plugins/reality-check/commands/set.md +203 -0
- package/plugins/reality-check/lib/state/reality-check-state.js +509 -0
- package/plugins/reality-check/skills/reality-analysis/SKILL.md +317 -0
- package/plugins/ship/.claude-plugin/plugin.json +21 -0
- package/plugins/ship/commands/ship-ci-review-loop.md +443 -0
- package/plugins/ship/commands/ship-deployment.md +330 -0
- package/plugins/ship/commands/ship-error-handling.md +254 -0
- package/plugins/ship/commands/ship.md +370 -0
- package/plugins/ship/lib/patterns/review-patterns.js +511 -0
- package/plugins/ship/lib/patterns/slop-patterns.js +641 -0
- package/plugins/ship/lib/platform/detect-platform.js +514 -0
- package/plugins/ship/lib/platform/verify-tools.js +235 -0
- package/plugins/ship/lib/state/workflow-state.js +635 -0
- package/plugins/ship/lib/state/workflow-state.schema.json +282 -0
- package/plugins/ship/lib/utils/context-optimizer.js +222 -0
- package/scripts/install/claude.sh +50 -0
- package/scripts/install/codex.sh +181 -0
- package/scripts/install/opencode.sh +211 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-coverage-checker
|
|
3
|
+
description: Validate test coverage quality for new code. Use this agent before the first review round to verify tests exist, are meaningful, and actually exercise the new code (not just path matching).
|
|
4
|
+
tools: Bash(git:*), Read, Grep, Glob
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Test Coverage Checker Agent
|
|
9
|
+
|
|
10
|
+
Validate that new work has appropriate, meaningful test coverage.
|
|
11
|
+
This is an advisory agent - it reports coverage gaps but does NOT block the workflow.
|
|
12
|
+
|
|
13
|
+
**Important**: This agent validates test QUALITY, not just test EXISTENCE. A test file
|
|
14
|
+
that exists but doesn't meaningfully exercise the new code is flagged as a gap.
|
|
15
|
+
|
|
16
|
+
## Scope
|
|
17
|
+
|
|
18
|
+
Analyze files in: `git diff --name-only origin/main..HEAD`
|
|
19
|
+
|
|
20
|
+
## Phase 1: Get Changed Files
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Get base branch
|
|
24
|
+
BASE_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "main")
|
|
25
|
+
|
|
26
|
+
# Get changed source files (exclude test files)
|
|
27
|
+
CHANGED_SOURCE=$(git diff --name-only origin/${BASE_BRANCH}..HEAD 2>/dev/null | \
|
|
28
|
+
grep -E '\.(js|ts|jsx|tsx|py|rs|go|rb|java|kt|swift|cpp|c|cs)$' | \
|
|
29
|
+
grep -v -E '(test|spec|_test|Test)\.')
|
|
30
|
+
|
|
31
|
+
# Get changed test files
|
|
32
|
+
CHANGED_TESTS=$(git diff --name-only origin/${BASE_BRANCH}..HEAD 2>/dev/null | \
|
|
33
|
+
grep -E '(test|spec|_test|Test)\.')
|
|
34
|
+
|
|
35
|
+
echo "SOURCE_FILES=$CHANGED_SOURCE"
|
|
36
|
+
echo "TEST_FILES=$CHANGED_TESTS"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Phase 2: Detect Test Conventions
|
|
40
|
+
|
|
41
|
+
Detect the project's test file naming convention:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Check for common test patterns
|
|
45
|
+
if ls tests/ 2>/dev/null | head -1; then
|
|
46
|
+
echo "TEST_DIR=tests"
|
|
47
|
+
elif ls __tests__/ 2>/dev/null | head -1; then
|
|
48
|
+
echo "TEST_DIR=__tests__"
|
|
49
|
+
elif ls test/ 2>/dev/null | head -1; then
|
|
50
|
+
echo "TEST_DIR=test"
|
|
51
|
+
elif ls spec/ 2>/dev/null | head -1; then
|
|
52
|
+
echo "TEST_DIR=spec"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# Check naming convention
|
|
56
|
+
if ls **/*.test.* 2>/dev/null | head -1; then
|
|
57
|
+
echo "TEST_PATTERN=.test."
|
|
58
|
+
elif ls **/*.spec.* 2>/dev/null | head -1; then
|
|
59
|
+
echo "TEST_PATTERN=.spec."
|
|
60
|
+
elif ls **/test_*.* 2>/dev/null | head -1; then
|
|
61
|
+
echo "TEST_PATTERN=test_"
|
|
62
|
+
fi
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Phase 3: Map Source to Test Files
|
|
66
|
+
|
|
67
|
+
For each source file, find corresponding test file:
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
const testMappings = {
|
|
71
|
+
// JavaScript/TypeScript patterns
|
|
72
|
+
'src/foo.ts': ['tests/foo.test.ts', '__tests__/foo.test.ts', 'src/foo.test.ts', 'src/__tests__/foo.test.ts'],
|
|
73
|
+
'lib/bar.js': ['tests/bar.test.js', 'lib/bar.test.js', 'test/bar.test.js'],
|
|
74
|
+
|
|
75
|
+
// Python patterns
|
|
76
|
+
'src/module.py': ['tests/test_module.py', 'test/test_module.py', 'src/test_module.py'],
|
|
77
|
+
|
|
78
|
+
// Rust patterns
|
|
79
|
+
'src/lib.rs': ['tests/lib_test.rs', 'src/lib_tests.rs'],
|
|
80
|
+
|
|
81
|
+
// Go patterns
|
|
82
|
+
'pkg/handler.go': ['pkg/handler_test.go']
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function findTestFile(sourceFile) {
|
|
86
|
+
const basename = sourceFile.split('/').pop().replace(/\.[^.]+$/, '');
|
|
87
|
+
const dir = sourceFile.split('/').slice(0, -1).join('/');
|
|
88
|
+
const ext = sourceFile.split('.').pop();
|
|
89
|
+
|
|
90
|
+
// Generate possible test file locations
|
|
91
|
+
const candidates = [
|
|
92
|
+
`tests/${basename}.test.${ext}`,
|
|
93
|
+
`tests/${basename}.spec.${ext}`,
|
|
94
|
+
`test/${basename}.test.${ext}`,
|
|
95
|
+
`__tests__/${basename}.test.${ext}`,
|
|
96
|
+
`${dir}/${basename}.test.${ext}`,
|
|
97
|
+
`${dir}/${basename}.spec.${ext}`,
|
|
98
|
+
`${dir}/__tests__/${basename}.test.${ext}`,
|
|
99
|
+
// Python style
|
|
100
|
+
`tests/test_${basename}.${ext}`,
|
|
101
|
+
`test/test_${basename}.${ext}`,
|
|
102
|
+
// Go style (test in same dir)
|
|
103
|
+
`${dir}/${basename}_test.${ext}`
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
return candidates;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Phase 4: Check Coverage
|
|
111
|
+
|
|
112
|
+
For each changed source file:
|
|
113
|
+
1. Find corresponding test file
|
|
114
|
+
2. Check if test file exists
|
|
115
|
+
3. If source modified, check if test was also modified
|
|
116
|
+
4. Analyze new functions/classes for test coverage
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const gaps = [];
|
|
120
|
+
const covered = [];
|
|
121
|
+
|
|
122
|
+
for (const sourceFile of changedSourceFiles) {
|
|
123
|
+
const testCandidates = findTestFile(sourceFile);
|
|
124
|
+
const existingTest = testCandidates.find(t => fileExists(t));
|
|
125
|
+
|
|
126
|
+
if (!existingTest) {
|
|
127
|
+
gaps.push({
|
|
128
|
+
file: sourceFile,
|
|
129
|
+
reason: 'No test file found',
|
|
130
|
+
candidates: testCandidates.slice(0, 3)
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check if test was updated along with source
|
|
136
|
+
const testModified = changedTestFiles.includes(existingTest);
|
|
137
|
+
|
|
138
|
+
if (!testModified) {
|
|
139
|
+
gaps.push({
|
|
140
|
+
file: sourceFile,
|
|
141
|
+
reason: 'Source modified but test file not updated',
|
|
142
|
+
testFile: existingTest
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
covered.push({
|
|
146
|
+
file: sourceFile,
|
|
147
|
+
testFile: existingTest
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Phase 5: Analyze New Exports
|
|
154
|
+
|
|
155
|
+
Check for new functions/classes that might need tests:
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
async function findNewExports(file) {
|
|
159
|
+
// Get diff for the file
|
|
160
|
+
const diff = await exec(`git diff origin/${BASE_BRANCH}..HEAD -- ${file}`);
|
|
161
|
+
|
|
162
|
+
// Find added function/class declarations
|
|
163
|
+
const newExports = [];
|
|
164
|
+
const patterns = [
|
|
165
|
+
/^\+\s*export\s+(function|const|class|async function)\s+(\w+)/gm,
|
|
166
|
+
/^\+\s*export\s+default\s+(function|class)\s*(\w*)/gm,
|
|
167
|
+
/^\+\s*module\.exports\s*=\s*\{([^}]+)\}/gm,
|
|
168
|
+
/^\+\s*def\s+(\w+)\(/gm, // Python
|
|
169
|
+
/^\+\s*pub\s+fn\s+(\w+)/gm, // Rust
|
|
170
|
+
/^\+\s*func\s+(\w+)/gm // Go
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
for (const pattern of patterns) {
|
|
174
|
+
let match;
|
|
175
|
+
while ((match = pattern.exec(diff)) !== null) {
|
|
176
|
+
newExports.push(match[2] || match[1]);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return newExports;
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Phase 6: Validate Test Quality
|
|
185
|
+
|
|
186
|
+
**Critical**: Don't just check if test files exist - verify tests actually exercise the new code.
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
async function validateTestQuality(sourceFile, testFile, newExports) {
|
|
190
|
+
const testContent = await readFile(testFile);
|
|
191
|
+
const sourceContent = await readFile(sourceFile);
|
|
192
|
+
const issues = [];
|
|
193
|
+
|
|
194
|
+
// 1. Check if new exports are actually tested
|
|
195
|
+
for (const exportName of newExports) {
|
|
196
|
+
const testMentions = testContent.match(new RegExp(exportName, 'g'));
|
|
197
|
+
if (!testMentions || testMentions.length === 0) {
|
|
198
|
+
issues.push({
|
|
199
|
+
type: 'untested-export',
|
|
200
|
+
export: exportName,
|
|
201
|
+
message: `New export '${exportName}' is not referenced in test file`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 2. Check for meaningful assertions (not just trivial tests)
|
|
207
|
+
const trivialPatterns = [
|
|
208
|
+
/expect\s*\(\s*true\s*\)/,
|
|
209
|
+
/expect\s*\(\s*1\s*\)\s*\.toBe\s*\(\s*1\s*\)/,
|
|
210
|
+
/assert\s*\(\s*True\s*\)/,
|
|
211
|
+
/\.toBeDefined\s*\(\s*\)/ // Only toBeDefined without other checks
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
for (const pattern of trivialPatterns) {
|
|
215
|
+
if (pattern.test(testContent)) {
|
|
216
|
+
issues.push({
|
|
217
|
+
type: 'trivial-assertion',
|
|
218
|
+
message: 'Test contains trivial assertions that don\'t validate behavior'
|
|
219
|
+
});
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 3. Check test describes/its match the source functionality
|
|
225
|
+
const describeTitles = testContent.match(/describe\s*\(\s*['"`]([^'"`]+)['"`]/g) || [];
|
|
226
|
+
const itTitles = testContent.match(/it\s*\(\s*['"`]([^'"`]+)['"`]/g) || [];
|
|
227
|
+
|
|
228
|
+
if (describeTitles.length === 0 && itTitles.length === 0) {
|
|
229
|
+
issues.push({
|
|
230
|
+
type: 'no-test-structure',
|
|
231
|
+
message: 'Test file lacks describe/it blocks - may not be a real test'
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 4. Check for edge case coverage hints
|
|
236
|
+
const edgeCasePatterns = ['null', 'undefined', 'empty', 'error', 'invalid', 'edge', 'boundary'];
|
|
237
|
+
const hasEdgeCases = edgeCasePatterns.some(p => testContent.toLowerCase().includes(p));
|
|
238
|
+
|
|
239
|
+
if (!hasEdgeCases && newExports.length > 0) {
|
|
240
|
+
issues.push({
|
|
241
|
+
type: 'missing-edge-cases',
|
|
242
|
+
message: 'Tests may lack edge case coverage (no null/error/boundary tests detected)',
|
|
243
|
+
severity: 'warning'
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 5. Check if test actually imports/requires the source
|
|
248
|
+
const sourceBasename = sourceFile.split('/').pop().replace(/\.[^.]+$/, '');
|
|
249
|
+
const importPatterns = [
|
|
250
|
+
new RegExp(`from\\s+['"][^'"]*${sourceBasename}['"]`),
|
|
251
|
+
new RegExp(`require\\s*\\(\\s*['"][^'"]*${sourceBasename}['"]`),
|
|
252
|
+
new RegExp(`import\\s+.*${sourceBasename}`)
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
const importsSource = importPatterns.some(p => p.test(testContent));
|
|
256
|
+
if (!importsSource) {
|
|
257
|
+
issues.push({
|
|
258
|
+
type: 'no-source-import',
|
|
259
|
+
message: `Test file doesn't appear to import '${sourceBasename}'`,
|
|
260
|
+
severity: 'critical'
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
testFile,
|
|
266
|
+
sourceFile,
|
|
267
|
+
quality: issues.length === 0 ? 'good' : issues.some(i => i.severity === 'critical') ? 'poor' : 'needs-improvement',
|
|
268
|
+
issues
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Phase 7: Analyze Test Coverage Depth
|
|
274
|
+
|
|
275
|
+
Check if tests cover the actual logic paths in the new code:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
async function analyzeTestDepth(sourceFile, testFile, diff) {
|
|
279
|
+
const analysis = {
|
|
280
|
+
sourceComplexity: 'unknown',
|
|
281
|
+
testCoverage: 'unknown',
|
|
282
|
+
suggestions: []
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Extract conditionals and branches from new code
|
|
286
|
+
const newBranches = [];
|
|
287
|
+
const branchPatterns = [
|
|
288
|
+
/^\+.*if\s*\(/gm,
|
|
289
|
+
/^\+.*else\s*\{/gm,
|
|
290
|
+
/^\+.*\?\s*.*:/gm, // Ternary
|
|
291
|
+
/^\+.*switch\s*\(/gm,
|
|
292
|
+
/^\+.*case\s+/gm,
|
|
293
|
+
/^\+.*catch\s*\(/gm
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
for (const pattern of branchPatterns) {
|
|
297
|
+
const matches = diff.match(pattern) || [];
|
|
298
|
+
newBranches.push(...matches);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (newBranches.length > 3) {
|
|
302
|
+
analysis.sourceComplexity = 'high';
|
|
303
|
+
analysis.suggestions.push('New code has multiple branches - ensure each path is tested');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Check for async/await patterns that need error testing
|
|
307
|
+
const hasAsync = /^\+.*async\s+|^\+.*await\s+/m.test(diff);
|
|
308
|
+
if (hasAsync) {
|
|
309
|
+
const testContent = await readFile(testFile);
|
|
310
|
+
const hasAsyncTests = /\.rejects|\.resolves|async.*expect|try.*catch.*expect/i.test(testContent);
|
|
311
|
+
|
|
312
|
+
if (!hasAsyncTests) {
|
|
313
|
+
analysis.suggestions.push('New async code detected - add tests for promise rejection scenarios');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return analysis;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Output Format (JSON)
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"scope": "new-work-only",
|
|
326
|
+
"coverage": {
|
|
327
|
+
"filesAnalyzed": 5,
|
|
328
|
+
"filesWithTests": 3,
|
|
329
|
+
"filesMissingTests": 2,
|
|
330
|
+
"coveragePercent": 60
|
|
331
|
+
},
|
|
332
|
+
"gaps": [
|
|
333
|
+
{
|
|
334
|
+
"file": "src/new-feature.ts",
|
|
335
|
+
"reason": "No test file found",
|
|
336
|
+
"candidates": ["tests/new-feature.test.ts", "__tests__/new-feature.test.ts"],
|
|
337
|
+
"newExports": ["handleFeature", "FeatureConfig"]
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"file": "src/modified.ts",
|
|
341
|
+
"reason": "Source modified but test file not updated",
|
|
342
|
+
"testFile": "tests/modified.test.ts",
|
|
343
|
+
"newExports": ["newFunction"]
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
"qualityIssues": [
|
|
347
|
+
{
|
|
348
|
+
"file": "src/api-client.ts",
|
|
349
|
+
"testFile": "tests/api-client.test.ts",
|
|
350
|
+
"quality": "needs-improvement",
|
|
351
|
+
"issues": [
|
|
352
|
+
{
|
|
353
|
+
"type": "untested-export",
|
|
354
|
+
"export": "handleRetry",
|
|
355
|
+
"message": "New export 'handleRetry' is not referenced in test file"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"type": "missing-edge-cases",
|
|
359
|
+
"message": "Tests may lack edge case coverage",
|
|
360
|
+
"severity": "warning"
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
"suggestions": ["New async code detected - add tests for promise rejection scenarios"]
|
|
364
|
+
}
|
|
365
|
+
],
|
|
366
|
+
"covered": [
|
|
367
|
+
{
|
|
368
|
+
"file": "src/utils.ts",
|
|
369
|
+
"testFile": "tests/utils.test.ts",
|
|
370
|
+
"quality": "good"
|
|
371
|
+
}
|
|
372
|
+
],
|
|
373
|
+
"summary": {
|
|
374
|
+
"status": "quality-issues-found",
|
|
375
|
+
"recommendation": "2 files missing tests, 1 file has tests but doesn't exercise new code"
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Report Output
|
|
381
|
+
|
|
382
|
+
```markdown
|
|
383
|
+
## Test Coverage Report
|
|
384
|
+
|
|
385
|
+
### Summary
|
|
386
|
+
| Metric | Value |
|
|
387
|
+
|--------|-------|
|
|
388
|
+
| Files Analyzed | ${filesAnalyzed} |
|
|
389
|
+
| Files with Tests | ${filesWithTests} |
|
|
390
|
+
| Files Missing Tests | ${filesMissingTests} |
|
|
391
|
+
| Tests with Quality Issues | ${qualityIssues.length} |
|
|
392
|
+
| Effective Coverage | ${effectiveCoveragePercent}% |
|
|
393
|
+
|
|
394
|
+
### Missing Test Files
|
|
395
|
+
${gaps.map(g => `
|
|
396
|
+
**${g.file}**
|
|
397
|
+
- Reason: ${g.reason}
|
|
398
|
+
- New exports: ${g.newExports?.join(', ') || 'N/A'}
|
|
399
|
+
${g.candidates ? `- Suggested test location: ${g.candidates[0]}` : ''}
|
|
400
|
+
`).join('\n')}
|
|
401
|
+
|
|
402
|
+
### Test Quality Issues
|
|
403
|
+
${qualityIssues.map(q => `
|
|
404
|
+
**${q.file}** → ${q.testFile} (Quality: ${q.quality})
|
|
405
|
+
${q.issues.map(i => `- ⚠️ ${i.message}`).join('\n')}
|
|
406
|
+
${q.suggestions?.map(s => `- 💡 ${s}`).join('\n') || ''}
|
|
407
|
+
`).join('\n')}
|
|
408
|
+
|
|
409
|
+
### Well-Covered Files
|
|
410
|
+
${covered.filter(c => c.quality === 'good').map(c => `- ✓ ${c.file} → ${c.testFile}`).join('\n')}
|
|
411
|
+
|
|
412
|
+
### Recommendation
|
|
413
|
+
${summary.recommendation}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Behavior
|
|
417
|
+
|
|
418
|
+
- **Advisory only** - Does NOT block workflow
|
|
419
|
+
- Reports coverage gaps to review-orchestrator
|
|
420
|
+
- Suggestions included in PR description
|
|
421
|
+
- Implementation-agent may optionally add tests based on findings
|
|
422
|
+
|
|
423
|
+
## Integration Points
|
|
424
|
+
|
|
425
|
+
This agent is called:
|
|
426
|
+
1. **Before first review round** - In parallel with deslop-work
|
|
427
|
+
2. Results passed to review-orchestrator for context
|
|
428
|
+
|
|
429
|
+
## Success Criteria
|
|
430
|
+
|
|
431
|
+
- Correctly identifies test file conventions
|
|
432
|
+
- Maps source files to test files
|
|
433
|
+
- Detects new exports that need testing
|
|
434
|
+
- **Validates tests actually exercise the new code** (not just path matching)
|
|
435
|
+
- **Flags trivial or meaningless tests** (e.g., `expect(true).toBe(true)`)
|
|
436
|
+
- **Checks for edge case coverage** in tests
|
|
437
|
+
- **Verifies tests import the source file** they claim to test
|
|
438
|
+
- Provides actionable recommendations
|
|
439
|
+
- Does NOT block workflow on missing tests
|
|
440
|
+
|
|
441
|
+
## Model Choice: Sonnet
|
|
442
|
+
|
|
443
|
+
This agent uses **sonnet** because:
|
|
444
|
+
- Test quality validation requires understanding code relationships
|
|
445
|
+
- Pattern detection needs more than simple matching
|
|
446
|
+
- Analyzing test meaningfulness requires moderate reasoning
|
|
447
|
+
- Advisory role means occasional misses are acceptable
|