@windyroad/retrospective 0.16.0 → 0.17.0-preview.277
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/bin/wr-retrospective-check-tarball-shipped-shims +2 -0
- package/package.json +2 -1
- package/scripts/check-ask-hygiene.sh +163 -0
- package/scripts/check-briefing-budgets.sh +121 -0
- package/scripts/check-internal-id-leaks.sh +169 -0
- package/scripts/check-readme-jtbd-currency.sh +207 -0
- package/scripts/check-skill-md-budgets.sh +130 -0
- package/scripts/check-tarball-shipped-shims.sh +227 -0
- package/scripts/check-tickets-deferred-cause.sh +220 -0
- package/scripts/list-plugin-attribution.sh +139 -0
- package/scripts/measure-context-budget.sh +270 -0
- package/scripts/test/check-ask-hygiene.bats +248 -0
- package/scripts/test/check-briefing-budgets.bats +307 -0
- package/scripts/test/check-internal-id-leaks.bats +286 -0
- package/scripts/test/check-readme-jtbd-currency.bats +217 -0
- package/scripts/test/check-skill-md-budgets.bats +336 -0
- package/scripts/test/check-tarball-shipped-shims.bats +284 -0
- package/scripts/test/check-tickets-deferred-cause.bats +330 -0
- package/scripts/test/list-plugin-attribution.bats +149 -0
- package/scripts/test/measure-context-budget.bats +236 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# Behavioural fixture for the tarball-shipped-shims advisory detector — per
|
|
4
|
+
# WR-P154 (P137 namespace-prefix detector must run against npm pack output
|
|
5
|
+
# not source tree) and the WR-ADR-049 / WR-ADR-052 / WR-ADR-055 cluster.
|
|
6
|
+
#
|
|
7
|
+
# Contract: `check-tarball-shipped-shims.sh [<root-dir>]` is a diagnose-only
|
|
8
|
+
# advisory script. It walks workspaces under `<root-dir>/packages/*/`, runs
|
|
9
|
+
# `npm pack --dry-run --json` per workspace to enumerate the file set that
|
|
10
|
+
# ships, then asserts that every WR-ADR-049-grammar bin shim
|
|
11
|
+
# (`bin/wr-<plugin>-<name>`) in the tarball has its `exec`'d
|
|
12
|
+
# `scripts/<name>.sh` target also in the tarball.
|
|
13
|
+
#
|
|
14
|
+
# Surface: shipped publish-manifest integrity for bin/scripts/ shim
|
|
15
|
+
# resolvability — different correctness axis than check-internal-id-leaks.sh
|
|
16
|
+
# (which measures source-tree namespace-prefix drift).
|
|
17
|
+
#
|
|
18
|
+
# WR-ADR-049-grammar shims that this script considers:
|
|
19
|
+
# bin/wr-<plugin>-<name>
|
|
20
|
+
# Non-grammar bins (e.g. `bin/install.mjs`, `bin/check-deps.sh`,
|
|
21
|
+
# `bin/windyroad-<plugin>` legacy installers) are skipped — they don't
|
|
22
|
+
# follow the script-resolution-via-bin-on-PATH ADR-049 contract this
|
|
23
|
+
# script enforces.
|
|
24
|
+
#
|
|
25
|
+
# Exit codes:
|
|
26
|
+
# 0 = always (advisory only — drift is signal, not failure)
|
|
27
|
+
# 2 = parse error (root dir missing/unreadable, or npm unavailable)
|
|
28
|
+
#
|
|
29
|
+
# Output format on drift (terse machine-readable per WR-ADR-038):
|
|
30
|
+
# TARBALL_DRIFT package=<name> shim=<bin/wr-...> target=<scripts/...> tarball-status=missing
|
|
31
|
+
#
|
|
32
|
+
# Followed by a final summary line (always emitted when any drift exists):
|
|
33
|
+
# TOTAL packages=<N> with_drift=<M> missing_targets=<K>
|
|
34
|
+
#
|
|
35
|
+
# Output is empty (no lines) when no shipped artefact carries broken
|
|
36
|
+
# shims — silent-on-pass per WR-ADR-045 hook injection budget.
|
|
37
|
+
#
|
|
38
|
+
# Output ordering (deterministic for stable retro-summary diffs):
|
|
39
|
+
# TARBALL_DRIFT lines sorted by `<package>/<shim>` identifier.
|
|
40
|
+
# TOTAL line last.
|
|
41
|
+
#
|
|
42
|
+
# Read-only — does NOT mutate any artefact. Per WR-ADR-052, this fixture
|
|
43
|
+
# is BEHAVIOURAL — it asserts script output on temp-fixture trees, NOT
|
|
44
|
+
# script source content. No greps of check-tarball-shipped-shims.sh source.
|
|
45
|
+
#
|
|
46
|
+
# @problem P154 (P137 namespace-prefix detector must run against
|
|
47
|
+
# npm pack output not source tree)
|
|
48
|
+
# @problem P140 (Step 6.5 fix-and-continue — same prevention surface)
|
|
49
|
+
# @adr ADR-049 (Plugin script resolution via bin/ on PATH)
|
|
50
|
+
# @adr ADR-038 (Progressive disclosure — terse machine-readable signal)
|
|
51
|
+
# @adr ADR-045 (Hook injection budget — silent-on-pass)
|
|
52
|
+
# @adr ADR-052 (Behavioural-tests-default — fixture pattern)
|
|
53
|
+
# @adr ADR-055 (Plugin-published namespace-prefixed permalinks —
|
|
54
|
+
# sibling adopter-context decision)
|
|
55
|
+
# @jtbd JTBD-302 (Trust That the README Describes the Plugin I Just
|
|
56
|
+
# Installed — executable correctness axis of adopter-facing content)
|
|
57
|
+
# @jtbd JTBD-101 (Extend the Suite with New Plugins — secondary
|
|
58
|
+
# plugin-developer feedback surface)
|
|
59
|
+
|
|
60
|
+
setup() {
|
|
61
|
+
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
62
|
+
SCRIPT="$SCRIPTS_DIR/check-tarball-shipped-shims.sh"
|
|
63
|
+
FIXTURE_ROOT="$(mktemp -d)"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
teardown() {
|
|
67
|
+
rm -rf "$FIXTURE_ROOT"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Helper: write a workspace package.json with a controlled `files` array.
|
|
71
|
+
# Args: <plugin> <files-json-array>
|
|
72
|
+
# Example: write_package_json alpha '["bin/", "scripts/"]'
|
|
73
|
+
write_package_json() {
|
|
74
|
+
local plugin="$1"
|
|
75
|
+
local files="$2"
|
|
76
|
+
local plugin_dir="$FIXTURE_ROOT/packages/$plugin"
|
|
77
|
+
mkdir -p "$plugin_dir"
|
|
78
|
+
cat > "$plugin_dir/package.json" <<EOF
|
|
79
|
+
{
|
|
80
|
+
"name": "@test/$plugin",
|
|
81
|
+
"version": "0.1.0",
|
|
82
|
+
"files": $files
|
|
83
|
+
}
|
|
84
|
+
EOF
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Helper: write an WR-ADR-049-grammar bin shim that exec-s a scripts/ target.
|
|
88
|
+
# Args: <plugin> <name> (shim becomes bin/wr-<plugin>-<name>, exec-s ../scripts/<name>.sh)
|
|
89
|
+
write_adr049_shim() {
|
|
90
|
+
local plugin="$1"
|
|
91
|
+
local name="$2"
|
|
92
|
+
local bin_dir="$FIXTURE_ROOT/packages/$plugin/bin"
|
|
93
|
+
mkdir -p "$bin_dir"
|
|
94
|
+
cat > "$bin_dir/wr-$plugin-$name" <<EOF
|
|
95
|
+
#!/usr/bin/env bash
|
|
96
|
+
exec "\$(dirname "\$0")/../scripts/$name.sh" "\$@"
|
|
97
|
+
EOF
|
|
98
|
+
chmod +x "$bin_dir/wr-$plugin-$name"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Helper: write a script body matching the shim's exec target.
|
|
102
|
+
write_script() {
|
|
103
|
+
local plugin="$1"
|
|
104
|
+
local name="$2"
|
|
105
|
+
local script_dir="$FIXTURE_ROOT/packages/$plugin/scripts"
|
|
106
|
+
mkdir -p "$script_dir"
|
|
107
|
+
cat > "$script_dir/$name.sh" <<'EOF'
|
|
108
|
+
#!/usr/bin/env bash
|
|
109
|
+
echo "fixture script body"
|
|
110
|
+
EOF
|
|
111
|
+
chmod +x "$script_dir/$name.sh"
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Helper: write a non-WR-ADR-049-grammar bin entry (e.g. legacy installer).
|
|
115
|
+
# These should be silently ignored by the script.
|
|
116
|
+
write_non_grammar_bin() {
|
|
117
|
+
local plugin="$1"
|
|
118
|
+
local name="$2"
|
|
119
|
+
local bin_dir="$FIXTURE_ROOT/packages/$plugin/bin"
|
|
120
|
+
mkdir -p "$bin_dir"
|
|
121
|
+
cat > "$bin_dir/$name" <<'EOF'
|
|
122
|
+
#!/usr/bin/env bash
|
|
123
|
+
echo "legacy installer"
|
|
124
|
+
EOF
|
|
125
|
+
chmod +x "$bin_dir/$name"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# ── Existence + executable ──────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
@test "check-tarball-shipped-shims: script exists" {
|
|
131
|
+
[ -f "$SCRIPT" ]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@test "check-tarball-shipped-shims: script is executable" {
|
|
135
|
+
[ -x "$SCRIPT" ]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# ── Empty / clean trees ─────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
@test "check-tarball-shipped-shims: empty tree (no packages dir) produces no output and exits 0" {
|
|
141
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
142
|
+
[ "$status" -eq 0 ]
|
|
143
|
+
[ -z "$output" ]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@test "check-tarball-shipped-shims: packages dir with no workspaces produces no output and exits 0" {
|
|
147
|
+
mkdir -p "$FIXTURE_ROOT/packages"
|
|
148
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
149
|
+
[ "$status" -eq 0 ]
|
|
150
|
+
[ -z "$output" ]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@test "check-tarball-shipped-shims: workspace with no WR-ADR-049 shims produces no output" {
|
|
154
|
+
write_package_json "alpha" '["bin/"]'
|
|
155
|
+
write_non_grammar_bin "alpha" "windyroad-alpha"
|
|
156
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
157
|
+
[ "$status" -eq 0 ]
|
|
158
|
+
[ -z "$output" ]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@test "check-tarball-shipped-shims: clean workspace (shim + target both ship) produces no output" {
|
|
162
|
+
write_package_json "alpha" '["bin/", "scripts/"]'
|
|
163
|
+
write_adr049_shim "alpha" "good"
|
|
164
|
+
write_script "alpha" "good"
|
|
165
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
166
|
+
[ "$status" -eq 0 ]
|
|
167
|
+
[ -z "$output" ]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# ── Drift detection — the iter-20 P033 sibling-finding shape ────────────────
|
|
171
|
+
|
|
172
|
+
@test "check-tarball-shipped-shims: shim present in tarball, target NOT in tarball — flagged" {
|
|
173
|
+
# The canonical broken shape: scripts/ exists on disk, shim references it,
|
|
174
|
+
# but package.json#files omits scripts/ so the target isn't shipped.
|
|
175
|
+
write_package_json "alpha" '["bin/"]'
|
|
176
|
+
write_adr049_shim "alpha" "broken"
|
|
177
|
+
write_script "alpha" "broken"
|
|
178
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
179
|
+
[ "$status" -eq 0 ]
|
|
180
|
+
echo "$output" | grep -E "^TARBALL_DRIFT package=@test/alpha shim=bin/wr-alpha-broken target=scripts/broken.sh tarball-status=missing$"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@test "check-tarball-shipped-shims: TOTAL summary emitted on any drift" {
|
|
184
|
+
write_package_json "alpha" '["bin/"]'
|
|
185
|
+
write_adr049_shim "alpha" "broken"
|
|
186
|
+
write_script "alpha" "broken"
|
|
187
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
188
|
+
[ "$status" -eq 0 ]
|
|
189
|
+
echo "$output" | grep -E "^TOTAL packages=1 with_drift=1 missing_targets=1$"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@test "check-tarball-shipped-shims: multiple shims in one workspace — each missing target flagged" {
|
|
193
|
+
write_package_json "alpha" '["bin/"]'
|
|
194
|
+
write_adr049_shim "alpha" "alpha-one"
|
|
195
|
+
write_script "alpha" "alpha-one"
|
|
196
|
+
write_adr049_shim "alpha" "alpha-two"
|
|
197
|
+
write_script "alpha" "alpha-two"
|
|
198
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
199
|
+
[ "$status" -eq 0 ]
|
|
200
|
+
echo "$output" | grep -E "^TARBALL_DRIFT package=@test/alpha shim=bin/wr-alpha-alpha-one target=scripts/alpha-one.sh tarball-status=missing$"
|
|
201
|
+
echo "$output" | grep -E "^TARBALL_DRIFT package=@test/alpha shim=bin/wr-alpha-alpha-two target=scripts/alpha-two.sh tarball-status=missing$"
|
|
202
|
+
echo "$output" | grep -E "^TOTAL packages=1 with_drift=1 missing_targets=2$"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@test "check-tarball-shipped-shims: drift across multiple packages aggregates correctly" {
|
|
206
|
+
write_package_json "alpha" '["bin/"]'
|
|
207
|
+
write_adr049_shim "alpha" "a-broken"
|
|
208
|
+
write_script "alpha" "a-broken"
|
|
209
|
+
write_package_json "beta" '["bin/"]'
|
|
210
|
+
write_adr049_shim "beta" "b-broken"
|
|
211
|
+
write_script "beta" "b-broken"
|
|
212
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
213
|
+
[ "$status" -eq 0 ]
|
|
214
|
+
echo "$output" | grep -E "^TOTAL packages=2 with_drift=2 missing_targets=2$"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# ── Mixed clean + drift workspaces ──────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
@test "check-tarball-shipped-shims: clean workspace + broken workspace — only broken flagged, TOTAL counts only with_drift" {
|
|
220
|
+
write_package_json "alpha" '["bin/", "scripts/"]'
|
|
221
|
+
write_adr049_shim "alpha" "good"
|
|
222
|
+
write_script "alpha" "good"
|
|
223
|
+
write_package_json "beta" '["bin/"]'
|
|
224
|
+
write_adr049_shim "beta" "broken"
|
|
225
|
+
write_script "beta" "broken"
|
|
226
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
227
|
+
[ "$status" -eq 0 ]
|
|
228
|
+
! echo "$output" | grep -q "alpha"
|
|
229
|
+
echo "$output" | grep -E "^TARBALL_DRIFT package=@test/beta shim=bin/wr-beta-broken target=scripts/broken.sh tarball-status=missing$"
|
|
230
|
+
echo "$output" | grep -E "^TOTAL packages=1 with_drift=1 missing_targets=1$"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ── Determinism ─────────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
@test "check-tarball-shipped-shims: TARBALL_DRIFT lines sorted deterministically by package/shim" {
|
|
236
|
+
write_package_json "zeta" '["bin/"]'
|
|
237
|
+
write_adr049_shim "zeta" "z-shim"
|
|
238
|
+
write_script "zeta" "z-shim"
|
|
239
|
+
write_package_json "alpha" '["bin/"]'
|
|
240
|
+
write_adr049_shim "alpha" "a-shim"
|
|
241
|
+
write_script "alpha" "a-shim"
|
|
242
|
+
write_package_json "mu" '["bin/"]'
|
|
243
|
+
write_adr049_shim "mu" "m-shim"
|
|
244
|
+
write_script "mu" "m-shim"
|
|
245
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
246
|
+
[ "$status" -eq 0 ]
|
|
247
|
+
local first
|
|
248
|
+
first=$(echo "$output" | grep '^TARBALL_DRIFT' | head -1)
|
|
249
|
+
echo "$first" | grep -q "package=@test/alpha"
|
|
250
|
+
local last
|
|
251
|
+
last=$(echo "$output" | grep '^TARBALL_DRIFT' | tail -1)
|
|
252
|
+
echo "$last" | grep -q "package=@test/zeta"
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# ── Mixed grammar — non-WR-ADR-049 bins ignored ─────────────────────────────
|
|
256
|
+
|
|
257
|
+
@test "check-tarball-shipped-shims: legacy bin (non-WR-ADR-049 grammar) alongside grammar shim — only grammar shim checked" {
|
|
258
|
+
write_package_json "alpha" '["bin/"]'
|
|
259
|
+
write_non_grammar_bin "alpha" "windyroad-alpha"
|
|
260
|
+
write_adr049_shim "alpha" "broken"
|
|
261
|
+
write_script "alpha" "broken"
|
|
262
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
263
|
+
[ "$status" -eq 0 ]
|
|
264
|
+
! echo "$output" | grep -q "windyroad-alpha"
|
|
265
|
+
echo "$output" | grep -E "^TARBALL_DRIFT package=@test/alpha shim=bin/wr-alpha-broken target=scripts/broken.sh tarball-status=missing$"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# ── Pre-check error path ────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
@test "check-tarball-shipped-shims: missing root dir exits 2" {
|
|
271
|
+
run "$SCRIPT" "/nonexistent/path/$$"
|
|
272
|
+
[ "$status" -eq 2 ]
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
# ── Silent-on-pass invariant ────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
@test "check-tarball-shipped-shims: no TOTAL line emitted when output is empty" {
|
|
278
|
+
write_package_json "alpha" '["bin/", "scripts/"]'
|
|
279
|
+
write_adr049_shim "alpha" "good"
|
|
280
|
+
write_script "alpha" "good"
|
|
281
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
282
|
+
[ "$status" -eq 0 ]
|
|
283
|
+
[ -z "$output" ]
|
|
284
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
#
|
|
3
|
+
# packages/retrospective/scripts/test/check-tickets-deferred-cause.bats
|
|
4
|
+
#
|
|
5
|
+
# Behavioural tests for `check-tickets-deferred-cause.sh` — the
|
|
6
|
+
# Tickets Deferred cause-allowlist advisory script (P148). Mirrors
|
|
7
|
+
# the fixture-based test pattern of `check-ask-hygiene.bats`.
|
|
8
|
+
#
|
|
9
|
+
# Tests are behavioural per ADR-005 / ADR-037 / P081 — they exercise
|
|
10
|
+
# the script end-to-end against fixture retro summary directories
|
|
11
|
+
# and assert on stdout / stderr / exit shape. No structural greps of
|
|
12
|
+
# the script source itself.
|
|
13
|
+
#
|
|
14
|
+
# @problem P148 (Agent defers ticket creation — broadens Stage 1 fallback gate)
|
|
15
|
+
# @problem P081 (Structural-content tests are wasteful — behavioural preferred)
|
|
16
|
+
# @adr ADR-044 (Decision-Delegation Contract)
|
|
17
|
+
# @adr ADR-040 (Tier 3 advisory-not-fail-closed)
|
|
18
|
+
# @adr ADR-013 Rule 6 (non-interactive fail-safe)
|
|
19
|
+
# @adr ADR-005 / ADR-037 (Plugin testing strategy — behavioural tests)
|
|
20
|
+
# @jtbd JTBD-001 / JTBD-006 / JTBD-201
|
|
21
|
+
|
|
22
|
+
SCRIPT="${BATS_TEST_DIRNAME}/../check-tickets-deferred-cause.sh"
|
|
23
|
+
|
|
24
|
+
setup() {
|
|
25
|
+
TEST_DIR="$(mktemp -d)"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
teardown() {
|
|
29
|
+
rm -rf "$TEST_DIR"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ── Pre-checks ──────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
@test "script file exists and is executable" {
|
|
35
|
+
[ -f "$SCRIPT" ]
|
|
36
|
+
[ -x "$SCRIPT" ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@test "missing retros dir exits 2 with error message on stderr" {
|
|
40
|
+
run bash "$SCRIPT" "$TEST_DIR/does-not-exist"
|
|
41
|
+
[ "$status" -eq 2 ]
|
|
42
|
+
[[ "$output" == *"retros dir not found"* ]]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@test "empty retros dir exits 0 with empty stdout" {
|
|
46
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
47
|
+
[ "$status" -eq 0 ]
|
|
48
|
+
[ -z "$output" ]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@test "default retros-dir argument is docs/retros (when omitted)" {
|
|
52
|
+
cd "$TEST_DIR"
|
|
53
|
+
run bash "$SCRIPT"
|
|
54
|
+
[ "$status" -eq 2 ]
|
|
55
|
+
[[ "$output" == *"docs/retros"* ]]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ── No-defer steady state ───────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
@test "retro file with no Tickets Deferred section emits no output" {
|
|
61
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
62
|
+
## Session Retrospective
|
|
63
|
+
|
|
64
|
+
### Briefing Changes
|
|
65
|
+
- Added: foo
|
|
66
|
+
|
|
67
|
+
### Problems Created/Updated
|
|
68
|
+
- P148: opened
|
|
69
|
+
|
|
70
|
+
### No Action Needed
|
|
71
|
+
- learning captured
|
|
72
|
+
RETRO
|
|
73
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
74
|
+
[ "$status" -eq 0 ]
|
|
75
|
+
[ -z "$output" ]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ── Good fixture (skill_unavailable cause) ──────────────────────────────────
|
|
79
|
+
|
|
80
|
+
@test "good fixture: skill_unavailable cause → 0 violations" {
|
|
81
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
82
|
+
## Session Retrospective
|
|
83
|
+
|
|
84
|
+
### Tickets Deferred
|
|
85
|
+
|
|
86
|
+
| Observation | Cause | Citation |
|
|
87
|
+
|-------------|-------|----------|
|
|
88
|
+
| Polling regex deadlock observation | `skill_unavailable` | Step 2b detection |
|
|
89
|
+
| SIGTERM flush caveat | `skill_unavailable` | Step 4a verification |
|
|
90
|
+
RETRO
|
|
91
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
92
|
+
[ "$status" -eq 0 ]
|
|
93
|
+
[[ "$output" == *"deferred=2"* ]]
|
|
94
|
+
[[ "$output" == *"with_valid_cause=2"* ]]
|
|
95
|
+
[[ "$output" == *"violations=0"* ]]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# ── Bad fixture (session_pressure cause — the P148 anti-pattern) ────────────
|
|
99
|
+
|
|
100
|
+
@test "bad fixture: session_pressure cause → all entries are violations" {
|
|
101
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
102
|
+
## Session Retrospective
|
|
103
|
+
|
|
104
|
+
### Tickets Deferred
|
|
105
|
+
|
|
106
|
+
| Observation | Cause | Citation |
|
|
107
|
+
|-------------|-------|----------|
|
|
108
|
+
| Polling regex deadlock | `session_pressure` | Step 2b |
|
|
109
|
+
| SIGTERM flush caveat | `context_heavyweight` | Step 4a |
|
|
110
|
+
RETRO
|
|
111
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
112
|
+
[ "$status" -eq 0 ]
|
|
113
|
+
[[ "$output" == *"deferred=2"* ]]
|
|
114
|
+
[[ "$output" == *"with_valid_cause=0"* ]]
|
|
115
|
+
[[ "$output" == *"violations=2"* ]]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# ── Legacy fixture (no Cause column — pre-P148 retro shape) ─────────────────
|
|
119
|
+
|
|
120
|
+
@test "legacy fixture: no Cause column → all entries are violations" {
|
|
121
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
122
|
+
## Session Retrospective
|
|
123
|
+
|
|
124
|
+
### Tickets Deferred
|
|
125
|
+
|
|
126
|
+
| Observation | Citation |
|
|
127
|
+
|-------------|----------|
|
|
128
|
+
| Polling regex deadlock | Step 2b |
|
|
129
|
+
| SIGTERM flush caveat | Step 4a |
|
|
130
|
+
RETRO
|
|
131
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
132
|
+
[ "$status" -eq 0 ]
|
|
133
|
+
[[ "$output" == *"deferred=2"* ]]
|
|
134
|
+
[[ "$output" == *"violations=2"* ]]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@test "legacy fixture exit code is 0 — advisory contract holds for AFK safety" {
|
|
138
|
+
# JTBD-006 line 32 (extended AFK safety): legacy retros lacking the Cause
|
|
139
|
+
# column would break AFK loops if exit code went non-zero. Fail-closed
|
|
140
|
+
# escalation belongs at a future hook tier per P135 R6 trajectory, not
|
|
141
|
+
# at the script.
|
|
142
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
143
|
+
### Tickets Deferred
|
|
144
|
+
|
|
145
|
+
| Observation | Citation |
|
|
146
|
+
|-------------|----------|
|
|
147
|
+
| obs1 | step1 |
|
|
148
|
+
RETRO
|
|
149
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
150
|
+
[ "$status" -eq 0 ]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# ── Mixed fixture ───────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
@test "mixed fixture: one valid + one invalid → violations=1, with_valid_cause=1" {
|
|
156
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
157
|
+
### Tickets Deferred
|
|
158
|
+
|
|
159
|
+
| Observation | Cause | Citation |
|
|
160
|
+
|-------------|-------|----------|
|
|
161
|
+
| Valid observation | `skill_unavailable` | Step 2b |
|
|
162
|
+
| Invalid observation | `session_pressure` | Step 4a |
|
|
163
|
+
RETRO
|
|
164
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
165
|
+
[ "$status" -eq 0 ]
|
|
166
|
+
[[ "$output" == *"deferred=2"* ]]
|
|
167
|
+
[[ "$output" == *"with_valid_cause=1"* ]]
|
|
168
|
+
[[ "$output" == *"violations=1"* ]]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# ── Empty Cause cell ────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
@test "empty Cause cell counts as a violation (cause-required invariant)" {
|
|
174
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
175
|
+
### Tickets Deferred
|
|
176
|
+
|
|
177
|
+
| Observation | Cause | Citation |
|
|
178
|
+
|-------------|-------|----------|
|
|
179
|
+
| Observation with empty cause | | Step 2b |
|
|
180
|
+
RETRO
|
|
181
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
182
|
+
[ "$status" -eq 0 ]
|
|
183
|
+
[[ "$output" == *"violations=1"* ]]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# ── Format tolerance ────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
@test "Cause cell tolerates surrounding whitespace and bold markers" {
|
|
189
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
190
|
+
### Tickets Deferred
|
|
191
|
+
|
|
192
|
+
| Observation | Cause | Citation |
|
|
193
|
+
|-------------|-------|----------|
|
|
194
|
+
| obs1 | `skill_unavailable` | Step 2b |
|
|
195
|
+
| obs2 | **skill_unavailable** | Step 4a |
|
|
196
|
+
RETRO
|
|
197
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
198
|
+
[ "$status" -eq 0 ]
|
|
199
|
+
[[ "$output" == *"deferred=2"* ]]
|
|
200
|
+
[[ "$output" == *"with_valid_cause=2"* ]]
|
|
201
|
+
[[ "$output" == *"violations=0"* ]]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@test "placeholder template row is skipped — not counted as a deferred entry" {
|
|
205
|
+
# The retro summary template includes a placeholder example row in the
|
|
206
|
+
# SKILL.md template (with `<one-line observation summary>` literal text).
|
|
207
|
+
# When a retro is rendered with no real deferred entries, the template
|
|
208
|
+
# row may persist; the script must NOT count it as a real deferred
|
|
209
|
+
# observation.
|
|
210
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
211
|
+
### Tickets Deferred
|
|
212
|
+
|
|
213
|
+
| Observation | Cause | Citation |
|
|
214
|
+
|-------------|-------|----------|
|
|
215
|
+
| <one-line observation summary> | `skill_unavailable` | <retro-step-citation> |
|
|
216
|
+
RETRO
|
|
217
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
218
|
+
[ "$status" -eq 0 ]
|
|
219
|
+
[ -z "$output" ]
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# ── Multi-file behaviour ────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
@test "multiple retro files emit per-file lines plus a TOTAL summary line" {
|
|
225
|
+
cat > "$TEST_DIR/2026-04-25-retro.md" <<'RETRO'
|
|
226
|
+
### Tickets Deferred
|
|
227
|
+
|
|
228
|
+
| Observation | Cause | Citation |
|
|
229
|
+
|-------------|-------|----------|
|
|
230
|
+
| good obs | `skill_unavailable` | Step 2b |
|
|
231
|
+
RETRO
|
|
232
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
233
|
+
### Tickets Deferred
|
|
234
|
+
|
|
235
|
+
| Observation | Cause | Citation |
|
|
236
|
+
|-------------|-------|----------|
|
|
237
|
+
| bad obs | `session_pressure` | Step 4a |
|
|
238
|
+
RETRO
|
|
239
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
240
|
+
[ "$status" -eq 0 ]
|
|
241
|
+
[[ "$output" == *"RETRO 2026-04-25"* ]]
|
|
242
|
+
[[ "$output" == *"RETRO 2026-04-29"* ]]
|
|
243
|
+
[[ "$output" == *"TOTAL files=2 deferred=2 with_valid_cause=1 violations=1"* ]]
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@test "files sorted oldest-first by date prefix" {
|
|
247
|
+
for d in 27 25 26; do
|
|
248
|
+
cat > "$TEST_DIR/2026-04-$d-retro.md" <<RETRO
|
|
249
|
+
### Tickets Deferred
|
|
250
|
+
|
|
251
|
+
| Observation | Cause | Citation |
|
|
252
|
+
|-------------|-------|----------|
|
|
253
|
+
| obs$d | \`skill_unavailable\` | Step$d |
|
|
254
|
+
RETRO
|
|
255
|
+
done
|
|
256
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
257
|
+
[ "$status" -eq 0 ]
|
|
258
|
+
line1="${lines[0]}"
|
|
259
|
+
line2="${lines[1]}"
|
|
260
|
+
line3="${lines[2]}"
|
|
261
|
+
[[ "$line1" == *"2026-04-25"* ]]
|
|
262
|
+
[[ "$line2" == *"2026-04-26"* ]]
|
|
263
|
+
[[ "$line3" == *"2026-04-27"* ]]
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# ── File-type filtering ─────────────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
@test "ask-hygiene trail files are skipped (not retro summaries)" {
|
|
269
|
+
cat > "$TEST_DIR/2026-04-29-ask-hygiene.md" <<'TRAIL'
|
|
270
|
+
### Tickets Deferred
|
|
271
|
+
|
|
272
|
+
| Observation | Cause | Citation |
|
|
273
|
+
|-------------|-------|----------|
|
|
274
|
+
| should be ignored | `session_pressure` | irrelevant |
|
|
275
|
+
TRAIL
|
|
276
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
277
|
+
[ "$status" -eq 0 ]
|
|
278
|
+
[ -z "$output" ]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@test "context-analysis files are skipped (not retro summaries)" {
|
|
282
|
+
cat > "$TEST_DIR/2026-04-29-context-analysis.md" <<'CTX'
|
|
283
|
+
### Tickets Deferred
|
|
284
|
+
|
|
285
|
+
| Observation | Cause | Citation |
|
|
286
|
+
|-------------|-------|----------|
|
|
287
|
+
| should be ignored | `session_pressure` | irrelevant |
|
|
288
|
+
CTX
|
|
289
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
290
|
+
[ "$status" -eq 0 ]
|
|
291
|
+
[ -z "$output" ]
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@test "files without a YYYY-MM-DD date prefix are skipped" {
|
|
295
|
+
cat > "$TEST_DIR/README.md" <<'RM'
|
|
296
|
+
### Tickets Deferred
|
|
297
|
+
|
|
298
|
+
| Observation | Cause | Citation |
|
|
299
|
+
|-------------|-------|----------|
|
|
300
|
+
| should be ignored | `session_pressure` | irrelevant |
|
|
301
|
+
RM
|
|
302
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
303
|
+
[ "$status" -eq 0 ]
|
|
304
|
+
[ -z "$output" ]
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# ── Cross-shell portability (P124 / P133 lessons) ───────────────────────────
|
|
308
|
+
|
|
309
|
+
@test "script glob iteration uses portable for-loop existence check" {
|
|
310
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
311
|
+
[ "$status" -eq 0 ]
|
|
312
|
+
[[ "$output" != *"*.md"* ]]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# ── Read-only contract ──────────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
@test "script is read-only — fixture tree unchanged after run" {
|
|
318
|
+
cat > "$TEST_DIR/2026-04-29-retro.md" <<'RETRO'
|
|
319
|
+
### Tickets Deferred
|
|
320
|
+
|
|
321
|
+
| Observation | Cause | Citation |
|
|
322
|
+
|-------------|-------|----------|
|
|
323
|
+
| obs1 | `skill_unavailable` | Step 2b |
|
|
324
|
+
RETRO
|
|
325
|
+
pre_hash=$(find "$TEST_DIR" -type f -exec cksum {} \; 2>/dev/null | sort | cksum | awk '{print $1}')
|
|
326
|
+
run bash "$SCRIPT" "$TEST_DIR"
|
|
327
|
+
[ "$status" -eq 0 ]
|
|
328
|
+
post_hash=$(find "$TEST_DIR" -type f -exec cksum {} \; 2>/dev/null | sort | cksum | awk '{print $1}')
|
|
329
|
+
[ "$pre_hash" = "$post_hash" ]
|
|
330
|
+
}
|