@windyroad/itil 0.36.0 → 0.37.0-preview.446
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/package.json
CHANGED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# packages/itil/scripts/derive-release-vehicle.sh
|
|
3
|
+
#
|
|
4
|
+
# Derive the release-vehicle citation for a problem ticket by walking git
|
|
5
|
+
# history. Input: ticket ID (e.g. `P267` or `267`). The script reads the
|
|
6
|
+
# ticket body for a `.changeset/<name>.md` filename reference, finds the
|
|
7
|
+
# `chore: version packages` deletion commit via `git log --diff-filter=D`,
|
|
8
|
+
# resolves the merge PR via the ancestry-path merge commit (or `gh pr list`
|
|
9
|
+
# fallback when available), and emits a structured citation block on stdout.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# derive-release-vehicle.sh <ticket-id> [<problems-dir>]
|
|
13
|
+
#
|
|
14
|
+
# <ticket-id>: `P<NNN>` or bare `<NNN>`. Case-insensitive.
|
|
15
|
+
# <problems-dir>: defaults to ./docs/problems. Dual-tolerant lookup spans
|
|
16
|
+
# flat layout (`<problems-dir>/<NNN>-*.md`) AND per-state
|
|
17
|
+
# subdir layout (`<problems-dir>/*/<NNN>-*.md`) per ADR-031.
|
|
18
|
+
#
|
|
19
|
+
# Output (stdout, multi-line key:value block):
|
|
20
|
+
# RELEASE_VEHICLE:
|
|
21
|
+
# changeset: .changeset/<name>.md
|
|
22
|
+
# version-packages-commit: <SHA>
|
|
23
|
+
# pr: #<N>
|
|
24
|
+
# merge-commit: <SHA>
|
|
25
|
+
# release-date: <YYYY-MM-DD>
|
|
26
|
+
#
|
|
27
|
+
# Exit codes:
|
|
28
|
+
# 0 = OK (full citation emitted)
|
|
29
|
+
# 1 = ticket file not found
|
|
30
|
+
# 2 = no changeset reference in ticket body
|
|
31
|
+
# 3 = changeset still present in working tree (unreleased)
|
|
32
|
+
# 4 = deletion commit found but no merge PR / merge commit resolvable
|
|
33
|
+
#
|
|
34
|
+
# @problem P267 — Codify derive-release-vehicle.sh helper for K→V release-
|
|
35
|
+
# cycle citation. K→V transitions composed by hand are
|
|
36
|
+
# fragile to wrong-release-cited errors when sessions
|
|
37
|
+
# pre-apply transitions across sibling tickets (observed
|
|
38
|
+
# 2026-05-18 P250 K→V cited P247's release refs).
|
|
39
|
+
# @adr ADR-049 (bin/ on PATH shim — adopter-safe script resolution; helper
|
|
40
|
+
# is invoked as `wr-itil-derive-release-vehicle`)
|
|
41
|
+
# @adr ADR-022 (Verifying lifecycle — citation supports the K→V transition's
|
|
42
|
+
# `## Fix Released` section)
|
|
43
|
+
# @adr ADR-014 (single-commit grain — helper is read-only, no commit impact)
|
|
44
|
+
# @adr ADR-038 (progressive disclosure — short structured stdout block)
|
|
45
|
+
# @adr ADR-005 (Plugin testing strategy — behavioural bats per P081)
|
|
46
|
+
# @jtbd JTBD-001 (Enforce Governance Without Slowing Down — deterministic
|
|
47
|
+
# citation prevents cross-cite errors)
|
|
48
|
+
# @jtbd JTBD-006 (Progress the Backlog While I'm Away — orchestrator per-iter
|
|
49
|
+
# K→V audit trail is trustworthy)
|
|
50
|
+
# @jtbd JTBD-101 (Extend the Plugin Suite — sibling shim naming grammar)
|
|
51
|
+
|
|
52
|
+
set -uo pipefail
|
|
53
|
+
|
|
54
|
+
usage() {
|
|
55
|
+
cat >&2 <<'EOF'
|
|
56
|
+
USAGE: derive-release-vehicle.sh <ticket-id> [<problems-dir>]
|
|
57
|
+
<ticket-id> — P<NNN> or bare <NNN> (e.g. P267 or 267)
|
|
58
|
+
<problems-dir> — defaults to ./docs/problems
|
|
59
|
+
|
|
60
|
+
Exit codes:
|
|
61
|
+
0 ok (full citation emitted)
|
|
62
|
+
1 ticket file not found
|
|
63
|
+
2 no changeset reference in ticket body
|
|
64
|
+
3 changeset still present in working tree (unreleased)
|
|
65
|
+
4 deletion commit found but no merge PR / merge commit resolvable
|
|
66
|
+
EOF
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
RAW_ID="${1:-}"
|
|
70
|
+
PROBLEMS_DIR="${2:-docs/problems}"
|
|
71
|
+
|
|
72
|
+
if [ -z "$RAW_ID" ]; then
|
|
73
|
+
usage
|
|
74
|
+
exit 2
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Normalise to three-digit numeric portion.
|
|
78
|
+
NNN="$(printf '%s\n' "$RAW_ID" | grep -oE '[0-9]+' | head -1)"
|
|
79
|
+
if [ -z "$NNN" ]; then
|
|
80
|
+
echo "ERROR: ticket id must contain digits (got: $RAW_ID)" >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
NNN="$(printf '%03d' "$((10#$NNN))")"
|
|
84
|
+
|
|
85
|
+
# ── Locate ticket file (dual-tolerant per ADR-031) ──────────────────────────
|
|
86
|
+
TICKET_FILE=""
|
|
87
|
+
for candidate in \
|
|
88
|
+
"$PROBLEMS_DIR/$NNN-"*.md \
|
|
89
|
+
"$PROBLEMS_DIR"/*/"$NNN-"*.md; do
|
|
90
|
+
if [ -f "$candidate" ]; then
|
|
91
|
+
TICKET_FILE="$candidate"
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
if [ -z "$TICKET_FILE" ]; then
|
|
97
|
+
echo "ERROR: ticket P$NNN not found under $PROBLEMS_DIR" >&2
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# ── Extract changeset filename reference from ticket body ───────────────────
|
|
102
|
+
# Match `.changeset/<name>.md` — kebab + alphanumeric.
|
|
103
|
+
CHANGESET_PATH="$(
|
|
104
|
+
grep -oE '\.changeset/[a-z0-9._-]+\.md' "$TICKET_FILE" 2>/dev/null \
|
|
105
|
+
| head -1
|
|
106
|
+
)"
|
|
107
|
+
|
|
108
|
+
if [ -z "$CHANGESET_PATH" ]; then
|
|
109
|
+
echo "ERROR: no .changeset/<name>.md reference in $TICKET_FILE" >&2
|
|
110
|
+
exit 2
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# ── Released? Changeset must be ABSENT from working tree (deleted by
|
|
114
|
+
# chore: version packages) AND have a deletion commit in git history. ────
|
|
115
|
+
if [ -f "$CHANGESET_PATH" ]; then
|
|
116
|
+
echo "ERROR: changeset $CHANGESET_PATH still present in working tree (unreleased)" >&2
|
|
117
|
+
exit 3
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# ── Find the deletion commit (chore: version packages) ─────────────────────
|
|
121
|
+
# `--diff-filter=D` filters to the commit that deleted the file. `--all`
|
|
122
|
+
# searches across branches. First match (oldest deletion) is canonical.
|
|
123
|
+
DELETE_SHA="$(
|
|
124
|
+
git log --all --diff-filter=D --format='%H' -- "$CHANGESET_PATH" 2>/dev/null \
|
|
125
|
+
| tail -1
|
|
126
|
+
)"
|
|
127
|
+
|
|
128
|
+
if [ -z "$DELETE_SHA" ]; then
|
|
129
|
+
echo "ERROR: changeset $CHANGESET_PATH has no deletion commit in git history (unreleased)" >&2
|
|
130
|
+
exit 3
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# ── Resolve merge PR + merge commit ────────────────────────────────────────
|
|
134
|
+
# Strategy 1: walk first-parent merges from DELETE_SHA forward toward main,
|
|
135
|
+
# match the merge commit that introduced DELETE_SHA into main. The
|
|
136
|
+
# `git log --merges --first-parent --ancestry-path DELETE_SHA..HEAD` form
|
|
137
|
+
# enumerates merge commits on main whose history descends from DELETE_SHA.
|
|
138
|
+
MERGE_SHA=""
|
|
139
|
+
PR_NUMBER=""
|
|
140
|
+
|
|
141
|
+
# Determine the target ref — prefer origin/main, fall back to local main,
|
|
142
|
+
# fall back to HEAD's branch.
|
|
143
|
+
TARGET_REF=""
|
|
144
|
+
for ref in origin/main main HEAD; do
|
|
145
|
+
if git rev-parse --verify "$ref" >/dev/null 2>&1; then
|
|
146
|
+
TARGET_REF="$ref"
|
|
147
|
+
break
|
|
148
|
+
fi
|
|
149
|
+
done
|
|
150
|
+
|
|
151
|
+
if [ -n "$TARGET_REF" ]; then
|
|
152
|
+
# First merge commit on the first-parent path from DELETE_SHA..TARGET_REF.
|
|
153
|
+
# `tail -1` picks the closest ancestor (oldest merge that brought
|
|
154
|
+
# DELETE_SHA into the target ref).
|
|
155
|
+
MERGE_SHA="$(
|
|
156
|
+
git log --merges --first-parent --ancestry-path \
|
|
157
|
+
--format='%H' "$DELETE_SHA^..$TARGET_REF" 2>/dev/null \
|
|
158
|
+
| tail -1
|
|
159
|
+
)"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
if [ -n "$MERGE_SHA" ]; then
|
|
163
|
+
# Extract `#<N>` from the merge commit subject (canonical PR-merge shape
|
|
164
|
+
# `Merge pull request #<N> from ...`).
|
|
165
|
+
MERGE_SUBJECT="$(git log -1 --format='%s' "$MERGE_SHA" 2>/dev/null)"
|
|
166
|
+
PR_NUMBER="$(printf '%s\n' "$MERGE_SUBJECT" | grep -oE '#[0-9]+' | head -1)"
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Strategy 2: gh pr list fallback when git-history path didn't resolve a
|
|
170
|
+
# PR number and `gh` is installed + authenticated.
|
|
171
|
+
if [ -z "$PR_NUMBER" ] && command -v gh >/dev/null 2>&1; then
|
|
172
|
+
PR_NUMBER="$(
|
|
173
|
+
gh pr list --state merged --search "$DELETE_SHA" \
|
|
174
|
+
--json number --jq '.[0].number' 2>/dev/null \
|
|
175
|
+
| sed 's/^/#/'
|
|
176
|
+
)"
|
|
177
|
+
[ "$PR_NUMBER" = "#" ] && PR_NUMBER=""
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if [ -z "$PR_NUMBER" ] || [ -z "$MERGE_SHA" ]; then
|
|
181
|
+
echo "ERROR: deletion commit $DELETE_SHA found but no merge PR resolvable" >&2
|
|
182
|
+
exit 4
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# ── Release date from the merge commit's committer date ────────────────────
|
|
186
|
+
RELEASE_DATE="$(git log -1 --format='%cs' "$MERGE_SHA" 2>/dev/null)"
|
|
187
|
+
|
|
188
|
+
# ── Emit the structured citation block ─────────────────────────────────────
|
|
189
|
+
cat <<EOF
|
|
190
|
+
RELEASE_VEHICLE:
|
|
191
|
+
changeset: $CHANGESET_PATH
|
|
192
|
+
version-packages-commit: $DELETE_SHA
|
|
193
|
+
pr: $PR_NUMBER
|
|
194
|
+
merge-commit: $MERGE_SHA
|
|
195
|
+
release-date: $RELEASE_DATE
|
|
196
|
+
EOF
|
|
197
|
+
exit 0
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# @problem P267 — Codify derive-release-vehicle.sh helper for K→V release-cycle
|
|
4
|
+
# citation. K→V transitions composed by hand from inline
|
|
5
|
+
# pre-flight evidence are fragile to wrong-release-cited errors
|
|
6
|
+
# (observed 2026-05-18 session 7 iter 1 — P250's K→V cited
|
|
7
|
+
# P247's release refs). Helper makes citation deterministic.
|
|
8
|
+
#
|
|
9
|
+
# Contract: `derive-release-vehicle.sh <ticket-id> [<problems-dir>]` reads the
|
|
10
|
+
# ticket file for a changeset filename reference, walks `git log
|
|
11
|
+
# --diff-filter=D` for the deletion commit (chore: version packages), then
|
|
12
|
+
# resolves the merge PR + merge commit. Emits a structured citation block on
|
|
13
|
+
# stdout.
|
|
14
|
+
#
|
|
15
|
+
# Output (stdout, multi-line key:value block):
|
|
16
|
+
# RELEASE_VEHICLE:
|
|
17
|
+
# changeset: .changeset/<name>.md
|
|
18
|
+
# version-packages-commit: <SHA>
|
|
19
|
+
# pr: #<N>
|
|
20
|
+
# merge-commit: <SHA>
|
|
21
|
+
# release-date: <YYYY-MM-DD>
|
|
22
|
+
#
|
|
23
|
+
# Exit codes:
|
|
24
|
+
# 0 = OK (full citation emitted)
|
|
25
|
+
# 1 = ticket file not found
|
|
26
|
+
# 2 = no changeset reference in ticket body
|
|
27
|
+
# 3 = changeset not yet deleted (unreleased)
|
|
28
|
+
# 4 = deletion commit found but no merge PR / merge commit resolvable
|
|
29
|
+
#
|
|
30
|
+
# @adr ADR-049 (bin/ on PATH shim — adopter-safe script resolution)
|
|
31
|
+
# @adr ADR-022 (Verifying lifecycle — citation supports K→V transition)
|
|
32
|
+
# @adr ADR-014 (single-commit grain — helper is read-only, no commit impact)
|
|
33
|
+
# @adr ADR-005 (Plugin testing strategy — behavioural bats per P081)
|
|
34
|
+
# @jtbd JTBD-001 (Enforce Governance — deterministic citation prevents
|
|
35
|
+
# cross-cite errors)
|
|
36
|
+
# @jtbd JTBD-006 (Progress Backlog AFK — orchestrator per-iter K→V audit
|
|
37
|
+
# trail trustworthy)
|
|
38
|
+
# @jtbd JTBD-101 (Extend the Suite — sibling shim naming grammar)
|
|
39
|
+
|
|
40
|
+
setup() {
|
|
41
|
+
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
42
|
+
SCRIPT="$SCRIPTS_DIR/derive-release-vehicle.sh"
|
|
43
|
+
FIXTURE_DIR="$(mktemp -d)"
|
|
44
|
+
cd "$FIXTURE_DIR"
|
|
45
|
+
git init -q -b main
|
|
46
|
+
git config user.email test@example.com
|
|
47
|
+
git config user.name "Test"
|
|
48
|
+
mkdir -p docs/problems/known-error docs/problems/verifying .changeset
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
teardown() {
|
|
52
|
+
cd /
|
|
53
|
+
rm -rf "$FIXTURE_DIR"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# ── Existence + executable ──────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
@test "derive-release-vehicle: script exists" {
|
|
59
|
+
[ -f "$SCRIPT" ]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@test "derive-release-vehicle: script is executable" {
|
|
63
|
+
[ -x "$SCRIPT" ]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ── Usage / argument errors ─────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
@test "derive-release-vehicle: no arguments → exit non-zero with usage" {
|
|
69
|
+
run "$SCRIPT"
|
|
70
|
+
[ "$status" -ne 0 ]
|
|
71
|
+
echo "$output" | grep -qi "USAGE"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@test "derive-release-vehicle: ticket file not found → exit 1" {
|
|
75
|
+
run "$SCRIPT" P999 docs/problems
|
|
76
|
+
[ "$status" -eq 1 ]
|
|
77
|
+
echo "$output" | grep -qi "not found"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ── Exit 2: no changeset reference in body ──────────────────────────────────
|
|
81
|
+
|
|
82
|
+
@test "derive-release-vehicle: ticket body has no changeset reference → exit 2" {
|
|
83
|
+
cat > docs/problems/known-error/100-no-changeset.md <<'EOF'
|
|
84
|
+
# Problem 100: No Changeset
|
|
85
|
+
|
|
86
|
+
**Status**: Known Error
|
|
87
|
+
|
|
88
|
+
## Description
|
|
89
|
+
|
|
90
|
+
This ticket body does NOT reference any .changeset filename.
|
|
91
|
+
EOF
|
|
92
|
+
git add docs/problems/known-error/100-no-changeset.md
|
|
93
|
+
git commit -q -m "init"
|
|
94
|
+
|
|
95
|
+
run "$SCRIPT" P100 docs/problems
|
|
96
|
+
[ "$status" -eq 2 ]
|
|
97
|
+
echo "$output" | grep -qiE "no .?changeset|changeset.*reference"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# ── Exit 3: changeset still present (unreleased) ────────────────────────────
|
|
101
|
+
|
|
102
|
+
@test "derive-release-vehicle: changeset still in working tree (unreleased) → exit 3" {
|
|
103
|
+
cat > .changeset/p101-unreleased.md <<'EOF'
|
|
104
|
+
---
|
|
105
|
+
'@windyroad/itil': patch
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
Stub for P101.
|
|
109
|
+
EOF
|
|
110
|
+
cat > docs/problems/known-error/101-unreleased.md <<'EOF'
|
|
111
|
+
# Problem 101: Unreleased
|
|
112
|
+
|
|
113
|
+
**Status**: Known Error
|
|
114
|
+
|
|
115
|
+
## Fix Strategy
|
|
116
|
+
|
|
117
|
+
Ship via `.changeset/p101-unreleased.md`.
|
|
118
|
+
EOF
|
|
119
|
+
git add .
|
|
120
|
+
git commit -q -m "init"
|
|
121
|
+
|
|
122
|
+
run "$SCRIPT" P101 docs/problems
|
|
123
|
+
[ "$status" -eq 3 ]
|
|
124
|
+
echo "$output" | grep -qi "unreleased\|not.*delet"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ── Exit 0: happy path — full citation emitted ──────────────────────────────
|
|
128
|
+
|
|
129
|
+
@test "derive-release-vehicle: happy path — full citation block on stdout" {
|
|
130
|
+
# Set up the canonical release-cycle shape:
|
|
131
|
+
# commit A: ticket + changeset added
|
|
132
|
+
# commit B (on PR branch): chore: version packages — deletes changeset
|
|
133
|
+
# commit C (on main): merge commit referencing PR #42
|
|
134
|
+
cat > .changeset/p102-happy.md <<'EOF'
|
|
135
|
+
---
|
|
136
|
+
'@windyroad/itil': patch
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
P102 happy-path fix.
|
|
140
|
+
EOF
|
|
141
|
+
cat > docs/problems/known-error/102-happy.md <<'EOF'
|
|
142
|
+
# Problem 102: Happy
|
|
143
|
+
|
|
144
|
+
**Status**: Known Error
|
|
145
|
+
|
|
146
|
+
## Fix Strategy
|
|
147
|
+
|
|
148
|
+
Ship via `.changeset/p102-happy.md`.
|
|
149
|
+
EOF
|
|
150
|
+
git add .
|
|
151
|
+
git commit -q -m "feat(itil): P102 fix + changeset"
|
|
152
|
+
|
|
153
|
+
# Simulate the changeset-release/main PR branch: version-packages commit
|
|
154
|
+
# deletes the changeset; merge commit lands on main with a "#<N>" reference.
|
|
155
|
+
git checkout -q -b changeset-release/main
|
|
156
|
+
git rm -q .changeset/p102-happy.md
|
|
157
|
+
git commit -q -m "chore: version packages"
|
|
158
|
+
git checkout -q main
|
|
159
|
+
git merge --no-ff -q changeset-release/main -m "Merge pull request #42 from windyroad/changeset-release/main"
|
|
160
|
+
|
|
161
|
+
run "$SCRIPT" P102 docs/problems
|
|
162
|
+
[ "$status" -eq 0 ]
|
|
163
|
+
echo "$output" | grep -q "RELEASE_VEHICLE:"
|
|
164
|
+
echo "$output" | grep -q "changeset: .changeset/p102-happy.md"
|
|
165
|
+
echo "$output" | grep -qE "version-packages-commit: [0-9a-f]{7,40}"
|
|
166
|
+
echo "$output" | grep -qE "pr: #42"
|
|
167
|
+
echo "$output" | grep -qE "merge-commit: [0-9a-f]{7,40}"
|
|
168
|
+
echo "$output" | grep -qE "release-date: [0-9]{4}-[0-9]{2}-[0-9]{2}"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@test "derive-release-vehicle: per-state subdir layout (verifying/) is reachable" {
|
|
172
|
+
# ADR-031 per-state subdir — ticket may live in docs/problems/verifying/
|
|
173
|
+
# post-K→V. Helper must dual-tolerantly find it.
|
|
174
|
+
cat > .changeset/p103-subdir.md <<'EOF'
|
|
175
|
+
---
|
|
176
|
+
'@windyroad/itil': patch
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
P103.
|
|
180
|
+
EOF
|
|
181
|
+
cat > docs/problems/verifying/103-subdir.md <<'EOF'
|
|
182
|
+
# Problem 103: Subdir
|
|
183
|
+
|
|
184
|
+
**Status**: Verification Pending
|
|
185
|
+
|
|
186
|
+
## Fix Strategy
|
|
187
|
+
|
|
188
|
+
Ship via `.changeset/p103-subdir.md`.
|
|
189
|
+
EOF
|
|
190
|
+
git add .
|
|
191
|
+
git commit -q -m "init"
|
|
192
|
+
|
|
193
|
+
git checkout -q -b changeset-release/main
|
|
194
|
+
git rm -q .changeset/p103-subdir.md
|
|
195
|
+
git commit -q -m "chore: version packages"
|
|
196
|
+
git checkout -q main
|
|
197
|
+
git merge --no-ff -q changeset-release/main -m "Merge pull request #43 from windyroad/changeset-release/main"
|
|
198
|
+
|
|
199
|
+
run "$SCRIPT" P103 docs/problems
|
|
200
|
+
[ "$status" -eq 0 ]
|
|
201
|
+
echo "$output" | grep -q "changeset: .changeset/p103-subdir.md"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@test "derive-release-vehicle: accepts bare numeric ID (no P prefix)" {
|
|
205
|
+
cat > .changeset/p104-bare.md <<'EOF'
|
|
206
|
+
---
|
|
207
|
+
'@windyroad/itil': patch
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
P104.
|
|
211
|
+
EOF
|
|
212
|
+
cat > docs/problems/known-error/104-bare.md <<'EOF'
|
|
213
|
+
# Problem 104: Bare
|
|
214
|
+
|
|
215
|
+
**Status**: Known Error
|
|
216
|
+
|
|
217
|
+
## Fix Strategy
|
|
218
|
+
|
|
219
|
+
Ship via `.changeset/p104-bare.md`.
|
|
220
|
+
EOF
|
|
221
|
+
git add .
|
|
222
|
+
git commit -q -m "init"
|
|
223
|
+
|
|
224
|
+
git checkout -q -b changeset-release/main
|
|
225
|
+
git rm -q .changeset/p104-bare.md
|
|
226
|
+
git commit -q -m "chore: version packages"
|
|
227
|
+
git checkout -q main
|
|
228
|
+
git merge --no-ff -q changeset-release/main -m "Merge pull request #44 from windyroad/changeset-release/main"
|
|
229
|
+
|
|
230
|
+
# Plain "104" — no P prefix.
|
|
231
|
+
run "$SCRIPT" 104 docs/problems
|
|
232
|
+
[ "$status" -eq 0 ]
|
|
233
|
+
echo "$output" | grep -q "RELEASE_VEHICLE:"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# ── Exit 4: deletion commit found but no merge PR resolvable ────────────────
|
|
237
|
+
|
|
238
|
+
@test "derive-release-vehicle: deletion on main branch, no merge PR → exit 4" {
|
|
239
|
+
# Direct commit to main (no PR merge) — helper finds the deletion commit
|
|
240
|
+
# but cannot resolve a #<N> merge PR. Exit 4.
|
|
241
|
+
cat > .changeset/p105-no-pr.md <<'EOF'
|
|
242
|
+
---
|
|
243
|
+
'@windyroad/itil': patch
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
P105.
|
|
247
|
+
EOF
|
|
248
|
+
cat > docs/problems/known-error/105-no-pr.md <<'EOF'
|
|
249
|
+
# Problem 105: No PR
|
|
250
|
+
|
|
251
|
+
**Status**: Known Error
|
|
252
|
+
|
|
253
|
+
## Fix Strategy
|
|
254
|
+
|
|
255
|
+
Ship via `.changeset/p105-no-pr.md`.
|
|
256
|
+
EOF
|
|
257
|
+
git add .
|
|
258
|
+
git commit -q -m "init"
|
|
259
|
+
|
|
260
|
+
# Delete on main directly (no merge commit, no PR reference).
|
|
261
|
+
git rm -q .changeset/p105-no-pr.md
|
|
262
|
+
git commit -q -m "chore: version packages (direct)"
|
|
263
|
+
|
|
264
|
+
run "$SCRIPT" P105 docs/problems
|
|
265
|
+
[ "$status" -eq 4 ]
|
|
266
|
+
echo "$output" | grep -qiE "no.*(merge|pr)|cannot resolve"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# ── Default problems-dir resolution ─────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
@test "derive-release-vehicle: defaults to ./docs/problems when problems-dir omitted" {
|
|
272
|
+
cat > .changeset/p106-default.md <<'EOF'
|
|
273
|
+
---
|
|
274
|
+
'@windyroad/itil': patch
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
P106.
|
|
278
|
+
EOF
|
|
279
|
+
cat > docs/problems/known-error/106-default.md <<'EOF'
|
|
280
|
+
# Problem 106: Default
|
|
281
|
+
|
|
282
|
+
**Status**: Known Error
|
|
283
|
+
|
|
284
|
+
## Fix Strategy
|
|
285
|
+
|
|
286
|
+
Ship via `.changeset/p106-default.md`.
|
|
287
|
+
EOF
|
|
288
|
+
git add .
|
|
289
|
+
git commit -q -m "init"
|
|
290
|
+
|
|
291
|
+
git checkout -q -b changeset-release/main
|
|
292
|
+
git rm -q .changeset/p106-default.md
|
|
293
|
+
git commit -q -m "chore: version packages"
|
|
294
|
+
git checkout -q main
|
|
295
|
+
git merge --no-ff -q changeset-release/main -m "Merge pull request #46 from windyroad/changeset-release/main"
|
|
296
|
+
|
|
297
|
+
# Omit problems-dir.
|
|
298
|
+
run "$SCRIPT" P106
|
|
299
|
+
[ "$status" -eq 0 ]
|
|
300
|
+
echo "$output" | grep -q "changeset: .changeset/p106-default.md"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# ── Bin shim parity ─────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
@test "derive-release-vehicle: bin shim wr-itil-derive-release-vehicle exists and is executable" {
|
|
306
|
+
BIN="$(cd "$SCRIPTS_DIR/../bin" && pwd)/wr-itil-derive-release-vehicle"
|
|
307
|
+
[ -f "$BIN" ]
|
|
308
|
+
[ -x "$BIN" ]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@test "derive-release-vehicle: bin shim dispatches to the canonical script" {
|
|
312
|
+
BIN="$(cd "$SCRIPTS_DIR/../bin" && pwd)/wr-itil-derive-release-vehicle"
|
|
313
|
+
cat > .changeset/p107-shim.md <<'EOF'
|
|
314
|
+
---
|
|
315
|
+
'@windyroad/itil': patch
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
P107.
|
|
319
|
+
EOF
|
|
320
|
+
cat > docs/problems/known-error/107-shim.md <<'EOF'
|
|
321
|
+
# Problem 107: Shim
|
|
322
|
+
|
|
323
|
+
**Status**: Known Error
|
|
324
|
+
|
|
325
|
+
## Fix Strategy
|
|
326
|
+
|
|
327
|
+
Ship via `.changeset/p107-shim.md`.
|
|
328
|
+
EOF
|
|
329
|
+
git add .
|
|
330
|
+
git commit -q -m "init"
|
|
331
|
+
|
|
332
|
+
git checkout -q -b changeset-release/main
|
|
333
|
+
git rm -q .changeset/p107-shim.md
|
|
334
|
+
git commit -q -m "chore: version packages"
|
|
335
|
+
git checkout -q main
|
|
336
|
+
git merge --no-ff -q changeset-release/main -m "Merge pull request #47 from windyroad/changeset-release/main"
|
|
337
|
+
|
|
338
|
+
run "$BIN" P107 docs/problems
|
|
339
|
+
[ "$status" -eq 0 ]
|
|
340
|
+
echo "$output" | grep -q "RELEASE_VEHICLE:"
|
|
341
|
+
echo "$output" | grep -q "pr: #47"
|
|
342
|
+
}
|
|
@@ -157,6 +157,33 @@ The `## Fix Released` section contains: release marker (version, commit SHA, or
|
|
|
157
157
|
|
|
158
158
|
When this transition is folded into a `fix(<scope>): ... (closes P<NNN>)` commit (the common case), the `git mv` + `Edit` + re-stage + README refresh all join that single commit — never split across commits.
|
|
159
159
|
|
|
160
|
+
**Release-vehicle citation (P267)**: derive the release marker deterministically — never hand-type it from `git log` browse output. Hand-typed citations are fragile to wrong-release-cited errors when a session pre-applies transitions for multiple sibling tickets before working any of them (origin: 2026-05-18 session 7 iter 1 — P250's K→V cited P247's release refs). Invoke the helper:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
wr-itil-derive-release-vehicle <NNN>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`wr-itil-derive-release-vehicle` is the ADR-049 `$PATH` shim (adopter-safe — resolves the canonical `derive-release-vehicle.sh` relative to the script, NOT cwd; P317/RFC-009) that reads the ticket body for the `.changeset/<name>.md` reference, walks `git log --diff-filter=D` for the deletion commit (`chore: version packages`), resolves the merge PR via the first-parent ancestry-path merge commit (or `gh pr list` fallback when available), and emits a structured citation block on stdout:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
RELEASE_VEHICLE:
|
|
170
|
+
changeset: .changeset/<name>.md
|
|
171
|
+
version-packages-commit: <SHA>
|
|
172
|
+
pr: #<N>
|
|
173
|
+
merge-commit: <SHA>
|
|
174
|
+
release-date: <YYYY-MM-DD>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Use the structured values verbatim when authoring the `## Fix Released` section's release marker (e.g. `Released in @windyroad/itil@<version> (merge commit <merge-commit>, PR #<N>, released <release-date>)`). The helper is **mechanical** per ADR-044 framework-resolution — no `AskUserQuestion` per transition.
|
|
178
|
+
|
|
179
|
+
**Helper exit-code routing**:
|
|
180
|
+
|
|
181
|
+
- Exit 0 (full citation emitted): use the values in the `## Fix Released` section.
|
|
182
|
+
- Exit 1 (ticket file not found): the upstream ticket-discovery step (Step 2) should have caught this; if it fires here, halt and report.
|
|
183
|
+
- Exit 2 (no `.changeset/<name>.md` reference in ticket body): add the changeset reference to the ticket's Fix Strategy section, OR cite the release marker manually with explicit `<!-- no-changeset-reference -->` comment so a future review can audit the gap.
|
|
184
|
+
- Exit 3 (changeset still in working tree — unreleased): the fix has not yet been released to npm. Halt the transition — `.known-error.md` → `.verifying.md` requires a shipped release per ADR-022. The orchestrator's Step 6.5 drain should fire first.
|
|
185
|
+
- Exit 4 (deletion commit found but no merge PR resolvable): direct-to-main commit (no PR), or `gh pr list` unavailable + first-parent walk did not match. Cite the deletion commit SHA manually in the `## Fix Released` section with an explicit `<!-- no-pr -->` comment.
|
|
186
|
+
|
|
160
187
|
**Verification Pending → Closed**:
|
|
161
188
|
|
|
162
189
|
```bash
|