Skip to content

3.0.0: central hook hub, multiple worktrees per repo, health commands - #12

Merged
rousan merged 6 commits into
mainfrom
develop
Jun 30, 2026
Merged

3.0.0: central hook hub, multiple worktrees per repo, health commands#12
rousan merged 6 commits into
mainfrom
develop

Conversation

@rousan

@rousan rousan commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Breaking — runtime v3. A large redesign bundling several changes (run mx migrate once per runtime after upgrading).

1. Central hook hub (<runtime>/hooks/)

Per-repo hydrate.sh/health.sh and per-work hooks/ are gone. All lifecycle hooks live in one directory, one executable per event, where you branch on MX_* inside.

Events: pre/post-worktree-create (post = the old "hydrate"), pre/post-worktree-remove, pre/post-work-archive, pre/post-work-unarchive, pre/post-repo-fetch, repo-health. pre-* non-zero aborts (HOOK_FAILED); post-* warns; repo-health stdout feeds mx repo health. Any language (shebang); stamp-if-missing (never clobbers your logic); delete a file to disable.

2. Multiple worktrees of one repo per work

Each worktree has a name — its wt/<name> directory and the selector for rm/port/unarchive — defaulting to the repo name. Pass a distinct positional name to hold several worktrees of one repo:

mx work -n feat worktree add app              # wt/app
mx work -n feat worktree add app app-pr2 --branch fix   # wt/app-pr2
mx work -n feat worktree rm app-pr2
mx work -n feat port set app-pr2 web          # independent ports per worktree

work.json now always records name (uniform shape; mx migrate backfills = repo into existing manifests). Back-compatible: a name-less entry defaults to repo. Hooks gain $MX_WORKTREE_NAME.

3. Repos carry repo.json, no scripts

{ "name": … } (extensible). mx repo add/new write it.

4. Dropped worktree hydrate + --no-hydrate

Redundant with hooks: post-worktree-create always fires on worktree add (no-op the hook to skip).

5. Archive frees ports; unarchive re-hydrates

mx work archive now clears each worktree's ports (the worktrees and their servers are gone, so the numbers are reusable while archived). mx work unarchive re-creates the worktrees and fires post-worktree-create per worktree, where the hook re-allocates ports and re-hydrates just like a fresh worktree add.

Migration (v2 → v3)

mx migrate stamps the hooks/ hub, writes each repo.json, backfills work.json worktree names, and retires the old scripts — deleting defaults, keeping customized ones with a warning so you can fold the logic into the central hooks. --dry-run previews plan + warnings.

Code

  • Core: HOOK_EVENTS/hookScript/runtimeHooksDir, repoConfigFile/read|writeRepoConfig, stampRuntimeHooks, worktreeName/findWorktreeByName, RUNTIME_VERSION = 3; multi-worktree threading through works.ts/ports.ts/inferContext; v2→v3 migration (default-vs-customized script detection + name backfill).
  • CLI: new hooks.ts runner (replaces hydrate.ts+workhooks.ts); positional worktree name; name-keyed rm/port/unarchive; hydrate subcommand + --no-hydrate removed; HYDRATE_FAILED folded into HOOK_FAILED.
  • Templates: templates/hooks/<event> (11 no-ops); removed templates/repo/.
  • 76 tests (incl. full multi-worktree edge cases, v1→v3 + v2→v3 migration, default/customized script handling, name-less back-compat). All docs, READMEs, runtime CLAUDE.md updated; npm/package.json → 3.0.0.

Verification

typecheck / lint / test / build green. Smoke-tested end-to-end: every hook fires with correct MX_* context (incl. MX_WORKTREE_NAME), pre-veto aborts, repo-health captured; two worktrees of one repo (wt/app + wt/app-2) with independent ports and uniform work.json; mx migrate v2→v3 stamps hooks, writes repo.json, backfills names, removes a default hydrate.sh and keeps a customized health.sh with a warning.


6. Health: mx work health + mx health (added after the original PR)

New mx work health / mx work -n <name> health audits a work folder the way mx repo health audits a clone, purely local:

  • stray non-mx-native files in the work root
  • worktrees recorded in work.json but missing on disk (and the inverse for archived works)
  • cross-work port collisions (only possible via a hand-edited work.json)
  • archive invariants: an archived work should have its ports freed and worktrees removed

New mx health [--all]: whole-runtime overview, every repo block followed by every active work block (--all adds archived works).

Health views now show only metric rows (each carries a ✓/⚠). Info-only fields (default branch, worktrees-in-works, work status, active port count) stay in mx info. last fetched is a metric for remote repos with a 24h freshness rule. The repo/work name carries an aggregate ✓/⚠ for quick scanning, and each failing check renders inline as the row hint. The repo-health/work-health hooks follow a silent-when-healthy convention: empty output or a bare ok renders the extra row as OK ✓, any other output renders ⚠ with the message.

7. Removed the per-work SessionStart context-index hook

v2 stamped works/<feature>/.claude/settings.json with a SessionStart hook that printed context/INDEX.json, but Claude Code caps hook output (~2KB) so a real index was truncated. v3 drops it: mx work new/mx sync no longer stamp it, and mx migrate removes a default-stamped one (a customized settings.json is kept with a warning). Loading the whole index is now the session's job, with a "load the mx ctx index as whole" trigger documented in the runtime CLAUDE.md.

Code (additions)

  • Core: workHealth/listWorkHealth/WorkHealth in workhealth.ts; work-health added to HOOK_EVENTS; isDefaultClaudeSettings() in the v2→v3 migration (removes the default SessionStart hook).
  • CLI: commands/health.ts; renderWorkHealthDetail; metric-only rendering with name-level aggregate + inline hints.
  • Templates: templates/hooks/work-health; both health hooks document the silent-when-healthy convention.
  • Tests: now 85 (added work-health checks, port-collision, archive invariants, hook capture, and the SessionStart-removal migration).

rousan added 2 commits June 29, 2026 22:46
BREAKING (runtime v3). Replace the per-repo hydrate.sh/health.sh and the
per-work hooks/ with a single runtime-wide hook hub at <runtime>/hooks/ —
one executable per event, where the user branches on MX_* context inside.

Events: pre/post-worktree-create (post = the old hydrate), pre/post-
worktree-remove, pre/post-work-archive, pre/post-work-unarchive,
pre/post-repo-fetch, and repo-health. pre-* non-zero aborts the op
(HOOK_FAILED); post-* warns; repo-health stdout feeds `mx repo health`.
Hooks are any-language executables (shebang), stamp-if-missing so user
logic is never clobbered; delete a file to disable an event.

Repos now carry repo.json ({ "name": … }, extensible) and no scripts.
The CLI fires hooks around worktree add/rm, archive/unarchive, repo fetch
(hooks.ts replaces hydrate.ts + workhooks.ts); HYDRATE_FAILED folded into
HOOK_FAILED.

Migration v2→v3 (mx migrate): stamps the hooks hub, writes each repo.json,
and retires old scripts — deletes defaults, keeps customized ones with a
warning so the user can fold the logic into the central hooks. --dry-run
previews the plan + warnings.

Core: HOOK_EVENTS / hookScript / runtimeHooksDir, repoConfigFile /
read/writeRepoConfig, stampRuntimeHooks; RUNTIME_VERSION = 3. Tests, all
docs, templates, and the runtime CLAUDE.md updated. 69 tests green.
Multi-worktree: each worktree gains a `name` (its wt/<name> directory and
the rm/port/unarchive selector), defaulting to the repo name. Pass a
distinct positional name to add a second worktree of the same repo:
  mx work -n feat worktree add app app-pr2 --branch fix
Selectors and ports are keyed by worktree name (independent ports per
worktree), workspace folders use wt/<name>, and inferContext resolves the
wt/<name> segment to its repo via work.json. work.json now ALWAYS records
`name` (uniform shape, new + migrated). Back-compatible: a name-less entry
defaults to repo. New worktreeName / findWorktreeByName in @mx/core; hooks
gain $MX_WORKTREE_NAME.

Migration: the v2->v3 step backfills `name` (= repo) into existing
work.json worktrees so old and new manifests match.

Dropped `mx work worktree hydrate` and `--no-hydrate` — with hooks as the
source of truth they're redundant; post-worktree-create always fires on
add (no-op the hook to skip).

7 new edge-case tests (distinct-name add, default-name collision, rm by
name, independent ports, archive/unarchive round-trip, invalid name,
v2-style name-less back-compat) + migration backfill assertion; 76 total.
Docs, templates, READMEs, and runtime CLAUDE.md updated. Part of 3.0.0.
@rousan rousan changed the title Centralize lifecycle hooks into a runtime hook hub (3.0.0) 3.0.0: central hook hub + multiple worktrees per repo Jun 29, 2026
rousan added 4 commits June 29, 2026 23:23
mx work archive now clears each worktree's ports (the worktrees/servers
are gone, so the numbers become reusable while archived). mx work
unarchive re-creates the worktrees and fires post-worktree-create for
each one, so the hook re-allocates ports and re-hydrates exactly like a
fresh worktree add. Updated tests (ports freed + allocatedPorts empty
after archive; restored ports empty), docs, and history. Part of 3.0.0.
…dex hook

Health
- New mx work health / mx work -n <name> health: local work-folder audit
  (stray non-mx-native root files, worktree presence vs work.json,
  cross-work port collisions from hand-edits, archive invariants), plus a
  central work-health hook captured as the extra row.
- New mx health [--all]: whole-runtime overview, every repo block followed
  by every active work block (--all adds archived).
- Health views show only metric rows (each carries a tick/warn); info-only
  fields (default branch, last fetched detail, worktrees-in-works, work
  status, active port count) stay in mx info. last fetched is a metric for
  remote repos with a 24h freshness rule. The repo/work name carries an
  aggregate marker for quick scanning. Failing checks render inline as the
  row hint (no separate issues block).
- repo-health / work-health hooks follow a silent-when-healthy convention:
  empty output or a bare "ok" renders the extra row as OK with a tick, any
  other output renders a warning with the message and flags the block.

Context-index loading
- Remove the per-work .claude/settings.json SessionStart hook. Claude Code
  caps hook output (~2KB) so a real INDEX.json was truncated. mx no longer
  stamps it; the v2 to v3 migration removes a default-stamped one (a
  customized settings.json is kept with a warning). Loading the whole index
  is now the session's job, with a "load the mx ctx index as whole" trigger.

Adds workHealth / listWorkHealth / WorkHealth to @mx/core, the work-health
HOOK_EVENTS entry, commands/health.ts, and isDefaultClaudeSettings() in the
v2 to v3 migration. Docs, help text, and templates updated. 85 tests pass.
@rousan rousan changed the title 3.0.0: central hook hub + multiple worktrees per repo 3.0.0: central hook hub, multiple worktrees per repo, health commands Jun 30, 2026
@rousan
rousan merged commit 440eb82 into main Jun 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant