superpowers-mcp 6.0.3 → 6.2.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.
Files changed (37) hide show
  1. package/README.ja.md +15 -2
  2. package/README.ko.md +15 -2
  3. package/README.md +21 -2
  4. package/README.zh-TW.md +22 -3
  5. package/out/server.js +1 -1
  6. package/package.json +1 -1
  7. package/skills/brainstorming/SKILL.md +1 -9
  8. package/skills/brainstorming/scripts/stop-server.ps1 +11 -3
  9. package/skills/brainstorming/visual-companion.md +7 -0
  10. package/skills/dispatching-parallel-agents/SKILL.md +0 -18
  11. package/skills/executing-plans/SKILL.md +6 -12
  12. package/skills/finishing-a-development-branch/SKILL.md +64 -105
  13. package/skills/receiving-code-review/SKILL.md +0 -8
  14. package/skills/requesting-code-review/SKILL.md +6 -14
  15. package/skills/subagent-driven-development/SKILL.md +314 -228
  16. package/skills/subagent-driven-development/implementer-prompt.md +6 -3
  17. package/skills/subagent-driven-development/re-review-prompt.md +106 -0
  18. package/skills/subagent-driven-development/scripts/review-package +11 -9
  19. package/skills/subagent-driven-development/scripts/review-package.ps1 +17 -10
  20. package/skills/subagent-driven-development/scripts/sdd-workspace +26 -8
  21. package/skills/subagent-driven-development/scripts/sdd-workspace.ps1 +29 -4
  22. package/skills/subagent-driven-development/scripts/task-brief +4 -3
  23. package/skills/subagent-driven-development/scripts/task-brief.ps1 +5 -4
  24. package/skills/subagent-driven-development/task-reviewer-prompt.md +3 -5
  25. package/skills/systematic-debugging/SKILL.md +1 -14
  26. package/skills/systematic-debugging/find-polluter.ps1 +20 -4
  27. package/skills/test-driven-development/SKILL.md +10 -61
  28. package/skills/test-driven-development/writing-good-tests.md +198 -0
  29. package/skills/using-git-worktrees/SKILL.md +9 -44
  30. package/skills/using-superpowers/references/antigravity-tools.md +1 -1
  31. package/skills/using-superpowers/references/codex-tools.md +1 -1
  32. package/skills/using-superpowers/references/gemini-tools.md +44 -32
  33. package/skills/verification-before-completion/SKILL.md +0 -19
  34. package/skills/writing-plans/SKILL.md +0 -6
  35. package/skills/writing-skills/SKILL.md +1 -11
  36. package/skills/test-driven-development/testing-anti-patterns.md +0 -299
  37. package/skills/using-superpowers/references/copilot-tools.md +0 -42
@@ -106,9 +106,12 @@ Subagent (general-purpose):
106
106
 
107
107
  ## After Review Findings
108
108
 
109
- If a reviewer finds issues and you fix them, re-run the tests that cover
110
- the amended code and append the results to your report file. Reviewers
111
- will not re-run tests for you — your report is the test evidence.
109
+ If the task review finds issues, you will be resumed with the findings.
110
+ Fix them, re-run the tests that cover the amended code, and append a fix
111
+ report to your report file: what you changed, the covering tests you
112
+ ran, the command, and the output. Reviewers will not re-run tests for
113
+ you — your report is the test evidence. Then reply with the same short
114
+ status contract as your first report.
112
115
 
113
116
  ## Report Format
114
117
 
@@ -0,0 +1,106 @@
1
+ # Scoped Re-Review Prompt Template
2
+
3
+ Use this template when dispatching a re-review after a fix round. The
4
+ re-reviewer verifies the findings were addressed and checks the fix diff for
5
+ new breakage. It is not a fresh review — the full review already happened.
6
+
7
+ **Purpose:** Verify each finding from the previous review was addressed, and
8
+ that the fix itself broke nothing.
9
+
10
+ ```
11
+ Subagent (general-purpose):
12
+ description: "Re-review Task N fix round R"
13
+ model: [MODEL — REQUIRED: choose per SKILL.md Model Selection; an omitted
14
+ model silently inherits the session's most expensive one]
15
+ prompt: |
16
+ You are re-reviewing one task's fix round. A previous review produced
17
+ findings; an implementer has attempted to fix them. Your job is to
18
+ verdict each finding and inspect the fix diff — nothing else.
19
+
20
+ ## The Task
21
+
22
+ Read the task brief: [BRIEF_FILE]
23
+
24
+ ## The Findings Under Verification
25
+
26
+ [FINDINGS]
27
+
28
+ ## The Fix
29
+
30
+ Read the implementer's report (fix reports are appended at the end):
31
+ [REPORT_FILE]
32
+
33
+ **Fix base:** [FIX_BASE_SHA] (the head the previous review saw)
34
+ **Head:** [HEAD_SHA]
35
+ **Diff file:** [DIFF_FILE]
36
+
37
+ Read the diff file once — it contains the fix commits, a stat summary,
38
+ and the fix diff with surrounding context. Do not re-run git commands.
39
+ If the diff file is missing, fetch the diff yourself:
40
+ `git diff --stat [FIX_BASE_SHA]..[HEAD_SHA]` and
41
+ `git diff [FIX_BASE_SHA]..[HEAD_SHA]`.
42
+
43
+ Your review is read-only on this checkout. Do not mutate the working
44
+ tree, the index, HEAD, or branch state in any way.
45
+
46
+ ## Scope
47
+
48
+ Your scope is the findings list and the fix diff. Verdict every finding.
49
+ Inspect the fix diff for new problems the fix itself introduced. Do NOT
50
+ re-review code the fix did not touch: if you notice an issue entirely
51
+ outside the fix diff, report it under Out-of-Scope Observations — it
52
+ does not block this task and does not extend the loop. A broad
53
+ whole-branch review happens after all tasks are complete.
54
+
55
+ ## Tests
56
+
57
+ The implementer re-ran the tests covering the amended code and appended
58
+ the results to the report file. Treat the report as unverified claims:
59
+ confirm the fix report names the covering tests and shows their output,
60
+ and verify the claims against the diff. Do not re-run the suite to
61
+ confirm their report. Run a test only when reading the code raises a
62
+ specific doubt that no existing run answers — and then a focused test,
63
+ never a package-wide suite.
64
+
65
+ ## Output Format
66
+
67
+ Your final message is the report itself: begin directly with the first
68
+ finding's verdict. Every line is a verdict, a finding with file:line,
69
+ or a check you ran — no preamble, no process narration.
70
+
71
+ ### Finding Verdicts
72
+
73
+ For each finding in The Findings Under Verification, in order:
74
+ - **[finding one-liner]** — ADDRESSED | NOT ADDRESSED, with file:line
75
+ evidence. "Attempted" is not addressed: the specific defect must no
76
+ longer exist.
77
+
78
+ ### New Breakage in the Fix Diff
79
+
80
+ Anything the fix itself broke or introduced, with severity
81
+ (Critical/Important/Minor) and file:line. "None" if clean.
82
+
83
+ ### Out-of-Scope Observations
84
+
85
+ Issues you noticed entirely outside the fix diff. Non-blocking; the
86
+ controller ledgers these for the final review. "None" if none.
87
+
88
+ ### Verdict
89
+
90
+ **Fix round:** [All findings addressed, no new Critical/Important
91
+ breakage | Findings remain open] — list the open ones.
92
+ ```
93
+
94
+ **Placeholders:**
95
+ - `[MODEL]` — REQUIRED: reviewer model per SKILL.md Model Selection; scoped
96
+ re-reviews of small fix diffs take a cheap-to-mid tier
97
+ - `[BRIEF_FILE]` — the task brief file (same file the implementer worked from)
98
+ - `[FINDINGS]` — the Critical/Important findings and spec gaps from the
99
+ previous review, copied verbatim, one per bullet
100
+ - `[REPORT_FILE]` — the implementer's report file (fix reports appended)
101
+ - `[FIX_BASE_SHA]` — the head the previous review saw
102
+ - `[HEAD_SHA]` — current commit
103
+ - `[DIFF_FILE]` — the path `scripts/review-package PLAN_FILE FIX_BASE HEAD` printed
104
+
105
+ **Re-reviewer returns:** per-finding verdicts (ADDRESSED / NOT ADDRESSED),
106
+ new breakage in the fix diff, out-of-scope observations, and a round verdict.
@@ -4,26 +4,28 @@
4
4
  # call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit
5
5
  # tasks intact.
6
6
  #
7
- # Usage: review-package BASE HEAD [OUTFILE]
8
- # Default OUTFILE: <repo-root>/.superpowers/sdd/review-<base7>..<head7>.diff
7
+ # Usage: review-package PLAN_FILE BASE HEAD [OUTFILE]
8
+ # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/review-<base7>..<head7>.diff
9
9
  # (named per range, so a re-review after fixes gets a distinct fresh file).
10
10
  set -euo pipefail
11
11
 
12
- if [ $# -lt 2 ] || [ $# -gt 3 ]; then
13
- echo "usage: review-package BASE HEAD [OUTFILE]" >&2
12
+ if [ $# -lt 3 ] || [ $# -gt 4 ]; then
13
+ echo "usage: review-package PLAN_FILE BASE HEAD [OUTFILE]" >&2
14
14
  exit 2
15
15
  fi
16
16
 
17
- base=$1
18
- head=$2
17
+ plan=$1
18
+ base=$2
19
+ head=$3
20
+ [ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
19
21
 
20
22
  git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
21
23
  git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
22
24
 
23
- if [ $# -eq 3 ]; then
24
- out=$3
25
+ if [ $# -eq 4 ]; then
26
+ out=$4
25
27
  else
26
- dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
28
+ dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
27
29
  out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
28
30
  fi
29
31
 
@@ -1,34 +1,41 @@
1
1
  #!/usr/bin/env pwsh
2
2
  # Generate a review package: commit list, stat summary, and net diff.
3
- # Usage: ./review-package.ps1 BASE HEAD [OUTFILE]
3
+ # Usage: ./review-package.ps1 PLAN_FILE BASE HEAD [OUTFILE]
4
+ # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/review-<base7>..<head7>.diff
4
5
 
5
6
  $ErrorActionPreference = "Stop"
6
7
 
7
- if ($args.Count -lt 2 -or $args.Count -gt 3) {
8
- Write-Error "usage: review-package.ps1 BASE HEAD [OUTFILE]"
8
+ if ($args.Count -lt 3 -or $args.Count -gt 4) {
9
+ [Console]::Error.WriteLine("usage: review-package.ps1 PLAN_FILE BASE HEAD [OUTFILE]")
9
10
  exit 2
10
11
  }
11
12
 
12
- $base = $args[0]
13
- $head = $args[1]
13
+ $plan = $args[0]
14
+ $base = $args[1]
15
+ $head = $args[2]
16
+
17
+ if (-not (Test-Path -LiteralPath $plan -PathType Leaf)) {
18
+ [Console]::Error.WriteLine("no such plan file: $plan")
19
+ exit 2
20
+ }
14
21
 
15
22
  & git rev-parse --verify --quiet $base *> $null
16
23
  if ($LASTEXITCODE -ne 0) {
17
- Write-Error "bad BASE: $base"
24
+ [Console]::Error.WriteLine("bad BASE: $base")
18
25
  exit 2
19
26
  }
20
27
 
21
28
  & git rev-parse --verify --quiet $head *> $null
22
29
  if ($LASTEXITCODE -ne 0) {
23
- Write-Error "bad HEAD: $head"
30
+ [Console]::Error.WriteLine("bad HEAD: $head")
24
31
  exit 2
25
32
  }
26
33
 
27
- if ($args.Count -eq 3) {
28
- $out = $args[2]
34
+ if ($args.Count -eq 4) {
35
+ $out = $args[3]
29
36
  } else {
30
37
  $scriptDir = Split-Path -Parent $PSCommandPath
31
- $dir = (& (Join-Path $scriptDir "sdd-workspace.ps1")).Trim()
38
+ $dir = (& (Join-Path $scriptDir "sdd-workspace.ps1") $plan | Select-Object -First 1).Trim()
32
39
  $baseShort = (& git rev-parse --short $base).Trim()
33
40
  $headShort = (& git rev-parse --short $head).Trim()
34
41
  $out = Join-Path $dir "review-$baseShort..$headShort.diff"
@@ -1,22 +1,40 @@
1
1
  #!/usr/bin/env bash
2
- # Resolve and ensure the working-tree directory SDD uses for its short-lived
3
- # artifacts: task briefs, implementer reports, review packages, and the
4
- # progress ledger. Print the directory's absolute path.
2
+ # Resolve and ensure the working-tree directory SDD uses for one plan's
3
+ # short-lived artifacts: task briefs, implementer reports, review packages,
4
+ # and the progress ledger. Print the plan directory's absolute path.
5
+ #
6
+ # One directory per plan (.superpowers/sdd/<plan-basename>/) so a follow-up
7
+ # plan in the same working tree can never read or overwrite another plan's
8
+ # artifacts. A stale ledger misread as current progress makes controllers
9
+ # skip whole task sequences — plan-scoping removes that failure structurally.
5
10
  #
6
11
  # The workspace lives in the working tree (not under .git/) because Claude Code
7
12
  # treats .git/ as a protected path and denies agent writes there — which blocks
8
13
  # an implementer subagent from writing its report file. A self-ignoring
9
- # .gitignore keeps the workspace out of `git status` and out of accidental
10
- # commits without modifying any tracked file.
14
+ # .gitignore at .superpowers/sdd/ keeps every plan's workspace out of
15
+ # `git status` and out of accidental commits without modifying any tracked file.
11
16
  #
12
17
  # Single source of truth for the workspace location, so task-brief and
13
18
  # review-package cannot drift to different directories.
14
19
  #
15
- # Usage: sdd-workspace
20
+ # Usage: sdd-workspace PLAN_FILE
16
21
  set -euo pipefail
17
22
 
23
+ if [ $# -ne 1 ]; then
24
+ echo "usage: sdd-workspace PLAN_FILE" >&2
25
+ exit 2
26
+ fi
27
+
28
+ plan=$1
29
+ [ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
30
+
31
+ slug=$(basename "$plan" .md)
32
+ [ -n "$slug" ] && [ "$slug" != "." ] && [ "$slug" != ".." ] \
33
+ || { echo "cannot derive a workspace name from: $plan" >&2; exit 2; }
34
+
18
35
  root=$(git rev-parse --show-toplevel)
19
- dir="$root/.superpowers/sdd"
36
+ base="$root/.superpowers/sdd"
37
+ dir="$base/$slug"
20
38
  mkdir -p "$dir"
21
- printf '*\n' > "$dir/.gitignore"
39
+ printf '*\n' > "$base/.gitignore"
22
40
  cd "$dir" && pwd
@@ -1,11 +1,36 @@
1
1
  #!/usr/bin/env pwsh
2
- # Resolve and ensure the working-tree directory SDD uses for short-lived artifacts.
3
- # Usage: ./sdd-workspace.ps1
2
+ # Resolve and ensure the working-tree directory SDD uses for one plan's
3
+ # short-lived artifacts: task briefs, implementer reports, review packages,
4
+ # and the progress ledger. Print the plan directory's absolute path.
5
+ #
6
+ # One directory per plan (.superpowers/sdd/<plan-basename>/) so a follow-up
7
+ # plan in the same working tree can never read or overwrite another plan's
8
+ # artifacts.
9
+ #
10
+ # Usage: ./sdd-workspace.ps1 PLAN_FILE
4
11
 
5
12
  $ErrorActionPreference = "Stop"
6
13
 
14
+ if ($args.Count -ne 1) {
15
+ [Console]::Error.WriteLine("usage: sdd-workspace.ps1 PLAN_FILE")
16
+ exit 2
17
+ }
18
+
19
+ $plan = $args[0]
20
+ if (-not (Test-Path -LiteralPath $plan -PathType Leaf)) {
21
+ [Console]::Error.WriteLine("no such plan file: $plan")
22
+ exit 2
23
+ }
24
+
25
+ $slug = [System.IO.Path]::GetFileName($plan) -replace '\.md$', ''
26
+ if ([string]::IsNullOrEmpty($slug) -or $slug -eq "." -or $slug -eq "..") {
27
+ [Console]::Error.WriteLine("cannot derive a workspace name from: $plan")
28
+ exit 2
29
+ }
30
+
7
31
  $root = (& git rev-parse --show-toplevel).Trim()
8
- $dir = Join-Path $root ".superpowers/sdd"
32
+ $base = Join-Path $root ".superpowers/sdd"
33
+ $dir = Join-Path $base $slug
9
34
  New-Item -ItemType Directory -Force -Path $dir | Out-Null
10
- Set-Content -Path (Join-Path $dir ".gitignore") -Value "*" -NoNewline -Encoding ascii
35
+ Set-Content -Path (Join-Path $base ".gitignore") -Value "*" -NoNewline -Encoding ascii
11
36
  (Resolve-Path $dir).Path
@@ -4,8 +4,9 @@
4
4
  # through the controller's context.
5
5
  #
6
6
  # Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
7
- # Default OUTFILE: <repo-root>/.superpowers/sdd/task-<N>-brief.md
8
- # (per worktree; concurrent runs in the same working tree share it).
7
+ # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
8
+ # (per plan and per worktree; concurrent runs of the SAME plan in the same
9
+ # working tree share it).
9
10
  set -euo pipefail
10
11
 
11
12
  if [ $# -lt 2 ] || [ $# -gt 3 ]; then
@@ -20,7 +21,7 @@ n=$2
20
21
  if [ $# -eq 3 ]; then
21
22
  out=$3
22
23
  else
23
- dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
24
+ dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace" "$plan")
24
25
  out="$dir/task-${n}-brief.md"
25
26
  fi
26
27
 
@@ -1,18 +1,19 @@
1
1
  #!/usr/bin/env pwsh
2
2
  # Extract one task's full text from an implementation plan.
3
3
  # Usage: ./task-brief.ps1 PLAN_FILE TASK_NUMBER [OUTFILE]
4
+ # Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
4
5
 
5
6
  $ErrorActionPreference = "Stop"
6
7
 
7
8
  if ($args.Count -lt 2 -or $args.Count -gt 3) {
8
- Write-Error "usage: task-brief.ps1 PLAN_FILE TASK_NUMBER [OUTFILE]"
9
+ [Console]::Error.WriteLine("usage: task-brief.ps1 PLAN_FILE TASK_NUMBER [OUTFILE]")
9
10
  exit 2
10
11
  }
11
12
 
12
13
  $plan = $args[0]
13
14
  $taskNumber = $args[1]
14
15
  if (-not (Test-Path -LiteralPath $plan -PathType Leaf)) {
15
- Write-Error "no such plan file: $plan"
16
+ [Console]::Error.WriteLine("no such plan file: $plan")
16
17
  exit 2
17
18
  }
18
19
 
@@ -20,7 +21,7 @@ if ($args.Count -eq 3) {
20
21
  $out = $args[2]
21
22
  } else {
22
23
  $scriptDir = Split-Path -Parent $PSCommandPath
23
- $dir = (& (Join-Path $scriptDir "sdd-workspace.ps1")).Trim()
24
+ $dir = (& (Join-Path $scriptDir "sdd-workspace.ps1") $plan | Select-Object -First 1).Trim()
24
25
  $out = Join-Path $dir "task-$taskNumber-brief.md"
25
26
  }
26
27
 
@@ -43,7 +44,7 @@ foreach ($line in [System.IO.File]::ReadLines((Resolve-Path -LiteralPath $plan).
43
44
 
44
45
  Set-Content -Path $out -Value $selected -Encoding utf8
45
46
  if ((-not (Test-Path -LiteralPath $out)) -or ((Get-Item -LiteralPath $out).Length -eq 0)) {
46
- Write-Error "task $taskNumber not found in $plan (no heading matching 'Task $taskNumber')"
47
+ [Console]::Error.WriteLine("task $taskNumber not found in $plan (no heading matching 'Task $taskNumber')")
47
48
  exit 3
48
49
  }
49
50
 
@@ -179,11 +179,9 @@ Subagent (general-purpose):
179
179
  - `[BASE_SHA]` — commit before this task
180
180
  - `[HEAD_SHA]` — current commit
181
181
  - `[DIFF_FILE]` — REQUIRED: the path the controller wrote the review
182
- package to (`scripts/review-package BASE HEAD`, or `scripts/review-package.ps1 BASE HEAD` on Windows PowerShell, prints the unique path it
183
- wrote; the package never enters the controller's context)
182
+ package to (`scripts/review-package PLAN_FILE BASE HEAD`, or
183
+ `scripts/review-package.ps1 PLAN_FILE BASE HEAD` on Windows PowerShell,
184
+ prints the unique path it wrote; the package never enters the controller's context)
184
185
 
185
186
  **Reviewer returns:** Spec Compliance verdict (✅/❌/⚠️), Strengths, Issues
186
187
  (Critical/Important/Minor), Task quality verdict
187
-
188
- A fix dispatch can address spec gaps and quality findings together;
189
- re-review after fixes covers both verdicts.
@@ -7,8 +7,6 @@ description: Use when encountering any bug, test failure, or unexpected behavior
7
7
 
8
8
  ## Overview
9
9
 
10
- Random fixes waste time and create new bugs. Quick patches mask underlying issues.
11
-
12
10
  **Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
13
11
 
14
12
  **Violating the letter of this process is violating the spirit of debugging.**
@@ -188,6 +186,7 @@ You MUST complete each phase before proceeding to the next.
188
186
  - Test passes now?
189
187
  - No other tests broken?
190
188
  - Issue actually resolved?
189
+ - Use the `superpowers:verification-before-completion` skill before claiming success
191
190
 
192
191
  4. **If Fix Doesn't Work**
193
192
  - STOP
@@ -282,15 +281,3 @@ These techniques are part of systematic debugging and available in this director
282
281
  - **`root-cause-tracing.md`** - Trace bugs backward through call stack to find original trigger
283
282
  - **`defense-in-depth.md`** - Add validation at multiple layers after finding root cause
284
283
  - **`condition-based-waiting.md`** - Replace arbitrary timeouts with condition polling
285
-
286
- **Related skills:**
287
- - **superpowers:test-driven-development** - For creating failing test case (Phase 4, Step 1)
288
- - **superpowers:verification-before-completion** - Verify fix worked before claiming success
289
-
290
- ## Real-World Impact
291
-
292
- From debugging sessions:
293
- - Systematic approach: 15-30 minutes to fix
294
- - Random fixes approach: 2-3 hours of thrashing
295
- - First-time fix rate: 95% vs 40%
296
- - New bugs introduced: Near zero vs common
@@ -17,10 +17,26 @@ Write-Output "Searching for test that creates: $pollutionCheck"
17
17
  Write-Output "Test pattern: $testPattern"
18
18
  Write-Output ""
19
19
 
20
- $testFiles = @(Get-ChildItem -Path $testPattern -File -Recurse -ErrorAction SilentlyContinue | Sort-Object FullName)
21
- if ($testFiles.Count -eq 0) {
22
- $testFiles = @(Get-ChildItem -Path . -File -Recurse | Where-Object { $_.FullName -like (Join-Path (Get-Location) $testPattern) } | Sort-Object FullName)
23
- }
20
+ # Accept the pattern written with or without a leading ./ (or .\)
21
+ $testPattern = $testPattern -replace '^\.[/\\]', ''
22
+
23
+ # '**/' can't match zero directory levels in a -like comparison, so a
24
+ # pattern like src/**/*.test.ts would skip src/top.test.ts; also try the
25
+ # pattern with '**/' collapsed to cover files directly under the base
26
+ # directory.
27
+ $patterns = @($testPattern)
28
+ $collapsed = $testPattern -replace '\*\*[/\\]', ''
29
+ if ($collapsed -ne $testPattern) { $patterns += $collapsed }
30
+
31
+ $root = (Get-Location).Path.Replace('\', '/')
32
+ $testFiles = @(Get-ChildItem -Path . -File -Recurse -ErrorAction SilentlyContinue | Where-Object {
33
+ $full = $_.FullName.Replace('\', '/')
34
+ $hit = $false
35
+ foreach ($p in $patterns) {
36
+ if ($full -like "$root/$p") { $hit = $true; break }
37
+ }
38
+ $hit
39
+ } | Sort-Object FullName -Unique)
24
40
 
25
41
  $total = $testFiles.Count
26
42
  Write-Output "Found $total test files"
@@ -203,69 +203,25 @@ Next failing test for next feature.
203
203
  | **Clear** | Name describes behavior | `test('test1')` |
204
204
  | **Shows intent** | Demonstrates desired API | Obscures what code should do |
205
205
 
206
- ## Why Order Matters
207
-
208
- **"I'll write tests after to verify it works"**
209
-
210
- Tests written after code pass immediately. Passing immediately proves nothing:
211
- - Might test wrong thing
212
- - Might test implementation, not behavior
213
- - Might miss edge cases you forgot
214
- - You never saw it catch the bug
215
-
216
- Test-first forces you to see the test fail, proving it actually tests something.
217
-
218
- **"I already manually tested all the edge cases"**
219
-
220
- Manual testing is ad-hoc. You think you tested everything but:
221
- - No record of what you tested
222
- - Can't re-run when code changes
223
- - Easy to forget cases under pressure
224
- - "It worked when I tried it" ≠ comprehensive
225
-
226
- Automated tests are systematic. They run the same way every time.
227
-
228
- **"Deleting X hours of work is wasteful"**
229
-
230
- Sunk cost fallacy. The time is already gone. Your choice now:
231
- - Delete and rewrite with TDD (X more hours, high confidence)
232
- - Keep it and add tests after (30 min, low confidence, likely bugs)
233
-
234
- The "waste" is keeping code you can't trust. Working code without real tests is technical debt.
235
-
236
- **"TDD is dogmatic, being pragmatic means adapting"**
237
-
238
- TDD IS pragmatic:
239
- - Finds bugs before commit (faster than debugging after)
240
- - Prevents regressions (tests catch breaks immediately)
241
- - Documents behavior (tests show how to use code)
242
- - Enables refactoring (change freely, tests catch breaks)
243
-
244
- "Pragmatic" shortcuts = debugging in production = slower.
245
-
246
- **"Tests after achieve the same goals - it's spirit not ritual"**
247
-
248
- No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"
249
-
250
- Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones.
251
-
252
- Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't).
253
-
254
- 30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work.
206
+ When writing or changing any test, read [writing-good-tests.md](writing-good-tests.md) for the rules that keep tests honest:
207
+ - Name the production change that would make the test fail — before writing it
208
+ - Assert on real behavior, never on mock behavior
209
+ - Keep test-only code in test utilities, out of production classes
210
+ - Understand a dependency's side effects before mocking it
255
211
 
256
212
  ## Common Rationalizations
257
213
 
258
214
  | Excuse | Reality |
259
215
  |--------|---------|
260
216
  | "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
261
- | "I'll test after" | Tests passing immediately prove nothing. |
262
- | "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
263
- | "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
264
- | "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
217
+ | "I'll test after" | Tests written after pass immediately — which proves nothing. They may test the wrong thing, test the implementation instead of the behavior, or miss the edge case you forgot. You never watched it fail, so you never proved it can catch the bug. Test-first forces that failure. |
218
+ | "Tests after achieve same goals (spirit not ritual)" | Tests-after answer "what does this do?"; tests-first answer "what should this do?" Tests written after are biased by the code you already wrote — you verify the cases you remembered, not the ones you'd have discovered. Coverage without proof the tests work. |
219
+ | "Already manually tested" | Manual testing is ad-hoc: no record of what you covered, no way to re-run it when the code changes, easy to forget cases under pressure. "Worked when I tried it" ≠ comprehensive. Automated tests run the same way every time. |
220
+ | "Deleting X hours is wasteful" | Sunk cost fallacy — that time is already spent either way. The real choice: rewrite with TDD (high confidence) vs. keep it and bolt tests on after (low confidence, likely bugs). Keeping code you can't trust is the waste. |
265
221
  | "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
266
222
  | "Need to explore first" | Fine. Throw away exploration, start with TDD. |
267
223
  | "Test hard = design unclear" | Listen to test. Hard to test = hard to use. |
268
- | "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
224
+ | "TDD will slow me down" | TDD IS the pragmatic path: catches bugs before commit, prevents regressions, lets you refactor without fear. "Pragmatic" shortcuts mean debugging in production — slower, not faster. |
269
225
  | "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
270
226
  | "Existing code has no tests" | You're improving it. Add tests for existing code. |
271
227
 
@@ -354,13 +310,6 @@ Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix
354
310
 
355
311
  Never fix bugs without a test.
356
312
 
357
- ## Testing Anti-Patterns
358
-
359
- When adding mocks or test utilities, read [testing-anti-patterns.md](testing-anti-patterns.md) to avoid common pitfalls:
360
- - Testing mock behavior instead of real behavior
361
- - Adding test-only methods to production classes
362
- - Mocking without understanding dependencies
363
-
364
313
  ## Final Rule
365
314
 
366
315
  ```