@windyroad/tdd 0.4.3 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +14 -13
- package/hooks/lib/tdd-gate.sh +23 -0
- package/hooks/test/tdd-gate.bats +55 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,6 +49,20 @@ Once active, the workflow is enforced on every edit:
|
|
|
49
49
|
|
|
50
50
|
Test files and config/doc files are always writable regardless of state.
|
|
51
51
|
|
|
52
|
+
## Supported test layouts
|
|
53
|
+
|
|
54
|
+
The hook associates an implementation file with its tracked test by matching one of the following path shapes (per [P201](../../docs/problems/verifying/201-windyroad-tdd-hook-only-recognises-same-dir-or-tests-test-associations.md)):
|
|
55
|
+
|
|
56
|
+
| Shape | Impl path | Test path |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Same-directory | `src/foo.js` | `src/foo.test.js` or `src/foo.spec.js` |
|
|
59
|
+
| `__tests__/`-adjacent | `src/foo.js` | `src/__tests__/foo.test.js` |
|
|
60
|
+
| Parent `__tests__/` | `src/components/Hero.tsx` | `src/__tests__/Hero.test.tsx` |
|
|
61
|
+
| `test/`-mirror (Vitest default; many Jest setups) | `src/foo.js`, `src/a/b/foo.js`, `packages/<pkg>/src/foo.js` | `test/foo.test.js`, `test/a/b/foo.test.js`, `packages/<pkg>/test/foo.test.js` |
|
|
62
|
+
| Cucumber step-definitions | `features/step_definitions/checkout.steps.js` | `features/checkout.feature` |
|
|
63
|
+
|
|
64
|
+
The `test/`-mirror rule replaces the **last** `src` path segment with `test`, so it works at any nesting depth and for monorepo workspace layouts.
|
|
65
|
+
|
|
52
66
|
## How It Works
|
|
53
67
|
|
|
54
68
|
| Hook | Trigger | What it does |
|
|
@@ -60,19 +74,6 @@ Test files and config/doc files are always writable regardless of state.
|
|
|
60
74
|
| `tdd-setup-marker.sh` | Skill completes | Marks test setup as done |
|
|
61
75
|
| `tdd-reset.sh` | Session end | Resets the TDD state |
|
|
62
76
|
|
|
63
|
-
## Jobs to be Done
|
|
64
|
-
|
|
65
|
-
This plugin serves the [Jobs to be Done](../../docs/jtbd/) below. Per [ADR-051](../../docs/decisions/051-jtbd-anchored-readme-with-drift-advisory.proposed.md), the persona-grouped JTBD anchor is the canonical source of truth for the README's value framing.
|
|
66
|
-
|
|
67
|
-
### Solo developer
|
|
68
|
-
|
|
69
|
-
- **[JTBD-001 Enforce Governance Without Slowing Down](../../docs/jtbd/solo-developer/JTBD-001-enforce-governance.proposed.md)** — implementation edits are blocked until a failing test exists; the agent cannot "forget" to write the test first.
|
|
70
|
-
- **[JTBD-002 Ship AI-Assisted Code with Confidence](../../docs/jtbd/solo-developer/JTBD-002-ship-with-confidence.proposed.md)** — Red-Green-Refactor enforcement turns the test suite into the agent's safety net, not an after-thought.
|
|
71
|
-
|
|
72
|
-
### Plugin user
|
|
73
|
-
|
|
74
|
-
- **[JTBD-302 Trust That the README Describes the Plugin I Just Installed](../../docs/jtbd/plugin-user/JTBD-302-trust-readme-describes-installed-behaviour.proposed.md)** — this README is anchored on current JTBD job IDs; drift between prose and shipped behaviour is detectable at retro time per ADR-051.
|
|
75
|
-
|
|
76
77
|
## Updating and Uninstalling
|
|
77
78
|
|
|
78
79
|
```bash
|
package/hooks/lib/tdd-gate.sh
CHANGED
|
@@ -157,6 +157,22 @@ tdd_find_test_for_impl() {
|
|
|
157
157
|
return
|
|
158
158
|
fi
|
|
159
159
|
|
|
160
|
+
# test/-mirror layout (P201): src/foo.js → test/foo.test.js, recursive for
|
|
161
|
+
# nested src/a/b/foo.js → test/a/b/foo.test.js, and workspace
|
|
162
|
+
# packages/<pkg>/src/foo.js → packages/<pkg>/test/foo.test.js.
|
|
163
|
+
# Compute the mirror once — depends only on DIR (the impl's directory).
|
|
164
|
+
# Replaces the LAST `src` path segment with `test`.
|
|
165
|
+
local MIRROR_DIR=""
|
|
166
|
+
case "$DIR" in
|
|
167
|
+
src) MIRROR_DIR="test" ;;
|
|
168
|
+
src/*) MIRROR_DIR="test/${DIR#src/}" ;;
|
|
169
|
+
*/src) MIRROR_DIR="${DIR%/src}/test" ;;
|
|
170
|
+
*/src/*)
|
|
171
|
+
local _mirror_prefix="${DIR%/src/*}"
|
|
172
|
+
MIRROR_DIR="${_mirror_prefix}/test/${DIR#"${_mirror_prefix}"/src/}"
|
|
173
|
+
;;
|
|
174
|
+
esac
|
|
175
|
+
|
|
160
176
|
# Check tracked test files for a match
|
|
161
177
|
# Priority: exact match in tracked files (any convention)
|
|
162
178
|
while IFS= read -r tracked; do
|
|
@@ -188,6 +204,13 @@ tdd_find_test_for_impl() {
|
|
|
188
204
|
;;
|
|
189
205
|
esac
|
|
190
206
|
|
|
207
|
+
# test/-mirror layout (P201): match when tracked_dir is the computed mirror
|
|
208
|
+
if [ -n "$MIRROR_DIR" ] && [ "$tracked_dir" = "$MIRROR_DIR" ]; then
|
|
209
|
+
case "$tracked_base" in
|
|
210
|
+
"${STEM}.test."*|"${STEM}.spec."*) echo "$tracked"; return ;;
|
|
211
|
+
esac
|
|
212
|
+
fi
|
|
213
|
+
|
|
191
214
|
# Cucumber: features/step_definitions/foo.steps.js → features/foo.feature
|
|
192
215
|
# If this impl is inside a step_definitions/ directory, look in the parent for a .feature file
|
|
193
216
|
case "$DIR" in
|
package/hooks/test/tdd-gate.bats
CHANGED
|
@@ -238,6 +238,61 @@ teardown() {
|
|
|
238
238
|
[ -z "$result" ]
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
# --- tdd_find_test_for_impl: test/-mirror layout (P201) ---
|
|
242
|
+
# Vitest default + many Jest setups mirror src/ under a sibling test/ tree.
|
|
243
|
+
# A new behavioural rule: replace the LAST `src` path segment with `test`
|
|
244
|
+
# to map an impl path to its mirrored test directory.
|
|
245
|
+
|
|
246
|
+
@test "find_test_for_impl: test/-mirror at top level (src/foo.js → test/foo.test.js)" {
|
|
247
|
+
tdd_add_test_file "$TEST_SESSION" "test/foo.test.js"
|
|
248
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "src/foo.js")
|
|
249
|
+
[ "$result" = "test/foo.test.js" ]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@test "find_test_for_impl: test/-mirror with .spec variant (src/foo.js → test/foo.spec.js)" {
|
|
253
|
+
tdd_add_test_file "$TEST_SESSION" "test/foo.spec.js"
|
|
254
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "src/foo.js")
|
|
255
|
+
[ "$result" = "test/foo.spec.js" ]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@test "find_test_for_impl: test/-mirror preserves .tsx (src/Hero.tsx → test/Hero.test.tsx)" {
|
|
259
|
+
tdd_add_test_file "$TEST_SESSION" "test/Hero.test.tsx"
|
|
260
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "src/Hero.tsx")
|
|
261
|
+
[ "$result" = "test/Hero.test.tsx" ]
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@test "find_test_for_impl: test/-mirror recursive nested (src/a/b/foo.js → test/a/b/foo.test.js)" {
|
|
265
|
+
tdd_add_test_file "$TEST_SESSION" "test/a/b/foo.test.js"
|
|
266
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "src/a/b/foo.js")
|
|
267
|
+
[ "$result" = "test/a/b/foo.test.js" ]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@test "find_test_for_impl: test/-mirror workspace layout (packages/foo/src/x.ts → packages/foo/test/x.test.ts)" {
|
|
271
|
+
tdd_add_test_file "$TEST_SESSION" "packages/foo/test/x.test.ts"
|
|
272
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "packages/foo/src/x.ts")
|
|
273
|
+
[ "$result" = "packages/foo/test/x.test.ts" ]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@test "find_test_for_impl: test/-mirror workspace nested (packages/foo/src/a/b/x.ts → packages/foo/test/a/b/x.test.ts)" {
|
|
277
|
+
tdd_add_test_file "$TEST_SESSION" "packages/foo/test/a/b/x.test.ts"
|
|
278
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "packages/foo/src/a/b/x.ts")
|
|
279
|
+
[ "$result" = "packages/foo/test/a/b/x.test.ts" ]
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@test "find_test_for_impl: test/-mirror does NOT match wrong stem (src/foo.js + test/bar.test.js → empty)" {
|
|
283
|
+
tdd_add_test_file "$TEST_SESSION" "test/bar.test.js"
|
|
284
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "src/foo.js")
|
|
285
|
+
[ -z "$result" ]
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@test "find_test_for_impl: test/-mirror does NOT match without src/ anchor (lib/foo.js + test/foo.test.js → empty)" {
|
|
289
|
+
# Ticket scope is explicitly src/-anchored mirror; lib/foo.js → test/foo.test.js
|
|
290
|
+
# is NOT a recognised pairing (the ticket frames it as "src/foo.js → test/foo.test.js").
|
|
291
|
+
tdd_add_test_file "$TEST_SESSION" "test/foo.test.js"
|
|
292
|
+
result=$(tdd_find_test_for_impl "$TEST_SESSION" "lib/foo.js")
|
|
293
|
+
[ -z "$result" ]
|
|
294
|
+
}
|
|
295
|
+
|
|
241
296
|
# --- State for impl files (via association) ---
|
|
242
297
|
|
|
243
298
|
@test "read_state_for_impl: returns IDLE when no associated test" {
|