@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,307 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# @problem P099 — docs/briefing/<topic>.md (Tier 3 of ADR-040) accumulates
|
|
4
|
+
# without rotation. ADR-040 names a 2-5 KB / topic ceiling but the budget
|
|
5
|
+
# was informational only. P099 promotes Tier 3 to advisory enforcement: a
|
|
6
|
+
# read-only diagnostic script surfaces topic files over the configured
|
|
7
|
+
# ceiling so run-retro Step 3 can route them through the rotation
|
|
8
|
+
# AskUserQuestion (interactive) or defer to the retro summary (AFK).
|
|
9
|
+
#
|
|
10
|
+
# Contract: `check-briefing-budgets.sh [<briefing-dir>]` is a diagnose-only
|
|
11
|
+
# advisory script. It walks `<briefing-dir>/<topic>.md` files (default
|
|
12
|
+
# `docs/briefing`), measures byte size per file, and reports each topic
|
|
13
|
+
# file whose size is at or above the threshold (default 5120 bytes,
|
|
14
|
+
# overridable via BRIEFING_TIER3_MAX_BYTES env var).
|
|
15
|
+
#
|
|
16
|
+
# Exit codes:
|
|
17
|
+
# 0 = always (advisory only — overflow is signal, not failure)
|
|
18
|
+
# 2 = parse error (briefing dir missing or unreadable)
|
|
19
|
+
#
|
|
20
|
+
# Output format on overflow (one line per file, terse machine-readable
|
|
21
|
+
# per ADR-038 progressive-disclosure budget):
|
|
22
|
+
# OVER <basename> bytes=<N> threshold=<N>
|
|
23
|
+
#
|
|
24
|
+
# Output is empty (no lines) when no topic files exceed the threshold.
|
|
25
|
+
# README.md is excluded from the scan — it is Tier 2, not Tier 3.
|
|
26
|
+
#
|
|
27
|
+
# The script is read-only — it does NOT mutate any briefing file.
|
|
28
|
+
# Rotation candidates are surfaced to the user via run-retro Step 3
|
|
29
|
+
# (AskUserQuestion interactive path or retro-summary AFK fallback).
|
|
30
|
+
#
|
|
31
|
+
# @jtbd JTBD-006 (Progress the Backlog While I'm Away — AFK-safe advisory)
|
|
32
|
+
# @jtbd JTBD-001 (Enforce Governance Without Slowing Down — read-only,
|
|
33
|
+
# no interactive friction on the happy path; cost amortised across
|
|
34
|
+
# every subsequent topic-file load)
|
|
35
|
+
# @jtbd JTBD-101 (Extend the Suite with Clear Patterns — reusable
|
|
36
|
+
# advisory-script + bats + ADR-amendment shape for accumulator surfaces)
|
|
37
|
+
#
|
|
38
|
+
# Cross-reference:
|
|
39
|
+
# P099: docs/problems/099-briefing-md-grows-unbounded-via-run-retro-appends-violating-progressive-disclosure.open.md
|
|
40
|
+
# ADR-040 — Session-start briefing surface (Tier 3 budget; this script
|
|
41
|
+
# promotes Tier 3 from informational to advisory enforcement)
|
|
42
|
+
# ADR-038 — Progressive disclosure (per-row byte budget on diff output)
|
|
43
|
+
# ADR-013 Rule 1 / Rule 6 — interactive AskUserQuestion path / AFK fallback
|
|
44
|
+
# ADR-005 — Plugin testing strategy (script-level bats governance)
|
|
45
|
+
|
|
46
|
+
setup() {
|
|
47
|
+
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
48
|
+
SCRIPT="$SCRIPTS_DIR/check-briefing-budgets.sh"
|
|
49
|
+
FIXTURE_DIR="$(mktemp -d)"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
teardown() {
|
|
53
|
+
rm -rf "$FIXTURE_DIR"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Helper: write a markdown file with N bytes of body content. The header
|
|
57
|
+
# adds a small constant overhead; body fills to roughly the requested
|
|
58
|
+
# size so the total file size approximates the requested target.
|
|
59
|
+
write_briefing_entry() {
|
|
60
|
+
local path="$1"
|
|
61
|
+
local target_bytes="$2"
|
|
62
|
+
: > "$path"
|
|
63
|
+
printf '# Topic\n\n' >> "$path"
|
|
64
|
+
local header_size=$(wc -c < "$path" | tr -d ' ')
|
|
65
|
+
local body_target=$(( target_bytes - header_size ))
|
|
66
|
+
if [ "$body_target" -gt 0 ]; then
|
|
67
|
+
# Repeated 80-byte line keeps things readable
|
|
68
|
+
local line="- entry text padded out to a known length for byte-budget testing. "
|
|
69
|
+
local line_size=${#line}
|
|
70
|
+
line+=$'\n'
|
|
71
|
+
local line_count=$(( (body_target + line_size) / (line_size + 1) ))
|
|
72
|
+
local i=0
|
|
73
|
+
while [ "$i" -lt "$line_count" ]; do
|
|
74
|
+
printf '%s' "$line" >> "$path"
|
|
75
|
+
i=$(( i + 1 ))
|
|
76
|
+
done
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ── Existence + executable ──────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
@test "check-briefing-budgets: script exists" {
|
|
83
|
+
[ -f "$SCRIPT" ]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@test "check-briefing-budgets: script is executable" {
|
|
87
|
+
[ -x "$SCRIPT" ]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# ── Default-threshold behaviour (5120 bytes per ADR-040 Tier 3 ceiling) ─────
|
|
91
|
+
|
|
92
|
+
@test "check-briefing-budgets: empty briefing dir produces no output and exits 0" {
|
|
93
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
94
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
95
|
+
[ "$status" -eq 0 ]
|
|
96
|
+
[ -z "$output" ]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@test "check-briefing-budgets: all files under threshold produces no output" {
|
|
100
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
101
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/small-topic.md" 1024
|
|
102
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/medium-topic.md" 3000
|
|
103
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
104
|
+
[ "$status" -eq 0 ]
|
|
105
|
+
[ -z "$output" ]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@test "check-briefing-budgets: file over threshold emits OVER line with bytes + threshold" {
|
|
109
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
110
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/bloated-topic.md" 10000
|
|
111
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
112
|
+
[ "$status" -eq 0 ]
|
|
113
|
+
echo "$output" | grep -E "^OVER bloated-topic.md bytes=[0-9]+ threshold=5120$"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@test "check-briefing-budgets: file exactly at threshold emits OVER (>= boundary)" {
|
|
117
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
118
|
+
# Create a file exactly 5120 bytes
|
|
119
|
+
printf '%.0s.' $(seq 1 5120) > "$FIXTURE_DIR/briefing/edge.md"
|
|
120
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
121
|
+
[ "$status" -eq 0 ]
|
|
122
|
+
echo "$output" | grep -E "^OVER edge.md bytes=5120 threshold=5120$"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@test "check-briefing-budgets: only over-threshold files appear in output" {
|
|
126
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
127
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/under.md" 2000
|
|
128
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/over-one.md" 8000
|
|
129
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/over-two.md" 12000
|
|
130
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
131
|
+
[ "$status" -eq 0 ]
|
|
132
|
+
# Both over-files present
|
|
133
|
+
echo "$output" | grep -E "^OVER over-one.md bytes=[0-9]+ threshold=5120$"
|
|
134
|
+
echo "$output" | grep -E "^OVER over-two.md bytes=[0-9]+ threshold=5120$"
|
|
135
|
+
# Under-file absent
|
|
136
|
+
! echo "$output" | grep -q "under.md"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@test "check-briefing-budgets: README.md is excluded from the scan (Tier 2 not Tier 3)" {
|
|
140
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
141
|
+
# Bloated README that would otherwise trip the threshold
|
|
142
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/README.md" 20000
|
|
143
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/topic.md" 3000
|
|
144
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
145
|
+
[ "$status" -eq 0 ]
|
|
146
|
+
[ -z "$output" ]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# ── Configurable threshold via env var ──────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
@test "check-briefing-budgets: BRIEFING_TIER3_MAX_BYTES env var overrides default" {
|
|
152
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
153
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/topic.md" 3000
|
|
154
|
+
# Default 5120: under threshold, no output. With env var set to 2000:
|
|
155
|
+
# over threshold, expect OVER line.
|
|
156
|
+
BRIEFING_TIER3_MAX_BYTES=2000 run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
157
|
+
[ "$status" -eq 0 ]
|
|
158
|
+
echo "$output" | grep -E "^OVER topic.md bytes=[0-9]+ threshold=2000$"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@test "check-briefing-budgets: env var threshold of 0 emits every file (sanity)" {
|
|
162
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
163
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/a.md" 100
|
|
164
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/b.md" 200
|
|
165
|
+
BRIEFING_TIER3_MAX_BYTES=0 run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
166
|
+
[ "$status" -eq 0 ]
|
|
167
|
+
echo "$output" | grep -q "OVER a.md "
|
|
168
|
+
echo "$output" | grep -q "OVER b.md "
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# ── Argument and error handling ─────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
@test "check-briefing-budgets: defaults to docs/briefing when no arg provided" {
|
|
174
|
+
cd "$FIXTURE_DIR"
|
|
175
|
+
mkdir -p docs/briefing
|
|
176
|
+
write_briefing_entry "docs/briefing/big.md" 10000
|
|
177
|
+
run "$SCRIPT"
|
|
178
|
+
[ "$status" -eq 0 ]
|
|
179
|
+
echo "$output" | grep -E "^OVER big.md bytes=[0-9]+ threshold=5120$"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@test "check-briefing-budgets: missing briefing dir exits 2 with parse error on stderr" {
|
|
183
|
+
run "$SCRIPT" "$FIXTURE_DIR/does-not-exist"
|
|
184
|
+
[ "$status" -eq 2 ]
|
|
185
|
+
echo "$output" | grep -iE "not found|missing|does not exist"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@test "check-briefing-budgets: ignores non-markdown files in the briefing dir" {
|
|
189
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
190
|
+
# Bloated non-markdown (e.g. a stray log) should not trip the scan
|
|
191
|
+
printf '%.0s.' $(seq 1 20000) > "$FIXTURE_DIR/briefing/stray.txt"
|
|
192
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/topic.md" 1000
|
|
193
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
194
|
+
[ "$status" -eq 0 ]
|
|
195
|
+
[ -z "$output" ]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# ── Output stability ────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
@test "check-briefing-budgets: output is sorted by filename for stable diffs" {
|
|
201
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
202
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/zebra.md" 8000
|
|
203
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/alpha.md" 8000
|
|
204
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/middle.md" 8000
|
|
205
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
206
|
+
[ "$status" -eq 0 ]
|
|
207
|
+
# First line is alpha, last is zebra
|
|
208
|
+
first_line=$(echo "$output" | head -1)
|
|
209
|
+
last_line=$(echo "$output" | tail -1)
|
|
210
|
+
echo "$first_line" | grep -q "alpha.md"
|
|
211
|
+
echo "$last_line" | grep -q "zebra.md"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# ── MUST_SPLIT signal (P145) ────────────────────────────────────────────────
|
|
215
|
+
#
|
|
216
|
+
# Files at ratio >= 2.0x the threshold also emit a MUST_SPLIT line that
|
|
217
|
+
# names the same basename and a `reason=` code. This promotes ADR-040's
|
|
218
|
+
# Tier 3 reassessment trigger ("≥ 3 topic files exceed 2× the configured
|
|
219
|
+
# ceiling for ≥ 2 consecutive retro cycles") from policy-revisit-time to
|
|
220
|
+
# per-cycle enforcement on the same threshold. The MUST_SPLIT line is
|
|
221
|
+
# the "no defer" signal: run-retro Step 3 Tier 3 silent-agent rotation
|
|
222
|
+
# is forced to pick split-by-subtopic / split-by-date for these files.
|
|
223
|
+
#
|
|
224
|
+
# Output format on >= 2x ratio (one line per file, in addition to the
|
|
225
|
+
# existing OVER line):
|
|
226
|
+
# MUST_SPLIT <basename> reason=<code>
|
|
227
|
+
#
|
|
228
|
+
# @problem P145
|
|
229
|
+
|
|
230
|
+
@test "check-briefing-budgets: file at exactly 2x threshold emits MUST_SPLIT" {
|
|
231
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
232
|
+
# Exactly 10240 bytes = 2.0x of 5120 default threshold
|
|
233
|
+
printf '%.0s.' $(seq 1 10240) > "$FIXTURE_DIR/briefing/exactly-2x.md"
|
|
234
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
235
|
+
[ "$status" -eq 0 ]
|
|
236
|
+
echo "$output" | grep -E "^MUST_SPLIT exactly-2x.md reason=ratio-exceeds-2x$"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@test "check-briefing-budgets: file just under 2x does NOT emit MUST_SPLIT" {
|
|
240
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
241
|
+
# 10239 bytes = 1.9998x of 5120 — under the 2.0x trigger
|
|
242
|
+
printf '%.0s.' $(seq 1 10239) > "$FIXTURE_DIR/briefing/just-under-2x.md"
|
|
243
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
244
|
+
[ "$status" -eq 0 ]
|
|
245
|
+
# OVER line still fires (>= threshold)
|
|
246
|
+
echo "$output" | grep -E "^OVER just-under-2x.md bytes=10239 threshold=5120"
|
|
247
|
+
# MUST_SPLIT does NOT fire (< 2x ratio)
|
|
248
|
+
! echo "$output" | grep -q "MUST_SPLIT"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@test "check-briefing-budgets: file well over 2x emits both OVER and MUST_SPLIT" {
|
|
252
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
253
|
+
# 4.0x ceiling — mirrors today's afk-subprocess.md state
|
|
254
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/very-bloated.md" 20480
|
|
255
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
256
|
+
[ "$status" -eq 0 ]
|
|
257
|
+
echo "$output" | grep -E "^OVER very-bloated.md bytes=[0-9]+ threshold=5120"
|
|
258
|
+
echo "$output" | grep -E "^MUST_SPLIT very-bloated.md reason=ratio-exceeds-2x$"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@test "check-briefing-budgets: file under threshold emits NEITHER OVER nor MUST_SPLIT" {
|
|
262
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
263
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/small.md" 4096
|
|
264
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
265
|
+
[ "$status" -eq 0 ]
|
|
266
|
+
! echo "$output" | grep -q "small.md"
|
|
267
|
+
! echo "$output" | grep -q "MUST_SPLIT"
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@test "check-briefing-budgets: BRIEFING_TIER3_MAX_BYTES env override flows through to MUST_SPLIT" {
|
|
271
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
272
|
+
# 4096 bytes is 2.0x of 2048 — should trigger MUST_SPLIT under the override
|
|
273
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/topic.md" 4096
|
|
274
|
+
BRIEFING_TIER3_MAX_BYTES=2048 run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
275
|
+
[ "$status" -eq 0 ]
|
|
276
|
+
echo "$output" | grep -E "^OVER topic.md bytes=[0-9]+ threshold=2048"
|
|
277
|
+
echo "$output" | grep -E "^MUST_SPLIT topic.md reason=ratio-exceeds-2x$"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@test "check-briefing-budgets: mixed OVER + MUST_SPLIT output is sorted deterministically" {
|
|
281
|
+
mkdir -p "$FIXTURE_DIR/briefing"
|
|
282
|
+
# Three OVER files; two of them also MUST_SPLIT. Output must be
|
|
283
|
+
# deterministic so retro summary diffs stay stable across cycles.
|
|
284
|
+
# Contract: OVER block (sorted by basename) followed by MUST_SPLIT
|
|
285
|
+
# block (sorted by basename).
|
|
286
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/zebra-over-only.md" 6000
|
|
287
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/alpha-must-split.md" 12000
|
|
288
|
+
write_briefing_entry "$FIXTURE_DIR/briefing/middle-must-split.md" 15000
|
|
289
|
+
run "$SCRIPT" "$FIXTURE_DIR/briefing"
|
|
290
|
+
[ "$status" -eq 0 ]
|
|
291
|
+
# Three OVER lines — alpha first, middle next, zebra last
|
|
292
|
+
over_lines=$(echo "$output" | grep "^OVER ")
|
|
293
|
+
[ "$(echo "$over_lines" | wc -l | tr -d ' ')" = "3" ]
|
|
294
|
+
echo "$over_lines" | sed -n '1p' | grep -q "alpha-must-split.md"
|
|
295
|
+
echo "$over_lines" | sed -n '2p' | grep -q "middle-must-split.md"
|
|
296
|
+
echo "$over_lines" | sed -n '3p' | grep -q "zebra-over-only.md"
|
|
297
|
+
# Two MUST_SPLIT lines — alpha first, middle second; zebra-over-only NOT present
|
|
298
|
+
must_lines=$(echo "$output" | grep "^MUST_SPLIT ")
|
|
299
|
+
[ "$(echo "$must_lines" | wc -l | tr -d ' ')" = "2" ]
|
|
300
|
+
echo "$must_lines" | sed -n '1p' | grep -q "alpha-must-split.md"
|
|
301
|
+
echo "$must_lines" | sed -n '2p' | grep -q "middle-must-split.md"
|
|
302
|
+
! echo "$must_lines" | grep -q "zebra-over-only.md"
|
|
303
|
+
# All OVER lines come before any MUST_SPLIT line (block ordering)
|
|
304
|
+
first_must_line_no=$(echo "$output" | grep -n "^MUST_SPLIT " | head -1 | cut -d: -f1)
|
|
305
|
+
last_over_line_no=$(echo "$output" | grep -n "^OVER " | tail -1 | cut -d: -f1)
|
|
306
|
+
[ "$last_over_line_no" -lt "$first_must_line_no" ]
|
|
307
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# Behavioural fixture for the internal-ID-leak advisory detector — per
|
|
4
|
+
# ADR-055 (Plugin-published artefacts use namespace-prefixed permalinks).
|
|
5
|
+
#
|
|
6
|
+
# Contract: `check-internal-id-leaks.sh [<root-dir>]` is a diagnose-only
|
|
7
|
+
# advisory script. It walks shipped-artefact surfaces under
|
|
8
|
+
# `<root-dir>/packages/*/` (default `<root-dir>` is `.`) and reports
|
|
9
|
+
# bare internal-ID tokens that lack the `WR-` namespace prefix.
|
|
10
|
+
#
|
|
11
|
+
# Surfaces scanned:
|
|
12
|
+
# - packages/<plugin>/skills/<skill>/SKILL.md
|
|
13
|
+
# - packages/<plugin>/agents/*.md
|
|
14
|
+
# - packages/<plugin>/hooks/*.sh
|
|
15
|
+
# - packages/<plugin>/CHANGELOG.md
|
|
16
|
+
#
|
|
17
|
+
# Bare tokens flagged (regex):
|
|
18
|
+
# ADR-NNN (3+ digits)
|
|
19
|
+
# JTBD-NNN (3+ digits)
|
|
20
|
+
# P-NNN or PNNN (3 digits — problem ticket form)
|
|
21
|
+
#
|
|
22
|
+
# Tokens that DO NOT trigger:
|
|
23
|
+
# WR-ADR-NNN, WR-JTBD-NNN, WR-P-NNN, WR-PNNN (namespace-prefixed)
|
|
24
|
+
# docstring annotation lines beginning with `# @adr` / `# @jtbd` /
|
|
25
|
+
# `# @problem` (maintainer-facing, never expanded into adopter context)
|
|
26
|
+
# REFERENCE.md sibling files (lazy-loaded, maintainer-facing per ADR-054)
|
|
27
|
+
#
|
|
28
|
+
# Exit codes:
|
|
29
|
+
# 0 = always (advisory only — drift is signal, not failure)
|
|
30
|
+
# 2 = parse error (root dir missing or unreadable)
|
|
31
|
+
#
|
|
32
|
+
# Output format on drift (terse machine-readable per ADR-038):
|
|
33
|
+
# OVER <plugin>/<file> bare_count=<N>
|
|
34
|
+
#
|
|
35
|
+
# Followed by a final summary line:
|
|
36
|
+
# TOTAL packages=<N> with_leaks=<M> drift_instances=<K>
|
|
37
|
+
#
|
|
38
|
+
# Output is empty (no lines) when no shipped artefact carries bare tokens.
|
|
39
|
+
# Silent-on-pass per ADR-045 hook injection budget discipline.
|
|
40
|
+
#
|
|
41
|
+
# Read-only — does NOT mutate any artefact. Per ADR-052, this fixture is
|
|
42
|
+
# BEHAVIOURAL — it asserts script output on temp-fixture trees, NOT
|
|
43
|
+
# script source content. No greps of check-internal-id-leaks.sh source.
|
|
44
|
+
#
|
|
45
|
+
# @problem P137 (Plugin-published artefacts reference internal IDs that
|
|
46
|
+
# adopter projects can't resolve)
|
|
47
|
+
# @adr ADR-055 (Plugin-published artefacts use namespace-prefixed
|
|
48
|
+
# permalinks — strategy + advisory detector)
|
|
49
|
+
# @adr ADR-038 (Progressive disclosure — terse machine-readable signal)
|
|
50
|
+
# @adr ADR-045 (Hook injection budget — silent-on-pass)
|
|
51
|
+
# @adr ADR-052 (Behavioural-tests-default — fixture pattern)
|
|
52
|
+
# @adr ADR-005 (Plugin testing strategy)
|
|
53
|
+
# @jtbd JTBD-302 (Trust That the README Describes the Plugin I Just
|
|
54
|
+
# Installed — semantic correctness axis of adopter-facing content)
|
|
55
|
+
|
|
56
|
+
setup() {
|
|
57
|
+
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
58
|
+
SCRIPT="$SCRIPTS_DIR/check-internal-id-leaks.sh"
|
|
59
|
+
FIXTURE_ROOT="$(mktemp -d)"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
teardown() {
|
|
63
|
+
rm -rf "$FIXTURE_ROOT"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Helper: write a SKILL.md with given body content under fixture plugin.
|
|
67
|
+
# Uses %b to interpret \n in the body argument as a real newline so test
|
|
68
|
+
# fixtures can compose multi-line content inline.
|
|
69
|
+
write_skill() {
|
|
70
|
+
local plugin="$1"
|
|
71
|
+
local skill="$2"
|
|
72
|
+
local body="$3"
|
|
73
|
+
local skill_dir="$FIXTURE_ROOT/packages/$plugin/skills/$skill"
|
|
74
|
+
mkdir -p "$skill_dir"
|
|
75
|
+
printf '%b\n' "$body" > "$skill_dir/SKILL.md"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Helper: write an agent file with given body.
|
|
79
|
+
write_agent() {
|
|
80
|
+
local plugin="$1"
|
|
81
|
+
local agent="$2"
|
|
82
|
+
local body="$3"
|
|
83
|
+
local agent_dir="$FIXTURE_ROOT/packages/$plugin/agents"
|
|
84
|
+
mkdir -p "$agent_dir"
|
|
85
|
+
printf '%b\n' "$body" > "$agent_dir/$agent.md"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Helper: write a hook script with given body.
|
|
89
|
+
write_hook() {
|
|
90
|
+
local plugin="$1"
|
|
91
|
+
local hook="$2"
|
|
92
|
+
local body="$3"
|
|
93
|
+
local hook_dir="$FIXTURE_ROOT/packages/$plugin/hooks"
|
|
94
|
+
mkdir -p "$hook_dir"
|
|
95
|
+
printf '%b\n' "$body" > "$hook_dir/$hook.sh"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Helper: write a CHANGELOG.md with given body.
|
|
99
|
+
write_changelog() {
|
|
100
|
+
local plugin="$1"
|
|
101
|
+
local body="$2"
|
|
102
|
+
local plugin_dir="$FIXTURE_ROOT/packages/$plugin"
|
|
103
|
+
mkdir -p "$plugin_dir"
|
|
104
|
+
printf '%b\n' "$body" > "$plugin_dir/CHANGELOG.md"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# ── Existence + executable ──────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
@test "check-internal-id-leaks: script exists" {
|
|
110
|
+
[ -f "$SCRIPT" ]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@test "check-internal-id-leaks: script is executable" {
|
|
114
|
+
[ -x "$SCRIPT" ]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# ── Empty / clean trees ─────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
@test "check-internal-id-leaks: empty tree produces no output and exits 0" {
|
|
120
|
+
mkdir -p "$FIXTURE_ROOT/packages"
|
|
121
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
122
|
+
[ "$status" -eq 0 ]
|
|
123
|
+
[ -z "$output" ]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@test "check-internal-id-leaks: clean SKILL.md (no IDs at all) produces no output" {
|
|
127
|
+
write_skill "alpha" "clean" "# Skill\n\nThis skill has no references at all."
|
|
128
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
129
|
+
[ "$status" -eq 0 ]
|
|
130
|
+
[ -z "$output" ]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@test "check-internal-id-leaks: SKILL.md with WR-prefixed refs only produces no output" {
|
|
134
|
+
write_skill "alpha" "wr-only" "# Skill\n\nPer WR-ADR-014 and WR-JTBD-101 and WR-P137."
|
|
135
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
136
|
+
[ "$status" -eq 0 ]
|
|
137
|
+
[ -z "$output" ]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# ── Bare-ID detection across surfaces ───────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
@test "check-internal-id-leaks: bare ADR-NNN in SKILL.md is flagged" {
|
|
143
|
+
write_skill "alpha" "leaky" "# Skill\n\nPer ADR-014 the workflow is..."
|
|
144
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
145
|
+
[ "$status" -eq 0 ]
|
|
146
|
+
echo "$output" | grep -E "^OVER alpha/skills/leaky/SKILL.md bare_count=[0-9]+"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@test "check-internal-id-leaks: bare JTBD-NNN in SKILL.md is flagged" {
|
|
150
|
+
write_skill "alpha" "leaky" "# Skill\n\nServes JTBD-101 and JTBD-302."
|
|
151
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
152
|
+
[ "$status" -eq 0 ]
|
|
153
|
+
echo "$output" | grep -E "^OVER alpha/skills/leaky/SKILL.md bare_count=[0-9]+"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@test "check-internal-id-leaks: bare P-NNN in SKILL.md is flagged" {
|
|
157
|
+
write_skill "alpha" "leaky" "# Skill\n\nCloses P137 and P078."
|
|
158
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
159
|
+
[ "$status" -eq 0 ]
|
|
160
|
+
echo "$output" | grep -E "^OVER alpha/skills/leaky/SKILL.md bare_count=[0-9]+"
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@test "check-internal-id-leaks: bare IDs in agent file are flagged" {
|
|
164
|
+
write_agent "beta" "specialist" "# Agent\n\nPer ADR-013 Rule 6 escalate."
|
|
165
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
166
|
+
[ "$status" -eq 0 ]
|
|
167
|
+
echo "$output" | grep -E "^OVER beta/agents/specialist.md bare_count=[0-9]+"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@test "check-internal-id-leaks: bare IDs in hook script body are flagged" {
|
|
171
|
+
write_hook "gamma" "guard" "#!/usr/bin/env bash\n# This deny message points at ADR-014 and P137 from prose."
|
|
172
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
173
|
+
[ "$status" -eq 0 ]
|
|
174
|
+
echo "$output" | grep -E "^OVER gamma/hooks/guard.sh bare_count=[0-9]+"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@test "check-internal-id-leaks: bare IDs in CHANGELOG.md are flagged" {
|
|
178
|
+
write_changelog "delta" "## 0.1.0\n\n- Per ADR-014 + P081 the new behaviour ships."
|
|
179
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
180
|
+
[ "$status" -eq 0 ]
|
|
181
|
+
echo "$output" | grep -E "^OVER delta/CHANGELOG.md bare_count=[0-9]+"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# ── Exclusions ──────────────────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
@test "check-internal-id-leaks: docstring @adr annotations on hook lines are NOT flagged" {
|
|
187
|
+
write_hook "alpha" "annotated" "#!/usr/bin/env bash\n# @adr ADR-014 (commit discipline)\n# @jtbd JTBD-101\n# @problem P137"
|
|
188
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
189
|
+
[ "$status" -eq 0 ]
|
|
190
|
+
[ -z "$output" ]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@test "check-internal-id-leaks: REFERENCE.md sibling files are NOT scanned" {
|
|
194
|
+
local skill_dir="$FIXTURE_ROOT/packages/alpha/skills/with-ref"
|
|
195
|
+
mkdir -p "$skill_dir"
|
|
196
|
+
printf '# Skill\nClean body.\n' > "$skill_dir/SKILL.md"
|
|
197
|
+
printf '# Reference\nADR-014 is fine here.\n' > "$skill_dir/REFERENCE.md"
|
|
198
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
199
|
+
[ "$status" -eq 0 ]
|
|
200
|
+
[ -z "$output" ]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# ── Counting + summary ──────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
@test "check-internal-id-leaks: bare_count matches number of bare tokens in file" {
|
|
206
|
+
write_skill "alpha" "trio" "# Skill\n\nADR-014 and JTBD-101 and P137 — three bare tokens."
|
|
207
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
208
|
+
[ "$status" -eq 0 ]
|
|
209
|
+
echo "$output" | grep -E "^OVER alpha/skills/trio/SKILL.md bare_count=3$"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@test "check-internal-id-leaks: TOTAL summary line emitted on any drift" {
|
|
213
|
+
write_skill "alpha" "leaky" "Per ADR-014 the workflow is."
|
|
214
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
215
|
+
[ "$status" -eq 0 ]
|
|
216
|
+
echo "$output" | grep -E "^TOTAL packages=1 with_leaks=1 drift_instances=1$"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@test "check-internal-id-leaks: TOTAL summary aggregates across files + packages" {
|
|
220
|
+
write_skill "alpha" "leaky" "Per ADR-014."
|
|
221
|
+
write_skill "alpha" "another" "JTBD-101."
|
|
222
|
+
write_agent "beta" "specialist" "P137."
|
|
223
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
224
|
+
[ "$status" -eq 0 ]
|
|
225
|
+
echo "$output" | grep -E "^TOTAL packages=2 with_leaks=3 drift_instances=3$"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@test "check-internal-id-leaks: no TOTAL line emitted when output is empty" {
|
|
229
|
+
write_skill "alpha" "clean" "No bare references here."
|
|
230
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
231
|
+
[ "$status" -eq 0 ]
|
|
232
|
+
[ -z "$output" ]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# ── Determinism ─────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
@test "check-internal-id-leaks: OVER lines are sorted by package/file identifier" {
|
|
238
|
+
write_skill "zeta" "z-skill" "ADR-014."
|
|
239
|
+
write_skill "alpha" "a-skill" "ADR-014."
|
|
240
|
+
write_skill "mu" "m-skill" "ADR-014."
|
|
241
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
242
|
+
[ "$status" -eq 0 ]
|
|
243
|
+
local first
|
|
244
|
+
first=$(echo "$output" | grep '^OVER' | head -1)
|
|
245
|
+
echo "$first" | grep -q "alpha/skills/a-skill/SKILL.md"
|
|
246
|
+
local last
|
|
247
|
+
last=$(echo "$output" | grep '^OVER' | tail -1)
|
|
248
|
+
echo "$last" | grep -q "zeta/skills/z-skill/SKILL.md"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# ── Pre-check error path ────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
@test "check-internal-id-leaks: missing root dir exits 2" {
|
|
254
|
+
run "$SCRIPT" "/nonexistent/path/$$"
|
|
255
|
+
[ "$status" -eq 2 ]
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# ── Boundary tokens that must NOT match ─────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
@test "check-internal-id-leaks: WR-prefixed token mid-sentence does not flag" {
|
|
261
|
+
write_skill "alpha" "wr-mid" "Per WR-ADR-014 — clean."
|
|
262
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
263
|
+
[ "$status" -eq 0 ]
|
|
264
|
+
[ -z "$output" ]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@test "check-internal-id-leaks: WR-prefixed JTBD does not flag" {
|
|
268
|
+
write_skill "alpha" "wr-jtbd" "Serves WR-JTBD-302."
|
|
269
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
270
|
+
[ "$status" -eq 0 ]
|
|
271
|
+
[ -z "$output" ]
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@test "check-internal-id-leaks: standalone P3 (less than 3 digits) does not flag" {
|
|
275
|
+
write_skill "alpha" "edge" "Phase P3 of the rollout."
|
|
276
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
277
|
+
[ "$status" -eq 0 ]
|
|
278
|
+
[ -z "$output" ]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@test "check-internal-id-leaks: lowercase adr-014 does not flag (case-sensitive)" {
|
|
282
|
+
write_skill "alpha" "lower" "in adr-014 prose context."
|
|
283
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
284
|
+
[ "$status" -eq 0 ]
|
|
285
|
+
[ -z "$output" ]
|
|
286
|
+
}
|