@windyroad/jtbd 0.11.0-preview.521 → 0.12.0

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.11.0"
93
+ "version": "0.12.0"
94
94
  }
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # Generated by scripts/sync-shim-wrappers.sh from
3
+ # packages/shared/lib/shim-wrapper-template.sh. DO NOT EDIT individual
4
+ # shim files in packages/*/bin/wr-* directly; edit the template + run
5
+ # `npm run sync:shim-wrappers` to regenerate.
6
+ #
7
+ # Resolution (ADR-080):
8
+ # 1. If the wrapper's parent dir is semver-shaped, treat as installed-
9
+ # cache execution and resolve to the highest-version sibling's
10
+ # scripts/ entry below.
11
+ # 2. Otherwise (parent dir is e.g. `architect`), treat as source-
12
+ # monorepo execution and dispatch to own scripts/. The source-repo-
13
+ # guard `exec` is the anchor parsed by
14
+ # packages/retrospective/scripts/check-tarball-shipped-shims.sh.
15
+ # 3. If the cache parent contains zero semver-shaped siblings, exit
16
+ # 127 with a stderr message naming the cache parent (per SQ-080-2).
17
+ #
18
+ # @adr ADR-080 (highest-version-wins shim wrapper plugin scaffold)
19
+ # @adr ADR-049 (plugin-bundled scripts resolve via bin/ on $PATH — amended)
20
+ # @problem P343 (mid-session staleness window)
21
+
22
+ set -euo pipefail
23
+
24
+ SHIM_DIR="$(cd "$(dirname "$0")" && pwd)"
25
+ OWN_VERSION_DIR="$(dirname "$SHIM_DIR")"
26
+ OWN_VERSION_NAME="$(basename "$OWN_VERSION_DIR")"
27
+ CACHE_PARENT="$(dirname "$OWN_VERSION_DIR")"
28
+
29
+ SEMVER_RE='^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'
30
+
31
+ # Source-repo guard: own parent dir is NOT semver → dispatch to own scripts/.
32
+ if ! [[ "$OWN_VERSION_NAME" =~ $SEMVER_RE ]]; then
33
+ exec "$SHIM_DIR/../scripts/mark-oversight-confirmed.sh" "$@"
34
+ fi
35
+
36
+ # Cache execution: pick the highest-semver sibling under CACHE_PARENT.
37
+ HIGHEST=""
38
+ while IFS= read -r dir; do
39
+ name="$(basename "$dir")"
40
+ [[ "$name" =~ $SEMVER_RE ]] || continue
41
+ if [[ -z "$HIGHEST" ]] || [[ "$(printf '%s\n%s\n' "$HIGHEST" "$name" | sort -V | tail -1)" == "$name" ]]; then
42
+ HIGHEST="$name"
43
+ fi
44
+ done < <(find "$CACHE_PARENT" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
45
+
46
+ if [[ -z "$HIGHEST" ]]; then
47
+ printf 'wr-shim: no cached versions in %s\n' "$CACHE_PARENT" >&2
48
+ exit 127
49
+ fi
50
+
51
+ exec "$CACHE_PARENT/$HIGHEST/scripts/mark-oversight-confirmed.sh" "$@"
package/hooks/hooks.json CHANGED
@@ -7,7 +7,8 @@
7
7
  { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-eval.sh" }] }
8
8
  ],
9
9
  "PreToolUse": [
10
- { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-enforce-edit.sh" }] }
10
+ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-enforce-edit.sh" }] },
11
+ { "matcher": "Edit|Write", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-oversight-marker-discipline.sh" }] }
11
12
  ],
12
13
  "PostToolUse": [
13
14
  { "matcher": "Agent", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/jtbd-mark-reviewed.sh" }] },
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env bash
2
+ # jtbd-oversight-marker-discipline.sh — PreToolUse:Edit|Write hook
3
+ # (P348 / ADR-068 amendment 2026-06-02). JTBD-side sibling of
4
+ # architect-oversight-marker-discipline.sh. Denies Edit/Write operations
5
+ # that introduce `human-oversight: confirmed` into a docs/jtbd/ artefact's
6
+ # (persona.md or JTBD-NNN-*.md) frontmatter unless a session-scoped
7
+ # evidence marker proves the user has substance-confirmed THAT specific
8
+ # artefact via AskUserQuestion.
9
+ #
10
+ # P348 captured AFK iter subprocesses silently writing `human-oversight:
11
+ # confirmed` without any user confirmation event. ADR-068 mirrors ADR-066's
12
+ # marker contract on the JTBD/persona surface, and JTBD-006's audit-trail
13
+ # outcome + JTBD-201/202's auditability constraints depend on the marker
14
+ # being honest. This hook is the structural guard.
15
+ #
16
+ # Allow paths (exit 0 silently per ADR-045 Pattern 1):
17
+ # - tool_name not Edit|Write
18
+ # - file outside the project root (P004)
19
+ # - file not under docs/jtbd/
20
+ # - file not a `.md` (README, etc.)
21
+ # - basename README.md
22
+ # - the Edit/Write does NOT introduce `human-oversight: confirmed`
23
+ # - the session-scoped marker `/tmp/oversight-confirmed-<sha>-<sid>`
24
+ # exists for THIS specific JTBD/persona path under THIS session
25
+ #
26
+ # Deny path (PreToolUse deny JSON, hook exit 0):
27
+ # - all of: docs/jtbd/**/*.md, the change introduces
28
+ # `human-oversight: confirmed`, AND no matching marker for this
29
+ # artefact under this session
30
+ #
31
+ # Recovery (mechanical per ADR-013 Rule 1):
32
+ # The SKILL flow that hosts the substance-confirm AskUserQuestion calls
33
+ # `wr-jtbd-mark-oversight-confirmed <jtbd-or-persona-path>` immediately
34
+ # after the user's answer lands. AFK iter subprocesses MUST instead write
35
+ # `human-oversight: unconfirmed` (new enum value per ADR-068 amendment
36
+ # 2026-06-02), which the drain (/wr-jtbd:confirm-jobs-and-personas)
37
+ # later promotes interactively.
38
+ #
39
+ # @adr ADR-068 (JTBD/persona human-oversight marker)
40
+ # @adr ADR-066 (parent contract — architect surface)
41
+ # @adr ADR-049 (PATH shim resolution for invocation)
42
+ # @adr ADR-050 (multi-SID candidate enumeration for marker write)
43
+ # @adr ADR-045 (Pattern 1 silent-on-pass PreToolUse)
44
+ # @adr ADR-013 (Rule 6 fail-safe-defer in non-interactive contexts)
45
+ # @problem P348 (iter subprocesses set human-oversight: confirmed without user event)
46
+
47
+ set -uo pipefail
48
+
49
+ INPUT=$(cat)
50
+
51
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) || TOOL_NAME=""
52
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null) || FILE_PATH=""
53
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null) || SESSION_ID=""
54
+
55
+ case "$TOOL_NAME" in
56
+ Edit|Write) ;;
57
+ *) exit 0 ;;
58
+ esac
59
+
60
+ [ -n "$FILE_PATH" ] || exit 0
61
+ [ -n "$SESSION_ID" ] || exit 0
62
+
63
+ case "$FILE_PATH" in
64
+ /*)
65
+ case "$FILE_PATH" in
66
+ "$PWD"/*) ;;
67
+ *) exit 0 ;;
68
+ esac
69
+ ;;
70
+ esac
71
+
72
+ # Scope: docs/jtbd/**/*.md. Personas live at docs/jtbd/<persona>/persona.md;
73
+ # jobs live at docs/jtbd/<persona>/JTBD-NNN-*.md. Both are valid targets.
74
+ case "$FILE_PATH" in
75
+ */docs/jtbd/*.md|docs/jtbd/*.md) ;;
76
+ *) exit 0 ;;
77
+ esac
78
+
79
+ BASENAME=$(basename "$FILE_PATH")
80
+ case "$BASENAME" in
81
+ README.md) exit 0 ;;
82
+ esac
83
+
84
+ case "$TOOL_NAME" in
85
+ Write)
86
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty' 2>/dev/null) || NEW_CONTENT=""
87
+ OLD_CONTENT=""
88
+ if [ -f "$FILE_PATH" ]; then
89
+ OLD_CONTENT=$(cat "$FILE_PATH" 2>/dev/null) || OLD_CONTENT=""
90
+ fi
91
+ ;;
92
+ Edit)
93
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty' 2>/dev/null) || NEW_CONTENT=""
94
+ OLD_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.old_string // empty' 2>/dev/null) || OLD_CONTENT=""
95
+ ;;
96
+ esac
97
+
98
+ MARKER_RE='^[[:space:]]*human-oversight:[[:space:]]*confirmed[[:space:]]*$'
99
+
100
+ if ! echo "$NEW_CONTENT" | grep -qiE "$MARKER_RE"; then
101
+ exit 0
102
+ fi
103
+
104
+ if [ -n "$OLD_CONTENT" ] && echo "$OLD_CONTENT" | grep -qiE "$MARKER_RE"; then
105
+ exit 0
106
+ fi
107
+
108
+ abs_dir=$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd) || abs_dir=""
109
+ if [ -n "$abs_dir" ]; then
110
+ ABS_PATH="$abs_dir/$(basename "$FILE_PATH")"
111
+ else
112
+ ABS_PATH="$FILE_PATH"
113
+ fi
114
+
115
+ if command -v sha256sum >/dev/null 2>&1; then
116
+ PATH_HASH=$(printf '%s' "$ABS_PATH" | sha256sum | cut -d' ' -f1 | cut -c1-16)
117
+ elif command -v shasum >/dev/null 2>&1; then
118
+ PATH_HASH=$(printf '%s' "$ABS_PATH" | shasum -a 256 | cut -d' ' -f1 | cut -c1-16)
119
+ else
120
+ exit 0
121
+ fi
122
+
123
+ MARKER_DIR="${SESSION_MARKER_DIR:-/tmp}"
124
+ MARKER="$MARKER_DIR/oversight-confirmed-${PATH_HASH}-${SESSION_ID}"
125
+
126
+ if [ -f "$MARKER" ]; then
127
+ exit 0
128
+ fi
129
+
130
+ cat <<EOF
131
+ {
132
+ "hookSpecificOutput": {
133
+ "hookEventName": "PreToolUse",
134
+ "permissionDecision": "deny",
135
+ "permissionDecisionReason": "BLOCKED: '${BASENAME}' is about to receive 'human-oversight: confirmed' but no substance-confirm evidence marker exists for this JTBD/persona in this session (P348 / ADR-068). The marker '/tmp/oversight-confirmed-<sha>-<sid>' is written by 'wr-jtbd-mark-oversight-confirmed <artefact-path>' immediately after an AskUserQuestion lands the user's substance-confirm answer. If you are an AFK iter subprocess without AskUserQuestion access, write 'human-oversight: unconfirmed' instead — the drain (/wr-jtbd:confirm-jobs-and-personas) will promote it interactively. To recover this Edit/Write: surface the substance-confirm AskUserQuestion to the user, call wr-jtbd-mark-oversight-confirmed with this artefact's path, then retry."
136
+ }
137
+ }
138
+ EOF
139
+ exit 0
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P348 / ADR-068 amendment 2026-06-02: jtbd-oversight-marker-discipline.sh
4
+ # is the JTBD-side sibling of architect-oversight-marker-discipline.sh.
5
+ # Denies Edit/Write that introduces `human-oversight: confirmed` into a
6
+ # docs/jtbd/ artefact's frontmatter unless a session-scoped evidence marker
7
+ # proves the user has substance-confirmed THAT artefact.
8
+ #
9
+ # Behavioural — exercises the hook with constructed PreToolUse stdin JSON
10
+ # payloads under SESSION_MARKER_DIR sandboxing.
11
+
12
+ setup() {
13
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
14
+ HOOK="$REPO_ROOT/packages/jtbd/hooks/jtbd-oversight-marker-discipline.sh"
15
+ MARK_SCRIPT="$REPO_ROOT/packages/jtbd/scripts/mark-oversight-confirmed.sh"
16
+
17
+ DIR="$(mktemp -d)"
18
+ mkdir -p "$DIR/docs/jtbd/developer"
19
+ MARK_DIR="$(mktemp -d)"
20
+ export SESSION_MARKER_DIR="$MARK_DIR"
21
+
22
+ ORIG_DIR="$PWD"
23
+ cd "$DIR"
24
+
25
+ SID="jtbd-discipline-test-$$"
26
+ }
27
+
28
+ teardown() {
29
+ cd "$ORIG_DIR"
30
+ rm -rf "$DIR" "$MARK_DIR"
31
+ unset SESSION_MARKER_DIR
32
+ }
33
+
34
+ expected_marker() {
35
+ local f="$1"
36
+ local abs_dir abs path_hash
37
+ abs_dir="$(cd "$(dirname "$f")" && pwd)"
38
+ abs="$abs_dir/$(basename "$f")"
39
+ if command -v sha256sum >/dev/null 2>&1; then
40
+ path_hash=$(printf '%s' "$abs" | sha256sum | cut -d' ' -f1 | cut -c1-16)
41
+ else
42
+ path_hash=$(printf '%s' "$abs" | shasum -a 256 | cut -d' ' -f1 | cut -c1-16)
43
+ fi
44
+ printf '%s/oversight-confirmed-%s-%s\n' "$MARK_DIR" "$path_hash" "$SID"
45
+ }
46
+
47
+ mk_existing_artefact() {
48
+ local subpath="$1"; shift
49
+ local dir
50
+ dir="$(dirname "$DIR/docs/jtbd/$subpath")"
51
+ mkdir -p "$dir"
52
+ {
53
+ echo "---"
54
+ echo "status: \"proposed\""
55
+ echo "date: 2026-06-02"
56
+ for line in "$@"; do echo "$line"; done
57
+ echo "---"
58
+ echo "# $(basename "$subpath" .md)"
59
+ } > "$DIR/docs/jtbd/$subpath"
60
+ }
61
+
62
+ # ── Positive paths ───────────────────────────────────────────────────────
63
+
64
+ @test "Write introducing 'human-oversight: confirmed' to a JTBD with marker present is allowed" {
65
+ mk_existing_artefact "developer/JTBD-300-test.proposed.md"
66
+ art="$DIR/docs/jtbd/developer/JTBD-300-test.proposed.md"
67
+ : > "$(expected_marker "$art")"
68
+ new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# JTBD-300\n'
69
+ json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
70
+ '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
71
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
72
+ [ "$status" -eq 0 ]
73
+ [[ "$output" != *"BLOCKED"* ]]
74
+ }
75
+
76
+ @test "Edit to a persona introducing the marker with evidence is allowed" {
77
+ mk_existing_artefact "developer/persona.md"
78
+ art="$DIR/docs/jtbd/developer/persona.md"
79
+ : > "$(expected_marker "$art")"
80
+ old='date: 2026-06-02'
81
+ new=$'date: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02'
82
+ json=$(jq -nc --arg p "$art" --arg s "$SID" --arg o "$old" --arg n "$new" \
83
+ '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}')
84
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
85
+ [ "$status" -eq 0 ]
86
+ [[ "$output" != *"BLOCKED"* ]]
87
+ }
88
+
89
+ # ── Negative paths ───────────────────────────────────────────────────────
90
+
91
+ @test "Write introducing 'human-oversight: confirmed' to a JTBD WITHOUT marker is denied" {
92
+ mk_existing_artefact "developer/JTBD-310-orphan.proposed.md"
93
+ art="$DIR/docs/jtbd/developer/JTBD-310-orphan.proposed.md"
94
+ new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# JTBD-310\n'
95
+ json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
96
+ '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
97
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
98
+ [ "$status" -eq 0 ]
99
+ [[ "$output" == *'"permissionDecision": "deny"'* ]]
100
+ [[ "$output" == *"BLOCKED"* ]]
101
+ [[ "$output" == *"wr-jtbd-mark-oversight-confirmed"* ]]
102
+ [[ "$output" == *"/wr-jtbd:confirm-jobs-and-personas"* ]]
103
+ }
104
+
105
+ @test "Edit introducing the marker to persona WITHOUT marker is denied" {
106
+ mk_existing_artefact "tech-lead/persona.md"
107
+ art="$DIR/docs/jtbd/tech-lead/persona.md"
108
+ old='date: 2026-06-02'
109
+ new=$'date: 2026-06-02\nhuman-oversight: confirmed'
110
+ json=$(jq -nc --arg p "$art" --arg s "$SID" --arg o "$old" --arg n "$new" \
111
+ '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:$o,new_string:$n}}')
112
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
113
+ [[ "$output" == *'"permissionDecision": "deny"'* ]]
114
+ }
115
+
116
+ # ── AFK-unconfirmed-write ────────────────────────────────────────────────
117
+
118
+ @test "Write introducing 'human-oversight: unconfirmed' is allowed without marker (AFK path)" {
119
+ mk_existing_artefact "developer/JTBD-320-afk.proposed.md"
120
+ art="$DIR/docs/jtbd/developer/JTBD-320-afk.proposed.md"
121
+ new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: unconfirmed\n---\n\n# JTBD-320-afk\n'
122
+ json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
123
+ '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
124
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
125
+ [ "$status" -eq 0 ]
126
+ [[ "$output" != *"BLOCKED"* ]]
127
+ }
128
+
129
+ # ── Scope / non-fire paths ───────────────────────────────────────────────
130
+
131
+ @test "Edit to a non-JTBD path exits 0 silently even with the marker line" {
132
+ mkdir -p "$DIR/src"
133
+ echo "// stub" > "$DIR/src/foo.ts"
134
+ json=$(jq -nc --arg p "$DIR/src/foo.ts" --arg s "$SID" \
135
+ '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"// stub",new_string:"// human-oversight: confirmed"}}')
136
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
137
+ [ "$status" -eq 0 ]
138
+ [ -z "$output" ]
139
+ }
140
+
141
+ @test "Edit to docs/jtbd/README.md exits 0 silently" {
142
+ echo "# index" > "$DIR/docs/jtbd/README.md"
143
+ art="$DIR/docs/jtbd/README.md"
144
+ new=$'# index\nhuman-oversight: confirmed'
145
+ json=$(jq -nc --arg p "$art" --arg s "$SID" --arg n "$new" \
146
+ '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"# index",new_string:$n}}')
147
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
148
+ [ "$status" -eq 0 ]
149
+ [ -z "$output" ]
150
+ }
151
+
152
+ @test "Edit whose new content lacks the marker exits 0 silently" {
153
+ mk_existing_artefact "developer/JTBD-330-rename.proposed.md"
154
+ art="$DIR/docs/jtbd/developer/JTBD-330-rename.proposed.md"
155
+ json=$(jq -nc --arg p "$art" --arg s "$SID" \
156
+ '{tool_name:"Edit",session_id:$s,tool_input:{file_path:$p,old_string:"# JTBD-330-rename",new_string:"# JTBD-330 renamed"}}')
157
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
158
+ [ "$status" -eq 0 ]
159
+ [ -z "$output" ]
160
+ }
161
+
162
+ # ── End-to-end ───────────────────────────────────────────────────────────
163
+
164
+ @test "mark-oversight-confirmed.sh writes a marker that satisfies the JTBD hook" {
165
+ mk_existing_artefact "developer/JTBD-340-e2e.proposed.md"
166
+ art="$DIR/docs/jtbd/developer/JTBD-340-e2e.proposed.md"
167
+ : > "$MARK_DIR/jtbd-announced-$SID"
168
+ bash "$MARK_SCRIPT" "$art"
169
+ [ -f "$(expected_marker "$art")" ]
170
+ new_content=$'---\nstatus: "proposed"\ndate: 2026-06-02\nhuman-oversight: confirmed\noversight-date: 2026-06-02\n---\n\n# JTBD-340-e2e\n'
171
+ json=$(jq -nc --arg p "$art" --arg s "$SID" --arg c "$new_content" \
172
+ '{tool_name:"Write",session_id:$s,tool_input:{file_path:$p,content:$c}}')
173
+ run bash -c "echo '$(echo "$json" | sed "s/'/'\\\\''/g")' | bash '$HOOK'"
174
+ [ "$status" -eq 0 ]
175
+ [[ "$output" != *"BLOCKED"* ]]
176
+ }
177
+
178
+ @test "tool_name=Bash exits 0 silently regardless of file path" {
179
+ json=$(jq -nc --arg s "$SID" \
180
+ '{tool_name:"Bash",session_id:$s,tool_input:{command:"echo human-oversight: confirmed"}}')
181
+ run bash -c "echo '$json' | bash '$HOOK'"
182
+ [ "$status" -eq 0 ]
183
+ [ -z "$output" ]
184
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/jtbd",
3
- "version": "0.11.0-preview.521",
3
+ "version": "0.12.0",
4
4
  "description": "Jobs-to-be-done enforcement for UI changes",
5
5
  "bin": {
6
6
  "windyroad-jtbd": "./bin/install.mjs"
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # wr-jtbd — mark a JTBD/persona's human-oversight: confirmed marker write as
3
+ # user-substance-confirmed (P348 / ADR-068 amendment 2026-06-02).
4
+ #
5
+ # JTBD-side sibling of packages/architect/scripts/mark-oversight-confirmed.sh.
6
+ # Companion to jtbd-oversight-marker-discipline.sh (also P348). SKILLs invoke
7
+ # this script AFTER an AskUserQuestion lands the user's substance-confirm
8
+ # answer for a specific job or persona; the script writes the evidence marker
9
+ # that the hook reads to permit the subsequent Edit/Write that introduces
10
+ # `human-oversight: confirmed` into that artefact's frontmatter.
11
+ #
12
+ # Why the marker is required:
13
+ # ADR-068 mirrors ADR-066's `human-oversight: confirmed` marker contract on
14
+ # the JTBD/persona surface. P348 captured iter subprocesses silently
15
+ # writing the `confirmed` value without a user confirmation event,
16
+ # contradicting JTBD-006's audit-trail outcome and JTBD-201/202's
17
+ # auditability persona constraints. The hook enforces the boundary
18
+ # structurally; this script is the evidence-write side that legitimate
19
+ # substance-confirm flows use.
20
+ #
21
+ # AFK iter subprocesses with no AskUserQuestion access MUST write
22
+ # `human-oversight: unconfirmed` instead (the new enum value codified in
23
+ # ADR-068 amendment 2026-06-02), which the drain
24
+ # (/wr-jtbd:confirm-jobs-and-personas) later promotes.
25
+ #
26
+ # Marker convention:
27
+ # /tmp/oversight-confirmed-<sha256-of-path>-<session-id>
28
+ # Shared marker namespace with the architect hook — both hooks consume the
29
+ # same marker file shape. Written under EVERY recent candidate session SID
30
+ # per ADR-050 Option C.
31
+ #
32
+ # Usage:
33
+ # wr-jtbd-mark-oversight-confirmed <artefact-path>
34
+ # <artefact-path> — the JTBD or persona file path the user just
35
+ # substance-confirmed.
36
+ #
37
+ # Exit codes:
38
+ # 0 — marker(s) written for at least one candidate SID, OR no candidate
39
+ # SID was discoverable (cold-path: no announce markers yet).
40
+ # 2 — bad argument (missing or empty artefact-path).
41
+ #
42
+ # @adr ADR-068 (JTBD/persona human-oversight marker)
43
+ # @adr ADR-049 (PATH shim grammar)
44
+ # @adr ADR-050 (multi-SID candidate enumeration)
45
+ # @adr ADR-013 (Rule 6 fail-safe-defer in non-interactive contexts)
46
+ # @problem P348 (iter subprocesses set human-oversight: confirmed without user event)
47
+
48
+ set -uo pipefail
49
+
50
+ ARTEFACT_PATH="${1:-}"
51
+
52
+ if [ -z "$ARTEFACT_PATH" ]; then
53
+ echo "wr-jtbd-mark-oversight-confirmed: missing <artefact-path>" >&2
54
+ exit 2
55
+ fi
56
+
57
+ # Normalize to absolute path so the hash is stable regardless of CWD.
58
+ abs_dir="$(cd "$(dirname "$ARTEFACT_PATH")" 2>/dev/null && pwd)" || abs_dir=""
59
+ if [ -n "$abs_dir" ]; then
60
+ ABS_PATH="$abs_dir/$(basename "$ARTEFACT_PATH")"
61
+ else
62
+ ABS_PATH="$ARTEFACT_PATH"
63
+ fi
64
+
65
+ # Path hash — sha256, first 16 hex chars. Portable across macOS (shasum) and
66
+ # Linux (sha256sum / shasum).
67
+ if command -v sha256sum >/dev/null 2>&1; then
68
+ PATH_HASH=$(printf '%s' "$ABS_PATH" | sha256sum | cut -d' ' -f1 | cut -c1-16)
69
+ elif command -v shasum >/dev/null 2>&1; then
70
+ PATH_HASH=$(printf '%s' "$ABS_PATH" | shasum -a 256 | cut -d' ' -f1 | cut -c1-16)
71
+ else
72
+ echo "wr-jtbd-mark-oversight-confirmed: no sha256 tool available" >&2
73
+ exit 2
74
+ fi
75
+
76
+ MARKER_DIR="${SESSION_MARKER_DIR:-/tmp}"
77
+ WINDOW_MINS="${SESSION_CANDIDATE_WINDOW_MINS:-1440}"
78
+
79
+ # Candidate SID enumeration — recent announce markers across all systems
80
+ # within the mtime window. Inlined for plugin self-containment (no cross-
81
+ # plugin lib source — jtbd must not depend on architect-internal helpers
82
+ # per ADR-002 plugin packaging).
83
+ candidates=$(
84
+ {
85
+ if [ -n "${CLAUDE_SESSION_ID:-}" ]; then
86
+ echo "$CLAUDE_SESSION_ID"
87
+ fi
88
+ find "$MARKER_DIR" -maxdepth 1 -name '*-announced-*' -mmin "-${WINDOW_MINS}" 2>/dev/null \
89
+ | sed 's|.*/||; s/.*-announced-//'
90
+ } | awk 'NF && !seen[$0]++'
91
+ )
92
+
93
+ [ -n "$candidates" ] || exit 0
94
+
95
+ while IFS= read -r sid; do
96
+ [ -n "$sid" ] || continue
97
+ : > "$MARKER_DIR/oversight-confirmed-${PATH_HASH}-${sid}"
98
+ done <<< "$candidates"
99
+
100
+ exit 0
@@ -89,14 +89,18 @@ 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:
92
+ **Born-confirmed write (ADR-068 — structurally gated by P348 amendment 2026-06-02).** Once the user confirms a persona via this AskUserQuestion pass, IMMEDIATELY call the marker-evidence helper THEN insert the two lines into that persona's frontmatter — after the `description:` line:
93
+
94
+ ```bash
95
+ wr-jtbd-mark-oversight-confirmed docs/jtbd/<persona-name>/persona.md
96
+ ```
93
97
 
94
98
  ```yaml
95
99
  human-oversight: confirmed
96
100
  oversight-date: YYYY-MM-DD # today
97
101
  ```
98
102
 
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.
103
+ The `wr-jtbd-mark-oversight-confirmed` call writes the session-scoped evidence marker (`/tmp/oversight-confirmed-<sha>-<sid>`) that the `jtbd-oversight-marker-discipline.sh` PreToolUse hook reads to authorise the subsequent Edit/Write — without the helper call, the hook will DENY the marker write. This is the load-bearing born-confirmed gate: a persona authored through update-guide enters the world human-oversighted ONLY because the helper above paired the user's substance-confirm answer to the marker write. Do NOT write the marker for a persona the user has not confirmed. AFK iter subprocesses spawned via `claude -p` have no `AskUserQuestion` access; they MUST write `human-oversight: unconfirmed` (the AFK fallback enum value codified in ADR-068 amendment 2026-06-02), which the drain (`/wr-jtbd:confirm-jobs-and-personas`) later promotes interactively. The marker is orthogonal to status.
100
104
 
101
105
  ### 5. Draft jobs
102
106
 
@@ -144,14 +148,18 @@ Use AskUserQuestion to present the drafted jobs and ask:
144
148
  - Do the job statements ring true?
145
149
  - Any missing jobs or user flows?
146
150
 
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:
151
+ **Born-confirmed write (ADR-068 — structurally gated by P348 amendment 2026-06-02).** Once the user confirms a job via this AskUserQuestion pass, IMMEDIATELY call the marker-evidence helper THEN insert the two lines into that job's frontmatter — after the `date-created:` line:
152
+
153
+ ```bash
154
+ wr-jtbd-mark-oversight-confirmed docs/jtbd/<persona-name>/JTBD-NNN-<kebab-title>.proposed.md
155
+ ```
148
156
 
149
157
  ```yaml
150
158
  human-oversight: confirmed
151
159
  oversight-date: YYYY-MM-DD # today
152
160
  ```
153
161
 
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`.
162
+ Without the `wr-jtbd-mark-oversight-confirmed` call, the `jtbd-oversight-marker-discipline.sh` PreToolUse hook will DENY the marker write. A job authored through update-guide is born human-oversighted ONLY because the helper above paired the user's substance-confirm answer to the marker write. Do NOT write the marker for a job the user has not confirmed. AFK iter subprocesses MUST write `human-oversight: unconfirmed` instead (the AFK fallback enum value codified in ADR-068 amendment 2026-06-02); the drain (`/wr-jtbd:confirm-jobs-and-personas`) later promotes it interactively. The marker is orthogonal to `status:`.
155
163
 
156
164
  ### 7. Generate README.md index
157
165