@windyroad/jtbd 0.8.3 → 0.8.4-preview.431

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.
@@ -90,5 +90,5 @@
90
90
  }
91
91
  },
92
92
  "name": "wr-jtbd",
93
- "version": "0.8.3"
93
+ "version": "0.8.4"
94
94
  }
package/README.md CHANGED
@@ -33,6 +33,14 @@ The plugin works automatically. On first use in a project without a JTBD directo
33
33
 
34
34
  This examines your existing features and asks about your user jobs, personas, and desired outcomes to generate `docs/jtbd/<persona>/persona.md` plus per-job files at `docs/jtbd/<persona>/JTBD-NNN-<title>.<status>.md`. If a legacy `docs/JOBS_TO_BE_DONE.md` exists, it is migrated into the directory structure on first run (per [ADR-008](../../docs/decisions/008-jtbd-directory-structure.proposed.md)).
35
35
 
36
+ **Confirm jobs and personas that lack human oversight:**
37
+
38
+ ```
39
+ /wr-jtbd:confirm-jobs-and-personas
40
+ ```
41
+
42
+ The `confirm-jobs-and-personas` skill drains the set of jobs/personas that were recorded without a human confirming they reflect real user/business need (per [ADR-068](../../docs/decisions/068-jtbd-persona-human-oversight-marker-and-confirm-drain.proposed.md)). It surfaces each via AskUserQuestion so you confirm, amend, or reject it, then writes a `human-oversight: confirmed` marker. Detection is a token-cheap grep over `docs/jtbd/` frontmatter; a session-start nudge reports the unoversighted count. Jobs/personas created through `update-guide` are born oversighted, so the unconfirmed set only shrinks. This is the read-write oversight drain — distinct from the read-only `/wr-jtbd:review-jobs` alignment reviewer.
43
+
36
44
  ## How It Works
37
45
 
38
46
  | Hook | Trigger | What it does |
@@ -41,6 +49,7 @@ This examines your existing features and asks about your user jobs, personas, an
41
49
  | `jtbd-enforce-edit.sh` | Edit or Write | Blocks edits until the JTBD agent has reviewed |
42
50
  | `jtbd-mark-reviewed.sh` | Agent completes | Marks the review as done (TTL: 3600s) |
43
51
  | `jtbd-slide-marker.sh` | Agent or Bash | Slides the review marker forward across non-edit operations so an active review session is not invalidated by intervening Bash or sub-agent calls |
52
+ | `jtbd-oversight-nudge.sh` | Session start | Reports how many jobs/personas lack human oversight and points to `/wr-jtbd:confirm-jobs-and-personas`; silent when none, and self-suppressed inside AFK iterations |
44
53
 
45
54
  ## Agent
46
55
 
@@ -50,22 +59,6 @@ The `wr-jtbd:agent` reads your `docs/jtbd/` directory and reviews proposed UI ch
50
59
  - Persona definitions and constraints
51
60
  - Screen-to-job mappings
52
61
 
53
- ## Jobs to be Done
54
-
55
- This plugin serves the [Jobs to be Done](../../docs/jtbd/) below. Per [ADR-051](../../docs/decisions/051-jtbd-anchored-readme-with-drift-advisory.proposed.md), the persona-grouped JTBD anchor is the canonical source of truth for the README's value framing.
56
-
57
- ### Solo developer
58
-
59
- - **[JTBD-001 Enforce Governance Without Slowing Down](../../docs/jtbd/solo-developer/JTBD-001-enforce-governance.proposed.md)** — JTBD review fires automatically on every UI edit; manual review is replaced by an agent reading the persona files the project already maintains.
60
-
61
- ### Tech lead / consultant
62
-
63
- - **[JTBD-202 Run Pre-Flight Governance Checks Before Release or Handover](../../docs/jtbd/tech-lead/JTBD-202-pre-flight-governance-check.proposed.md)** — `/wr-jtbd:review-jobs` produces an on-demand alignment report against documented jobs, attachable to a release note or handover doc.
64
-
65
- ### Plugin user
66
-
67
- - **[JTBD-302 Trust That the README Describes the Plugin I Just Installed](../../docs/jtbd/plugin-user/JTBD-302-trust-readme-describes-installed-behaviour.proposed.md)** — this README is anchored on current JTBD job IDs; drift between prose and shipped behaviour is detectable at retro time per ADR-051.
68
-
69
62
  ## Updating and Uninstalling
70
63
 
71
64
  ```bash
package/agents/agent.md CHANGED
@@ -44,6 +44,22 @@ All review criteria come from the JTBD documentation. Read the docs first and ap
44
44
  - If the change involves API interactions, do the actions align with the job's expected flow?
45
45
  - Are new actions documented in the relevant job's action list?
46
46
 
47
+ ### Unratified Dependency (build-upon guard — ADR-068 enforcement surface 3)
48
+
49
+ When the change or plan under review **explicitly cites, implements, or serves** a specific persona or job — an `@jtbd JTBD-NNN` annotation, a `persona: <name>` reference, or it is authoring that artifact's own flow — check whether that persona/job has been **ratified** (carries `human-oversight: confirmed` in its frontmatter) before letting the change stand. You have `Bash`, so run the predicate by **exit code** (you do NOT need to grep frontmatter yourself):
50
+
51
+ ```bash
52
+ wr-jtbd-is-job-or-persona-unconfirmed <persona-name | JTBD-NNN>
53
+ ```
54
+
55
+ - **Exit 0** (frontmatter lacks the marker AND the artifact is not superseded) → the artifact is **unratified**. Emit **ISSUES FOUND / [Unratified Dependency]** with action: "ratify `<persona | JTBD-NNN>` via `/wr-jtbd:confirm-jobs-and-personas` before this lands." (The predicate prints the resolved path on stdout.)
56
+ - **Exit 1** (ratified, or superseded) → do NOT flag.
57
+ - **Exit 2** (ref not found) → the change cites a persona/job that does not exist; that is a separate Job Gap / Persona Mismatch, not an Unratified Dependency.
58
+
59
+ **Key the flag on the oversight marker, NEVER on `status:`.** `status: proposed`/`accepted` and `human-oversight:` are orthogonal axes (ADR-066). Building on a **ratified** job whose `status` is still `proposed` is fine — do NOT flag it; only the *unratified* (marker-absent, non-superseded) case flags.
60
+
61
+ **Bound to explicit cite/implement — NOT ambient alignment.** You already match every change to a job ID for the PASS verdict (see Job Alignment above); the `[Unratified Dependency]` flag must NOT fire on that mere match — only on an **explicit** dependency the change names. This is the inverse-P078 / P132 over-fire guard. Note: the JTBD unratified set is currently large (the P288 drain is in progress), so unlike the architect surface this will fire more often until that drain completes — that is the intended forcing function, not noise. The `developer`-persona jobs still pending the P288 drain (e.g. `JTBD-001`) are the canonical first-fire cases — the `developer` persona itself was ratified via P289.
62
+
47
63
  ## Output Formatting
48
64
 
49
65
  When referencing JTBD IDs, problem IDs (P<NNN>), or ADR IDs in prose output, always include the human-readable title on first mention. Use the format `JTBD-001 (Enforce Governance Without Slowing Down)`, not bare `JTBD-001`.
@@ -59,11 +75,11 @@ If there are misalignments or gaps:
59
75
 
60
76
  > **JTBD Review: ISSUES FOUND**
61
77
  >
62
- > 1. **[Job Gap / Persona Mismatch / Missing Annotation]**
78
+ > 1. **[Job Gap / Persona Mismatch / Missing Annotation / Unratified Dependency]**
63
79
  > - **File**: `path`, Line ~N
64
- > - **Issue**: What is misaligned
80
+ > - **Issue**: What is misaligned (for **Unratified Dependency**: the change builds on `<persona | JTBD-NNN>` which lacks `human-oversight: confirmed`)
65
81
  > - **Job**: Which job is affected (or "no matching job")
66
- > - **Fix**: Suggested resolution (update JTBD doc, adjust UI, add annotation)
82
+ > - **Fix**: Suggested resolution (update JTBD doc, adjust UI, add annotation; for **Unratified Dependency**: ratify via `/wr-jtbd:confirm-jobs-and-personas` before this lands)
67
83
  >
68
84
  > 2. ...
69
85
 
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bats
2
+ # Doc-lint guard: jtbd agent.md must carry the [Unratified Dependency] verdict
3
+ # (ADR-068 enforcement surface 3 / RFC-011 / P323) — flag a change or plan that
4
+ # explicitly cites/implements/serves a persona or job lacking `human-oversight:
5
+ # confirmed` (unratified, non-superseded), keyed on the oversight marker NOT
6
+ # `status:`. The JTBD twin of the architect side's surface 3 (RFC-010 / P318).
7
+ #
8
+ # tdd-review: structural-permitted (justification: P176 — agent behaviour is
9
+ # prompt-driven with no skill-invocation harness to exercise the verdict
10
+ # behaviourally; ADR-052 Surface 2 structural-justified case, NOT an ADR-005
11
+ # Permitted Exception). When P176 lands, upgrade to a behavioural test that
12
+ # feeds the agent a change citing an unratified persona/job and asserts the
13
+ # verdict. The single-artifact predicate IS behaviourally tested today — see
14
+ # packages/jtbd/scripts/test/is-job-or-persona-unconfirmed.bats.
15
+ #
16
+ # Cross-reference:
17
+ # ADR-068 (JTBD oversight marker + drain — surface 3 amendment 2026-05-27)
18
+ # ADR-074 (Confirm a decision's substance before building dependent work)
19
+ # ADR-066 (oversight marker; orthogonal status/oversight axes)
20
+ # RFC-011 / P323 (this enforcement surface); RFC-010 / P318 (the ADR-side twin)
21
+ # ADR-052 Surface 2 (structural-justified verdict) + P176 (harness gap)
22
+ # @jtbd JTBD-202 (pre-flight governance checks) / JTBD-101 (extend the suite)
23
+
24
+ setup() {
25
+ AGENT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
26
+ AGENT_FILE="${AGENT_DIR}/agent.md"
27
+ }
28
+
29
+ @test "agent.md lists [Unratified Dependency] as a verdict/issue type (ADR-068 surface 3)" {
30
+ run grep -n '\[Unratified Dependency\]' "$AGENT_FILE"
31
+ [ "$status" -eq 0 ]
32
+ }
33
+
34
+ @test "agent.md has an 'Unratified Dependency (build-upon guard' section citing ADR-068" {
35
+ run grep -niE "Unratified Dependency \(build-upon guard" "$AGENT_FILE"
36
+ [ "$status" -eq 0 ]
37
+ run grep -n "ADR-068" "$AGENT_FILE"
38
+ [ "$status" -eq 0 ]
39
+ }
40
+
41
+ @test "agent.md keys the flag on the oversight marker, NOT on status (orthogonal axes)" {
42
+ # ADR-066 / user correction 2026-05-27: building on a ratified-but-proposed job is fine.
43
+ run grep -niE "NEVER on .?status|not .?\`?status|orthogonal" "$AGENT_FILE"
44
+ [ "$status" -eq 0 ]
45
+ }
46
+
47
+ @test "agent.md invokes the predicate by exit code (the jtbd agent has Bash; not a prose-grep mirror)" {
48
+ run grep -n "wr-jtbd-is-job-or-persona-unconfirmed" "$AGENT_FILE"
49
+ [ "$status" -eq 0 ]
50
+ run grep -niE "exit code|exit 0|exit 1" "$AGENT_FILE"
51
+ [ "$status" -eq 0 ]
52
+ }
53
+
54
+ @test "agent.md guards against over-firing on ambient alignment (inverse-P078 / explicit cite)" {
55
+ run grep -niE "explicit(ly)? cite|ambient alignment|over-?fire|NOT fire on that mere match" "$AGENT_FILE"
56
+ [ "$status" -eq 0 ]
57
+ }
58
+
59
+ @test "agent.md routes the fix to /wr-jtbd:confirm-jobs-and-personas (the surface-2 drain)" {
60
+ run grep -n "/wr-jtbd:confirm-jobs-and-personas" "$AGENT_FILE"
61
+ [ "$status" -eq 0 ]
62
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical detect-unoversighted script.
3
+ exec "$(dirname "$0")/../scripts/detect-unoversighted.sh" "$@"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # ADR-049 $PATH shim — dispatches to the canonical is-job-or-persona-unconfirmed script.
3
+ exec "$(dirname "$0")/../scripts/is-job-or-persona-unconfirmed.sh" "$@"
package/hooks/hooks.json CHANGED
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "hooks": {
3
+ "SessionStart": [
4
+ { "matcher": "startup", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-oversight-nudge.sh" }] }
5
+ ],
3
6
  "UserPromptSubmit": [
4
7
  { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-eval.sh" }] }
5
8
  ],
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ # wr-jtbd — SessionStart hook (ADR-068)
3
+ #
4
+ # Surfaces a one-line nudge when jobs/personas lack the human-oversight marker,
5
+ # so the user can drain them via /wr-jtbd:confirm-jobs-and-personas. Sibling of
6
+ # packages/architect/hooks/architect-oversight-nudge.sh (ADR-066); same shape as
7
+ # the ADR-040 session-start surface.
8
+ #
9
+ # Detection is token-cheap: delegates to detect-unoversighted.sh (a grep over
10
+ # docs/jtbd/ frontmatter — no body reads, no per-file LLM call). Silent when the
11
+ # unoversighted count is zero (steady state once the set is drained).
12
+ #
13
+ # AFK self-suppress: shares the suite-wide WR_SUPPRESS_OVERSIGHT_NUDGE guard with
14
+ # the architect oversight nudge (ADR-068 § shared cross-plugin contracts). AFK
15
+ # orchestrators export it once (work-problems Step 5) and every oversight nudge
16
+ # self-suppresses — so this interactive batch-confirm nudge never fires into an
17
+ # absent-user subprocess (JTBD-006 friction guard). Only the literal "1" suppresses.
18
+
19
+ set -euo pipefail
20
+
21
+ if [ "${WR_SUPPRESS_OVERSIGHT_NUDGE:-}" = "1" ]; then
22
+ exit 0
23
+ fi
24
+
25
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
26
+ JTBD_DIR="$PROJECT_DIR/docs/jtbd"
27
+
28
+ [ -d "$JTBD_DIR" ] || exit 0
29
+
30
+ DETECT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$0")/..}/scripts/detect-unoversighted.sh"
31
+ [ -x "$DETECT" ] || DETECT="$(dirname "$0")/../scripts/detect-unoversighted.sh"
32
+
33
+ COUNT="$(bash "$DETECT" "$JTBD_DIR" 2>/dev/null | grep -c . || true)"
34
+ COUNT="${COUNT:-0}"
35
+
36
+ [ "$COUNT" -gt 0 ] 2>/dev/null || exit 0
37
+
38
+ if [ "$COUNT" -eq 1 ]; then
39
+ echo "[wr-jtbd] 1 job/persona lacks human oversight — run /wr-jtbd:confirm-jobs-and-personas to confirm it."
40
+ else
41
+ echo "[wr-jtbd] $COUNT jobs/personas lack human oversight — run /wr-jtbd:confirm-jobs-and-personas to confirm them."
42
+ fi
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # ADR-068: jtbd-oversight-nudge.sh (SessionStart) emits a one-line nudge when
4
+ # jobs/personas lack the human-oversight marker, is silent when none do, and
5
+ # self-suppresses under the shared AFK guard (WR_SUPPRESS_OVERSIGHT_NUDGE=1).
6
+ # Behavioural — exercises the hook against fixture docs/jtbd/ trees.
7
+
8
+ setup() {
9
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
10
+ HOOK="$REPO_ROOT/packages/jtbd/hooks/jtbd-oversight-nudge.sh"
11
+ PLUGIN_ROOT="$REPO_ROOT/packages/jtbd"
12
+ DIR="$(mktemp -d)"
13
+ mkdir -p "$DIR/docs/jtbd/solo-developer"
14
+ }
15
+
16
+ teardown() { rm -rf "$DIR"; }
17
+
18
+ mk_unmarked() {
19
+ mkdir -p "$(dirname "$DIR/docs/jtbd/$1")"
20
+ { echo "---"; echo "status: proposed"; echo "date-created: 2026-05-25"; echo "---"; echo "# $1"; } \
21
+ > "$DIR/docs/jtbd/$1"
22
+ }
23
+ mk_marked() {
24
+ mkdir -p "$(dirname "$DIR/docs/jtbd/$1")"
25
+ { echo "---"; echo "status: proposed"; echo "human-oversight: confirmed"; echo "---"; echo "# $1"; } \
26
+ > "$DIR/docs/jtbd/$1"
27
+ }
28
+
29
+ @test "emits a count line when there are unoversighted jobs/personas" {
30
+ mk_unmarked "solo-developer/persona.md"
31
+ mk_unmarked "solo-developer/JTBD-001-foo.proposed.md"
32
+ run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
33
+ [ "$status" -eq 0 ]
34
+ [[ "$output" == *"2 jobs/personas lack human oversight"* ]]
35
+ [[ "$output" == *"/wr-jtbd:confirm-jobs-and-personas"* ]]
36
+ }
37
+
38
+ @test "uses singular wording for exactly one unoversighted artifact" {
39
+ mk_unmarked "solo-developer/persona.md"
40
+ run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
41
+ [[ "$output" == *"1 job/persona lacks human oversight"* ]]
42
+ }
43
+
44
+ @test "silent when every job/persona is confirmed" {
45
+ mk_marked "solo-developer/persona.md"
46
+ run env CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
47
+ [ "$status" -eq 0 ]
48
+ [ -z "$output" ]
49
+ }
50
+
51
+ @test "shared AFK guard suppresses the nudge entirely" {
52
+ mk_unmarked "solo-developer/persona.md"
53
+ run env WR_SUPPRESS_OVERSIGHT_NUDGE=1 CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
54
+ [ "$status" -eq 0 ]
55
+ [ -z "$output" ]
56
+ }
57
+
58
+ @test "guard value other than 1 does not suppress" {
59
+ mk_unmarked "solo-developer/persona.md"
60
+ mk_unmarked "solo-developer/JTBD-001-foo.proposed.md"
61
+ run env WR_SUPPRESS_OVERSIGHT_NUDGE=0 CLAUDE_PROJECT_DIR="$DIR" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
62
+ [[ "$output" == *"lack human oversight"* ]]
63
+ }
64
+
65
+ @test "silent when project has no docs/jtbd dir" {
66
+ run env CLAUDE_PROJECT_DIR="$DIR/empty" CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT" bash "$HOOK"
67
+ [ "$status" -eq 0 ]
68
+ [ -z "$output" ]
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/jtbd",
3
- "version": "0.8.3",
3
+ "version": "0.8.4-preview.431",
4
4
  "description": "Jobs-to-be-done enforcement for UI changes",
5
5
  "bin": {
6
6
  "windyroad-jtbd": "./bin/install.mjs"
@@ -23,6 +23,7 @@
23
23
  "agents/",
24
24
  "hooks/",
25
25
  "skills/",
26
+ "scripts/",
26
27
  ".claude-plugin/",
27
28
  "lib/"
28
29
  ]
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bash
2
+ # wr-jtbd — detect jobs/personas lacking the human-oversight marker (ADR-068)
3
+ #
4
+ # @jtbd JTBD-202 (Run Pre-Flight Governance Checks — oversight state of the JTBD corpus)
5
+ # @jtbd JTBD-101 (Extend the Suite with New Plugins — reusable adopter-portable primitive)
6
+ #
7
+ # Sibling of packages/architect/scripts/detect-unoversighted.sh (ADR-066). Token-cheap:
8
+ # greps each job/persona's YAML frontmatter for `human-oversight: confirmed`. No body
9
+ # reads, no per-file LLM call. A file is "unoversighted" when its frontmatter does not
10
+ # carry that marker (or has no frontmatter).
11
+ #
12
+ # Usage:
13
+ # detect-unoversighted.sh [JTBD_DIR]
14
+ # JTBD_DIR defaults to docs/jtbd
15
+ #
16
+ # Output: one unoversighted job/persona file path per line, sorted. Empty output = the
17
+ # whole set is confirmed. Always exits 0 (it is a detector, not a gate).
18
+ #
19
+ # Consumed by: jtbd-oversight-nudge.sh (SessionStart count) and
20
+ # /wr-jtbd:confirm-jobs-and-personas (the drain list). Marker contract: ADR-068 (= ADR-066 field).
21
+
22
+ set -euo pipefail
23
+
24
+ JTBD_DIR="${1:-docs/jtbd}"
25
+ [ -d "$JTBD_DIR" ] || exit 0
26
+
27
+ # JTBD layout (ADR-008): docs/jtbd/<persona>/persona.md + docs/jtbd/<persona>/JTBD-NNN-*.md,
28
+ # with docs/jtbd/README.md as the top-level index. Match the per-persona files; README is
29
+ # never a job/persona record.
30
+ shopt -s nullglob
31
+ for f in "$JTBD_DIR"/*/*.md "$JTBD_DIR"/*.md; do
32
+ base="$(basename "$f")"
33
+ [ "$base" = "README.md" ] && continue
34
+ # Superseded artifacts (if an adopter uses a .superseded.md suffix) are retired.
35
+ case "$base" in *.superseded.md) continue ;; esac
36
+
37
+ fm="$(awk '
38
+ NR==1 && $0 != "---" { exit }
39
+ NR==1 { next }
40
+ /^---[[:space:]]*$/ { exit }
41
+ { print }
42
+ ' "$f")"
43
+
44
+ if ! printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*confirmed[[:space:]]*$'; then
45
+ echo "$f"
46
+ fi
47
+ done | sort
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ # wr-jtbd — predicate: is a referenced persona or job unconfirmed? (ADR-068 surface 3)
3
+ #
4
+ # Single-artifact sibling of detect-unoversighted.sh (ADR-068). Where the
5
+ # detector LISTS the whole unoversighted set and always exits 0, this answers
6
+ # ONE question for ONE persona/job via its EXIT CODE — for the build-upon guard
7
+ # the wr-jtbd:agent runs (the [Unratified Dependency] verdict, RFC-011 / P323).
8
+ # The JTBD twin of packages/architect/scripts/is-decision-unconfirmed.sh
9
+ # (ADR-074). A separate script (not a mode flag on the detector) keeps the
10
+ # detector's "always exit 0, path-list on stdout" contract intact.
11
+ #
12
+ # "Unconfirmed" mirrors detect-unoversighted.sh EXACTLY:
13
+ # - the artifact's frontmatter lacks `human-oversight: confirmed`, AND
14
+ # - the artifact is not superseded.
15
+ # CANONICAL SHAPE: detect-unoversighted.sh. Keep the frontmatter-extraction
16
+ # awk block + the `human-oversight: confirmed` grep + the superseded skip in
17
+ # sync with that script. The `@test "agrees with detect-unoversighted ..."`
18
+ # case in test/is-job-or-persona-unconfirmed.bats fails if these two drift.
19
+ #
20
+ # Usage:
21
+ # is-job-or-persona-unconfirmed.sh <ref> [JTBD_DIR]
22
+ # <ref> = JTBD-NNN | NNN | <persona-name> | path/to/<file>.md
23
+ # JTBD_DIR defaults to docs/jtbd
24
+ #
25
+ # Ref resolution (ADR-008 layout: docs/jtbd/<persona>/persona.md +
26
+ # docs/jtbd/<persona>/JTBD-NNN-*.md):
27
+ # - a path to an existing file → that file
28
+ # - JTBD-NNN or a bare NNN → docs/jtbd/*/JTBD-NNN-*.md (first match)
29
+ # - anything else (a persona name) → docs/jtbd/<name>/persona.md
30
+ #
31
+ # Exit codes:
32
+ # 0 = unconfirmed — the build-upon guard SHOULD fire. Prints the resolved path.
33
+ # 1 = confirmed OR superseded — guard should NOT fire. No stdout.
34
+ # 2 = not found / unparseable ref. No stdout; reason on stderr.
35
+
36
+ set -uo pipefail
37
+
38
+ REF="${1:-}"
39
+ JTBD_DIR="${2:-docs/jtbd}"
40
+
41
+ [ -n "$REF" ] || { echo "is-job-or-persona-unconfirmed: missing <ref>" >&2; exit 2; }
42
+
43
+ # ── Resolve the persona/job file ──────────────────────────────────────────
44
+ file=""
45
+ if [ -f "$REF" ]; then
46
+ file="$REF"
47
+ elif printf '%s' "$REF" | grep -qiE 'JTBD-?[0-9]+|^[0-9]+$'; then
48
+ # Job ref: JTBD-NNN or a bare numeric. Match the per-persona job file.
49
+ num="$(printf '%s' "$REF" | grep -oE '[0-9]+' | head -1)"
50
+ [ -n "$num" ] || { echo "is-job-or-persona-unconfirmed: cannot parse job id from '$REF'" >&2; exit 2; }
51
+ shopt -s nullglob
52
+ for cand in "$JTBD_DIR"/*/JTBD-"$num"-*.md "$JTBD_DIR"/*/"$num"-*.md; do
53
+ file="$cand"; break
54
+ done
55
+ shopt -u nullglob
56
+ else
57
+ # Persona-name ref → the persona's persona.md.
58
+ cand="$JTBD_DIR/$REF/persona.md"
59
+ [ -f "$cand" ] && file="$cand"
60
+ fi
61
+
62
+ [ -n "$file" ] && [ -f "$file" ] || {
63
+ echo "is-job-or-persona-unconfirmed: no persona/job file for '$REF' under $JTBD_DIR" >&2
64
+ exit 2
65
+ }
66
+
67
+ base="$(basename "$file")"
68
+
69
+ # Superseded artifacts are retired — a newer job/persona replaced them. The
70
+ # build-upon guard does not fire (mirror of detect-unoversighted.sh's skip).
71
+ case "$base" in *.superseded.md) exit 1 ;; esac
72
+
73
+ # Extract the frontmatter block (mirror of detect-unoversighted.sh): lines
74
+ # between the leading `---` and the next `---`. No leading `---` ⇒ no
75
+ # frontmatter ⇒ treated as unconfirmed.
76
+ fm="$(awk '
77
+ NR==1 && $0 != "---" { exit }
78
+ NR==1 { next }
79
+ /^---[[:space:]]*$/ { exit }
80
+ { print }
81
+ ' "$file")"
82
+
83
+ if printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*confirmed[[:space:]]*$'; then
84
+ exit 1 # confirmed — OK to build on
85
+ fi
86
+
87
+ # Unconfirmed — the build-upon guard SHOULD fire. Name the file for the guard.
88
+ echo "$file"
89
+ exit 0
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # ADR-068: detect-unoversighted.sh prints jobs/personas whose frontmatter lacks the
4
+ # `human-oversight: confirmed` marker. Behavioural — exercises the script against
5
+ # fixture docs/jtbd/<persona>/ trees and asserts on stdout.
6
+
7
+ setup() {
8
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
9
+ SCRIPT="$REPO_ROOT/packages/jtbd/scripts/detect-unoversighted.sh"
10
+ DIR="$(mktemp -d)"
11
+ mkdir -p "$DIR/docs/jtbd/solo-developer"
12
+ }
13
+
14
+ teardown() { rm -rf "$DIR"; }
15
+
16
+ mk() { # mk <relpath under docs/jtbd> <extra frontmatter lines...>
17
+ local rel="$1"; shift
18
+ mkdir -p "$(dirname "$DIR/docs/jtbd/$rel")"
19
+ {
20
+ echo "---"
21
+ echo "status: proposed"
22
+ echo "date-created: 2026-05-25"
23
+ for line in "$@"; do echo "$line"; done
24
+ echo "---"
25
+ echo "# $rel"
26
+ } > "$DIR/docs/jtbd/$rel"
27
+ }
28
+
29
+ @test "a job without the marker is reported" {
30
+ mk "solo-developer/JTBD-001-foo.proposed.md"
31
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
32
+ [ "$status" -eq 0 ]
33
+ [[ "$output" == *"JTBD-001-foo.proposed.md"* ]]
34
+ }
35
+
36
+ @test "a persona file without the marker is reported" {
37
+ mk "solo-developer/persona.md"
38
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
39
+ [[ "$output" == *"solo-developer/persona.md"* ]]
40
+ }
41
+
42
+ @test "a job carrying human-oversight: confirmed is NOT reported" {
43
+ mk "solo-developer/JTBD-002-bar.proposed.md" "human-oversight: confirmed" "oversight-date: 2026-05-25"
44
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
45
+ [[ "$output" != *"JTBD-002-bar.proposed.md"* ]]
46
+ }
47
+
48
+ @test "the top-level docs/jtbd/README.md is never reported" {
49
+ echo "# JTBD index" > "$DIR/docs/jtbd/README.md"
50
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
51
+ [[ "$output" != *"README.md"* ]]
52
+ }
53
+
54
+ @test "a per-persona README is also excluded" {
55
+ echo "# persona index" > "$DIR/docs/jtbd/solo-developer/README.md"
56
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
57
+ [[ "$output" != *"README.md"* ]]
58
+ }
59
+
60
+ @test "a file with no frontmatter counts as unoversighted" {
61
+ mkdir -p "$DIR/docs/jtbd/tech-lead"
62
+ echo "# bare persona, no frontmatter" > "$DIR/docs/jtbd/tech-lead/persona.md"
63
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
64
+ [[ "$output" == *"tech-lead/persona.md"* ]]
65
+ }
66
+
67
+ @test "marker match is case-insensitive and tolerant of trailing space" {
68
+ mk "solo-developer/JTBD-003-baz.proposed.md" "human-oversight: confirmed "
69
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
70
+ [[ "$output" != *"JTBD-003-baz.proposed.md"* ]]
71
+ }
72
+
73
+ @test "missing jtbd dir exits 0 with no output" {
74
+ run bash "$SCRIPT" "$DIR/docs/nonexistent"
75
+ [ "$status" -eq 0 ]
76
+ [ -z "$output" ]
77
+ }
78
+
79
+ @test "fully-confirmed corpus produces empty output" {
80
+ mk "solo-developer/persona.md" "human-oversight: confirmed"
81
+ mk "solo-developer/JTBD-001-foo.proposed.md" "human-oversight: confirmed"
82
+ run bash "$SCRIPT" "$DIR/docs/jtbd"
83
+ [ "$status" -eq 0 ]
84
+ [ -z "$output" ]
85
+ }
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # ADR-068 surface 3 / RFC-011 / P323: is-job-or-persona-unconfirmed.sh is the
4
+ # single-artifact predicate for the JTBD build-upon guard (the wr-jtbd:agent
5
+ # [Unratified Dependency] verdict). It answers "is this referenced persona or
6
+ # job unconfirmed?" via its EXIT CODE, where "unconfirmed" mirrors
7
+ # detect-unoversighted.sh EXACTLY (frontmatter lacks `human-oversight:
8
+ # confirmed`, and the artifact is not superseded). The JTBD twin of the
9
+ # architect side's is-decision-unconfirmed.sh (ADR-074).
10
+ #
11
+ # Behavioural — exercises the script against fixture trees and asserts on its
12
+ # exit code + stdout, not its source text (ADR-052).
13
+
14
+ setup() {
15
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
16
+ SCRIPT="$REPO_ROOT/packages/jtbd/scripts/is-job-or-persona-unconfirmed.sh"
17
+ DETECT="$REPO_ROOT/packages/jtbd/scripts/detect-unoversighted.sh"
18
+ DIR="$(mktemp -d)"
19
+ mkdir -p "$DIR/docs/jtbd"
20
+ }
21
+
22
+ teardown() {
23
+ rm -rf "$DIR"
24
+ }
25
+
26
+ # mk_persona <persona-name> <confirmed?yes|no>
27
+ mk_persona() {
28
+ local name="$1"; local confirmed="$2"
29
+ mkdir -p "$DIR/docs/jtbd/$name"
30
+ {
31
+ echo "---"
32
+ echo "name: $name"
33
+ echo "description: test persona $name"
34
+ [ "$confirmed" = "yes" ] && { echo "human-oversight: confirmed"; echo "oversight-date: 2026-05-27"; }
35
+ echo "---"
36
+ echo "# $name"
37
+ } > "$DIR/docs/jtbd/$name/persona.md"
38
+ }
39
+
40
+ # mk_job <persona-name> <NNN> <slug> <confirmed?yes|no> [state]
41
+ mk_job() {
42
+ local persona="$1"; local num="$2"; local slug="$3"; local confirmed="$4"; local state="${5:-proposed}"
43
+ mkdir -p "$DIR/docs/jtbd/$persona"
44
+ {
45
+ echo "---"
46
+ echo "status: $state"
47
+ echo "job-id: $slug"
48
+ echo "persona: $persona"
49
+ [ "$confirmed" = "yes" ] && { echo "human-oversight: confirmed"; echo "oversight-date: 2026-05-27"; }
50
+ echo "---"
51
+ echo "# $slug"
52
+ } > "$DIR/docs/jtbd/$persona/JTBD-$num-$slug.md"
53
+ }
54
+
55
+ @test "persona without the marker is unconfirmed (exit 0, prints path)" {
56
+ mk_persona "solo-developer" "no"
57
+ run bash "$SCRIPT" "solo-developer" "$DIR/docs/jtbd"
58
+ [ "$status" -eq 0 ]
59
+ [[ "$output" == *"solo-developer/persona.md"* ]]
60
+ }
61
+
62
+ @test "persona carrying human-oversight: confirmed is confirmed (exit 1, no stdout)" {
63
+ mk_persona "tech-lead" "yes"
64
+ run bash "$SCRIPT" "tech-lead" "$DIR/docs/jtbd"
65
+ [ "$status" -eq 1 ]
66
+ [ -z "$output" ]
67
+ }
68
+
69
+ @test "job (JTBD-NNN) without the marker is unconfirmed (exit 0, prints path)" {
70
+ mk_job "solo-developer" "001" "enforce-governance" "no"
71
+ run bash "$SCRIPT" "JTBD-001" "$DIR/docs/jtbd"
72
+ [ "$status" -eq 0 ]
73
+ [[ "$output" == *"JTBD-001-enforce-governance.md"* ]]
74
+ }
75
+
76
+ @test "job carrying the marker is confirmed (exit 1)" {
77
+ mk_job "tech-lead" "201" "restore-service-fast" "yes"
78
+ run bash "$SCRIPT" "JTBD-201" "$DIR/docs/jtbd"
79
+ [ "$status" -eq 1 ]
80
+ }
81
+
82
+ @test "superseded job (even without marker) does NOT fire the guard (exit 1)" {
83
+ mkdir -p "$DIR/docs/jtbd/solo-developer"
84
+ {
85
+ echo "---"; echo "status: superseded"; echo "persona: solo-developer"; echo "---"; echo "# retired"
86
+ } > "$DIR/docs/jtbd/solo-developer/JTBD-009-retired.superseded.md"
87
+ run bash "$SCRIPT" "JTBD-009" "$DIR/docs/jtbd"
88
+ [ "$status" -eq 1 ]
89
+ }
90
+
91
+ @test "a bare numeric job ref resolves" {
92
+ mk_job "solo-developer" "002" "ship-with-confidence" "no"
93
+ run bash "$SCRIPT" "002" "$DIR/docs/jtbd"
94
+ [ "$status" -eq 0 ]
95
+ [[ "$output" == *"JTBD-002-ship-with-confidence.md"* ]]
96
+ }
97
+
98
+ @test "a direct path ref resolves" {
99
+ mk_persona "plugin-user" "no"
100
+ run bash "$SCRIPT" "$DIR/docs/jtbd/plugin-user/persona.md"
101
+ [ "$status" -eq 0 ]
102
+ }
103
+
104
+ @test "an unknown persona name exits 2 (not found)" {
105
+ run bash "$SCRIPT" "ghost-persona" "$DIR/docs/jtbd"
106
+ [ "$status" -eq 2 ]
107
+ }
108
+
109
+ @test "an unknown job ref exits 2 (not found)" {
110
+ run bash "$SCRIPT" "JTBD-999" "$DIR/docs/jtbd"
111
+ [ "$status" -eq 2 ]
112
+ }
113
+
114
+ @test "agrees with detect-unoversighted on the same fixture (sync guard)" {
115
+ # The two scripts share the frontmatter/marker/superseded shape. This guard
116
+ # fails if a future edit drifts one from the other.
117
+ mk_persona "solo-developer" "no"
118
+ mk_persona "tech-lead" "yes"
119
+ detect_out="$(bash "$DETECT" "$DIR/docs/jtbd")"
120
+ # solo-developer is in the detector's unoversighted list AND the predicate exits 0.
121
+ [[ "$detect_out" == *"solo-developer/persona.md"* ]]
122
+ run bash "$SCRIPT" "solo-developer" "$DIR/docs/jtbd"; [ "$status" -eq 0 ]
123
+ # tech-lead is NOT in the detector's list AND the predicate exits 1.
124
+ [[ "$detect_out" != *"tech-lead/persona.md"* ]]
125
+ run bash "$SCRIPT" "tech-lead" "$DIR/docs/jtbd"; [ "$status" -eq 1 ]
126
+ }
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: wr-jtbd:confirm-jobs-and-personas
3
+ description: Drain the set of Jobs To Be Done and personas that lack human oversight. Surfaces each auto-derived job/persona via AskUserQuestion so a human confirms, amends, or rejects it, then writes the human-oversight marker. Use when the session-start nudge reports jobs/personas lack oversight, or any time you want to review the documented JTBD corpus. This is the read-write oversight drain — distinct from /wr-jtbd:review-jobs (the read-only alignment reviewer).
4
+ allowed-tools: Read, Glob, Grep, Bash, Edit, AskUserQuestion
5
+ ---
6
+
7
+ # Confirm Jobs and Personas — human-oversight drain
8
+
9
+ Lift auto-derived Jobs To Be Done and personas to human decisions. Documented jobs/personas are load-bearing: the JTBD edit gate reviews every project change against `docs/jtbd/`, so a job or persona that was agent-derived without a human confirming it reflects real need propagates wrong alignment verdicts. This skill drains the **unoversighted set** (jobs/personas lacking `human-oversight: confirmed`, per ADR-068): it surfaces each via `AskUserQuestion`, and writes the oversight marker only when a human confirms.
10
+
11
+ This is the P288 / ADR-068 drain surface — the JTBD sibling of `/wr-architect:review-decisions`. It is **read-write** (writes the marker on confirm). It is NOT the same as `/wr-jtbd:review-jobs`, which is a read-only alignment review ("do my changes trace to documented jobs?"); this skill confirms the jobs/personas themselves.
12
+
13
+ ## When to use
14
+
15
+ - The session-start nudge reported `N jobs/personas lack human oversight`.
16
+ - Pre-handover / pre-release: confirm the JTBD corpus reflects real user/business need.
17
+ - After an `update-guide` run or agent-derived job/persona authoring landed files without confirmation.
18
+ - Any focused sitting — designed for **batches over multiple sittings**, not one blocking pass.
19
+
20
+ ## How it works
21
+
22
+ The marker persists (ADR-009 never-re-ask principle), so a partially-drained set resumes cleanly on the next run.
23
+
24
+ ### Step 1: Enumerate the unoversighted set
25
+
26
+ ```bash
27
+ wr-jtbd-detect-unoversighted docs/jtbd
28
+ ```
29
+
30
+ The `wr-jtbd-detect-unoversighted` command is a `$PATH`-resolved shim (ADR-049) dispatching `packages/jtbd/scripts/detect-unoversighted.sh`. It prints one unoversighted job/persona path per line (README excluded; token-cheap grep over frontmatter — no body reads). Empty output → the corpus is fully confirmed; report and stop.
31
+
32
+ ### Step 2: Cluster + order
33
+
34
+ Read **only the frontmatter + title + Job Statement / persona "Who" section** of each unoversighted file (not full bodies — keep it cheap). Group by persona directory and order **load-bearing first**: personas before their jobs (a persona frames its jobs), and jobs that other artifacts (problems, RFCs, READMEs) cite most heavily before narrow ones.
35
+
36
+ ### Step 3: Present each via AskUserQuestion (batched)
37
+
38
+ For each job/persona in the ordered queue, surface it as an `AskUserQuestion` (cap **4 per call** per ADR-013 Rule 1; issue further calls sequentially):
39
+
40
+ - **Question**: the job statement (for a JTBD) or the persona definition (who they are + key constraints), in one line.
41
+ - **Context**: grounded in what the file actually says (per ADR-026) — the persona served, the desired outcomes, any cited problems/RFCs.
42
+ - **Options** per artifact:
43
+ - **Confirm** — the job/persona accurately reflects real need; write the marker.
44
+ - **Amend** — mostly right but needs a change; capture it, apply it to the file, then write the marker.
45
+ - **Reject** — the auto-derived job/persona does not reflect real need; do NOT write the marker. Note the rework (a `wr-jtbd:update-guide` rewrite, or retirement).
46
+ - **Defer** — skip this sitting; leave unoversighted for later.
47
+
48
+ This is a genuine human-decision surface (the point of P288/ADR-068) — `AskUserQuestion` is correct here, not over-asking. Do not auto-confirm; do not prose-ask.
49
+
50
+ ### Step 4: Apply the outcome
51
+
52
+ - **Confirm / Amend**: write `human-oversight: confirmed` + `oversight-date: <today, YYYY-MM-DD>` into the file's frontmatter (insert after the `status:`/`date-created:` line if absent; never duplicate). For Amend, apply the directed change first. Edits go through the standard JTBD / architect edit gate per ADR-014.
53
+ - **Reject**: leave the marker absent; record the rework.
54
+ - **Defer**: no write.
55
+
56
+ **Unoversighted ≠ unusable** (ADR-068): an unconfirmed job/persona stays fully readable and review-anchorable. The marker records provenance; it never quarantines the doc or blocks reviews from reading it.
57
+
58
+ ### Step 5: Commit + report
59
+
60
+ Commit the confirmed/amended files per ADR-014 (one commit per drain sitting is acceptable). Report: confirmed / amended / rejected / deferred counts, and the remaining unoversighted count (re-run the detector). The session-start nudge count drops by the number confirmed.
61
+
62
+ ## Notes
63
+
64
+ - **Never re-ask** — a confirmed job/persona carries the marker permanently and is excluded from future runs (ADR-009). Write-once **except** when the job statement / persona definition is materially rewritten — a material amend clears the marker for re-confirmation (ADR-068 Reassessment).
65
+ - **AFK** — interactive by construction (the confirm IS the human decision); not dispatched in AFK iteration subprocesses. The session-start nudge self-suppresses there (`WR_SUPPRESS_OVERSIGHT_NUDGE=1`).
66
+ - **Born-confirmed going forward** — `/wr-jtbd:update-guide` writes the marker when the user confirms a new/edited job or persona, so new artifacts enter the set already oversighted and the unoversighted count only shrinks.
67
+
68
+ ## Related
69
+
70
+ - **ADR-068** — this drain + the marker + detector + nudge. **ADR-066 / P283** — the architect precedent this mirrors.
71
+ - **ADR-008** — JTBD directory structure (the marker is additive to its frontmatter contract).
72
+ - **ADR-009** — never-re-ask persistent-marker principle. **ADR-013 / ADR-044** — structured user interaction + decision-delegation taxonomy.
73
+ - `packages/jtbd/skills/review-jobs/SKILL.md` — the read-only alignment reviewer (distinct from this drain).
74
+ - `packages/jtbd/skills/update-guide/SKILL.md` — born-confirmed write site.
@@ -80,7 +80,7 @@ description: <one-line description>
80
80
  <bullet list of frustrations this product addresses>
81
81
  ```
82
82
 
83
- Use kebab-case for the directory name (e.g., `solo-developer`, `tech-lead`).
83
+ Use kebab-case for the directory name (e.g., `developer`, `tech-lead`).
84
84
 
85
85
  ### 4. Confirm personas with the user
86
86
 
@@ -89,6 +89,15 @@ Use AskUserQuestion to present the drafted personas and ask:
89
89
  - Any missing user segments?
90
90
  - Any constraints or pain points to add?
91
91
 
92
+ **Born-confirmed write (ADR-068).** Once the user confirms a persona via this AskUserQuestion pass, write the human-oversight marker into that persona's frontmatter — insert after the `description:` line:
93
+
94
+ ```yaml
95
+ human-oversight: confirmed
96
+ oversight-date: YYYY-MM-DD # today
97
+ ```
98
+
99
+ This is the born-confirmed gate: a persona authored through update-guide enters the world already human-oversighted (it does not appear in `/wr-jtbd:confirm-jobs-and-personas`' unoversighted set). Do NOT write the marker for a persona the user has not confirmed. The marker is orthogonal to status.
100
+
92
101
  ### 5. Draft jobs
93
102
 
94
103
  For each job (3-8 per persona), create a file at
@@ -135,6 +144,15 @@ Use AskUserQuestion to present the drafted jobs and ask:
135
144
  - Do the job statements ring true?
136
145
  - Any missing jobs or user flows?
137
146
 
147
+ **Born-confirmed write (ADR-068).** Once the user confirms a job via this AskUserQuestion pass, write the human-oversight marker into that job's frontmatter — insert after the `date-created:` line:
148
+
149
+ ```yaml
150
+ human-oversight: confirmed
151
+ oversight-date: YYYY-MM-DD # today
152
+ ```
153
+
154
+ A job authored through update-guide is born human-oversighted, so the `/wr-jtbd:confirm-jobs-and-personas` unoversighted set only ever shrinks. Do NOT write the marker for a job the user has not confirmed (drafted-but-unconfirmed jobs stay unmarked). The marker is orthogonal to `status:` — a `proposed` job can be `human-oversight: confirmed`.
155
+
138
156
  ### 7. Generate README.md index
139
157
 
140
158
  Write `docs/jtbd/README.md` with tables grouping jobs by persona and status: