@windyroad/architect 0.7.0-preview.315 → 0.7.1-preview.317
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.
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared derive-first dispatch helper — canonical source-of-truth.
|
|
3
|
+
#
|
|
4
|
+
# P132 Phase 2a-iii-A extracted this helper from three declaration-skill
|
|
5
|
+
# surfaces. Phase 2a-iii-B (2026-05-16) added wr-architect:create-adr as
|
|
6
|
+
# the 4th adopter, which required moving the canonical source from
|
|
7
|
+
# packages/itil/lib/ to packages/shared/ per ADR-017 (Shared code
|
|
8
|
+
# duplicated into per-package lib/ kept in sync by script + CI drift
|
|
9
|
+
# check). The per-package lib/ copies are byte-identical to this file:
|
|
10
|
+
#
|
|
11
|
+
# - packages/itil/lib/derive-first-dispatch.sh (sync target)
|
|
12
|
+
# - packages/architect/lib/derive-first-dispatch.sh (sync target)
|
|
13
|
+
#
|
|
14
|
+
# Sync mechanism: scripts/sync-derive-first-dispatch.sh (mirrors the
|
|
15
|
+
# sync-install-utils.sh pattern). CI guard: npm run check:derive-first-dispatch.
|
|
16
|
+
# Drift test: packages/shared/test/sync-derive-first-dispatch.bats.
|
|
17
|
+
#
|
|
18
|
+
# Maintainer-side SKILL.md surfaces that source the helper:
|
|
19
|
+
# - packages/itil/skills/capture-problem/SKILL.md Step 1.5
|
|
20
|
+
# - packages/itil/skills/manage-incident/SKILL.md Step 4
|
|
21
|
+
# - packages/itil/skills/manage-problem/SKILL.md Step 4
|
|
22
|
+
# - packages/architect/skills/create-adr/SKILL.md Step 2 (P132 Phase 2a-iii-B)
|
|
23
|
+
#
|
|
24
|
+
# Each caller passes surface-specific signal definitions; this helper
|
|
25
|
+
# centralises the dispatch mechanism: slug derivation, two-sided lexical
|
|
26
|
+
# classifier, RISK-POLICY matrix lookup, and the I2-isomorphic stderr
|
|
27
|
+
# advisory format.
|
|
28
|
+
#
|
|
29
|
+
# <!-- DERIVE-FIRST-DISPATCH-CONTRACT-SOURCE: P132 Phase 2a-iii-A + Phase 2a-iii-B -->
|
|
30
|
+
# Drift in the stderr advisory format here re-opens P132 — any change MUST
|
|
31
|
+
# update all four caller SKILL.md surfaces in the same commit.
|
|
32
|
+
#
|
|
33
|
+
# Usage (sourced):
|
|
34
|
+
# . packages/<pkg>/lib/derive-first-dispatch.sh # callers source their own package's copy
|
|
35
|
+
#
|
|
36
|
+
# Exported functions:
|
|
37
|
+
# emit_stderr_advisory <skill> <field> <value> <source> [reversibility]
|
|
38
|
+
# derive_kebab_slug <description> [max_tokens=8]
|
|
39
|
+
# lexical_classify_two_sided <text> <side_a_patterns_var> <side_b_patterns_var>
|
|
40
|
+
# risk_policy_matrix_lookup <text> <impact_high> <impact_mod> <impact_low>
|
|
41
|
+
# <likelihood_high> <likelihood_med> <likelihood_low>
|
|
42
|
+
#
|
|
43
|
+
# @adr ADR-002 (Monorepo per-plugin packages — architecture context for ADR-017)
|
|
44
|
+
# @adr ADR-017 (Shared code duplicated into per-package lib/ kept in sync)
|
|
45
|
+
# @adr ADR-044 (Decision-Delegation Contract — derive-first framework boundary)
|
|
46
|
+
# @adr ADR-026 (cost-source grounding — stderr advisory)
|
|
47
|
+
# @adr ADR-013 Rule 5 (policy-authorised silent proceed)
|
|
48
|
+
# @adr ADR-052 (behavioural-by-default — tested via scripts/test/derive-first-dispatch.bats
|
|
49
|
+
# and packages/shared/test/sync-derive-first-dispatch.bats)
|
|
50
|
+
# @problem P132 (agents over-ask in interactive sessions — Phase 2a-iii-A shared helper +
|
|
51
|
+
# Phase 2a-iii-B 4th-adopter migration to packages/shared/)
|
|
52
|
+
# @problem P185 (capture-problem Step 1.5 worked-example precedent)
|
|
53
|
+
# @jtbd JTBD-001 (enforce governance without slowing down — primary)
|
|
54
|
+
# @jtbd JTBD-101 (extend the suite with consistent patterns)
|
|
55
|
+
#
|
|
56
|
+
# NOT exporting `set -e` at file scope — callers source the helper and
|
|
57
|
+
# expect functions that return AMBIGUOUS sentinels rather than errexit.
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# emit_stderr_advisory — canonical I2-isomorphic stderr advisory format.
|
|
61
|
+
#
|
|
62
|
+
# Format: <skill>: derived <field>=<value> from <source>; <reversibility>
|
|
63
|
+
#
|
|
64
|
+
# This is the single source-of-truth for the advisory sentence shape
|
|
65
|
+
# across all derive-first declaration-skill surfaces. The format is
|
|
66
|
+
# load-bearing for cross-skill consistency — drift here re-opens P132.
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
emit_stderr_advisory() {
|
|
69
|
+
local skill="$1"
|
|
70
|
+
local field="$2"
|
|
71
|
+
local value="$3"
|
|
72
|
+
local source_desc="$4"
|
|
73
|
+
local reversibility="${5:-re-invoke or update if mis-rated}"
|
|
74
|
+
printf '%s: derived %s=%s from %s; %s\n' \
|
|
75
|
+
"$skill" "$field" "$value" "$source_desc" "$reversibility" >&2
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
# derive_kebab_slug — kebab-case slug from prose.
|
|
80
|
+
#
|
|
81
|
+
# Lowercases, strips non-alphanumeric (preserves space and hyphen as
|
|
82
|
+
# token separators), drops stopwords, joins surviving tokens with `-`,
|
|
83
|
+
# caps the token count (default 8 per the SKILL.md surface contract).
|
|
84
|
+
#
|
|
85
|
+
# Used at:
|
|
86
|
+
# - capture-problem Step 1.4 Title derivation
|
|
87
|
+
# - manage-incident Step 4 Title derivation
|
|
88
|
+
# - manage-problem Step 4 Title derivation
|
|
89
|
+
# - create-adr Step 2 Title derivation (P132 Phase 2a-iii-B)
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
derive_kebab_slug() {
|
|
92
|
+
local description="$1"
|
|
93
|
+
local max_tokens="${2:-8}"
|
|
94
|
+
# Stopword list — common English function words plus "I/you/we" pronouns.
|
|
95
|
+
local stopwords='^(the|a|an|and|or|but|if|then|else|when|while|for|to|of|in|on|at|by|from|with|as|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|may|might|must|can|i|you|we|they|it|its|this|that|these|those|so|because|since|just|only|than|like|some|any|all|each|every|no|not)$'
|
|
96
|
+
|
|
97
|
+
printf '%s' "$description" \
|
|
98
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
99
|
+
| tr -c 'a-z0-9 -' ' ' \
|
|
100
|
+
| tr -s ' ' \
|
|
101
|
+
| tr ' ' '\n' \
|
|
102
|
+
| grep -vE "$stopwords" \
|
|
103
|
+
| grep -v '^$' \
|
|
104
|
+
| head -n "$max_tokens" \
|
|
105
|
+
| paste -sd '-' -
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# lexical_classify_two_sided — two-sided binary lexical classifier.
|
|
110
|
+
#
|
|
111
|
+
# Used by capture-problem Step 1.5 Type classification (technical vs
|
|
112
|
+
# user-business). Callers pass description text plus two regex pattern
|
|
113
|
+
# arrays (by name); helper counts hits per side and echoes one of:
|
|
114
|
+
#
|
|
115
|
+
# SIDE_A_UNAMBIGUOUS|<matched signals (comma-separated)>
|
|
116
|
+
# ≥1 side-A signal hit AND 0 side-B signals hit.
|
|
117
|
+
# SIDE_B_UNAMBIGUOUS|<matched signals (comma-separated)>
|
|
118
|
+
# 0 side-A signals hit AND ≥1 side-B signal hit.
|
|
119
|
+
# AMBIGUOUS|<a=N b=N>
|
|
120
|
+
# Mixed (both sides matched) OR zero (neither side matched).
|
|
121
|
+
#
|
|
122
|
+
# Caller is responsible for:
|
|
123
|
+
# - Mapping SIDE_A/SIDE_B to its domain values (e.g. technical / user-business).
|
|
124
|
+
# - Calling emit_stderr_advisory on the unambiguous path.
|
|
125
|
+
# - Firing AskUserQuestion on the AMBIGUOUS path (ADR-044 category-5 taste fallback).
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
lexical_classify_two_sided() {
|
|
128
|
+
local description="$1"
|
|
129
|
+
local -n _side_a_patterns_ref="$2"
|
|
130
|
+
local -n _side_b_patterns_ref="$3"
|
|
131
|
+
local a_hits=()
|
|
132
|
+
local b_hits=()
|
|
133
|
+
local pattern
|
|
134
|
+
|
|
135
|
+
for pattern in "${_side_a_patterns_ref[@]}"; do
|
|
136
|
+
if printf '%s' "$description" | grep -qiE "$pattern" 2>/dev/null; then
|
|
137
|
+
a_hits+=("$pattern")
|
|
138
|
+
fi
|
|
139
|
+
done
|
|
140
|
+
for pattern in "${_side_b_patterns_ref[@]}"; do
|
|
141
|
+
if printf '%s' "$description" | grep -qiE "$pattern" 2>/dev/null; then
|
|
142
|
+
b_hits+=("$pattern")
|
|
143
|
+
fi
|
|
144
|
+
done
|
|
145
|
+
|
|
146
|
+
local a_count="${#a_hits[@]}"
|
|
147
|
+
local b_count="${#b_hits[@]}"
|
|
148
|
+
|
|
149
|
+
if (( a_count >= 1 && b_count == 0 )); then
|
|
150
|
+
local joined
|
|
151
|
+
joined=$(IFS=,; echo "${a_hits[*]}")
|
|
152
|
+
printf 'SIDE_A_UNAMBIGUOUS|%s\n' "$joined"
|
|
153
|
+
elif (( a_count == 0 && b_count >= 1 )); then
|
|
154
|
+
local joined
|
|
155
|
+
joined=$(IFS=,; echo "${b_hits[*]}")
|
|
156
|
+
printf 'SIDE_B_UNAMBIGUOUS|%s\n' "$joined"
|
|
157
|
+
else
|
|
158
|
+
printf 'AMBIGUOUS|a=%d b=%d\n' "$a_count" "$b_count"
|
|
159
|
+
fi
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
# risk_policy_matrix_lookup — RISK-POLICY.md Impact × Likelihood lookup.
|
|
164
|
+
#
|
|
165
|
+
# Used by:
|
|
166
|
+
# - manage-incident Step 4 Severity derivation
|
|
167
|
+
# - manage-problem Step 4 Priority derivation
|
|
168
|
+
#
|
|
169
|
+
# Caller passes description text plus six regex pattern arrays (by
|
|
170
|
+
# name) keyed by impact band (high/mod/low) and likelihood band
|
|
171
|
+
# (high/med/low). Helper echoes one of:
|
|
172
|
+
#
|
|
173
|
+
# <score>|<label>|impact=<L>+likelihood=<L>
|
|
174
|
+
# Single dominant impact band AND single dominant likelihood band
|
|
175
|
+
# matched. Score = impact_val * likelihood_val; label per
|
|
176
|
+
# RISK-POLICY.md § Label Bands (Very Low / Low / Medium / High /
|
|
177
|
+
# Very High).
|
|
178
|
+
# AMBIGUOUS|<reason>
|
|
179
|
+
# Multi-band hit (signals point to conflicting cells) OR zero hit
|
|
180
|
+
# (no mappable signal). Caller fires AskUserQuestion as the
|
|
181
|
+
# genuine ADR-044 category-5 (taste) fallback surface.
|
|
182
|
+
#
|
|
183
|
+
# Band-to-numeric mapping (preserves RISK-POLICY.md Impact / Likelihood
|
|
184
|
+
# Levels table):
|
|
185
|
+
# impact: high = 5 (Severe), mod = 3 (Moderate), low = 1 (Negligible)
|
|
186
|
+
# likelihood: high = 5 (Almost certain), med = 3 (Possible), low = 1 (Rare)
|
|
187
|
+
#
|
|
188
|
+
# Label bands (RISK-POLICY.md):
|
|
189
|
+
# 1-2 Very Low
|
|
190
|
+
# 3-4 Low
|
|
191
|
+
# 5-9 Medium
|
|
192
|
+
# 10-16 High
|
|
193
|
+
# 17-25 Very High
|
|
194
|
+
#
|
|
195
|
+
# This helper preserves the band-to-score mapping; callers that need a
|
|
196
|
+
# wider granularity (e.g. Significant=4 / Minor=2) must extend the
|
|
197
|
+
# pattern arrays' band-buckets in a follow-on contract change.
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
risk_policy_matrix_lookup() {
|
|
200
|
+
local description="$1"
|
|
201
|
+
local -n _impact_high_ref="$2"
|
|
202
|
+
local -n _impact_mod_ref="$3"
|
|
203
|
+
local -n _impact_low_ref="$4"
|
|
204
|
+
local -n _likelihood_high_ref="$5"
|
|
205
|
+
local -n _likelihood_med_ref="$6"
|
|
206
|
+
local -n _likelihood_low_ref="$7"
|
|
207
|
+
|
|
208
|
+
local pat
|
|
209
|
+
local impact_high_hits=0
|
|
210
|
+
local impact_mod_hits=0
|
|
211
|
+
local impact_low_hits=0
|
|
212
|
+
local likelihood_high_hits=0
|
|
213
|
+
local likelihood_med_hits=0
|
|
214
|
+
local likelihood_low_hits=0
|
|
215
|
+
|
|
216
|
+
for pat in "${_impact_high_ref[@]}"; do
|
|
217
|
+
if printf '%s' "$description" | grep -qiE "$pat" 2>/dev/null; then
|
|
218
|
+
impact_high_hits=$((impact_high_hits + 1))
|
|
219
|
+
fi
|
|
220
|
+
done
|
|
221
|
+
for pat in "${_impact_mod_ref[@]}"; do
|
|
222
|
+
if printf '%s' "$description" | grep -qiE "$pat" 2>/dev/null; then
|
|
223
|
+
impact_mod_hits=$((impact_mod_hits + 1))
|
|
224
|
+
fi
|
|
225
|
+
done
|
|
226
|
+
for pat in "${_impact_low_ref[@]}"; do
|
|
227
|
+
if printf '%s' "$description" | grep -qiE "$pat" 2>/dev/null; then
|
|
228
|
+
impact_low_hits=$((impact_low_hits + 1))
|
|
229
|
+
fi
|
|
230
|
+
done
|
|
231
|
+
for pat in "${_likelihood_high_ref[@]}"; do
|
|
232
|
+
if printf '%s' "$description" | grep -qiE "$pat" 2>/dev/null; then
|
|
233
|
+
likelihood_high_hits=$((likelihood_high_hits + 1))
|
|
234
|
+
fi
|
|
235
|
+
done
|
|
236
|
+
for pat in "${_likelihood_med_ref[@]}"; do
|
|
237
|
+
if printf '%s' "$description" | grep -qiE "$pat" 2>/dev/null; then
|
|
238
|
+
likelihood_med_hits=$((likelihood_med_hits + 1))
|
|
239
|
+
fi
|
|
240
|
+
done
|
|
241
|
+
for pat in "${_likelihood_low_ref[@]}"; do
|
|
242
|
+
if printf '%s' "$description" | grep -qiE "$pat" 2>/dev/null; then
|
|
243
|
+
likelihood_low_hits=$((likelihood_low_hits + 1))
|
|
244
|
+
fi
|
|
245
|
+
done
|
|
246
|
+
|
|
247
|
+
local nonzero_impact=0
|
|
248
|
+
(( impact_high_hits > 0 )) && nonzero_impact=$((nonzero_impact + 1))
|
|
249
|
+
(( impact_mod_hits > 0 )) && nonzero_impact=$((nonzero_impact + 1))
|
|
250
|
+
(( impact_low_hits > 0 )) && nonzero_impact=$((nonzero_impact + 1))
|
|
251
|
+
|
|
252
|
+
if (( nonzero_impact != 1 )); then
|
|
253
|
+
printf 'AMBIGUOUS|impact-bands-hit=%d\n' "$nonzero_impact"
|
|
254
|
+
return 0
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
local impact_band=0
|
|
258
|
+
local impact_label=""
|
|
259
|
+
if (( impact_high_hits > 0 )); then
|
|
260
|
+
impact_band=5
|
|
261
|
+
impact_label="Severe"
|
|
262
|
+
elif (( impact_mod_hits > 0 )); then
|
|
263
|
+
impact_band=3
|
|
264
|
+
impact_label="Moderate"
|
|
265
|
+
elif (( impact_low_hits > 0 )); then
|
|
266
|
+
impact_band=1
|
|
267
|
+
impact_label="Negligible"
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
local nonzero_likelihood=0
|
|
271
|
+
(( likelihood_high_hits > 0 )) && nonzero_likelihood=$((nonzero_likelihood + 1))
|
|
272
|
+
(( likelihood_med_hits > 0 )) && nonzero_likelihood=$((nonzero_likelihood + 1))
|
|
273
|
+
(( likelihood_low_hits > 0 )) && nonzero_likelihood=$((nonzero_likelihood + 1))
|
|
274
|
+
|
|
275
|
+
if (( nonzero_likelihood != 1 )); then
|
|
276
|
+
printf 'AMBIGUOUS|likelihood-bands-hit=%d\n' "$nonzero_likelihood"
|
|
277
|
+
return 0
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
local likelihood_band=0
|
|
281
|
+
local likelihood_label=""
|
|
282
|
+
if (( likelihood_high_hits > 0 )); then
|
|
283
|
+
likelihood_band=5
|
|
284
|
+
likelihood_label="Almost-certain"
|
|
285
|
+
elif (( likelihood_med_hits > 0 )); then
|
|
286
|
+
likelihood_band=3
|
|
287
|
+
likelihood_label="Possible"
|
|
288
|
+
elif (( likelihood_low_hits > 0 )); then
|
|
289
|
+
likelihood_band=1
|
|
290
|
+
likelihood_label="Rare"
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
local score=$((impact_band * likelihood_band))
|
|
294
|
+
local label
|
|
295
|
+
if (( score >= 17 )); then
|
|
296
|
+
label="Very High"
|
|
297
|
+
elif (( score >= 10 )); then
|
|
298
|
+
label="High"
|
|
299
|
+
elif (( score >= 5 )); then
|
|
300
|
+
label="Medium"
|
|
301
|
+
elif (( score >= 3 )); then
|
|
302
|
+
label="Low"
|
|
303
|
+
else
|
|
304
|
+
label="Very Low"
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
printf '%d|%s|impact=%s+likelihood=%s\n' \
|
|
308
|
+
"$score" "$label" "$impact_label" "$likelihood_label"
|
|
309
|
+
}
|
package/package.json
CHANGED
|
@@ -18,19 +18,50 @@ Scan for existing ADRs:
|
|
|
18
18
|
- Read any decisions related to the topic being discussed (if the user has mentioned a topic)
|
|
19
19
|
- If `docs/decisions/` does not exist, create it
|
|
20
20
|
|
|
21
|
-
### 2. Gather context
|
|
21
|
+
### 2. Gather context (P132 derive-first; ADR-044 category-4 silent-framework on derivable fields; category-1 direction-setting only on user-judgment fields)
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Shared dispatch helper**: this surface invokes `packages/architect/lib/derive-first-dispatch.sh` for the canonical slug derivation (Title) and I2-isomorphic stderr advisory format. The canonical source-of-truth lives at `packages/shared/derive-first-dispatch.sh`; the architect package carries a synced per-package copy at `packages/architect/lib/derive-first-dispatch.sh` per ADR-017 (Shared code duplicated into per-package lib/ kept in sync). The same helper is sourced by `/wr-itil:capture-problem` Step 1.5, `/wr-itil:manage-incident` Step 4, and `/wr-itil:manage-problem` Step 4 (each from its own per-package `packages/itil/lib/` copy); drift in the advisory shape across the four surfaces re-opens P132.
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
**Derive-first dispatch.** ADR creation is fundamentally user-judgment-bound — only the user knows the decision space, the alternatives considered, and the chosen-option rationale. But the **declaration-skeleton fields** (Title, status, date, reassessment-date, context-and-problem-statement) carry observable evidence in the user's prose, the working tree, and the wall-clock — the framework can resolve them without firing `AskUserQuestion`. The retained `AskUserQuestion` surfaces (Decision Drivers, Considered Options, Decision Outcome, Consequences, Confirmation, decision-makers) are the genuine **category-1 direction-setting** fields.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
2. **What options were considered?** At least 2 alternatives (including "do nothing" if applicable). For each option, ask for key pros and cons.
|
|
29
|
-
3. **What was chosen and why?** The selected option and the primary reason.
|
|
30
|
-
4. **Who are the decision-makers?** Who made or is making this decision.
|
|
31
|
-
5. **Any consequences to note?** Known good, neutral, or bad outcomes.
|
|
27
|
+
The P132 inverse-P078 trap (`docs/problems/known-error/132-...md`) is the load-bearing motivation. create-adr Step 2 is the **fourth declaration-skill surface** under Phase 2a to ship the derive-first dispatch (after `/wr-itil:capture-problem` Step 1.5, `/wr-itil:manage-incident` Step 4, and `/wr-itil:manage-problem` Step 4). The pattern is I2-isomorphic across all four — the stderr advisory shape `<skill>: derived <field>=<value> from <source>; <reversibility>` is identical beyond substituted values per the helper's `emit_stderr_advisory` function (architect verdict 2026-05-16 P132 Phase 2a-iii-B: pattern lock-in across the 4-surface set).
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
Resolve each field via the following dispatch. **The order is load-bearing** — every derivable field resolves silently with a stderr advisory citing the source; only user-judgment fields fire `AskUserQuestion`.
|
|
30
|
+
|
|
31
|
+
| Field | Dispatch | ADR-044 category |
|
|
32
|
+
|-------|----------|------------------|
|
|
33
|
+
| **Title** | Derive silently. Kebab-case the first 8-10 non-stopword tokens of the user's prose problem-statement (same slug derivation as `/wr-itil:capture-problem` Step 1.4, `/wr-itil:manage-incident` Step 4, and `/wr-itil:manage-problem` Step 4 — uses the shared helper's `derive_kebab_slug` function). Emit stderr advisory: `create-adr: derived title='<slug>' from problem-statement; re-invoke with the desired title or rename the file if the slug is wrong`. Do NOT fire AskUserQuestion. | category-4 silent-framework |
|
|
34
|
+
| **status** (frontmatter) | Always `proposed` for new ADRs per Step 4 template convention. No ask, no advisory needed — SKILL convention is unambiguous. | category-4 silent-framework |
|
|
35
|
+
| **date** (frontmatter) | Today's date (`date +%Y-%m-%d`) per Step 4 template. No ask, no advisory needed — wall-clock derivation is unambiguous. | category-4 silent-framework |
|
|
36
|
+
| **reassessment-date** (frontmatter) | Today + 3 months (`date -v+3m +%Y-%m-%d` on BSD-date / `date -d '+3 months' +%Y-%m-%d` on GNU-date) per Step 4 template. Emit stderr advisory: `create-adr: derived reassessment-date='<YYYY-MM-DD>' from today+3-months default; re-invoke with --reassessment-date= or edit the frontmatter to override`. | category-4 silent-framework |
|
|
37
|
+
| **Context and Problem Statement** | Pull verbatim from `$ARGUMENTS` prose into the Step 4 template's `## Context and Problem Statement` section. **Fallback**: when `$ARGUMENTS` carries NO problem prose (only flags or empty body), fire AskUserQuestion as the genuine category-1 direction-setting surface — *"only the user knows the problem being solved."* Question text: *"What problem does this ADR solve? Why is a decision needed now?"* This is the prose-fallback path; the typical maintainer invocation carries the problem-statement in arguments. | category-1 direction-setting (fallback only; category-4 silent-framework on the typical path where prose is present) |
|
|
38
|
+
| **decision-makers** | Retain AskUserQuestion. Architect verdict 2026-05-16: silent derivation from `git config user.name` would conflate "who committed the ADR" with "who made the decision" — a multi-party decision is one of the canonical mis-attribution risks ADR-013's identity model rejects. Once-per-ADR ask is low-friction in absolute terms. Question text: *"Who are the decision-makers?"* | category-1 direction-setting |
|
|
39
|
+
| **Decision Drivers** | Retain AskUserQuestion. Only the user knows which factors weighted the decision. This is the create-adr-equivalent of manage-problem Step 4's Description (the user-judgment surface). | category-1 direction-setting |
|
|
40
|
+
| **Considered Options** | Retain AskUserQuestion. Only the user knows the alternatives evaluated. ADR-044 cat-5 (taste) would only apply if the framework could offer 2+ valid options — but the alternative space is genuinely user-knowledge (the framework can offer "do nothing" + a status-quo option but the actual alternatives are the user's). Architect verdict 2026-05-16: confirmed cat-1 over cat-5. Per MADR 4.0: ≥2 alternatives including "do nothing" where applicable. | category-1 direction-setting |
|
|
41
|
+
| **Decision Outcome** / **Rationale** | Retain AskUserQuestion. The chosen option + primary reason for the choice. | category-1 direction-setting |
|
|
42
|
+
| **Consequences** (Good / Neutral / Bad) | Retain AskUserQuestion. Only the user knows the expected consequences of the decision. | category-1 direction-setting |
|
|
43
|
+
| **Confirmation** | Retain AskUserQuestion. Testable verification criteria. | category-1 direction-setting |
|
|
44
|
+
| **consulted** / **informed** (frontmatter) | Default to empty list per Step 4 template; fold into the decision-makers AskUserQuestion call if the user surfaces stakeholders. | category-4 silent-framework (default empty); category-1 (when user cites stakeholders) |
|
|
45
|
+
|
|
46
|
+
**Inferred fields (no ask, no advisory needed)**:
|
|
47
|
+
|
|
48
|
+
- **supersedes** (frontmatter): empty list by default; populated only via Step 6 supersession handling when the user explicitly cites a superseded decision.
|
|
49
|
+
|
|
50
|
+
**Stderr advisory contract**: each derived field emits a SINGLE line to stderr (NOT stdout, NOT in the ADR body) via the shared helper's `emit_stderr_advisory` function in `packages/architect/lib/derive-first-dispatch.sh`. The canonical format produced by the helper:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
create-adr: derived <field>=<value> from <source>; <reversibility-clause>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The advisory text shape is I2-isomorphic — same sentence structure across all four derive-first declaration-skill surfaces (`capture-problem`, `manage-incident`, `manage-problem`, `create-adr`) beyond substituted values + source names. The helper is the single source-of-truth for this format; drift here re-opens P132. Embedding the advisory in stdout would risk machine-readers parsing it as an ADR-body line; embedding it in the ADR body would violate the MADR 4.0 schema. Stderr is the correct channel — visible to interactive maintainers in the terminal; invisible to ADR consumers; loggable by orchestrators that capture subprocess stderr.
|
|
57
|
+
|
|
58
|
+
**ADR-026 cost-source grounding**: each derived field cites its source in the advisory (problem-statement token sequence for Title; today's date for date / reassessment-date; default convention for status). The `re-invoke or update if mis-rated` clause carries the reversibility marker ADR-026 mandates for ungrounded outputs.
|
|
59
|
+
|
|
60
|
+
**AFK fail-safe (ADR-013 Rule 6)**: under AFK orchestration, derivable fields (Title / status / date / reassessment-date / Context-when-prose-present) resolve without interactive input. The 6 retained cat-1 AskUserQuestion surfaces (decision-makers / Decision Drivers / Considered Options / Decision Outcome / Consequences / Confirmation) WILL halt AFK execution — that is **correct behaviour** because ADR creation is genuinely user-judgment-bound (the user authors the decision; the framework cannot). JTBD-006 protection: AFK orchestrators that need ADR creation should call `/wr-architect:capture-adr` (the lightweight aside surface) for the skeleton + Title derivation, then defer the cat-1 field collection to the user's next interactive session via the capture-adr deferred-flagged-sections mechanism.
|
|
61
|
+
|
|
62
|
+
**Cross-skill consistency note**: this is the **fourth declaration-skill surface** to ship the derive-first dispatch (after `/wr-itil:capture-problem` Step 1.5, `/wr-itil:manage-incident` Step 4, and `/wr-itil:manage-problem` Step 4 in commits b7cc645 / 43255d2 / 30fd22b). Phase 2a-iii-B (2026-05-16) closes Phase 2a's full 4-surface scope — the I2-isomorphic stderr advisory format is now locked-in across `capture-problem`, `manage-incident`, `manage-problem`, AND `create-adr` via the shared helper at `packages/shared/derive-first-dispatch.sh` with synced per-package lib/ copies. Per ADR-017, drift between copies is caught by `npm run check:derive-first-dispatch` in CI.
|
|
63
|
+
|
|
64
|
+
If the user has already provided context in `$ARGUMENTS` or earlier conversation, use what they've given and only fire AskUserQuestion for the cat-1 fields still missing.
|
|
34
65
|
|
|
35
66
|
### 2b. Decision-boundary analysis (multi-decision check)
|
|
36
67
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# ADR-044 alignment contract assertions for create-adr SKILL.md Step 2
|
|
3
|
+
# (P132 Phase 2a-iii-B derive-first refactor, 2026-05-16).
|
|
4
|
+
#
|
|
5
|
+
# tdd-review: structural-permitted (justification: SKILL.md prose contract
|
|
6
|
+
# assertions; behavioural skill-runtime harness pending P012 + P081 Phase 2;
|
|
7
|
+
# expected to migrate to behavioural form once the harness exists. Added
|
|
8
|
+
# during P132 Phase 2a-iii-B per the inline plan's bridge-marker rule —
|
|
9
|
+
# isomorphic precedent at manage-problem-adr-044-step4-derive-first.bats.)
|
|
10
|
+
#
|
|
11
|
+
# This file is the dedicated structural-grep-permitted home for the ADR-044
|
|
12
|
+
# alignment contract during the bridge window. After P081 Phase 2 retrofits
|
|
13
|
+
# the project's structural-grep tests to behavioural form, this file's
|
|
14
|
+
# assertions migrate too.
|
|
15
|
+
#
|
|
16
|
+
# @problem P132 (agents over-ask in interactive sessions — Phase 2a-iii-B
|
|
17
|
+
# create-adr argument-collection derive-first refactor as 4th adopter)
|
|
18
|
+
# @problem P185 (capture-problem Step 1.5 worked-example precedent)
|
|
19
|
+
# @problem P136 (ADR-044 alignment audit master)
|
|
20
|
+
# @adr ADR-002 (Monorepo per-plugin packages)
|
|
21
|
+
# @adr ADR-017 (Shared code duplicated into per-package lib/ kept in sync)
|
|
22
|
+
# @adr ADR-044 (Decision-Delegation Contract)
|
|
23
|
+
# @adr ADR-013 amended Rule 1 (structured user interaction)
|
|
24
|
+
# @adr ADR-026 (cost-source grounding — stderr advisory shape)
|
|
25
|
+
# @adr ADR-052 (behavioural-by-default with structural bridge window)
|
|
26
|
+
# @jtbd JTBD-001 (enforce governance without slowing down — primary)
|
|
27
|
+
# @jtbd JTBD-006 (work backlog AFK — queued for return, not guessed at)
|
|
28
|
+
# @jtbd JTBD-101 (extend the suite with consistent patterns)
|
|
29
|
+
|
|
30
|
+
setup() {
|
|
31
|
+
SKILL_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
|
|
32
|
+
SKILL_FILE="${SKILL_DIR}/SKILL.md"
|
|
33
|
+
[ -f "$SKILL_FILE" ]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# ----------------------------------------------------------------------
|
|
37
|
+
# Step 2 derive-first refactor (P132 Phase 2a-iii-B) — cat-4 silent-framework
|
|
38
|
+
# on Title + frontmatter defaults + Context-and-Problem-Statement; cat-1
|
|
39
|
+
# direction-setting on Decision Drivers / Considered Options / Decision
|
|
40
|
+
# Outcome / Consequences / Confirmation / decision-makers.
|
|
41
|
+
# ----------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
@test "SKILL.md Step 2 cross-references ADR-044 category-4 (silent-framework) for derivable fields (P132 derive-first)" {
|
|
44
|
+
# P132 Phase 2a-iii-B: Title (kebab from prose), status (always proposed),
|
|
45
|
+
# date (today), reassessment-date (today+3mo), context-and-problem-statement
|
|
46
|
+
# (verbatim from $ARGUMENTS prose) resolve via silent-framework per
|
|
47
|
+
# ADR-044 category 4.
|
|
48
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
49
|
+
[ "$status" -eq 0 ]
|
|
50
|
+
[[ "$output" == *"silent-framework"* ]] || [[ "$output" == *"category 4"* ]] || [[ "$output" == *"category-4"* ]]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@test "SKILL.md Step 2 cross-references ADR-044 category-1 (direction-setting) for user-judgment fields" {
|
|
54
|
+
# Decision Drivers / Considered Options / Decision Outcome / Consequences /
|
|
55
|
+
# Confirmation retain AskUserQuestion as the genuine cat-1 surfaces —
|
|
56
|
+
# ADR creation is fundamentally user-judgment-bound; only the user knows
|
|
57
|
+
# the alternative space, the chosen-option rationale, and the testable
|
|
58
|
+
# confirmation criteria.
|
|
59
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
60
|
+
[ "$status" -eq 0 ]
|
|
61
|
+
[[ "$output" == *"direction-setting"* ]] || [[ "$output" == *"category 1"* ]] || [[ "$output" == *"category-1"* ]]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@test "SKILL.md Step 2 derives Title from prose silently (P132 inverse-P078)" {
|
|
65
|
+
# The 2026-05-06 I001 declaration regression cited in P132 was the agent
|
|
66
|
+
# asking "Title" with N candidate options when kebab-casing the description
|
|
67
|
+
# would have produced the slug directly. create-adr Step 2 must ship the
|
|
68
|
+
# same derive-first pattern as the 3 itil declaration-skill surfaces.
|
|
69
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
70
|
+
[ "$status" -eq 0 ]
|
|
71
|
+
[[ "$output" == *"Title"* ]] || [[ "$output" == *"title"* ]]
|
|
72
|
+
[[ "$output" == *"derive"* ]] || [[ "$output" == *"derived"* ]]
|
|
73
|
+
[[ "$output" == *"kebab"* ]] || [[ "$output" == *"prose"* ]]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@test "SKILL.md Step 2 derives status / date / reassessment-date silently (P132 derive-first)" {
|
|
77
|
+
# Frontmatter defaults (status=proposed, date=today, reassessment-date=
|
|
78
|
+
# today+3mo) are SKILL conventions — no user judgment required at capture.
|
|
79
|
+
# Step 4's existing template already mandates these defaults; Step 2 must
|
|
80
|
+
# name them as silent-framework cat-4 surfaces explicitly so the
|
|
81
|
+
# I2-isomorphic stderr advisory contract covers them.
|
|
82
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
83
|
+
[ "$status" -eq 0 ]
|
|
84
|
+
[[ "$output" == *"status"* ]]
|
|
85
|
+
[[ "$output" == *"date"* ]]
|
|
86
|
+
[[ "$output" == *"reassessment"* ]] || [[ "$output" == *"proposed"* ]]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@test "SKILL.md Step 2 retains Considered Options + Decision Outcome as AskUserQuestion (negative-of-negative guard)" {
|
|
90
|
+
# Regression-resistance: the refactor MUST preserve the genuine cat-1
|
|
91
|
+
# direction-setting surfaces on Considered Options + Decision Outcome.
|
|
92
|
+
# The framework cannot generate the alternative space — only the user
|
|
93
|
+
# knows the alternatives evaluated. Architect verdict 2026-05-16:
|
|
94
|
+
# confirmed cat-1 over cat-5.
|
|
95
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
96
|
+
[ "$status" -eq 0 ]
|
|
97
|
+
[[ "$output" == *"AskUserQuestion"* ]]
|
|
98
|
+
[[ "$output" == *"Considered Options"* ]] || [[ "$output" == *"options"* ]] || [[ "$output" == *"alternative"* ]]
|
|
99
|
+
[[ "$output" == *"Decision Outcome"* ]] || [[ "$output" == *"chosen"* ]] || [[ "$output" == *"rationale"* ]]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@test "SKILL.md Step 2 retains decision-makers as AskUserQuestion (architect verdict — no silent derive from git user.name)" {
|
|
103
|
+
# Architect verdict 2026-05-16: decision-makers MUST stay cat-1
|
|
104
|
+
# AskUserQuestion. `git config user.name` would conflate "who committed
|
|
105
|
+
# the ADR" with "who made the decision" — a multi-party decision is one
|
|
106
|
+
# of the canonical mis-attribution risks ADR-013's identity model rejects.
|
|
107
|
+
# Once-per-ADR ask is low-friction in absolute terms.
|
|
108
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
109
|
+
[ "$status" -eq 0 ]
|
|
110
|
+
[[ "$output" == *"decision-makers"* ]] || [[ "$output" == *"decision makers"* ]]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@test "SKILL.md Step 2 cites P132 (inverse-P078 audit traceability)" {
|
|
114
|
+
# P132 + ADR-044 must appear in Step 2 or Related section so the audit
|
|
115
|
+
# trail for the Phase 2a-iii-B refactor is recoverable from the SKILL.md
|
|
116
|
+
# surface.
|
|
117
|
+
run grep -nE "P132" "$SKILL_FILE"
|
|
118
|
+
[ "$status" -eq 0 ]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@test "SKILL.md Step 2 documents stderr advisory shape for derived fields (ADR-026 grounding)" {
|
|
122
|
+
# ADR-026 cost-source grounding: each silent derivation emits a stderr
|
|
123
|
+
# advisory citing the source. Pattern parity with capture-problem Step
|
|
124
|
+
# 1.5 + manage-incident Step 4 + manage-problem Step 4 (I2-isomorphic
|
|
125
|
+
# across the four declaration-skill surfaces per Phase 2a-iii-B).
|
|
126
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
127
|
+
[ "$status" -eq 0 ]
|
|
128
|
+
[[ "$output" == *"stderr"* ]] || [[ "$output" == *"advisory"* ]]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@test "SKILL.md Step 2 cross-references the 3 prior derive-first surfaces (cross-skill consistency lock-in)" {
|
|
132
|
+
# Phase 2a-iii-B is the 4th adopter. The Step 2 prose must explicitly
|
|
133
|
+
# cite the 3 prior surfaces (capture-problem + manage-incident +
|
|
134
|
+
# manage-problem) so the I2-isomorphic stderr advisory format is
|
|
135
|
+
# locked-in by reference across the 4-surface set.
|
|
136
|
+
run awk '/^### 2\. /,/^### 2b\. /' "$SKILL_FILE"
|
|
137
|
+
[ "$status" -eq 0 ]
|
|
138
|
+
[[ "$output" == *"capture-problem"* ]]
|
|
139
|
+
[[ "$output" == *"manage-incident"* ]]
|
|
140
|
+
[[ "$output" == *"manage-problem"* ]]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@test "SKILL.md Step 2 references derive-first-dispatch.sh helper (sourced from architect package per ADR-017)" {
|
|
144
|
+
# Per ADR-017 self-contained-published-package property: create-adr lives
|
|
145
|
+
# in @windyroad/architect, so it MUST source the helper from its own
|
|
146
|
+
# package lib (packages/architect/lib/derive-first-dispatch.sh), NOT
|
|
147
|
+
# cross-package from packages/itil/lib/. The canonical source lives at
|
|
148
|
+
# packages/shared/derive-first-dispatch.sh; both itil and architect have
|
|
149
|
+
# synced per-package lib/ copies.
|
|
150
|
+
run grep -E "packages/architect/lib/derive-first-dispatch" "$SKILL_FILE"
|
|
151
|
+
[ "$status" -eq 0 ]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# ----------------------------------------------------------------------
|
|
155
|
+
# Negative-of-negative guards — Step 2b multi-decision split + Step 5
|
|
156
|
+
# confirm-with-user remain unchanged.
|
|
157
|
+
# ----------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
@test "SKILL.md Step 2b multi-decision AskUserQuestion is preserved (cat-1 direction-setting, not touched by Phase 2a-iii-B)" {
|
|
160
|
+
# Step 2b is a separate cat-1 direction-setting surface — only the
|
|
161
|
+
# user knows whether multiple decisions can be independently accepted.
|
|
162
|
+
# The Phase 2a-iii-B refactor MUST NOT touch Step 2b's AskUserQuestion gate.
|
|
163
|
+
run awk '/^### 2b\. /,/^### 3\. /' "$SKILL_FILE"
|
|
164
|
+
[ "$status" -eq 0 ]
|
|
165
|
+
[[ "$output" == *"AskUserQuestion"* ]]
|
|
166
|
+
[[ "$output" == *"decision"* ]] || [[ "$output" == *"split"* ]]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@test "SKILL.md Step 5 confirm-with-user AskUserQuestion is preserved (post-write review surface)" {
|
|
170
|
+
# Step 5 is the genuine cat-2 deviation-approval post-write review
|
|
171
|
+
# surface (analogous to manage-incident Step 6 evidence-first gate).
|
|
172
|
+
# The Phase 2a-iii-B refactor MUST NOT touch Step 5's AskUserQuestion gate.
|
|
173
|
+
run awk '/^### 5\. /,/^### 6\. /' "$SKILL_FILE"
|
|
174
|
+
[ "$status" -eq 0 ]
|
|
175
|
+
[[ "$output" == *"AskUserQuestion"* ]]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# ----------------------------------------------------------------------
|
|
179
|
+
# P081 + P132 bridge marker
|
|
180
|
+
# ----------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
@test "bats file carries the tdd-review: structural-permitted marker" {
|
|
183
|
+
run grep -nE "tdd-review:[[:space:]]+structural-permitted" "${BATS_TEST_FILENAME}"
|
|
184
|
+
[ "$status" -eq 0 ]
|
|
185
|
+
}
|