@minhduydev/mdpi 0.4.1 → 0.6.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/dist/index.js +4 -2
- package/dist/template/.pi/AGENTS.md +1 -1
- package/dist/template/.pi/README.md +2 -3
- package/dist/template/.pi/VERSION +1 -1
- package/dist/template/.pi/agents/explore.md +1 -1
- package/dist/template/.pi/agents/scout.md +1 -1
- package/dist/template/.pi/extensions/templates-injector.ts +35 -7
- package/dist/template/.pi/prompts/INDEX.md +3 -9
- package/dist/template/.pi/prompts/gc.md +2 -1
- package/dist/template/.pi/prompts/verify.md +24 -0
- package/dist/template/.pi/skills/INDEX.md +40 -8
- package/dist/template/.pi/skills/dcp-hygiene/SKILL.md +1 -1
- package/dist/template/.pi/skills/frontend-design/SKILL.md +1 -1
- package/dist/template/.pi/skills/frontend-design/references/animation/motion-advanced.md +88 -15
- package/dist/template/.pi/skills/frontend-design/references/animation/motion-core.md +148 -13
- package/dist/template/.pi/skills/frontend-design/references/shadcn/setup.md +127 -20
- package/dist/template/.pi/skills/nextjs-app-router/SKILL.md +334 -0
- package/dist/template/.pi/skills/nextjs-cache/SKILL.md +262 -0
- package/dist/template/.pi/skills/react-best-practices/SKILL.md +79 -1
- package/dist/template/.pi/skills/react-compiler/SKILL.md +237 -0
- package/dist/template/.pi/skills/react-hook-form/SKILL.md +374 -0
- package/dist/template/.pi/skills/react-server-actions/SKILL.md +299 -0
- package/dist/template/.pi/skills/shadcn-ui/SKILL.md +404 -0
- package/dist/template/.pi/skills/tanstack-query/SKILL.md +330 -0
- package/dist/template/.pi/skills/v0/SKILL.md +264 -0
- package/dist/template/.pi/skills/zustand/SKILL.md +333 -0
- package/package.json +1 -1
- package/dist/template/.pi/context/fallow.md +0 -137
- package/dist/template/.pi/prompts/loop-check.md +0 -87
- package/dist/template/.pi/prompts/loop-init.md +0 -157
- package/dist/template/.pi/prompts/loop-review.md +0 -90
- package/dist/template/.pi/skills/loop-audit/SKILL.md +0 -141
- package/dist/template/.pi/skills/loop-cost/SKILL.md +0 -130
- package/dist/template/.pi/skills/loop-engineering/SKILL.md +0 -175
- package/dist/template/.pi/templates/loop-github-action.yml +0 -162
- package/dist/template/.pi/templates/loop-orchestrator.sh +0 -514
- package/dist/template/.pi/templates/loop-orchestrator.test.ts +0 -332
- package/dist/template/.pi/templates/loop-orchestrator.ts +0 -936
- package/dist/template/.pi/templates/loop-state.json +0 -24
- package/dist/template/.pi/templates/loop-state.md +0 -98
- package/dist/template/.pi/templates/loop-vision.md +0 -110
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zustand
|
|
3
|
+
description: Use when implementing global or shared state management in React with Zustand v5. Covers store creation, slices pattern, middleware, selective subscriptions, React 19 + SSR integration. MUST load before any state management implementation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Zustand v5
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Managing global or shared client-side state in React
|
|
11
|
+
- Replacing Redux, Jotai, or Context for state management
|
|
12
|
+
- Creating stores that are used across multiple components
|
|
13
|
+
- Implementing slices for domain-separated state
|
|
14
|
+
- Persisting state to localStorage or sessionStorage
|
|
15
|
+
- Using middleware (immer, devtools, persist)
|
|
16
|
+
|
|
17
|
+
## When NOT to Use
|
|
18
|
+
|
|
19
|
+
- Server state (use TanStack Query or Server Components)
|
|
20
|
+
- Form state (use React Hook Form or Server Actions)
|
|
21
|
+
- Single-component local state (use `useState` or `useReducer`)
|
|
22
|
+
- Server Components (Zustand is client-only)
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install zustand
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Basic Store
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// stores/counter.ts
|
|
34
|
+
import { create } from 'zustand'
|
|
35
|
+
|
|
36
|
+
interface CounterState {
|
|
37
|
+
count: number
|
|
38
|
+
increment: () => void
|
|
39
|
+
decrement: () => void
|
|
40
|
+
reset: () => void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const useCounterStore = create<CounterState>((set) => ({
|
|
44
|
+
count: 0,
|
|
45
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
46
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
47
|
+
reset: () => set({ count: 0 }),
|
|
48
|
+
}))
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// components/Counter.tsx
|
|
53
|
+
'use client'
|
|
54
|
+
|
|
55
|
+
import { useCounterStore } from '@/stores/counter'
|
|
56
|
+
|
|
57
|
+
export function Counter() {
|
|
58
|
+
const { count, increment, decrement } = useCounterStore()
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div>
|
|
62
|
+
<p>Count: {count}</p>
|
|
63
|
+
<button onClick={increment}>+</button>
|
|
64
|
+
<button onClick={decrement}>-</button>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Slices Pattern
|
|
71
|
+
|
|
72
|
+
Split store into domain slices:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
// stores/index.ts
|
|
76
|
+
import { create } from 'zustand'
|
|
77
|
+
import { createAuthSlice, type AuthSlice } from './slices/auth'
|
|
78
|
+
import { createCartSlice, type CartSlice } from './slices/cart'
|
|
79
|
+
|
|
80
|
+
type Store = AuthSlice & CartSlice
|
|
81
|
+
|
|
82
|
+
export const useStore = create<Store>()((...args) => ({
|
|
83
|
+
...createAuthSlice(...args),
|
|
84
|
+
...createCartSlice(...args),
|
|
85
|
+
}))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// stores/slices/auth.ts
|
|
90
|
+
import type { StateCreator } from 'zustand'
|
|
91
|
+
|
|
92
|
+
export interface AuthSlice {
|
|
93
|
+
user: User | null
|
|
94
|
+
login: (credentials: Credentials) => Promise<void>
|
|
95
|
+
logout: () => void
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const createAuthSlice: StateCreator<AuthSlice, [], [], AuthSlice> = (set) => ({
|
|
99
|
+
user: null,
|
|
100
|
+
login: async (credentials) => {
|
|
101
|
+
const user = await api.login(credentials)
|
|
102
|
+
set({ user })
|
|
103
|
+
},
|
|
104
|
+
logout: () => set({ user: null }),
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Selective Subscriptions
|
|
109
|
+
|
|
110
|
+
Zustand re-renders only when **used** state changes:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
// ❌ Whole store — re-renders on any change
|
|
114
|
+
const { count, name } = useStore()
|
|
115
|
+
|
|
116
|
+
// ✅ Selective — re-renders only when count changes
|
|
117
|
+
const count = useStore((state) => state.count)
|
|
118
|
+
const increment = useStore((state) => state.increment) // Stable reference
|
|
119
|
+
|
|
120
|
+
// ✅ Multiple values — useShallow for objects
|
|
121
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
122
|
+
|
|
123
|
+
const { name, email } = useStore(
|
|
124
|
+
useShallow((state) => ({ name: state.user.name, email: state.user.email }))
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`useShallow` does shallow equality — avoids re-render when both values are the same.
|
|
129
|
+
|
|
130
|
+
## Middleware
|
|
131
|
+
|
|
132
|
+
### Persist (localStorage)
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { create } from 'zustand'
|
|
136
|
+
import { persist } from 'zustand/middleware'
|
|
137
|
+
|
|
138
|
+
export const useSettingsStore = create(
|
|
139
|
+
persist(
|
|
140
|
+
(set) => ({
|
|
141
|
+
theme: 'light',
|
|
142
|
+
setTheme: (theme: 'light' | 'dark') => set({ theme }),
|
|
143
|
+
}),
|
|
144
|
+
{
|
|
145
|
+
name: 'app-settings', // localStorage key
|
|
146
|
+
partialize: (state) => ({ theme: state.theme }), // Only persist theme
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Immer (Immutable Updates)
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { create } from 'zustand'
|
|
156
|
+
import { immer } from 'zustand/middleware/immer'
|
|
157
|
+
|
|
158
|
+
export const useTodoStore = create(
|
|
159
|
+
immer((set) => ({
|
|
160
|
+
todos: [] as Todo[],
|
|
161
|
+
addTodo: (text: string) =>
|
|
162
|
+
set((state) => {
|
|
163
|
+
state.todos.push({ id: crypto.randomUUID(), text, done: false })
|
|
164
|
+
}),
|
|
165
|
+
toggleTodo: (id: string) =>
|
|
166
|
+
set((state) => {
|
|
167
|
+
const todo = state.todos.find((t) => t.id === id)
|
|
168
|
+
if (todo) todo.done = !todo.done
|
|
169
|
+
}),
|
|
170
|
+
}))
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### DevTools
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { create } from 'zustand'
|
|
178
|
+
import { devtools } from 'zustand/middleware'
|
|
179
|
+
|
|
180
|
+
export const useStore = create(
|
|
181
|
+
devtools(
|
|
182
|
+
(set) => ({
|
|
183
|
+
count: 0,
|
|
184
|
+
increment: () => set((s) => ({ count: s.count + 1 }), false, 'increment'),
|
|
185
|
+
}),
|
|
186
|
+
{ name: 'AppStore' } // Name in Redux DevTools
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Combining Multiple Middleware
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { create } from 'zustand'
|
|
195
|
+
import { devtools, persist, immer } from 'zustand/middleware'
|
|
196
|
+
|
|
197
|
+
export const useStore = create(
|
|
198
|
+
devtools(
|
|
199
|
+
persist(
|
|
200
|
+
immer((set) => ({
|
|
201
|
+
// store...
|
|
202
|
+
})),
|
|
203
|
+
{ name: 'app-storage' }
|
|
204
|
+
),
|
|
205
|
+
{ name: 'AppStore' }
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## React 19 + Server Components
|
|
211
|
+
|
|
212
|
+
Zustand is **client-only**. Pattern for Next.js App Router:
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// stores/useStore.ts
|
|
216
|
+
import { create } from 'zustand'
|
|
217
|
+
|
|
218
|
+
export const useStore = create<Store>((set) => ({
|
|
219
|
+
// ...
|
|
220
|
+
}))
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
// components/ClientWrapper.tsx
|
|
225
|
+
'use client'
|
|
226
|
+
|
|
227
|
+
import { useStore } from '@/stores/useStore'
|
|
228
|
+
|
|
229
|
+
export function ClientWrapper({ children }) {
|
|
230
|
+
const data = useStore((s) => s.data)
|
|
231
|
+
|
|
232
|
+
return <div>{children}</div>
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Rules**:
|
|
237
|
+
- Stores are defined outside components (module scope)
|
|
238
|
+
- Store consumers must be in `'use client'` components
|
|
239
|
+
- Server Components can import the store type but cannot `useStore()`
|
|
240
|
+
- Use React Context to provide a store instance if you need SSR hydration
|
|
241
|
+
|
|
242
|
+
## SSR Hydration Pattern
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
// app/providers.tsx
|
|
246
|
+
'use client'
|
|
247
|
+
|
|
248
|
+
import { type ReactNode, createContext, useContext, useRef } from 'react'
|
|
249
|
+
import { type StoreApi, useStore } from 'zustand'
|
|
250
|
+
|
|
251
|
+
// Create context for store
|
|
252
|
+
const StoreContext = createContext<StoreApi<AppStore> | null>(null)
|
|
253
|
+
|
|
254
|
+
export function StoreProvider({ children }: { children: ReactNode }) {
|
|
255
|
+
const storeRef = useRef<StoreApi<AppStore>>()
|
|
256
|
+
|
|
257
|
+
if (!storeRef.current) {
|
|
258
|
+
storeRef.current = createAppStore()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<StoreContext.Provider value={storeRef.current}>
|
|
263
|
+
{children}
|
|
264
|
+
</StoreContext.Provider>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Hook to use the store
|
|
269
|
+
export function useAppStore<T>(selector: (state: AppStore) => T): T {
|
|
270
|
+
const store = useContext(StoreContext)
|
|
271
|
+
if (!store) throw new Error('Missing StoreProvider')
|
|
272
|
+
return useStore(store, selector)
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Async Actions
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
// Async actions are just async functions in the store:
|
|
280
|
+
interface UserStore {
|
|
281
|
+
user: User | null
|
|
282
|
+
loading: boolean
|
|
283
|
+
error: Error | null
|
|
284
|
+
fetchUser: (id: string) => Promise<void>
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export const useUserStore = create<UserStore>((set) => ({
|
|
288
|
+
user: null,
|
|
289
|
+
loading: false,
|
|
290
|
+
error: null,
|
|
291
|
+
fetchUser: async (id) => {
|
|
292
|
+
set({ loading: true, error: null })
|
|
293
|
+
try {
|
|
294
|
+
const user = await api.getUser(id)
|
|
295
|
+
set({ user, loading: false })
|
|
296
|
+
} catch (error) {
|
|
297
|
+
set({ error: error as Error, loading: false })
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
}))
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## When to Use Zustand vs Context vs TanStack Query
|
|
304
|
+
|
|
305
|
+
| Tool | Best for |
|
|
306
|
+
|------|----------|
|
|
307
|
+
| **Zustand** | Global client state: theme, auth, cart, UI preferences |
|
|
308
|
+
| **React Context** | Dependency injection, theming, auth provider — static values that rarely change |
|
|
309
|
+
| **TanStack Query** | Server state: data fetching, caching, mutations |
|
|
310
|
+
| **useState/useReducer** | Local component state |
|
|
311
|
+
|
|
312
|
+
## Common Pitfalls
|
|
313
|
+
|
|
314
|
+
| Pitfall | Fix |
|
|
315
|
+
|---------|-----|
|
|
316
|
+
| Using Zustand for server state | Use TanStack Query for fetched data — Zustand for client-only state |
|
|
317
|
+
| `useStore()` without selector — re-renders on any change | Always use selectors: `useStore(s => s.count)` |
|
|
318
|
+
| Multiple values returned as new object every render | Use `useShallow` for object selectors |
|
|
319
|
+
| Store in Server Component | Move store usage to `'use client'` |
|
|
320
|
+
| `getState()` in render | `getState()` is for callbacks/outside React, not render |
|
|
321
|
+
| Large stores with everything in one file | Use slices pattern to separate domains |
|
|
322
|
+
| Recreating store on every render | Define store outside component or use `useRef` for context pattern |
|
|
323
|
+
|
|
324
|
+
## Verification
|
|
325
|
+
|
|
326
|
+
- [ ] Store defined outside component (module scope) or via `useRef` in provider
|
|
327
|
+
- [ ] All store consumers are in `'use client'` components
|
|
328
|
+
- [ ] Selectors used for granular subscriptions — no destructured `useStore()`
|
|
329
|
+
- [ ] `useShallow` used for multi-value object selectors
|
|
330
|
+
- [ ] Server state (fetched data) managed by TanStack Query, not Zustand
|
|
331
|
+
- [ ] Slices pattern used for stores with multiple domains
|
|
332
|
+
- [ ] `persist` middleware configured with `partialize` to avoid storing sensitive data
|
|
333
|
+
- [ ] DevTools middleware enabled (development only)
|
package/package.json
CHANGED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
purpose: Fallow codebase intelligence commands for AI agents — dead code, duplication, complexity, and audit gating
|
|
3
|
-
updated: 2026-06-04
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Fallow — Codebase Intelligence Reference
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
Fallow is a Rust-native, deterministic static analysis tool for TypeScript/JavaScript codebases.
|
|
11
|
-
**No AI inside the analyzer** — same input always produces the same output.
|
|
12
|
-
It builds a complete module graph to find issues no linter or type checker can see.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Commands
|
|
17
|
-
|
|
18
|
-
### Full Analysis (single pass)
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npx fallow # All analyses: dead code + duplication + health
|
|
22
|
-
npx fallow --format json # Structured output for agent parsing
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Dead Code
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npx fallow dead-code # Full dead code report
|
|
29
|
-
npx fallow dead-code --format json --quiet # JSON for agents
|
|
30
|
-
npx fallow dead-code --unused-exports # Only unused exports
|
|
31
|
-
npx fallow dead-code --unused-dependencies # Only unused deps
|
|
32
|
-
npx fallow dead-code --circular # Only circular deps
|
|
33
|
-
npx fallow fix --dry-run # Preview safe auto-fixes
|
|
34
|
-
npx fallow fix --yes # Apply auto-fixes
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Trace (investigate before deleting)
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npx fallow dead-code --trace FILE:EXPORT_NAME # Why is this export flagged?
|
|
41
|
-
npx fallow dead-code --trace-dependency PACKAGE_NAME # Where is this dep imported?
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Duplication
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
npx fallow dupes # Find code clones
|
|
48
|
-
npx fallow dupes --mode strict # Exact matches only
|
|
49
|
-
npx fallow dupes --mode weak # Structural matches
|
|
50
|
-
npx fallow dupes --trace FILE:LINE # Deep-dive a specific clone group
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Health (complexity)
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
npx fallow health # Complexity hotspots + refactoring targets
|
|
57
|
-
npx fallow health --format json # Structured output
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Audit Gate (for CI / pre-commit)
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npx fallow audit # Check changed files (verdict: pass/warn/fail)
|
|
64
|
-
npx fallow audit --format json # Structured verdict for agents
|
|
65
|
-
npx fallow audit --gate new-only # Only flag new issues, not pre-existing
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Workflow Patterns
|
|
71
|
-
|
|
72
|
-
### Post-Edit Verification Loop
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
# 1. Make changes
|
|
76
|
-
# 2. Run audit
|
|
77
|
-
npx fallow audit --format json --quiet
|
|
78
|
-
# 3. If verdict is "fail", inspect findings
|
|
79
|
-
# 4. Fix or investigate with --trace
|
|
80
|
-
# 5. Re-run audit until pass
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Codebase Cleanup
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npx fallow # Full picture
|
|
87
|
-
npx fallow dead-code --format json # Find unused code
|
|
88
|
-
npx fallow fix --dry-run # Preview auto-removals
|
|
89
|
-
npx fallow fix --yes # Apply auto-fixes
|
|
90
|
-
npx fallow dupes # Find duplication
|
|
91
|
-
npx fallow health # Find complexity hotspots
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Monorepo / Workspace
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
npx fallow --workspace # Analyze all workspaces
|
|
98
|
-
npx fallow --workspace packages/pkg # Analyze specific workspace
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Understanding Output
|
|
104
|
-
|
|
105
|
-
Every finding in `--format json` includes:
|
|
106
|
-
|
|
107
|
-
```json
|
|
108
|
-
{
|
|
109
|
-
"path": "src/utils/example.ts:42",
|
|
110
|
-
"issue_type": "unused-exports",
|
|
111
|
-
"actions": [
|
|
112
|
-
{
|
|
113
|
-
"type": "delete-export",
|
|
114
|
-
"auto_fixable": true,
|
|
115
|
-
"description": "Remove unused export"
|
|
116
|
-
}
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
The `actions[]` array is machine-actionable. Agents can inspect `auto_fixable` flags and apply safe fixes programmatically.
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Config
|
|
126
|
-
|
|
127
|
-
Fallow auto-detects your project. For custom config, run:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
npx fallow init # Generates .fallow/config.yaml with auto-detected settings
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Common config patterns:
|
|
134
|
-
- `ignorePatterns` — exclude directories from analysis (e.g., `.pi/`)
|
|
135
|
-
- `entry` — declare additional entry points
|
|
136
|
-
- `publicPackages` — packages with public API surface
|
|
137
|
-
- `rules` — custom issue severity rules
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: NO-GO qualification gate — decide whether a task is safe to run as an unattended loop
|
|
3
|
-
argument-hint: "<task description> [--help]"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Loop-Check: $ARGUMENTS
|
|
7
|
-
|
|
8
|
-
Qualify a task before it is ever scheduled as an unattended loop. Emit a single GO/NO-GO verdict with the cited condition that produced it. This prompt is the gate *before* the gate — it refuses work a loop cannot honestly judge.
|
|
9
|
-
|
|
10
|
-
> Refuse first, test second, checklist last. Never let "looks automatable" override the refuse-list.
|
|
11
|
-
|
|
12
|
-
## Parse Arguments
|
|
13
|
-
|
|
14
|
-
| Argument | Default | Description |
|
|
15
|
-
| ------------- | -------- | ------------------------------------------------ |
|
|
16
|
-
| `<task>` | required | The task proposed for unattended looping |
|
|
17
|
-
| `--help` | false | Show this usage |
|
|
18
|
-
|
|
19
|
-
## Step 1 — Refuse-List (Immediate NO-GO)
|
|
20
|
-
|
|
21
|
-
If the task touches any of these, **stop here** and emit NO-GO. Do not proceed to the 2-condition test.
|
|
22
|
-
|
|
23
|
-
- **auth** — login, sessions, tokens, permissions, auth flows, auth module rewrites
|
|
24
|
-
- **payments** — billing, checkout, refunds, subscriptions, payment provider integration
|
|
25
|
-
- **architecture** — framework/library switches, schema migrations, new DB tables, new services, breaking API changes
|
|
26
|
-
|
|
27
|
-
> Rationale: these need human judgement a loop cannot provide. The refuse-list is the honest ceiling, not a configurable preference.
|
|
28
|
-
|
|
29
|
-
If a refuse-list category is hit → `CONDITION: <refused category> refused` (e.g. `CONDITION: architecture refused`).
|
|
30
|
-
|
|
31
|
-
## Step 2 — The 2-Condition Test
|
|
32
|
-
|
|
33
|
-
Both conditions must hold. If either fails, emit NO-GO citing the failed condition.
|
|
34
|
-
|
|
35
|
-
1. **Verification is automated.** There exists an objective command whose **exit code** decides pass/fail — `npm test`, `tsc --noEmit`, `eslint`, a custom gate script. The stop condition is computational (exit code), never an LLM's opinion. If the only "check" is "the agent says it looks good" → FAIL.
|
|
36
|
-
2. **The token budget absorbs the waste.** The cost of one wasted run (gate fails, no-op, early-exit) is small enough that running on the scheduled cadence does not blow the budget. If a single failed run is expensive (large model, long context, many turns) and there is no per-run cap → FAIL.
|
|
37
|
-
|
|
38
|
-
If condition 1 fails → `CONDITION: verification not automated (no exit-code gate)`.
|
|
39
|
-
If condition 2 fails → `CONDITION: budget does not absorb waste (uncapped/expensive failed run)`.
|
|
40
|
-
|
|
41
|
-
## Step 3 — 30-Second Checklist
|
|
42
|
-
|
|
43
|
-
Confirm every item. Any miss → NO-GO citing the missing item.
|
|
44
|
-
|
|
45
|
-
- [ ] **Objective gate exists** — a named command with a pass = exit 0 contract (write it down; "tests" without a command is not a gate).
|
|
46
|
-
- [ ] **Hard stop exists** — a non-negotiable exit condition: repeated gate failure, budget cap hit, or forbidden path touched. The loop exits; it does not improvise.
|
|
47
|
-
- [ ] **Human approves merge/deploy/dependency changes** — the loop may open PRs and stage work; a human merges, deploys, and changes deps. No auto-merge.
|
|
48
|
-
|
|
49
|
-
Missing gate → `CONDITION: no objective gate`.
|
|
50
|
-
Missing hard stop → `CONDITION: no hard stop`.
|
|
51
|
-
No human-approval boundary → `CONDITION: no human-approval boundary on merge/deploy/deps`.
|
|
52
|
-
|
|
53
|
-
## Step 4 — Emit Verdict
|
|
54
|
-
|
|
55
|
-
Output **exactly** these two lines and nothing else as the decision:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
VERDICT: GO
|
|
59
|
-
CONDITION: verification automated + budget absorbs waste
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
or
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
VERDICT: NO-GO
|
|
66
|
-
CONDITION: <cited condition from Step 1, 2, or 3>
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
- `VERDICT` is `GO` or `NO-GO` — no other values.
|
|
70
|
-
- `CONDITION` cites the specific condition that produced the verdict. For GO, cite both passing conditions. For NO-GO, cite the first failing condition (Step 1 overrides Step 2 overrides Step 3).
|
|
71
|
-
- Do not add rationale, recommendations, or next steps after the verdict lines. The verdict is the contract.
|
|
72
|
-
|
|
73
|
-
## Worked Examples
|
|
74
|
-
|
|
75
|
-
- **"triage failing CI nightly"** with `npm test` gate, no-op early-exit <5k tokens → `VERDICT: GO` / `CONDITION: verification automated + budget absorbs waste`
|
|
76
|
-
- **"rewrite auth module"** → `VERDICT: NO-GO` / `CONDITION: auth refused`
|
|
77
|
-
- **"migrate from REST to GraphQL"** → `VERDICT: NO-GO` / `CONDITION: architecture refused`
|
|
78
|
-
- **"keep docs in sync with code weekly"** with no gate command → `VERDICT: NO-GO` / `CONDITION: verification not automated (no exit-code gate)`
|
|
79
|
-
|
|
80
|
-
## Related
|
|
81
|
-
|
|
82
|
-
| Companion | Role |
|
|
83
|
-
|---|---|
|
|
84
|
-
| `loop-engineering` skill | The 2-condition test, refuse-list, and 5 building blocks this gate codifies |
|
|
85
|
-
| `/loop-init` | Scaffold `.pi/loops/<name>/` once a task is GO |
|
|
86
|
-
| `/loop-review` | Maker/checker verification — the gate *during* a run |
|
|
87
|
-
| `loop-audit` | Readiness scoring (L0-L3) for an already-GO loop |
|