@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.
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # @problem P153 — Published skills enumerate repo-relative directories.
4
+ # `/wr-retrospective:analyze-context` SKILL.md L56-67 used a `for plugin_dir
5
+ # in packages/*/hooks; do ... done` glob loop that expanded to nothing in
6
+ # adopter trees, silently emitting zero PLUGIN-HOOKS / PLUGIN-SKILLS rows.
7
+ #
8
+ # Fix: extract the loop into a helper script that probes for the source-
9
+ # tree first (preserves dev-session output) and falls back to a `$PATH`-
10
+ # derived plugin-cache walk so adopter sessions resolve too. Same row
11
+ # shape; backward compatible with deep-layer SKILL.md Step 4 sum-check.
12
+ #
13
+ # Contract:
14
+ # list-plugin-attribution.sh [<project-root>]
15
+ #
16
+ # Default project-root is the current working directory.
17
+ #
18
+ # Output (one line per row, terse machine-readable per ADR-038 ≤150 bytes):
19
+ # PLUGIN-HOOKS <plugin> bytes=<N>
20
+ # PLUGIN-SKILLS <plugin> bytes=<N>
21
+ # PLUGIN-ATTRIBUTION not-measured reason=<reason>
22
+ #
23
+ # Exit code: 0 always (advisory, matches measure-context-budget.sh contract).
24
+ #
25
+ # Resolution order:
26
+ # 1. Source-tree mode — if `<project-root>/packages/*/hooks` glob expands
27
+ # to anything, walk it. Same applies to `packages/*/skills`.
28
+ # 2. Cache-fallback mode — sniff `$PATH` entries that match
29
+ # `*/cache/<owner>/<plugin>/<version>/bin`; walk back to each
30
+ # plugin's root and emit the same row shape.
31
+ # 3. Neither — emit `PLUGIN-ATTRIBUTION not-measured
32
+ # reason=no-plugin-source-resolvable`.
33
+ #
34
+ # @adr ADR-049 (Plugin-bundled scripts via `bin/` on `$PATH` —
35
+ # reassessment-criteria clause 3 explicitly anticipates this surface)
36
+ # @adr ADR-038 (Progressive disclosure — per-row byte budget)
37
+ # @adr ADR-026 (Agent output grounding — explicit not-measured sentinels)
38
+ # @adr ADR-005 (Plugin testing strategy)
39
+ # @adr ADR-037 (Skill testing strategy — bats-contract precedent)
40
+ # @jtbd JTBD-301 (Plugin-user) / JTBD-101 (Plugin-developer)
41
+
42
+ setup() {
43
+ SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
44
+ SCRIPT="$SCRIPTS_DIR/list-plugin-attribution.sh"
45
+ FIXTURE_DIR="$(mktemp -d)"
46
+ # Minimal PATH for bash + coreutils, but no plugin-cache bin dirs.
47
+ # Tests that exercise cache-fallback explicitly prepend a synthetic
48
+ # cache bin to this MIN_PATH.
49
+ MIN_PATH="/usr/bin:/bin"
50
+ }
51
+
52
+ teardown() {
53
+ rm -rf "$FIXTURE_DIR"
54
+ }
55
+
56
+ # ── Existence + executable ──────────────────────────────────────────────────
57
+
58
+ @test "list-plugin-attribution: script file exists at expected path" {
59
+ [ -f "$SCRIPT" ]
60
+ }
61
+
62
+ @test "list-plugin-attribution: script is executable" {
63
+ [ -x "$SCRIPT" ]
64
+ }
65
+
66
+ # ── Exit code ───────────────────────────────────────────────────────────────
67
+
68
+ @test "list-plugin-attribution: empty fixture exits 0 (advisory)" {
69
+ # Run with an empty PATH so the cache-fallback yields nothing too.
70
+ run env PATH="$MIN_PATH" bash "$SCRIPT" "$FIXTURE_DIR"
71
+ [ "$status" -eq 0 ]
72
+ }
73
+
74
+ # ── Source-tree mode ────────────────────────────────────────────────────────
75
+
76
+ @test "list-plugin-attribution: source-tree mode emits PLUGIN-HOOKS row for packages/<plugin>/hooks" {
77
+ mkdir -p "$FIXTURE_DIR/packages/foo/hooks"
78
+ printf '%s' 'echo hi' > "$FIXTURE_DIR/packages/foo/hooks/h.sh"
79
+ run env PATH="$MIN_PATH" bash "$SCRIPT" "$FIXTURE_DIR"
80
+ [ "$status" -eq 0 ]
81
+ echo "$output" | grep -qE '^PLUGIN-HOOKS foo bytes=[0-9]+$'
82
+ }
83
+
84
+ @test "list-plugin-attribution: source-tree mode emits PLUGIN-SKILLS row for packages/<plugin>/skills" {
85
+ mkdir -p "$FIXTURE_DIR/packages/foo/skills/wizard"
86
+ printf '%s' '# Wizard' > "$FIXTURE_DIR/packages/foo/skills/wizard/SKILL.md"
87
+ run env PATH="$MIN_PATH" bash "$SCRIPT" "$FIXTURE_DIR"
88
+ [ "$status" -eq 0 ]
89
+ echo "$output" | grep -qE '^PLUGIN-SKILLS foo bytes=[0-9]+$'
90
+ }
91
+
92
+ @test "list-plugin-attribution: source-tree mode reports nonzero hook bytes when files present" {
93
+ mkdir -p "$FIXTURE_DIR/packages/foo/hooks"
94
+ printf '%s' 'echo hi' > "$FIXTURE_DIR/packages/foo/hooks/h.sh" # 7 bytes
95
+ run env PATH="$MIN_PATH" bash "$SCRIPT" "$FIXTURE_DIR"
96
+ bytes=$(echo "$output" | awk '/^PLUGIN-HOOKS foo bytes=/{ split($3, a, "="); print a[2] }')
97
+ [ "${bytes:-0}" -gt 0 ]
98
+ }
99
+
100
+ @test "list-plugin-attribution: source-tree mode emits one row per plugin (multi-plugin)" {
101
+ mkdir -p "$FIXTURE_DIR/packages/foo/hooks" "$FIXTURE_DIR/packages/bar/hooks"
102
+ printf 'x' > "$FIXTURE_DIR/packages/foo/hooks/h.sh"
103
+ printf 'x' > "$FIXTURE_DIR/packages/bar/hooks/h.sh"
104
+ run env PATH="$MIN_PATH" bash "$SCRIPT" "$FIXTURE_DIR"
105
+ echo "$output" | grep -qE '^PLUGIN-HOOKS foo bytes='
106
+ echo "$output" | grep -qE '^PLUGIN-HOOKS bar bytes='
107
+ }
108
+
109
+ # ── Cache-fallback mode ─────────────────────────────────────────────────────
110
+
111
+ @test "list-plugin-attribution: cache-fallback mode resolves plugins via PATH-derived bin dirs" {
112
+ # Synthesise an adopter-shaped layout: ~/.claude/plugins/cache/<owner>/<plugin>/<version>/
113
+ cache_root="$FIXTURE_DIR/.claude/plugins/cache/wroad/myplug/0.0.1"
114
+ mkdir -p "$cache_root/bin" "$cache_root/hooks" "$cache_root/skills/s1"
115
+ printf '%s' 'exec true' > "$cache_root/bin/wr-myplug-cmd"
116
+ chmod +x "$cache_root/bin/wr-myplug-cmd"
117
+ printf 'hook' > "$cache_root/hooks/h.sh"
118
+ printf 'skill' > "$cache_root/skills/s1/SKILL.md"
119
+
120
+ # CWD has no packages/ dir → forces cache-fallback path.
121
+ empty_cwd="$FIXTURE_DIR/empty"
122
+ mkdir -p "$empty_cwd"
123
+
124
+ run env PATH="$cache_root/bin:$MIN_PATH" bash "$SCRIPT" "$empty_cwd"
125
+ [ "$status" -eq 0 ]
126
+ echo "$output" | grep -qE '^PLUGIN-HOOKS myplug bytes=[0-9]+$'
127
+ echo "$output" | grep -qE '^PLUGIN-SKILLS myplug bytes=[0-9]+$'
128
+ }
129
+
130
+ # ── Neither source nor cache resolves ───────────────────────────────────────
131
+
132
+ @test "list-plugin-attribution: emits not-measured sentinel when nothing resolves" {
133
+ # Empty fixture, empty PATH → zero source + zero cache.
134
+ run env PATH="$MIN_PATH" bash "$SCRIPT" "$FIXTURE_DIR"
135
+ [ "$status" -eq 0 ]
136
+ echo "$output" | grep -qE '^PLUGIN-ATTRIBUTION not-measured reason=no-plugin-source-resolvable$'
137
+ }
138
+
139
+ # ── Output budget ───────────────────────────────────────────────────────────
140
+
141
+ @test "list-plugin-attribution: every output row is under 150 bytes (ADR-038)" {
142
+ mkdir -p "$FIXTURE_DIR/packages/foo/hooks" "$FIXTURE_DIR/packages/foo/skills/s1"
143
+ printf 'x' > "$FIXTURE_DIR/packages/foo/hooks/h.sh"
144
+ printf 'x' > "$FIXTURE_DIR/packages/foo/skills/s1/SKILL.md"
145
+ run env PATH="$MIN_PATH" bash "$SCRIPT" "$FIXTURE_DIR"
146
+ while IFS= read -r line; do
147
+ [ "${#line}" -le 150 ]
148
+ done <<< "$output"
149
+ }
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # @problem P101 — wr-retrospective has no context-usage analysis. ADR-043
4
+ # (Progressive context-usage measurement and reporting for retrospective
5
+ # sessions) introduces a read-only diagnostic script
6
+ # `packages/retrospective/scripts/measure-context-budget.sh` as the cheap-
7
+ # layer measurement primitive. This fixture pins the script's behavioural
8
+ # contract.
9
+ #
10
+ # Contract: `measure-context-budget.sh [<project-root>]` walks the
11
+ # session's on-disk context contributors and reports per-source bucket
12
+ # byte totals. Default project-root is $CLAUDE_PROJECT_DIR if set, else
13
+ # the current working directory.
14
+ #
15
+ # Threshold default 10240 (the 5% / 200K cheap-layer envelope per ADR-043),
16
+ # overridable via CONTEXT_BUDGET_MAX_BYTES.
17
+ #
18
+ # Exit codes:
19
+ # 0 = always (advisory only — overflow is signal, not failure)
20
+ # 2 = parse error (project root missing or unreadable)
21
+ #
22
+ # Output format (one line per bucket, terse machine-readable per ADR-038
23
+ # progressive-disclosure budget — ≤150 bytes per row):
24
+ # BUCKET <name> bytes=<N>
25
+ # BUCKET <name> not-measured reason=<reason>
26
+ #
27
+ # Plus one trailing diagnostic row carrying the threshold:
28
+ # THRESHOLD bytes=<N>
29
+ #
30
+ # @adr ADR-043 (Progressive context-usage measurement; this script is the
31
+ # measurement primitive)
32
+ # @adr ADR-038 (Progressive disclosure — per-row byte budget)
33
+ # @adr ADR-026 (Agent output grounding — explicit not-measured sentinels)
34
+ # @adr ADR-013 Rule 1 / Rule 6 — interactive vs AFK
35
+ # @adr ADR-005 (Plugin testing strategy — behavioural fixture)
36
+ # @adr ADR-037 (Skill testing strategy — bats-contract precedent)
37
+ # @jtbd JTBD-001 / JTBD-005 / JTBD-006
38
+
39
+ setup() {
40
+ SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
41
+ SCRIPT="$SCRIPTS_DIR/measure-context-budget.sh"
42
+ FIXTURE_DIR="$(mktemp -d)"
43
+ }
44
+
45
+ teardown() {
46
+ rm -rf "$FIXTURE_DIR"
47
+ }
48
+
49
+ # ── Existence + executable ──────────────────────────────────────────────────
50
+
51
+ @test "measure-context-budget: script file exists at expected path" {
52
+ [ -f "$SCRIPT" ]
53
+ }
54
+
55
+ @test "measure-context-budget: script is executable" {
56
+ [ -x "$SCRIPT" ]
57
+ }
58
+
59
+ # ── Exit codes ──────────────────────────────────────────────────────────────
60
+
61
+ @test "measure-context-budget: empty project root exits 0 (advisory)" {
62
+ run bash "$SCRIPT" "$FIXTURE_DIR"
63
+ [ "$status" -eq 0 ]
64
+ }
65
+
66
+ @test "measure-context-budget: missing project root exits 2 (parse error)" {
67
+ run bash "$SCRIPT" "/path/that/does/not/exist/zz_$$"
68
+ [ "$status" -eq 2 ]
69
+ }
70
+
71
+ # ── Output shape — every run emits all buckets ──────────────────────────────
72
+
73
+ @test "measure-context-budget: output contains hooks bucket row" {
74
+ run bash "$SCRIPT" "$FIXTURE_DIR"
75
+ [ "$status" -eq 0 ]
76
+ echo "$output" | grep -q '^BUCKET hooks '
77
+ }
78
+
79
+ @test "measure-context-budget: output contains skills bucket row" {
80
+ run bash "$SCRIPT" "$FIXTURE_DIR"
81
+ echo "$output" | grep -q '^BUCKET skills '
82
+ }
83
+
84
+ @test "measure-context-budget: output contains briefing bucket row" {
85
+ run bash "$SCRIPT" "$FIXTURE_DIR"
86
+ echo "$output" | grep -q '^BUCKET briefing '
87
+ }
88
+
89
+ @test "measure-context-budget: output contains decisions bucket row" {
90
+ run bash "$SCRIPT" "$FIXTURE_DIR"
91
+ echo "$output" | grep -q '^BUCKET decisions '
92
+ }
93
+
94
+ @test "measure-context-budget: output contains problems bucket row" {
95
+ run bash "$SCRIPT" "$FIXTURE_DIR"
96
+ echo "$output" | grep -q '^BUCKET problems '
97
+ }
98
+
99
+ @test "measure-context-budget: output contains jtbd bucket row" {
100
+ run bash "$SCRIPT" "$FIXTURE_DIR"
101
+ echo "$output" | grep -q '^BUCKET jtbd '
102
+ }
103
+
104
+ @test "measure-context-budget: output contains project-claude-md bucket row" {
105
+ run bash "$SCRIPT" "$FIXTURE_DIR"
106
+ echo "$output" | grep -q '^BUCKET project-claude-md '
107
+ }
108
+
109
+ @test "measure-context-budget: output contains memory bucket row" {
110
+ run bash "$SCRIPT" "$FIXTURE_DIR"
111
+ echo "$output" | grep -q '^BUCKET memory '
112
+ }
113
+
114
+ # ── Framework-injected sentinel (ADR-026 ungrounded-field rule) ─────────────
115
+
116
+ @test "measure-context-budget: framework-injected bucket emits not-measured sentinel" {
117
+ run bash "$SCRIPT" "$FIXTURE_DIR"
118
+ [ "$status" -eq 0 ]
119
+ echo "$output" | grep -q '^BUCKET framework-injected not-measured '
120
+ echo "$output" | grep -q 'reason=framework-injected-no-on-disk-source'
121
+ }
122
+
123
+ # ── Surface-absent sentinels (ADR-026 ungrounded-field rule) ────────────────
124
+ # Empty fixture has no docs/decisions/, docs/problems/, docs/jtbd/, etc.
125
+
126
+ @test "measure-context-budget: empty fixture marks decisions not-measured" {
127
+ run bash "$SCRIPT" "$FIXTURE_DIR"
128
+ echo "$output" | grep -q '^BUCKET decisions not-measured '
129
+ }
130
+
131
+ @test "measure-context-budget: empty fixture marks problems not-measured" {
132
+ run bash "$SCRIPT" "$FIXTURE_DIR"
133
+ echo "$output" | grep -q '^BUCKET problems not-measured '
134
+ }
135
+
136
+ @test "measure-context-budget: empty fixture marks jtbd not-measured" {
137
+ run bash "$SCRIPT" "$FIXTURE_DIR"
138
+ echo "$output" | grep -q '^BUCKET jtbd not-measured '
139
+ }
140
+
141
+ @test "measure-context-budget: empty fixture marks briefing not-measured" {
142
+ run bash "$SCRIPT" "$FIXTURE_DIR"
143
+ echo "$output" | grep -q '^BUCKET briefing not-measured '
144
+ }
145
+
146
+ @test "measure-context-budget: empty fixture marks project-claude-md not-measured" {
147
+ run bash "$SCRIPT" "$FIXTURE_DIR"
148
+ echo "$output" | grep -q '^BUCKET project-claude-md not-measured '
149
+ }
150
+
151
+ # ── Surface-present byte counts ─────────────────────────────────────────────
152
+
153
+ @test "measure-context-budget: populated decisions bucket reports byte count" {
154
+ mkdir -p "$FIXTURE_DIR/docs/decisions"
155
+ printf '# ADR-001\nbody body body\n' > "$FIXTURE_DIR/docs/decisions/001-foo.proposed.md"
156
+ run bash "$SCRIPT" "$FIXTURE_DIR"
157
+ [ "$status" -eq 0 ]
158
+ echo "$output" | grep -qE '^BUCKET decisions bytes=[0-9]+$'
159
+ # Sanity: byte count is non-zero
160
+ decisions_line=$(echo "$output" | grep '^BUCKET decisions ')
161
+ bytes_value="${decisions_line##*bytes=}"
162
+ [ "$bytes_value" -gt 0 ]
163
+ }
164
+
165
+ @test "measure-context-budget: populated problems bucket reports byte count" {
166
+ mkdir -p "$FIXTURE_DIR/docs/problems"
167
+ printf '# Problem 001\nbody\n' > "$FIXTURE_DIR/docs/problems/001-foo.open.md"
168
+ run bash "$SCRIPT" "$FIXTURE_DIR"
169
+ echo "$output" | grep -qE '^BUCKET problems bytes=[0-9]+$'
170
+ }
171
+
172
+ @test "measure-context-budget: populated briefing bucket reports byte count" {
173
+ mkdir -p "$FIXTURE_DIR/docs/briefing"
174
+ printf '# Topic\nentry\n' > "$FIXTURE_DIR/docs/briefing/foo.md"
175
+ run bash "$SCRIPT" "$FIXTURE_DIR"
176
+ echo "$output" | grep -qE '^BUCKET briefing bytes=[0-9]+$'
177
+ }
178
+
179
+ @test "measure-context-budget: project CLAUDE.md reports byte count" {
180
+ printf '# Project\ninstructions\n' > "$FIXTURE_DIR/CLAUDE.md"
181
+ run bash "$SCRIPT" "$FIXTURE_DIR"
182
+ echo "$output" | grep -qE '^BUCKET project-claude-md bytes=[0-9]+$'
183
+ claude_md_line=$(echo "$output" | grep '^BUCKET project-claude-md ')
184
+ bytes_value="${claude_md_line##*bytes=}"
185
+ [ "$bytes_value" -gt 0 ]
186
+ }
187
+
188
+ # ── Threshold ────────────────────────────────────────────────────────────────
189
+
190
+ @test "measure-context-budget: trailing THRESHOLD row emitted" {
191
+ run bash "$SCRIPT" "$FIXTURE_DIR"
192
+ [ "$status" -eq 0 ]
193
+ echo "$output" | grep -qE '^THRESHOLD bytes=[0-9]+$'
194
+ }
195
+
196
+ @test "measure-context-budget: default threshold is 10240" {
197
+ unset CONTEXT_BUDGET_MAX_BYTES
198
+ run bash "$SCRIPT" "$FIXTURE_DIR"
199
+ echo "$output" | grep -q '^THRESHOLD bytes=10240$'
200
+ }
201
+
202
+ @test "measure-context-budget: CONTEXT_BUDGET_MAX_BYTES override respected" {
203
+ CONTEXT_BUDGET_MAX_BYTES=42 run bash "$SCRIPT" "$FIXTURE_DIR"
204
+ [ "$status" -eq 0 ]
205
+ echo "$output" | grep -q '^THRESHOLD bytes=42$'
206
+ }
207
+
208
+ # ── Per-row byte budget (ADR-038 progressive-disclosure ≤150 bytes/row) ─────
209
+
210
+ @test "measure-context-budget: every row is at most 150 bytes" {
211
+ run bash "$SCRIPT" "$FIXTURE_DIR"
212
+ [ "$status" -eq 0 ]
213
+ while IFS= read -r line; do
214
+ [ "${#line}" -le 150 ]
215
+ done <<< "$output"
216
+ }
217
+
218
+ # ── Read-only contract — script does not mutate the project tree ────────────
219
+
220
+ @test "measure-context-budget: read-only — fixture tree unchanged after run" {
221
+ mkdir -p "$FIXTURE_DIR/docs/decisions"
222
+ printf '# ADR-001\nbody\n' > "$FIXTURE_DIR/docs/decisions/001-foo.proposed.md"
223
+ pre_hash=$(find "$FIXTURE_DIR" -type f -exec cksum {} \; 2>/dev/null | sort | cksum | awk '{print $1}')
224
+ run bash "$SCRIPT" "$FIXTURE_DIR"
225
+ [ "$status" -eq 0 ]
226
+ post_hash=$(find "$FIXTURE_DIR" -type f -exec cksum {} \; 2>/dev/null | sort | cksum | awk '{print $1}')
227
+ [ "$pre_hash" = "$post_hash" ]
228
+ }
229
+
230
+ # ── CLAUDE_PROJECT_DIR fallback when no arg ─────────────────────────────────
231
+
232
+ @test "measure-context-budget: CLAUDE_PROJECT_DIR env var respected when no arg" {
233
+ CLAUDE_PROJECT_DIR="$FIXTURE_DIR" run bash "$SCRIPT"
234
+ [ "$status" -eq 0 ]
235
+ echo "$output" | grep -q '^BUCKET hooks '
236
+ }