@windyroad/itil 0.25.0 → 0.26.0-preview.291
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/README.md +3 -0
- package/bin/wr-itil-reconcile-rfcs +2 -0
- package/hooks/hooks.json +6 -0
- package/hooks/itil-rfc-trailer-advisory.sh +198 -0
- package/hooks/lib/create-gate.sh +30 -0
- package/hooks/manage-problem-enforce-create.sh +89 -44
- package/hooks/test/itil-rfc-trailer-advisory.bats +273 -0
- package/hooks/test/manage-problem-enforce-create.bats +105 -1
- package/package.json +1 -1
- package/scripts/reconcile-rfcs.sh +329 -0
- package/scripts/test/reconcile-rfcs.bats +433 -0
- package/scripts/test/update-problem-rfcs-section.bats +242 -0
- package/scripts/update-problem-rfcs-section.sh +160 -0
- package/skills/capture-rfc/SKILL.md +276 -0
- package/skills/manage-rfc/SKILL.md +260 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# packages/itil/scripts/update-problem-rfcs-section.sh
|
|
3
|
+
#
|
|
4
|
+
# Idempotently refresh the auto-maintained `## RFCs` section on a
|
|
5
|
+
# problem ticket file based on which RFCs in <rfcs-dir> claim the
|
|
6
|
+
# ticket via their YAML frontmatter `problems:` list.
|
|
7
|
+
#
|
|
8
|
+
# Called inline by `/wr-itil:capture-rfc` Step 6 and `/wr-itil:manage-rfc`
|
|
9
|
+
# Step 7 (transitions) + Step 9 (review re-rank) so the cross-tier
|
|
10
|
+
# reverse-trace is current at every commit per ADR-014 single-commit
|
|
11
|
+
# grain. Closes ADR-060 Phase 1 item 10 + Confirmation criterion 3
|
|
12
|
+
# (skill-side primary surface; architect Q1 + Q3 verdicts).
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# update-problem-rfcs-section.sh <problem-file> [<rfcs-dir>]
|
|
16
|
+
#
|
|
17
|
+
# Default <rfcs-dir> is `docs/rfcs`.
|
|
18
|
+
#
|
|
19
|
+
# Lazy-empty discipline (per JTBD-101 atomic-fix-adopter friction
|
|
20
|
+
# guard + architect Q3 verdict): if zero RFCs trace this problem, the
|
|
21
|
+
# `## RFCs` section is REMOVED entirely. Atomic-fix tickets with no
|
|
22
|
+
# RFC trace stay free of empty-table noise.
|
|
23
|
+
#
|
|
24
|
+
# Idempotent: running over a current section is a no-op (no file diff).
|
|
25
|
+
#
|
|
26
|
+
# Section placement (per architect Q3 verdict):
|
|
27
|
+
# - Before `## Fix Released` if present (closure section stays at tail
|
|
28
|
+
# per ADR-022 trailing-audit-artefact convention).
|
|
29
|
+
# - Else at EOF.
|
|
30
|
+
#
|
|
31
|
+
# Output:
|
|
32
|
+
# - Rewrites the problem ticket in-place when the section needs an
|
|
33
|
+
# update; otherwise leaves the file untouched.
|
|
34
|
+
# - Emits exit 0 always (caller skills don't need defensive handling).
|
|
35
|
+
#
|
|
36
|
+
# @adr ADR-060 (Phase 1 item 10 + Confirmation criterion 3 — auto-
|
|
37
|
+
# maintained reverse trace; architect Q3 — table format, lazy empty,
|
|
38
|
+
# between `## Related` and `## Fix Released`)
|
|
39
|
+
# @adr ADR-014 (called by capture-rfc Step 6 + manage-rfc Step 7+9
|
|
40
|
+
# to ride the same single-purpose commit)
|
|
41
|
+
# @adr ADR-022 (`## Fix Released` is the trailing closure section;
|
|
42
|
+
# `## RFCs` precedes it)
|
|
43
|
+
# @adr ADR-052 (behavioural bats coverage in
|
|
44
|
+
# packages/itil/scripts/test/update-problem-rfcs-section.bats)
|
|
45
|
+
# @problem P170
|
|
46
|
+
|
|
47
|
+
set -uo pipefail
|
|
48
|
+
|
|
49
|
+
PROBLEM_FILE="${1:?missing problem-file arg}"
|
|
50
|
+
RFCS_DIR="${2:-docs/rfcs}"
|
|
51
|
+
|
|
52
|
+
[ -f "$PROBLEM_FILE" ] || exit 0
|
|
53
|
+
|
|
54
|
+
PBASE="$(basename "$PROBLEM_FILE")"
|
|
55
|
+
PNUM="${PBASE%%-*}"
|
|
56
|
+
PID="P${PNUM}"
|
|
57
|
+
|
|
58
|
+
# ── Step 1: scan rfcs-dir for RFCs whose frontmatter claims PID ─────────────
|
|
59
|
+
|
|
60
|
+
ROWS_TMP=$(mktemp)
|
|
61
|
+
trap 'rm -f "$ROWS_TMP"' EXIT
|
|
62
|
+
|
|
63
|
+
shopt -s nullglob
|
|
64
|
+
for f in "$RFCS_DIR"/RFC-[0-9][0-9][0-9]-*.md; do
|
|
65
|
+
base="$(basename "$f")"
|
|
66
|
+
num="${base#RFC-}"
|
|
67
|
+
num="${num%%-*}"
|
|
68
|
+
rfc_id="RFC-${num}"
|
|
69
|
+
case "$base" in
|
|
70
|
+
*.proposed.md) ticket_status="proposed" ;;
|
|
71
|
+
*.accepted.md) ticket_status="accepted" ;;
|
|
72
|
+
*.in-progress.md) ticket_status="in-progress" ;;
|
|
73
|
+
*.verifying.md) ticket_status="verifying" ;;
|
|
74
|
+
*.closed.md) ticket_status="closed" ;;
|
|
75
|
+
*) continue ;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
# Parse frontmatter `problems: [P<NNN>, P<NNN>, ...]` (single-line form).
|
|
79
|
+
raw=$(awk '/^problems:/ { print; exit }' "$f")
|
|
80
|
+
inner=$(echo "$raw" | sed -E 's/^[[:space:]]*problems:[[:space:]]*\[//; s/\][[:space:]]*$//')
|
|
81
|
+
|
|
82
|
+
claims=0
|
|
83
|
+
while IFS= read -r tok; do
|
|
84
|
+
tok=$(echo "$tok" | tr -d ' "'\''')
|
|
85
|
+
[ "$tok" = "$PID" ] && { claims=1; break; }
|
|
86
|
+
done <<< "$(echo "$inner" | tr ',' '\n')"
|
|
87
|
+
|
|
88
|
+
[ "$claims" -eq 0 ] && continue
|
|
89
|
+
|
|
90
|
+
# Extract title from `# RFC-<NNN>: <Title>` heading.
|
|
91
|
+
title=$(awk -v rid="$rfc_id" 'BEGIN{prefix="^# " rid ": "} { if (match($0, prefix)) { print substr($0, RSTART + RLENGTH); exit } }' "$f")
|
|
92
|
+
[ -z "$title" ] && title="(untitled)"
|
|
93
|
+
|
|
94
|
+
printf '%s\t%s\t%s\n' "$rfc_id" "$ticket_status" "$title" >> "$ROWS_TMP"
|
|
95
|
+
done
|
|
96
|
+
shopt -u nullglob
|
|
97
|
+
|
|
98
|
+
SORTED_ROWS=$(sort -k1,1 "$ROWS_TMP" 2>/dev/null || true)
|
|
99
|
+
HAS_ROWS=0
|
|
100
|
+
[ -n "$SORTED_ROWS" ] && HAS_ROWS=1
|
|
101
|
+
|
|
102
|
+
# ── Step 2: rewrite problem file with idempotent section ────────────────────
|
|
103
|
+
|
|
104
|
+
TMPFILE=$(mktemp)
|
|
105
|
+
trap 'rm -f "$ROWS_TMP" "$TMPFILE"' EXIT
|
|
106
|
+
|
|
107
|
+
# Single-pass awk:
|
|
108
|
+
# - drops any existing `## RFCs` section (header through line before
|
|
109
|
+
# next `## ` header or EOF).
|
|
110
|
+
# - splits the rest into PRE (lines before `## Fix Released`) and
|
|
111
|
+
# POST (`## Fix Released` and after).
|
|
112
|
+
PRE_LINES=$(awk '
|
|
113
|
+
BEGIN { skip=0; in_post=0 }
|
|
114
|
+
/^## RFCs[[:space:]]*$/ { skip=1; next }
|
|
115
|
+
/^## / && skip==1 { skip=0 }
|
|
116
|
+
skip==1 { next }
|
|
117
|
+
/^## Fix Released/ { in_post=1 }
|
|
118
|
+
in_post==0 { print }
|
|
119
|
+
' "$PROBLEM_FILE")
|
|
120
|
+
|
|
121
|
+
POST_LINES=$(awk '
|
|
122
|
+
BEGIN { skip=0; in_post=0 }
|
|
123
|
+
/^## RFCs[[:space:]]*$/ { skip=1; next }
|
|
124
|
+
/^## / && skip==1 { skip=0 }
|
|
125
|
+
skip==1 { next }
|
|
126
|
+
/^## Fix Released/ { in_post=1 }
|
|
127
|
+
in_post==1 { print }
|
|
128
|
+
' "$PROBLEM_FILE")
|
|
129
|
+
|
|
130
|
+
# Trim trailing blank lines off PRE_LINES.
|
|
131
|
+
PRE_LINES=$(printf '%s\n' "$PRE_LINES" | awk '
|
|
132
|
+
{ lines[NR]=$0 }
|
|
133
|
+
END {
|
|
134
|
+
last=NR
|
|
135
|
+
while (last > 0 && lines[last] ~ /^[[:space:]]*$/) last--
|
|
136
|
+
for (i=1; i<=last; i++) print lines[i]
|
|
137
|
+
}
|
|
138
|
+
')
|
|
139
|
+
|
|
140
|
+
{
|
|
141
|
+
printf '%s\n' "$PRE_LINES"
|
|
142
|
+
if [ "$HAS_ROWS" -eq 1 ]; then
|
|
143
|
+
printf '\n## RFCs\n\n'
|
|
144
|
+
printf '| RFC | Status | Title |\n'
|
|
145
|
+
printf '|-----|--------|-------|\n'
|
|
146
|
+
while IFS=$'\t' read -r rid st ti; do
|
|
147
|
+
[ -z "$rid" ] && continue
|
|
148
|
+
printf '| %s | %s | %s |\n' "$rid" "$st" "$ti"
|
|
149
|
+
done <<< "$SORTED_ROWS"
|
|
150
|
+
fi
|
|
151
|
+
if [ -n "$POST_LINES" ]; then
|
|
152
|
+
printf '\n%s\n' "$POST_LINES"
|
|
153
|
+
fi
|
|
154
|
+
} > "$TMPFILE"
|
|
155
|
+
|
|
156
|
+
# Idempotency: only replace when content changed.
|
|
157
|
+
if ! cmp -s "$PROBLEM_FILE" "$TMPFILE"; then
|
|
158
|
+
mv "$TMPFILE" "$PROBLEM_FILE"
|
|
159
|
+
fi
|
|
160
|
+
exit 0
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wr-itil:capture-rfc
|
|
3
|
+
description: Lightweight RFC-capture skill for aside-invocation during foreground work — mandatory problem-trace per ADR-060 I1 invariant, skeleton RFC file, single commit per capture, no inline README refresh. Defers full duplicate analysis and README refresh to /wr-itil:manage-rfc. Use this when the user (or agent) wants to capture an RFC quickly with a clear problem trace. For full lifecycle management, use /wr-itil:manage-rfc.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Capture RFC Skill
|
|
8
|
+
|
|
9
|
+
Capture a Request for Change (RFC) ticket quickly during foreground work. Lightweight aside-invocation surface that complements the heavyweight `/wr-itil:manage-rfc` flow. Mirrors `/wr-itil:capture-problem` shape per ADR-032 lightweight + heavyweight skill split.
|
|
10
|
+
|
|
11
|
+
This skill is one half of the capture-then-manage RFC framework introduced by ADR-060 (Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology, accepted 2026-05-05). The other half is `/wr-itil:manage-rfc` (heavyweight intake + lifecycle management).
|
|
12
|
+
|
|
13
|
+
**Related JTBDs**: JTBD-008 (primary — Decompose a Fix Into Coordinated Changes; this skill IS the capture-time decomposition surface), JTBD-001 (extended scope — change-set-level governance), JTBD-101 (atomic-fix-adopter friction guard — capture-rfc remains opt-in, never auto-fires on atomic captures).
|
|
14
|
+
|
|
15
|
+
## When to invoke
|
|
16
|
+
|
|
17
|
+
- **Multi-commit fix at the start of work**: agent / user observes that a problem fix decomposes into multiple coordinated changes (a refactor across packages, a phased migration, a framework evolution). Capture an RFC scoping the work *before* the first commit lands so each phase competes for WSJF attention as a first-class entity (per JTBD-008).
|
|
18
|
+
- **Retrospective migration**: lifting an existing multi-commit problem into the RFC framework (e.g. RFC-001 retro on P168 in Slice 4 of `docs/plans/170-rfc-framework-story-map.md`). The bounded-escape carve-out at Step 2 permits Closed/Verifying/Parked problem traces for this case.
|
|
19
|
+
- **Ad-hoc planned change**: user names work that doesn't yet have a problem ticket but clearly serves one. The I1 invariant requires the problem ticket to exist FIRST — this skill refuses without a `--problem` trace, redirecting the user to `/wr-itil:capture-problem` to open the problem first, then back to capture-rfc.
|
|
20
|
+
|
|
21
|
+
**Use `/wr-itil:manage-rfc` instead** when:
|
|
22
|
+
- The work is moving an existing RFC through its lifecycle (proposed → accepted → in-progress → verifying → closed).
|
|
23
|
+
- The user wants to walk a full intake flow with structured re-rank prompts and scope-expansion deviation-approval.
|
|
24
|
+
- Multi-RFC coordination decisions need to be captured (manage-rfc handles meta-RFC and cross-RFC ADR creation; capture-rfc creates one RFC per invocation).
|
|
25
|
+
|
|
26
|
+
## Argument grammar
|
|
27
|
+
|
|
28
|
+
**Positional**: `<problem-trace> <description>` where `<problem-trace>` is `P<NNN>` or `P<NNN>,P<NNN>,...` (no spaces inside the trace; multiple problems comma-separated).
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/wr-itil:capture-rfc P168 Pipeline consume-catalog and bootstrap-from-reports — multi-commit retrofit
|
|
32
|
+
/wr-itil:capture-rfc P038,P064 Voice-and-tone gates on external comms — coordinated rollout across changeset/PR/release-notes
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**ADR-060 § Phase 1 item 2 phrasing footnote**: ADR-060 names "mandatory `--problem P<NNN>` flag" verbatim. This skill uses the **positional** form (no `--problem` prefix) to match the lightweight aside-invocation grammar of `capture-problem` (per ADR-032) and because Claude Code skill arguments don't carry a proper CLI flag parser. The hard-block intent (ADR-060 § Confirmation criterion 1: "without a problem trace") is preserved verbatim — only the surface syntax differs. The `--problem` phrasing in ADR-060 reads as exemplar, not contract.
|
|
36
|
+
|
|
37
|
+
## Rule 6 audit (per ADR-032 + ADR-013 + ADR-060)
|
|
38
|
+
|
|
39
|
+
This skill has **one direction-setting AskUserQuestion** (problem-trace, when arguments are non-empty but contain no parseable trace) and **one optional taste AskUserQuestion** (title/scope summary, silent-default if unavailable). Every other potentially-interactive decision is framework-mediated per ADR-044:
|
|
40
|
+
|
|
41
|
+
| Decision | Resolution | Authority class |
|
|
42
|
+
|----------|-----------|-----------------|
|
|
43
|
+
| Problem trace presence | I1 hard-block — refuse on missing trace; emit deny log + halt-with-stderr-directive | direction-setting (the user/caller MUST supply; framework cannot guess) |
|
|
44
|
+
| Problem trace validation | Mechanical: each `P<NNN>` must exist in `docs/problems/`. Open/Known Error/Verifying = pass; Closed/Parked = advisory-warn but proceed (bounded-escape carve-out — see Step 2 rationale) | silent-mechanical |
|
|
45
|
+
| RFC ID allocation | Mechanical: `max(local, origin) + 1`, three-digit padded | silent-mechanical |
|
|
46
|
+
| Title kebab-slug | Mechanical: first 8-10 non-stopword tokens of description | silent-mechanical |
|
|
47
|
+
| Title prose / scope summary refinement | Optional `AskUserQuestion`; silent-default to derived form when unavailable | taste |
|
|
48
|
+
| File write / frontmatter | Mechanical: shape per `docs/rfcs/README.md` § RFC body structure | silent-mechanical |
|
|
49
|
+
| Single commit | Mechanical: `docs(rfcs): capture RFC-<NNN> <title>` | silent-mechanical |
|
|
50
|
+
| Empty arguments | Halt-with-stderr-directive: print "capture-rfc requires `<problem-trace> <description>` — invoke /wr-itil:manage-rfc instead for the full intake flow" and exit. AFK orchestrators MUST NOT invoke capture-rfc with empty arguments. | n/a |
|
|
51
|
+
|
|
52
|
+
Per ADR-013 Rule 6 fail-safe + ADR-044 + P132 + inverse-P078: every silent-mechanical branch above resolves without user input, so AFK and interactive contexts behave identically modulo the optional taste prompt.
|
|
53
|
+
|
|
54
|
+
## Steps
|
|
55
|
+
|
|
56
|
+
### 0. Preflight (Phase 1 cross-directory)
|
|
57
|
+
|
|
58
|
+
**Phase 1 sequencing note**: this skill's preflight uses `wr-itil-reconcile-readme docs/problems` (the existing problems-README reconciliation contract per P118) because the sibling `wr-itil-reconcile-rfcs` script lands in Slice 3 task B5.T6. RFC trace integrity depends on the problems README being clean — every RFC traces to ≥ 1 problem (I1), so an out-of-date problems README directly threatens the trace validation in Step 2. Once Slice 3 ships `wr-itil-reconcile-rfcs docs/rfcs`, swap this preflight to call BOTH (the cross-tier integrity check holds at both surfaces).
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
wr-itil-reconcile-readme docs/problems > /tmp/wr-itil-drift-$$.txt
|
|
62
|
+
reconcile_exit=$?
|
|
63
|
+
if [ "$reconcile_exit" -eq 1 ]; then
|
|
64
|
+
wr-itil-classify-readme-drift /tmp/wr-itil-drift-$$.txt docs/problems
|
|
65
|
+
classify_exit=$?
|
|
66
|
+
rm -f /tmp/wr-itil-drift-$$.txt
|
|
67
|
+
# classify_exit 0 (INLINE_REFRESH): proceed (no inline refresh in this skill).
|
|
68
|
+
# classify_exit 1 (HALT_ROUTE_RECONCILE): halt; invoke /wr-itil:reconcile-readme.
|
|
69
|
+
# classify_exit 2 (parse error): conservative halt-and-route.
|
|
70
|
+
fi
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 1. Parse arguments
|
|
74
|
+
|
|
75
|
+
The arguments must begin with a problem-trace token (`P<NNN>` or comma-separated `P<NNN>,P<NNN>,...`). The remainder is the description.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Tokenise: first token = problem-trace; rest = description
|
|
79
|
+
problem_trace="$1"; shift
|
|
80
|
+
description="$*"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If `$problem_trace` does not match `^P[0-9]{3}(,P[0-9]{3})*$` (regex), this is an I1 violation — go to Step 2's deny path. If `$description` is empty, halt with the empty-arguments directive from the Rule 6 audit table above.
|
|
84
|
+
|
|
85
|
+
Derive a kebab-case title slug from the first 8-10 non-stopword tokens of `$description` (matching `capture-problem` slug derivation).
|
|
86
|
+
|
|
87
|
+
### 2. Validate problem trace + I1 hard-block enforcement
|
|
88
|
+
|
|
89
|
+
For each `P<NNN>` in the trace list:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Check existence in any lifecycle status
|
|
93
|
+
trace_files=$(ls docs/problems/<NNN>-*.md 2>/dev/null)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**I1 hard-block (per ADR-060 § Confirmation criterion 1)**:
|
|
97
|
+
|
|
98
|
+
- **Trace token absent OR malformed**: emit deny log entry + halt with stderr directive:
|
|
99
|
+
```bash
|
|
100
|
+
mkdir -p logs
|
|
101
|
+
printf '{"timestamp":"%s","session_id":"%s","reason":"%s","args":%s}\n' \
|
|
102
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(get_current_session_id 2>/dev/null || echo unknown)" \
|
|
103
|
+
"<missing|malformed|unresolved>-trace" \
|
|
104
|
+
"$(printf '%s' "$ARGUMENTS" | jq -Rs .)" \
|
|
105
|
+
>> logs/rfc-capture-denials.jsonl
|
|
106
|
+
echo "/wr-itil:capture-rfc requires a leading problem-trace argument (P<NNN> or P<NNN>,P<NNN>...). Open the driving problem via /wr-itil:capture-problem first, then re-invoke capture-rfc with the trace." >&2
|
|
107
|
+
exit 1
|
|
108
|
+
```
|
|
109
|
+
The deny log feeds the trace-violation-rate reassessment criterion in ADR-060 § Reassessment Criteria (>20% denial rate triggers ADR-060 reassessment).
|
|
110
|
+
|
|
111
|
+
- **Each `P<NNN>` must resolve to a file in `docs/problems/`**. If any does not, emit deny log entry with `reason: unresolved-trace` + the unresolved IDs, halt, exit 1.
|
|
112
|
+
|
|
113
|
+
**Bounded-escape carve-out for Closed/Verifying/Parked traces**:
|
|
114
|
+
|
|
115
|
+
For each resolved trace file, classify by suffix:
|
|
116
|
+
- `.open.md`, `.known-error.md` → pass silently.
|
|
117
|
+
- `.verifying.md` → pass with advisory note in the report ("trace P<NNN> is Verification Pending — RFC may close before driving problem closes").
|
|
118
|
+
- `.closed.md`, `.parked.md` → pass with advisory-warn in the report ("trace P<NNN> is <Closed|Parked> — capture proceeds for retrospective/historical RFC; bounded-escape carve-out per ADR-060 § Confirmation criterion 5 + Phase 1 item 9").
|
|
119
|
+
|
|
120
|
+
**Why advisory and not hard-block at capture-time**: the bounded-escape contract in ADR-060 § Confirmation criterion 2 scopes I1 hard-block to `accepted → in-progress` and `→ verifying` lifecycle transitions; advisory-with-escalation only fires at `→ closed` transition. Capture-time tolerance for Closed/Parked traces is **load-bearing for the Phase 1 dogfood pass**: RFC-001 retro on P168 (P168 is `.verifying.md`) is structurally impossible without it. This is NOT a relaxation of I1; it is the bounded-escape window the ADR carved out at the right lifecycle phase. See ADR-060 § Confirmation criterion 5 (no semantic loss) + Phase 1 item 9 (retro migration as dogfood pass).
|
|
121
|
+
|
|
122
|
+
### 3. Compute next RFC ID
|
|
123
|
+
|
|
124
|
+
Same `max(local, origin) + 1` formula as `capture-problem` Step 3, scanning `docs/rfcs/RFC-*.md` instead:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
local_max=$(ls docs/rfcs/RFC-*.md 2>/dev/null | sed 's|.*/RFC-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
128
|
+
origin_max=$(git ls-tree --name-only origin/main docs/rfcs/ 2>/dev/null | sed 's|^docs/rfcs/RFC-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
129
|
+
next=$(printf '%03d' $(( $(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Log the renumber decision in the operation report if origin and local diverged.
|
|
133
|
+
|
|
134
|
+
### 4. Optional taste prompt for title / scope summary
|
|
135
|
+
|
|
136
|
+
If interactive (AskUserQuestion available) AND the description is short enough that the derived title slug may not capture intent, fire one `AskUserQuestion` with `header: "RFC title"` offering: (a) the derived kebab-slug as default, (b) "edit". This is **taste** authority class per ADR-044 — silent-default to (a) when AskUserQuestion is unavailable or the description already reads as a clean title.
|
|
137
|
+
|
|
138
|
+
### 5. Write the RFC file
|
|
139
|
+
|
|
140
|
+
**File path**: `docs/rfcs/RFC-<NNN>-<kebab-title>.proposed.md`
|
|
141
|
+
|
|
142
|
+
**Template** (mirrors `docs/rfcs/README.md` § RFC body structure):
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
---
|
|
146
|
+
status: proposed
|
|
147
|
+
rfc-id: <kebab-slug>
|
|
148
|
+
reported: <YYYY-MM-DD>
|
|
149
|
+
decision-makers: [<git config user.name>]
|
|
150
|
+
problems: [P<NNN>, P<NNN>, ...]
|
|
151
|
+
adrs: []
|
|
152
|
+
jtbd: []
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
# RFC-<NNN>: <Title>
|
|
156
|
+
|
|
157
|
+
**Status**: proposed
|
|
158
|
+
**Reported**: <YYYY-MM-DD>
|
|
159
|
+
**Problems**: <P<NNN> [, P<NNN>, ...]>
|
|
160
|
+
**ADRs**: (none)
|
|
161
|
+
**JTBD**: (none)
|
|
162
|
+
|
|
163
|
+
## Summary
|
|
164
|
+
|
|
165
|
+
<description from arguments>
|
|
166
|
+
|
|
167
|
+
## Driving problem trace
|
|
168
|
+
|
|
169
|
+
<for each P<NNN>: one-line summary linking the RFC scope to the problem's symptom or RCA finding>
|
|
170
|
+
|
|
171
|
+
## Scope
|
|
172
|
+
|
|
173
|
+
(deferred — populate at /wr-itil:manage-rfc accepted transition)
|
|
174
|
+
|
|
175
|
+
## Tasks
|
|
176
|
+
|
|
177
|
+
- [ ] (deferred — populate at /wr-itil:manage-rfc accepted transition)
|
|
178
|
+
|
|
179
|
+
## Commits
|
|
180
|
+
|
|
181
|
+
(maintained automatically — populated by the commit-message RFC trailer hook per ADR-060 Phase 1 item 12; lands in Slice 3 task B5.T9)
|
|
182
|
+
|
|
183
|
+
## Related
|
|
184
|
+
|
|
185
|
+
(captured via /wr-itil:capture-rfc; expand at next /wr-itil:manage-rfc invocation)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The deferred-section pattern matches `capture-problem`'s placeholder approach — the captured RFC is intentionally minimal; full scope and task decomposition land at the manage-rfc accepted-transition step.
|
|
189
|
+
|
|
190
|
+
### 6. Single commit — `## RFCs` reverse-trace refresh; no rfcs README refresh
|
|
191
|
+
|
|
192
|
+
**Stage list**: the new RFC file PLUS each driving problem ticket file whose `## RFCs` section needs the reverse-trace row added (per ADR-060 Phase 1 item 10 + Confirmation criterion 3 — auto-maintained reverse trace; architect Q1 verdict skill-side primary). **Do NOT** stage `docs/rfcs/README.md`. The deferred-rfcs-README-refresh contract is the load-bearing capture-time speed differentiator from `/wr-itil:manage-rfc` (mirrors `capture-problem` deferred-`docs/problems/README.md` contract). The next `/wr-itil:manage-rfc review` invocation OR `wr-itil-reconcile-rfcs` refreshes the rfcs README.
|
|
193
|
+
|
|
194
|
+
The reverse-trace refresh on driving problem tickets, however, IS in-commit per ADR-014 single-commit grain — the cross-tier `## RFCs` table on a problem ticket must stay current the moment a new RFC traces it. Otherwise the trailer hook (`itil-rfc-trailer-advisory.sh`) would fire on every capture commit and the reverse-trace would lag by one manage-rfc invocation.
|
|
195
|
+
|
|
196
|
+
For each problem ID in `$problem_trace`, invoke the helper before commit:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
for pid_token in $(echo "$problem_trace" | tr ',' ' '); do
|
|
200
|
+
pid_num="${pid_token#P}"
|
|
201
|
+
problem_file=$(ls docs/problems/${pid_num}-*.md 2>/dev/null | head -1)
|
|
202
|
+
[ -z "$problem_file" ] && continue
|
|
203
|
+
bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-rfcs-section.sh" "$problem_file" docs/rfcs
|
|
204
|
+
git add "$problem_file"
|
|
205
|
+
done
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The helper (`packages/itil/scripts/update-problem-rfcs-section.sh`) is idempotent: running over a current section is a no-op. Lazy-empty discipline applies (zero traced RFCs → section absent) — capture-rfc invocations always have ≥ 1 trace at this step, so this surface always emits a populated section. The `git add` is conditional on the helper actually modifying the file — `cmp -s` no-op-on-current is the helper's idempotency contract; `git add` of an unchanged file is also a no-op.
|
|
209
|
+
|
|
210
|
+
Stage the new RFC file:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git add docs/rfcs/RFC-<NNN>-<slug>.proposed.md
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Satisfy the commit gate per ADR-014:
|
|
217
|
+
|
|
218
|
+
- **Primary**: delegate to subagent type `wr-risk-scorer:pipeline` via the Agent tool.
|
|
219
|
+
- **Fallback**: invoke `/wr-risk-scorer:assess-release` via the Skill tool when the subagent type is unavailable.
|
|
220
|
+
|
|
221
|
+
Commit message:
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
docs(rfcs): capture RFC-<NNN> <title>
|
|
225
|
+
|
|
226
|
+
Refs: RFC-<NNN>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The `capture` verb mirrors `capture-problem`'s audit signal (lightweight aside path vs. heavyweight `manage-rfc` intake). The `Refs: RFC-<NNN>` trailer is the commit-message RFC trailer convention per ADR-060 finding 8 + Phase 1 item 12 (the trailer-recognition hook lands in Slice 3 task B5.T9).
|
|
230
|
+
|
|
231
|
+
### 7. Report
|
|
232
|
+
|
|
233
|
+
After the commit, report:
|
|
234
|
+
|
|
235
|
+
- The new RFC file path and ID.
|
|
236
|
+
- The traced problems with their lifecycle states (Open / Known Error / Verifying / Closed / Parked).
|
|
237
|
+
- Any advisory warnings (Verifying, Closed, Parked traces).
|
|
238
|
+
- Trailing pointer: `Run /wr-itil:manage-rfc <RFC-<NNN>> next to populate Scope / Tasks and advance to accepted; refresh docs/rfcs/README.md.`
|
|
239
|
+
|
|
240
|
+
The trailing pointer is **not optional** — it is the user-visible signal that the RFC is intentionally skeleton-only and how to advance it.
|
|
241
|
+
|
|
242
|
+
## Composition with manage-rfc
|
|
243
|
+
|
|
244
|
+
| Concern | manage-rfc | capture-rfc |
|
|
245
|
+
|---------|------------|-------------|
|
|
246
|
+
| Problem-trace I1 enforcement | Hard-block at lifecycle transitions to irreversible states; advisory at `→ closed` | Hard-block at capture-time; deny logged to `logs/rfc-capture-denials.jsonl` |
|
|
247
|
+
| Multi-RFC / meta-RFC coordination | Step 9 review supports cross-RFC re-rank + meta-RFC ADR creation | Out of scope: capture-rfc creates one RFC per invocation |
|
|
248
|
+
| Skeleton-fill | Full-intake; AskUserQuestion for missing fields; deviation-approval on scope expansion | Deferred-placeholder pattern; one optional taste prompt only |
|
|
249
|
+
| README refresh | P094 / P062 inline (regenerate + stage in same commit) | Deferred to `/wr-itil:manage-rfc review` or `wr-itil-reconcile-rfcs` (Slice 3) |
|
|
250
|
+
| Status transitions | Step 7 owns proposed → accepted → in-progress → verifying → closed | Out of scope (creation only) |
|
|
251
|
+
| Commit grain | One commit per intake / per transition | One commit per capture |
|
|
252
|
+
| Use case | Full lifecycle management | Aside-invocation; capture-and-continue |
|
|
253
|
+
|
|
254
|
+
The two skills share the `/tmp/wr-itil-rfc-capture-grep-${SESSION_ID}` create-gate marker (sibling to `/tmp/manage-problem-grep-${SESSION_ID}`; sibling-marker option per architect verdict on capture-rfc sub-decision (a) — preserves audit-trail per-surface granularity).
|
|
255
|
+
|
|
256
|
+
## Related
|
|
257
|
+
|
|
258
|
+
- **ADR-060** — Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology. Driving accepted ADR.
|
|
259
|
+
- **P170** — `docs/problems/170-...open.md` — driver problem ticket.
|
|
260
|
+
- **`docs/plans/170-rfc-framework-story-map.md`** — Slice 2 task B5.T3 lands this skill.
|
|
261
|
+
- **JTBD-008** — Decompose a Fix Into Coordinated Changes. Primary persona-anchor.
|
|
262
|
+
- **JTBD-001** (extended scope) — change-set-level governance composition.
|
|
263
|
+
- **JTBD-101** (atomic-fix-adopter friction guard) — capture-rfc remains opt-in aside-invocation.
|
|
264
|
+
- **`docs/rfcs/README.md`** — RFC tier lifecycle index + frontmatter shape spec (Slice 2 tasks B5.T1 + B5.T2 — committed `adc53c8`).
|
|
265
|
+
- **ADR-014** — governance skills commit their own work. Single-commit grain per capture.
|
|
266
|
+
- **ADR-022** — problem lifecycle conventions; RFC lifecycle mirrors.
|
|
267
|
+
- **ADR-032** — governance-skill aside-invocation pattern. Lightweight + heavyweight split.
|
|
268
|
+
- **ADR-038** — progressive disclosure. SKILL.md (this file) + future REFERENCE.md split (deferred per ADR-054 — REFERENCE.md lands when SKILL.md size pressure surfaces empirically).
|
|
269
|
+
- **ADR-044** — decision delegation contract. Authority classes named in the Rule 6 audit table above.
|
|
270
|
+
- **ADR-049** — plugin-bundled scripts via `bin/` on `$PATH`. `wr-itil-reconcile-rfcs` shim follows this grammar (Slice 3 B5.T7).
|
|
271
|
+
- **ADR-051** — load-bearing-from-the-start. I1 hard-block ships behaviourally on day one, not deferred.
|
|
272
|
+
- **ADR-052** — behavioural-tests default. Bats coverage in Slice 2 task B5.T5 (no structural grep on SKILL.md content per P081).
|
|
273
|
+
- **P057** — staging trap rule (re-stage after Edit) — applies to capture-rfc only when an Edit follows the Write (rare; lifecycle-transition territory belongs to manage-rfc).
|
|
274
|
+
- **P078** capture-on-correction — capture-rfc may be the correct response to a strong-signal user correction that names multi-commit work; orchestrators should offer capture-rfc as one option in the capture menu.
|
|
275
|
+
- **P119** — create-gate marker. Hook generalisation per architect verdict on sub-decision (a) — `manage-problem-enforce-create.sh` widens to also accept `docs/rfcs/RFC-*.proposed.md` Writes, with case-branched deny messages naming the right skill (capture-rfc vs capture-problem).
|
|
276
|
+
- **P132** + inverse-P078 — mechanical-stage carve-outs prevent over-asking; named in the Rule 6 audit table.
|