@complexthings/superpowers-agent 9.2.1 → 10.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/.agents/skills/claude-handoff/SKILL.md +18 -0
- package/.agents/skills/code-review/SKILL.md +89 -0
- package/.agents/skills/{improve-codebase-architecture → codebase-design}/DEEPENING.md +1 -1
- package/.agents/skills/{improve-codebase-architecture/INTERFACE-DESIGN.md → codebase-design/DESIGN-IT-TWICE.md} +3 -3
- package/.agents/skills/codebase-design/SKILL.md +114 -0
- package/.agents/skills/design-an-interface/SKILL.md +94 -0
- package/.agents/skills/{diagnose → diagnosing-bugs}/SKILL.md +29 -12
- package/.agents/skills/{grill-with-docs → domain-modeling}/CONTEXT-FORMAT.md +1 -4
- package/.agents/skills/domain-modeling/SKILL.md +74 -0
- package/.agents/skills/fable-mode/SKILL.md +95 -0
- package/.agents/skills/git-guardrails-claude-code/SKILL.md +95 -0
- package/.agents/skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh +25 -0
- package/.agents/skills/grill-me/SKILL.md +7 -0
- package/.agents/skills/grill-with-docs/SKILL.md +3 -86
- package/.agents/skills/grilling/SKILL.md +14 -0
- package/.agents/skills/handoff/SKILL.md +2 -1
- package/.agents/skills/i-have-adhd/SKILL.md +120 -0
- package/.agents/skills/implement/SKILL.md +11 -0
- package/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +3 -3
- package/.agents/skills/improve-codebase-architecture/SKILL.md +13 -28
- package/.agents/skills/loop-me/SKILL.md +32 -0
- package/.agents/skills/prototype/SKILL.md +1 -1
- package/.agents/skills/qa/SKILL.md +130 -0
- package/.agents/skills/request-refactor-plan/SKILL.md +68 -0
- package/.agents/skills/research/SKILL.md +12 -0
- package/.agents/skills/resolving-merge-conflicts/SKILL.md +14 -0
- package/.agents/skills/scaffold-exercises/SKILL.md +106 -0
- package/.agents/skills/setup-matt-pocock-skills/SKILL.md +11 -9
- package/.agents/skills/setup-matt-pocock-skills/domain.md +2 -2
- package/.agents/skills/setup-matt-pocock-skills/issue-tracker-github.md +23 -0
- package/.agents/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +23 -0
- package/.agents/skills/setup-matt-pocock-skills/issue-tracker-local.md +11 -0
- package/.agents/skills/skill-creator/LICENSE.txt +202 -0
- package/.agents/skills/skill-creator/SKILL.md +485 -0
- package/.agents/skills/skill-creator/agents/analyzer.md +274 -0
- package/.agents/skills/skill-creator/agents/comparator.md +202 -0
- package/.agents/skills/skill-creator/agents/grader.md +223 -0
- package/.agents/skills/skill-creator/assets/eval_review.html +146 -0
- package/.agents/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.agents/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.agents/skills/skill-creator/references/schemas.md +430 -0
- package/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/.agents/skills/skill-creator/scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/.agents/skills/skill-creator/scripts/__pycache__/run_eval.cpython-314.pyc +0 -0
- package/.agents/skills/skill-creator/scripts/__pycache__/utils.cpython-314.pyc +0 -0
- package/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.agents/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.agents/skills/skill-creator/scripts/improve_description.py +247 -0
- package/.agents/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.agents/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.agents/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.agents/skills/skill-creator/scripts/run_loop.py +328 -0
- package/.agents/skills/skill-creator/scripts/utils.py +47 -0
- package/.agents/skills/tdd/SKILL.md +17 -90
- package/.agents/skills/tdd/tests.md +16 -0
- package/.agents/skills/teach/GLOSSARY-FORMAT.md +35 -0
- package/.agents/skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/.agents/skills/teach/MISSION-FORMAT.md +31 -0
- package/.agents/skills/teach/RESOURCES-FORMAT.md +32 -0
- package/.agents/skills/teach/SKILL.md +140 -0
- package/.agents/skills/{to-prd → to-spec}/SKILL.md +11 -12
- package/.agents/skills/to-tickets/SKILL.md +114 -0
- package/.agents/skills/triage/AGENT-BRIEF.md +40 -1
- package/.agents/skills/triage/OUT-OF-SCOPE.md +5 -1
- package/.agents/skills/triage/SKILL.md +20 -11
- package/.agents/skills/wayfinder/SKILL.md +127 -0
- package/.agents/skills/writing-great-skills/GLOSSARY.md +201 -0
- package/.agents/skills/writing-great-skills/SKILL.md +83 -0
- package/.agents/superpowers-agent +103 -222
- package/.agents/superpowers-bootstrap.md +3 -3
- package/.agents/templates/AGENTS.md.template +11 -34
- package/.agents/templates/SUPERPOWERS.md.template +4 -4
- package/.github/copilot-instructions.md +23 -99
- package/.github/hooks/rtk-rewrite.json +22 -0
- package/AGENTS.md +7 -6
- package/README.md +53 -174
- package/package.json +1 -1
- package/skills/collaboration/brainstorming/SKILL.md +39 -139
- package/skills/collaboration/brainstorming/skill.json +2 -2
- package/skills/collaboration/leveraging-cli-tools/SKILL.md +70 -71
- package/skills/collaboration/leveraging-cli-tools/references/copilot-instructions.md +30 -0
- package/skills/collaboration/leveraging-cli-tools/scripts/setup-ponytail.sh +185 -0
- package/skills/collaboration/leveraging-cli-tools/scripts/setup-rtk.sh +217 -0
- package/skills/collaboration/leveraging-cli-tools/skill.json +1 -1
- package/skills/meta/create-skill-json/SKILL.md +4 -4
- package/skills/meta/create-skill-json/skill.json +1 -1
- package/skills/meta/create-skill-json/test-scenarios.md +1 -1
- package/skills/setup-skills/SKILL.md +18 -11
- package/skills/setup-skills/skill.json +8 -0
- package/.agents/skills/caveman/SKILL.md +0 -49
- package/.agents/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
- package/.agents/skills/karpathy-guidelines/SKILL.md +0 -75
- package/.agents/skills/review/SKILL.md +0 -78
- package/.agents/skills/tdd/deep-modules.md +0 -33
- package/.agents/skills/tdd/interface-design.md +0 -31
- package/.agents/skills/tdd/refactoring.md +0 -10
- package/.agents/skills/to-issues/SKILL.md +0 -83
- package/.agents/skills/zoom-out/SKILL.md +0 -7
- package/skills/architecture/ABOUT.md +0 -20
- package/skills/architecture/preserving-productive-tensions/SKILL.md +0 -146
- package/skills/architecture/preserving-productive-tensions/skill.json +0 -9
- package/skills/collaboration/brainstorming/spec-document-reviewer-prompt.md +0 -50
- package/skills/collaboration/brainstorming/visual-companion.md +0 -277
- package/skills/collaboration/dispatching-parallel-agents/SKILL.md +0 -174
- package/skills/collaboration/dispatching-parallel-agents/skill.json +0 -9
- package/skills/collaboration/executing-plans/SKILL.md +0 -130
- package/skills/collaboration/executing-plans/skill.json +0 -9
- package/skills/collaboration/finishing-a-development-branch/SKILL.md +0 -261
- package/skills/collaboration/finishing-a-development-branch/skill.json +0 -9
- package/skills/collaboration/leveraging-cli-tools/scripts/slim.py +0 -167
- package/skills/collaboration/receiving-code-review/SKILL.md +0 -233
- package/skills/collaboration/receiving-code-review/skill.json +0 -9
- package/skills/collaboration/requesting-code-review/SKILL.md +0 -110
- package/skills/collaboration/requesting-code-review/code-reviewer.md +0 -146
- package/skills/collaboration/requesting-code-review/skill.json +0 -12
- package/skills/collaboration/subagent-driven-development/SKILL.md +0 -255
- package/skills/collaboration/subagent-driven-development/code-quality-reviewer-prompt.md +0 -26
- package/skills/collaboration/subagent-driven-development/implementer-prompt.md +0 -113
- package/skills/collaboration/subagent-driven-development/skill.json +0 -15
- package/skills/collaboration/subagent-driven-development/spec-reviewer-prompt.md +0 -61
- package/skills/collaboration/using-git-worktrees/SKILL.md +0 -366
- package/skills/collaboration/using-git-worktrees/skill.json +0 -9
- package/skills/collaboration/writing-plans/SKILL.md +0 -121
- package/skills/collaboration/writing-plans/plan-document-reviewer-prompt.md +0 -52
- package/skills/collaboration/writing-plans/skill.json +0 -9
- package/skills/debugging/defense-in-depth/SKILL.md +0 -380
- package/skills/debugging/defense-in-depth/skill.json +0 -9
- package/skills/debugging/root-cause-tracing/SKILL.md +0 -361
- package/skills/debugging/root-cause-tracing/find-polluter.sh +0 -63
- package/skills/debugging/root-cause-tracing/skill.json +0 -12
- package/skills/debugging/systematic-debugging/SKILL.md +0 -299
- package/skills/debugging/systematic-debugging/condition-based-waiting-example.ts +0 -158
- package/skills/debugging/systematic-debugging/condition-based-waiting.md +0 -115
- package/skills/debugging/systematic-debugging/defense-in-depth.md +0 -122
- package/skills/debugging/systematic-debugging/find-polluter.sh +0 -63
- package/skills/debugging/systematic-debugging/root-cause-tracing.md +0 -169
- package/skills/debugging/systematic-debugging/skill.json +0 -9
- package/skills/debugging/systematic-debugging/test-academic.md +0 -14
- package/skills/debugging/systematic-debugging/test-pressure-1.md +0 -58
- package/skills/debugging/systematic-debugging/test-pressure-2.md +0 -68
- package/skills/debugging/systematic-debugging/test-pressure-3.md +0 -69
- package/skills/debugging/verification-before-completion/SKILL.md +0 -143
- package/skills/debugging/verification-before-completion/skill.json +0 -9
- package/skills/finding-skills/SKILL.md +0 -101
- package/skills/finding-skills/skill.json +0 -8
- package/skills/meta/create-agents-md/SKILL.md +0 -182
- package/skills/meta/create-agents-md/skill.json +0 -9
- package/skills/meta/creating-prompts/SKILL.md +0 -349
- package/skills/meta/creating-prompts/examples/do-example.md +0 -65
- package/skills/meta/creating-prompts/examples/plan-example.md +0 -75
- package/skills/meta/creating-prompts/examples/refine-example.md +0 -65
- package/skills/meta/creating-prompts/examples/research-example.md +0 -63
- package/skills/meta/creating-prompts/scripts/get-next-number.sh +0 -27
- package/skills/meta/creating-prompts/skill.json +0 -20
- package/skills/meta/creating-prompts/templates/do-template.md +0 -59
- package/skills/meta/creating-prompts/templates/plan-template.md +0 -58
- package/skills/meta/creating-prompts/templates/refine-template.md +0 -54
- package/skills/meta/creating-prompts/templates/research-template.md +0 -56
- package/skills/meta/using-superpowers/SKILL.md +0 -108
- package/skills/meta/using-superpowers/skill.json +0 -5
- package/skills/meta/writing-prompts/SKILL.md +0 -122
- package/skills/meta/writing-prompts/references/platforms.md +0 -114
- package/skills/meta/writing-prompts/skill.json +0 -9
- package/skills/problem-solving/ABOUT.md +0 -40
- package/skills/problem-solving/collision-zone-thinking/SKILL.md +0 -188
- package/skills/problem-solving/collision-zone-thinking/references/historical-examples.md +0 -393
- package/skills/problem-solving/collision-zone-thinking/skill.json +0 -9
- package/skills/problem-solving/inversion-exercise/SKILL.md +0 -174
- package/skills/problem-solving/inversion-exercise/skill.json +0 -9
- package/skills/problem-solving/meta-pattern-recognition/SKILL.md +0 -116
- package/skills/problem-solving/meta-pattern-recognition/skill.json +0 -9
- package/skills/problem-solving/scale-game/SKILL.md +0 -222
- package/skills/problem-solving/scale-game/skill.json +0 -9
- package/skills/problem-solving/simplification-cascades/SKILL.md +0 -113
- package/skills/problem-solving/simplification-cascades/skill.json +0 -9
- package/skills/problem-solving/when-stuck/SKILL.md +0 -69
- package/skills/problem-solving/when-stuck/skill.json +0 -9
- package/skills/research/ABOUT.md +0 -20
- package/skills/research/tracing-knowledge-lineages/SKILL.md +0 -241
- package/skills/research/tracing-knowledge-lineages/skill.json +0 -9
- package/skills/testing/condition-based-waiting/SKILL.md +0 -359
- package/skills/testing/condition-based-waiting/example.ts +0 -158
- package/skills/testing/condition-based-waiting/skill.json +0 -12
- package/skills/testing/test-driven-development/SKILL.md +0 -434
- package/skills/testing/test-driven-development/skill.json +0 -9
- package/skills/testing/testing-anti-patterns/SKILL.md +0 -298
- package/skills/testing/testing-anti-patterns/skill.json +0 -9
- package/skills/testing/verification-before-completion/SKILL.md +0 -246
- package/skills/testing/verification-before-completion/skill.json +0 -10
- package/skills/using-a-skill/SKILL.md +0 -101
- package/skills/using-a-skill/skill.json +0 -8
- /package/.agents/skills/{diagnose → diagnosing-bugs}/scripts/hitl-loop.template.sh +0 -0
- /package/.agents/skills/{grill-with-docs → domain-modeling}/ADR-FORMAT.md +0 -0
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: defense-in-depth
|
|
3
|
-
description: Validate at every layer data passes through to make bugs impossible, not just fixed; apply when invalid data causes failures deep in execution or a dangerous operation must never run on bad input
|
|
4
|
-
metadata:
|
|
5
|
-
version: 4.0.0
|
|
6
|
-
languages: all
|
|
7
|
-
when_to_use: when invalid data causes failures deep in execution, or a dangerous operation must never execute on bad data
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Defense-in-Depth Validation
|
|
11
|
-
|
|
12
|
-
## Overview
|
|
13
|
-
|
|
14
|
-
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.
|
|
15
|
-
|
|
16
|
-
**Core principle:** Validate at EVERY layer data passes through. Make the bug structurally impossible.
|
|
17
|
-
|
|
18
|
-
Single validation: "We fixed the bug"
|
|
19
|
-
Multiple layers: "We made the bug impossible"
|
|
20
|
-
|
|
21
|
-
## When to Apply This Skill
|
|
22
|
-
|
|
23
|
-
Apply defense-in-depth validation when:
|
|
24
|
-
|
|
25
|
-
- A bug was caused by invalid data reaching deep into the execution path
|
|
26
|
-
- A value flows through multiple functions, services, or modules before use
|
|
27
|
-
- Mocks or test fixtures bypass application-layer validation
|
|
28
|
-
- Refactoring risk: a single validation point may not survive future code changes
|
|
29
|
-
- A dangerous operation (file deletion, git operations, external API calls) must never execute on bad data
|
|
30
|
-
|
|
31
|
-
Do NOT add defensive checks inside internal, same-trust-level function calls between modules you own. Validation at every internal call is over-engineering — it creates noise, maintenance burden, and obscures where actual trust boundaries are.
|
|
32
|
-
|
|
33
|
-
## Two Types of Invalidity
|
|
34
|
-
|
|
35
|
-
Before choosing where to validate, classify the failure:
|
|
36
|
-
|
|
37
|
-
**Syntactic invalidity** — the structure or format is wrong:
|
|
38
|
-
- Empty string when a value is required
|
|
39
|
-
- A number outside an allowed range
|
|
40
|
-
- A path that does not exist on disk
|
|
41
|
-
|
|
42
|
-
**Semantic invalidity** — the structure is valid but the meaning is wrong for this operation:
|
|
43
|
-
- A directory that exists but belongs to a different user
|
|
44
|
-
- An order being shipped to a restricted country
|
|
45
|
-
- A project being initialized inside the source repository
|
|
46
|
-
|
|
47
|
-
Syntactic checks happen at the boundary (Layer 1). Semantic checks require domain context and belong deeper in the call stack (Layer 2).
|
|
48
|
-
|
|
49
|
-
## The Six Layers
|
|
50
|
-
|
|
51
|
-
### Layer 1: Entry Point Validation
|
|
52
|
-
|
|
53
|
-
Reject syntactically invalid input at the API boundary before it travels anywhere.
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
function createProject(name: string, workingDirectory: string) {
|
|
57
|
-
if (!workingDirectory || workingDirectory.trim() === '') {
|
|
58
|
-
throw new Error('workingDirectory cannot be empty');
|
|
59
|
-
}
|
|
60
|
-
if (!existsSync(workingDirectory)) {
|
|
61
|
-
throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
|
|
62
|
-
}
|
|
63
|
-
if (!statSync(workingDirectory).isDirectory()) {
|
|
64
|
-
throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
|
|
65
|
-
}
|
|
66
|
-
// ... proceed
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**Allowlist, not denylist.** Define what IS valid and reject everything else. Denylists are trivially bypassed via encoding tricks, alternate representations, and case variations. They also produce false positives — blocking `'` breaks `O'Brian`.
|
|
71
|
-
|
|
72
|
-
**Canonicalize before validating.** Especially for paths:
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
// WRONG — bypassed by /etc/./passwd or /etc/../etc/passwd
|
|
76
|
-
if (input === '/etc/passwd') throw ...
|
|
77
|
-
|
|
78
|
-
// RIGHT — normalize first, then check
|
|
79
|
-
const normalized = path.resolve(input);
|
|
80
|
-
if (normalized.startsWith('/etc')) throw ...
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Important:** Valid input is not necessarily safe input. A valid email address can carry an XSS payload. A valid URL can carry a SQL injection. Validation confirms shape and structure; it does not prevent exploitation. Use parameterized queries and output encoding for that — those are orthogonal concerns.
|
|
84
|
-
|
|
85
|
-
**Regex pitfalls:** Always anchor patterns (`^` and `$`) — `/\d{4}/` matches `abc1234def`. Avoid catastrophic backtracking in complex patterns; test with adversarial inputs.
|
|
86
|
-
|
|
87
|
-
### Layer 2: Business Logic Validation
|
|
88
|
-
|
|
89
|
-
Validate semantic correctness inside the domain. Does this data make sense for THIS operation in THIS context?
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
function initializeWorkspace(projectDir: string, sessionId: string) {
|
|
93
|
-
if (!projectDir) {
|
|
94
|
-
throw new Error('projectDir required for workspace initialization');
|
|
95
|
-
}
|
|
96
|
-
// Semantic check: project dir must not be inside the agent's own source tree
|
|
97
|
-
if (projectDir.startsWith(SOURCE_ROOT)) {
|
|
98
|
-
throw new Error(`projectDir cannot be inside source: ${projectDir}`);
|
|
99
|
-
}
|
|
100
|
-
// ... proceed
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Domain validation belongs inside the domain object, not only at the application service layer. Putting validation only in services leaks domain knowledge upward and allows domain objects to be constructed in invalid states.
|
|
105
|
-
|
|
106
|
-
**Use the Execute/CanExecute pattern for domain operations:**
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
class Delivery {
|
|
110
|
-
canDeliver(): { ok: true } | { ok: false; errors: string[] } {
|
|
111
|
-
const errors: string[] = [];
|
|
112
|
-
if (!this.destination) errors.push('destination required');
|
|
113
|
-
if (this.weight > MAX_WEIGHT) errors.push('weight exceeds limit');
|
|
114
|
-
return errors.length ? { ok: false, errors } : { ok: true };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
deliver(): void {
|
|
118
|
-
const check = this.canDeliver();
|
|
119
|
-
if (!check.ok) throw new Error(check.errors.join('; '));
|
|
120
|
-
// ... perform delivery
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
`canDeliver` is a query (safe to call, collects all errors). `deliver` enforces the contract internally. This follows Command-Query Separation — callers can check before acting without putting the object into an invalid state.
|
|
126
|
-
|
|
127
|
-
### Layer 3: Persistence Constraints
|
|
128
|
-
|
|
129
|
-
Database schema is the last line of defense when application layers fail. It is not a substitute for earlier layers — it is the backstop.
|
|
130
|
-
|
|
131
|
-
```sql
|
|
132
|
-
CREATE TABLE projects (
|
|
133
|
-
id UUID PRIMARY KEY,
|
|
134
|
-
name TEXT NOT NULL CHECK (length(name) > 0),
|
|
135
|
-
directory TEXT NOT NULL,
|
|
136
|
-
owner_id UUID NOT NULL REFERENCES users(id),
|
|
137
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
CREATE UNIQUE INDEX projects_directory_idx ON projects (directory);
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
If application code sends a NULL that should never be null, the database will reject it. This surfaces bugs that would otherwise produce silent data corruption.
|
|
144
|
-
|
|
145
|
-
For ORMs, define constraints at both the schema and model level:
|
|
146
|
-
|
|
147
|
-
```python
|
|
148
|
-
class Project(Base):
|
|
149
|
-
__tablename__ = "projects"
|
|
150
|
-
id = Column(UUID, primary_key=True, default=uuid4)
|
|
151
|
-
name = Column(Text, nullable=False)
|
|
152
|
-
directory = Column(Text, nullable=False, unique=True)
|
|
153
|
-
owner_id = Column(UUID, ForeignKey("users.id"), nullable=False)
|
|
154
|
-
|
|
155
|
-
@validates("name")
|
|
156
|
-
def validate_name(self, key, value):
|
|
157
|
-
if not value or not value.strip():
|
|
158
|
-
raise ValueError("Project name cannot be empty")
|
|
159
|
-
return value
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Layer 4: Environment Guards
|
|
163
|
-
|
|
164
|
-
Prevent dangerous operations in specific runtime contexts — most critically, tests. Tests can bypass ALL upstream validation via mocks, fixtures, and direct instantiation. A guard inside the dangerous operation itself cannot be bypassed regardless of how the operation was invoked.
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
async function gitInit(directory: string) {
|
|
168
|
-
if (process.env.NODE_ENV === 'test') {
|
|
169
|
-
const normalized = normalize(resolve(directory));
|
|
170
|
-
const tmpDir = normalize(resolve(tmpdir()));
|
|
171
|
-
|
|
172
|
-
if (!normalized.startsWith(tmpDir)) {
|
|
173
|
-
throw new Error(
|
|
174
|
-
`Refusing git init outside temp dir during tests: ${directory}`
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// ... proceed
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Layer 5: Test Boundary Guards
|
|
183
|
-
|
|
184
|
-
Test fixtures must assert their own preconditions before the test body runs. This makes the fixture responsible for its own correctness rather than silently propagating bad state.
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
function createTestProject(dir?: string): Project {
|
|
188
|
-
const projectDir = dir ?? mkdtempSync(join(tmpdir(), 'test-project-'));
|
|
189
|
-
|
|
190
|
-
// Assert preconditions before proceeding
|
|
191
|
-
if (!projectDir || projectDir.trim() === '') {
|
|
192
|
-
throw new Error('Test fixture: projectDir cannot be empty');
|
|
193
|
-
}
|
|
194
|
-
if (!projectDir.startsWith(tmpdir())) {
|
|
195
|
-
throw new Error(`Test fixture: projectDir must be in tmpdir, got: ${projectDir}`);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return Project.create('test-project', projectDir);
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### Layer 6: Debug Instrumentation
|
|
203
|
-
|
|
204
|
-
Capture context for forensics when other layers fail. Structured logging with the call stack allows you to trace exactly which code path produced a bad value.
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
async function gitInit(directory: string) {
|
|
208
|
-
logger.debug('About to git init', {
|
|
209
|
-
directory,
|
|
210
|
-
cwd: process.cwd(),
|
|
211
|
-
stack: new Error().stack,
|
|
212
|
-
});
|
|
213
|
-
// ... proceed
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
This layer does not prevent bugs — it makes them diagnosable in production and staging where you cannot attach a debugger.
|
|
218
|
-
|
|
219
|
-
## Parse, Don't Validate
|
|
220
|
-
|
|
221
|
-
The most robust form of defense-in-depth uses the type system to make invalid states unrepresentable. Parse at the boundary; pass a typed object everywhere else.
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
// Instead of passing `string` everywhere and re-validating:
|
|
225
|
-
class WorkingDirectory {
|
|
226
|
-
private constructor(public readonly value: string) {}
|
|
227
|
-
|
|
228
|
-
static parse(input: string): WorkingDirectory {
|
|
229
|
-
if (!input || input.trim() === '') {
|
|
230
|
-
throw new Error('WorkingDirectory cannot be empty');
|
|
231
|
-
}
|
|
232
|
-
const resolved = resolve(input);
|
|
233
|
-
if (!existsSync(resolved)) {
|
|
234
|
-
throw new Error(`WorkingDirectory does not exist: ${resolved}`);
|
|
235
|
-
}
|
|
236
|
-
if (!statSync(resolved).isDirectory()) {
|
|
237
|
-
throw new Error(`WorkingDirectory is not a directory: ${resolved}`);
|
|
238
|
-
}
|
|
239
|
-
return new WorkingDirectory(resolved);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Downstream functions get Layer 1 for free — no re-checking needed
|
|
244
|
-
function initializeWorkspace(dir: WorkingDirectory, sessionId: string) {
|
|
245
|
-
// dir.value is already known valid and canonicalized
|
|
246
|
-
}
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
In Python, use dataclasses with `__post_init__` or Pydantic validators:
|
|
250
|
-
|
|
251
|
-
```python
|
|
252
|
-
from pydantic import BaseModel, field_validator
|
|
253
|
-
from pathlib import Path
|
|
254
|
-
|
|
255
|
-
class WorkingDirectory(BaseModel):
|
|
256
|
-
value: Path
|
|
257
|
-
|
|
258
|
-
@field_validator("value")
|
|
259
|
-
@classmethod
|
|
260
|
-
def must_be_existing_directory(cls, v: Path) -> Path:
|
|
261
|
-
if not v.exists():
|
|
262
|
-
raise ValueError(f"Directory does not exist: {v}")
|
|
263
|
-
if not v.is_dir():
|
|
264
|
-
raise ValueError(f"Path is not a directory: {v}")
|
|
265
|
-
return v.resolve()
|
|
266
|
-
|
|
267
|
-
# Now downstream code receives a WorkingDirectory with guaranteed invariants
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
This pattern **complements** (not replaces) the other layers — business logic, environment guards, and DB constraints still apply.
|
|
271
|
-
|
|
272
|
-
## Assertions vs Runtime Validation
|
|
273
|
-
|
|
274
|
-
Two complementary checks serve different purposes:
|
|
275
|
-
|
|
276
|
-
**Assertions** — for programmer errors (contract violations between modules you control):
|
|
277
|
-
```python
|
|
278
|
-
def calculate_discount(price: float, pct: float) -> float:
|
|
279
|
-
assert price > 0, f"price must be positive: {price}"
|
|
280
|
-
assert 0 <= pct <= 1, f"pct must be in [0, 1]: {pct}"
|
|
281
|
-
return price * (1 - pct)
|
|
282
|
-
```
|
|
283
|
-
Assertions crash loudly in development so bugs never silently propagate. They may be disabled in production (`python -O`) — use them for invariants you own, not external inputs.
|
|
284
|
-
|
|
285
|
-
**Runtime validation** — for user/environment errors (external data you don't control):
|
|
286
|
-
```python
|
|
287
|
-
def handle_discount_request(request_body: dict) -> float:
|
|
288
|
-
price = request_body.get("price")
|
|
289
|
-
pct = request_body.get("discount_pct")
|
|
290
|
-
if price is None or not isinstance(price, (int, float)):
|
|
291
|
-
raise ValidationError("price is required and must be a number")
|
|
292
|
-
if price <= 0:
|
|
293
|
-
raise ValidationError(f"price must be positive, got {price}")
|
|
294
|
-
# ...
|
|
295
|
-
```
|
|
296
|
-
Runtime validation surfaces actionable error messages to users. It must stay active in production and must never crash the process unexpectedly.
|
|
297
|
-
|
|
298
|
-
## Collecting vs Failing Fast
|
|
299
|
-
|
|
300
|
-
**Fail fast** — for single operations: reject and return immediately when validation fails. Do not partially process corrupt data.
|
|
301
|
-
|
|
302
|
-
```python
|
|
303
|
-
# Guard clause pattern: fail fast, clear intent
|
|
304
|
-
def process_order(order):
|
|
305
|
-
if order is None:
|
|
306
|
-
raise ValueError("order cannot be None")
|
|
307
|
-
if order.amount <= 0:
|
|
308
|
-
raise ValueError(f"amount must be positive, got {order.amount}")
|
|
309
|
-
# only reach here with valid data
|
|
310
|
-
_do_processing(order)
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
**Collect errors** — for user-facing forms: gather all validation failures before returning so users fix everything at once, not one error at a time.
|
|
314
|
-
|
|
315
|
-
```typescript
|
|
316
|
-
type ValidationResult<T> =
|
|
317
|
-
| { ok: true; value: T }
|
|
318
|
-
| { ok: false; errors: string[] };
|
|
319
|
-
|
|
320
|
-
function validateOrder(input: unknown): ValidationResult<Order> {
|
|
321
|
-
const errors: string[] = [];
|
|
322
|
-
if (!input.customerId) errors.push('customerId required');
|
|
323
|
-
if (!input.items?.length) errors.push('order must have at least one item');
|
|
324
|
-
if (input.total <= 0) errors.push('total must be positive');
|
|
325
|
-
if (errors.length) return { ok: false, errors };
|
|
326
|
-
return { ok: true, value: input as Order };
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
Use **exceptions** for unexpected failures (programmer errors, infrastructure down).
|
|
331
|
-
Use **result objects** for expected validation failures (user input, external data).
|
|
332
|
-
|
|
333
|
-
## Applying the Pattern
|
|
334
|
-
|
|
335
|
-
When you find a bug caused by invalid data:
|
|
336
|
-
|
|
337
|
-
1. **Trace the data flow** — Where does the bad value originate? What is its full path through the system?
|
|
338
|
-
2. **Map all checkpoints** — List every function the value passes through
|
|
339
|
-
3. **Classify the invalidity** — Syntactic (structure) or semantic (meaning)?
|
|
340
|
-
4. **Add validation at each applicable layer** — Entry, business, persistence, environment, test fixture, debug logging
|
|
341
|
-
5. **Test each layer independently** — Intentionally bypass Layer 1 and verify Layer 2 still catches it. Verify DB constraints reject invalid rows even when application code sends them.
|
|
342
|
-
|
|
343
|
-
## Anti-Patterns
|
|
344
|
-
|
|
345
|
-
| Anti-pattern | Problem | Fix |
|
|
346
|
-
|---|---|---|
|
|
347
|
-
| Single-point validation | One bypass reaches all downstream code | Add checks at each applicable layer |
|
|
348
|
-
| IsValid method without enforcement | Requires object to enter invalid state; caller can ignore the check | Use Execute/CanExecute: command enforces its own preconditions |
|
|
349
|
-
| Exceptions for expected failures | User sees a generic error, not specific messages | Return result objects, collect all errors |
|
|
350
|
-
| Denylist validation | Trivially bypassed via encoding or alternate representations | Allowlist known-valid patterns instead |
|
|
351
|
-
| Client-only validation | JavaScript can be disabled or bypassed | Always re-validate on the server |
|
|
352
|
-
| No persistence constraints | Silent data corruption when app layers fail | Add NOT NULL, CHECK, UNIQUE constraints |
|
|
353
|
-
| Missing environment guards in tests | Dangerous operations run against real state | Guard in the operation itself, not just callers |
|
|
354
|
-
| Trusting internal sources | Internal microservices, message queues, and partner APIs also produce bad data | Treat all external data as untrusted regardless of source |
|
|
355
|
-
| Validating format as a security control | A valid email can carry XSS; a valid URL can carry SQLi | Use output encoding and parameterized queries for security; validation is a separate concern |
|
|
356
|
-
| Over-validating same-trust internal calls | Noise, maintenance burden, obscured trust boundaries | Validate at trust boundaries only — public APIs, service interfaces, and entry points |
|
|
357
|
-
|
|
358
|
-
## Real-World Example
|
|
359
|
-
|
|
360
|
-
**Bug:** Empty `projectDir` caused `git init` to run in the source code directory.
|
|
361
|
-
|
|
362
|
-
**Data flow:**
|
|
363
|
-
1. Test setup → empty string (missing fixture initialization)
|
|
364
|
-
2. `Project.create(name, '')`
|
|
365
|
-
3. `WorkspaceManager.createWorkspace('')`
|
|
366
|
-
4. `git init` runs in `process.cwd()` — the source repository
|
|
367
|
-
|
|
368
|
-
**Six layers added:**
|
|
369
|
-
- Layer 1: `Project.create()` validates not empty, exists, is a directory, is writable
|
|
370
|
-
- Layer 2: `WorkspaceManager` validates `projectDir` is not empty and not inside source root
|
|
371
|
-
- Layer 3: DB schema `NOT NULL` on directory column
|
|
372
|
-
- Layer 4: `WorktreeManager` refuses `git init` outside `tmpdir()` when `NODE_ENV=test`
|
|
373
|
-
- Layer 5: Test fixture asserts `projectDir` is non-empty and inside `tmpdir()` before returning
|
|
374
|
-
- Layer 6: Stack trace logging immediately before `git init`
|
|
375
|
-
|
|
376
|
-
**Result:** All 1847 tests passed. The bug was structurally impossible to reproduce — each layer independently prevented it.
|
|
377
|
-
|
|
378
|
-
All six layers were necessary. Different code paths bypassed entry validation. Mocks bypassed business logic checks. Edge cases on different platforms needed environment guards. Without the test fixture guard, missing initialization silently propagated.
|
|
379
|
-
|
|
380
|
-
**Don't stop at one validation point.** Add checks at every layer.
|