@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.
@@ -497,5 +497,5 @@
497
497
  }
498
498
  },
499
499
  "name": "wr-itil",
500
- "version": "1.1.2"
500
+ "version": "1.2.0"
501
501
  }
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "1.1.2",
3
+ "version": "1.2.0-preview.1102",
4
4
  "description": "ITIL-aligned IT service management for Claude Code (problem, and future incident/change skills)",
5
5
  "bin": {
6
6
  "windyroad-itil": "./bin/install.mjs"
@@ -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 RFC-trace gate
5
- # (P314 Phase 2 / RFC-005 B3). Before fix work commences on a Known
6
- # Error, ADR-072 requires an RFC tracing the problem; ADR-073 requires
7
- # that a *missing* RFC be auto-created (never blocked), everywhere the
8
- # gate fires (interactive /wr-itil:manage-problem AND AFK
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
- # This script answers the deterministic question "does any RFC trace
12
- # this problem?" and, when none does, emits an auto-create DIRECTIVE
13
- # on stdout naming the canonical vehicle (/wr-itil:capture-rfc). It does
14
- # NOT create the RFC itself: the create is skill-orchestrated through
15
- # capture-rfc (the canonical ADR-070-compliant problem-traced-skeleton
16
- # vehicle already allocates the next RFC ID, refreshes the README, and
17
- # carries no Considered-Options block), so the create logic is not
18
- # duplicated here. Architect verdict 2026-06-16 (P314 iter 11): the
19
- # detect-in-script / create-via-capture-rfc split is consistent with the
20
- # ADR-060 I1 load-bearing-from-the-start standard (the sibling trace
21
- # invariant enforces identically deterministic detection in committed
22
- # shell, create orchestrated through capture).
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
- # Default <rfcs-dir> is `docs/rfcs`.
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 (ADR-073 — auto-create-not-block; the gate NEVER blocks):
33
- # - An RFC traces the problem when its frontmatter `problems:` array
34
- # contains the exact PID (boundary-safe: P31 / P3140 do NOT match a
35
- # P314 query). exit 0, EMPTY stdout (fix proceeds; no auto-create).
36
- # - No RFC traces the problem exit 0, stdout carries the directive
37
- # `no-rfc-trace: P<NNN> ...capture-rfc...ADR-073...` for the calling
38
- # skill to act on (auto-create then proceed). Exit stays 0: a missing
39
- # RFC is never a block (ADR-073).
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
- # @adr ADR-072 (RFC required at the propose-fix step on a Known Error
43
- # the gate placement this predicate serves)
44
- # @adr ADR-073 (fix-time gate auto-creates a missing RFC, everywhere —
45
- # never blocks; hence exit 0 on the absent branch + a directive, not a
46
- # deny)
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
- # ── Scan rfcs-dir for any RFC whose frontmatter `problems:` claims PID. ──────
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
- # An RFC traces the problem — the fix proceeds; no auto-create needed.
159
+ # Something already proposes a fix for this problem — work proceeds.
106
160
  exit 0
107
161
  fi
108
162
 
109
- # No RFC traces the problem. Emit the auto-create directive (ADR-073) and
110
- # exit 0 — a missing RFC is NEVER a block. The calling skill auto-creates a
111
- # problem-traced skeleton via /wr-itil:capture-rfc, then proceeds with the fix.
112
- printf 'no-rfc-trace: %s — auto-create a problem-traced RFC via /wr-itil:capture-rfc before fix work, then proceed (ADR-072 placement / ADR-073 auto-create-not-block)\n' "$PID"
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
- return readFacts()
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 + Tasks under the `--fix-time` flag, per ADR-073), 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.
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 auto-create path** (`--fix-time`), where the I13 propose-fix gate auto-fires it on an RFC-less Known Error and it **authors the full RFC** from the traced problem. Per the ADR-073 P399 amendment, fix-time RFC scope is **framework-mediated** (derived from the already-pinned problem + ADR-071, not direction-setting), so auto-firing with full authoring is correct NOT a re-ask of a decided question (P132 / inverse-P078).
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's auto-create branch (`/wr-itil:manage-problem` + `/wr-itil:work-problems`); the user-aside path omits it. 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`.
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: `max(local, origin) + 1`, three-digit padded | silent-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`) | 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 (ADR-073 § ADR-044 reclassification, P399). Born `unconfirmed`; ratified at `manage-rfc accepted` | silent-framework |
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
- Same `max(local, origin) + 1` formula as `capture-problem` Step 3, scanning `docs/rfcs/RFC-*.md` instead:
161
+ Ask the allocator. Do NOT scan a directory yourself:
160
162
 
161
163
  ```bash
162
- local_max=$(ls docs/rfcs/RFC-*.md 2>/dev/null | sed 's|.*/RFC-||;s|-.*||' | grep -oE '^[0-9]+' | sort -n | tail -1)
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
- Log the renumber decision in the operation report if origin and local diverged.
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 (RFC scope is direction-setting per ADR-073).
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 produces the RFC per ADR-072). See ADR-022 for rationale.
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 produces the RFC per ADR-072) | Root cause documented, reproduction test exists, workaround in place |
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 RFC-trace gate (ADR-072 placement / ADR-073 auto-create — RFC-005 B3/B4).** BEFORE the traversal below, enforce the fix-time RFC-trace invariant: a fix proposed on a Known Error requires an RFC tracing the problem (ADR-071 unconditional; ADR-072 places the gate here, conforming to ADR-022 Known Error semantics — the fix is proposed *after* Known Error and produces the RFC). Run the load-bearing predicate (ADR-049 `$PATH` shim — never repo-relative from a SKILL):
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
- - **Empty stdout** (an RFC already traces the problem): proceed to the traversal below.
192
- - **Non-empty stdout** (directive `no-rfc-trace: P<NNN> …`): the predicate has confirmed only that *no RFC's `problems:` array names this PID* — it has NOT decided that no fix vehicle exists. Distinguish **two sub-cases** before acting (P371; ADR-073 auto-create is intended ONLY when no fix vehicle exists, NOT when an existing vehicle merely lacks the trace edge — architect-confirmed):
193
- - **(a) Existing-vehicle-untraced an RFC is already this ticket's fix vehicle but just hasn't wired the trace edge.** Read the ticket's `## Fix Strategy` / `## Resolution` / `## Dependencies` / `## Related` sections for an RFC cited as the **fix vehicle** — i.e. the fix IS that RFC's task set (the recurring shape: a rework / follow-on Known Error whose fix is an existing RFC's B-tasks, so that RFC's `problems:` array names the *original driver* problem, not this ticket). This is a **judgement read of the citation context, NOT a blind "any cited RFC" match**: an RFC named only as context / `composes with` / `**Related**` background is NOT a fix vehicle — wiring its trace edge would pollute its `problems:` array. If a genuine existing fix vehicle is found: **wire** this problem's trace edge into that RFC's frontmatter `problems:` array (Edit the `problems: [...]` line to include `P<NNN>` + a short inline `**Problems**:` body annotation noting the edge was wired because the vehicle already exists), then run `wr-itil-update-problem-rfcs-section <problem-file>` so the ticket's derived `## RFCs` section reflects the wired trace. Re-run the predicate (now empty) and proceed. Do **NOT** auto-create — minting a second RFC here produces a redundant duplicate that fragments the fix across two trace vehicles (the exact P371 defect). Structured-log the wire event (e.g. `I13: wired P<NNN> trace edge into existing fix-vehicle RFC-<NNN>`) to the iter summary `notes` for the JTBD-006 audit trail.
194
- - **(b) No-vehicle no existing RFC is this ticket's fix vehicle.** **auto-create AND fully author** a problem-traced RFC — do NOT block, do NOT skip, do NOT ask (ADR-073 auto-create-everywhere; the create AND its full scope authoring are framework-mediated, not cat-1 direction-setting — the scope is *derived* from the already-traced problem under ADR-071's pinned direction, not new direction *invented*; so NO `AskUserQuestion` consent gate fires here P132 / inverse-P078; ADR-073 § ADR-044 reclassification, P399). Delegate to `/wr-itil:capture-rfc --fix-time` with a problem trace derived from this Known Error (`P<NNN>` + a fix-scoped title from the problem's `## Fix Strategy` / root cause), which is the canonical ADR-070-compliant vehicle (allocates the next RFC ID, refreshes the RFC README, emits no Considered-Options block — no create logic is duplicated). The `--fix-time` flag makes capture-rfc author a **populated `## Scope`** (the fix being proposed + implementation approach) and a **real `## Tasks`** decomposition from the problem's RCA + Fix Strategy — NOT an empty skeleton placeholder (ADR-073 P399: the deferred "flesh out later" step never self-fires, P375, so author it now while the fix context is in hand). Then re-run the predicate (now empty) and proceed. The authored RFC is the fix-proposal artifact, born `human-oversight: unconfirmed` (ratified at `manage-rfc accepted`). Structured-log the auto-create event to the iter summary `notes` (the ADR-073 reassessment criterion — auto-authored-RFC-mis-scoped signal feeds `/wr-retrospective:run-retro`).
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** itemdraw 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`); the create half is orchestrated through `capture-rfc` (the ADR-060 I1 load-bearing-from-the-start standarddeterministic detection in committed shell, create through the canonical capture surface). This gate fires at **every** fix-time surface; the AFK `/wr-itil:work-problems` orchestrator dispatches its fix work *through this same manage-problem traversal*, so the gate covers the AFK surface transitively (ADR-073 "everywhere").
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 an RFC traces the problem; if the `## Fix Strategy` prose itself references none (a freshly auto-created RFC may not yet be cited inline), fall through to the legacy direct-implementation path (step 6 below) using the traced/auto-created RFC.
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 and produces the RFC (per ADR-072). Releasing the fix is a separate Known Error → Verification Pending transition — do NOT stay on `.known-error.md` after the fix ships.
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, fix proposal produces the RFC per ADR-072).
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: