@windyroad/retrospective 0.20.4 → 0.21.0-preview.387

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.
@@ -66,5 +66,5 @@
66
66
  }
67
67
  },
68
68
  "name": "wr-retrospective",
69
- "version": "0.20.4"
69
+ "version": "0.21.0"
70
70
  }
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 JTBD currency per ADR-051, SKILL.md runtime budgets per ADR-054) so doc-content drift surfaces in the retro summary alongside session-level reflections.
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-051 Phase 1 detector — surfaces drift between plugin READMEs and the JTBD jobs they cite |
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
- # P159: PreToolUse:Bash hook — denies `git commit` invocations whose
3
- # post-commit working tree exhibits JTBD-currency drift in any
4
- # packages/<plugin>/README.md (no JTBD-NNN anchor, stale or
5
- # deprecated-only citation, or skill directory missing from README).
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
- # Hook-level enforcement at commit time replaces ADR-051 Phase 1's
8
- # retro-time advisory surface (shipped under P158, df47ad1). The user
9
- # correction (P159) and the architect verdict identified the retro
10
- # surface as too late: the most-common drift class (contributor adds
11
- # skill/hook/agent and forgets the README) ships in a commit that
12
- # does not touch README.md, so a retro-time consumer sees the drift
13
- # only after the contributor has already committed.
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
- # Detection delegates to the existing detector script
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
- # `./docs/jtbd/`). The hook reads the detector's
19
- # `TOTAL packages=<N> with_jtbd=<M> drift_instances=<K>` summary and
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 ADR-051's structural anchors.
143
- # Adopter projects without `./packages/` or `./docs/jtbd/` are not
144
- # subject to the rule; the hook is a no-op for them.
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" "./docs/jtbd" 2>/dev/null) || exit 0
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 ~163 bytes for worst-case slug +
192
- # hint). Names the offending plugin slug, the primary drift hint,
193
- # the wr-jtbd:agent recovery path with hand-edit fallback (graceful
194
- # degradation per architect F advisory), the BYPASS env, and the
195
- # P159 cite.
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
- # P159: retrospective-readme-jtbd-currency.sh PreToolUse:Bash hook must
4
- # deny `git commit` invocations whose post-commit working tree exhibits
5
- # JTBD-currency drift (no JTBD-NNN anchor in a plugin README, stale or
6
- # deprecated-only citations, or skills/<dir>/ missing from the README).
7
- # Hook-level enforcement at commit time replaces ADR-051 Phase 1's
8
- # retro-time advisory surface, which the user correction (P159) and the
9
- # architect verdict identify as too late: the most-common drift class
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
- # Detection delegates to the existing
15
- # `packages/retrospective/scripts/check-readme-jtbd-currency.sh`
16
- # detector, which the hook invokes against the project's working tree
17
- # (`./packages/` + `./docs/jtbd/`). The hook reads the detector's
18
- # `TOTAL packages=<N> with_jtbd=<M> drift_instances=<K>` summary line
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
- # Per ADR-005 (plugin testing strategy) — hook bats live under
22
- # packages/<plugin>/hooks/test/ and assert on emitted JSON, not source
23
- # content. Per ADR-052 / P081 behavioural; no source greps. Per
24
- # ADR-045 Pattern 1 allow paths emit 0 bytes; deny-band ≤300 bytes.
25
- # Per ADR-013 Rule 1 — deny redirects to mechanical recovery (here: the
26
- # wr-jtbd:agent for prose-weaving guidance, with hand-edit fallback).
27
- # Per ADR-013 Rule 6fail-open outside a git work tree, on parse
28
- # errors, or in projects without ADR-051's structural anchors
29
- # (./packages/ or ./docs/jtbd/) so adopter projects are not blocked.
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: build a stub project layout with a drifted plugin README.
60
- # README has no JTBD-NNN citation; jtbd dir has one resolving job.
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 docs/jtbd/plugin-user
63
- printf '%s\n' "# @windyroad/stub" "no jtbd anchor here" > packages/stub/README.md
64
- cat > docs/jtbd/plugin-user/JTBD-302-trust-readme.proposed.md <<'EOF'
65
- ---
66
- status: proposed
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 docs/jtbd/plugin-user
80
- printf '%s\n' "# @windyroad/stub" "Serves JTBD-302." > packages/stub/README.md
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: drifted README (no JTBD-NNN cite) on git commit triggers 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" == *"P159"* ]]
78
+ [[ "$output" == *"P294"* ]]
118
79
  }
119
80
 
120
- @test "deny: skill-inventory-drift on git commit triggers deny" {
121
- make_skill_drift_project
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" == *"\"permissionDecision\": \"deny\""* ]]
85
+ [[ "$output" == *"stub"* ]]
125
86
  }
126
87
 
127
- @test "deny message names the offending plugin slug" {
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" == *"stub"* ]]
92
+ [[ "$output" == *"name the skill in the README"* ]]
132
93
  }
133
94
 
134
- @test "deny message names the wr-jtbd:agent recovery path" {
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" == *"wr-jtbd"* ]]
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: chore release commit (chore: version packages) is subject to the gate" {
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 (cites resolving JTBD-NNN) on git commit allows the commit" {
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/, no docs/jtbd/ — adopter project shape.
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/ but no docs/jtbd/ exits 0 without deny (fail-open)" {
221
- mkdir -p packages/stub
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 and
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" == *"P159"* ]]
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/retrospective",
3
- "version": "0.20.4",
3
+ "version": "0.21.0-preview.387",
4
4
  "description": "Session retrospectives that update briefings and create problem tickets",
5
5
  "bin": {
6
6
  "windyroad-retrospective": "./bin/install.mjs"
@@ -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-051 (JTBD-anchored README rule).
5
- # Walks packages/*/README.md and emits a drift signal per package:
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
- # - has_jtbd_anchor=<yes|no> at least one JTBD-\d{3} match in the README
8
- # - cited_jobs=<count> count of distinct JTBD IDs cited
9
- # - known_jobs=<count> count of cited IDs that resolve to a current
10
- # docs/jtbd/<persona>/JTBD-NNN-*.md (any status)
11
- # - drift_hints=<comma-list> — signal vocabulary:
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> with_jtbd=<M> drift_instances=<K>
15
+ # TOTAL packages=<N> drift_instances=<K>
19
16
  #
20
- # Exit code is always 0 — the script is advisory per ADR-013 Rule 6
21
- # fail-safe / ADR-040 declarative-first / ADR-051 Phase 1.
22
- # Drift count is emitted as data on stdout; downstream consumers
23
- # (run-retro Step 2b future wiring, release-pre-flight habit, Phase 2
24
- # escalation per ADR-051 Phase 2 criterion) decide whether to act.
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>] [<jtbd-dir>]
35
+ # check-readme-jtbd-currency.sh [<packages-dir>]
28
36
  #
29
37
  # Defaults:
30
38
  # <packages-dir> = ./packages
31
- # <jtbd-dir> = ./docs/jtbd
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 or jtbd-dir missing or unreadable)
45
+ # 2 = parse error (packages-dir missing or unreadable)
36
46
  #
37
47
  # Output format (one line per package, alphabetical):
38
- # README package=<name> has_jtbd_anchor=<yes|no> cited_jobs=<N> known_jobs=<M> drift_hints=<csv>
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 problem)
41
- # @adr ADR-051 (JTBD-anchored README with declarative drift advisory this script's normative source)
42
- # @adr ADR-008 (JTBD directory structurethe resolution target layout)
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-005 / ADR-037 (Plugin testing strategy behavioural tests via bats)
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
- known_count=0
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
- # Soft heuristic: skill inventory drift every directory under
183
- # packages/<plugin>/skills/ should be named in the README.
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
- if ! grep -q -F "$skill" "$readme" 2>/dev/null; then
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 has_jtbd_anchor=$has_anchor cited_jobs=$cited_count known_jobs=$known_count drift_hints=$hints"
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 with_jtbd=$total_with_jtbd drift_instances=$total_drift_instances"
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 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`).
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-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.
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-051 (JTBD-anchored README rule with declarative drift advisory)
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-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)
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
- JTBD_DIR="$TEST_DIR/docs/jtbd"
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 layout into PKG_DIR
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" "$JTBD_DIR"
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" "$JTBD_DIR"
56
+ run bash "$SCRIPT" "$PKG_DIR"
83
57
  [ "$status" -eq 0 ]
84
58
  [ -z "$output" ]
85
59
  }
86
60
 
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" {
61
+ @test "legacy second argument (former jtbd-dir) is accepted and ignored" {
106
62
  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"
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" == *"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"* ]]
67
+ [[ "$output" == *"TOTAL packages=1 drift_instances=0"* ]]
119
68
  }
120
69
 
121
- # ── Stale-ID fixture: cited JTBD does not resolve ───────────────────────────
70
+ # ── Drift fixture: skills/ directory not named in README ────────────────────
122
71
 
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"
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" "$JTBD_DIR"
77
+ run bash "$SCRIPT" "$PKG_DIR"
130
78
  [ "$status" -eq 0 ]
131
79
  [[ "$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"* ]]
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
- # ── Deprecated-only fixture: cited JTBD resolves only to deprecated ─────────
86
+ # ── Clean fixture: every skill named in README ──────────────────────────────
140
87
 
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"
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" "$JTBD_DIR"
93
+ run bash "$SCRIPT" "$PKG_DIR"
147
94
  [ "$status" -eq 0 ]
148
95
  [[ "$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"* ]]
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
- # ── Skill-inventory-drift fixture ───────────────────────────────────────────
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 "skill-inventory-drift fixture: skills/ directory not named in README emits skill-inventory-drift hint" {
159
- mkdir -p "$PKG_DIR/stub/skills/orphan-widget"
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
- This plugin serves JTBD-302." > "$PKG_DIR/stub/README.md"
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" "$JTBD_DIR"
110
+ run bash "$SCRIPT" "$PKG_DIR"
165
111
  [ "$status" -eq 0 ]
166
- [[ "$output" == *"package=stub"* ]]
167
- [[ "$output" == *"skill-inventory-drift"* ]]
168
- [[ "$output" == *"drift_instances=1"* ]]
112
+ [[ "$output" != *"missing-jtbd-section"* ]]
113
+ [[ "$output" != *"stale-jtbd-citation"* ]]
114
+ [[ "$output" == *"TOTAL packages=1 drift_instances=0"* ]]
169
115
  }
170
116
 
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"
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" "$JTBD_DIR"
123
+ run bash "$SCRIPT" "$PKG_DIR"
178
124
  [ "$status" -eq 0 ]
179
- [[ "$output" == *"package=stub"* ]]
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
- make_plugin "alpha" "# alpha
187
- Serves JTBD-302."
188
- make_plugin "bravo" "# bravo
189
- No anchor here."
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
- 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"
141
+ No skills directory at all."
194
142
 
195
- run bash "$SCRIPT" "$PKG_DIR" "$JTBD_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 has missing-jtbd-section, alpha + charlie are clean
201
- [[ "$output" == *"TOTAL packages=3 with_jtbd=2 drift_instances=1"* ]]
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
- make_plugin "with-readme" "# with-readme
209
- Serves JTBD-302."
210
- make_jtbd "plugin-user" "JTBD-302" "trust-readme" "proposed"
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" "$JTBD_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
- **JTBD currency advisory (ADR-051 Phase 1, P158).** Beyond the categorical pipeline-instability detection above, Step 2b also runs the ADR-051 Phase 1 advisory detector on every retro to surface README-content drift between the suite's plugin READMEs and the documented JTBD jobs they cite. Drift here is not pipeline-instability in the conventional sense it is content-currency but the surfacing channel is the same: the retro summary's Pipeline Instability section. Wiring deferred from the original ADR-051 landing per Confirmation criterion 5 ("wiring into `/wr-retrospective:run-retro` Step 2b is deferred to a follow-on iter once the detector is empirically validated against current READMEs"); the empirical-validation precondition was met by commit `8df1692` (the retroactive refresh of 12 plugin READMEs to JTBD-anchored shape; detector now reports `drift_instances=1`).
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`, greps each for `JTBD-\d{3}` citations, resolves cited IDs against `docs/jtbd/<persona>/JTBD-NNN-*.md` (any status suffix), and emits:
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> has_jtbd_anchor=<yes|no> cited_jobs=<N> known_jobs=<M> drift_hints=<csv>`
167
- - Trailing summary: `TOTAL packages=<N> with_jtbd=<M> drift_instances=<K>`
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: `missing-jtbd-section`, `stale-jtbd-citation`, `deprecated-jtbd-citation`, `skill-inventory-drift`. Always exits 0 — the script is advisory per ADR-013 Rule 6 / ADR-040 declarative-first / ADR-051 Phase 1.
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 `JTBD currency advisory: clean (<N> packages)` to the retro summary's Pipeline Instability section.
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
- **Phase 2 escalation criterion (per ADR-051)**: if the detector emits `drift_instances 2` across 3 consecutive `chore: version packages` releases without correction, escalate to a load-bearing hook per ADR-013 Rule 6 escalation pattern. Phase 2 is out of scope for this wiring; the criterion is captured here so future contributors know the bar.
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