@windyroad/retrospective 0.20.4 → 0.21.0-preview.389
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/README.md +2 -15
- package/hooks/retrospective-readme-jtbd-currency.sh +31 -27
- package/hooks/test/retrospective-readme-jtbd-currency.bats +61 -95
- package/package.json +1 -1
- package/scripts/check-readme-jtbd-currency.sh +46 -111
- package/scripts/test/check-readme-jtbd-currency.bats +73 -125
- package/skills/run-retro/SKILL.md +7 -7
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Restart Claude Code after installing.
|
|
|
32
32
|
/wr-retrospective:run-retro
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
This walks through the session's work, identifies what went well and what didn't, updates `docs/BRIEFING.md`, and creates problem tickets for any failures. The retro also runs every shipped advisory script (briefing budgets, ask-hygiene, README
|
|
35
|
+
This walks through the session's work, identifies what went well and what didn't, updates `docs/BRIEFING.md`, and creates problem tickets for any failures. The retro also runs every shipped advisory script (briefing budgets, ask-hygiene, README skill-inventory currency per ADR-069, SKILL.md runtime budgets per ADR-054) so doc-content drift surfaces in the retro summary alongside session-level reflections.
|
|
36
36
|
|
|
37
37
|
**Analyse session context usage:**
|
|
38
38
|
|
|
@@ -57,7 +57,7 @@ Each script ships as a `bin/`-resolvable shim per ADR-049. They emit signal-as-d
|
|
|
57
57
|
|
|
58
58
|
| Shim | Purpose |
|
|
59
59
|
|------|---------|
|
|
60
|
-
| `wr-retrospective-check-readme-jtbd-currency` | ADR-
|
|
60
|
+
| `wr-retrospective-check-readme-jtbd-currency` | ADR-069 detector — surfaces skill-inventory drift (a shipped skill not named in the plugin's README) |
|
|
61
61
|
| `wr-retrospective-check-skill-md-budgets` | ADR-054 detector — flags SKILL.md files over the WARN / MUST_SPLIT byte budget |
|
|
62
62
|
| `wr-retrospective-check-internal-id-leaks` | ADR-055 detector — flags internal IDs leaking into published artefacts |
|
|
63
63
|
| `wr-retrospective-list-plugin-attribution` | Per-plugin context attribution support for `analyze-context` |
|
|
@@ -71,19 +71,6 @@ Each script ships as a `bin/`-resolvable shim per ADR-049. They emit signal-as-d
|
|
|
71
71
|
| `session-start-briefing.sh` | Session start | Surfaces the latest `docs/BRIEFING.md` and any pending retrospective items so the new session begins with prior-session context (per [ADR-040](../../docs/decisions/040-session-start-briefing-surface.proposed.md)) |
|
|
72
72
|
| `retrospective-reminder.sh` | Session end | Reminds you to run a retrospective |
|
|
73
73
|
|
|
74
|
-
## Jobs to be Done
|
|
75
|
-
|
|
76
|
-
This plugin serves the [Jobs to be Done](../../docs/jtbd/) below. Per [ADR-051](../../docs/decisions/051-jtbd-anchored-readme-with-drift-advisory.proposed.md), the persona-grouped JTBD anchor is the canonical source of truth for the README's value framing.
|
|
77
|
-
|
|
78
|
-
### Solo developer
|
|
79
|
-
|
|
80
|
-
- **[JTBD-006 Progress the Backlog While I'm Away](../../docs/jtbd/solo-developer/JTBD-006-work-backlog-afk.proposed.md)** — every AFK iteration runs a retrospective before exiting so per-iter friction is captured as problem tickets and surfaced on return; learnings flow back into `docs/BRIEFING.md` so the next session starts with context.
|
|
81
|
-
- **[JTBD-002 Ship AI-Assisted Code with Confidence](../../docs/jtbd/solo-developer/JTBD-002-ship-with-confidence.proposed.md)** — release-readiness signals (pipeline-instability scan, ask-hygiene check, doc-currency advisory) accumulate at retro time so unresolved friction is visible before the next release.
|
|
82
|
-
|
|
83
|
-
### Plugin user
|
|
84
|
-
|
|
85
|
-
- **[JTBD-302 Trust That the README Describes the Plugin I Just Installed](../../docs/jtbd/plugin-user/JTBD-302-trust-readme-describes-installed-behaviour.proposed.md)** — this README is anchored on current JTBD job IDs; drift between prose and shipped behaviour is detectable at retro time per ADR-051.
|
|
86
|
-
|
|
87
74
|
## Updating and Uninstalling
|
|
88
75
|
|
|
89
76
|
```bash
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
# post-commit working tree exhibits
|
|
4
|
-
# packages/<plugin>/README.md (
|
|
5
|
-
#
|
|
2
|
+
# ADR-069 (P294): PreToolUse:Bash hook — denies `git commit` invocations
|
|
3
|
+
# whose post-commit working tree exhibits skill-inventory drift in any
|
|
4
|
+
# packages/<plugin>/README.md (a directory under packages/<plugin>/skills/
|
|
5
|
+
# is not named in the README).
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
7
|
+
# HISTORY: this hook formerly (P159, under superseded ADR-051) also gated on
|
|
8
|
+
# JTBD-ID-citation drift (no JTBD-NNN anchor, stale/deprecated citation).
|
|
9
|
+
# ADR-069 superseded ADR-051: plugin READMEs market the persona's problem
|
|
10
|
+
# derived FROM the JTBD but MUST NOT cite JTBD IDs. The JTBD-ID anchor + its
|
|
11
|
+
# docs/jtbd/ resolution are removed; the mechanical skill-inventory-drift
|
|
12
|
+
# signal (ADR-051's original P152 empirical core) survives as the load-
|
|
13
|
+
# bearing currency gate. Filename retained deliberately per ADR-069.
|
|
14
14
|
#
|
|
15
|
-
#
|
|
15
|
+
# Hook-level enforcement at commit time (vs retro-time advisory) is retained
|
|
16
|
+
# per the carried-forward load-bearing-from-the-start-for-drift-class driver:
|
|
17
|
+
# the most-common drift class (contributor adds a skill and forgets the
|
|
18
|
+
# README) ships in a commit that does not touch README.md, so a retro-time
|
|
19
|
+
# consumer sees the drift only after the contributor has already committed.
|
|
20
|
+
#
|
|
21
|
+
# Detection delegates to the detector script
|
|
16
22
|
# (`packages/retrospective/scripts/check-readme-jtbd-currency.sh`),
|
|
17
|
-
# invoked against the project's working tree (`./packages/`
|
|
18
|
-
#
|
|
19
|
-
# `
|
|
20
|
-
# denies when `drift_instances > 0`.
|
|
23
|
+
# invoked against the project's working tree (`./packages/`). The hook reads
|
|
24
|
+
# the detector's `TOTAL packages=<N> drift_instances=<K>` summary and denies
|
|
25
|
+
# when `drift_instances > 0`.
|
|
21
26
|
#
|
|
22
27
|
# Allow paths (exit 0 silently per ADR-045 Pattern 1):
|
|
23
28
|
# - tool_name != "Bash" (only Bash invocations are gated)
|
|
@@ -139,11 +144,11 @@ fi
|
|
|
139
144
|
# Fail-open if not inside a git working tree.
|
|
140
145
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
141
146
|
|
|
142
|
-
# Fail-open if the project lacks
|
|
143
|
-
# Adopter projects without `./packages/`
|
|
144
|
-
#
|
|
147
|
+
# Fail-open if the project lacks the structural anchor (`./packages/`).
|
|
148
|
+
# Adopter projects without `./packages/` are not subject to the rule; the
|
|
149
|
+
# hook is a no-op for them. (ADR-069: the `./docs/jtbd/` guard is removed —
|
|
150
|
+
# skill-inventory drift does not consult docs/jtbd/.)
|
|
145
151
|
[ -d "./packages" ] || exit 0
|
|
146
|
-
[ -d "./docs/jtbd" ] || exit 0
|
|
147
152
|
|
|
148
153
|
# Fail-open if the detector script itself is missing (defensive —
|
|
149
154
|
# hook + detector ship together, but install-time corruption or
|
|
@@ -152,7 +157,7 @@ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
|
152
157
|
|
|
153
158
|
# Run the detector. Capture exit code + output. Fail-open on detector
|
|
154
159
|
# error (exit != 0).
|
|
155
|
-
DETECTOR_OUTPUT=$(bash "$DETECTOR" "./packages"
|
|
160
|
+
DETECTOR_OUTPUT=$(bash "$DETECTOR" "./packages" 2>/dev/null) || exit 0
|
|
156
161
|
|
|
157
162
|
# Parse the TOTAL summary line. If absent, no packages were
|
|
158
163
|
# enumerated — fail-open (no drift to report).
|
|
@@ -188,12 +193,11 @@ OFFENDING_HINTS=$(echo "$OFFENDING_LINE" | grep -oE 'drift_hints=[A-Za-z0-9,_-]+
|
|
|
188
193
|
PRIMARY_HINT="${OFFENDING_HINTS%%,*}"
|
|
189
194
|
|
|
190
195
|
# Deny — voice/tone budget per ADR-045 deny-band ≤300 bytes total
|
|
191
|
-
# (envelope ~137 bytes; REASON ~
|
|
192
|
-
# hint). Names the offending plugin slug, the
|
|
193
|
-
# the
|
|
194
|
-
#
|
|
195
|
-
|
|
196
|
-
REASON="BLOCKED: P159 JTBD drift in ${OFFENDING_SLUG} (${PRIMARY_HINT}). Recovery: wr-jtbd:agent OR cite a JTBD-NNN in README. Bypass: BYPASS_JTBD_CURRENCY=1."
|
|
196
|
+
# (envelope ~137 bytes; REASON ~145 bytes for worst-case slug + the
|
|
197
|
+
# single inventory hint). Names the offending plugin slug, the drift
|
|
198
|
+
# hint, the mechanical recovery (name the skill in the README — NOT
|
|
199
|
+
# a JTBD-ID citation per ADR-069), the BYPASS env, and the P294 cite.
|
|
200
|
+
REASON="BLOCKED: P294 README inventory drift in ${OFFENDING_SLUG} (${PRIMARY_HINT}). Recovery: name the skill in the README. Bypass: BYPASS_JTBD_CURRENCY=1."
|
|
197
201
|
|
|
198
202
|
cat <<EOF
|
|
199
203
|
{
|
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env bats
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# deny `git commit` invocations whose post-commit working tree exhibits
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# (contributor adds skill/hook/agent and forgets the README) ships in a
|
|
11
|
-
# commit that doesn't touch README.md, so a retro-time consumer sees the
|
|
12
|
-
# drift only after the contributor has already committed.
|
|
3
|
+
# ADR-069 (P294): retrospective-readme-jtbd-currency.sh PreToolUse:Bash hook
|
|
4
|
+
# must deny `git commit` invocations whose post-commit working tree exhibits
|
|
5
|
+
# skill-inventory drift (a directory under packages/<plugin>/skills/ is not
|
|
6
|
+
# named in that plugin's README). Hook-level enforcement at commit time is
|
|
7
|
+
# retained per the carried-forward load-bearing-from-the-start-for-drift-class
|
|
8
|
+
# driver: the most-common drift class (contributor adds a skill and forgets
|
|
9
|
+
# the README) ships in a commit that doesn't touch README.md.
|
|
13
10
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# and denies when `drift_instances > 0`.
|
|
11
|
+
# HISTORY: under superseded ADR-051 (P159) this hook also gated on
|
|
12
|
+
# JTBD-ID-citation drift. ADR-069 superseded that rule — READMEs market the
|
|
13
|
+
# persona's problem derived FROM the JTBD but MUST NOT cite JTBD IDs. The
|
|
14
|
+
# JTBD-ID anchor + its docs/jtbd/ resolution (and the docs/jtbd/ activation
|
|
15
|
+
# guard) are removed; only skill-inventory-drift remains.
|
|
20
16
|
#
|
|
21
|
-
#
|
|
22
|
-
# packages
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# Per ADR-
|
|
28
|
-
#
|
|
29
|
-
#
|
|
17
|
+
# Detection delegates to
|
|
18
|
+
# `packages/retrospective/scripts/check-readme-jtbd-currency.sh`, invoked
|
|
19
|
+
# against the project's working tree (`./packages/`). The hook reads the
|
|
20
|
+
# detector's `TOTAL packages=<N> drift_instances=<K>` summary and denies
|
|
21
|
+
# when `drift_instances > 0`.
|
|
22
|
+
#
|
|
23
|
+
# Per ADR-005 / ADR-052 / P081 — behavioural; assert on emitted JSON, no
|
|
24
|
+
# source greps. Per ADR-045 Pattern 1 — allow paths emit 0 bytes; deny-band
|
|
25
|
+
# ≤300 bytes. Per ADR-013 Rule 1 — deny redirects to mechanical recovery
|
|
26
|
+
# (here: "name the skill in the README"). Per ADR-013 Rule 6 — fail-open
|
|
27
|
+
# outside a git work tree, on parse errors, or in projects without
|
|
28
|
+
# `./packages/`.
|
|
30
29
|
|
|
31
30
|
setup() {
|
|
32
31
|
SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
@@ -56,86 +55,49 @@ run_bash_hook() {
|
|
|
56
55
|
echo "$json" | bash "$HOOK"
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
# Helper:
|
|
60
|
-
#
|
|
58
|
+
# Helper: stub project with skill-inventory drift —
|
|
59
|
+
# packages/stub/skills/orphan/ exists but the README doesn't name "orphan".
|
|
61
60
|
make_drifted_project() {
|
|
62
|
-
mkdir -p packages/stub
|
|
63
|
-
printf '%s\n' "# @windyroad/stub" "
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
job-id: trust-readme
|
|
68
|
-
persona: plugin-user
|
|
69
|
-
date-created: 2026-05-04
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
# JTBD-302
|
|
73
|
-
EOF
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# Helper: build a stub project layout with a clean plugin README.
|
|
77
|
-
# README cites a resolving JTBD-NNN; no skill drift.
|
|
61
|
+
mkdir -p packages/stub/skills/orphan
|
|
62
|
+
printf '%s\n' "# @windyroad/stub" "Markets a problem in prose; names no skills." > packages/stub/README.md
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Helper: clean stub project — every skill directory is named in the README.
|
|
78
66
|
make_clean_project() {
|
|
79
|
-
mkdir -p packages/stub
|
|
80
|
-
printf '%s\n' "# @windyroad/stub" "
|
|
81
|
-
cat > docs/jtbd/plugin-user/JTBD-302-trust-readme.proposed.md <<'EOF'
|
|
82
|
-
---
|
|
83
|
-
status: proposed
|
|
84
|
-
job-id: trust-readme
|
|
85
|
-
persona: plugin-user
|
|
86
|
-
date-created: 2026-05-04
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
# JTBD-302
|
|
90
|
-
EOF
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
# Helper: build a stub project with skill-inventory-drift —
|
|
94
|
-
# packages/stub/skills/orphan/ exists but README doesn't name "orphan".
|
|
95
|
-
make_skill_drift_project() {
|
|
96
|
-
mkdir -p packages/stub/skills/orphan docs/jtbd/plugin-user
|
|
97
|
-
printf '%s\n' "# @windyroad/stub" "Serves JTBD-302." > packages/stub/README.md
|
|
98
|
-
cat > docs/jtbd/plugin-user/JTBD-302-trust-readme.proposed.md <<'EOF'
|
|
99
|
-
---
|
|
100
|
-
status: proposed
|
|
101
|
-
job-id: trust-readme
|
|
102
|
-
persona: plugin-user
|
|
103
|
-
date-created: 2026-05-04
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
# JTBD-302
|
|
107
|
-
EOF
|
|
67
|
+
mkdir -p packages/stub/skills/do-thing
|
|
68
|
+
printf '%s\n' "# @windyroad/stub" "Run /wr-stub:do-thing to solve the problem." > packages/stub/README.md
|
|
108
69
|
}
|
|
109
70
|
|
|
110
71
|
# ── Trap detection: deny when drift detected ───────────────────────────────
|
|
111
72
|
|
|
112
|
-
@test "deny:
|
|
73
|
+
@test "deny: skill-inventory drift on git commit triggers deny" {
|
|
113
74
|
make_drifted_project
|
|
114
75
|
run run_bash_hook "git commit -m 'feat'"
|
|
115
76
|
[ "$status" -eq 0 ]
|
|
116
77
|
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
117
|
-
[[ "$output" == *"
|
|
78
|
+
[[ "$output" == *"P294"* ]]
|
|
118
79
|
}
|
|
119
80
|
|
|
120
|
-
@test "deny
|
|
121
|
-
|
|
81
|
+
@test "deny message names the offending plugin slug" {
|
|
82
|
+
make_drifted_project
|
|
122
83
|
run run_bash_hook "git commit -m 'feat'"
|
|
123
84
|
[ "$status" -eq 0 ]
|
|
124
|
-
[[ "$output" == *"
|
|
85
|
+
[[ "$output" == *"stub"* ]]
|
|
125
86
|
}
|
|
126
87
|
|
|
127
|
-
@test "deny message names the
|
|
88
|
+
@test "deny message names the mechanical recovery (name the skill in the README)" {
|
|
128
89
|
make_drifted_project
|
|
129
90
|
run run_bash_hook "git commit -m 'feat'"
|
|
130
91
|
[ "$status" -eq 0 ]
|
|
131
|
-
[[ "$output" == *"
|
|
92
|
+
[[ "$output" == *"name the skill in the README"* ]]
|
|
132
93
|
}
|
|
133
94
|
|
|
134
|
-
@test "deny message
|
|
95
|
+
@test "deny message does NOT instruct citing a JTBD ID (ADR-069 regression guard)" {
|
|
135
96
|
make_drifted_project
|
|
136
97
|
run run_bash_hook "git commit -m 'feat'"
|
|
137
98
|
[ "$status" -eq 0 ]
|
|
138
|
-
[[ "$output"
|
|
99
|
+
[[ "$output" != *"JTBD-NNN"* ]]
|
|
100
|
+
[[ "$output" != *"wr-jtbd"* ]]
|
|
139
101
|
}
|
|
140
102
|
|
|
141
103
|
@test "deny message stays under ADR-045 deny-band (<300 bytes)" {
|
|
@@ -145,16 +107,13 @@ EOF
|
|
|
145
107
|
[ "${#output}" -lt 300 ]
|
|
146
108
|
}
|
|
147
109
|
|
|
148
|
-
@test "deny:
|
|
110
|
+
@test "deny: canonical release commit shape (chore: version packages) is subject to the gate" {
|
|
149
111
|
make_drifted_project
|
|
112
|
+
# Not a `git commit` invocation — the bare message must NOT deny.
|
|
150
113
|
run run_bash_hook "chore: version packages"
|
|
151
|
-
# Not a `git commit` invocation — should NOT trigger deny because the
|
|
152
|
-
# command field is the message, not the invocation. The actual release
|
|
153
|
-
# path runs `git commit` which IS gated; verify that shape:
|
|
154
114
|
[ "$status" -eq 0 ]
|
|
155
115
|
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
156
|
-
|
|
157
|
-
# Now the canonical release commit shape:
|
|
116
|
+
# The canonical release commit shape IS gated:
|
|
158
117
|
run run_bash_hook "git commit -m 'chore: version packages'"
|
|
159
118
|
[ "$status" -eq 0 ]
|
|
160
119
|
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
@@ -167,9 +126,18 @@ EOF
|
|
|
167
126
|
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
168
127
|
}
|
|
169
128
|
|
|
129
|
+
@test "deny: skill drift with NO docs/jtbd present still denies (ADR-069 guard removed)" {
|
|
130
|
+
make_drifted_project
|
|
131
|
+
# deliberately no docs/jtbd/ — under ADR-051 the hook was a no-op here;
|
|
132
|
+
# ADR-069 removed that guard, so inventory drift is now evaluated.
|
|
133
|
+
run run_bash_hook "git commit -m 'feat'"
|
|
134
|
+
[ "$status" -eq 0 ]
|
|
135
|
+
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
136
|
+
}
|
|
137
|
+
|
|
170
138
|
# ── Allow paths: each non-trap shape must NOT deny ─────────────────────────
|
|
171
139
|
|
|
172
|
-
@test "allow: clean README (
|
|
140
|
+
@test "allow: clean README (all skills named) on git commit allows the commit" {
|
|
173
141
|
make_clean_project
|
|
174
142
|
run run_bash_hook "git commit -m 'feat'"
|
|
175
143
|
[ "$status" -eq 0 ]
|
|
@@ -211,15 +179,14 @@ EOF
|
|
|
211
179
|
}
|
|
212
180
|
|
|
213
181
|
@test "allow: project without packages/ dir exits 0 without deny (fail-open)" {
|
|
214
|
-
# No packages
|
|
182
|
+
# No packages/ — adopter project shape.
|
|
215
183
|
run run_bash_hook "git commit -m 'feat'"
|
|
216
184
|
[ "$status" -eq 0 ]
|
|
217
185
|
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
218
186
|
}
|
|
219
187
|
|
|
220
|
-
@test "allow: project with packages/
|
|
221
|
-
|
|
222
|
-
echo "# stub" > packages/stub/README.md
|
|
188
|
+
@test "allow: project with packages/ and no docs/jtbd/, clean README, allows (docs/jtbd no longer required)" {
|
|
189
|
+
make_clean_project
|
|
223
190
|
run run_bash_hook "git commit -m 'feat'"
|
|
224
191
|
[ "$status" -eq 0 ]
|
|
225
192
|
[[ "$output" != *"\"permissionDecision\": \"deny\""* ]]
|
|
@@ -266,8 +233,7 @@ EOF
|
|
|
266
233
|
#
|
|
267
234
|
# The hook must fire on ACTUAL `git commit` invocations, NOT on Bash that
|
|
268
235
|
# merely MENTIONS the phrase "git commit" in argument vectors or heredoc
|
|
269
|
-
# bodies. Mirrors P268 regression fixtures in command-detect.bats
|
|
270
|
-
# P272 sibling fixtures in itil-changeset-discipline.bats.
|
|
236
|
+
# bodies. Mirrors P268 regression fixtures in command-detect.bats.
|
|
271
237
|
|
|
272
238
|
@test "P275 allow: grep with literal 'git commit' pattern on drifted project does NOT deny" {
|
|
273
239
|
make_drifted_project
|
|
@@ -316,7 +282,7 @@ EOF
|
|
|
316
282
|
run run_bash_hook "GIT_AUTHOR_NAME=foo git commit -m 'feat'"
|
|
317
283
|
[ "$status" -eq 0 ]
|
|
318
284
|
[[ "$output" == *"\"permissionDecision\": \"deny\""* ]]
|
|
319
|
-
[[ "$output" == *"
|
|
285
|
+
[[ "$output" == *"P294"* ]]
|
|
320
286
|
}
|
|
321
287
|
|
|
322
288
|
@test "P275 deny: cd-prefixed git commit on drifted project still triggers deny" {
|
package/package.json
CHANGED
|
@@ -1,57 +1,64 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# packages/retrospective/scripts/check-readme-jtbd-currency.sh
|
|
3
3
|
#
|
|
4
|
-
# Diagnose-only advisory script for ADR-
|
|
5
|
-
# Walks packages/*/README.md and
|
|
4
|
+
# Diagnose-only advisory script for ADR-069 (README markets the persona's
|
|
5
|
+
# problem; skill-inventory currency gate). Walks packages/*/README.md and
|
|
6
|
+
# emits a skill-inventory-drift signal per package:
|
|
6
7
|
#
|
|
7
|
-
# -
|
|
8
|
-
# -
|
|
9
|
-
# -
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# missing-jtbd-section (no JTBD-\d{3} at all)
|
|
13
|
-
# stale-jtbd-citation (cited ID has no resolving file)
|
|
14
|
-
# deprecated-jtbd-citation (cited ID resolves only to .deprecated.md / .superseded.md)
|
|
15
|
-
# skill-inventory-drift (a directory under packages/<plugin>/skills/ is not named in README)
|
|
8
|
+
# - skills=<count> — directories under packages/<plugin>/skills/
|
|
9
|
+
# - in_readme=<count> — those skill directories named in the README
|
|
10
|
+
# - drift_hints=<csv> — signal vocabulary (inventory-only):
|
|
11
|
+
# skill-inventory-drift (a directory under packages/<plugin>/skills/
|
|
12
|
+
# is not named in the README)
|
|
16
13
|
#
|
|
17
14
|
# Plus a trailing TOTAL line summarising the window:
|
|
18
|
-
# TOTAL packages=<N>
|
|
15
|
+
# TOTAL packages=<N> drift_instances=<K>
|
|
19
16
|
#
|
|
20
|
-
# Exit code is always 0 — the script is advisory
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
17
|
+
# Exit code is always 0 — the script is advisory; the commit-hook
|
|
18
|
+
# (retrospective-readme-jtbd-currency.sh) reads drift_instances and decides
|
|
19
|
+
# whether to deny. drift_instances counts packages with a non-empty
|
|
20
|
+
# drift_hints set.
|
|
21
|
+
#
|
|
22
|
+
# HISTORY / NAME-RETENTION: this script formerly enforced ADR-051's
|
|
23
|
+
# JTBD-ID-citation rule (grep for JTBD-\d{3}, resolve against docs/jtbd/).
|
|
24
|
+
# ADR-069 (P294) superseded ADR-051: plugin READMEs market to the persona's
|
|
25
|
+
# problem derived FROM the JTBD, but MUST NOT cite JTBD IDs. The JTBD-ID
|
|
26
|
+
# anchor + its docs/jtbd/ resolution are removed; the mechanical
|
|
27
|
+
# skill-inventory-drift signal (ADR-051's original P152 empirical core)
|
|
28
|
+
# survives as the load-bearing currency gate. The `jtbd-currency` filename
|
|
29
|
+
# is retained deliberately per ADR-069 — a rename would ripple into mutable
|
|
30
|
+
# ADRs 054/055/057 + the ADR-049 bin-grammar three-touch tax for a filename
|
|
31
|
+
# adopters rarely see. A clean rename is deferred to a future
|
|
32
|
+
# check-*-currency.sh family consolidation (ADR-069 Reassessment Criteria).
|
|
25
33
|
#
|
|
26
34
|
# Usage:
|
|
27
|
-
# check-readme-jtbd-currency.sh [<packages-dir>]
|
|
35
|
+
# check-readme-jtbd-currency.sh [<packages-dir>]
|
|
28
36
|
#
|
|
29
37
|
# Defaults:
|
|
30
38
|
# <packages-dir> = ./packages
|
|
31
|
-
#
|
|
39
|
+
#
|
|
40
|
+
# A second argument (formerly <jtbd-dir>) is accepted and ignored for
|
|
41
|
+
# backward compatibility with pre-ADR-069 callers.
|
|
32
42
|
#
|
|
33
43
|
# Exit codes:
|
|
34
44
|
# 0 = always (advisory only — count is signal, not failure)
|
|
35
|
-
# 2 = parse error (packages-dir
|
|
45
|
+
# 2 = parse error (packages-dir missing or unreadable)
|
|
36
46
|
#
|
|
37
47
|
# Output format (one line per package, alphabetical):
|
|
38
|
-
# README package=<name>
|
|
48
|
+
# README package=<name> skills=<N> in_readme=<M> drift_hints=<csv>
|
|
39
49
|
#
|
|
40
|
-
# @problem P152 (No pressure or nudge for documentation currency — the driver
|
|
41
|
-
# @
|
|
42
|
-
# @adr ADR-
|
|
50
|
+
# @problem P152 (No pressure or nudge for documentation currency — the original driver)
|
|
51
|
+
# @problem P294 (ADR-051 superseded — README markets the persona's problem, no JTBD-ID citation)
|
|
52
|
+
# @adr ADR-069 (README markets persona problem; skill-inventory currency gate — this script's normative source)
|
|
53
|
+
# @adr ADR-051 (superseded — original JTBD-anchored README rule this script no longer enforces)
|
|
43
54
|
# @adr ADR-013 Rule 6 (non-interactive fail-safe — advisory script never blocks AFK)
|
|
44
55
|
# @adr ADR-040 (declarative-first / advisory-then-escalate precedent)
|
|
45
56
|
# @adr ADR-049 (bin/-on-PATH script resolution — paired wr-retrospective-check-readme-jtbd-currency shim)
|
|
46
|
-
# @adr ADR-
|
|
47
|
-
# @jtbd JTBD-302 (Trust That the README Describes the Plugin I Just Installed — primary served job)
|
|
48
|
-
# @jtbd JTBD-007 (Keep Plugins Current Across Projects — currency expansion)
|
|
49
|
-
# @jtbd JTBD-101 (Extend the Suite with New Plugins — clear patterns the detector documents)
|
|
57
|
+
# @adr ADR-052 / P081 (behavioural tests via bats; no structural-grep on this source)
|
|
50
58
|
|
|
51
59
|
set -uo pipefail
|
|
52
60
|
|
|
53
61
|
PACKAGES_DIR="${1:-packages}"
|
|
54
|
-
JTBD_DIR="${2:-docs/jtbd}"
|
|
55
62
|
|
|
56
63
|
# ── Pre-checks ──────────────────────────────────────────────────────────────
|
|
57
64
|
|
|
@@ -60,34 +67,6 @@ if [ ! -d "$PACKAGES_DIR" ]; then
|
|
|
60
67
|
exit 2
|
|
61
68
|
fi
|
|
62
69
|
|
|
63
|
-
if [ ! -d "$JTBD_DIR" ]; then
|
|
64
|
-
echo "check-readme-jtbd-currency: jtbd dir not found: $JTBD_DIR" >&2
|
|
65
|
-
exit 2
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
# ── Build the JTBD index ────────────────────────────────────────────────────
|
|
69
|
-
# Map JTBD-NNN -> comma-separated status suffixes (proposed|validated|deprecated|superseded)
|
|
70
|
-
# A JTBD ID may resolve to multiple files (e.g. during a status transition);
|
|
71
|
-
# we record ALL status suffixes per ID for downstream use.
|
|
72
|
-
|
|
73
|
-
declare -A JTBD_STATUS_BY_ID
|
|
74
|
-
declare -A JTBD_RESOLVED
|
|
75
|
-
|
|
76
|
-
for jpath in "$JTBD_DIR"/*/JTBD-*.md; do
|
|
77
|
-
[ -e "$jpath" ] || continue
|
|
78
|
-
base="$(basename "$jpath")"
|
|
79
|
-
if [[ "$base" =~ ^(JTBD-[0-9]{3})-.*\.([a-z]+)\.md$ ]]; then
|
|
80
|
-
id="${BASH_REMATCH[1]}"
|
|
81
|
-
status="${BASH_REMATCH[2]}"
|
|
82
|
-
JTBD_RESOLVED["$id"]=1
|
|
83
|
-
if [ -z "${JTBD_STATUS_BY_ID[$id]:-}" ]; then
|
|
84
|
-
JTBD_STATUS_BY_ID["$id"]="$status"
|
|
85
|
-
else
|
|
86
|
-
JTBD_STATUS_BY_ID["$id"]="${JTBD_STATUS_BY_ID[$id]},$status"
|
|
87
|
-
fi
|
|
88
|
-
fi
|
|
89
|
-
done
|
|
90
|
-
|
|
91
70
|
# ── Helpers ─────────────────────────────────────────────────────────────────
|
|
92
71
|
|
|
93
72
|
append_hint() {
|
|
@@ -102,22 +81,9 @@ append_hint() {
|
|
|
102
81
|
fi
|
|
103
82
|
}
|
|
104
83
|
|
|
105
|
-
is_deprecated_only() {
|
|
106
|
-
local statuses="$1"
|
|
107
|
-
IFS=',' read -ra arr <<< "$statuses"
|
|
108
|
-
for s in "${arr[@]}"; do
|
|
109
|
-
case "$s" in
|
|
110
|
-
deprecated|superseded) ;;
|
|
111
|
-
*) return 1 ;;
|
|
112
|
-
esac
|
|
113
|
-
done
|
|
114
|
-
return 0
|
|
115
|
-
}
|
|
116
|
-
|
|
117
84
|
# ── Scan packages ───────────────────────────────────────────────────────────
|
|
118
85
|
|
|
119
86
|
total_packages=0
|
|
120
|
-
total_with_jtbd=0
|
|
121
87
|
total_drift_instances=0
|
|
122
88
|
|
|
123
89
|
package_dirs=()
|
|
@@ -142,52 +108,21 @@ for pdir in "${sorted_dirs[@]}"; do
|
|
|
142
108
|
|
|
143
109
|
total_packages=$(( total_packages + 1 ))
|
|
144
110
|
|
|
145
|
-
# Extract distinct JTBD-NNN matches from the README
|
|
146
|
-
cited_ids=()
|
|
147
|
-
while IFS= read -r id; do
|
|
148
|
-
[ -z "$id" ] && continue
|
|
149
|
-
cited_ids+=("$id")
|
|
150
|
-
done < <(grep -oE 'JTBD-[0-9]{3}' "$readme" 2>/dev/null | sort -u)
|
|
151
|
-
|
|
152
|
-
cited_count="${#cited_ids[@]}"
|
|
153
|
-
|
|
154
|
-
has_anchor="no"
|
|
155
|
-
if [ "$cited_count" -gt 0 ]; then
|
|
156
|
-
has_anchor="yes"
|
|
157
|
-
total_with_jtbd=$(( total_with_jtbd + 1 ))
|
|
158
|
-
fi
|
|
159
|
-
|
|
160
111
|
hints=""
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if [ "$cited_count" -eq 0 ]; then
|
|
164
|
-
hints=$(append_hint "$hints" "missing-jtbd-section")
|
|
165
|
-
else
|
|
166
|
-
has_stale=0
|
|
167
|
-
has_deprecated_only=0
|
|
168
|
-
for id in "${cited_ids[@]}"; do
|
|
169
|
-
if [ -n "${JTBD_RESOLVED[$id]:-}" ]; then
|
|
170
|
-
known_count=$(( known_count + 1 ))
|
|
171
|
-
if is_deprecated_only "${JTBD_STATUS_BY_ID[$id]}"; then
|
|
172
|
-
has_deprecated_only=1
|
|
173
|
-
fi
|
|
174
|
-
else
|
|
175
|
-
has_stale=1
|
|
176
|
-
fi
|
|
177
|
-
done
|
|
178
|
-
[ "$has_stale" -eq 1 ] && hints=$(append_hint "$hints" "stale-jtbd-citation")
|
|
179
|
-
[ "$has_deprecated_only" -eq 1 ] && hints=$(append_hint "$hints" "deprecated-jtbd-citation")
|
|
180
|
-
fi
|
|
112
|
+
skills_count=0
|
|
113
|
+
in_readme_count=0
|
|
181
114
|
|
|
182
|
-
#
|
|
183
|
-
#
|
|
115
|
+
# Skill inventory drift: every directory under packages/<plugin>/skills/
|
|
116
|
+
# should be named in the README so the inventory stays current.
|
|
184
117
|
if [ -d "$pdir/skills" ]; then
|
|
185
118
|
for sdir in "$pdir/skills"/*/; do
|
|
186
119
|
[ -d "$sdir" ] || continue
|
|
187
120
|
skill="$(basename "$sdir")"
|
|
188
|
-
|
|
121
|
+
skills_count=$(( skills_count + 1 ))
|
|
122
|
+
if grep -q -F "$skill" "$readme" 2>/dev/null; then
|
|
123
|
+
in_readme_count=$(( in_readme_count + 1 ))
|
|
124
|
+
else
|
|
189
125
|
hints=$(append_hint "$hints" "skill-inventory-drift")
|
|
190
|
-
break
|
|
191
126
|
fi
|
|
192
127
|
done
|
|
193
128
|
fi
|
|
@@ -197,11 +132,11 @@ for pdir in "${sorted_dirs[@]}"; do
|
|
|
197
132
|
total_drift_instances=$(( total_drift_instances + 1 ))
|
|
198
133
|
fi
|
|
199
134
|
|
|
200
|
-
echo "README package=$package
|
|
135
|
+
echo "README package=$package skills=$skills_count in_readme=$in_readme_count drift_hints=$hints"
|
|
201
136
|
done
|
|
202
137
|
|
|
203
138
|
if [ "$total_packages" -gt 0 ]; then
|
|
204
|
-
echo "TOTAL packages=$total_packages
|
|
139
|
+
echo "TOTAL packages=$total_packages drift_instances=$total_drift_instances"
|
|
205
140
|
fi
|
|
206
141
|
|
|
207
142
|
exit 0
|
|
@@ -2,37 +2,36 @@
|
|
|
2
2
|
#
|
|
3
3
|
# packages/retrospective/scripts/test/check-readme-jtbd-currency.bats
|
|
4
4
|
#
|
|
5
|
-
# Behavioural tests for `check-readme-jtbd-currency.sh` — the
|
|
6
|
-
# README
|
|
7
|
-
# pattern of sibling detectors
|
|
8
|
-
# `check-briefing-budgets.bats`, `check-ask-hygiene.bats`).
|
|
5
|
+
# Behavioural tests for `check-readme-jtbd-currency.sh` — the skill-inventory
|
|
6
|
+
# README currency advisory (ADR-069 / P294, superseding ADR-051's JTBD-ID
|
|
7
|
+
# rule). Mirrors the fixture-based pattern of sibling detectors
|
|
8
|
+
# (`check-briefing-budgets.bats`, `check-ask-hygiene.bats`).
|
|
9
9
|
#
|
|
10
|
-
# Tests are behavioural per ADR-005 / ADR-
|
|
11
|
-
# script end-to-end against fixture packages/
|
|
12
|
-
#
|
|
10
|
+
# Tests are behavioural per ADR-005 / ADR-052 / P081 — they exercise the
|
|
11
|
+
# script end-to-end against fixture packages/ trees and assert on stdout /
|
|
12
|
+
# exit code shape. No structural greps of the script source.
|
|
13
13
|
#
|
|
14
|
-
# @problem P152 (No pressure or nudge for documentation currency)
|
|
14
|
+
# @problem P152 (No pressure or nudge for documentation currency — original driver)
|
|
15
|
+
# @problem P294 (ADR-051 superseded — README markets the persona's problem, no JTBD-ID citation)
|
|
15
16
|
# @problem P081 (Structural-content tests are wasteful — behavioural preferred)
|
|
16
|
-
# @adr ADR-
|
|
17
|
+
# @adr ADR-069 (README markets persona problem; skill-inventory currency gate)
|
|
18
|
+
# @adr ADR-051 (superseded — original JTBD-anchored README rule)
|
|
17
19
|
# @adr ADR-013 Rule 6 (non-interactive fail-safe)
|
|
18
|
-
# @adr ADR-005 / ADR-
|
|
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)
|
|
20
|
+
# @adr ADR-005 / ADR-052 (Plugin testing strategy — behavioural tests)
|
|
21
21
|
|
|
22
22
|
SCRIPT="${BATS_TEST_DIRNAME}/../check-readme-jtbd-currency.sh"
|
|
23
23
|
|
|
24
24
|
setup() {
|
|
25
25
|
TEST_DIR="$(mktemp -d)"
|
|
26
26
|
PKG_DIR="$TEST_DIR/packages"
|
|
27
|
-
|
|
28
|
-
mkdir -p "$PKG_DIR" "$JTBD_DIR/plugin-user" "$JTBD_DIR/solo-developer"
|
|
27
|
+
mkdir -p "$PKG_DIR"
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
teardown() {
|
|
32
31
|
rm -rf "$TEST_DIR"
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
# Helper: write a synthetic plugin
|
|
34
|
+
# Helper: write a synthetic plugin README into PKG_DIR
|
|
36
35
|
make_plugin() {
|
|
37
36
|
local name="$1"
|
|
38
37
|
local readme_content="$2"
|
|
@@ -40,25 +39,6 @@ make_plugin() {
|
|
|
40
39
|
printf '%s\n' "$readme_content" > "$PKG_DIR/$name/README.md"
|
|
41
40
|
}
|
|
42
41
|
|
|
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
42
|
# ── Pre-checks ──────────────────────────────────────────────────────────────
|
|
63
43
|
|
|
64
44
|
@test "script file exists and is executable" {
|
|
@@ -67,149 +47,117 @@ EOF
|
|
|
67
47
|
}
|
|
68
48
|
|
|
69
49
|
@test "missing packages dir exits 2 with error message on stderr" {
|
|
70
|
-
run bash "$SCRIPT" "$TEST_DIR/does-not-exist"
|
|
50
|
+
run bash "$SCRIPT" "$TEST_DIR/does-not-exist"
|
|
71
51
|
[ "$status" -eq 2 ]
|
|
72
52
|
[[ "$output" == *"packages dir not found"* ]]
|
|
73
53
|
}
|
|
74
54
|
|
|
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
55
|
@test "empty packages dir exits 0 with empty stdout" {
|
|
82
|
-
run bash "$SCRIPT" "$PKG_DIR"
|
|
56
|
+
run bash "$SCRIPT" "$PKG_DIR"
|
|
83
57
|
[ "$status" -eq 0 ]
|
|
84
58
|
[ -z "$output" ]
|
|
85
59
|
}
|
|
86
60
|
|
|
87
|
-
|
|
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" {
|
|
61
|
+
@test "legacy second argument (former jtbd-dir) is accepted and ignored" {
|
|
106
62
|
make_plugin "stub" "# @windyroad/stub
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
make_jtbd "solo-developer" "JTBD-007" "keep-plugins-current" "proposed"
|
|
110
|
-
|
|
111
|
-
run bash "$SCRIPT" "$PKG_DIR" "$JTBD_DIR"
|
|
63
|
+
A plugin that solves a problem."
|
|
64
|
+
run bash "$SCRIPT" "$PKG_DIR" "/some/legacy/jtbd/path"
|
|
112
65
|
[ "$status" -eq 0 ]
|
|
113
66
|
[[ "$output" == *"package=stub"* ]]
|
|
114
|
-
[[ "$output" == *"
|
|
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"* ]]
|
|
67
|
+
[[ "$output" == *"TOTAL packages=1 drift_instances=0"* ]]
|
|
119
68
|
}
|
|
120
69
|
|
|
121
|
-
# ──
|
|
70
|
+
# ── Drift fixture: skills/ directory not named in README ────────────────────
|
|
122
71
|
|
|
123
|
-
@test "
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
72
|
+
@test "drift fixture: skills/ directory not named in README emits skill-inventory-drift" {
|
|
73
|
+
mkdir -p "$PKG_DIR/stub/skills/orphan-widget"
|
|
74
|
+
printf '%s\n' "# @windyroad/stub
|
|
75
|
+
This README documents no skills." > "$PKG_DIR/stub/README.md"
|
|
128
76
|
|
|
129
|
-
run bash "$SCRIPT" "$PKG_DIR"
|
|
77
|
+
run bash "$SCRIPT" "$PKG_DIR"
|
|
130
78
|
[ "$status" -eq 0 ]
|
|
131
79
|
[[ "$output" == *"package=stub"* ]]
|
|
132
|
-
[[ "$output" == *"
|
|
133
|
-
[[ "$output" == *"
|
|
134
|
-
[[ "$output" == *"
|
|
135
|
-
[[ "$output" == *"
|
|
136
|
-
[[ "$output" == *"TOTAL packages=1 with_jtbd=1 drift_instances=1"* ]]
|
|
80
|
+
[[ "$output" == *"skills=1"* ]]
|
|
81
|
+
[[ "$output" == *"in_readme=0"* ]]
|
|
82
|
+
[[ "$output" == *"drift_hints="*"skill-inventory-drift"* ]]
|
|
83
|
+
[[ "$output" == *"TOTAL packages=1 drift_instances=1"* ]]
|
|
137
84
|
}
|
|
138
85
|
|
|
139
|
-
# ──
|
|
86
|
+
# ── Clean fixture: every skill named in README ──────────────────────────────
|
|
140
87
|
|
|
141
|
-
@test "
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
88
|
+
@test "clean fixture: all skills/ directories named in README emit empty drift_hints" {
|
|
89
|
+
mkdir -p "$PKG_DIR/stub/skills/manage-secret"
|
|
90
|
+
printf '%s\n' "# @windyroad/stub
|
|
91
|
+
Run /wr-stub:manage-secret to rotate a secret." > "$PKG_DIR/stub/README.md"
|
|
145
92
|
|
|
146
|
-
run bash "$SCRIPT" "$PKG_DIR"
|
|
93
|
+
run bash "$SCRIPT" "$PKG_DIR"
|
|
147
94
|
[ "$status" -eq 0 ]
|
|
148
95
|
[[ "$output" == *"package=stub"* ]]
|
|
149
|
-
[[ "$output" == *"
|
|
150
|
-
[[ "$output" == *"
|
|
151
|
-
[[ "$output"
|
|
152
|
-
[[ "$output" == *"
|
|
153
|
-
[[ "$output" == *"drift_instances=1"* ]]
|
|
96
|
+
[[ "$output" == *"skills=1"* ]]
|
|
97
|
+
[[ "$output" == *"in_readme=1"* ]]
|
|
98
|
+
[[ "$output" != *"skill-inventory-drift"* ]]
|
|
99
|
+
[[ "$output" == *"TOTAL packages=1 drift_instances=0"* ]]
|
|
154
100
|
}
|
|
155
101
|
|
|
156
|
-
# ──
|
|
102
|
+
# ── Regression guard: a JTBD-NNN citation must NOT change the verdict ────────
|
|
103
|
+
# (ADR-069 removed the JTBD-ID rule; the detector no longer reads JTBD IDs.)
|
|
157
104
|
|
|
158
|
-
@test "
|
|
159
|
-
mkdir -p "$PKG_DIR/stub/skills/
|
|
105
|
+
@test "regression: README with NO JTBD-NNN citation is clean when its skills are named" {
|
|
106
|
+
mkdir -p "$PKG_DIR/stub/skills/do-thing"
|
|
160
107
|
printf '%s\n' "# @windyroad/stub
|
|
161
|
-
|
|
162
|
-
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
108
|
+
Markets a problem in plain prose. Run /wr-stub:do-thing. No JTBD IDs here." > "$PKG_DIR/stub/README.md"
|
|
163
109
|
|
|
164
|
-
run bash "$SCRIPT" "$PKG_DIR"
|
|
110
|
+
run bash "$SCRIPT" "$PKG_DIR"
|
|
165
111
|
[ "$status" -eq 0 ]
|
|
166
|
-
[[ "$output"
|
|
167
|
-
[[ "$output"
|
|
168
|
-
[[ "$output" == *"drift_instances=
|
|
112
|
+
[[ "$output" != *"missing-jtbd-section"* ]]
|
|
113
|
+
[[ "$output" != *"stale-jtbd-citation"* ]]
|
|
114
|
+
[[ "$output" == *"TOTAL packages=1 drift_instances=0"* ]]
|
|
169
115
|
}
|
|
170
116
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
117
|
+
# ── Package with no skills/ directory ───────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
@test "package without a skills/ directory reports skills=0 and no drift" {
|
|
120
|
+
make_plugin "umbrella" "# @windyroad/umbrella
|
|
121
|
+
A marketplace package with no skills of its own."
|
|
176
122
|
|
|
177
|
-
run bash "$SCRIPT" "$PKG_DIR"
|
|
123
|
+
run bash "$SCRIPT" "$PKG_DIR"
|
|
178
124
|
[ "$status" -eq 0 ]
|
|
179
|
-
[[ "$output" == *"package=
|
|
125
|
+
[[ "$output" == *"package=umbrella"* ]]
|
|
126
|
+
[[ "$output" == *"skills=0"* ]]
|
|
127
|
+
[[ "$output" == *"in_readme=0"* ]]
|
|
180
128
|
[[ "$output" != *"skill-inventory-drift"* ]]
|
|
181
129
|
}
|
|
182
130
|
|
|
183
131
|
# ── Multi-package aggregation ───────────────────────────────────────────────
|
|
184
132
|
|
|
185
133
|
@test "multi-package aggregation: emits one README line per package + TOTAL summary" {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
134
|
+
mkdir -p "$PKG_DIR/alpha/skills/run-alpha"
|
|
135
|
+
printf '%s\n' "# alpha
|
|
136
|
+
Run /wr-alpha:run-alpha." > "$PKG_DIR/alpha/README.md"
|
|
137
|
+
mkdir -p "$PKG_DIR/bravo/skills/lost-skill"
|
|
138
|
+
printf '%s\n' "# bravo
|
|
139
|
+
README forgot to mention its skill." > "$PKG_DIR/bravo/README.md"
|
|
190
140
|
make_plugin "charlie" "# charlie
|
|
191
|
-
|
|
192
|
-
make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
|
|
193
|
-
make_jtbd "solo-developer" "JTBD-007" "keep-plugins-current" "proposed"
|
|
141
|
+
No skills directory at all."
|
|
194
142
|
|
|
195
|
-
run bash "$SCRIPT" "$PKG_DIR"
|
|
143
|
+
run bash "$SCRIPT" "$PKG_DIR"
|
|
196
144
|
[ "$status" -eq 0 ]
|
|
197
145
|
[[ "$output" == *"package=alpha"* ]]
|
|
198
146
|
[[ "$output" == *"package=bravo"* ]]
|
|
199
147
|
[[ "$output" == *"package=charlie"* ]]
|
|
200
|
-
# bravo
|
|
201
|
-
[[ "$output" == *"TOTAL packages=3
|
|
148
|
+
# only bravo drifts (lost-skill not named)
|
|
149
|
+
[[ "$output" == *"TOTAL packages=3 drift_instances=1"* ]]
|
|
202
150
|
}
|
|
203
151
|
|
|
204
152
|
# ── Package without README is skipped ───────────────────────────────────────
|
|
205
153
|
|
|
206
154
|
@test "package without README.md is silently skipped" {
|
|
207
|
-
mkdir -p "$PKG_DIR/no-readme"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
155
|
+
mkdir -p "$PKG_DIR/no-readme/skills/x"
|
|
156
|
+
mkdir -p "$PKG_DIR/with-readme/skills/y"
|
|
157
|
+
printf '%s\n' "# with-readme
|
|
158
|
+
Run /wr-with-readme:y." > "$PKG_DIR/with-readme/README.md"
|
|
211
159
|
|
|
212
|
-
run bash "$SCRIPT" "$PKG_DIR"
|
|
160
|
+
run bash "$SCRIPT" "$PKG_DIR"
|
|
213
161
|
[ "$status" -eq 0 ]
|
|
214
162
|
[[ "$output" != *"package=no-readme"* ]]
|
|
215
163
|
[[ "$output" == *"package=with-readme"* ]]
|
|
@@ -159,22 +159,22 @@ The shape mirrors P068's Step 4a Verification-close housekeeping: glob / evidenc
|
|
|
159
159
|
|
|
160
160
|
6. **Non-interactive / AFK fallback (ADR-013 Rule 6)**: when `AskUserQuestion` is unavailable (autonomous retro, batch session-wrap), do NOT auto-create tickets — record each detection in the retro summary's new **Pipeline Instability** section with its category, citations, and dedup status (`new` or `matches P<NNN>`). The user reviews on return and runs `/wr-itil:manage-problem` per accepted detection. Same trust-boundary shape as Step 4a's AFK deferral: surface the evidence, defer the decision. This matches the user's documented preference (feedback_verify_from_own_observation.md memory): surface observations from the agent's own in-session activity, but ticket-creation decisions remain user-confirmed.
|
|
161
161
|
|
|
162
|
-
**
|
|
162
|
+
**README inventory currency advisory (ADR-069, P294).** Beyond the categorical pipeline-instability detection above, Step 2b also runs the README inventory-currency detector on every retro to surface drift between each plugin's shipped skills and the skills its README names. (Under superseded ADR-051 this detector also flagged JTBD-ID-citation drift; ADR-069 superseded that — READMEs market the persona's problem derived FROM the JTBD but MUST NOT cite IDs — so the detector is now skill-inventory-only.) The surfacing channel is the retro summary's Pipeline Instability section.
|
|
163
163
|
|
|
164
|
-
**Mechanism**: invoke `wr-retrospective-check-readme-jtbd-currency` (resolves on `$PATH` to `packages/retrospective/scripts/check-readme-jtbd-currency.sh` per ADR-049 naming grammar). The script walks `packages/*/README.md
|
|
164
|
+
**Mechanism**: invoke `wr-retrospective-check-readme-jtbd-currency` (resolves on `$PATH` to `packages/retrospective/scripts/check-readme-jtbd-currency.sh` per ADR-049 naming grammar; filename retained per ADR-069). The script walks `packages/*/README.md` and, for each, checks that every directory under `packages/<plugin>/skills/` is named in the README, emitting:
|
|
165
165
|
|
|
166
|
-
- Per-package: `README package=<name>
|
|
167
|
-
- Trailing summary: `TOTAL packages=<N>
|
|
166
|
+
- Per-package: `README package=<name> skills=<N> in_readme=<M> drift_hints=<csv>`
|
|
167
|
+
- Trailing summary: `TOTAL packages=<N> drift_instances=<K>`
|
|
168
168
|
|
|
169
|
-
Drift-hint vocabulary: `
|
|
169
|
+
Drift-hint vocabulary: `skill-inventory-drift` (inventory-only per ADR-069). Always exits 0 — the script is advisory per ADR-013 Rule 6 / ADR-040 declarative-first; the commit-hook `retrospective-readme-jtbd-currency.sh` is the load-bearing surface.
|
|
170
170
|
|
|
171
171
|
**Interpretation**:
|
|
172
172
|
|
|
173
|
-
1. **`drift_instances == 0`** — emit a one-line `
|
|
173
|
+
1. **`drift_instances == 0`** — emit a one-line `README inventory currency: clean (<N> packages)` to the retro summary's Pipeline Instability section.
|
|
174
174
|
2. **`drift_instances ≥ 1`** — emit the detector's full per-package output as a fenced code block in the Pipeline Instability section. Each affected package's `drift_hints` enumerate the specific findings. Per ADR-013 Rule 6, the user reviews on return and tickets via `/wr-itil:manage-problem` per accepted finding (same trust-boundary shape as the categorical detection above — surface the evidence; defer the ticket-creation decision).
|
|
175
175
|
3. **Detector failure** — if the detector exits non-zero (parse error, missing `packages/` or `docs/jtbd/` directories, runtime exception), log the failure inline as `JTBD currency advisory failed: <stderr>` but do NOT halt the retro. Same fail-open contract as Step 3's `check-briefing-budgets.sh` defensive trip — the cheap layer trips silently and degrades to a one-line pointer rather than blocking the retro.
|
|
176
176
|
|
|
177
|
-
**
|
|
177
|
+
**Already load-bearing (ADR-069)**: the commit-hook `retrospective-readme-jtbd-currency.sh` denies `git commit` when `drift_instances ≥ 1` (skill-inventory-drift), per the carried-forward load-bearing-from-the-start-for-drift-class driver. This Step 2b advisory is the backup / cross-cutting surface — it catches drift in sessions that bypass the commit-hook (`BYPASS_JTBD_CURRENCY=1`) and summarises across packages; it is not the gate.
|
|
178
178
|
|
|
179
179
|
**Interaction with other surfaces:**
|
|
180
180
|
|