@windyroad/itil 1.1.2 → 1.2.0-preview.1102
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/bin/wr-itil-next-rfc-id +51 -0
- package/package.json +1 -1
- package/scripts/check-fix-rfc-trace.sh +110 -43
- package/scripts/next-rfc-id.sh +98 -0
- package/scripts/story-map-query.mjs +76 -3
- package/scripts/story-map-query.sh +1 -1
- package/skills/capture-rfc/SKILL.md +13 -11
- package/skills/manage-problem/SKILL.md +38 -10
- package/skills/transition-problem/SKILL.md +1 -1
- package/skills/work-problems/SKILL.md +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Generated by scripts/sync-shim-wrappers.sh from
|
|
3
|
+
# packages/shared/lib/shim-wrapper-template.sh. DO NOT EDIT individual
|
|
4
|
+
# shim files in packages/*/bin/wr-* directly; edit the template + run
|
|
5
|
+
# `npm run sync:shim-wrappers` to regenerate.
|
|
6
|
+
#
|
|
7
|
+
# Resolution (ADR-080):
|
|
8
|
+
# 1. If the wrapper's parent dir is semver-shaped, treat as installed-
|
|
9
|
+
# cache execution and resolve to the highest-version sibling's
|
|
10
|
+
# scripts/ entry below.
|
|
11
|
+
# 2. Otherwise (parent dir is e.g. `architect`), treat as source-
|
|
12
|
+
# monorepo execution and dispatch to own scripts/. The source-repo-
|
|
13
|
+
# guard `exec` is the anchor parsed by
|
|
14
|
+
# packages/retrospective/scripts/check-tarball-shipped-shims.sh.
|
|
15
|
+
# 3. If the cache parent contains zero semver-shaped siblings, exit
|
|
16
|
+
# 127 with a stderr message naming the cache parent (per SQ-080-2).
|
|
17
|
+
#
|
|
18
|
+
# @adr ADR-080 (highest-version-wins shim wrapper plugin scaffold)
|
|
19
|
+
# @adr ADR-049 (plugin-bundled scripts resolve via bin/ on $PATH — amended)
|
|
20
|
+
# @problem P343 (mid-session staleness window)
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
SHIM_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
25
|
+
OWN_VERSION_DIR="$(dirname "$SHIM_DIR")"
|
|
26
|
+
OWN_VERSION_NAME="$(basename "$OWN_VERSION_DIR")"
|
|
27
|
+
CACHE_PARENT="$(dirname "$OWN_VERSION_DIR")"
|
|
28
|
+
|
|
29
|
+
SEMVER_RE='^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'
|
|
30
|
+
|
|
31
|
+
# Source-repo guard: own parent dir is NOT semver → dispatch to own scripts/.
|
|
32
|
+
if ! [[ "$OWN_VERSION_NAME" =~ $SEMVER_RE ]]; then
|
|
33
|
+
exec "$SHIM_DIR/../scripts/next-rfc-id.sh" "$@"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Cache execution: pick the highest-semver sibling under CACHE_PARENT.
|
|
37
|
+
HIGHEST=""
|
|
38
|
+
while IFS= read -r dir; do
|
|
39
|
+
name="$(basename "$dir")"
|
|
40
|
+
[[ "$name" =~ $SEMVER_RE ]] || continue
|
|
41
|
+
if [[ -z "$HIGHEST" ]] || [[ "$(printf '%s\n%s\n' "$HIGHEST" "$name" | sort -V | tail -1)" == "$name" ]]; then
|
|
42
|
+
HIGHEST="$name"
|
|
43
|
+
fi
|
|
44
|
+
done < <(find "$CACHE_PARENT" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
|
|
45
|
+
|
|
46
|
+
if [[ -z "$HIGHEST" ]]; then
|
|
47
|
+
printf 'wr-shim: no cached versions in %s\n' "$CACHE_PARENT" >&2
|
|
48
|
+
exit 127
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
exec "$CACHE_PARENT/$HIGHEST/scripts/next-rfc-id.sh" "$@"
|
package/package.json
CHANGED
|
@@ -1,67 +1,88 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# packages/itil/scripts/check-fix-rfc-trace.sh
|
|
3
3
|
#
|
|
4
|
-
# The load-bearing PREDICATE half of the fix-time
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# /wr-itil:work-problems).
|
|
4
|
+
# The load-bearing PREDICATE half of the fix-time trace gate. Before fix work
|
|
5
|
+
# commences on a Known Error, the framework requires a fix vehicle that traces
|
|
6
|
+
# the problem. This script answers the deterministic question — "does anything
|
|
7
|
+
# propose a fix for this problem?" — and, when nothing does, emits a directive
|
|
8
|
+
# on stdout telling the calling skill what to do about it.
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
10
|
+
# A FIX PROPOSAL IS A RELEASE ROW ON A STORY MAP, NEVER A DOCUMENT. This
|
|
11
|
+
# predicate used to scan the RFC document directory alone, and its directive
|
|
12
|
+
# told the caller to create a new document — the artefact the current decision
|
|
13
|
+
# retired. It now answers from the UNION of both tiers:
|
|
14
|
+
#
|
|
15
|
+
# - a legacy RFC document whose frontmatter `problems:` array names the PID;
|
|
16
|
+
# - a release row carrying an RFC identity whose cards' stories name the PID.
|
|
17
|
+
#
|
|
18
|
+
# The union is a strict widening: nothing that read as traced before reads as
|
|
19
|
+
# untraced now, so repointing the reader cannot hard-stop work that used to
|
|
20
|
+
# proceed. That is why the reader moves first, ahead of the writer.
|
|
21
|
+
#
|
|
22
|
+
# The row half resolves through the sibling story-map query, which owns the
|
|
23
|
+
# single oversight-hash definition — hence the .sh wrapper and not the .mjs
|
|
24
|
+
# behind it. It is captured into a variable, its stderr discarded and its exit
|
|
25
|
+
# status branched on, so it can only ever contribute a trace it actually found.
|
|
26
|
+
# It reads </dev/null so it cannot consume a caller's inherited stdin.
|
|
27
|
+
#
|
|
28
|
+
# FAILING CLOSED IS STRUCTURAL, NOT A CONVENTION. The query reports maps it
|
|
29
|
+
# could not read separately from rows it found, so "this map cannot answer" can
|
|
30
|
+
# never be mistaken for "no row proposes a fix". Drawing a second row over a map
|
|
31
|
+
# that already carries one would fragment the fix across two vehicles, which is
|
|
32
|
+
# the failure this gate exists to prevent.
|
|
23
33
|
#
|
|
24
34
|
# Usage:
|
|
25
|
-
# check-fix-rfc-trace.sh <problem-file> [<rfcs-dir>]
|
|
35
|
+
# check-fix-rfc-trace.sh <problem-file> [<rfcs-dir>] [<maps-dir>]
|
|
26
36
|
#
|
|
27
|
-
#
|
|
37
|
+
# Defaults are `docs/rfcs` and `docs/story-maps`.
|
|
28
38
|
#
|
|
29
39
|
# The PID is derived from the problem filename: `<NNN>-<slug>.md`
|
|
30
40
|
# (under any docs/problems/<state>/ subdir) → `P<NNN>`.
|
|
31
41
|
#
|
|
32
|
-
# Behaviour
|
|
33
|
-
# -
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
42
|
+
# Behaviour:
|
|
43
|
+
# - Something proposes a fix → exit 0, EMPTY stdout. Work proceeds.
|
|
44
|
+
# - Nothing proposes a fix, and a row can be drawn → exit 0, stdout carries a
|
|
45
|
+
# directive naming the row to draw and the identity to give it. Exit 0
|
|
46
|
+
# because the caller resolves this itself, with no person involved: drawing
|
|
47
|
+
# a row on a map a person already approved inherits that approval. The work
|
|
48
|
+
# still does not begin until the row exists — that halt lives in the calling
|
|
49
|
+
# skill, which is where the drawing happens.
|
|
50
|
+
# - A map could not be read → exit 3, directive naming the maps and asking for
|
|
51
|
+
# a re-render. MECHANICAL, no person: a renderer clears this condition, so
|
|
52
|
+
# the caller re-renders and asks again, and escalates only if a clean
|
|
53
|
+
# re-render still reports it.
|
|
54
|
+
# - The repository holds no story maps at all → exit 3, directive naming the
|
|
55
|
+
# one thing a person has to do — draw the first map for this journey, which
|
|
56
|
+
# is new substance nothing may mint on their behalf. The caller records that
|
|
57
|
+
# single item and moves to the next problem; it does not stop.
|
|
40
58
|
# - Missing problem file / no args → exit 2 (caller misuse), stderr usage.
|
|
41
59
|
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
60
|
+
# Exit 3 is a refusal the caller can act on without reading prose, and is
|
|
61
|
+
# distinct from caller-misuse 2.
|
|
62
|
+
#
|
|
63
|
+
# @adr ADR-119 (a fix proposal draws a release row, never a document; the reader
|
|
64
|
+
# is repointed ahead of the writer and fails closed during the window)
|
|
65
|
+
# @adr ADR-103 (a release row is the RFC; the map is the approval surface, and
|
|
66
|
+
# a proposal needing a new map queues for a person)
|
|
47
67
|
# @adr ADR-071 (every fix goes through an RFC — unconditional, no carve-out)
|
|
48
|
-
# @adr ADR-070 (the auto-created RFC is a problem-traced skeleton with no
|
|
49
|
-
# independent decisions — guaranteed by routing the create through
|
|
50
|
-
# capture-rfc rather than a second create surface)
|
|
51
68
|
# @adr ADR-060 (I1 load-bearing-from-the-start; I13 fix-proposal invariant)
|
|
52
69
|
# @adr ADR-049 (invoked via the wr-itil-check-fix-rfc-trace bin shim on
|
|
53
70
|
# $PATH; never repo-relative from a SKILL)
|
|
54
71
|
# @adr ADR-052 (behavioural bats coverage in
|
|
55
72
|
# packages/itil/scripts/test/check-fix-rfc-trace.bats)
|
|
56
73
|
# @problem P314
|
|
74
|
+
# @problem P508
|
|
57
75
|
|
|
58
76
|
set -uo pipefail
|
|
59
77
|
|
|
78
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
79
|
+
|
|
60
80
|
PROBLEM_FILE="${1:-}"
|
|
61
81
|
RFCS_DIR="${2:-docs/rfcs}"
|
|
82
|
+
MAPS_DIR="${3:-docs/story-maps}"
|
|
62
83
|
|
|
63
84
|
if [ -z "$PROBLEM_FILE" ]; then
|
|
64
|
-
echo "usage: check-fix-rfc-trace.sh <problem-file> [<rfcs-dir>]" >&2
|
|
85
|
+
echo "usage: check-fix-rfc-trace.sh <problem-file> [<rfcs-dir>] [<maps-dir>]" >&2
|
|
65
86
|
exit 2
|
|
66
87
|
fi
|
|
67
88
|
|
|
@@ -79,7 +100,7 @@ if ! [[ "$PNUM" =~ ^[0-9]+$ ]]; then
|
|
|
79
100
|
fi
|
|
80
101
|
PID="P${PNUM}"
|
|
81
102
|
|
|
82
|
-
# ──
|
|
103
|
+
# ── Tier 1: a legacy RFC document whose `problems:` array claims PID. ────────
|
|
83
104
|
# Mirrors the PID-boundary-safe parse in update-problem-rfcs-section.sh.
|
|
84
105
|
traced=0
|
|
85
106
|
if [ -d "$RFCS_DIR" ]; then
|
|
@@ -101,13 +122,59 @@ if [ -d "$RFCS_DIR" ]; then
|
|
|
101
122
|
shopt -u nullglob
|
|
102
123
|
fi
|
|
103
124
|
|
|
125
|
+
# ── Tier 2: a release row proposing a fix for PID. ──────────────────────────
|
|
126
|
+
# Any failure here leaves `traced` and `stale_maps` untouched, so the row half
|
|
127
|
+
# can only ever add a trace, never assert one it did not find.
|
|
128
|
+
stale_maps=""
|
|
129
|
+
map_count=0
|
|
130
|
+
if [ -d "$MAPS_DIR" ]; then
|
|
131
|
+
shopt -s nullglob
|
|
132
|
+
maps=("$MAPS_DIR"/*.html "$MAPS_DIR"/*/*.html)
|
|
133
|
+
shopt -u nullglob
|
|
134
|
+
map_count="${#maps[@]}"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [ "$map_count" -gt 0 ]; then
|
|
138
|
+
if rows_json="$("$HERE/story-map-query.sh" find-problem "$PID" --maps-dir "$MAPS_DIR" 2>/dev/null </dev/null)"; then
|
|
139
|
+
# Parsed by the same runtime that produced it. A shell-side JSON parse would
|
|
140
|
+
# be a second, worse reader of a format this repo already owns one of.
|
|
141
|
+
if summary="$(printf '%s' "$rows_json" | node -e '
|
|
142
|
+
let raw = "";
|
|
143
|
+
process.stdin.on("data", (c) => { raw += c; });
|
|
144
|
+
process.stdin.on("end", () => {
|
|
145
|
+
const d = JSON.parse(raw);
|
|
146
|
+
process.stdout.write(
|
|
147
|
+
(d.hits || []).length + "\n" +
|
|
148
|
+
(d.unanswerable || []).map((u) => u.path).join(" ") + "\n"
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
' 2>/dev/null)"; then
|
|
152
|
+
[ "$(printf '%s' "$summary" | sed -n 1p)" -gt 0 ] 2>/dev/null && traced=1
|
|
153
|
+
stale_maps="$(printf '%s' "$summary" | sed -n 2p)"
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
|
|
104
158
|
if [ "$traced" -eq 1 ]; then
|
|
105
|
-
#
|
|
159
|
+
# Something already proposes a fix for this problem — work proceeds.
|
|
106
160
|
exit 0
|
|
107
161
|
fi
|
|
108
162
|
|
|
109
|
-
#
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
printf 'no-rfc-trace: %s —
|
|
163
|
+
# ── Nothing proposes a fix. Which of the three refusals is it? ───────────────
|
|
164
|
+
|
|
165
|
+
if [ -n "${stale_maps// /}" ]; then
|
|
166
|
+
printf 'no-rfc-trace: %s — these story maps were edited without being re-rendered, so they cannot say whether a row already proposes this fix: %s. Re-render them and ask again. Do not draw a row first: if one of them already carries it, a second row would split the fix across two vehicles. Nobody needs to be asked about this — re-rendering is mechanical.\n' \
|
|
167
|
+
"$PID" "${stale_maps% }"
|
|
168
|
+
exit 3
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if [ "$map_count" -eq 0 ]; then
|
|
172
|
+
printf 'no-rfc-trace: %s — a fix is proposed as a release row on a story map, and this repository has no story maps yet. Drawing the first map for a journey decides what that journey is, so it needs a person and must not be created automatically. Record one item for the maintainer — draw a story map covering this work — and carry on to the next problem rather than stopping.\n' \
|
|
173
|
+
"$PID"
|
|
174
|
+
exit 3
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
NEXT_ID="$("$HERE/next-rfc-id.sh" --rfcs-dir "$RFCS_DIR" --maps-dir "$MAPS_DIR" 2>/dev/null </dev/null || true)"
|
|
178
|
+
printf 'no-rfc-trace: %s — no release row proposes a fix for this problem. Draw one on a story map that already covers this journey, give it at least one story card, and make sure that card'"'"'s story file names %s in its own problems list — the link from a row to a problem is read through its cards, so a row without one will still read as untraced. Give the row the identity %s. Fix work does not begin until the row exists.\n' \
|
|
179
|
+
"$PID" "$PID" "${NEXT_ID:-the next free RFC identity}"
|
|
113
180
|
exit 0
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# packages/itil/scripts/next-rfc-id.sh
|
|
3
|
+
#
|
|
4
|
+
# THE single definition of "the next free RFC identity".
|
|
5
|
+
#
|
|
6
|
+
# A fix proposal draws a release row on a story map; it does not create a
|
|
7
|
+
# standalone RFC document. That leaves one identity space spread across two
|
|
8
|
+
# tiers, and the rule that used to allocate from the document directory alone
|
|
9
|
+
# cannot see the rows. It was already wrong before the tier changed: rows held
|
|
10
|
+
# identities above the highest document, so the next allocation collided with a
|
|
11
|
+
# row that already existed.
|
|
12
|
+
#
|
|
13
|
+
# So the scan is the union of every place an identity can be recorded:
|
|
14
|
+
#
|
|
15
|
+
# 1. RFC document filenames under the documents directory;
|
|
16
|
+
# 2. every `RFC-NNN` occurrence in the story-map corpus, which covers both a
|
|
17
|
+
# row's authored identity in the data island and the derived list in the
|
|
18
|
+
# map's <meta> block;
|
|
19
|
+
# 3. the same two, across the whole of git history — an identity can survive
|
|
20
|
+
# on a branch, or in a commit whose file was later deleted, and be present
|
|
21
|
+
# nowhere in the working tree. A live-tree-only scan re-issues it.
|
|
22
|
+
#
|
|
23
|
+
# Identities are never reused, so the answer is one past the highest seen — a
|
|
24
|
+
# gap below the maximum is a deleted identity, not a free slot.
|
|
25
|
+
#
|
|
26
|
+
# Every surface that needs an identity calls this. A second copy in a calling
|
|
27
|
+
# skill is the drift class this corpus has removed repeatedly, and it is the
|
|
28
|
+
# specific way the collision above went unnoticed: two rules, two answers, and
|
|
29
|
+
# the one nobody was reading was right.
|
|
30
|
+
#
|
|
31
|
+
# Usage:
|
|
32
|
+
# next-rfc-id.sh [--rfcs-dir DIR] [--maps-dir DIR] [--no-git]
|
|
33
|
+
#
|
|
34
|
+
# Prints one `RFC-NNN` line, zero-padded to three digits. Defaults are
|
|
35
|
+
# `docs/rfcs` and `docs/story-maps`. `--no-git` restricts the scan to the
|
|
36
|
+
# working tree, for hermetic tests; outside a git repository the history half
|
|
37
|
+
# is skipped automatically.
|
|
38
|
+
#
|
|
39
|
+
# @adr ADR-119 (a fix proposal draws a release row, never a document; the
|
|
40
|
+
# identity is allocated by scanning row identities including git history)
|
|
41
|
+
# @adr ADR-115 (identities are never reused — hence highest+1, not lowest gap)
|
|
42
|
+
# @adr ADR-049 (invoked via the wr-itil-next-rfc-id bin shim on $PATH)
|
|
43
|
+
# @adr ADR-052 (behavioural bats in packages/itil/scripts/test/next-rfc-id.bats)
|
|
44
|
+
# @problem P508
|
|
45
|
+
|
|
46
|
+
set -uo pipefail
|
|
47
|
+
|
|
48
|
+
RFCS_DIR="docs/rfcs"
|
|
49
|
+
MAPS_DIR="docs/story-maps"
|
|
50
|
+
USE_GIT=1
|
|
51
|
+
|
|
52
|
+
while [ $# -gt 0 ]; do
|
|
53
|
+
case "$1" in
|
|
54
|
+
--rfcs-dir) RFCS_DIR="${2:-}"; shift 2 ;;
|
|
55
|
+
--maps-dir) MAPS_DIR="${2:-}"; shift 2 ;;
|
|
56
|
+
--no-git) USE_GIT=0; shift ;;
|
|
57
|
+
*) echo "usage: next-rfc-id.sh [--rfcs-dir DIR] [--maps-dir DIR] [--no-git]" >&2; exit 2 ;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
# Every identity seen, one `RFC-NNN` token per line. Callers reduce; this only
|
|
62
|
+
# emits.
|
|
63
|
+
seen() {
|
|
64
|
+
# 1. Document filenames.
|
|
65
|
+
if [ -d "$RFCS_DIR" ]; then
|
|
66
|
+
find "$RFCS_DIR" -maxdepth 1 -name 'RFC-[0-9][0-9][0-9]-*.md' 2>/dev/null
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# 2. The story-map corpus, at any nesting depth.
|
|
70
|
+
if [ -d "$MAPS_DIR" ]; then
|
|
71
|
+
grep -rhoE 'RFC-[0-9]{3}' "$MAPS_DIR" 2>/dev/null
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# 3. Git history. `git grep` takes revisions BEFORE the `--`; anything after
|
|
75
|
+
# it is a pathspec, so piping revisions into xargs as trailing arguments
|
|
76
|
+
# would silently re-grep the working tree and look like it worked — the
|
|
77
|
+
# answer would even be plausible, because the working tree usually holds the
|
|
78
|
+
# highest identity anyway. Hence the sh -c wrapper, which puts "$@" in the
|
|
79
|
+
# revision slot.
|
|
80
|
+
#
|
|
81
|
+
# `git grep` exits 1 on a batch with no match, which xargs reports as 123;
|
|
82
|
+
# both are absorbed so a quiet batch cannot empty the scan under pipefail.
|
|
83
|
+
if [ "$USE_GIT" -eq 1 ] && git rev-parse --git-dir >/dev/null 2>&1; then
|
|
84
|
+
git rev-list --all 2>/dev/null \
|
|
85
|
+
| RFCS_DIR="$RFCS_DIR" MAPS_DIR="$MAPS_DIR" xargs -n 200 sh -c '
|
|
86
|
+
git grep -hoE "RFC-[0-9]{3}" "$@" -- "$RFCS_DIR" "$MAPS_DIR" 2>/dev/null || true
|
|
87
|
+
' _ 2>/dev/null || true
|
|
88
|
+
fi
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
HIGHEST="$(seen \
|
|
92
|
+
| grep -oE 'RFC-[0-9]{3}' \
|
|
93
|
+
| sed -E 's/^RFC-0*//' \
|
|
94
|
+
| grep -E '^[0-9]+$' \
|
|
95
|
+
| sort -n \
|
|
96
|
+
| tail -1)"
|
|
97
|
+
|
|
98
|
+
printf 'RFC-%03d\n' "$(( ${HIGHEST:-0} + 1 ))"
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
// get <MAP-ID> one map: backbone, releases, tasks
|
|
16
16
|
// find-story <STORY-ID> which maps hold a story, and in which cell
|
|
17
17
|
// find-rfc <RFC-ID> which release rows carry an RFC and their stories
|
|
18
|
+
// find-problem <P-ID> which release rows propose a fix for a problem,
|
|
19
|
+
// and which maps could not answer
|
|
18
20
|
// unratified only the maps needing ratification, with a reason
|
|
19
21
|
//
|
|
20
22
|
// @adr ADR-102 (story maps render from JSON through a canonical template)
|
|
@@ -109,10 +111,21 @@ function rowStatus(row, derived) {
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
function corpus() {
|
|
112
|
-
|
|
113
|
-
.map((f) => ({ ...f, data: island(f.path), derived: derivedIsland(f.path) }))
|
|
114
|
+
const all = readFacts()
|
|
115
|
+
.map((f) => ({ ...f, data: island(f.path), derived: derivedIsland(f.path) }));
|
|
116
|
+
const maps = all
|
|
114
117
|
.filter((m) => m.data)
|
|
115
118
|
.sort((a, b) => String(a.data.storyMapId).localeCompare(String(b.data.storyMapId)));
|
|
119
|
+
// The maps dropped by that filter, carried alongside rather than discarded.
|
|
120
|
+
// `list`, `get`, `summary` and `unratified` all dereference `m.data`
|
|
121
|
+
// unguarded, so they must not see these — but a predicate asking "does any
|
|
122
|
+
// row name this problem?" has to report that a map could not answer, or an
|
|
123
|
+
// island-less map reads as a clean "no" and the caller proceeds. Stdin can be
|
|
124
|
+
// read once, so this cannot be recovered by calling readFacts() again. An
|
|
125
|
+
// array property is invisible to .map()/.filter()/JSON.stringify, which is
|
|
126
|
+
// why it does not leak into any existing op's output.
|
|
127
|
+
maps.islandless = all.filter((m) => !m.data);
|
|
128
|
+
return maps;
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
function summary(m) {
|
|
@@ -198,18 +211,78 @@ const OPS = {
|
|
|
198
211
|
return hits;
|
|
199
212
|
},
|
|
200
213
|
|
|
214
|
+
/** Which release rows propose a fix for a problem.
|
|
215
|
+
*
|
|
216
|
+
* A row IS the fix vehicle, so this is the row-model half of the propose-fix
|
|
217
|
+
* trace question. The row-to-problem edge is DERIVED, not authored: the
|
|
218
|
+
* renderer walks each row's cards to their story files and unions their
|
|
219
|
+
* `problems:` frontmatter into `rowProblems`. A row drawn without a card
|
|
220
|
+
* whose story names the problem therefore does not answer here — which is a
|
|
221
|
+
* true answer, not a bug, and matches the floor that a row carrying an
|
|
222
|
+
* identity has at least one card.
|
|
223
|
+
*
|
|
224
|
+
* DELIBERATE SHAPE DIVERGENCE from `find-story` and `find-rfc`, which return
|
|
225
|
+
* flat arrays. This returns `{hits, unanswerable}` because the obvious
|
|
226
|
+
* consumer test on a flat array is `length > 0`, which would read "this map
|
|
227
|
+
* could not answer" as a trace hit and let fix work begin with no vehicle.
|
|
228
|
+
* Fail-closed has to be the shape, not a convention the caller remembers.
|
|
229
|
+
* Do not normalise this back to an array.
|
|
230
|
+
*/
|
|
231
|
+
'find-problem': (maps, [pid]) => {
|
|
232
|
+
if (!pid) throw new Error('find-problem needs a problem id, e.g. P508');
|
|
233
|
+
const hits = [];
|
|
234
|
+
// A map with no authored island cannot be read at all.
|
|
235
|
+
const unanswerable = (maps.islandless ?? []).map((m) => ({
|
|
236
|
+
storyMapId: null,
|
|
237
|
+
path: m.path,
|
|
238
|
+
status: 'stale',
|
|
239
|
+
why: 'no authored data island',
|
|
240
|
+
}));
|
|
241
|
+
for (const m of maps) {
|
|
242
|
+
const releases = m.data.releases ?? [];
|
|
243
|
+
const derived = m.derived ?? {};
|
|
244
|
+
// `rows` is emitted for every release the renderer saw, so its absence on
|
|
245
|
+
// a map that HAS releases means the map was edited and not re-rendered —
|
|
246
|
+
// the same condition `rowStatus` names `stale`. A map with no releases is
|
|
247
|
+
// silent rather than stale: it holds no row that could answer.
|
|
248
|
+
if (releases.length && !derived.rows) {
|
|
249
|
+
unanswerable.push({
|
|
250
|
+
storyMapId: m.data.storyMapId,
|
|
251
|
+
path: m.path,
|
|
252
|
+
status: 'stale',
|
|
253
|
+
why: 'edited but not re-rendered',
|
|
254
|
+
});
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
for (const row of releases) {
|
|
258
|
+
if (!row.rfc) continue; // a speculative row is not a fix vehicle
|
|
259
|
+
if (!((derived.rowProblems ?? {})[row.id] ?? []).includes(pid)) continue;
|
|
260
|
+
hits.push({
|
|
261
|
+
storyMapId: m.data.storyMapId,
|
|
262
|
+
rowId: row.id,
|
|
263
|
+
rfc: row.rfc,
|
|
264
|
+
ratified: m.ratified,
|
|
265
|
+
path: m.path,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { hits, unanswerable };
|
|
270
|
+
},
|
|
271
|
+
|
|
201
272
|
unratified: (maps) => maps.filter((m) => !m.ratified).map(summary),
|
|
202
273
|
};
|
|
203
274
|
|
|
204
275
|
function main(argv) {
|
|
205
276
|
const [op, ...rest] = argv;
|
|
206
277
|
if (!op || !OPS[op]) {
|
|
207
|
-
console.error('usage: story-map-query <list|get|find-story|find-rfc|unratified> [args] [--maps-dir DIR]');
|
|
278
|
+
console.error('usage: story-map-query <list|get|find-story|find-rfc|find-problem|unratified> [args] [--maps-dir DIR]');
|
|
208
279
|
console.error('');
|
|
209
280
|
console.error(' list every map: status, jobs, problems, RFC rows, ratification');
|
|
210
281
|
console.error(' get <MAP-ID> one map: backbone, release bands, cards');
|
|
211
282
|
console.error(' find-story <STORY-ID> which maps hold a story, and in which cell');
|
|
212
283
|
console.error(' find-rfc <RFC-ID> which release rows carry an RFC and their stories');
|
|
284
|
+
console.error(' find-problem <P-ID> which rows propose a fix for a problem, plus');
|
|
285
|
+
console.error(' any map that could not answer');
|
|
213
286
|
console.error(' unratified maps needing ratification, each with a reason');
|
|
214
287
|
return 2;
|
|
215
288
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Read-only JSON query over the story-map corpus.
|
|
3
3
|
#
|
|
4
|
-
# Usage: wr-itil-story-map-query <list|get|find-story|find-rfc|unratified> [args] [--maps-dir DIR]
|
|
4
|
+
# Usage: wr-itil-story-map-query <list|get|find-story|find-rfc|find-problem|unratified> [args] [--maps-dir DIR]
|
|
5
5
|
#
|
|
6
6
|
# WHY THIS IS A BASH ENTRY POINT AND NOT JUST A .mjs. Ratification state is
|
|
7
7
|
# drift-invalidated and must come from ONE hash definition — the one in
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
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 by default (or a fully-authored Scope
|
|
3
|
+
description: Lightweight RFC-capture skill for aside-invocation during foreground work — mandatory problem-trace per ADR-060 I1 invariant, skeleton RFC file by default (or a fully-authored Scope plus the fix's stories on a story map under the `--fix-time` flag, now reachable only when a person invokes this skill directly), 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
4
|
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ Capture a Request for Change (RFC) ticket quickly during foreground work. Lightw
|
|
|
10
10
|
|
|
11
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
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 — every fix goes through an RFC per ADR-071). capture-rfc is invoked two ways: (1) the **user-aside path** (deliberate, default skeleton — the user fleshes scope out later at `/wr-itil:manage-rfc accepted`); and (2) the **fix-time
|
|
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 — every fix goes through an RFC per ADR-071). capture-rfc is invoked two ways: (1) the **user-aside path** (deliberate, default skeleton — the user fleshes scope out later at `/wr-itil:manage-rfc accepted`); and (2) the **fix-time authoring path** (`--fix-time`), which **authors the full RFC** from the traced problem rather than a skeleton. **The I13 propose-fix gate no longer fires this path.** A fix proposal draws a release row on a story map (ADR-119); the gate draws the row itself and names no document-creating command, so `--fix-time` is now reached only when a person invokes this skill directly. What the flag does is unchanged and still correct where it is used: the scope is derived from the already-pinned problem under ADR-071, so it is framework-mediated rather than direction-setting (P132 / inverse-P078). Repointing what this skill *writes* is the following slice; this file has so far only been corrected about who calls it and how it numbers.
|
|
14
14
|
|
|
15
15
|
## Output Formatting
|
|
16
16
|
|
|
@@ -35,7 +35,9 @@ When referencing RFC IDs, problem IDs, ADR IDs, JTBD IDs, or story IDs in prose
|
|
|
35
35
|
|
|
36
36
|
**Optional flag (Phase 2)**: `--stories STORY-<NNN>,STORY-<NNN>,...` — ORDERED execution sequence per ADR-060 line 262. Cardinality 0..N: an RFC whose work is not decomposed into stories OMITS the flag and capture-rfc populates `stories: []` in frontmatter (a structural state, NOT a reduced-ceremony path — every fix goes through an RFC per ADR-071); story-decomposed RFCs supply the ordered list. The flag accepts STORY-IDs that don't yet resolve to files (forward-reference is permitted at capture; the existence check happens at `manage-rfc <NNN> accepted` transition per ADR-060 working-the-problem flow line 304).
|
|
37
37
|
|
|
38
|
-
**Optional flag (fix-time authoring, ADR-073 P399)**: `--fix-time` — switches Step 5 from the deferred-placeholder template to **full authoring**: capture-rfc reads the traced problem ticket(s) and authors a populated `## Scope` (the fix being proposed + chosen implementation approach as prose) and, per ADR-089, a **story** work-breakdown on a story map — NOT a `## Tasks` decomposition (superseded; the fix's stories are authored via capture-story-map + capture-story and listed in `stories:`), instead of the `(deferred — populate at manage-rfc accepted)` placeholders. Set by the I13 propose-fix gate
|
|
38
|
+
**Optional flag (fix-time authoring, ADR-073 P399)**: `--fix-time` — switches Step 5 from the deferred-placeholder template to **full authoring**: capture-rfc reads the traced problem ticket(s) and authors a populated `## Scope` (the fix being proposed + chosen implementation approach as prose) and, per ADR-089, a **story** work-breakdown on a story map — NOT a `## Tasks` decomposition (superseded; the fix's stories are authored via capture-story-map + capture-story and listed in `stories:`), instead of the `(deferred — populate at manage-rfc accepted)` placeholders. Set on a direct invocation by a person who wants the scope authored now. **It is no longer set by the I13 propose-fix gate** at either `/wr-itil:manage-problem` or `/wr-itil:work-problems` — that gate draws a release row instead (ADR-119).
|
|
39
|
+
|
|
40
|
+
> **The option-bearing guard rides here now.** ADR-119 discharged only the vehicle-shape half of ADR-073; its other half still holds, and it says a fix whose approach-choice no ratified decision covers gets that decision recorded BEFORE the fix is authored. That guard used to arrive with the caller — `/wr-itil:manage-problem` queues rather than mints when the fix approach is a choice no existing decision record covers. Direct invocation is now the only route to this flag, so the guard has to be stated here or it is carried nowhere: if the fix this RFC proposes turns on a choice between real alternatives that no ratified decision settles, record that decision first and author the scope against it. Authoring the scope IS framework-mediated once the approach is settled — that is what the audit row below means, and it is not a licence to settle the approach in passing. The authored RFC still carries NO Considered-Options block (ADR-070 — chosen-path prose only) and is still born `human-oversight: unconfirmed` (ratified at `manage-rfc accepted`). Composes with `--stories`.
|
|
39
41
|
|
|
40
42
|
```
|
|
41
43
|
/wr-itil:capture-rfc P168 Pipeline consume-catalog and bootstrap-from-reports — multi-commit retrofit
|
|
@@ -53,10 +55,10 @@ This skill has **one direction-setting AskUserQuestion** (problem-trace, when ar
|
|
|
53
55
|
|----------|-----------|-----------------|
|
|
54
56
|
| 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) |
|
|
55
57
|
| 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 |
|
|
56
|
-
| RFC ID allocation | Mechanical: `
|
|
58
|
+
| RFC ID allocation | Mechanical: ask `wr-itil-next-rfc-id`. It is the single rule, and the only one that sees release rows, documents and git history at once — a directory scan re-issues an identity a row already holds | silent-mechanical |
|
|
57
59
|
| Title kebab-slug | Mechanical: first 8-10 non-stopword tokens of description | silent-mechanical |
|
|
58
60
|
| Title prose / scope summary refinement | Optional `AskUserQuestion`; silent-default to derived form when unavailable | taste |
|
|
59
|
-
| Fix-time Scope/Stories authoring (`--fix-time
|
|
61
|
+
| Fix-time Scope/Stories authoring (`--fix-time`, direct invocation only — the I13 gate draws a release row instead, ADR-119) | Framework-mediated: author `## Scope` + a **story** work-breakdown on a story map (NOT `## Tasks` — superseded per ADR-089) from the traced problem's RCA + Fix Strategy. NO `AskUserQuestion` — the scope is *derived* from already-pinned ADR-071 direction, not new direction-setting. Born `unconfirmed`; ratified at `manage-rfc accepted` | silent-framework |
|
|
60
62
|
| File write / frontmatter | Mechanical: shape per `docs/rfcs/README.md` § RFC body structure | silent-mechanical |
|
|
61
63
|
| Single commit | Mechanical: `docs(rfcs): capture RFC-<NNN> <title>` | silent-mechanical |
|
|
62
64
|
| 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 |
|
|
@@ -156,15 +158,15 @@ wr-itil-mark-rfc-capture-gate
|
|
|
156
158
|
|
|
157
159
|
### 3. Compute next RFC ID
|
|
158
160
|
|
|
159
|
-
|
|
161
|
+
Ask the allocator. Do NOT scan a directory yourself:
|
|
160
162
|
|
|
161
163
|
```bash
|
|
162
|
-
|
|
163
|
-
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)
|
|
164
|
-
next=$(printf '%03d' $(( 10#$(echo -e "${local_max:-0}\n${origin_max:-0}" | sort -n | tail -1) + 1 )))
|
|
164
|
+
wr-itil-next-rfc-id
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
**Why this and not a directory scan.** An RFC identity is now carried by a release row on a story map as well as by a document, and rows already hold identities above the highest document. A rule that reads `docs/rfcs/` alone therefore hands back an identity a row already owns — a collision that is live, not hypothetical. It also cannot see an identity that survives only in git history, which happens whenever a map is deleted or lives on a branch. The allocator reads all three — documents, rows, and history — and returns the first identity none of them holds. Identities are never reused, so it answers one past the highest seen rather than filling a gap.
|
|
168
|
+
|
|
169
|
+
This is deliberately the ONLY rule in the repository that answers this question. A second copy anywhere is how the collision above went unnoticed for as long as it did.
|
|
168
170
|
|
|
169
171
|
### 4. Optional taste prompt for title / scope summary
|
|
170
172
|
|
|
@@ -318,7 +320,7 @@ The two skills share the `/tmp/wr-itil-rfc-capture-grep-${SESSION_ID}` create-ga
|
|
|
318
320
|
- **`docs/plans/170-rfc-framework-story-map.md`** — Slice 2 task B5.T3 lands this skill.
|
|
319
321
|
- **JTBD-008** — Decompose a Fix Into Coordinated Changes. Primary persona-anchor.
|
|
320
322
|
- **JTBD-001** (extended scope) — change-set-level governance composition.
|
|
321
|
-
- **JTBD-101** (atomic-fix-adopter) — every fix goes through an RFC (ADR-071); capture-rfc is a deliberate aside-invocation, not auto-fired
|
|
323
|
+
- **JTBD-101** (atomic-fix-adopter) — every fix goes through an RFC (ADR-071); capture-rfc is a deliberate aside-invocation, not auto-fired. The two readings of ADR-073 elsewhere in this file are about different questions and do not conflict: *whether to open an RFC at all*, and *whether its approach turns on an unsettled choice*, are a person's calls; authoring the scope once both are settled is derived from the traced problem and is framework-mediated.
|
|
322
324
|
- **`docs/rfcs/README.md`** — RFC tier lifecycle index + frontmatter shape spec (Slice 2 tasks B5.T1 + B5.T2 — committed `adc53c8`).
|
|
323
325
|
- **ADR-014** — governance skills commit their own work. Single-commit grain per capture.
|
|
324
326
|
- **ADR-022** — problem lifecycle conventions; RFC lifecycle mirrors.
|
|
@@ -48,14 +48,14 @@ The preamble check is a one-shot; the `.intake-scaffold-done` and `.intake-scaff
|
|
|
48
48
|
2. When the user explicitly confirms ("it's fixed", "verified", "working"): `git mv` from `.verifying.md` to `.closed.md`, update the Status field to "Closed", and reference the problem in the commit message (e.g., "Closes P008").
|
|
49
49
|
3. Never assume the fix works — always wait for explicit user confirmation before closing.
|
|
50
50
|
|
|
51
|
-
The `.verifying.md` suffix distinguishes "fix released, awaiting user verification" from "root cause identified AND workaround documented; fix not yet proposed" (the Known Error meaning per ADR-022 corrected semantics, 2026-06-08 amendment; the fix proposal happens AFTER Known Error and
|
|
51
|
+
The `.verifying.md` suffix distinguishes "fix released, awaiting user verification" from "root cause identified AND workaround documented; fix not yet proposed" (the Known Error meaning per ADR-022 corrected semantics, 2026-06-08 amendment; the fix proposal happens AFTER Known Error and draws a release row on a story map). See ADR-022 for rationale.
|
|
52
52
|
|
|
53
53
|
## Problem Lifecycle
|
|
54
54
|
|
|
55
55
|
| Status | File suffix | Meaning | Entry criteria |
|
|
56
56
|
|--------|-----------|---------|----------------|
|
|
57
57
|
| **Open** | `.open.md` | Reported, under investigation | New problem identified |
|
|
58
|
-
| **Known Error** | `.known-error.md` | Root cause identified AND workaround documented; **fix not yet proposed** (fix proposal
|
|
58
|
+
| **Known Error** | `.known-error.md` | Root cause identified AND workaround documented; **fix not yet proposed** (the fix proposal draws a release row on a story map) | Root cause documented, reproduction test exists, workaround in place |
|
|
59
59
|
| **Verification Pending** | `.verifying.md` | Fix released, awaiting user verification (ADR-022) | Fix shipped; `## Fix Released` section written; user action remaining |
|
|
60
60
|
| **Parked** | `.parked.md` | Blocked on upstream or suspended by user decision | Upstream blocker identified, or user explicitly suspends; reason and un-park trigger documented |
|
|
61
61
|
| **Closed** | `.closed.md` | Fix verified in production OR ticket determined no longer relevant via evidence | (a) User explicitly confirms the released fix works (canonical Verifying → Closed path), OR (b) auto-closed by `/wr-itil:review-problems` Step 4.6 relevance-close pass per ADR-079 Phase 1 + Phase 2 evidence shapes — `file-no-longer-exists` / `ADR-shipped-confirmed` / `named-skill-or-feature-exists` / `self-marker-in-body` / `driver-child-ticket-closed` (cumulative; multi-shape matches emit comma-joined) with `## Closed as no longer relevant` audit section per ADR-026 grounding (extends ADR-022 lifecycle: Open\|Known Error → Closed bypasses Verifying when no fix was released). Partial-scope umbrellas emit `CLOSE-CANDIDATE-WITH-CAVEAT` and ride the maintainer's `AskUserQuestion` surface-batch-confirm path. |
|
|
@@ -182,22 +182,50 @@ What "work" means depends on the problem's status:
|
|
|
182
182
|
- **AFK** (`/wr-itil:work-problems` orchestrator): NEVER ask mid-loop — queue the substance to the iteration's `outstanding_questions` (ADR-044 AFK carve-out) and skip the build; do not guess. This is the **queue-and-continue** universal default per ADR-013 Rule 6 (P352, 2026-06-06 amendment): the iter queues the substance + advances; the orchestrator main turn surfaces the queued question at loop end via the Step 2.5 batched AskUserQuestion.
|
|
183
183
|
4. This ask is **ADR-044 category-1 direction-setting** and is EXCLUDED from the lazy-AskUserQuestion regression metric (it is legitimate, not lazy). The trigger is narrow — detection is mechanical (the predicate); only genuine unconfirmed decisions about to be built on fire it. Do NOT over-fire on confirmed/superseded/obvious decisions (inverse-P078 / P132 guard). A born-`proposed` marker is fine for *recording*; it is not a licence to *build* (ADR-066 carve-out).
|
|
184
184
|
|
|
185
|
-
**I13 propose-fix
|
|
185
|
+
**I13 propose-fix trace gate (RFC-005 B3/B4).** BEFORE the traversal below, enforce the fix-time trace invariant: a fix proposed on a Known Error requires a fix vehicle that traces the problem (ADR-071 unconditional; ADR-072 places the gate here, conforming to ADR-022 Known Error semantics — the fix is proposed *after* Known Error).
|
|
186
|
+
|
|
187
|
+
**A fix proposal is a release row on a story map. It is never a new document under `docs/rfcs/`.** The row carries the RFC identity, and the map is where a person approves the work; a fix written up as its own document reaches neither. The documents already on disk stay readable and keep working — they simply stop being the thing a fix proposal creates, and each converts to a row when its own problem is next worked.
|
|
188
|
+
|
|
189
|
+
Run the load-bearing predicate (ADR-049 `$PATH` shim — never repo-relative from a SKILL):
|
|
186
190
|
|
|
187
191
|
```bash
|
|
188
192
|
wr-itil-check-fix-rfc-trace <problem-file>
|
|
189
193
|
```
|
|
190
194
|
|
|
191
|
-
- **
|
|
192
|
-
- **
|
|
193
|
-
- **
|
|
194
|
-
- **
|
|
195
|
+
- **Exit 0, empty stdout** (something already proposes a fix for this problem — a release row, or a legacy document whose `problems:` array names it): proceed to the traversal below.
|
|
196
|
+
- **Exit 3** (the predicate refuses to answer, and says which of two reasons on stdout):
|
|
197
|
+
- **A map was edited without being re-rendered.** Mechanical, and nobody is asked about it: re-render the maps the directive names with `wr-itil-render-story-map <map.html>`, then run the predicate again. Escalate only if a clean re-render still refuses.
|
|
198
|
+
- **The repository holds no story maps at all.** Drawing the first map for a journey decides what that journey *is*, so it needs a person and must not be created automatically. Record **one** item — draw a story map covering this work — and carry on to the next problem. Interactively that is an `AskUserQuestion`; under the AFK orchestrator it is a single `outstanding_questions` entry. Do not stop the loop.
|
|
199
|
+
- **Exit 0, non-empty stdout** (directive `no-rfc-trace: P<NNN> …`): nothing proposes a fix yet. The predicate has confirmed only that *no row and no document names this PID* — it has NOT decided that no fix vehicle exists. Distinguish **two sub-cases** before acting (P371 — the auto-draw below is intended ONLY when no fix vehicle exists, NOT when an existing vehicle merely lacks the trace edge — architect-confirmed):
|
|
200
|
+
- **(a) Existing-vehicle-untraced — a vehicle is already this ticket's fix but just hasn't wired the trace edge.** Read the ticket's `## Fix Strategy` / `## Resolution` / `## Dependencies` / `## Related` sections for an RFC cited as the **fix vehicle** — i.e. the fix IS that RFC's task set (the recurring shape: a rework / follow-on Known Error whose fix is an existing vehicle's remaining tasks, so that vehicle's trace names the *original driver* problem, not this ticket). This is a **judgement read of the citation context, NOT a blind "any cited RFC" match**: an RFC named only as context / `composes with` / `**Related**` background is NOT a fix vehicle — wiring its trace edge would pollute its trace. If a genuine existing fix vehicle is found:
|
|
201
|
+
- **If it is a release row**, add a story card to that row for this ticket's fix and make the card's story file name `P<NNN>` in its own `problems:` list. That card IS the trace edge; the link from a row to a problem is read through its cards, so there is nothing else to wire.
|
|
202
|
+
- **If it is a legacy document**, wire this problem into its frontmatter `problems:` array (Edit the `problems: [...]` line to include `P<NNN>` + a short inline `**Problems**:` body annotation noting the edge was wired because the vehicle already exists). This is the one edit a legacy document still takes, and only because the vehicle predates the row model; do not author new scope into it.
|
|
203
|
+
Then run `wr-itil-update-problem-rfcs-section <problem-file>` so the ticket's derived `## RFCs` section reflects the wired trace, re-run the predicate (now empty) and proceed. Do **NOT** draw a second vehicle — a duplicate fragments the fix across two traces (the exact P371 defect). Structured-log the wire event (e.g. `I13: wired P<NNN> trace edge into existing fix vehicle <ID>`) to the iter summary `notes` for the JTBD-006 audit trail.
|
|
204
|
+
- **(b) No-vehicle — nothing is this ticket's fix vehicle yet. Draw the release row.** Do NOT block, do NOT skip, and do NOT ask: this is framework-mediated, not direction-setting, and drawing a row onto a map a person has already approved inherits that approval rather than needing a fresh one. NO `AskUserQuestion` consent gate fires on this path (P132 / inverse-P078; ADR-044 framework-resolution boundary).
|
|
205
|
+
|
|
206
|
+
1. Pick a story map that **already covers this journey** — right persona, and its job traces already include the job this fix's story serves.
|
|
207
|
+
2. Take the identity the predicate's directive named. Do not compute one yourself: `wr-itil-next-rfc-id` is the single definition, and it is the only rule that sees rows, documents and git history at once. A rule that reads only the document directory re-issues an identity a row already holds.
|
|
208
|
+
3. Draw the row and give it at least one card:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
wr-itil-story-map-edit <map.html> add-band --id rfc-<nnn> --name "<what shipping this row gets the user>" --rfc RFC-<NNN>
|
|
212
|
+
wr-itil-story-map-edit <map.html> add-card --story STORY-<NNN> --activity <existing-activity-id> --release rfc-<nnn> --title "<the story's title>"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The card's story is captured through `/wr-itil:capture-story` as normal, and **its frontmatter `problems:` list must name `P<NNN>`** — the link from a row to a problem is read through its cards, so a row without that card will still read as untraced and this gate will loop. A row carrying an identity with no card is a defect on the same footing as an untraced one.
|
|
216
|
+
4. Re-run the predicate (now empty) and proceed. Structured-log the draw event to the iter summary `notes`.
|
|
217
|
+
|
|
218
|
+
**Queue for a person instead — minting nothing — in either of these cases.**
|
|
219
|
+
- **The draw would change what the map's approval covers.** The keys that decide a map's ratification are enumerated in exactly one place, `oversight_map_substance_keys()` in `lib/story-oversight.sh`; adding a release row and a card touches none of them, which is precisely why the row inherits approval. If drawing this row would instead need a **new map**, a **new activity column**, or a **new job on the map's traces** — a map that covers the journey but does not trace the job this fix's story serves — then it changes substance a person approved, and doing it silently would void the very approval it was relying on. Derive this from that one function rather than restating its members here, so an amendment to the tuple cannot leave this gate behind.
|
|
220
|
+
- **The fix approach is a choice no existing decision record covers.** Recording a new decision is a person's call, not a byproduct of working a ticket.
|
|
221
|
+
|
|
222
|
+
Interactively, surface the queued item via `AskUserQuestion`. Under the AFK orchestrator, queue it at `outstanding_questions` and move to the next problem — never ask mid-loop.
|
|
195
223
|
|
|
196
|
-
The predicate is the load-bearing detection half (committed shell + behavioural bats per ADR-052: `packages/itil/scripts/test/check-fix-rfc-trace.bats`)
|
|
224
|
+
The predicate is the load-bearing detection half (committed shell + behavioural bats per ADR-052: `packages/itil/scripts/test/check-fix-rfc-trace.bats`), and it reads BOTH tiers — a release row's cards and a legacy document's `problems:` array — so repointing it cannot hard-stop work that used to proceed. This gate fires at **every** fix-time surface; the AFK `/wr-itil:work-problems` orchestrator dispatches its fix work *through this same manage-problem traversal*, so the gate covers the AFK surface transitively.
|
|
197
225
|
|
|
198
226
|
The Phase 2 working-the-problem traversal makes "implement the fix" concretely traceable via stories (per ADR-060 lines 300-320). Replaces the prior vague "implement the fix following the project's development workflow" with a deterministic problem → RFC → story dispatch:
|
|
199
227
|
|
|
200
|
-
1. **Read the problem's `## Fix Strategy` section** — extract referenced RFC IDs (anchor links / inline references like `RFC-NNN`). The I13 gate above has already guaranteed
|
|
228
|
+
1. **Read the problem's `## Fix Strategy` section** — extract referenced RFC IDs (anchor links / inline references like `RFC-NNN`). The I13 gate above has already guaranteed a vehicle traces the problem; if the `## Fix Strategy` prose itself references none (a row drawn moments ago may not yet be cited inline), fall through to the legacy direct-implementation path (step 6 below) using the row the gate just drew, or the vehicle it found.
|
|
201
229
|
2. **For each referenced RFC** (in the order they appear in the Fix Strategy section), read its frontmatter `stories:` array (per ADR-060 line 259, the array is ORDERED — array position IS execution sequence):
|
|
202
230
|
- **Non-empty `stories:` array** (story-decomposed RFC): pick the first story whose lifecycle status is `accepted` or `in-progress` — skip `done` stories that already shipped, skip `draft` stories that aren't ready (the `manage-story <NNN> accepted` gate enforces INVEST shape; a draft story is structurally unready). Continue to step 3.
|
|
203
231
|
- **Empty `stories: []`** (a **legacy** pre-ADR-089 RFC, or one not yet decomposed): per **ADR-089** every RFC has ≥1 story — an empty `stories:` is a **back-fill** state, NOT a legitimate atomic shape. Back-fill the fix's story onto the RFC's story map (add ≥1 story, transition it `accepted` via `manage-story`), then re-traverse from step 2. The empty-stories atomic fallback is removed — do NOT close the problem on a story-less RFC.
|
|
@@ -677,7 +705,7 @@ If the edit touched only `## Root Cause Analysis`, `## Symptoms`, `## Workaround
|
|
|
677
705
|
|
|
678
706
|
**Open → Known Error** (rename file, update content):
|
|
679
707
|
|
|
680
|
-
Known Error means "root cause identified AND workaround documented; fix not yet proposed" (per ADR-022 corrected semantics, 2026-06-08 amendment). The fix is proposed AFTER Known Error
|
|
708
|
+
Known Error means "root cause identified AND workaround documented; fix not yet proposed" (per ADR-022 corrected semantics, 2026-06-08 amendment). The fix is proposed AFTER Known Error, by drawing a release row on a story map. Releasing the fix is a separate Known Error → Verification Pending transition — do NOT stay on `.known-error.md` after the fix ships.
|
|
681
709
|
|
|
682
710
|
Pre-flight checks before allowing transition:
|
|
683
711
|
- [ ] Root cause is documented (not just "Preliminary Hypothesis")
|
|
@@ -16,7 +16,7 @@ The deprecated `/wr-itil:manage-problem <NNN> known-error` subcommand route rema
|
|
|
16
16
|
|
|
17
17
|
- `<NNN>` — the ticket ID (data parameter, e.g. `042`). Required.
|
|
18
18
|
- `<status>` — the destination status. One of:
|
|
19
|
-
- `known-error` — Open → Known Error (root cause identified AND workaround documented; fix not yet proposed — per ADR-022 corrected semantics
|
|
19
|
+
- `known-error` — Open → Known Error (root cause identified AND workaround documented; fix not yet proposed — per ADR-022 corrected semantics; the fix proposal draws a release row on a story map).
|
|
20
20
|
- `verifying` — Known Error → Verification Pending (fix released, awaiting user verification per ADR-022).
|
|
21
21
|
- `close` — Verification Pending → Closed (user has confirmed the fix works in production).
|
|
22
22
|
|
|
@@ -681,7 +681,7 @@ rm -f "$ITER_JSON"
|
|
|
681
681
|
|
|
682
682
|
1. **Context**: this is one iteration of the AFK work-problems loop. The user is AFK. The orchestrator selected `P<NNN> (<title>)` as the highest-WSJF actionable ticket.
|
|
683
683
|
2. **Task**: apply the `/wr-itil:manage-problem` workflow for `work highest WSJF problem that can be progressed non-interactively as the user is AFK`. Follow manage-problem SKILL.md verbatim, including architect / jtbd / style-guide / voice-tone gate reviews and the commit gate (manage-problem Step 11). Because this subprocess has the Agent tool in its own surface, the normal review-via-subagent paths work — no inline-verdict fallback needed.
|
|
684
|
-
3. **Constraints**: commit the completed work per ADR-014. Do NOT push, do NOT run `push:watch`, do NOT run `release:watch` — the orchestrator's Step 6.5 owns release cadence. Do NOT invoke `capture-*` background skills mid-iter (AFK carve-out — ADR-032), **EXCEPT** (a) **retro-surfaced observations of recurring class-of-behaviour** — those route to `/wr-itil:capture-problem` per the **P342 mechanical-stage carve-out** (see retro-on-exit constraint #4 below; same trust-boundary as `/wr-retrospective:run-retro` Step 4a verification close-on-evidence — P342); and (b) **the I13 fix-time RFC auto-create** — when the propose-fix gate inside the delegated `/wr-itil:manage-problem` traversal detects an RFC-less Known Error (`wr-itil-check-fix-rfc-trace` emits a `no-rfc-trace:` directive), the iter auto-creates AND fully authors a problem-traced RFC via `/wr-itil:capture-rfc --fix-time` (authoring a populated `## Scope` + real `## Tasks` from the problem's RCA + Fix Strategy, NOT an empty skeleton — ADR-073 P399; the deferred "flesh out later" step never self-fires, P375) then proceeds (ADR-073 auto-create-everywhere) — **UNLESS** an existing RFC cited in the ticket is already its fix vehicle and merely lacks the trace edge, in which case the iter **wires** the trace edge into that RFC's `problems:` array (running `wr-itil-update-problem-rfcs-section`) rather than minting a redundant duplicate RFC that fragments the fix (P371; existing-vehicle-untraced sub-case — ADR-073 auto-create is the *no-vehicle* case only; vehicle-vs-merely-related is a judgement read of citation context, structured-logged as `I13: wired P<NNN> trace edge into existing RFC-<NNN>`; the load-bearing branch prose lives in the delegated `/wr-itil:manage-problem` I13 gate). This is NOT an aside-capture distraction: the auto-created RFC is the **mandatory vehicle for THIS iter's own fix** (ADR-071), not a tangential observation — it is in-scope working of the current ticket, framework-mediated (NOT cat-1 direction-setting → NO `AskUserQuestion`, P132), and the loop is NEVER skipped or blocked for a missing RFC (ADR-073). Structured-log the auto-create event to the iter summary (`notes`) per JTBD-006 audit-trail + the ADR-073 reassessment criterion (auto-created-RFC-under-scoped signal feeds `/wr-retrospective:run-retro`). Do NOT use `ScheduleWakeup` under any circumstance (P083 — iteration workers must not self-reschedule). **NEVER call `AskUserQuestion` mid-loop in AFK** (P135 / ADR-044): direction / deviation-approval / one-time-override / silent-framework observations queue at `ITERATION_SUMMARY.outstanding_questions` for loop-end batched presentation. **This includes the manage-problem substance-confirm-before-build guard (ADR-074 (Confirm a decision's substance before building dependent work)):** when the propose-fix step detects that the fix builds on a born-`proposed` decision whose substance is unconfirmed (via `wr-architect-is-decision-unconfirmed`), the iter does NOT implement on it and does NOT ask mid-loop — it queues a `category: "direction"` entry naming the unconfirmed ADR + its Decision Outcome for loop-end confirmation, and routes the ticket to `action: skipped`, `skip_reason_category: user-answerable`. Building on the unconfirmed substance instead (or guessing the choice) is the P315 failure this guard exists to prevent. The queued substance-confirm is a legitimate cat-1 direction ask — it is NOT counted as lazy in the Step 2d Ask Hygiene Pass (ADR-074 lazy-count exclusion). Per-iter `AskUserQuestion` calls are sub-contracting framework-resolved decisions back to the user (lazy deferral per Step 2d Ask Hygiene Pass classification). Non-interactive defaults apply per ADR-013 Rule 6 + ADR-044's framework-resolution boundary. **Treat the user as transient** (P130): even when observably present at orchestrator dispatch time, the user may answer one question and disappear for hours; presence is not a reliable signal and is not the goal. The iter's job is to progress the ticket and accumulate questions for batched surfacing — not to ask "is it OK to proceed?" at a mechanical-stage boundary. **Do NOT poll `bats` output with a bats-console-summary regex against TAP-format output** (P146 — bash until-loop-deadlock antipattern). The bats-console-summary line `<N> tests, <M> failures` is emitted ONLY by bats's *default* (non-TAP) formatter; `bats --tap` does not emit a console summary, so a polling loop of shape `until [ -f $OUT ] && grep -qE '^[0-9]+ tests?,' $OUT; do sleep 5; done` spins forever after bats completes (silent deadlock — no error, no exit; recovery requires manual SIGTERM with metadata loss per the P146/P147 stuck-before-emit subclass). When you need to wait on a backgrounded bats run, prefer `wait $bg_pid` (Unix idiom — completion signaled by process exit, no regex required) or, for the Bash tool, `run_in_background=true` + `BashOutput` polling on the tool's exit-state field rather than regex-poll on stdout. If you genuinely must regex-poll TAP output, anchor on the TAP plan line `^[0-9]+\.\.[0-9]+` (e.g. `1..1455`) — TAP's plan line is emitted on completion and is format-stable across bats versions; the bats-console-summary line is not. The console-summary vs TAP-format divergence is the load-bearing detail: `bats` and `bats --tap` produce structurally different stdout, and the antipattern assumes the former when iter dispatch typically uses the latter. **Do NOT poll subprocess completion with `pgrep -f '<pattern>'` inside an `until` / `while` loop** (P232 — self-referential pgrep deadlock; sibling variant of P146). `pgrep -f` matches against the FULL command line of every running process, so the polling loop's own `zsh -c` argument (which contains the literal `pgrep -f '<pattern>'` text) matches itself; with multiple concurrent polling loops, each loop matches the others and spins forever. Worked example of the antipattern: `until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done` — the 2026-05-16 P232 deadlock witness; 4 concurrent polling loops each matched the others' command lines while no actual bats process ran; 45 min wall-clock + $20-30 wasted before manual SIGTERM. The same self-reference shape applies to `while pgrep -f ...; do sleep; done` and to `until ! pkill -0 -f '<pattern>'` / `while pkill -0 -f '<pattern>'` (signal-0 polling). The structural fix is the same as P146: prefer `wait $bg_pid` (Unix idiom — shell-native completion signal, no regex / no pgrep) or Bash-tool `run_in_background=true` + `BashOutput` polling (harness-tracked completion state). The hook `packages/itil/hooks/itil-bash-polling-antipattern-detect.sh` denies these shapes at PreToolUse:Bash, but the prompt rule belongs here too — structural enforcement + prompt discipline together close the class. **Do NOT leave a backgrounded task unreaped at turn-end** (`run_in_background: true` on an Agent or Bash tool call, or a `&`-detached shell job, whose completion you intend to observe in a *later* turn) inside iter dispatch contexts (P370 — turn-end-mid-background work-loss; sibling-class to P083 / P146 / P232). The iter subprocess is dispatched via `claude -p`, a single-shot CLI invocation with NO auto-resume affordance: its turn boundary IS its process boundary. A background task that outlives the turn never resumes — the iter exits at turn-end with the task incomplete and its own work staged but uncommitted (witnessed: iter 11 of a prior loop — $8.02 / 17 min / 8 staged files / 11 GREEN bats / ZERO commits; recovery required orchestrator main-turn salvage). **The prohibition is on the cross-turn / turn-end-survivor shape, NOT on backgrounding per se:** the P146/P232-sanctioned idiom of launching `run_in_background=true` + `BashOutput`-poll-then-`wait $bg_pid` (or plain `wait $bg_pid` on a `&` job) **within the same turn** is fine — it reaps the task before turn-end. Use foreground-synchronous invocation instead: the Agent tool WITHOUT `run_in_background: true` (the result returns in-turn, so the commit step is reached), or intra-turn background that you `wait` on before the turn closes. The distinction from the P146/P232 polling antipatterns: those forbid *how* you wait (regex / pgrep poll loops); this forbids *deferring a task's completion past the turn boundary*, where `claude -p` has no notification re-entry to bring you back. The interactive Claude Code session masks this hazard (notification-driven re-entry); the AFK iter subprocess does not. **If the fix changes shippable code or package behaviour** (any path under `packages/<plugin>/{src,bin,hooks,skills,scripts,lib,agents}` excluding test paths — `test/`, `hooks/test/`, `scripts/test/` — and excluding `README.md` + `docs/*.md`), **the iter MUST author a `.changeset/*.md` entry in the same single ADR-014-grain commit as the fix** (the changeset names the bumping plugin via the YAML frontmatter `"@windyroad/<plugin>": <patch|minor|major>` per the changesets-action contract). **Doc-only changes** (under `docs/`, `*.md`) **and test-only changes** (under any `test/` path) **that ship no behaviour MAY omit the changeset**. The orchestrator's Step 6.5 release-cadence drain runs `release:watch` only when `.changeset/` is non-empty after push — without an iter-authored changeset, code-shape fixes accumulate without ever shipping to npm (violating JTBD-006's audit-trail expectation + JTBD-007's "Keep Plugins Current" closure dependency). Hook `packages/itil/hooks/itil-changeset-discipline.sh` (P141) provides hook-level enforcement at `git commit` time as defence-in-depth — but plugin hook execution depends on the marketplace cache carrying the current hook version, so the prompt-time constraint here MUST land independently (composes-with the hook; does NOT rely on the hook being installed). Inbound-reported from downstream consumer bbstats as their P195 — see [Related](#related) for `**Origin**: inbound-reported (bbstats#195)` per ADR-076. **`@jtbd JTBD-006`** (load-bearing) **`@jtbd JTBD-007`** (closure-dependent).
|
|
684
|
+
3. **Constraints**: commit the completed work per ADR-014. Do NOT push, do NOT run `push:watch`, do NOT run `release:watch` — the orchestrator's Step 6.5 owns release cadence. Do NOT invoke `capture-*` background skills mid-iter (AFK carve-out — ADR-032), **EXCEPT** (a) **retro-surfaced observations of recurring class-of-behaviour** — those route to `/wr-itil:capture-problem` per the **P342 mechanical-stage carve-out** (see retro-on-exit constraint #4 below; same trust-boundary as `/wr-retrospective:run-retro` Step 4a verification close-on-evidence — P342); and (b) **the I13 fix-time row draw** — when the propose-fix gate inside the delegated `/wr-itil:manage-problem` traversal detects a Known Error nothing yet proposes a fix for (`wr-itil-check-fix-rfc-trace` emits a `no-rfc-trace:` directive), the iter **draws a release row on a story map that already covers the journey**, gives it at least one story card, and makes that card's story name the problem in its own `problems:` list — then proceeds. **A fix proposal is a release row; it is never a new document under `docs/rfcs/`.** Take the identity from the directive, which comes from `wr-itil-next-rfc-id` — the single rule that sees rows, documents and git history at once, and the only one that will not re-issue an identity a row already holds. **UNLESS** an existing vehicle cited in the ticket is already this ticket's fix and merely lacks the trace edge, in which case the iter **wires** that edge — a card on the existing row, or the `problems:` array of a legacy document — rather than drawing a duplicate that fragments the fix across two vehicles (P371; existing-vehicle-untraced sub-case; vehicle-vs-merely-related is a judgement read of citation context, structured-logged as `I13: wired P<NNN> trace edge into existing fix vehicle <ID>`; the load-bearing branch prose lives in the delegated `/wr-itil:manage-problem` I13 gate). This is NOT an aside-capture distraction: the row is the **mandatory vehicle for THIS iter’s own fix** (ADR-071), not a tangential observation — it is in-scope working of the current ticket, framework-mediated (NOT cat-1 direction-setting → NO `AskUserQuestion`, P132), and drawing a row onto a map a person has already approved inherits that approval rather than needing a fresh one. **Two things the iter must NOT do silently**: draw a row whose creation would change what the map’s approval covers — a new map, a new activity column, or a new job on the map’s traces, judged from `oversight_map_substance_keys()` in `lib/story-oversight.sh`, the one place those keys are enumerated — or pick a fix approach no existing decision record covers. Either of those queues ONE entry at `outstanding_questions` and the iter moves to the next problem; the loop is never stopped for it. The predicate can also refuse outright (exit 3), and the two refusals are handled differently: a map edited without being re-rendered is **mechanical** — re-render it with `wr-itil-render-story-map` and ask again, asking nobody — while a repository with no story maps at all queues ONE entry (draw a story map covering this work) and the iter carries on to the next problem rather than halting. Structured-log the draw event to the iter summary (`notes`) per JTBD-006 audit-trail. Do NOT use `ScheduleWakeup` under any circumstance (P083 — iteration workers must not self-reschedule). **NEVER call `AskUserQuestion` mid-loop in AFK** (P135 / ADR-044): direction / deviation-approval / one-time-override / silent-framework observations queue at `ITERATION_SUMMARY.outstanding_questions` for loop-end batched presentation. **This includes the manage-problem substance-confirm-before-build guard (ADR-074 (Confirm a decision's substance before building dependent work)):** when the propose-fix step detects that the fix builds on a born-`proposed` decision whose substance is unconfirmed (via `wr-architect-is-decision-unconfirmed`), the iter does NOT implement on it and does NOT ask mid-loop — it queues a `category: "direction"` entry naming the unconfirmed ADR + its Decision Outcome for loop-end confirmation, and routes the ticket to `action: skipped`, `skip_reason_category: user-answerable`. Building on the unconfirmed substance instead (or guessing the choice) is the P315 failure this guard exists to prevent. The queued substance-confirm is a legitimate cat-1 direction ask — it is NOT counted as lazy in the Step 2d Ask Hygiene Pass (ADR-074 lazy-count exclusion). Per-iter `AskUserQuestion` calls are sub-contracting framework-resolved decisions back to the user (lazy deferral per Step 2d Ask Hygiene Pass classification). Non-interactive defaults apply per ADR-013 Rule 6 + ADR-044's framework-resolution boundary. **Treat the user as transient** (P130): even when observably present at orchestrator dispatch time, the user may answer one question and disappear for hours; presence is not a reliable signal and is not the goal. The iter's job is to progress the ticket and accumulate questions for batched surfacing — not to ask "is it OK to proceed?" at a mechanical-stage boundary. **Do NOT poll `bats` output with a bats-console-summary regex against TAP-format output** (P146 — bash until-loop-deadlock antipattern). The bats-console-summary line `<N> tests, <M> failures` is emitted ONLY by bats's *default* (non-TAP) formatter; `bats --tap` does not emit a console summary, so a polling loop of shape `until [ -f $OUT ] && grep -qE '^[0-9]+ tests?,' $OUT; do sleep 5; done` spins forever after bats completes (silent deadlock — no error, no exit; recovery requires manual SIGTERM with metadata loss per the P146/P147 stuck-before-emit subclass). When you need to wait on a backgrounded bats run, prefer `wait $bg_pid` (Unix idiom — completion signaled by process exit, no regex required) or, for the Bash tool, `run_in_background=true` + `BashOutput` polling on the tool's exit-state field rather than regex-poll on stdout. If you genuinely must regex-poll TAP output, anchor on the TAP plan line `^[0-9]+\.\.[0-9]+` (e.g. `1..1455`) — TAP's plan line is emitted on completion and is format-stable across bats versions; the bats-console-summary line is not. The console-summary vs TAP-format divergence is the load-bearing detail: `bats` and `bats --tap` produce structurally different stdout, and the antipattern assumes the former when iter dispatch typically uses the latter. **Do NOT poll subprocess completion with `pgrep -f '<pattern>'` inside an `until` / `while` loop** (P232 — self-referential pgrep deadlock; sibling variant of P146). `pgrep -f` matches against the FULL command line of every running process, so the polling loop's own `zsh -c` argument (which contains the literal `pgrep -f '<pattern>'` text) matches itself; with multiple concurrent polling loops, each loop matches the others and spins forever. Worked example of the antipattern: `until ! pgrep -f 'bats --recursive' > /dev/null 2>&1; do sleep 5; done` — the 2026-05-16 P232 deadlock witness; 4 concurrent polling loops each matched the others' command lines while no actual bats process ran; 45 min wall-clock + $20-30 wasted before manual SIGTERM. The same self-reference shape applies to `while pgrep -f ...; do sleep; done` and to `until ! pkill -0 -f '<pattern>'` / `while pkill -0 -f '<pattern>'` (signal-0 polling). The structural fix is the same as P146: prefer `wait $bg_pid` (Unix idiom — shell-native completion signal, no regex / no pgrep) or Bash-tool `run_in_background=true` + `BashOutput` polling (harness-tracked completion state). The hook `packages/itil/hooks/itil-bash-polling-antipattern-detect.sh` denies these shapes at PreToolUse:Bash, but the prompt rule belongs here too — structural enforcement + prompt discipline together close the class. **Do NOT leave a backgrounded task unreaped at turn-end** (`run_in_background: true` on an Agent or Bash tool call, or a `&`-detached shell job, whose completion you intend to observe in a *later* turn) inside iter dispatch contexts (P370 — turn-end-mid-background work-loss; sibling-class to P083 / P146 / P232). The iter subprocess is dispatched via `claude -p`, a single-shot CLI invocation with NO auto-resume affordance: its turn boundary IS its process boundary. A background task that outlives the turn never resumes — the iter exits at turn-end with the task incomplete and its own work staged but uncommitted (witnessed: iter 11 of a prior loop — $8.02 / 17 min / 8 staged files / 11 GREEN bats / ZERO commits; recovery required orchestrator main-turn salvage). **The prohibition is on the cross-turn / turn-end-survivor shape, NOT on backgrounding per se:** the P146/P232-sanctioned idiom of launching `run_in_background=true` + `BashOutput`-poll-then-`wait $bg_pid` (or plain `wait $bg_pid` on a `&` job) **within the same turn** is fine — it reaps the task before turn-end. Use foreground-synchronous invocation instead: the Agent tool WITHOUT `run_in_background: true` (the result returns in-turn, so the commit step is reached), or intra-turn background that you `wait` on before the turn closes. The distinction from the P146/P232 polling antipatterns: those forbid *how* you wait (regex / pgrep poll loops); this forbids *deferring a task's completion past the turn boundary*, where `claude -p` has no notification re-entry to bring you back. The interactive Claude Code session masks this hazard (notification-driven re-entry); the AFK iter subprocess does not. **If the fix changes shippable code or package behaviour** (any path under `packages/<plugin>/{src,bin,hooks,skills,scripts,lib,agents}` excluding test paths — `test/`, `hooks/test/`, `scripts/test/` — and excluding `README.md` + `docs/*.md`), **the iter MUST author a `.changeset/*.md` entry in the same single ADR-014-grain commit as the fix** (the changeset names the bumping plugin via the YAML frontmatter `"@windyroad/<plugin>": <patch|minor|major>` per the changesets-action contract). **Doc-only changes** (under `docs/`, `*.md`) **and test-only changes** (under any `test/` path) **that ship no behaviour MAY omit the changeset**. The orchestrator's Step 6.5 release-cadence drain runs `release:watch` only when `.changeset/` is non-empty after push — without an iter-authored changeset, code-shape fixes accumulate without ever shipping to npm (violating JTBD-006's audit-trail expectation + JTBD-007's "Keep Plugins Current" closure dependency). Hook `packages/itil/hooks/itil-changeset-discipline.sh` (P141) provides hook-level enforcement at `git commit` time as defence-in-depth — but plugin hook execution depends on the marketplace cache carrying the current hook version, so the prompt-time constraint here MUST land independently (composes-with the hook; does NOT rely on the hook being installed). Inbound-reported from downstream consumer bbstats as their P195 — see [Related](#related) for `**Origin**: inbound-reported (bbstats#195)` per ADR-076. **`@jtbd JTBD-006`** (load-bearing) **`@jtbd JTBD-007`** (closure-dependent).
|
|
685
685
|
4. **Retro-on-exit (P086) + retro-surfaced observation classification (P342) + iter-owned BRIEFING commit (P212)**: before emitting `ITERATION_SUMMARY`, invoke `/wr-retrospective:run-retro`. Retro runs INSIDE this subprocess so its Step 2b pipeline-instability scan has access to the iteration's rich tool-call history (hook misbehaviour, repeat-workaround patterns, subagent-delegation friction, release-path instability). Tickets retro creates ride a separate path: they delegate through `/wr-itil:manage-problem` which IS ADR-014 in-scope and self-commits each ticket per its own Step 11. Those commits land independently and the orchestrator picks them up on the next Step 1 scan.
|
|
686
686
|
|
|
687
687
|
**BRIEFING.md commit responsibility — iter owns, run-retro does not (P212).** run-retro is explicitly out-of-scope for self-commit per ADR-014's Scope section (which lists `packages/retrospective/skills/run-retro/SKILL.md` under "Out of scope for now"). Retro therefore EDITS but DOES NOT COMMIT `docs/BRIEFING.md` / `docs/briefing/*.md`. The iter subprocess (NOT run-retro, NOT the orchestrator main turn) owns the BRIEFING commit. After retro completes, run `git status --porcelain docs/BRIEFING.md docs/briefing/`. If non-empty, the iter:
|