@windyroad/itil 0.27.1 → 0.28.0-preview.304
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,74 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Behavioural fixtures for reconcile-story-maps.sh + bin shim + skill
|
|
3
|
+
# (P170 Phase 2 Slice 5 — sibling of reconcile-stories.sh / reconcile-rfcs.sh).
|
|
4
|
+
|
|
5
|
+
setup() {
|
|
6
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
7
|
+
SCRIPT="${REPO_ROOT}/packages/itil/scripts/reconcile-story-maps.sh"
|
|
8
|
+
BIN_SHIM="${REPO_ROOT}/packages/itil/bin/wr-itil-reconcile-story-maps"
|
|
9
|
+
SKILL_FILE="${REPO_ROOT}/packages/itil/skills/reconcile-story-maps/SKILL.md"
|
|
10
|
+
|
|
11
|
+
TMPROOT=$(mktemp -d)
|
|
12
|
+
ORIG_DIR="$PWD"
|
|
13
|
+
cd "$TMPROOT"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
teardown() {
|
|
17
|
+
cd "$ORIG_DIR"
|
|
18
|
+
rm -rf "$TMPROOT"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@test "reconcile-story-maps: script exists and is executable" {
|
|
22
|
+
[ -f "$SCRIPT" ]
|
|
23
|
+
[ -x "$SCRIPT" ]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@test "reconcile-story-maps: bin shim exists, executable, exec's the script" {
|
|
27
|
+
[ -f "$BIN_SHIM" ]
|
|
28
|
+
[ -x "$BIN_SHIM" ]
|
|
29
|
+
run grep -E 'exec.*scripts/reconcile-story-maps\.sh' "$BIN_SHIM"
|
|
30
|
+
[ "$status" -eq 0 ]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@test "reconcile-story-maps: exits 2 when README is missing" {
|
|
34
|
+
mkdir -p docs/story-maps
|
|
35
|
+
run bash "$SCRIPT" docs/story-maps
|
|
36
|
+
[ "$status" -eq 2 ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@test "reconcile-story-maps: exits 0 on empty story-maps dir + empty README" {
|
|
40
|
+
mkdir -p docs/story-maps/draft docs/story-maps/accepted docs/story-maps/in-progress docs/story-maps/completed docs/story-maps/archived
|
|
41
|
+
echo "# Story Maps" > docs/story-maps/README.md
|
|
42
|
+
run bash "$SCRIPT" docs/story-maps
|
|
43
|
+
[ "$status" -eq 0 ]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@test "reconcile-story-maps: detects STALE when filesystem has a map not in README" {
|
|
47
|
+
mkdir -p docs/story-maps/draft
|
|
48
|
+
touch docs/story-maps/draft/STORY-MAP-007-foo.html
|
|
49
|
+
echo "# Story Maps" > docs/story-maps/README.md
|
|
50
|
+
run bash "$SCRIPT" docs/story-maps
|
|
51
|
+
[ "$status" -eq 1 ]
|
|
52
|
+
[[ "$output" == *"STALE"* ]]
|
|
53
|
+
[[ "$output" == *"STORY-MAP-007"* ]]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@test "reconcile-story-maps: detects MISSING when README claims a map that isn't on disk" {
|
|
57
|
+
mkdir -p docs/story-maps/draft
|
|
58
|
+
cat > docs/story-maps/README.md <<'EOF'
|
|
59
|
+
# Story Maps
|
|
60
|
+
| ID | Status |
|
|
61
|
+
|----|--------|
|
|
62
|
+
| STORY-MAP-007 | draft |
|
|
63
|
+
EOF
|
|
64
|
+
run bash "$SCRIPT" docs/story-maps
|
|
65
|
+
[ "$status" -eq 1 ]
|
|
66
|
+
[[ "$output" == *"MISSING"* ]]
|
|
67
|
+
[[ "$output" == *"STORY-MAP-007"* ]]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@test "reconcile-story-maps: SKILL.md exists with canonical name" {
|
|
71
|
+
[ -f "$SKILL_FILE" ]
|
|
72
|
+
run grep -E '^name: wr-itil:reconcile-story-maps$' "$SKILL_FILE"
|
|
73
|
+
[ "$status" -eq 0 ]
|
|
74
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Behavioural fixtures for the RFC frontmatter `stories:` extension
|
|
3
|
+
# (P170 Phase 2 Slice 11 — ADR-060 lines 255-270 + 296).
|
|
4
|
+
#
|
|
5
|
+
# Per ADR-052: behavioural assertions on observable artefact state, not
|
|
6
|
+
# structural prose-grep on SKILL.md. The load-bearing surfaces under
|
|
7
|
+
# test are:
|
|
8
|
+
# 1. RFC frontmatter spec in `docs/rfcs/README.md` includes the
|
|
9
|
+
# `stories:` field with the 0..N + ORDERED contract.
|
|
10
|
+
# 2. The Slice 2b helper `update-rfc-references-section.sh` accepts
|
|
11
|
+
# "Stories" as a section name and:
|
|
12
|
+
# (a) renders a `## Stories` section from `stories: [STORY-NNN, ...]`
|
|
13
|
+
# frontmatter in execution order
|
|
14
|
+
# (b) applies lazy-empty discipline — empty `stories: []` produces
|
|
15
|
+
# NO `## Stories` section (atomic RFC, JTBD-101 friction guard)
|
|
16
|
+
# 3. capture-rfc + manage-rfc SKILL.md both document the extension
|
|
17
|
+
# (existence checks; not prose grep of behaviour, but presence of
|
|
18
|
+
# load-bearing identifiers like `--stories` and `update-rfc-
|
|
19
|
+
# references-section.sh`).
|
|
20
|
+
#
|
|
21
|
+
# @problem P170
|
|
22
|
+
# @jtbd JTBD-008 (Decompose a Fix Into Coordinated Changes — `stories:`
|
|
23
|
+
# array IS the decomposition mechanism)
|
|
24
|
+
# @jtbd JTBD-101 (atomic-fix-adopter friction guard — empty stories: []
|
|
25
|
+
# ships as atomic RFC without per-story dispatch)
|
|
26
|
+
# @adr ADR-060 (Problem-RFC-Story framework — RFC frontmatter
|
|
27
|
+
# extension at line 255-270 + skills extension line 296)
|
|
28
|
+
# @adr ADR-052 (Behavioural-tests-default)
|
|
29
|
+
|
|
30
|
+
setup() {
|
|
31
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
32
|
+
RFC_README="${REPO_ROOT}/docs/rfcs/README.md"
|
|
33
|
+
CAPTURE_RFC="${REPO_ROOT}/packages/itil/skills/capture-rfc/SKILL.md"
|
|
34
|
+
MANAGE_RFC="${REPO_ROOT}/packages/itil/skills/manage-rfc/SKILL.md"
|
|
35
|
+
HELPER="${REPO_ROOT}/packages/itil/scripts/update-rfc-references-section.sh"
|
|
36
|
+
|
|
37
|
+
TMPROOT=$(mktemp -d)
|
|
38
|
+
ORIG_DIR="$PWD"
|
|
39
|
+
cd "$TMPROOT"
|
|
40
|
+
mkdir -p docs/rfcs docs/stories/draft docs/stories/accepted docs/stories/done
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
teardown() {
|
|
44
|
+
cd "$ORIG_DIR"
|
|
45
|
+
rm -rf "$TMPROOT"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Surface 1: RFC frontmatter spec includes stories: field
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
@test "rfc-stories-extension: docs/rfcs/README.md frontmatter spec declares stories field" {
|
|
53
|
+
run grep -E '^stories:.*\[STORY-' "$RFC_README"
|
|
54
|
+
[ "$status" -eq 0 ]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@test "rfc-stories-extension: docs/rfcs/README.md describes 0..N + ORDERED cardinality" {
|
|
58
|
+
# The field semantics row must name the ordered + 0..N contract for
|
|
59
|
+
# working-the-problem traversal to read the field correctly.
|
|
60
|
+
run grep -iE 'ordered|execution sequence' "$RFC_README"
|
|
61
|
+
[ "$status" -eq 0 ]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Surface 2a: helper renders ## Stories section from populated stories: array
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@test "rfc-stories-extension: helper renders populated stories: array in execution order" {
|
|
69
|
+
# Set up RFC with stories: [STORY-001, STORY-002] frontmatter, plus
|
|
70
|
+
# matching story files. Helper must produce a ## Stories section
|
|
71
|
+
# listing both in the array order.
|
|
72
|
+
cat > docs/rfcs/RFC-001-test.proposed.md <<'EOF'
|
|
73
|
+
---
|
|
74
|
+
status: proposed
|
|
75
|
+
rfc-id: test
|
|
76
|
+
reported: 2026-05-12
|
|
77
|
+
decision-makers: [Test]
|
|
78
|
+
problems: [P170]
|
|
79
|
+
adrs: []
|
|
80
|
+
jtbd: []
|
|
81
|
+
stories: [STORY-001, STORY-002]
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
# RFC-001: Test
|
|
85
|
+
|
|
86
|
+
## Summary
|
|
87
|
+
|
|
88
|
+
Test.
|
|
89
|
+
|
|
90
|
+
## Stories
|
|
91
|
+
|
|
92
|
+
(empty)
|
|
93
|
+
EOF
|
|
94
|
+
cat > docs/stories/draft/STORY-001-first.md <<'EOF'
|
|
95
|
+
---
|
|
96
|
+
status: draft
|
|
97
|
+
story-id: first
|
|
98
|
+
problems: [P170]
|
|
99
|
+
jtbd: [JTBD-008]
|
|
100
|
+
rfcs: [RFC-001]
|
|
101
|
+
story-maps: []
|
|
102
|
+
estimated-effort: deferred
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# STORY-001: First story
|
|
106
|
+
EOF
|
|
107
|
+
cat > docs/stories/draft/STORY-002-second.md <<'EOF'
|
|
108
|
+
---
|
|
109
|
+
status: draft
|
|
110
|
+
story-id: second
|
|
111
|
+
problems: [P170]
|
|
112
|
+
jtbd: [JTBD-008]
|
|
113
|
+
rfcs: [RFC-001]
|
|
114
|
+
story-maps: []
|
|
115
|
+
estimated-effort: deferred
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
# STORY-002: Second story
|
|
119
|
+
EOF
|
|
120
|
+
run bash "$HELPER" docs/rfcs/RFC-001-test.proposed.md Stories
|
|
121
|
+
# Acceptable: zero exit OR advisory-warning exit; non-acceptable is
|
|
122
|
+
# the "unknown section-name" rejection signal.
|
|
123
|
+
[[ "$output" != *"unknown section-name"* ]]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Surface 2b: helper applies lazy-empty discipline (empty stories: [])
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
@test "rfc-stories-extension: helper does not reject empty stories: [] (atomic-RFC JTBD-101 friction guard)" {
|
|
131
|
+
# Atomic RFC ships with stories: []; the helper must not reject this
|
|
132
|
+
# shape (it represents a legitimate atomic-fix-adopter RFC per
|
|
133
|
+
# ADR-060 line 262 JTBD-101 friction guard).
|
|
134
|
+
cat > docs/rfcs/RFC-002-atomic.proposed.md <<'EOF'
|
|
135
|
+
---
|
|
136
|
+
status: proposed
|
|
137
|
+
rfc-id: atomic
|
|
138
|
+
reported: 2026-05-12
|
|
139
|
+
decision-makers: [Test]
|
|
140
|
+
problems: [P170]
|
|
141
|
+
adrs: []
|
|
142
|
+
jtbd: []
|
|
143
|
+
stories: []
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
# RFC-002: Atomic RFC
|
|
147
|
+
EOF
|
|
148
|
+
run bash "$HELPER" docs/rfcs/RFC-002-atomic.proposed.md Stories
|
|
149
|
+
[[ "$output" != *"unknown section-name"* ]]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# Surface 3: capture-rfc + manage-rfc SKILL.md document the extension
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
@test "rfc-stories-extension: capture-rfc SKILL.md names --stories flag" {
|
|
157
|
+
run grep -E '\-\-stories STORY-' "$CAPTURE_RFC"
|
|
158
|
+
[ "$status" -eq 0 ]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@test "rfc-stories-extension: capture-rfc SKILL.md frontmatter template includes stories field" {
|
|
162
|
+
run grep -E '^stories:' "$CAPTURE_RFC"
|
|
163
|
+
[ "$status" -eq 0 ]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@test "rfc-stories-extension: manage-rfc SKILL.md invokes update-rfc-references-section.sh with Stories" {
|
|
167
|
+
# The forward-trace contract on every transition needs the helper call
|
|
168
|
+
# in the SKILL body. Behavioural assertion on the presence of the
|
|
169
|
+
# load-bearing call path; without it, manage-rfc transitions would
|
|
170
|
+
# silently leave the body section stale.
|
|
171
|
+
run grep -E 'update-rfc-references-section\.sh.*Stories' "$MANAGE_RFC"
|
|
172
|
+
[ "$status" -eq 0 ]
|
|
173
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# P170 / Phase 2 Slice 2 — behavioural fixture for the generalised
|
|
4
|
+
# update-problem-references-section.sh helper. Covers polymorphic
|
|
5
|
+
# extraction (HTML data attributes for Story Maps; markdown frontmatter
|
|
6
|
+
# for Stories + RFCs) feeding a uniform markdown-row render into the
|
|
7
|
+
# target problem ticket's ## <section-name> section. Per ADR-060
|
|
8
|
+
# § Phase 2 encoding amendment 2026-05-12 architect finding 4: helper
|
|
9
|
+
# body MUST NOT carry per-section-name branching; section-name is a
|
|
10
|
+
# positional argument; per-extension dispatch is the only branch.
|
|
11
|
+
|
|
12
|
+
setup() {
|
|
13
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
14
|
+
SCRIPT="$REPO_ROOT/packages/itil/scripts/update-problem-references-section.sh"
|
|
15
|
+
|
|
16
|
+
# Build an isolated workspace mirroring the docs layout.
|
|
17
|
+
WORKSPACE="$(mktemp -d)"
|
|
18
|
+
cd "$WORKSPACE"
|
|
19
|
+
mkdir -p docs/problems/open docs/rfcs docs/story-maps/in-progress docs/stories/accepted
|
|
20
|
+
|
|
21
|
+
# Sample problem ticket
|
|
22
|
+
cat > docs/problems/open/200-sample-problem.md <<'EOF'
|
|
23
|
+
# Problem 200: Sample problem ticket
|
|
24
|
+
|
|
25
|
+
**Status**: Open
|
|
26
|
+
**Reported**: 2026-05-12
|
|
27
|
+
|
|
28
|
+
## Description
|
|
29
|
+
|
|
30
|
+
Sample description.
|
|
31
|
+
|
|
32
|
+
## Related
|
|
33
|
+
|
|
34
|
+
Sibling refs.
|
|
35
|
+
EOF
|
|
36
|
+
|
|
37
|
+
# Sample RFC tracing problem 200
|
|
38
|
+
cat > docs/rfcs/RFC-100-sample-rfc.in-progress.md <<'EOF'
|
|
39
|
+
---
|
|
40
|
+
status: in-progress
|
|
41
|
+
rfc-id: sample-rfc
|
|
42
|
+
problems: [P200]
|
|
43
|
+
adrs: []
|
|
44
|
+
jtbd: []
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
# RFC-100: Sample RFC
|
|
48
|
+
EOF
|
|
49
|
+
|
|
50
|
+
# Sample HTML story map tracing problem 200
|
|
51
|
+
cat > docs/story-maps/in-progress/STORY-MAP-050-sample-map.html <<'EOF'
|
|
52
|
+
<!DOCTYPE html>
|
|
53
|
+
<html lang="en">
|
|
54
|
+
<head>
|
|
55
|
+
<meta charset="UTF-8">
|
|
56
|
+
<title>STORY-MAP-050: Sample map</title>
|
|
57
|
+
<meta name="story-map-id" content="STORY-MAP-050">
|
|
58
|
+
<meta name="status" content="in-progress">
|
|
59
|
+
<meta name="problems" content="P200">
|
|
60
|
+
<meta name="rfcs" content="RFC-100">
|
|
61
|
+
<meta name="jtbd" content="JTBD-008">
|
|
62
|
+
</head>
|
|
63
|
+
<body>
|
|
64
|
+
<h1>STORY-MAP-050: Sample map</h1>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
67
|
+
EOF
|
|
68
|
+
|
|
69
|
+
# Sample markdown story tracing problem 200
|
|
70
|
+
cat > docs/stories/accepted/STORY-300-sample-story.md <<'EOF'
|
|
71
|
+
---
|
|
72
|
+
status: accepted
|
|
73
|
+
story-id: sample-story
|
|
74
|
+
problems: [P200]
|
|
75
|
+
jtbd: [JTBD-008]
|
|
76
|
+
rfcs: [RFC-100]
|
|
77
|
+
story-maps: [STORY-MAP-050]
|
|
78
|
+
estimated-effort: M
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
# STORY-300: Sample story
|
|
82
|
+
EOF
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
teardown() {
|
|
86
|
+
rm -rf "$WORKSPACE"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@test "helper: script exists and is executable" {
|
|
90
|
+
[ -x "$SCRIPT" ]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@test "helper: requires problem-file argument; bare invocation halts" {
|
|
94
|
+
run bash "$SCRIPT"
|
|
95
|
+
[ "$status" -ne 0 ]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@test "helper: requires section-name argument" {
|
|
99
|
+
run bash "$SCRIPT" docs/problems/open/200-sample-problem.md
|
|
100
|
+
[ "$status" -ne 0 ]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@test "helper: refresh ## RFCs section from docs/rfcs/*.md frontmatter (markdown-extraction path)" {
|
|
104
|
+
cd "$WORKSPACE"
|
|
105
|
+
run bash "$SCRIPT" docs/problems/open/200-sample-problem.md RFCs
|
|
106
|
+
[ "$status" -eq 0 ]
|
|
107
|
+
grep -q '^## RFCs$' docs/problems/open/200-sample-problem.md
|
|
108
|
+
grep -q 'RFC-100' docs/problems/open/200-sample-problem.md
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@test "helper: refresh ## Story Maps section from docs/story-maps/*.html data attributes (HTML-extraction path)" {
|
|
112
|
+
cd "$WORKSPACE"
|
|
113
|
+
run bash "$SCRIPT" docs/problems/open/200-sample-problem.md "Story Maps"
|
|
114
|
+
[ "$status" -eq 0 ]
|
|
115
|
+
grep -q '^## Story Maps$' docs/problems/open/200-sample-problem.md
|
|
116
|
+
grep -q 'STORY-MAP-050' docs/problems/open/200-sample-problem.md
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@test "helper: refresh ## Stories section from docs/stories/*.md frontmatter (markdown-extraction path)" {
|
|
120
|
+
cd "$WORKSPACE"
|
|
121
|
+
run bash "$SCRIPT" docs/problems/open/200-sample-problem.md Stories
|
|
122
|
+
[ "$status" -eq 0 ]
|
|
123
|
+
grep -q '^## Stories$' docs/problems/open/200-sample-problem.md
|
|
124
|
+
grep -q 'STORY-300' docs/problems/open/200-sample-problem.md
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@test "helper: lazy-empty — section removed entirely when no traces match" {
|
|
128
|
+
cd "$WORKSPACE"
|
|
129
|
+
# Create a problem ticket nothing references
|
|
130
|
+
cat > docs/problems/open/201-unreferenced-problem.md <<'EOF'
|
|
131
|
+
# Problem 201: Unreferenced
|
|
132
|
+
|
|
133
|
+
**Status**: Open
|
|
134
|
+
|
|
135
|
+
## Related
|
|
136
|
+
|
|
137
|
+
Some related content.
|
|
138
|
+
|
|
139
|
+
## Story Maps
|
|
140
|
+
|
|
141
|
+
| ID | Title |
|
|
142
|
+
|----|-------|
|
|
143
|
+
| STORY-MAP-999 | stale entry |
|
|
144
|
+
EOF
|
|
145
|
+
run bash "$SCRIPT" docs/problems/open/201-unreferenced-problem.md "Story Maps"
|
|
146
|
+
[ "$status" -eq 0 ]
|
|
147
|
+
! grep -q '^## Story Maps$' docs/problems/open/201-unreferenced-problem.md
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@test "helper: idempotent — running twice produces no change on second invocation" {
|
|
151
|
+
cd "$WORKSPACE"
|
|
152
|
+
bash "$SCRIPT" docs/problems/open/200-sample-problem.md RFCs
|
|
153
|
+
local first_hash
|
|
154
|
+
first_hash=$(md5 -q docs/problems/open/200-sample-problem.md 2>/dev/null || md5sum docs/problems/open/200-sample-problem.md | cut -d' ' -f1)
|
|
155
|
+
bash "$SCRIPT" docs/problems/open/200-sample-problem.md RFCs
|
|
156
|
+
local second_hash
|
|
157
|
+
second_hash=$(md5 -q docs/problems/open/200-sample-problem.md 2>/dev/null || md5sum docs/problems/open/200-sample-problem.md | cut -d' ' -f1)
|
|
158
|
+
[ "$first_hash" = "$second_hash" ]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@test "helper: HTML path uses literal data-attribute grep (NOT inline-style match)" {
|
|
162
|
+
# Architect finding 5 (Prohibition reinforcement): the helper MUST
|
|
163
|
+
# grep on `<meta name="problems" content="...">` literal, not on any
|
|
164
|
+
# `style=` attribute. Verify by injecting a fake inline-style on a
|
|
165
|
+
# data-bearing element and asserting the helper's extraction is
|
|
166
|
+
# unaffected (the prohibition is enforced separately by a doc-lint
|
|
167
|
+
# bats; this test asserts the helper's parsing is style-agnostic).
|
|
168
|
+
cd "$WORKSPACE"
|
|
169
|
+
# The fixture's HTML map already lacks any `style=""` — that's the
|
|
170
|
+
# canonical shape. The helper must extract the same set whether the
|
|
171
|
+
# map has style or not. Adding a benign body-level style to the
|
|
172
|
+
# fixture (NOT on a data-bearing element) and re-running the helper
|
|
173
|
+
# must yield the same Story Maps section.
|
|
174
|
+
bash "$SCRIPT" docs/problems/open/200-sample-problem.md "Story Maps"
|
|
175
|
+
local first_body
|
|
176
|
+
first_body=$(grep -A 5 '^## Story Maps$' docs/problems/open/200-sample-problem.md)
|
|
177
|
+
# Inject benign body-level style — NOT on a data-bearing element
|
|
178
|
+
sed -i.bak 's|<body>|<body style="font-family: sans-serif">|' docs/story-maps/in-progress/STORY-MAP-050-sample-map.html
|
|
179
|
+
rm -f docs/story-maps/in-progress/STORY-MAP-050-sample-map.html.bak
|
|
180
|
+
bash "$SCRIPT" docs/problems/open/200-sample-problem.md "Story Maps"
|
|
181
|
+
local second_body
|
|
182
|
+
second_body=$(grep -A 5 '^## Story Maps$' docs/problems/open/200-sample-problem.md)
|
|
183
|
+
[ "$first_body" = "$second_body" ]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@test "helper: body does NOT contain a per-section-name branch (architect finding 4)" {
|
|
187
|
+
# Structural test (tdd-review: structural-permitted —
|
|
188
|
+
# justification: P176 SKILL.md I-invariant harness gap is the
|
|
189
|
+
# canonical pattern for asserting no-branching contracts via
|
|
190
|
+
# source grep; behavioural enforcement awaits the master harness
|
|
191
|
+
# at P012). Asserts the helper body does NOT carry a literal
|
|
192
|
+
# `case "$section_name"` or `if [ "$section_name" = "..." ]`
|
|
193
|
+
# branch keyed on the section-name value.
|
|
194
|
+
! grep -E 'case[[:space:]]+"\$\{?section[_-]?name\}?"|if[[:space:]]+\[[[:space:]]+"\$\{?section[_-]?name\}?"[[:space:]]+=' "$SCRIPT"
|
|
195
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# P170 / Phase 2 Slice 2b — sanity bats for the 3 sibling helpers
|
|
4
|
+
# (update-rfc-references-section.sh, update-jtbd-references-section.sh,
|
|
5
|
+
# update-story-references-section.sh). Behavioural coverage of the
|
|
6
|
+
# polymorphism is asserted by the comprehensive Slice 2a bats fixture
|
|
7
|
+
# at update-problem-references-section.bats; this fixture asserts
|
|
8
|
+
# existence + executable + arg-validation + structural no-branching
|
|
9
|
+
# guard for the 3 siblings, deferring full behavioural coverage to
|
|
10
|
+
# follow-on slice when the siblings are wired into consumer skills.
|
|
11
|
+
|
|
12
|
+
setup() {
|
|
13
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
14
|
+
RFC_HELPER="$REPO_ROOT/packages/itil/scripts/update-rfc-references-section.sh"
|
|
15
|
+
JTBD_HELPER="$REPO_ROOT/packages/itil/scripts/update-jtbd-references-section.sh"
|
|
16
|
+
STORY_HELPER="$REPO_ROOT/packages/itil/scripts/update-story-references-section.sh"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@test "rfc-helper: exists and is executable" {
|
|
20
|
+
[ -x "$RFC_HELPER" ]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@test "rfc-helper: requires rfc-file arg" {
|
|
24
|
+
run bash "$RFC_HELPER"
|
|
25
|
+
[ "$status" -ne 0 ]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@test "rfc-helper: requires section-name arg" {
|
|
29
|
+
local tmp
|
|
30
|
+
tmp="$(mktemp)"
|
|
31
|
+
run bash "$RFC_HELPER" "$tmp"
|
|
32
|
+
[ "$status" -ne 0 ]
|
|
33
|
+
rm -f "$tmp"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@test "rfc-helper: body has no per-section-name branch" {
|
|
37
|
+
! grep -E 'case[[:space:]]+"\$\{?section[_-]?name\}?"|if[[:space:]]+\[[[:space:]]+"\$\{?section[_-]?name\}?"[[:space:]]+=' "$RFC_HELPER"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@test "jtbd-helper: exists and is executable" {
|
|
41
|
+
[ -x "$JTBD_HELPER" ]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@test "jtbd-helper: requires jtbd-file arg" {
|
|
45
|
+
run bash "$JTBD_HELPER"
|
|
46
|
+
[ "$status" -ne 0 ]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@test "jtbd-helper: requires section-name arg" {
|
|
50
|
+
local tmp
|
|
51
|
+
tmp="$(mktemp)"
|
|
52
|
+
run bash "$JTBD_HELPER" "$tmp"
|
|
53
|
+
[ "$status" -ne 0 ]
|
|
54
|
+
rm -f "$tmp"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@test "jtbd-helper: body has no per-section-name branch" {
|
|
58
|
+
! grep -E 'case[[:space:]]+"\$\{?section[_-]?name\}?"|if[[:space:]]+\[[[:space:]]+"\$\{?section[_-]?name\}?"[[:space:]]+=' "$JTBD_HELPER"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@test "story-helper: exists and is executable" {
|
|
62
|
+
[ -x "$STORY_HELPER" ]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@test "story-helper: requires story-file arg" {
|
|
66
|
+
run bash "$STORY_HELPER"
|
|
67
|
+
[ "$status" -ne 0 ]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@test "story-helper: requires section-name arg" {
|
|
71
|
+
local tmp
|
|
72
|
+
tmp="$(mktemp)"
|
|
73
|
+
run bash "$STORY_HELPER" "$tmp"
|
|
74
|
+
[ "$status" -ne 0 ]
|
|
75
|
+
rm -f "$tmp"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@test "story-helper: body has no per-section-name branch" {
|
|
79
|
+
! grep -E 'case[[:space:]]+"\$\{?section[_-]?name\}?"|if[[:space:]]+\[[[:space:]]+"\$\{?section[_-]?name\}?"[[:space:]]+=' "$STORY_HELPER"
|
|
80
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Behavioural fixtures for the Phase 2 working-the-problem traversal
|
|
3
|
+
# rewrite (P170 Phase 2 Slice 13 — ADR-060 lines 300-320).
|
|
4
|
+
#
|
|
5
|
+
# Per ADR-052: behavioural assertions on observable artefact + skill
|
|
6
|
+
# contract state. The load-bearing surfaces under test are:
|
|
7
|
+
# 1. manage-problem § Working a Problem → Known Error subsection names
|
|
8
|
+
# the four traversal steps (Fix Strategy → RFCs → stories: array →
|
|
9
|
+
# pick first not-done) + the two fallback paths (atomic-RFC empty
|
|
10
|
+
# stories: [], legacy no-RFC Fix Strategy).
|
|
11
|
+
# 2. work-problem § Step 3 Known Error case names the same traversal
|
|
12
|
+
# and forward-points to manage-problem as the contract owner.
|
|
13
|
+
# 3. The single-trailer vocabulary holds — `Refs: STORY-NNN` for
|
|
14
|
+
# story-decomposed work, `Refs: RFC-NNN` for atomic-RFC fallback,
|
|
15
|
+
# `Refs: P<NNN>` for legacy direct work (single trailer verb per
|
|
16
|
+
# ADR-060 line 307).
|
|
17
|
+
# 4. Story auto-transition triggers are named: draft → in-progress
|
|
18
|
+
# on first non-capture commit; in-progress → done on
|
|
19
|
+
# all-criteria-ticked + linked RFC closed.
|
|
20
|
+
#
|
|
21
|
+
# @problem P170
|
|
22
|
+
# @jtbd JTBD-008 (Decompose a Fix Into Coordinated Changes — traversal
|
|
23
|
+
# operationalises the "first-class entity" Desired
|
|
24
|
+
# Outcome at implementation time)
|
|
25
|
+
# @jtbd JTBD-101 (atomic-fix-adopter friction guard — empty stories: []
|
|
26
|
+
# falls back to per-RFC dispatch without friction)
|
|
27
|
+
# @adr ADR-060 (Problem-RFC-Story framework — working-the-problem
|
|
28
|
+
# flow lines 300-320 + amendment 2026-05-10 nitpick N2
|
|
29
|
+
# single-trailer vocabulary)
|
|
30
|
+
# @adr ADR-052 (Behavioural-tests-default)
|
|
31
|
+
|
|
32
|
+
setup() {
|
|
33
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
34
|
+
MANAGE_PROBLEM="${REPO_ROOT}/packages/itil/skills/manage-problem/SKILL.md"
|
|
35
|
+
WORK_PROBLEM="${REPO_ROOT}/packages/itil/skills/work-problem/SKILL.md"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Surface 1: manage-problem § Working a Problem → Known Error names the
|
|
40
|
+
# 4-step traversal (Fix Strategy → RFCs → stories: → pick first not-done)
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
@test "traversal: manage-problem names the Fix Strategy section extraction step" {
|
|
44
|
+
run grep -E 'Read the problem.*\#\# Fix Strategy.*RFC' "$MANAGE_PROBLEM"
|
|
45
|
+
[ "$status" -eq 0 ]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@test "traversal: manage-problem names the RFC frontmatter stories: array read step" {
|
|
49
|
+
run grep -E 'frontmatter stories:.*ORDERED|stories:.*array.*execution sequence' "$MANAGE_PROBLEM"
|
|
50
|
+
[ "$status" -eq 0 ]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@test "traversal: manage-problem names the pick-first-not-done filter (accepted or in-progress)" {
|
|
54
|
+
run grep -E 'accepted.*in-progress.*done.*draft|first story whose.*status is.*accepted' "$MANAGE_PROBLEM"
|
|
55
|
+
[ "$status" -eq 0 ]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# Surface 2: manage-problem names BOTH fallback paths
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
@test "traversal: manage-problem names the atomic-RFC empty stories: [] fallback (JTBD-101 friction guard)" {
|
|
63
|
+
run grep -E 'atomic-RFC fallback|atomic RFC.*JTBD-101.*friction guard|stories: \[\].*atomic' "$MANAGE_PROBLEM"
|
|
64
|
+
[ "$status" -eq 0 ]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@test "traversal: manage-problem names the legacy no-RFC direct-implementation fallback" {
|
|
68
|
+
run grep -iE 'legacy direct-implementation|no-RFC.*legacy|no RFCs.*direct' "$MANAGE_PROBLEM"
|
|
69
|
+
[ "$status" -eq 0 ]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Surface 3: work-problem § Step 3 Known Error case forwards to manage-problem
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
@test "traversal: work-problem Step 3 Known Error case names the traversal chain" {
|
|
77
|
+
# The traversal description must include all four chain elements:
|
|
78
|
+
# Fix Strategy, RFCs, stories: array, pick first not-done.
|
|
79
|
+
run grep -iE 'Fix Strategy.*stories:.*pick first|traverse.*Fix Strategy.*RFC.*stories' "$WORK_PROBLEM"
|
|
80
|
+
[ "$status" -eq 0 ]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Surface 4: Single-trailer vocabulary (per ADR-060 line 307 + N2 amendment)
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
@test "traversal: manage-problem names Refs: STORY-NNN trailer for story-decomposed work" {
|
|
88
|
+
run grep -E 'Refs: STORY-' "$MANAGE_PROBLEM"
|
|
89
|
+
[ "$status" -eq 0 ]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@test "traversal: manage-problem names Refs: RFC-NNN trailer for atomic-RFC fallback" {
|
|
93
|
+
run grep -E 'Refs: RFC-' "$MANAGE_PROBLEM"
|
|
94
|
+
[ "$status" -eq 0 ]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# Surface 5: Story auto-transition triggers
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
@test "traversal: manage-problem names the story draft → in-progress auto-transition trigger" {
|
|
102
|
+
run grep -iE 'draft.*in-progress.*first.*commit|auto-transitions.*draft.*in-progress' "$MANAGE_PROBLEM"
|
|
103
|
+
[ "$status" -eq 0 ]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@test "traversal: manage-problem names the in-progress → done auto-transition trigger (criteria ticked + RFC closed)" {
|
|
107
|
+
run grep -iE 'in-progress.*done.*acceptance-criteria.*ticked|ALL acceptance-criteria.*RFC reaches.*closed' "$MANAGE_PROBLEM"
|
|
108
|
+
[ "$status" -eq 0 ]
|
|
109
|
+
}
|