@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,217 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
#
|
|
3
|
+
# packages/retrospective/scripts/test/check-readme-jtbd-currency.bats
|
|
4
|
+
#
|
|
5
|
+
# Behavioural tests for `check-readme-jtbd-currency.sh` — the JTBD-anchored
|
|
6
|
+
# README drift advisory (ADR-051 / P152 Phase 1). Mirrors the fixture-based
|
|
7
|
+
# pattern of sibling detectors (`check-tickets-deferred-cause.bats`,
|
|
8
|
+
# `check-briefing-budgets.bats`, `check-ask-hygiene.bats`).
|
|
9
|
+
#
|
|
10
|
+
# Tests are behavioural per ADR-005 / ADR-037 / P081 — they exercise the
|
|
11
|
+
# script end-to-end against fixture packages/ + docs/jtbd/ trees and assert
|
|
12
|
+
# on stdout / exit code shape. No structural greps of the script source.
|
|
13
|
+
#
|
|
14
|
+
# @problem P152 (No pressure or nudge for documentation currency)
|
|
15
|
+
# @problem P081 (Structural-content tests are wasteful — behavioural preferred)
|
|
16
|
+
# @adr ADR-051 (JTBD-anchored README rule with declarative drift advisory)
|
|
17
|
+
# @adr ADR-013 Rule 6 (non-interactive fail-safe)
|
|
18
|
+
# @adr ADR-005 / ADR-037 (Plugin testing strategy — behavioural tests)
|
|
19
|
+
# @jtbd JTBD-302 (Trust That the README Describes the Plugin I Just Installed)
|
|
20
|
+
# @jtbd JTBD-007 (Keep Plugins Current Across Projects — currency expansion)
|
|
21
|
+
|
|
22
|
+
SCRIPT="${BATS_TEST_DIRNAME}/../check-readme-jtbd-currency.sh"
|
|
23
|
+
|
|
24
|
+
setup() {
|
|
25
|
+
TEST_DIR="$(mktemp -d)"
|
|
26
|
+
PKG_DIR="$TEST_DIR/packages"
|
|
27
|
+
JTBD_DIR="$TEST_DIR/docs/jtbd"
|
|
28
|
+
mkdir -p "$PKG_DIR" "$JTBD_DIR/plugin-user" "$JTBD_DIR/solo-developer"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
teardown() {
|
|
32
|
+
rm -rf "$TEST_DIR"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Helper: write a synthetic plugin layout into PKG_DIR
|
|
36
|
+
make_plugin() {
|
|
37
|
+
local name="$1"
|
|
38
|
+
local readme_content="$2"
|
|
39
|
+
mkdir -p "$PKG_DIR/$name"
|
|
40
|
+
printf '%s\n' "$readme_content" > "$PKG_DIR/$name/README.md"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Helper: write a synthetic JTBD job file into JTBD_DIR
|
|
44
|
+
make_jtbd() {
|
|
45
|
+
local persona="$1"
|
|
46
|
+
local id="$2"
|
|
47
|
+
local slug="$3"
|
|
48
|
+
local status="$4"
|
|
49
|
+
mkdir -p "$JTBD_DIR/$persona"
|
|
50
|
+
cat > "$JTBD_DIR/$persona/$id-$slug.$status.md" <<EOF
|
|
51
|
+
---
|
|
52
|
+
status: $status
|
|
53
|
+
job-id: $slug
|
|
54
|
+
persona: $persona
|
|
55
|
+
date-created: 2026-05-03
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# $id: stub job for fixture
|
|
59
|
+
EOF
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# ── Pre-checks ──────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
@test "script file exists and is executable" {
|
|
65
|
+
[ -f "$SCRIPT" ]
|
|
66
|
+
[ -x "$SCRIPT" ]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@test "missing packages dir exits 2 with error message on stderr" {
|
|
70
|
+
run bash "$SCRIPT" "$TEST_DIR/does-not-exist" "$JTBD_DIR"
|
|
71
|
+
[ "$status" -eq 2 ]
|
|
72
|
+
[[ "$output" == *"packages dir not found"* ]]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@test "missing jtbd dir exits 2 with error message on stderr" {
|
|
76
|
+
run bash "$SCRIPT" "$PKG_DIR" "$TEST_DIR/does-not-exist"
|
|
77
|
+
[ "$status" -eq 2 ]
|
|
78
|
+
[[ "$output" == *"jtbd dir not found"* ]]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@test "empty packages dir exits 0 with empty stdout" {
|
|
82
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
83
|
+
[ "$status" -eq 0 ]
|
|
84
|
+
[ -z "$output" ]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# ── Drift fixture: README has no JTBD citation ──────────────────────────────
|
|
88
|
+
|
|
89
|
+
@test "drift fixture: README with no JTBD-NNN cite emits has_jtbd_anchor=no + missing-jtbd-section drift hint" {
|
|
90
|
+
make_plugin "stub" "# @windyroad/stub
|
|
91
|
+
This README documents nothing about JTBD."
|
|
92
|
+
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
93
|
+
|
|
94
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
95
|
+
[ "$status" -eq 0 ]
|
|
96
|
+
[[ "$output" == *"package=stub"* ]]
|
|
97
|
+
[[ "$output" == *"has_jtbd_anchor=no"* ]]
|
|
98
|
+
[[ "$output" == *"cited_jobs=0"* ]]
|
|
99
|
+
[[ "$output" == *"drift_hints="*"missing-jtbd-section"* ]]
|
|
100
|
+
[[ "$output" == *"TOTAL packages=1 with_jtbd=0 drift_instances=1"* ]]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# ── Clean fixture: README cites a current JTBD ID ───────────────────────────
|
|
104
|
+
|
|
105
|
+
@test "clean fixture: README citing a resolving JTBD-NNN emits has_jtbd_anchor=yes with empty drift_hints" {
|
|
106
|
+
make_plugin "stub" "# @windyroad/stub
|
|
107
|
+
This plugin serves JTBD-302 and JTBD-007."
|
|
108
|
+
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
109
|
+
make_jtbd "solo-developer" "JTBD-007" "keep-plugins-current" "proposed"
|
|
110
|
+
|
|
111
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
112
|
+
[ "$status" -eq 0 ]
|
|
113
|
+
[[ "$output" == *"package=stub"* ]]
|
|
114
|
+
[[ "$output" == *"has_jtbd_anchor=yes"* ]]
|
|
115
|
+
[[ "$output" == *"cited_jobs=2"* ]]
|
|
116
|
+
[[ "$output" == *"known_jobs=2"* ]]
|
|
117
|
+
[[ "$output" == *"drift_hints="$'\n'* || "$output" == *"drift_hints= "* || "$output" == *"drift_hints="*$'\n'* ]]
|
|
118
|
+
[[ "$output" == *"TOTAL packages=1 with_jtbd=1 drift_instances=0"* ]]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# ── Stale-ID fixture: cited JTBD does not resolve ───────────────────────────
|
|
122
|
+
|
|
123
|
+
@test "stale-ID fixture: README citing JTBD-NNN with no resolving file emits stale-jtbd-citation hint" {
|
|
124
|
+
make_plugin "stub" "# @windyroad/stub
|
|
125
|
+
This plugin serves JTBD-999 (which doesn't exist)."
|
|
126
|
+
# No JTBD-999 file; only JTBD-302 exists in the fixture
|
|
127
|
+
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
128
|
+
|
|
129
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
130
|
+
[ "$status" -eq 0 ]
|
|
131
|
+
[[ "$output" == *"package=stub"* ]]
|
|
132
|
+
[[ "$output" == *"has_jtbd_anchor=yes"* ]]
|
|
133
|
+
[[ "$output" == *"cited_jobs=1"* ]]
|
|
134
|
+
[[ "$output" == *"known_jobs=0"* ]]
|
|
135
|
+
[[ "$output" == *"stale-jtbd-citation"* ]]
|
|
136
|
+
[[ "$output" == *"TOTAL packages=1 with_jtbd=1 drift_instances=1"* ]]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ── Deprecated-only fixture: cited JTBD resolves only to deprecated ─────────
|
|
140
|
+
|
|
141
|
+
@test "deprecated-only fixture: cited JTBD resolves to .deprecated.md emits deprecated-jtbd-citation hint" {
|
|
142
|
+
make_plugin "stub" "# @windyroad/stub
|
|
143
|
+
This plugin serves JTBD-888."
|
|
144
|
+
make_jtbd "plugin-user" "JTBD-888" "old-job" "deprecated"
|
|
145
|
+
|
|
146
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
147
|
+
[ "$status" -eq 0 ]
|
|
148
|
+
[[ "$output" == *"package=stub"* ]]
|
|
149
|
+
[[ "$output" == *"has_jtbd_anchor=yes"* ]]
|
|
150
|
+
[[ "$output" == *"cited_jobs=1"* ]]
|
|
151
|
+
[[ "$output" == *"known_jobs=1"* ]]
|
|
152
|
+
[[ "$output" == *"deprecated-jtbd-citation"* ]]
|
|
153
|
+
[[ "$output" == *"drift_instances=1"* ]]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# ── Skill-inventory-drift fixture ───────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
@test "skill-inventory-drift fixture: skills/ directory not named in README emits skill-inventory-drift hint" {
|
|
159
|
+
mkdir -p "$PKG_DIR/stub/skills/orphan-widget"
|
|
160
|
+
printf '%s\n' "# @windyroad/stub
|
|
161
|
+
This plugin serves JTBD-302." > "$PKG_DIR/stub/README.md"
|
|
162
|
+
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
163
|
+
|
|
164
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
165
|
+
[ "$status" -eq 0 ]
|
|
166
|
+
[[ "$output" == *"package=stub"* ]]
|
|
167
|
+
[[ "$output" == *"skill-inventory-drift"* ]]
|
|
168
|
+
[[ "$output" == *"drift_instances=1"* ]]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@test "skill-inventory-drift NOT flagged when skills/ directories are all named in README" {
|
|
172
|
+
mkdir -p "$PKG_DIR/stub/skills/manage-secret"
|
|
173
|
+
printf '%s\n' "# @windyroad/stub
|
|
174
|
+
This plugin serves JTBD-302 via the manage-secret skill." > "$PKG_DIR/stub/README.md"
|
|
175
|
+
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
176
|
+
|
|
177
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
178
|
+
[ "$status" -eq 0 ]
|
|
179
|
+
[[ "$output" == *"package=stub"* ]]
|
|
180
|
+
[[ "$output" != *"skill-inventory-drift"* ]]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# ── Multi-package aggregation ───────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
@test "multi-package aggregation: emits one README line per package + TOTAL summary" {
|
|
186
|
+
make_plugin "alpha" "# alpha
|
|
187
|
+
Serves JTBD-302."
|
|
188
|
+
make_plugin "bravo" "# bravo
|
|
189
|
+
No anchor here."
|
|
190
|
+
make_plugin "charlie" "# charlie
|
|
191
|
+
Serves JTBD-302 and JTBD-007."
|
|
192
|
+
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
193
|
+
make_jtbd "solo-developer" "JTBD-007" "keep-plugins-current" "proposed"
|
|
194
|
+
|
|
195
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
196
|
+
[ "$status" -eq 0 ]
|
|
197
|
+
[[ "$output" == *"package=alpha"* ]]
|
|
198
|
+
[[ "$output" == *"package=bravo"* ]]
|
|
199
|
+
[[ "$output" == *"package=charlie"* ]]
|
|
200
|
+
# bravo has missing-jtbd-section, alpha + charlie are clean
|
|
201
|
+
[[ "$output" == *"TOTAL packages=3 with_jtbd=2 drift_instances=1"* ]]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# ── Package without README is skipped ───────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
@test "package without README.md is silently skipped" {
|
|
207
|
+
mkdir -p "$PKG_DIR/no-readme"
|
|
208
|
+
make_plugin "with-readme" "# with-readme
|
|
209
|
+
Serves JTBD-302."
|
|
210
|
+
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
211
|
+
|
|
212
|
+
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
213
|
+
[ "$status" -eq 0 ]
|
|
214
|
+
[[ "$output" != *"package=no-readme"* ]]
|
|
215
|
+
[[ "$output" == *"package=with-readme"* ]]
|
|
216
|
+
[[ "$output" == *"TOTAL packages=1"* ]]
|
|
217
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# @problem P097 — SKILL.md files mix runtime-necessary steps with maintainer-
|
|
4
|
+
# facing rationale, bloating every skill invocation. ADR-054 codifies the
|
|
5
|
+
# classification taxonomy ([runtime] / [reference] / [deprecated]), the
|
|
6
|
+
# sibling REFERENCE.md lazy-load pattern, and the byte budgets (WARN ≥
|
|
7
|
+
# 8192, MUST_SPLIT ≥ 16384). This bats fixture covers the advisory
|
|
8
|
+
# detector script that surfaces SKILL.md files exceeding the budget.
|
|
9
|
+
#
|
|
10
|
+
# Contract: `check-skill-md-budgets.sh [<root-dir>]` is a diagnose-only
|
|
11
|
+
# advisory script. It walks `<root-dir>/packages/*/skills/*/SKILL.md` and
|
|
12
|
+
# `<root-dir>/.claude/skills/*/SKILL.md` (default `<root-dir>` is `.`),
|
|
13
|
+
# measures byte size per file, and reports each SKILL.md whose size is at
|
|
14
|
+
# or above the WARN threshold (default 8192 bytes, overridable via
|
|
15
|
+
# SKILL_MD_WARN_BYTES env var).
|
|
16
|
+
#
|
|
17
|
+
# Exit codes:
|
|
18
|
+
# 0 = always (advisory only — overflow is signal, not failure)
|
|
19
|
+
# 2 = parse error (root dir missing or unreadable)
|
|
20
|
+
#
|
|
21
|
+
# Output format on overflow (one line per file, terse machine-readable
|
|
22
|
+
# per ADR-038 progressive-disclosure budget):
|
|
23
|
+
# OVER <plugin>/<skill> bytes=<N> threshold=<N>
|
|
24
|
+
#
|
|
25
|
+
# Files at >= MUST_SPLIT (default 16384, overridable via SKILL_MD_MUST_SPLIT_BYTES)
|
|
26
|
+
# also emit a second line:
|
|
27
|
+
# MUST_SPLIT <plugin>/<skill> reason=<code>
|
|
28
|
+
#
|
|
29
|
+
# This mirrors the OVER / MUST_SPLIT pair shape from `check-briefing-budgets.sh`
|
|
30
|
+
# (P099 / P145 / ADR-040) deliberately so adopters learn one concept across
|
|
31
|
+
# two surfaces.
|
|
32
|
+
#
|
|
33
|
+
# Output ordering (deterministic for stable retro-summary diffs):
|
|
34
|
+
# 1. All OVER lines, sorted by `<plugin>/<skill>` identifier.
|
|
35
|
+
# 2. Then all MUST_SPLIT lines, sorted by identifier.
|
|
36
|
+
#
|
|
37
|
+
# Output is empty (no lines) when no SKILL.md exceeds the WARN threshold.
|
|
38
|
+
# REFERENCE.md sibling files (per ADR-054) are excluded from the scan —
|
|
39
|
+
# they are intentionally lazy-loaded and not subject to the runtime budget.
|
|
40
|
+
#
|
|
41
|
+
# Read-only — does NOT mutate any SKILL.md file. Extraction priority is
|
|
42
|
+
# surfaced to the maintainer; rotation is opportunistic per ADR-052
|
|
43
|
+
# migration shape.
|
|
44
|
+
#
|
|
45
|
+
# This fixture is BEHAVIOURAL per ADR-052 — it asserts script output on
|
|
46
|
+
# temp-fixture skill trees, NOT script source content. No greps of
|
|
47
|
+
# check-skill-md-budgets.sh source.
|
|
48
|
+
#
|
|
49
|
+
# @jtbd JTBD-001 (Enforce Governance Without Slowing Down — solo dev;
|
|
50
|
+
# read-only, no interactive friction on the happy path)
|
|
51
|
+
# @jtbd JTBD-006 (Progress the Backlog While I'm Away — AFK-safe advisory)
|
|
52
|
+
# @jtbd JTBD-101 (Extend the Suite with Clear Patterns — reusable
|
|
53
|
+
# advisory-script + bats + ADR-amendment shape for context-budget surfaces)
|
|
54
|
+
#
|
|
55
|
+
# Cross-reference:
|
|
56
|
+
# P097: docs/problems/097-skill-md-runtime-size-mixes-policy-with-runtime-steps.*.md
|
|
57
|
+
# ADR-054 — SKILL.md runtime budget policy (taxonomy + sibling pattern + budget)
|
|
58
|
+
# ADR-040 — Session-start briefing surface (Tier 3 OVER / MUST_SPLIT precedent)
|
|
59
|
+
# ADR-038 — Progressive disclosure (per-row byte budget on diff output)
|
|
60
|
+
# ADR-052 — Behavioural-tests-default (this fixture's pattern)
|
|
61
|
+
# ADR-005 — Plugin testing strategy (script-level bats governance)
|
|
62
|
+
|
|
63
|
+
setup() {
|
|
64
|
+
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
65
|
+
SCRIPT="$SCRIPTS_DIR/check-skill-md-budgets.sh"
|
|
66
|
+
FIXTURE_ROOT="$(mktemp -d)"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
teardown() {
|
|
70
|
+
rm -rf "$FIXTURE_ROOT"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Helper: write a SKILL.md with N bytes of body content under a fixture
|
|
74
|
+
# plugin/skill path. Creates the directory tree as needed.
|
|
75
|
+
write_skill() {
|
|
76
|
+
local plugin="$1"
|
|
77
|
+
local skill="$2"
|
|
78
|
+
local target_bytes="$3"
|
|
79
|
+
local skill_dir="$FIXTURE_ROOT/packages/$plugin/skills/$skill"
|
|
80
|
+
mkdir -p "$skill_dir"
|
|
81
|
+
local path="$skill_dir/SKILL.md"
|
|
82
|
+
: > "$path"
|
|
83
|
+
printf '# Skill\n\n' >> "$path"
|
|
84
|
+
local header_size
|
|
85
|
+
header_size=$(wc -c < "$path" | tr -d ' ')
|
|
86
|
+
local body_target=$(( target_bytes - header_size ))
|
|
87
|
+
if [ "$body_target" -gt 0 ]; then
|
|
88
|
+
local line="- step text padded out to a known length for byte-budget testing. "
|
|
89
|
+
local line_size=${#line}
|
|
90
|
+
line+=$'\n'
|
|
91
|
+
local line_count=$(( (body_target + line_size) / (line_size + 1) ))
|
|
92
|
+
local i=0
|
|
93
|
+
while [ "$i" -lt "$line_count" ]; do
|
|
94
|
+
printf '%s' "$line" >> "$path"
|
|
95
|
+
i=$(( i + 1 ))
|
|
96
|
+
done
|
|
97
|
+
fi
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Helper: write a project-local SKILL.md under .claude/skills/<skill>/SKILL.md
|
|
101
|
+
# (the project-local skill surface, in scope per ADR-054 §Phase 1 deliverable).
|
|
102
|
+
write_local_skill() {
|
|
103
|
+
local skill="$1"
|
|
104
|
+
local target_bytes="$2"
|
|
105
|
+
local skill_dir="$FIXTURE_ROOT/.claude/skills/$skill"
|
|
106
|
+
mkdir -p "$skill_dir"
|
|
107
|
+
local path="$skill_dir/SKILL.md"
|
|
108
|
+
: > "$path"
|
|
109
|
+
printf '# Skill\n\n' >> "$path"
|
|
110
|
+
local header_size
|
|
111
|
+
header_size=$(wc -c < "$path" | tr -d ' ')
|
|
112
|
+
local body_target=$(( target_bytes - header_size ))
|
|
113
|
+
if [ "$body_target" -gt 0 ]; then
|
|
114
|
+
local line="- step text padded out to a known length for byte-budget testing. "
|
|
115
|
+
local line_size=${#line}
|
|
116
|
+
line+=$'\n'
|
|
117
|
+
local line_count=$(( (body_target + line_size) / (line_size + 1) ))
|
|
118
|
+
local i=0
|
|
119
|
+
while [ "$i" -lt "$line_count" ]; do
|
|
120
|
+
printf '%s' "$line" >> "$path"
|
|
121
|
+
i=$(( i + 1 ))
|
|
122
|
+
done
|
|
123
|
+
fi
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ── Existence + executable ──────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
@test "check-skill-md-budgets: script exists" {
|
|
129
|
+
[ -f "$SCRIPT" ]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@test "check-skill-md-budgets: script is executable" {
|
|
133
|
+
[ -x "$SCRIPT" ]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# ── Default-threshold behaviour (WARN 8192, MUST_SPLIT 16384) ───────────────
|
|
137
|
+
|
|
138
|
+
@test "check-skill-md-budgets: empty tree produces no output and exits 0" {
|
|
139
|
+
mkdir -p "$FIXTURE_ROOT/packages"
|
|
140
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
141
|
+
[ "$status" -eq 0 ]
|
|
142
|
+
[ -z "$output" ]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@test "check-skill-md-budgets: all skills under WARN produces no output" {
|
|
146
|
+
write_skill "alpha" "small" 1024
|
|
147
|
+
write_skill "beta" "medium" 4096
|
|
148
|
+
write_skill "gamma" "near-warn" 7000
|
|
149
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
150
|
+
[ "$status" -eq 0 ]
|
|
151
|
+
[ -z "$output" ]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@test "check-skill-md-budgets: skill at WARN band emits OVER line with bytes + threshold" {
|
|
155
|
+
write_skill "alpha" "warn-band" 10000
|
|
156
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
157
|
+
[ "$status" -eq 0 ]
|
|
158
|
+
echo "$output" | grep -E "^OVER alpha/warn-band bytes=[0-9]+ threshold=8192$"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@test "check-skill-md-budgets: skill exactly at WARN threshold emits OVER (>= boundary)" {
|
|
162
|
+
local skill_dir="$FIXTURE_ROOT/packages/alpha/skills/edge"
|
|
163
|
+
mkdir -p "$skill_dir"
|
|
164
|
+
printf '%.0s.' $(seq 1 8192) > "$skill_dir/SKILL.md"
|
|
165
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
166
|
+
[ "$status" -eq 0 ]
|
|
167
|
+
echo "$output" | grep -E "^OVER alpha/edge bytes=8192 threshold=8192$"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@test "check-skill-md-budgets: skill at WARN band but under MUST_SPLIT emits OVER only (no MUST_SPLIT)" {
|
|
171
|
+
write_skill "alpha" "warn-only" 12000
|
|
172
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
173
|
+
[ "$status" -eq 0 ]
|
|
174
|
+
echo "$output" | grep -E "^OVER alpha/warn-only bytes=[0-9]+ threshold=8192"
|
|
175
|
+
! echo "$output" | grep -q "MUST_SPLIT"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@test "check-skill-md-budgets: skill at exactly MUST_SPLIT emits OVER + MUST_SPLIT" {
|
|
179
|
+
local skill_dir="$FIXTURE_ROOT/packages/alpha/skills/exactly-must-split"
|
|
180
|
+
mkdir -p "$skill_dir"
|
|
181
|
+
printf '%.0s.' $(seq 1 16384) > "$skill_dir/SKILL.md"
|
|
182
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
183
|
+
[ "$status" -eq 0 ]
|
|
184
|
+
echo "$output" | grep -E "^OVER alpha/exactly-must-split bytes=16384 threshold=8192"
|
|
185
|
+
echo "$output" | grep -E "^MUST_SPLIT alpha/exactly-must-split reason=ratio-exceeds-must-split$"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@test "check-skill-md-budgets: skill well over MUST_SPLIT emits both lines" {
|
|
189
|
+
write_skill "alpha" "very-bloated" 80000
|
|
190
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
191
|
+
[ "$status" -eq 0 ]
|
|
192
|
+
echo "$output" | grep -E "^OVER alpha/very-bloated bytes=[0-9]+ threshold=8192"
|
|
193
|
+
echo "$output" | grep -E "^MUST_SPLIT alpha/very-bloated reason=ratio-exceeds-must-split$"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@test "check-skill-md-budgets: only over-threshold skills appear in output" {
|
|
197
|
+
write_skill "alpha" "under" 2000
|
|
198
|
+
write_skill "beta" "over-warn" 10000
|
|
199
|
+
write_skill "gamma" "over-must-split" 20000
|
|
200
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
201
|
+
[ "$status" -eq 0 ]
|
|
202
|
+
echo "$output" | grep -E "^OVER beta/over-warn bytes=[0-9]+ threshold=8192"
|
|
203
|
+
echo "$output" | grep -E "^OVER gamma/over-must-split bytes=[0-9]+ threshold=8192"
|
|
204
|
+
! echo "$output" | grep -q "alpha/under"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# ── Sibling REFERENCE.md exclusion (ADR-054) ────────────────────────────────
|
|
208
|
+
|
|
209
|
+
@test "check-skill-md-budgets: REFERENCE.md sibling is excluded from the scan" {
|
|
210
|
+
write_skill "alpha" "with-ref" 1000
|
|
211
|
+
# Write a bloated REFERENCE.md that would otherwise trip MUST_SPLIT
|
|
212
|
+
printf '%.0s.' $(seq 1 50000) > "$FIXTURE_ROOT/packages/alpha/skills/with-ref/REFERENCE.md"
|
|
213
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
214
|
+
[ "$status" -eq 0 ]
|
|
215
|
+
# No output — only SKILL.md is measured, not REFERENCE.md
|
|
216
|
+
[ -z "$output" ]
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# ── Project-local .claude/skills discovery ──────────────────────────────────
|
|
220
|
+
|
|
221
|
+
@test "check-skill-md-budgets: .claude/skills/* SKILL.md files are scanned" {
|
|
222
|
+
write_local_skill "install-updates" 12000
|
|
223
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
224
|
+
[ "$status" -eq 0 ]
|
|
225
|
+
# The project-local prefix is .claude in the identifier
|
|
226
|
+
echo "$output" | grep -E "^OVER .claude/install-updates bytes=[0-9]+ threshold=8192"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@test "check-skill-md-budgets: project-local + plugin-skill outputs both appear" {
|
|
230
|
+
write_skill "alpha" "plugin-skill" 10000
|
|
231
|
+
write_local_skill "local-skill" 12000
|
|
232
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
233
|
+
[ "$status" -eq 0 ]
|
|
234
|
+
echo "$output" | grep -E "^OVER .claude/local-skill"
|
|
235
|
+
echo "$output" | grep -E "^OVER alpha/plugin-skill"
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# ── Configurable thresholds via env vars ────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
@test "check-skill-md-budgets: SKILL_MD_WARN_BYTES env var overrides default" {
|
|
241
|
+
write_skill "alpha" "small-by-default" 4096
|
|
242
|
+
# Default 8192: no output. With env var 2000: over threshold.
|
|
243
|
+
SKILL_MD_WARN_BYTES=2000 run "$SCRIPT" "$FIXTURE_ROOT"
|
|
244
|
+
[ "$status" -eq 0 ]
|
|
245
|
+
echo "$output" | grep -E "^OVER alpha/small-by-default bytes=[0-9]+ threshold=2000"
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@test "check-skill-md-budgets: SKILL_MD_MUST_SPLIT_BYTES env var overrides default" {
|
|
249
|
+
write_skill "alpha" "moderate" 9000
|
|
250
|
+
# Default WARN 8192: emits OVER. Default MUST_SPLIT 16384: no MUST_SPLIT.
|
|
251
|
+
# With MUST_SPLIT override 8500: should emit MUST_SPLIT too.
|
|
252
|
+
SKILL_MD_MUST_SPLIT_BYTES=8500 run "$SCRIPT" "$FIXTURE_ROOT"
|
|
253
|
+
[ "$status" -eq 0 ]
|
|
254
|
+
echo "$output" | grep -E "^OVER alpha/moderate"
|
|
255
|
+
echo "$output" | grep -E "^MUST_SPLIT alpha/moderate reason=ratio-exceeds-must-split$"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@test "check-skill-md-budgets: env var threshold of 0 emits every skill (sanity)" {
|
|
259
|
+
write_skill "alpha" "tiny-one" 100
|
|
260
|
+
write_skill "beta" "tiny-two" 200
|
|
261
|
+
SKILL_MD_WARN_BYTES=0 run "$SCRIPT" "$FIXTURE_ROOT"
|
|
262
|
+
[ "$status" -eq 0 ]
|
|
263
|
+
echo "$output" | grep -q "OVER alpha/tiny-one"
|
|
264
|
+
echo "$output" | grep -q "OVER beta/tiny-two"
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# ── Argument and error handling ─────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
@test "check-skill-md-budgets: defaults to current directory when no arg provided" {
|
|
270
|
+
cd "$FIXTURE_ROOT"
|
|
271
|
+
write_skill "alpha" "default-arg" 12000
|
|
272
|
+
run "$SCRIPT"
|
|
273
|
+
[ "$status" -eq 0 ]
|
|
274
|
+
echo "$output" | grep -E "^OVER alpha/default-arg bytes=[0-9]+ threshold=8192"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
@test "check-skill-md-budgets: missing root dir exits 2 with parse error on stderr" {
|
|
278
|
+
run "$SCRIPT" "$FIXTURE_ROOT/does-not-exist"
|
|
279
|
+
[ "$status" -eq 2 ]
|
|
280
|
+
echo "$output" | grep -iE "not found|missing|does not exist"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@test "check-skill-md-budgets: ignores non-SKILL.md files in skill dirs" {
|
|
284
|
+
local skill_dir="$FIXTURE_ROOT/packages/alpha/skills/with-extras"
|
|
285
|
+
mkdir -p "$skill_dir"
|
|
286
|
+
# Bloated non-SKILL.md files should not trip the scan
|
|
287
|
+
printf '%.0s.' $(seq 1 50000) > "$skill_dir/NOTES.md"
|
|
288
|
+
printf '%.0s.' $(seq 1 50000) > "$skill_dir/scratch.txt"
|
|
289
|
+
# Small SKILL.md under threshold
|
|
290
|
+
printf '%.0s.' $(seq 1 1000) > "$skill_dir/SKILL.md"
|
|
291
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
292
|
+
[ "$status" -eq 0 ]
|
|
293
|
+
[ -z "$output" ]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# ── Output stability ────────────────────────────────────────────────────────
|
|
297
|
+
|
|
298
|
+
@test "check-skill-md-budgets: output is sorted by identifier for stable diffs" {
|
|
299
|
+
write_skill "zeta" "one" 10000
|
|
300
|
+
write_skill "alpha" "one" 10000
|
|
301
|
+
write_skill "middle" "one" 10000
|
|
302
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
303
|
+
[ "$status" -eq 0 ]
|
|
304
|
+
first_line=$(echo "$output" | head -1)
|
|
305
|
+
last_line=$(echo "$output" | tail -1)
|
|
306
|
+
echo "$first_line" | grep -q "alpha/one"
|
|
307
|
+
echo "$last_line" | grep -q "zeta/one"
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@test "check-skill-md-budgets: mixed OVER + MUST_SPLIT output uses block ordering" {
|
|
311
|
+
# Three OVER files; two of them also MUST_SPLIT. Output must be
|
|
312
|
+
# deterministic so retro summary diffs stay stable across cycles.
|
|
313
|
+
# Contract: OVER block (sorted by identifier) followed by MUST_SPLIT
|
|
314
|
+
# block (sorted by identifier).
|
|
315
|
+
write_skill "zeta" "over-only" 10000
|
|
316
|
+
write_skill "alpha" "must-split" 20000
|
|
317
|
+
write_skill "middle" "must-split" 25000
|
|
318
|
+
run "$SCRIPT" "$FIXTURE_ROOT"
|
|
319
|
+
[ "$status" -eq 0 ]
|
|
320
|
+
# Three OVER lines — alpha first, middle next, zeta last
|
|
321
|
+
over_lines=$(echo "$output" | grep "^OVER ")
|
|
322
|
+
[ "$(echo "$over_lines" | wc -l | tr -d ' ')" = "3" ]
|
|
323
|
+
echo "$over_lines" | sed -n '1p' | grep -q "alpha/must-split"
|
|
324
|
+
echo "$over_lines" | sed -n '2p' | grep -q "middle/must-split"
|
|
325
|
+
echo "$over_lines" | sed -n '3p' | grep -q "zeta/over-only"
|
|
326
|
+
# Two MUST_SPLIT lines — alpha first, middle second; zeta-over-only NOT present
|
|
327
|
+
must_lines=$(echo "$output" | grep "^MUST_SPLIT ")
|
|
328
|
+
[ "$(echo "$must_lines" | wc -l | tr -d ' ')" = "2" ]
|
|
329
|
+
echo "$must_lines" | sed -n '1p' | grep -q "alpha/must-split"
|
|
330
|
+
echo "$must_lines" | sed -n '2p' | grep -q "middle/must-split"
|
|
331
|
+
! echo "$must_lines" | grep -q "zeta/over-only"
|
|
332
|
+
# All OVER lines come before any MUST_SPLIT line (block ordering)
|
|
333
|
+
first_must_line_no=$(echo "$output" | grep -n "^MUST_SPLIT " | head -1 | cut -d: -f1)
|
|
334
|
+
last_over_line_no=$(echo "$output" | grep -n "^OVER " | tail -1 | cut -d: -f1)
|
|
335
|
+
[ "$last_over_line_no" -lt "$first_must_line_no" ]
|
|
336
|
+
}
|