@windyroad/itil 0.46.0-preview.514 → 0.47.0-preview.516

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -497,5 +497,5 @@
497
497
  }
498
498
  },
499
499
  "name": "wr-itil",
500
- "version": "0.46.0"
500
+ "version": "0.47.0"
501
501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.46.0-preview.514",
3
+ "version": "0.47.0-preview.516",
4
4
  "description": "ITIL-aligned IT service management for Claude Code (problem, and future incident/change skills)",
5
5
  "bin": {
6
6
  "windyroad-itil": "./bin/install.mjs"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: wr-itil:capture-problem
3
3
  description: Lightweight problem-capture skill for aside-invocation during foreground work — minimal duplicate-check, skeleton ticket file, single commit per capture, no inline README refresh. Defers full duplicate analysis and README refresh to /wr-itil:review-problems. Use this when the user (or agent mid-iter) wants to capture an observation quickly without disrupting current task flow. For full-intake new-problem creation, use /wr-itil:manage-problem.
4
- allowed-tools: Read, Write, Edit, Bash, Grep, Glob
4
+ allowed-tools: Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion
5
5
  ---
6
6
 
7
7
  # Capture Problem Skill
@@ -23,7 +23,7 @@ This skill is the foreground-lightweight-capture variant of `/wr-itil:manage-pro
23
23
 
24
24
  ## Rule 6 audit (per ADR-032 + ADR-013)
25
25
 
26
- This skill has **at most one classification-only AskUserQuestion (persona, on JTBD-cited descriptions with disagreeing personas only) and zero control-flow branches keyed on the answer**. Each potentially-interactive decision is framework-mediated per ADR-044:
26
+ This skill has **at most one direction-setting AskUserQuestion (the I12 derive-then-ratify proposal, fired only when persona/JTBD derivation fails or is ambiguous) and zero control-flow branches keyed on the answer's substance**. Each potentially-interactive decision is framework-mediated per ADR-044:
27
27
 
28
28
  | Decision | Resolution |
29
29
  |----------|-----------|
@@ -33,12 +33,12 @@ This skill has **at most one classification-only AskUserQuestion (persona, on JT
33
33
  | Effort default | Framework-policy: `M` flagged "deferred — re-rate at next /wr-itil:review-problems". |
34
34
  | Multi-concern split | Out of scope: capture-problem creates one ticket per invocation. Multi-concern observations route to `/wr-itil:manage-problem` (its Step 4b owns the split). |
35
35
  | Empty `$ARGUMENTS` | Halt-with-stderr-directive: print "capture-problem requires a description in $ARGUMENTS — invoke /wr-itil:manage-problem instead for the full intake flow" and exit. AFK orchestrators MUST NOT invoke capture-problem with empty arguments — caller-side contract. |
36
- | JTBD-trace derivation (P287 retained surface) | **Derive-first; silent-framework per ADR-044 category 4 on lexical citations or `--jtbd=` flag pre-resolution.** Step 1.5b runs a lexical detector (`\bJTBD-[0-9]+\b`) against the description. Any cited JTBD IDs are recorded silently. No AskUserQuestion in this dispatch capture-time JTBD anchoring is optional; the next reviewer can refine at `/wr-itil:review-problems` or `/wr-itil:manage-problem` ingestion. |
37
- | Persona derivation (P287 retained surface, decoupled from type) | **Derive-first; silent-framework per ADR-044 category 4 when JTBDs cited and agree.** If JTBDs were cited at Step 1.5b, persona derives from the cited JTBDs' frontmatter. Disagreement across cited JTBDs falls back to AskUserQuestion (genuine taste). Empty persona is legal no hard-block. |
36
+ | JTBD-trace + persona derivation (I12 derive-then-ratify — ADR-060 Amendment 2026-06-02) | **Derive-then-ratify per ADR-044 category 1 (direction-setting) on the AskUserQuestion fallback path; silent-framework category 4 on the derive-success path.** Step 1.5b runs lexical detection (`\bJTBD-[0-9]+\b`) + flag pre-resolution (`--jtbd=` / `--persona=`) + cited-JTBD persona derivation. **Derive-success** → silent-proceed with derived values + stderr advisory. **Derive-failure or ambiguity** → AskUserQuestion proposes up-to-3 candidate persona+JTBD pairs + a Reject option (4-option cap per ADR-044 Rule 1). User response semantics: **REJECT** → halt-with-stderr-directive + exit non-zero (REJECT of proposed persona/JTBD = REJECT of the problem; no ticket created). **Option-pick** (acceptance of a proposed candidate as-is) → silent-proceed with picked values. **Free-text correction** → silent-proceed with corrected values (correction-as-acceptance). |
37
+ | AFK halt-without-flags (NEW per Amendment 2026-06-02) | **Halt-with-stderr-directive per ADR-044 silent-framework category 4 caller-side contract.** When invoked with `--no-prompt` (AFK mode marker) AND derivation fails (no JTBD-NNN citations + no `--persona=` flag + no `--jtbd=` flag, OR cited-JTBD persona-disagreement) capture HALTS with stderr message `capture-problem: cannot derive persona/JTBD interactively under AFK and no --persona/--jtbd flags supplied; capture refused — re-invoke with explicit anchoring`. Exit non-zero. AFK orchestrators (`/wr-itil:work-problems` capture-on-correction sub-flow, agent-mid-iter `capture-problem` invocations via the Agent tool) MUST pre-resolve persona+JTBD via flags before invoking. JTBD-006 compatibility: halt-with-stderr is the audit-trail-preserving form of "queued"; the directive surfaces on user return; the AFK loop continues to the next problem. |
38
38
 
39
- **P287 amendment 2026-06-02 — type-classification retired**: the maintainer-side type-classification dispatch (technical vs user-business) was REMOVED per twice-confirmed user direction (2026-05-25 + 2026-06-02). The redundant axis was already covered by RFC/Story persona-anchoring per ADR-060 Phase 4. JTBD-trace + persona dispatch survive as the JTBD-as-source-of-truth surface; the I12 hard-block (type-keyed JTBD-required halt) is also retired pending ADR-060 amendment substance ratification.
39
+ **P287 / ADR-060 Amendment 2026-06-02 — type-classification retired + I12 hard-block replaced with derive-then-ratify**: the maintainer-side type-classification dispatch (technical vs user-business) was REMOVED per twice-confirmed user direction (2026-05-25 P287 base + 2026-06-02 ADR-060 amendment substance — *"I12 hard-block was wrong. Replacement: the persona and jtbd should be derived by the LLM. And if a persona/jtbd cannot be found then it should be proposed for User ratification. If the user rejects the persona or job to be done then that is should be treated as a rejection of the problem; corrections to the persona or job to be done are treated as acceptance and acceptance is treated as acceptance. Applies to ALL problems."*). The redundant type axis was already covered by RFC/Story persona-anchoring per ADR-060 Phase 4. The original I12 hard-block (type-keyed JTBD-required halt) was REPLACED wholesale in ADR-060 Amendment 2026-06-02 with the derive-then-ratify contract codified in Step 1.5b below. The contract applies to ALL problems (no type-keyed gating; the type axis itself is GONE).
40
40
 
41
- Per ADR-013 Rule 6 fail-safe: every decision above resolves without interactive user input in non-interactive contexts. The Persona fallback AskUserQuestion fires only on cited-JTBD-disagreement (genuine ADR-044 category 5 taste); AFK orchestrators avoid this branch by passing `--persona=<value>` or by capturing without JTBD citations.
41
+ Per ADR-013 Rule 6 fail-safe: every decision above resolves without interactive user input in non-interactive contexts. The derive-then-ratify AskUserQuestion fires only on derivation-failure (interactive surface) AND only when `--no-prompt` is absent (AFK callers pre-resolve via flags or halt-with-stderr-directive never silently swallow the ratification gate).
42
42
 
43
43
  ## Steps
44
44
 
@@ -65,34 +65,42 @@ fi
65
65
 
66
66
  | Flag | Effect on Step 1.5b |
67
67
  |------|-------------------|
68
- | `--jtbd=JTBD-NNN[,JTBD-NNN...]` | Pre-resolves the JTBD-trace value. Step 1.5b skips the JTBD-trace lexical dispatch. Comma-separated list of JTBD IDs (no spaces). |
68
+ | `--jtbd=JTBD-NNN[,JTBD-NNN...]` | Pre-resolves the JTBD-trace value. Step 1.5b skips JTBD-trace derivation. Comma-separated list of JTBD IDs (no spaces). |
69
69
  | `--persona=<value>` | Pre-resolves the persona value. Step 1.5b skips persona derivation. Value MUST be one of: `developer`, `tech-lead`, `plugin-developer`, `plugin-user`. |
70
+ | `--no-prompt` | **AFK mode marker** (REVIVED 2026-06-02 per ADR-060 Amendment 2026-06-02). Suppresses the I12 derive-then-ratify AskUserQuestion fallback. When set AND derivation fails (no JTBD-NNN citations + no `--persona=` flag + no `--jtbd=` flag, OR cited-JTBD persona-disagreement) → capture halts-with-stderr-directive and exits non-zero. AFK orchestrators (`/wr-itil:work-problems` capture-on-correction sub-flow, agent-mid-iter `capture-problem` invocations) MUST pass this flag PLUS pre-resolved `--persona` + `--jtbd` flags to avoid the halt. |
70
71
 
71
- Strip recognised leading flags from `$ARGUMENTS`; the remainder (after flags) is the free-text description. Unknown leading flags halt-with-stderr-directive: print "capture-problem: unknown flag '<flag>' — recognised flags: --jtbd=JTBD-NNN, --persona=<value>" and exit.
72
+ Strip recognised leading flags from `$ARGUMENTS`; the remainder (after flags) is the free-text description. Unknown leading flags halt-with-stderr-directive: print "capture-problem: unknown flag '<flag>' — recognised flags: --jtbd=JTBD-NNN, --persona=<value>, --no-prompt" and exit.
72
73
 
73
- **P287 retirement note**: `--type=technical`, `--type=user-business`, and `--no-prompt` are RETIRED (P287, 2026-06-02). The type-classification axis was removed per twice-confirmed user direction; the AFK-default flag `--no-prompt` is obsolete since the only AskUserQuestion it suppressed is gone. AFK orchestrators that previously passed `--no-prompt` should drop the flag; capture-problem is now silent-by-default.
74
+ **P287 + ADR-060 Amendment 2026-06-02 note**: `--type=technical` and `--type=user-business` are RETIRED (P287, 2026-06-02 type-classification axis removed). `--no-prompt` was retired in P287 base alongside the type-axis removal AND is now REVIVED 2026-06-02 per the ADR-060 amendment's I12 derive-then-ratify contract its semantic is now the AFK-mode marker for the persona/JTBD AskUserQuestion fallback, NOT the historical type-classification suppress.
74
75
 
75
76
  Empty description (post-flag-strip) halts per the Rule 6 audit above.
76
77
 
77
78
  Derive a kebab-case title slug from the first 8-10 non-stopword tokens of the description (matching the existing `manage-problem` slug derivation pattern).
78
79
 
79
- ### 1.5b JTBD-trace + persona dispatch (P287 decoupled from type)
80
+ ### 1.5b JTBD-trace + persona dispatch — I12 derive-then-ratify (ADR-060 Amendment 2026-06-02)
80
81
 
81
- Per ADR-060 § Phase 3 + Phase 4 in-scope amendment (2026-05-13), as amended by P287 (2026-06-02 — type-classification retired). Fires UNCONDITIONALLY (no longer keyed on `type_value = user-business`; the type axis was removed). Both `jtbd_trace_value` and `persona_value` are OPTIONAL capture-time anchoring is best-effort; the next reviewer can refine at `/wr-itil:review-problems` or `/wr-itil:manage-problem` ingestion.
82
+ Per ADR-060 § Phase 3 + Phase 4 in-scope amendment (2026-05-13), as amended by P287 (2026-06-02 base — type-classification retired) AND by ADR-060 Amendment 2026-06-02 (I12 hard-block REPLACED with derive-then-ratify; applies to ALL problems; no type-keyed gating). Fires UNCONDITIONALLY. Both `jtbd_trace_value` and `persona_value` are REQUIRED on every captured ticket via the derive-then-ratify contract; on derivation failure or ambiguity, the dispatch proposes candidates for user ratification via `AskUserQuestion` (direction-setting per ADR-044 category 1).
82
83
 
83
- **Resolve `jtbd_trace_value`** (an ORDERED list of JTBD IDs, possibly empty) via the following dispatch:
84
+ **Resolve `jtbd_trace_value`** (an ORDERED list of JTBD IDs) via the following derive-then-ratify dispatch:
84
85
 
85
- 1. **If `--jtbd=JTBD-NNN[,JTBD-NNN...]` was set in Step 1**: parse comma-separated list; assign to `jtbd_trace_value`; do NOT run the lexical detector; do NOT fire AskUserQuestion (silent-proceed per ADR-013 Rule 5).
86
- 2. **Else** run the **lexical JTBD-trace detector** against the description: `grep -oE '\bJTBD-[0-9]+\b' | sort -u`. If matches found, set `jtbd_trace_value` to the matched IDs (de-duplicated, sorted ascending) and emit stderr advisory: `capture-problem: derived jtbd-trace=<id-list> from description JTBD-NNN citations; re-invoke with --jtbd= to override`. Do NOT fire AskUserQuestion.
87
- 3. **Else (no flag, no lexical detection)**: leave `jtbd_trace_value` empty. The `**JTBD**:` line is omitted from the Step 4 skeleton template. No hard-block capture-time JTBD anchoring is optional under P287; the I12 hard-block (type-keyed JTBD-required halt) was retired alongside the type axis. ADR-060 amendment substance (whether JTBD anchoring should become a nullable-field-conditional gate keyed on some other discriminator) is queued for user re-confirmation per ADR-074.
86
+ 1. **If `--jtbd=JTBD-NNN[,JTBD-NNN...]` was set in Step 1**: parse comma-separated list; assign to `jtbd_trace_value`; do NOT run the lexical detector; do NOT fire `AskUserQuestion` (silent-framework per ADR-044 category 4). Caller pre-resolved.
87
+ 2. **Else** run the **lexical JTBD-trace detector** against the description: `grep -oE '\bJTBD-[0-9]+\b' | sort -u`. If ≥1 match found, set `jtbd_trace_value` to the matched IDs (de-duplicated, sorted ascending) and emit stderr advisory: `capture-problem: derived jtbd-trace=<id-list> from description JTBD-NNN citations; re-invoke with --jtbd= to override`. Do NOT fire `AskUserQuestion` (silent-framework).
88
+ 3. **Else (no flag, no lexical detection derive-failure path)**: enter the **derive-then-ratify dispatch**. Propose up-to-3 candidate JTBD IDs to the user via `AskUserQuestion`. The candidates come from LLM analysis of the description's domain signals (e.g. "AFK loop" + "iter dispatch" propose JTBD-006; "ADR / governance" propose JTBD-001; "plugin discoverability" propose JTBD-101). The 4th option is **Reject** (4-option cap per ADR-044 Rule 1). User response semantics:
89
+ - **REJECT** → halt-with-stderr-directive (`capture-problem: user rejected proposed JTBD trace; per I12 derive-then-ratify (ADR-060 Amendment 2026-06-02), rejection of proposed persona/JTBD = rejection of the problem; no ticket created`); exit non-zero.
90
+ - **Option-pick** (acceptance of a proposed JTBD as-is) → assign `jtbd_trace_value` to the picked ID; proceed silently.
91
+ - **Free-text correction** (user supplies a different JTBD-NNN ID via the AskUserQuestion free-text path) → validate the supplied ID matches `\bJTBD-[0-9]+\b` AND a `docs/jtbd/<persona>/JTBD-<NNN>-*.md` file exists; assign `jtbd_trace_value` to the corrected ID; proceed silently (correction-as-acceptance).
92
+ 4. **AFK halt (the `--no-prompt` branch)**: if `--no-prompt` was set in Step 1 AND control reaches step 3 (no flag + no lexical detection) → SKIP the `AskUserQuestion`; halt-with-stderr-directive (`capture-problem: cannot derive JTBD interactively under AFK and no --jtbd= flag supplied; capture refused — re-invoke with explicit anchoring`); exit non-zero.
88
93
 
89
- **Resolve `persona_value`** (a scalar enum value OR empty) via the following dispatch:
94
+ **Resolve `persona_value`** (a scalar enum value drawn from `{developer, tech-lead, plugin-developer, plugin-user}`) via the following derive-then-ratify dispatch:
90
95
 
91
- 1. **If `--persona=<value>` was set in Step 1**: validate `<value>` `{developer, tech-lead, plugin-developer, plugin-user}`; halt with directive if invalid; otherwise assign and proceed silently.
92
- 2. **Else if `jtbd_trace_value` is non-empty**: derive persona from cited JTBDs' frontmatter. Read each cited `docs/jtbd/<persona>/JTBD-<NNN>-*.md` file; extract its `persona:` (and optionally `secondary-persona:`) frontmatter values; if all cited JTBDs agree on a single persona, set `persona_value` to that persona silently and emit stderr advisory: `capture-problem: derived persona=<value> from cited JTBD <id> frontmatter`. If cited JTBDs disagree, fire AskUserQuestion with the union-of-derived-personas as options (genuine taste per ADR-044 category 5 — cited JTBDs have ratified-coherent contradictory persona constraints, only the user can resolve which applies to THIS problem).
93
- 3. **Else (no JTBDs cited, no `--persona=` flag)**: leave `persona_value` empty. `persona:` frontmatter is OPTIONAL capture-time persona anchoring is best-effort.
96
+ 1. **If `--persona=<value>` was set in Step 1**: validate `<value>` against the enum; halt-with-directive if invalid; otherwise assign and proceed silently. Caller pre-resolved.
97
+ 2. **Else if `jtbd_trace_value` is non-empty AND cited JTBDs agree on a single persona**: derive `persona_value` from the cited JTBDs' `persona:` (and optionally `secondary-persona:`) frontmatter; emit stderr advisory: `capture-problem: derived persona=<value> from cited JTBD <id> frontmatter`; proceed silently.
98
+ 3. **Else (no flag + (no cited JTBDs OR cited-JTBD persona-disagreement) — derive-failure / ambiguity path)**: enter the **derive-then-ratify dispatch**. Propose up-to-3 candidate persona values to the user via `AskUserQuestion`. Candidate generation: on cited-JTBD persona-disagreement, the candidates are the union-of-derived-personas from the cited JTBDs. On no-cited-JTBDs, the candidates come from LLM analysis of the description's persona signals (e.g. "ADR / governance" → propose `developer`; "plugin install / autocomplete" → propose `plugin-user`; "plugin maintainer / scaffold" → propose `plugin-developer`). The 4th option is **Reject** (4-option cap per ADR-044 Rule 1). User response semantics same as JTBD-trace step 3: REJECT → halt-with-stderr-directive + exit non-zero (rejection of proposed persona = rejection of the problem); option-pick → assign + proceed silently (acceptance); free-text correction → validate against enum + assign + proceed silently (correction-as-acceptance).
99
+ 4. **AFK halt (the `--no-prompt` branch)**: if `--no-prompt` was set in Step 1 AND control reaches step 3 → SKIP the `AskUserQuestion`; halt-with-stderr-directive (`capture-problem: cannot derive persona interactively under AFK and no --persona= flag supplied; capture refused — re-invoke with explicit anchoring`); exit non-zero.
94
100
 
95
- **JTBD-301 scope preservation**: this dispatch fires on the maintainer-side `/wr-itil:capture-problem` only. Plugin-user-side `.github/ISSUE_TEMPLATE/problem-report.yml` MUST NOT prompt for JTBD trace or persona — preserves the JTBD-301 firewall per ADR-060 P4.3 maintainer-side / plugin-user-side asymmetry clarifier. Triage during `/wr-itil:manage-problem` ingestion assigns both fields from the reporter's symptom signals (per the JTBD-301 maintainer-side-complement extension landed 2026-05-13).
101
+ **JTBD-301 scope preservation**: this dispatch fires on the maintainer-side `/wr-itil:capture-problem` only. Plugin-user-side `.github/ISSUE_TEMPLATE/problem-report.yml` MUST NOT prompt for JTBD trace or persona — preserves the JTBD-301 firewall per ADR-060 P4.3 maintainer-side / plugin-user-side asymmetry clarifier. Triage during `/wr-itil:manage-problem` ingestion assigns both fields from the reporter's symptom signals (per the JTBD-301 maintainer-side-complement extension, amended 2026-06-02 to remove the type-axis residue).
102
+
103
+ **ADR-044 authority taxonomy**: silent-framework (category 4) on the derive-success paths (flag pre-resolution, lexical detection, cited-JTBD agreement); **direction-setting (category 1)** on the derive-failure AskUserQuestion fallback paths (the user is being asked to ratify the captured ticket's substance — persona + JTBD trace are direction-setting for the ticket's future trace per ADR-060 amendment's I12 reframe — NOT a taste preference between equally-valid options).
96
104
 
97
105
  ### 2. Minimal-grep duplicate check (3-keyword title-only) + hang-off-check subagent dispatch (P346 Phase 3 amendment, 2026-05-31)
98
106
 
@@ -320,10 +328,10 @@ The trailing pointer is **not optional** — it is the user-visible signal that
320
328
  |---------|----------------|-----------------|
321
329
  | Duplicate-check | Wide-net grep + AskUserQuestion branch on matches | 3-keyword title-only grep, list-only (no branch) |
322
330
  | Multi-concern split | Step 4b AskUserQuestion | Out of scope (one ticket per invocation) |
323
- | Skeleton-fill | Full-intake; AskUserQuestion for missing fields | Deferred-placeholder pattern; no classification AskUserQuestion (P287 retired the type prompt) |
331
+ | Skeleton-fill | Full-intake; AskUserQuestion for missing fields | Deferred-placeholder pattern; I12 derive-then-ratify AskUserQuestion fires on derivation-failure (REJECT/CORRECTION/ACCEPT semantics) |
324
332
  | Type-tag prompt | RETIRED (P287, 2026-06-02) | RETIRED (P287, 2026-06-02) — the technical/user-business axis was removed as redundant with RFC/Story persona-anchoring per ADR-060 Phase 4 |
325
- | JTBD-trace + persona | Step 4-equivalent ingestion path | Step 1.5b derive-first dispatch — lexical citations + `--jtbd=` flag pre-resolve silently; persona derives from cited JTBDs' frontmatter; persona-disagreement AskUserQuestion is the only taste fallback |
326
- | AskUserQuestion authority | Multiple branches (deviation-approval / direction-setting / taste / mechanical) | Zero unconditional AskUserQuestion fires; persona-disagreement fallback only (silent-framework per ADR-044 cat. 4 by default; cat. 5 taste on JTBD-persona disagreement); zero control-flow branches |
333
+ | JTBD-trace + persona | Step 4-equivalent ingestion path | Step 1.5b I12 derive-then-ratify dispatch (ADR-060 Amendment 2026-06-02) flag pre-resolution (`--jtbd=` / `--persona=`) silent-proceeds; lexical detection of JTBD-NNN citations silent-proceeds; cited-JTBD persona derivation silent-proceeds; derivation-failure → AskUserQuestion proposal with REJECT (= problem rejected; no ticket) / option-pick (acceptance) / free-text correction (correction-as-acceptance); AFK callers pre-resolve via flags or halt-with-stderr-directive when `--no-prompt` |
334
+ | AskUserQuestion authority | Multiple branches (deviation-approval / direction-setting / taste / mechanical) | One direction-setting branch on the I12 derive-failure fallback (ADR-044 category 1); silent-framework (category 4) on derive-success paths; zero control-flow branches keyed on the answer's substance (REJECT/option-pick/correction are uniform handlers) |
327
335
  | README refresh | P094 inline (regenerate + stage in same commit) | Deferred to next `/wr-itil:review-problems` |
328
336
  | Status transitions | Step 7 owns Open → Known Error → Verifying → Closed | Out of scope (creation only) |
329
337
  | Commit grain | One commit per intake (or per split-concern set) | One commit per capture |
@@ -3,20 +3,32 @@
3
3
  # P170 / Phase 3 P3.1 + Phase 4 P4.2 — behavioural fixture for
4
4
  # capture-problem Step 1.5b JTBD-trace + persona dispatch. Per ADR-060
5
5
  # § Phase 3 + Phase 4 in-scope amendment (2026-05-13), as amended by
6
- # P287 (2026-06-02 — type-classification + I12 hard-block retired):
6
+ # P287 (2026-06-02 base — type-classification retired) AND ADR-060
7
+ # Amendment 2026-06-02 (I12 hard-block REPLACED with derive-then-ratify;
8
+ # applies to ALL problems; no type-keyed gating):
7
9
  #
8
10
  # - Lexical JTBD-trace detection: description-contains-JTBD-NNN-ID →
9
11
  # silent-resolve jtbd_trace_value to the matched IDs.
10
12
  # - --jtbd=JTBD-NNN[,...] flag pre-resolves jtbd_trace_value silently.
11
13
  # - --persona=<value> flag pre-resolves persona_value silently.
12
- # - Skeleton template carries **JTBD**: and **Persona**: body fields
13
- # (frontmatter migration deferred to follow-on slice).
14
+ # - Derive-failure (no flag + no lexical detection + no cited-JTBD
15
+ # agreement) AskUserQuestion proposal with REJECT/option-pick/
16
+ # free-text correction semantics (REJECT = problem rejected; no
17
+ # ticket; option-pick = acceptance; correction = correction-as-
18
+ # acceptance).
19
+ # - --no-prompt + derive-failure → halt-with-stderr-directive (AFK
20
+ # callers MUST pre-resolve via flags).
21
+ # - Skeleton template carries **JTBD**: and **Persona**: body fields.
14
22
  #
15
- # P287 retirement: the I12 hard-block (type=user-business + empty jtbd
16
- # halt) was retired alongside the type axis. JTBD-trace is now purely
17
- # best-effort capture-time anchoring; the I12 reference-impl predicate
18
- # below is preserved as a NEGATIVE assertion (never blocks) for
19
- # regression-guard purposes per architect-review verdict 2026-06-02.
23
+ # i12_should_halt_afk predicate (NEW per ADR-060 Amendment 2026-06-02)
24
+ # encodes the AFK halt-without-flags branch. The historical
25
+ # i12_should_block predicate is preserved as a regression guard
26
+ # (never returns 0) against re-introduction of the type-keyed hard-block.
27
+ #
28
+ # Persona enum aligned 2026-06-02 to `docs/jtbd/<persona>/` directory
29
+ # names: developer / tech-lead / plugin-developer / plugin-user
30
+ # (architect AMEND finding 1 — historical `solo-developer` value was
31
+ # stale ADR-060 P4.2 spec text and is corrected in the amendment).
20
32
  #
21
33
  # Reference-impl pattern: this fixture exercises the algorithm directly
22
34
  # via shell helpers; the SKILL.md prose at runtime executes the same
@@ -44,6 +56,51 @@ i12_should_block() {
44
56
  return 1
45
57
  }
46
58
 
59
+ # ADR-060 Amendment 2026-06-02 — NEW positive predicate for I12 derive-
60
+ # then-ratify AFK halt-without-flags branch. Returns 0 (halt) when:
61
+ # - --no-prompt is set AND
62
+ # - derivation failed (no flag pre-resolution + no lexical detection
63
+ # + no cited-JTBD agreement).
64
+ # Returns 1 (proceed) otherwise. Inputs:
65
+ # $1: no_prompt_flag ("1" if --no-prompt set, "" otherwise)
66
+ # $2: derivation_resolved ("1" if persona+JTBD resolved by any of
67
+ # flag/lexical/cited-JTBD path; "" otherwise)
68
+ i12_should_halt_afk() {
69
+ local no_prompt="$1"
70
+ local derivation_resolved="$2"
71
+ if [ "$no_prompt" = "1" ] && [ -z "$derivation_resolved" ]; then
72
+ return 0 # halt
73
+ fi
74
+ return 1 # proceed (interactive ratification fires, or derivation succeeded)
75
+ }
76
+
77
+ # ADR-060 Amendment 2026-06-02 — reference impl for AskUserQuestion
78
+ # response semantics in the I12 derive-then-ratify dispatch. Returns:
79
+ # "REJECT" when user picked the Reject option
80
+ # "ACCEPT:<v>" when user picked a proposed option <v> as-is
81
+ # "CORRECT:<v>" when user supplied free-text correction <v>
82
+ # Behaviourally the SKILL must treat REJECT as halt-with-stderr-directive
83
+ # (no ticket); ACCEPT and CORRECT both yield ticket-with-value.
84
+ classify_ratification_response() {
85
+ local response="$1"
86
+ case "$response" in
87
+ REJECT) echo "REJECT" ;;
88
+ OPTION:*) echo "ACCEPT:${response#OPTION:}" ;;
89
+ FREETEXT:*) echo "CORRECT:${response#FREETEXT:}" ;;
90
+ *) echo "UNKNOWN:$response" ;;
91
+ esac
92
+ }
93
+
94
+ # Returns 0 when the response yields a ticket; 1 when it halts capture.
95
+ ratification_creates_ticket() {
96
+ local classified="$1"
97
+ case "$classified" in
98
+ REJECT) return 1 ;; # no ticket; capture halts
99
+ ACCEPT:*|CORRECT:*) return 0 ;; # ticket created
100
+ *) return 1 ;;
101
+ esac
102
+ }
103
+
47
104
  # Reference implementation of --jtbd= flag parser. Accepts CSV; returns
48
105
  # space-separated IDs (canonicalised) OR empty if the flag wasn't set.
49
106
  parse_jtbd_flag() {
@@ -55,15 +112,29 @@ parse_jtbd_flag() {
55
112
  }
56
113
 
57
114
  # Reference implementation of --persona= validator. Returns the value
58
- # if it's in the closed enum; halts (returns 1) otherwise.
115
+ # if it's in the closed enum; halts (returns 1) otherwise. Enum aligned
116
+ # 2026-06-02 to docs/jtbd/<persona>/ directory names (architect AMEND
117
+ # finding 1 — `solo-developer` was stale ADR-060 P4.2 text).
59
118
  validate_persona() {
60
119
  local val="$1"
61
120
  case "$val" in
62
- solo-developer|tech-lead|plugin-developer|plugin-user) echo "$val"; return 0 ;;
121
+ developer|tech-lead|plugin-developer|plugin-user) echo "$val"; return 0 ;;
63
122
  *) return 1 ;;
64
123
  esac
65
124
  }
66
125
 
126
+ # Reference implementation of --no-prompt flag detector. Returns "1" if
127
+ # any of the supplied args is --no-prompt; empty otherwise. AFK marker
128
+ # per ADR-060 Amendment 2026-06-02 I12 derive-then-ratify contract.
129
+ parse_no_prompt_flag() {
130
+ for arg in "$@"; do
131
+ case "$arg" in
132
+ --no-prompt) echo "1"; return 0 ;;
133
+ esac
134
+ done
135
+ echo ""
136
+ }
137
+
67
138
  @test "P3.1 detect_jtbd_trace: description with single JTBD-NNN citation extracts ID" {
68
139
  result=$(detect_jtbd_trace "Adopters want JTBD-101 to scale down for atomic fixes")
69
140
  [ "$result" = "JTBD-101" ]
@@ -117,9 +188,9 @@ validate_persona() {
117
188
  [ -z "$result" ]
118
189
  }
119
190
 
120
- @test "P4.2 validate_persona: closed enum accepts solo-developer" {
121
- result=$(validate_persona "solo-developer")
122
- [ "$result" = "solo-developer" ]
191
+ @test "P4.2 validate_persona: closed enum accepts developer (architect AMEND 2026-06-02 — was solo-developer)" {
192
+ result=$(validate_persona "developer")
193
+ [ "$result" = "developer" ]
123
194
  }
124
195
 
125
196
  @test "P4.2 validate_persona: closed enum accepts tech-lead" {
@@ -138,6 +209,85 @@ validate_persona() {
138
209
  ! validate_persona "maintainer"
139
210
  }
140
211
 
212
+ @test "P4.2 validate_persona: rejects stale solo-developer (architect AMEND 2026-06-02 regression guard)" {
213
+ # Pre-Amendment-2026-06-02 ADR-060 P4.2 text named `solo-developer` but
214
+ # docs/jtbd/ directory layout uses `developer/`. The amendment reconciled
215
+ # the enum. This test guards against drift back to the stale value.
216
+ ! validate_persona "solo-developer"
217
+ }
218
+
219
+ # ---------------------------------------------------------------------------
220
+ # ADR-060 Amendment 2026-06-02 — I12 derive-then-ratify positive controls.
221
+ # These exercise the new contract: AskUserQuestion fires on derivation-
222
+ # failure with REJECT/option-pick/free-text-correction semantics; AFK
223
+ # callers pre-resolve via flags or halt-with-stderr-directive on
224
+ # --no-prompt + derive-failure.
225
+ # ---------------------------------------------------------------------------
226
+
227
+ @test "I12 derive-then-ratify: i12_should_halt_afk halts on --no-prompt + derive-failure" {
228
+ # AFK caller passed --no-prompt; derivation failed (no flag pre-resolution,
229
+ # no lexical detection, no cited-JTBD agreement). MUST halt.
230
+ i12_should_halt_afk "1" ""
231
+ }
232
+
233
+ @test "I12 derive-then-ratify: i12_should_halt_afk proceeds when --no-prompt set but derivation succeeded" {
234
+ # AFK caller passed --no-prompt AND pre-resolved via flags. Derivation
235
+ # succeeded; proceed silently with derived values.
236
+ ! i12_should_halt_afk "1" "1"
237
+ }
238
+
239
+ @test "I12 derive-then-ratify: i12_should_halt_afk proceeds when no --no-prompt (interactive mode)" {
240
+ # Interactive caller; derivation failed; AskUserQuestion fires (proceed
241
+ # past the AFK halt gate, into the ratification dispatch).
242
+ ! i12_should_halt_afk "" ""
243
+ }
244
+
245
+ @test "I12 derive-then-ratify: i12_should_halt_afk proceeds when interactive AND derivation succeeded" {
246
+ ! i12_should_halt_afk "" "1"
247
+ }
248
+
249
+ @test "I12 derive-then-ratify: REJECT response halts capture (no ticket created)" {
250
+ classified=$(classify_ratification_response "REJECT")
251
+ [ "$classified" = "REJECT" ]
252
+ ! ratification_creates_ticket "$classified"
253
+ }
254
+
255
+ @test "I12 derive-then-ratify: option-pick (ACCEPTANCE) yields ticket with proposed values" {
256
+ classified=$(classify_ratification_response "OPTION:developer")
257
+ [ "$classified" = "ACCEPT:developer" ]
258
+ ratification_creates_ticket "$classified"
259
+ }
260
+
261
+ @test "I12 derive-then-ratify: free-text correction (CORRECTION-AS-ACCEPTANCE) yields ticket with corrected values" {
262
+ classified=$(classify_ratification_response "FREETEXT:plugin-user")
263
+ [ "$classified" = "CORRECT:plugin-user" ]
264
+ ratification_creates_ticket "$classified"
265
+ }
266
+
267
+ @test "I12 derive-then-ratify: parse_no_prompt_flag detects --no-prompt anywhere in args" {
268
+ result=$(parse_no_prompt_flag "--persona=developer" "--no-prompt" "description text")
269
+ [ "$result" = "1" ]
270
+ }
271
+
272
+ @test "I12 derive-then-ratify: parse_no_prompt_flag empty when --no-prompt absent" {
273
+ result=$(parse_no_prompt_flag "--persona=developer" "description text")
274
+ [ -z "$result" ]
275
+ }
276
+
277
+ @test "I12 derive-then-ratify: flag pre-resolution short-circuits derive-failure (AFK-safe path)" {
278
+ # AFK orchestrator pattern: pass --no-prompt PLUS --persona + --jtbd to
279
+ # avoid the AFK halt. Verifies the load-bearing caller-side contract.
280
+ no_prompt=$(parse_no_prompt_flag "--persona=developer" "--jtbd=JTBD-006" "--no-prompt" "fix work-problems iter halt")
281
+ [ "$no_prompt" = "1" ]
282
+ persona=$(validate_persona "developer")
283
+ [ "$persona" = "developer" ]
284
+ jtbd=$(parse_jtbd_flag "--jtbd=JTBD-006")
285
+ [ "$jtbd" = "JTBD-006" ]
286
+ # Derivation resolved (both flags present); halt predicate proceeds.
287
+ derivation_resolved="1"
288
+ ! i12_should_halt_afk "$no_prompt" "$derivation_resolved"
289
+ }
290
+
141
291
  @test "SKILL.md: Step 1.5b section header exists for JTBD-trace + persona dispatch" {
142
292
  grep -qE '^### 1\.5b JTBD-trace \+ persona dispatch' "$SKILL_FILE"
143
293
  }
@@ -162,14 +312,34 @@ validate_persona() {
162
312
  grep -qE '^\*\*Persona\*\*:' "$SKILL_FILE"
163
313
  }
164
314
 
165
- @test "SKILL.md: Step 1.5b names nullable-field-conditional shape (NOT type-conditional)" {
166
- grep -qE 'nullable-field-conditional' "$SKILL_FILE"
167
- }
168
-
169
- @test "SKILL.md: Step 1.5b cites I12 + ADR-060 amendment 2026-05-13" {
170
- grep -qE 'ADR-060 § Phase 3 \+ Phase 4 in-scope amendment' "$SKILL_FILE"
315
+ @test "SKILL.md: Step 1.5b cites ADR-060 Amendment 2026-06-02 (I12 derive-then-ratify)" {
316
+ grep -qE 'ADR-060 Amendment 2026-06-02' "$SKILL_FILE"
171
317
  }
172
318
 
173
319
  @test "SKILL.md: Step 1.5b preserves JTBD-301 firewall on plugin-user-side intake" {
174
320
  grep -qE 'plugin-user-side .* MUST NOT (prompt|carry)' "$SKILL_FILE"
175
321
  }
322
+
323
+ @test "SKILL.md: Step 1.5b names derive-then-ratify contract" {
324
+ grep -qE 'derive-then-ratify' "$SKILL_FILE"
325
+ }
326
+
327
+ @test "SKILL.md: --no-prompt flag declared in flag table (AFK mode marker)" {
328
+ grep -qE '\| `--no-prompt`' "$SKILL_FILE"
329
+ }
330
+
331
+ @test "SKILL.md: Step 1.5b names REJECT-as-problem-rejection semantics" {
332
+ grep -qE 'REJECT.*=.*[Rr]ejection of the problem|rejection of proposed persona/JTBD = (rejection|REJECT) of the problem' "$SKILL_FILE"
333
+ }
334
+
335
+ @test "SKILL.md: Step 1.5b names AFK halt-with-stderr-directive on --no-prompt + derive-failure" {
336
+ grep -qE 'halt-with-stderr-directive.*AFK|AFK.*halt-with-stderr-directive|cannot derive .* under AFK' "$SKILL_FILE"
337
+ }
338
+
339
+ @test "SKILL.md: allowed-tools includes AskUserQuestion (for I12 ratification dispatch)" {
340
+ grep -qE '^allowed-tools:.*AskUserQuestion' "$SKILL_FILE"
341
+ }
342
+
343
+ @test "SKILL.md: ADR-044 authority taxonomy names direction-setting (category 1) for ratification fallback" {
344
+ grep -qE 'direction-setting.*category 1|category 1.*direction-setting' "$SKILL_FILE"
345
+ }
@@ -284,15 +284,21 @@ EOF
284
284
  # the runtime consumes.
285
285
  # ---------------------------------------------------------------------------
286
286
 
287
- @test "capture-problem: allowed-tools omits AskUserQuestion (no interactive branches)" {
288
- # The skill's contract is NO AskUserQuestion at all — duplicate-check,
289
- # priority-default, effort-default are framework-mediated mechanical
290
- # stages per ADR-044. AskUserQuestion in allowed-tools would let
291
- # future drift sneak prompts back in.
287
+ @test "capture-problem: allowed-tools includes AskUserQuestion (I12 derive-then-ratify dispatch — ADR-060 Amendment 2026-06-02)" {
288
+ # Amended 2026-06-02: the I12 derive-then-ratify contract (ADR-060
289
+ # Amendment 2026-06-02) requires AskUserQuestion for the derive-failure
290
+ # ratification fallback (REJECT / option-pick / free-text correction
291
+ # semantics). Pre-amendment contract was "no AskUserQuestion at all";
292
+ # the amendment promoted persona+JTBD anchoring from optional to
293
+ # required-via-ratification, applies to ALL problems (no type-keyed
294
+ # gating). The historical no-AskUserQuestion contract is preserved at
295
+ # the derive-success paths (silent-framework per ADR-044 cat 4); the
296
+ # AskUserQuestion fires only on derive-failure (ADR-044 cat 1
297
+ # direction-setting). See SKILL.md Step 1.5b dispatch + Rule 6 row.
292
298
  run grep -E '^allowed-tools:' "$SKILL_FILE"
293
299
  [ "$status" -eq 0 ]
294
300
  run grep -E '^allowed-tools:.*AskUserQuestion' "$SKILL_FILE"
295
- [ "$status" -ne 0 ]
301
+ [ "$status" -eq 0 ]
296
302
  }
297
303
 
298
304
  @test "capture-problem: allowed-tools includes Bash (for create-gate marker write)" {
@@ -414,9 +414,17 @@ rm -f "$ITER_JSON"
414
414
 
415
415
  **P342 classification taxonomy — retro-surfaced observations.** When the iter-retro's Step 4b Stage 1 surfaces a ticketable observation, the routing depends on classification:
416
416
 
417
- - **Recurring class-of-behaviour observation** (sibling iters hit same pattern; SKILL-contract drift; hook misbehaviour; framework-gap; pipeline instability with concrete fix path): **auto-ticket via `/wr-itil:capture-problem`** (or `/wr-itil:manage-problem` if capture-problem sibling not yet available). This is the **mechanical-stage carve-out per run-retro Step 4a precedent** — the retro IS the system designed to mechanically observe and surface recurring class-of-behaviour, so its output ticketing is policy-authorised silent proceed per ADR-013 Rule 5. The capture-problem dispatch commits its own ticket per ADR-014; the ticket enters the WSJF queue on the orchestrator's next Step 1 scan. This is the routing that closes the silent-queue-accumulation gap P342 names.
417
+ - **Recurring class-of-behaviour observation** (sibling iters hit same pattern; SKILL-contract drift; hook misbehaviour; framework-gap; pipeline instability with concrete fix path): **auto-ticket via `/wr-itil:capture-problem` with pre-resolved persona + JTBD flags** (or `/wr-itil:manage-problem` if capture-problem sibling not yet available). This is the **mechanical-stage carve-out per run-retro Step 4a precedent** — the retro IS the system designed to mechanically observe and surface recurring class-of-behaviour, so its output ticketing is policy-authorised silent proceed per ADR-013 Rule 5. The capture-problem dispatch commits its own ticket per ADR-014; the ticket enters the WSJF queue on the orchestrator's next Step 1 scan. This is the routing that closes the silent-queue-accumulation gap P342 names.
418
+
419
+ **Dispatch shape under the I12 derive-then-ratify contract (ADR-060 Amendment 2026-06-02; R007 paired-capability gap)**: AFK callers MUST pre-resolve persona + JTBD via flags or capture-problem halts-with-stderr-directive (per capture-problem SKILL.md Step 1.5b AFK halt clause). The halt stderr is unobservable to the AFK user — silent loop-stall, violating JTBD-006's audit-trail guarantee. The iter subprocess derives both values from iter context BEFORE invoking capture-problem:
420
+
421
+ 1. **Persona derivation from iter context**: the iter is dispatched against a specific ticket carrying Origin + RFC trace + story trace; derive persona from those signals. Default to `developer` when context is ambiguous — it is the dominant persona across this monorepo's JTBD corpus. **Validate the derived value against the persona enum `{developer | tech-lead | plugin-developer | plugin-user}` BEFORE dispatch** (capture-problem halts-with-directive on invalid `--persona=` per its SKILL.md Step 1.5b validation rule). On invalid-derivation, route to `outstanding_questions` (genuinely-ambiguous branch below) instead of dispatching with a bad value.
422
+ 2. **JTBD derivation from iter context**: read the iter-prompt content. Cite `JTBD-006` for AFK-loop-continuity / iter-dispatch / orchestrator-mechanic contexts; `JTBD-001` for governance / ADR / decision-record contexts; `JTBD-101` for plugin-discoverability / plugin-developer / suite-extension contexts. Multi-JTBD entries are allowed (comma-separated, no spaces — per capture-problem's `--jtbd=` flag grammar).
423
+ 3. **Dispatch shape**: `/wr-itil:capture-problem --no-prompt --persona=<derived> --jtbd=<derived-list> "<description>"`. The `--no-prompt` flag is the AFK-mode marker that suppresses the I12 derive-then-ratify `AskUserQuestion` fallback inside capture-problem (per its SKILL.md Step 1.5b AFK halt clause); combined with the pre-resolved `--persona` + `--jtbd` flags, the derive-success silent-proceed path fires per ADR-044 category 4 silent-framework.
424
+ 4. **Genuinely-ambiguous derivation** (cannot pick persona/JTBD cleanly from iter context; signals contradict; derived persona fails enum validation): do NOT invoke capture-problem (would halt-with-stderr-directive into the iter subprocess's unobservable stderr; the observation is lost). Instead, queue the observation as an `outstanding_questions` entry with `category: "direction"`, naming the candidate-anchoring options for the orchestrator main-turn Step 2.5 surface. The orchestrator's `AskUserQuestion` on user return resolves the anchoring, then the user (or a future retro pass) creates the ticket.
425
+
418
426
  - **Direction-setting observation** (genuine user-judgment-bound question — design choice, deviation-approval, framework boundary): route to `outstanding_questions` entry per the ITERATION_SUMMARY schema. Orchestrator-level Step 2.5 surfaces these at loop end per the existing batched `AskUserQuestion` flow. These observations preserve the user's authority surface and MUST NOT auto-ticket.
419
- - **Ambiguous** (retro cannot cleanly distinguish recurring-class from direction-setting): **default to auto-ticket** per the P342 trust-boundary asymmetry. The ticket lifecycle (`/wr-itil:manage-problem` Step 9d / `/wr-itil:review-problems` Step 4) will surface any embedded direction-setting question through the standard problem-review flow. Defaulting to queue would re-introduce the silent-queue-accumulation hazard P342 closes; defaulting to ticket has zero observation-drop risk.
427
+ - **Ambiguous** (retro cannot cleanly distinguish recurring-class from direction-setting): **default to auto-ticket** per the P342 trust-boundary asymmetry, using the same persona + JTBD derivation contract above. The ticket lifecycle (`/wr-itil:manage-problem` Step 9d / `/wr-itil:review-problems` Step 4) will surface any embedded direction-setting question through the standard problem-review flow. Defaulting to queue would re-introduce the silent-queue-accumulation hazard P342 closes; defaulting to ticket has zero observation-drop risk. If persona/JTBD derivation itself fails (the recurring-class derivation branch's step 4), fall through to `outstanding_questions` rather than dispatch a halt-bound capture-problem.
420
428
 
421
429
  The classification is silent agent judgement (no `AskUserQuestion` per observation — that would re-route mechanical decisions back to the user, the lazy-deferral surface P135 / ADR-044 close). The mirror locus is run-retro `Step 4b` — same trust-boundary applies whether retro fires in iter context (this surface) OR standalone in main turn (run-retro Step 4b).
422
430
  5. **Output**: end the final message with the `ITERATION_SUMMARY` block defined below — this is how the orchestrator consumes the iteration's result.
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # R007 paired-capability gap (post-commit 54ecf83 — ADR-060 Amendment
4
+ # 2026-06-02 + I12 derive-then-ratify in capture-problem):
5
+ #
6
+ # The P342 mechanical-stage carve-out at Step 5 iter-prompt authorises
7
+ # retro-surfaced recurring class-of-behaviour observations to auto-ticket
8
+ # via `/wr-itil:capture-problem`. The new capture-problem contract
9
+ # requires AFK callers (iter subprocesses) to pre-resolve persona + JTBD
10
+ # via `--no-prompt --persona=<value> --jtbd=JTBD-NNN` flags, OR capture
11
+ # halts-with-stderr-directive. The halt stderr is unobservable to the
12
+ # AFK user — silent loop-stall.
13
+ #
14
+ # The fix shape amends the work-problems Step 5 iter-prompt body's P342
15
+ # classification taxonomy bullet for "Recurring class-of-behaviour":
16
+ # - Derive persona from iter context; default `developer` on ambiguity.
17
+ # - Validate persona against the enum
18
+ # `{developer | tech-lead | plugin-developer | plugin-user}` before
19
+ # dispatch.
20
+ # - Derive JTBD from iter context. Cite JTBD-006 (AFK-loop-continuity),
21
+ # JTBD-001 (governance), JTBD-101 (plugin-developer).
22
+ # - Dispatch shape:
23
+ # /wr-itil:capture-problem --no-prompt --persona=<derived>
24
+ # --jtbd=<derived> "<description>"
25
+ # - On genuine derive-failure (cannot cleanly resolve, invalid enum):
26
+ # route to outstanding_questions, NOT capture-problem dispatch.
27
+ #
28
+ # Doc-lint contract assertions per ADR-037 Permitted Exception.
29
+ #
30
+ # @problem P342 (originating mechanical-stage carve-out)
31
+ # @problem P078 (capture-on-correction; sibling caller for the same dispatch)
32
+ # @adr ADR-060 Amendment 2026-06-02 (I12 derive-then-ratify)
33
+ # @adr ADR-044 (Decision-Delegation Contract — silent-framework cat 4 on
34
+ # derive-success; direction cat 1 on outstanding_questions route)
35
+ # @adr ADR-014 (single-commit grain — SKILL + bats + changeset)
36
+ # @jtbd JTBD-006 (Progress the Backlog While I'm Away — audit-trail outcome:
37
+ # halt-with-stderr in iter is invisible to AFK user; route to
38
+ # outstanding_questions or pre-resolve flags to preserve trail)
39
+
40
+ setup() {
41
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
42
+ SKILL_MD="$REPO_ROOT/packages/itil/skills/work-problems/SKILL.md"
43
+ }
44
+
45
+ @test "work-problems R007: SKILL.md exists" {
46
+ [ -f "$SKILL_MD" ]
47
+ }
48
+
49
+ # ── Dispatch shape: --no-prompt + --persona + --jtbd flags ─────────────────
50
+
51
+ @test "work-problems R007: iter-prompt dispatch shape names --no-prompt flag" {
52
+ # AFK-mode marker. Without --no-prompt, capture-problem's I12 derive-
53
+ # then-ratify AskUserQuestion fallback could fire inside the iter
54
+ # subprocess where the user is absent.
55
+ run grep -nE '\-\-no-prompt' "$SKILL_MD"
56
+ [ "$status" -eq 0 ]
57
+ }
58
+
59
+ @test "work-problems R007: iter-prompt dispatch shape names --persona flag" {
60
+ # Pre-resolves persona to silent-proceed path. Required by capture-
61
+ # problem SKILL.md Step 1.5b silent-framework branch.
62
+ run grep -nE '\-\-persona=' "$SKILL_MD"
63
+ [ "$status" -eq 0 ]
64
+ }
65
+
66
+ @test "work-problems R007: iter-prompt dispatch shape names --jtbd flag" {
67
+ # Pre-resolves JTBD-trace to silent-proceed path.
68
+ run grep -nE '\-\-jtbd=' "$SKILL_MD"
69
+ [ "$status" -eq 0 ]
70
+ }
71
+
72
+ @test "work-problems R007: iter-prompt cites I12 derive-then-ratify contract authority" {
73
+ # The dispatch contract's upstream authority is ADR-060 Amendment
74
+ # 2026-06-02 + I12 derive-then-ratify. Cite so future authors don't
75
+ # drift on the rationale.
76
+ run grep -nE 'I12 derive-then-ratify|derive-then-ratify.*ADR-060|ADR-060 Amendment 2026-06-02' "$SKILL_MD"
77
+ [ "$status" -eq 0 ]
78
+ }
79
+
80
+ @test "work-problems R007: iter-prompt cites R007 paired-capability gap" {
81
+ # R007 names the gap this amendment closes. Cited so the iter-prompt
82
+ # ties back to the originating risk-register entry.
83
+ run grep -nE 'R007.*paired-capability|paired-capability.*R007|R007 paired-capability gap' "$SKILL_MD"
84
+ [ "$status" -eq 0 ]
85
+ }
86
+
87
+ # ── Persona derivation contract ────────────────────────────────────────────
88
+
89
+ @test "work-problems R007: iter-prompt derives persona from iter context" {
90
+ # Derive-don't-ask: persona signals come from the ticket the iter
91
+ # is dispatched against (Origin + RFC + story trace).
92
+ run grep -nE 'derive persona|Persona derivation' "$SKILL_MD"
93
+ [ "$status" -eq 0 ]
94
+ }
95
+
96
+ @test "work-problems R007: iter-prompt defaults persona to developer on ambiguity" {
97
+ # Default chosen per JTBD-006 + dominant-persona in this monorepo.
98
+ run grep -nE 'Default to .?developer.?|default to .?developer.? if|default to .?developer.? when' "$SKILL_MD"
99
+ [ "$status" -eq 0 ]
100
+ }
101
+
102
+ @test "work-problems R007: iter-prompt names the persona enum for pre-dispatch validation" {
103
+ # capture-problem halts-with-directive on invalid --persona= value
104
+ # (SKILL.md Step 1.5b). Iter must validate against the enum BEFORE
105
+ # dispatch, or fall through to outstanding_questions.
106
+ run grep -nE 'developer.*tech-lead.*plugin-developer.*plugin-user|persona enum' "$SKILL_MD"
107
+ [ "$status" -eq 0 ]
108
+ }
109
+
110
+ # ── JTBD derivation contract ───────────────────────────────────────────────
111
+
112
+ @test "work-problems R007: iter-prompt derives JTBD from iter context" {
113
+ # Derive-don't-ask: JTBD signals come from iter-prompt content.
114
+ run grep -nE 'derive.*JTBD|JTBD derivation' "$SKILL_MD"
115
+ [ "$status" -eq 0 ]
116
+ }
117
+
118
+ @test "work-problems R007: iter-prompt cites JTBD-006 for AFK-loop-continuity contexts" {
119
+ run grep -nE 'JTBD-006.*AFK|AFK.*JTBD-006|JTBD-006.*loop-continuity|JTBD-006.*iter-dispatch' "$SKILL_MD"
120
+ [ "$status" -eq 0 ]
121
+ }
122
+
123
+ @test "work-problems R007: iter-prompt cites JTBD-001 for governance contexts" {
124
+ run grep -nE 'JTBD-001.*governance|governance.*JTBD-001|JTBD-001.*ADR|JTBD-001.*decision' "$SKILL_MD"
125
+ [ "$status" -eq 0 ]
126
+ }
127
+
128
+ @test "work-problems R007: iter-prompt cites JTBD-101 for plugin-developer contexts" {
129
+ run grep -nE 'JTBD-101.*plugin|plugin.*JTBD-101|JTBD-101.*discoverability|JTBD-101.*suite-extension' "$SKILL_MD"
130
+ [ "$status" -eq 0 ]
131
+ }
132
+
133
+ # ── Fall-through to outstanding_questions on derive-failure ────────────────
134
+
135
+ @test "work-problems R007: iter-prompt routes genuinely-ambiguous derivation to outstanding_questions" {
136
+ # Architect AMEND closed: when derivation cannot cleanly resolve (or
137
+ # persona fails enum validation), do NOT invoke capture-problem with
138
+ # a bad value (would halt-with-stderr-directive into unobservable iter
139
+ # subprocess stderr); instead, queue for orchestrator main-turn Step
140
+ # 2.5 surfacing.
141
+ run grep -nE 'Genuinely-ambiguous|cannot pick persona/JTBD cleanly|fall through to .?outstanding_questions' "$SKILL_MD"
142
+ [ "$status" -eq 0 ]
143
+ }
144
+
145
+ @test "work-problems R007: iter-prompt names the unobservable-stderr failure mode" {
146
+ # Documents WHY the dispatch contract matters: the halt stderr does
147
+ # not surface to the AFK user; the loop silently stalls. JTBD-006
148
+ # audit-trail violation framing.
149
+ run grep -nE 'stderr is unobservable|unobservable.*stderr|silent loop-stall|halt stderr' "$SKILL_MD"
150
+ [ "$status" -eq 0 ]
151
+ }
152
+
153
+ # ── Composition with the existing P342 carve-out ───────────────────────────
154
+
155
+ @test "work-problems R007: amendment preserves the P342 mechanical-stage carve-out framing" {
156
+ # The R007 fix is ADDITIVE — it carries the new dispatch shape into
157
+ # the existing Recurring-class auto-ticket bullet. The P342 carve-out
158
+ # itself (mechanical-stage / Step 4a precedent / ADR-013 Rule 5)
159
+ # remains the authority for auto-ticketing at all.
160
+ run grep -nE 'mechanical-stage carve-out|policy-authorised silent proceed' "$SKILL_MD"
161
+ [ "$status" -eq 0 ]
162
+ }
163
+
164
+ @test "work-problems R007: Ambiguous bullet reuses the persona+JTBD derivation contract" {
165
+ # The Ambiguous-classification default-to-auto-ticket path also goes
166
+ # through the new dispatch contract (same flag-resolution discipline);
167
+ # otherwise the Ambiguous branch becomes a halt vector.
168
+ run grep -nE 'same persona \+ JTBD derivation contract|using the same persona \+ JTBD derivation' "$SKILL_MD"
169
+ [ "$status" -eq 0 ]
170
+ }