@windyroad/itil 0.27.1 → 0.28.0-preview.303
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 +9 -1
- package/bin/wr-itil-reconcile-stories +2 -0
- package/bin/wr-itil-reconcile-story-maps +2 -0
- package/hooks/hooks.json +4 -0
- package/hooks/itil-readme-refresh-discipline.sh +118 -0
- package/hooks/lib/readme-refresh-detect.sh +161 -0
- package/hooks/test/itil-readme-refresh-discipline.bats +261 -0
- package/lib/migrate-problems-layout.sh +128 -0
- package/package.json +1 -1
- package/scripts/reconcile-stories.sh +236 -0
- package/scripts/reconcile-story-maps.sh +98 -0
- package/scripts/test/reconcile-stories.bats +173 -0
- package/scripts/test/reconcile-story-maps.bats +74 -0
- package/scripts/test/rfc-stories-extension.bats +173 -0
- package/scripts/test/update-problem-references-section.bats +195 -0
- package/scripts/test/update-references-section-sibling-helpers.bats +80 -0
- package/scripts/test/working-the-problem-traversal.bats +109 -0
- package/scripts/update-jtbd-references-section.sh +131 -0
- package/scripts/update-problem-references-section.sh +284 -0
- package/scripts/update-rfc-references-section.sh +152 -0
- package/scripts/update-story-references-section.sh +128 -0
- package/skills/capture-rfc/SKILL.md +28 -3
- package/skills/capture-story/SKILL.md +373 -0
- package/skills/capture-story/test/capture-story-behavioural.bats +227 -0
- package/skills/capture-story-map/SKILL.md +229 -0
- package/skills/capture-story-map/test/capture-story-map-behavioural.bats +98 -0
- package/skills/list-stories/SKILL.md +151 -0
- package/skills/list-stories/test/list-stories-contract.bats +127 -0
- package/skills/list-story-maps/SKILL.md +93 -0
- package/skills/list-story-maps/test/list-story-maps-contract.bats +46 -0
- package/skills/manage-problem/SKILL.md +42 -4
- package/skills/manage-problem/test/manage-problem-auto-migrate-step.bats +53 -0
- package/skills/manage-rfc/SKILL.md +12 -0
- package/skills/manage-story/SKILL.md +242 -0
- package/skills/manage-story/test/manage-story-contract.bats +171 -0
- package/skills/manage-story-map/SKILL.md +158 -0
- package/skills/manage-story-map/test/manage-story-map-contract.bats +63 -0
- package/skills/reconcile-stories/SKILL.md +110 -0
- package/skills/reconcile-story-maps/SKILL.md +70 -0
- package/skills/work-problem/SKILL.md +1 -1
- package/skills/work-problems/SKILL.md +25 -0
- package/skills/work-problems/test/work-problems-auto-migrate-step.bats +57 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Behavioural fixtures for /wr-itil:capture-story (P170 Phase 2 Slice 7).
|
|
3
|
+
#
|
|
4
|
+
# Per ADR-052 (Behavioural-tests-default for skill testing), these tests
|
|
5
|
+
# exercise the load-bearing primitives the skill dispatches and assert
|
|
6
|
+
# observable state — NOT the prose contents of SKILL.md.
|
|
7
|
+
#
|
|
8
|
+
# Behavioural surfaces under test:
|
|
9
|
+
# 1. Next-ID computation — capture-story uses the inline
|
|
10
|
+
# max(local, origin) + 1 formula scanning docs/stories/*/STORY-*.md
|
|
11
|
+
# + git ls-tree -r origin/main docs/stories/ per ADR-019 inline
|
|
12
|
+
# collision-guard approved at Slice 3 design review (option a).
|
|
13
|
+
# 2. ID collision-on-origin renumber — when an origin RFC IS not yet
|
|
14
|
+
# pulled locally, the formula must compute the higher of the two
|
|
15
|
+
# and increment from there (no silent collision).
|
|
16
|
+
# 3. Reverse-trace helper "Stories" section-name support — the three
|
|
17
|
+
# Slice 2a/2b helpers (update-problem / update-jtbd / update-rfc
|
|
18
|
+
# -references-section.sh) must accept "Stories" as a section name.
|
|
19
|
+
# 4. Frontmatter shape — a captured story file conforms to ADR-060
|
|
20
|
+
# lines 220-228 (status: draft / story-id / problems / jtbd / etc.).
|
|
21
|
+
# 5. NO update-story-references-section.sh "Stories" path —
|
|
22
|
+
# story-maps are HTML; the story-tier reverse-trace helper does NOT
|
|
23
|
+
# accept "Stories" (per architect amend finding 2 on Slice 7).
|
|
24
|
+
#
|
|
25
|
+
# @problem P170
|
|
26
|
+
# @jtbd JTBD-008 (Decompose a Fix Into Coordinated Changes — capture-time
|
|
27
|
+
# decomposition surface)
|
|
28
|
+
# @jtbd JTBD-001 (extended scope — change-set-level governance)
|
|
29
|
+
# @adr ADR-060 (Problem-RFC-Story framework — story tier)
|
|
30
|
+
# @adr ADR-052 (Behavioural-tests-default)
|
|
31
|
+
# @adr ADR-019 (ID collision-on-origin renumber)
|
|
32
|
+
# @adr ADR-014 (single-commit grain)
|
|
33
|
+
|
|
34
|
+
setup() {
|
|
35
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
|
|
36
|
+
SKILL_FILE="${REPO_ROOT}/packages/itil/skills/capture-story/SKILL.md"
|
|
37
|
+
HELPER_PROBLEM="${REPO_ROOT}/packages/itil/scripts/update-problem-references-section.sh"
|
|
38
|
+
HELPER_JTBD="${REPO_ROOT}/packages/itil/scripts/update-jtbd-references-section.sh"
|
|
39
|
+
HELPER_RFC="${REPO_ROOT}/packages/itil/scripts/update-rfc-references-section.sh"
|
|
40
|
+
HELPER_STORY="${REPO_ROOT}/packages/itil/scripts/update-story-references-section.sh"
|
|
41
|
+
|
|
42
|
+
TMPROOT=$(mktemp -d)
|
|
43
|
+
ORIG_DIR="$PWD"
|
|
44
|
+
cd "$TMPROOT"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
teardown() {
|
|
48
|
+
cd "$ORIG_DIR"
|
|
49
|
+
rm -rf "$TMPROOT"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Surface 0: SKILL.md exists with correct name (minimum discoverability)
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
@test "capture-story: SKILL.md exists" {
|
|
57
|
+
[ -f "$SKILL_FILE" ]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@test "capture-story: SKILL.md frontmatter declares wr-itil:capture-story name" {
|
|
61
|
+
# Discoverable on / autocomplete depends on the canonical name.
|
|
62
|
+
run grep -E '^name: wr-itil:capture-story$' "$SKILL_FILE"
|
|
63
|
+
[ "$status" -eq 0 ]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Surface 1: Next-ID computation — inline formula matches capture-rfc precedent
|
|
68
|
+
# (verified against a fixture stories directory).
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
@test "capture-story: next-ID formula computes 001 for empty stories directory" {
|
|
72
|
+
mkdir -p docs/stories/draft
|
|
73
|
+
local_max=$(ls docs/stories/*/STORY-*.md 2>/dev/null | sed 's|.*/STORY-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
74
|
+
origin_max="" # no git fixture; treat as empty
|
|
75
|
+
next=$(printf '%03d' $(( 10#$(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
|
|
76
|
+
[ "$next" = "001" ]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@test "capture-story: next-ID formula computes 003 when STORY-002 exists locally" {
|
|
80
|
+
mkdir -p docs/stories/draft docs/stories/in-progress
|
|
81
|
+
touch docs/stories/draft/STORY-002-foo.md
|
|
82
|
+
touch docs/stories/in-progress/STORY-001-bar.md
|
|
83
|
+
local_max=$(ls docs/stories/*/STORY-*.md 2>/dev/null | sed 's|.*/STORY-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
84
|
+
next=$(printf '%03d' $(( 10#${local_max:-0} + 1 )))
|
|
85
|
+
[ "$next" = "003" ]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@test "capture-story: next-ID formula picks max(local, origin) when origin is higher (collision-on-origin renumber)" {
|
|
89
|
+
# Simulates the renumber: local has STORY-002, origin has STORY-005.
|
|
90
|
+
# Formula must pick 005 + 1 = 006, not 002 + 1 = 003 (silent collision).
|
|
91
|
+
local_max=2
|
|
92
|
+
origin_max=5
|
|
93
|
+
next=$(printf '%03d' $(( 10#$(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
|
|
94
|
+
[ "$next" = "006" ]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@test "capture-story: next-ID formula picks max(local, origin) when local is higher" {
|
|
98
|
+
# Reverse case: local STORY-007, origin STORY-003. Local wins → 008.
|
|
99
|
+
local_max=7
|
|
100
|
+
origin_max=3
|
|
101
|
+
next=$(printf '%03d' $(( 10#$(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
|
|
102
|
+
[ "$next" = "008" ]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Surface 2: Reverse-trace helpers accept "Stories" section name
|
|
107
|
+
# (Slice 2a/2b shipped these — verify they're still wired correctly).
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
@test "capture-story: update-problem-references-section.sh accepts 'Stories' section name without rejecting" {
|
|
111
|
+
# Set up minimal fixture: a problem file with a Story Maps placeholder
|
|
112
|
+
# section. The helper should accept "Stories" as a valid lookup key
|
|
113
|
+
# (architect finding on Slice 7: the absence of Stories support here
|
|
114
|
+
# would block the inline reverse-trace refresh in capture-story Step 6).
|
|
115
|
+
mkdir -p docs/problems/known-error docs/stories/draft
|
|
116
|
+
cat > docs/problems/known-error/170-test-problem.md <<EOF
|
|
117
|
+
# P170: Test problem
|
|
118
|
+
|
|
119
|
+
## Story Maps
|
|
120
|
+
|
|
121
|
+
(empty)
|
|
122
|
+
|
|
123
|
+
## Stories
|
|
124
|
+
|
|
125
|
+
(empty)
|
|
126
|
+
EOF
|
|
127
|
+
run bash "$HELPER_PROBLEM" docs/problems/known-error/170-test-problem.md Stories
|
|
128
|
+
# Acceptable outcomes: success (0) or no-op-with-stderr; rejection (e.g.
|
|
129
|
+
# "unknown section-name") would fail this assertion.
|
|
130
|
+
[[ "$output" != *"unknown section-name"* ]]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@test "capture-story: update-jtbd-references-section.sh accepts 'Stories' section name" {
|
|
134
|
+
mkdir -p docs/jtbd/solo-developer docs/stories/draft
|
|
135
|
+
cat > docs/jtbd/solo-developer/JTBD-008-test.proposed.md <<EOF
|
|
136
|
+
# JTBD-008: Test
|
|
137
|
+
|
|
138
|
+
## Stories
|
|
139
|
+
|
|
140
|
+
(empty)
|
|
141
|
+
EOF
|
|
142
|
+
run bash "$HELPER_JTBD" docs/jtbd/solo-developer/JTBD-008-test.proposed.md Stories
|
|
143
|
+
[[ "$output" != *"unknown section-name"* ]]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@test "capture-story: update-rfc-references-section.sh accepts 'Stories' section name" {
|
|
147
|
+
mkdir -p docs/rfcs docs/stories/draft
|
|
148
|
+
cat > docs/rfcs/RFC-002-test.verifying.md <<EOF
|
|
149
|
+
# RFC-002: Test
|
|
150
|
+
|
|
151
|
+
## Stories
|
|
152
|
+
|
|
153
|
+
(empty)
|
|
154
|
+
EOF
|
|
155
|
+
run bash "$HELPER_RFC" docs/rfcs/RFC-002-test.verifying.md Stories
|
|
156
|
+
[[ "$output" != *"unknown section-name"* ]]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# ---------------------------------------------------------------------------
|
|
160
|
+
# Surface 3: NO update-story-references-section.sh "Stories" path
|
|
161
|
+
# (architect amend finding 2 on Slice 7 — story-maps are HTML, no markdown
|
|
162
|
+
# reverse-trace section auto-maintained on the HTML map; capture-story does
|
|
163
|
+
# NOT stage story-map files.)
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
@test "capture-story: update-story-references-section.sh does NOT accept 'Stories' section name (architect finding 2 — story-maps are HTML, manually authored)" {
|
|
167
|
+
mkdir -p docs/stories/draft
|
|
168
|
+
cat > docs/stories/draft/STORY-001-test.md <<EOF
|
|
169
|
+
# STORY-001: Test
|
|
170
|
+
EOF
|
|
171
|
+
run bash "$HELPER_STORY" docs/stories/draft/STORY-001-test.md Stories
|
|
172
|
+
# The helper's lookup table supports RFCs + Story Maps but NOT Stories
|
|
173
|
+
# (per architect finding 2). The helper MUST exit non-zero AND name
|
|
174
|
+
# the supported sections in its error stream.
|
|
175
|
+
[ "$status" -ne 0 ]
|
|
176
|
+
[[ "$output" == *"unknown section-name"* ]] || [[ "$output" == *"Supported"* ]]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# Surface 4: Frontmatter shape — verify ADR-060 lines 220-228 fields
|
|
181
|
+
# round-trip through a minimal capture sequence.
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
@test "capture-story: captured frontmatter contains required fields per ADR-060 lines 220-228" {
|
|
185
|
+
# Construct the frontmatter the way the skill prescribes (Step 5).
|
|
186
|
+
mkdir -p docs/stories/draft
|
|
187
|
+
next_id="001"
|
|
188
|
+
slug="test-capture"
|
|
189
|
+
description="Test capture"
|
|
190
|
+
reported=$(date -u +%Y-%m-%d)
|
|
191
|
+
cat > "docs/stories/draft/STORY-${next_id}-${slug}.md" <<EOF
|
|
192
|
+
---
|
|
193
|
+
status: draft
|
|
194
|
+
story-id: ${slug}
|
|
195
|
+
reported: ${reported}
|
|
196
|
+
decision-makers: [Test]
|
|
197
|
+
problems: [P170]
|
|
198
|
+
jtbd: [JTBD-008]
|
|
199
|
+
rfcs: []
|
|
200
|
+
story-maps: []
|
|
201
|
+
estimated-effort: deferred
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
# STORY-${next_id}: ${description}
|
|
205
|
+
EOF
|
|
206
|
+
# Assert each required frontmatter field appears (grep is sufficient —
|
|
207
|
+
# ADR-052 prefers behavioural assertion on the observable file state).
|
|
208
|
+
story_file="docs/stories/draft/STORY-${next_id}-${slug}.md"
|
|
209
|
+
for field in status story-id reported decision-makers problems jtbd rfcs story-maps estimated-effort; do
|
|
210
|
+
run grep -E "^${field}:" "$story_file"
|
|
211
|
+
[ "$status" -eq 0 ]
|
|
212
|
+
done
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@test "capture-story: captured story file lands at docs/stories/draft/ per ADR-060 lines 145-147" {
|
|
216
|
+
# Story-maps use HTML and lifecycle subdirs (draft/accepted/in-progress/
|
|
217
|
+
# completed/archived); stories use markdown and lifecycle subdirs
|
|
218
|
+
# (draft/accepted/in-progress/done/archived). Captured story lands at
|
|
219
|
+
# draft/ per the skill's Step 5 prescription.
|
|
220
|
+
mkdir -p docs/stories/draft
|
|
221
|
+
touch docs/stories/draft/STORY-001-test.md
|
|
222
|
+
[ -f docs/stories/draft/STORY-001-test.md ]
|
|
223
|
+
# Negative assertion: NOT in any other subdir at capture time.
|
|
224
|
+
[ ! -f docs/stories/accepted/STORY-001-test.md ]
|
|
225
|
+
[ ! -f docs/stories/in-progress/STORY-001-test.md ]
|
|
226
|
+
[ ! -f docs/stories/done/STORY-001-test.md ]
|
|
227
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wr-itil:capture-story-map
|
|
3
|
+
description: Lightweight story-map-capture skill for aside-invocation during foreground work — mandatory leading problem-trace AND JTBD-trace per ADR-060 I3 + I4 invariants, skeleton HTML file at `docs/story-maps/draft/STORY-MAP-NNN-<slug>.html` per ADR-060 § Phase 2 encoding amendment 2026-05-12, single commit per capture, no inline README refresh. Defers full backbone/ribs/slices authoring + lifecycle transitions to /wr-itil:manage-story-map. Use when the user (or agent) wants to capture a new story-map quickly with clear problem + JTBD anchoring.
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Capture Story Map Skill
|
|
8
|
+
|
|
9
|
+
Capture a story-map (HTML artefact representing Patton's backbone × ribs × slices layout) quickly during foreground work. Lightweight aside-invocation surface that complements the heavyweight `/wr-itil:manage-story-map` flow. Mirrors `/wr-itil:capture-story` shape per ADR-032 lightweight + heavyweight skill split, applied at the story-map tier with HTML encoding.
|
|
10
|
+
|
|
11
|
+
**Related JTBDs**: JTBD-008 (primary — Decompose a Fix Into Coordinated Changes; story-maps represent the journey-context decomposition), JTBD-001 (extended scope), JTBD-302 (README-currency rule for `docs/story-maps/README.md`).
|
|
12
|
+
|
|
13
|
+
## When to invoke
|
|
14
|
+
|
|
15
|
+
- **Decomposing a problem or RFC into a journey-shaped layout** — agent / user observes that the fix decomposes into multiple coordinated changes that map onto a user-journey backbone × ribs × slices spatial layout (per Patton's User Story Mapping). Capture the story-map BEFORE individual stories so the spatial-placement context informs story decomposition.
|
|
16
|
+
- **Retrospective story-map for shipped work** — lifting an existing multi-commit decomposition into a story-map artefact (e.g. STORY-MAP-001 retro on P170 Phase 1 + Phase 2 framework code — Slice 14 of P170 Phase 2).
|
|
17
|
+
- **Cross-RFC journey lens** — a single story-map can reference stories from multiple RFCs (the map is a journey-context lens on the story corpus per ADR-060 line 317).
|
|
18
|
+
|
|
19
|
+
**Use `/wr-itil:manage-story-map` instead** when:
|
|
20
|
+
- The work is moving an existing story-map through its lifecycle (draft → accepted → in-progress → completed → archived).
|
|
21
|
+
- The user wants to author or refine the backbone/ribs/slices structure with full intake.
|
|
22
|
+
- Cross-map coordination decisions need to be captured.
|
|
23
|
+
|
|
24
|
+
## Argument grammar
|
|
25
|
+
|
|
26
|
+
**Positional (both mandatory)**: `<problem-trace> <jtbd-trace> <description>` where:
|
|
27
|
+
- `<problem-trace>` is `P<NNN>` or `P<NNN>,P<NNN>,...`
|
|
28
|
+
- `<jtbd-trace>` is `JTBD-<NNN>` or `JTBD-<NNN>,JTBD-<NNN>,...`
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
/wr-itil:capture-story-map P170 JTBD-008 RFC framework Phase 1 + Phase 2 bootstrap
|
|
32
|
+
/wr-itil:capture-story-map P170 JTBD-008,JTBD-001 Story map for the P170 RFC framework work
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Positional grammar mirrors `/wr-itil:capture-story` shape (footnote per ADR-060 line 285 phrasing — `--problem` / `--jtbd` flag-form was the ADR-exemplar but positional is the lightweight-aside grammar that Claude Code skills support natively).
|
|
36
|
+
|
|
37
|
+
## Rule 6 audit (per ADR-032 + ADR-013 + ADR-060)
|
|
38
|
+
|
|
39
|
+
| Decision | Resolution | Authority class |
|
|
40
|
+
|----------|-----------|-----------------|
|
|
41
|
+
| Problem-trace presence | I3 hard-block — refuse on missing trace; emit deny log + halt | direction-setting |
|
|
42
|
+
| Problem-trace validation | Mechanical: each `P<NNN>` exists in `docs/problems/`; dual-tolerant lookup | silent-mechanical |
|
|
43
|
+
| JTBD-trace presence | I4 hard-block — refuse on missing trace; emit deny log + halt | direction-setting |
|
|
44
|
+
| JTBD-trace validation | Mechanical: each `JTBD-<NNN>` resolves to a file in `docs/jtbd/` | silent-mechanical |
|
|
45
|
+
| STORY-MAP ID allocation | Mechanical: `max(local, origin) + 1` enumerating `docs/story-maps/*/STORY-MAP-*.html` (ADR-019 inline collision-guard) | silent-mechanical |
|
|
46
|
+
| Title kebab-slug | Mechanical: first 8-10 non-stopword tokens of description | silent-mechanical |
|
|
47
|
+
| Title prose refinement | Optional taste AskUserQuestion; silent-default to derived form | taste |
|
|
48
|
+
| HTML file write | Mechanical: schema per ADR-060 § Phase 2 encoding amendment 2026-05-12 lines 381-435 | silent-mechanical |
|
|
49
|
+
| Reverse-trace `## Story Maps` refresh | Mechanical: inline on driving problem + JTBD files via Slice 2a/2b helpers | silent-mechanical |
|
|
50
|
+
| README refresh | Mechanical: deferred to `/wr-itil:manage-story-map review` or `wr-itil-reconcile-story-maps` | silent-mechanical |
|
|
51
|
+
| Empty arguments | Halt-with-stderr-directive | n/a |
|
|
52
|
+
|
|
53
|
+
## Steps
|
|
54
|
+
|
|
55
|
+
### 0. Preflight
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
wr-itil-reconcile-readme docs/problems > /tmp/wr-itil-drift-$$.txt
|
|
59
|
+
reconcile_exit=$?
|
|
60
|
+
# Halt-and-route on drift per the standard pattern.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 1. Parse arguments
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
problem_trace="$1"; shift
|
|
67
|
+
jtbd_trace="$1"; shift
|
|
68
|
+
description="$*"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Validate `$problem_trace` matches `^P[0-9]{3}(,P[0-9]{3})*$`. Validate `$jtbd_trace` matches `^JTBD-[0-9]{3}(,JTBD-[0-9]{3})*$`. If `$description` is empty, halt with empty-arguments directive.
|
|
72
|
+
|
|
73
|
+
Derive kebab-case title slug from first 8-10 non-stopword tokens of `$description`.
|
|
74
|
+
|
|
75
|
+
### 2. Validate problem trace + I3 hard-block
|
|
76
|
+
|
|
77
|
+
For each `P<NNN>`:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Dual-tolerant ticket discovery (RFC-002 migration window).
|
|
81
|
+
trace_files=$(ls docs/problems/<NNN>-*.md docs/problems/*/<NNN>-*.md 2>/dev/null)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**I3 hard-block** (per ADR-060 line 187): trace absent / malformed / unresolved → emit deny log entry to `logs/story-map-capture-denials.jsonl`, halt with stderr directive naming `/wr-itil:capture-problem` as the open-the-driving-problem-first surface.
|
|
85
|
+
|
|
86
|
+
### 2.5. Validate JTBD trace + I4 hard-block
|
|
87
|
+
|
|
88
|
+
For each `JTBD-<NNN>`:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
jtbd_file=$(ls docs/jtbd/*/JTBD-<NNN>-*.md 2>/dev/null | head -1)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**I4 hard-block** (per ADR-060 line 188): trace absent / malformed / unresolved → emit deny log + halt. Story-maps without JTBD trace are structurally meaningless per ADR-060 ("a map with no JTBD trace is structurally meaningless"; Patton's central thesis is journey-around-user-value).
|
|
95
|
+
|
|
96
|
+
### 3. Compute next STORY-MAP ID
|
|
97
|
+
|
|
98
|
+
Inline `max(local, origin) + 1` per ADR-019 collision-guard (architect Slice 3 design review option a — inline-only path, mirrors capture-rfc + capture-story precedent):
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
local_max=$(ls docs/story-maps/*/STORY-MAP-*.html 2>/dev/null | sed 's|.*/STORY-MAP-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
102
|
+
origin_max=$(git ls-tree -r --name-only origin/main docs/story-maps/ 2>/dev/null | sed 's|.*/STORY-MAP-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
103
|
+
next=$(printf '%03d' $(( 10#$(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. Optional taste prompt for title
|
|
107
|
+
|
|
108
|
+
Same shape as capture-story Step 4 — silent-default when unavailable.
|
|
109
|
+
|
|
110
|
+
### 5. Write the story-map file
|
|
111
|
+
|
|
112
|
+
**File path**: `docs/story-maps/draft/STORY-MAP-<NNN>-<kebab-title>.html`
|
|
113
|
+
|
|
114
|
+
**Template** (per ADR-060 § Phase 2 encoding amendment 2026-05-12 lines 381-420 + `docs/STYLE-GUIDE.md` rules):
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<!DOCTYPE html>
|
|
118
|
+
<html lang="en">
|
|
119
|
+
<head>
|
|
120
|
+
<meta charset="UTF-8">
|
|
121
|
+
<title>STORY-MAP-<NNN>: <Title></title>
|
|
122
|
+
<meta name="story-map-id" content="STORY-MAP-<NNN>">
|
|
123
|
+
<meta name="status" content="draft">
|
|
124
|
+
<meta name="problems" content="<P<NNN>[,P<NNN>...]>">
|
|
125
|
+
<meta name="rfcs" content="">
|
|
126
|
+
<meta name="jtbd" content="<JTBD-<NNN>[,JTBD-<NNN>...]>">
|
|
127
|
+
<meta name="adrs" content="">
|
|
128
|
+
<meta name="reported" content="<YYYY-MM-DD>">
|
|
129
|
+
<meta name="decision-makers" content="<git config user.name>">
|
|
130
|
+
<style>
|
|
131
|
+
body { font-family: system-ui, sans-serif; max-width: 1200px; margin: 1rem auto; padding: 0 1rem; }
|
|
132
|
+
h1 { font-size: 1.5rem; }
|
|
133
|
+
h2 { font-size: 1.125rem; margin-top: 1.5rem; }
|
|
134
|
+
.backbone { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 1rem; margin-bottom: 2rem; }
|
|
135
|
+
.rib-header { grid-column: 1 / -1; border-bottom: 1px solid #ccc; padding-bottom: 0.25rem; }
|
|
136
|
+
.rib { display: contents; }
|
|
137
|
+
.slice { border: 1px solid #ccc; padding: 0.5rem; text-decoration: none; color: inherit; display: block; }
|
|
138
|
+
.slice:hover { border-color: #666; }
|
|
139
|
+
</style>
|
|
140
|
+
</head>
|
|
141
|
+
<body>
|
|
142
|
+
<h1>STORY-MAP-<NNN>: <Title></h1>
|
|
143
|
+
|
|
144
|
+
<p>(Story-map purpose paragraph — populated at /wr-itil:manage-story-map accepted transition.)</p>
|
|
145
|
+
|
|
146
|
+
<section class="backbone" style="--cols: 1">
|
|
147
|
+
<header class="rib-header">
|
|
148
|
+
<h2 data-rib="placeholder">Backbone — populate at /wr-itil:manage-story-map accepted transition</h2>
|
|
149
|
+
</header>
|
|
150
|
+
<div class="rib">
|
|
151
|
+
<!-- Slice cards as <a class="slice" href="../../stories/<state>/STORY-NNN-<slug>.md"
|
|
152
|
+
data-story-id="STORY-NNN" data-rfc="RFC-NNN" data-jtbd="JTBD-NNN"
|
|
153
|
+
data-status="<draft|accepted|in-progress|done|archived>">Story title</a>
|
|
154
|
+
per docs/story-maps/README.md schema. Populated by manage-story-map.
|
|
155
|
+
-->
|
|
156
|
+
</div>
|
|
157
|
+
</section>
|
|
158
|
+
</body>
|
|
159
|
+
</html>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Per `docs/STYLE-GUIDE.md`: NO inline `style=""` on `<a class="slice">` or `<h2 data-rib>` data-bearing elements; embedded `<style>` block in `<head>` is the only permitted styling source; `--cols` custom-property on `.backbone` is the layout-container exception.
|
|
163
|
+
|
|
164
|
+
### 6. Single commit — `## Story Maps` reverse-trace refresh
|
|
165
|
+
|
|
166
|
+
**Stage list**: new HTML file PLUS driving problem files (refresh `## Story Maps` section via `update-problem-references-section.sh <file> "Story Maps"`) PLUS driving JTBD files (refresh `## Story Maps` section via `update-jtbd-references-section.sh <file> "Story Maps"`). Do NOT stage `docs/story-maps/README.md` (deferred).
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
for pid_token in $(echo "$problem_trace" | tr ',' ' '); do
|
|
170
|
+
pid_num="${pid_token#P}"
|
|
171
|
+
problem_file=$(ls docs/problems/${pid_num}-*.md docs/problems/*/${pid_num}-*.md 2>/dev/null | head -1)
|
|
172
|
+
[ -z "$problem_file" ] && continue
|
|
173
|
+
bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-problem-references-section.sh" "$problem_file" "Story Maps"
|
|
174
|
+
git add "$problem_file"
|
|
175
|
+
done
|
|
176
|
+
|
|
177
|
+
for jid_token in $(echo "$jtbd_trace" | tr ',' ' '); do
|
|
178
|
+
jtbd_file=$(ls docs/jtbd/*/${jid_token}-*.md 2>/dev/null | head -1)
|
|
179
|
+
[ -z "$jtbd_file" ] && continue
|
|
180
|
+
bash "$(wr-itil-script-path 2>/dev/null || echo packages/itil/scripts)/update-jtbd-references-section.sh" "$jtbd_file" "Story Maps"
|
|
181
|
+
git add "$jtbd_file"
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
git add docs/story-maps/draft/STORY-MAP-<NNN>-<slug>.html
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Commit message:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
feat(itil): capture STORY-MAP-<NNN> <title>
|
|
191
|
+
|
|
192
|
+
Refs: STORY-MAP-<NNN>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 7. Report
|
|
196
|
+
|
|
197
|
+
After commit, report:
|
|
198
|
+
- New story-map file path + ID.
|
|
199
|
+
- Traced problems + JTBDs.
|
|
200
|
+
- Trailing pointer: `Run /wr-itil:manage-story-map <STORY-MAP-<NNN>> next to author backbone/ribs/slices structure and advance draft → accepted; refresh docs/story-maps/README.md.`
|
|
201
|
+
|
|
202
|
+
## Composition with manage-story-map
|
|
203
|
+
|
|
204
|
+
| Concern | manage-story-map | capture-story-map |
|
|
205
|
+
|---------|------------------|-------------------|
|
|
206
|
+
| I3 + I4 enforcement | Re-validated at every lifecycle transition | Hard-block at capture-time |
|
|
207
|
+
| I5 no-WSJF-leak | Behavioural test asserts no WSJF field at every transition | Already absent at capture (frontmatter has no WSJF) |
|
|
208
|
+
| Backbone/ribs/slices authoring | Step 7-9 author the spatial layout | Deferred-placeholder pattern; one rib placeholder only |
|
|
209
|
+
| Status transitions | draft → accepted → in-progress → completed → archived | Out of scope (creation only) |
|
|
210
|
+
| README refresh | Inline per transition | Deferred to `/wr-itil:manage-story-map review` or `wr-itil-reconcile-story-maps` |
|
|
211
|
+
| Commit grain | One commit per intake / per transition | One commit per capture |
|
|
212
|
+
|
|
213
|
+
## Related
|
|
214
|
+
|
|
215
|
+
- **ADR-060** — Problem-RFC-Story framework + Phase 2 amendment 2026-05-10 + encoding amendment 2026-05-12.
|
|
216
|
+
- **ADR-060 lines 145-189** — story-map tier spec + I3-I5 invariants.
|
|
217
|
+
- **ADR-060 lines 381-435** — HTML encoding schema (the source-of-truth for the file template).
|
|
218
|
+
- **`docs/STYLE-GUIDE.md`** — story-map HTML style rules (prohibited inline `style=""` on data-bearing elements).
|
|
219
|
+
- **`docs/VOICE-AND-TONE.md`** — story-map prose guidance (HTML content section).
|
|
220
|
+
- **`docs/story-maps/README.md`** — story-map tier lifecycle index + schema spec.
|
|
221
|
+
- **P170** — driver problem ticket.
|
|
222
|
+
- **JTBD-008** — Decompose a Fix Into Coordinated Changes. Primary persona-anchor.
|
|
223
|
+
- **JTBD-302** — Trust That the README Describes the Plugin I Just Installed (README-currency rule for `docs/story-maps/README.md`).
|
|
224
|
+
- **ADR-032** — governance-skill aside-invocation pattern.
|
|
225
|
+
- **ADR-049** — bin/ on PATH; `wr-itil-reconcile-story-maps` shim ships in Slice 5.
|
|
226
|
+
- **ADR-052** — behavioural-tests default. Bats at `packages/itil/skills/capture-story-map/test/capture-story-map-behavioural.bats`.
|
|
227
|
+
- **Capture-story precedent** — `packages/itil/skills/capture-story/SKILL.md` — sibling skill at the story tier; capture-story-map mirrors with story-map-tier extensions (HTML encoding, no optional --rfc / --story-map flags).
|
|
228
|
+
|
|
229
|
+
$ARGUMENTS
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Behavioural fixtures for /wr-itil:capture-story-map (P170 Phase 2 Slice 3 — ADR-060).
|
|
3
|
+
#
|
|
4
|
+
# Mirrors capture-story-behavioural.bats with story-map-tier adjustments
|
|
5
|
+
# (HTML encoding, I3 + I4 invariants instead of I6 + I9, no optional
|
|
6
|
+
# --rfc / --story-map flags).
|
|
7
|
+
|
|
8
|
+
setup() {
|
|
9
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
|
|
10
|
+
SKILL_FILE="${REPO_ROOT}/packages/itil/skills/capture-story-map/SKILL.md"
|
|
11
|
+
HELPER_PROBLEM="${REPO_ROOT}/packages/itil/scripts/update-problem-references-section.sh"
|
|
12
|
+
HELPER_JTBD="${REPO_ROOT}/packages/itil/scripts/update-jtbd-references-section.sh"
|
|
13
|
+
|
|
14
|
+
TMPROOT=$(mktemp -d)
|
|
15
|
+
ORIG_DIR="$PWD"
|
|
16
|
+
cd "$TMPROOT"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
teardown() {
|
|
20
|
+
cd "$ORIG_DIR"
|
|
21
|
+
rm -rf "$TMPROOT"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@test "capture-story-map: SKILL.md exists" {
|
|
25
|
+
[ -f "$SKILL_FILE" ]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@test "capture-story-map: SKILL.md frontmatter declares wr-itil:capture-story-map name" {
|
|
29
|
+
run grep -E '^name: wr-itil:capture-story-map$' "$SKILL_FILE"
|
|
30
|
+
[ "$status" -eq 0 ]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@test "capture-story-map: SKILL.md names I3 trace-to-problem invariant" {
|
|
34
|
+
run grep -E 'I3 hard-block|I3.*trace-to-problem' "$SKILL_FILE"
|
|
35
|
+
[ "$status" -eq 0 ]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@test "capture-story-map: SKILL.md names I4 trace-to-JTBD invariant" {
|
|
39
|
+
run grep -E 'I4 hard-block|I4.*trace-to-JTBD' "$SKILL_FILE"
|
|
40
|
+
[ "$status" -eq 0 ]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@test "capture-story-map: next-ID formula computes 001 for empty story-maps directory" {
|
|
44
|
+
mkdir -p docs/story-maps/draft
|
|
45
|
+
local_max=$(ls docs/story-maps/*/STORY-MAP-*.html 2>/dev/null | sed 's|.*/STORY-MAP-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
46
|
+
next=$(printf '%03d' $(( 10#${local_max:-0} + 1 )))
|
|
47
|
+
[ "$next" = "001" ]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@test "capture-story-map: next-ID formula computes 003 when STORY-MAP-002 exists locally" {
|
|
51
|
+
mkdir -p docs/story-maps/draft docs/story-maps/in-progress
|
|
52
|
+
touch docs/story-maps/draft/STORY-MAP-002-foo.html
|
|
53
|
+
touch docs/story-maps/in-progress/STORY-MAP-001-bar.html
|
|
54
|
+
local_max=$(ls docs/story-maps/*/STORY-MAP-*.html 2>/dev/null | sed 's|.*/STORY-MAP-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
|
|
55
|
+
next=$(printf '%03d' $(( 10#${local_max:-0} + 1 )))
|
|
56
|
+
[ "$next" = "003" ]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@test "capture-story-map: update-problem-references-section.sh accepts 'Story Maps' section name" {
|
|
60
|
+
mkdir -p docs/problems/known-error docs/story-maps/draft
|
|
61
|
+
cat > docs/problems/known-error/170-test.md <<'EOF'
|
|
62
|
+
# P170: Test
|
|
63
|
+
|
|
64
|
+
## Story Maps
|
|
65
|
+
|
|
66
|
+
(empty)
|
|
67
|
+
EOF
|
|
68
|
+
run bash "$HELPER_PROBLEM" docs/problems/known-error/170-test.md "Story Maps"
|
|
69
|
+
[[ "$output" != *"unknown section-name"* ]]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@test "capture-story-map: update-jtbd-references-section.sh accepts 'Story Maps' section name" {
|
|
73
|
+
mkdir -p docs/jtbd/solo-developer docs/story-maps/draft
|
|
74
|
+
cat > docs/jtbd/solo-developer/JTBD-008-test.proposed.md <<'EOF'
|
|
75
|
+
# JTBD-008: Test
|
|
76
|
+
|
|
77
|
+
## Story Maps
|
|
78
|
+
|
|
79
|
+
(empty)
|
|
80
|
+
EOF
|
|
81
|
+
run bash "$HELPER_JTBD" docs/jtbd/solo-developer/JTBD-008-test.proposed.md "Story Maps"
|
|
82
|
+
[[ "$output" != *"unknown section-name"* ]]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@test "capture-story-map: SKILL.md prescribes HTML encoding per ADR-060 amendment 2026-05-12" {
|
|
86
|
+
run grep -E 'HTML5|<!DOCTYPE html>|encoding amendment 2026-05-12' "$SKILL_FILE"
|
|
87
|
+
[ "$status" -eq 0 ]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@test "capture-story-map: SKILL.md prescribes draft/ landing subdir" {
|
|
91
|
+
run grep -E 'docs/story-maps/draft/STORY-MAP' "$SKILL_FILE"
|
|
92
|
+
[ "$status" -eq 0 ]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@test "capture-story-map: SKILL.md prohibits inline style on data-bearing elements per STYLE-GUIDE.md" {
|
|
96
|
+
run grep -iE 'NO inline.*style.*data-bearing|prohibited inline.*style' "$SKILL_FILE"
|
|
97
|
+
[ "$status" -eq 0 ]
|
|
98
|
+
}
|