@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.
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +7 -2
- package/agents/test/fixtures/proceed-new-genuinely-new.md +0 -43
- package/agents/test/fixtures/proceed-new-subtle-sibling.md +0 -47
- package/agents/test/fixtures/regression-p347-vs-p346.md +0 -54
- package/agents/test/hang-off-check.bats +0 -225
- package/hooks/test/block-list.bats +0 -153
- package/hooks/test/command-detect.bats +0 -181
- package/hooks/test/itil-assistant-output-gate.bats +0 -114
- package/hooks/test/itil-assistant-output-review.bats +0 -224
- package/hooks/test/itil-bash-polling-antipattern-detect.bats +0 -154
- package/hooks/test/itil-changeset-discipline.bats +0 -603
- package/hooks/test/itil-claude-space-protection.bats +0 -294
- package/hooks/test/itil-commit-trailer-transition-advisory.bats +0 -80
- package/hooks/test/itil-correction-detect.bats +0 -189
- package/hooks/test/itil-deferral-cadence-gate.bats +0 -187
- package/hooks/test/itil-fictional-defer-detect.bats +0 -292
- package/hooks/test/itil-fix-title-lifecycle-advisory.bats +0 -129
- package/hooks/test/itil-mid-loop-ask-detect.bats +0 -220
- package/hooks/test/itil-no-implement-draft-gate.bats +0 -210
- package/hooks/test/itil-pending-questions-surface.bats +0 -307
- package/hooks/test/itil-readme-refresh-discipline.bats +0 -642
- package/hooks/test/itil-rfc-oversight-nudge.bats +0 -83
- package/hooks/test/itil-rfc-trailer-advisory.bats +0 -375
- package/hooks/test/itil-story-mirror-migration-nudge.bats +0 -122
- package/hooks/test/manage-problem-enforce-create.bats +0 -424
- package/hooks/test/p057-staging-trap-detect.bats +0 -231
- package/hooks/test/pre-publish-intake-gate.bats +0 -144
- package/hooks/test/runtime-sid-marker.bats +0 -90
- package/hooks/test/session-id.bats +0 -370
- package/scripts/migrate-problems-add-type.sh +0 -116
- package/scripts/test/catchup-scan.bats +0 -274
- package/scripts/test/check-afk-accept-eligible.bats +0 -310
- package/scripts/test/check-fail-soft-skip-discipline.bats +0 -172
- package/scripts/test/check-fix-rfc-trace.bats +0 -164
- package/scripts/test/check-locale-discipline.bats +0 -291
- package/scripts/test/check-problems-readme-budget.bats +0 -192
- package/scripts/test/check-rfc-has-stories.bats +0 -69
- package/scripts/test/check-rfc-rejected-alternatives.bats +0 -108
- package/scripts/test/check-rfc-stories-ratified.bats +0 -82
- package/scripts/test/check-upstream-responses.bats +0 -503
- package/scripts/test/classify-readme-drift.bats +0 -392
- package/scripts/test/derive-first-dispatch.bats +0 -274
- package/scripts/test/derive-release-vehicle.bats +0 -454
- package/scripts/test/detect-unratified-stories-maps.bats +0 -60
- package/scripts/test/dual-tolerant-glob-rfc-002-t2.bats +0 -309
- package/scripts/test/effort-tally.bats +0 -170
- package/scripts/test/evaluate-relevance.bats +0 -773
- package/scripts/test/kind-a-commands.bats +0 -46
- package/scripts/test/mark-create-gate.bats +0 -59
- package/scripts/test/mark-story-oversight-confirmed.bats +0 -53
- package/scripts/test/migrate-problems-add-type.bats +0 -280
- package/scripts/test/migrate-story-status-mirror.bats +0 -167
- package/scripts/test/no-type-regression-guard.bats +0 -74
- package/scripts/test/oversight-mirror-corpus-lint.bats +0 -150
- package/scripts/test/plugin-exercise-index.bats +0 -386
- package/scripts/test/plugin-maturity-doc-lint.bats +0 -470
- package/scripts/test/plugin-maturity-populate.bats +0 -631
- package/scripts/test/plugin-maturity-render.bats +0 -469
- package/scripts/test/plugin-validate-ci-gate.bats +0 -208
- package/scripts/test/reconcile-readme.bats +0 -1007
- package/scripts/test/reconcile-rfcs.bats +0 -496
- package/scripts/test/reconcile-stories.bats +0 -173
- package/scripts/test/reconcile-story-maps.bats +0 -74
- package/scripts/test/release-watch-poll-loop.bats +0 -180
- package/scripts/test/resolve-governance-plugin-dirs.bats +0 -78
- package/scripts/test/rfc-stories-extension.bats +0 -175
- package/scripts/test/skill-invocations.bats +0 -535
- package/scripts/test/skill-md-dual-tolerant-coverage-rfc-002-t3.bats +0 -374
- package/scripts/test/story-oversight-lib.bats +0 -190
- package/scripts/test/update-jtbd-references-related-problems.bats +0 -178
- package/scripts/test/update-problem-references-section.bats +0 -242
- package/scripts/test/update-problem-rfcs-section.bats +0 -242
- package/scripts/test/update-references-section-sibling-helpers.bats +0 -80
- package/scripts/test/update-rfc-commits-section.bats +0 -77
- package/scripts/test/verify-iter-summary.bats +0 -196
- package/scripts/test/working-the-problem-traversal.bats +0 -116
- package/skills/capture-problem/test/capture-problem-step-1-5b-jtbd-trace.bats +0 -398
- package/skills/capture-problem/test/capture-problem.bats +0 -389
- package/skills/capture-story/test/capture-story-behavioural.bats +0 -317
- package/skills/capture-story-map/test/capture-story-map-behavioural.bats +0 -98
- package/skills/close-incident/test/close-incident-contract.bats +0 -146
- package/skills/link-incident/test/link-incident-contract.bats +0 -125
- package/skills/list-incidents/test/list-incidents-contract.bats +0 -115
- package/skills/list-problems/test/list-problems-contract.bats +0 -100
- package/skills/list-stories/test/list-stories-contract.bats +0 -127
- package/skills/list-story-maps/test/list-story-maps-contract.bats +0 -46
- package/skills/manage-incident/test/manage-incident-adr-044-contract.bats +0 -206
- package/skills/manage-incident/test/manage-incident-close-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-link-forwarder.bats +0 -67
- package/skills/manage-incident/test/manage-incident-list-forwarder.bats +0 -68
- package/skills/manage-incident/test/manage-incident-mitigate-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident-restore-forwarder.bats +0 -66
- package/skills/manage-incident/test/manage-incident.bats +0 -171
- package/skills/manage-problem/test/manage-problem-adr-044-step4-derive-first.bats +0 -151
- package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +0 -53
- package/skills/manage-problem/test/manage-problem-concern-boundary.bats +0 -64
- package/skills/manage-problem/test/manage-problem-effort-buckets.bats +0 -56
- package/skills/manage-problem/test/manage-problem-external-root-cause-detection.bats +0 -119
- package/skills/manage-problem/test/manage-problem-first-run-intake-prompt.bats +0 -43
- package/skills/manage-problem/test/manage-problem-git-mv-restage-reminder.bats +0 -48
- package/skills/manage-problem/test/manage-problem-id-collision-guard.bats +0 -39
- package/skills/manage-problem/test/manage-problem-list-forwarder.bats +0 -66
- package/skills/manage-problem/test/manage-problem-next-id-origin-lookup.bats +0 -50
- package/skills/manage-problem/test/manage-problem-no-prose-options.bats +0 -74
- package/skills/manage-problem/test/manage-problem-output-formatting.bats +0 -30
- package/skills/manage-problem/test/manage-problem-parked-and-cache.bats +0 -73
- package/skills/manage-problem/test/manage-problem-readme-refresh-on-transition.bats +0 -62
- package/skills/manage-problem/test/manage-problem-readme-tie-break-order.bats +0 -318
- package/skills/manage-problem/test/manage-problem-readme-vq-sort-order.bats +0 -206
- package/skills/manage-problem/test/manage-problem-release-vehicle-seed.bats +0 -83
- package/skills/manage-problem/test/manage-problem-review-forwarder.bats +0 -71
- package/skills/manage-problem/test/manage-problem-step-9d-recovery-path.bats +0 -97
- package/skills/manage-problem/test/manage-problem-transition-forwarder.bats +0 -96
- package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +0 -365
- package/skills/manage-problem/test/manage-problem-verification-detection.bats +0 -64
- package/skills/manage-problem/test/manage-problem-verification-pending.bats +0 -114
- package/skills/manage-problem/test/manage-problem-work-forwarder.bats +0 -88
- package/skills/manage-story/test/manage-story-contract.bats +0 -174
- package/skills/manage-story-map/test/manage-story-map-contract.bats +0 -63
- package/skills/mitigate-incident/test/mitigate-incident-contract.bats +0 -258
- package/skills/reconcile-readme/test/reconcile-readme-contract.bats +0 -201
- package/skills/report-upstream/test/report-upstream-contract.bats +0 -394
- package/skills/restore-incident/test/restore-incident-contract.bats +0 -170
- package/skills/review-problems/test/inbound-channels-cache-shape.bats +0 -127
- package/skills/review-problems/test/inbound-discovery-contract.bats +0 -383
- package/skills/review-problems/test/jtbd-301-verdict-shape-contract.bats +0 -225
- package/skills/review-problems/test/review-problems-contract.bats +0 -235
- package/skills/review-problems/test/review-problems-likely-verified-cell-shape.bats +0 -229
- package/skills/scaffold-intake/test/scaffold-intake-contract.bats +0 -126
- package/skills/scaffold-intake/test/scaffold-intake-fixture.bats +0 -164
- package/skills/scaffold-intake/test/scaffold-intake-secrets-absent.bats +0 -87
- package/skills/transition-problem/test/transition-problem-contract.bats +0 -285
- package/skills/transition-problems/test/transition-problems-contract.bats +0 -331
- package/skills/update-upstream/test/update-upstream-contract.bats +0 -375
- package/skills/work-problem/test/work-problem-contract.bats +0 -294
- package/skills/work-problems/test/work-problems-above-appetite-remediation.bats +0 -139
- package/skills/work-problems/test/work-problems-adr-013-rule-6-p352-amendment.bats +0 -166
- package/skills/work-problems/test/work-problems-auto-migrate-step.bats +0 -57
- package/skills/work-problems/test/work-problems-cost-logging.bats +0 -109
- package/skills/work-problems/test/work-problems-deviation-candidate-shape.bats +0 -121
- package/skills/work-problems/test/work-problems-first-run-intake-prompt.bats +0 -36
- package/skills/work-problems/test/work-problems-inter-iteration-verify.bats +0 -59
- package/skills/work-problems/test/work-problems-mid-loop-userpromptsubmit-handler.bats +0 -69
- package/skills/work-problems/test/work-problems-no-mid-loop-asking.bats +0 -192
- package/skills/work-problems/test/work-problems-p341-pre-all-done-gate.bats +0 -122
- package/skills/work-problems/test/work-problems-p342-r007-derive-then-pass-flags.bats +0 -170
- package/skills/work-problems/test/work-problems-p342-retro-auto-ticket-carveout.bats +0 -120
- package/skills/work-problems/test/work-problems-preflight-failure-handling.bats +0 -248
- package/skills/work-problems/test/work-problems-preflight-session-continuity.bats +0 -139
- package/skills/work-problems/test/work-problems-preflight.bats +0 -69
- package/skills/work-problems/test/work-problems-release-cadence.bats +0 -63
- package/skills/work-problems/test/work-problems-step-0-iter-error-staleness.bats +0 -93
- package/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats +0 -138
- package/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats +0 -238
- package/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats +0 -174
- package/skills/work-problems/test/work-problems-step-2-5-routing.bats +0 -80
- package/skills/work-problems/test/work-problems-step-2-5b-cross-halt-routing.bats +0 -149
- package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +0 -191
- package/skills/work-problems/test/work-problems-step-5-bats-polling-discipline.bats +0 -91
- package/skills/work-problems/test/work-problems-step-5-delegation.bats +0 -333
- package/skills/work-problems/test/work-problems-step-5-idle-timeout-sigterm.bats +0 -421
- package/skills/work-problems/test/work-problems-step-5-is-error-transient-halt.bats +0 -278
- package/skills/work-problems/test/work-problems-step-5-iter-changeset-required.bats +0 -95
- package/skills/work-problems/test/work-problems-step-5-prompt-body-re-grounding.bats +0 -128
- package/skills/work-problems/test/work-problems-step-5-stream-timeout-salvage.bats +0 -251
- package/skills/work-problems/test/work-problems-step-6-5-always-drain.bats +0 -203
- package/skills/work-problems/test/work-problems-step-6-5-cache-refresh-chain.bats +0 -174
- package/skills/work-problems/test/work-problems-step-6-5-fix-and-continue.bats +0 -254
- package/skills/work-problems/test/work-problems-step-6-5-postrelease-kv-callback.bats +0 -209
- 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
|
-
}
|