feat(hooks): tell session hooks which kind of worktree they are in #410

Merged
mph merged 1 commit from hook-worktree-kind into main 2026-07-28 08:03:42 +00:00
Owner

Summary

iw start --effort shipped in #409, but the session hook that makes iw start useful was never
told about the new worktree kind. SessionContext carried a single identity field named issueId,
which an effort worktree filled with its slug — so kanon's start hook sent
/iterative-works:triage-issue mail-capture, and triage stops on a tracker lookup for an issue that
does not exist. Every iw start --effort opened a session that dead-ended.

What changed

SessionContextissueId is now key (the identity the worktree is addressed under) plus
kind (Issue or Effort). A hook reads kind before interpreting key, because only an issue
key resolves against the tracker. This is a breaking change to the hook API; the only consumer
in existence is kanon's start hook, updated alongside.

Hook version requirements are now enforced. iw-run already had read_iw_version,
compare_versions and check_version_requirement parsing // REQUIRES: iw-cli >= X.Y.Z — it was
simply never applied to hook files. The parser is extracted into read_version_requirement and
reused by a new filter_compatible_hooks, wired into both hook-collection sites.

Commands and hooks differ deliberately:

unsatisfied requirement
command error + exit — it is what you asked to run
hook warn + skip, command proceeds

A hook decorates a command rather than being the thing you asked for, so one stale plugin hook must
not brick every command it attaches to. And since hooks are compiled alongside the command, a hook
written against a newer core otherwise fails at compile time with no hint as to why — which is
exactly the failure this header exists to prevent.

Verification

core.test · dashboard.test · full BATS suite 176 ok / 0 not ok (7 new, covering satisfied,
unsatisfied, malformed, absent, mixed and empty hook lists) · scala-cli compile --scalac-option -Werror core/. Tests were written first and observed failing (value key is not a member of SessionContext) before the model changed.

The updated kanon hook was compiled against this core exactly as iw-run compiles it — exit 0.

Note for the release

kanon's hook declares // REQUIRES: iw-cli >= 0.7.0, so it is correctly skipped until VERSION
reaches 0.7.0:

Warning: skipping hook claude-session.hook-start.scala: requires iw-cli >= 0.7.0 (installed: 0.6.2)

Declaring anything lower would be false — no released version carries ctx.key. The bump follows
this merge.

## Summary `iw start --effort` shipped in #409, but the session hook that makes `iw start` useful was never told about the new worktree kind. `SessionContext` carried a single identity field named `issueId`, which an effort worktree filled with its slug — so kanon's start hook sent `/iterative-works:triage-issue mail-capture`, and triage stops on a tracker lookup for an issue that does not exist. Every `iw start --effort` opened a session that dead-ended. ## What changed **`SessionContext`** — `issueId` is now `key` (the identity the worktree is addressed under) plus `kind` (`Issue` or `Effort`). A hook reads `kind` before interpreting `key`, because only an issue key resolves against the tracker. This is a **breaking change to the hook API**; the only consumer in existence is kanon's start hook, updated alongside. **Hook version requirements are now enforced.** `iw-run` already had `read_iw_version`, `compare_versions` and `check_version_requirement` parsing `// REQUIRES: iw-cli >= X.Y.Z` — it was simply never applied to hook files. The parser is extracted into `read_version_requirement` and reused by a new `filter_compatible_hooks`, wired into both hook-collection sites. Commands and hooks differ deliberately: | | unsatisfied requirement | |---|---| | command | error + exit — it *is* what you asked to run | | hook | warn + skip, command proceeds | A hook decorates a command rather than being the thing you asked for, so one stale plugin hook must not brick every command it attaches to. And since hooks are compiled *alongside* the command, a hook written against a newer core otherwise fails at compile time with no hint as to why — which is exactly the failure this header exists to prevent. ## Verification `core.test` · `dashboard.test` · full BATS suite 176 ok / 0 not ok (7 new, covering satisfied, unsatisfied, malformed, absent, mixed and empty hook lists) · `scala-cli compile --scalac-option -Werror core/`. Tests were written first and observed failing (`value key is not a member of SessionContext`) before the model changed. The updated kanon hook was compiled against this core exactly as `iw-run` compiles it — exit 0. ## Note for the release kanon's hook declares `// REQUIRES: iw-cli >= 0.7.0`, so it is correctly skipped until VERSION reaches 0.7.0: ``` Warning: skipping hook claude-session.hook-start.scala: requires iw-cli >= 0.7.0 (installed: 0.6.2) ``` Declaring anything lower would be false — no released version carries `ctx.key`. The bump follows this merge.
feat(hooks): tell session hooks which kind of worktree they are in
All checks were successful
CI / format (pull_request) Successful in 6s
CI / lint (pull_request) Successful in 7s
CI / contract (pull_request) Has been skipped
CI / compile (pull_request) Successful in 1m16s
CI / dashboard-build (pull_request) Successful in 1m3s
CI / test (pull_request) Successful in 8m49s
3e37ec088d
SessionContext carried a single identity field named issueId, which an
effort worktree filled with its slug. A session hook had no way to tell the
two apart, so kanon's start hook sent "/iterative-works:triage-issue
<slug>" for an effort worktree and triage stopped on a tracker lookup for
an issue that does not exist.

The field is now named key, for the identity the worktree is addressed
under, and kind says which sort it is so a hook can route on it.

Hooks may already declare "// REQUIRES: iw-cli >= X.Y.Z"; that header was
only enforced for command files. It now filters hooks too, warning and
skipping rather than exiting: a hook decorates a command instead of being
the thing the user asked to run, and it is compiled alongside that command,
so a hook written against a newer core otherwise fails at compile time with
no hint as to why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mph merged commit fc5e167a5f into main 2026-07-28 08:03:42 +00:00
mph deleted branch hook-worktree-kind 2026-07-28 08:03:43 +00:00
Sign in to join this conversation.
No description provided.