1	# shellcheck shell=sh
2	# sptc hook common helpers — sourced by each hook wrapper.
3	# DRAFT (pending throwaway-CC validation: Windows-shell, env-file timing, whoami-in-hook).
4	# spt-core is harness-agnostic; this is the CC adapter glue that maps CC hook input -> `spt api`.
5
6	ADAPTER=claude-spt
7
8	# CC spills additionalContext over ~10,000 chars to a file (dropping it from the inline context the
9	# agent sees). We pre-empt below this with margin for the marker + CC's own framing. Override-able
10	# for tests. (ADR-0002 Open #2.)
11	SPTC_CTX_CAP="${SPTC_CTX_CAP:-9000}"
12
13	# Resolve the spt binary: PATH first (post-bootstrap), then known install locations.
14	spt_bin() {
15	  if command -v spt >/dev/null 2>&1; then printf 'spt'; return 0; fi
16	  for p in \
17	    "$HOME/.local/bin/spt" \
18	    "$LOCALAPPDATA/spt-core/bin/spt.exe" \
19	    "$HOME/AppData/Local/spt-core/bin/spt.exe"; do
20	    [ -x "$p" ] && { printf '%s' "$p"; return 0; }
21	  done
22	  printf 'spt'  # last resort; caller tolerates failure
23	}
24
25	# Extract a top-level string field from a flat JSON object on stdin payload ($1=json, $2=key).
26	# CC hook input is a flat object for the fields we need (session_id, source, prompt, agent_id).
27	json_str() {
28	  printf '%s' "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -n1
29	}
30
31	# Decide the SessionStart registration verb for this spawn ($1 = CC hook `source`; reads env
32	# $SPT_ENDPOINT_ID). Pure — no spt/CC. [impl->REQ-DIST-SHORTCUT-BASENAME]
33	#   boundary — a /clear or /compact within a live session (rebind the perch to the new session id)
34	#   bind     — spt-hosted: `spt endpoint run` spawned us via [session.self] + injected the endpoint
35	#              id into $SPT_ENDPOINT_ID; the perch already exists, self-register post-spawn
36	#   seed     — harness-hosted: user-launched CC; record an ephemeral seed for /sptc:ready|live
37	sptc_register_verb() {
38	  case "$1" in
39	    clear|compact) printf boundary ;;
40	    *) if [ -n "${SPT_ENDPOINT_ID:-}" ]; then printf bind; else printf seed; fi ;;
41	  esac
42	}
43
44	# Resolve this session's perch id via spt whoami (off $OWL_SESSION_ID / $SPT_AGENT_ID).
45	# Empty => no perch yet (session never readied) => caller no-ops.
46	sptc_self_id() {
47	  _sid="$1"; _spt="$(spt_bin)"
48	  OWL_SESSION_ID="${OWL_SESSION_ID:-$_sid}" "$_spt" whoami 2>/dev/null | head -n1
49	}
50
51	# Decode an spt envelope body to plain text: literal `<br>` -> newline, then HTML entities
52	# (< > " then & LAST, to avoid double-decoding). Exactly the live-agent
53	# body-parsing rule (spt-proto::event, ADR-0001 grammar).
54	sptc_unescape() {
55	  printf '%s' "$1" | sed \
56	    -e 's/
/\
57	/g' \
58	    -e 's/</</g' -e 's/>/>/g' -e 's/"/"/g' -e 's/&/\&/g'
59	}
60
61	# Render an `api poll` drain ($1) for CC. Canonical format: every message is a
62	# self-delimiting `<EVENT type="msg" from="<sender>">body</EVENT>` envelope (spt-proto::event) —
63	# the same grammar the live listener emits. Multi-message drains split cleanly on `</EVENT>`.
64	# Sender is preserved as `from=` (reply-correlation). NOTE: targets canonical <EVENT>; the current
65	# 0.6.0 binary still emits a `__REPLY_TO__` relic at the poll surface until the REQ-MSG-ENVELOPE
66	# refactor lands — finalize/validate against poll only post-refactor. [unit->REQ-UPS-INJECTION]
67	render_frames() {
68	  _in="$1"
69	  [ -z "$_in" ] && return 0
70	  # Normalise to one <EVENT>…</EVENT> per line (body newlines are
-escaped, so each envelope
71	  # is single-line), then parse each.
72	  printf '%s' "$_in" | sed 's#</EVENT>#</EVENT>\
73	#g' | while IFS= read -r _ev; do
74	    case "$_ev" in
75	      *"<EVENT"*"</EVENT>"*) ;;
76	      *) continue ;;
77	    esac
78	    _sender=$(printf '%s' "$_ev" | sed -n 's/.*<EVENT[^>]* from="\([^"]*\)".*/\1/p')
79	    _raw=$(printf '%s' "$_ev" | sed -n 's#.*<EVENT[^>]*>\(.*\)</EVENT>.*#\1#p')
80	    _body=$(sptc_unescape "$_raw")
81	    if [ -n "$_sender" ]; then
82	      printf '<sptc_messages from="%s">\n%s\n</sptc_messages>\n' "$_sender" "$_body"
83	    else
84	      printf '<sptc_messages>\n%s\n</sptc_messages>\n' "$_body"
85	    fi
86	  done
87	}
88
89	# Extract the skill name from a `/sptc:<skill>` slash-command prompt ($1=prompt). UPS fires on a
90	# slash-command with the token intact (ADR-0002 validation 2026-06-15: `prompt:"/sptc:send doyle"`),
91	# so the wrapper detects the command here. Leading-only (after optional whitespace) to avoid firing
92	# on prose that merely mentions `/sptc:x` mid-sentence. Empty => not a sptc slash-command.
93	# [unit->REQ-UPS-INJECTION]
94	sptc_skill_key() {
95	  printf '%s' "$1" | sed -n 's#^[[:space:]]*/sptc:\([a-z][a-z0-9-]*\).*#\1#p' | head -n1
96	}
97
98	# Inject a skill's operative instructions ($1=skill name) as additionalContext. Resolves the
99	# file-backed/inline body from the adapter `[strings.skills].<skill>` via `spt adapter get-string`
100	# (F-003: pointer values resolve to file contents at read time). No-op (silent) if spt is absent,
101	# the adapter is unregistered, or the key is unset — the thin SKILL.md skeleton still loaded by CC
102	# is the floor. The injected body is what the agent follows for this `/sptc:<skill>` invocation.
103	# [impl->REQ-UPS-INJECTION]
104	sptc_inject_skill() {
105	  _skill="$1"; [ -z "$_skill" ] && return 0
106	  _spt="$(spt_bin)"
107	  _body=$("$_spt" adapter get-string "$ADAPTER" "skills.$_skill" 2>/dev/null) || return 0
108	  [ -z "$_body" ] && return 0
109	  printf '<sptc_skill name="%s">\n%s\n</sptc_skill>\n' "$_skill" "$_body"
110	}
111
112	# Emit additionalContext under CC's ~10k spill threshold ($1=text, $2=cap, $3=spill_path). Under the
113	# cap -> emit as-is. Over -> spill the FULL text to a file the agent can Read and emit ONLY a concise
114	# pointer marker (never a partial inline — a head-cut would split a <sptc_messages>/<EVENT> block and
115	# lose a message silently). The agent reads the spill file to see everything; nothing is dropped. CC
116	# would otherwise spill oversized additionalContext itself, evicting it from the inline context.
117	# [impl->REQ-UPS-INJECTION]
118	sptc_cap_output() {
119	  _text="$1"; _cap="$2"; _spill="$3"
120	  [ -z "$_text" ] && return 0
121	  _len=$(printf '%s' "$_text" | wc -c | tr -d ' ')
122	  if [ "$_len" -le "$_cap" ]; then
123	    printf '%s' "$_text"
124	    return 0
125	  fi
126	  printf '%s' "$_text" > "$_spill" 2>/dev/null || _spill="(spill failed; content too large to inline)"
127	  printf '<sptc_overflow bytes="%s" cap="%s" spilled_to="%s">\nDelivery exceeded CC'\''s additionalContext cap. The full content (skill instructions and/or %s message bytes) was written to the file above — read it now to see everything; it is NOT inlined here to avoid silently dropping a message.\n</sptc_overflow>\n' \
128	    "$_len" "$_cap" "$_spill" "$_len"
129	}
130

# shellcheck shell=sh
# sptc hook common helpers — sourced by each hook wrapper.
# DRAFT (pending throwaway-CC validation: Windows-shell, env-file timing, whoami-in-hook).
# spt-core is harness-agnostic; this is the CC adapter glue that maps CC hook input -> `spt api`.

ADAPTER=claude-spt

# CC spills additionalContext over ~10,000 chars to a file (dropping it from the inline context the
# agent sees). We pre-empt below this with margin for the marker + CC's own framing. Override-able
# for tests. (ADR-0002 Open #2.)
SPTC_CTX_CAP="${SPTC_CTX_CAP:-9000}"

# Resolve the spt binary: PATH first (post-bootstrap), then known install locations.
spt_bin() {
  if command -v spt >/dev/null 2>&1; then printf 'spt'; return 0; fi
  for p in \
    "$HOME/.local/bin/spt" \
    "$LOCALAPPDATA/spt-core/bin/spt.exe" \
    "$HOME/AppData/Local/spt-core/bin/spt.exe"; do
    [ -x "$p" ] && { printf '%s' "$p"; return 0; }
  done
  printf 'spt'  # last resort; caller tolerates failure
}

# Extract a top-level string field from a flat JSON object on stdin payload ($1=json, $2=key).
# CC hook input is a flat object for the fields we need (session_id, source, prompt, agent_id).
json_str() {
  printf '%s' "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -n1
}

# Decide the SessionStart registration verb for this spawn ($1 = CC hook `source`; reads env
# $SPT_ENDPOINT_ID). Pure — no spt/CC. [impl->REQ-DIST-SHORTCUT-BASENAME]
#   boundary — a /clear or /compact within a live session (rebind the perch to the new session id)
#   bind     — spt-hosted: `spt endpoint run` spawned us via [session.self] + injected the endpoint
#              id into $SPT_ENDPOINT_ID; the perch already exists, self-register post-spawn
#   seed     — harness-hosted: user-launched CC; record an ephemeral seed for /sptc:ready|live
sptc_register_verb() {
  case "$1" in
    clear|compact) printf boundary ;;
    *) if [ -n "${SPT_ENDPOINT_ID:-}" ]; then printf bind; else printf seed; fi ;;
  esac
}

# Resolve this session's perch id via spt whoami (off $OWL_SESSION_ID / $SPT_AGENT_ID).
# Empty => no perch yet (session never readied) => caller no-ops.
sptc_self_id() {
  _sid="$1"; _spt="$(spt_bin)"
  OWL_SESSION_ID="${OWL_SESSION_ID:-$_sid}" "$_spt" whoami 2>/dev/null | head -n1
}

# Decode an spt envelope body to plain text: literal `<br>` -> newline, then HTML entities
# (< > " then & LAST, to avoid double-decoding). Exactly the live-agent
# body-parsing rule (spt-proto::event, ADR-0001 grammar).
sptc_unescape() {
  printf '%s' "$1" | sed \
    -e 's/
/\
/g' \
    -e 's/</</g' -e 's/>/>/g' -e 's/"/"/g' -e 's/&/\&/g'
}

# Render an `api poll` drain ($1) for CC. Canonical format: every message is a
# self-delimiting `<EVENT type="msg" from="<sender>">body</EVENT>` envelope (spt-proto::event) —
# the same grammar the live listener emits. Multi-message drains split cleanly on `</EVENT>`.
# Sender is preserved as `from=` (reply-correlation). NOTE: targets canonical <EVENT>; the current
# 0.6.0 binary still emits a `__REPLY_TO__` relic at the poll surface until the REQ-MSG-ENVELOPE
# refactor lands — finalize/validate against poll only post-refactor. [unit->REQ-UPS-INJECTION]
render_frames() {
  _in="$1"
  [ -z "$_in" ] && return 0
  # Normalise to one <EVENT>…</EVENT> per line (body newlines are
-escaped, so each envelope
  # is single-line), then parse each.
  printf '%s' "$_in" | sed 's#</EVENT>#</EVENT>\
#g' | while IFS= read -r _ev; do
    case "$_ev" in
      *"<EVENT"*"</EVENT>"*) ;;
      *) continue ;;
    esac
    _sender=$(printf '%s' "$_ev" | sed -n 's/.*<EVENT[^>]* from="\([^"]*\)".*/\1/p')
    _raw=$(printf '%s' "$_ev" | sed -n 's#.*<EVENT[^>]*>\(.*\)</EVENT>.*#\1#p')
    _body=$(sptc_unescape "$_raw")
    if [ -n "$_sender" ]; then
      printf '<sptc_messages from="%s">\n%s\n</sptc_messages>\n' "$_sender" "$_body"
    else
      printf '<sptc_messages>\n%s\n</sptc_messages>\n' "$_body"
    fi
  done
}

# Extract the skill name from a `/sptc:<skill>` slash-command prompt ($1=prompt). UPS fires on a
# slash-command with the token intact (ADR-0002 validation 2026-06-15: `prompt:"/sptc:send doyle"`),
# so the wrapper detects the command here. Leading-only (after optional whitespace) to avoid firing
# on prose that merely mentions `/sptc:x` mid-sentence. Empty => not a sptc slash-command.
# [unit->REQ-UPS-INJECTION]
sptc_skill_key() {
  printf '%s' "$1" | sed -n 's#^[[:space:]]*/sptc:\([a-z][a-z0-9-]*\).*#\1#p' | head -n1
}

# Inject a skill's operative instructions ($1=skill name) as additionalContext. Resolves the
# file-backed/inline body from the adapter `[strings.skills].<skill>` via `spt adapter get-string`
# (F-003: pointer values resolve to file contents at read time). No-op (silent) if spt is absent,
# the adapter is unregistered, or the key is unset — the thin SKILL.md skeleton still loaded by CC
# is the floor. The injected body is what the agent follows for this `/sptc:<skill>` invocation.
# [impl->REQ-UPS-INJECTION]
sptc_inject_skill() {
  _skill="$1"; [ -z "$_skill" ] && return 0
  _spt="$(spt_bin)"
  _body=$("$_spt" adapter get-string "$ADAPTER" "skills.$_skill" 2>/dev/null) || return 0
  [ -z "$_body" ] && return 0
  printf '<sptc_skill name="%s">\n%s\n</sptc_skill>\n' "$_skill" "$_body"
}

# Emit additionalContext under CC's ~10k spill threshold ($1=text, $2=cap, $3=spill_path). Under the
# cap -> emit as-is. Over -> spill the FULL text to a file the agent can Read and emit ONLY a concise
# pointer marker (never a partial inline — a head-cut would split a <sptc_messages>/<EVENT> block and
# lose a message silently). The agent reads the spill file to see everything; nothing is dropped. CC
# would otherwise spill oversized additionalContext itself, evicting it from the inline context.
# [impl->REQ-UPS-INJECTION]
sptc_cap_output() {
  _text="$1"; _cap="$2"; _spill="$3"
  [ -z "$_text" ] && return 0
  _len=$(printf '%s' "$_text" | wc -c | tr -d ' ')
  if [ "$_len" -le "$_cap" ]; then
    printf '%s' "$_text"
    return 0
  fi
  printf '%s' "$_text" > "$_spill" 2>/dev/null || _spill="(spill failed; content too large to inline)"
  printf '<sptc_overflow bytes="%s" cap="%s" spilled_to="%s">\nDelivery exceeded CC'\''s additionalContext cap. The full content (skill instructions and/or %s message bytes) was written to the file above — read it now to see everything; it is NOT inlined here to avoid silently dropping a message.\n</sptc_overflow>\n' \
    "$_len" "$_cap" "$_spill" "$_len"
}


/grill-with-docs the SessionStart hook for spt-claude-code needs to be relaying information about SPT to the agent, including their ID. please reference CONTEXT.md and legacy SPT (sibling project clau…


Now CONTEXT.md + legacy owl sibling. Find sibling path.


1	# spt-claude-code — glossary
2
3	> Authoritative for meaning (grill-with-docs convention). Glossary only — no
4	> implementation detail. Decisions/rationale live in `SCOPE.md` and `docs/adr/`.
5
6	**spt-claude-code** — the rebuilt Claude Code harness adapter. The `claude-spt`
7	adapter in spt-core terms. Simultaneously spt-core's v1 acceptance proof (legacy
8	parity), its first casual-end-user entrypoint (a CC plugin), and an invisible spt-core
9	installer. Built by maintainer perri from the public spt-releases surface only.
10
11	**claude-spt** — the adapter_name spt-claude-code registers under (rides every `api`
12	invocation + the manifest). The CC adapter's identity inside spt-core.
13
14	**legacy spt / `claude_skill_owl`** — the sister project; today's shipped CC integration
15	(owl.exe + `spt` plugin, v1.11.25). The parity *target* (user-facing features), not a
16	1:1 port — most of its machinery now lives in spt.exe.
17
18	**skeleton plugin** — the thin marketplace artifact on `SaberMage/cplugs`: namespaced
19	`/spt:*` skill stubs + hooks + a SessionStart bootstrap that installs spt-core. Low-churn;
20	the volatile bulk (binary + manifest) is spt-core-conducted, not in the plugin.
21
22	**fetch-stub skill** — a `/spt:X` SKILL.md that is a 2-line stub fetching its real
23	instructions at runtime from the adapter `[strings]` (`spt adapter get-string`/`skill-help`).
24	Keeps skill files static while instructions update via spt-core. Distinct from a `[hints]`
25	entry (proactive, keyword-triggered, UPS-hook-delivered).
26
27	**ccs profile** — a profile under spt-claude-code (`claude-spt:glm`, `claude-spt:kimi`)
28	that leaf-replaces the launch command + history/digest log dir to use the `ccs` backend.
29	NOT its own adapter — ccs is structurally Claude Code.
30
31	**`cc` launcher (capsule-style)** — a generated `cc`/`cc <id>` script at project root that
32	spawns-or-attaches a CC endpoint via spt-core's spt-hosted topology (broker PTY + inject +
33	attach). The spt-core realization of legacy's unbuilt "Capsule" milestone. _Avoid_: equating
34	it with psmux/sendkeys — spt-core's broker is the terminal host.
35
36	**Psyche** — a LiveAgent's detached *companion* process. When a session goes live
37	(`/sptc:live`), spt-core's daemon hosts a Psyche alongside it: the Psyche owns its own
38	perch (`<parent>-psyche`), is woken by daemon *pulses*, and on each pulse authors a
39	*commune*. It never replies or notifies (that is the echo-commune, a different actor) and
40	exits at session end. A *ready* agent has no Psyche — live-vs-ready is the command, not a
41	profile. (Realized here by the `claude-spt-psyche` runner.)
42
43	**commune** — the context-delta a Psyche writes on each pulse: a brief that lets the
44	parent agent resume coherently after a context wipe (`/clear` / compact). A file-drop the
45	daemon ingests, not an `api` verb.
46
47	**psyche sandbox** — the constrained surface a Psyche's `claude` turns run under:
48	Read/Edit/Write tools only, slash-commands disabled, permissions auto-approved, cheap
49	pinned model. Deliberately narrower than the parent agent (which is unconstrained). Mirrors
50	legacy owl's psyche box; see `docs/adr/0003-*`.
51
52	**casual end user** — a user who *uses* an spt-powered system mostly-invisibly, vs the
53	adapter/shell *developer* who is spt-core's nominal target. spt-claude-code serves casual
54	users: install the plugin, get spt-core for free.
55
56	**experimplate** — the standalone reusable project-workflow template (its own sibling repo)
57	extracted from spt-core's working style. spt-claude-code is its first consumer. Carries
58	traceable-reqs gating, JIT plans, grill-with-docs scaffolding, release/changelog, and
59	same-repo published-docs. Defined in its own folder, not here.
60

# spt-claude-code — glossary

> Authoritative for meaning (grill-with-docs convention). Glossary only — no
> implementation detail. Decisions/rationale live in `SCOPE.md` and `docs/adr/`.

**spt-claude-code** — the rebuilt Claude Code harness adapter. The `claude-spt`
adapter in spt-core terms. Simultaneously spt-core's v1 acceptance proof (legacy
parity), its first casual-end-user entrypoint (a CC plugin), and an invisible spt-core
installer. Built by maintainer perri from the public spt-releases surface only.

**claude-spt** — the adapter_name spt-claude-code registers under (rides every `api`
invocation + the manifest). The CC adapter's identity inside spt-core.

**legacy spt / `claude_skill_owl`** — the sister project; today's shipped CC integration
(owl.exe + `spt` plugin, v1.11.25). The parity *target* (user-facing features), not a
1:1 port — most of its machinery now lives in spt.exe.

**skeleton plugin** — the thin marketplace artifact on `SaberMage/cplugs`: namespaced
`/spt:*` skill stubs + hooks + a SessionStart bootstrap that installs spt-core. Low-churn;
the volatile bulk (binary + manifest) is spt-core-conducted, not in the plugin.

**fetch-stub skill** — a `/spt:X` SKILL.md that is a 2-line stub fetching its real
instructions at runtime from the adapter `[strings]` (`spt adapter get-string`/`skill-help`).
Keeps skill files static while instructions update via spt-core. Distinct from a `[hints]`
entry (proactive, keyword-triggered, UPS-hook-delivered).

**ccs profile** — a profile under spt-claude-code (`claude-spt:glm`, `claude-spt:kimi`)
that leaf-replaces the launch command + history/digest log dir to use the `ccs` backend.
NOT its own adapter — ccs is structurally Claude Code.

**`cc` launcher (capsule-style)** — a generated `cc`/`cc <id>` script at project root that
spawns-or-attaches a CC endpoint via spt-core's spt-hosted topology (broker PTY + inject +
attach). The spt-core realization of legacy's unbuilt "Capsule" milestone. _Avoid_: equating
it with psmux/sendkeys — spt-core's broker is the terminal host.

**Psyche** — a LiveAgent's detached *companion* process. When a session goes live
(`/sptc:live`), spt-core's daemon hosts a Psyche alongside it: the Psyche owns its own
perch (`<parent>-psyche`), is woken by daemon *pulses*, and on each pulse authors a
*commune*. It never replies or notifies (that is the echo-commune, a different actor) and
exits at session end. A *ready* agent has no Psyche — live-vs-ready is the command, not a
profile. (Realized here by the `claude-spt-psyche` runner.)

**commune** — the context-delta a Psyche writes on each pulse: a brief that lets the
parent agent resume coherently after a context wipe (`/clear` / compact). A file-drop the
daemon ingests, not an `api` verb.

**psyche sandbox** — the constrained surface a Psyche's `claude` turns run under:
Read/Edit/Write tools only, slash-commands disabled, permissions auto-approved, cheap
pinned model. Deliberately narrower than the parent agent (which is unconstrained). Mirrors
legacy owl's psyche box; see `docs/adr/0003-*`.

**casual end user** — a user who *uses* an spt-powered system mostly-invisibly, vs the
adapter/shell *developer* who is spt-core's nominal target. spt-claude-code serves casual
users: install the plugin, get spt-core for free.

**experimplate** — the standalone reusable project-workflow template (its own sibling repo)
extracted from spt-core's working style. spt-claude-code is its first consumer. Carries
traceable-reqs gating, JIT plans, grill-with-docs scaffolding, release/changelog, and
same-repo published-docs. Defined in its own folder, not here.


Legacy hook script — find the plugin hook shell, not Rust test artifacts.


No files found


1	{
2	  "hooks": {
3	    "SessionStart": [
4	      {
5	        "hooks": [
6	          {
7	            "type": "command",
8	            "command": "$CLAUDE_PLUGIN_ROOT/owl.exe plugin-session-start"
9	          }
10	        ]
11	      }
12	    ],
13	    "SessionEnd": [
14	      {
15	        "hooks": [
16	          {
17	            "type": "command",
18	            "command": "$CLAUDE_PLUGIN_ROOT/owl.exe cleanup-session"
19	          }
20	        ]
21	      }
22	    ],
23	    "PreToolUse": [
24	      {
25	        "hooks": [
26	          {
27	            "type": "command",
28	            "command": "$CLAUDE_PLUGIN_ROOT/owl.exe hook-check"
29	          }
30	        ]
31	      }
32	    ],
33	    "Stop": [
34	      {
35	        "hooks": [
36	          {
37	            "type": "command",
38	            "command": "$CLAUDE_PLUGIN_ROOT/owl.exe hook-idle"
39	          }
40	        ]
41	      }
42	    ],
43	    "UserPromptSubmit": [
44	      {
45	        "hooks": [
46	          {
47	            "type": "command",
48	            "command": "$CLAUDE_PLUGIN_ROOT/owl.exe hook-prompt"
49	          }
50	        ]
51	      }
52	    ],
53	    "SubagentStart": [
54	      {
55	        "hooks": [
56	          {
57	            "type": "command",
58	            "command": "$CLAUDE_PLUGIN_ROOT/owl.exe hook-subagent-start"
59	          }
60	        ]
61	      }
62	    ],
63	    "SubagentStop": [
64	      {
65	        "hooks": [
66	          {
67	            "type": "command",
68	            "command": "$CLAUDE_PLUGIN_ROOT/owl.exe hook-subagent-stop"
69	          }
70	        ]
71	      }
72	    ]
73	  }
74	}
75


Legacy SessionStart = `owl.exe plugin-session-start` (Rust). Need what additionalContext it emits. Grep owl src.


Found 8 files
C:\Users\decid\Documents\projects\claude_skill_owl\src\owl\mod.rs
C:\Users\decid\Documents\projects\claude_skill_owl\src\live\wrapper\mod.rs
C:\Users\decid\Documents\projects\claude_skill_owl\src\cli.rs
C:\Users\decid\Documents\projects\claude_skill_owl\src\owl\resume.rs
C:\Users\decid\Documents\projects\claude_skill_owl\src\common\hook_output.rs
C:\Users\decid\Documents\projects\claude_skill_owl\src\owl\sync_prompt.rs
C:\Users\decid\Documents\projects\claude_skill_owl\src\owl\plugin_session_start.rs
C:\Users\decid\Documents\projects\claude_skill_owl\src\live\context.rs