aboutsummaryrefslogtreecommitdiff
path: root/scripts/tests/inbox-boundary-check-hook.bats
blob: 58668a5be05dc59be0a4d0d234b8f96ba05b4fe5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env bats
# hooks/inbox-boundary-check.sh — Stop hook that soft-nudges the agent to
# process pending inbox/ handoffs before yielding. Blocks the stop ONCE (emits
# a block decision + reason) when inbox-status reports pending items; on the
# harness re-entry (stop_hook_active: true) it steps aside so an unprocessable
# item or a mid-task pause never wedges. Self-skips where there's no inbox/ or
# no inbox-status. The real inbox-status is copied into the test project so the
# hook runs against real code, not a stub.

setup() {
    REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../.." && pwd)"
    SCRIPT="$REPO_ROOT/hooks/inbox-boundary-check.sh"
    INBOX_STATUS="$REPO_ROOT/claude-templates/.ai/scripts/inbox-status"
    TMPDIR_T="$(mktemp -d)"
    CWD="$TMPDIR_T/proj"
    mkdir -p "$CWD/.ai/scripts"
    cp "$INBOX_STATUS" "$CWD/.ai/scripts/inbox-status"
    chmod +x "$CWD/.ai/scripts/inbox-status"
}

teardown() {
    rm -rf "$TMPDIR_T"
}

# Feed Stop-hook JSON on stdin. $1 = stop_hook_active (default false).
run_hook() {
    local active="${1:-false}"
    printf '{"cwd":"%s","hook_event_name":"Stop","stop_hook_active":%s}' \
        "$CWD" "$active" | bash "$SCRIPT"
}

@test "pending handoffs block the stop with a count in the reason" {
    mkdir -p "$CWD/inbox"
    printf 'x\n' >"$CWD/inbox/2026-07-19-from-home-thing.org"
    printf 'y\n' >"$CWD/inbox/2026-07-19-from-work-other.org"
    run run_hook
    [ "$status" -eq 0 ]
    # Valid JSON with a block decision.
    echo "$output" | jq -e '.decision == "block"'
    echo "$output" | jq -e '.reason | test("2 pending")'
    echo "$output" | jq -e '.reason | test("inbox.org")'
}

@test "a clean inbox (only artifacts) emits nothing" {
    mkdir -p "$CWD/inbox"
    touch "$CWD/inbox/.gitkeep"
    printf 'done\n' >"$CWD/inbox/PROCESSED-2026-07-19-old.org"
    run run_hook
    [ "$status" -eq 0 ]
    [ -z "$output" ]
}

@test "soft-nudge: stop_hook_active=true steps aside even with pending items" {
    mkdir -p "$CWD/inbox"
    printf 'x\n' >"$CWD/inbox/2026-07-19-from-home-thing.org"
    run run_hook true
    [ "$status" -eq 0 ]
    [ -z "$output" ]
}

@test "no inbox/ directory is a silent no-op" {
    run run_hook
    [ "$status" -eq 0 ]
    [ -z "$output" ]
}

@test "inbox-status absent degrades to a silent no-op" {
    mkdir -p "$CWD/inbox"
    printf 'x\n' >"$CWD/inbox/2026-07-19-from-home-thing.org"
    rm -f "$CWD/.ai/scripts/inbox-status"
    # Also ensure nothing named inbox-status is on PATH for this run.
    run env PATH="/usr/bin:/bin" bash -c '
        printf "{\"cwd\":\"'"$CWD"'\",\"hook_event_name\":\"Stop\"}" | bash "'"$SCRIPT"'"'
    [ "$status" -eq 0 ]
    [ -z "$output" ]
}

@test "the emitted reason names the project for context" {
    mkdir -p "$CWD/inbox"
    printf 'x\n' >"$CWD/inbox/2026-07-19-from-home-thing.org"
    run run_hook
    echo "$output" | jq -e '.reason | test("proj")'
}