@windyroad/tdd 0.3.0 → 0.3.1-preview.218
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/hooks/tdd-post-write.sh
CHANGED
|
@@ -72,6 +72,13 @@ fi
|
|
|
72
72
|
# Write new state for this specific test file
|
|
73
73
|
tdd_write_state "$SESSION_ID" "$TEST_FILE" "$NEW_STATE"
|
|
74
74
|
|
|
75
|
+
# P096 Phase 2 — silent-on-GREEN-unchanged: when both old and new state
|
|
76
|
+
# are GREEN, the assistant already knows the file passes; emit nothing.
|
|
77
|
+
# Transitions and any non-GREEN state still emit the full block below.
|
|
78
|
+
if [ "$OLD_STATE" = "GREEN" ] && [ "$NEW_STATE" = "GREEN" ]; then
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
75
82
|
# Read last test output for context
|
|
76
83
|
STDOUT_FILE="/tmp/tdd-test-stdout-${SESSION_ID}"
|
|
77
84
|
TEST_OUTPUT=""
|
|
@@ -95,21 +102,35 @@ File written: ${FILE_PATH} (${FILE_TYPE})
|
|
|
95
102
|
Test result: exit code ${TEST_EXIT}
|
|
96
103
|
EOF
|
|
97
104
|
|
|
105
|
+
# P096 Phase 2 — dedupe RED test output: when consecutive RED edits on
|
|
106
|
+
# the same test file produce identical last-50-lines output, only the
|
|
107
|
+
# first emission carries the body; subsequent emissions skip the test
|
|
108
|
+
# output block. Hash file is keyed by session + encoded test path.
|
|
98
109
|
if [ $TEST_EXIT -ne 0 ] && [ -n "$TEST_OUTPUT" ]; then
|
|
99
|
-
echo ""
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
ENCODED_TEST=$(echo "$TEST_FILE" | sed 's|/|__|g')
|
|
111
|
+
HASH_FILE="/tmp/tdd-stdout-hash-${SESSION_ID}-${ENCODED_TEST}"
|
|
112
|
+
NEW_HASH=$(printf '%s' "$TEST_OUTPUT" | shasum 2>/dev/null | awk '{print $1}')
|
|
113
|
+
PREV_HASH=""
|
|
114
|
+
[ -f "$HASH_FILE" ] && PREV_HASH=$(cat "$HASH_FILE" 2>/dev/null)
|
|
115
|
+
if [ -n "$NEW_HASH" ] && [ "$NEW_HASH" = "$PREV_HASH" ]; then
|
|
116
|
+
echo ""
|
|
117
|
+
echo "Test output unchanged from previous emission (hash match)."
|
|
118
|
+
else
|
|
119
|
+
echo ""
|
|
120
|
+
echo "Test output (last 50 lines):"
|
|
121
|
+
echo "$TEST_OUTPUT"
|
|
122
|
+
[ -n "$NEW_HASH" ] && echo "$NEW_HASH" > "$HASH_FILE"
|
|
123
|
+
fi
|
|
102
124
|
fi
|
|
103
125
|
|
|
126
|
+
# P096 Phase 2 — GREEN ACTION line dropped (standing prose the assistant
|
|
127
|
+
# already knows; the STATE UPDATE block above carries the transition
|
|
128
|
+
# signal). RED and BLOCKED keep their actionable next-step ACTION line.
|
|
104
129
|
case "$NEW_STATE" in
|
|
105
130
|
RED)
|
|
106
131
|
echo ""
|
|
107
132
|
echo "ACTION: Tests are failing for ${TEST_FILE}. Write implementation code to make them pass."
|
|
108
133
|
;;
|
|
109
|
-
GREEN)
|
|
110
|
-
echo ""
|
|
111
|
-
echo "ACTION: Tests are passing for ${TEST_FILE}. You may refactor or write a new failing test for the next behavior."
|
|
112
|
-
;;
|
|
113
134
|
BLOCKED)
|
|
114
135
|
echo ""
|
|
115
136
|
echo "ACTION: Test runner timed out for ${TEST_FILE} (exit code ${TEST_EXIT}). Fix the test setup before continuing."
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# P096 Phase 2: tdd-post-write.sh injection trims.
|
|
4
|
+
#
|
|
5
|
+
# Three behaviours covered:
|
|
6
|
+
# 1. Silent on GREEN-unchanged (OLD=GREEN, NEW=GREEN -> exit 0 with no output).
|
|
7
|
+
# 2. Hash-based dedupe of RED test output across consecutive RED edits with
|
|
8
|
+
# identical last-50-lines output.
|
|
9
|
+
# 3. GREEN ACTION line dropped (the standing prose "Tests are passing... You
|
|
10
|
+
# may refactor..." is no longer emitted on GREEN transitions).
|
|
11
|
+
#
|
|
12
|
+
# Per ADR-038 progressive-disclosure pattern: dynamic state on transition
|
|
13
|
+
# stays; standing prose / repeated content is suppressed.
|
|
14
|
+
|
|
15
|
+
setup() {
|
|
16
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
17
|
+
HOOK="$REPO_ROOT/packages/tdd/hooks/tdd-post-write.sh"
|
|
18
|
+
|
|
19
|
+
WORKDIR="$(mktemp -d)"
|
|
20
|
+
# Use relative paths in the hook input so dirname/basename matching in
|
|
21
|
+
# tdd_find_test_for_impl behaves predictably across mktemp prefixes.
|
|
22
|
+
TEST_FILE="src/widget.test.ts"
|
|
23
|
+
IMPL_FILE="src/widget.ts"
|
|
24
|
+
mkdir -p "$WORKDIR/src"
|
|
25
|
+
: > "$WORKDIR/$TEST_FILE"
|
|
26
|
+
: > "$WORKDIR/$IMPL_FILE"
|
|
27
|
+
|
|
28
|
+
SID="tdd-post-write-phase2-$$-$RANDOM"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
teardown() {
|
|
32
|
+
rm -rf "/tmp/tdd-state-${SID}" \
|
|
33
|
+
"/tmp/tdd-test-files-${SID}" \
|
|
34
|
+
"/tmp/tdd-test-stdout-${SID}" \
|
|
35
|
+
"/tmp/tdd-stdout-hash-${SID}-"*
|
|
36
|
+
rm -rf "$WORKDIR"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Write a package.json whose `test` script prints fixed stdout then exits
|
|
40
|
+
# 0 (pass) or 1 (fail). Uses `true`/`false` as the exit primitive so the
|
|
41
|
+
# trailing test-file argument injected by `npm test -- <file>` is absorbed
|
|
42
|
+
# without triggering "exit: too many arguments".
|
|
43
|
+
write_pkg_json() {
|
|
44
|
+
local exit_code="${1:-0}"
|
|
45
|
+
local stdout_text="${2:-}"
|
|
46
|
+
local exit_cmd="true"
|
|
47
|
+
[ "$exit_code" -ne 0 ] && exit_cmd="false"
|
|
48
|
+
cat > "$WORKDIR/package.json" <<JSON
|
|
49
|
+
{
|
|
50
|
+
"name": "test-tdd-post-write-phase2",
|
|
51
|
+
"version": "0.0.0",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"test": "printf '%s\\n' '${stdout_text}' && ${exit_cmd}"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
JSON
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
run_hook_for_impl() {
|
|
60
|
+
local sid="$1"
|
|
61
|
+
local impl="$2"
|
|
62
|
+
(cd "$WORKDIR" && \
|
|
63
|
+
echo "{\"session_id\":\"$sid\",\"tool_input\":{\"file_path\":\"$impl\"}}" | \
|
|
64
|
+
bash "$HOOK")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
run_hook_for_test() {
|
|
68
|
+
local sid="$1"
|
|
69
|
+
local test_file="$2"
|
|
70
|
+
(cd "$WORKDIR" && \
|
|
71
|
+
echo "{\"session_id\":\"$sid\",\"tool_input\":{\"file_path\":\"$test_file\"}}" | \
|
|
72
|
+
bash "$HOOK")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Register the test file in the tracked set by firing the hook on it once.
|
|
76
|
+
# This is the prerequisite for tdd_find_test_for_impl to associate the impl
|
|
77
|
+
# with the test on subsequent impl-file invocations.
|
|
78
|
+
register_test_file() {
|
|
79
|
+
local sid="$1"
|
|
80
|
+
local test_file="$2"
|
|
81
|
+
run_hook_for_test "$sid" "$test_file" >/dev/null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# --- Behaviour 1: silent on GREEN-unchanged ---
|
|
85
|
+
|
|
86
|
+
@test "tdd-post-write: GREEN -> GREEN unchanged emits nothing" {
|
|
87
|
+
write_pkg_json 0 "all good"
|
|
88
|
+
# First invocation: writing the test file enters tracked state, runs
|
|
89
|
+
# the test, classifies as GREEN. Output expected (we discard it).
|
|
90
|
+
register_test_file "$SID" "$TEST_FILE"
|
|
91
|
+
|
|
92
|
+
# Second invocation: edit impl. Test still passes. OLD == NEW == GREEN.
|
|
93
|
+
run run_hook_for_impl "$SID" "$IMPL_FILE"
|
|
94
|
+
[ "$status" -eq 0 ]
|
|
95
|
+
[ -z "$output" ]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@test "tdd-post-write: GREEN -> GREEN unchanged exits 0 with no STATE UPDATE" {
|
|
99
|
+
write_pkg_json 0 "all good"
|
|
100
|
+
register_test_file "$SID" "$TEST_FILE"
|
|
101
|
+
|
|
102
|
+
run run_hook_for_impl "$SID" "$IMPL_FILE"
|
|
103
|
+
[[ "$output" != *"TDD STATE UPDATE"* ]]
|
|
104
|
+
[[ "$output" != *"State unchanged"* ]]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# --- Behaviour 2: dedupe RED test output across consecutive identical RED edits ---
|
|
108
|
+
|
|
109
|
+
@test "tdd-post-write: consecutive RED with identical output suppresses second emit's test-output block" {
|
|
110
|
+
write_pkg_json 1 "FAIL_assertion_mismatch_line_7"
|
|
111
|
+
register_test_file "$SID" "$TEST_FILE"
|
|
112
|
+
|
|
113
|
+
# Second invocation: same impl, same failing test, same output. Still
|
|
114
|
+
# RED. Hash matches the previous emission, so the output block is
|
|
115
|
+
# suppressed (replaced with the "unchanged" marker).
|
|
116
|
+
run run_hook_for_impl "$SID" "$IMPL_FILE"
|
|
117
|
+
[[ "$output" == *"TDD STATE UPDATE"* ]]
|
|
118
|
+
[[ "$output" == *"Test output unchanged from previous emission"* ]]
|
|
119
|
+
[[ "$output" != *"Test output (last 50 lines):"* ]]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@test "tdd-post-write: RED with changed output emits the full body" {
|
|
123
|
+
write_pkg_json 1 "FAIL_assertion_mismatch_line_7"
|
|
124
|
+
register_test_file "$SID" "$TEST_FILE"
|
|
125
|
+
run_hook_for_impl "$SID" "$IMPL_FILE" >/dev/null
|
|
126
|
+
|
|
127
|
+
# Different output: re-run with a different stdout. Hash mismatches,
|
|
128
|
+
# full output block re-emits.
|
|
129
|
+
write_pkg_json 1 "FAIL_assertion_mismatch_line_9"
|
|
130
|
+
run run_hook_for_impl "$SID" "$IMPL_FILE"
|
|
131
|
+
[[ "$output" == *"Test output (last 50 lines):"* ]]
|
|
132
|
+
[[ "$output" == *"FAIL_assertion_mismatch_line_9"* ]]
|
|
133
|
+
[[ "$output" != *"Test output unchanged"* ]]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# --- Behaviour 3: GREEN ACTION line dropped ---
|
|
137
|
+
|
|
138
|
+
@test "tdd-post-write: RED -> GREEN transition emits STATE UPDATE but no GREEN ACTION line" {
|
|
139
|
+
# Drive RED first.
|
|
140
|
+
write_pkg_json 1 "FAIL"
|
|
141
|
+
register_test_file "$SID" "$TEST_FILE"
|
|
142
|
+
# Now transition to GREEN.
|
|
143
|
+
write_pkg_json 0 "ok"
|
|
144
|
+
run run_hook_for_impl "$SID" "$IMPL_FILE"
|
|
145
|
+
[[ "$output" == *"TDD STATE UPDATE"* ]]
|
|
146
|
+
[[ "$output" == *"State transition: RED -> GREEN"* ]]
|
|
147
|
+
[[ "$output" != *"You may refactor"* ]]
|
|
148
|
+
[[ "$output" != *"ACTION: Tests are passing"* ]]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# --- Negative: RED keeps its actionable ACTION line ---
|
|
152
|
+
|
|
153
|
+
@test "tdd-post-write: IDLE -> RED still emits ACTION: Tests are failing" {
|
|
154
|
+
write_pkg_json 1 "FAIL"
|
|
155
|
+
run run_hook_for_test "$SID" "$TEST_FILE"
|
|
156
|
+
[[ "$output" == *"ACTION: Tests are failing"* ]]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# --- Negative: empty session_id falls through cleanly ---
|
|
160
|
+
|
|
161
|
+
@test "tdd-post-write: empty session_id exits 0 without crashing" {
|
|
162
|
+
write_pkg_json 0 "ok"
|
|
163
|
+
run bash -c "cd '$WORKDIR' && echo '{\"session_id\":\"\",\"tool_input\":{\"file_path\":\"$IMPL_FILE\"}}' | bash '$HOOK'"
|
|
164
|
+
[ "$status" -eq 0 ]
|
|
165
|
+
}
|