@windyroad/itil 2.3.0-preview.1212 → 2.4.0-preview.1221
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/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -0
- package/bin/wr-itil-check-goal-condition-drift +51 -0
- package/package.json +1 -1
- package/scripts/check-goal-condition-drift.sh +178 -0
- package/skills/work-problem/SKILL.md +14 -4
- package/skills/work-problems/SKILL.md +60 -9
- package/skills-codex/work-problem/SKILL.md +14 -4
package/README.md
CHANGED
|
@@ -105,6 +105,7 @@ See [the "Add `manage-incident` Skill to `wr-itil` Plugin" architecture rule](..
|
|
|
105
105
|
| `/wr-itil:manage-story` | Heavyweight story lifecycle management — draft → accepted → in-progress → done → archived; I7+I8+I10+I12 hard-block at accepted transition; INVEST 4-axis check; auto-transitions on `Refs: STORY-NNN` commit trailer + linked RFC closure (Phase 2 / the "Problem tickets strain as fixes decompose into multiple coordinated changes — need an RFC framework that ties all changes back to problems (and unifies technical with user/business problems)" problem). **I12 (the "A release row is the RFC, and the map is the approval surface" architecture rule)**: `accepted` requires the story to be approved, and an implementing commit against an unapproved story is blocked. Approval is the story map's — ratify the map and every story on it is approved with it | Experimental |
|
|
106
106
|
| `/wr-itil:capture-story-map` | Lightweight story-map-capture skill — mandatory problem-trace AND JTBD-trace per the "Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology" architecture rule I3 + I4 invariants; HTML skeleton at `docs/story-maps/draft/STORY-MAP-NNN-<slug>.html` per the "Problem-RFC-Story framework with mandatory problem-trace and unified problem ontology" architecture rule § Phase 2 encoding amendment 2026-05-12 (Phase 2 / the "Problem tickets strain as fixes decompose into multiple coordinated changes — need an RFC framework that ties all changes back to problems (and unifies technical with user/business problems)" problem) | Experimental |
|
|
107
107
|
| `/wr-itil:manage-story-map` | Heavyweight story-map lifecycle management — draft → accepted → in-progress → completed → archived; backbone/ribs/slices authoring guidance; reverse-trace `## Story Maps` refresh on driving problems + JTBDs (Phase 2 / the "Problem tickets strain as fixes decompose into multiple coordinated changes — need an RFC framework that ties all changes back to problems (and unifies technical with user/business problems)" problem) | Experimental |
|
|
108
|
+
| `/wr-itil:migrate-story-map` | Replace one eligible legacy story map with a manifest-bound canonical map from a complete adopter-ratified mapping | Experimental |
|
|
108
109
|
| `/wr-itil:reconcile-story-maps` | Detect and correct drift between `docs/story-maps/README.md` and on-disk story-map HTML inventory (Phase 2 / the "Problem tickets strain as fixes decompose into multiple coordinated changes — need an RFC framework that ties all changes back to problems (and unifies technical with user/business problems)" problem) | Experimental |
|
|
109
110
|
| `/wr-itil:list-story-maps` | Read-only display of story-maps grouped by lifecycle state; no WSJF (I5 invariant — maps are planning artefacts, not work items) (Phase 2 / the "Problem tickets strain as fixes decompose into multiple coordinated changes — need an RFC framework that ties all changes back to problems (and unifies technical with user/business problems)" problem) | Experimental |
|
|
110
111
|
| `/wr-itil:manage-incident` | Declare, triage, mitigate, and close an incident with evidence-first discipline | Experimental |
|
|
@@ -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 (highest-version-wins-shim-wrapper-for-plugin-scaffold-template-shims-architecture-rule):
|
|
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 highest-version-wins-shim-wrapper-for-plugin-scaffold-template-shims-architecture-rule (highest-version-wins shim wrapper plugin scaffold)
|
|
19
|
+
# @adr plugin-bundled-scripts-invoked-from-skill-md-resolve-via-bin-on-path-architecture-rule (plugin-bundled scripts resolve via bin/ on $PATH — amended)
|
|
20
|
+
# @problem install-updates-refreshes-the-global-plugin-cache-but-does-not-fix-path-ordering-stale-plugin-version-shims-stay-first-on-path-so-subsequent-shim-invocations-run-old-code-problem (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/check-goal-condition-drift.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/check-goal-condition-drift.sh" "$@"
|
package/package.json
CHANGED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# wr-itil — assert every copy-paste /goal block embeds the canonical goal
|
|
3
|
+
# condition VERBATIM.
|
|
4
|
+
#
|
|
5
|
+
# Step 0e of /wr-itil:work-problems declares its canonical goal condition once,
|
|
6
|
+
# then repeats it inside copy-paste-ready blocks (a headless launch one-liner
|
|
7
|
+
# and an interactive block the user types). Those copies are prose, so they
|
|
8
|
+
# drift: before this check existed, the headless one-liner had silently lost
|
|
9
|
+
# two clauses from the canonical text — "(fresh open/known-error glob)
|
|
10
|
+
# classifying every ticket" and "naming the gate that could not complete" —
|
|
11
|
+
# which is how a drifted condition reaches a user as a copy-paste command.
|
|
12
|
+
#
|
|
13
|
+
# Contract:
|
|
14
|
+
# - The canonical condition is the first fenced block after the line
|
|
15
|
+
# carrying `<!-- CANONICAL-GOAL-CONDITION-SOURCE -->`.
|
|
16
|
+
# - Every fenced block introduced by `<!-- CANONICAL-CONDITION-EMBED -->`
|
|
17
|
+
# MUST contain that canonical text as a verbatim substring. An invocation
|
|
18
|
+
# carrier is permitted only as a prefix or suffix around it.
|
|
19
|
+
#
|
|
20
|
+
# This is the CHECK-ONLY variant of the shared-code-duplicated-into-per-package-lib-kept-in-sync-by-script-ci-drift-check-architecture-rule canonical-plus-check shape:
|
|
21
|
+
# there is no sync script, because a block carrying a prefix/suffix carrier
|
|
22
|
+
# cannot be mechanically regenerated from the canonical.
|
|
23
|
+
#
|
|
24
|
+
# Usage:
|
|
25
|
+
# check-goal-condition-drift.sh [<skill-md-path>] [--check]
|
|
26
|
+
# Default path: the work-problems SKILL.md, resolved relative to this
|
|
27
|
+
# script so it works from any cwd (plugin-bundled-scripts-invoked-from-skill-md-resolve-via-bin-on-path-architecture-rule — never cwd-relative).
|
|
28
|
+
# `--check` is accepted for symmetry with the sync-script family and is
|
|
29
|
+
# a no-op; this script is always a check.
|
|
30
|
+
#
|
|
31
|
+
# Exit codes:
|
|
32
|
+
# 0 — every embed contains the canonical text verbatim.
|
|
33
|
+
# 1 — at least one embed diverged (or none were found).
|
|
34
|
+
# 2 — usage error, or the canonical block is missing/empty.
|
|
35
|
+
#
|
|
36
|
+
# @adr shared-code-duplicated-into-per-package-lib-kept-in-sync-by-script-ci-drift-check-architecture-rule (canonical body + --check drift mode + CI step)
|
|
37
|
+
# @adr plugin-bundled-scripts-invoked-from-skill-md-resolve-via-bin-on-path-architecture-rule (plugin-bundled scripts resolve via bin/ on $PATH)
|
|
38
|
+
# @adr per-ticket-goal-anchors-each-afk-iteration-architecture-rule (per-ticket goal anchors each AFK iteration)
|
|
39
|
+
|
|
40
|
+
set -uo pipefail
|
|
41
|
+
|
|
42
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
43
|
+
DEFAULT_SKILL="$SCRIPT_DIR/../skills/work-problems/SKILL.md"
|
|
44
|
+
|
|
45
|
+
SKILL_MD=""
|
|
46
|
+
for arg in "$@"; do
|
|
47
|
+
case "$arg" in
|
|
48
|
+
--check) ;;
|
|
49
|
+
-*) printf 'check-goal-condition-drift: unknown option %s\n' "$arg" >&2; exit 2 ;;
|
|
50
|
+
*) SKILL_MD="$arg" ;;
|
|
51
|
+
esac
|
|
52
|
+
done
|
|
53
|
+
[ -n "$SKILL_MD" ] || SKILL_MD="$DEFAULT_SKILL"
|
|
54
|
+
|
|
55
|
+
if [ ! -f "$SKILL_MD" ]; then
|
|
56
|
+
printf 'check-goal-condition-drift: no such file: %s\n' "$SKILL_MD" >&2
|
|
57
|
+
exit 2
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Extract the fenced block that follows the first line carrying $1.
|
|
61
|
+
# Prints the block's contents (without the fence lines).
|
|
62
|
+
extract_block_after() {
|
|
63
|
+
awk -v marker="$1" '
|
|
64
|
+
index($0, marker) > 0 && !seen { seen = 1; next }
|
|
65
|
+
seen && !infence && /^[[:space:]]*```/ { infence = 1; next }
|
|
66
|
+
seen && infence && /^[[:space:]]*```/ { exit }
|
|
67
|
+
seen && infence { print }
|
|
68
|
+
' "$2"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
CANONICAL="$(extract_block_after 'CANONICAL-GOAL-CONDITION-SOURCE' "$SKILL_MD" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d '\n')"
|
|
72
|
+
|
|
73
|
+
if [ -z "$CANONICAL" ]; then
|
|
74
|
+
printf 'check-goal-condition-drift: canonical block missing or empty (marker: CANONICAL-GOAL-CONDITION-SOURCE) in %s\n' "$SKILL_MD" >&2
|
|
75
|
+
exit 2
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# Collect every embed block. Each is the fenced block after an EMBED marker.
|
|
79
|
+
# The marker must be a STANDALONE comment line. Anchoring matters: the prose
|
|
80
|
+
# above the canonical block names the marker inline when explaining the check,
|
|
81
|
+
# and a substring match would count that mention as an embed — inflating the
|
|
82
|
+
# count and, worse, "verifying" a block that is not a copy-paste block at all.
|
|
83
|
+
EMBED_LINES="$(grep -nE '^[[:space:]]*<!--[[:space:]]*CANONICAL-CONDITION-EMBED[[:space:]]*-->[[:space:]]*$' "$SKILL_MD" | cut -d: -f1)"
|
|
84
|
+
|
|
85
|
+
if [ -z "$EMBED_LINES" ]; then
|
|
86
|
+
printf 'check-goal-condition-drift: no CANONICAL-CONDITION-EMBED blocks found in %s\n' "$SKILL_MD" >&2
|
|
87
|
+
printf ' The canonical condition is declared but never embedded — either the\n' >&2
|
|
88
|
+
printf ' copy-paste blocks lost their markers, or the check is pointed at the\n' >&2
|
|
89
|
+
printf ' wrong file. Failing rather than passing vacuously.\n' >&2
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
FAILED=0
|
|
94
|
+
INDEX=0
|
|
95
|
+
while IFS= read -r lineno; do
|
|
96
|
+
INDEX=$((INDEX + 1))
|
|
97
|
+
BLOCK="$(tail -n "+${lineno}" "$SKILL_MD" | extract_block_after 'CANONICAL-CONDITION-EMBED' /dev/stdin | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -d '\n')"
|
|
98
|
+
if [ -z "$BLOCK" ]; then
|
|
99
|
+
printf 'DRIFT: embed #%d (line %s) has no fenced block after its marker\n' "$INDEX" "$lineno" >&2
|
|
100
|
+
FAILED=1
|
|
101
|
+
continue
|
|
102
|
+
fi
|
|
103
|
+
case "$BLOCK" in
|
|
104
|
+
*"$CANONICAL"*) ;;
|
|
105
|
+
*)
|
|
106
|
+
printf 'DRIFT: embed #%d (line %s) does not contain the canonical condition verbatim\n' "$INDEX" "$lineno" >&2
|
|
107
|
+
printf ' canonical: %s\n' "$CANONICAL" >&2
|
|
108
|
+
printf ' embed: %s\n' "$BLOCK" >&2
|
|
109
|
+
FAILED=1
|
|
110
|
+
;;
|
|
111
|
+
esac
|
|
112
|
+
done <<EOF
|
|
113
|
+
$EMBED_LINES
|
|
114
|
+
EOF
|
|
115
|
+
|
|
116
|
+
if [ "$FAILED" -ne 0 ]; then
|
|
117
|
+
printf '\ncheck-goal-condition-drift: FAILED — repair the embed(s) to contain the canonical text verbatim.\n' >&2
|
|
118
|
+
printf 'An invocation carrier is allowed only as a prefix or suffix around it.\n' >&2
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# Second coupling: the unattended-declaration discriminator.
|
|
124
|
+
#
|
|
125
|
+
# /wr-itil:work-problem's pinned short-circuit keys on a SENTENCE emitted by
|
|
126
|
+
# /wr-itil:work-problems Step 5 item 1. That is prose-to-prose coupling across
|
|
127
|
+
# two files with nothing binding them: reword either side and the short-circuit
|
|
128
|
+
# silently collapses to "never", which re-admits an unanswerable verification
|
|
129
|
+
# prompt and an off-grain ranking commit into an absent-user subprocess — the
|
|
130
|
+
# exact failure per-ticket-goal-anchors-each-afk-iteration-architecture-rule removed. The marked sentence must be identical on both
|
|
131
|
+
# sides.
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
SIBLING_SKILL="$(dirname "$SKILL_MD")/../work-problem/SKILL.md"
|
|
135
|
+
|
|
136
|
+
# Extract the bolded sentence immediately FOLLOWING marker $1 on its line in
|
|
137
|
+
# file $2. Anchoring on the marker is load-bearing: these marker lines carry
|
|
138
|
+
# several bold spans, and an unanchored greedy match silently returns whichever
|
|
139
|
+
# one happens to be last — which made this check compare two unrelated phrases
|
|
140
|
+
# and report drift on a correct tree.
|
|
141
|
+
extract_marked_sentence() {
|
|
142
|
+
grep -F "$1" "$2" 2>/dev/null | head -1 \
|
|
143
|
+
| sed -n "s/.*$1[^*]*\*\*\([^*]*\)\*\*.*/\1/p" \
|
|
144
|
+
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
SOURCE_SENTENCE="$(extract_marked_sentence 'UNATTENDED-DECLARATION-SOURCE' "$SKILL_MD")"
|
|
148
|
+
|
|
149
|
+
if [ -z "$SOURCE_SENTENCE" ]; then
|
|
150
|
+
printf 'DRIFT: no UNATTENDED-DECLARATION-SOURCE sentence found in %s\n' "$SKILL_MD" >&2
|
|
151
|
+
printf ' The dispatch prompt must carry the bolded unattended declaration that\n' >&2
|
|
152
|
+
printf ' the singular skill keys its pinned short-circuit on (per-ticket-goal-anchors-each-afk-iteration-architecture-rule).\n' >&2
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
if [ ! -f "$SIBLING_SKILL" ]; then
|
|
157
|
+
# Guard the search root: a check over a missing file would assert nothing.
|
|
158
|
+
printf 'check-goal-condition-drift: sibling skill not found: %s\n' "$SIBLING_SKILL" >&2
|
|
159
|
+
exit 2
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
CONSUMER_SENTENCE="$(extract_marked_sentence 'UNATTENDED-DECLARATION-CONSUMER' "$SIBLING_SKILL")"
|
|
163
|
+
|
|
164
|
+
if [ -z "$CONSUMER_SENTENCE" ]; then
|
|
165
|
+
printf 'DRIFT: no UNATTENDED-DECLARATION-CONSUMER sentence found in %s\n' "$SIBLING_SKILL" >&2
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if [ "$SOURCE_SENTENCE" != "$CONSUMER_SENTENCE" ]; then
|
|
170
|
+
printf 'DRIFT: the unattended declaration differs between the two skills\n' >&2
|
|
171
|
+
printf ' dispatcher (%s): %s\n' "$(basename "$(dirname "$SKILL_MD")")" "$SOURCE_SENTENCE" >&2
|
|
172
|
+
printf ' consumer (%s): %s\n' "$(basename "$(dirname "$SIBLING_SKILL")")" "$CONSUMER_SENTENCE" >&2
|
|
173
|
+
printf ' The short-circuit keys on this sentence; a mismatch collapses it to "never".\n' >&2
|
|
174
|
+
exit 1
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
printf 'check-goal-condition-drift: OK (%d embed(s) contain the canonical condition verbatim; unattended declaration matches across both skills)\n' "$INDEX"
|
|
178
|
+
exit 0
|
|
@@ -13,7 +13,7 @@ This skill is the "Problem 071: Argument-based skill subcommands are not discove
|
|
|
13
13
|
## Name distinction (work-problem vs work-problems)
|
|
14
14
|
|
|
15
15
|
- **`/wr-itil:work-problem`** (singular, this skill) — one ticket per invocation. Framework-mediated selection (WSJF + tie-break ladder). Intended for a user who wants to dispatch the next-highest ticket and then stop. User-override path: `/wr-itil:work-problem <NNN>` to pin a specific ticket.
|
|
16
|
-
- **`/wr-itil:work-problems`** (plural, AFK orchestrator) — loops through the backlog by WSJF,
|
|
16
|
+
- **`/wr-itil:work-problems`** (plural, AFK orchestrator) — loops through the backlog by WSJF, dispatching each iteration to this skill as `/wr-itil:work-problem <NNN>` in a fresh `claude -p` subprocess (per the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule as amended by the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem; the earlier Agent-tool shape is superseded — an Agent-tool subagent has no Agent tool of its own, so governance gates could not be satisfied inside it). Each dispatch is anchored by a per-ticket goal per the "Per-ticket goal anchors each AFK iteration" architecture rule. Intended for AFK batch runs; non-interactive selection; stops only when nothing actionable remains.
|
|
17
17
|
|
|
18
18
|
Both names coexist intentionally per the "Problem 071: Argument-based skill subcommands are not discoverable in Claude Code autocomplete" problem's out-of-scope note on the naming coexistence. The plural orchestrator uses this skill as its per-iteration unit.
|
|
19
19
|
|
|
@@ -54,6 +54,14 @@ fi
|
|
|
54
54
|
- **Cache fresh** (no output): read `docs/problems/README.md` and use the cached WSJF Rankings table for Step 2.
|
|
55
55
|
- **Cache stale** (prints "stale") or `README.md` missing: **delegate to `/wr-itil:review-problems`** via the Skill tool to refresh the ranking before proceeding. Do NOT re-implement the re-scoring logic here — that would fork the review path and break the "Problem 062: `manage-problem` does not refresh `docs/problems/README.md` on single-ticket transitions; fast-path cache goes stale silently" problem's canonical-cache-writer contract. The review skill's Step 4 verification prompt runs on this refresh path (the "Problem 048: manage-problem does not surface Fix Released problems as verification candidates when the fix path has been exercised" problem Candidate 1: Verification Queue prompts always fire so pending verifications don't accumulate off-ledger).
|
|
56
56
|
|
|
57
|
+
**Pinned + unattended short-circuit — skip this whole step (the "Per-ticket goal anchors each AFK iteration" architecture rule).** <!-- @jtbd the ": Progress the Backlog While I'm Away" user outcome (Progress the Backlog While I'm Away — an unanswerable prompt and an off-grain ranking commit must not enter an absent-user subprocess) --> When this skill is invoked against a **pinned ticket** (`/wr-itil:work-problem <NNN>`) AND the invocation **declares the run unattended**, skip the freshness check entirely: do not read the ranking, do not delegate to `/wr-itil:review-problems`, do not prompt, do not commit. Proceed straight to Step 3 with the named ticket.
|
|
58
|
+
|
|
59
|
+
Two things make this necessary rather than merely cheaper. The refresh delegation fires the review skill's Verification Queue prompt — **unanswerable in a subprocess with no user attached** (the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6; the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule). And its ranking rewrite is an **off-grain commit** inside what is supposed to be a single per-ticket unit of work (the "Governance Skills Commit Their Own Completed Work" architecture rule), which can also trip the orchestrator's unexpected-dirty-state halt. The ranking is not consulted at all when the ticket is pinned, so the refresh buys nothing here.
|
|
60
|
+
|
|
61
|
+
**The discriminator is a declaration, not a detection.** This skill is prose: it cannot observe a TTY, and under `claude -p` there is nothing to sniff. The unattended state is **declared by the dispatcher**. The exact sentence this short-circuit keys on, emitted by `/wr-itil:work-problems` Step 5 item 1: <!-- UNATTENDED-DECLARATION-CONSUMER --> **The user is AFK and this run is unattended.** A rule keyed on something the skill cannot observe would silently collapse to always or never — so the two surfaces are bound mechanically, not by hope: `wr-itil-check-goal-condition-drift` asserts the marked sentence is identical on both sides and fails CI if either is reworded.
|
|
62
|
+
|
|
63
|
+
**The interactive pinned path is unchanged.** `/wr-itil:work-problem <NNN>` is also the documented *user-override* path, where a user IS present, the verification prompt IS answerable, and firing it is a deliberate piggyback keeping pending verifications from accumulating off-ledger. It is **the absent user, not the pin**, that makes the refresh wrong. The unattended loop carries the verification cadence on other surfaces; the interactive singular path has none, so a blanket short-circuit would delete a self-firing cadence and hand the maintainer something to remember.
|
|
64
|
+
|
|
57
65
|
### 2. Select the ticket (framework-mediated)
|
|
58
66
|
|
|
59
67
|
Read the WSJF Rankings table from the now-fresh `docs/problems/README.md`. Apply the framework's tie-break ladder mechanically to pick the next ticket — selection is **framework-mediated** per the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule's Framework-Mediated Surface (Prioritisation row). The agent picks, reports the choice + the tie-break rung that decided, and proceeds. **No `AskUserQuestion` fires for selection** — the WSJF formula + tie-break ladder already resolve the decision.
|
|
@@ -123,7 +131,7 @@ After the delegated `/wr-itil:manage-problem <NNN>` completes:
|
|
|
123
131
|
|
|
124
132
|
## Goal anchor for headless runs (the "agent ends the work-problems loop (emits ALL_DONE) prematurely while actionable Tier-2 backlog remains, by rationalising the remainder as out-of-scope / interactive-gated" problem / the "AFK loops anchor completion with the native `/goal` external evaluator" architecture rule)
|
|
125
133
|
|
|
126
|
-
A headless single-ticket run can anchor its completion with Claude Code's native `/goal` external evaluator (≥ v2.1.139), so a fresh model — not the working agent — judges whether the ticket genuinely reached an end state: `claude -p "/goal Run /wr-itil:work-problem to work the top ticket. Complete when the report printed in the conversation shows a committed outcome (with commit SHA) or a recorded blocker for the selected ticket."` (No turn-bound: trust the goal — the loop stops only at its real end states: a committed outcome, a recorded blocker, or quota exhaustion.)
|
|
134
|
+
A headless single-ticket run can anchor its completion with Claude Code's native `/goal` external evaluator (≥ v2.1.139), so a fresh model — not the working agent — judges whether the ticket genuinely reached an end state: `claude -p "/goal Run /wr-itil:work-problem to work the top ticket. Complete when the report printed in the conversation shows a committed outcome (with commit SHA) or a recorded blocker for the selected ticket."` (No turn-bound: trust the goal — the loop stops only at its real end states: a committed outcome, a recorded blocker, or quota exhaustion.) **On the Claude Code surface** there is no programmatic mid-session surface for setting a goal (probed 2026-07-06 at v2.1.201; re-probed 2026-09-18 at v2.1.276 — no `--goal` flag, and the Skill tool rejects it as a UI command) — interactive users type `/goal` themselves; the skill proceeds identically either way. That is a property of *that runtime's* surface, not of goals generally: the **Codex surface** exposes `thread/goal/get` / `set` / `clear` and can set a goal for itself directly (the "Per-ticket goal anchors each AFK iteration" architecture rule, one rule / two mechanisms). The plural orchestrator's anchor contract (canonical condition, printed-evidence rule, one-directional semantics) lives at `/wr-itil:work-problems` Step 0e.
|
|
127
135
|
|
|
128
136
|
## Related
|
|
129
137
|
|
|
@@ -134,11 +142,13 @@ A headless single-ticket run can anchor its completion with Claude Code's native
|
|
|
134
142
|
- **the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule** (`docs/decisions/044-decision-delegation-contract.proposed.md`) — Decision-Delegation Contract; this skill's Step 2 selection is framework-mediated per the ADR's Prioritisation row. Step 4 scope-expansion is a category-2 (deviation-approval) surface per the ADR's 6-class taxonomy.
|
|
135
143
|
- **the "Governance Skills Commit Their Own Completed Work" architecture rule** — governance skills commit their own work. The delegated `/wr-itil:manage-problem <NNN>` owns the per-ticket commit; this skill does not re-commit.
|
|
136
144
|
- **the "Inter-iteration release cadence for AFK loops" architecture rule** — release cadence. AFK orchestrator owns release cadence; this skill does NOT auto-release.
|
|
137
|
-
- **the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule** — governance skill invocation patterns. `/wr-itil:work-problems`
|
|
145
|
+
- **the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule** — governance skill invocation patterns. `/wr-itil:work-problems` dispatches iterations to this skill in a fresh `claude -p` subprocess (the Agent-tool shape is superseded per the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem); this singular skill is the canonical execution unit.
|
|
138
146
|
- **the "Skill testing strategy — contract-assertion bats companion to" architecture rule** (`docs/decisions/037-skill-testing-strategy.proposed.md`) — contract-assertion bats pattern applied to this skill.
|
|
139
147
|
- **the "Problem 031: `manage-problem work` incorrectly determines cache is fresh" problem** — git-history freshness check rationale (mtime unreliable in worktrees). Applies to the README cache this skill reads.
|
|
140
148
|
- **the "Problem 062: `manage-problem` does not refresh `docs/problems/README.md` on single-ticket transitions; fast-path cache goes stale silently" problem** — `/wr-itil:review-problems` is the canonical README.md cache writer. This skill defers to it for refreshes.
|
|
141
|
-
- **the "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem** — `/wr-itil:work-problems` Step 5
|
|
149
|
+
- **the "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem** — established the AFK iteration-isolation wrapper and the `ITERATION_SUMMARY` return contract. Its Agent-tool spawn mechanism was superseded by **the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem** (an Agent-tool subagent has no Agent tool of its own, so governance gates could not be satisfied); `/wr-itil:work-problems` Step 5 now dispatches `/wr-itil:work-problem <NNN>` in a `claude -p` subprocess, anchored per-ticket under **the "Per-ticket goal anchors each AFK iteration" architecture rule**.
|
|
150
|
+
- **the "Per-ticket goal anchors each AFK iteration" architecture rule** — per-ticket goal anchors each AFK iteration; the pinned + unattended short-circuit at Step 1 above, and the declaration-as-discriminator contract.
|
|
151
|
+
- **the ": Progress the Backlog While I'm Away" user outcome** (Progress the Backlog While I'm Away) — the job this skill serves as the loop's per-iteration execution unit; the Step 1 short-circuit exists so an unanswerable prompt and an off-grain commit never enter an absent-user subprocess.
|
|
142
152
|
- **the ": Enforce Governance Without Slowing Down" user outcome** (`docs/jtbd/developer/the ": Enforce Governance Without Slowing Down" user outcome-enforce-governance.proposed.md`) — discoverable surface via `/wr-itil:` autocomplete. Users type `/wr-itil:work-problem` rather than remembering the `manage-problem work` subcommand.
|
|
143
153
|
- **the "Extend the Suite with New Plugins" user outcome** (`docs/jtbd/plugin-developer/the "Extend the Suite with New Plugins" user outcome-extend-suite.proposed.md`) — one skill per distinct user intent.
|
|
144
154
|
- `packages/itil/skills/manage-problem/SKILL.md` — hosts the thin-router forwarder for the deprecated `manage-problem work` form; also the delegated execution target for each ticket.
|
|
@@ -307,7 +307,7 @@ Step 0b / Step 0c / Step 0d (and **any future Step 0x pre-flight** that reuses t
|
|
|
307
307
|
|
|
308
308
|
The loop's stop decision is anchored by Claude Code's native [`/goal`](https://code.claude.com/docs/en/goal) command (≥ v2.1.139): a per-turn **external evaluator** (the configured small fast model, wrapping a session-scoped prompt-based Stop hook) judges a completion condition against what the orchestrator has printed in the transcript. This breaks the "agent ends the work-problems loop (emits ALL_DONE) prematurely while actionable Tier-2 backlog remains, by rationalising the remainder as out-of-scope / interactive-gated" problem same-actor conflation — the working agent that is prone to inventing subjective stops no longer decides whether stopping is justified; Step 2.4 Gate (0) remains the first-line objective *self*-check, and `/goal` is the *external* check that the orchestrator keeps turning until Gate (0) genuinely passes.
|
|
309
309
|
|
|
310
|
-
**Canonical goal condition** (owned here; the Step 2.4 Gate (0) table shape and this condition are a coupled contract — reshape both in the same commit)
|
|
310
|
+
**Canonical goal condition** (owned here; the Step 2.4 Gate (0) table shape and this condition are a coupled contract — reshape both in the same commit). Every copy-paste block below embeds this text **verbatim** as a substring, with an invocation carrier permitted only as a prefix or suffix; `wr-itil-check-goal-condition-drift` asserts that containment in CI and exits non-zero on divergence (the "Shared code duplicated into per-package lib/ kept in sync by script + CI drift check" architecture rule canonical-plus-check shape, check-only variant — there is no sync script, because a block carrying a carrier cannot be mechanically regenerated from the canonical). <!-- CANONICAL-GOAL-CONDITION-SOURCE -->
|
|
311
311
|
|
|
312
312
|
```
|
|
313
313
|
The /wr-itil:work-problems AFK backlog drain is complete: the final summary printed in the conversation contains a Step 2.4 Gate (0) re-scan table (fresh open/known-error glob) classifying every ticket and showing ZERO dispatchable tickets, followed by the ALL_DONE sentinel — or the session ends with a Hard-fail halt directive naming the gate that could not complete — or the summary reports quota exhaustion.
|
|
@@ -315,19 +315,30 @@ The /wr-itil:work-problems AFK backlog drain is complete: the final summary prin
|
|
|
315
315
|
|
|
316
316
|
There is no turn-bound: the loop runs until a real end state (printed Gate (0) zero-dispatchable + ALL_DONE, a Hard-fail halt, or quota exhaustion). Trust the goal — a turn cap would just re-create the premature stop this anchor exists to prevent (the "Agent silently ships X-prime (a hedged/lesser version of the requested X) instead of asking before deviating" problem). the "Ship quota-pacing surface to prevent weekly-quota exhaustion — advisory or blocking nudge when burn rate exceeds sustainable pace, so users retain Claude tokens for non-Claude-Code surfaces (chat, cowork) for the full week" problem/the "Mechanical quota-pace throttle — frequently-firing PreToolUse hook, calculated sleep, never blocks" architecture rule quota pacing throttles token burn so an honest ALL_DONE is reachable within the window.
|
|
317
317
|
|
|
318
|
-
**Placement —
|
|
318
|
+
**Placement — scope decides where a goal lives (the "Per-ticket goal anchors each AFK iteration" architecture rule, superseding the "AFK loops anchor completion with the native `/goal` external evaluator" architecture rule in part).** The rule is about goal **scope**, and scope is runtime-independent:
|
|
319
319
|
|
|
320
|
-
**
|
|
320
|
+
- A **drain-scoped** goal (the canonical condition above — "the backlog is empty of dispatchable tickets") belongs on the **orchestrator session**, and NEVER on a `claude -p` iter subprocess. An iter carrying a backlog-empty goal would be pushed past its one-ticket carve-out (the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule / the "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem / the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem) — that is the hazard the superseded blanket rule was actually arguing against, and it still stands.
|
|
321
|
+
- A **per-ticket** goal (scoped to the single ticket the iter is working) belongs **on that iter**, and is the default dispatch shape per Step 5. It reinforces the one-ticket carve-out rather than violating it.
|
|
321
322
|
|
|
322
|
-
|
|
323
|
+
**Carrier — one rule, two mechanisms (the "Per-ticket goal anchors each AFK iteration" architecture rule).** How a goal reaches a session is runtime-specific; only the mechanism differs, never the rule above. On the **Claude Code surface** there is no programmatic mid-session goal surface — empirically probed 2026-07-06 at v2.1.201 and re-probed 2026-09-18 at v2.1.276: no `--goal` CLI flag, and the Skill tool rejects it ("goal is a UI command, not a skill"), so only the user can type it mid-session. This is a property of THAT runtime's surface, not of goals generally: the **Codex surface** exposes `thread/goal/get` / `set` / `clear` directly and carries the same rule by setting the goal natively (see `packages/itil/scripts/codex-work-problems.md`). On Claude Code, therefore:
|
|
323
324
|
|
|
325
|
+
- **Headless AFK launch (the anchor-guaranteed path)** — start the orchestrator with the goal set. Copy-paste-complete one-liner. The condition text below embeds the canonical block **verbatim**; the invocation carrier is a prefix only (a bare condition would set a goal over an empty session), and the `CANONICAL-CONDITION-EMBED` marker is what `wr-itil-check-goal-condition-drift` asserts:
|
|
326
|
+
|
|
327
|
+
<!-- CANONICAL-CONDITION-EMBED -->
|
|
324
328
|
```bash
|
|
325
|
-
claude -p --permission-mode bypassPermissions "/goal Run /wr-itil:work-problems to drain the problem backlog. The drain is complete
|
|
329
|
+
claude -p --permission-mode bypassPermissions "/goal Run /wr-itil:work-problems to drain the problem backlog. The /wr-itil:work-problems AFK backlog drain is complete: the final summary printed in the conversation contains a Step 2.4 Gate (0) re-scan table (fresh open/known-error glob) classifying every ticket and showing ZERO dispatchable tickets, followed by the ALL_DONE sentinel — or the session ends with a Hard-fail halt directive naming the gate that could not complete — or the summary reports quota exhaustion."
|
|
326
330
|
```
|
|
327
331
|
|
|
328
|
-
|
|
332
|
+
**Launch shape only — NEVER emit this into an already-running loop session.** It starts a *new* orchestrator. Pasted into a session that is already inside `/wr-itil:work-problems`, it yields two concurrent orchestrators on one repo — both dispatching iters, both committing, both running the Step 6.5 push/release drain, colliding on the same git index and the same `docs/problems/README.md`. When the loop is already running and unanchored, emit the interactive block below instead.
|
|
333
|
+
|
|
334
|
+
- **Interactive invocation (nudge-and-proceed)** — when the loop starts without an active goal (no `/goal` directive or evaluator-reason lines visible in the session context), surface the command **for the user to type into this session** — never the headless launcher above — then **proceed with the loop regardless**. The block below embeds the canonical condition verbatim and is likewise drift-checked:
|
|
329
335
|
|
|
330
|
-
|
|
336
|
+
<!-- CANONICAL-CONDITION-EMBED -->
|
|
337
|
+
```text
|
|
338
|
+
/goal The /wr-itil:work-problems AFK backlog drain is complete: the final summary printed in the conversation contains a Step 2.4 Gate (0) re-scan table (fresh open/known-error glob) classifying every ticket and showing ZERO dispatchable tickets, followed by the ALL_DONE sentinel — or the session ends with a Hard-fail halt directive naming the gate that could not complete — or the summary reports quota exhaustion.
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
No carrier is needed here: the loop is already running in the session the user types into. The anchor is defense-in-depth over Gate (0), never a precondition — halting an AFK loop for a missing anchor would itself defeat the ": Progress the Backlog While I'm Away" user outcome. No `AskUserQuestion` fires here (mechanical stage; the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule category 4).
|
|
331
342
|
|
|
332
343
|
**One-directional anchor.** The goal forces continuation; it never authorises a stop. A goal that is cleared (or was never set) does NOT discharge Gate (0) — `ALL_DONE` still requires the full Step 2.4 sequence. Requirements floor: `/goal` needs workspace trust + hooks enabled; below the floor the loop degrades honestly to Gate (0)-only behaviour.
|
|
333
344
|
|
|
@@ -555,6 +566,21 @@ If a problem is skipped by this step, add it to a "skipped" list with the reason
|
|
|
555
566
|
- **Agent-tool dispatch to a `general-purpose` subagent** (the "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem amendment) works for context isolation but fails at the governance-gate layer: subagents spawned via the Agent tool do NOT have the Agent tool in their own surface (three-source evidence — ToolSearch probe, Claude Code docs at `code.claude.com/docs/en/subagents.md`, empirical runtime error `"No such tool available: Agent. Agent is not available inside subagents."`). Without Agent, the iteration worker cannot set architect + JTBD PreToolUse edit-gate markers (only settable via Agent-tool PostToolUse hook), cannot satisfy the risk-scorer commit gate, and silently halts on every gate-covered iteration. the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem diagnoses and closes this gap.
|
|
556
567
|
- **`claude -p` subprocess dispatch** (this step, per the "Problem 084: work-problems iteration-worker has no Agent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem / the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule amendment): the subprocess is a full main Claude Code session with Agent available in its own surface. Governance review runs at full depth via the normal `wr-architect:agent` / `wr-jtbd:agent` / `wr-risk-scorer:pipeline` delegation path inside the subprocess; PostToolUse marker hooks fire correctly matching the subprocess's own `$CLAUDE_SESSION_ID`; the commit gate unlocks natively. Context isolation preserved by the process boundary (each subprocess is a distinct process with its own session state; orchestrator's main context only sees the stdout). This is the AFK iteration-isolation wrapper — subprocess-boundary variant under the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule.
|
|
557
568
|
|
|
569
|
+
**Canonical per-ticket goal condition (the "Per-ticket goal anchors each AFK iteration" architecture rule — owned here).** Each iter is anchored by a goal scoped to ITS ONE TICKET, never to the backlog drain (a drain-scoped goal on an iter would push it past the one-ticket carve-out — see Step 0e Placement). Substitute `<NNN>`:
|
|
570
|
+
|
|
571
|
+
<!-- CANONICAL-PER-TICKET-CONDITION-SOURCE -->
|
|
572
|
+
```text
|
|
573
|
+
Ticket P<NNN> has reached a real end state: the final message prints an ITERATION_SUMMARY block whose action is `worked` carrying a commit_sha, or whose action is `skipped` carrying a skip_reason_category, or the run halts naming a concrete blocker.
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Three properties of this text are load-bearing:
|
|
577
|
+
|
|
578
|
+
- **It is satisfied by artefacts the iter already prints.** The condition discharges at the same moment the `ITERATION_SUMMARY` block appears — which is the moment the iter would have exited anyway. This is what keeps the goal clear of the idle-timeout SIGTERM guard above: an iter held open past its final commit by an *unmet* goal presents exactly the no-new-commits-plus-climbing-wall-clock signature the poll loop kills on, and a kill there costs the run's metadata (the "SIGTERM-clean-flush guarantee is conditional on subprocess having emitted ITERATION_SUMMARY before going idle — needs SKILL.md caveat + behavioural-test second-source for stuck-before-emit subclass" problem class). Never write a per-ticket condition that outlives the summary.
|
|
579
|
+
- **It enumerates every end state the iter can print.** Too narrow, and an iter that genuinely cannot progress gets pushed into expanding its scope instead of reporting a clean skip.
|
|
580
|
+
- **It deliberately omits quota exhaustion.** Quota death surfaces as a non-zero exit with NO `ITERATION_SUMMARY` emitted at all — the process and its evaluator are simply gone — so it is detected out-of-band by the Step 5 exit-code semantics, the only surface that can see it. Naming it here would make the condition unsatisfiable by construction. (This is a genuine difference from the orchestrator's drain-scoped condition, where quota exhaustion IS reported in the summary and therefore CAN be named. The two are different conditions, not two copies of one.)
|
|
581
|
+
|
|
582
|
+
**Runtime carrier (the "Per-ticket goal anchors each AFK iteration" architecture rule, one rule / two mechanisms).** On the Claude Code surface the goal is carried by prefixing it to the dispatch prompt, as the `ITERATION_PROMPT` assembly below does. On the Codex surface the orchestrator sets it natively via `thread/goal/set` at dispatch instead (see `packages/itil/scripts/codex-work-problems.md`). Same rule, different mechanism.
|
|
583
|
+
|
|
558
584
|
**Dispatch command shape (Bash, backgrounded with idle-timeout poll loop per the "AFK orchestrator should SIGTERM stuck `claude -p` subprocesses after idle-timeout — and SIGTERM appears to flush a clean JSON" problem):**
|
|
559
585
|
|
|
560
586
|
```bash
|
|
@@ -564,6 +590,17 @@ cat > "$ITERATION_PROMPT_FILE" <<'PROMPT_EOF'
|
|
|
564
590
|
PROMPT_EOF
|
|
565
591
|
ITERATION_PROMPT=$(cat "$ITERATION_PROMPT_FILE")
|
|
566
592
|
|
|
593
|
+
# Per-ticket goal anchor (the "Per-ticket goal anchors each AFK iteration" architecture rule). Scoped to THIS ticket, never to the drain:
|
|
594
|
+
# a drain-scoped goal here would push the iter past its one-ticket carve-out.
|
|
595
|
+
# Text is the canonical per-ticket condition above; TICKET_NNN is the selected id.
|
|
596
|
+
PER_TICKET_GOAL="/goal Ticket P${TICKET_NNN} has reached a real end state: the final message prints an ITERATION_SUMMARY block whose action is \`worked\` carrying a commit_sha, or whose action is \`skipped\` carrying a skip_reason_category, or the run halts naming a concrete blocker."
|
|
597
|
+
|
|
598
|
+
# The goal is a PREFIX to the work instruction — a bare condition would anchor an
|
|
599
|
+
# empty session. The iteration prompt body follows and carries the work itself.
|
|
600
|
+
ITERATION_PROMPT="${PER_TICKET_GOAL}
|
|
601
|
+
|
|
602
|
+
${ITERATION_PROMPT}"
|
|
603
|
+
|
|
567
604
|
ITER_JSON=$(mktemp)
|
|
568
605
|
DISPATCH_START_EPOCH=$(date +%s)
|
|
569
606
|
IDLE_TIMEOUT_S="${WORK_PROBLEMS_IDLE_TIMEOUT_S:-3600}"
|
|
@@ -685,8 +722,8 @@ rm -f "$ITER_JSON" "$ITERATION_PROMPT_FILE"
|
|
|
685
722
|
|
|
686
723
|
**Re-ground per iter (the "work-problems orchestrator carries prior-ticket Fix Strategy text into iter dispatch without re-grounding in design intent" problem — orchestrator-side construction invariant)**: each iter's prompt body MUST be re-grounded per iter against the CURRENT ticket's identity (ID + title) only. The orchestrator does NOT inline the target ticket's `## Fix Strategy` section verbatim into the dispatch prompt — the subprocess reads Fix Strategy from disk via `/wr-itil:manage-problem` inside its own context, where the design rationale travels with the ticket file and stays anchored to the correct ticket. Across iterations, no prior-iter content leaks into iter N's prompt body — specifically, prior ticket ID, prior Fix Strategy text, prior outcome reason, prior commit SHA, prior retro findings, and prior outstanding-question entries MUST NOT carry across the iter boundary into the new prompt. The construction is template-driven and reset per iter; no global accumulator carries from iter to iter. The "self-contained" opener above is a subprocess-side property (the subprocess has no prior conversation context); the re-grounding invariant is the symmetric orchestrator-side property (the orchestrator main turn does not carry prior-iter prompt content into the next iter's dispatch construction). the "work-problems orchestrator carries prior-ticket Fix Strategy text into iter dispatch without re-grounding in design intent" problem reported as inbound from downstream consumer bbstats as their the "ADRs accumulate forward-chronology evidence inline (Phase 2 dogfood evidence, amendment history, cross-iter cross-references) — `decisions` bucket dominates context at 41% / 1.3 MiB" problem — without this invariant, an iter inherits a stale design-rationale frame and may land fixes anchored on the wrong ticket's intent, degrading the ": Progress the Backlog While I'm Away" user outcome audit trail. **`@jtbd the ": Progress the Backlog While I'm Away" user outcome`** (load-bearing).
|
|
687
724
|
|
|
688
|
-
1. **Context**: this is one iteration of the AFK work-problems loop. The user is AFK
|
|
689
|
-
2. **Task**:
|
|
725
|
+
1. **Context (the unattended declaration — load-bearing carrier per the "Per-ticket goal anchors each AFK iteration" architecture rule)**: this is one iteration of the AFK work-problems loop. <!-- UNATTENDED-DECLARATION-SOURCE --> **The user is AFK and this run is unattended.** The orchestrator selected `P<NNN> (<title>)` as the highest-WSJF actionable ticket. This sentence is the **discriminator** the singular skill keys its pinned short-circuit on (`/wr-itil:work-problem` Step 1): prose cannot observe a TTY and a `claude -p` subprocess offers nothing to sniff, so the unattended state is **declared by the dispatcher**, never detected by the skill. Do not drop or reword the declaration — a rule keyed on something the skill cannot observe silently collapses to always or never.
|
|
726
|
+
2. **Task**: run `/wr-itil:work-problem P<NNN>` — the singular skill, pinned to the ticket the orchestrator already selected. It is the loop's per-iteration execution unit (both SKILLs have long documented this; the "Per-ticket goal anchors each AFK iteration" architecture rule makes the dispatch match). It delegates the actual work to `/wr-itil:manage-problem <NNN>`, so the manage-problem workflow still runs verbatim — architect / jtbd / style-guide / voice-tone gate reviews and the commit gate (manage-problem Step 11) all apply. Because this subprocess has the Agent tool in its own surface, the normal review-via-subagent paths work — no inline-verdict fallback needed. Because the dispatch is pinned AND declares the run unattended, the singular skill skips its ranking-freshness check (it must not delegate a README refresh, must not prompt, must not commit a ranking rewrite inside this per-ticket unit of work).
|
|
690
727
|
3. **Constraints**: commit the completed work per the "Governance Skills Commit Their Own Completed Work" architecture rule. 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 — the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule), **EXCEPT** (a) **retro-surfaced observations of recurring class-of-behaviour** — those route to `/wr-itil:capture-problem` per the **the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem 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 — the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem); 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 (the "manage-problem I13 propose-fix gate auto-creates a new RFC instead of wiring an existing fix-vehicle's trace edge" problem; 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** (the "Every fix goes through an RFC" architecture rule), not a tangential observation — it is in-scope working of the current ticket, framework-mediated (NOT cat-1 direction-setting → NO `AskUserQuestion`, the "Agents over-ask in interactive sessions — conflating mechanical-stages with user-interactive-stages of multi-stage skill contracts (inverse-)" problem), 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 the ": Progress the Backlog While I'm Away" user outcome audit-trail. Do NOT use `ScheduleWakeup` under any circumstance (the "Problem 083: work-problems Step 5 iteration-worker prompt does not forbid ScheduleWakeup / time-deferring primitives — subagent can abandon synchronous-completion contract" problem — iteration workers must not self-reschedule). **NEVER call `AskUserQuestion` mid-loop in AFK** (the "Decision-delegation contract — agents over-apply Rule 1's interactive default to framework-resolved decisions; codify the framework-resolution boundary + AFK loop's batched-questions-as-deliverable + lazy-AskUserQuestion measurement" problem / the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule): 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 (the ": Confirm a decision's substance before building dependent work on it" architecture rule (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 "Agent implements dependent work on genuine new decisions before human-confirming their SUBSTANCE — surfaces only meta-questions" problem 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 (the ": Confirm a decision's substance before building dependent work on it" architecture rule 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 the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6 + the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule's framework-resolution boundary. **Treat the user as transient** (the "`/wr-itil:work-problems` orchestrator defaults to subprocess dispatch even when the user is observably interactive — loses real-time presence advantage" problem): 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** (the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem — 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 "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "SIGTERM-clean-flush guarantee is conditional on subprocess having emitted ITERATION_SUMMARY before going idle — needs SKILL.md caveat + behavioural-test second-source for stuck-before-emit subclass" problem 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** (the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem — self-referential pgrep deadlock; sibling variant of the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem). `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 the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem 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 the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem: 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 (the "Iter subprocess ends its turn waiting on a backgrounded task and never resumes — `claude -p` has no auto-resume; commit-bearing work is lost" problem — turn-end-mid-background work-loss; sibling-class to the "Problem 083: work-problems Step 5 iteration-worker prompt does not forbid ScheduleWakeup / time-deferring primitives — subagent can abandon synchronous-completion contract" problem / the "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem / the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem). 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 "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem-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 "AFK iteration subprocess `bash until`-loop polls bats-output file with bats-console regex against TAP-format output — deadlocks indefinitely, manual SIGTERM required, JSON metadata lost" problem/the "bash until-loop with `pgrep -f 'bats --recursive'` self-references the polling loop's own command line — new variant of stuck-before-emit deadlock; SKILL.md prompt warning insufficient" problem 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 the "Governance Skills Commit Their Own Completed Work" architecture rule-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 the ": Progress the Backlog While I'm Away" user outcome's audit-trail expectation + the ": Keep Plugins Current Across Projects" user outcome's "Keep Plugins Current" closure dependency). Hook `packages/itil/hooks/itil-changeset-discipline.sh` (the "AFK iter `packages/<plugin>/` commits without changesets — orchestrator-main-turn back-fill is fragile recovery, hook-level enforcement preferable" problem) 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 the "Briefing Tier 3 rotation repeat-deferral — 13 of 14 topic files over budget with 2 in MUST_SPLIT (≥2× ceiling) branch" problem — see [Related](#related) for `**Origin**: inbound-reported (bbstats#195)` per the "Inbound-reported problems rank ahead of internally-discovered problems via a sort tier" architecture rule. **`@jtbd the ": Progress the Backlog While I'm Away" user outcome`** (load-bearing) **`@jtbd the ": Keep Plugins Current Across Projects" user outcome`** (closure-dependent).
|
|
691
728
|
4. **Retro-on-exit (the "Problem 086: AFK iteration subprocess does not run retro before returning — per-iteration lessons learnt are lost when the subprocess exits" problem) + retro-surfaced observation classification (the "Iter retros queue their own observations as `outstanding-questions.jsonl` entries for user-direction triage instead of auto-ticketing — same trust-boundary as `/wr-retrospective:run-retro` Step 4a" problem) + iter-owned BRIEFING commit (the "work-problems iteration boundary leaves run-retro BRIEFING.md edits uncommitted" problem)**: 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 the "Governance Skills Commit Their Own Completed Work" architecture rule 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.
|
|
692
729
|
|
|
@@ -774,6 +811,20 @@ Extracted fields (explicit field list):
|
|
|
774
811
|
- `.usage.output_tokens` — generated tokens.
|
|
775
812
|
- `.usage.cache_creation_input_tokens` — tokens written to the prompt cache on this invocation.
|
|
776
813
|
- `.usage.cache_read_input_tokens` — tokens read from the prompt cache on this invocation (cache-read is the signal for warm-cache reuse across subsequent subprocess invocations in the same Bash session; high values here indicate the iteration benefited from prior-invocation caching).
|
|
814
|
+
- `.modelUsage` — **the goal evaluator's own entry only** (the "Per-ticket goal anchors each AFK iteration" architecture rule). Under the per-ticket goal (Step 5), the response carries TWO model entries: the worker model, and the small fast model running the goal evaluation. Extract the evaluator entry's `costUSD` + token counts and report them as their own line, distinct from the worker's. This allowlist is deliberately closed, so an entry not named here is **spent and never counted** — without this line, the "Per-ticket goal anchors each AFK iteration" architecture rule's measure-rather-than-pre-budget position would be true in principle and false in practice, and the Session Cost table would under-report what the loop actually spent. Identify the evaluator entry as the `.modelUsage` key that is NOT the worker model (the worker is the key whose `costUSD` dominates; the evaluator is the configured small fast model). Do NOT extract any other `.modelUsage` field beyond the evaluator's cost + token counts.
|
|
815
|
+
|
|
816
|
+
```bash
|
|
817
|
+
# Evaluator-only slice of .modelUsage (the "Per-ticket goal anchors each AFK iteration" architecture rule). Worker = max costUSD; evaluator = the rest.
|
|
818
|
+
read -r ITER_EVAL_COST ITER_EVAL_IN ITER_EVAL_OUT < <(
|
|
819
|
+
jq -r '[.modelUsage | to_entries | sort_by(.value.costUSD) | .[:-1] | map(.value)
|
|
820
|
+
| (map(.costUSD) | add // 0),
|
|
821
|
+
(map(.inputTokens + .cacheCreationInputTokens + .cacheReadInputTokens) | add // 0),
|
|
822
|
+
(map(.outputTokens) | add // 0)] | @tsv' <<<"$SUBPROCESS_OUTPUT"
|
|
823
|
+
)
|
|
824
|
+
SESSION_EVAL_COST=$(awk "BEGIN { printf \"%.4f\", ${SESSION_EVAL_COST:-0} + $ITER_EVAL_COST }")
|
|
825
|
+
SESSION_EVAL_IN=$(( ${SESSION_EVAL_IN:-0} + ITER_EVAL_IN ))
|
|
826
|
+
SESSION_EVAL_OUT=$(( ${SESSION_EVAL_OUT:-0} + ITER_EVAL_OUT ))
|
|
827
|
+
```
|
|
777
828
|
|
|
778
829
|
Use `jq` (or an equivalent JSON parser) to extract them:
|
|
779
830
|
|
|
@@ -24,7 +24,7 @@ This skill is the "Problem 071: Argument-based skill subcommands are not discove
|
|
|
24
24
|
## Name distinction (work-problem vs work-problems)
|
|
25
25
|
|
|
26
26
|
- **`/wr-itil:work-problem`** (singular, this skill) — one ticket per invocation. Framework-mediated selection (WSJF + tie-break ladder). Intended for a user who wants to dispatch the next-highest ticket and then stop. User-override path: `/wr-itil:work-problem <NNN>` to pin a specific ticket.
|
|
27
|
-
- **`/wr-itil:work-problems`** (plural, AFK orchestrator) — loops through the backlog by WSJF,
|
|
27
|
+
- **`/wr-itil:work-problems`** (plural, AFK orchestrator) — loops through the backlog by WSJF, dispatching each iteration to this skill as `/wr-itil:work-problem <NNN>` in a fresh `native Codex subagent` subprocess (per the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule as amended by the "Problem 084: work-problems iteration-worker has no native Codex subagent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem; the earlier native-Codex-subagent-tool shape is superseded — an native-Codex-subagent-tool subagent has no native Codex subagent tool of its own, so governance gates could not be satisfied inside it). Each dispatch is anchored by a per-ticket goal per the "Per-ticket goal anchors each AFK iteration" architecture rule. Intended for AFK batch runs; non-interactive selection; stops only when nothing actionable remains.
|
|
28
28
|
|
|
29
29
|
Both names coexist intentionally per the "Problem 071: Argument-based skill subcommands are not discoverable in Codex autocomplete" problem's out-of-scope note on the naming coexistence. The plural orchestrator uses this skill as its per-iteration unit.
|
|
30
30
|
|
|
@@ -65,6 +65,14 @@ fi
|
|
|
65
65
|
- **Cache fresh** (no output): read `docs/problems/README.md` and use the cached WSJF Rankings table for Step 2.
|
|
66
66
|
- **Cache stale** (prints "stale") or `README.md` missing: **delegate to `/wr-itil:review-problems`** via the installed skill invocation to refresh the ranking before proceeding. Do NOT re-implement the re-scoring logic here — that would fork the review path and break the "Problem 062: `manage-problem` does not refresh `docs/problems/README.md` on single-ticket transitions; fast-path cache goes stale silently" problem's canonical-cache-writer contract. The review skill's Step 4 verification prompt runs on this refresh path (the "Problem 048: manage-problem does not surface Fix Released problems as verification candidates when the fix path has been exercised" problem Candidate 1: Verification Queue prompts always fire so pending verifications don't accumulate off-ledger).
|
|
67
67
|
|
|
68
|
+
**Pinned + unattended short-circuit — skip this whole step (the "Per-ticket goal anchors each AFK iteration" architecture rule).** <!-- @jtbd the ": Progress the Backlog While I'm Away" user outcome (Progress the Backlog While I'm Away — an unanswerable prompt and an off-grain ranking commit must not enter an absent-user subprocess) --> When this skill is invoked against a **pinned ticket** (`/wr-itil:work-problem <NNN>`) AND the invocation **declares the run unattended**, skip the freshness check entirely: do not read the ranking, do not delegate to `/wr-itil:review-problems`, do not prompt, do not commit. Proceed straight to Step 3 with the named ticket.
|
|
69
|
+
|
|
70
|
+
Two things make this necessary rather than merely cheaper. The refresh delegation fires the review skill's Verification Queue prompt — **unanswerable in a subprocess with no user attached** (the "Structured User Interaction for Governance-Skill Decisions" architecture rule Rule 6; the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule). And its ranking rewrite is an **off-grain commit** inside what is supposed to be a single per-ticket unit of work (the "Governance Skills Commit Their Own Completed Work" architecture rule), which can also trip the orchestrator's unexpected-dirty-state halt. The ranking is not consulted at all when the ticket is pinned, so the refresh buys nothing here.
|
|
71
|
+
|
|
72
|
+
**The discriminator is a declaration, not a detection.** This skill is prose: it cannot observe a TTY, and under `native Codex subagent` there is nothing to sniff. The unattended state is **declared by the dispatcher**. The exact sentence this short-circuit keys on, emitted by `/wr-itil:work-problems` Step 5 item 1: <!-- UNATTENDED-DECLARATION-CONSUMER --> **The user is AFK and this run is unattended.** A rule keyed on something the skill cannot observe would silently collapse to always or never — so the two surfaces are bound mechanically, not by hope: `<itil-plugin-root>/bin/wr-itil-check-goal-condition-drift` asserts the marked sentence is identical on both sides and fails CI if either is reworded.
|
|
73
|
+
|
|
74
|
+
**The interactive pinned path is unchanged.** `/wr-itil:work-problem <NNN>` is also the documented *user-override* path, where a user IS present, the verification prompt IS answerable, and firing it is a deliberate piggyback keeping pending verifications from accumulating off-ledger. It is **the absent user, not the pin**, that makes the refresh wrong. The unattended loop carries the verification cadence on other surfaces; the interactive singular path has none, so a blanket short-circuit would delete a self-firing cadence and hand the maintainer something to remember.
|
|
75
|
+
|
|
68
76
|
### 2. Select the ticket (framework-mediated)
|
|
69
77
|
|
|
70
78
|
Read the WSJF Rankings table from the now-fresh `docs/problems/README.md`. Apply the framework's tie-break ladder mechanically to pick the next ticket — selection is **framework-mediated** per the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule's Framework-Mediated Surface (Prioritisation row). The agent picks, reports the choice + the tie-break rung that decided, and proceeds. **No `request_user_input` fires for selection** — the WSJF formula + tie-break ladder already resolve the decision.
|
|
@@ -134,7 +142,7 @@ After the delegated `/wr-itil:manage-problem <NNN>` completes:
|
|
|
134
142
|
|
|
135
143
|
## Goal anchor for headless runs (the "agent ends the work-problems loop (emits ALL_DONE) prematurely while actionable Tier-2 backlog remains, by rationalising the remainder as out-of-scope / interactive-gated" problem / the "AFK loops anchor completion with the native `/goal` external evaluator" architecture rule)
|
|
136
144
|
|
|
137
|
-
A headless single-ticket run can anchor its completion with Codex's native `/goal` external evaluator (≥ v2.1.139), so a fresh model — not the working agent — judges whether the ticket genuinely reached an end state: `native Codex subagent "/goal Run /wr-itil:work-problem to work the top ticket. Complete when the report printed in the conversation shows a committed outcome (with commit SHA) or a recorded blocker for the selected ticket."` (No turn-bound: trust the goal — the loop stops only at its real end states: a committed outcome, a recorded blocker, or quota exhaustion.)
|
|
145
|
+
A headless single-ticket run can anchor its completion with Codex's native `/goal` external evaluator (≥ v2.1.139), so a fresh model — not the working agent — judges whether the ticket genuinely reached an end state: `native Codex subagent "/goal Run /wr-itil:work-problem to work the top ticket. Complete when the report printed in the conversation shows a committed outcome (with commit SHA) or a recorded blocker for the selected ticket."` (No turn-bound: trust the goal — the loop stops only at its real end states: a committed outcome, a recorded blocker, or quota exhaustion.) **On the Codex surface** there is no programmatic mid-session surface for setting a goal (probed 2026-07-06 at v2.1.201; re-probed 2026-09-18 at v2.1.276 — no `--goal` flag, and the installed skill invocation rejects it as a UI command) — interactive users type `/goal` themselves; the skill proceeds identically either way. That is a property of *that runtime's* surface, not of goals generally: the **Codex surface** exposes `thread/goal/get` / `set` / `clear` and can set a goal for itself directly (the "Per-ticket goal anchors each AFK iteration" architecture rule, one rule / two mechanisms). The plural orchestrator's anchor contract (canonical condition, printed-evidence rule, one-directional semantics) lives at `/wr-itil:work-problems` Step 0e.
|
|
138
146
|
|
|
139
147
|
## Related
|
|
140
148
|
|
|
@@ -145,11 +153,13 @@ A headless single-ticket run can anchor its completion with Codex's native `/goa
|
|
|
145
153
|
- **the "— Decision-Delegation Contract: when agents act on the framework vs ask the user" architecture rule** (`docs/decisions/044-decision-delegation-contract.proposed.md`) — Decision-Delegation Contract; this skill's Step 2 selection is framework-mediated per the ADR's Prioritisation row. Step 4 scope-expansion is a category-2 (deviation-approval) surface per the ADR's 6-class taxonomy.
|
|
146
154
|
- **the "Governance Skills Commit Their Own Completed Work" architecture rule** — governance skills commit their own work. The delegated `/wr-itil:manage-problem <NNN>` owns the per-ticket commit; this skill does not re-commit.
|
|
147
155
|
- **the "Inter-iteration release cadence for AFK loops" architecture rule** — release cadence. AFK orchestrator owns release cadence; this skill does NOT auto-release.
|
|
148
|
-
- **the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule** — governance skill invocation patterns. `/wr-itil:work-problems`
|
|
156
|
+
- **the "Governance skill invocation patterns — foreground + background with deferred-question resumption" architecture rule** — governance skill invocation patterns. `/wr-itil:work-problems` dispatches iterations to this skill in a fresh `native Codex subagent` subprocess (the native-Codex-subagent-tool shape is superseded per the "Problem 084: work-problems iteration-worker has no native Codex subagent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem); this singular skill is the canonical execution unit.
|
|
149
157
|
- **the "Skill testing strategy — contract-assertion bats companion to" architecture rule** (`docs/decisions/037-skill-testing-strategy.proposed.md`) — contract-assertion bats pattern applied to this skill.
|
|
150
158
|
- **the "Problem 031: `manage-problem work` incorrectly determines cache is fresh" problem** — git-history freshness check rationale (mtime unreliable in worktrees). Applies to the README cache this skill reads.
|
|
151
159
|
- **the "Problem 062: `manage-problem` does not refresh `docs/problems/README.md` on single-ticket transitions; fast-path cache goes stale silently" problem** — `/wr-itil:review-problems` is the canonical README.md cache writer. This skill defers to it for refreshes.
|
|
152
|
-
- **the "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem** — `/wr-itil:work-problems` Step 5
|
|
160
|
+
- **the "Problem 077: work-problems Step 5 does not delegate iterations to a subagent, so context pressure accumulates in the orchestrator's main turn" problem** — established the AFK iteration-isolation wrapper and the `ITERATION_SUMMARY` return contract. Its native-Codex-subagent-tool spawn mechanism was superseded by **the "Problem 084: work-problems iteration-worker has no native Codex subagent tool so architect + JTBD edit gates AND risk-scorer commit gate block all progress" problem** (an native-Codex-subagent-tool subagent has no native Codex subagent tool of its own, so governance gates could not be satisfied); `/wr-itil:work-problems` Step 5 now dispatches `/wr-itil:work-problem <NNN>` in a `native Codex subagent` subprocess, anchored per-ticket under **the "Per-ticket goal anchors each AFK iteration" architecture rule**.
|
|
161
|
+
- **the "Per-ticket goal anchors each AFK iteration" architecture rule** — per-ticket goal anchors each AFK iteration; the pinned + unattended short-circuit at Step 1 above, and the declaration-as-discriminator contract.
|
|
162
|
+
- **the ": Progress the Backlog While I'm Away" user outcome** (Progress the Backlog While I'm Away) — the job this skill serves as the loop's per-iteration execution unit; the Step 1 short-circuit exists so an unanswerable prompt and an off-grain commit never enter an absent-user subprocess.
|
|
153
163
|
- **the ": Enforce Governance Without Slowing Down" user outcome** (`docs/jtbd/developer/the ": Enforce Governance Without Slowing Down" user outcome-enforce-governance.proposed.md`) — discoverable surface via `/wr-itil:` autocomplete. Users type `/wr-itil:work-problem` rather than remembering the `manage-problem work` subcommand.
|
|
154
164
|
- **the "Extend the Suite with New Plugins" user outcome** (`docs/jtbd/plugin-developer/the "Extend the Suite with New Plugins" user outcome-extend-suite.proposed.md`) — one skill per distinct user intent.
|
|
155
165
|
- `<itil-plugin-root>/skills/manage-problem/SKILL.md` — hosts the thin-router forwarder for the deprecated `manage-problem work` form; also the delegated execution target for each ticket.
|