claude-termux 1.0.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/CLAUDE.md +60 -0
- package/GEMINI.md +20 -0
- package/README.md +135 -0
- package/TERMUX.md +204 -0
- package/agents/accessibility-reviewer.md +96 -0
- package/agents/ai-prompt-optimizer.md +94 -0
- package/agents/api-tester.md +102 -0
- package/agents/code-generator.md +94 -0
- package/agents/code-reviewer.md +47 -0
- package/agents/component-generator.md +102 -0
- package/agents/doc-generator.md +91 -0
- package/agents/migration-generator.md +94 -0
- package/agents/performance-analyzer.md +90 -0
- package/agents/proactive-mode.md +91 -0
- package/agents/readme-generator.md +101 -0
- package/agents/security-auditor.md +86 -0
- package/agents/terraform-generator.md +94 -0
- package/agents/test-generator.md +76 -0
- package/commands/brainstorm.md +5 -0
- package/commands/execute-plan.md +5 -0
- package/commands/write-plan.md +5 -0
- package/hooks/auto-context.json +31 -0
- package/hooks/hooks.json +15 -0
- package/hooks/run-hook.cmd +19 -0
- package/hooks/session-start.sh +52 -0
- package/hooks/smart-session.sh +96 -0
- package/install.sh +210 -0
- package/lib/skills-core.js +208 -0
- package/mcp.json +34 -0
- package/package.json +49 -0
- package/plugins/README.md +47 -0
- package/plugins/installed_plugins.json +5 -0
- package/plugins/known_marketplaces.json +10 -0
- package/plugins/marketplace-info/marketplace.json +517 -0
- package/postinstall.js +238 -0
- package/settings.json +27 -0
- package/settings.local.json +25 -0
- package/skills/api-development/SKILL.md +11 -0
- package/skills/api-development/openapi/api-documentation.yaml +108 -0
- package/skills/brainstorming/SKILL.md +54 -0
- package/skills/code-quality/SKILL.md +196 -0
- package/skills/condition-based-waiting/SKILL.md +120 -0
- package/skills/condition-based-waiting/example.ts +158 -0
- package/skills/database-development/SKILL.md +11 -0
- package/skills/database-development/migrations/migration.template.sql +49 -0
- package/skills/defense-in-depth/SKILL.md +127 -0
- package/skills/deployment/SKILL.md +11 -0
- package/skills/deployment/ci-cd/github-actions.yml +95 -0
- package/skills/deployment/docker/Dockerfile.template +39 -0
- package/skills/dispatching-parallel-agents/SKILL.md +180 -0
- package/skills/documentation-generation/SKILL.md +8 -0
- package/skills/documentation-generation/templates/README.template.md +60 -0
- package/skills/error-handling/SKILL.md +267 -0
- package/skills/executing-plans/SKILL.md +76 -0
- package/skills/finishing-a-development-branch/SKILL.md +200 -0
- package/skills/frontend-design/frontend-design/SKILL.md +42 -0
- package/skills/integration-testing/SKILL.md +13 -0
- package/skills/integration-testing/examples/contract-test.py +317 -0
- package/skills/integration-testing/examples/e2e-test.js +147 -0
- package/skills/integration-testing/examples/test-isolation.md +94 -0
- package/skills/logging-monitoring/SKILL.md +66 -0
- package/skills/mobile-development/SKILL.md +11 -0
- package/skills/mobile-development/responsive/responsive.css +80 -0
- package/skills/performance-optimization/SKILL.md +9 -0
- package/skills/performance-optimization/profiling/profile.template.js +21 -0
- package/skills/receiving-code-review/SKILL.md +209 -0
- package/skills/refactoring/SKILL.md +11 -0
- package/skills/refactoring/code-smells/common-smells.md +115 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +146 -0
- package/skills/root-cause-tracing/SKILL.md +174 -0
- package/skills/root-cause-tracing/find-polluter.sh +63 -0
- package/skills/security-review/SKILL.md +11 -0
- package/skills/security-review/checklists/owasp-checklist.md +31 -0
- package/skills/sharing-skills/SKILL.md +194 -0
- package/skills/subagent-driven-development/SKILL.md +240 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
- package/skills/subagent-driven-development/implementer-prompt.md +78 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +295 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +364 -0
- package/skills/testing-anti-patterns/SKILL.md +302 -0
- package/skills/testing-skills-with-subagents/SKILL.md +387 -0
- package/skills/testing-skills-with-subagents/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/ui-ux-review/SKILL.md +13 -0
- package/skills/ui-ux-review/checklists/ux-heuristics.md +61 -0
- package/skills/using-git-worktrees/SKILL.md +213 -0
- package/skills/using-superpowers/SKILL.md +101 -0
- package/skills/verification-before-completion/SKILL.md +139 -0
- package/skills/writing-plans/SKILL.md +116 -0
- package/skills/writing-skills/SKILL.md +622 -0
- package/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/writing-skills/persuasion-principles.md +187 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: condition-based-waiting
|
|
3
|
+
description: Use when tests have race conditions, timing dependencies, or inconsistent pass/fail behavior - replaces arbitrary timeouts with condition polling to wait for actual state changes, eliminating flaky tests from timing guesses
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Condition-Based Waiting
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI.
|
|
11
|
+
|
|
12
|
+
**Core principle:** Wait for the actual condition you care about, not a guess about how long it takes.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
```dot
|
|
17
|
+
digraph when_to_use {
|
|
18
|
+
"Test uses setTimeout/sleep?" [shape=diamond];
|
|
19
|
+
"Testing timing behavior?" [shape=diamond];
|
|
20
|
+
"Document WHY timeout needed" [shape=box];
|
|
21
|
+
"Use condition-based waiting" [shape=box];
|
|
22
|
+
|
|
23
|
+
"Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
|
|
24
|
+
"Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
|
|
25
|
+
"Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Use when:**
|
|
30
|
+
- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
|
|
31
|
+
- Tests are flaky (pass sometimes, fail under load)
|
|
32
|
+
- Tests timeout when run in parallel
|
|
33
|
+
- Waiting for async operations to complete
|
|
34
|
+
|
|
35
|
+
**Don't use when:**
|
|
36
|
+
- Testing actual timing behavior (debounce, throttle intervals)
|
|
37
|
+
- Always document WHY if using arbitrary timeout
|
|
38
|
+
|
|
39
|
+
## Core Pattern
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// ❌ BEFORE: Guessing at timing
|
|
43
|
+
await new Promise(r => setTimeout(r, 50));
|
|
44
|
+
const result = getResult();
|
|
45
|
+
expect(result).toBeDefined();
|
|
46
|
+
|
|
47
|
+
// ✅ AFTER: Waiting for condition
|
|
48
|
+
await waitFor(() => getResult() !== undefined);
|
|
49
|
+
const result = getResult();
|
|
50
|
+
expect(result).toBeDefined();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Patterns
|
|
54
|
+
|
|
55
|
+
| Scenario | Pattern |
|
|
56
|
+
|----------|---------|
|
|
57
|
+
| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
|
|
58
|
+
| Wait for state | `waitFor(() => machine.state === 'ready')` |
|
|
59
|
+
| Wait for count | `waitFor(() => items.length >= 5)` |
|
|
60
|
+
| Wait for file | `waitFor(() => fs.existsSync(path))` |
|
|
61
|
+
| Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |
|
|
62
|
+
|
|
63
|
+
## Implementation
|
|
64
|
+
|
|
65
|
+
Generic polling function:
|
|
66
|
+
```typescript
|
|
67
|
+
async function waitFor<T>(
|
|
68
|
+
condition: () => T | undefined | null | false,
|
|
69
|
+
description: string,
|
|
70
|
+
timeoutMs = 5000
|
|
71
|
+
): Promise<T> {
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
|
|
74
|
+
while (true) {
|
|
75
|
+
const result = condition();
|
|
76
|
+
if (result) return result;
|
|
77
|
+
|
|
78
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
79
|
+
throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
See @example.ts for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`) from actual debugging session.
|
|
88
|
+
|
|
89
|
+
## Common Mistakes
|
|
90
|
+
|
|
91
|
+
**❌ Polling too fast:** `setTimeout(check, 1)` - wastes CPU
|
|
92
|
+
**✅ Fix:** Poll every 10ms
|
|
93
|
+
|
|
94
|
+
**❌ No timeout:** Loop forever if condition never met
|
|
95
|
+
**✅ Fix:** Always include timeout with clear error
|
|
96
|
+
|
|
97
|
+
**❌ Stale data:** Cache state before loop
|
|
98
|
+
**✅ Fix:** Call getter inside loop for fresh data
|
|
99
|
+
|
|
100
|
+
## When Arbitrary Timeout IS Correct
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Tool ticks every 100ms - need 2 ticks to verify partial output
|
|
104
|
+
await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
|
|
105
|
+
await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
|
|
106
|
+
// 200ms = 2 ticks at 100ms intervals - documented and justified
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Requirements:**
|
|
110
|
+
1. First wait for triggering condition
|
|
111
|
+
2. Based on known timing (not guessing)
|
|
112
|
+
3. Comment explaining WHY
|
|
113
|
+
|
|
114
|
+
## Real-World Impact
|
|
115
|
+
|
|
116
|
+
From debugging session (2025-10-03):
|
|
117
|
+
- Fixed 15 flaky tests across 3 files
|
|
118
|
+
- Pass rate: 60% → 100%
|
|
119
|
+
- Execution time: 40% faster
|
|
120
|
+
- No more race conditions
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Complete implementation of condition-based waiting utilities
|
|
2
|
+
// From: Lace test infrastructure improvements (2025-10-03)
|
|
3
|
+
// Context: Fixed 15 flaky tests by replacing arbitrary timeouts
|
|
4
|
+
|
|
5
|
+
import type { ThreadManager } from '~/threads/thread-manager';
|
|
6
|
+
import type { LaceEvent, LaceEventType } from '~/threads/types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Wait for a specific event type to appear in thread
|
|
10
|
+
*
|
|
11
|
+
* @param threadManager - The thread manager to query
|
|
12
|
+
* @param threadId - Thread to check for events
|
|
13
|
+
* @param eventType - Type of event to wait for
|
|
14
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
15
|
+
* @returns Promise resolving to the first matching event
|
|
16
|
+
*
|
|
17
|
+
* Example:
|
|
18
|
+
* await waitForEvent(threadManager, agentThreadId, 'TOOL_RESULT');
|
|
19
|
+
*/
|
|
20
|
+
export function waitForEvent(
|
|
21
|
+
threadManager: ThreadManager,
|
|
22
|
+
threadId: string,
|
|
23
|
+
eventType: LaceEventType,
|
|
24
|
+
timeoutMs = 5000
|
|
25
|
+
): Promise<LaceEvent> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
|
|
29
|
+
const check = () => {
|
|
30
|
+
const events = threadManager.getEvents(threadId);
|
|
31
|
+
const event = events.find((e) => e.type === eventType);
|
|
32
|
+
|
|
33
|
+
if (event) {
|
|
34
|
+
resolve(event);
|
|
35
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
36
|
+
reject(new Error(`Timeout waiting for ${eventType} event after ${timeoutMs}ms`));
|
|
37
|
+
} else {
|
|
38
|
+
setTimeout(check, 10); // Poll every 10ms for efficiency
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
check();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Wait for a specific number of events of a given type
|
|
48
|
+
*
|
|
49
|
+
* @param threadManager - The thread manager to query
|
|
50
|
+
* @param threadId - Thread to check for events
|
|
51
|
+
* @param eventType - Type of event to wait for
|
|
52
|
+
* @param count - Number of events to wait for
|
|
53
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
54
|
+
* @returns Promise resolving to all matching events once count is reached
|
|
55
|
+
*
|
|
56
|
+
* Example:
|
|
57
|
+
* // Wait for 2 AGENT_MESSAGE events (initial response + continuation)
|
|
58
|
+
* await waitForEventCount(threadManager, agentThreadId, 'AGENT_MESSAGE', 2);
|
|
59
|
+
*/
|
|
60
|
+
export function waitForEventCount(
|
|
61
|
+
threadManager: ThreadManager,
|
|
62
|
+
threadId: string,
|
|
63
|
+
eventType: LaceEventType,
|
|
64
|
+
count: number,
|
|
65
|
+
timeoutMs = 5000
|
|
66
|
+
): Promise<LaceEvent[]> {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
|
|
70
|
+
const check = () => {
|
|
71
|
+
const events = threadManager.getEvents(threadId);
|
|
72
|
+
const matchingEvents = events.filter((e) => e.type === eventType);
|
|
73
|
+
|
|
74
|
+
if (matchingEvents.length >= count) {
|
|
75
|
+
resolve(matchingEvents);
|
|
76
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
77
|
+
reject(
|
|
78
|
+
new Error(
|
|
79
|
+
`Timeout waiting for ${count} ${eventType} events after ${timeoutMs}ms (got ${matchingEvents.length})`
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
setTimeout(check, 10);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
check();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Wait for an event matching a custom predicate
|
|
93
|
+
* Useful when you need to check event data, not just type
|
|
94
|
+
*
|
|
95
|
+
* @param threadManager - The thread manager to query
|
|
96
|
+
* @param threadId - Thread to check for events
|
|
97
|
+
* @param predicate - Function that returns true when event matches
|
|
98
|
+
* @param description - Human-readable description for error messages
|
|
99
|
+
* @param timeoutMs - Maximum time to wait (default 5000ms)
|
|
100
|
+
* @returns Promise resolving to the first matching event
|
|
101
|
+
*
|
|
102
|
+
* Example:
|
|
103
|
+
* // Wait for TOOL_RESULT with specific ID
|
|
104
|
+
* await waitForEventMatch(
|
|
105
|
+
* threadManager,
|
|
106
|
+
* agentThreadId,
|
|
107
|
+
* (e) => e.type === 'TOOL_RESULT' && e.data.id === 'call_123',
|
|
108
|
+
* 'TOOL_RESULT with id=call_123'
|
|
109
|
+
* );
|
|
110
|
+
*/
|
|
111
|
+
export function waitForEventMatch(
|
|
112
|
+
threadManager: ThreadManager,
|
|
113
|
+
threadId: string,
|
|
114
|
+
predicate: (event: LaceEvent) => boolean,
|
|
115
|
+
description: string,
|
|
116
|
+
timeoutMs = 5000
|
|
117
|
+
): Promise<LaceEvent> {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
|
|
121
|
+
const check = () => {
|
|
122
|
+
const events = threadManager.getEvents(threadId);
|
|
123
|
+
const event = events.find(predicate);
|
|
124
|
+
|
|
125
|
+
if (event) {
|
|
126
|
+
resolve(event);
|
|
127
|
+
} else if (Date.now() - startTime > timeoutMs) {
|
|
128
|
+
reject(new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`));
|
|
129
|
+
} else {
|
|
130
|
+
setTimeout(check, 10);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
check();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Usage example from actual debugging session:
|
|
139
|
+
//
|
|
140
|
+
// BEFORE (flaky):
|
|
141
|
+
// ---------------
|
|
142
|
+
// const messagePromise = agent.sendMessage('Execute tools');
|
|
143
|
+
// await new Promise(r => setTimeout(r, 300)); // Hope tools start in 300ms
|
|
144
|
+
// agent.abort();
|
|
145
|
+
// await messagePromise;
|
|
146
|
+
// await new Promise(r => setTimeout(r, 50)); // Hope results arrive in 50ms
|
|
147
|
+
// expect(toolResults.length).toBe(2); // Fails randomly
|
|
148
|
+
//
|
|
149
|
+
// AFTER (reliable):
|
|
150
|
+
// ----------------
|
|
151
|
+
// const messagePromise = agent.sendMessage('Execute tools');
|
|
152
|
+
// await waitForEventCount(threadManager, threadId, 'TOOL_CALL', 2); // Wait for tools to start
|
|
153
|
+
// agent.abort();
|
|
154
|
+
// await messagePromise;
|
|
155
|
+
// await waitForEventCount(threadManager, threadId, 'TOOL_RESULT', 2); // Wait for results
|
|
156
|
+
// expect(toolResults.length).toBe(2); // Always succeeds
|
|
157
|
+
//
|
|
158
|
+
// Result: 60% pass rate → 100%, 40% faster execution
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
database-development skill helps design efficient, scalable database schemas and manage database migrations effectively.
|
|
2
|
+
|
|
3
|
+
For code review, check that:
|
|
4
|
+
1. Database schema follows normalization principles
|
|
5
|
+
2. Proper indexes are created for query optimization
|
|
6
|
+
3. Foreign key constraints are defined
|
|
7
|
+
4. Migration files are reversible
|
|
8
|
+
5. Connection pooling is implemented
|
|
9
|
+
6. Query N+1 problems are avoided
|
|
10
|
+
7. Database transactions are used appropriately
|
|
11
|
+
8. Sensitive data is encrypted in database
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- Migration: [Description]
|
|
2
|
+
-- Created: [Date]
|
|
3
|
+
-- Author: [Author]
|
|
4
|
+
|
|
5
|
+
-- Enable transaction for safety
|
|
6
|
+
BEGIN;
|
|
7
|
+
|
|
8
|
+
-- Add new table
|
|
9
|
+
CREATE TABLE [table_name] (
|
|
10
|
+
id SERIAL PRIMARY KEY,
|
|
11
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
12
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
13
|
+
-- Add columns here
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
-- Add indexes if needed
|
|
17
|
+
CREATE INDEX [index_name] ON [table_name]([column_name]);
|
|
18
|
+
|
|
19
|
+
-- Add foreign key constraints
|
|
20
|
+
ALTER TABLE [table_name]
|
|
21
|
+
ADD CONSTRAINT [constraint_name]
|
|
22
|
+
FOREIGN KEY ([column_name])
|
|
23
|
+
REFERENCES [other_table]([column_name]);
|
|
24
|
+
|
|
25
|
+
-- Add columns to existing tables
|
|
26
|
+
ALTER TABLE [existing_table]
|
|
27
|
+
ADD COLUMN [column_name] [type] [constraints];
|
|
28
|
+
|
|
29
|
+
-- Update timestamp
|
|
30
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
31
|
+
RETURNS TRIGGER AS $$
|
|
32
|
+
BEGIN
|
|
33
|
+
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
34
|
+
RETURN NEW;
|
|
35
|
+
END;
|
|
36
|
+
$$ language 'plpgsql';
|
|
37
|
+
|
|
38
|
+
CREATE TRIGGER update_[table_name]_updated_at
|
|
39
|
+
BEFORE UPDATE ON [table_name]
|
|
40
|
+
FOR EACH ROW
|
|
41
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
42
|
+
|
|
43
|
+
COMMIT;
|
|
44
|
+
|
|
45
|
+
-- Rollback script (keep for reference):
|
|
46
|
+
-- BEGIN;
|
|
47
|
+
-- DROP TABLE IF EXISTS [table_name];
|
|
48
|
+
-- ALTER TABLE [existing_table] DROP COLUMN [column_name];
|
|
49
|
+
-- COMMIT;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: defense-in-depth
|
|
3
|
+
description: Use when invalid data causes failures deep in execution, requiring validation at multiple system layers - validates at every layer data passes through to make bugs structurally impossible
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Defense-in-Depth Validation
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
When you fix a bug caused by invalid data, adding validation at one place feels sufficient. But that single check can be bypassed by different code paths, refactoring, or mocks.
|
|
11
|
+
|
|
12
|
+
**Core principle:** Validate at EVERY layer data passes through. Make the bug structurally impossible.
|
|
13
|
+
|
|
14
|
+
## Why Multiple Layers
|
|
15
|
+
|
|
16
|
+
Single validation: "We fixed the bug"
|
|
17
|
+
Multiple layers: "We made the bug impossible"
|
|
18
|
+
|
|
19
|
+
Different layers catch different cases:
|
|
20
|
+
- Entry validation catches most bugs
|
|
21
|
+
- Business logic catches edge cases
|
|
22
|
+
- Environment guards prevent context-specific dangers
|
|
23
|
+
- Debug logging helps when other layers fail
|
|
24
|
+
|
|
25
|
+
## The Four Layers
|
|
26
|
+
|
|
27
|
+
### Layer 1: Entry Point Validation
|
|
28
|
+
**Purpose:** Reject obviously invalid input at API boundary
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
function createProject(name: string, workingDirectory: string) {
|
|
32
|
+
if (!workingDirectory || workingDirectory.trim() === '') {
|
|
33
|
+
throw new Error('workingDirectory cannot be empty');
|
|
34
|
+
}
|
|
35
|
+
if (!existsSync(workingDirectory)) {
|
|
36
|
+
throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
|
|
37
|
+
}
|
|
38
|
+
if (!statSync(workingDirectory).isDirectory()) {
|
|
39
|
+
throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
|
|
40
|
+
}
|
|
41
|
+
// ... proceed
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Layer 2: Business Logic Validation
|
|
46
|
+
**Purpose:** Ensure data makes sense for this operation
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
function initializeWorkspace(projectDir: string, sessionId: string) {
|
|
50
|
+
if (!projectDir) {
|
|
51
|
+
throw new Error('projectDir required for workspace initialization');
|
|
52
|
+
}
|
|
53
|
+
// ... proceed
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Layer 3: Environment Guards
|
|
58
|
+
**Purpose:** Prevent dangerous operations in specific contexts
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
async function gitInit(directory: string) {
|
|
62
|
+
// In tests, refuse git init outside temp directories
|
|
63
|
+
if (process.env.NODE_ENV === 'test') {
|
|
64
|
+
const normalized = normalize(resolve(directory));
|
|
65
|
+
const tmpDir = normalize(resolve(tmpdir()));
|
|
66
|
+
|
|
67
|
+
if (!normalized.startsWith(tmpDir)) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Refusing git init outside temp dir during tests: ${directory}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ... proceed
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Layer 4: Debug Instrumentation
|
|
78
|
+
**Purpose:** Capture context for forensics
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
async function gitInit(directory: string) {
|
|
82
|
+
const stack = new Error().stack;
|
|
83
|
+
logger.debug('About to git init', {
|
|
84
|
+
directory,
|
|
85
|
+
cwd: process.cwd(),
|
|
86
|
+
stack,
|
|
87
|
+
});
|
|
88
|
+
// ... proceed
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Applying the Pattern
|
|
93
|
+
|
|
94
|
+
When you find a bug:
|
|
95
|
+
|
|
96
|
+
1. **Trace the data flow** - Where does bad value originate? Where used?
|
|
97
|
+
2. **Map all checkpoints** - List every point data passes through
|
|
98
|
+
3. **Add validation at each layer** - Entry, business, environment, debug
|
|
99
|
+
4. **Test each layer** - Try to bypass layer 1, verify layer 2 catches it
|
|
100
|
+
|
|
101
|
+
## Example from Session
|
|
102
|
+
|
|
103
|
+
Bug: Empty `projectDir` caused `git init` in source code
|
|
104
|
+
|
|
105
|
+
**Data flow:**
|
|
106
|
+
1. Test setup → empty string
|
|
107
|
+
2. `Project.create(name, '')`
|
|
108
|
+
3. `WorkspaceManager.createWorkspace('')`
|
|
109
|
+
4. `git init` runs in `process.cwd()`
|
|
110
|
+
|
|
111
|
+
**Four layers added:**
|
|
112
|
+
- Layer 1: `Project.create()` validates not empty/exists/writable
|
|
113
|
+
- Layer 2: `WorkspaceManager` validates projectDir not empty
|
|
114
|
+
- Layer 3: `WorktreeManager` refuses git init outside tmpdir in tests
|
|
115
|
+
- Layer 4: Stack trace logging before git init
|
|
116
|
+
|
|
117
|
+
**Result:** All 1847 tests passed, bug impossible to reproduce
|
|
118
|
+
|
|
119
|
+
## Key Insight
|
|
120
|
+
|
|
121
|
+
All four layers were necessary. During testing, each layer caught bugs the others missed:
|
|
122
|
+
- Different code paths bypassed entry validation
|
|
123
|
+
- Mocks bypassed business logic checks
|
|
124
|
+
- Edge cases on different platforms needed environment guards
|
|
125
|
+
- Debug logging identified structural misuse
|
|
126
|
+
|
|
127
|
+
**Don't stop at one validation point.** Add checks at every layer.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
deployment skill helps automate application deployment through containerization, CI/CD pipelines, and monitoring setup.
|
|
2
|
+
|
|
3
|
+
For code review, check that:
|
|
4
|
+
1. Dockerfile follows best practices
|
|
5
|
+
2. Multi-stage builds are used to reduce image size
|
|
6
|
+
3. Health checks are implemented
|
|
7
|
+
4. Environment variables are properly managed
|
|
8
|
+
5. CI/CD pipeline tests all deployments
|
|
9
|
+
6. Monitoring and logging are configured
|
|
10
|
+
7. Rollback strategies are in place
|
|
11
|
+
8. Zero-downtime deployment is considered
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
name: CI/CD Pipeline
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, develop ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
node-version: [18.x, 20.x]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: ${{ matrix.node-version }}
|
|
23
|
+
cache: 'npm'
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm ci
|
|
27
|
+
|
|
28
|
+
- name: Run linting
|
|
29
|
+
run: npm run lint
|
|
30
|
+
|
|
31
|
+
- name: Run type checking
|
|
32
|
+
run: npm run type-check
|
|
33
|
+
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: npm run test
|
|
36
|
+
|
|
37
|
+
- name: Run e2e tests
|
|
38
|
+
run: npm run test:e2e
|
|
39
|
+
|
|
40
|
+
security:
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@v4
|
|
44
|
+
|
|
45
|
+
- name: Run security audit
|
|
46
|
+
run: npm audit --audit-level=high
|
|
47
|
+
|
|
48
|
+
- name: Run Snyk security scan
|
|
49
|
+
uses: snyk/actions/node@master
|
|
50
|
+
env:
|
|
51
|
+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
|
52
|
+
|
|
53
|
+
build:
|
|
54
|
+
needs: [test, security]
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
|
|
60
|
+
- name: Setup Node.js
|
|
61
|
+
uses: actions/setup-node@v4
|
|
62
|
+
with:
|
|
63
|
+
node-version: '20.x'
|
|
64
|
+
cache: 'npm'
|
|
65
|
+
|
|
66
|
+
- name: Install dependencies
|
|
67
|
+
run: npm ci
|
|
68
|
+
|
|
69
|
+
- name: Build application
|
|
70
|
+
run: npm run build
|
|
71
|
+
|
|
72
|
+
- name: Upload build artifacts
|
|
73
|
+
uses: actions/upload-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
name: build-artifacts
|
|
76
|
+
path: dist/
|
|
77
|
+
|
|
78
|
+
deploy:
|
|
79
|
+
needs: build
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
if: github.ref == 'refs/heads/main'
|
|
82
|
+
|
|
83
|
+
environment: production
|
|
84
|
+
|
|
85
|
+
steps:
|
|
86
|
+
- name: Download build artifacts
|
|
87
|
+
uses: actions/download-artifact@v4
|
|
88
|
+
with:
|
|
89
|
+
name: build-artifacts
|
|
90
|
+
path: dist/
|
|
91
|
+
|
|
92
|
+
- name: Deploy to production
|
|
93
|
+
run: |
|
|
94
|
+
echo "Deploy to production server"
|
|
95
|
+
# Add deployment commands here
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Multi-stage build for optimal image size
|
|
2
|
+
FROM node:18-alpine AS builder
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
COPY package*.json ./
|
|
6
|
+
RUN npm ci --only=production
|
|
7
|
+
|
|
8
|
+
# Copy source code
|
|
9
|
+
COPY . .
|
|
10
|
+
|
|
11
|
+
# Build application
|
|
12
|
+
RUN npm run build
|
|
13
|
+
|
|
14
|
+
# Production stage
|
|
15
|
+
FROM node:18-alpine AS production
|
|
16
|
+
|
|
17
|
+
# Create app user
|
|
18
|
+
RUN addgroup -g 1001 -S nodejs
|
|
19
|
+
RUN adduser -S nextjs -u 1001
|
|
20
|
+
|
|
21
|
+
WORKDIR /app
|
|
22
|
+
|
|
23
|
+
# Copy built application
|
|
24
|
+
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
|
|
25
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
26
|
+
COPY --from=builder /app/package.json ./package.json
|
|
27
|
+
|
|
28
|
+
# Health check
|
|
29
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
30
|
+
CMD curl -f http://localhost:3000/health || exit 1
|
|
31
|
+
|
|
32
|
+
USER nextjs
|
|
33
|
+
|
|
34
|
+
EXPOSE 3000
|
|
35
|
+
|
|
36
|
+
ENV NODE_ENV=production
|
|
37
|
+
ENV PORT=3000
|
|
38
|
+
|
|
39
|
+
CMD ["npm", "start"]
|