IW-682: Port allocation for worktrees (iw port-slot) #757

Merged
mph merged 17 commits from IW-682 into main 2026-10-08 06:22:39 +00:00
Member

Summary

iw-cli gives each worktree a port slot: a whole number unique among the worktrees of one repository. The main checkout holds 0; a linked worktree claims the lowest free slot from 1. A project start hook computes its ports as base + slot × step and no longer needs its own allocator (the generic form of medeca-modul-poptavky find_next_offset).

Changes

  • Phase 1 (#752), slot model + slot store: PortSlot, PortSlots.lowestFree, PortSlot.port(base, step), the iw-port-slot.json format and PortSlotError (core/model/PortSlot.scala). WorktreePortSlots.claim/list (core/adapters/WorktreePortSlots.scala) store the slot in the per-worktree git dir, discover other worktrees with git worktree list, and hold a cross-process lock in the common git dir. The atomic file write is extracted from WorktreeServices into AtomicFile, and both stores use it.
  • Phase 2 (#753), iw port-slot claim|list + docs: PortSlotOps capability on CommandEnv, PortSlotCommand, commands/port-slot.scala, a README row, and a "Port slots" section plus a rewritten example hook in docs/worktree-services.md.

git worktree remove / iw rm releases the slot with no extra step. A claim is idempotent and atomic across processes.

Testing

  • Unit: PortSlotTest, AtomicFileTest, ServiceDeclarationTest, CommandDeclarationTest.
  • Harness: PortSlotHarnessTest.
  • Integration: WorktreePortSlotsItest (real git worktrees).
  • E2E: test/port-slot.bats (claim, list, not-a-checkout, a start hook that claims and declares with the slot free after iw rm, help).
  • Pre-push checks (core compile with warnings as errors, scalafix, unit tests, command compile) passed on this head.

Review notes

  • The docs example hook says // REQUIRES: iw-cli >= 0.21.0; VERSION is 0.20.0. The release that ships this must be 0.21.0, or the header must change.
  • Follow-ups filed: #440 and #585 (comments), #754, #755, #756.
  • Review packet: project-management/issues/IW-682/review-packet.md.

Release notes

Datum: 2026-10-08

Každá pracovní kopie projektu teď od iw-cli dostane vlastní číslo, které žádná jiná pracovní kopie téhož repozitáře nemá. Projekt z něj snadno spočítá porty pro své služby, a tak už se nestane, že by dvě souběžně spuštěné pracovní kopie obsadily stejný port. Projekty už nemusí psát a udržovat vlastní skripty, které hledají volné místo mezi ostatními pracovními kopiemi.

Hlavní kopie repozitáře má vždy číslo nula. Každá další pracovní kopie si při prvním vyžádání vezme nejnižší volné číslo od jedničky výš a při každém dalším vyžádání dostane totéž číslo znovu. Projekt si sám zvolí výchozí port a rozestup, například výchozí port 8085 s rozestupem 100 dá první pracovní kopii port 8185. Když se pracovní kopie odstraní, její číslo se samo uvolní a další nová kopie ho může použít. Žádný úklid navíc není potřeba. Ani když se dvě pracovní kopie zakládají ve stejnou chvíli, nedostanou stejné číslo.

Číslo si vyžádá spouštěcí skript projektu, buď přímo v kódu, nebo novým příkazem iw port-slot. Ten také umí vypsat, která pracovní kopie drží které číslo. Dokumentace k deklaraci služeb teď ukazuje celý postup na příkladu: skript si vyžádá číslo, spočítá z něj porty a ohlásí služby, na které pak odkazuje přehled.

Projekty, které si porty dosud počítaly samy, například pomocí souboru s posunem portů, mohou přejít postupně. Nový mechanismus jejich dosavadní soubory nevidí, proto je vhodné pracovní kopie po přechodu založit znovu, nebo do té doby oba způsoby nechat na různých výchozích portech. Číslování platí vždy pro jeden repozitář: pokud má na jednom počítači stejný projekt naklonovaný víc lidí, měl by každý klon používat jiný výchozí port.

🤖 Generated with Claude Code

Closes #682

## Summary iw-cli gives each worktree a **port slot**: a whole number unique among the worktrees of one repository. The main checkout holds 0; a linked worktree claims the lowest free slot from 1. A project start hook computes its ports as `base + slot × step` and no longer needs its own allocator (the generic form of medeca-modul-poptavky `find_next_offset`). ## Changes - **Phase 1 (#752), slot model + slot store:** `PortSlot`, `PortSlots.lowestFree`, `PortSlot.port(base, step)`, the `iw-port-slot.json` format and `PortSlotError` (`core/model/PortSlot.scala`). `WorktreePortSlots.claim`/`list` (`core/adapters/WorktreePortSlots.scala`) store the slot in the per-worktree git dir, discover other worktrees with `git worktree list`, and hold a cross-process lock in the common git dir. The atomic file write is extracted from `WorktreeServices` into `AtomicFile`, and both stores use it. - **Phase 2 (#753), `iw port-slot claim|list` + docs:** `PortSlotOps` capability on `CommandEnv`, `PortSlotCommand`, `commands/port-slot.scala`, a README row, and a "Port slots" section plus a rewritten example hook in `docs/worktree-services.md`. `git worktree remove` / `iw rm` releases the slot with no extra step. A claim is idempotent and atomic across processes. ## Testing - Unit: `PortSlotTest`, `AtomicFileTest`, `ServiceDeclarationTest`, `CommandDeclarationTest`. - Harness: `PortSlotHarnessTest`. - Integration: `WorktreePortSlotsItest` (real git worktrees). - E2E: `test/port-slot.bats` (claim, list, not-a-checkout, a start hook that claims and declares with the slot free after `iw rm`, help). - Pre-push checks (core compile with warnings as errors, scalafix, unit tests, command compile) passed on this head. ## Review notes - The docs example hook says `// REQUIRES: iw-cli >= 0.21.0`; `VERSION` is 0.20.0. The release that ships this must be 0.21.0, or the header must change. - Follow-ups filed: #440 and #585 (comments), #754, #755, #756. - Review packet: `project-management/issues/IW-682/review-packet.md`. ## Release notes **Datum:** 2026-10-08 Každá pracovní kopie projektu teď od iw-cli dostane vlastní číslo, které žádná jiná pracovní kopie téhož repozitáře nemá. Projekt z něj snadno spočítá porty pro své služby, a tak už se nestane, že by dvě souběžně spuštěné pracovní kopie obsadily stejný port. Projekty už nemusí psát a udržovat vlastní skripty, které hledají volné místo mezi ostatními pracovními kopiemi. Hlavní kopie repozitáře má vždy číslo nula. Každá další pracovní kopie si při prvním vyžádání vezme nejnižší volné číslo od jedničky výš a při každém dalším vyžádání dostane totéž číslo znovu. Projekt si sám zvolí výchozí port a rozestup, například výchozí port 8085 s rozestupem 100 dá první pracovní kopii port 8185. Když se pracovní kopie odstraní, její číslo se samo uvolní a další nová kopie ho může použít. Žádný úklid navíc není potřeba. Ani když se dvě pracovní kopie zakládají ve stejnou chvíli, nedostanou stejné číslo. Číslo si vyžádá spouštěcí skript projektu, buď přímo v kódu, nebo novým příkazem `iw port-slot`. Ten také umí vypsat, která pracovní kopie drží které číslo. Dokumentace k deklaraci služeb teď ukazuje celý postup na příkladu: skript si vyžádá číslo, spočítá z něj porty a ohlásí služby, na které pak odkazuje přehled. Projekty, které si porty dosud počítaly samy, například pomocí souboru s posunem portů, mohou přejít postupně. Nový mechanismus jejich dosavadní soubory nevidí, proto je vhodné pracovní kopie po přechodu založit znovu, nebo do té doby oba způsoby nechat na různých výchozích portech. Číslování platí vždy pro jeden repozitář: pokud má na jednom počítači stejný projekt naklonovaný víc lidí, měl by každý klon používat jiný výchozí port. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Closes #682
Port allocation for worktrees, so project start hooks need not compute their own ports

Artifacts:
- analysis.md
- issue.md

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8ZD5foFWAE2zV1xYojPMa
Port allocation for worktrees, so project start hooks need not compute their own ports

Artifacts:
- tasks.md
- review-state.json (tasks_ready)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8ZD5foFWAE2zV1xYojPMa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8ZD5foFWAE2zV1xYojPMa
Issue: IW-682
Phase: 1 - Slot model + slot store

This commit marks the checkpoint for phase 1 context generation.
Use wf-implement IW-682 to return to this point.

The analysis now names GitDirReader (IW-702) and AtomicFile as the shared
helpers, the --separate-git-dir main checkout rule, and the corrected reason
for an idempotent claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Slot model + slot store

Artifacts:
- phase-01-tasks.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue: IW-682
Phase: 2 - `iw port-slot` + docs

This commit marks the checkpoint for phase 2 context generation.
Use wf-implement IW-682 to return to this point.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`iw port-slot` + docs

Artifacts:
- phase-02-tasks.md

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs(IW-682): review packet and release notes
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 10s
CI / lint (pull_request) Successful in 12s
CI / compile (pull_request) Successful in 1m37s
CI / dashboard-build (pull_request) Successful in 1m27s
CI / test (pull_request) Successful in 19m1s
6eb87a459c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mph merged commit 567bd57d76 into main 2026-10-08 06:22:39 +00:00
Sign in to join this conversation.
No description provided.