@windyroad/jtbd 0.6.0-preview.161 → 0.7.0-preview.168

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "wr-jtbd",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Jobs-to-be-done enforcement for Claude Code"
5
5
  }
@@ -1,14 +1,26 @@
1
1
  #!/bin/bash
2
- # JTBD - UserPromptSubmit hook
2
+ # JTBD - UserPromptSubmit hook (P095 / ADR-038)
3
3
  # Detects the docs/jtbd/ directory in the project and injects the
4
4
  # delegation instruction. Canonical layout is docs/jtbd/ only
5
- # (ADR-008, Option 3 chosen 2026-04-20). Legacy docs/JOBS_TO_BE_DONE.md
6
- # is NOT consulted at runtime — projects still on that layout must run
7
- # /wr-jtbd:update-guide to migrate. The update-guide skill is the sole
8
- # component permitted to read the legacy file (for one-shot migration).
5
+ # (ADR-008, Option 3 chosen 2026-04-20).
6
+ #
7
+ # Progressive disclosure (ADR-038): full MANDATORY block on first
8
+ # prompt; terse reminder on subsequent prompts in the same session.
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ # shellcheck source=lib/session-marker.sh
12
+ source "$SCRIPT_DIR/lib/session-marker.sh"
13
+
14
+ INPUT=$(cat)
15
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || echo "")
9
16
 
10
17
  if [ -f "docs/jtbd/README.md" ]; then
11
- cat <<'HOOK_OUTPUT'
18
+ if has_announced "jtbd" "$SESSION_ID"; then
19
+ cat <<'HOOK_OUTPUT'
20
+ MANDATORY JTBD gate active (docs/jtbd/ present). Delegate to wr-jtbd:agent before editing project files. See turn-1 instructions for full scope and exclusions.
21
+ HOOK_OUTPUT
22
+ else
23
+ cat <<'HOOK_OUTPUT'
12
24
  INSTRUCTION: MANDATORY JTBD CHECK. YOU MUST FOLLOW THIS.
13
25
  DETECTED: docs/jtbd/ exists in this project.
14
26
 
@@ -30,6 +42,8 @@ plan files, docs/problems/ (problem tickets), docs/BRIEFING.md,
30
42
  RISK-POLICY.md, .risk-reports/, docs/jtbd/,
31
43
  docs/PRODUCT_DISCOVERY.md, docs/VOICE-AND-TONE.md, docs/STYLE-GUIDE.md.
32
44
  HOOK_OUTPUT
45
+ mark_announced "jtbd" "$SESSION_ID"
46
+ fi
33
47
  else
34
48
  cat <<'HOOK_OUTPUT'
35
49
  NOTE: This project has no docs/jtbd/ directory. Edits to project files
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # Shared session-announcement marker helpers (P095 / ADR-038).
3
+ #
4
+ # Used by UserPromptSubmit hooks to gate verbose MANDATORY instruction
5
+ # prose behind a once-per-session check. First prompt of a session emits
6
+ # the full block AND calls mark_announced; subsequent prompts see the
7
+ # marker via has_announced and emit only a terse reminder.
8
+ #
9
+ # Why no TTL or drift check (unlike review-gate.sh): announcement is
10
+ # bookkeeping for prose verbosity, not enforcement. PreToolUse gates
11
+ # still block unauthorised edits regardless of announcement state; the
12
+ # delegated agent re-reads policy when it runs. Extending the marker's
13
+ # lifetime across policy changes mid-session is safe — the gate, not
14
+ # the announcement, is load-bearing.
15
+ #
16
+ # Marker path convention: /tmp/${SYSTEM}-announced-${SESSION_ID}
17
+ # (mirrors the /tmp/${SYSTEM}-reviewed-${SESSION_ID} convention from
18
+ # style-guide/voice-tone/risk-scorer review-gate.sh; the -announced-
19
+ # suffix distinguishes announcement markers from clearance markers).
20
+ #
21
+ # Empty SESSION_ID fallback: has_announced returns 1 (not announced,
22
+ # full block emits) and mark_announced is a no-op (no file written).
23
+ # This covers manual hook invocation, test harnesses, and any rare
24
+ # case where Claude Code does not pass a session_id on stdin.
25
+
26
+ # Returns 0 if the hook for SYSTEM has already announced in SESSION_ID,
27
+ # 1 otherwise. Empty SESSION_ID => returns 1 (never announced).
28
+ #
29
+ # Usage: has_announced "architect" "$SESSION_ID"
30
+ has_announced() {
31
+ local SYSTEM="$1"
32
+ local SESSION_ID="$2"
33
+ [ -n "$SESSION_ID" ] || return 1
34
+ [ -f "/tmp/${SYSTEM}-announced-${SESSION_ID}" ]
35
+ }
36
+
37
+ # Writes the announcement marker for SYSTEM in SESSION_ID. Empty
38
+ # SESSION_ID => no-op. Safe to call more than once per session.
39
+ #
40
+ # Usage: mark_announced "architect" "$SESSION_ID"
41
+ mark_announced() {
42
+ local SYSTEM="$1"
43
+ local SESSION_ID="$2"
44
+ [ -n "$SESSION_ID" ] || return 0
45
+ : > "/tmp/${SYSTEM}-announced-${SESSION_ID}"
46
+ }
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # P095 / ADR-038: jtbd-eval.sh UserPromptSubmit hook must emit the full
4
+ # MANDATORY block only on the first prompt of a session; subsequent
5
+ # prompts emit a terse reminder.
6
+
7
+ setup() {
8
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
9
+ HOOK="$REPO_ROOT/packages/jtbd/hooks/jtbd-eval.sh"
10
+
11
+ WORKDIR="$(mktemp -d)"
12
+ mkdir -p "$WORKDIR/docs/jtbd"
13
+ : > "$WORKDIR/docs/jtbd/README.md"
14
+
15
+ SID="jtbd-eval-test-$$-$RANDOM"
16
+ }
17
+
18
+ teardown() {
19
+ rm -f "/tmp/jtbd-announced-${SID}"
20
+ rm -f "/tmp/jtbd-announced-${SID}-alt"
21
+ rm -rf "$WORKDIR"
22
+ }
23
+
24
+ run_hook() {
25
+ local sid="$1"
26
+ (cd "$WORKDIR" && echo "{\"session_id\":\"$sid\"}" | bash "$HOOK")
27
+ }
28
+
29
+ @test "jtbd-eval: first invocation emits the full MANDATORY block" {
30
+ run run_hook "$SID"
31
+ [ "$status" -eq 0 ]
32
+ [ "${#output}" -gt 400 ]
33
+ [[ "$output" == *"MANDATORY JTBD CHECK"* ]]
34
+ [[ "$output" == *"wr-jtbd:agent"* ]]
35
+ [[ "$output" == *"SCOPE:"* ]]
36
+ }
37
+
38
+ @test "jtbd-eval: first invocation writes the announcement marker" {
39
+ run run_hook "$SID"
40
+ [ -f "/tmp/jtbd-announced-${SID}" ]
41
+ }
42
+
43
+ @test "jtbd-eval: second invocation emits only a terse reminder" {
44
+ run_hook "$SID" >/dev/null
45
+ run run_hook "$SID"
46
+ [ "$status" -eq 0 ]
47
+ [ "${#output}" -lt 250 ]
48
+ [[ "$output" == *"jtbd"* ]] || [[ "$output" == *"JTBD"* ]]
49
+ [[ "$output" == *"wr-jtbd:agent"* ]]
50
+ [[ "$output" != *"Does NOT apply to"* ]]
51
+ }
52
+
53
+ @test "jtbd-eval: terse reminder preserves the MANDATORY signal word" {
54
+ run_hook "$SID" >/dev/null
55
+ run run_hook "$SID"
56
+ [[ "$output" == *"MANDATORY"* ]] || [[ "$output" == *"REQUIRED"* ]] || [[ "$output" == *"NON-OPTIONAL"* ]]
57
+ }
58
+
59
+ @test "jtbd-eval: terse reminder names the trigger artifact" {
60
+ run_hook "$SID" >/dev/null
61
+ run run_hook "$SID"
62
+ [[ "$output" == *"docs/jtbd"* ]]
63
+ }
64
+
65
+ @test "jtbd-eval: different session_id re-emits the full block" {
66
+ run_hook "$SID" >/dev/null
67
+ local SID2="${SID}-alt"
68
+ run run_hook "$SID2"
69
+ [ "${#output}" -gt 400 ]
70
+ [[ "$output" == *"MANDATORY JTBD CHECK"* ]]
71
+ rm -f "/tmp/jtbd-announced-${SID2}"
72
+ }
73
+
74
+ @test "jtbd-eval: empty session_id emits the full block and writes no marker" {
75
+ run run_hook ""
76
+ [ "${#output}" -gt 400 ]
77
+ [[ "$output" == *"MANDATORY JTBD CHECK"* ]]
78
+ [ ! -f "/tmp/jtbd-announced-" ]
79
+ }
80
+
81
+ @test "jtbd-eval: absent docs/jtbd/ emits the no-docs NOTE (unchanged by this ADR)" {
82
+ local NO_JTBD="$(mktemp -d)"
83
+ run bash -c "cd '$NO_JTBD' && echo '{\"session_id\":\"$SID\"}' | bash '$HOOK'"
84
+ [[ "$output" == *"no docs/jtbd/ directory"* ]]
85
+ [[ "$output" != *"MANDATORY JTBD CHECK"* ]]
86
+ rm -rf "$NO_JTBD"
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/jtbd",
3
- "version": "0.6.0-preview.161",
3
+ "version": "0.7.0-preview.168",
4
4
  "description": "Jobs-to-be-done enforcement for UI changes",
5
5
  "bin": {
6
6
  "windyroad-jtbd": "./bin/install.mjs"