devflow-kit 1.1.0 → 1.3.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 -1
- package/README.md +23 -6
- package/dist/cli.js +2 -0
- package/dist/commands/ambient.js +5 -4
- package/dist/commands/init.js +4 -2
- package/dist/commands/memory.js +4 -4
- package/dist/commands/skills.d.ts +11 -0
- package/dist/commands/skills.js +116 -0
- package/dist/commands/uninstall.js +11 -1
- package/dist/plugins.js +67 -3
- package/dist/utils/installer.js +20 -2
- package/package.json +4 -2
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +22 -0
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +4 -2
- package/plugins/devflow-ambient/README.md +8 -8
- package/plugins/devflow-ambient/commands/ambient.md +14 -14
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +16 -9
- package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +6 -2
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +13 -6
- 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-code-review/skills/architecture-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/complexity-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/consistency-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/database-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/dependencies-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/documentation-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/performance-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/regression-patterns/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/review-methodology/SKILL.md +1 -1
- package/plugins/devflow-code-review/skills/security-patterns/SKILL.md +1 -1
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +10 -7
- package/plugins/devflow-core-skills/skills/test-driven-development/SKILL.md +5 -8
- package/plugins/devflow-debug/.claude-plugin/plugin.json +10 -3
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +22 -0
- package/plugins/devflow-go/.claude-plugin/plugin.json +22 -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 +19 -5
- package/plugins/devflow-implement/agents/coder.md +11 -6
- package/plugins/devflow-implement/skills/self-review/SKILL.md +1 -1
- package/plugins/devflow-java/.claude-plugin/plugin.json +22 -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 +22 -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 +22 -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 +13 -3
- package/plugins/devflow-resolve/skills/security-patterns/SKILL.md +1 -1
- package/plugins/devflow-rust/.claude-plugin/plugin.json +22 -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 +10 -3
- package/plugins/devflow-self-review/skills/self-review/SKILL.md +1 -1
- package/plugins/devflow-specify/.claude-plugin/plugin.json +15 -4
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +22 -0
- package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
- package/scripts/hooks/{ambient-prompt.sh → ambient-prompt} +4 -4
- package/scripts/hooks/{background-memory-update.sh → background-memory-update} +3 -3
- package/scripts/hooks/{ensure-memory-gitignore.sh → ensure-memory-gitignore} +1 -1
- package/scripts/hooks/{pre-compact-memory.sh → pre-compact-memory} +2 -2
- package/scripts/hooks/run-hook +23 -0
- package/scripts/hooks/session-start-memory +151 -0
- package/scripts/hooks/{stop-update-memory.sh → stop-update-memory} +4 -4
- package/shared/agents/coder.md +11 -6
- package/shared/agents/reviewer.md +8 -0
- package/shared/skills/ambient-router/SKILL.md +16 -9
- package/shared/skills/ambient-router/references/skill-catalog.md +6 -2
- package/shared/skills/architecture-patterns/SKILL.md +1 -1
- package/shared/skills/complexity-patterns/SKILL.md +1 -1
- package/shared/skills/consistency-patterns/SKILL.md +1 -1
- package/shared/skills/database-patterns/SKILL.md +1 -1
- package/shared/skills/dependencies-patterns/SKILL.md +1 -1
- package/shared/skills/documentation-patterns/SKILL.md +1 -1
- 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/performance-patterns/SKILL.md +1 -1
- 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/regression-patterns/SKILL.md +1 -1
- package/shared/skills/review-methodology/SKILL.md +1 -1
- 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/security-patterns/SKILL.md +1 -1
- package/shared/skills/self-review/SKILL.md +1 -1
- package/shared/skills/test-driven-development/SKILL.md +5 -8
- package/shared/skills/typescript/references/patterns.md +3 -3
- package/src/templates/settings.json +3 -3
- 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/scripts/hooks/session-start-memory.sh +0 -126
- /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,242 @@
|
|
|
1
|
+
# Rust Ownership Deep Dive
|
|
2
|
+
|
|
3
|
+
Advanced ownership patterns, lifetime elision, interior mutability, and pinning.
|
|
4
|
+
|
|
5
|
+
## Lifetime Elision Rules
|
|
6
|
+
|
|
7
|
+
The compiler applies three rules to infer lifetimes. When they don't resolve, annotate manually.
|
|
8
|
+
|
|
9
|
+
### Rule 1: Each Reference Parameter Gets Its Own Lifetime
|
|
10
|
+
|
|
11
|
+
```rust
|
|
12
|
+
// Compiler sees: fn first(s: &str) -> &str
|
|
13
|
+
// Compiler infers: fn first<'a>(s: &'a str) -> &'a str
|
|
14
|
+
fn first(s: &str) -> &str {
|
|
15
|
+
&s[..1]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Rule 2: Single Input Lifetime Applies to All Outputs
|
|
20
|
+
|
|
21
|
+
```rust
|
|
22
|
+
// One input reference — output borrows from it
|
|
23
|
+
fn trim(s: &str) -> &str {
|
|
24
|
+
s.trim()
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Rule 3: &self Lifetime Applies to All Outputs in Methods
|
|
29
|
+
|
|
30
|
+
```rust
|
|
31
|
+
impl Config {
|
|
32
|
+
// &self lifetime flows to return
|
|
33
|
+
fn database_url(&self) -> &str {
|
|
34
|
+
&self.db_url
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### When Elision Fails
|
|
40
|
+
|
|
41
|
+
```rust
|
|
42
|
+
// Two input lifetimes — compiler can't decide which output borrows from
|
|
43
|
+
// Must annotate: output borrows from `a`, not `b`
|
|
44
|
+
fn longest<'a>(a: &'a str, b: &str) -> &'a str {
|
|
45
|
+
if a.len() >= b.len() { a } else { a }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Interior Mutability
|
|
52
|
+
|
|
53
|
+
Mutate data behind a shared reference when ownership rules are too strict.
|
|
54
|
+
|
|
55
|
+
### Cell — Copy Types Only
|
|
56
|
+
|
|
57
|
+
```rust
|
|
58
|
+
use std::cell::Cell;
|
|
59
|
+
|
|
60
|
+
struct Counter {
|
|
61
|
+
count: Cell<u32>, // Mutate through &self
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
impl Counter {
|
|
65
|
+
fn increment(&self) {
|
|
66
|
+
self.count.set(self.count.get() + 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### RefCell — Runtime Borrow Checking
|
|
72
|
+
|
|
73
|
+
```rust
|
|
74
|
+
use std::cell::RefCell;
|
|
75
|
+
|
|
76
|
+
struct Cache {
|
|
77
|
+
data: RefCell<HashMap<String, String>>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl Cache {
|
|
81
|
+
fn get_or_insert(&self, key: &str, value: &str) -> String {
|
|
82
|
+
let mut data = self.data.borrow_mut(); // Panics if already borrowed
|
|
83
|
+
data.entry(key.to_string())
|
|
84
|
+
.or_insert_with(|| value.to_string())
|
|
85
|
+
.clone()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Mutex — Thread-Safe Interior Mutability
|
|
91
|
+
|
|
92
|
+
```rust
|
|
93
|
+
use std::sync::Mutex;
|
|
94
|
+
|
|
95
|
+
struct SharedState {
|
|
96
|
+
data: Mutex<Vec<String>>,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
impl SharedState {
|
|
100
|
+
fn push(&self, item: String) -> Result<(), AppError> {
|
|
101
|
+
let mut data = self.data.lock()
|
|
102
|
+
.map_err(|_| AppError::LockPoisoned)?;
|
|
103
|
+
data.push(item);
|
|
104
|
+
Ok(())
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Decision Guide
|
|
110
|
+
|
|
111
|
+
| Type | Thread-Safe | Cost | Use Case |
|
|
112
|
+
|------|-------------|------|----------|
|
|
113
|
+
| `Cell<T>` | No | Zero | Copy types, single-threaded |
|
|
114
|
+
| `RefCell<T>` | No | Runtime borrow check | Non-Copy, single-threaded |
|
|
115
|
+
| `Mutex<T>` | Yes | Lock overhead | Multi-threaded mutation |
|
|
116
|
+
| `RwLock<T>` | Yes | Lock overhead | Multi-threaded, read-heavy |
|
|
117
|
+
| `Atomic*` | Yes | Hardware atomic | Counters, flags |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Cow — Clone on Write
|
|
122
|
+
|
|
123
|
+
Defer cloning until mutation is actually needed.
|
|
124
|
+
|
|
125
|
+
```rust
|
|
126
|
+
use std::borrow::Cow;
|
|
127
|
+
|
|
128
|
+
// Returns borrowed if no processing needed, owned if modified
|
|
129
|
+
fn normalize_path(path: &str) -> Cow<'_, str> {
|
|
130
|
+
if path.contains("//") {
|
|
131
|
+
Cow::Owned(path.replace("//", "/"))
|
|
132
|
+
} else {
|
|
133
|
+
Cow::Borrowed(path)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Function accepts both owned and borrowed transparently
|
|
138
|
+
fn process(input: Cow<'_, str>) {
|
|
139
|
+
println!("{}", input); // No allocation if already borrowed
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Cow in APIs
|
|
144
|
+
|
|
145
|
+
```rust
|
|
146
|
+
// Accept Cow for flexible ownership — caller decides allocation
|
|
147
|
+
pub fn log_message(msg: Cow<'_, str>) {
|
|
148
|
+
eprintln!("[LOG] {}", msg);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Caller with borrowed data — zero-copy
|
|
152
|
+
log_message(Cow::Borrowed("static message"));
|
|
153
|
+
|
|
154
|
+
// Caller with owned data — no extra clone
|
|
155
|
+
log_message(Cow::Owned(format!("dynamic: {}", value)));
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Pin for Async and Self-Referential Types
|
|
161
|
+
|
|
162
|
+
### Why Pin Exists
|
|
163
|
+
|
|
164
|
+
Self-referential structs break if moved in memory. `Pin` guarantees the value won't move.
|
|
165
|
+
|
|
166
|
+
```rust
|
|
167
|
+
use std::pin::Pin;
|
|
168
|
+
use std::future::Future;
|
|
169
|
+
|
|
170
|
+
// Async functions return self-referential futures
|
|
171
|
+
// Pin ensures the future stays in place while polled
|
|
172
|
+
fn fetch_data(url: &str) -> Pin<Box<dyn Future<Output = Result<Data, Error>> + '_>> {
|
|
173
|
+
Box::pin(async move {
|
|
174
|
+
let response = reqwest::get(url).await?;
|
|
175
|
+
let data = response.json::<Data>().await?;
|
|
176
|
+
Ok(data)
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Pin in Practice
|
|
182
|
+
|
|
183
|
+
```rust
|
|
184
|
+
use tokio::pin;
|
|
185
|
+
|
|
186
|
+
async fn process_stream(stream: impl Stream<Item = Data>) {
|
|
187
|
+
// pin! macro pins the stream to the stack
|
|
188
|
+
pin!(stream);
|
|
189
|
+
|
|
190
|
+
while let Some(item) = stream.next().await {
|
|
191
|
+
handle(item).await;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### When You Need Pin
|
|
197
|
+
|
|
198
|
+
| Scenario | Need Pin? |
|
|
199
|
+
|----------|-----------|
|
|
200
|
+
| Returning `async` blocks as trait objects | Yes |
|
|
201
|
+
| Implementing `Future` manually | Yes |
|
|
202
|
+
| Using `tokio::select!` on futures | Yes (automatically handled) |
|
|
203
|
+
| Normal async/await | No (compiler handles it) |
|
|
204
|
+
| Storing futures in collections | Yes (`Pin<Box<dyn Future>>`) |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Ownership Transfer Patterns
|
|
209
|
+
|
|
210
|
+
### Take Pattern — Move Out of Option
|
|
211
|
+
|
|
212
|
+
```rust
|
|
213
|
+
struct Connection {
|
|
214
|
+
session: Option<Session>,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
impl Connection {
|
|
218
|
+
fn close(&mut self) -> Option<Session> {
|
|
219
|
+
self.session.take() // Moves out, leaves None
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Swap Pattern — Replace In Place
|
|
225
|
+
|
|
226
|
+
```rust
|
|
227
|
+
use std::mem;
|
|
228
|
+
|
|
229
|
+
fn rotate_buffer(current: &mut Vec<u8>, new_data: Vec<u8>) -> Vec<u8> {
|
|
230
|
+
mem::replace(current, new_data) // Returns old, installs new
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Entry Pattern — Conditional Insertion
|
|
235
|
+
|
|
236
|
+
```rust
|
|
237
|
+
use std::collections::HashMap;
|
|
238
|
+
|
|
239
|
+
fn get_or_create(map: &mut HashMap<String, Vec<Item>>, key: &str) -> &mut Vec<Item> {
|
|
240
|
+
map.entry(key.to_string()).or_insert_with(Vec::new)
|
|
241
|
+
}
|
|
242
|
+
```
|
|
@@ -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
|
+
```
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devflow-self-review",
|
|
3
3
|
"description": "Self-review workflow: Simplifier + Scrutinizer for code quality",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"agents": [
|
|
6
|
-
|
|
4
|
+
"version": "1.3.0",
|
|
5
|
+
"agents": [
|
|
6
|
+
"simplifier",
|
|
7
|
+
"scrutinizer",
|
|
8
|
+
"validator"
|
|
9
|
+
],
|
|
10
|
+
"skills": [
|
|
11
|
+
"self-review",
|
|
12
|
+
"core-patterns"
|
|
13
|
+
]
|
|
7
14
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: self-review
|
|
3
|
-
description:
|
|
3
|
+
description: This skill should be used when evaluating implementation quality before submission, checking correctness, security, and simplicity.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
allowed-tools: Read, Grep, Glob, Edit, Write, Bash
|
|
6
6
|
---
|
|
@@ -5,11 +5,22 @@
|
|
|
5
5
|
"name": "DevFlow Contributors",
|
|
6
6
|
"email": "dean@keren.dev"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.3.0",
|
|
9
9
|
"homepage": "https://github.com/dean0x/devflow",
|
|
10
10
|
"repository": "https://github.com/dean0x/devflow",
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"keywords": [
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
"keywords": [
|
|
13
|
+
"specification",
|
|
14
|
+
"requirements",
|
|
15
|
+
"planning",
|
|
16
|
+
"issues",
|
|
17
|
+
"github"
|
|
18
|
+
],
|
|
19
|
+
"agents": [
|
|
20
|
+
"skimmer",
|
|
21
|
+
"synthesizer"
|
|
22
|
+
],
|
|
23
|
+
"skills": [
|
|
24
|
+
"agent-teams"
|
|
25
|
+
]
|
|
15
26
|
}
|