devflow-kit 1.0.0 → 1.2.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/CHANGELOG.md +69 -0
- package/README.md +35 -11
- package/dist/cli.js +5 -1
- package/dist/commands/ambient.d.ts +18 -0
- package/dist/commands/ambient.js +136 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +97 -10
- package/dist/commands/memory.d.ts +22 -0
- package/dist/commands/memory.js +175 -0
- package/dist/commands/uninstall.js +72 -5
- package/dist/plugins.js +74 -3
- package/dist/utils/post-install.d.ts +12 -0
- package/dist/utils/post-install.js +82 -1
- package/dist/utils/safe-delete-install.d.ts +7 -0
- package/dist/utils/safe-delete-install.js +40 -5
- package/package.json +2 -1
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +7 -0
- package/plugins/devflow-ambient/README.md +49 -0
- package/plugins/devflow-ambient/commands/ambient.md +110 -0
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +89 -0
- package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +68 -0
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -4
- package/plugins/devflow-code-review/agents/reviewer.md +8 -0
- package/plugins/devflow-code-review/commands/code-review-teams.md +11 -1
- package/plugins/devflow-code-review/commands/code-review.md +12 -2
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +3 -6
- package/plugins/devflow-core-skills/skills/docs-framework/SKILL.md +10 -6
- package/plugins/devflow-core-skills/skills/test-driven-development/SKILL.md +139 -0
- package/plugins/devflow-core-skills/skills/test-driven-development/references/rationalization-prevention.md +111 -0
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/skills/go/SKILL.md +187 -0
- package/plugins/devflow-go/skills/go/references/concurrency.md +312 -0
- package/plugins/devflow-go/skills/go/references/detection.md +129 -0
- package/plugins/devflow-go/skills/go/references/patterns.md +232 -0
- package/plugins/devflow-go/skills/go/references/violations.md +205 -0
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -3
- package/plugins/devflow-implement/agents/coder.md +11 -6
- package/plugins/devflow-java/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-java/skills/java/SKILL.md +183 -0
- package/plugins/devflow-java/skills/java/references/detection.md +120 -0
- package/plugins/devflow-java/skills/java/references/modern-java.md +270 -0
- package/plugins/devflow-java/skills/java/references/patterns.md +235 -0
- package/plugins/devflow-java/skills/java/references/violations.md +213 -0
- package/plugins/devflow-python/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-python/skills/python/SKILL.md +188 -0
- package/plugins/devflow-python/skills/python/references/async.md +220 -0
- package/plugins/devflow-python/skills/python/references/detection.md +128 -0
- package/plugins/devflow-python/skills/python/references/patterns.md +226 -0
- package/plugins/devflow-python/skills/python/references/violations.md +204 -0
- package/plugins/devflow-react/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/SKILL.md +1 -1
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/references/patterns.md +3 -3
- package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-rust/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-rust/skills/rust/SKILL.md +193 -0
- package/plugins/devflow-rust/skills/rust/references/detection.md +131 -0
- package/plugins/devflow-rust/skills/rust/references/ownership.md +242 -0
- package/plugins/devflow-rust/skills/rust/references/patterns.md +210 -0
- package/plugins/devflow-rust/skills/rust/references/violations.md +191 -0
- package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
- package/scripts/hooks/ambient-prompt.sh +48 -0
- package/scripts/hooks/background-memory-update.sh +49 -8
- package/scripts/hooks/ensure-memory-gitignore.sh +17 -0
- package/scripts/hooks/pre-compact-memory.sh +12 -6
- package/scripts/hooks/session-start-memory.sh +50 -8
- package/scripts/hooks/stop-update-memory.sh +10 -6
- package/shared/agents/coder.md +11 -6
- package/shared/agents/reviewer.md +8 -0
- package/shared/skills/ambient-router/SKILL.md +89 -0
- package/shared/skills/ambient-router/references/skill-catalog.md +68 -0
- package/shared/skills/docs-framework/SKILL.md +10 -6
- package/shared/skills/go/SKILL.md +187 -0
- package/shared/skills/go/references/concurrency.md +312 -0
- package/shared/skills/go/references/detection.md +129 -0
- package/shared/skills/go/references/patterns.md +232 -0
- package/shared/skills/go/references/violations.md +205 -0
- package/shared/skills/java/SKILL.md +183 -0
- package/shared/skills/java/references/detection.md +120 -0
- package/shared/skills/java/references/modern-java.md +270 -0
- package/shared/skills/java/references/patterns.md +235 -0
- package/shared/skills/java/references/violations.md +213 -0
- package/shared/skills/python/SKILL.md +188 -0
- package/shared/skills/python/references/async.md +220 -0
- package/shared/skills/python/references/detection.md +128 -0
- package/shared/skills/python/references/patterns.md +226 -0
- package/shared/skills/python/references/violations.md +204 -0
- package/shared/skills/react/SKILL.md +1 -1
- package/shared/skills/react/references/patterns.md +3 -3
- package/shared/skills/rust/SKILL.md +193 -0
- package/shared/skills/rust/references/detection.md +131 -0
- package/shared/skills/rust/references/ownership.md +242 -0
- package/shared/skills/rust/references/patterns.md +210 -0
- package/shared/skills/rust/references/violations.md +191 -0
- package/shared/skills/test-driven-development/SKILL.md +139 -0
- package/shared/skills/test-driven-development/references/rationalization-prevention.md +111 -0
- package/shared/skills/typescript/references/patterns.md +3 -3
- package/src/templates/managed-settings.json +14 -0
- package/plugins/devflow-code-review/skills/react/SKILL.md +0 -276
- package/plugins/devflow-code-review/skills/react/references/patterns.md +0 -1331
- package/plugins/devflow-core-skills/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-core-skills/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-core-skills/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-core-skills/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-core-skills/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-core-skills/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-core-skills/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-core-skills/skills/frontend-design/references/violations.md +0 -453
- package/plugins/devflow-core-skills/skills/react/references/violations.md +0 -565
- package/plugins/devflow-implement/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-implement/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-implement/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-implement/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-implement/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-implement/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-implement/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-implement/skills/frontend-design/references/violations.md +0 -453
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-react}/skills/react/references/violations.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/SKILL.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/violations.md +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Rust Extended Patterns
|
|
2
|
+
|
|
3
|
+
Extended correct patterns for Rust. Reference from main SKILL.md.
|
|
4
|
+
|
|
5
|
+
## Typestate Pattern
|
|
6
|
+
|
|
7
|
+
Encode valid state transitions in the type system so invalid sequences don't compile.
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// States are zero-sized types — no runtime cost
|
|
11
|
+
struct Draft;
|
|
12
|
+
struct Published;
|
|
13
|
+
struct Archived;
|
|
14
|
+
|
|
15
|
+
struct Article<State> {
|
|
16
|
+
title: String,
|
|
17
|
+
body: String,
|
|
18
|
+
_state: std::marker::PhantomData<State>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Article<Draft> {
|
|
22
|
+
pub fn new(title: String, body: String) -> Self {
|
|
23
|
+
Article { title, body, _state: std::marker::PhantomData }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub fn publish(self) -> Article<Published> {
|
|
27
|
+
Article { title: self.title, body: self.body, _state: std::marker::PhantomData }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl Article<Published> {
|
|
32
|
+
pub fn archive(self) -> Article<Archived> {
|
|
33
|
+
Article { title: self.title, body: self.body, _state: std::marker::PhantomData }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// article.archive() on Draft won't compile — transition enforced at compile time
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Error Handling Hierarchy
|
|
43
|
+
|
|
44
|
+
Layer errors from specific to general using `thiserror` for libraries and `anyhow` for applications.
|
|
45
|
+
|
|
46
|
+
```rust
|
|
47
|
+
// Library: precise, typed errors
|
|
48
|
+
#[derive(thiserror::Error, Debug)]
|
|
49
|
+
pub enum RepoError {
|
|
50
|
+
#[error("entity {entity} with id {id} not found")]
|
|
51
|
+
NotFound { entity: &'static str, id: String },
|
|
52
|
+
#[error("duplicate key: {0}")]
|
|
53
|
+
Duplicate(String),
|
|
54
|
+
#[error("connection failed")]
|
|
55
|
+
Connection(#[from] sqlx::Error),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Application: ergonomic error propagation
|
|
59
|
+
use anyhow::{Context, Result};
|
|
60
|
+
|
|
61
|
+
fn run() -> Result<()> {
|
|
62
|
+
let config = load_config()
|
|
63
|
+
.context("failed to load configuration")?;
|
|
64
|
+
let db = connect_db(&config.database_url)
|
|
65
|
+
.context("failed to connect to database")?;
|
|
66
|
+
serve(db, config.port)
|
|
67
|
+
.context("server exited with error")
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Trait Objects vs Generics
|
|
74
|
+
|
|
75
|
+
### Use Generics for Performance (Monomorphization)
|
|
76
|
+
|
|
77
|
+
```rust
|
|
78
|
+
fn largest<T: PartialOrd>(list: &[T]) -> Option<&T> {
|
|
79
|
+
list.iter().reduce(|a, b| if a >= b { a } else { b })
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Use Trait Objects for Heterogeneous Collections
|
|
84
|
+
|
|
85
|
+
```rust
|
|
86
|
+
trait Handler: Send + Sync {
|
|
87
|
+
fn handle(&self, request: &Request) -> Response;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
struct Router {
|
|
91
|
+
routes: Vec<Box<dyn Handler>>, // Different concrete types in one Vec
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Decision Guide
|
|
96
|
+
|
|
97
|
+
| Criteria | Generics | Trait Objects |
|
|
98
|
+
|----------|----------|--------------|
|
|
99
|
+
| Known types at compile time | Yes | No |
|
|
100
|
+
| Heterogeneous collection | No | Yes |
|
|
101
|
+
| Performance-critical | Yes | Acceptable overhead |
|
|
102
|
+
| Binary size concern | Increases | Minimal |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Smart Pointers
|
|
107
|
+
|
|
108
|
+
### Box — Heap Allocation
|
|
109
|
+
|
|
110
|
+
```rust
|
|
111
|
+
// Recursive types require indirection
|
|
112
|
+
enum List<T> {
|
|
113
|
+
Cons(T, Box<List<T>>),
|
|
114
|
+
Nil,
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Rc/Arc — Shared Ownership
|
|
119
|
+
|
|
120
|
+
```rust
|
|
121
|
+
use std::sync::Arc;
|
|
122
|
+
|
|
123
|
+
// Shared read-only config across threads
|
|
124
|
+
let config = Arc::new(load_config()?);
|
|
125
|
+
let config_clone = Arc::clone(&config);
|
|
126
|
+
tokio::spawn(async move {
|
|
127
|
+
use_config(&config_clone).await;
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### When to Use Each
|
|
132
|
+
|
|
133
|
+
| Pointer | Use Case |
|
|
134
|
+
|---------|----------|
|
|
135
|
+
| `Box<T>` | Single owner, heap allocation, recursive types |
|
|
136
|
+
| `Rc<T>` | Multiple owners, single-threaded |
|
|
137
|
+
| `Arc<T>` | Multiple owners, multi-threaded |
|
|
138
|
+
| `Cow<'a, T>` | Clone-on-write, flexible borrowing |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## From/Into Conversions
|
|
143
|
+
|
|
144
|
+
```rust
|
|
145
|
+
// Implement From for automatic Into
|
|
146
|
+
impl From<CreateUserRequest> for User {
|
|
147
|
+
fn from(req: CreateUserRequest) -> Self {
|
|
148
|
+
User {
|
|
149
|
+
id: Uuid::new_v4(),
|
|
150
|
+
name: req.name,
|
|
151
|
+
email: req.email,
|
|
152
|
+
created_at: Utc::now(),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Callers get Into for free
|
|
158
|
+
fn save_user(user: impl Into<User>) -> Result<(), DbError> {
|
|
159
|
+
let user: User = user.into();
|
|
160
|
+
// ...
|
|
161
|
+
Ok(())
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Derive and Trait Best Practices
|
|
168
|
+
|
|
169
|
+
```rust
|
|
170
|
+
// Derive the standard set for data types
|
|
171
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
172
|
+
pub struct UserId(String);
|
|
173
|
+
|
|
174
|
+
// Derive serde for serialization boundaries
|
|
175
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
176
|
+
pub struct ApiResponse<T> {
|
|
177
|
+
pub data: T,
|
|
178
|
+
pub metadata: Metadata,
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Trait Implementation Order Convention
|
|
183
|
+
|
|
184
|
+
1. Standard library traits (`Debug`, `Display`, `Clone`, `PartialEq`)
|
|
185
|
+
2. Conversion traits (`From`, `Into`, `TryFrom`)
|
|
186
|
+
3. Iterator traits (`Iterator`, `IntoIterator`)
|
|
187
|
+
4. Serde traits (`Serialize`, `Deserialize`)
|
|
188
|
+
5. Custom traits (domain-specific)
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Module Organization
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
src/
|
|
196
|
+
├── lib.rs # Public API re-exports
|
|
197
|
+
├── error.rs # Crate-level error types
|
|
198
|
+
├── domain/
|
|
199
|
+
│ ├── mod.rs # Domain re-exports
|
|
200
|
+
│ ├── user.rs # User entity and logic
|
|
201
|
+
│ └── order.rs # Order entity and logic
|
|
202
|
+
├── repo/
|
|
203
|
+
│ ├── mod.rs # Repository trait definitions
|
|
204
|
+
│ └── postgres.rs # Concrete implementation
|
|
205
|
+
└── api/
|
|
206
|
+
├── mod.rs # Route registration
|
|
207
|
+
└── handlers.rs # HTTP handlers
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Keep `lib.rs` thin — re-export only the public API. Internal modules use `pub(crate)`.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Rust Violation Examples
|
|
2
|
+
|
|
3
|
+
Extended violation patterns for Rust reviews. Reference from main SKILL.md.
|
|
4
|
+
|
|
5
|
+
## Unwrap Abuse
|
|
6
|
+
|
|
7
|
+
### Unwrap in Library Code
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// VIOLATION: Panics on None — caller has no way to handle failure
|
|
11
|
+
pub fn get_username(users: &HashMap<u64, String>, id: u64) -> &str {
|
|
12
|
+
users.get(&id).unwrap() // Panics if id not found
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// VIOLATION: Unwrap on parse without context
|
|
16
|
+
let port: u16 = std::env::var("PORT").unwrap().parse().unwrap();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Expect Without Useful Message
|
|
20
|
+
|
|
21
|
+
```rust
|
|
22
|
+
// VIOLATION: Message doesn't help diagnose the problem
|
|
23
|
+
let config = load_config().expect("failed");
|
|
24
|
+
|
|
25
|
+
// CORRECT: Actionable message
|
|
26
|
+
let config = load_config().expect("failed to load config from config.toml — does file exist?");
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Unnecessary Cloning
|
|
32
|
+
|
|
33
|
+
### Clone to Satisfy Borrow Checker
|
|
34
|
+
|
|
35
|
+
```rust
|
|
36
|
+
// VIOLATION: Cloning to work around borrow issues
|
|
37
|
+
fn process_items(items: &Vec<Item>) {
|
|
38
|
+
let cloned = items.clone(); // Entire Vec cloned
|
|
39
|
+
for item in &cloned {
|
|
40
|
+
println!("{}", item.name);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// CORRECT: Just borrow
|
|
45
|
+
fn process_items(items: &[Item]) {
|
|
46
|
+
for item in items {
|
|
47
|
+
println!("{}", item.name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Clone in Hot Loop
|
|
53
|
+
|
|
54
|
+
```rust
|
|
55
|
+
// VIOLATION: Allocating on every iteration
|
|
56
|
+
for record in &records {
|
|
57
|
+
let key = record.id.clone(); // String allocation per iteration
|
|
58
|
+
map.insert(key, record);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// CORRECT: Borrow or use references
|
|
62
|
+
for record in &records {
|
|
63
|
+
map.insert(&record.id, record);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Stringly-Typed APIs
|
|
70
|
+
|
|
71
|
+
### String Where Enum Belongs
|
|
72
|
+
|
|
73
|
+
```rust
|
|
74
|
+
// VIOLATION: Any typo compiles and fails at runtime
|
|
75
|
+
fn set_status(status: &str) {
|
|
76
|
+
match status {
|
|
77
|
+
"active" => { /* ... */ }
|
|
78
|
+
"inactive" => { /* ... */ }
|
|
79
|
+
_ => panic!("unknown status"), // Runtime failure
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// CORRECT: Compiler enforces valid values
|
|
84
|
+
enum Status { Active, Inactive }
|
|
85
|
+
|
|
86
|
+
fn set_status(status: Status) {
|
|
87
|
+
match status {
|
|
88
|
+
Status::Active => { /* ... */ }
|
|
89
|
+
Status::Inactive => { /* ... */ }
|
|
90
|
+
} // Exhaustive — no default needed
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Unsafe Without Justification
|
|
97
|
+
|
|
98
|
+
### Bare Unsafe Block
|
|
99
|
+
|
|
100
|
+
```rust
|
|
101
|
+
// VIOLATION: No safety comment explaining invariants
|
|
102
|
+
unsafe {
|
|
103
|
+
let ptr = data.as_ptr();
|
|
104
|
+
std::ptr::copy_nonoverlapping(ptr, dest, len);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// CORRECT: Document why this is safe
|
|
108
|
+
// SAFETY: `data` is guaranteed to be valid for `len` bytes because
|
|
109
|
+
// it was allocated by `Vec::with_capacity(len)` and filled by `read_exact`.
|
|
110
|
+
// `dest` is a valid pointer from `alloc::alloc(layout)` with matching size.
|
|
111
|
+
unsafe {
|
|
112
|
+
std::ptr::copy_nonoverlapping(data.as_ptr(), dest, len);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Unnecessary Unsafe
|
|
117
|
+
|
|
118
|
+
```rust
|
|
119
|
+
// VIOLATION: Using unsafe when safe alternative exists
|
|
120
|
+
unsafe fn get_element(slice: &[u8], index: usize) -> u8 {
|
|
121
|
+
*slice.get_unchecked(index)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// CORRECT: Safe indexing with bounds check
|
|
125
|
+
fn get_element(slice: &[u8], index: usize) -> Option<u8> {
|
|
126
|
+
slice.get(index).copied()
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Ignoring Results
|
|
133
|
+
|
|
134
|
+
### Discarding Write Errors
|
|
135
|
+
|
|
136
|
+
```rust
|
|
137
|
+
// VIOLATION: Write failure silently ignored
|
|
138
|
+
let _ = file.write_all(data);
|
|
139
|
+
let _ = file.flush();
|
|
140
|
+
|
|
141
|
+
// CORRECT: Propagate errors
|
|
142
|
+
file.write_all(data)?;
|
|
143
|
+
file.flush()?;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Ignoring Lock Poisoning
|
|
147
|
+
|
|
148
|
+
```rust
|
|
149
|
+
// VIOLATION: Silently ignoring poisoned mutex
|
|
150
|
+
let guard = mutex.lock().unwrap_or_else(|e| e.into_inner());
|
|
151
|
+
|
|
152
|
+
// CORRECT: Handle or propagate the poison
|
|
153
|
+
let guard = mutex.lock().map_err(|_| AppError::LockPoisoned)?;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Concurrency Violations
|
|
159
|
+
|
|
160
|
+
### Shared Mutable State Without Synchronization
|
|
161
|
+
|
|
162
|
+
```rust
|
|
163
|
+
// VIOLATION: Data race potential — no synchronization
|
|
164
|
+
static mut COUNTER: u64 = 0;
|
|
165
|
+
|
|
166
|
+
fn increment() {
|
|
167
|
+
unsafe { COUNTER += 1; } // Undefined behavior under concurrency
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// CORRECT: Use atomic or mutex
|
|
171
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
172
|
+
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
173
|
+
|
|
174
|
+
fn increment() {
|
|
175
|
+
COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Blocking in Async Context
|
|
180
|
+
|
|
181
|
+
```rust
|
|
182
|
+
// VIOLATION: Blocks the async runtime thread
|
|
183
|
+
async fn read_file(path: &str) -> Result<String, io::Error> {
|
|
184
|
+
std::fs::read_to_string(path) // Blocking call in async fn
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// CORRECT: Use async file I/O or spawn_blocking
|
|
188
|
+
async fn read_file(path: &str) -> Result<String, io::Error> {
|
|
189
|
+
tokio::fs::read_to_string(path).await
|
|
190
|
+
}
|
|
191
|
+
```
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-driven-development
|
|
3
|
+
description: >-
|
|
4
|
+
Enforce RED-GREEN-REFACTOR cycle during implementation. Write failing tests before
|
|
5
|
+
production code. Distinct from test-patterns (which reviews test quality) — this
|
|
6
|
+
skill enforces the TDD workflow during code generation.
|
|
7
|
+
user-invocable: false
|
|
8
|
+
allowed-tools: Read, Grep, Glob
|
|
9
|
+
activation:
|
|
10
|
+
file-patterns:
|
|
11
|
+
- "**/*.ts"
|
|
12
|
+
- "**/*.tsx"
|
|
13
|
+
- "**/*.js"
|
|
14
|
+
- "**/*.jsx"
|
|
15
|
+
- "**/*.py"
|
|
16
|
+
exclude:
|
|
17
|
+
- "node_modules/**"
|
|
18
|
+
- "dist/**"
|
|
19
|
+
- "**/*.test.*"
|
|
20
|
+
- "**/*.spec.*"
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Test-Driven Development
|
|
24
|
+
|
|
25
|
+
Enforce the RED-GREEN-REFACTOR cycle for all implementation work. Tests define the design. Code satisfies the tests. Refactoring improves the design without changing behavior.
|
|
26
|
+
|
|
27
|
+
## Iron Law
|
|
28
|
+
|
|
29
|
+
> **TESTS FIRST, ALWAYS**
|
|
30
|
+
>
|
|
31
|
+
> Write the failing test before the production code. No exceptions. If you catch
|
|
32
|
+
> yourself writing production code without a failing test, stop immediately, delete
|
|
33
|
+
> the production code, write the test, watch it fail, then write the minimum code
|
|
34
|
+
> to make it pass. The test IS the specification.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## The Cycle
|
|
39
|
+
|
|
40
|
+
### Step 1: RED — Write a Failing Test
|
|
41
|
+
|
|
42
|
+
Write a test that describes the behavior you want. Run it. Watch it fail. The failure message IS your specification.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Describe what the code SHOULD do, not how it does it.
|
|
46
|
+
One behavior per test. One assertion per test (ideally).
|
|
47
|
+
Name tests as sentences: "returns error when email is invalid"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Checkpoint:** The test MUST fail before proceeding. A test that passes immediately proves nothing.
|
|
51
|
+
|
|
52
|
+
### Step 2: GREEN — Write Minimum Code to Pass
|
|
53
|
+
|
|
54
|
+
Write the simplest production code that makes the failing test pass. No more, no less.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Hardcode first if that's simplest. Generalize when the next test forces it.
|
|
58
|
+
Don't write code "you'll need later." Write code the test demands NOW.
|
|
59
|
+
Don't optimize. Don't refactor. Don't clean up. Just pass the test.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Checkpoint:** All tests pass. If any test fails, fix it before moving on.
|
|
63
|
+
|
|
64
|
+
### Step 3: REFACTOR — Improve Without Changing Behavior
|
|
65
|
+
|
|
66
|
+
Now clean up. Extract helpers, rename variables, simplify logic. Tests stay green throughout.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Run tests after every refactoring step.
|
|
70
|
+
If a test breaks during refactor, undo immediately — you changed behavior.
|
|
71
|
+
Apply DRY, extract patterns, improve readability.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Checkpoint:** All tests still pass. Code is clean. Repeat from Step 1 for next behavior.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Rationalization Prevention
|
|
79
|
+
|
|
80
|
+
These are the excuses developers use to skip TDD. Recognize and reject them.
|
|
81
|
+
|
|
82
|
+
| Excuse | Why It Feels Right | Why It's Wrong | Correct Action |
|
|
83
|
+
|--------|-------------------|---------------|----------------|
|
|
84
|
+
| "I'll write tests after" | Need to see the shape first | Tests ARE the shape — they define the interface before implementation exists | Write the test first |
|
|
85
|
+
| "Too simple to test" | It's just a getter/setter | Getters break, defaults change, edge cases hide in "simple" code | Write it — takes 30 seconds |
|
|
86
|
+
| "I'll refactor later" | Just get it working now | "Later" never comes; technical debt compounds silently | Refactor now in Step 3 |
|
|
87
|
+
| "Test is too hard to write" | Setup is complex, mocking is painful | Hard-to-test code = bad design; the test is telling you the interface is wrong | Simplify the interface first |
|
|
88
|
+
| "Need to see the whole picture" | Can't test what I haven't designed yet | TDD IS design; each test reveals the next piece of the interface | Let the test guide the design |
|
|
89
|
+
| "Tests slow me down" | Faster to just write the code | Faster until the first regression; TDD is faster for anything > 50 lines | Trust the cycle |
|
|
90
|
+
|
|
91
|
+
See `references/rationalization-prevention.md` for extended examples with code.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Process Enforcement
|
|
96
|
+
|
|
97
|
+
When implementing any feature under ambient BUILD/STANDARD:
|
|
98
|
+
|
|
99
|
+
1. **Identify the first behavior** — What is the simplest thing this feature must do?
|
|
100
|
+
2. **Write the test** — Describe that behavior as a failing test
|
|
101
|
+
3. **Run the test** — Confirm it fails (RED)
|
|
102
|
+
4. **Write minimum code** — Just enough to pass (GREEN)
|
|
103
|
+
5. **Refactor** — Clean up while tests stay green (REFACTOR)
|
|
104
|
+
6. **Repeat** — Next behavior, next test, next cycle
|
|
105
|
+
|
|
106
|
+
### File Organization
|
|
107
|
+
|
|
108
|
+
- Test file lives next to production file: `user.ts` → `user.test.ts`
|
|
109
|
+
- Follow project's existing test conventions (Jest, Vitest, pytest, etc.)
|
|
110
|
+
- Import the module under test, not internal helpers
|
|
111
|
+
|
|
112
|
+
### What to Test
|
|
113
|
+
|
|
114
|
+
| Test | Don't Test |
|
|
115
|
+
|------|-----------|
|
|
116
|
+
| Public API behavior | Private implementation details |
|
|
117
|
+
| Error conditions and edge cases | Framework internals |
|
|
118
|
+
| Integration points (boundaries) | Third-party library correctness |
|
|
119
|
+
| State transitions | Getter/setter plumbing (unless non-trivial) |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## When TDD Does Not Apply
|
|
124
|
+
|
|
125
|
+
- **QUICK depth** — Ambient classified as QUICK (chat, exploration, trivial edits)
|
|
126
|
+
- **Non-code tasks** — Documentation, configuration, CI changes
|
|
127
|
+
- **Exploratory prototyping** — User explicitly says "just spike this" or "prototype"
|
|
128
|
+
- **Existing test suite changes** — Modifying tests themselves (test-patterns skill applies instead)
|
|
129
|
+
|
|
130
|
+
When skipping TDD, never rationalize. State clearly: "Skipping TDD because: [specific reason from list above]."
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Integration with Ambient Mode
|
|
135
|
+
|
|
136
|
+
- **BUILD/STANDARD** → TDD enforced. Every new function/method gets test-first treatment.
|
|
137
|
+
- **BUILD/QUICK** → TDD skipped (trivial single-file edit).
|
|
138
|
+
- **BUILD/ESCALATE** → TDD mentioned in nudge toward `/implement`.
|
|
139
|
+
- **DEBUG/STANDARD** → TDD applies to the fix: write a test that reproduces the bug first, then fix.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# TDD Rationalization Prevention — Extended Examples
|
|
2
|
+
|
|
3
|
+
Detailed code examples showing how each rationalization leads to worse outcomes.
|
|
4
|
+
|
|
5
|
+
## "I'll write tests after"
|
|
6
|
+
|
|
7
|
+
### What happens:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// Developer writes production code first
|
|
11
|
+
function calculateDiscount(price: number, tier: string): number {
|
|
12
|
+
if (tier === 'gold') return price * 0.8;
|
|
13
|
+
if (tier === 'silver') return price * 0.9;
|
|
14
|
+
return price;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Then "writes tests after" — but only for the happy path they remember
|
|
18
|
+
test('gold tier gets 20% off', () => {
|
|
19
|
+
expect(calculateDiscount(100, 'gold')).toBe(80);
|
|
20
|
+
});
|
|
21
|
+
// Missing: negative prices, unknown tiers, zero prices, NaN handling
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### What TDD would have caught:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Test first — forces you to think about the contract
|
|
28
|
+
test('returns error for negative price', () => {
|
|
29
|
+
expect(calculateDiscount(-100, 'gold')).toEqual({ ok: false, error: 'NEGATIVE_PRICE' });
|
|
30
|
+
});
|
|
31
|
+
// Now the interface includes error handling from the start
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## "Too simple to test"
|
|
35
|
+
|
|
36
|
+
### What happens:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// "It's just a config getter, no test needed"
|
|
40
|
+
function getMaxRetries(): number {
|
|
41
|
+
return parseInt(process.env.MAX_RETRIES || '3');
|
|
42
|
+
}
|
|
43
|
+
// 6 months later: someone sets MAX_RETRIES="three" and prod crashes with NaN retries
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### What TDD would have caught:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
test('returns default when env var is not a number', () => {
|
|
50
|
+
process.env.MAX_RETRIES = 'three';
|
|
51
|
+
expect(getMaxRetries()).toBe(3); // Forces validation logic
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## "Test is too hard to write"
|
|
56
|
+
|
|
57
|
+
### What happens:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// "I can't test this easily because it needs database + email + filesystem"
|
|
61
|
+
async function processOrder(orderId: string) {
|
|
62
|
+
const db = new Database();
|
|
63
|
+
const order = await db.find(orderId);
|
|
64
|
+
await sendEmail(order.customerEmail, 'Your order is processing');
|
|
65
|
+
await fs.writeFile(`/invoices/${orderId}.pdf`, generateInvoice(order));
|
|
66
|
+
await db.update(orderId, { status: 'processing' });
|
|
67
|
+
}
|
|
68
|
+
// Result: untestable monolith, test would need real DB + email + filesystem
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### What TDD forces:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Hard-to-test = bad design. TDD forces dependency injection:
|
|
75
|
+
async function processOrder(
|
|
76
|
+
orderId: string,
|
|
77
|
+
deps: { db: OrderRepository; emailer: Emailer; invoices: InvoiceStore }
|
|
78
|
+
): Promise<Result<void, OrderError>> {
|
|
79
|
+
// Now trivially testable with mocks
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## "I'll refactor later"
|
|
84
|
+
|
|
85
|
+
### What happens:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Sprint 1: "just get it working"
|
|
89
|
+
function handleRequest(req: any) {
|
|
90
|
+
if (req.type === 'create') { /* 50 lines */ }
|
|
91
|
+
else if (req.type === 'update') { /* 50 lines */ }
|
|
92
|
+
else if (req.type === 'delete') { /* 30 lines */ }
|
|
93
|
+
// Sprint 2-10: more conditions added, function grows to 500 lines
|
|
94
|
+
// "Refactor later" never comes because nobody wants to touch it
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### What TDD enforces:
|
|
99
|
+
|
|
100
|
+
Step 3 (REFACTOR) happens every cycle. The function never grows beyond what's clean because you clean it every 5-10 minutes.
|
|
101
|
+
|
|
102
|
+
## "Tests slow me down"
|
|
103
|
+
|
|
104
|
+
### The math:
|
|
105
|
+
|
|
106
|
+
| Approach | Time to write | Time to first bug | Time to fix bug | Total (1 month) |
|
|
107
|
+
|----------|:---:|:---:|:---:|:---:|
|
|
108
|
+
| No TDD | 2h | 4h | 3h (no repro test) | 9h+ |
|
|
109
|
+
| TDD | 3h | Caught in test | 15min (test pinpoints) | 3h 15min |
|
|
110
|
+
|
|
111
|
+
TDD is slower for the first 30 minutes. It's faster for everything after that.
|
|
@@ -137,7 +137,7 @@ const isUndefined = (value: unknown): value is undefined =>
|
|
|
137
137
|
const isNullish = (value: unknown): value is null | undefined =>
|
|
138
138
|
value === null || value === undefined;
|
|
139
139
|
|
|
140
|
-
const isFunction = (value: unknown): value is
|
|
140
|
+
const isFunction = (value: unknown): value is (...args: unknown[]) => unknown =>
|
|
141
141
|
typeof value === 'function';
|
|
142
142
|
|
|
143
143
|
const isObject = (value: unknown): value is object =>
|
|
@@ -760,7 +760,7 @@ function debounce<T extends (...args: any[]) => any>(
|
|
|
760
760
|
fn: T,
|
|
761
761
|
delayMs: number
|
|
762
762
|
): (...args: Parameters<T>) => void {
|
|
763
|
-
let timeoutId:
|
|
763
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
764
764
|
|
|
765
765
|
return (...args: Parameters<T>) => {
|
|
766
766
|
if (timeoutId) clearTimeout(timeoutId);
|
|
@@ -774,7 +774,7 @@ function throttle<T extends (...args: any[]) => any>(
|
|
|
774
774
|
limitMs: number
|
|
775
775
|
): (...args: Parameters<T>) => void {
|
|
776
776
|
let lastRun = 0;
|
|
777
|
-
let timeoutId:
|
|
777
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
778
778
|
|
|
779
779
|
return (...args: Parameters<T>) => {
|
|
780
780
|
const now = Date.now();
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
"Bash(rm -rf ~*)",
|
|
6
6
|
"Bash(rm -rf .*)",
|
|
7
7
|
"Bash(* rm -rf /*)",
|
|
8
|
+
"Bash(rm -r /*)",
|
|
9
|
+
"Bash(rm -r ~*)",
|
|
10
|
+
"Bash(rm -r .*)",
|
|
11
|
+
"Bash(rm -fr /*)",
|
|
12
|
+
"Bash(rm -fr ~*)",
|
|
13
|
+
"Bash(rm -fr .*)",
|
|
14
|
+
"Bash(rm -f /*)",
|
|
15
|
+
"Bash(rm -f ~*)",
|
|
16
|
+
"Bash(rm -f .*)",
|
|
8
17
|
"Bash(dd if=*)",
|
|
9
18
|
"Bash(dd*of=/dev/*)",
|
|
10
19
|
"Bash(mkfs*)",
|
|
@@ -85,12 +94,17 @@
|
|
|
85
94
|
"Bash(crontab*)",
|
|
86
95
|
"Bash(rm /var/log*)",
|
|
87
96
|
"Bash(rm -rf /var/log*)",
|
|
97
|
+
"Bash(rm -r /var/log*)",
|
|
98
|
+
"Bash(rm -f /var/log*)",
|
|
99
|
+
"Bash(rm -fr /var/log*)",
|
|
88
100
|
"Bash(> /var/log*)",
|
|
89
101
|
"Bash(truncate /var/log*)",
|
|
90
102
|
"Bash(history -c*)",
|
|
91
103
|
"Bash(history -w*)",
|
|
92
104
|
"Bash(rm ~/.bash_history*)",
|
|
105
|
+
"Bash(rm -f ~/.bash_history*)",
|
|
93
106
|
"Bash(rm ~/.zsh_history*)",
|
|
107
|
+
"Bash(rm -f ~/.zsh_history*)",
|
|
94
108
|
"Bash(unset HISTFILE*)",
|
|
95
109
|
"Bash(curl 169.254.169.254*)",
|
|
96
110
|
"Bash(wget 169.254.169.254*)",
|