@complexthings/superpowers-agent 9.2.1 → 10.0.1
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 +2 -2
- 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,241 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: tracing-knowledge-lineages
|
|
3
|
-
description: "Trace the historical lineage of technical ideas, patterns, and architectures to rediscover proven solutions, understand WHY current approaches exist, and avoid repeating failures. Use when you encounter 'why do we do it this way?', 'is this a new problem?', 'what has been tried before?', 'has this pattern emerged before?', or when designing systems that resemble something from the past."
|
|
4
|
-
metadata:
|
|
5
|
-
category: research
|
|
6
|
-
version: 1.2.0
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Tracing Knowledge Lineages
|
|
10
|
-
|
|
11
|
-
## Why This Matters
|
|
12
|
-
|
|
13
|
-
Every technical decision carries invisible weight from the past. When you skip lineage tracing:
|
|
14
|
-
|
|
15
|
-
- You reinvent failed approaches because you don't know they failed
|
|
16
|
-
- You miss proven patterns that already solved your exact problem
|
|
17
|
-
- You don't understand WHY the current approach exists, so you can't safely deviate from it
|
|
18
|
-
- You create architecture that repeats historical mistakes your predecessors already paid for
|
|
19
|
-
|
|
20
|
-
The goal isn't to be constrained by history — it's to be *informed* by it. Knowing the lineage lets you deviate deliberately rather than ignorantly.
|
|
21
|
-
|
|
22
|
-
## When to Trace Lineages
|
|
23
|
-
|
|
24
|
-
**Always trace when:**
|
|
25
|
-
- You're designing something and someone says "isn't this like X?"
|
|
26
|
-
- You're questioning why something is done a certain way
|
|
27
|
-
- You're proposing to remove or replace an established pattern
|
|
28
|
-
- You're encountering a problem that "feels old"
|
|
29
|
-
- You're evaluating a technology that claims to be new
|
|
30
|
-
|
|
31
|
-
**Quick trace (5 minutes):** Is this pattern familiar? → Search for historical names → Find 2–3 examples → Note outcomes
|
|
32
|
-
|
|
33
|
-
**Deep trace (30+ minutes):** Before architectural decisions, major refactors, or when the quick trace surfaces surprising history
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## The Four Core Techniques
|
|
38
|
-
|
|
39
|
-
### 1. Decision Archaeology
|
|
40
|
-
|
|
41
|
-
Excavate the reasoning behind existing choices — not just what was decided, but why, and what alternatives were rejected.
|
|
42
|
-
|
|
43
|
-
**Sources to mine:**
|
|
44
|
-
- `git log --all --grep="why\|decision\|chose\|considered\|rejected"` — look for commit messages explaining reasoning
|
|
45
|
-
- Architecture Decision Records (ADRs), RFCs, and design docs in `/docs`, `/adr`, `/rfcs`
|
|
46
|
-
- Ticket trackers (JIRA, Linear, GitHub Issues) — search for the feature name + "alternative" or "rejected"
|
|
47
|
-
- Team Slack/Discord history — search around the dates of significant commits
|
|
48
|
-
- PR descriptions and review comments: `git log --merges --format="%H %s" | head -50`, then check PRs
|
|
49
|
-
|
|
50
|
-
**What to capture:**
|
|
51
|
-
- The alternatives that were *considered but rejected* — these are as valuable as what was chosen
|
|
52
|
-
- The constraints that shaped the decision (team size, deadline, scale, tooling available then)
|
|
53
|
-
- Whether those constraints still apply today
|
|
54
|
-
|
|
55
|
-
**Probe questions:**
|
|
56
|
-
> "What problem was this solving when it was introduced?"
|
|
57
|
-
> "What was the runner-up solution, and why was it rejected?"
|
|
58
|
-
> "What has changed since this decision was made?"
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
### 2. Failed Attempt Analysis
|
|
63
|
-
|
|
64
|
-
Failures are the most valuable and most hidden part of technical history. They rarely get documented, but they repeat.
|
|
65
|
-
|
|
66
|
-
**Finding hidden failures:**
|
|
67
|
-
- Reverted commits: `git log --all --diff-filter=R` or search for "revert" in commit history
|
|
68
|
-
- Short-lived branches: `git branch -r | xargs -I{} git log --oneline -1 {}` — branches that were created and abandoned
|
|
69
|
-
- Half-migrated code: look for dual implementations (old + new living side by side)
|
|
70
|
-
- "TODO: remove this once X" comments that were never removed
|
|
71
|
-
- Changelog entries that disappeared in later versions
|
|
72
|
-
|
|
73
|
-
**The failure taxonomy:**
|
|
74
|
-
| Type | Signal | What to learn |
|
|
75
|
-
|------|--------|---------------|
|
|
76
|
-
| Performance failure | Reverted optimization, "too slow" in commits | What the scale thresholds were |
|
|
77
|
-
| Complexity failure | "Simplified", "removed abstraction" | Where the abstraction broke down |
|
|
78
|
-
| Adoption failure | Feature removed, flag disabled | What made it hard to use |
|
|
79
|
-
| Timing failure | "Premature", "not ready" | What prerequisites were missing |
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
### 3. Revival Detection
|
|
84
|
-
|
|
85
|
-
Old patterns resurface under new names. Recognizing revivals lets you access 30+ years of operational experience immediately.
|
|
86
|
-
|
|
87
|
-
**Technically accurate revival lineages:**
|
|
88
|
-
|
|
89
|
-
| Modern Pattern | Historical Ancestor | What the revival added |
|
|
90
|
-
|---------------|--------------------|-----------------------|
|
|
91
|
-
| Serverless Functions | Time-sharing systems (1960s) + Unix daemons | Elastic billing, managed infra |
|
|
92
|
-
| GraphQL | SGML/XQL query languages + SOAP/WSDL | Typed schema, client-driven queries |
|
|
93
|
-
| NoSQL Document Stores | IMS hierarchical DB + CODASYL network DBs (1970s) | Horizontal scale, flexible schema |
|
|
94
|
-
| Microservices | CORBA/SOA (1990s–2000s) | Lightweight protocols, containers |
|
|
95
|
-
| Edge Computing | CDN + Akamai ESI (early 2000s) | Full compute, not just caching |
|
|
96
|
-
| Server Components (React) | Server-side rendering + JSP/PHP | Component model + streaming |
|
|
97
|
-
| Event Sourcing | Append-only ledgers + audit logs | Projections, temporal queries |
|
|
98
|
-
| Infrastructure as Code | LISP machine system images + Puppet/Chef | Declarative, version-controlled |
|
|
99
|
-
|
|
100
|
-
**Revival research steps:**
|
|
101
|
-
1. Name the modern pattern precisely
|
|
102
|
-
2. Ask: "what did this replace, and what did the replacement itself replace?"
|
|
103
|
-
3. Search: `[pattern name] history site:lobste.rs OR site:news.ycombinator.com`
|
|
104
|
-
4. Look for talks titled "X considered harmful" or "the return of Y"
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
### 4. Paradigm Shift Mapping
|
|
109
|
-
|
|
110
|
-
Understand what changed — constraints, tooling, scale, understanding — that made old approaches viable again or newly obsolete.
|
|
111
|
-
|
|
112
|
-
**The shift template:**
|
|
113
|
-
```
|
|
114
|
-
Old constraint: [what made X unworkable before]
|
|
115
|
-
New condition: [what changed]
|
|
116
|
-
Therefore: [why X is viable/necessary now]
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
**Example:**
|
|
120
|
-
```
|
|
121
|
-
Old constraint: RAM was expensive; storing state in memory meant high cost
|
|
122
|
-
New condition: RAM is effectively free at the scale most apps run
|
|
123
|
-
Therefore: In-memory state machines (XState, Zustand) beat complex DB state management
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
**Constraint categories to check:**
|
|
127
|
-
- **Cost**: storage, compute, bandwidth costs have changed dramatically
|
|
128
|
-
- **Speed**: network latency, CPU speed, disk I/O profiles
|
|
129
|
-
- **Scale**: how many users, requests, data volume
|
|
130
|
-
- **Tooling**: what primitives are now available (containers, managed databases, type systems)
|
|
131
|
-
- **Team**: what skills are now common vs. rare
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Search Strategy
|
|
136
|
-
|
|
137
|
-
### Code & Version Control
|
|
138
|
-
```bash
|
|
139
|
-
# Commits explaining reasoning
|
|
140
|
-
git log --all --grep="because\|reason\|trade-off\|instead of\|considered"
|
|
141
|
-
|
|
142
|
-
# Reverted changes (hidden failures)
|
|
143
|
-
git log --all --grep="[Rr]evert" --oneline
|
|
144
|
-
|
|
145
|
-
# Find old implementations
|
|
146
|
-
git log --all --full-history -- "**/old-*" "**/deprecated-*" "**/legacy-*"
|
|
147
|
-
|
|
148
|
-
# When was this pattern introduced?
|
|
149
|
-
git log -S "pattern_name" --oneline
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Documentation & Issues
|
|
153
|
-
- GitHub/GitLab: search `repo:org/name "why" OR "reason" OR "considered"`
|
|
154
|
-
- JIRA/Linear: filter by date range around when the pattern was introduced
|
|
155
|
-
- Confluence/Notion: search for the component name + "decision" or "RFC"
|
|
156
|
-
|
|
157
|
-
### Web Archaeology
|
|
158
|
-
- `site:news.ycombinator.com "[technology name]"` — HN discussions often contain expert history
|
|
159
|
-
- `site:lobste.rs "[technology name]"` — deeper technical discussions
|
|
160
|
-
- ACM Digital Library / IEEE Xplore for academic lineage
|
|
161
|
-
- The Wayback Machine for documentation of dead projects
|
|
162
|
-
- Wikipedia revision history for contested technical decisions
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
## Recognizing Lineage Signals
|
|
167
|
-
|
|
168
|
-
**In code:**
|
|
169
|
-
- Comments starting with "NB:", "NOTE:", "FIXME:", "HACK:", "WARNING:" — often explain constraints
|
|
170
|
-
- Unusually defensive code in an otherwise simple module
|
|
171
|
-
- Abstraction layers that wrap a single thing
|
|
172
|
-
- Feature flags that have been "temporary" for years
|
|
173
|
-
|
|
174
|
-
**In conversations:**
|
|
175
|
-
- "We tried that before" — ALWAYS follow up: "What happened?"
|
|
176
|
-
- "That won't work here" — ask for the specific failure scenario
|
|
177
|
-
- "We have reasons" — that phrase usually means there's archaeology to do
|
|
178
|
-
|
|
179
|
-
**In architecture:**
|
|
180
|
-
- Two implementations of the same thing existing simultaneously
|
|
181
|
-
- Naming like `*_v2`, `*_new`, `*_refactored`
|
|
182
|
-
- Explicit "Do Not Use" warnings pointing to another implementation
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## Synthesizing Your Findings
|
|
187
|
-
|
|
188
|
-
A lineage trace is only valuable if it produces actionable insight. Structure your output:
|
|
189
|
-
|
|
190
|
-
```markdown
|
|
191
|
-
## Lineage: [Pattern/Decision Name]
|
|
192
|
-
|
|
193
|
-
**What it is now:** [one sentence]
|
|
194
|
-
|
|
195
|
-
**Historical origin:** [oldest known ancestor + approximate era]
|
|
196
|
-
|
|
197
|
-
**Key evolution steps:**
|
|
198
|
-
1. [era] — [what existed and what problem it solved]
|
|
199
|
-
2. [era] — [what changed and why]
|
|
200
|
-
3. [current] — [how we got here]
|
|
201
|
-
|
|
202
|
-
**Failed attempts along the way:**
|
|
203
|
-
- [attempt] failed because [reason] — still relevant because [implication]
|
|
204
|
-
|
|
205
|
-
**What the history tells us:**
|
|
206
|
-
- The core problem being solved is [X], not [Y]
|
|
207
|
-
- This approach works when [conditions]; breaks when [other conditions]
|
|
208
|
-
- The constraint that shaped this was [Z] — that constraint [still applies / no longer applies]
|
|
209
|
-
|
|
210
|
-
**Implication for current work:**
|
|
211
|
-
[One concrete decision or caution derived from the lineage]
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## Informed Deviation: Using History to Break from It
|
|
217
|
-
|
|
218
|
-
Knowing the lineage doesn't mean being constrained by it — it means you can break from it *deliberately*. Before deviating from an established pattern:
|
|
219
|
-
|
|
220
|
-
1. **Name the constraint that shaped the original decision**
|
|
221
|
-
- "This was built when we had X constraint"
|
|
222
|
-
2. **Verify whether that constraint still applies**
|
|
223
|
-
- "That constraint no longer applies because Y"
|
|
224
|
-
3. **Check if the deviation was tried before**
|
|
225
|
-
- "This exact deviation was tried in [branch/ticket/year] and failed because Z"
|
|
226
|
-
4. **State your deviation explicitly**
|
|
227
|
-
- "We are intentionally deviating from the historical pattern because [conditions changed]"
|
|
228
|
-
|
|
229
|
-
The trap to avoid: "We don't need to do it that way anymore" without knowing *why* it was done that way in the first place.
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## Anti-Patterns
|
|
234
|
-
|
|
235
|
-
| Anti-pattern | What it looks like | The fix |
|
|
236
|
-
|---|---|---|
|
|
237
|
-
| **Present bias** | "That's old, we don't need to learn from it" | Old failures are especially relevant — same problems, different names |
|
|
238
|
-
| **Success bias** | Only tracing the winning path | Failed attempts teach more than successes |
|
|
239
|
-
| **Shallow attribution** | "This is like X" without understanding WHY | Trace through to first principles |
|
|
240
|
-
| **Archive paralysis** | Spending days on lineage for a one-day task | Time-box: quick trace = 5 min, deep trace = 30 min |
|
|
241
|
-
| **Context stripping** | "They tried X and it failed" without the why | Always capture the conditions under which it failed |
|
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: condition-based-waiting
|
|
3
|
-
description: Replace arbitrary timeouts with condition polling for reliable async tests. Use this skill whenever you see setTimeout/sleep/time.sleep in tests, tests are flaky or inconsistent across machines or CI, tests timeout under parallel load, or you're waiting for async operations like events, state changes, DOM updates, file writes, or API responses. Also use when debugging race conditions, investigating "why does this test sometimes fail", or looking for test speed improvements by removing unnecessary waits.
|
|
4
|
-
metadata:
|
|
5
|
-
version: 2.1.0
|
|
6
|
-
languages: all
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Condition-Based Waiting
|
|
10
|
-
|
|
11
|
-
## Overview
|
|
12
|
-
|
|
13
|
-
Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on a fast dev machine but fail under load or in CI.
|
|
14
|
-
|
|
15
|
-
**Core principle:** Wait for the actual condition you care about, not a guess about how long it takes.
|
|
16
|
-
|
|
17
|
-
## When to Use
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
Test uses setTimeout/sleep?
|
|
21
|
-
└─ Testing actual timing behavior? (debounce, throttle, tick intervals)
|
|
22
|
-
├─ YES → Document WHY the timeout is needed (see "When Arbitrary Timeout IS Correct")
|
|
23
|
-
└─ NO → Replace with condition-based waiting
|
|
24
|
-
└─ System emits events? → Prefer event-based waiting (fastest, no polling)
|
|
25
|
-
Otherwise → Use condition polling
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
**Use when:**
|
|
29
|
-
- Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
|
|
30
|
-
- Tests are flaky (pass sometimes, fail under load or in CI)
|
|
31
|
-
- Tests timeout when run in parallel
|
|
32
|
-
- Waiting for: async operations, DOM updates, state changes, file writes, queue drains
|
|
33
|
-
|
|
34
|
-
**Don't use when:**
|
|
35
|
-
- Testing actual timing behavior (debounce delay, throttle interval, tick frequency)
|
|
36
|
-
- Your test framework already has built-in auto-waiting (Playwright, Cypress) — use those instead
|
|
37
|
-
- Always document WHY when keeping an arbitrary timeout
|
|
38
|
-
|
|
39
|
-
## Decision: Poll vs Event
|
|
40
|
-
|
|
41
|
-
| Approach | Use when | Latency | CPU |
|
|
42
|
-
|----------|----------|---------|-----|
|
|
43
|
-
| Event listener | System emits events/promises | ~0ms | Zero |
|
|
44
|
-
| Framework built-ins | Playwright, Cypress, Testing Library | ~0ms | Zero |
|
|
45
|
-
| Polling | System has no observable events | 0–50ms | Low |
|
|
46
|
-
|
|
47
|
-
**Event-based (zero latency, preferred when possible):**
|
|
48
|
-
```typescript
|
|
49
|
-
// ✅ If system emits events, listen instead of polling
|
|
50
|
-
async function waitForEvent<T>(emitter: EventEmitter, event: string, timeoutMs = 5000): Promise<T> {
|
|
51
|
-
return new Promise((resolve, reject) => {
|
|
52
|
-
const timer = setTimeout(() => {
|
|
53
|
-
emitter.removeListener(event, resolve);
|
|
54
|
-
reject(new Error(`Timeout waiting for "${event}" event after ${timeoutMs}ms`));
|
|
55
|
-
}, timeoutMs);
|
|
56
|
-
emitter.once(event, (data: T) => { clearTimeout(timer); resolve(data); });
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Polling (when no events available):**
|
|
62
|
-
```typescript
|
|
63
|
-
// ✅ Poll at 50ms — responsive without hammering the CPU
|
|
64
|
-
await waitFor(() => system.isReady(), 'system to be ready');
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Core Pattern
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
// ❌ BEFORE: Guessing at timing
|
|
71
|
-
await new Promise(r => setTimeout(r, 300));
|
|
72
|
-
const result = getResult();
|
|
73
|
-
expect(result).toBeDefined();
|
|
74
|
-
|
|
75
|
-
// ✅ AFTER: Waiting for condition
|
|
76
|
-
await waitFor(() => getResult() !== undefined, 'result to be defined');
|
|
77
|
-
const result = getResult();
|
|
78
|
-
expect(result).toBeDefined();
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Quick Patterns
|
|
82
|
-
|
|
83
|
-
| Scenario | Pattern |
|
|
84
|
-
|----------|---------|
|
|
85
|
-
| Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'), 'DONE event')` |
|
|
86
|
-
| Wait for state | `waitFor(() => machine.state === 'ready' && machine.state, 'ready state')` |
|
|
87
|
-
| Wait for count | `waitFor(() => items.length >= 5 && items, '5+ items')` |
|
|
88
|
-
| Wait for file | `waitFor(() => fs.existsSync(path) && path, 'file to exist')` |
|
|
89
|
-
| Wait for disappearance | `waitFor(() => !getElement() || true, 'element removed')` |
|
|
90
|
-
| Complex condition | `waitFor(() => obj.ready && obj.value > 10 && obj, 'obj ready')` |
|
|
91
|
-
|
|
92
|
-
> **Note:** The condition must return a truthy value to resolve — returning `false`, `null`, `undefined`, or `0` triggers another poll.
|
|
93
|
-
|
|
94
|
-
## Generic Implementation (TypeScript)
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
async function waitFor<T>(
|
|
98
|
-
condition: () => T | undefined | null | false,
|
|
99
|
-
description: string,
|
|
100
|
-
timeoutMs = 5000,
|
|
101
|
-
intervalMs = 50 // 50ms matches Testing Library's default — fast enough, CPU-friendly
|
|
102
|
-
): Promise<T> {
|
|
103
|
-
const startTime = Date.now();
|
|
104
|
-
|
|
105
|
-
while (true) {
|
|
106
|
-
const result = condition();
|
|
107
|
-
if (result) return result as T;
|
|
108
|
-
|
|
109
|
-
const elapsed = Date.now() - startTime;
|
|
110
|
-
if (elapsed > timeoutMs) {
|
|
111
|
-
// Include current state in error — saves a debugging round-trip
|
|
112
|
-
const current = (() => { try { return condition(); } catch { return 'error evaluating'; } })();
|
|
113
|
-
throw new Error(
|
|
114
|
-
`Timeout waiting for "${description}" after ${timeoutMs}ms. Current value: ${JSON.stringify(current)}`
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
await new Promise(r => setTimeout(r, intervalMs));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
See `example.ts` for domain-specific helper implementations (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`).
|
|
124
|
-
|
|
125
|
-
## Throw-Based vs Falsy-Return Retry
|
|
126
|
-
|
|
127
|
-
Two valid retry styles — choose based on context:
|
|
128
|
-
|
|
129
|
-
**Falsy-return** (above implementation — simple boolean conditions):
|
|
130
|
-
```typescript
|
|
131
|
-
// Retries while condition returns falsy
|
|
132
|
-
await waitFor(() => queue.length > 0 && queue, 'queue to have items');
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
**Throw-based** (Testing Library style — assertions as conditions):
|
|
136
|
-
```typescript
|
|
137
|
-
// Retries while callback throws; stops when it doesn't throw
|
|
138
|
-
await waitFor(() => {
|
|
139
|
-
expect(getItems()).toHaveLength(3); // throws until length === 3
|
|
140
|
-
});
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Use throw-based when wrapping `expect()` assertions. Use falsy-return for plain boolean/value conditions. Don't mix the two styles in the same `waitFor` call.
|
|
144
|
-
|
|
145
|
-
## ⚠️ Fake Timer Incompatibility (Critical Pitfall)
|
|
146
|
-
|
|
147
|
-
The `setTimeout(r, interval)` inside `waitFor` **never fires** when Jest/Vitest fake timers are active:
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
// ❌ This hangs forever — fake timers swallow the setTimeout inside waitFor
|
|
151
|
-
vi.useFakeTimers();
|
|
152
|
-
await waitFor(() => store.isReady(), 'store ready'); // deadlock
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
**Fix option 1 — Flush pending timers before restoring real timers (recommended):**
|
|
156
|
-
```typescript
|
|
157
|
-
afterEach(() => {
|
|
158
|
-
jest.runOnlyPendingTimers(); // flush 3rd-party timers before switching
|
|
159
|
-
jest.useRealTimers();
|
|
160
|
-
});
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
**Fix option 2 — Advance fake timers to cover polling intervals:**
|
|
164
|
-
```typescript
|
|
165
|
-
vi.useFakeTimers();
|
|
166
|
-
const waiting = waitFor(() => store.isReady(), 'store ready');
|
|
167
|
-
vi.advanceTimersByTime(5000); // advance past all polling intervals
|
|
168
|
-
await waiting;
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
**Fix option 3 — Switch to real timers for the wait:**
|
|
172
|
-
```typescript
|
|
173
|
-
vi.useFakeTimers();
|
|
174
|
-
// ... trigger work with fake timers ...
|
|
175
|
-
vi.useRealTimers();
|
|
176
|
-
await waitFor(() => store.isReady(), 'store ready');
|
|
177
|
-
vi.useFakeTimers(); // restore if needed
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
**Fix option 4 — Skip `waitFor` entirely (when feasible):**
|
|
181
|
-
```typescript
|
|
182
|
-
// If condition can be checked synchronously after advancing timers:
|
|
183
|
-
vi.useFakeTimers();
|
|
184
|
-
vi.runAllTimers(); // flush all pending fake timers
|
|
185
|
-
expect(store.isReady()).toBe(true); // direct check, no waitFor needed
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
**Rule:** If your test uses fake timers, you cannot use `setTimeout`-based polling without explicitly advancing time.
|
|
189
|
-
|
|
190
|
-
## Framework Built-Ins (Use These First)
|
|
191
|
-
|
|
192
|
-
If you're already using one of these frameworks, use their built-in waiting — don't reinvent the wheel:
|
|
193
|
-
|
|
194
|
-
### Testing Library (`@testing-library/react`, etc.)
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
import { waitFor, waitForElementToBeRemoved, screen } from '@testing-library/react';
|
|
198
|
-
|
|
199
|
-
// waitFor: poll until assertion passes (retries when callback THROWS)
|
|
200
|
-
await waitFor(() => expect(screen.getByText('Done')).toBeVisible());
|
|
201
|
-
// Defaults: timeout=1000ms, interval=50ms
|
|
202
|
-
|
|
203
|
-
// findBy* = getBy* + waitFor (most common pattern)
|
|
204
|
-
const button = await screen.findByRole('button', { name: 'Submit' });
|
|
205
|
-
await screen.findByText('Loaded', {}, { timeout: 2000 });
|
|
206
|
-
|
|
207
|
-
// waitForElementToBeRemoved: wait for disappearance
|
|
208
|
-
await waitForElementToBeRemoved(() => screen.queryByText('Loading...'));
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
**Critical:** Testing Library's `waitFor` retries when the callback **throws** — not when it returns falsy. Always use `expect(...)` assertions inside, not boolean returns.
|
|
212
|
-
|
|
213
|
-
### Playwright (auto-waits on every action)
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
// ✅ Actions auto-wait (visible + stable + enabled + receives events):
|
|
217
|
-
await page.click('button#submit');
|
|
218
|
-
await page.fill('input[name=email]', 'test@example.com');
|
|
219
|
-
|
|
220
|
-
// ✅ Assertions auto-retry until timeout:
|
|
221
|
-
await expect(page.locator('.status')).toHaveText('Ready');
|
|
222
|
-
await expect(page.locator('.spinner')).toBeHidden();
|
|
223
|
-
await expect(page).toHaveURL('/dashboard');
|
|
224
|
-
|
|
225
|
-
// ✅ Explicit element state wait:
|
|
226
|
-
await page.locator('.spinner').waitFor({ state: 'hidden' });
|
|
227
|
-
|
|
228
|
-
// ✅ Custom condition evaluated in browser JS:
|
|
229
|
-
await page.waitForFunction(() => window.__appReady === true);
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
Manual polling is rarely needed in Playwright — auto-wait handles the common cases.
|
|
233
|
-
|
|
234
|
-
### Cypress (retry-ability built into query chains)
|
|
235
|
-
|
|
236
|
-
```javascript
|
|
237
|
-
// ✅ .get() + .should() retries the chain automatically
|
|
238
|
-
cy.get('.status').should('have.text', 'Ready');
|
|
239
|
-
cy.get('@mySpy').should('have.been.calledOnce');
|
|
240
|
-
|
|
241
|
-
// ✅ For explicit polling:
|
|
242
|
-
cy.waitUntil(() => cy.window().then(w => w.store?.initialized));
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Cross-Language Patterns
|
|
246
|
-
|
|
247
|
-
### Python (pytest-asyncio)
|
|
248
|
-
|
|
249
|
-
```python
|
|
250
|
-
import asyncio
|
|
251
|
-
|
|
252
|
-
async def wait_for_condition(condition_fn, description, timeout=5.0, interval=0.05):
|
|
253
|
-
"""Poll until condition_fn() returns truthy or timeout expires."""
|
|
254
|
-
deadline = asyncio.get_event_loop().time() + timeout
|
|
255
|
-
while asyncio.get_event_loop().time() < deadline:
|
|
256
|
-
if condition_fn():
|
|
257
|
-
return
|
|
258
|
-
await asyncio.sleep(interval)
|
|
259
|
-
current = condition_fn()
|
|
260
|
-
raise TimeoutError(
|
|
261
|
-
f'Timeout waiting for "{description}" after {timeout}s. Current: {current!r}'
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
@pytest.mark.asyncio
|
|
265
|
-
async def test_queue_drains():
|
|
266
|
-
items = list(range(10))
|
|
267
|
-
asyncio.get_event_loop().call_later(0.1, items.clear)
|
|
268
|
-
await wait_for_condition(lambda: len(items) == 0, 'queue to drain')
|
|
269
|
-
assert items == []
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
> **Pitfall:** Call the getter *inside* the loop — never cache state before entering the loop or you'll check a stale snapshot.
|
|
273
|
-
|
|
274
|
-
### Go
|
|
275
|
-
|
|
276
|
-
```go
|
|
277
|
-
// stdlib — good for goroutine synchronization
|
|
278
|
-
func TestWorkerFinishes(t *testing.T) {
|
|
279
|
-
done := make(chan struct{})
|
|
280
|
-
go func() { time.Sleep(50 * time.Millisecond); close(done) }()
|
|
281
|
-
|
|
282
|
-
select {
|
|
283
|
-
case <-done:
|
|
284
|
-
// success
|
|
285
|
-
case <-time.After(2 * time.Second):
|
|
286
|
-
t.Fatal("worker did not finish within 2s")
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// testify — good for polling arbitrary conditions
|
|
291
|
-
require.Eventually(t,
|
|
292
|
-
func() bool { return cache.Has("users") },
|
|
293
|
-
2*time.Second, // total timeout
|
|
294
|
-
10*time.Millisecond, // poll interval
|
|
295
|
-
"cache never populated 'users' key",
|
|
296
|
-
)
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
> **Pitfall (gomega/testify):** Pass a *getter function* — not a value. `Eventually(cache.Has("users"), ...)` snapshots once; `Eventually(func() bool { return cache.Has("users") }, ...)` re-evaluates live.
|
|
300
|
-
|
|
301
|
-
### Rust (tokio)
|
|
302
|
-
|
|
303
|
-
```rust
|
|
304
|
-
use std::sync::{Arc, Mutex};
|
|
305
|
-
use tokio::time::{sleep, timeout, Duration};
|
|
306
|
-
|
|
307
|
-
#[tokio::test]
|
|
308
|
-
async fn test_flag_set_by_background_task() {
|
|
309
|
-
let flag = Arc::new(Mutex::new(false));
|
|
310
|
-
let flag_clone = Arc::clone(&flag);
|
|
311
|
-
tokio::spawn(async move {
|
|
312
|
-
sleep(Duration::from_millis(100)).await;
|
|
313
|
-
*flag_clone.lock().unwrap() = true;
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
timeout(Duration::from_secs(2), async {
|
|
317
|
-
loop {
|
|
318
|
-
if *flag.lock().unwrap() { break; }
|
|
319
|
-
sleep(Duration::from_millis(50)).await; // use sleep, not yield_now
|
|
320
|
-
}
|
|
321
|
-
}).await.expect("flag was never set within 2s");
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
> **Pitfall:** Use `sleep()`, not `yield_now()`, in test polling loops — `yield_now` starves other tasks on the single-threaded test runtime.
|
|
326
|
-
|
|
327
|
-
## Common Mistakes
|
|
328
|
-
|
|
329
|
-
| Mistake | Fix |
|
|
330
|
-
|---------|-----|
|
|
331
|
-
| `setTimeout(check, 1)` — polls too fast | Use 50ms; sub-10ms wastes CPU with no benefit |
|
|
332
|
-
| No timeout — loops forever | Always include timeout with descriptive error |
|
|
333
|
-
| Check stale data — cached before loop | Call getter *inside* loop for fresh data |
|
|
334
|
-
| Vague timeout error — "condition not met" | Include current state: `Current value: ${JSON.stringify(val)}` |
|
|
335
|
-
| Returning falsy from Testing Library `waitFor` | Throw inside `waitFor` — use `expect()` assertions |
|
|
336
|
-
| `waitFor` under fake timers — hangs | See [Fake Timer Incompatibility] above |
|
|
337
|
-
|
|
338
|
-
## When Arbitrary Timeout IS Correct
|
|
339
|
-
|
|
340
|
-
Some behavior is inherently time-based. The pattern: wait for condition first, then wait for timed behavior:
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
// Tool ticks every 100ms — need 2 ticks to verify partial output
|
|
344
|
-
await waitForEvent('TOOL_STARTED'); // 1. Wait for triggering condition
|
|
345
|
-
await new Promise(r => setTimeout(r, 200)); // 2. Wait for known timing behavior
|
|
346
|
-
// 200ms = 2 ticks at 100ms intervals — documented and justified
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
**Requirements for a legitimate arbitrary timeout:**
|
|
350
|
-
1. First wait for the triggering condition (don't skip this)
|
|
351
|
-
2. Duration based on known timing (e.g., "this runs every 100ms") — not a guess
|
|
352
|
-
3. Comment explains WHY the specific duration was chosen
|
|
353
|
-
|
|
354
|
-
## Real-World Impact
|
|
355
|
-
|
|
356
|
-
From a debugging session replacing 15 arbitrary timeouts across 3 test files:
|
|
357
|
-
- Pass rate: **60% → 100%** (eliminated race conditions)
|
|
358
|
-
- Execution time: **40% faster** (no more waiting out unnecessary delays)
|
|
359
|
-
- Zero flaky failures in CI after the change
|