uv-suite 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.
- package/README.md +180 -0
- package/agents/claude-code/anti-slop-guard.md +84 -0
- package/agents/claude-code/architect.md +68 -0
- package/agents/claude-code/cartographer.md +99 -0
- package/agents/claude-code/devops.md +43 -0
- package/agents/claude-code/eval-writer.md +57 -0
- package/agents/claude-code/prototype-builder.md +59 -0
- package/agents/claude-code/reviewer.md +76 -0
- package/agents/claude-code/security.md +69 -0
- package/agents/claude-code/spec-writer.md +81 -0
- package/agents/claude-code/test-writer.md +54 -0
- package/agents/codex/anti-slop-guard.toml +12 -0
- package/agents/codex/architect.toml +11 -0
- package/agents/codex/cartographer.toml +16 -0
- package/agents/codex/devops.toml +8 -0
- package/agents/codex/eval-writer.toml +11 -0
- package/agents/codex/prototype-builder.toml +10 -0
- package/agents/codex/reviewer.toml +16 -0
- package/agents/codex/security.toml +14 -0
- package/agents/codex/spec-writer.toml +11 -0
- package/agents/codex/test-writer.toml +13 -0
- package/agents/cursor/anti-slop-guard.mdc +22 -0
- package/agents/cursor/architect.mdc +24 -0
- package/agents/cursor/cartographer.mdc +28 -0
- package/agents/cursor/devops.mdc +16 -0
- package/agents/cursor/eval-writer.mdc +21 -0
- package/agents/cursor/prototype-builder.mdc +25 -0
- package/agents/cursor/reviewer.mdc +26 -0
- package/agents/cursor/security.mdc +20 -0
- package/agents/cursor/spec-writer.mdc +27 -0
- package/agents/cursor/test-writer.mdc +28 -0
- package/agents/portable/anti-slop-guard.md +71 -0
- package/agents/portable/architect.md +83 -0
- package/agents/portable/cartographer.md +64 -0
- package/agents/portable/devops.md +56 -0
- package/agents/portable/eval-writer.md +70 -0
- package/agents/portable/prototype-builder.md +70 -0
- package/agents/portable/reviewer.md +79 -0
- package/agents/portable/security.md +63 -0
- package/agents/portable/spec-writer.md +89 -0
- package/agents/portable/test-writer.md +56 -0
- package/bin/cli.js +84 -0
- package/guardrails/architecture-slop.md +60 -0
- package/guardrails/comment-slop.md +53 -0
- package/guardrails/doc-slop.md +62 -0
- package/guardrails/error-handling-slop.md +65 -0
- package/guardrails/overengineering-slop.md +56 -0
- package/guardrails/test-slop.md +72 -0
- package/hooks/auto-lint.sh +41 -0
- package/hooks/block-destructive.sh +34 -0
- package/hooks/danger-zone-check.sh +42 -0
- package/hooks/session-review-reminder.sh +35 -0
- package/install.sh +230 -0
- package/package.json +39 -0
- package/personas/auto.json +80 -0
- package/personas/professional.json +109 -0
- package/personas/spike.json +54 -0
- package/personas/sport.json +39 -0
- package/settings.json +108 -0
- package/skills/architect/SKILL.md +26 -0
- package/skills/map-codebase/SKILL.md +50 -0
- package/skills/persona/SKILL.md +4 -0
- package/skills/prototype/SKILL.md +27 -0
- package/skills/review/SKILL.md +39 -0
- package/skills/security-review/SKILL.md +73 -0
- package/skills/slop-check/SKILL.md +30 -0
- package/skills/spec/SKILL.md +33 -0
- package/skills/write-evals/SKILL.md +28 -0
- package/skills/write-tests/SKILL.md +40 -0
- package/uv.sh +56 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const UV_SUITE_DIR = path.resolve(__dirname, '..');
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const command = args[0];
|
|
10
|
+
|
|
11
|
+
function usage() {
|
|
12
|
+
console.log(`
|
|
13
|
+
uv-suite - AI-assisted development framework
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
npx uv-suite install [--persona sport|professional|auto|spike] [--global]
|
|
17
|
+
npx uv-suite info
|
|
18
|
+
|
|
19
|
+
Commands:
|
|
20
|
+
install Install agents, skills, hooks, guardrails, and personas
|
|
21
|
+
info Show what would be installed
|
|
22
|
+
|
|
23
|
+
Personas:
|
|
24
|
+
spike Research & documentation (Opus, max effort)
|
|
25
|
+
sport New projects, prototyping (Sonnet, high effort)
|
|
26
|
+
professional Production code (default, all hooks + guardrails)
|
|
27
|
+
auto Fully autonomous (max effort, everything approved)
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
npx uv-suite install Install with Professional persona
|
|
31
|
+
npx uv-suite install --persona sport Install with Sport persona
|
|
32
|
+
npx uv-suite install --global Install to ~/.claude/
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function info() {
|
|
37
|
+
console.log(`
|
|
38
|
+
UV Suite v0.1.0
|
|
39
|
+
|
|
40
|
+
Contents:
|
|
41
|
+
10 agents Claude Code (.md), Cursor (.mdc), Codex (.toml)
|
|
42
|
+
9 skills Slash commands for Claude Code
|
|
43
|
+
4 hooks auto-lint, slop-check, danger-zone, block-destructive
|
|
44
|
+
6 guardrails Anti-slop rules (comment, overengineering, error, test, doc, architecture)
|
|
45
|
+
4 personas Spike, Sport, Professional, Auto
|
|
46
|
+
1 launcher uv.sh (session launcher with persona selection)
|
|
47
|
+
|
|
48
|
+
Source: ${UV_SUITE_DIR}
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function install() {
|
|
53
|
+
const installScript = path.join(UV_SUITE_DIR, 'install.sh');
|
|
54
|
+
if (!fs.existsSync(installScript)) {
|
|
55
|
+
console.error('Error: install.sh not found at', installScript);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Pass through all args after "install"
|
|
60
|
+
const installArgs = args.slice(1).join(' ');
|
|
61
|
+
try {
|
|
62
|
+
execSync(`bash "${installScript}" ${installArgs}`, { stdio: 'inherit' });
|
|
63
|
+
} catch (e) {
|
|
64
|
+
process.exit(e.status || 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
switch (command) {
|
|
69
|
+
case 'install':
|
|
70
|
+
install();
|
|
71
|
+
break;
|
|
72
|
+
case 'info':
|
|
73
|
+
info();
|
|
74
|
+
break;
|
|
75
|
+
case '--help':
|
|
76
|
+
case '-h':
|
|
77
|
+
case undefined:
|
|
78
|
+
usage();
|
|
79
|
+
break;
|
|
80
|
+
default:
|
|
81
|
+
console.error(`Unknown command: ${command}`);
|
|
82
|
+
usage();
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Guardrail: Architecture Slop
|
|
2
|
+
|
|
3
|
+
**Severity:** High (unjustified complexity has compounding cost)
|
|
4
|
+
**Category:** Architecture quality
|
|
5
|
+
|
|
6
|
+
## Pattern
|
|
7
|
+
|
|
8
|
+
Architecture decisions that sound sophisticated but don't match actual requirements. Buzzword-driven design. Complexity assumed rather than earned.
|
|
9
|
+
|
|
10
|
+
## Detection Rules
|
|
11
|
+
|
|
12
|
+
- Does the proposed architecture match the actual scale? (10 users don't need microservices)
|
|
13
|
+
- Is the complexity justified by a specific requirement, or just "best practice"?
|
|
14
|
+
- Could a simpler approach work? If yes, why isn't it the recommendation?
|
|
15
|
+
- Are buzzwords being used as reasoning? ("event-driven" is not a reason, it's a pattern)
|
|
16
|
+
- Is the "future-proofing" based on concrete plans or speculation?
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
### Slop
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
"We should use an event-driven microservices architecture with CQRS
|
|
24
|
+
and event sourcing" (for a CRUD app with 3 endpoints and 100 users)
|
|
25
|
+
|
|
26
|
+
"Let's implement a service mesh with circuit breakers and distributed
|
|
27
|
+
tracing" (for a monolith on a single server)
|
|
28
|
+
|
|
29
|
+
"We need a Kafka cluster for message passing" (for 10 events per minute)
|
|
30
|
+
|
|
31
|
+
"Let's use GraphQL for maximum flexibility" (for 5 fixed queries)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Not Slop
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
"A monolith with 3 REST endpoints is sufficient. We'll extract
|
|
38
|
+
services if/when we need independent scaling or deployment."
|
|
39
|
+
|
|
40
|
+
"PostgreSQL handles our query patterns well. We'll add read replicas
|
|
41
|
+
if/when we exceed 10k queries per second."
|
|
42
|
+
|
|
43
|
+
"A simple task queue (Redis + BullMQ) handles our async processing.
|
|
44
|
+
We'll move to Kafka if/when we need multi-consumer event streaming."
|
|
45
|
+
|
|
46
|
+
"REST with OpenAPI spec. GraphQL adds complexity we don't need for
|
|
47
|
+
our fixed client requirements."
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## The Challenge Test
|
|
51
|
+
|
|
52
|
+
For every architectural component, ask: **"What breaks if we don't have this?"**
|
|
53
|
+
|
|
54
|
+
- If the answer is "nothing for the next 6 months" → remove it
|
|
55
|
+
- If the answer is "we'd need to add it in 2 months when X happens" → document the upgrade path, don't build it now
|
|
56
|
+
- If the answer is "users can't complete the core flow" → keep it
|
|
57
|
+
|
|
58
|
+
## Fix
|
|
59
|
+
|
|
60
|
+
Start with the simplest architecture that meets current requirements. Document the concrete triggers that would justify more complexity. "We'll add caching when p99 latency exceeds 500ms" is better than "we're adding Redis for performance."
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Guardrail: Comment Slop
|
|
2
|
+
|
|
3
|
+
**Severity:** High (actively obscures code)
|
|
4
|
+
**Category:** Code quality
|
|
5
|
+
|
|
6
|
+
## Pattern
|
|
7
|
+
|
|
8
|
+
Comments that restate what the code already says, adding no information.
|
|
9
|
+
|
|
10
|
+
## Detection Rule
|
|
11
|
+
|
|
12
|
+
If deleting the comment and reading the code tells you the same thing, the comment is slop. Comments should explain **why**, not **what**.
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
|
|
16
|
+
### Slop
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// Initialize the database connection
|
|
20
|
+
const db = initDatabase();
|
|
21
|
+
|
|
22
|
+
// Set the user's name
|
|
23
|
+
user.name = newName;
|
|
24
|
+
|
|
25
|
+
// Loop through the items
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
|
|
28
|
+
// Return the result
|
|
29
|
+
return result;
|
|
30
|
+
|
|
31
|
+
// Check if the user is valid
|
|
32
|
+
if (isValid(user)) {
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Not Slop
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Retry with exponential backoff because the API rate-limits
|
|
39
|
+
// at 100 req/min and our batch size exceeds that
|
|
40
|
+
const db = initDatabase({ retries: 3, backoff: 'exponential' });
|
|
41
|
+
|
|
42
|
+
// Normalize Unicode before comparison — we've seen cases where
|
|
43
|
+
// visually identical names fail equality checks (NFD vs NFC)
|
|
44
|
+
user.name = newName.normalize('NFC');
|
|
45
|
+
|
|
46
|
+
// Skip soft-deleted records intentionally — the reports team
|
|
47
|
+
// needs historical data including deleted entries
|
|
48
|
+
const records = db.query('SELECT * FROM orders');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Fix
|
|
52
|
+
|
|
53
|
+
Delete the comment. If the code genuinely needs explanation, rename the variable or function to be self-documenting. Only add a comment when there's context the code can't express (business decisions, workarounds, non-obvious constraints).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Guardrail: Documentation Slop
|
|
2
|
+
|
|
3
|
+
**Severity:** Medium (misleads readers, creates false sense of documentation)
|
|
4
|
+
**Category:** Documentation quality
|
|
5
|
+
|
|
6
|
+
## Pattern
|
|
7
|
+
|
|
8
|
+
Documentation that uses words without saying anything specific. Vague adjectives, appeals to authority, feature lists that could describe any system.
|
|
9
|
+
|
|
10
|
+
## Detection Rules
|
|
11
|
+
|
|
12
|
+
**Vague adjectives (red flags):**
|
|
13
|
+
- "Robust", "scalable", "maintainable", "comprehensive", "extensive"
|
|
14
|
+
- "Elegant", "clean", "modern", "cutting-edge", "state-of-the-art"
|
|
15
|
+
- "Enterprise-grade", "production-ready", "battle-tested"
|
|
16
|
+
|
|
17
|
+
**Evasive verbs:**
|
|
18
|
+
- "Leverages", "utilizes", "facilitates", "empowers", "enables"
|
|
19
|
+
|
|
20
|
+
**Authority appeals:**
|
|
21
|
+
- "Best practices", "industry-standard", "widely adopted"
|
|
22
|
+
- Without naming the specific practice
|
|
23
|
+
|
|
24
|
+
**Generic feature lists:**
|
|
25
|
+
- "Comprehensive error handling" — what errors? What handling?
|
|
26
|
+
- "Flexible configuration options" — what options? What's configurable?
|
|
27
|
+
- "Extensive logging" — what's logged? Where? What format?
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Slop
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
## Overview
|
|
35
|
+
This module provides a robust, scalable, and maintainable solution for
|
|
36
|
+
handling user authentication. It leverages industry-standard best practices
|
|
37
|
+
to ensure secure and efficient credential management.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
- Comprehensive authentication flow
|
|
41
|
+
- Secure credential storage
|
|
42
|
+
- Flexible configuration options
|
|
43
|
+
- Extensive error handling
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Not Slop
|
|
47
|
+
|
|
48
|
+
```markdown
|
|
49
|
+
## What This Does
|
|
50
|
+
Handles login, signup, and session management using bcrypt for password
|
|
51
|
+
hashing and JWT for session tokens. Sessions expire after 24 hours.
|
|
52
|
+
|
|
53
|
+
## Endpoints
|
|
54
|
+
- POST /auth/signup — Create account (email + password)
|
|
55
|
+
- POST /auth/login — Get JWT token
|
|
56
|
+
- POST /auth/logout — Invalidate session
|
|
57
|
+
- GET /auth/me — Get current user (requires valid JWT)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Fix
|
|
61
|
+
|
|
62
|
+
Replace every vague adjective with a specific fact. "Robust error handling" becomes "Retries 3 times with exponential backoff on 5xx errors." If you can't make it specific, delete it — the absence of vague claims is better than their presence.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Guardrail: Error Handling Slop
|
|
2
|
+
|
|
3
|
+
**Severity:** Medium (adds noise, obscures real error paths)
|
|
4
|
+
**Category:** Code quality
|
|
5
|
+
|
|
6
|
+
## Pattern
|
|
7
|
+
|
|
8
|
+
Try/catch blocks around code that can't throw, or that catch and re-throw without adding value. Defensive checks for impossible states.
|
|
9
|
+
|
|
10
|
+
## Detection Rules
|
|
11
|
+
|
|
12
|
+
- Try/catch where the try block can't throw
|
|
13
|
+
- Catch that only logs and re-throws (the caller handles it)
|
|
14
|
+
- Error handling for impossible states (e.g., validating a non-null TypeScript param isn't null)
|
|
15
|
+
- Defensive checks deep inside internal functions (trust the caller at internal boundaries)
|
|
16
|
+
- Catching generic `Error` when only specific errors are possible
|
|
17
|
+
|
|
18
|
+
## Examples
|
|
19
|
+
|
|
20
|
+
### Slop
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// JSON.stringify doesn't throw on a plain object
|
|
24
|
+
try {
|
|
25
|
+
const json = JSON.stringify(user);
|
|
26
|
+
return json;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Failed to stringify user:', error);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Catch, log, re-throw — adds nothing
|
|
33
|
+
try {
|
|
34
|
+
const result = await fetchUser(id);
|
|
35
|
+
return result;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error fetching user:', error);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Null check on TypeScript non-null parameter
|
|
42
|
+
function processUser(user: User): void {
|
|
43
|
+
if (!user) throw new Error('User is required'); // TypeScript already prevents this
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Not Slop
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// No try/catch needed — let errors propagate naturally
|
|
51
|
+
const json = JSON.stringify(user);
|
|
52
|
+
return json;
|
|
53
|
+
|
|
54
|
+
// Handle the error meaningfully at the system boundary
|
|
55
|
+
try {
|
|
56
|
+
const result = await fetchUser(id);
|
|
57
|
+
return result;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return { error: 'User not found', status: 404 };
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Fix
|
|
64
|
+
|
|
65
|
+
Remove the try/catch. Only add error handling at system boundaries (user input, network calls, file I/O) or when converting the error to a different type/format. Let internal errors propagate naturally.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Guardrail: Over-Engineering Slop
|
|
2
|
+
|
|
3
|
+
**Severity:** High (inflates maintenance burden)
|
|
4
|
+
**Category:** Code quality
|
|
5
|
+
|
|
6
|
+
## Pattern
|
|
7
|
+
|
|
8
|
+
Abstractions, patterns, and indirection that serve no current purpose. AI models love creating "clean architecture" with factories, interfaces, and wrappers that only have one implementation.
|
|
9
|
+
|
|
10
|
+
## Detection Rules
|
|
11
|
+
|
|
12
|
+
- Interface with only one implementation
|
|
13
|
+
- Factory that creates only one type
|
|
14
|
+
- Abstract class with only one subclass
|
|
15
|
+
- Wrapper that adds no behavior
|
|
16
|
+
- Configuration for values that never change
|
|
17
|
+
- Generic type parameter that's always the same concrete type
|
|
18
|
+
- "Strategy pattern" with one strategy
|
|
19
|
+
- Builder pattern for objects with 2-3 fields
|
|
20
|
+
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
### Slop
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// Factory for a single implementation
|
|
27
|
+
interface PaymentProcessor {
|
|
28
|
+
process(payment: Payment): Promise<Result>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class PaymentProcessorFactory {
|
|
32
|
+
static create(type: string): PaymentProcessor {
|
|
33
|
+
switch (type) {
|
|
34
|
+
case 'stripe': return new StripePaymentProcessor();
|
|
35
|
+
default: throw new Error(`Unknown type: ${type}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class StripePaymentProcessor implements PaymentProcessor {
|
|
41
|
+
async process(payment: Payment): Promise<Result> {
|
|
42
|
+
return stripe.charges.create({ amount: payment.amount });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Not Slop
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Direct call — add abstraction when you need a second processor
|
|
51
|
+
await stripe.charges.create({ amount: payment.amount });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Fix
|
|
55
|
+
|
|
56
|
+
Delete the abstraction. Call the thing directly. Add abstraction when (not before) you need it. If you have two implementations, then the interface earns its place.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Guardrail: Test Slop
|
|
2
|
+
|
|
3
|
+
**Severity:** High (creates false sense of coverage)
|
|
4
|
+
**Category:** Testing quality
|
|
5
|
+
|
|
6
|
+
## Pattern
|
|
7
|
+
|
|
8
|
+
Tests that pass but don't actually verify behavior. They inflate coverage metrics without catching bugs.
|
|
9
|
+
|
|
10
|
+
## Detection Rules
|
|
11
|
+
|
|
12
|
+
- `expect(x).toBeTruthy()` or `expect(x).toBeDefined()` — almost always slop
|
|
13
|
+
- Tests where the mock is the only thing being tested (you're testing your setup, not your code)
|
|
14
|
+
- Snapshot tests on simple/trivial components
|
|
15
|
+
- Tests with no assertions
|
|
16
|
+
- Tests that test framework behavior ("does React render a component?")
|
|
17
|
+
- Test name doesn't match what's actually being tested
|
|
18
|
+
- Copy-pasted tests with only minor variations (use parameterized tests)
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### Slop
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// Tests existence, not behavior
|
|
26
|
+
test('should create a user', () => {
|
|
27
|
+
const user = createUser({ name: 'Alice' });
|
|
28
|
+
expect(user).toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Tests the mock, not the code
|
|
32
|
+
test('should fetch user', async () => {
|
|
33
|
+
mockFetch.mockResolvedValue({ name: 'Alice' });
|
|
34
|
+
const user = await fetchUser(1);
|
|
35
|
+
expect(user.name).toBe('Alice'); // You told the mock to return this
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Snapshot of a trivial component
|
|
39
|
+
test('should render', () => {
|
|
40
|
+
const tree = renderer.create(<Button>Click</Button>);
|
|
41
|
+
expect(tree.toJSON()).toMatchSnapshot();
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Not Slop
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Tests specific behavior
|
|
49
|
+
test('createUser hashes password before storing', async () => {
|
|
50
|
+
const user = await createUser({ name: 'Alice', password: 'secret123' });
|
|
51
|
+
expect(user.passwordHash).not.toBe('secret123');
|
|
52
|
+
expect(await bcrypt.compare('secret123', user.passwordHash)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Tests real behavior with real dependency
|
|
56
|
+
test('fetchUser returns null for non-existent user', async () => {
|
|
57
|
+
const user = await fetchUser(999);
|
|
58
|
+
expect(user).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Tests meaningful interaction
|
|
62
|
+
test('Button calls onClick when clicked', () => {
|
|
63
|
+
const onClick = jest.fn();
|
|
64
|
+
render(<Button onClick={onClick}>Click</Button>);
|
|
65
|
+
fireEvent.click(screen.getByText('Click'));
|
|
66
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Fix
|
|
71
|
+
|
|
72
|
+
Delete tests that can't fail meaningfully. Rewrite to test actual behavior: specific values, specific side effects, specific error conditions. Ask: "Would this test catch a real bug?"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite Hook: Auto-lint after file writes
|
|
3
|
+
# Event: PostToolUse (Write|Edit)
|
|
4
|
+
# Reads the tool input from stdin, extracts the file path, runs the appropriate linter.
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
8
|
+
|
|
9
|
+
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
EXT="${FILE_PATH##*.}"
|
|
14
|
+
|
|
15
|
+
case "$EXT" in
|
|
16
|
+
ts|tsx|js|jsx|mjs|cjs)
|
|
17
|
+
# Try prettier first, fall back to eslint
|
|
18
|
+
if command -v npx &>/dev/null; then
|
|
19
|
+
npx prettier --write "$FILE_PATH" 2>/dev/null || true
|
|
20
|
+
fi
|
|
21
|
+
;;
|
|
22
|
+
py)
|
|
23
|
+
if command -v ruff &>/dev/null; then
|
|
24
|
+
ruff format "$FILE_PATH" 2>/dev/null || true
|
|
25
|
+
elif command -v black &>/dev/null; then
|
|
26
|
+
black --quiet "$FILE_PATH" 2>/dev/null || true
|
|
27
|
+
fi
|
|
28
|
+
;;
|
|
29
|
+
go)
|
|
30
|
+
if command -v gofmt &>/dev/null; then
|
|
31
|
+
gofmt -w "$FILE_PATH" 2>/dev/null || true
|
|
32
|
+
fi
|
|
33
|
+
;;
|
|
34
|
+
rs)
|
|
35
|
+
if command -v rustfmt &>/dev/null; then
|
|
36
|
+
rustfmt "$FILE_PATH" 2>/dev/null || true
|
|
37
|
+
fi
|
|
38
|
+
;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite Hook: Block dangerous bash commands
|
|
3
|
+
# Event: PreToolUse (Bash)
|
|
4
|
+
# Blocks rm -rf, DROP TABLE, force push to main, etc.
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
|
8
|
+
|
|
9
|
+
if [ -z "$COMMAND" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Block patterns
|
|
14
|
+
if echo "$COMMAND" | grep -qEi "rm\s+-rf\s+/|rm\s+-rf\s+~|rm\s+-rf\s+\.\s*$"; then
|
|
15
|
+
echo "Blocked: recursive delete of root, home, or current directory" >&2
|
|
16
|
+
exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if echo "$COMMAND" | grep -qEi "drop\s+(table|database)|truncate\s+table"; then
|
|
20
|
+
echo "Blocked: destructive database operation — get human approval first" >&2
|
|
21
|
+
exit 2
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if echo "$COMMAND" | grep -qEi "git\s+push\s+.*--force.*\s+(main|master)"; then
|
|
25
|
+
echo "Blocked: force push to main/master" >&2
|
|
26
|
+
exit 2
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if echo "$COMMAND" | grep -qEi "git\s+reset\s+--hard\s+origin"; then
|
|
30
|
+
echo "Blocked: hard reset to origin — this discards local work" >&2
|
|
31
|
+
exit 2
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
exit 0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite Hook: Check if a file being modified is in a danger zone
|
|
3
|
+
# Event: PreToolUse (Edit|Write)
|
|
4
|
+
# If the file appears in DANGER-ZONES.md, warns Claude via systemMessage.
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
8
|
+
|
|
9
|
+
if [ -z "$FILE_PATH" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
DANGER_FILE="DANGER-ZONES.md"
|
|
14
|
+
if [ ! -f "$DANGER_FILE" ]; then
|
|
15
|
+
# Check project root
|
|
16
|
+
DANGER_FILE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}/DANGER-ZONES.md"
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [ ! -f "$DANGER_FILE" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Get just the filename/relative path to search for
|
|
24
|
+
BASENAME=$(basename "$FILE_PATH")
|
|
25
|
+
RELPATH=$(realpath --relative-to="${CLAUDE_PROJECT_DIR:-.}" "$FILE_PATH" 2>/dev/null || echo "$FILE_PATH")
|
|
26
|
+
|
|
27
|
+
# Search for the file in DANGER-ZONES.md
|
|
28
|
+
MATCH=$(grep -i "$BASENAME\|$RELPATH" "$DANGER_FILE" 2>/dev/null)
|
|
29
|
+
|
|
30
|
+
if [ -n "$MATCH" ]; then
|
|
31
|
+
# File is in a danger zone — warn but don't block
|
|
32
|
+
cat <<EOF
|
|
33
|
+
{
|
|
34
|
+
"continue": true,
|
|
35
|
+
"systemMessage": "WARNING: This file is in a DANGER ZONE. Check DANGER-ZONES.md before proceeding. Relevant entry:\n\n${MATCH}\n\nConsider flagging this modification to the human before continuing."
|
|
36
|
+
}
|
|
37
|
+
EOF
|
|
38
|
+
else
|
|
39
|
+
echo '{"continue": true}'
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
exit 0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite Hook: Remind to review before ending session
|
|
3
|
+
# Event: Stop
|
|
4
|
+
# If there are uncommitted changes, reminds the user to run /review and /slop-check.
|
|
5
|
+
|
|
6
|
+
# Check for uncommitted changes
|
|
7
|
+
STAGED=$(git diff --cached --stat 2>/dev/null)
|
|
8
|
+
UNSTAGED=$(git diff --stat 2>/dev/null)
|
|
9
|
+
UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | head -5)
|
|
10
|
+
|
|
11
|
+
if [ -z "$STAGED" ] && [ -z "$UNSTAGED" ] && [ -z "$UNTRACKED" ]; then
|
|
12
|
+
# No changes — nothing to remind about
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Build a summary of what's pending
|
|
17
|
+
SUMMARY=""
|
|
18
|
+
if [ -n "$STAGED" ]; then
|
|
19
|
+
SUMMARY="Staged changes:\n$STAGED\n"
|
|
20
|
+
fi
|
|
21
|
+
if [ -n "$UNSTAGED" ]; then
|
|
22
|
+
SUMMARY="${SUMMARY}Unstaged changes:\n$UNSTAGED\n"
|
|
23
|
+
fi
|
|
24
|
+
if [ -n "$UNTRACKED" ]; then
|
|
25
|
+
SUMMARY="${SUMMARY}Untracked files:\n$UNTRACKED\n"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
cat <<EOF
|
|
29
|
+
{
|
|
30
|
+
"continue": true,
|
|
31
|
+
"systemMessage": "SESSION END REMINDER: There are uncommitted changes in the working tree.\n\n${SUMMARY}\nConsider running /review and /slop-check before committing."
|
|
32
|
+
}
|
|
33
|
+
EOF
|
|
34
|
+
|
|
35
|
+
exit 0
|