pi-gauntlet 4.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +300 -0
  2. package/LICENSE +24 -0
  3. package/README.md +278 -0
  4. package/agents/code-reviewer.md +48 -0
  5. package/agents/conformance-reviewer.md +139 -0
  6. package/agents/implementer.md +40 -0
  7. package/agents/spec-council-member.md +47 -0
  8. package/agents/spec-council-synthesizer.md +39 -0
  9. package/agents/spec-reviewer.md +47 -0
  10. package/agents/spec-summarizer.md +42 -0
  11. package/bin/install-agents.mjs +141 -0
  12. package/extensions/phase-tracker.ts +622 -0
  13. package/extensions/plan-tracker.ts +308 -0
  14. package/extensions/verify-before-ship.ts +132 -0
  15. package/package.json +43 -0
  16. package/skills/brainstorming/SKILL.md +290 -0
  17. package/skills/dispatching-parallel-agents/SKILL.md +192 -0
  18. package/skills/finishing-a-development-branch/SKILL.md +311 -0
  19. package/skills/receiving-code-review/SKILL.md +200 -0
  20. package/skills/requesting-code-review/SKILL.md +115 -0
  21. package/skills/requesting-code-review/code-reviewer.md +166 -0
  22. package/skills/roasting-the-spec/SKILL.md +139 -0
  23. package/skills/subagent-driven-development/SKILL.md +223 -0
  24. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +25 -0
  25. package/skills/subagent-driven-development/implementer-prompt.md +113 -0
  26. package/skills/subagent-driven-development/spec-reviewer-prompt.md +68 -0
  27. package/skills/systematic-debugging/SKILL.md +151 -0
  28. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  29. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  30. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  31. package/skills/systematic-debugging/find-polluter.sh +63 -0
  32. package/skills/systematic-debugging/reference/rationalizations.md +61 -0
  33. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  34. package/skills/test-driven-development/SKILL.md +230 -0
  35. package/skills/test-driven-development/reference/examples.md +99 -0
  36. package/skills/test-driven-development/reference/rationalizations.md +65 -0
  37. package/skills/test-driven-development/reference/when-stuck.md +31 -0
  38. package/skills/test-driven-development/testing-anti-patterns.md +299 -0
  39. package/skills/using-git-worktrees/SKILL.md +193 -0
  40. package/skills/verification-before-completion/SKILL.md +169 -0
  41. package/skills/verification-before-completion/reference/conformance-check.md +220 -0
  42. package/skills/writing-plans/SKILL.md +244 -0
  43. package/skills/writing-skills/SKILL.md +429 -0
  44. package/skills/writing-skills/reference/anthropic-best-practices.md +1130 -0
  45. package/skills/writing-skills/reference/persuasion.md +187 -0
  46. package/skills/writing-skills/reference/testing-skills-with-subagents.md +384 -0
@@ -0,0 +1,113 @@
1
+ # Implementer Subagent Prompt Template
2
+
3
+ Use this template when dispatching an implementer subagent.
4
+
5
+ ```
6
+ Dispatch a subagent with this prompt:
7
+ description: "Implement Task N: [task name]"
8
+ prompt: |
9
+ You are implementing Task N: [task name]
10
+
11
+ ## Task Description
12
+
13
+ [FULL TEXT of task from plan - paste it here, don't make subagent read file]
14
+
15
+ ## Context
16
+
17
+ [Scene-setting: where this fits, dependencies, architectural context]
18
+
19
+ ## Before You Begin
20
+
21
+ If you have questions about:
22
+ - The requirements or acceptance criteria
23
+ - The approach or implementation strategy
24
+ - Dependencies or assumptions
25
+ - Anything unclear in the task description
26
+
27
+ **Ask them now.** Raise any concerns before starting work.
28
+
29
+ ## Your Job
30
+
31
+ Once you're clear on requirements:
32
+ 1. Implement exactly what the task specifies
33
+ 2. Write tests (following TDD — failing test first for production code)
34
+ 3. Verify implementation works
35
+ 4. Commit your work
36
+ 5. Self-review (see below)
37
+ 6. Report back
38
+
39
+ Work from: [directory]
40
+
41
+ **While you work:** If you encounter something unexpected or unclear, **ask questions**.
42
+ It's always OK to pause and clarify. Don't guess or make assumptions.
43
+
44
+ ## Code Organization
45
+
46
+ You reason best about code you can hold in context at once, and your edits are more
47
+ reliable when files are focused. Keep this in mind:
48
+ - Follow the file structure defined in the plan.
49
+ - Each file should have one clear responsibility with a well-defined interface.
50
+ - If a file you're creating is growing beyond the plan's intent, stop and report it
51
+ as DONE_WITH_CONCERNS — don't split files on your own without plan guidance.
52
+ - If an existing file you're modifying is already large or tangled, work carefully
53
+ and note it as a concern in your report.
54
+ - In existing codebases, follow established patterns. Improve code you're touching
55
+ the way a good developer would, but don't restructure things outside your task.
56
+
57
+ ## When You're in Over Your Head
58
+
59
+ It is always OK to stop and say "this is too hard for me." Bad work is worse than
60
+ no work. You will not be penalized for escalating.
61
+
62
+ **STOP and escalate when:**
63
+ - The task requires architectural decisions with multiple valid approaches.
64
+ - You need to understand code beyond what was provided and can't find clarity.
65
+ - You feel uncertain about whether your approach is correct.
66
+ - The task involves restructuring existing code in ways the plan didn't anticipate.
67
+ - You've been reading file after file trying to understand the system without progress.
68
+
69
+ **How to escalate:** Report back with status `BLOCKED` or `NEEDS_CONTEXT`. Describe
70
+ specifically what you're stuck on, what you've tried, and what kind of help you need.
71
+ The orchestrator can provide more context, re-dispatch with a more capable model,
72
+ or break the task into smaller pieces.
73
+
74
+ ## Before Reporting Back: Self-Review
75
+
76
+ Review your work with fresh eyes. Ask yourself:
77
+
78
+ **Completeness:**
79
+ - Did I fully implement everything in the spec?
80
+ - Did I miss any requirements?
81
+ - Are there edge cases I didn't handle?
82
+
83
+ **Quality:**
84
+ - Is this my best work?
85
+ - Are names clear and accurate (match what things do, not how they work)?
86
+ - Is the code clean and maintainable?
87
+
88
+ **Discipline:**
89
+ - Did I avoid overbuilding (YAGNI)?
90
+ - Did I only build what was requested?
91
+ - Did I follow existing patterns in the codebase?
92
+
93
+ **Testing:**
94
+ - Do tests actually verify behavior (not just mock behavior)?
95
+ - Did I follow TDD — failing test first for production code?
96
+ - Are tests comprehensive?
97
+
98
+ If you find issues during self-review, fix them now before reporting.
99
+
100
+ ## Report Format
101
+
102
+ When done, report:
103
+ - **Status:** `DONE` | `DONE_WITH_CONCERNS` | `BLOCKED` | `NEEDS_CONTEXT`
104
+ - What you implemented (or what you attempted, if blocked)
105
+ - What you tested and test results
106
+ - Files changed
107
+ - Self-review findings (if any)
108
+ - Any issues or concerns
109
+
110
+ Use `DONE_WITH_CONCERNS` if you completed the work but have doubts about correctness.
111
+ Use `BLOCKED` if you cannot complete the task. Use `NEEDS_CONTEXT` if you need
112
+ information that wasn't provided. Never silently produce work you're unsure about.
113
+ ```
@@ -0,0 +1,68 @@
1
+ # Spec Compliance Reviewer Prompt Template
2
+
3
+ Use this template when dispatching a spec compliance reviewer subagent.
4
+
5
+ **Purpose:** Verify implementer built what was requested (nothing more, nothing less)
6
+
7
+ ```
8
+ Dispatch a subagent with this prompt:
9
+ description: "Review spec compliance for Task N"
10
+ prompt: |
11
+ You are reviewing whether an implementation matches its specification.
12
+
13
+ ## What Was Requested
14
+
15
+ [FULL TEXT of task requirements]
16
+
17
+ ## What Implementer Claims They Built
18
+
19
+ [From implementer's report]
20
+
21
+ ## CRITICAL: Do Not Trust the Report
22
+
23
+ The implementer finished suspiciously quickly. Their report may be incomplete,
24
+ inaccurate, or optimistic. You MUST verify everything independently.
25
+
26
+ **DO NOT:**
27
+ - Take their word for what they implemented
28
+ - Trust their claims about completeness
29
+ - Accept their interpretation of requirements
30
+
31
+ **DO:**
32
+ - Read the actual code they wrote
33
+ - Compare actual implementation to requirements line by line
34
+ - Check for missing pieces they claimed to implement
35
+ - Look for extra features they didn't mention
36
+
37
+ ## Boundaries
38
+
39
+ - **Read code and compare to spec: yes**
40
+ - **Edit, create, or delete any files: NO**
41
+ - You are a reviewer. Your output is a written report listing what matches and what doesn't.
42
+ - If you find issues, describe them — do NOT fix them.
43
+
44
+ ## Your Job
45
+
46
+ Read the implementation code and verify:
47
+
48
+ **Missing requirements:**
49
+ - Did they implement everything that was requested?
50
+ - Are there requirements they skipped or missed?
51
+ - Did they claim something works but didn't actually implement it?
52
+
53
+ **Extra/unneeded work:**
54
+ - Did they build things that weren't requested?
55
+ - Did they over-engineer or add unnecessary features?
56
+ - Did they add "nice to haves" that weren't in spec?
57
+
58
+ **Misunderstandings:**
59
+ - Did they interpret requirements differently than intended?
60
+ - Did they solve the wrong problem?
61
+ - Did they implement the right feature but wrong way?
62
+
63
+ **Verify by reading code, not by trusting report.**
64
+
65
+ Report:
66
+ - ✅ Spec compliant (if everything matches after code inspection)
67
+ - ❌ Issues found: [list specifically what's missing or extra, with file:line references]
68
+ ```
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: systematic-debugging
3
+ description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
4
+ ---
5
+
6
+ > **Related skills:** Write a failing test for the bug with `/skill:test-driven-development`. Verify the fix with `/skill:verification-before-completion`.
7
+
8
+ # Systematic Debugging
9
+
10
+ ## Overview
11
+
12
+ Random fixes waste time and create new bugs. Quick patches mask underlying issues.
13
+
14
+ **Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
15
+
16
+ **Violating the letter of this process is violating the spirit of debugging.**
17
+
18
+ Debug discipline is enforced by this skill, not by runtime hooks. The pi `verify-before-ship` extension only gates ship commands; it does not track investigation patterns. Hold yourself to the process below.
19
+
20
+ ## The Iron Law
21
+
22
+ ```
23
+ NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
24
+ ```
25
+
26
+ If you haven't completed Phase 1, you cannot propose fixes.
27
+
28
+ ## When to Use
29
+
30
+ Use for ANY technical issue: test failures, bugs, unexpected behavior, performance problems, build failures, integration issues.
31
+
32
+ **Use this ESPECIALLY when:**
33
+ - Under time pressure (emergencies make guessing tempting)
34
+ - "Just one quick fix" seems obvious
35
+ - You've already tried multiple fixes
36
+ - Previous fix didn't work
37
+ - You don't fully understand the issue
38
+
39
+ **Don't skip when:**
40
+ - Issue seems simple (simple bugs have root causes too)
41
+ - You're in a hurry (rushing guarantees rework)
42
+
43
+ ## The Four Phases
44
+
45
+ You MUST complete each phase before proceeding to the next.
46
+
47
+ ### Phase 1: Root Cause Investigation
48
+
49
+ **BEFORE attempting ANY fix:**
50
+
51
+ 1. **Read Error Messages Carefully** — Don't skip past errors or warnings. Read stack traces completely. Note line numbers, file paths, error codes.
52
+
53
+ 2. **Reproduce Consistently** — Can you trigger it reliably? What are the exact steps? If not reproducible → gather more data, don't guess.
54
+
55
+ 3. **Check Recent Changes** — Git diff, recent commits, new dependencies, config changes, environmental differences.
56
+
57
+ 4. **Gather Evidence in Multi-Component Systems** — For each component boundary: log what enters, what exits, verify config propagation. Run once to see WHERE it breaks, then investigate that component.
58
+
59
+ **Example (multi-layer system):**
60
+ ```bash
61
+ # Layer 1: Workflow
62
+ echo "=== Secrets available: ==="
63
+ echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}"
64
+
65
+ # Layer 2: Build script
66
+ echo "=== Env vars in build script: ==="
67
+ env | grep IDENTITY || echo "IDENTITY not in environment"
68
+
69
+ # Layer 3: Signing
70
+ echo "=== Keychain state: ==="
71
+ security list-keychains
72
+ security find-identity -v
73
+ ```
74
+ **This reveals:** Which layer fails (e.g., secrets → workflow ✓, workflow → build ✗)
75
+
76
+ 5. **Trace Data Flow** — Where does the bad value originate? What called this with the bad value? Keep tracing up until you find the source. Fix at source, not at symptom. See `root-cause-tracing.md` for the complete technique.
77
+
78
+ ### Phase 2: Pattern Analysis
79
+
80
+ 1. **Find Working Examples** — Locate similar working code in same codebase.
81
+ 2. **Compare Against References** — Read reference implementation COMPLETELY. Don't skim.
82
+ 3. **Identify Differences** — List every difference, however small. Don't assume "that can't matter."
83
+ 4. **Understand Dependencies** — What components, settings, config, environment does this need?
84
+
85
+ ### Phase 3: Hypothesis and Testing
86
+
87
+ 1. **Form Single Hypothesis** — State clearly: "I think X is the root cause because Y." Be specific, not vague.
88
+ 2. **Test Minimally** — Make the SMALLEST possible change. One variable at a time. Don't fix multiple things at once.
89
+ 3. **Verify Before Continuing** — Did it work? Yes → Phase 4. No → Form NEW hypothesis. DON'T add more fixes on top.
90
+ 4. **When You Don't Know** — Say "I don't understand X." Don't pretend to know. Ask for help. Research more. The escape valve is real: an honest "I'm stuck on X" beats a confident wrong fix every time.
91
+
92
+ ### Phase 4: Implementation
93
+
94
+ 1. **Create Failing Test Case** — Use `/skill:test-driven-development` for writing proper failing tests. MUST have before fixing.
95
+
96
+ 2. **Implement Single Fix** — ONE change at a time. No "while I'm here" improvements. No bundled refactoring.
97
+
98
+ 3. **Verify Fix** — Test passes? No other tests broken? Issue actually resolved?
99
+
100
+ 4. **If Fix Doesn't Work:**
101
+ - If < 3 attempts: Return to Phase 1, re-analyze with new information
102
+ - **If ≥ 3 attempts: STOP (see below)**
103
+
104
+ ### When 3+ Fixes Fail: Question Architecture
105
+
106
+ **This is NOT a failed hypothesis — it's a wrong architecture.**
107
+
108
+ Pattern indicating architectural problem:
109
+ - Each fix reveals new shared state/coupling in different places
110
+ - Fixes require "massive refactoring" to implement
111
+ - Each fix creates new symptoms elsewhere
112
+
113
+ **STOP and question fundamentals:**
114
+ - Is this pattern fundamentally sound?
115
+ - Are we sticking with it through sheer inertia?
116
+ - Should we refactor architecture vs. continue fixing symptoms?
117
+
118
+ **Discuss with your human partner before attempting more fixes.**
119
+
120
+ ## Red Flags and Rationalizations
121
+
122
+ Read `reference/rationalizations.md` for the full table of excuses and the partner-signal redirections. Short version:
123
+
124
+ - "Quick fix for now, investigate later" → return to Phase 1.
125
+ - "Just try changing X and see if it works" → return to Phase 1.
126
+ - "It's probably X, let me fix that" → return to Phase 1.
127
+ - "One more fix attempt" after 2+ failures → question architecture, don't fix again.
128
+ - Each fix reveals a new problem in a different place → question architecture.
129
+
130
+ ## When Process Reveals "No Root Cause"
131
+
132
+ If investigation reveals issue is truly environmental, timing-dependent, or external:
133
+ 1. Document what you investigated
134
+ 2. Implement appropriate handling (retry, timeout, error message)
135
+ 3. Add monitoring/logging for future investigation
136
+
137
+ **But:** 95% of "no root cause" cases are incomplete investigation.
138
+
139
+ ## Supporting Techniques
140
+
141
+ These techniques are part of systematic debugging and available in this directory:
142
+
143
+ - **`root-cause-tracing.md`** — Trace bugs backward through call stack to find original trigger
144
+ - **`defense-in-depth.md`** — Add validation at multiple layers after finding root cause
145
+ - **`condition-based-waiting.md`** — Replace arbitrary timeouts with condition polling
146
+
147
+ Read directly when needed: `reference/rationalizations.md` and the supporting `*.md` files in this directory.
148
+
149
+ ## Project overrides
150
+
151
+ If `.pi/gauntlet-overrides.md` exists, read it. Any sections relevant to this skill — by name match, by topic (routing, verification, worktrees, etc.), or by workflow convention — override or extend the instructions above. Project-local `AGENTS.md` is already in context — check it for project-specific routing tables, service paths, and verification commands.
@@ -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,115 @@
1
+ # Condition-Based Waiting
2
+
3
+ ## Overview
4
+
5
+ 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.
6
+
7
+ **Core principle:** Wait for the actual condition you care about, not a guess about how long it takes.
8
+
9
+ ## When to Use
10
+
11
+ ```dot
12
+ digraph when_to_use {
13
+ "Test uses setTimeout/sleep?" [shape=diamond];
14
+ "Testing timing behavior?" [shape=diamond];
15
+ "Document WHY timeout needed" [shape=box];
16
+ "Use condition-based waiting" [shape=box];
17
+
18
+ "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
19
+ "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
20
+ "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
21
+ }
22
+ ```
23
+
24
+ **Use when:**
25
+ - Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
26
+ - Tests are flaky (pass sometimes, fail under load)
27
+ - Tests timeout when run in parallel
28
+ - Waiting for async operations to complete
29
+
30
+ **Don't use when:**
31
+ - Testing actual timing behavior (debounce, throttle intervals)
32
+ - Always document WHY if using arbitrary timeout
33
+
34
+ ## Core Pattern
35
+
36
+ ```typescript
37
+ // ❌ BEFORE: Guessing at timing
38
+ await new Promise(r => setTimeout(r, 50));
39
+ const result = getResult();
40
+ expect(result).toBeDefined();
41
+
42
+ // ✅ AFTER: Waiting for condition
43
+ await waitFor(() => getResult() !== undefined);
44
+ const result = getResult();
45
+ expect(result).toBeDefined();
46
+ ```
47
+
48
+ ## Quick Patterns
49
+
50
+ | Scenario | Pattern |
51
+ |----------|---------|
52
+ | Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
53
+ | Wait for state | `waitFor(() => machine.state === 'ready')` |
54
+ | Wait for count | `waitFor(() => items.length >= 5)` |
55
+ | Wait for file | `waitFor(() => fs.existsSync(path))` |
56
+ | Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |
57
+
58
+ ## Implementation
59
+
60
+ Generic polling function:
61
+ ```typescript
62
+ async function waitFor<T>(
63
+ condition: () => T | undefined | null | false,
64
+ description: string,
65
+ timeoutMs = 5000
66
+ ): Promise<T> {
67
+ const startTime = Date.now();
68
+
69
+ while (true) {
70
+ const result = condition();
71
+ if (result) return result;
72
+
73
+ if (Date.now() - startTime > timeoutMs) {
74
+ throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
75
+ }
76
+
77
+ await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
78
+ }
79
+ }
80
+ ```
81
+
82
+ See `condition-based-waiting-example.ts` in this directory for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`) from actual debugging session.
83
+
84
+ ## Common Mistakes
85
+
86
+ **❌ Polling too fast:** `setTimeout(check, 1)` - wastes CPU
87
+ **✅ Fix:** Poll every 10ms
88
+
89
+ **❌ No timeout:** Loop forever if condition never met
90
+ **✅ Fix:** Always include timeout with clear error
91
+
92
+ **❌ Stale data:** Cache state before loop
93
+ **✅ Fix:** Call getter inside loop for fresh data
94
+
95
+ ## When Arbitrary Timeout IS Correct
96
+
97
+ ```typescript
98
+ // Tool ticks every 100ms - need 2 ticks to verify partial output
99
+ await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
100
+ await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
101
+ // 200ms = 2 ticks at 100ms intervals - documented and justified
102
+ ```
103
+
104
+ **Requirements:**
105
+ 1. First wait for triggering condition
106
+ 2. Based on known timing (not guessing)
107
+ 3. Comment explaining WHY
108
+
109
+ ## Real-World Impact
110
+
111
+ From debugging session (2025-10-03):
112
+ - Fixed 15 flaky tests across 3 files
113
+ - Pass rate: 60% → 100%
114
+ - Execution time: 40% faster
115
+ - No more race conditions