| name | wr-itil:manage-problem |
| description | Create, update, or transition a problem ticket using an ITIL-aligned problem management workflow with WSJF prioritisation. Supports creating new problems, updating root cause analysis, transitioning status, and closing problems. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill |
| deprecated-arguments | true |
Problem Management Skill
Create, update, or transition problem tickets following an ITIL-aligned problem management process. This skill is the authoritative definition of the problem management workflow — no separate process document is needed.
Output Formatting
When referencing problem IDs, ADR IDs, or JTBD IDs in prose output, always include the human-readable title on first mention. Use the format P029 (Edit gate overhead for governance docs), not bare P029. Tables with separate ID and Title columns are fine as-is.
Brief-before-ID discipline at AskUserQuestion surfaces (P350). Title-on-first-mention is the minimum for prose output. For AskUserQuestion question/option/description text the rule is stricter: brief the artefact's purpose and substance BEFORE naming it by ID. P-NNN / ADR-NNN / JTBD-NNN / RFC-NNN references are audit-trail annotations, NEVER carriers of meaning — the user reads the prompt without project filesystem access (mobile clients, accessibility tooling, notification surfaces) and cannot follow links. Every option's substance MUST be self-contained in the briefing prose + the option label and description; IDs may appear ONLY after a self-contained explanation. Mirrors the canonical /wr-architect:create-adr Step 5 § 5a Rule 3 ("No IDs as explainers"). Applies to Step 4b's fix-strategy and verification dispatches and any other AskUserQuestion site emitted by this skill. See also session memory feedback_brief_before_id.md.
First-run intake-scaffold pointer (P065 / ADR-036)
This skill is one of the two host skills wired to surface the /wr-itil:scaffold-intake skill on first invocation in a project that has not yet adopted the OSS intake surface. The contract is documented in ADR-036 (Scaffold downstream OSS intake — skill + layered triggers).
Preamble check (run before Step 0 of any operation):
- Look for the four intake paths:
.github/ISSUE_TEMPLATE/config.yml, .github/ISSUE_TEMPLATE/problem-report.yml, SECURITY.md, SUPPORT.md, CONTRIBUTING.md.
- Look for
.claude/.intake-scaffold-declined (explicit decline marker — never re-prompt).
- Look for
.claude/.intake-scaffold-done (done marker — already scaffolded).
If any intake file is missing AND both markers are absent, surface the scaffold-intake skill:
| Mode | Behaviour |
|---|
| Foreground (interactive) | Fire one-shot AskUserQuestion per ADR-013 Rule 1: header "Scaffold OSS intake?", three options — Scaffold now (delegate to /wr-itil:scaffold-intake), Not now (ask again next session) (no marker; re-prompt next time), Decline (never prompt in this project) (write .claude/.intake-scaffold-declined). |
| AFK orchestrator (Rule 6 fail-safe) | Do not fire AskUserQuestion. Append a one-line "pending intake scaffold" note to the iteration's ITERATION_SUMMARY notes field. Do not auto-scaffold — JTBD-006 forbids the agent from making this judgement call. The user catches up on next interactive session. |
The preamble check is a one-shot; the .intake-scaffold-done and .intake-scaffold-declined markers (ADR-009 persistent-marker semantics) suppress re-prompts in subsequent sessions without TTL expiry.
Operations
- Create:
problem <title or description> — creates a new open problem
- Update:
problem <NNN> <update details> — updates an existing problem (add root cause, evidence, fix strategy)
- Transition:
problem <NNN> known-error — moves to known-error when root cause is confirmed
- List:
problem list — shows all open problems sorted by priority
- Work:
problem work — runs a review first, then begins working the highest-WSJF problem
- Review:
problem review — re-assess all open problems: update priorities per RISK-POLICY.md, estimate effort, calculate WSJF, and update files
Closing problems: Problems are closed ONLY after the user verifies the fix in production — not when the fix is committed or released. The workflow (per ADR-022):
- When the fix is released:
git mv the file from .known-error.md to .verifying.md, update the Status field to "Verification Pending", AND add a ## Fix Released section (e.g., Deployed in v0.26.X. Awaiting user verification.). All three edits land in the same commit per ADR-014.
- When the user explicitly confirms ("it's fixed", "verified", "working"):
git mv from .verifying.md to .closed.md, update the Status field to "Closed", and reference the problem in the commit message (e.g., "Closes P008").
- Never assume the fix works — always wait for explicit user confirmation before closing.
The .verifying.md suffix distinguishes "fix released, awaiting user verification" from "root cause identified AND workaround documented; fix not yet proposed" (the Known Error meaning per ADR-022 corrected semantics, 2026-06-08 amendment; the fix proposal happens AFTER Known Error and produces the RFC per ADR-072). See ADR-022 for rationale.
Problem Lifecycle
| Status | File suffix | Meaning | Entry criteria |
|---|
| Open | .open.md | Reported, under investigation | New problem identified |
| Known Error | .known-error.md | Root cause identified AND workaround documented; fix not yet proposed (fix proposal produces the RFC per ADR-072) | Root cause documented, reproduction test exists, workaround in place |
| Verification Pending | .verifying.md | Fix released, awaiting user verification (ADR-022) | Fix shipped; ## Fix Released section written; user action remaining |
| Parked | .parked.md | Blocked on upstream or suspended by user decision | Upstream blocker identified, or user explicitly suspends; reason and un-park trigger documented |
| Closed | .closed.md | Fix verified in production OR ticket determined no longer relevant via evidence | (a) User explicitly confirms the released fix works (canonical Verifying → Closed path), OR (b) auto-closed by /wr-itil:review-problems Step 4.6 relevance-close pass per ADR-079 Phase 1 + Phase 2 evidence shapes — file-no-longer-exists / ADR-shipped-confirmed / named-skill-or-feature-exists / self-marker-in-body / driver-child-ticket-closed (cumulative; multi-shape matches emit comma-joined) with ## Closed as no longer relevant audit section per ADR-026 grounding (extends ADR-022 lifecycle: Open|Known Error → Closed bypasses Verifying when no fix was released). Partial-scope umbrellas emit CLOSE-CANDIDATE-WITH-CAVEAT and ride the maintainer's AskUserQuestion surface-batch-confirm path. |
Parked problems are excluded from WSJF ranking and work selection. They are listed separately in review output so users can see them without them polluting the backlog. To park a problem:
- If the park reason is
upstream-blocked, run the external-root-cause detection block at Step 7 first (see "External-root-cause detection (P063)"). Park without recording the upstream dependency in ## Related would be the canonical audit-trail gap this block closes.
git mv docs/problems/<current>/<NNN>-<title>.md docs/problems/parked/<NNN>-<title>.md
- Update the Status field to "Parked"
- Add a
## Parked section with: reason for parking, expected trigger to un-park, date parked
To un-park: git mv back to docs/problems/open/<NNN>-<title>.md (or docs/problems/known-error/<NNN>-<title>.md if root cause is confirmed), update Status, remove ## Parked section.
Verification Pending problems are also excluded from WSJF ranking — their remaining work is user-side verification, not dev effort. They appear in a dedicated "Verification Queue" section in review output so the user can see what's waiting on them without mixing with dev-work ranking. See step 9c for the queue layout.
Allowed optional appendages: a problem ticket file may carry a ## Reported Upstream section appended after the standard sections. This is written by the /wr-itil:report-upstream skill (per ADR-024 Confirmation criterion 3a) and records the upstream issue or advisory URL, the matched template, and the disclosure path. The presence or absence of this section does not affect WSJF ranking or status transitions.
Test-driven resolution: When root cause is identified, create a failing test that reproduces the problem. Skip/disable the test if a feature-disabling workaround is applied. Re-enable the test when the permanent fix is implemented — the test passing confirms resolution.
WSJF Prioritisation
Problems are ranked using Weighted Shortest Job First (WSJF):
WSJF = (Severity × Status Multiplier) / Effort
Severity = Impact × Likelihood (1-25) from RISK-POLICY.md. Read the impact levels, likelihood levels, and risk matrix from the policy — do not hardcode them here.
Status Multiplier (known-errors have identified root cause AND documented workaround — ready for fix proposal; higher value per unit of work because the diagnostic uncertainty is resolved):
| Status | Multiplier |
|---|
| Known Error | 2.0 |
| Open | 1.0 |
| Verification Pending | 0 (excluded) |
| Parked | 0 (excluded) |
Verification Pending and Parked tickets are excluded from the main dev-work ranking per ADR-022 (verification) and the Parked policy above. Verification Pending remaining work is user-side confirmation, not dev effort, so mixing it into the dev-work queue would distort WSJF. Both are surfaced in dedicated sections (see step 9c) — not in the ranked table.
Effort (estimated fix size — smaller effort = higher priority):
| Effort | Divisor | Description |
|---|
| S | 1 | < 1 hour, single file, quick fix |
| M | 2 | 1-4 hours, few files, moderate change |
| L | 4 | 4 hours – 1 day, multiple files, significant change within a single plugin |
| XL | 8 | > 1 day, multi-day or cross-package work (multiple plugins, migration, new ADR required) |
Example: A Known Error with severity 8 (Impact 4 × Likelihood 2) and Small effort:
WSJF = (8 × 2.0) / 1 = 16.0 — do this first.
An Open problem with severity 6 (Impact 3 × Likelihood 2) and Large effort:
WSJF = (6 × 1.0) / 4 = 1.5 — lower priority despite medium severity.
An Open problem with severity 8 (Impact 2 × Likelihood 4) and Extra-Large effort (multi-day, cross-package):
WSJF = (8 × 1.0) / 8 = 1.0 — defer until severity climbs or scope shrinks.
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).
Transitive dependencies (P076)
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).
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.
Rule:
Effort(T)_transitive = max(
Effort(T)_marginal,
max{ Effort(U)_transitive | U ∈ Blocked_by(T) }
)
WSJF(T) = (Severity(T) × StatusMultiplier(T)) / Effort(T)_transitive
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.
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.
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.
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).
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.
Worked example: P073 has marginal effort S (one surface-row add). P073 is blocked by P038 (XL). Then:
Effort(P073)_transitive = max(S=1, Effort(P038)_transitive) = max(1, 8) = 8
WSJF(P073) = (Severity(P073) × 1.0) / 8 = 12 / 8 = 1.5
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.
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.
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.
Working a Problem
What "work" means depends on the problem's status:
Open problem (no confirmed root cause):
- Read the problem description and any preliminary hypotheses
- Investigate the root cause — read relevant source code, run experiments, query prod data. Do NOT guess.
- Document findings in the Root Cause Analysis section with evidence
- Create a failing reproduction test (can be skipped/disabled)
- Identify a workaround (even "delete and re-enter" counts)
- Update the problem file with all findings
- Transition to Known Error immediately — once root cause and workaround are documented,
git mv the file to .known-error.md and update the Status field. Do not wait for a separate review.
- If the fix is small enough, continue straight to implementing it (becoming a Known Error → Closed flow in one session)
Known Error (root cause identified AND workaround documented; ready for fix proposal):
Substance-confirm-before-build guard (ADR-074 — propose-fix surface, ADR-060 I13). BEFORE implementing any fix step below, check whether the fix builds on a genuine decision whose substance is unconfirmed. This closes P315 (dependent work built on a born-proposed decision the user later rejects):
- Collect the decisions this fix builds on: the
ADR-NNN references in the problem's ## Fix Strategy section PLUS the adrs: frontmatter array (and body ADR-NNN mentions) of each referenced RFC.
- For each, run the predicate (PATH shim per ADR-049 — adopter-safe, never source repo-relative lib files):
wr-architect-is-decision-unconfirmed ADR-<NNN> docs/decisions
Exit 0 = unconfirmed (born without human-oversight: confirmed, not superseded) → the guard fires for that ADR. Exit 1 = confirmed or superseded → OK to build. Exit 2 = not found → treat as not-a-blocker (surface in the report).
- If any referenced decision is unconfirmed: do NOT implement yet. Surface its substance (the chosen option the ADR records — not a grain/meta question) for human confirmation:
- Interactive:
AskUserQuestion presenting the ADR's Decision Outcome + Considered Options so the user confirms / amends / rejects the substantive choice. On confirm, the recording skill writes the human-oversight: confirmed marker (ADR-066) before the build proceeds.
- AFK (
/wr-itil:work-problems orchestrator): NEVER ask mid-loop — queue the substance to the iteration's outstanding_questions (ADR-044 AFK carve-out) and skip the build; do not guess. This is the queue-and-continue universal default per ADR-013 Rule 6 (P352, 2026-06-06 amendment): the iter queues the substance + advances; the orchestrator main turn surfaces the queued question at loop end via the Step 2.5 batched AskUserQuestion.
- This ask is ADR-044 category-1 direction-setting and is EXCLUDED from the lazy-AskUserQuestion regression metric (it is legitimate, not lazy). The trigger is narrow — detection is mechanical (the predicate); only genuine unconfirmed decisions about to be built on fire it. Do NOT over-fire on confirmed/superseded/obvious decisions (inverse-P078 / P132 guard). A born-
proposed marker is fine for recording; it is not a licence to build (ADR-066 carve-out).
I13 propose-fix RFC-trace gate (ADR-072 placement / ADR-073 auto-create — RFC-005 B3/B4). BEFORE the traversal below, enforce the fix-time RFC-trace invariant: a fix proposed on a Known Error requires an RFC tracing the problem (ADR-071 unconditional; ADR-072 places the gate here, conforming to ADR-022 Known Error semantics — the fix is proposed after Known Error and produces the RFC). Run the load-bearing predicate (ADR-049 $PATH shim — never repo-relative from a SKILL):
wr-itil-check-fix-rfc-trace <problem-file>
- Empty stdout (an RFC already traces the problem): proceed to the traversal below.
- Non-empty stdout (directive
no-rfc-trace: P<NNN> …): the predicate has confirmed only that no RFC's problems: array names this PID — it has NOT decided that no fix vehicle exists. Distinguish two sub-cases before acting (P371; ADR-073 auto-create is intended ONLY when no fix vehicle exists, NOT when an existing vehicle merely lacks the trace edge — architect-confirmed):
- (a) Existing-vehicle-untraced — an RFC is already this ticket's fix vehicle but just hasn't wired the trace edge. Read the ticket's
## Fix Strategy / ## Resolution / ## Dependencies / ## Related sections for an RFC cited as the fix vehicle — i.e. the fix IS that RFC's task set (the recurring shape: a rework / follow-on Known Error whose fix is an existing RFC's B-tasks, so that RFC's problems: array names the original driver problem, not this ticket). This is a judgement read of the citation context, NOT a blind "any cited RFC" match: an RFC named only as context / composes with / **Related** background is NOT a fix vehicle — wiring its trace edge would pollute its problems: array. If a genuine existing fix vehicle is found: wire this problem's trace edge into that RFC's frontmatter problems: array (Edit the problems: [...] line to include P<NNN> + a short inline **Problems**: body annotation noting the edge was wired because the vehicle already exists), then run wr-itil-update-problem-rfcs-section <problem-file> so the ticket's derived ## RFCs section reflects the wired trace. Re-run the predicate (now empty) and proceed. Do NOT auto-create — minting a second RFC here produces a redundant duplicate that fragments the fix across two trace vehicles (the exact P371 defect). Structured-log the wire event (e.g. I13: wired P<NNN> trace edge into existing fix-vehicle RFC-<NNN>) to the iter summary notes for the JTBD-006 audit trail.
- (b) No-vehicle — no existing RFC is this ticket's fix vehicle. auto-create AND fully author a problem-traced RFC — do NOT block, do NOT skip, do NOT ask (ADR-073 auto-create-everywhere; the create AND its full scope authoring are framework-mediated, not cat-1 direction-setting — the scope is derived from the already-traced problem under ADR-071's pinned direction, not new direction invented; so NO
AskUserQuestion consent gate fires here — P132 / inverse-P078; ADR-073 § ADR-044 reclassification, P399). Delegate to /wr-itil:capture-rfc --fix-time with a problem trace derived from this Known Error (P<NNN> + a fix-scoped title from the problem's ## Fix Strategy / root cause), which is the canonical ADR-070-compliant vehicle (allocates the next RFC ID, refreshes the RFC README, emits no Considered-Options block — no create logic is duplicated). The --fix-time flag makes capture-rfc author a populated ## Scope (the fix being proposed + implementation approach) and a real ## Tasks decomposition from the problem's RCA + Fix Strategy — NOT an empty skeleton placeholder (ADR-073 P399: the deferred "flesh out later" step never self-fires, P375, so author it now while the fix context is in hand). Then re-run the predicate (now empty) and proceed. The authored RFC is the fix-proposal artifact, born human-oversight: unconfirmed (ratified at manage-rfc accepted). Structured-log the auto-create event to the iter summary notes (the ADR-073 reassessment criterion — auto-authored-RFC-mis-scoped signal feeds /wr-retrospective:run-retro).
The predicate is the load-bearing detection half (committed shell + behavioural bats per ADR-052: packages/itil/scripts/test/check-fix-rfc-trace.bats); the create half is orchestrated through capture-rfc (the ADR-060 I1 load-bearing-from-the-start standard — deterministic detection in committed shell, create through the canonical capture surface). This gate fires at every fix-time surface; the AFK /wr-itil:work-problems orchestrator dispatches its fix work through this same manage-problem traversal, so the gate covers the AFK surface transitively (ADR-073 "everywhere").
The Phase 2 working-the-problem traversal makes "implement the fix" concretely traceable via stories (per ADR-060 lines 300-320). Replaces the prior vague "implement the fix following the project's development workflow" with a deterministic problem → RFC → story dispatch:
- Read the problem's
## Fix Strategy section — extract referenced RFC IDs (anchor links / inline references like RFC-NNN). The I13 gate above has already guaranteed an RFC traces the problem; if the ## Fix Strategy prose itself references none (a freshly auto-created RFC may not yet be cited inline), fall through to the legacy direct-implementation path (step 6 below) using the traced/auto-created RFC.
- For each referenced RFC (in the order they appear in the Fix Strategy section), read its frontmatter
stories: array (per ADR-060 line 259, the array is ORDERED — array position IS execution sequence):
- Non-empty
stories: array (story-decomposed RFC): pick the first story whose lifecycle status is accepted or in-progress — skip done stories that already shipped, skip draft stories that aren't ready (the manage-story <NNN> accepted gate enforces INVEST shape; a draft story is structurally unready). Continue to step 3.
- Empty
stories: [] (a legacy pre-ADR-089 RFC, or one not yet decomposed): per ADR-089 every RFC has ≥1 story — an empty stories: is a back-fill state, NOT a legitimate atomic shape. Back-fill the fix's story onto the RFC's story map (add ≥1 story, transition it accepted via manage-story), then re-traverse from step 2. The empty-stories atomic fallback is removed — do NOT close the problem on a story-less RFC.
- Read the picked story's body —
## User value statement (INVEST Valuable), ## Acceptance criteria (INVEST Testable observable behaviours), ## Implementation notes (architecture sketches, library decisions). The story's frontmatter estimated-effort field (set at manage-story accepted transition per I10 INVEST Estimable) sets the appetite for the iteration.
- Implement the story scope — follow the project's standard development workflow (plan if needed, architect/JTBD review, behavioural tests per ADR-052, single-commit grain per ADR-014). Confine the implementation to the picked story's acceptance criteria; deviating into adjacent unscoped work is a scope-expansion signal — surface it via the
## Scope expansion AskUserQuestion below.
- Commit with the
Refs: STORY-<NNN> trailer (single-trailer vocabulary per ADR-060 line 307 + amendment 2026-05-10 nitpick N2 — same trailer verb whether the commit is the story's first implementation commit or a continuation). On the FIRST commit AFTER the capture commit (subject prefix discriminates: feat(itil): capture STORY-NNN ... is the capture; any other subject prefix is an implementation commit), /wr-itil:manage-story auto-transitions the story draft → in-progress. As acceptance criteria checkboxes are ticked across multiple commits, the same trailer continues to attribute the work.
- Story
done auto-transition: when ALL acceptance-criteria checkboxes in the story body are ticked AND the linked RFC reaches closed, /wr-itil:manage-story auto-transitions the story in-progress → done. (When a story's RFC is still in-progress but the acceptance criteria are all ticked, the story stays at in-progress until the RFC closes — this preserves the trace coupling per ADR-060 line 309.)
- Pick the next not-done story from the RFC's
stories: array. Repeat from step 3.
- When all stories under all referenced RFCs are done — the problem is fix-released. Include the problem doc closure in the final commit (
git mv to .verifying.md, update Status) per ADR-022. Push, create changeset, release per the lean release principle.
Legacy empty-stories back-fill (per ADR-089): a pre-ADR-089 RFC whose stories: is empty is a back-fill case, not an atomic fallback — ADR-089 requires ≥1 story, so the empty-stories atomic dispatch is removed. Decompose the fix into ≥1 story on the RFC's story map (add the story, transition it accepted), then traverse it via the normal story path above. The Refs: RFC-<NNN> trailer remains valid for cross-cutting RFC work with no single story (e.g. an RFC-level enforcement change spanning several files); it is no longer an atomic-empty-stories fallback. Legacy on-disk RFCs still carrying stories: [] are surfaced for back-fill by wr-itil-check-rfc-has-stories at their next manage-rfc accepted transition.
Legacy direct-implementation path (step 1 no-RFCs case): a Phase 1-shape Known Error whose Fix Strategy references no RFCs continues to work via the pre-Phase-2 flow — read the root cause analysis and fix strategy, implement the fix following the project's development workflow, include the problem doc closure in the fix commit (git mv to .verifying.md, update Status), push + changeset + release. This preserves backwards compatibility with all existing Known Error problems (which were captured before the RFC framework was Phase-1-graduated).
Scope expansion during work: If investigation or architect review reveals that the problem's scope has grown significantly (e.g., effort re-sized from S to L, additional files discovered), use AskUserQuestion before continuing:
- Option 1:
Continue with expanded scope — keep working this problem at its new size
- Option 2:
Update problem and re-rank — save findings to the problem file, re-score WSJF, and re-run the work selection to let the user pick from the updated queue
- Option 3:
Pick a different problem — park this one and work something else
- Use
header: "Scope change" and multiSelect: false
In both cases: After completing work on one problem, run problem work again to pick up the next highest-WSJF problem. Keep going until the user says stop or no more problems are actionable.
Steps
0a. Auto-migrate adopter layout (P170 / RFC-002 / ADR-031)
Before the README-reconciliation preflight (Step 0) and any other layout-dependent logic, source the shared shell migration routine and call the idempotent entrypoint:
wr-itil-migrate-problems-layout "$PWD"
wr-itil-migrate-problems-layout is the ADR-049 $PATH shim (adopter-safe — resolves the canonical lib/migrate-problems-layout.sh relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline source packages/itil/lib/migrate-problems-layout.sh; migrate_problems_to_per_state_layout "$PWD". NEVER source packages/... repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
The routine is idempotent and partial-migration-safe. It no-ops when no flat-layout files (docs/problems/*.<state>.md at the top level of docs/problems/) are detected — the common case in this monorepo (post-Slice-5 T5a 2026-05-10) and in freshly-migrated adopter repos.
On a flat-layout adopter repo (first invocation post-update — JTBD-101 plugin-developer auto-migration path), the routine:
- Creates the five state subdirectories (
docs/problems/open/, /known-error/, /verifying/, /parked/, /closed/).
- Runs
git mv docs/problems/<NNN>-<slug>.<state>.md docs/problems/<state>/<NNN>-<slug>.md for every existing ticket. nullglob is enabled so partial-migration tails don't trip on literal-glob expansion.
- Emits a standalone commit (per ADR-031 § Backward Compatibility line 124 "not folded into other work — so adopters can audit / revert in isolation") with subject
docs(problems): auto-migrate to per-state subdirectory layout (ADR-031) and footer trailer RISK_BYPASS: adr-031-migration (recognised by the commit-gate hook per T11; allows the migration to skip the full risk-score overhead while preserving the audit trail).
AFK authorisation per ADR-013 Rule 6: this fires unconditionally even in AFK / non-interactive / orchestrated mode. Pure-rename + pure-mkdir + standalone-commit actions are policy-authorised under ADR-019 precedent — they are fully reversible (git revert), have no external-comms surface, no secrets, no destructive overwrite. No AskUserQuestion gate.
First-fire signal (JTBD-006 AFK transparency per T8 jtbd-review nitpick c): the routine emits a single stderr line migrate-problems-layout: relocated N tickets to per-state subdirs (ADR-031) on the migrating invocation; silent on no-op re-invocations.
After Step 0a completes (whether no-op or migration), proceed to Step 0 README reconciliation preflight. The reconcile-readme script reads the post-migration layout; the in-flow Step 5 / Step 7 README refresh paths re-render the README from the per-state subdir shape.
0. README reconciliation preflight (P118)
Before parsing the request, run the diagnose-only reconciliation check. The contract here catches cross-session drift that per-operation refresh paths (P094 refresh-on-create + P062 refresh-on-transition) cannot retroactively see — if any past session committed a ticket change without staging the README refresh, the next manage-problem invocation reads a stale README that lies about what is open / verifying / closed.
wr-itil-reconcile-readme docs/problems
The wr-itil-reconcile-readme command is a $PATH-resolved shim shipped in packages/itil/bin/ that dispatches the canonical packages/itil/scripts/reconcile-readme.sh body. ADR-049 — never invoke the canonical script via repo-relative path; the path does not resolve in adopter trees.
Exit-code routing:
- Exit 0 (clean): continue to Step 1.
- Exit 1 (drift detected): structured diff lines printed to stdout, one per drift entry (≤150 bytes per ADR-038 progressive-disclosure budget). Capture stdout to a temp file and classify the drift via the uncommitted-rename carve-out (P149) before halt-routing — see "Drift classification carve-out" immediately below.
- Exit 2 (parse error): README missing or malformed. Halt with the parse-error message; this needs investigation, not mechanical reconciliation. AFK orchestrators halt-with-report per ADR-013 Rule 6.
Drift classification carve-out (P149)
The Exit 1 halt-and-route path is correct for committed cross-session drift — a past session committed a ticket transition without staging the README refresh, and proceeding now would re-encode the drift into the post-operation refresh and propagate the lie. It is wrong for uncommitted-rename-rooted drift — when the current working tree carries a staged ticket rename (a same-session git mv that the in-flow P094 / P062 refresh at Step 5 / Step 7 will reconcile in the upcoming commit per ADR-014's single-commit grain). Halting in the latter case forces a separate /wr-itil:reconcile-readme commit, splitting one logical change across two commits and violating the ADR-014 grain.
Run the classifier on Exit 1 to distinguish the two cases:
wr-itil-reconcile-readme docs/problems > /tmp/wr-itil-drift-$$.txt
reconcile_exit=$?
if [ "$reconcile_exit" -eq 1 ]; then
wr-itil-classify-readme-drift /tmp/wr-itil-drift-$$.txt docs/problems
classify_exit=$?
rm -f /tmp/wr-itil-drift-$$.txt
fi
The wr-itil-classify-readme-drift command is a $PATH-resolved shim (ADR-049 naming grammar) dispatching packages/itil/scripts/classify-readme-drift.sh. It cross-references the drifting IDs from the script's stdout against git status --porcelain docs/problems/ filtered for staged rename (R) entries — the destination path's ticket ID is the post-rename status the in-flow refresh will reconcile.
Classifier exit-code routing:
classify_exit == 0 (INLINE_REFRESH): every drifting ID is the destination of a staged rename in the working tree. Log a one-line note ("Step 0 reconcile drift covered by N staged rename(s); deferring README refresh to in-flow Step 5 / Step 7 per P094 / P062 + ADR-014 single-commit grain") and continue to Step 1. Do NOT invoke /wr-itil:reconcile-readme — the in-flow refresh will land the README correction in the same commit as the ticket work.
classify_exit == 1 (HALT_ROUTE_RECONCILE): at least one drifting ID is NOT covered by a staged rename — committed cross-session drift OR mixed (some IDs in working tree, some committed-only). Halt this invocation with a directive to invoke /wr-itil:reconcile-readme (interactive mode) or auto-route through the same skill in non-interactive mode (per ADR-013 Rule 6, AFK orchestrator). The reconciliation must complete and commit before this manage-problem invocation proceeds. Mixed routes to halt because /wr-itil:reconcile-readme resolves both classes safely; the in-flow refresh only handles the rename'd subset.
classify_exit == 2 (parse error): classifier received empty / missing drift input — contract violation upstream. Fall back to the conservative halt-and-route path.
This is a preflight CHECK only — manage-problem does NOT itself apply edits. The edit application lives in /wr-itil:reconcile-readme's Step 4 with narrative preservation. Per architect verdict on P118 (Q3): manage-problem and work-problems Step 0 invoke the script (cheap mechanical check); transition-problem does NOT (P062 already covers transition-time refresh inside the same commit, redundant preflight there would pay the cost on every transition).
This step is a robustness layer ON TOP of P094 + P062, not a supersession of either — both per-operation contracts remain in force at Step 5 (creation refresh) and Step 7 (transition refresh).
0.5. Deferred-placeholder + README-cadence advisory (per P271)
After Step 0's README reconciliation preflight and before Step 1's request parsing, check whether the deferred-placeholder backlog has accumulated past threshold AND the docs/problems/README.md "Last reviewed" cadence has slipped. This is the interactive sibling of /wr-itil:work-problems Step 0c. At an interactive manage-problem surface the right shape per ADR-013 Rule 1 is advisory not auto-dispatch — the user is at the keyboard; the advisory is the surface; the user invokes /wr-itil:review-problems directly if they want the heavyweight re-rate pass. Auto-dispatching a heavyweight skill mid-interactive-session would break JTBD-001's "Reviews complete in under 60 seconds so they don't break flow" outcome.
Mechanism:
preflight_reason="$(wr-itil-check-deferred-placeholder-staleness "$PWD")"
The helper is the same one Step 0c (work-problems) uses — single source of truth for the two-axis trigger rule (count ≥ 3 deferred placeholders AND README age > 7 days). See /wr-itil:work-problems SKILL.md Step 0c for the canonical contract; the threshold constants live in the helper.
Routing on the helper's five-outcome enum:
preflight_reason | Action |
|---|
no-deferred-placeholders | Silent-pass. Proceed to Step 1. |
below-threshold count=<N> threshold=3 | Silent-pass. Proceed to Step 1. |
fresh-readme count=<N> age=<X>s threshold=<Y>s | Silent-pass per ADR-013 Rule 5 — the cadence is in spec. |
no-readme count=<N> | Emit an advisory note to stdout (visible to the interactive maintainer) naming the placeholder count + a directive: "<N> deferred-placeholder ticket(s) accumulated AND docs/problems/README.md is missing/malformed; run /wr-itil:review-problems to re-rate and rebuild the README." Then proceed to Step 1. |
stale-readme count=<N> age=<X>s threshold=<Y>s | Emit an advisory note naming the placeholder count + readable age: "<N> deferred-placeholder ticket(s) accumulated AND the WSJF Rankings cadence is <X> days stale (> 7-day threshold); run /wr-itil:review-problems to re-rate and refresh." Then proceed to Step 1. |
Why advisory not auto-dispatch at this surface (ADR-013 Rule 1 + JTBD-001):
- The interactive user is at the keyboard. The advisory IS the surface; auto-dispatching a heavyweight skill mid-interactive-session would force a flow break that JTBD-001 explicitly proscribes ("Reviews complete in under 60 seconds so they don't break flow").
- The advisory preserves the user's authority to pick when to absorb the re-rate cost — at the next natural break, after a release, before the next session, etc.
AskUserQuestion would also be valid here (ADR-013 Rule 1 interactive surface), but for a single optional pre-flight directive the advisory shape carries less round-trip friction than a structured question. The advisory is a directive, not a decision — the user already knows to run review-problems if they want to.
ADR-079 composition note: when the user follows the advisory and invokes /wr-itil:review-problems, that skill includes Step 4.6 relevance-close per ADR-079 — relevance-close fires as a side-effect of any review pass.
Fail-soft: any error in this step (helper missing, malformed output) MUST NOT block manage-problem — log an advisory note and proceed to Step 1.
After Step 0.5 completes (whether silent-pass or advisory emitted), proceed to Step 1.
1. Parse the request
Determine the operation from $ARGUMENTS:
- If arguments start with a number (e.g., "011") followed by a status word (
known-error, verifying, or close), delegate to /wr-itil:transition-problem via the Skill tool. See "Deprecated-argument forwarders" below.
- If arguments start with a bare number (e.g., "011" with no status word after it), this is an update flow — handled inline by the ticket-body edit steps (Step 6).
- If arguments contain "list", delegate to
/wr-itil:list-problems via the Skill tool. See "Deprecated-argument forwarders" below.
- If arguments contain "work", delegate to
/wr-itil:work-problem via the Skill tool. See "Deprecated-argument forwarders" below.
- If arguments contain "review", delegate to
/wr-itil:review-problems via the Skill tool. See "Deprecated-argument forwarders" below.
- Otherwise, this is a new problem creation
Deprecated-argument forwarders (ADR-010 amended + P071)
Per ADR-010's amended Skill Granularity section, word-argument subcommands that name distinct user intents are being split into their own named skills. During the deprecation window, this skill's Step 1 parser retains the legacy argument routes as thin-router forwarders that re-invoke the new named skill via the Skill tool AND emit a one-line systemMessage with the canonical deprecation notice so the user learns the new invocation shape.
Forwarder for list (P071 split slice 1 — new skill /wr-itil:list-problems):
When $ARGUMENTS contains the word list as a top-level argument (not inside a ticket body edit), delegate to /wr-itil:list-problems via the Skill tool and emit this systemMessage verbatim:
/wr-itil:manage-problem list is deprecated; use /wr-itil:list-problems directly. This forwarder will be removed in @windyroad/itil's next major version.
The forwarder does NOT re-implement the list logic locally — it invokes the Skill tool with wr-itil:list-problems and returns the new skill's output verbatim. Duplicating the logic would harden the deprecation window into a permanent fork.
Forwarder for review (P071 split slice 2 — new skill /wr-itil:review-problems):
When $ARGUMENTS contains the word review as a top-level argument (not inside a ticket body edit), delegate to /wr-itil:review-problems via the Skill tool and emit this systemMessage verbatim:
/wr-itil:manage-problem review is deprecated; use /wr-itil:review-problems directly. This forwarder will be removed in @windyroad/itil's next major version.
The forwarder does NOT re-implement the review logic locally — it invokes the Skill tool with wr-itil:review-problems and returns the new skill's output verbatim. Duplicating the Step 9 re-scoring / auto-transition / verification-prompt / README-refresh stack would harden the deprecation window into a permanent fork.
Forwarder for work (P071 split slice 3 — new skill /wr-itil:work-problem, singular):
When $ARGUMENTS contains the word work as a top-level argument (not inside a ticket body edit), delegate to /wr-itil:work-problem via the Skill tool and emit this systemMessage verbatim:
/wr-itil:manage-problem work is deprecated; use /wr-itil:work-problem directly. This forwarder will be removed in @windyroad/itil's next major version.
The forwarder does NOT re-implement the selection logic locally — it invokes the Skill tool with wr-itil:work-problem and returns the new skill's output verbatim. Duplicating the freshness-check / AskUserQuestion selection / delegate-to-manage-problem <NNN> stack would harden the deprecation window into a permanent fork. Note the singular/plural distinction: the forwarder targets /wr-itil:work-problem (singular, one ticket per invocation), NOT /wr-itil:work-problems (plural AFK orchestrator). The two names coexist intentionally per P071.
Forwarder for <NNN> <status> transitions (P071 split slice 4 — new skill /wr-itil:transition-problem):
When $ARGUMENTS starts with a three-digit ticket ID followed by a status word (known-error, verifying, or close), delegate to /wr-itil:transition-problem via the Skill tool and emit the canonical deprecation notice verbatim, naming the specific argument form the user supplied:
/wr-itil:manage-problem <NNN> <status> is deprecated; use /wr-itil:transition-problem <NNN> <status> directly. This forwarder will be removed in @windyroad/itil's next major version.
The parser must distinguish a bare <NNN> (update flow — handled inline by Step 6) from a <NNN> <status> pair (transition — delegated). The status-word tokens that trigger the transition forwarder are fixed: known-error, verifying, close. Any other suffix after <NNN> routes to the inline update flow per Step 6. This preserves the two legitimate shapes the original subcommand carried while splitting the transition intent out cleanly.
The forwarder does NOT re-implement the Step 7 transition logic locally — it invokes the Skill tool with wr-itil:transition-problem and returns the new skill's output verbatim. Duplicating the pre-flight-checks / P063-external-root-cause-detection / P057-staging-trap / P062-README-refresh stack would harden the deprecation window into a permanent fork. Per ADR-010 amended "Split-skill execution ownership" (P093), the forwarder is one-way — /wr-itil:transition-problem hosts its own inline Step 7 block and does NOT re-invoke /wr-itil:manage-problem. The in-skill Step 7 block below stays in place for in-skill callers (Step 9b auto-transition, the Parked path, Step 9d closure inside review); the split skill carries a scoped inline copy for the user-initiated transition path only ("copy, not move"). Lifecycle completeness (known-error + verifying + close) is covered per ADR-022's three-status mandate.
2. For new problems: Check for duplicates FIRST
Before creating, search existing problems for similar issues. The user may not know a problem already exists.
-
Extract keywords from the description/title (e.g., "foul drawn", "checkpoint", "delete", "stuck saving")
-
Search all files in docs/problems/ for those keywords using Grep
-
Read the title and status of each match
-
If matches are found, present them to the user via AskUserQuestion:
- "I found existing problems that may be related: P011 (stuck saving, CLOSED), P023 (foul drawn garbled, OPEN). Would you like to: (a) Update an existing problem, (b) Create a new problem anyway, (c) Cancel?"
-
If the user chooses to update, switch to the update flow for that problem ID
-
If no matches found, proceed to create
-
After the grep completes (whether duplicates were found or not), write the per-session create-gate marker so the PreToolUse:Write hook (packages/itil/hooks/manage-problem-enforce-create.sh, P119) allows the subsequent Write of the new .open.md file. The marker is /tmp/manage-problem-grep-${SESSION_ID}. Per P260 / ADR-050 Option C, the agent writes it under EVERY recent candidate session SID — not just one — by sourcing the discovery helpers (P124) and piping the candidate set into mark_step2_complete_candidates:
wr-itil-mark-create-gate
wr-itil-mark-create-gate is the ADR-049 $PATH shim (adopter-safe — resolves its hooks/lib siblings relative to the script, NOT cwd; P317/RFC-009) that internalises the former inline source packages/itil/hooks/lib/{session-id,create-gate}.sh + get_candidate_session_ids | mark_step2_complete_candidates. NEVER source packages/... repo-relative from a SKILL — those paths only resolve in the source monorepo, not adopter installs.
Why every candidate, not one (P260 / ADR-050 Option C): under /wr-itil:work-problems the orchestrator main turn fires PreToolUse hooks concurrently with its backgrounded iter subprocess (Step 5). Both sessions write the same per-machine runtime-sid marker (last-writer-wins), so the single-SID get_current_session_id can return the subprocess SID while the orchestrator's Write carries the orchestrator SID on its stdin — marker mismatch, create-gate deny. No agent-side algorithm can predict the right single SID from filesystem state alone (ADR-050 §Context). The command instead enumerates EVERY candidate SID the hook might read — the get_current_session_id pick (env-var > runtime-sid > announce-marker priority, P124) PLUS every recent /tmp/<system>-announced-<UUID> UUID within a 24h mtime window (ADR-038 announce markers, set on prompt 1 of every session by architect / jtbd / tdd / style-guide / voice-tone / itil-assistant-gate / itil-correction-detect hooks) — and writes the marker under each. Whichever SID the hook reads from the Write's stdin, a matching marker provably exists. The candidate set is bounded to recent same-machine announce markers + the runtime-sid value — NOT a global fail-open: the P119 audit invariant holds (every marker still records that THIS session ran the duplicate-check grep). The marker is per-session, so a single write covers all new tickets for the rest of this session, enabling Step 4b multi-concern splits and same-session unrelated-ticket creation without re-running the grep.
Why helpers instead of inline ${CLAUDE_SESSION_ID:-default}: the agent's process does NOT export CLAUDE_SESSION_ID today; the hook side reads session_id from its stdin JSON payload (per the Claude Code PreToolUse contract). The prior fallback wrote the marker under default while the hook checked the real UUID — mismatch caused the Write deny on every first ticket of a session until the agent ad-hoc scraped a UUID-bearing marker. The helpers canonicalise that scrape so every agent context discovers candidate SIDs the same way. P124.
Phase 4 (P142 / ADR-050) — the helper reads the runtime stdin session_id from a per-machine marker written by the itil-runtime-sid-marker.sh PreToolUse hook on every tool call. Because every Bash call that sources the helper is itself a PreToolUse:Bash event, the marker the helper reads was written moments earlier with the same session_id the runtime Write hook will see — so SID-mismatch denial is structurally impossible in non-concurrent flow. The Phase 3 announce-marker priority logic is preserved as cold-path fallback (first tool call of a session, before any PreToolUse fires).
Phase 5 (P260 / ADR-050 Option C) — the "structurally impossible" guarantee above holds ONLY when no second session writes the runtime-sid marker concurrently. Under /wr-itil:work-problems, the orchestrator main turn and its backgrounded subprocess write the per-machine runtime-sid marker concurrently (last-writer-wins), re-introducing the mismatch (this was P260, surfaced by the 2026-05-18 P254/P255 foreground captures). The candidate-set marker-write above is the mitigation: it does not depend on the runtime marker carrying the right SID at Write-time, because it marks under every recent candidate SID.
Search strategy: Search problem filenames AND file content. A match on the filename (kebab-case title) or the Description/Symptoms sections counts. Cast a wide net — false positives are cheap (user chooses), but false negatives mean duplicate problems.
Sub-step 2.8 — Hang-off-check via fresh-context subagent (P346 Phase 3 amendment, 2026-05-31; ADR-032 5th invocation pattern)
The wide-net grep + AskUserQuestion at sub-steps 1-6 above handles the title/keyword-overlap class. Sub-step 2.8 closes a wider gap: parent tickets where the new problem's scope belongs absorbed as an Investigation Tasks expansion / Phase N section rather than as a sibling. The wrongly-captured P347 sibling of P346 on 2026-05-31 (now closed as duplicate-of-P346) is the canonical regression — the main agent mid-iter pattern-matched the existing capture flow under session-context bias.
Sub-step 2.8 adds a mechanical pre-filter + fresh-context subagent dispatch that closes this gap without re-introducing the main agent's bias. Mirrors the /wr-itil:capture-problem Step 2 sub-step 2b dispatch verbatim.
Mechanical pre-filter — grep docs/problems/open/*.md + docs/problems/verifying/*.md BODIES for tokens shared with the new problem's description: any ADR-NNN / RFC-NNN / JTBD-NNN reference, SKILL path (/wr-<plugin>:<skill> or packages/<plugin>/skills/<skill>/), or file path (packages/..., docs/..., .github/..., bin/..., scripts/...). Collect candidates that share ≥1 signal; cap at 5; empty or >5 → skip dispatch.
JTBD-301 firewall — sub-step 2.8 fires on maintainer-internal new-problem captures ONLY. The dispatch MUST be skipped when manage-problem is ingesting a plugin-user-reported issue from .github/ISSUE_TEMPLATE/problem-report.yml (plugin-user descriptions do not carry the same authorial intent as maintainer-internal captures; a plugin-user describing their friction in maintainer vocabulary could plausibly trigger a wrong-parent HANG_OFF). When ingesting plugin-user reports, triage stays user-judgement per JTBD-301. Mirrors the existing Step 1.5 / Step 4 firewall patterns in /wr-itil:capture-problem (see line 116 of packages/itil/skills/capture-problem/SKILL.md).
AFK safe-default: when --no-prompt is propagated, the dispatch still fires (the subagent verdict is non-interactive by construction — no AskUserQuestion), and ambiguous-multi-parent cases collapse to PROCEED_NEW per the subagent's Rule 6 contract. This satisfies JTBD-006's safe-default contract.
Dispatch — delegate to wr-itil:hang-off-check via the Agent tool with the same structured payload shape capture-problem uses (SURFACE: manage-problem-step-2.8; <new-capture> payload; <candidates> payload with P<NNN> | <title> | <path> | shared-signals: ... per row). The subagent reads candidate bodies in full and emits:
-
HANG_OFF: P<NNN> with Rationale, Signals matched, Where to absorb → halt manage-problem's new-problem creation; route to the parent-ticket update flow (Step 6 ticket-body edit on the named parent). Record the hang-off decision + rationale in the parent ticket's Investigation Tasks bullet or ### Phase N — <name> section per the subagent's Where to absorb directive. Single-commit grain preserved (the parent-ticket amendment commit IS this manage-problem invocation's commit per ADR-014).
-
PROCEED_NEW with Rationale + Per-candidate explanation → continue to Step 3 (ID assignment). Append the subagent's rationale + per-candidate explanation to the new ticket's ## Related section as the audit trail per ADR-026 grounding + JTBD-201.
Why a subagent (not in-SKILL checks): the main agent is biased by session context — mid-iter, mid-work, pattern-matching existing flows ("I captured X then dispatched iter; do the same shape for Y"). A fresh subagent invocation starts clean, reads only the structured inputs, and reasons about candidate absorption without the bias. Same architectural pattern as wr-architect:agent / wr-jtbd:agent / tdd:review-test / wr-risk-scorer:pipeline — codified as ADR-032's 5th invocation pattern under the P346 amendment 2026-05-31.
Cross-references: packages/itil/agents/hang-off-check.md (the subagent); packages/itil/agents/test/fixtures/regression-p347-vs-p346.md (canonical behavioural fixture); docs/decisions/032-governance-skill-invocation-patterns.proposed.md § Foreground fresh-context-subagent-as-decision-arbiter variant; docs/rfcs/RFC-013-p346-backlog-flow-control-multi-phase.proposed.md (multi-phase trace per ADR-071); docs/problems/.../346-...md (driver master ticket).
Hook contract (P119): writing a .open.md (or any .<status>.md) file under docs/problems/ without first running this Step 2 grep + marker-touch is blocked by the manage-problem-enforce-create.sh PreToolUse hook with a permissionDecision: deny directing the agent back to this skill. Agents that try to bypass the skill (e.g. mid-retrospective inline capture, post-mortem wrap-up, or any "I'll just write it directly" shortcut) will hit the deny and be redirected here. Do not work around the deny by setting the marker manually — the marker exists to record that this Step 2 ran, and a marker without a grep is the audit-trail gap P119 closes.
3. For new problems: Assign the next ID
Compute the next ID as the max of the local and origin highest IDs, plus one, zero-padded to 3 digits. Comparing against origin/<base> is required by ADR-019 (confirmation criterion 2): without it, parallel sessions can mint the same ID for different problems and force a destructive surgical rebase on push (P040 incident).
local_max=$(ls docs/problems/*.md docs/problems/*/*.md 2>/dev/null | sed 's|.*/||' | grep -oE '^[0-9]+' | sort -n | tail -1)
origin_max=$(git ls-tree --name-only -r origin/main docs/problems/ 2>/dev/null | sed 's|.*/||' | grep -oE '^[0-9]+' | sort -n | tail -1)
next=$(printf '%03d' $(( 10#$(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
If the local choice would have collided with an origin ticket created since the last fetch, the git ls-tree origin/<base> lookup catches it here and the renumber is automatic. Log the renumber decision in the operation report (e.g. "Bumped next ID from 042 → 043 to avoid collision with origin").
4. For new problems: Gather information (P132 derive-first; ADR-044 category-4 silent-framework on derivable fields; category-1 direction-setting fallback only on Description)
Shared dispatch helper: this surface invokes packages/itil/lib/derive-first-dispatch.sh for the canonical slug derivation (Title), RISK-POLICY matrix lookup (Priority), and I2-isomorphic stderr advisory format. The helper is sourced by /wr-itil:capture-problem, /wr-itil:manage-incident, and /wr-itil:manage-problem; drift in the advisory shape re-opens P132. Surface-specific signal definitions (priority impact / likelihood regex lists) stay inline below — the helper owns the mechanism, not the per-surface signals (architect verdict 2026-05-15 P132 Phase 2a-iii-A: "Helper must preserve per-surface signal definitions; only the dispatch mechanism is shared").
Derive-first dispatch. Problem-declaration inputs carry observable evidence in the user's prose, the working tree, RISK-POLICY.md, and the wall-clock — the framework can resolve most fields without firing AskUserQuestion. Only Description is genuinely user-knowledge (without prose there is literally nothing to capture); only Description retains the AskUserQuestion gate.
The P132 inverse-P078 trap (docs/problems/known-error/132-...md) is the load-bearing motivation. The 2026-05-06 I001 declaration regression cited in P132 fired a 4-question AskUserQuestion with 3 of 4 sub-questions being lazy classifications (Title kebab-derivable, Severity matrix-derivable, Start time git-log-derivable). manage-problem Step 4 is the second declaration-skill surface under Phase 2a (after manage-incident Step 4 in commit b7cc645) to ship the derive-first dispatch. The pattern is isomorphic across /wr-itil:capture-problem Step 1.5 (P185 worked example), /wr-itil:manage-incident Step 4, and this skill.
Resolve each field via the following dispatch. The order is load-bearing — every field except Description resolves silently with a stderr advisory citing the source; Description alone fires AskUserQuestion as the genuine category-1 surface.
| Field | Dispatch | ADR-044 category |
|---|
| Title | Derive silently. Kebab-case the first 8-10 non-stopword tokens of the user's prose description (same slug derivation as /wr-itil:capture-problem Step 1.4 and /wr-itil:manage-incident Step 4). Emit stderr advisory: manage-problem: derived title='<slug>' from description; re-invoke with the desired title or rename the file if the slug is wrong. Do NOT fire AskUserQuestion. | category-4 silent-framework |
| Description | Pull verbatim from $ARGUMENTS prose into Step 5's ## Description section. Fallback: when $ARGUMENTS carries NO prose at all (only flags / status / no body), fire AskUserQuestion as the genuine category-1 direction-setting surface — "only the user knows the goals that haven't been written down yet." Question text: "What is happening? What should happen instead?" This is the ONLY user-knowledge field at Step 4. | category-1 direction-setting (fallback only; category-4 silent-framework on the typical path where prose is present) |
| Priority (Impact × Likelihood) | Derive silently when description signals map to a clear RISK-POLICY.md Impact × Likelihood cell. Cross-reference signals: (a) impact — service-disruption keywords (down / degraded / unavailable / data loss → high; latency / throughput / slow → moderate; cosmetic / typo / minor friction → low); (b) likelihood — reproducibility keywords (every invocation / reproducible / 100% → high; intermittent / flaky / sometimes → medium; one-off / single observation → low); (c) named anchors — explicit Impact: <label> / Likelihood: <label> or Priority: <score> mentions in prose take precedence. When the cross-reference produces a single clear cell, set it silently and emit stderr advisory: manage-problem: priority derived as <score> (<label>) from RISK-POLICY matrix + evidence: <evidence list>; re-invoke or update if mis-rated. Ambiguous-evidence fallback (no mappable signal, or signals point to conflicting cells): fire AskUserQuestion with the Impact (1-5) × Likelihood (1-5) options as the genuine ADR-044 category-5 (taste) fallback surface. The fallback is genuine ambiguity, NOT defaults. | category-4 silent-framework (derivable); category-5 fallback (ambiguous) |
Inferred fields (no ask, no advisory needed):
- Reported date: today's date (
date +%Y-%m-%d)
- Status: always "Open" for new problems
- Symptoms: infer from description verbatim into Step 5's
## Symptoms section
- Workaround: default to "None identified yet." unless explicit workaround prose appears in
$ARGUMENTS
Stderr advisory contract: each derived field emits a SINGLE line to stderr (NOT stdout, NOT in the ticket body) per the capture-problem Step 1.5 + manage-incident Step 4 pattern. The advisory text shape is I2-isomorphic — identical sentence structure across the three declaration-skill surfaces (<skill>: derived <field>=<value> from <source>; <reversibility-clause>) beyond substituted values + source names. Embedding the advisory in stdout would risk machine-readers parsing it as a ticket-body line; embedding it in the ticket body would violate the required-section schema. Stderr is the correct channel — visible to interactive maintainers in the terminal; invisible to ticket consumers; loggable by orchestrators that capture subprocess stderr.
ADR-026 cost-source grounding: each derived field cites its source in the advisory (description token sequence for Title; RISK-POLICY matrix cell + named evidence for Priority). The re-invoke or update if mis-rated clause carries the reversibility marker ADR-026 mandates for ungrounded outputs.
AFK fail-safe (ADR-013 Rule 6): under AFK orchestration, all derivable fields resolve without interactive input; only Description-when-absent can block. The orchestrator should halt-with-stderr citing the missing-prose case rather than guess (Description is genuinely user-judgment per JTBD-006's "Problems requiring my judgment ... are queued for my return, not guessed at"). The typical AFK manage-problem call carries prose in $ARGUMENTS (or the orchestrator's per-iter context supplies it), so the halt-on-Description path is the rare-corner-case behaviour, not the routine flow.
Cross-skill consistency note: this is the third declaration-skill surface to ship the derive-first dispatch (after /wr-itil:capture-problem Step 1.5 and /wr-itil:manage-incident Step 4 in commit b7cc645). The architect verdict 2026-05-15 P132 Phase 2a-ii flagged this triplet as the pattern-lock point — the I2-isomorphic stderr advisory format is now established across three skills before Phase 2a-iii (/wr-architect:create-adr argument-collection) extends the same pattern to a fourth.
4b. For new problems: Concern-boundary analysis (multi-concern check)
Before writing the problem file, perform a concern-boundary analysis on the gathered description to prevent conflated tickets that make WSJF scoring meaningless (P016).
Self-check: Read the description and root cause information gathered in step 4. Answer: "How many distinct root causes are present? If fixed independently, how many separate fix paths exist?"
- Single concern (one root cause, one fix path): proceed directly to step 5.
- Multiple concerns (two or more distinct root causes, different components, or if the architect review flagged this needs its own ADR): present a split prompt.
Split prompt — use AskUserQuestion:
header: "Multi-concern problem"
multiSelect: false
- Options:
Split into separate problems (Recommended) — description: "Create one problem ticket per distinct concern, with consecutive IDs. Each ticket gets its own priority, WSJF score, and fix path."
Keep as a single problem — description: "Create one ticket covering all concerns. Use this only if the concerns are so tightly coupled that they cannot be fixed independently."
Non-interactive fallback: When AskUserQuestion is unavailable (e.g., non-interactive/AFK mode), automatically split into separate problems and note the auto-split in output. Do not block creation.
ADR-013 Rule 6 carve-out audit (P352, 2026-06-06 amendment): the universal AFK default is queue-and-continue; this site is a documented AUTO-DEFAULT carve-out. Authorising principle: policy-authorised safe default per ADR-044 category 4 (silent framework). Splitting is fully reversible (manual combine via Related cross-references + WSJF re-rank), the framework's lifecycle model rewards explicit per-concern WSJF ranking, and "split when in doubt" is the persona-correct safe heuristic for JTBD-006 — the loop progresses, over-splits are cheap to combine, and halt would cost more loop throughput than the over-split risk. Symmetric with the parallel /wr-architect:create-adr Step 1 multi-decision auto-split (same authority, same rationale).
Split implementation: When splitting, assign consecutive IDs (e.g., if next ID is 035, create P035 and P036). Create each problem file independently. Cross-reference each ticket in the other's "Related" section.
Scope: This step applies only to new problem creation (steps 2–5). It does NOT apply to updates, status transitions, or reviews of existing tickets.
5. For new problems: Write the problem file
File path: docs/problems/open/<NNN>-<kebab-case-title>.md
Template:
# Problem <NNN>: <Title>
**Status**: Open
**Reported**: <YYYY-MM-DD>
**Priority**: <score> (<label>) — Impact: <label> (<n>) x Likelihood: <label> (<n>)
**Origin**: internal
## Description
<description>
## Symptoms
<bullet list of observable symptoms>
## Workaround
<workaround or "None identified yet.">
## Impact Assessment
- **Who is affected**: <personas>
- **Frequency**: <when/how often>
- **Severity**: <High/Medium/Low — reason>
- **Analytics**: <data source or N/A>
## Root Cause Analysis
### Investigation Tasks
- [ ] Investigate root cause
- [ ] Create reproduction test
- [ ] Create INVEST story for permanent fix
## Dependencies
- **Blocks**: <tickets that can't close until this one does — bare IDs, comma-separated; leave empty if none>
- **Blocked by**: <tickets that must close first — bare IDs, comma-separated; drives the transitive-effort rule; leave empty if none>
- **Composes with**: <tickets whose work overlaps but neither blocks the other — does NOT propagate effort; leave empty if none>
## Related
<links to related files, problems, ADRs>
**Origin** field (ADR-076) records where the problem came from and is an authoritative input to the ADR-076 reported-first ranking tier. Two values:
internal — internally discovered (the default for tickets created by hand or by an internal observation).
inbound-reported (#NN) — reported to us by an external user via an upstream channel, where #NN is the upstream issue/discussion number. Written by ADR-062's inbound-discovery safe-and-valid branch at ticket creation. This is the inbound direction and is distinct from the ## Reported Upstream section (which records the outbound direction — a ticket we reported up to someone else). The **Origin** field, not the regenerable .upstream-cache.json, is the rank-determining source of truth (ADR-076). When backfilling existing tickets during a review, stamp reported ones with inbound-reported (#NN); leave the rest internal.
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.
Concrete example (for P073 referencing two upstreams):
## Dependencies
- **Blocks**: (none)
- **Blocked by**: P038, P064
- **Composes with**: (none)
README.md refresh on new ticket (P094)
After writing the new .open.md file, regenerate docs/problems/README.md to insert the new ticket's row into the WSJF Rankings, and stage the refreshed README in the same commit as the new ticket. Without this refresh, new tickets are absent from the ranked table until the next /wr-itil:review-problems invocation or the next Step 7 transition — staleness accumulates silently on every creation-only session.
Mechanism: use the same rendering rules as Step 7's P062 block (glob docs/problems/*.open.md / *.known-error.md / *.verifying.md / *.parked.md; rank open/known-error by WSJF; list verifyings in the Verification Queue ordered by release age; list parkeds in the Parked section). The refresh is a render, not a re-rank — existing WSJF values on the other ticket files are trusted per P062's established discipline. Only the new ticket's own WSJF is consumed from its freshly-written file.
WSJF Rankings tier + tie-break sort (P138 + ADR-076): rows render tier-first — Tier 0 Critical-bypass (Severity Very High ≥17 OR security-classified OR incident-linked) → Tier 1 Inbound-reported (**Origin**: inbound-reported) → Tier 2 Internal — and within each tier by the multi-key (WSJF desc, Known-Error-first, Effort-divisor asc, Reported-date asc, ID asc) so the rendered top-to-bottom row order matches /wr-itil:work-problems SKILL.md Step 3's selection 1:1. The ADR-076 tier partition sets the top-level order; within a tier WSJF desc sets the band, then the next three keys are the canonical tie-break ladder (Known Error before Open; smaller effort before larger; older Reported date before newer); ID asc is the deterministic final tiebreaker for full-tie cases. The table MUST include a Reported column so the third tie-break input is visible, and an Origin column so the Tier 1 partition is visible — without them, users cannot reconcile the rendered order against the orchestrator's selection. Any future change to the tie-break ladder OR the reported-first tier MUST update this render block, the Step 7 P062 block, the Step 9e template, AND /wr-itil:review-problems SKILL.md Step 3 / Step 5 — drift here re-opens P138 / ADR-076.
Verification Queue sort direction (P150): rows in the Verification Queue table are sorted by Released date ASC (oldest at row 1; same-day releases tiebreak by ID ASC) per ADR-022 + P048 user-task semantics — older entries are the most likely-verified candidates the user wants to surface first when closing the queue. Newest-first ordering pushes those actionable closure candidates below the fold and contradicts the section header. Any future change to the VQ sort direction MUST update this render block, the Step 7 P062 block, the Step 9c presentation block, the Step 9e template, AND /wr-itil:review-problems + /wr-itil:transition-problem + /wr-itil:transition-problems + /wr-itil:reconcile-readme + /wr-itil:list-problems — drift here re-opens P150.
Likely-verified cell shape (P186): the Likely verified? column carries an evidence-first cell — yes — observed: <evidence> / no — not observed / no — observed regression. The 14-day age-based heuristic (originally introduced by P048 Candidate 4) is superseded — age is preserved separately via the Released column; the Likely verified? column is reserved for session-observed evidence (Step 4 user confirmation, in-session test invocation outcome per ADR-026 grounding, or run-retro Step 4a close-on-evidence citation). Any future change to the cell shape MUST update this render block, the Step 7 P062 block, the Step 9c presentation block, the Step 9e template, AND /wr-itil:review-problems + /wr-itil:transition-problem + /wr-itil:transition-problems + /wr-itil:reconcile-readme + /wr-itil:list-problems — drift here re-opens P186.
-
After Write-ing the new .open.md file (and, for multi-concern splits per step 4b, after all split files are written), regenerate docs/problems/README.md in-place reflecting the new filename set.
-
Update the "Last reviewed" line per the inline P134 rotation mechanism below. The mechanism is inlined here at the execution site (not deferred via cross-reference to the § Last-reviewed line discipline subsection further down this SKILL) so a single-pass agent reading this Step 5 does not silently skip the archive step. Skipping the BEFORE-rewrite archive step destroys the displaced fragment and re-opens P331 (iter-7 + iter-8 of 2026-05-30's AFK work-problems session silently skipped the rotation in 2 of 9 transition-bearing iters under exactly that failure mode). The mechanism MUST execute IN ORDER:
- Read line 3 of
docs/problems/README.md: awk 'NR==3' docs/problems/README.md (head -3 | tail -1 or sed -n '3p' are acceptable equivalents).
- Append-if-non-empty (BEFORE step 3, not after) — if line 3 is non-empty AND not a same-session same-verb near-duplicate of the new fragment, append the existing line 3 verbatim to
docs/problems/README-history.md under a ## YYYY-MM-DD heading (creating the heading on first append for that date; subsequent same-day appends nest under the existing heading). Run this BEFORE the Edit-tool rewrite in step 3 — Edit's replace pattern destroys the displaced content otherwise.
- Rewrite line 3 of
docs/problems/README.md with the new fragment of form > Last reviewed: YYYY-MM-DD **<event>** — <one-line summary> (e.g. P<NNN> opened — <one-line title>). Soft cap ≤ 1024 bytes per fragment; hard ceiling 5120 bytes per ADR-040 Tier 3 envelope.
- Stage both —
git add docs/problems/README.md docs/problems/README-history.md so the same single commit per ADR-014 captures both files.
Canonical rationale anchor: § Last-reviewed line discipline (P134) subsection below. The cross-reference is preserved for the "why"; the "what" is inlined above for execution-time legibility per P331.
-
git add docs/problems/README.md — the stage list at Step 11 must include it alongside the new .open.md file (Step 11's git add -u catch-all handles tracked-file modifications; the new README render lands via this path when README.md already exists in git, and via an explicit git add docs/problems/README.md when it is newly created). When line-3 truncation displaces prior content, also git add docs/problems/README-history.md.
For the multi-concern split path (step 4b), the refresh fires once after all split tickets are written, not per-split — a single render captures the full new set in one pass.
Last-reviewed line discipline (P134)
The "Last reviewed" line (line 3 of docs/problems/README.md) was designed as a short audit marker — one ticket name + one transition reason — but historically accumulated multi-paragraph session-summary fragments unbounded ("Prior:" stacking on every refresh). At ~62 KB / 76 KB it crossed the Read-tool 25K-token whole-file limit and could no longer be window-read at any offset/limit. P134 closes the accumulator on this surface; sibling to P099 on docs/briefing/<topic>.md.
Contract — applies to every refresh that touches line 3 (Step 5 P094 creation, Step 6 P094 conditional update, Step 7 P062 transition; mirrored in transition-problem, transition-problems, review-problems, reconcile-readme):
- Single most-recent fragment only on line 3. The "Last reviewed" parenthetical names ONE event — the operation this refresh covers. Do NOT prepend a
Prior: segment, do NOT stack multi-paragraph rationale, do NOT carry history forward inline.
- Soft cap: ≤ 1024 bytes per fragment. Authoring guidance — keep the fragment dense and audit-meaningful (ticket ID + verb + one-line summary + ADR/JTBD anchors when load-bearing). Multi-paragraph rationale belongs in retros, ticket bodies, and ADR amendments — never on line 3.