mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
fix: harden unicode safety checks
This commit is contained in:
@@ -39,7 +39,7 @@ Fix 3: Changed to SELECT *
|
||||
→ Fixed production path, forgot sandbox path
|
||||
→ AI reviewed and missed it AGAIN (4th occurrence)
|
||||
|
||||
Fix 4: Test caught it instantly on first run ✅
|
||||
Fix 4: Test caught it instantly on first run PASS:
|
||||
```
|
||||
|
||||
The pattern: **sandbox/production path inconsistency** is the #1 AI-introduced regression.
|
||||
@@ -249,14 +249,14 @@ User: "バグチェックして" (or "/bug-check")
|
||||
**Frequency**: Most common (observed in 3 out of 4 regressions)
|
||||
|
||||
```typescript
|
||||
// ❌ AI adds field to production path only
|
||||
// FAIL: AI adds field to production path only
|
||||
if (isSandboxMode()) {
|
||||
return { data: { id, email, name } }; // Missing new field
|
||||
}
|
||||
// Production path
|
||||
return { data: { id, email, name, notification_settings } };
|
||||
|
||||
// ✅ Both paths must return the same shape
|
||||
// PASS: Both paths must return the same shape
|
||||
if (isSandboxMode()) {
|
||||
return { data: { id, email, name, notification_settings: null } };
|
||||
}
|
||||
@@ -282,7 +282,7 @@ it("sandbox and production return same fields", async () => {
|
||||
**Frequency**: Common with Supabase/Prisma when adding new columns
|
||||
|
||||
```typescript
|
||||
// ❌ New column added to response but not to SELECT
|
||||
// FAIL: New column added to response but not to SELECT
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("id, email, name") // notification_settings not here
|
||||
@@ -291,7 +291,7 @@ const { data } = await supabase
|
||||
return { data: { ...data, notification_settings: data.notification_settings } };
|
||||
// → notification_settings is always undefined
|
||||
|
||||
// ✅ Use SELECT * or explicitly include new columns
|
||||
// PASS: Use SELECT * or explicitly include new columns
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
@@ -303,13 +303,13 @@ const { data } = await supabase
|
||||
**Frequency**: Moderate — when adding error handling to existing components
|
||||
|
||||
```typescript
|
||||
// ❌ Error state set but old data not cleared
|
||||
// FAIL: Error state set but old data not cleared
|
||||
catch (err) {
|
||||
setError("Failed to load");
|
||||
// reservations still shows data from previous tab!
|
||||
}
|
||||
|
||||
// ✅ Clear related state on error
|
||||
// PASS: Clear related state on error
|
||||
catch (err) {
|
||||
setReservations([]); // Clear stale data
|
||||
setError("Failed to load");
|
||||
@@ -319,14 +319,14 @@ catch (err) {
|
||||
### Pattern 4: Optimistic Update Without Proper Rollback
|
||||
|
||||
```typescript
|
||||
// ❌ No rollback on failure
|
||||
// FAIL: No rollback on failure
|
||||
const handleRemove = async (id: string) => {
|
||||
setItems(prev => prev.filter(i => i.id !== id));
|
||||
await fetch(`/api/items/${id}`, { method: "DELETE" });
|
||||
// If API fails, item is gone from UI but still in DB
|
||||
};
|
||||
|
||||
// ✅ Capture previous state and rollback on failure
|
||||
// PASS: Capture previous state and rollback on failure
|
||||
const handleRemove = async (id: string) => {
|
||||
const prevItems = [...items];
|
||||
setItems(prev => prev.filter(i => i.id !== id));
|
||||
@@ -362,11 +362,11 @@ No bug in /api/user/notifications → Don't write test (yet)
|
||||
|
||||
| AI Regression Pattern | Test Strategy | Priority |
|
||||
|---|---|---|
|
||||
| Sandbox/production mismatch | Assert same response shape in sandbox mode | 🔴 High |
|
||||
| SELECT clause omission | Assert all required fields in response | 🔴 High |
|
||||
| Error state leakage | Assert state cleanup on error | 🟡 Medium |
|
||||
| Missing rollback | Assert state restored on API failure | 🟡 Medium |
|
||||
| Type cast masking null | Assert field is not undefined | 🟡 Medium |
|
||||
| Sandbox/production mismatch | Assert same response shape in sandbox mode | High |
|
||||
| SELECT clause omission | Assert all required fields in response | High |
|
||||
| Error state leakage | Assert state cleanup on error | Medium |
|
||||
| Missing rollback | Assert state restored on API failure | Medium |
|
||||
| Type cast masking null | Assert field is not undefined | Medium |
|
||||
|
||||
## DO / DON'T
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Backend architecture patterns and best practices for scalable server-side applic
|
||||
### RESTful API Structure
|
||||
|
||||
```typescript
|
||||
// ✅ Resource-based URLs
|
||||
// PASS: Resource-based URLs
|
||||
GET /api/markets # List resources
|
||||
GET /api/markets/:id # Get single resource
|
||||
POST /api/markets # Create resource
|
||||
@@ -31,7 +31,7 @@ PUT /api/markets/:id # Replace resource
|
||||
PATCH /api/markets/:id # Update resource
|
||||
DELETE /api/markets/:id # Delete resource
|
||||
|
||||
// ✅ Query parameters for filtering, sorting, pagination
|
||||
// PASS: Query parameters for filtering, sorting, pagination
|
||||
GET /api/markets?status=active&sort=volume&limit=20&offset=0
|
||||
```
|
||||
|
||||
@@ -131,7 +131,7 @@ export default withAuth(async (req, res) => {
|
||||
### Query Optimization
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Select only needed columns
|
||||
// PASS: GOOD: Select only needed columns
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status, volume')
|
||||
@@ -139,7 +139,7 @@ const { data } = await supabase
|
||||
.order('volume', { ascending: false })
|
||||
.limit(10)
|
||||
|
||||
// ❌ BAD: Select everything
|
||||
// FAIL: BAD: Select everything
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -148,13 +148,13 @@ const { data } = await supabase
|
||||
### N+1 Query Prevention
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: N+1 query problem
|
||||
// FAIL: BAD: N+1 query problem
|
||||
const markets = await getMarkets()
|
||||
for (const market of markets) {
|
||||
market.creator = await getUser(market.creator_id) // N queries
|
||||
}
|
||||
|
||||
// ✅ GOOD: Batch fetch
|
||||
// PASS: GOOD: Batch fetch
|
||||
const markets = await getMarkets()
|
||||
const creatorIds = markets.map(m => m.creator_id)
|
||||
const creators = await getUsers(creatorIds) // 1 query
|
||||
|
||||
@@ -77,9 +77,9 @@ Output:
|
||||
```
|
||||
| Metric | Before | After | Delta | Verdict |
|
||||
|--------|--------|-------|-------|---------|
|
||||
| LCP | 1.2s | 1.4s | +200ms | ⚠ WARN |
|
||||
| LCP | 1.2s | 1.4s | +200ms | WARNING: WARN |
|
||||
| Bundle | 180KB | 175KB | -5KB | ✓ BETTER |
|
||||
| Build | 12s | 14s | +2s | ⚠ WARN |
|
||||
| Build | 12s | 14s | +2s | WARNING: WARN |
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
@@ -14,7 +14,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "🤝"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Carrier Relationship Management
|
||||
|
||||
@@ -28,7 +28,7 @@ if (projectPath && projectPath !== cwd) {
|
||||
if (existsSync(projectPath)) {
|
||||
console.log(`→ cd ${projectPath}`);
|
||||
} else {
|
||||
console.log(`⚠ Path not found: ${projectPath}`);
|
||||
console.log(`WARNING: Path not found: ${projectPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ function main() {
|
||||
// Check if previous session ID exists in sessions array
|
||||
const alreadySaved = context.sessions?.some(s => s.id === prevSession.sessionId);
|
||||
if (!alreadySaved) {
|
||||
summaryLines.push(`⚠ Last session wasn't saved — run /ck:save to capture it`);
|
||||
summaryLines.push(`WARNING: Last session wasn't saved — run /ck:save to capture it`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ function main() {
|
||||
const claudeMdGoal = extractClaudeMdGoal(cwd);
|
||||
if (claudeMdGoal && context.goal &&
|
||||
claudeMdGoal.toLowerCase().trim() !== context.goal.toLowerCase().trim()) {
|
||||
summaryLines.push(`⚠ Goal mismatch — ck: "${context.goal.slice(0, 40)}" · CLAUDE.md: "${claudeMdGoal.slice(0, 40)}"`);
|
||||
summaryLines.push(`WARNING: Goal mismatch — ck: "${context.goal.slice(0, 40)}" · CLAUDE.md: "${claudeMdGoal.slice(0, 40)}"`);
|
||||
summaryLines.push(` Run /ck:save with updated goal to sync`);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ function main() {
|
||||
'```',
|
||||
``,
|
||||
`After the block, add one line: "Ready — what are we working on?"`,
|
||||
`If you see ⚠ warnings above, mention them briefly after the block.`,
|
||||
`If you see WARNING: warnings above, mention them briefly after the block.`,
|
||||
].join('\n'));
|
||||
|
||||
return parts;
|
||||
|
||||
@@ -96,7 +96,7 @@ ORDER BY hour DESC;
|
||||
### Efficient Filtering
|
||||
|
||||
```sql
|
||||
-- ✅ GOOD: Use indexed columns first
|
||||
-- PASS: GOOD: Use indexed columns first
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE date >= '2025-01-01'
|
||||
@@ -105,7 +105,7 @@ WHERE date >= '2025-01-01'
|
||||
ORDER BY date DESC
|
||||
LIMIT 100;
|
||||
|
||||
-- ❌ BAD: Filter on non-indexed columns first
|
||||
-- FAIL: BAD: Filter on non-indexed columns first
|
||||
SELECT *
|
||||
FROM markets_analytics
|
||||
WHERE volume > 1000
|
||||
@@ -116,7 +116,7 @@ WHERE volume > 1000
|
||||
### Aggregations
|
||||
|
||||
```sql
|
||||
-- ✅ GOOD: Use ClickHouse-specific aggregation functions
|
||||
-- PASS: GOOD: Use ClickHouse-specific aggregation functions
|
||||
SELECT
|
||||
toStartOfDay(created_at) AS day,
|
||||
market_id,
|
||||
@@ -129,7 +129,7 @@ WHERE created_at >= today() - INTERVAL 7 DAY
|
||||
GROUP BY day, market_id
|
||||
ORDER BY day DESC, total_volume DESC;
|
||||
|
||||
-- ✅ Use quantile for percentiles (more efficient than percentile)
|
||||
-- PASS: Use quantile for percentiles (more efficient than percentile)
|
||||
SELECT
|
||||
quantile(0.50)(trade_size) AS median,
|
||||
quantile(0.95)(trade_size) AS p95,
|
||||
@@ -172,7 +172,7 @@ const clickhouse = new ClickHouse({
|
||||
}
|
||||
})
|
||||
|
||||
// ✅ Batch insert (efficient)
|
||||
// PASS: Batch insert (efficient)
|
||||
async function bulkInsertTrades(trades: Trade[]) {
|
||||
const values = trades.map(trade => `(
|
||||
'${trade.id}',
|
||||
@@ -188,7 +188,7 @@ async function bulkInsertTrades(trades: Trade[]) {
|
||||
`).toPromise()
|
||||
}
|
||||
|
||||
// ❌ Individual inserts (slow)
|
||||
// FAIL: Individual inserts (slow)
|
||||
async function insertTrade(trade: Trade) {
|
||||
// Don't do this in a loop!
|
||||
await clickhouse.query(`
|
||||
|
||||
@@ -48,12 +48,12 @@ Universal coding standards applicable across all projects.
|
||||
### Variable Naming
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive names
|
||||
// PASS: GOOD: Descriptive names
|
||||
const marketSearchQuery = 'election'
|
||||
const isUserAuthenticated = true
|
||||
const totalRevenue = 1000
|
||||
|
||||
// ❌ BAD: Unclear names
|
||||
// FAIL: BAD: Unclear names
|
||||
const q = 'election'
|
||||
const flag = true
|
||||
const x = 1000
|
||||
@@ -62,12 +62,12 @@ const x = 1000
|
||||
### Function Naming
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Verb-noun pattern
|
||||
// PASS: GOOD: Verb-noun pattern
|
||||
async function fetchMarketData(marketId: string) { }
|
||||
function calculateSimilarity(a: number[], b: number[]) { }
|
||||
function isValidEmail(email: string): boolean { }
|
||||
|
||||
// ❌ BAD: Unclear or noun-only
|
||||
// FAIL: BAD: Unclear or noun-only
|
||||
async function market(id: string) { }
|
||||
function similarity(a, b) { }
|
||||
function email(e) { }
|
||||
@@ -76,7 +76,7 @@ function email(e) { }
|
||||
### Immutability Pattern (CRITICAL)
|
||||
|
||||
```typescript
|
||||
// ✅ ALWAYS use spread operator
|
||||
// PASS: ALWAYS use spread operator
|
||||
const updatedUser = {
|
||||
...user,
|
||||
name: 'New Name'
|
||||
@@ -84,7 +84,7 @@ const updatedUser = {
|
||||
|
||||
const updatedArray = [...items, newItem]
|
||||
|
||||
// ❌ NEVER mutate directly
|
||||
// FAIL: NEVER mutate directly
|
||||
user.name = 'New Name' // BAD
|
||||
items.push(newItem) // BAD
|
||||
```
|
||||
@@ -92,7 +92,7 @@ items.push(newItem) // BAD
|
||||
### Error Handling
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Comprehensive error handling
|
||||
// PASS: GOOD: Comprehensive error handling
|
||||
async function fetchData(url: string) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
@@ -108,7 +108,7 @@ async function fetchData(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ❌ BAD: No error handling
|
||||
// FAIL: BAD: No error handling
|
||||
async function fetchData(url) {
|
||||
const response = await fetch(url)
|
||||
return response.json()
|
||||
@@ -118,14 +118,14 @@ async function fetchData(url) {
|
||||
### Async/Await Best Practices
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Parallel execution when possible
|
||||
// PASS: GOOD: Parallel execution when possible
|
||||
const [users, markets, stats] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchMarkets(),
|
||||
fetchStats()
|
||||
])
|
||||
|
||||
// ❌ BAD: Sequential when unnecessary
|
||||
// FAIL: BAD: Sequential when unnecessary
|
||||
const users = await fetchUsers()
|
||||
const markets = await fetchMarkets()
|
||||
const stats = await fetchStats()
|
||||
@@ -134,7 +134,7 @@ const stats = await fetchStats()
|
||||
### Type Safety
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper types
|
||||
// PASS: GOOD: Proper types
|
||||
interface Market {
|
||||
id: string
|
||||
name: string
|
||||
@@ -146,7 +146,7 @@ function getMarket(id: string): Promise<Market> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// ❌ BAD: Using 'any'
|
||||
// FAIL: BAD: Using 'any'
|
||||
function getMarket(id: any): Promise<any> {
|
||||
// Implementation
|
||||
}
|
||||
@@ -157,7 +157,7 @@ function getMarket(id: any): Promise<any> {
|
||||
### Component Structure
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Functional component with types
|
||||
// PASS: GOOD: Functional component with types
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
@@ -182,7 +182,7 @@ export function Button({
|
||||
)
|
||||
}
|
||||
|
||||
// ❌ BAD: No types, unclear structure
|
||||
// FAIL: BAD: No types, unclear structure
|
||||
export function Button(props) {
|
||||
return <button onClick={props.onClick}>{props.children}</button>
|
||||
}
|
||||
@@ -191,7 +191,7 @@ export function Button(props) {
|
||||
### Custom Hooks
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Reusable custom hook
|
||||
// PASS: GOOD: Reusable custom hook
|
||||
export function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
||||
|
||||
@@ -213,25 +213,25 @@ const debouncedQuery = useDebounce(searchQuery, 500)
|
||||
### State Management
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Proper state updates
|
||||
// PASS: GOOD: Proper state updates
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// Functional update for state based on previous state
|
||||
setCount(prev => prev + 1)
|
||||
|
||||
// ❌ BAD: Direct state reference
|
||||
// FAIL: BAD: Direct state reference
|
||||
setCount(count + 1) // Can be stale in async scenarios
|
||||
```
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Clear conditional rendering
|
||||
// PASS: GOOD: Clear conditional rendering
|
||||
{isLoading && <Spinner />}
|
||||
{error && <ErrorMessage error={error} />}
|
||||
{data && <DataDisplay data={data} />}
|
||||
|
||||
// ❌ BAD: Ternary hell
|
||||
// FAIL: BAD: Ternary hell
|
||||
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}
|
||||
```
|
||||
|
||||
@@ -254,7 +254,7 @@ GET /api/markets?status=active&limit=10&offset=0
|
||||
### Response Format
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Consistent response structure
|
||||
// PASS: GOOD: Consistent response structure
|
||||
interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
@@ -285,7 +285,7 @@ return NextResponse.json({
|
||||
```typescript
|
||||
import { z } from 'zod'
|
||||
|
||||
// ✅ GOOD: Schema validation
|
||||
// PASS: GOOD: Schema validation
|
||||
const CreateMarketSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().min(1).max(2000),
|
||||
@@ -348,14 +348,14 @@ types/market.types.ts # camelCase with .types suffix
|
||||
### When to Comment
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Explain WHY, not WHAT
|
||||
// PASS: GOOD: Explain WHY, not WHAT
|
||||
// Use exponential backoff to avoid overwhelming the API during outages
|
||||
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)
|
||||
|
||||
// Deliberately using mutation here for performance with large arrays
|
||||
items.push(newItem)
|
||||
|
||||
// ❌ BAD: Stating the obvious
|
||||
// FAIL: BAD: Stating the obvious
|
||||
// Increment counter by 1
|
||||
count++
|
||||
|
||||
@@ -395,12 +395,12 @@ export async function searchMarkets(
|
||||
```typescript
|
||||
import { useMemo, useCallback } from 'react'
|
||||
|
||||
// ✅ GOOD: Memoize expensive computations
|
||||
// PASS: GOOD: Memoize expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ GOOD: Memoize callbacks
|
||||
// PASS: GOOD: Memoize callbacks
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
@@ -411,7 +411,7 @@ const handleSearch = useCallback((query: string) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ GOOD: Lazy load heavy components
|
||||
// PASS: GOOD: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
|
||||
export function Dashboard() {
|
||||
@@ -426,13 +426,13 @@ export function Dashboard() {
|
||||
### Database Queries
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Select only needed columns
|
||||
// PASS: GOOD: Select only needed columns
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('id, name, status')
|
||||
.limit(10)
|
||||
|
||||
// ❌ BAD: Select everything
|
||||
// FAIL: BAD: Select everything
|
||||
const { data } = await supabase
|
||||
.from('markets')
|
||||
.select('*')
|
||||
@@ -459,12 +459,12 @@ test('calculates similarity correctly', () => {
|
||||
### Test Naming
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Descriptive test names
|
||||
// PASS: GOOD: Descriptive test names
|
||||
test('returns empty array when no markets match query', () => { })
|
||||
test('throws error when OpenAI API key is missing', () => { })
|
||||
test('falls back to substring search when Redis unavailable', () => { })
|
||||
|
||||
// ❌ BAD: Vague test names
|
||||
// FAIL: BAD: Vague test names
|
||||
test('works', () => { })
|
||||
test('test search', () => { })
|
||||
```
|
||||
@@ -475,12 +475,12 @@ Watch for these anti-patterns:
|
||||
|
||||
### 1. Long Functions
|
||||
```typescript
|
||||
// ❌ BAD: Function > 50 lines
|
||||
// FAIL: BAD: Function > 50 lines
|
||||
function processMarketData() {
|
||||
// 100 lines of code
|
||||
}
|
||||
|
||||
// ✅ GOOD: Split into smaller functions
|
||||
// PASS: GOOD: Split into smaller functions
|
||||
function processMarketData() {
|
||||
const validated = validateData()
|
||||
const transformed = transformData(validated)
|
||||
@@ -490,7 +490,7 @@ function processMarketData() {
|
||||
|
||||
### 2. Deep Nesting
|
||||
```typescript
|
||||
// ❌ BAD: 5+ levels of nesting
|
||||
// FAIL: BAD: 5+ levels of nesting
|
||||
if (user) {
|
||||
if (user.isAdmin) {
|
||||
if (market) {
|
||||
@@ -503,7 +503,7 @@ if (user) {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ GOOD: Early returns
|
||||
// PASS: GOOD: Early returns
|
||||
if (!user) return
|
||||
if (!user.isAdmin) return
|
||||
if (!market) return
|
||||
@@ -515,11 +515,11 @@ if (!hasPermission) return
|
||||
|
||||
### 3. Magic Numbers
|
||||
```typescript
|
||||
// ❌ BAD: Unexplained numbers
|
||||
// FAIL: BAD: Unexplained numbers
|
||||
if (retryCount > 3) { }
|
||||
setTimeout(callback, 500)
|
||||
|
||||
// ✅ GOOD: Named constants
|
||||
// PASS: GOOD: Named constants
|
||||
const MAX_RETRIES = 3
|
||||
const DEBOUNCE_DELAY_MS = 500
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ Component Breakdown:
|
||||
│ CLAUDE.md │ N │ ~X,XXX │
|
||||
└─────────────────┴────────┴───────────┘
|
||||
|
||||
⚠ Issues Found (N):
|
||||
WARNING: Issues Found (N):
|
||||
[ranked by token savings]
|
||||
|
||||
Top 3 Optimizations:
|
||||
|
||||
@@ -14,7 +14,7 @@ This is the v1.8+ canonical loop skill name. It supersedes `autonomous-loops` wh
|
||||
Start
|
||||
|
|
||||
+-- Need strict CI/PR control? -- yes --> continuous-pr
|
||||
|
|
||||
|
|
||||
+-- Need RFC decomposition? -- yes --> rfc-dag
|
||||
|
|
||||
+-- Need exploratory parallel generation? -- yes --> infinite
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
# Called by observer-loop.sh before spawning any Claude session.
|
||||
#
|
||||
# Config (env vars, all optional):
|
||||
# OBSERVER_INTERVAL_SECONDS default: 300 (per-project cooldown)
|
||||
# OBSERVER_LAST_RUN_LOG default: ~/.claude/observer-last-run.log
|
||||
# OBSERVER_ACTIVE_HOURS_START default: 800 (8:00 AM local, set to 0 to disable)
|
||||
# OBSERVER_ACTIVE_HOURS_END default: 2300 (11:00 PM local, set to 0 to disable)
|
||||
# OBSERVER_MAX_IDLE_SECONDS default: 1800 (30 min; set to 0 to disable)
|
||||
# OBSERVER_INTERVAL_SECONDS default: 300 (per-project cooldown)
|
||||
# OBSERVER_LAST_RUN_LOG default: ~/.claude/observer-last-run.log
|
||||
# OBSERVER_ACTIVE_HOURS_START default: 800 (8:00 AM local, set to 0 to disable)
|
||||
# OBSERVER_ACTIVE_HOURS_END default: 2300 (11:00 PM local, set to 0 to disable)
|
||||
# OBSERVER_MAX_IDLE_SECONDS default: 1800 (30 min; set to 0 to disable)
|
||||
#
|
||||
# Gate execution order (cheapest first):
|
||||
# Gate 1: Time window check (~0ms, string comparison)
|
||||
# Gate 2: Project cooldown log (~1ms, file read + mkdir lock)
|
||||
# Gate 3: Idle detection (~5-50ms, OS syscall; fail open)
|
||||
# Gate 1: Time window check (~0ms, string comparison)
|
||||
# Gate 2: Project cooldown log (~1ms, file read + mkdir lock)
|
||||
# Gate 3: Idle detection (~5-50ms, OS syscall; fail open)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
# and creates instincts. Uses Haiku model for cost efficiency.
|
||||
#
|
||||
# v2.1: Project-scoped — detects current project and analyzes
|
||||
# project-specific observations into project-scoped instincts.
|
||||
# project-specific observations into project-scoped instincts.
|
||||
#
|
||||
# Usage:
|
||||
# start-observer.sh # Start observer for current project (or global)
|
||||
# start-observer.sh --reset # Clear lock and restart observer for current project
|
||||
# start-observer.sh stop # Stop running observer
|
||||
# start-observer.sh status # Check if observer is running
|
||||
# start-observer.sh # Start observer for current project (or global)
|
||||
# start-observer.sh --reset # Clear lock and restart observer for current project
|
||||
# start-observer.sh stop # Stop running observer
|
||||
# start-observer.sh status # Check if observer is running
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Claude Code passes hook data via stdin as JSON.
|
||||
#
|
||||
# v2.1: Project-scoped observations — detects current project context
|
||||
# and writes observations to project-specific directory.
|
||||
# and writes observations to project-specific directory.
|
||||
#
|
||||
# Registered via plugin hooks/hooks.json (auto-loaded when plugin is enabled).
|
||||
# Can also be registered manually in ~/.claude/settings.json.
|
||||
@@ -92,9 +92,9 @@ if [ -n "${CLV2_CONFIG:-}" ] && [ -f "$(dirname "$CLV2_CONFIG")/disabled" ]; the
|
||||
fi
|
||||
|
||||
# Prevent observe.sh from firing on non-human sessions to avoid:
|
||||
# - ECC observing its own Haiku observer sessions (self-loop)
|
||||
# - ECC observing other tools' automated sessions
|
||||
# - automated sessions creating project-scoped homunculus metadata
|
||||
# - ECC observing its own Haiku observer sessions (self-loop)
|
||||
# - ECC observing other tools' automated sessions
|
||||
# - automated sessions creating project-scoped homunculus metadata
|
||||
|
||||
# Layer 1: entrypoint. Only interactive terminal sessions should continue.
|
||||
# sdk-ts: Agent SDK sessions can be human-interactive (e.g. via Happy).
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
# Sourced by observe.sh and start-observer.sh.
|
||||
#
|
||||
# Exports:
|
||||
# _CLV2_PROJECT_ID - Short hash identifying the project (or "global")
|
||||
# _CLV2_PROJECT_NAME - Human-readable project name
|
||||
# _CLV2_PROJECT_ROOT - Absolute path to project root
|
||||
# _CLV2_PROJECT_DIR - Project-scoped storage directory under homunculus
|
||||
# _CLV2_PROJECT_ID - Short hash identifying the project (or "global")
|
||||
# _CLV2_PROJECT_NAME - Human-readable project name
|
||||
# _CLV2_PROJECT_ROOT - Absolute path to project root
|
||||
# _CLV2_PROJECT_DIR - Project-scoped storage directory under homunculus
|
||||
#
|
||||
# Also sets unprefixed convenience aliases:
|
||||
# PROJECT_ID, PROJECT_NAME, PROJECT_ROOT, PROJECT_DIR
|
||||
# PROJECT_ID, PROJECT_NAME, PROJECT_ROOT, PROJECT_DIR
|
||||
#
|
||||
# Detection priority:
|
||||
# 1. CLAUDE_PROJECT_DIR env var (if set)
|
||||
# 2. git remote URL (hashed for uniqueness across machines)
|
||||
# 3. git repo root path (fallback, machine-specific)
|
||||
# 4. "global" (no project context detected)
|
||||
# 1. CLAUDE_PROJECT_DIR env var (if set)
|
||||
# 2. git remote URL (hashed for uniqueness across machines)
|
||||
# 3. git repo root path (fallback, machine-specific)
|
||||
# 4. "global" (no project context detected)
|
||||
|
||||
_CLV2_HOMUNCULUS_DIR="${HOME}/.claude/homunculus"
|
||||
_CLV2_PROJECTS_DIR="${_CLV2_HOMUNCULUS_DIR}/projects"
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
#
|
||||
# Hook config (in ~/.claude/settings.json):
|
||||
# {
|
||||
# "hooks": {
|
||||
# "Stop": [{
|
||||
# "matcher": "*",
|
||||
# "hooks": [{
|
||||
# "type": "command",
|
||||
# "command": "~/.claude/skills/continuous-learning/evaluate-session.sh"
|
||||
# }]
|
||||
# }]
|
||||
# }
|
||||
# "hooks": {
|
||||
# "Stop": [{
|
||||
# "matcher": "*",
|
||||
# "hooks": [{
|
||||
# "type": "command",
|
||||
# "command": "~/.claude/skills/continuous-learning/evaluate-session.sh"
|
||||
# }]
|
||||
# }]
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# Patterns to detect: error_resolution, debugging_techniques, workarounds, project_specific
|
||||
|
||||
@@ -15,7 +15,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "🌐"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Customs & Trade Compliance
|
||||
|
||||
@@ -357,7 +357,7 @@ Phase 12: Diff Review
|
||||
✓ No hardcoded secrets
|
||||
✓ Migrations included
|
||||
|
||||
RECOMMENDATION: ⚠️ Fix pip-audit vulnerabilities before deploying
|
||||
RECOMMENDATION: WARNING: Fix pip-audit vulnerabilities before deploying
|
||||
|
||||
NEXT STEPS:
|
||||
1. Update vulnerable dependencies
|
||||
|
||||
@@ -15,7 +15,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "⚡"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Energy Procurement
|
||||
|
||||
@@ -23,7 +23,7 @@ Modern frontend patterns for React, Next.js, and performant user interfaces.
|
||||
### Composition Over Inheritance
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD: Component composition
|
||||
// PASS: GOOD: Component composition
|
||||
interface CardProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'outlined'
|
||||
@@ -294,17 +294,17 @@ export function useMarkets() {
|
||||
### Memoization
|
||||
|
||||
```typescript
|
||||
// ✅ useMemo for expensive computations
|
||||
// PASS: useMemo for expensive computations
|
||||
const sortedMarkets = useMemo(() => {
|
||||
return markets.sort((a, b) => b.volume - a.volume)
|
||||
}, [markets])
|
||||
|
||||
// ✅ useCallback for functions passed to children
|
||||
// PASS: useCallback for functions passed to children
|
||||
const handleSearch = useCallback((query: string) => {
|
||||
setSearchQuery(query)
|
||||
}, [])
|
||||
|
||||
// ✅ React.memo for pure components
|
||||
// PASS: React.memo for pure components
|
||||
export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
return (
|
||||
<div className="market-card">
|
||||
@@ -320,7 +320,7 @@ export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// ✅ Lazy load heavy components
|
||||
// PASS: Lazy load heavy components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
|
||||
|
||||
@@ -515,7 +515,7 @@ export class ErrorBoundary extends React.Component<
|
||||
```typescript
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
// ✅ List animations
|
||||
// PASS: List animations
|
||||
export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
@@ -534,7 +534,7 @@ export function AnimatedMarketList({ markets }: { markets: Market[] }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Modal animations
|
||||
// PASS: Modal animations
|
||||
export function Modal({ isOpen, onClose, children }: ModalProps) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
|
||||
@@ -138,8 +138,7 @@ Create `.gitmessage` in repo root:
|
||||
|
||||
```
|
||||
# <type>(<scope>): <subject>
|
||||
#
|
||||
# Types: feat, fix, docs, style, refactor, test, chore, perf, ci, revert
|
||||
# # Types: feat, fix, docs, style, refactor, test, chore, perf, ci, revert
|
||||
# Scope: api, ui, db, auth, etc.
|
||||
# Subject: imperative mood, no period, max 50 chars
|
||||
#
|
||||
|
||||
@@ -15,7 +15,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "📊"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Inventory Demand Planning
|
||||
|
||||
@@ -36,12 +36,12 @@ A 4-phase loop that progressively refines context:
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ DISPATCH │─────▶│ EVALUATE │ │
|
||||
│ │ DISPATCH │─────│ EVALUATE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ ▲ │ │
|
||||
│ │ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ LOOP │◀─────│ REFINE │ │
|
||||
│ │ LOOP │─────│ REFINE │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ Max 3 cycles, then proceed │
|
||||
|
||||
@@ -26,22 +26,22 @@ Standards for readable, maintainable Java (17+) code in Spring Boot services.
|
||||
## Naming
|
||||
|
||||
```java
|
||||
// ✅ Classes/Records: PascalCase
|
||||
// PASS: Classes/Records: PascalCase
|
||||
public class MarketService {}
|
||||
public record Money(BigDecimal amount, Currency currency) {}
|
||||
|
||||
// ✅ Methods/fields: camelCase
|
||||
// PASS: Methods/fields: camelCase
|
||||
private final MarketRepository marketRepository;
|
||||
public Market findBySlug(String slug) {}
|
||||
|
||||
// ✅ Constants: UPPER_SNAKE_CASE
|
||||
// PASS: Constants: UPPER_SNAKE_CASE
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
```
|
||||
|
||||
## Immutability
|
||||
|
||||
```java
|
||||
// ✅ Favor records and final fields
|
||||
// PASS: Favor records and final fields
|
||||
public record MarketDto(Long id, String name, MarketStatus status) {}
|
||||
|
||||
public class Market {
|
||||
@@ -54,10 +54,10 @@ public class Market {
|
||||
## Optional Usage
|
||||
|
||||
```java
|
||||
// ✅ Return Optional from find* methods
|
||||
// PASS: Return Optional from find* methods
|
||||
Optional<Market> market = marketRepository.findBySlug(slug);
|
||||
|
||||
// ✅ Map/flatMap instead of get()
|
||||
// PASS: Map/flatMap instead of get()
|
||||
return market
|
||||
.map(MarketResponse::from)
|
||||
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
|
||||
@@ -66,13 +66,13 @@ return market
|
||||
## Streams Best Practices
|
||||
|
||||
```java
|
||||
// ✅ Use streams for transformations, keep pipelines short
|
||||
// PASS: Use streams for transformations, keep pipelines short
|
||||
List<String> names = markets.stream()
|
||||
.map(Market::name)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
|
||||
// ❌ Avoid complex nested streams; prefer loops for clarity
|
||||
// FAIL: Avoid complex nested streams; prefer loops for clarity
|
||||
```
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -14,7 +14,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "📦"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Logistics Exception Management
|
||||
|
||||
@@ -15,7 +15,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "🏭"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Production Scheduling
|
||||
|
||||
@@ -15,7 +15,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "🔍"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Quality & Non-Conformance Management
|
||||
|
||||
@@ -15,7 +15,7 @@ origin: ECC
|
||||
metadata:
|
||||
author: evos
|
||||
clawdbot:
|
||||
emoji: "🔄"
|
||||
emoji: ""
|
||||
---
|
||||
|
||||
# Returns & Reverse Logistics
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Output: JSON to stdout
|
||||
#
|
||||
# Environment:
|
||||
# RULES_DISTILL_DIR Override ~/.claude/rules (for testing only)
|
||||
# RULES_DISTILL_DIR Override ~/.claude/rules (for testing only)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
# script always picks up project-level skills without relying on the caller.
|
||||
#
|
||||
# Environment:
|
||||
# RULES_DISTILL_GLOBAL_DIR Override ~/.claude/skills (for testing only;
|
||||
# do not set in production — intended for bats tests)
|
||||
# RULES_DISTILL_PROJECT_DIR Override project dir detection (for testing only)
|
||||
# RULES_DISTILL_GLOBAL_DIR Override ~/.claude/skills (for testing only;
|
||||
# do not set in production — intended for bats tests)
|
||||
# RULES_DISTILL_PROJECT_DIR Override project dir detection (for testing only)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ This skill ensures all code follows security best practices and identifies poten
|
||||
|
||||
### 1. Secrets Management
|
||||
|
||||
#### ❌ NEVER Do This
|
||||
#### FAIL: NEVER Do This
|
||||
```typescript
|
||||
const apiKey = "sk-proj-xxxxx" // Hardcoded secret
|
||||
const dbPassword = "password123" // In source code
|
||||
```
|
||||
|
||||
#### ✅ ALWAYS Do This
|
||||
#### PASS: ALWAYS Do This
|
||||
```typescript
|
||||
const apiKey = process.env.OPENAI_API_KEY
|
||||
const dbUrl = process.env.DATABASE_URL
|
||||
@@ -108,14 +108,14 @@ function validateFileUpload(file: File) {
|
||||
|
||||
### 3. SQL Injection Prevention
|
||||
|
||||
#### ❌ NEVER Concatenate SQL
|
||||
#### FAIL: NEVER Concatenate SQL
|
||||
```typescript
|
||||
// DANGEROUS - SQL Injection vulnerability
|
||||
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
|
||||
await db.query(query)
|
||||
```
|
||||
|
||||
#### ✅ ALWAYS Use Parameterized Queries
|
||||
#### PASS: ALWAYS Use Parameterized Queries
|
||||
```typescript
|
||||
// Safe - parameterized query
|
||||
const { data } = await supabase
|
||||
@@ -140,10 +140,10 @@ await db.query(
|
||||
|
||||
#### JWT Token Handling
|
||||
```typescript
|
||||
// ❌ WRONG: localStorage (vulnerable to XSS)
|
||||
// FAIL: WRONG: localStorage (vulnerable to XSS)
|
||||
localStorage.setItem('token', token)
|
||||
|
||||
// ✅ CORRECT: httpOnly cookies
|
||||
// PASS: CORRECT: httpOnly cookies
|
||||
res.setHeader('Set-Cookie',
|
||||
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
|
||||
```
|
||||
@@ -300,18 +300,18 @@ app.use('/api/search', searchLimiter)
|
||||
|
||||
#### Logging
|
||||
```typescript
|
||||
// ❌ WRONG: Logging sensitive data
|
||||
// FAIL: WRONG: Logging sensitive data
|
||||
console.log('User login:', { email, password })
|
||||
console.log('Payment:', { cardNumber, cvv })
|
||||
|
||||
// ✅ CORRECT: Redact sensitive data
|
||||
// PASS: CORRECT: Redact sensitive data
|
||||
console.log('User login:', { email, userId })
|
||||
console.log('Payment:', { last4: card.last4, userId })
|
||||
```
|
||||
|
||||
#### Error Messages
|
||||
```typescript
|
||||
// ❌ WRONG: Exposing internal details
|
||||
// FAIL: WRONG: Exposing internal details
|
||||
catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message, stack: error.stack },
|
||||
@@ -319,7 +319,7 @@ catch (error) {
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ CORRECT: Generic error messages
|
||||
// PASS: CORRECT: Generic error messages
|
||||
catch (error) {
|
||||
console.error('Internal error:', error)
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -24,7 +24,7 @@ This skill ensures cloud infrastructure, CI/CD pipelines, and deployment configu
|
||||
#### Principle of Least Privilege
|
||||
|
||||
```yaml
|
||||
# ✅ CORRECT: Minimal permissions
|
||||
# PASS: CORRECT: Minimal permissions
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:GetObject # Only read access
|
||||
@@ -32,7 +32,7 @@ iam_role:
|
||||
resources:
|
||||
- arn:aws:s3:::my-bucket/* # Specific bucket only
|
||||
|
||||
# ❌ WRONG: Overly broad permissions
|
||||
# FAIL: WRONG: Overly broad permissions
|
||||
iam_role:
|
||||
permissions:
|
||||
- s3:* # All S3 actions
|
||||
@@ -65,14 +65,14 @@ aws iam enable-mfa-device \
|
||||
#### Cloud Secrets Managers
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT: Use cloud secrets manager
|
||||
// PASS: CORRECT: Use cloud secrets manager
|
||||
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
|
||||
|
||||
const client = new SecretsManager({ region: 'us-east-1' });
|
||||
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
|
||||
const apiKey = JSON.parse(secret.SecretString).key;
|
||||
|
||||
// ❌ WRONG: Hardcoded or in environment variables only
|
||||
// FAIL: WRONG: Hardcoded or in environment variables only
|
||||
const apiKey = process.env.API_KEY; // Not rotated, not audited
|
||||
```
|
||||
|
||||
@@ -99,17 +99,17 @@ aws secretsmanager rotate-secret \
|
||||
#### VPC and Firewall Configuration
|
||||
|
||||
```terraform
|
||||
# ✅ CORRECT: Restricted security group
|
||||
# PASS: CORRECT: Restricted security group
|
||||
resource "aws_security_group" "app" {
|
||||
name = "app-sg"
|
||||
|
||||
|
||||
ingress {
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["10.0.0.0/16"] # Internal VPC only
|
||||
}
|
||||
|
||||
|
||||
egress {
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
@@ -118,7 +118,7 @@ resource "aws_security_group" "app" {
|
||||
}
|
||||
}
|
||||
|
||||
# ❌ WRONG: Open to the internet
|
||||
# FAIL: WRONG: Open to the internet
|
||||
resource "aws_security_group" "bad" {
|
||||
ingress {
|
||||
from_port = 0
|
||||
@@ -142,7 +142,7 @@ resource "aws_security_group" "bad" {
|
||||
#### CloudWatch/Logging Configuration
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT: Comprehensive logging
|
||||
// PASS: CORRECT: Comprehensive logging
|
||||
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
|
||||
|
||||
const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
@@ -177,7 +177,7 @@ const logSecurityEvent = async (event: SecurityEvent) => {
|
||||
#### Secure Pipeline Configuration
|
||||
|
||||
```yaml
|
||||
# ✅ CORRECT: Secure GitHub Actions workflow
|
||||
# PASS: CORRECT: Secure GitHub Actions workflow
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
@@ -189,18 +189,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read # Minimal permissions
|
||||
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
# Scan for secrets
|
||||
- name: Secret scanning
|
||||
uses: trufflesecurity/trufflehog@main
|
||||
|
||||
|
||||
# Dependency audit
|
||||
- name: Audit dependencies
|
||||
run: npm audit --audit-level=high
|
||||
|
||||
|
||||
# Use OIDC, not long-lived tokens
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
@@ -237,18 +237,18 @@ jobs:
|
||||
#### Cloudflare Security Configuration
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT: Cloudflare Workers with security headers
|
||||
// PASS: CORRECT: Cloudflare Workers with security headers
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const response = await fetch(request);
|
||||
|
||||
|
||||
// Add security headers
|
||||
const headers = new Headers(response.headers);
|
||||
headers.set('X-Frame-Options', 'DENY');
|
||||
headers.set('X-Content-Type-Options', 'nosniff');
|
||||
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
headers.set('Permissions-Policy', 'geolocation=(), microphone=()');
|
||||
|
||||
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
headers
|
||||
@@ -281,17 +281,17 @@ export default {
|
||||
#### Automated Backups
|
||||
|
||||
```terraform
|
||||
# ✅ CORRECT: Automated RDS backups
|
||||
# PASS: CORRECT: Automated RDS backups
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = 20
|
||||
engine = "postgres"
|
||||
|
||||
|
||||
backup_retention_period = 30 # 30 days retention
|
||||
backup_window = "03:00-04:00"
|
||||
maintenance_window = "mon:04:00-mon:05:00"
|
||||
|
||||
|
||||
enabled_cloudwatch_logs_exports = ["postgresql"]
|
||||
|
||||
|
||||
deletion_protection = true # Prevent accidental deletion
|
||||
}
|
||||
```
|
||||
@@ -327,10 +327,10 @@ Before ANY production cloud deployment:
|
||||
### S3 Bucket Exposure
|
||||
|
||||
```bash
|
||||
# ❌ WRONG: Public bucket
|
||||
# FAIL: WRONG: Public bucket
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
|
||||
|
||||
# ✅ CORRECT: Private bucket with specific access
|
||||
# PASS: CORRECT: Private bucket with specific access
|
||||
aws s3api put-bucket-acl --bucket my-bucket --acl private
|
||||
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
```
|
||||
@@ -338,12 +338,12 @@ aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
|
||||
### RDS Public Access
|
||||
|
||||
```terraform
|
||||
# ❌ WRONG
|
||||
# FAIL: WRONG
|
||||
resource "aws_db_instance" "bad" {
|
||||
publicly_accessible = true # NEVER do this!
|
||||
}
|
||||
|
||||
# ✅ CORRECT
|
||||
# PASS: CORRECT
|
||||
resource "aws_db_instance" "good" {
|
||||
publicly_accessible = false
|
||||
vpc_security_group_ids = [aws_security_group.db.id]
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
# script always picks up project-level skills without relying on the caller.
|
||||
#
|
||||
# Environment:
|
||||
# SKILL_STOCKTAKE_GLOBAL_DIR Override ~/.claude/skills (for testing only;
|
||||
# do not set in production — intended for bats tests)
|
||||
# SKILL_STOCKTAKE_PROJECT_DIR Override project dir detection (for testing only)
|
||||
# SKILL_STOCKTAKE_GLOBAL_DIR Override ~/.claude/skills (for testing only;
|
||||
# do not set in production — intended for bats tests)
|
||||
# SKILL_STOCKTAKE_PROJECT_DIR Override project dir detection (for testing only)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Usage: save-results.sh RESULTS_JSON <<< "$EVAL_JSON"
|
||||
#
|
||||
# stdin format:
|
||||
# { "skills": {...}, "mode"?: "full"|"quick", "batch_progress"?: {...} }
|
||||
# { "skills": {...}, "mode"?: "full"|"quick", "batch_progress"?: {...} }
|
||||
#
|
||||
# Always sets evaluated_at to current UTC time via `date -u`.
|
||||
# Merges stdin .skills into existing results.json (new entries override old).
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
# script always picks up project-level skills without relying on the caller.
|
||||
#
|
||||
# Environment:
|
||||
# SKILL_STOCKTAKE_GLOBAL_DIR Override ~/.claude/skills (for testing only;
|
||||
# do not set in production — intended for bats tests)
|
||||
# SKILL_STOCKTAKE_PROJECT_DIR Override project dir detection (for testing only)
|
||||
# SKILL_STOCKTAKE_GLOBAL_DIR Override ~/.claude/skills (for testing only;
|
||||
# do not set in production — intended for bats tests)
|
||||
# SKILL_STOCKTAKE_PROJECT_DIR Override project dir detection (for testing only)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
#
|
||||
# Hook config (in ~/.claude/settings.json):
|
||||
# {
|
||||
# "hooks": {
|
||||
# "PreToolUse": [{
|
||||
# "matcher": "Edit|Write",
|
||||
# "hooks": [{
|
||||
# "type": "command",
|
||||
# "command": "~/.claude/skills/strategic-compact/suggest-compact.sh"
|
||||
# }]
|
||||
# }]
|
||||
# }
|
||||
# "hooks": {
|
||||
# "PreToolUse": [{
|
||||
# "matcher": "Edit|Write",
|
||||
# "hooks": [{
|
||||
# "type": "command",
|
||||
# "command": "~/.claude/skills/strategic-compact/suggest-compact.sh"
|
||||
# }]
|
||||
# }]
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# Criteria for suggesting compact:
|
||||
|
||||
@@ -367,39 +367,39 @@ npm run test:coverage
|
||||
|
||||
## Common Testing Mistakes to Avoid
|
||||
|
||||
### ❌ WRONG: Testing Implementation Details
|
||||
### FAIL: WRONG: Testing Implementation Details
|
||||
```typescript
|
||||
// Don't test internal state
|
||||
expect(component.state.count).toBe(5)
|
||||
```
|
||||
|
||||
### ✅ CORRECT: Test User-Visible Behavior
|
||||
### PASS: CORRECT: Test User-Visible Behavior
|
||||
```typescript
|
||||
// Test what users see
|
||||
expect(screen.getByText('Count: 5')).toBeInTheDocument()
|
||||
```
|
||||
|
||||
### ❌ WRONG: Brittle Selectors
|
||||
### FAIL: WRONG: Brittle Selectors
|
||||
```typescript
|
||||
// Breaks easily
|
||||
await page.click('.css-class-xyz')
|
||||
```
|
||||
|
||||
### ✅ CORRECT: Semantic Selectors
|
||||
### PASS: CORRECT: Semantic Selectors
|
||||
```typescript
|
||||
// Resilient to changes
|
||||
await page.click('button:has-text("Submit")')
|
||||
await page.click('[data-testid="submit-button"]')
|
||||
```
|
||||
|
||||
### ❌ WRONG: No Test Isolation
|
||||
### FAIL: WRONG: No Test Isolation
|
||||
```typescript
|
||||
// Tests depend on each other
|
||||
test('creates user', () => { /* ... */ })
|
||||
test('updates same user', () => { /* depends on previous test */ })
|
||||
```
|
||||
|
||||
### ✅ CORRECT: Independent Tests
|
||||
### PASS: CORRECT: Independent Tests
|
||||
```typescript
|
||||
// Each test sets up its own data
|
||||
test('creates user', () => {
|
||||
|
||||
@@ -273,7 +273,7 @@ Examples:
|
||||
# Audio: every 50 words
|
||||
{"type": "word", "value": 50}
|
||||
|
||||
# Audio: every 30 seconds
|
||||
# Audio: every 30 seconds
|
||||
{"type": "time", "value": 30}
|
||||
|
||||
# Visual: 5 frames every 2 seconds
|
||||
|
||||
@@ -78,7 +78,7 @@ def ensure_private_dir(path: Path) -> Path:
|
||||
def parse_args() -> tuple[bool, Path]:
|
||||
clear = False
|
||||
output_dir: str | None = None
|
||||
|
||||
|
||||
args = sys.argv[1:]
|
||||
for arg in args:
|
||||
if arg == "--clear":
|
||||
@@ -87,7 +87,7 @@ def parse_args() -> tuple[bool, Path]:
|
||||
raise SystemExit(f"Unknown flag: {arg}")
|
||||
elif not arg.startswith("-"):
|
||||
output_dir = arg
|
||||
|
||||
|
||||
if output_dir is None:
|
||||
events_dir = os.environ.get("VIDEODB_EVENTS_DIR")
|
||||
if events_dir:
|
||||
@@ -147,10 +147,10 @@ def is_fatal_error(exc: Exception) -> bool:
|
||||
async def listen_with_retry():
|
||||
"""Main listen loop with auto-reconnect and exponential backoff."""
|
||||
global _first_connection
|
||||
|
||||
|
||||
retry_count = 0
|
||||
backoff = INITIAL_BACKOFF
|
||||
|
||||
|
||||
while retry_count < MAX_RETRIES:
|
||||
try:
|
||||
conn = videodb.connect()
|
||||
@@ -168,11 +168,11 @@ async def listen_with_retry():
|
||||
raise
|
||||
retry_count += 1
|
||||
log(f"Connection error: {e}")
|
||||
|
||||
|
||||
if retry_count >= MAX_RETRIES:
|
||||
log(f"Max retries ({MAX_RETRIES}) exceeded, exiting")
|
||||
break
|
||||
|
||||
|
||||
log(f"Reconnecting in {backoff}s (attempt {retry_count}/{MAX_RETRIES})...")
|
||||
await asyncio.sleep(backoff)
|
||||
backoff = min(backoff * 2, MAX_BACKOFF)
|
||||
@@ -233,20 +233,20 @@ async def main_async():
|
||||
"""Async main with signal handling."""
|
||||
loop = asyncio.get_running_loop()
|
||||
shutdown_event = asyncio.Event()
|
||||
|
||||
|
||||
def handle_signal():
|
||||
log("Received shutdown signal")
|
||||
shutdown_event.set()
|
||||
|
||||
|
||||
# Register signal handlers
|
||||
for sig in (signal.SIGINT, signal.SIGTERM):
|
||||
with contextlib.suppress(NotImplementedError):
|
||||
loop.add_signal_handler(sig, handle_signal)
|
||||
|
||||
|
||||
# Run listener with cancellation support
|
||||
listen_task = asyncio.create_task(listen_with_retry())
|
||||
shutdown_task = asyncio.create_task(shutdown_event.wait())
|
||||
|
||||
|
||||
_done, pending = await asyncio.wait(
|
||||
[listen_task, shutdown_task],
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
@@ -254,7 +254,7 @@ async def main_async():
|
||||
|
||||
if listen_task.done():
|
||||
await listen_task
|
||||
|
||||
|
||||
# Cancel remaining tasks
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
@@ -266,7 +266,7 @@ async def main_async():
|
||||
for sig in (signal.SIGINT, signal.SIGTERM):
|
||||
with contextlib.suppress(NotImplementedError):
|
||||
loop.remove_signal_handler(sig)
|
||||
|
||||
|
||||
log("Shutdown complete")
|
||||
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ Automatically translate visa application documents from images to professional E
|
||||
|
||||
## Features
|
||||
|
||||
- 🔄 **Automatic OCR**: Tries multiple OCR methods (macOS Vision, EasyOCR, Tesseract)
|
||||
- 📄 **Bilingual PDF**: Original image + professional English translation
|
||||
- 🌍 **Multi-language**: Supports Chinese, and other languages
|
||||
- 📋 **Professional Format**: Suitable for official visa applications
|
||||
- 🚀 **Fully Automated**: No manual intervention required
|
||||
- **Automatic OCR**: Tries multiple OCR methods (macOS Vision, EasyOCR, Tesseract)
|
||||
- **Bilingual PDF**: Original image + professional English translation
|
||||
- **Multi-language**: Supports Chinese, and other languages
|
||||
- **Professional Format**: Suitable for official visa applications
|
||||
- **Fully Automated**: No manual intervention required
|
||||
|
||||
## Supported Documents
|
||||
|
||||
@@ -75,11 +75,11 @@ pip install pytesseract
|
||||
|
||||
## Perfect For
|
||||
|
||||
- 🇦🇺 Australia visa applications
|
||||
- 🇺🇸 USA visa applications
|
||||
- 🇨🇦 Canada visa applications
|
||||
- 🇬🇧 UK visa applications
|
||||
- 🇪🇺 EU visa applications
|
||||
- Australia visa applications
|
||||
- USA visa applications
|
||||
- Canada visa applications
|
||||
- UK visa applications
|
||||
- EU visa applications
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user