@windyroad/itil 0.61.1 → 0.61.2

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.
Files changed (171) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +7 -2
  3. package/agents/test/fixtures/proceed-new-genuinely-new.md +0 -43
  4. package/agents/test/fixtures/proceed-new-subtle-sibling.md +0 -47
  5. package/agents/test/fixtures/regression-p347-vs-p346.md +0 -54
  6. package/agents/test/hang-off-check.bats +0 -225
  7. package/hooks/test/block-list.bats +0 -153
  8. package/hooks/test/command-detect.bats +0 -181
  9. package/hooks/test/itil-assistant-output-gate.bats +0 -114
  10. package/hooks/test/itil-assistant-output-review.bats +0 -224
  11. package/hooks/test/itil-bash-polling-antipattern-detect.bats +0 -154
  12. package/hooks/test/itil-changeset-discipline.bats +0 -603
  13. package/hooks/test/itil-claude-space-protection.bats +0 -294
  14. package/hooks/test/itil-commit-trailer-transition-advisory.bats +0 -80
  15. package/hooks/test/itil-correction-detect.bats +0 -189
  16. package/hooks/test/itil-deferral-cadence-gate.bats +0 -187
  17. package/hooks/test/itil-fictional-defer-detect.bats +0 -292
  18. package/hooks/test/itil-fix-title-lifecycle-advisory.bats +0 -129
  19. package/hooks/test/itil-mid-loop-ask-detect.bats +0 -220
  20. package/hooks/test/itil-no-implement-draft-gate.bats +0 -210
  21. package/hooks/test/itil-pending-questions-surface.bats +0 -307
  22. package/hooks/test/itil-readme-refresh-discipline.bats +0 -642
  23. package/hooks/test/itil-rfc-oversight-nudge.bats +0 -83
  24. package/hooks/test/itil-rfc-trailer-advisory.bats +0 -375
  25. package/hooks/test/itil-story-mirror-migration-nudge.bats +0 -122
  26. package/hooks/test/manage-problem-enforce-create.bats +0 -424
  27. package/hooks/test/p057-staging-trap-detect.bats +0 -231
  28. package/hooks/test/pre-publish-intake-gate.bats +0 -144
  29. package/hooks/test/runtime-sid-marker.bats +0 -90
  30. package/hooks/test/session-id.bats +0 -370
  31. package/scripts/migrate-problems-add-type.sh +0 -116
  32. package/scripts/test/catchup-scan.bats +0 -274
  33. package/scripts/test/check-afk-accept-eligible.bats +0 -310
  34. package/scripts/test/check-fail-soft-skip-discipline.bats +0 -172
  35. package/scripts/test/check-fix-rfc-trace.bats +0 -164
  36. package/scripts/test/check-locale-discipline.bats +0 -291
  37. package/scripts/test/check-problems-readme-budget.bats +0 -192
  38. package/scripts/test/check-rfc-has-stories.bats +0 -69
  39. package/scripts/test/check-rfc-rejected-alternatives.bats +0 -108
  40. package/scripts/test/check-rfc-stories-ratified.bats +0 -82
  41. package/scripts/test/check-upstream-responses.bats +0 -503
  42. package/scripts/test/classify-readme-drift.bats +0 -392
  43. package/scripts/test/derive-first-dispatch.bats +0 -274
  44. package/scripts/test/derive-release-vehicle.bats +0 -454
  45. package/scripts/test/detect-unratified-stories-maps.bats +0 -60
  46. package/scripts/test/dual-tolerant-glob-rfc-002-t2.bats +0 -309
  47. package/scripts/test/effort-tally.bats +0 -170
  48. package/scripts/test/evaluate-relevance.bats +0 -773
  49. package/scripts/test/kind-a-commands.bats +0 -46
  50. package/scripts/test/mark-create-gate.bats +0 -59
  51. package/scripts/test/mark-story-oversight-confirmed.bats +0 -53
  52. package/scripts/test/migrate-problems-add-type.bats +0 -280
  53. package/scripts/test/migrate-story-status-mirror.bats +0 -167
  54. package/scripts/test/no-type-regression-guard.bats +0 -74
  55. package/scripts/test/oversight-mirror-corpus-lint.bats +0 -150
  56. package/scripts/test/plugin-exercise-index.bats +0 -386
  57. package/scripts/test/plugin-maturity-doc-lint.bats +0 -470
  58. package/scripts/test/plugin-maturity-populate.bats +0 -631
  59. package/scripts/test/plugin-maturity-render.bats +0 -469
  60. package/scripts/test/plugin-validate-ci-gate.bats +0 -208
  61. package/scripts/test/reconcile-readme.bats +0 -1007
  62. package/scripts/test/reconcile-rfcs.bats +0 -496
  63. package/scripts/test/reconcile-stories.bats +0 -173
  64. package/scripts/test/reconcile-story-maps.bats +0 -74
  65. package/scripts/test/release-watch-poll-loop.bats +0 -180
  66. package/scripts/test/resolve-governance-plugin-dirs.bats +0 -78
  67. package/scripts/test/rfc-stories-extension.bats +0 -175
  68. package/scripts/test/skill-invocations.bats +0 -535
  69. package/scripts/test/skill-md-dual-tolerant-coverage-rfc-002-t3.bats +0 -374
  70. package/scripts/test/story-oversight-lib.bats +0 -190
  71. package/scripts/test/update-jtbd-references-related-problems.bats +0 -178
  72. package/scripts/test/update-problem-references-section.bats +0 -242
  73. package/scripts/test/update-problem-rfcs-section.bats +0 -242
  74. package/scripts/test/update-references-section-sibling-helpers.bats +0 -80
  75. package/scripts/test/update-rfc-commits-section.bats +0 -77
  76. package/scripts/test/verify-iter-summary.bats +0 -196
  77. package/scripts/test/working-the-problem-traversal.bats +0 -116
  78. package/skills/capture-problem/test/capture-problem-step-1-5b-jtbd-trace.bats +0 -398
  79. package/skills/capture-problem/test/capture-problem.bats +0 -389
  80. package/skills/capture-story/test/capture-story-behavioural.bats +0 -317
  81. package/skills/capture-story-map/test/capture-story-map-behavioural.bats +0 -98
  82. package/skills/close-incident/test/close-incident-contract.bats +0 -146
  83. package/skills/link-incident/test/link-incident-contract.bats +0 -125
  84. package/skills/list-incidents/test/list-incidents-contract.bats +0 -115
  85. package/skills/list-problems/test/list-problems-contract.bats +0 -100
  86. package/skills/list-stories/test/list-stories-contract.bats +0 -127
  87. package/skills/list-story-maps/test/list-story-maps-contract.bats +0 -46
  88. package/skills/manage-incident/test/manage-incident-adr-044-contract.bats +0 -206
  89. package/skills/manage-incident/test/manage-incident-close-forwarder.bats +0 -67
  90. package/skills/manage-incident/test/manage-incident-link-forwarder.bats +0 -67
  91. package/skills/manage-incident/test/manage-incident-list-forwarder.bats +0 -68
  92. package/skills/manage-incident/test/manage-incident-mitigate-forwarder.bats +0 -66
  93. package/skills/manage-incident/test/manage-incident-restore-forwarder.bats +0 -66
  94. package/skills/manage-incident/test/manage-incident.bats +0 -171
  95. package/skills/manage-problem/test/manage-problem-adr-044-step4-derive-first.bats +0 -151
  96. package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +0 -53
  97. package/skills/manage-problem/test/manage-problem-concern-boundary.bats +0 -64
  98. package/skills/manage-problem/test/manage-problem-effort-buckets.bats +0 -56
  99. package/skills/manage-problem/test/manage-problem-external-root-cause-detection.bats +0 -119
  100. package/skills/manage-problem/test/manage-problem-first-run-intake-prompt.bats +0 -43
  101. package/skills/manage-problem/test/manage-problem-git-mv-restage-reminder.bats +0 -48
  102. package/skills/manage-problem/test/manage-problem-id-collision-guard.bats +0 -39
  103. package/skills/manage-problem/test/manage-problem-list-forwarder.bats +0 -66
  104. package/skills/manage-problem/test/manage-problem-next-id-origin-lookup.bats +0 -50
  105. package/skills/manage-problem/test/manage-problem-no-prose-options.bats +0 -74
  106. package/skills/manage-problem/test/manage-problem-output-formatting.bats +0 -30
  107. package/skills/manage-problem/test/manage-problem-parked-and-cache.bats +0 -73
  108. package/skills/manage-problem/test/manage-problem-readme-refresh-on-transition.bats +0 -62
  109. package/skills/manage-problem/test/manage-problem-readme-tie-break-order.bats +0 -318
  110. package/skills/manage-problem/test/manage-problem-readme-vq-sort-order.bats +0 -206
  111. package/skills/manage-problem/test/manage-problem-release-vehicle-seed.bats +0 -83
  112. package/skills/manage-problem/test/manage-problem-review-forwarder.bats +0 -71
  113. package/skills/manage-problem/test/manage-problem-step-9d-recovery-path.bats +0 -97
  114. package/skills/manage-problem/test/manage-problem-transition-forwarder.bats +0 -96
  115. package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +0 -365
  116. package/skills/manage-problem/test/manage-problem-verification-detection.bats +0 -64
  117. package/skills/manage-problem/test/manage-problem-verification-pending.bats +0 -114
  118. package/skills/manage-problem/test/manage-problem-work-forwarder.bats +0 -88
  119. package/skills/manage-story/test/manage-story-contract.bats +0 -174
  120. package/skills/manage-story-map/test/manage-story-map-contract.bats +0 -63
  121. package/skills/mitigate-incident/test/mitigate-incident-contract.bats +0 -258
  122. package/skills/reconcile-readme/test/reconcile-readme-contract.bats +0 -201
  123. package/skills/report-upstream/test/report-upstream-contract.bats +0 -394
  124. package/skills/restore-incident/test/restore-incident-contract.bats +0 -170
  125. package/skills/review-problems/test/inbound-channels-cache-shape.bats +0 -127
  126. package/skills/review-problems/test/inbound-discovery-contract.bats +0 -383
  127. package/skills/review-problems/test/jtbd-301-verdict-shape-contract.bats +0 -225
  128. package/skills/review-problems/test/review-problems-contract.bats +0 -235
  129. package/skills/review-problems/test/review-problems-likely-verified-cell-shape.bats +0 -229
  130. package/skills/scaffold-intake/test/scaffold-intake-contract.bats +0 -126
  131. package/skills/scaffold-intake/test/scaffold-intake-fixture.bats +0 -164
  132. package/skills/scaffold-intake/test/scaffold-intake-secrets-absent.bats +0 -87
  133. package/skills/transition-problem/test/transition-problem-contract.bats +0 -285
  134. package/skills/transition-problems/test/transition-problems-contract.bats +0 -331
  135. package/skills/update-upstream/test/update-upstream-contract.bats +0 -375
  136. package/skills/work-problem/test/work-problem-contract.bats +0 -294
  137. package/skills/work-problems/test/work-problems-above-appetite-remediation.bats +0 -139
  138. package/skills/work-problems/test/work-problems-adr-013-rule-6-p352-amendment.bats +0 -166
  139. package/skills/work-problems/test/work-problems-auto-migrate-step.bats +0 -57
  140. package/skills/work-problems/test/work-problems-cost-logging.bats +0 -109
  141. package/skills/work-problems/test/work-problems-deviation-candidate-shape.bats +0 -121
  142. package/skills/work-problems/test/work-problems-first-run-intake-prompt.bats +0 -36
  143. package/skills/work-problems/test/work-problems-inter-iteration-verify.bats +0 -59
  144. package/skills/work-problems/test/work-problems-mid-loop-userpromptsubmit-handler.bats +0 -69
  145. package/skills/work-problems/test/work-problems-no-mid-loop-asking.bats +0 -192
  146. package/skills/work-problems/test/work-problems-p341-pre-all-done-gate.bats +0 -122
  147. package/skills/work-problems/test/work-problems-p342-r007-derive-then-pass-flags.bats +0 -170
  148. package/skills/work-problems/test/work-problems-p342-retro-auto-ticket-carveout.bats +0 -120
  149. package/skills/work-problems/test/work-problems-preflight-failure-handling.bats +0 -248
  150. package/skills/work-problems/test/work-problems-preflight-session-continuity.bats +0 -139
  151. package/skills/work-problems/test/work-problems-preflight.bats +0 -69
  152. package/skills/work-problems/test/work-problems-release-cadence.bats +0 -63
  153. package/skills/work-problems/test/work-problems-step-0-iter-error-staleness.bats +0 -93
  154. package/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats +0 -138
  155. package/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats +0 -238
  156. package/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats +0 -174
  157. package/skills/work-problems/test/work-problems-step-2-5-routing.bats +0 -80
  158. package/skills/work-problems/test/work-problems-step-2-5b-cross-halt-routing.bats +0 -149
  159. package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +0 -191
  160. package/skills/work-problems/test/work-problems-step-5-bats-polling-discipline.bats +0 -91
  161. package/skills/work-problems/test/work-problems-step-5-delegation.bats +0 -333
  162. package/skills/work-problems/test/work-problems-step-5-idle-timeout-sigterm.bats +0 -421
  163. package/skills/work-problems/test/work-problems-step-5-is-error-transient-halt.bats +0 -278
  164. package/skills/work-problems/test/work-problems-step-5-iter-changeset-required.bats +0 -95
  165. package/skills/work-problems/test/work-problems-step-5-prompt-body-re-grounding.bats +0 -128
  166. package/skills/work-problems/test/work-problems-step-5-stream-timeout-salvage.bats +0 -251
  167. package/skills/work-problems/test/work-problems-step-6-5-always-drain.bats +0 -203
  168. package/skills/work-problems/test/work-problems-step-6-5-cache-refresh-chain.bats +0 -174
  169. package/skills/work-problems/test/work-problems-step-6-5-fix-and-continue.bats +0 -254
  170. package/skills/work-problems/test/work-problems-step-6-5-postrelease-kv-callback.bats +0 -209
  171. package/skills/work-problems/test/work-problems-stop-condition-questions.bats +0 -85
@@ -1,631 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # @problem P237 — Phase 3a population script behavioural confirmation.
4
- # @problem P087 — parent: plugin maturity battle-hardening signal.
5
- #
6
- # Contract under test: `packages/itil/scripts/plugin-maturity-populate.sh`
7
- # reads two Phase 2 NDJSON streams (`wr-itil-skill-invocations` +
8
- # `wr-itil-plugin-exercise-index`), applies ADR-053 §promotion criteria +
9
- # §Bootstrapping clause, and writes `maturity:` field per surface and per
10
- # plugin root in each `packages/<plugin>/.claude-plugin/plugin.json`.
11
- # Idempotent — re-running with unchanged inputs (including pinned `--now`)
12
- # produces byte-identical plugin.json output.
13
- #
14
- # Confirmation criteria 1-3 from ADR-063 §Confirmation are the load-bearing
15
- # behavioural assertions; criteria 4-9 belong to Phase 3b / 3c sibling
16
- # tickets (P238 / P239).
17
- #
18
- # @adr ADR-063 (Plugin maturity presentation layer — Phase 3a contract)
19
- # @adr ADR-053 (Plugin maturity taxonomy — promotion criteria + Bootstrapping)
20
- # @adr ADR-058 (Phase 2 NDJSON measurement — input shape)
21
- # @adr ADR-049 (Shim grammar — `wr-itil-plugin-maturity-populate` on $PATH)
22
- # @adr ADR-052 (Behavioural tests default — NDJSON-fixture-driven, not
23
- # structural grep on script body; AskUserQuestion negative-presence is
24
- # the documented carve-out)
25
- # @adr ADR-044 (Silent-framework carve-out — Phase 3a band recomputation
26
- # is mechanical, policy-resolved per ADR-053 §promotion criteria; no
27
- # `AskUserQuestion` per band recompute)
28
- # @adr ADR-013 Rule 6 (non-interactive fail-safe — exit 0 always)
29
- # @jtbd JTBD-201 (Restore Service Fast — audit-trail composition; the
30
- # per-surface evidence block IS the durable audit-trail surface)
31
-
32
- setup() {
33
- SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
34
- SCRIPT="$SCRIPTS_DIR/plugin-maturity-populate.sh"
35
- FIXTURE_DIR="$(mktemp -d)"
36
- PROJECT_ROOT="$FIXTURE_DIR/project"
37
- mkdir -p "$PROJECT_ROOT/packages"
38
- }
39
-
40
- teardown() {
41
- rm -rf "$FIXTURE_DIR"
42
- }
43
-
44
- # Helper: create a synthetic plugin under packages/<name>/ with a
45
- # minimal plugin.json and the declared surface inventory. Surfaces are
46
- # passed as `kind:name` tokens — e.g. `skill:manage-problem`,
47
- # `agent:agent`, `hook:itil-changeset-discipline`.
48
- make_plugin() {
49
- local plugin="$1"; shift
50
- local pkg="$PROJECT_ROOT/packages/$plugin"
51
- mkdir -p "$pkg/.claude-plugin"
52
- cat >"$pkg/.claude-plugin/plugin.json" <<EOF
53
- {
54
- "name": "wr-$plugin",
55
- "version": "0.1.0",
56
- "description": "fixture plugin"
57
- }
58
- EOF
59
- for token in "$@"; do
60
- local kind="${token%%:*}"
61
- local name="${token#*:}"
62
- case "$kind" in
63
- skill) mkdir -p "$pkg/skills/$name"; echo "fixture" >"$pkg/skills/$name/SKILL.md" ;;
64
- agent) mkdir -p "$pkg/agents"; echo "fixture" >"$pkg/agents/$name.md" ;;
65
- hook) mkdir -p "$pkg/hooks"; echo "fixture" >"$pkg/hooks/$name.sh" ;;
66
- command) mkdir -p "$pkg/commands"; echo "fixture" >"$pkg/commands/$name.md" ;;
67
- esac
68
- done
69
- }
70
-
71
- # Helper: write a synthetic transcript NDJSON file (`wr-itil-skill-invocations`
72
- # output shape, schema_version 1.0) with one record per (kind, surface).
73
- # Args: file, then triples of kind, surface, invocations.
74
- write_transcript_ndjson() {
75
- local out="$1"; shift
76
- : >"$out"
77
- while [ "$#" -ge 3 ]; do
78
- local kind="$1"; local surface="$2"; local invocations="$3"
79
- shift 3
80
- local plugin
81
- # Extract plugin name: skill/agent surfaces use `wr-<plugin>:<rest>`,
82
- # bash-attributed surfaces use `wr-<plugin>-<rest>`.
83
- if [[ "$surface" == *:* ]]; then
84
- plugin="${surface%%:*}"
85
- plugin="${plugin#wr-}"
86
- else
87
- plugin="${surface#wr-}"
88
- plugin="${plugin%%-*}"
89
- fi
90
- printf '{"schema_version":"1.0","axis":"skill-invocations","surface":"%s","kind":"%s","plugin":"%s","window_days":30,"invocations":%d,"first_invocation_iso":"2026-04-20T00:00:00Z","last_invocation_iso":"2026-05-17T00:00:00Z"}\n' \
91
- "$surface" "$kind" "$plugin" "$invocations" >>"$out"
92
- done
93
- }
94
-
95
- # Helper: write a synthetic exercise NDJSON file (`wr-itil-plugin-exercise-index`
96
- # output shape, schema_version 1.0). Args: file, then 5-tuples of
97
- # plugin, commits_window, days_shipped, closed_tickets, breaking_age (or NULL).
98
- write_exercise_ndjson() {
99
- local out="$1"; shift
100
- : >"$out"
101
- while [ "$#" -ge 5 ]; do
102
- local plugin="$1"; local cw="$2"; local ds="$3"; local ctw="$4"; local bca="$5"
103
- shift 5
104
- if [ "$bca" = "NULL" ]; then
105
- bca="null"
106
- fi
107
- printf '{"schema_version":"1.0","axis":"plugin-exercise-index","plugin":"%s","commits_window":%d,"window_days":60,"days_shipped":%d,"closed_tickets_window":%d,"tickets_window_days":90,"breaking_change_age_days":%s,"composite_index":1.0}\n' \
108
- "$plugin" "$cw" "$ds" "$ctw" "$bca" >>"$out"
109
- done
110
- }
111
-
112
- # Helper: extract a JSON field from a plugin.json via python3 stdlib.
113
- # Args: plugin-json-path, dotted-path (e.g. `maturity.band` or
114
- # `skills.manage-problem.maturity.band`).
115
- get_json_field() {
116
- local file="$1"; local path="$2"
117
- python3 -c "
118
- import json, sys
119
- with open('$file') as fh:
120
- obj = json.load(fh)
121
- for part in '$path'.split('.'):
122
- if not isinstance(obj, dict) or part not in obj:
123
- print('MISSING'); sys.exit(0)
124
- obj = obj[part]
125
- print(obj if not isinstance(obj, (dict, list)) else json.dumps(obj))
126
- "
127
- }
128
-
129
- # ── Existence / executable ──────────────────────────────────────────────────
130
-
131
- @test "plugin-maturity-populate: canonical script exists" {
132
- [ -f "$SCRIPT" ]
133
- }
134
-
135
- @test "plugin-maturity-populate: canonical script is executable" {
136
- [ -x "$SCRIPT" ]
137
- }
138
-
139
- @test "plugin-maturity-populate: shim file exists with ADR-049 grammar" {
140
- local shim="$SCRIPTS_DIR/../bin/wr-itil-plugin-maturity-populate"
141
- [ -f "$shim" ]
142
- [ -x "$shim" ]
143
- grep -q 'exec.*scripts/plugin-maturity-populate.sh' "$shim"
144
- }
145
-
146
- # ── Confirmation #1: idempotency (ADR-063 §Confirmation 1) ──────────────────
147
- # Run twice with the same --now pin; assert byte-identical plugin.json.
148
-
149
- @test "plugin-maturity-populate: idempotency — second run produces byte-identical plugin.json" {
150
- make_plugin "fixp" "skill:manage-problem" "agent:agent"
151
- local trans="$FIXTURE_DIR/transcript.ndjson"
152
- local exer="$FIXTURE_DIR/exercise.ndjson"
153
- write_transcript_ndjson "$trans" \
154
- "skill" "wr-fixp:manage-problem" 50 \
155
- "agent" "wr-fixp:agent" 200
156
- write_exercise_ndjson "$exer" \
157
- "fixp" 30 20 3 NULL
158
-
159
- run "$SCRIPT" \
160
- --transcript-ndjson="$trans" \
161
- --exercise-ndjson="$exer" \
162
- --project-root="$PROJECT_ROOT" \
163
- --now=2026-05-17T12:00:00Z
164
- [ "$status" -eq 0 ]
165
-
166
- local pj="$PROJECT_ROOT/packages/fixp/.claude-plugin/plugin.json"
167
- local checksum_1
168
- checksum_1=$(python3 -c "import hashlib; print(hashlib.sha256(open('$pj','rb').read()).hexdigest())")
169
-
170
- run "$SCRIPT" \
171
- --transcript-ndjson="$trans" \
172
- --exercise-ndjson="$exer" \
173
- --project-root="$PROJECT_ROOT" \
174
- --now=2026-05-17T12:00:00Z
175
- [ "$status" -eq 0 ]
176
-
177
- local checksum_2
178
- checksum_2=$(python3 -c "import hashlib; print(hashlib.sha256(open('$pj','rb').read()).hexdigest())")
179
- [ "$checksum_1" = "$checksum_2" ]
180
- }
181
-
182
- # ── Confirmation #2: band-mapping during bootstrapping (ADR-063 #2 first half) ─
183
- # 20 days_shipped → bootstrapping active (suite-oldest < 60d).
184
- # 200 invocations + 20 days_shipped → meets provisional Alpha conditions
185
- # (≥100 invocations + ≥14 days). Lower invocations → Experimental.
186
-
187
- @test "plugin-maturity-populate: bootstrapping — provisional Alpha on high-invocation surface" {
188
- make_plugin "alphap" "agent:agent"
189
- local trans="$FIXTURE_DIR/transcript.ndjson"
190
- local exer="$FIXTURE_DIR/exercise.ndjson"
191
- write_transcript_ndjson "$trans" "agent" "wr-alphap:agent" 200
192
- write_exercise_ndjson "$exer" "alphap" 30 20 3 NULL
193
-
194
- run "$SCRIPT" \
195
- --transcript-ndjson="$trans" \
196
- --exercise-ndjson="$exer" \
197
- --project-root="$PROJECT_ROOT" \
198
- --now=2026-05-17T12:00:00Z
199
- [ "$status" -eq 0 ]
200
-
201
- local pj="$PROJECT_ROOT/packages/alphap/.claude-plugin/plugin.json"
202
- local band
203
- band=$(get_json_field "$pj" "maturity.agents.agent.band")
204
- [ "$band" = "Alpha" ]
205
- }
206
-
207
- @test "plugin-maturity-populate: bootstrapping — Experimental on low-invocation surface" {
208
- make_plugin "expp" "skill:list-incidents"
209
- local trans="$FIXTURE_DIR/transcript.ndjson"
210
- local exer="$FIXTURE_DIR/exercise.ndjson"
211
- write_transcript_ndjson "$trans" "skill" "wr-expp:list-incidents" 2
212
- write_exercise_ndjson "$exer" "expp" 5 20 1 NULL
213
-
214
- run "$SCRIPT" \
215
- --transcript-ndjson="$trans" \
216
- --exercise-ndjson="$exer" \
217
- --project-root="$PROJECT_ROOT" \
218
- --now=2026-05-17T12:00:00Z
219
- [ "$status" -eq 0 ]
220
-
221
- local pj="$PROJECT_ROOT/packages/expp/.claude-plugin/plugin.json"
222
- local band
223
- band=$(get_json_field "$pj" "maturity.skills.list-incidents.band")
224
- [ "$band" = "Experimental" ]
225
- }
226
-
227
- # ── Confirmation #2 second half: steady-state post-sunset (ADR-063 #2) ──────
228
- # Suite-oldest ≥60 days_shipped → bootstrapping inactive.
229
- # 796 invocations + 200 days_shipped + 15 tickets + null breaking → Beta-
230
- # floor satisfied, Stable-floor not (invocations <1000). Expect Beta.
231
-
232
- @test "plugin-maturity-populate: steady-state — Beta on heavy-invocation aged surface" {
233
- make_plugin "betap" "agent:agent"
234
- local trans="$FIXTURE_DIR/transcript.ndjson"
235
- local exer="$FIXTURE_DIR/exercise.ndjson"
236
- write_transcript_ndjson "$trans" "agent" "wr-betap:agent" 796
237
- write_exercise_ndjson "$exer" "betap" 100 200 15 NULL
238
-
239
- run "$SCRIPT" \
240
- --transcript-ndjson="$trans" \
241
- --exercise-ndjson="$exer" \
242
- --project-root="$PROJECT_ROOT" \
243
- --now=2026-07-01T12:00:00Z
244
- [ "$status" -eq 0 ]
245
-
246
- local pj="$PROJECT_ROOT/packages/betap/.claude-plugin/plugin.json"
247
- local band
248
- band=$(get_json_field "$pj" "maturity.agents.agent.band")
249
- [ "$band" = "Beta" ]
250
- }
251
-
252
- # ── Confirmation #3: no AskUserQuestion per band recompute ──────────────────
253
- # ADR-044 silent-framework carve-out (ADR-063 §scope-limited carve-out).
254
- # Negative-presence check on stdin / stderr — case-insensitive per architect
255
- # adjustment G.
256
-
257
- @test "plugin-maturity-populate: no AskUserQuestion invocation during band recompute" {
258
- make_plugin "silentp" "skill:list-stories"
259
- local trans="$FIXTURE_DIR/transcript.ndjson"
260
- local exer="$FIXTURE_DIR/exercise.ndjson"
261
- write_transcript_ndjson "$trans" "skill" "wr-silentp:list-stories" 50
262
- write_exercise_ndjson "$exer" "silentp" 10 20 2 NULL
263
-
264
- run "$SCRIPT" \
265
- --transcript-ndjson="$trans" \
266
- --exercise-ndjson="$exer" \
267
- --project-root="$PROJECT_ROOT" \
268
- --now=2026-05-17T12:00:00Z
269
- [ "$status" -eq 0 ]
270
-
271
- # Case-insensitive scan of combined stdout + stderr for any
272
- # AskUserQuestion-token spelling.
273
- printf '%s' "$output" | grep -i -E 'askuserquestion|<askuser' && return 1 || true
274
- }
275
-
276
- # ── Schema shape (ADR-063 §plugin.json maturity field schema) ───────────────
277
-
278
- @test "plugin-maturity-populate: per-surface maturity record carries schema_version + band + computed_at + evidence" {
279
- make_plugin "shapep" "skill:manage-problem"
280
- local trans="$FIXTURE_DIR/transcript.ndjson"
281
- local exer="$FIXTURE_DIR/exercise.ndjson"
282
- write_transcript_ndjson "$trans" "skill" "wr-shapep:manage-problem" 100
283
- write_exercise_ndjson "$exer" "shapep" 20 25 5 NULL
284
-
285
- run "$SCRIPT" \
286
- --transcript-ndjson="$trans" \
287
- --exercise-ndjson="$exer" \
288
- --project-root="$PROJECT_ROOT" \
289
- --now=2026-05-17T12:00:00Z
290
- [ "$status" -eq 0 ]
291
-
292
- local pj="$PROJECT_ROOT/packages/shapep/.claude-plugin/plugin.json"
293
- [ "$(get_json_field "$pj" "maturity.skills.manage-problem.schema_version")" = "2.0" ]
294
- [ "$(get_json_field "$pj" "maturity.skills.manage-problem.band")" != "MISSING" ]
295
- [ "$(get_json_field "$pj" "maturity.skills.manage-problem.computed_at")" != "MISSING" ]
296
- [ "$(get_json_field "$pj" "maturity.skills.manage-problem.evidence.invocations_30d")" = "100" ]
297
- [ "$(get_json_field "$pj" "maturity.skills.manage-problem.evidence.days_shipped")" = "25" ]
298
- [ "$(get_json_field "$pj" "maturity.skills.manage-problem.evidence.closed_tickets_window")" = "5" ]
299
- }
300
-
301
- @test "plugin-maturity-populate: plugin root rollup carries schema_version + band + rollup_invocations_30d + bootstrapping, no evidence" {
302
- # P269 amendment: rollup now carries rollup_invocations_30d (sum of non-null
303
- # per-surface invocations_30d) + bootstrapping (snapshot of window state at
304
- # populate time) so the renderer's AND-gated compound predicate
305
- # (plugin-maturity-render.sh:146) can fire. The §evidence dict is still NOT
306
- # written on the rollup — only at per-surface entries.
307
- make_plugin "rollupp" "skill:s1" "skill:s2"
308
- local trans="$FIXTURE_DIR/transcript.ndjson"
309
- local exer="$FIXTURE_DIR/exercise.ndjson"
310
- write_transcript_ndjson "$trans" \
311
- "skill" "wr-rollupp:s1" 200 \
312
- "skill" "wr-rollupp:s2" 1
313
- write_exercise_ndjson "$exer" "rollupp" 10 20 2 NULL
314
-
315
- run "$SCRIPT" \
316
- --transcript-ndjson="$trans" \
317
- --exercise-ndjson="$exer" \
318
- --project-root="$PROJECT_ROOT" \
319
- --now=2026-05-17T12:00:00Z
320
- [ "$status" -eq 0 ]
321
-
322
- local pj="$PROJECT_ROOT/packages/rollupp/.claude-plugin/plugin.json"
323
- [ "$(get_json_field "$pj" "maturity.schema_version")" = "2.0" ]
324
- [ "$(get_json_field "$pj" "maturity.band")" != "MISSING" ]
325
- [ "$(get_json_field "$pj" "maturity.evidence")" = "MISSING" ]
326
- [ "$(get_json_field "$pj" "maturity.rollup_invocations_30d")" != "MISSING" ]
327
- [ "$(get_json_field "$pj" "maturity.bootstrapping")" != "MISSING" ]
328
- }
329
-
330
- # ── P269: rollup_invocations_30d sums non-null per-surface entries ───────────
331
-
332
- @test "plugin-maturity-populate: rollup_invocations_30d equals sum of non-null per-surface invocations" {
333
- # P269 fix details step 1: rollup_invocations_30d = sum across non-null
334
- # per-surface entries. 200 + 1 = 201.
335
- make_plugin "summp" "skill:s1" "skill:s2"
336
- local trans="$FIXTURE_DIR/transcript.ndjson"
337
- local exer="$FIXTURE_DIR/exercise.ndjson"
338
- write_transcript_ndjson "$trans" \
339
- "skill" "wr-summp:s1" 200 \
340
- "skill" "wr-summp:s2" 1
341
- write_exercise_ndjson "$exer" "summp" 10 20 2 NULL
342
-
343
- run "$SCRIPT" \
344
- --transcript-ndjson="$trans" \
345
- --exercise-ndjson="$exer" \
346
- --project-root="$PROJECT_ROOT" \
347
- --now=2026-05-17T12:00:00Z
348
- [ "$status" -eq 0 ]
349
-
350
- local pj="$PROJECT_ROOT/packages/summp/.claude-plugin/plugin.json"
351
- [ "$(get_json_field "$pj" "maturity.rollup_invocations_30d")" = "201" ]
352
- }
353
-
354
- # ── P269: rollup_invocations_30d sums non-null only (hooks excluded) ────────
355
-
356
- @test "plugin-maturity-populate: rollup_invocations_30d excludes null hook surfaces from sum" {
357
- # Mixed surfaces — a skill with invocations + a hook with null sentinel.
358
- # The sum should equal the skill invocations alone; the null hook does NOT
359
- # participate in the sum (architect §C null sentinel preserves
360
- # "not measurable" vs "measurably zero" semantics).
361
- make_plugin "mixp" "skill:thing" "hook:somehook"
362
- local trans="$FIXTURE_DIR/transcript.ndjson"
363
- local exer="$FIXTURE_DIR/exercise.ndjson"
364
- write_transcript_ndjson "$trans" "skill" "wr-mixp:thing" 75
365
- write_exercise_ndjson "$exer" "mixp" 10 20 2 NULL
366
-
367
- run "$SCRIPT" \
368
- --transcript-ndjson="$trans" \
369
- --exercise-ndjson="$exer" \
370
- --project-root="$PROJECT_ROOT" \
371
- --now=2026-05-17T12:00:00Z
372
- [ "$status" -eq 0 ]
373
-
374
- local pj="$PROJECT_ROOT/packages/mixp/.claude-plugin/plugin.json"
375
- [ "$(get_json_field "$pj" "maturity.rollup_invocations_30d")" = "75" ]
376
- }
377
-
378
- # ── P269: rollup_invocations_30d is null when ALL per-surface entries are null ─
379
-
380
- @test "plugin-maturity-populate: rollup_invocations_30d is null on hook-only plugin" {
381
- # Hook-only plugin: every per-surface invocations_30d is null. The rollup
382
- # MUST emit null (not 0) so the renderer's AND-gated compound predicate
383
- # falls through correctly — `0 invocations / 30d` would lie.
384
- make_plugin "hookonly" "hook:a" "hook:b"
385
- local trans="$FIXTURE_DIR/transcript.ndjson"
386
- local exer="$FIXTURE_DIR/exercise.ndjson"
387
- : >"$trans" # empty transcript — hooks never appear there
388
- write_exercise_ndjson "$exer" "hookonly" 5 30 1 NULL
389
-
390
- run "$SCRIPT" \
391
- --transcript-ndjson="$trans" \
392
- --exercise-ndjson="$exer" \
393
- --project-root="$PROJECT_ROOT" \
394
- --now=2026-05-17T12:00:00Z
395
- [ "$status" -eq 0 ]
396
-
397
- local pj="$PROJECT_ROOT/packages/hookonly/.claude-plugin/plugin.json"
398
- # `get_json_field` collapses null to display "None"; check the raw JSON.
399
- local raw
400
- raw=$(python3 -c "
401
- import json
402
- obj = json.load(open('$pj'))
403
- print(json.dumps(obj['maturity']['rollup_invocations_30d']))
404
- ")
405
- [ "$raw" = "null" ]
406
- }
407
-
408
- # ── P269: bootstrapping flag write — true during bootstrapping window ───────
409
-
410
- @test "plugin-maturity-populate: rollup bootstrapping=true during bootstrapping window" {
411
- # suite_oldest_days < 60 → bootstrapping active → rollup carries true.
412
- make_plugin "bsp" "skill:s1"
413
- local trans="$FIXTURE_DIR/transcript.ndjson"
414
- local exer="$FIXTURE_DIR/exercise.ndjson"
415
- write_transcript_ndjson "$trans" "skill" "wr-bsp:s1" 50
416
- write_exercise_ndjson "$exer" "bsp" 10 20 2 NULL # days_shipped=20 < 60
417
-
418
- run "$SCRIPT" \
419
- --transcript-ndjson="$trans" \
420
- --exercise-ndjson="$exer" \
421
- --project-root="$PROJECT_ROOT" \
422
- --now=2026-05-17T12:00:00Z
423
- [ "$status" -eq 0 ]
424
-
425
- local pj="$PROJECT_ROOT/packages/bsp/.claude-plugin/plugin.json"
426
- local raw
427
- raw=$(python3 -c "
428
- import json
429
- obj = json.load(open('$pj'))
430
- print(json.dumps(obj['maturity']['bootstrapping']))
431
- ")
432
- [ "$raw" = "true" ]
433
- }
434
-
435
- # ── P269: bootstrapping flag write — false once max(days_shipped) ≥ 60 ──────
436
-
437
- @test "plugin-maturity-populate: rollup bootstrapping=false once suite_oldest_days >= 60" {
438
- # max(days_shipped) >= 60 → bootstrapping inactive → rollup carries false.
439
- # Independent of --now (auto-derives from data per architect §D).
440
- make_plugin "stp" "skill:s1"
441
- local trans="$FIXTURE_DIR/transcript.ndjson"
442
- local exer="$FIXTURE_DIR/exercise.ndjson"
443
- write_transcript_ndjson "$trans" "skill" "wr-stp:s1" 50
444
- write_exercise_ndjson "$exer" "stp" 100 200 15 NULL # days_shipped=200 >= 60
445
-
446
- run "$SCRIPT" \
447
- --transcript-ndjson="$trans" \
448
- --exercise-ndjson="$exer" \
449
- --project-root="$PROJECT_ROOT" \
450
- --now=2026-05-17T12:00:00Z
451
- [ "$status" -eq 0 ]
452
-
453
- local pj="$PROJECT_ROOT/packages/stp/.claude-plugin/plugin.json"
454
- local raw
455
- raw=$(python3 -c "
456
- import json
457
- obj = json.load(open('$pj'))
458
- print(json.dumps(obj['maturity']['bootstrapping']))
459
- ")
460
- [ "$raw" = "false" ]
461
- }
462
-
463
- # ── Granularity contract (ADR-063 #10): rollup = worst-case among surfaces ──
464
-
465
- @test "plugin-maturity-populate: rollup band equals worst-case among constituent surfaces" {
466
- make_plugin "worstp" "skill:hot" "skill:cold"
467
- local trans="$FIXTURE_DIR/transcript.ndjson"
468
- local exer="$FIXTURE_DIR/exercise.ndjson"
469
- # hot → Alpha (200 invocations + 20 days during bootstrapping);
470
- # cold → Experimental (1 invocation). Worst-case = Experimental.
471
- write_transcript_ndjson "$trans" \
472
- "skill" "wr-worstp:hot" 200 \
473
- "skill" "wr-worstp:cold" 1
474
- write_exercise_ndjson "$exer" "worstp" 10 20 2 NULL
475
-
476
- run "$SCRIPT" \
477
- --transcript-ndjson="$trans" \
478
- --exercise-ndjson="$exer" \
479
- --project-root="$PROJECT_ROOT" \
480
- --now=2026-05-17T12:00:00Z
481
- [ "$status" -eq 0 ]
482
-
483
- local pj="$PROJECT_ROOT/packages/worstp/.claude-plugin/plugin.json"
484
- [ "$(get_json_field "$pj" "maturity.band")" = "Experimental" ]
485
- }
486
-
487
- # ── Hook evidence shape (architect adjustment C) — null invocation sentinel ──
488
- # Hooks are not transcript-observable; invocations_30d MUST be `null`, not 0,
489
- # to preserve "not measurable" vs "measurably zero" semantics.
490
-
491
- @test "plugin-maturity-populate: hook surfaces emit null invocations_30d sentinel" {
492
- make_plugin "hookp" "hook:itil-fictional-defer-detect"
493
- local trans="$FIXTURE_DIR/transcript.ndjson"
494
- local exer="$FIXTURE_DIR/exercise.ndjson"
495
- # Empty transcript NDJSON — hooks never appear in transcript stream.
496
- : >"$trans"
497
- write_exercise_ndjson "$exer" "hookp" 5 30 1 NULL
498
-
499
- run "$SCRIPT" \
500
- --transcript-ndjson="$trans" \
501
- --exercise-ndjson="$exer" \
502
- --project-root="$PROJECT_ROOT" \
503
- --now=2026-05-17T12:00:00Z
504
- [ "$status" -eq 0 ]
505
-
506
- local pj="$PROJECT_ROOT/packages/hookp/.claude-plugin/plugin.json"
507
- # Read the raw JSON and check that invocations_30d is literally null,
508
- # not 0. (`get_json_field` collapses null/0 to display; use python3 directly.)
509
- local raw
510
- raw=$(python3 -c "
511
- import json
512
- obj = json.load(open('$pj'))
513
- print(json.dumps(obj['maturity']['hooks']['itil-fictional-defer-detect']['evidence']['invocations_30d']))
514
- ")
515
- [ "$raw" = "null" ]
516
- }
517
-
518
- # ── Deprecated-band overlay preservation (architect adjustment I) ───────────
519
- # Pre-existing author-declared `band: "Deprecated"` + `supersededBy:` MUST
520
- # survive recompute unchanged. Recompute does NOT overwrite Deprecated
521
- # downward.
522
-
523
- @test "plugin-maturity-populate: preserves author-declared Deprecated band + supersededBy pointer" {
524
- make_plugin "depp" "skill:oldskill"
525
- local pj="$PROJECT_ROOT/packages/depp/.claude-plugin/plugin.json"
526
- # Hand-author the Deprecated entry that the script must preserve.
527
- python3 <<EOF
528
- import json
529
- obj = json.load(open("$pj"))
530
- obj.setdefault("maturity", {}).setdefault("skills", {})["oldskill"] = {
531
- "schema_version": "2.0",
532
- "band": "Deprecated",
533
- "computed_at": "2026-04-01T00:00:00Z",
534
- "supersededBy": "wr-depp:newskill",
535
- "evidence": {
536
- "invocations_30d": 50,
537
- "days_shipped": 100,
538
- "closed_tickets_window": 3,
539
- "breaking_change_age_days": None,
540
- },
541
- }
542
- with open("$pj","w") as fh:
543
- json.dump(obj, fh, indent=2, sort_keys=True)
544
- EOF
545
- local trans="$FIXTURE_DIR/transcript.ndjson"
546
- local exer="$FIXTURE_DIR/exercise.ndjson"
547
- write_transcript_ndjson "$trans" "skill" "wr-depp:oldskill" 50
548
- write_exercise_ndjson "$exer" "depp" 5 100 3 NULL
549
-
550
- run "$SCRIPT" \
551
- --transcript-ndjson="$trans" \
552
- --exercise-ndjson="$exer" \
553
- --project-root="$PROJECT_ROOT" \
554
- --now=2026-05-17T12:00:00Z
555
- [ "$status" -eq 0 ]
556
-
557
- [ "$(get_json_field "$pj" "maturity.skills.oldskill.band")" = "Deprecated" ]
558
- [ "$(get_json_field "$pj" "maturity.skills.oldskill.supersededBy")" = "wr-depp:newskill" ]
559
- }
560
-
561
- # ── Bootstrapping clause sunset auto-derivation (architect adjustment D) ────
562
- # Sunset is computed from `max(days_shipped)` ≥ 60 in the exercise NDJSON.
563
- # Same surface evidence → Experimental during bootstrapping (suite-oldest < 60d);
564
- # Beta+ once suite-oldest ≥ 60d (no calendar-date hard-code required).
565
-
566
- @test "plugin-maturity-populate: bootstrapping lapses when max(days_shipped) ≥ 60" {
567
- make_plugin "p1" "agent:agent"
568
- make_plugin "p2" "agent:agent"
569
- local trans="$FIXTURE_DIR/transcript.ndjson"
570
- local exer="$FIXTURE_DIR/exercise.ndjson"
571
- # Both plugins have 796 invocations + 200 days_shipped + 15 tickets.
572
- # In steady-state these would each be Beta. Under bootstrapping (oldest <60d)
573
- # they would be Alpha. Test: with one plugin aged 200d, bootstrapping is
574
- # inactive and the band is Beta. Independent of --now (no hard-coded date).
575
- write_transcript_ndjson "$trans" \
576
- "agent" "wr-p1:agent" 796 \
577
- "agent" "wr-p2:agent" 796
578
- write_exercise_ndjson "$exer" \
579
- "p1" 100 200 15 NULL \
580
- "p2" 100 25 15 NULL
581
-
582
- run "$SCRIPT" \
583
- --transcript-ndjson="$trans" \
584
- --exercise-ndjson="$exer" \
585
- --project-root="$PROJECT_ROOT" \
586
- --now=2026-05-17T12:00:00Z
587
- [ "$status" -eq 0 ]
588
-
589
- local pj1="$PROJECT_ROOT/packages/p1/.claude-plugin/plugin.json"
590
- local pj2="$PROJECT_ROOT/packages/p2/.claude-plugin/plugin.json"
591
- # p1 aged 200d → Beta. p2 aged 25d → Beta-floor unmet (days <60), demotes
592
- # to Alpha steady-state OR Experimental depending on the days_shipped cell;
593
- # only assert here that bootstrapping is inactive (the p1 outcome).
594
- [ "$(get_json_field "$pj1" "maturity.agents.agent.band")" = "Beta" ]
595
- }
596
-
597
- # ── ADR-013 Rule 6 fail-safe — missing NDJSON inputs ────────────────────────
598
-
599
- @test "plugin-maturity-populate: exits 0 when transcript NDJSON missing" {
600
- make_plugin "failsafe" "skill:thing"
601
- local exer="$FIXTURE_DIR/exercise.ndjson"
602
- write_exercise_ndjson "$exer" "failsafe" 10 20 2 NULL
603
-
604
- run "$SCRIPT" \
605
- --transcript-ndjson="$FIXTURE_DIR/does-not-exist.ndjson" \
606
- --exercise-ndjson="$exer" \
607
- --project-root="$PROJECT_ROOT" \
608
- --now=2026-05-17T12:00:00Z
609
- [ "$status" -eq 0 ]
610
- }
611
-
612
- @test "plugin-maturity-populate: exits 0 when no packages directory exists" {
613
- rm -rf "$PROJECT_ROOT/packages"
614
- local trans="$FIXTURE_DIR/transcript.ndjson"
615
- local exer="$FIXTURE_DIR/exercise.ndjson"
616
- : >"$trans"; : >"$exer"
617
-
618
- run "$SCRIPT" \
619
- --transcript-ndjson="$trans" \
620
- --exercise-ndjson="$exer" \
621
- --project-root="$PROJECT_ROOT" \
622
- --now=2026-05-17T12:00:00Z
623
- [ "$status" -eq 0 ]
624
- }
625
-
626
- # ── Privacy posture (mirrored from ADR-058 §Confirmation #3) ────────────────
627
- # No network primitive in the script body — defensive negative-grep.
628
-
629
- @test "plugin-maturity-populate: script body invokes no network egress primitives" {
630
- ! grep -E '(\bcurl\b|\bwget\b|\bnc\b|fetch\b|http\.client|urllib|socket\.connect)' "$SCRIPT"
631
- }