@windyroad/itil 0.24.0-preview.268 → 0.24.1-preview.270
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 +2 -1
- package/scripts/check-problems-readme-budget.sh +73 -0
- package/scripts/classify-readme-drift.sh +115 -0
- package/scripts/reconcile-readme.sh +208 -0
- package/scripts/test/check-problems-readme-budget.bats +192 -0
- package/scripts/test/classify-readme-drift.bats +262 -0
- package/scripts/test/reconcile-readme.bats +509 -0
- package/scripts/test/release-watch-poll-loop.bats +180 -0
- package/skills/work-problems/SKILL.md +41 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# @problem P149 — manage-problem Step 0 reconcile halt-on-drift directive
|
|
4
|
+
# doesn't distinguish uncommitted-rename-rooted drift (same-session pending,
|
|
5
|
+
# in-flow P094/P062 refresh will land it in the upcoming commit per ADR-014)
|
|
6
|
+
# from committed cross-session drift (must halt and route to
|
|
7
|
+
# /wr-itil:reconcile-readme).
|
|
8
|
+
#
|
|
9
|
+
# Contract: `classify-readme-drift.sh <drift-stdout-file> [<problems-dir>]`
|
|
10
|
+
# reads the captured stdout of reconcile-readme.sh exit-1 output AND the
|
|
11
|
+
# working-tree state via `git status --porcelain`, then classifies the drift.
|
|
12
|
+
#
|
|
13
|
+
# Output (stdout, one classification line):
|
|
14
|
+
# INLINE_REFRESH covered=<N> — every drift ID is the destination
|
|
15
|
+
# of a staged rename in the working
|
|
16
|
+
# tree; defer to in-flow P094/P062
|
|
17
|
+
# refresh per ADR-014.
|
|
18
|
+
# HALT_ROUTE_RECONCILE uncovered=<N> — at least one drift ID is NOT
|
|
19
|
+
# covered by a working-tree rename;
|
|
20
|
+
# committed cross-session drift OR
|
|
21
|
+
# mixed; route to
|
|
22
|
+
# /wr-itil:reconcile-readme.
|
|
23
|
+
#
|
|
24
|
+
# Exit codes:
|
|
25
|
+
# 0 = INLINE_REFRESH
|
|
26
|
+
# 1 = HALT_ROUTE_RECONCILE
|
|
27
|
+
# 2 = parse error (drift-stdout-file missing or empty)
|
|
28
|
+
#
|
|
29
|
+
# @adr ADR-014 (single-commit grain — the carve-out preserves it for the
|
|
30
|
+
# in-flow path while keeping cross-session drift safety)
|
|
31
|
+
# @adr ADR-005 (Plugin testing strategy — behavioural bats per P081)
|
|
32
|
+
# @jtbd JTBD-006 (Progress the Backlog While I'm Away — AFK loop continuity)
|
|
33
|
+
# @jtbd JTBD-001 (Enforce Governance Without Slowing Down — single-commit grain)
|
|
34
|
+
|
|
35
|
+
setup() {
|
|
36
|
+
SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
37
|
+
SCRIPT="$SCRIPTS_DIR/classify-readme-drift.sh"
|
|
38
|
+
FIXTURE_DIR="$(mktemp -d)"
|
|
39
|
+
cd "$FIXTURE_DIR"
|
|
40
|
+
git init -q
|
|
41
|
+
git config user.email test@example.com
|
|
42
|
+
git config user.name "Test"
|
|
43
|
+
mkdir -p docs/problems
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
teardown() {
|
|
47
|
+
cd /
|
|
48
|
+
rm -rf "$FIXTURE_DIR"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# ── Existence + executable ──────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
@test "classify-readme-drift: script exists" {
|
|
54
|
+
[ -f "$SCRIPT" ]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@test "classify-readme-drift: script is executable" {
|
|
58
|
+
[ -x "$SCRIPT" ]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# ── Exit 0 (INLINE_REFRESH): every drift ID covered by staged rename ────────
|
|
62
|
+
|
|
63
|
+
@test "classify-readme-drift: single drift ID covered by staged rename → INLINE_REFRESH" {
|
|
64
|
+
# Simulate the P148 scenario: prior session staged a rename for a ticket
|
|
65
|
+
# transition; reconcile-readme observes the README is stale (still claims
|
|
66
|
+
# the old status) and emits a DRIFT line. The in-flow P094/P062 refresh
|
|
67
|
+
# at Step 5/Step 7 will reconcile the README in the same commit per
|
|
68
|
+
# ADR-014.
|
|
69
|
+
echo "stub" > docs/problems/148-foo.open.md
|
|
70
|
+
git add docs/problems/148-foo.open.md
|
|
71
|
+
git commit -q -m "init"
|
|
72
|
+
git mv docs/problems/148-foo.open.md docs/problems/148-foo.verifying.md
|
|
73
|
+
|
|
74
|
+
cat > drift.txt <<'EOF'
|
|
75
|
+
DRIFT P148 wsjf-rankings: claims=open actual=verifying
|
|
76
|
+
MISSING P148 verification-queue: actual=verifying
|
|
77
|
+
EOF
|
|
78
|
+
|
|
79
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
80
|
+
[ "$status" -eq 0 ]
|
|
81
|
+
echo "$output" | grep -q "INLINE_REFRESH"
|
|
82
|
+
echo "$output" | grep -q "covered=1"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@test "classify-readme-drift: multiple drift IDs all covered by staged renames → INLINE_REFRESH" {
|
|
86
|
+
echo "stub" > docs/problems/100-aaa.open.md
|
|
87
|
+
echo "stub" > docs/problems/101-bbb.open.md
|
|
88
|
+
git add docs/problems/
|
|
89
|
+
git commit -q -m "init"
|
|
90
|
+
|
|
91
|
+
git mv docs/problems/100-aaa.open.md docs/problems/100-aaa.known-error.md
|
|
92
|
+
git mv docs/problems/101-bbb.open.md docs/problems/101-bbb.verifying.md
|
|
93
|
+
|
|
94
|
+
cat > drift.txt <<'EOF'
|
|
95
|
+
DRIFT P100 wsjf-rankings: claims=open actual=known-error
|
|
96
|
+
DRIFT P101 wsjf-rankings: claims=open actual=verifying
|
|
97
|
+
MISSING P101 verification-queue: actual=verifying
|
|
98
|
+
EOF
|
|
99
|
+
|
|
100
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
101
|
+
[ "$status" -eq 0 ]
|
|
102
|
+
echo "$output" | grep -q "INLINE_REFRESH"
|
|
103
|
+
echo "$output" | grep -q "covered=2"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@test "classify-readme-drift: rename-with-modification (RM) is recognised as covered" {
|
|
107
|
+
# `git mv` followed by an `Edit` to the renamed file's body shows up as
|
|
108
|
+
# `RM` in `git status --porcelain` (rename + unstaged modification). The
|
|
109
|
+
# carve-out must treat both `R ` and `RM` as covered.
|
|
110
|
+
cat > docs/problems/200-xyz.open.md <<'EOF'
|
|
111
|
+
# Problem 200: XYZ
|
|
112
|
+
**Status**: Open
|
|
113
|
+
EOF
|
|
114
|
+
git add docs/problems/200-xyz.open.md
|
|
115
|
+
git commit -q -m "init"
|
|
116
|
+
|
|
117
|
+
git mv docs/problems/200-xyz.open.md docs/problems/200-xyz.verifying.md
|
|
118
|
+
# Modify the renamed file's content (without re-staging) — emits RM.
|
|
119
|
+
cat >> docs/problems/200-xyz.verifying.md <<'EOF'
|
|
120
|
+
|
|
121
|
+
## Fix Released
|
|
122
|
+
Deployed in v0.99.0.
|
|
123
|
+
EOF
|
|
124
|
+
|
|
125
|
+
cat > drift.txt <<'EOF'
|
|
126
|
+
DRIFT P200 wsjf-rankings: claims=open actual=verifying
|
|
127
|
+
EOF
|
|
128
|
+
|
|
129
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
130
|
+
[ "$status" -eq 0 ]
|
|
131
|
+
echo "$output" | grep -q "INLINE_REFRESH"
|
|
132
|
+
echo "$output" | grep -q "covered=1"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# ── Exit 1 (HALT_ROUTE_RECONCILE): committed cross-session drift ────────────
|
|
136
|
+
|
|
137
|
+
@test "classify-readme-drift: single drift ID not covered by any rename → HALT_ROUTE_RECONCILE" {
|
|
138
|
+
# Simulate the P118 originating scenario: a prior session committed a
|
|
139
|
+
# ticket transition without staging the README refresh. The README is
|
|
140
|
+
# stale; the working tree is clean (no pending rename). Halt and route
|
|
141
|
+
# to /wr-itil:reconcile-readme.
|
|
142
|
+
cat > docs/problems/074-foo.closed.md <<'EOF'
|
|
143
|
+
# Problem 074: Foo
|
|
144
|
+
**Status**: Closed
|
|
145
|
+
EOF
|
|
146
|
+
git add docs/problems/074-foo.closed.md
|
|
147
|
+
git commit -q -m "init"
|
|
148
|
+
|
|
149
|
+
cat > drift.txt <<'EOF'
|
|
150
|
+
DRIFT P074 wsjf-rankings: claims=open actual=closed
|
|
151
|
+
EOF
|
|
152
|
+
|
|
153
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
154
|
+
[ "$status" -eq 1 ]
|
|
155
|
+
echo "$output" | grep -q "HALT_ROUTE_RECONCILE"
|
|
156
|
+
echo "$output" | grep -q "uncovered=1"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@test "classify-readme-drift: mixed (some covered, some uncovered) → HALT_ROUTE_RECONCILE" {
|
|
160
|
+
# Mixed case: one ID has a staged rename, another is committed drift.
|
|
161
|
+
# The architect's confirmation: mixed routes to halt (the safe path),
|
|
162
|
+
# because reconcile-readme will resolve both, and the in-flow refresh
|
|
163
|
+
# only handles the rename'd one.
|
|
164
|
+
cat > docs/problems/074-foo.closed.md <<'EOF'
|
|
165
|
+
# Problem 074: Foo
|
|
166
|
+
**Status**: Closed
|
|
167
|
+
EOF
|
|
168
|
+
cat > docs/problems/148-bar.open.md <<'EOF'
|
|
169
|
+
# Problem 148: Bar
|
|
170
|
+
**Status**: Open
|
|
171
|
+
EOF
|
|
172
|
+
git add docs/problems/
|
|
173
|
+
git commit -q -m "init"
|
|
174
|
+
|
|
175
|
+
git mv docs/problems/148-bar.open.md docs/problems/148-bar.verifying.md
|
|
176
|
+
|
|
177
|
+
cat > drift.txt <<'EOF'
|
|
178
|
+
DRIFT P074 wsjf-rankings: claims=open actual=closed
|
|
179
|
+
DRIFT P148 wsjf-rankings: claims=open actual=verifying
|
|
180
|
+
EOF
|
|
181
|
+
|
|
182
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
183
|
+
[ "$status" -eq 1 ]
|
|
184
|
+
echo "$output" | grep -q "HALT_ROUTE_RECONCILE"
|
|
185
|
+
# Only P074 is uncovered.
|
|
186
|
+
echo "$output" | grep -q "uncovered=1"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@test "classify-readme-drift: clean working tree (no renames) → HALT_ROUTE_RECONCILE for any drift" {
|
|
190
|
+
cat > docs/problems/050-zzz.verifying.md <<'EOF'
|
|
191
|
+
# Problem 050: Zzz
|
|
192
|
+
**Status**: Verification Pending
|
|
193
|
+
EOF
|
|
194
|
+
git add docs/problems/050-zzz.verifying.md
|
|
195
|
+
git commit -q -m "init"
|
|
196
|
+
|
|
197
|
+
cat > drift.txt <<'EOF'
|
|
198
|
+
MISSING P050 verification-queue: actual=verifying
|
|
199
|
+
EOF
|
|
200
|
+
|
|
201
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
202
|
+
[ "$status" -eq 1 ]
|
|
203
|
+
echo "$output" | grep -q "HALT_ROUTE_RECONCILE"
|
|
204
|
+
echo "$output" | grep -q "uncovered=1"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# ── Exit 2: parse errors ────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
@test "classify-readme-drift: missing drift-stdout-file → exit 2" {
|
|
210
|
+
run "$SCRIPT" /nonexistent/drift.txt docs/problems
|
|
211
|
+
[ "$status" -eq 2 ]
|
|
212
|
+
echo "$output" | grep -qi "PARSE_ERROR"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@test "classify-readme-drift: empty drift-stdout-file → exit 2" {
|
|
216
|
+
: > drift.txt
|
|
217
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
218
|
+
[ "$status" -eq 2 ]
|
|
219
|
+
echo "$output" | grep -qi "PARSE_ERROR"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@test "classify-readme-drift: no arguments → exit 2 with usage" {
|
|
223
|
+
run "$SCRIPT"
|
|
224
|
+
[ "$status" -eq 2 ]
|
|
225
|
+
echo "$output" | grep -qi "USAGE"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# ── Default problems-dir resolution ─────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
@test "classify-readme-drift: defaults to ./docs/problems when problems-dir omitted" {
|
|
231
|
+
echo "stub" > docs/problems/300-default.open.md
|
|
232
|
+
git add docs/problems/300-default.open.md
|
|
233
|
+
git commit -q -m "init"
|
|
234
|
+
git mv docs/problems/300-default.open.md docs/problems/300-default.verifying.md
|
|
235
|
+
|
|
236
|
+
cat > drift.txt <<'EOF'
|
|
237
|
+
DRIFT P300 wsjf-rankings: claims=open actual=verifying
|
|
238
|
+
EOF
|
|
239
|
+
|
|
240
|
+
run "$SCRIPT" drift.txt
|
|
241
|
+
[ "$status" -eq 0 ]
|
|
242
|
+
echo "$output" | grep -q "INLINE_REFRESH"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# ── Non-git working tree (defensive — outside a repo) ───────────────────────
|
|
246
|
+
|
|
247
|
+
@test "classify-readme-drift: outside git repo with drift → HALT_ROUTE_RECONCILE (no rename evidence)" {
|
|
248
|
+
# If the script is invoked from outside a git repo (defensive — should not
|
|
249
|
+
# happen in the SKILL.md flow but safest fail-closed), there is no rename
|
|
250
|
+
# evidence, so all drift is treated as uncovered.
|
|
251
|
+
NON_GIT="$(mktemp -d)"
|
|
252
|
+
cd "$NON_GIT"
|
|
253
|
+
mkdir -p docs/problems
|
|
254
|
+
cat > drift.txt <<'EOF'
|
|
255
|
+
DRIFT P074 wsjf-rankings: claims=open actual=closed
|
|
256
|
+
EOF
|
|
257
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
258
|
+
[ "$status" -eq 1 ]
|
|
259
|
+
echo "$output" | grep -q "HALT_ROUTE_RECONCILE"
|
|
260
|
+
cd /
|
|
261
|
+
rm -rf "$NON_GIT"
|
|
262
|
+
}
|