@windyroad/itil 0.38.0-preview.464 → 0.39.0-preview.470
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# packages/itil/bin/wr-itil-plugin-validate-ci-gate
|
|
3
|
+
#
|
|
4
|
+
# ADR-049 shim — resolves the canonical body in this package's
|
|
5
|
+
# scripts/ dir. Canonical body at
|
|
6
|
+
# `packages/itil/scripts/plugin-validate-ci-gate.sh`; this shim is
|
|
7
|
+
# the `$PATH`-resolvable entrypoint that CI and adopter trees invoke.
|
|
8
|
+
#
|
|
9
|
+
# P263 — implements ADR-063 §Confirmation #11 manifest-validity gate.
|
|
10
|
+
|
|
11
|
+
exec "$(dirname "$0")/../scripts/plugin-validate-ci-gate.sh" "$@"
|
package/package.json
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# packages/itil/scripts/plugin-validate-ci-gate.sh
|
|
3
|
+
#
|
|
4
|
+
# P263 / ADR-063 §Confirmation #11 — CI pre-publish manifest-validity
|
|
5
|
+
# gate. Loops every `packages/*/.claude-plugin/plugin.json` in CWD,
|
|
6
|
+
# runs `claude plugin validate <plugin_dir>` (NON-strict — see RCA
|
|
7
|
+
# note below), accumulates failures, and exits non-zero if ANY plugin
|
|
8
|
+
# manifest fails validation.
|
|
9
|
+
#
|
|
10
|
+
# WHY NON-STRICT (P263 / P258 refined RCA, 2026-05-30):
|
|
11
|
+
# - The historical P258 incident's failure class was a RECOGNISED
|
|
12
|
+
# top-level key (`hooks` / `skills` / `agents` / `commands`)
|
|
13
|
+
# carrying wrong-typed content — caught by `claude plugin validate`
|
|
14
|
+
# non-strict as a hard ERROR (`Validation errors: hooks: Invalid
|
|
15
|
+
# input`).
|
|
16
|
+
# - ADR-063 Amendment 2026-05-18's chosen safe-extension pattern
|
|
17
|
+
# places maturity records at top-level `maturity:` — an
|
|
18
|
+
# UNRECOGNISED top-level key — because unrecognised keys are
|
|
19
|
+
# warning-only and the plugin still loads. This is the durable
|
|
20
|
+
# design. `--strict` would promote `unknown field 'maturity'` to
|
|
21
|
+
# an error and REJECT every @windyroad/* plugin.
|
|
22
|
+
# - Non-strict therefore catches the historical incident's
|
|
23
|
+
# mechanism without rejecting the safe-extension pattern. The
|
|
24
|
+
# prose Confirmation #11 originally named `claude plugin install
|
|
25
|
+
# --dry-run`; P258 investigation refined to `claude plugin
|
|
26
|
+
# validate --strict`; this script's RCA refined further to
|
|
27
|
+
# non-strict.
|
|
28
|
+
#
|
|
29
|
+
# CLI VERSION PIN (set in `.github/workflows/ci.yml`):
|
|
30
|
+
# CI installs `@anthropic-ai/claude-code@2.1.150` before invoking
|
|
31
|
+
# this script. The exact pin protects against Anthropic-side CLI
|
|
32
|
+
# behaviour change silently breaking the gate. 2.1.150 is the
|
|
33
|
+
# version P263 iter 6 empirically tested against. Bump the pin
|
|
34
|
+
# only after re-running the iter-6 probe against the new version.
|
|
35
|
+
#
|
|
36
|
+
# LOOP CONTRACT:
|
|
37
|
+
# - Walks `packages/*/.claude-plugin/plugin.json` from CWD.
|
|
38
|
+
# - Does NOT short-circuit on first failure — every plugin is
|
|
39
|
+
# exercised so CI surfaces every defect at once.
|
|
40
|
+
# - `nullglob` makes a zero-plugin tree a no-op exit 0 (adopter-
|
|
41
|
+
# tree portability per ADR-049).
|
|
42
|
+
#
|
|
43
|
+
# References:
|
|
44
|
+
# ADR-063 — Amendment 2026-05-18 + §Confirmation #11
|
|
45
|
+
# ADR-049 — PATH-on-shim grammar (`wr-itil-plugin-validate-ci-gate`)
|
|
46
|
+
# ADR-052 — behavioural tests default (bats at
|
|
47
|
+
# `scripts/test/plugin-validate-ci-gate.bats`)
|
|
48
|
+
# ADR-014 — single-commit grain (script + shim + bats + CI + ADR
|
|
49
|
+
# amendment + changeset land together)
|
|
50
|
+
# P258 — root-cause driver (recognised vs unrecognised key
|
|
51
|
+
# distinction)
|
|
52
|
+
# P246 — sibling-class "gate-the-actual-load-bearing-surface"
|
|
53
|
+
# P263 — this implementation's ticket
|
|
54
|
+
|
|
55
|
+
set -e
|
|
56
|
+
shopt -s nullglob
|
|
57
|
+
|
|
58
|
+
fail=0
|
|
59
|
+
for manifest in packages/*/.claude-plugin/plugin.json; do
|
|
60
|
+
plugin_dir=$(dirname "$(dirname "$manifest")")
|
|
61
|
+
name=$(basename "$plugin_dir")
|
|
62
|
+
echo "--- $name ---"
|
|
63
|
+
if ! claude plugin validate "$plugin_dir"; then
|
|
64
|
+
echo "FAIL: $name plugin manifest validation"
|
|
65
|
+
fail=1
|
|
66
|
+
fi
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
[ "$fail" = "0" ] || exit 1
|
|
70
|
+
exit 0
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# @problem P263 — CI pre-publish manifest-validity gate. Adds the
|
|
4
|
+
# `claude plugin validate` (non-strict) loop ADR-063
|
|
5
|
+
# §Confirmation #11 names as the post-bats coverage
|
|
6
|
+
# layer that catches P258-class manifest breakages
|
|
7
|
+
# (recognised top-level key with wrong-typed content)
|
|
8
|
+
# without rejecting the ADR-063 `maturity:` safe-
|
|
9
|
+
# extension pattern that --strict would reject.
|
|
10
|
+
#
|
|
11
|
+
# Contract: `plugin-validate-ci-gate.sh` (invoked via ADR-049 shim
|
|
12
|
+
# `wr-itil-plugin-validate-ci-gate`) walks every
|
|
13
|
+
# `packages/*/.claude-plugin/plugin.json` in CWD, invokes `claude
|
|
14
|
+
# plugin validate <plugin_dir>` (NON-strict) per plugin, and exits
|
|
15
|
+
# non-zero if ANY plugin's manifest fails validation. The loop does
|
|
16
|
+
# NOT short-circuit on first failure — every plugin is exercised so
|
|
17
|
+
# the CI failure surfaces every defect at once.
|
|
18
|
+
#
|
|
19
|
+
# Behavioural test strategy (ADR-052): a programmable stub `claude`
|
|
20
|
+
# on PATH simulates `claude plugin validate`. The stub exits 1 when
|
|
21
|
+
# the manifest carries a top-level `hooks:` key whose body holds a
|
|
22
|
+
# `schema_version` sub-key (the P258 reproduction class — recognised
|
|
23
|
+
# key, wrong-typed content); exits 0 otherwise (including the ADR-063
|
|
24
|
+
# top-level `maturity:` safe-extension shape — unrecognised key, warn-
|
|
25
|
+
# only under non-strict). The stub captures the load-bearing
|
|
26
|
+
# behavioural contract of `claude plugin validate` non-strict for
|
|
27
|
+
# the two manifest shapes P263's design tension turns on. The real
|
|
28
|
+
# CLI is exercised at the CI workflow layer (`.github/workflows/
|
|
29
|
+
# ci.yml`) — split per ADR-052 (bats covers script-owned loop +
|
|
30
|
+
# failure-aggregation behaviour with a faithful test double; CI
|
|
31
|
+
# exercises the real CLI surface end-to-end).
|
|
32
|
+
#
|
|
33
|
+
# @adr ADR-063 Amendment 2026-05-18 §Confirmation #11 (the gate
|
|
34
|
+
# criterion this script satisfies)
|
|
35
|
+
# @adr ADR-049 (bin/ on PATH shim — `wr-itil-plugin-validate-ci-gate`)
|
|
36
|
+
# @adr ADR-052 (Behavioural tests default — stub-claude faithfully
|
|
37
|
+
# models the validator's non-strict exit contract for
|
|
38
|
+
# the two manifest shapes P263 turns on)
|
|
39
|
+
# @adr ADR-014 (single-commit grain — script + shim + bats + CI +
|
|
40
|
+
# ADR amendment + changeset land together)
|
|
41
|
+
# @adr ADR-022 (KE WSJF multiplier — informs prioritisation)
|
|
42
|
+
# @jtbd JTBD-101 (Extend the Suite with New Plugins — primary
|
|
43
|
+
# alignment; named Desired Outcome at L19 strengthened
|
|
44
|
+
# by this gate)
|
|
45
|
+
# @jtbd JTBD-202 (Pre-Flight Governance Checks Before Release —
|
|
46
|
+
# secondary alignment; gate fires in Quality Gates job)
|
|
47
|
+
|
|
48
|
+
setup() {
|
|
49
|
+
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
50
|
+
SCRIPT="$SCRIPTS_DIR/plugin-validate-ci-gate.sh"
|
|
51
|
+
SHIM="$(cd "$SCRIPTS_DIR/../bin" && pwd)/wr-itil-plugin-validate-ci-gate"
|
|
52
|
+
FIXTURE_DIR="$(mktemp -d)"
|
|
53
|
+
STUB_DIR="$FIXTURE_DIR/stub-bin"
|
|
54
|
+
mkdir -p "$STUB_DIR"
|
|
55
|
+
cd "$FIXTURE_DIR"
|
|
56
|
+
mkdir -p packages
|
|
57
|
+
write_stub_claude
|
|
58
|
+
export PATH="$STUB_DIR:$PATH"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
teardown() {
|
|
62
|
+
cd /
|
|
63
|
+
rm -rf "$FIXTURE_DIR"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Programmable stub for `claude plugin validate <plugin_dir>`. Faithful
|
|
67
|
+
# model of the non-strict exit contract for the two manifest shapes
|
|
68
|
+
# P263 turns on:
|
|
69
|
+
# - Top-level `hooks:` carrying a `schema_version` sub-key (the P258
|
|
70
|
+
# reproduction — recognised key with wrong-typed content) → exit 1.
|
|
71
|
+
# - Anything else (including ADR-063 top-level `maturity:` safe-
|
|
72
|
+
# extension shape, where `maturity:` is unrecognised → warn-only
|
|
73
|
+
# under non-strict) → exit 0.
|
|
74
|
+
write_stub_claude() {
|
|
75
|
+
cat > "$STUB_DIR/claude" <<'STUB'
|
|
76
|
+
#!/usr/bin/env bash
|
|
77
|
+
[[ "$1" == "plugin" && "$2" == "validate" ]] || { echo "stub: unsupported args $*" >&2; exit 2; }
|
|
78
|
+
plugin_dir="$3"
|
|
79
|
+
manifest="$plugin_dir/.claude-plugin/plugin.json"
|
|
80
|
+
[ -f "$manifest" ] || { echo "stub: manifest not found at $manifest" >&2; exit 1; }
|
|
81
|
+
# Detect P258 reproduction: top-level "hooks": { ... "schema_version": ... }
|
|
82
|
+
if python3 -c "
|
|
83
|
+
import json, sys
|
|
84
|
+
with open(sys.argv[1]) as f:
|
|
85
|
+
m = json.load(f)
|
|
86
|
+
for key in ('hooks', 'skills', 'agents', 'commands'):
|
|
87
|
+
v = m.get(key)
|
|
88
|
+
if isinstance(v, dict):
|
|
89
|
+
for sub in v.values():
|
|
90
|
+
if isinstance(sub, dict) and 'schema_version' in sub:
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
sys.exit(0)
|
|
93
|
+
" "$manifest"; then
|
|
94
|
+
echo "stub: $plugin_dir OK"
|
|
95
|
+
exit 0
|
|
96
|
+
else
|
|
97
|
+
echo "stub: $plugin_dir Validation errors: hooks: Invalid input" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
STUB
|
|
101
|
+
chmod +x "$STUB_DIR/claude"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Helper: write a synthetic plugin manifest at packages/<name>/.claude-plugin/plugin.json
|
|
105
|
+
write_plugin() {
|
|
106
|
+
local name="$1"
|
|
107
|
+
local body="$2"
|
|
108
|
+
mkdir -p "packages/$name/.claude-plugin"
|
|
109
|
+
printf '%s\n' "$body" > "packages/$name/.claude-plugin/plugin.json"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# ── Existence / executable ──────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
@test "plugin-validate-ci-gate: canonical script exists" {
|
|
115
|
+
[ -f "$SCRIPT" ]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@test "plugin-validate-ci-gate: canonical script is executable" {
|
|
119
|
+
[ -x "$SCRIPT" ]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@test "plugin-validate-ci-gate: ADR-049 shim exists" {
|
|
123
|
+
[ -f "$SHIM" ]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@test "plugin-validate-ci-gate: ADR-049 shim is executable" {
|
|
127
|
+
[ -x "$SHIM" ]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@test "plugin-validate-ci-gate: shim is thin (single exec line to canonical body)" {
|
|
131
|
+
grep -qE '^exec .*plugin-validate-ci-gate\.sh' "$SHIM"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# ── Fixture A: positive — ADR-063 safe-extension shape passes ───────────────
|
|
135
|
+
|
|
136
|
+
@test "plugin-validate-ci-gate: ADR-063 maturity: top-level safe-extension shape → exit 0" {
|
|
137
|
+
write_plugin "alpha" '{
|
|
138
|
+
"name": "alpha",
|
|
139
|
+
"version": "1.0.0",
|
|
140
|
+
"description": "test plugin",
|
|
141
|
+
"maturity": {
|
|
142
|
+
"schema_version": "2.0",
|
|
143
|
+
"band": "Experimental"
|
|
144
|
+
}
|
|
145
|
+
}'
|
|
146
|
+
|
|
147
|
+
run "$SCRIPT"
|
|
148
|
+
[ "$status" -eq 0 ]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@test "plugin-validate-ci-gate: multiple plugins all ADR-063-shaped → exit 0" {
|
|
152
|
+
write_plugin "alpha" '{"name":"alpha","version":"1.0.0","description":"t","maturity":{"schema_version":"2.0","band":"Experimental"}}'
|
|
153
|
+
write_plugin "beta" '{"name":"beta","version":"1.0.0","description":"t","maturity":{"schema_version":"2.0","band":"Stable"}}'
|
|
154
|
+
write_plugin "gamma" '{"name":"gamma","version":"1.0.0","description":"t"}'
|
|
155
|
+
|
|
156
|
+
run "$SCRIPT"
|
|
157
|
+
[ "$status" -eq 0 ]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ── Fixture B: negative — P258 reproduction shape fails ─────────────────────
|
|
161
|
+
|
|
162
|
+
@test "plugin-validate-ci-gate: P258 reproduction (top-level hooks: with schema_version) → exit 1" {
|
|
163
|
+
write_plugin "broken" '{
|
|
164
|
+
"name": "broken",
|
|
165
|
+
"version": "1.0.0",
|
|
166
|
+
"description": "test plugin with P258 mistake",
|
|
167
|
+
"hooks": {
|
|
168
|
+
"some-hook": {
|
|
169
|
+
"schema_version": "1.0",
|
|
170
|
+
"band": "Experimental"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}'
|
|
174
|
+
|
|
175
|
+
run "$SCRIPT"
|
|
176
|
+
[ "$status" -ne 0 ]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@test "plugin-validate-ci-gate: mixed pass/fail loops every plugin (no short-circuit) → exit 1" {
|
|
180
|
+
write_plugin "alpha" '{"name":"alpha","version":"1.0.0","description":"t","maturity":{"schema_version":"2.0","band":"Experimental"}}'
|
|
181
|
+
write_plugin "broken" '{"name":"broken","version":"1.0.0","description":"t","hooks":{"h":{"schema_version":"1.0","band":"Experimental"}}}'
|
|
182
|
+
write_plugin "gamma" '{"name":"gamma","version":"1.0.0","description":"t"}'
|
|
183
|
+
|
|
184
|
+
run "$SCRIPT"
|
|
185
|
+
[ "$status" -ne 0 ]
|
|
186
|
+
# All three plugins must be exercised — no short-circuit on first failure.
|
|
187
|
+
echo "$output" | grep -q "alpha"
|
|
188
|
+
echo "$output" | grep -q "broken"
|
|
189
|
+
echo "$output" | grep -q "gamma"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# ── No-plugins case (CI is always run against the monorepo tree, but the
|
|
193
|
+
# fail-safe matters for adopter-tree invocation per ADR-049 portability).
|
|
194
|
+
|
|
195
|
+
@test "plugin-validate-ci-gate: no plugins under packages/ → exit 0 (nothing to validate)" {
|
|
196
|
+
# No packages/<name>/.claude-plugin/plugin.json written.
|
|
197
|
+
run "$SCRIPT"
|
|
198
|
+
[ "$status" -eq 0 ]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# ── Shim PATH-resolution behaviour (ADR-049 contract) ──────────────────────
|
|
202
|
+
|
|
203
|
+
@test "plugin-validate-ci-gate: invoking shim runs the canonical body" {
|
|
204
|
+
write_plugin "alpha" '{"name":"alpha","version":"1.0.0","description":"t","maturity":{"schema_version":"2.0","band":"Experimental"}}'
|
|
205
|
+
|
|
206
|
+
run "$SHIM"
|
|
207
|
+
[ "$status" -eq 0 ]
|
|
208
|
+
}
|