@windyroad/itil 0.61.1 → 0.61.2

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.
Files changed (171) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +7 -2
  3. package/agents/test/fixtures/proceed-new-genuinely-new.md +0 -43
  4. package/agents/test/fixtures/proceed-new-subtle-sibling.md +0 -47
  5. package/agents/test/fixtures/regression-p347-vs-p346.md +0 -54
  6. package/agents/test/hang-off-check.bats +0 -225
  7. package/hooks/test/block-list.bats +0 -153
  8. package/hooks/test/command-detect.bats +0 -181
  9. package/hooks/test/itil-assistant-output-gate.bats +0 -114
  10. package/hooks/test/itil-assistant-output-review.bats +0 -224
  11. package/hooks/test/itil-bash-polling-antipattern-detect.bats +0 -154
  12. package/hooks/test/itil-changeset-discipline.bats +0 -603
  13. package/hooks/test/itil-claude-space-protection.bats +0 -294
  14. package/hooks/test/itil-commit-trailer-transition-advisory.bats +0 -80
  15. package/hooks/test/itil-correction-detect.bats +0 -189
  16. package/hooks/test/itil-deferral-cadence-gate.bats +0 -187
  17. package/hooks/test/itil-fictional-defer-detect.bats +0 -292
  18. package/hooks/test/itil-fix-title-lifecycle-advisory.bats +0 -129
  19. package/hooks/test/itil-mid-loop-ask-detect.bats +0 -220
  20. package/hooks/test/itil-no-implement-draft-gate.bats +0 -210
  21. package/hooks/test/itil-pending-questions-surface.bats +0 -307
  22. package/hooks/test/itil-readme-refresh-discipline.bats +0 -642
  23. package/hooks/test/itil-rfc-oversight-nudge.bats +0 -83
  24. package/hooks/test/itil-rfc-trailer-advisory.bats +0 -375
  25. package/hooks/test/itil-story-mirror-migration-nudge.bats +0 -122
  26. package/hooks/test/manage-problem-enforce-create.bats +0 -424
  27. package/hooks/test/p057-staging-trap-detect.bats +0 -231
  28. package/hooks/test/pre-publish-intake-gate.bats +0 -144
  29. package/hooks/test/runtime-sid-marker.bats +0 -90
  30. package/hooks/test/session-id.bats +0 -370
  31. package/scripts/migrate-problems-add-type.sh +0 -116
  32. package/scripts/test/catchup-scan.bats +0 -274
  33. package/scripts/test/check-afk-accept-eligible.bats +0 -310
  34. package/scripts/test/check-fail-soft-skip-discipline.bats +0 -172
  35. package/scripts/test/check-fix-rfc-trace.bats +0 -164
  36. package/scripts/test/check-locale-discipline.bats +0 -291
  37. package/scripts/test/check-problems-readme-budget.bats +0 -192
  38. package/scripts/test/check-rfc-has-stories.bats +0 -69
  39. package/scripts/test/check-rfc-rejected-alternatives.bats +0 -108
  40. package/scripts/test/check-rfc-stories-ratified.bats +0 -82
  41. package/scripts/test/check-upstream-responses.bats +0 -503
  42. package/scripts/test/classify-readme-drift.bats +0 -392
  43. package/scripts/test/derive-first-dispatch.bats +0 -274
  44. package/scripts/test/derive-release-vehicle.bats +0 -454
  45. package/scripts/test/detect-unratified-stories-maps.bats +0 -60
  46. package/scripts/test/dual-tolerant-glob-rfc-002-t2.bats +0 -309
  47. package/scripts/test/effort-tally.bats +0 -170
  48. package/scripts/test/evaluate-relevance.bats +0 -773
  49. package/scripts/test/kind-a-commands.bats +0 -46
  50. package/scripts/test/mark-create-gate.bats +0 -59
  51. package/scripts/test/mark-story-oversight-confirmed.bats +0 -53
  52. package/scripts/test/migrate-problems-add-type.bats +0 -280
  53. package/scripts/test/migrate-story-status-mirror.bats +0 -167
  54. package/scripts/test/no-type-regression-guard.bats +0 -74
  55. package/scripts/test/oversight-mirror-corpus-lint.bats +0 -150
  56. package/scripts/test/plugin-exercise-index.bats +0 -386
  57. package/scripts/test/plugin-maturity-doc-lint.bats +0 -470
  58. package/scripts/test/plugin-maturity-populate.bats +0 -631
  59. package/scripts/test/plugin-maturity-render.bats +0 -469
  60. package/scripts/test/plugin-validate-ci-gate.bats +0 -208
  61. package/scripts/test/reconcile-readme.bats +0 -1007
  62. package/scripts/test/reconcile-rfcs.bats +0 -496
  63. package/scripts/test/reconcile-stories.bats +0 -173
  64. package/scripts/test/reconcile-story-maps.bats +0 -74
  65. package/scripts/test/release-watch-poll-loop.bats +0 -180
  66. package/scripts/test/resolve-governance-plugin-dirs.bats +0 -78
  67. package/scripts/test/rfc-stories-extension.bats +0 -175
  68. package/scripts/test/skill-invocations.bats +0 -535
  69. package/scripts/test/skill-md-dual-tolerant-coverage-rfc-002-t3.bats +0 -374
  70. package/scripts/test/story-oversight-lib.bats +0 -190
  71. package/scripts/test/update-jtbd-references-related-problems.bats +0 -178
  72. package/scripts/test/update-problem-references-section.bats +0 -242
  73. package/scripts/test/update-problem-rfcs-section.bats +0 -242
  74. package/scripts/test/update-references-section-sibling-helpers.bats +0 -80
  75. package/scripts/test/update-rfc-commits-section.bats +0 -77
  76. package/scripts/test/verify-iter-summary.bats +0 -196
  77. package/scripts/test/working-the-problem-traversal.bats +0 -116
  78. package/skills/capture-problem/test/capture-problem-step-1-5b-jtbd-trace.bats +0 -398
  79. package/skills/capture-problem/test/capture-problem.bats +0 -389
  80. package/skills/capture-story/test/capture-story-behavioural.bats +0 -317
  81. package/skills/capture-story-map/test/capture-story-map-behavioural.bats +0 -98
  82. package/skills/close-incident/test/close-incident-contract.bats +0 -146
  83. package/skills/link-incident/test/link-incident-contract.bats +0 -125
  84. package/skills/list-incidents/test/list-incidents-contract.bats +0 -115
  85. package/skills/list-problems/test/list-problems-contract.bats +0 -100
  86. package/skills/list-stories/test/list-stories-contract.bats +0 -127
  87. package/skills/list-story-maps/test/list-story-maps-contract.bats +0 -46
  88. package/skills/manage-incident/test/manage-incident-adr-044-contract.bats +0 -206
  89. package/skills/manage-incident/test/manage-incident-close-forwarder.bats +0 -67
  90. package/skills/manage-incident/test/manage-incident-link-forwarder.bats +0 -67
  91. package/skills/manage-incident/test/manage-incident-list-forwarder.bats +0 -68
  92. package/skills/manage-incident/test/manage-incident-mitigate-forwarder.bats +0 -66
  93. package/skills/manage-incident/test/manage-incident-restore-forwarder.bats +0 -66
  94. package/skills/manage-incident/test/manage-incident.bats +0 -171
  95. package/skills/manage-problem/test/manage-problem-adr-044-step4-derive-first.bats +0 -151
  96. package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +0 -53
  97. package/skills/manage-problem/test/manage-problem-concern-boundary.bats +0 -64
  98. package/skills/manage-problem/test/manage-problem-effort-buckets.bats +0 -56
  99. package/skills/manage-problem/test/manage-problem-external-root-cause-detection.bats +0 -119
  100. package/skills/manage-problem/test/manage-problem-first-run-intake-prompt.bats +0 -43
  101. package/skills/manage-problem/test/manage-problem-git-mv-restage-reminder.bats +0 -48
  102. package/skills/manage-problem/test/manage-problem-id-collision-guard.bats +0 -39
  103. package/skills/manage-problem/test/manage-problem-list-forwarder.bats +0 -66
  104. package/skills/manage-problem/test/manage-problem-next-id-origin-lookup.bats +0 -50
  105. package/skills/manage-problem/test/manage-problem-no-prose-options.bats +0 -74
  106. package/skills/manage-problem/test/manage-problem-output-formatting.bats +0 -30
  107. package/skills/manage-problem/test/manage-problem-parked-and-cache.bats +0 -73
  108. package/skills/manage-problem/test/manage-problem-readme-refresh-on-transition.bats +0 -62
  109. package/skills/manage-problem/test/manage-problem-readme-tie-break-order.bats +0 -318
  110. package/skills/manage-problem/test/manage-problem-readme-vq-sort-order.bats +0 -206
  111. package/skills/manage-problem/test/manage-problem-release-vehicle-seed.bats +0 -83
  112. package/skills/manage-problem/test/manage-problem-review-forwarder.bats +0 -71
  113. package/skills/manage-problem/test/manage-problem-step-9d-recovery-path.bats +0 -97
  114. package/skills/manage-problem/test/manage-problem-transition-forwarder.bats +0 -96
  115. package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +0 -365
  116. package/skills/manage-problem/test/manage-problem-verification-detection.bats +0 -64
  117. package/skills/manage-problem/test/manage-problem-verification-pending.bats +0 -114
  118. package/skills/manage-problem/test/manage-problem-work-forwarder.bats +0 -88
  119. package/skills/manage-story/test/manage-story-contract.bats +0 -174
  120. package/skills/manage-story-map/test/manage-story-map-contract.bats +0 -63
  121. package/skills/mitigate-incident/test/mitigate-incident-contract.bats +0 -258
  122. package/skills/reconcile-readme/test/reconcile-readme-contract.bats +0 -201
  123. package/skills/report-upstream/test/report-upstream-contract.bats +0 -394
  124. package/skills/restore-incident/test/restore-incident-contract.bats +0 -170
  125. package/skills/review-problems/test/inbound-channels-cache-shape.bats +0 -127
  126. package/skills/review-problems/test/inbound-discovery-contract.bats +0 -383
  127. package/skills/review-problems/test/jtbd-301-verdict-shape-contract.bats +0 -225
  128. package/skills/review-problems/test/review-problems-contract.bats +0 -235
  129. package/skills/review-problems/test/review-problems-likely-verified-cell-shape.bats +0 -229
  130. package/skills/scaffold-intake/test/scaffold-intake-contract.bats +0 -126
  131. package/skills/scaffold-intake/test/scaffold-intake-fixture.bats +0 -164
  132. package/skills/scaffold-intake/test/scaffold-intake-secrets-absent.bats +0 -87
  133. package/skills/transition-problem/test/transition-problem-contract.bats +0 -285
  134. package/skills/transition-problems/test/transition-problems-contract.bats +0 -331
  135. package/skills/update-upstream/test/update-upstream-contract.bats +0 -375
  136. package/skills/work-problem/test/work-problem-contract.bats +0 -294
  137. package/skills/work-problems/test/work-problems-above-appetite-remediation.bats +0 -139
  138. package/skills/work-problems/test/work-problems-adr-013-rule-6-p352-amendment.bats +0 -166
  139. package/skills/work-problems/test/work-problems-auto-migrate-step.bats +0 -57
  140. package/skills/work-problems/test/work-problems-cost-logging.bats +0 -109
  141. package/skills/work-problems/test/work-problems-deviation-candidate-shape.bats +0 -121
  142. package/skills/work-problems/test/work-problems-first-run-intake-prompt.bats +0 -36
  143. package/skills/work-problems/test/work-problems-inter-iteration-verify.bats +0 -59
  144. package/skills/work-problems/test/work-problems-mid-loop-userpromptsubmit-handler.bats +0 -69
  145. package/skills/work-problems/test/work-problems-no-mid-loop-asking.bats +0 -192
  146. package/skills/work-problems/test/work-problems-p341-pre-all-done-gate.bats +0 -122
  147. package/skills/work-problems/test/work-problems-p342-r007-derive-then-pass-flags.bats +0 -170
  148. package/skills/work-problems/test/work-problems-p342-retro-auto-ticket-carveout.bats +0 -120
  149. package/skills/work-problems/test/work-problems-preflight-failure-handling.bats +0 -248
  150. package/skills/work-problems/test/work-problems-preflight-session-continuity.bats +0 -139
  151. package/skills/work-problems/test/work-problems-preflight.bats +0 -69
  152. package/skills/work-problems/test/work-problems-release-cadence.bats +0 -63
  153. package/skills/work-problems/test/work-problems-step-0-iter-error-staleness.bats +0 -93
  154. package/skills/work-problems/test/work-problems-step-0b-cache-staleness-behavioural.bats +0 -138
  155. package/skills/work-problems/test/work-problems-step-0c-deferred-placeholder-staleness-behavioural.bats +0 -238
  156. package/skills/work-problems/test/work-problems-step-0d-outbound-responses-staleness-behavioural.bats +0 -174
  157. package/skills/work-problems/test/work-problems-step-2-5-routing.bats +0 -80
  158. package/skills/work-problems/test/work-problems-step-2-5b-cross-halt-routing.bats +0 -149
  159. package/skills/work-problems/test/work-problems-step-3-5-jtbd-ratification-predicate.bats +0 -191
  160. package/skills/work-problems/test/work-problems-step-5-bats-polling-discipline.bats +0 -91
  161. package/skills/work-problems/test/work-problems-step-5-delegation.bats +0 -333
  162. package/skills/work-problems/test/work-problems-step-5-idle-timeout-sigterm.bats +0 -421
  163. package/skills/work-problems/test/work-problems-step-5-is-error-transient-halt.bats +0 -278
  164. package/skills/work-problems/test/work-problems-step-5-iter-changeset-required.bats +0 -95
  165. package/skills/work-problems/test/work-problems-step-5-prompt-body-re-grounding.bats +0 -128
  166. package/skills/work-problems/test/work-problems-step-5-stream-timeout-salvage.bats +0 -251
  167. package/skills/work-problems/test/work-problems-step-6-5-always-drain.bats +0 -203
  168. package/skills/work-problems/test/work-problems-step-6-5-cache-refresh-chain.bats +0 -174
  169. package/skills/work-problems/test/work-problems-step-6-5-fix-and-continue.bats +0 -254
  170. package/skills/work-problems/test/work-problems-step-6-5-postrelease-kv-callback.bats +0 -209
  171. package/skills/work-problems/test/work-problems-stop-condition-questions.bats +0 -85
@@ -1,1007 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # @problem P118 — docs/problems/README.md drifts from filesystem truth
4
- # across sessions despite P094 (refresh-on-create) and P062 (refresh-on-
5
- # transition) both being Closed. This script is the cross-session
6
- # robustness layer ON TOP of those per-operation contracts.
7
- #
8
- # Contract: `reconcile-readme.sh [<problems-dir>]` is a diagnose-only
9
- # mechanical drift detector. It reads `<problems-dir>/<NNN>-*.<status>.md`
10
- # files (default `docs/problems`), parses the WSJF Rankings + Verification
11
- # Queue + Closed tables in `<problems-dir>/README.md`, and reports each
12
- # disagreement between README claim and filesystem ground truth.
13
- #
14
- # Exit codes:
15
- # 0 = clean (README matches filesystem for every parsed row)
16
- # 1 = drift detected (structured diff to stdout, one row per drift)
17
- # 2 = parse error (README missing or malformed beyond recovery)
18
- #
19
- # Diff output budget per ADR-038 progressive disclosure: each diff row
20
- # ≤ 150 bytes; output is consumed by Claude in agent context, so it
21
- # must stay terse and machine-readable, not narrative.
22
- #
23
- # The script is read-only — it does NOT mutate the README. Narrative
24
- # content (the long "Last reviewed" prose paragraph, the Closed-section
25
- # closure-via free text) is preserved by the agent-applied-edits pattern
26
- # in the `/wr-itil:reconcile-readme` skill which wraps this script.
27
- #
28
- # @jtbd JTBD-006 (Progress the Backlog While I'm Away — orchestrators
29
- # read the README to pick the highest-WSJF actionable ticket; drift
30
- # burns iterations on already-transitioned tickets)
31
- # @jtbd JTBD-001 (Enforce Governance Without Slowing Down — read-only
32
- # diagnostic, no interactive friction on the happy path)
33
- #
34
- # Cross-reference:
35
- # P118: docs/problems/118-readme-drifts-from-filesystem-truth-despite-refresh-contracts-closed.open.md
36
- # ADR-014 amended (Reconciliation as preflight robustness layer)
37
- # ADR-022 — Verification Pending lifecycle status conventions
38
- # ADR-038 — Progressive disclosure (per-row byte budget on diff output)
39
- # ADR-005 — Plugin testing strategy (script-level bats governance)
40
-
41
- setup() {
42
- SCRIPTS_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
43
- SCRIPT="$SCRIPTS_DIR/reconcile-readme.sh"
44
- FIXTURE_DIR="$(mktemp -d)"
45
- }
46
-
47
- teardown() {
48
- rm -rf "$FIXTURE_DIR"
49
- }
50
-
51
- # ── Existence + executable ──────────────────────────────────────────────────
52
-
53
- @test "reconcile-readme: script exists" {
54
- [ -f "$SCRIPT" ]
55
- }
56
-
57
- @test "reconcile-readme: script is executable" {
58
- [ -x "$SCRIPT" ]
59
- }
60
-
61
- # ── Exit code 0: clean state ─────────────────────────────────────────────────
62
-
63
- @test "reconcile-readme: exit 0 when WSJF Rankings matches filesystem .open.md set" {
64
- cat > "$FIXTURE_DIR/100-foo.open.md" <<EOF
65
- # Problem 100: Foo
66
- **Status**: Open
67
- **WSJF**: 5.0
68
- EOF
69
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
70
- # Problem Backlog
71
-
72
- ## WSJF Rankings
73
-
74
- | WSJF | ID | Title | Severity | Status | Effort |
75
- |------|-----|-------|----------|--------|--------|
76
- | 5.0 | P100 | Foo | 12 High | Open | M |
77
-
78
- ## Verification Queue
79
-
80
- | ID | Title | Released | Likely verified? |
81
- |----|-------|----------|------------------|
82
-
83
- ## Closed
84
-
85
- | ID | Title | Closed via |
86
- |----|-------|-----------|
87
- EOF
88
- run "$SCRIPT" "$FIXTURE_DIR"
89
- [ "$status" -eq 0 ]
90
- }
91
-
92
- # ── Exit code 1: drift cases ─────────────────────────────────────────────────
93
-
94
- @test "reconcile-readme: exit 1 when README ranks ticket Open but file is .closed.md (P074 case)" {
95
- # The exact symptom this ticket addresses: a prior session closed the
96
- # ticket without staging the README refresh, leaving the WSJF Rankings
97
- # row stale in subsequent sessions.
98
- cat > "$FIXTURE_DIR/074-foo.closed.md" <<EOF
99
- # Problem 074: Foo
100
- **Status**: Closed
101
- EOF
102
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
103
- # Problem Backlog
104
-
105
- ## WSJF Rankings
106
-
107
- | WSJF | ID | Title | Severity | Status | Effort |
108
- |------|-----|-------|----------|--------|--------|
109
- | 6.0 | P074 | Foo | 12 High | Open | M |
110
-
111
- ## Verification Queue
112
-
113
- | ID | Title | Released | Likely verified? |
114
- |----|-------|----------|------------------|
115
-
116
- ## Closed
117
-
118
- | ID | Title | Closed via |
119
- |----|-------|-----------|
120
- EOF
121
- run "$SCRIPT" "$FIXTURE_DIR"
122
- [ "$status" -eq 1 ]
123
- # Output mentions P074 as the drift entry.
124
- echo "$output" | grep -q "P074"
125
- }
126
-
127
- @test "reconcile-readme: exit 1 when ticket on disk as .open.md is missing from WSJF Rankings" {
128
- # The other half of the drift class: a ticket created in a prior
129
- # session that never refreshed README — the file exists but the
130
- # row was never inserted.
131
- cat > "$FIXTURE_DIR/079-bar.open.md" <<EOF
132
- # Problem 079: Bar
133
- **Status**: Open
134
- **WSJF**: 3.0
135
- EOF
136
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
137
- # Problem Backlog
138
-
139
- ## WSJF Rankings
140
-
141
- | WSJF | ID | Title | Severity | Status | Effort |
142
- |------|-----|-------|----------|--------|--------|
143
-
144
- ## Verification Queue
145
-
146
- | ID | Title | Released | Likely verified? |
147
- |----|-------|----------|------------------|
148
-
149
- ## Closed
150
-
151
- | ID | Title | Closed via |
152
- |----|-------|-----------|
153
- EOF
154
- run "$SCRIPT" "$FIXTURE_DIR"
155
- [ "$status" -eq 1 ]
156
- echo "$output" | grep -q "P079"
157
- }
158
-
159
- @test "reconcile-readme: exit 1 when README ranks ticket Open but file is .verifying.md (P110 case)" {
160
- cat > "$FIXTURE_DIR/110-baz.verifying.md" <<EOF
161
- # Problem 110: Baz
162
- **Status**: Verification Pending
163
- EOF
164
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
165
- # Problem Backlog
166
-
167
- ## WSJF Rankings
168
-
169
- | WSJF | ID | Title | Severity | Status | Effort |
170
- |------|-----|-------|----------|--------|--------|
171
- | 4.0 | P110 | Baz | 8 Med | Open | M |
172
-
173
- ## Verification Queue
174
-
175
- | ID | Title | Released | Likely verified? |
176
- |----|-------|----------|------------------|
177
-
178
- ## Closed
179
-
180
- | ID | Title | Closed via |
181
- |----|-------|-----------|
182
- EOF
183
- run "$SCRIPT" "$FIXTURE_DIR"
184
- [ "$status" -eq 1 ]
185
- echo "$output" | grep -q "P110"
186
- }
187
-
188
- @test "reconcile-readme: exit 1 when Verification Queue lists ticket but file is .closed.md (stale VQ)" {
189
- cat > "$FIXTURE_DIR/056-qux.closed.md" <<EOF
190
- # Problem 056: Qux
191
- **Status**: Closed
192
- EOF
193
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
194
- # Problem Backlog
195
-
196
- ## WSJF Rankings
197
-
198
- | WSJF | ID | Title | Severity | Status | Effort |
199
- |------|-----|-------|----------|--------|--------|
200
-
201
- ## Verification Queue
202
-
203
- | ID | Title | Released | Likely verified? |
204
- |----|-------|----------|------------------|
205
- | P056 | Qux | 2026-01-01 | yes |
206
-
207
- ## Closed
208
-
209
- | ID | Title | Closed via |
210
- |----|-------|-----------|
211
- EOF
212
- run "$SCRIPT" "$FIXTURE_DIR"
213
- [ "$status" -eq 1 ]
214
- echo "$output" | grep -q "P056"
215
- }
216
-
217
- # ── Diff output is structured + within byte budget (ADR-038) ────────────────
218
-
219
- @test "reconcile-readme: drift output emits one structured line per drift entry" {
220
- # Two distinct drift entries; output should contain at least two
221
- # rows (one per ID).
222
- cat > "$FIXTURE_DIR/074-foo.closed.md" <<EOF
223
- **Status**: Closed
224
- EOF
225
- cat > "$FIXTURE_DIR/079-bar.open.md" <<EOF
226
- **WSJF**: 3.0
227
- EOF
228
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
229
- ## WSJF Rankings
230
-
231
- | WSJF | ID | Title | Severity | Status | Effort |
232
- |------|-----|-------|----------|--------|--------|
233
- | 6.0 | P074 | Foo | 12 High | Open | M |
234
-
235
- ## Verification Queue
236
-
237
- | ID | Title | Released | Likely verified? |
238
- |----|-------|----------|------------------|
239
-
240
- ## Closed
241
-
242
- | ID | Title | Closed via |
243
- |----|-------|-----------|
244
- EOF
245
- run "$SCRIPT" "$FIXTURE_DIR"
246
- [ "$status" -eq 1 ]
247
- # Both drift IDs surface in output.
248
- echo "$output" | grep -q "P074"
249
- echo "$output" | grep -q "P079"
250
- }
251
-
252
- @test "reconcile-readme: each diff row stays under 150 bytes (ADR-038 progressive-disclosure budget)" {
253
- cat > "$FIXTURE_DIR/074-foo.closed.md" <<EOF
254
- **Status**: Closed
255
- EOF
256
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
257
- ## WSJF Rankings
258
-
259
- | WSJF | ID | Title | Severity | Status | Effort |
260
- |------|-----|-------|----------|--------|--------|
261
- | 6.0 | P074 | Foo | 12 High | Open | M |
262
-
263
- ## Verification Queue
264
-
265
- | ID | Title | Released | Likely verified? |
266
- |----|-------|----------|------------------|
267
-
268
- ## Closed
269
-
270
- | ID | Title | Closed via |
271
- |----|-------|-----------|
272
- EOF
273
- run "$SCRIPT" "$FIXTURE_DIR"
274
- [ "$status" -eq 1 ]
275
- # Filter to data rows only (drift entries start with a marker char).
276
- while IFS= read -r line; do
277
- # Skip empty lines + the header line.
278
- [ -z "$line" ] && continue
279
- case "$line" in
280
- DRIFT*|MISSING*|STALE*|MISMATCH*)
281
- len=${#line}
282
- [ "$len" -le 150 ] || {
283
- echo "Diff row over 150 bytes ($len): $line" >&2
284
- return 1
285
- }
286
- ;;
287
- esac
288
- done <<< "$output"
289
- }
290
-
291
- # ── Exit code 2: parse error ────────────────────────────────────────────────
292
-
293
- @test "reconcile-readme: exit 2 when README is missing" {
294
- cat > "$FIXTURE_DIR/100-foo.open.md" <<EOF
295
- **WSJF**: 5.0
296
- EOF
297
- # No README.md created.
298
- run "$SCRIPT" "$FIXTURE_DIR"
299
- [ "$status" -eq 2 ]
300
- }
301
-
302
- @test "reconcile-readme: exit 2 when README has no WSJF Rankings header (parse error)" {
303
- cat > "$FIXTURE_DIR/100-foo.open.md" <<EOF
304
- **WSJF**: 5.0
305
- EOF
306
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
307
- # Problem Backlog
308
-
309
- This README has no WSJF Rankings section header.
310
- EOF
311
- run "$SCRIPT" "$FIXTURE_DIR"
312
- [ "$status" -eq 2 ]
313
- }
314
-
315
- # ── Verification Pending tickets must NOT appear in WSJF Rankings (ADR-022) ─
316
-
317
- @test "reconcile-readme: .verifying.md tickets in WSJF Rankings are flagged as drift" {
318
- # ADR-022 — Verification Pending tickets are excluded from WSJF Rankings
319
- # (they belong in the Verification Queue section). A .verifying.md row
320
- # in the dev-work table is drift.
321
- cat > "$FIXTURE_DIR/105-verify.verifying.md" <<EOF
322
- **Status**: Verification Pending
323
- EOF
324
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
325
- ## WSJF Rankings
326
-
327
- | WSJF | ID | Title | Severity | Status | Effort |
328
- |------|-----|-------|----------|--------|--------|
329
- | 8.0 | P105 | Verify | 12 High | Open | M |
330
-
331
- ## Verification Queue
332
-
333
- | ID | Title | Released | Likely verified? |
334
- |----|-------|----------|------------------|
335
-
336
- ## Closed
337
-
338
- | ID | Title | Closed via |
339
- |----|-------|-----------|
340
- EOF
341
- run "$SCRIPT" "$FIXTURE_DIR"
342
- [ "$status" -eq 1 ]
343
- echo "$output" | grep -q "P105"
344
- }
345
-
346
- # ── Default problems-dir resolution ─────────────────────────────────────────
347
-
348
- @test "reconcile-readme: defaults to ./docs/problems when no arg passed" {
349
- # cd into a fixture root that has docs/problems/README.md to confirm
350
- # the default-arg branch executes.
351
- mkdir -p "$FIXTURE_DIR/docs/problems"
352
- cat > "$FIXTURE_DIR/docs/problems/README.md" <<'EOF'
353
- ## WSJF Rankings
354
-
355
- | WSJF | ID | Title | Severity | Status | Effort |
356
- |------|-----|-------|----------|--------|--------|
357
-
358
- ## Verification Queue
359
-
360
- | ID | Title | Released | Likely verified? |
361
- |----|-------|----------|------------------|
362
-
363
- ## Closed
364
-
365
- | ID | Title | Closed via |
366
- |----|-------|-----------|
367
- EOF
368
- cd "$FIXTURE_DIR"
369
- run "$SCRIPT"
370
- [ "$status" -eq 0 ]
371
- }
372
-
373
- # ── Parked tickets are excluded (ADR-022 multiplier 0 + own section) ────────
374
-
375
- @test "reconcile-readme: .parked.md tickets are not flagged as missing from WSJF Rankings" {
376
- # Parked tickets live in their own section; they are NOT expected in
377
- # WSJF Rankings. A .parked.md file with no WSJF Rankings row is
378
- # correct, not drift.
379
- cat > "$FIXTURE_DIR/005-parked.parked.md" <<EOF
380
- **Status**: Parked
381
- EOF
382
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
383
- ## WSJF Rankings
384
-
385
- | WSJF | ID | Title | Severity | Status | Effort |
386
- |------|-----|-------|----------|--------|--------|
387
-
388
- ## Verification Queue
389
-
390
- | ID | Title | Released | Likely verified? |
391
- |----|-------|----------|------------------|
392
-
393
- ## Closed
394
-
395
- | ID | Title | Closed via |
396
- |----|-------|-----------|
397
-
398
- ## Parked
399
-
400
- | ID | Title | Reason | Parked since |
401
- |----|-------|--------|-------------|
402
- | P005 | Parked | Upstream | 2026-04-16 |
403
- EOF
404
- run "$SCRIPT" "$FIXTURE_DIR"
405
- [ "$status" -eq 0 ]
406
- }
407
-
408
- # ── Closed tickets never tracked (P001-P028 don't all appear) ───────────────
409
-
410
- @test "reconcile-readme: .closed.md tickets absent from Closed section are not drift (history is partial)" {
411
- # The Closed section is curated narrative — it lists "recently closed
412
- # this session" with closure-via prose. It is NOT exhaustive over
413
- # every .closed.md file. A .closed.md file absent from the Closed
414
- # section is allowed; the only Closed-section drift is when a row
415
- # in that section names an ID that is NOT .closed.md on disk.
416
- cat > "$FIXTURE_DIR/001-old.closed.md" <<EOF
417
- **Status**: Closed
418
- EOF
419
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
420
- ## WSJF Rankings
421
-
422
- | WSJF | ID | Title | Severity | Status | Effort |
423
- |------|-----|-------|----------|--------|--------|
424
-
425
- ## Verification Queue
426
-
427
- | ID | Title | Released | Likely verified? |
428
- |----|-------|----------|------------------|
429
-
430
- ## Closed
431
-
432
- | ID | Title | Closed via |
433
- |----|-------|-----------|
434
- EOF
435
- run "$SCRIPT" "$FIXTURE_DIR"
436
- [ "$status" -eq 0 ]
437
- }
438
-
439
- # ── P133: defensive rename of `status` local → `ticket_status` ──────────────
440
-
441
- @test "reconcile-readme: drift detection still works when caller-environment exports status=anything (P133 regression)" {
442
- # P133 — `status` is a read-only built-in under zsh (alias for `$?`). The
443
- # script's `#!/usr/bin/env bash` shebang means it never runs under zsh
444
- # directly, but a caller may export `status=…` into the script's environment
445
- # and the script must not depend on the bash-builtin name for its own state.
446
- # After the P133 rename (`status` → `ticket_status`), the script's drift
447
- # detection is independent of any caller-set `status` env var. This test
448
- # asserts the behaviour: caller exports `status=junk`, script still emits
449
- # correct drift output (does not pick up the caller's value).
450
- cat > "$FIXTURE_DIR/074-foo.closed.md" <<EOF
451
- **Status**: Closed
452
- EOF
453
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
454
- ## WSJF Rankings
455
-
456
- | WSJF | ID | Title | Severity | Status | Effort |
457
- |------|-----|-------|----------|--------|--------|
458
- | 6.0 | P074 | Foo | 12 High | Open | M |
459
-
460
- ## Verification Queue
461
-
462
- | ID | Title | Released | Likely verified? |
463
- |----|-------|----------|------------------|
464
-
465
- ## Closed
466
-
467
- | ID | Title | Closed via |
468
- |----|-------|-----------|
469
- EOF
470
- # `env status=junk` sets the env var on the script invocation; bats's
471
- # `run` still captures the script exit code into the test-scope `$status`.
472
- run env status=junk "$SCRIPT" "$FIXTURE_DIR"
473
- [ "$status" -eq 1 ]
474
- echo "$output" | grep -q "P074"
475
- # Drift line must report actual filesystem status (`closed`), not the
476
- # caller's bogus `junk` value — script reads from FS_STATUS, not env.
477
- echo "$output" | grep -q "actual=closed"
478
- ! echo "$output" | grep -q "actual=junk"
479
- }
480
-
481
- # ── README Closed-section row pointing to non-existent or wrong-status file ──
482
-
483
- @test "reconcile-readme: exit 1 when Closed section names ID that is .open.md on disk" {
484
- cat > "$FIXTURE_DIR/099-still-open.open.md" <<EOF
485
- **Status**: Open
486
- **WSJF**: 3.0
487
- EOF
488
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
489
- ## WSJF Rankings
490
-
491
- | WSJF | ID | Title | Severity | Status | Effort |
492
- |------|-----|-------|----------|--------|--------|
493
- | 3.0 | P099 | Still Open | 15 High | Open | L |
494
-
495
- ## Verification Queue
496
-
497
- | ID | Title | Released | Likely verified? |
498
- |----|-------|----------|------------------|
499
-
500
- ## Closed
501
-
502
- | ID | Title | Closed via |
503
- |----|-------|-----------|
504
- | P099 | Still Open | (incorrectly listed) |
505
- EOF
506
- run "$SCRIPT" "$FIXTURE_DIR"
507
- [ "$status" -eq 1 ]
508
- echo "$output" | grep -q "P099"
509
- }
510
-
511
- # ── RFC-002 T4: dual-tolerant layout enumeration ────────────────────────────
512
- #
513
- # Contract: during the P170/RFC-002 migration window, problem tickets MAY
514
- # live under either the flat layout (`docs/problems/<NNN>-<title>.<state>.md`)
515
- # OR the per-state subdir layout (`docs/problems/<state>/<NNN>-<title>.md`).
516
- # `reconcile-readme.sh` MUST enumerate ground-truth status from both,
517
- # otherwise:
518
- # - tickets in the un-migrated layout-half show as MISSING in WSJF Rankings,
519
- # - or tickets in the migrated layout-half are not validated against
520
- # README claims, masking drift.
521
- #
522
- # Behavioural contract: drift output (or exit 0) MUST be IDENTICAL regardless
523
- # of which layout the source tickets reside in. The script's stdout is the
524
- # observable signal; tests assert against stdout content + exit code, NOT
525
- # against script source (P081 — no structural-grep on script content).
526
- #
527
- # T6 (post-T5 verification) drops the flat-layout half. These tests update
528
- # at T6 to single-layout fixtures, NOT removed — the contract becomes
529
- # "per-state layout enumerates correctly" but remains behavioural.
530
- #
531
- # @rfc RFC-002 T4
532
- # @adr ADR-031 (per-state subdir is post-migration ground truth)
533
- # @adr ADR-051 (load-bearing-from-the-start — T4 ships with bats coverage)
534
- # @adr ADR-052 (behavioural-tests-default — fixture-driven not source-grep)
535
-
536
- @test "reconcile-readme T4: per-state subdir .open ticket recognised, no drift when README matches" {
537
- # Per-state layout — same Open ticket as the flat-only baseline test
538
- # at line ~63 ("WSJF Rankings matches filesystem .open.md set"). The
539
- # rendered drift output (exit 0) MUST be identical regardless of which
540
- # layout the ticket resides in.
541
- mkdir -p "$FIXTURE_DIR/open"
542
- cat > "$FIXTURE_DIR/open/100-foo.md" <<EOF
543
- # Problem 100: Foo
544
- **Status**: Open
545
- **WSJF**: 5.0
546
- EOF
547
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
548
- # Problem Backlog
549
-
550
- ## WSJF Rankings
551
-
552
- | WSJF | ID | Title | Severity | Status | Effort |
553
- |------|-----|-------|----------|--------|--------|
554
- | 5.0 | P100 | Foo | 12 High | Open | M |
555
-
556
- ## Verification Queue
557
-
558
- | ID | Title | Released | Likely verified? |
559
- |----|-------|----------|------------------|
560
-
561
- ## Closed
562
-
563
- | ID | Title | Closed via |
564
- |----|-------|-----------|
565
- EOF
566
- run "$SCRIPT" "$FIXTURE_DIR"
567
- [ "$status" -eq 0 ]
568
- }
569
-
570
- @test "reconcile-readme T4: per-state subdir .closed ticket flagged when README ranks it Open (drift parity with flat)" {
571
- # Mirrors the flat-layout P074 case at line ~94. Same drift, different
572
- # source layout — drift output must surface P074 the same way.
573
- mkdir -p "$FIXTURE_DIR/closed"
574
- cat > "$FIXTURE_DIR/closed/074-foo.md" <<EOF
575
- # Problem 074: Foo
576
- **Status**: Closed
577
- EOF
578
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
579
- # Problem Backlog
580
-
581
- ## WSJF Rankings
582
-
583
- | WSJF | ID | Title | Severity | Status | Effort |
584
- |------|-----|-------|----------|--------|--------|
585
- | 6.0 | P074 | Foo | 12 High | Open | M |
586
-
587
- ## Verification Queue
588
-
589
- | ID | Title | Released | Likely verified? |
590
- |----|-------|----------|------------------|
591
-
592
- ## Closed
593
-
594
- | ID | Title | Closed via |
595
- |----|-------|-----------|
596
- EOF
597
- run "$SCRIPT" "$FIXTURE_DIR"
598
- [ "$status" -eq 1 ]
599
- echo "$output" | grep -q "P074"
600
- echo "$output" | grep -q "actual=closed"
601
- }
602
-
603
- @test "reconcile-readme T4: per-state subdir .verifying ticket in WSJF Rankings flagged as drift (ADR-022)" {
604
- # Mirrors the flat-layout P105 case at line ~317 — Verification Pending
605
- # tickets must NOT appear in WSJF Rankings (ADR-022).
606
- mkdir -p "$FIXTURE_DIR/verifying"
607
- cat > "$FIXTURE_DIR/verifying/105-verify.md" <<EOF
608
- **Status**: Verification Pending
609
- EOF
610
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
611
- ## WSJF Rankings
612
-
613
- | WSJF | ID | Title | Severity | Status | Effort |
614
- |------|-----|-------|----------|--------|--------|
615
- | 8.0 | P105 | Verify | 12 High | Open | M |
616
-
617
- ## Verification Queue
618
-
619
- | ID | Title | Released | Likely verified? |
620
- |----|-------|----------|------------------|
621
-
622
- ## Closed
623
-
624
- | ID | Title | Closed via |
625
- |----|-------|-----------|
626
- EOF
627
- run "$SCRIPT" "$FIXTURE_DIR"
628
- [ "$status" -eq 1 ]
629
- echo "$output" | grep -q "P105"
630
- }
631
-
632
- @test "reconcile-readme T4: per-state subdir .open ticket missing from WSJF Rankings is flagged MISSING" {
633
- # Mirrors the flat-layout P079 case at line ~127 — a ticket on disk
634
- # but absent from README WSJF Rankings is drift.
635
- mkdir -p "$FIXTURE_DIR/open"
636
- cat > "$FIXTURE_DIR/open/079-bar.md" <<EOF
637
- # Problem 079: Bar
638
- **Status**: Open
639
- **WSJF**: 3.0
640
- EOF
641
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
642
- ## WSJF Rankings
643
-
644
- | WSJF | ID | Title | Severity | Status | Effort |
645
- |------|-----|-------|----------|--------|--------|
646
-
647
- ## Verification Queue
648
-
649
- | ID | Title | Released | Likely verified? |
650
- |----|-------|----------|------------------|
651
-
652
- ## Closed
653
-
654
- | ID | Title | Closed via |
655
- |----|-------|-----------|
656
- EOF
657
- run "$SCRIPT" "$FIXTURE_DIR"
658
- [ "$status" -eq 1 ]
659
- echo "$output" | grep -q "P079"
660
- }
661
-
662
- @test "reconcile-readme T4: per-state subdir .verifying ticket listed in Verification Queue is clean (no drift)" {
663
- # Verification Queue expects .verifying.md tickets — per-state
664
- # equivalent is docs/problems/verifying/<NNN>-*.md.
665
- mkdir -p "$FIXTURE_DIR/verifying"
666
- cat > "$FIXTURE_DIR/verifying/056-qux.md" <<EOF
667
- **Status**: Verification Pending
668
- EOF
669
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
670
- ## WSJF Rankings
671
-
672
- | WSJF | ID | Title | Severity | Status | Effort |
673
- |------|-----|-------|----------|--------|--------|
674
-
675
- ## Verification Queue
676
-
677
- | ID | Title | Released | Likely verified? |
678
- |----|-------|----------|------------------|
679
- | P056 | Qux | 2026-01-01 | yes |
680
-
681
- ## Closed
682
-
683
- | ID | Title | Closed via |
684
- |----|-------|-----------|
685
- EOF
686
- run "$SCRIPT" "$FIXTURE_DIR"
687
- [ "$status" -eq 0 ]
688
- }
689
-
690
- @test "reconcile-readme T4: mixed-layout fixture (flat + per-state) enumerates BOTH and stays clean" {
691
- # Mid-migration race: some tickets already moved to per-state subdir,
692
- # others still in flat layout. README accurately tracks both. Script
693
- # must recognise both layout-halves as filesystem ground truth and
694
- # report exit 0 (no drift) when README rows match.
695
- cat > "$FIXTURE_DIR/100-foo.open.md" <<EOF
696
- **Status**: Open
697
- EOF
698
- mkdir -p "$FIXTURE_DIR/open"
699
- cat > "$FIXTURE_DIR/open/200-bar.md" <<EOF
700
- **Status**: Open
701
- EOF
702
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
703
- ## WSJF Rankings
704
-
705
- | WSJF | ID | Title | Severity | Status | Effort |
706
- |------|-----|-------|----------|--------|--------|
707
- | 5.0 | P100 | Foo | 12 High | Open | M |
708
- | 4.0 | P200 | Bar | 12 High | Open | M |
709
-
710
- ## Verification Queue
711
-
712
- | ID | Title | Released | Likely verified? |
713
- |----|-------|----------|------------------|
714
-
715
- ## Closed
716
-
717
- | ID | Title | Closed via |
718
- |----|-------|-----------|
719
- EOF
720
- run "$SCRIPT" "$FIXTURE_DIR"
721
- [ "$status" -eq 0 ]
722
- }
723
-
724
- @test "reconcile-readme T4: mixed-layout fixture surfaces drift from BOTH halves" {
725
- # Both layout-halves contain a drifting ticket; both surfaces in
726
- # output. Confirms the script enumerates both, not just one or the
727
- # other.
728
- cat > "$FIXTURE_DIR/100-foo.closed.md" <<EOF
729
- **Status**: Closed
730
- EOF
731
- mkdir -p "$FIXTURE_DIR/closed"
732
- cat > "$FIXTURE_DIR/closed/200-bar.md" <<EOF
733
- **Status**: Closed
734
- EOF
735
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
736
- ## WSJF Rankings
737
-
738
- | WSJF | ID | Title | Severity | Status | Effort |
739
- |------|-----|-------|----------|--------|--------|
740
- | 6.0 | P100 | Foo | 12 High | Open | M |
741
- | 5.0 | P200 | Bar | 12 High | Open | M |
742
-
743
- ## Verification Queue
744
-
745
- | ID | Title | Released | Likely verified? |
746
- |----|-------|----------|------------------|
747
-
748
- ## Closed
749
-
750
- | ID | Title | Closed via |
751
- |----|-------|-----------|
752
- EOF
753
- run "$SCRIPT" "$FIXTURE_DIR"
754
- [ "$status" -eq 1 ]
755
- echo "$output" | grep -q "P100"
756
- echo "$output" | grep -q "P200"
757
- # Both surface as DRIFT (claims=open, actual=closed).
758
- echo "$output" | grep -q "actual=closed"
759
- }
760
-
761
- @test "reconcile-readme T4: per-state subdir wins on cross-layout ID collision (post-migration is authoritative)" {
762
- # Mid-migration race window: same ID may transiently appear in both
763
- # layouts between the `git mv` and the README refresh in T5 bulk
764
- # migration commit. Per-state subdir is the migration target —
765
- # ADR-031 §"Authoritative state signal" treats subdirectory as
766
- # authoritative. The script must therefore report per-state status,
767
- # not flat-layout status, when both are present.
768
- cat > "$FIXTURE_DIR/300-collision.open.md" <<EOF
769
- **Status**: Open
770
- EOF
771
- mkdir -p "$FIXTURE_DIR/closed"
772
- cat > "$FIXTURE_DIR/closed/300-collision.md" <<EOF
773
- **Status**: Closed
774
- EOF
775
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
776
- ## WSJF Rankings
777
-
778
- | WSJF | ID | Title | Severity | Status | Effort |
779
- |------|-----|-------|----------|--------|--------|
780
- | 5.0 | P300 | Collision | 12 High | Open | M |
781
-
782
- ## Verification Queue
783
-
784
- | ID | Title | Released | Likely verified? |
785
- |----|-------|----------|------------------|
786
-
787
- ## Closed
788
-
789
- | ID | Title | Closed via |
790
- |----|-------|-----------|
791
- EOF
792
- run "$SCRIPT" "$FIXTURE_DIR"
793
- # Per-state subdir is authoritative → README's "Open" claim drifts
794
- # against actual=closed. If flat-layout were preferred, the script
795
- # would report exit 0 (no drift). Asserting actual=closed proves
796
- # per-state-wins semantics.
797
- [ "$status" -eq 1 ]
798
- echo "$output" | grep -q "P300"
799
- echo "$output" | grep -q "actual=closed"
800
- }
801
-
802
- @test "reconcile-readme T4: per-state subdir .parked ticket excluded from WSJF Rankings (ADR-022 multiplier 0)" {
803
- # Mirrors the flat-layout P005 case at line ~375 — Parked tickets
804
- # have their own section; absence from WSJF Rankings is correct.
805
- mkdir -p "$FIXTURE_DIR/parked"
806
- cat > "$FIXTURE_DIR/parked/005-parked.md" <<EOF
807
- **Status**: Parked
808
- EOF
809
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
810
- ## WSJF Rankings
811
-
812
- | WSJF | ID | Title | Severity | Status | Effort |
813
- |------|-----|-------|----------|--------|--------|
814
-
815
- ## Verification Queue
816
-
817
- | ID | Title | Released | Likely verified? |
818
- |----|-------|----------|------------------|
819
-
820
- ## Closed
821
-
822
- | ID | Title | Closed via |
823
- |----|-------|-----------|
824
-
825
- ## Parked
826
-
827
- | ID | Title | Reason | Parked since |
828
- |----|-------|--------|-------------|
829
- | P005 | Parked | Upstream | 2026-04-16 |
830
- EOF
831
- run "$SCRIPT" "$FIXTURE_DIR"
832
- [ "$status" -eq 0 ]
833
- }
834
-
835
- @test "reconcile-readme T4: per-state subdir .known-error ticket recognised in WSJF Rankings as Open-class" {
836
- # Known-error tickets belong in WSJF Rankings (ADR-022 — open + known-error
837
- # are the dev-work classes). Per-state subdir equivalent is
838
- # docs/problems/known-error/<NNN>-*.md.
839
- mkdir -p "$FIXTURE_DIR/known-error"
840
- cat > "$FIXTURE_DIR/known-error/150-ke.md" <<EOF
841
- **Status**: Known Error
842
- EOF
843
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
844
- ## WSJF Rankings
845
-
846
- | WSJF | ID | Title | Severity | Status | Effort |
847
- |------|-----|-------|----------|--------|--------|
848
- | 4.0 | P150 | KE | 8 Med | Known Error | M |
849
-
850
- ## Verification Queue
851
-
852
- | ID | Title | Released | Likely verified? |
853
- |----|-------|----------|------------------|
854
-
855
- ## Closed
856
-
857
- | ID | Title | Closed via |
858
- |----|-------|-----------|
859
- EOF
860
- run "$SCRIPT" "$FIXTURE_DIR"
861
- [ "$status" -eq 0 ]
862
- }
863
-
864
- # ── P252: Inbound Upstream Reports section excluded from VQ slice ───────────
865
- #
866
- # Contract: when `docs/problems/README.md` carries a `## Inbound Upstream
867
- # Reports` section (per ADR-062 + RFC-004), its rows MUST NOT be counted as
868
- # Verification Queue rows. Pre-fix the script slices VQ as
869
- # `[VQ_START, CLOSED_START)`, which swallows the entire Inbound table and
870
- # mis-attributes its `| P<NNN> |` cross-ref cells to the VQ. Post-fix the
871
- # VQ slice terminates at `## Inbound Upstream Reports` when present.
872
- #
873
- # Behavioural assertion shape: reconcile against a README that has VQ
874
- # matching filesystem ground truth AND an Inbound Upstream Reports table
875
- # carrying cross-refs to Open tickets → exit 0. Pre-fix this would emit
876
- # 31-ish STALE entries. Post-fix it emits none.
877
- #
878
- # @problem P252
879
- # @adr ADR-062 (Inbound upstream-report discovery + assessment pipeline)
880
- # @rfc RFC-004 (P079 inbound-upstream-report discovery)
881
- # @jtbd JTBD-006 (Progress the Backlog While I'm Away)
882
- # @jtbd JTBD-302 (Trust That the README Describes the Plugin I Just Installed)
883
-
884
- @test "reconcile-readme P252: Inbound Upstream Reports cross-refs are NOT counted as Verification Queue rows" {
885
- # Two Open tickets cross-referenced as inbound-mirror entries. Pre-fix
886
- # the script slices VQ as [VQ_START, CLOSED_START), which swallows the
887
- # Inbound table — both P-IDs get reported as STALE verification-queue.
888
- # Post-fix the VQ slice terminates at Inbound; both IDs are correctly
889
- # recognised as Open (their .open.md files exist) and no drift fires.
890
- cat > "$FIXTURE_DIR/198-foo.open.md" <<EOF
891
- **Status**: Open
892
- EOF
893
- cat > "$FIXTURE_DIR/199-bar.open.md" <<EOF
894
- **Status**: Open
895
- EOF
896
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
897
- ## WSJF Rankings
898
-
899
- | WSJF | ID | Title | Severity | Status | Effort |
900
- |------|-----|-------|----------|--------|--------|
901
- | 5.0 | P198 | Foo | 12 High | Open | M |
902
- | 4.0 | P199 | Bar | 12 High | Open | M |
903
-
904
- ## Verification Queue
905
-
906
- | ID | Title | Released | Likely verified? |
907
- |----|-------|----------|------------------|
908
-
909
- ## Inbound Upstream Reports
910
-
911
- Inbound mirrors discovered via the discovery pipeline (ADR-062 / RFC-004).
912
- The `Matched local ticket` column carries cross-refs that look like VQ rows
913
- but live in a different section and MUST NOT be parsed as VQ entries.
914
-
915
- | # | Inbound | Matched local ticket |
916
- |---|---------|----------------------|
917
- | 1 | upstream/issues/42 | P198 |
918
- | 2 | upstream/issues/43 | P199 |
919
-
920
- ## Closed
921
-
922
- | ID | Title | Closed via |
923
- |----|-------|-----------|
924
- EOF
925
- run "$SCRIPT" "$FIXTURE_DIR"
926
- # Clean state — both P-IDs are correctly Open on disk AND in WSJF
927
- # Rankings; the Inbound section's cross-refs are scoped out.
928
- [ "$status" -eq 0 ]
929
- # Defensive: even if exit 0 lands by accident, output must not contain
930
- # any false-positive STALE entries citing Inbound IDs.
931
- ! echo "$output" | grep -q "P198 verification-queue"
932
- ! echo "$output" | grep -q "P199 verification-queue"
933
- }
934
-
935
- @test "reconcile-readme P252: real VQ drift surfaces, Inbound cross-refs stay silent" {
936
- # Mixed fixture: one genuine STALE VQ row (P056 — README lists in VQ,
937
- # disk has it Closed) + an Inbound section with cross-refs to Open
938
- # tickets. Drift output MUST cite P056 only; Inbound IDs MUST NOT
939
- # appear in any verification-queue drift line.
940
- cat > "$FIXTURE_DIR/056-qux.closed.md" <<EOF
941
- **Status**: Closed
942
- EOF
943
- cat > "$FIXTURE_DIR/198-foo.open.md" <<EOF
944
- **Status**: Open
945
- EOF
946
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
947
- ## WSJF Rankings
948
-
949
- | WSJF | ID | Title | Severity | Status | Effort |
950
- |------|-----|-------|----------|--------|--------|
951
- | 5.0 | P198 | Foo | 12 High | Open | M |
952
-
953
- ## Verification Queue
954
-
955
- | ID | Title | Released | Likely verified? |
956
- |----|-------|----------|------------------|
957
- | P056 | Qux | 2026-01-01 | yes |
958
-
959
- ## Inbound Upstream Reports
960
-
961
- | # | Inbound | Matched local ticket |
962
- |---|---------|----------------------|
963
- | 1 | upstream/issues/42 | P198 |
964
-
965
- ## Closed
966
-
967
- | ID | Title | Closed via |
968
- |----|-------|-----------|
969
- EOF
970
- run "$SCRIPT" "$FIXTURE_DIR"
971
- [ "$status" -eq 1 ]
972
- # P056 is the genuine drift (in VQ, file is Closed).
973
- echo "$output" | grep -q "P056"
974
- # P198 must NOT appear in any verification-queue drift line — it's an
975
- # Inbound cross-ref, not a VQ row.
976
- ! echo "$output" | grep -q "P198 verification-queue"
977
- }
978
-
979
- @test "reconcile-readme P252: missing Inbound section preserves existing behaviour (back-compat)" {
980
- # A README that does NOT have an Inbound Upstream Reports section must
981
- # continue to slice VQ as [VQ_START, CLOSED_START) without regression.
982
- # This guards against the fix accidentally requiring the Inbound header
983
- # to exist.
984
- cat > "$FIXTURE_DIR/056-qux.closed.md" <<EOF
985
- **Status**: Closed
986
- EOF
987
- cat > "$FIXTURE_DIR/README.md" <<'EOF'
988
- ## WSJF Rankings
989
-
990
- | WSJF | ID | Title | Severity | Status | Effort |
991
- |------|-----|-------|----------|--------|--------|
992
-
993
- ## Verification Queue
994
-
995
- | ID | Title | Released | Likely verified? |
996
- |----|-------|----------|------------------|
997
- | P056 | Qux | 2026-01-01 | yes |
998
-
999
- ## Closed
1000
-
1001
- | ID | Title | Closed via |
1002
- |----|-------|-----------|
1003
- EOF
1004
- run "$SCRIPT" "$FIXTURE_DIR"
1005
- [ "$status" -eq 1 ]
1006
- echo "$output" | grep -q "P056"
1007
- }