@windyroad/itil 0.16.0 → 0.17.0-preview.168
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 +1 -1
- package/skills/manage-problem/SKILL.md +79 -0
- package/skills/manage-problem/test/manage-problem-transitive-dependencies.bats +365 -0
- package/skills/report-upstream/SKILL.md +130 -18
- package/skills/report-upstream/test/report-upstream-contract.bats +85 -0
- package/skills/review-problems/SKILL.md +12 -0
- package/skills/review-problems/test/review-problems-contract.bats +30 -0
package/package.json
CHANGED
|
@@ -92,6 +92,46 @@ WSJF = (8 × 1.0) / 8 = **1.0** — defer until severity climbs or scope shrinks
|
|
|
92
92
|
|
|
93
93
|
When estimating effort, read the problem's root cause analysis and fix strategy. If effort is unknown, default to M (2). Effort is a **live estimate**, not a set-once label: re-rate it when root cause is confirmed, when architect review narrows or expands scope, and during each `manage-problem review`. A note capturing the reason for any bucket change makes the ranking audit-able (see steps 7 and 9b).
|
|
94
94
|
|
|
95
|
+
### Transitive dependencies (P076)
|
|
96
|
+
|
|
97
|
+
> **Serves**: JTBD-001 (enforce governance without slowing down — queue must not lie), JTBD-006 (progress the backlog while I'm away — AFK orchestrator iterates top-down on a trustworthy rank), JTBD-201 (restore service fast with an audit trail — ranking decisions must be defensible post-hoc).
|
|
98
|
+
|
|
99
|
+
Effort is scored per-ticket as a **marginal** estimate (the work this ticket adds on top of its upstream dependencies). When a ticket has upstream dependencies — other tickets that must close first before this one can reach "done" — the ticket's effective effort for WSJF purposes is the **transitive closure** of its marginal effort plus all blocking upstreams, not the marginal alone.
|
|
100
|
+
|
|
101
|
+
**Rule**:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Effort(T)_transitive = max(
|
|
105
|
+
Effort(T)_marginal,
|
|
106
|
+
max{ Effort(U)_transitive | U ∈ Blocked_by(T) }
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
WSJF(T) = (Severity(T) × StatusMultiplier(T)) / Effort(T)_transitive
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A dependent ticket cannot reach its "done" state without the upstream work happening first. Scoring the dependent at its marginal-only effort lies about what it costs to deliver — the **queue** would rank it higher than its blocker even though the blocker's work is strictly contained within it.
|
|
113
|
+
|
|
114
|
+
**Dependency signal**: drive the closure from the ticket's `## Dependencies` section (see the Step 5 template). Only `**Blocked by**` entries propagate effort; `**Composes with**` does NOT propagate — compositional overlap shares surface but neither side strictly blocks.
|
|
115
|
+
|
|
116
|
+
**Upstream status carve-out**: an upstream ticket in `.closed.md`, `.verifying.md`, or `.parked.md` contributes **0** to the transitive closure. Closed upstream work is done; verifying upstream work is user-side (not dev effort and excluded from dev ranking per the WSJF multiplier table); parked upstream work is suspended (excluded from ranking until un-parked). Without this carve-out, a ticket blocked by a closed ticket would inherit XL forever.
|
|
117
|
+
|
|
118
|
+
**Cycle handling**: when two or more tickets mutually block each other (e.g., shared gate-surface tickets that each list the other under `**Blocked by**`), treat the strongly-connected component as a **bundle**. The bundle's effective effort is `max{ marginal | members }`. All bundle members surface the same WSJF in review output — the shared WSJF is a **computed artefact** of the rendering, not written as a field into individual ticket files. Bundle members retain their individual Status suffixes and individual ticket files (ADR-022 suffix-based lifecycle).
|
|
119
|
+
|
|
120
|
+
**Re-rate on upstream status change**: when a dependency transitions to `.closed.md` / `.verifying.md` / `.parked.md`, the dependent ticket's transitive closure shrinks and the effort drops accordingly. Step 9b catches this automatically — no transition-time graph re-walk is required.
|
|
121
|
+
|
|
122
|
+
**Worked example**: P073 has marginal effort S (one surface-row add). P073 is blocked by P038 (XL). Then:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Effort(P073)_transitive = max(S=1, Effort(P038)_transitive) = max(1, 8) = 8
|
|
126
|
+
WSJF(P073) = (Severity(P073) × 1.0) / 8 = 12 / 8 = 1.5
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
P073's WSJF matches P038's by construction — P073 cannot out-rank the ticket whose work is strictly contained within it. Contrast with the marginal-only (incorrect) computation: `12 / 2 = 6.0`, which would mis-rank P073 as "top of queue" despite being blocked.
|
|
130
|
+
|
|
131
|
+
**Determinism**: the rule is deterministic from the graph — no `AskUserQuestion` branch is required when Step 9b re-rates a ticket. The re-rate fires silently and is logged in the review output per the Step 9b re-rate message format.
|
|
132
|
+
|
|
133
|
+
**Reassessment criteria**: this rule lives inline in manage-problem's SKILL.md (following ADR-022's precedent for inline WSJF additions). If a second skill (e.g., manage-incident or a future cross-plugin `work-backlog` orchestrator) adopts the `## Dependencies` section and the transitive-effort rule, extract to a sibling ADR at that point — wider adoption justifies the ADR cost that today's single-skill scope does not.
|
|
134
|
+
|
|
95
135
|
## Working a Problem
|
|
96
136
|
|
|
97
137
|
What "work" means depends on the problem's status:
|
|
@@ -286,11 +326,29 @@ Before writing the problem file, perform a concern-boundary analysis on the gath
|
|
|
286
326
|
- [ ] Create reproduction test
|
|
287
327
|
- [ ] Create INVEST story for permanent fix
|
|
288
328
|
|
|
329
|
+
## Dependencies
|
|
330
|
+
|
|
331
|
+
- **Blocks**: <tickets that can't close until this one does — bare IDs, comma-separated; leave empty if none>
|
|
332
|
+
- **Blocked by**: <tickets that must close first — bare IDs, comma-separated; drives the transitive-effort rule; leave empty if none>
|
|
333
|
+
- **Composes with**: <tickets whose work overlaps but neither blocks the other — does NOT propagate effort; leave empty if none>
|
|
334
|
+
|
|
289
335
|
## Related
|
|
290
336
|
|
|
291
337
|
<links to related files, problems, ADRs>
|
|
292
338
|
```
|
|
293
339
|
|
|
340
|
+
The `## Dependencies` section uses **bare ticket IDs** (`P038`, not `[P038](./038-...)` link syntax) — review output renders to links on demand. An empty row is valid and explicit: `- **Blocked by**: (none)` reads better than omitting the row. The transitive-effort rule in the WSJF Prioritisation section consumes this section at review time.
|
|
341
|
+
|
|
342
|
+
**Concrete example** (for P073 referencing two upstreams):
|
|
343
|
+
|
|
344
|
+
```markdown
|
|
345
|
+
## Dependencies
|
|
346
|
+
|
|
347
|
+
- **Blocks**: (none)
|
|
348
|
+
- **Blocked by**: P038, P064
|
|
349
|
+
- **Composes with**: (none)
|
|
350
|
+
```
|
|
351
|
+
|
|
294
352
|
### 6. For updates: Edit the existing file
|
|
295
353
|
|
|
296
354
|
Find the file matching the problem ID:
|
|
@@ -469,6 +527,27 @@ Parked problems and Verification Pending problems are excluded from WSJF ranking
|
|
|
469
527
|
- Update the Status field to "Known Error"
|
|
470
528
|
- This happens automatically — do not ask the user
|
|
471
529
|
|
|
530
|
+
**Step 9b.1: Dependency-graph traversal — propagate transitive effort (P076)**
|
|
531
|
+
|
|
532
|
+
After every `.open.md` / `.known-error.md` ticket has a marginal effort, run a **second pass** that walks the dependency graph and propagates effort up per the transitive-dependency rule (see the WSJF Prioritisation section's "Transitive dependencies" subsection). This is a deterministic re-rate — no `AskUserQuestion` required.
|
|
533
|
+
|
|
534
|
+
1. **Build the graph**: for each `.open.md` / `.known-error.md` ticket, parse the `## Dependencies` section. Record `Blocked by` edges (bare IDs) into an adjacency map. Ignore `Composes with` (does not propagate) and `Blocks` (derivable from inverse).
|
|
535
|
+
2. **Classify upstream status**: for each upstream ID referenced in any `Blocked by` edge, resolve the file suffix. Upstreams in `.closed.md`, `.verifying.md`, or `.parked.md` contribute **0** to the closure per the carve-out. Upstreams in `.open.md` or `.known-error.md` contribute their own transitive effort.
|
|
536
|
+
3. **Topologically sort** the open/known-error subgraph so upstream tickets are scored before their dependents. If a cycle is detected (two or more tickets mutually `Blocked by` each other), treat the strongly-connected component as a **bundle** with effort = `max{ marginal | members }`.
|
|
537
|
+
4. **Compute transitive effort** for each ticket in topological order using `Effort_transitive = max(marginal, max{ upstream transitive })`. Cycle-bundle members all receive the bundle's effort.
|
|
538
|
+
5. **Update Effort and WSJF lines**: if a ticket's transitive effort differs from its marginal, edit the Effort line to the transitive bucket (S → M / L / XL as needed), recompute WSJF, and update the Priority and WSJF lines. Write a short audit trail in a `<!-- transitive: <bucket> via <UPSTREAM> -->` HTML comment on the Effort line so the next review can distinguish a manually-set marginal from a propagated transitive.
|
|
539
|
+
6. **Report each re-rate** in the review summary using the concrete format:
|
|
540
|
+
|
|
541
|
+
```
|
|
542
|
+
P<NNN>: Effort <OLD> → <NEW> (transitive via <UPSTREAM>)
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
Example: `P073: Effort S → XL (transitive via P038)`. The shape is fixed so downstream audit tools can grep it deterministically.
|
|
546
|
+
|
|
547
|
+
7. **Cycle-bundle output**: for cycle bundles, surface a shared WSJF line covering all members, e.g. `Bundle [P038, P064]: effort XL (cycle), WSJF 3.0 (shared)`. The shared WSJF is a computed artefact of the review rendering — do NOT write a shared-bundle field into the individual ticket files.
|
|
548
|
+
|
|
549
|
+
The re-rate pass is part of Step 9b's output — a re-rate row appears in the step 9c ranked table with the transitive effort (not the marginal). Hide the marginal from the main table but preserve it in the ticket's HTML-comment audit trail so a future review knows where the propagation came from.
|
|
550
|
+
|
|
472
551
|
**Step 9c: Present summary and select problem to work**
|
|
473
552
|
|
|
474
553
|
After reviewing all problems, present a WSJF-ranked table for open/known-error problems (the main dev-work queue):
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Contract + behavioural tests: manage-problem SKILL.md must define a
|
|
3
|
+
# transitive-dependency rule for WSJF effort, extend Step 9b with
|
|
4
|
+
# dependency-graph traversal, and the problem-ticket template must carry
|
|
5
|
+
# a `## Dependencies` section.
|
|
6
|
+
#
|
|
7
|
+
# Mixed shape — structural assertions (ADR-037 Permitted Exception) for
|
|
8
|
+
# the contract prose Claude interprets at invocation time, AND one
|
|
9
|
+
# behavioural fixture test that exercises the transitive-closure
|
|
10
|
+
# algorithm directly so the rule is not merely "keyword present" (P081
|
|
11
|
+
# pressure — behavioural where feasible).
|
|
12
|
+
#
|
|
13
|
+
# Cross-reference:
|
|
14
|
+
# @problem P076 — docs/problems/076-wsjf-does-not-model-transitive-dependencies.open.md
|
|
15
|
+
# ADR-022: verification-pending status carve-out from the closure
|
|
16
|
+
# ADR-014: governance-skills commit their own work
|
|
17
|
+
# ADR-037: contract-assertion bats pattern for SKILL.md prose contracts
|
|
18
|
+
# @jtbd JTBD-001 (solo-developer — enforce governance without slowing down)
|
|
19
|
+
# @jtbd JTBD-006 (solo-developer — progress the backlog while I'm away)
|
|
20
|
+
# @jtbd JTBD-201 (tech-lead — restore service fast with an audit trail)
|
|
21
|
+
|
|
22
|
+
setup() {
|
|
23
|
+
SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
24
|
+
SKILL_FILE="${SKILL_DIR}/SKILL.md"
|
|
25
|
+
FIXTURE_DIR="$(mktemp -d)"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
teardown() {
|
|
29
|
+
[ -n "${FIXTURE_DIR:-}" ] && [ -d "$FIXTURE_DIR" ] && rm -rf "$FIXTURE_DIR"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
# Contract: WSJF section defines the transitive-dependency rule
|
|
34
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
@test "SKILL.md WSJF section has a Transitive dependencies subsection (P076)" {
|
|
37
|
+
# The rule must live in a discoverable subsection so Claude finds it
|
|
38
|
+
# when interpreting the WSJF scoring contract.
|
|
39
|
+
run grep -nE "^### .*[Tt]ransitive [Dd]ependenc" "$SKILL_FILE"
|
|
40
|
+
[ "$status" -eq 0 ]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@test "SKILL.md transitive rule defines effort as max(marginal, blocked-by closure) (P076)" {
|
|
44
|
+
# The core rule: Effort(T)_transitive = max(marginal, max{blocked-by})
|
|
45
|
+
# The prose must make the max-of relationship explicit so a dependent
|
|
46
|
+
# ticket inherits upstream effort.
|
|
47
|
+
run grep -inE "max.*marginal|marginal.*max|transitive.*effort|effort.*transitive" "$SKILL_FILE"
|
|
48
|
+
[ "$status" -eq 0 ]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@test "SKILL.md transitive rule cites Blocked by as the dependency signal (P076)" {
|
|
52
|
+
# Blocked_by is the formally-scoped edge in the ticket-dependency graph.
|
|
53
|
+
# The rule must name it (not just generic "dependencies") so Claude knows
|
|
54
|
+
# which `## Dependencies` row drives the effort propagation.
|
|
55
|
+
run grep -inE "Blocked[[:space:]-]?by" "$SKILL_FILE"
|
|
56
|
+
[ "$status" -eq 0 ]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@test "SKILL.md transitive rule carves Composes with OUT of the closure (P076)" {
|
|
60
|
+
# Compositional overlap does NOT strictly block — sibling work that
|
|
61
|
+
# shares surface should not inflate the dependent's effort.
|
|
62
|
+
run grep -inE "[Cc]omposes[[:space:]]with.*not|not.*[Cc]omposes|[Cc]omposes[[:space:]]with.*does NOT|[Cc]omposes[[:space:]]with.*excluded" "$SKILL_FILE"
|
|
63
|
+
[ "$status" -eq 0 ]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@test "SKILL.md transitive rule carves .closed / .verifying / .parked OUT of the closure (architect note)" {
|
|
67
|
+
# Architect correction: already-done or user-blocked upstreams contribute
|
|
68
|
+
# zero marginal dev effort — otherwise a ticket blocked by a closed
|
|
69
|
+
# ticket would inherit XL forever.
|
|
70
|
+
run grep -inE "(\.closed\.md|\.verifying\.md|\.parked\.md).*(contribut|0|zero|exclud)|contribut.*0.*(\.closed|\.verifying|\.parked)" "$SKILL_FILE"
|
|
71
|
+
[ "$status" -eq 0 ]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@test "SKILL.md transitive rule documents cycle bundling (shared WSJF as computed artefact) (P076)" {
|
|
75
|
+
# Cycles (e.g. P038/P064 mutual composition) must be handled — bundle's
|
|
76
|
+
# effort = max of members' marginals; shared WSJF surfaced in review
|
|
77
|
+
# output, not written as a field per-ticket (ADR-022 suffix-based pattern).
|
|
78
|
+
run grep -inE "cycle|bundle" "$SKILL_FILE"
|
|
79
|
+
[ "$status" -eq 0 ]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@test "SKILL.md transitive rule carries a worked example (P076)" {
|
|
83
|
+
# Worked example: P073 marginal S blocked by P038 XL → transitive XL →
|
|
84
|
+
# WSJF drops to match P038. The number-grounded example keeps the rule
|
|
85
|
+
# out of hand-wavy territory (ADR-026 grounded output).
|
|
86
|
+
run grep -inE "[Ww]orked[[:space:]]example|example.*transitive|transitive.*example" "$SKILL_FILE"
|
|
87
|
+
[ "$status" -eq 0 ]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@test "SKILL.md transitive rule notes reassessment-criteria for future ADR extraction (architect note)" {
|
|
91
|
+
# Architect guidance: keep a pointer for when another skill adopts the
|
|
92
|
+
# `## Dependencies` convention — at that point, extract to a sibling ADR.
|
|
93
|
+
# Inline note now avoids speculative ADR authoring today.
|
|
94
|
+
run grep -inE "[Rr]eassessment|[Ee]xtract.*ADR|sibling ADR|future ADR" "$SKILL_FILE"
|
|
95
|
+
[ "$status" -eq 0 ]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
99
|
+
# Contract: Step 9b review extends to dependency-graph traversal
|
|
100
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
@test "SKILL.md Step 9b describes a dependency-graph traversal pass (P076)" {
|
|
103
|
+
# After per-ticket marginal scoring, Step 9b must walk the graph and
|
|
104
|
+
# propagate effort up. The prose must name the traversal explicitly so
|
|
105
|
+
# Claude performs the second pass, not just the first.
|
|
106
|
+
run grep -inE "dependency[[:space:]-]?graph|graph traversal|topological.*sort|propagate.*effort|effort.*propagat" "$SKILL_FILE"
|
|
107
|
+
[ "$status" -eq 0 ]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@test "SKILL.md Step 9b reports transitive re-rates in the review summary (P076 + JTBD-006)" {
|
|
111
|
+
# The AFK orchestrator (JTBD-006) and the audit-trail persona (JTBD-201)
|
|
112
|
+
# depend on Step 9b emitting a visible re-rate line — not a silent
|
|
113
|
+
# update.
|
|
114
|
+
run grep -inE "transitive.*(re-?rate|rerat|re-?estimat)|report.*transitive|re-?rate.*transitive" "$SKILL_FILE"
|
|
115
|
+
[ "$status" -eq 0 ]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@test "SKILL.md Step 9b re-rate message format is concrete (architect note — bats-assertable shape)" {
|
|
119
|
+
# Architect correction 4: specify a concrete message shape so the
|
|
120
|
+
# contract can be grepped. Format: P<NNN>: Effort <OLD> → <NEW>
|
|
121
|
+
# (transitive via <UPSTREAM>).
|
|
122
|
+
run grep -inE "Effort.*→.*transitive via|transitive via.*P[0-9]" "$SKILL_FILE"
|
|
123
|
+
[ "$status" -eq 0 ]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
127
|
+
# Contract: Step 5 problem-ticket template includes `## Dependencies`
|
|
128
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
@test "SKILL.md Step 5 template includes a ## Dependencies section (P076)" {
|
|
131
|
+
# New tickets must carry an explicit dependency list so the graph is
|
|
132
|
+
# legible. Empty lists are allowed (default: no deps).
|
|
133
|
+
run grep -nE "^## Dependencies" "$SKILL_FILE"
|
|
134
|
+
[ "$status" -eq 0 ]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@test "SKILL.md Dependencies template lists Blocks / Blocked by / Composes with rows (P076)" {
|
|
138
|
+
# The three row labels must all be present in the template so the
|
|
139
|
+
# author knows which semantics apply. Blocks = downstream; Blocked by =
|
|
140
|
+
# drives effort propagation; Composes with = overlap without blocking.
|
|
141
|
+
run grep -inE "\*\*Blocks\*\*|\*\*Blocked by\*\*|\*\*Composes with\*\*" "$SKILL_FILE"
|
|
142
|
+
[ "$status" -eq 0 ]
|
|
143
|
+
# All three labels must be present.
|
|
144
|
+
blocks_hits=$(grep -cE "\*\*Blocks\*\*" "$SKILL_FILE" || true)
|
|
145
|
+
blocked_hits=$(grep -cE "\*\*Blocked by\*\*" "$SKILL_FILE" || true)
|
|
146
|
+
composes_hits=$(grep -cE "\*\*Composes with\*\*" "$SKILL_FILE" || true)
|
|
147
|
+
[ "$blocks_hits" -ge 1 ]
|
|
148
|
+
[ "$blocked_hits" -ge 1 ]
|
|
149
|
+
[ "$composes_hits" -ge 1 ]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@test "SKILL.md Dependencies rows use bare ticket IDs (architect note Q1)" {
|
|
153
|
+
# Bare IDs (P038) beat link syntax (`[P038](./038-...)`) — less
|
|
154
|
+
# maintenance; review output renders to links on demand. The template
|
|
155
|
+
# example must show bare-ID form (not `[P038](./038-...)`).
|
|
156
|
+
# Canonical shape: `- **Blocked by**: P<NNN>, P<NNN>` — match either
|
|
157
|
+
# bold-markdown label form or plain-label form, asserting the ID is
|
|
158
|
+
# bare (not bracketed).
|
|
159
|
+
run grep -inE "Blocked by\*{0,2}:[[:space:]]*P[0-9]" "$SKILL_FILE"
|
|
160
|
+
[ "$status" -eq 0 ]
|
|
161
|
+
# Assert the example does NOT use link syntax `[PNNN](./...)`.
|
|
162
|
+
! grep -E "Blocked by\*{0,2}:[[:space:]]*\[P[0-9]" "$SKILL_FILE"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
# Traceability: P076 cited
|
|
167
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
@test "SKILL.md cites P076 in the transitive-dependencies prose (traceability)" {
|
|
170
|
+
# Every governance-contract change must cite the problem ticket that
|
|
171
|
+
# motivated it — audit trail per ADR-014.
|
|
172
|
+
run grep -n "P076" "$SKILL_FILE"
|
|
173
|
+
[ "$status" -eq 0 ]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
177
|
+
# Behavioural: exercise the transitive-closure rule with fixture tickets
|
|
178
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
179
|
+
# These tests build a minimal fixture backlog and run a bash transcription
|
|
180
|
+
# of the transitive-closure rule against it. The rule's executable form is
|
|
181
|
+
# small enough to bash-implement; we assert the output matches the rule's
|
|
182
|
+
# spec so a prose drift (e.g. accidentally documenting `min` instead of
|
|
183
|
+
# `max`) would be caught at test time.
|
|
184
|
+
|
|
185
|
+
# Bash transcription of the transitive-closure rule — effort map is
|
|
186
|
+
# keyed by ticket ID, value is the integer divisor from the SKILL.md
|
|
187
|
+
# effort table (S=1, M=2, L=4, XL=8).
|
|
188
|
+
transitive_effort() {
|
|
189
|
+
local ticket="$1"
|
|
190
|
+
local backlog_dir="$2"
|
|
191
|
+
local file
|
|
192
|
+
file=$(ls "$backlog_dir"/${ticket}-*.md 2>/dev/null | head -1)
|
|
193
|
+
[ -z "$file" ] && { echo 0; return; }
|
|
194
|
+
|
|
195
|
+
# Extract marginal effort from the Effort line. The line shape matches
|
|
196
|
+
# the SKILL.md Priority/Effort convention: "**Effort**: <bucket> — ..."
|
|
197
|
+
local marginal_bucket
|
|
198
|
+
marginal_bucket=$(grep -oE '\*\*Effort\*\*: [SMLXL]+' "$file" | head -1 | grep -oE '[SMLXL]+' | head -1)
|
|
199
|
+
local marginal_divisor
|
|
200
|
+
case "$marginal_bucket" in
|
|
201
|
+
S) marginal_divisor=1 ;;
|
|
202
|
+
M) marginal_divisor=2 ;;
|
|
203
|
+
L) marginal_divisor=4 ;;
|
|
204
|
+
XL) marginal_divisor=8 ;;
|
|
205
|
+
*) marginal_divisor=2 ;; # default M
|
|
206
|
+
esac
|
|
207
|
+
|
|
208
|
+
# .closed.md / .verifying.md / .parked.md upstreams contribute 0 (architect carve-out)
|
|
209
|
+
case "$file" in
|
|
210
|
+
*.closed.md|*.verifying.md|*.parked.md)
|
|
211
|
+
echo 0
|
|
212
|
+
return
|
|
213
|
+
;;
|
|
214
|
+
esac
|
|
215
|
+
|
|
216
|
+
# Extract `Blocked by:` dependency IDs (bare, comma-separated).
|
|
217
|
+
# The template shape is `- **Blocked by**: P<NNN>, P<NNN>` (markdown
|
|
218
|
+
# list item under `## Dependencies`) — match the label with optional
|
|
219
|
+
# leading list marker.
|
|
220
|
+
local blocked_by_line
|
|
221
|
+
blocked_by_line=$(grep -E "^[[:space:]]*-?[[:space:]]*\*\*Blocked by\*\*:" "$file" | head -1 | sed 's/.*\*\*Blocked by\*\*://')
|
|
222
|
+
local max_upstream=0
|
|
223
|
+
local dep
|
|
224
|
+
for dep in $(echo "$blocked_by_line" | grep -oE 'P[0-9]+'); do
|
|
225
|
+
local upstream_effort
|
|
226
|
+
upstream_effort=$(transitive_effort "$dep" "$backlog_dir")
|
|
227
|
+
[ "$upstream_effort" -gt "$max_upstream" ] && max_upstream="$upstream_effort"
|
|
228
|
+
done
|
|
229
|
+
|
|
230
|
+
# Transitive = max(marginal, upstream closure)
|
|
231
|
+
if [ "$max_upstream" -gt "$marginal_divisor" ]; then
|
|
232
|
+
echo "$max_upstream"
|
|
233
|
+
else
|
|
234
|
+
echo "$marginal_divisor"
|
|
235
|
+
fi
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@test "behavioural: dependent ticket inherits upstream XL when blocked by XL ticket (P076 worked example)" {
|
|
239
|
+
# Fixture: P200 (marginal S) Blocked by: P201 (XL)
|
|
240
|
+
# Expected: transitive effort for P200 = XL divisor = 8
|
|
241
|
+
cat > "$FIXTURE_DIR/P201-upstream-xl.open.md" <<'EOF'
|
|
242
|
+
# Problem 201: upstream XL ticket
|
|
243
|
+
**Status**: Open
|
|
244
|
+
**Effort**: XL — multi-day cross-package work
|
|
245
|
+
|
|
246
|
+
## Dependencies
|
|
247
|
+
- **Blocked by**: (none)
|
|
248
|
+
EOF
|
|
249
|
+
cat > "$FIXTURE_DIR/P200-dependent-small.open.md" <<'EOF'
|
|
250
|
+
# Problem 200: dependent with small marginal effort
|
|
251
|
+
**Status**: Open
|
|
252
|
+
**Effort**: S — one-line surface add
|
|
253
|
+
|
|
254
|
+
## Dependencies
|
|
255
|
+
- **Blocked by**: P201
|
|
256
|
+
EOF
|
|
257
|
+
result=$(transitive_effort "P200" "$FIXTURE_DIR")
|
|
258
|
+
[ "$result" = "8" ]
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@test "behavioural: self-contained ticket keeps marginal effort (P076 no-op path)" {
|
|
262
|
+
# Fixture: P210 (marginal M) with empty Blocked by.
|
|
263
|
+
# Expected: transitive = marginal M divisor = 2.
|
|
264
|
+
cat > "$FIXTURE_DIR/P210-solo-m.open.md" <<'EOF'
|
|
265
|
+
# Problem 210: self-contained
|
|
266
|
+
**Status**: Open
|
|
267
|
+
**Effort**: M — couple of files
|
|
268
|
+
|
|
269
|
+
## Dependencies
|
|
270
|
+
- **Blocked by**: (none)
|
|
271
|
+
EOF
|
|
272
|
+
result=$(transitive_effort "P210" "$FIXTURE_DIR")
|
|
273
|
+
[ "$result" = "2" ]
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@test "behavioural: closed upstream contributes 0 to the closure (architect carve-out)" {
|
|
277
|
+
# Fixture: P220 (marginal S) Blocked by: P221 (was XL, now closed).
|
|
278
|
+
# Expected: transitive = marginal S = 1 (closed upstream contributes 0;
|
|
279
|
+
# otherwise P220 would inherit XL forever).
|
|
280
|
+
cat > "$FIXTURE_DIR/P221-closed-xl.closed.md" <<'EOF'
|
|
281
|
+
# Problem 221: closed upstream (was XL)
|
|
282
|
+
**Status**: Closed
|
|
283
|
+
**Effort**: XL
|
|
284
|
+
EOF
|
|
285
|
+
cat > "$FIXTURE_DIR/P220-dependent-on-closed.open.md" <<'EOF'
|
|
286
|
+
# Problem 220: dependent on closed
|
|
287
|
+
**Status**: Open
|
|
288
|
+
**Effort**: S
|
|
289
|
+
|
|
290
|
+
## Dependencies
|
|
291
|
+
- **Blocked by**: P221
|
|
292
|
+
EOF
|
|
293
|
+
result=$(transitive_effort "P220" "$FIXTURE_DIR")
|
|
294
|
+
[ "$result" = "1" ]
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@test "behavioural: marginal effort wins when upstream is smaller (P076 max-of semantics)" {
|
|
298
|
+
# Fixture: P230 (marginal L = 4) Blocked by: P231 (S = 1)
|
|
299
|
+
# Expected: transitive = max(L, S) = L divisor = 4.
|
|
300
|
+
# (Contrast: a buggy implementation using min or sum would produce 1 or 5.)
|
|
301
|
+
cat > "$FIXTURE_DIR/P231-small-upstream.open.md" <<'EOF'
|
|
302
|
+
# Problem 231: small upstream
|
|
303
|
+
**Status**: Open
|
|
304
|
+
**Effort**: S
|
|
305
|
+
EOF
|
|
306
|
+
cat > "$FIXTURE_DIR/P230-big-dependent.open.md" <<'EOF'
|
|
307
|
+
# Problem 230: bigger dependent
|
|
308
|
+
**Status**: Open
|
|
309
|
+
**Effort**: L
|
|
310
|
+
|
|
311
|
+
## Dependencies
|
|
312
|
+
- **Blocked by**: P231
|
|
313
|
+
EOF
|
|
314
|
+
result=$(transitive_effort "P230" "$FIXTURE_DIR")
|
|
315
|
+
[ "$result" = "4" ]
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@test "behavioural: transitive propagates across two hops (P076 closure semantics)" {
|
|
319
|
+
# Fixture: P240 (S) → blocked by P241 (S) → blocked by P242 (XL)
|
|
320
|
+
# Expected: transitive(P240) = max(S, transitive(P241)) = max(S, max(S, XL)) = XL = 8.
|
|
321
|
+
cat > "$FIXTURE_DIR/P242-deepest-xl.open.md" <<'EOF'
|
|
322
|
+
# Problem 242: deepest XL
|
|
323
|
+
**Status**: Open
|
|
324
|
+
**Effort**: XL
|
|
325
|
+
EOF
|
|
326
|
+
cat > "$FIXTURE_DIR/P241-middle-s.open.md" <<'EOF'
|
|
327
|
+
# Problem 241: middle S
|
|
328
|
+
**Status**: Open
|
|
329
|
+
**Effort**: S
|
|
330
|
+
|
|
331
|
+
## Dependencies
|
|
332
|
+
- **Blocked by**: P242
|
|
333
|
+
EOF
|
|
334
|
+
cat > "$FIXTURE_DIR/P240-top-s.open.md" <<'EOF'
|
|
335
|
+
# Problem 240: top S
|
|
336
|
+
**Status**: Open
|
|
337
|
+
**Effort**: S
|
|
338
|
+
|
|
339
|
+
## Dependencies
|
|
340
|
+
- **Blocked by**: P241
|
|
341
|
+
EOF
|
|
342
|
+
result=$(transitive_effort "P240" "$FIXTURE_DIR")
|
|
343
|
+
[ "$result" = "8" ]
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@test "behavioural: verification-pending upstream contributes 0 (architect carve-out for .verifying.md)" {
|
|
347
|
+
# Fixture: P250 (marginal S) Blocked by: P251 (was XL, now verifying).
|
|
348
|
+
# Expected: transitive = marginal S = 1 (verifying contributes 0 — the
|
|
349
|
+
# remaining work is user-side verification, not dev effort).
|
|
350
|
+
cat > "$FIXTURE_DIR/P251-verifying-xl.verifying.md" <<'EOF'
|
|
351
|
+
# Problem 251: verification pending (was XL)
|
|
352
|
+
**Status**: Verification Pending
|
|
353
|
+
**Effort**: XL
|
|
354
|
+
EOF
|
|
355
|
+
cat > "$FIXTURE_DIR/P250-dependent-on-verifying.open.md" <<'EOF'
|
|
356
|
+
# Problem 250: dependent on verifying
|
|
357
|
+
**Status**: Open
|
|
358
|
+
**Effort**: S
|
|
359
|
+
|
|
360
|
+
## Dependencies
|
|
361
|
+
- **Blocked by**: P251
|
|
362
|
+
EOF
|
|
363
|
+
result=$(transitive_effort "P250" "$FIXTURE_DIR")
|
|
364
|
+
[ "$result" = "1" ]
|
|
365
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wr-itil:report-upstream
|
|
3
|
-
description: Report a local problem ticket as a structured issue against an upstream repository, with bidirectional cross-references and SECURITY.md-aware routing for security-classified tickets. Implements the contract in ADR-024.
|
|
3
|
+
description: Report a local problem ticket as a structured issue against an upstream repository, with bidirectional cross-references and SECURITY.md-aware routing for security-classified tickets. Implements the contract in ADR-024, with ADR-033 governing problem-first classifier + default body shape.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,6 +10,8 @@ File a local `docs/problems/<NNN>` ticket as an issue (or private security advis
|
|
|
10
10
|
|
|
11
11
|
This skill implements the contract documented in [ADR-024](../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) (Cross-project problem-reporting contract). All step numbering below maps 1:1 to ADR-024 Decision Outcome.
|
|
12
12
|
|
|
13
|
+
[ADR-033](../../../docs/decisions/033-report-upstream-classifier-problem-first.proposed.md) (Report-upstream classifier is problem-first) partially supersedes ADR-024 Decision Outcome **Steps 3 and 5 only** — the classifier is problem-first with best-fit backward-compat fallback (per Step 3 below), and the structured default body is problem-shaped (per Step 5 below). ADR-024 Steps 1, 2, 4, 6, 7, 8 and all Consequences / Confirmation clauses remain in force unchanged.
|
|
14
|
+
|
|
13
15
|
## Invocation
|
|
14
16
|
|
|
15
17
|
```
|
|
@@ -77,18 +79,28 @@ For each `.yml` template found, fetch the file via `gh api repos/<owner>/<repo>/
|
|
|
77
79
|
|
|
78
80
|
### 3. Classify the local ticket and pick the best-matching template
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
82
|
+
This step is governed by [ADR-033](../../../docs/decisions/033-report-upstream-classifier-problem-first.proposed.md) (Report-upstream classifier is problem-first), which partially supersedes ADR-024 Decision Outcome Step 3. Classification is **problem-first with best-fit backward-compat fallback** — upstream repos that have adopted the problem-first intake shape (per `@windyroad/itil`) are targeted first; older repos that still ship bug/feature/question templates are served via a fallback.
|
|
83
|
+
|
|
84
|
+
**Preference order** (first match wins):
|
|
85
|
+
|
|
86
|
+
1. **`problem` shape (primary)** — any of the tokens `problem`, `issue`, `concern`, `defect`, `gap` appear in the local ticket title or body; or the body contains a scoped-npm package reference (`@scope/name`); or the body contains any of `root cause`, `reproduction`, `workaround`. This is the default for tickets authored via `/wr-itil:manage-problem`.
|
|
87
|
+
2. **`bug` shape (backward-compat fallback)** — no primary tokens match, and the prose is defect-like (contains `broken`, `fails`, `error`, `bug`, `regression`, or a specific observed-vs-expected contrast). Produces a bug-shaped body only when the upstream has no `problem-report.yml`.
|
|
88
|
+
3. **`feature` shape (backward-compat fallback)** — no primary tokens match, and the prose is proposal-like (contains `would be nice`, `enhancement`, `feature request`, `could we`, `wish`).
|
|
89
|
+
4. **`question` shape (backward-compat fallback)** — trailing fallback when the prose is a genuine question (ends in `?`, contains `how do I`, `is there a way`).
|
|
90
|
+
|
|
91
|
+
The CLI `--classification` argument overrides the heuristic. The security-path check in Step 4 fires **before** this classifier — security-classified tickets bypass the classifier entirely.
|
|
92
|
+
|
|
93
|
+
**Template-discovery preference order** (extends ADR-024 Step 1; search the upstream `.github/ISSUE_TEMPLATE/` directory in this order, first match wins):
|
|
85
94
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
-
|
|
95
|
+
1. `problem-report.yml` — preferred; the Windy-Road problem-first shape.
|
|
96
|
+
2. `problem.yml` — alternate naming for problem-shaped templates.
|
|
97
|
+
3. `problem-report.md` / `problem.md` — legacy markdown variants of the problem-shaped template.
|
|
98
|
+
4. `bug-report.yml` / `bug.yml` / `bug-report.md` / `bug.md` — if primary classifier picked `bug` shape OR no problem template exists and fallback is `bug`.
|
|
99
|
+
5. `feature-request.yml` / `feature.yml` / `feature-request.md` — `feature` shape fallback.
|
|
100
|
+
6. `question.yml` / `question.md` — `question` shape fallback. If absent, the upstream's `config.yml` likely routes questions elsewhere (Discussions); halt and surface the routing target.
|
|
101
|
+
7. Structured default body per Step 5 below — if no template matches.
|
|
90
102
|
|
|
91
|
-
Log the matched template name in the Step 7 back-write. If no template matches the classification, fall through to the structured default in Step 5.
|
|
103
|
+
Log the matched template name (or `structured default`) in the Step 7 back-write. If no template matches the classification, fall through to the structured default in Step 5.
|
|
92
104
|
|
|
93
105
|
### 4. Security-path routing check
|
|
94
106
|
|
|
@@ -102,19 +114,72 @@ If security-classified, route to Step 6. Otherwise, route to Step 5 (public-issu
|
|
|
102
114
|
|
|
103
115
|
### 5. Public-issue path
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
This step is governed by [ADR-033](../../../docs/decisions/033-report-upstream-classifier-problem-first.proposed.md) (Report-upstream classifier is problem-first), which partially supersedes ADR-024 Decision Outcome Step 5. The primary structured default body is **problem-shaped** and mirrors the `/wr-itil:manage-problem` ticket shape; the bug-shaped / feature-shaped / question-shaped bodies are retained as fallback-only templates for the backward-compat branches of the Step 3 classifier.
|
|
118
|
+
|
|
119
|
+
If the upstream had a matching template (Step 3), fill its required fields from the local ticket. Field-mapping table for the problem-first case (problem-report.yml template):
|
|
106
120
|
|
|
107
121
|
| Upstream template field (typical) | Local ticket source |
|
|
108
122
|
|---|---|
|
|
109
|
-
| `plugin` / `package` / `module` | Inferred from upstream repo name or local ticket's "Affected plugin"
|
|
123
|
+
| `plugin` / `package` / `module` | Inferred from upstream repo name or local ticket's "Affected plugin / component" |
|
|
110
124
|
| `version` | Local ticket's environment notes; or `npm view <pkg> version` for the latest if ambiguous |
|
|
111
125
|
| `claude-code-version` | `claude --version` if the report originates from a Claude Code session |
|
|
112
126
|
| `os` | Local ticket's environment notes; or `uname -srm` of the reporting host |
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
127
|
+
| `description` | Local ticket's `## Description` section |
|
|
128
|
+
| `symptoms` | Local ticket's `## Symptoms` section |
|
|
129
|
+
| `workaround` | Local ticket's `## Workaround` section (or "None identified yet.") |
|
|
130
|
+
| `frequency` | Local ticket's `## Impact Assessment` Frequency line |
|
|
131
|
+
| `evidence` | Commit SHAs, test output, transcript excerpts from Investigation Tasks |
|
|
132
|
+
|
|
133
|
+
For upstream repos whose matched template is `bug-report.yml` / `feature-request.yml` / `question.yml` (Step 3 backward-compat fallback), the skill fills the corresponding field set: `reproduction` ← `## Symptoms`; `expected` / `actual` ← observed-vs-expected contrast lines under `## Description`; `proposal` (for features) ← `## Description`.
|
|
134
|
+
|
|
135
|
+
#### Structured default body — problem-shaped (primary, per ADR-033)
|
|
136
|
+
|
|
137
|
+
Use this body when the Step 3 classifier picked `problem` shape AND the upstream has no `problem-report.yml` / `problem.yml` / `problem-report.md` / `problem.md`:
|
|
138
|
+
|
|
139
|
+
```markdown
|
|
140
|
+
## Description
|
|
141
|
+
|
|
142
|
+
<one-paragraph synthesis of the local ticket's Description>
|
|
143
|
+
|
|
144
|
+
## Symptoms
|
|
145
|
+
|
|
146
|
+
<bullet list from local ticket's Symptoms>
|
|
147
|
+
|
|
148
|
+
## Workaround
|
|
149
|
+
|
|
150
|
+
<from local ticket's Workaround section; "None identified yet." if absent>
|
|
151
|
+
|
|
152
|
+
## Affected plugin / component
|
|
153
|
+
|
|
154
|
+
<inferred from the local ticket's Impact Assessment or inferred from context>
|
|
155
|
+
|
|
156
|
+
## Frequency
|
|
157
|
+
|
|
158
|
+
<from the local ticket's Impact Assessment "Frequency" line>
|
|
159
|
+
|
|
160
|
+
## Environment
|
|
161
|
+
|
|
162
|
+
- Package: <inferred from upstream repo>
|
|
163
|
+
- Version: <detected via npm ls or local ticket's notes>
|
|
164
|
+
- Claude Code version: <claude --version>
|
|
165
|
+
- OS: <uname -srm>
|
|
166
|
+
|
|
167
|
+
## Evidence
|
|
168
|
+
|
|
169
|
+
<commit SHAs, test output, transcript excerpts — drawn from the local ticket's Investigation Tasks>
|
|
170
|
+
|
|
171
|
+
## Cross-reference
|
|
172
|
+
|
|
173
|
+
Reported from <downstream-repo-url>/<local-ticket-relative-path>
|
|
174
|
+
|
|
175
|
+
This issue is tracked locally as P<NNN> in the downstream project's `docs/problems/` directory.
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The body MUST include the `## Cross-reference` section so Step 7's back-write contract works (the downstream ticket's `## Reported Upstream` section records the upstream URL; the upstream issue body records the downstream reference).
|
|
116
179
|
|
|
117
|
-
|
|
180
|
+
#### Structured default body — bug-shaped (fallback-only)
|
|
181
|
+
|
|
182
|
+
Use this body only when the Step 3 classifier picked `bug` shape as backward-compat fallback (no primary `problem` tokens matched) AND the upstream has no matching template:
|
|
118
183
|
|
|
119
184
|
```markdown
|
|
120
185
|
## Summary
|
|
@@ -147,6 +212,50 @@ Reported from <downstream-repo-url>/<local-ticket-relative-path>
|
|
|
147
212
|
This issue is tracked locally as P<NNN> in the downstream project's `docs/problems/` directory.
|
|
148
213
|
```
|
|
149
214
|
|
|
215
|
+
#### Structured default body — feature-shaped (fallback-only)
|
|
216
|
+
|
|
217
|
+
Use this body only when the Step 3 classifier picked `feature` shape as backward-compat fallback AND the upstream has no matching template:
|
|
218
|
+
|
|
219
|
+
```markdown
|
|
220
|
+
## Proposal
|
|
221
|
+
|
|
222
|
+
<one-paragraph synthesis of the local ticket's Description>
|
|
223
|
+
|
|
224
|
+
## Motivation
|
|
225
|
+
|
|
226
|
+
<why this matters, from local ticket's Impact Assessment>
|
|
227
|
+
|
|
228
|
+
## Alternatives considered
|
|
229
|
+
|
|
230
|
+
<from local ticket's Root Cause Analysis or Candidate fix options>
|
|
231
|
+
|
|
232
|
+
## Cross-reference
|
|
233
|
+
|
|
234
|
+
Reported from <downstream-repo-url>/<local-ticket-relative-path>
|
|
235
|
+
|
|
236
|
+
This issue is tracked locally as P<NNN> in the downstream project's `docs/problems/` directory.
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### Structured default body — question-shaped (fallback-only)
|
|
240
|
+
|
|
241
|
+
Use this body only when the Step 3 classifier picked `question` shape as backward-compat fallback AND the upstream has no matching template (and no `config.yml` re-routing to Discussions):
|
|
242
|
+
|
|
243
|
+
```markdown
|
|
244
|
+
## Question
|
|
245
|
+
|
|
246
|
+
<the question itself, from the local ticket's title or Description>
|
|
247
|
+
|
|
248
|
+
## Context
|
|
249
|
+
|
|
250
|
+
<what prompted the question, from local ticket's Description or Symptoms>
|
|
251
|
+
|
|
252
|
+
## Cross-reference
|
|
253
|
+
|
|
254
|
+
Reported from <downstream-repo-url>/<local-ticket-relative-path>
|
|
255
|
+
|
|
256
|
+
This issue is tracked locally as P<NNN> in the downstream project's `docs/problems/` directory.
|
|
257
|
+
```
|
|
258
|
+
|
|
150
259
|
Open the issue:
|
|
151
260
|
|
|
152
261
|
```bash
|
|
@@ -236,13 +345,16 @@ Three distinct AFK branches per the architect review of ADR-024 + ADR-013 Rule 6
|
|
|
236
345
|
|
|
237
346
|
## References
|
|
238
347
|
|
|
239
|
-
- [ADR-024](../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) — primary contract this skill implements.
|
|
348
|
+
- [ADR-024](../../../docs/decisions/024-cross-project-problem-reporting-contract.proposed.md) — primary contract this skill implements. Steps 1, 2, 4, 6, 7, 8 and all Consequences remain authoritative.
|
|
349
|
+
- [ADR-033](../../../docs/decisions/033-report-upstream-classifier-problem-first.proposed.md) — partially supersedes ADR-024 Decision Outcome Steps 3 + 5; governs the problem-first classifier and problem-shaped structured default body.
|
|
240
350
|
- [ADR-027](../../../docs/decisions/027-governance-skill-auto-delegation.proposed.md) — Step-0 deferral rationale (held for reassessment).
|
|
241
351
|
- [ADR-028](../../../docs/decisions/028-voice-tone-gate-external-comms.proposed.md) — voice-tone gate on `gh issue create` and `gh api .../security-advisories`.
|
|
242
352
|
- [ADR-013](../../../docs/decisions/013-structured-user-interaction-for-governance-decisions.proposed.md) — interaction policy; Rule 1 governs Step 6 missing-SECURITY.md `AskUserQuestion`; Rule 6 governs the commit-gate AFK branch.
|
|
243
353
|
- [ADR-014](../../../docs/decisions/014-governance-skills-commit-their-own-work.proposed.md) — work → score → commit ordering.
|
|
244
354
|
- [ADR-015](../../../docs/decisions/015-on-demand-assessment-skills.proposed.md) — fallback path for `wr-risk-scorer:assess-release`.
|
|
245
355
|
- [P055](../../../docs/problems/055-no-standard-problem-reporting-channel.open.md) — upstream problem ticket (Part B).
|
|
356
|
+
- **P066** — intake templates in this repo adopted the problem-first shape (must ship before P067 so the skill's preference order matches the reference shape).
|
|
357
|
+
- **P067** — driver ticket for the problem-first classifier reform implemented via ADR-033.
|
|
246
358
|
- `packages/itil/skills/manage-problem/SKILL.md` — names the optional `## Reported Upstream` section as an allowed appendage to a problem ticket.
|
|
247
359
|
|
|
248
360
|
$ARGUMENTS
|
|
@@ -73,3 +73,88 @@ setup() {
|
|
|
73
73
|
run grep -F 'AFK behaviour summary' "$SKILL_MD"
|
|
74
74
|
[ "$status" -eq 0 ]
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
# ─── ADR-033 problem-first classifier contract (P067) ──────────────────────────
|
|
78
|
+
#
|
|
79
|
+
# ADR-033 partially supersedes ADR-024 Decision Outcome Steps 3 + 5 with a
|
|
80
|
+
# problem-first classifier and a problem-shaped structured default body. The
|
|
81
|
+
# following assertions pin the SKILL.md to ADR-033's Confirmation clauses
|
|
82
|
+
# (lines 157-164 of the ADR).
|
|
83
|
+
|
|
84
|
+
@test "report-upstream: SKILL.md cross-references ADR-033 (ADR-033 Confirmation)" {
|
|
85
|
+
run grep -F 'ADR-033' "$SKILL_MD"
|
|
86
|
+
[ "$status" -eq 0 ]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@test "report-upstream: SKILL.md Step 3 classifier lists problem-first tokens (ADR-033 Step 3)" {
|
|
90
|
+
# Primary classifier tokens per ADR-033: problem / issue / concern / defect / gap.
|
|
91
|
+
# All five must appear in the SKILL.md classifier narrative.
|
|
92
|
+
for token in problem issue concern defect gap; do
|
|
93
|
+
run grep -iE "\`${token}\`|\"${token}\"|'${token}'|${token} shape|${token}," "$SKILL_MD"
|
|
94
|
+
[ "$status" -eq 0 ] || {
|
|
95
|
+
echo "missing classifier token: $token"
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
done
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@test "report-upstream: SKILL.md template-discovery cites problem-report.yml first (ADR-033 Step 3)" {
|
|
102
|
+
# Preference order: problem-report.yml / problem.yml BEFORE bug-report.yml.
|
|
103
|
+
run grep -n 'problem-report.yml' "$SKILL_MD"
|
|
104
|
+
[ "$status" -eq 0 ]
|
|
105
|
+
problem_line=$(grep -n 'problem-report.yml' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
106
|
+
bug_line=$(grep -n 'bug-report.yml' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
107
|
+
[ -n "$problem_line" ] && [ -n "$bug_line" ]
|
|
108
|
+
[ "$problem_line" -lt "$bug_line" ]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@test "report-upstream: SKILL.md retains bug/feature/question fallbacks (ADR-033 Step 3 backward compat)" {
|
|
112
|
+
# Backward-compat fallbacks must still be documented for upstreams that
|
|
113
|
+
# have not adopted problem-first templates.
|
|
114
|
+
run grep -F 'bug-report.yml' "$SKILL_MD"
|
|
115
|
+
[ "$status" -eq 0 ]
|
|
116
|
+
run grep -F 'feature-request.yml' "$SKILL_MD"
|
|
117
|
+
[ "$status" -eq 0 ]
|
|
118
|
+
run grep -F 'question.yml' "$SKILL_MD"
|
|
119
|
+
[ "$status" -eq 0 ]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@test "report-upstream: SKILL.md structured default uses problem-shaped section order (ADR-033 Step 5)" {
|
|
123
|
+
# Problem-shaped default body per ADR-033: Description -> Symptoms ->
|
|
124
|
+
# Workaround -> Affected plugin / component -> Frequency -> Environment
|
|
125
|
+
# -> Evidence -> Cross-reference. Assert section order in the primary
|
|
126
|
+
# default block.
|
|
127
|
+
desc_line=$(grep -n '^## Description$' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
128
|
+
symptoms_line=$(grep -n '^## Symptoms$' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
129
|
+
workaround_line=$(grep -n '^## Workaround$' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
130
|
+
affected_line=$(grep -nE '^## Affected plugin' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
131
|
+
freq_line=$(grep -n '^## Frequency$' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
132
|
+
env_line=$(grep -n '^## Environment$' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
133
|
+
evidence_line=$(grep -n '^## Evidence$' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
134
|
+
xref_line=$(grep -n '^## Cross-reference$' "$SKILL_MD" | head -1 | cut -d: -f1)
|
|
135
|
+
|
|
136
|
+
[ -n "$desc_line" ] || { echo "missing ## Description"; return 1; }
|
|
137
|
+
[ -n "$symptoms_line" ] || { echo "missing ## Symptoms"; return 1; }
|
|
138
|
+
[ -n "$workaround_line" ] || { echo "missing ## Workaround"; return 1; }
|
|
139
|
+
[ -n "$affected_line" ] || { echo "missing ## Affected plugin / component"; return 1; }
|
|
140
|
+
[ -n "$freq_line" ] || { echo "missing ## Frequency"; return 1; }
|
|
141
|
+
[ -n "$env_line" ] || { echo "missing ## Environment"; return 1; }
|
|
142
|
+
[ -n "$evidence_line" ] || { echo "missing ## Evidence"; return 1; }
|
|
143
|
+
[ -n "$xref_line" ] || { echo "missing ## Cross-reference"; return 1; }
|
|
144
|
+
|
|
145
|
+
[ "$desc_line" -lt "$symptoms_line" ]
|
|
146
|
+
[ "$symptoms_line" -lt "$workaround_line" ]
|
|
147
|
+
[ "$workaround_line" -lt "$affected_line" ]
|
|
148
|
+
[ "$affected_line" -lt "$freq_line" ]
|
|
149
|
+
[ "$freq_line" -lt "$env_line" ]
|
|
150
|
+
[ "$env_line" -lt "$evidence_line" ]
|
|
151
|
+
[ "$evidence_line" -lt "$xref_line" ]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@test "report-upstream: SKILL.md cites ADR-033 as authority for Steps 3 + 5 (ADR-033 Confirmation)" {
|
|
155
|
+
# ADR-033 must be cited near the Step 3 / Step 5 headings, not only in the
|
|
156
|
+
# References section, so future maintainers see the authority inline.
|
|
157
|
+
run grep -niE 'adr-033|033.*problem-first' "$SKILL_MD"
|
|
158
|
+
[ "$status" -eq 0 ]
|
|
159
|
+
[ "${#lines[@]}" -ge 2 ]
|
|
160
|
+
}
|
|
@@ -49,6 +49,18 @@ For each `docs/problems/*.open.md` and `docs/problems/*.known-error.md` file (sk
|
|
|
49
49
|
- Re-stage explicitly per the P057 staging trap: `git add <new-path>` after the Edit.
|
|
50
50
|
- This happens automatically — do not ask the user. The transition's fix-strategy is documented; only the shipping is outstanding.
|
|
51
51
|
|
|
52
|
+
### 2.5. Dependency-graph traversal — propagate transitive effort (P076)
|
|
53
|
+
|
|
54
|
+
After Step 2 assigns each ticket its **marginal** effort, run a second pass that walks the `## Dependencies` graph and propagates effort up per the transitive-dependency rule defined in `/wr-itil:manage-problem`'s WSJF Prioritisation section (the canonical location). This is a deterministic re-rate — no `AskUserQuestion` required.
|
|
55
|
+
|
|
56
|
+
1. **Build the graph**: for each `.open.md` / `.known-error.md` ticket, parse the `## Dependencies` section. Record `**Blocked by**` edges (bare IDs) into an adjacency map. Ignore `**Composes with**` (does not propagate) and `**Blocks**` (derivable from inverse).
|
|
57
|
+
2. **Classify upstream status**: upstreams in `.closed.md`, `.verifying.md`, or `.parked.md` contribute **0** to the closure (architect carve-out per P076). Upstreams in `.open.md` or `.known-error.md` contribute their own transitive effort.
|
|
58
|
+
3. **Topologically sort** and compute `Effort_transitive = max(marginal, max{ upstream transitive })`. Cycle-bundle members all receive the bundle's effort = `max{ marginal | members }`.
|
|
59
|
+
4. **Update Effort and WSJF lines** when the transitive effort differs from the marginal. Add a `<!-- transitive: <bucket> via <UPSTREAM> -->` HTML comment on the Effort line so the next review can distinguish a manually-set marginal from a propagated transitive.
|
|
60
|
+
5. **Report each re-rate** in the review summary using the concrete format `P<NNN>: Effort <OLD> → <NEW> (transitive via <UPSTREAM>)`, e.g. `P073: Effort S → XL (transitive via P038)`. Cycle bundles surface a shared line: `Bundle [P038, P064]: effort XL (cycle), WSJF 3.0 (shared)`.
|
|
61
|
+
|
|
62
|
+
Re-read the WSJF Prioritisation → "Transitive dependencies (P076)" subsection in `packages/itil/skills/manage-problem/SKILL.md` if unsure — that is the canonical rule definition.
|
|
63
|
+
|
|
52
64
|
### 3. Present the refreshed ranking
|
|
53
65
|
|
|
54
66
|
After re-scoring, present three sections matching the README.md format (same rendering used by `/wr-itil:list-problems` and by the README cache — Step 5 writes the same layout):
|
|
@@ -175,3 +175,33 @@ setup() {
|
|
|
175
175
|
run grep -inE "If arguments start with|If arguments contain" "$SKILL_FILE"
|
|
176
176
|
[ "$status" -ne 0 ]
|
|
177
177
|
}
|
|
178
|
+
|
|
179
|
+
@test "SKILL.md has a dependency-graph-traversal pass for transitive effort (P076)" {
|
|
180
|
+
# P076: review-problems is the executor for the WSJF transitive-effort
|
|
181
|
+
# rule. Step 2 assigns marginal effort; a second pass must walk
|
|
182
|
+
# `## Dependencies` edges and propagate effort per the canonical rule
|
|
183
|
+
# in /wr-itil:manage-problem's WSJF Prioritisation section. Without
|
|
184
|
+
# this pass, the marginal-only ranking mis-ranks dependent tickets
|
|
185
|
+
# above their blockers (the exact failure P076 documents).
|
|
186
|
+
run grep -inE "transitive[[:space:]]?effort|graph traversal|propagate.*effort|dependency[[:space:]-]?graph" "$SKILL_FILE"
|
|
187
|
+
[ "$status" -eq 0 ]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@test "SKILL.md transitive-effort pass cites P076 and the canonical rule location (traceability)" {
|
|
191
|
+
# Audit trail: the Step 2.5 block must cite P076 (motivation) and
|
|
192
|
+
# point to /wr-itil:manage-problem's WSJF section as the canonical
|
|
193
|
+
# definition — duplication would fork the contract.
|
|
194
|
+
run grep -n "P076" "$SKILL_FILE"
|
|
195
|
+
[ "$status" -eq 0 ]
|
|
196
|
+
run grep -inE "manage-problem.*WSJF|WSJF.*manage-problem|canonical" "$SKILL_FILE"
|
|
197
|
+
[ "$status" -eq 0 ]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@test "SKILL.md transitive re-rate message format matches the canonical shape (P076)" {
|
|
201
|
+
# Architect correction 4: the re-rate message must be greppable and
|
|
202
|
+
# consistent across skills. Shape: `P<NNN>: Effort <OLD> → <NEW>
|
|
203
|
+
# (transitive via <UPSTREAM>)`. review-problems emits this in step 3's
|
|
204
|
+
# summary output so downstream audit tools can grep the review log.
|
|
205
|
+
run grep -inE "Effort.*→.*transitive via|transitive via.*P[0-9]" "$SKILL_FILE"
|
|
206
|
+
[ "$status" -eq 0 ]
|
|
207
|
+
}
|