@windyroad/itil 0.24.0 → 0.24.1-preview.273

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,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
+ }