@windyroad/itil 0.47.2 → 0.47.4-preview.539
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
|
@@ -75,23 +75,78 @@ if [ -z "$DRIFT_IDS" ]; then
|
|
|
75
75
|
exit 2
|
|
76
76
|
fi
|
|
77
77
|
|
|
78
|
-
# ── Build set of IDs covered by
|
|
79
|
-
# `git status --porcelain` v1 emits rename
|
|
80
|
-
#
|
|
81
|
-
# RM
|
|
78
|
+
# ── Build set of IDs covered by in-flight ticket renames in the working tree
|
|
79
|
+
# `git status --porcelain` v1 emits same-session rename evidence in two shapes:
|
|
80
|
+
#
|
|
81
|
+
# 1. R / RM rename entries (git's rename-detection matched the old + new path)
|
|
82
|
+
# R <old-path> -> <new-path>
|
|
83
|
+
# RM <old-path> -> <new-path> (rename + unstaged modification)
|
|
84
|
+
#
|
|
85
|
+
# 2. Same-ID D + (A or ??) pair (substantial-body edit defeated rename-
|
|
86
|
+
# detection so the move renders as delete + add for the same ticket ID
|
|
87
|
+
# — P306). We treat such a pair as equivalent to an R entry for drift-
|
|
88
|
+
# coverage purposes: the in-flow P094/P062 refresh will reconcile the
|
|
89
|
+
# README in the upcoming commit per ADR-014.
|
|
90
|
+
#
|
|
82
91
|
# We match the destination path's ticket ID — the post-rename status is what
|
|
83
|
-
# the in-flow
|
|
92
|
+
# the in-flow refresh will reconcile.
|
|
84
93
|
RENAMED_IDS=""
|
|
85
94
|
if git rev-parse --git-dir >/dev/null 2>&1; then
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
# `-u` (= `--untracked-files=all`) expands untracked directories so the
|
|
96
|
+
# `??` side of a D + `??` rename pair lists individual files, not the
|
|
97
|
+
# collapsed parent directory.
|
|
98
|
+
STATUS_LINES="$(git status --porcelain -u "$PROBLEMS_DIR" 2>/dev/null)"
|
|
99
|
+
|
|
100
|
+
# Shape 1: R / RM rename entries — extract destination-path ticket ID.
|
|
101
|
+
R_IDS="$(
|
|
102
|
+
printf '%s\n' "$STATUS_LINES" \
|
|
88
103
|
| awk '/^R/' \
|
|
89
104
|
| sed 's|.*-> ||' \
|
|
90
105
|
| sed "s|^${PROBLEMS_DIR}/||" \
|
|
91
106
|
| grep -oE '^[0-9]{3}' \
|
|
107
|
+
| awk '{ printf "P%s\n", $0 }'
|
|
108
|
+
)"
|
|
109
|
+
|
|
110
|
+
# Shape 2: same-ID D + (A or ??) pair — intersect deleted-IDs with added-
|
|
111
|
+
# IDs. `git status --porcelain` emits one entry per file:
|
|
112
|
+
# ` D <path>` staged delete (note: column 1 is space, column 2 is `D`)
|
|
113
|
+
# `D <path>` staged delete (alternate form when also touched in index)
|
|
114
|
+
# `A <path>` staged add
|
|
115
|
+
# `?? <path>` untracked add
|
|
116
|
+
# We treat any 3-char prefix line whose 1st-or-2nd column is `D`/`A`/`?`
|
|
117
|
+
# as candidate, then extract the leading `<NNN>` from the basename.
|
|
118
|
+
DELETED_IDS="$(
|
|
119
|
+
printf '%s\n' "$STATUS_LINES" \
|
|
120
|
+
| awk 'substr($0,1,2) ~ /D / || substr($0,1,2) ~ /^ D/' \
|
|
121
|
+
| sed 's|^...||' \
|
|
122
|
+
| sed "s|^${PROBLEMS_DIR}/||" \
|
|
123
|
+
| grep -oE '^[0-9]{3}' \
|
|
92
124
|
| awk '{ printf "P%s\n", $0 }' \
|
|
93
125
|
| sort -u
|
|
94
126
|
)"
|
|
127
|
+
ADDED_IDS="$(
|
|
128
|
+
printf '%s\n' "$STATUS_LINES" \
|
|
129
|
+
| awk 'substr($0,1,2) ~ /A / || substr($0,1,2) == "??"' \
|
|
130
|
+
| sed 's|^...||' \
|
|
131
|
+
| sed "s|^${PROBLEMS_DIR}/||" \
|
|
132
|
+
| grep -oE '^[0-9]{3}' \
|
|
133
|
+
| awk '{ printf "P%s\n", $0 }' \
|
|
134
|
+
| sort -u
|
|
135
|
+
)"
|
|
136
|
+
DA_PAIR_IDS=""
|
|
137
|
+
if [ -n "$DELETED_IDS" ] && [ -n "$ADDED_IDS" ]; then
|
|
138
|
+
DA_PAIR_IDS="$(
|
|
139
|
+
comm -12 \
|
|
140
|
+
<(printf '%s\n' "$DELETED_IDS") \
|
|
141
|
+
<(printf '%s\n' "$ADDED_IDS")
|
|
142
|
+
)"
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
RENAMED_IDS="$(
|
|
146
|
+
{ printf '%s\n' "$R_IDS"; printf '%s\n' "$DA_PAIR_IDS"; } \
|
|
147
|
+
| grep -v '^$' \
|
|
148
|
+
| sort -u
|
|
149
|
+
)"
|
|
95
150
|
fi
|
|
96
151
|
|
|
97
152
|
# ── Cross-reference each drift ID against the renamed set ───────────────────
|
|
@@ -251,8 +251,16 @@ if [ -d "$PROBLEMS_DIR" ]; then
|
|
|
251
251
|
# problem_rfc_ids["P168"] = "RFC-001 RFC-002 ..."
|
|
252
252
|
declare -A problem_rfc_rows
|
|
253
253
|
declare -A problem_rfc_ids
|
|
254
|
+
# P312 / ADR-031: scan both flat docs/problems/<NNN>-*.md AND per-state
|
|
255
|
+
# subdir docs/problems/<state>/<NNN>-*.md layouts so the reverse-trace
|
|
256
|
+
# remains valid post-migration. Mirrors reconcile-readme.sh lines 74-110.
|
|
254
257
|
shopt -s nullglob
|
|
255
|
-
|
|
258
|
+
problem_files=( "$PROBLEMS_DIR"/[0-9][0-9][0-9]-*.md )
|
|
259
|
+
for ticket_status in open known-error verifying closed parked; do
|
|
260
|
+
problem_files+=( "$PROBLEMS_DIR"/"$ticket_status"/[0-9][0-9][0-9]-*.md )
|
|
261
|
+
done
|
|
262
|
+
shopt -u nullglob
|
|
263
|
+
for pf in "${problem_files[@]}"; do
|
|
256
264
|
pbase="$(basename "$pf")"
|
|
257
265
|
pnum="${pbase%%-*}"
|
|
258
266
|
pid="P${pnum}"
|
|
@@ -274,7 +282,6 @@ if [ -d "$PROBLEMS_DIR" ]; then
|
|
|
274
282
|
done < <(awk -v start="$sec_start" 'NR>start { if (/^## /) exit; print }' "$pf")
|
|
275
283
|
problem_rfc_ids["$pid"]="$rfcs_in_p"
|
|
276
284
|
done
|
|
277
|
-
shopt -u nullglob
|
|
278
285
|
|
|
279
286
|
# 1. MISSING_REVERSE_TRACE: RFC claims P, P does not list RFC.
|
|
280
287
|
for rfc_id in "${!rfc_problems_claim[@]}"; do
|
|
@@ -132,6 +132,136 @@ EOF
|
|
|
132
132
|
echo "$output" | grep -q "covered=1"
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
# ── P306: same-ID D+A pair coverage (substantial-body in-flight rename) ─────
|
|
136
|
+
# When `git mv` is followed by a substantial body edit, git's rename-detection
|
|
137
|
+
# may not match the old/new paths and emits a delete+add pair instead of an R
|
|
138
|
+
# entry. The classifier must recognise a same-ID D+A pair as same-session
|
|
139
|
+
# coverage — equivalent to an R/RM entry — and return INLINE_REFRESH.
|
|
140
|
+
|
|
141
|
+
@test "classify-readme-drift: same-ID D+A pair (substantial-body rename) → INLINE_REFRESH" {
|
|
142
|
+
# Seed an open ticket, commit, then `git mv` + substantial-body edit. With
|
|
143
|
+
# rename-detection thresholds, git may emit `D ` + `A ` (or `??`) rather
|
|
144
|
+
# than `R `. We force the D+A shape by writing wholly different content to
|
|
145
|
+
# the destination path.
|
|
146
|
+
cat > docs/problems/306-foo.open.md <<'EOF'
|
|
147
|
+
# Problem 306: Foo
|
|
148
|
+
|
|
149
|
+
**Status**: Open
|
|
150
|
+
|
|
151
|
+
Original body — short.
|
|
152
|
+
EOF
|
|
153
|
+
git add docs/problems/306-foo.open.md
|
|
154
|
+
git commit -q -m "init"
|
|
155
|
+
|
|
156
|
+
# Create the new file at the verifying path with substantially different
|
|
157
|
+
# body BEFORE removing the old path (avoid empty-dir prune). git's
|
|
158
|
+
# rename-detection then sees delete + add as distinct entries rather than
|
|
159
|
+
# an R-rename, because the bodies are wholly different.
|
|
160
|
+
cat > docs/problems/306-foo.verifying.md <<'EOF'
|
|
161
|
+
# Problem 306: Foo
|
|
162
|
+
|
|
163
|
+
**Status**: Verification Pending
|
|
164
|
+
|
|
165
|
+
Wholly rewritten body so git rename-detection does not match the source.
|
|
166
|
+
This is a multi-paragraph substantive rewrite that exercises the D+A path.
|
|
167
|
+
|
|
168
|
+
## Fix Released
|
|
169
|
+
|
|
170
|
+
Deployed in vX.Y.Z. Adds a behavioural fixture that exercises the
|
|
171
|
+
classifier across the D+A coverage gap that P306 captured.
|
|
172
|
+
EOF
|
|
173
|
+
git rm -q docs/problems/306-foo.open.md
|
|
174
|
+
git add docs/problems/306-foo.verifying.md
|
|
175
|
+
|
|
176
|
+
cat > drift.txt <<'EOF'
|
|
177
|
+
DRIFT P306 wsjf-rankings: claims=open actual=verifying
|
|
178
|
+
MISSING P306 verification-queue: actual=verifying
|
|
179
|
+
EOF
|
|
180
|
+
|
|
181
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
182
|
+
[ "$status" -eq 0 ]
|
|
183
|
+
echo "$output" | grep -q "INLINE_REFRESH"
|
|
184
|
+
echo "$output" | grep -q "covered=1"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@test "classify-readme-drift: same-ID D+A pair with untracked add (D + ??) → INLINE_REFRESH" {
|
|
188
|
+
# Variant: the new path is untracked (not yet `git add`-ed). git status
|
|
189
|
+
# emits `D ` for the old path and `??` for the new path. The classifier
|
|
190
|
+
# must still recognise the same-ID pair as same-session coverage.
|
|
191
|
+
cat > docs/problems/307-bar.open.md <<'EOF'
|
|
192
|
+
# Problem 307: Bar
|
|
193
|
+
**Status**: Open
|
|
194
|
+
EOF
|
|
195
|
+
git add docs/problems/307-bar.open.md
|
|
196
|
+
git commit -q -m "init"
|
|
197
|
+
|
|
198
|
+
cat > docs/problems/307-bar.verifying.md <<'EOF'
|
|
199
|
+
# Problem 307: Bar
|
|
200
|
+
|
|
201
|
+
**Status**: Verification Pending
|
|
202
|
+
|
|
203
|
+
Untracked add side of the D+?? pair.
|
|
204
|
+
EOF
|
|
205
|
+
git rm -q docs/problems/307-bar.open.md
|
|
206
|
+
|
|
207
|
+
cat > drift.txt <<'EOF'
|
|
208
|
+
DRIFT P307 wsjf-rankings: claims=open actual=verifying
|
|
209
|
+
EOF
|
|
210
|
+
|
|
211
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
212
|
+
[ "$status" -eq 0 ]
|
|
213
|
+
echo "$output" | grep -q "INLINE_REFRESH"
|
|
214
|
+
echo "$output" | grep -q "covered=1"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@test "classify-readme-drift: D-only (no matching A for same ID) → HALT_ROUTE_RECONCILE" {
|
|
218
|
+
# Negative case: a delete without a corresponding add for the same ID is
|
|
219
|
+
# NOT a rename — it is a genuine deletion. Must HALT.
|
|
220
|
+
cat > docs/problems/308-baz.open.md <<'EOF'
|
|
221
|
+
# Problem 308: Baz
|
|
222
|
+
**Status**: Open
|
|
223
|
+
EOF
|
|
224
|
+
git add docs/problems/308-baz.open.md
|
|
225
|
+
git commit -q -m "init"
|
|
226
|
+
|
|
227
|
+
git rm -q docs/problems/308-baz.open.md
|
|
228
|
+
|
|
229
|
+
cat > drift.txt <<'EOF'
|
|
230
|
+
DRIFT P308 wsjf-rankings: claims=open actual=verifying
|
|
231
|
+
EOF
|
|
232
|
+
|
|
233
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
234
|
+
[ "$status" -eq 1 ]
|
|
235
|
+
echo "$output" | grep -q "HALT_ROUTE_RECONCILE"
|
|
236
|
+
echo "$output" | grep -q "uncovered=1"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@test "classify-readme-drift: mismatched D + A (different IDs) → HALT_ROUTE_RECONCILE" {
|
|
240
|
+
# Negative case: a delete for one ID + an add for a different ID is NOT a
|
|
241
|
+
# rename of either ticket — both are uncovered.
|
|
242
|
+
cat > docs/problems/309-alpha.open.md <<'EOF'
|
|
243
|
+
# Problem 309: Alpha
|
|
244
|
+
**Status**: Open
|
|
245
|
+
EOF
|
|
246
|
+
git add docs/problems/309-alpha.open.md
|
|
247
|
+
git commit -q -m "init"
|
|
248
|
+
|
|
249
|
+
cat > docs/problems/310-beta.open.md <<'EOF'
|
|
250
|
+
# Problem 310: Beta
|
|
251
|
+
**Status**: Open
|
|
252
|
+
EOF
|
|
253
|
+
git rm -q docs/problems/309-alpha.open.md
|
|
254
|
+
git add docs/problems/310-beta.open.md
|
|
255
|
+
|
|
256
|
+
cat > drift.txt <<'EOF'
|
|
257
|
+
DRIFT P309 wsjf-rankings: claims=open actual=verifying
|
|
258
|
+
EOF
|
|
259
|
+
|
|
260
|
+
run "$SCRIPT" drift.txt docs/problems
|
|
261
|
+
[ "$status" -eq 1 ]
|
|
262
|
+
echo "$output" | grep -q "HALT_ROUTE_RECONCILE"
|
|
263
|
+
}
|
|
264
|
+
|
|
135
265
|
# ── Exit 1 (HALT_ROUTE_RECONCILE): committed cross-session drift ────────────
|
|
136
266
|
|
|
137
267
|
@test "classify-readme-drift: single drift ID not covered by any rename → HALT_ROUTE_RECONCILE" {
|
|
@@ -131,6 +131,40 @@ EOF
|
|
|
131
131
|
fi
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
# Helper: write a problem ticket under the per-state subdir layout per
|
|
135
|
+
# ADR-031 (state is the parent directory; filename has NO `.state.md` suffix).
|
|
136
|
+
# Args: <pid-num> <slug> <state> <rfcs-rows-block>
|
|
137
|
+
# Used to regression-test P312 — reconcile-rfcs reverse-trace must traverse
|
|
138
|
+
# docs/problems/<state>/<NNN>-*.md, not just flat docs/problems/<NNN>-*.<state>.md.
|
|
139
|
+
write_problem_subdir() {
|
|
140
|
+
local num="$1" slug="$2" state="$3" rfcs_rows="${4:-}"
|
|
141
|
+
mkdir -p "$PROBLEMS_DIR/$state"
|
|
142
|
+
local file="$PROBLEMS_DIR/$state/${num}-${slug}.md"
|
|
143
|
+
cat > "$file" <<EOF
|
|
144
|
+
# Problem ${num}: ${slug}
|
|
145
|
+
|
|
146
|
+
**Status**: ${state}
|
|
147
|
+
|
|
148
|
+
## Description
|
|
149
|
+
|
|
150
|
+
stub
|
|
151
|
+
|
|
152
|
+
## Related
|
|
153
|
+
|
|
154
|
+
stub
|
|
155
|
+
EOF
|
|
156
|
+
if [ -n "$rfcs_rows" ]; then
|
|
157
|
+
cat >> "$file" <<EOF
|
|
158
|
+
|
|
159
|
+
## RFCs
|
|
160
|
+
|
|
161
|
+
| RFC | Status | Title |
|
|
162
|
+
|-----|--------|-------|
|
|
163
|
+
${rfcs_rows}
|
|
164
|
+
EOF
|
|
165
|
+
fi
|
|
166
|
+
}
|
|
167
|
+
|
|
134
168
|
# ── Existence + executable ──────────────────────────────────────────────────
|
|
135
169
|
|
|
136
170
|
@test "reconcile-rfcs: script exists" {
|
|
@@ -413,6 +447,35 @@ EOF
|
|
|
413
447
|
done <<< "$output"
|
|
414
448
|
}
|
|
415
449
|
|
|
450
|
+
# ── P312: per-state subdir reverse-trace (ADR-031 layout) ───────────────────
|
|
451
|
+
# Closes P312 — reconcile-rfcs reported spurious MISSING_REVERSE_TRACE for
|
|
452
|
+
# tickets that live under docs/problems/<state>/<NNN>-*.md because the
|
|
453
|
+
# reverse-trace pass only globbed the flat docs/problems/<NNN>-*.md layout.
|
|
454
|
+
# RFC-002-class dual-tolerant-glob fix mirroring the sibling already shipped
|
|
455
|
+
# in reconcile-readme.sh (P118).
|
|
456
|
+
|
|
457
|
+
@test "P312: reverse-trace clean when problem ticket lives in per-state subdir" {
|
|
458
|
+
write_rfc "001" "foo" "accepted"
|
|
459
|
+
write_minimal_readme "| 2.0 | RFC-001 | foo | 3 Med | Accepted | M | 2026-05-05 |"
|
|
460
|
+
# Ticket lives under docs/problems/verifying/168-p168.md (no .state suffix).
|
|
461
|
+
write_problem_subdir "168" "p168" "verifying" "| RFC-001 | accepted | foo |"
|
|
462
|
+
run bash "$SCRIPT" "$FIXTURE_DIR" "$PROBLEMS_DIR"
|
|
463
|
+
[ "$status" -eq 0 ]
|
|
464
|
+
[[ "$output" != *"MISSING_REVERSE_TRACE"* ]]
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@test "P312: reverse-trace detects missing trace when problem ticket lives in per-state subdir" {
|
|
468
|
+
write_rfc "001" "foo" "accepted"
|
|
469
|
+
write_minimal_readme "| 2.0 | RFC-001 | foo | 3 Med | Accepted | M | 2026-05-05 |"
|
|
470
|
+
# Subdir ticket WITHOUT a `## RFCs` section → MISSING_REVERSE_TRACE must fire.
|
|
471
|
+
write_problem_subdir "168" "p168" "verifying" ""
|
|
472
|
+
run bash "$SCRIPT" "$FIXTURE_DIR" "$PROBLEMS_DIR"
|
|
473
|
+
[ "$status" -eq 1 ]
|
|
474
|
+
[[ "$output" == *"MISSING_REVERSE_TRACE"* ]]
|
|
475
|
+
[[ "$output" == *"RFC-001"* ]]
|
|
476
|
+
[[ "$output" == *"P168"* ]]
|
|
477
|
+
}
|
|
478
|
+
|
|
416
479
|
# ── ADR-049 bin shim contract ───────────────────────────────────────────────
|
|
417
480
|
|
|
418
481
|
@test "wr-itil-reconcile-rfcs bin shim exists" {
|