Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions hooks/ways/issues-pull.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
#
# SessionStart and UserPromptSubmit accept plain stdout as context.
# PostToolUse needs hookSpecificOutput.additionalContext.
#
# PostToolUse also fires inside subagents, whose hook input carries the
# parent's session_id plus an agent_id. The whisper is the head agent's: a
# subagent that pulled would rotate the snapshot and consume the delta, so
# the hook exits before touching the store when agent_id is set.

MODE="${1:-prompt}"
GH_TASKS="$(dirname "$0")/softwaredev/delivery/issues/gh-tasks"
Expand All @@ -21,6 +26,8 @@ command -v gh >/dev/null 2>&1 && command -v jq >/dev/null 2>&1 || exit 0
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
[[ -n "$SESSION_ID" ]] || exit 0
AGENT_ID=$(echo "$INPUT" | jq -r '.agent_id // empty')
[[ -z "$AGENT_ID" ]] || exit 0
SOURCE=$(echo "$INPUT" | jq -r '.source // empty')
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
[[ -n "$CWD" && -d "$CWD" ]] && cd "$CWD"
Expand Down
2 changes: 1 addition & 1 deletion hooks/ways/softwaredev/delivery/issues/issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ refire: 0.15
<!-- epistemic: convention -->
# Issues as Tasks

Issues carrying the `tasklist` label are mirrored into this session's task list (ADR-180). GitHub is the shared truth. The task store is a cache. Hooks pull on session start, at a prompt when the snapshot is stale, and after any `gh issue` command run here. `/issues` is the on-demand path.
Issues carrying the `tasklist` label are mirrored into this session's task list (ADR-180). GitHub is the shared truth. The task store is a cache. Hooks pull on session start, at a prompt when the snapshot is stale, and after any `gh issue` command run here. The hooks act for the head agent only; a subagent's `gh issue` command leaves the store and the whisper untouched. `/issues` is the on-demand path.

## What a mirrored task looks like

Expand Down
21 changes: 18 additions & 3 deletions tests/gh-tasks-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
# in_progress, remote close and reopen move status, body change replaces
# description, local description edit survives an unchanged body); id
# conflict left untouched; unrecognized layout writes nothing; whisper
# deltas; link; the TaskCreated guard; attach on a resume (fresh team by age
# and cwd, carry-forward of open tasks, claimed and stale teams skipped);
# refusal without a session id.
# deltas; the hook skips subagent input; link; the TaskCreated guard;
# attach on a resume (fresh team by age and cwd, carry-forward of open
# tasks, claimed and stale teams skipped); refusal without a session id.

set -euo pipefail

Expand Down Expand Up @@ -205,6 +205,21 @@ fixture "$GH_FIXTURE" "[$(issue 12 'Add widget v2' CLOSED 'Body twelve'), $(issu
assert_has "full list shows the new issue" "$("$GH_TASKS" whisper --full)" "#31 Fresh"
assert_empty "no second report on the next prompt" "$("$GH_TASKS" whisper)"

# ── 9d2. the hook leaves the store alone for a subagent ────────
HOOK="$REPO_ROOT/hooks/ways/issues-pull.sh"
HOOK_CWD="$TMP/hookrepo"; git init -q "$HOOK_CWD"
hook_input() { jq -n --arg s "$CLAUDE_CODE_SESSION_ID" --arg c "$HOOK_CWD" --arg a "${1:-}" \
'{session_id:$s, cwd:$c, tool_name:"Bash"} + (if $a == "" then {} else {agent_id:$a} end)'; }
fixture "$GH_FIXTURE" "[$(issue 12 'Add widget v2' CLOSED 'Body twelve'), $(issue 14 'New one' OPEN 'x'), $(issue 30 'Odd' OPEN 'x'), $(issue 31 'Fresh' OPEN 'x'), $(issue 32 'Later' OPEN 'x')]"
"$GH_TASKS" pull 2>/dev/null
# #32 is pulled and unread; #33 is on GitHub and not yet pulled.
fixture "$GH_FIXTURE" "[$(issue 12 'Add widget v2' CLOSED 'Body twelve'), $(issue 14 'New one' OPEN 'x'), $(issue 30 'Odd' OPEN 'x'), $(issue 31 'Fresh' OPEN 'x'), $(issue 32 'Later' OPEN 'x'), $(issue 33 'Head only' OPEN 'x')]"
assert_empty "subagent input gets no whisper" "$(hook_input a512f45a8aee73bde | "$HOOK" post-gh)"
assert_eq "subagent input does not pull" "$([[ -e "$STORE/gh-33.json" ]] && echo present || echo absent)" "absent"
assert_eq "subagent input leaves the previous snapshot" "$([[ -e "$STATE/snapshot.prev.json" ]] && echo present || echo absent)" "present"
assert_has "the unread delta is still the head agent's" "$("$GH_TASKS" whisper)" 'opened #32 "Later"'
assert_has "head input still gets the PostToolUse whisper" "$(hook_input | "$HOOK" post-gh | jq -r .hookSpecificOutput.additionalContext)" 'opened #33 "Head only"'

# ── 9e. blocks edges the session added survive ─────────────────
jq '.blocks += ["gh-30"]' "$STORE/gh-14.json" >"$TMP/x" && mv "$TMP/x" "$STORE/gh-14.json"
"$GH_TASKS" pull 2>/dev/null
Expand Down
Loading