feat: multi-instance management (#109)#118
Merged
Merged
Conversation
Decompose GitHub issue #109 (multi-instance management) into 6 sub-issues (#110-#115) and a full Forgeplan artifact set. All 15 artifacts pass forgeplan validate with 0 MUST errors; 14 cross-references wired. Refs: PRD-024, PRD-025, PRD-026, PRD-027, PRD-028, PRD-029, ADR-003, ADR-004, SPEC-003, RFC-020, RFC-021, RFC-022, RFC-023, RFC-024, RFC-025 Also deprecates EVID-007 + EVID-008 (100% duplicates of EVID-004 + EVID-005). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refactor the 367-line bin/forgeplan-web.mjs into a 5-line entry +
bin/cli.mjs (root command) + bin/commands/{init,update,start}.mjs (one
file per subcommand) + bin/lib/{config,gitignore,forgeplan-binary}.mjs
(shared helpers). citty@0.2.2 provides defineCommand / runMain / typed
args / auto-help / subcommand routing.
Behavior preserved 1:1 — every flag (-y, --force, --quiet,
--no-gitignore, --experimental, --no-experimental) and every alias
(upgrade, serve, run) still works. New --scope user|project flag is
declared on init/update/start as a placeholder (TODO(109b)) for the
next wave to wire.
Rule 23 amended to permit citty as the single named exception
(verification snippet updated). npm ls --omit=dev shows only
citty@0.2.2 (no transitive deps in this version).
Smoke test (3-OS gate) passes. PASS line confirmed locally.
Refs: PRD-024, RFC-020, ADR-003
Closes: #110
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
109b — init now resolves scope from --scope flag, -y default (project), or interactive readline prompt (default user in TTY; --scope required in non-TTY). User scope writes to ~/.forgeplan-web/ and skips host .gitignore. Project scope behavior unchanged. 109c — start follows the resolution chain: explicit --scope wins, else project (cwd/.forgeplan-web/) → user (~/.forgeplan-web/) → prompt-init (or fail in non-TTY). update mirrors the same chain. New helpers: bin/lib/scope.mjs (resolveScope, userScopePath, projectScopePath, findScaffold) and bin/lib/prompt.mjs (readline-based TTY prompt — citty 0.2.2 doesn't expose consola; TODO(109b-prompt) revisits on citty bump). config.mjs gets readScope(). Rule 20 amended: user-scope writes are bounded to ~/.forgeplan-web/ and gitignore-silent. All write-boundary invariants preserved per scope. Smoke: PASS (project scope path; user-scope and resolution chain verified via manual scenarios documented in agent report). Refs: PRD-025, PRD-026, RFC-021, RFC-022 Closes: #111, #112 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port bits-ui Combobox (v2.18.1) into template/src/shared/ui/combobox/ mirroring the Select primitive shape: 5 sub-components (Combobox, ComboboxTrigger, ComboboxContent, ComboboxItem, ComboboxInput) + barrel index.ts. Re-exported from shared/ui/index.ts by name. Variants: default, mono. Sizes: sm, md. Pure CSS via app.css tokens (--bg, --bg-1, --bg-2, --fg, --fg-1, --accent, --line, ...) — works across light/dark/orch themes with no caller intervention. A11y: role=combobox / aria-expanded / aria-controls on trigger, role=listbox on content, role=option / aria-selected on items, focus-visible accent ring matching Select pattern. Keyboard: arrow-up/down navigation, Enter to select, Escape to close. /playground showcases all 4 variant×size combinations under the existing primitive grid; theme toggle in playground header swaps visuals automatically. svelte-check: 1053 files / 0 errors / 0 warnings. Rule-24 grep: clean (no upper-layer reaches into primitive internals). Build green; dist-experimental at 2.47M (under 3M cap). Refs: PRD-028, RFC-024 Closes: #114 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
109d — register every running forgeplan-web instance in
~/.forgeplan-web/instances.json so the UI (Wave 6) can list and switch
between them. Single global registry regardless of scaffold scope.
bin/lib/registry.mjs (172 lines) — pure node:* helpers: registryPath,
readRegistry, writeRegistryAtomic (tmp+rename, symlink defence),
isAlive (process.kill(pid,0) + 60s heartbeat freshness, EPERM=alive on
Windows), sweepStale, register, deregister, heartbeat, findFreePort
(increment from PORT base, max +100, exclusive listen probe),
makeInstanceId. Rule 23 clean (citty + node:* + relative siblings only).
bin/commands/start.mjs — port allocator, register child PID,
30s heartbeat (unref'd), SIGINT/SIGTERM forwarding + child-exit
deregister chain. forgeplanCli probed once at start (null on failure).
template/src/shared/server/registry.ts — TypeScript readonly mirror with
2s in-memory cache + single-flight, schema validation per row,
liveness sweep on every refresh. No fs.write*, no spawn.
template/src/routes/api/instances/+server.ts — GET-only endpoint
returning { ok, data: { instances }, cmd: "registry:read" }.
Rule 22 amended to add /api/instances as the second non-forgeplan
endpoint (after /api/update-check), with strict no-mutation +
no-network constraints.
Verified: 2 instances on 17400+17401 register; SIGKILL on instance A
swept at next start; heartbeat ticks at 30.05s delta; deregister within
2s of SIGTERM. svelte-check 0/0/0. Smoke PASS.
Refs: PRD-027, RFC-023, SPEC-003, ADR-004
Closes: #113
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…115) 109f — wire the new Combobox primitive (Wave 4) into HealthBar to switch between running forgeplan-web instances using the registry endpoint (Wave 5). Closes the #109 chain. New entity slice template/src/entities/instance/: - model/types.ts — Instance, InstanceScope, InstancesPayload (copy of SPEC-003 v1; TODO(spec-003-keep-in-sync) marks the duplication so schema bumps update both sides) - api/store.ts — instancePoller via shared/api/createPoller, 5s cadence - index.ts — barrel HealthBar.svelte branching logic: - instances.length >= 2 → Combobox (mono, sm) listing all instances with projectName + host:port; current entry marked; selecting another window.location.replace - instances.length < 2 → preserve existing /{project} static span Self-detection via window.location.host matched against instance.id. SSR-safe (typeof window guard). Rule 24 clean — Combobox styled only via its own variant/size props; consumer styles are layout/wrapper only. svelte-check 0/0/0. Smoke PASS. Two-instance scenario manually verified (5174↔5175 switch); single-instance fallback verified post-deregister. Refs: PRD-029, RFC-025 Closes: #115 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave 7 — close-out for the multi-instance migration. Evidence collected from each Build wave (smoke + svelte-check + manual scenarios) and linked back to its driving PRD. All R_eff > 0; all 8 artifacts moved draft → active. EvidencePacks created: - EVID-029 → PRD-024 + ADR-003 + RFC-020 (citty refactor; smoke PASS) - EVID-030 → PRD-025 + RFC-021 + ADR-004 (init scope; non-TTY guard) - EVID-031 → PRD-026 + RFC-022 (start resolution chain) - EVID-032 → PRD-027 + RFC-023 + SPEC-003 + ADR-004 (registry; 2-instance + sweep + heartbeat verified) - EVID-033 → PRD-028 + RFC-024 (Combobox; 4 variant×size; check 0/0) - EVID-034 → PRD-029 + RFC-025 (HealthBar switcher; 5174↔5175 + fallback) Each evidence body carries Structured Fields (verdict: supports, congruence_level: 3, evidence_type: test) so R_eff parses cleanly. Activation: PRD-024..029, ADR-003, ADR-004 — all draft → active. forgeplan health is clean. Closes the rule 11 gate (R_eff > 0 + active) for #109 PR merge. Refs: PRD-024, PRD-025, PRD-026, PRD-027, PRD-028, PRD-029, ADR-003, ADR-004, EVID-029..034 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 8, 2026
fedorovvvv
added a commit
that referenced
this pull request
May 9, 2026
## Summary - Activate 7 RFCs/SPEC in cluster A whose parent PRDs already shipped active via #109/#118 (`draft → active`). - Deprecate stale PRD-001 (title/body mismatch — both interpretations shipped out-of-band). - Health: `79 → 86 active`, `16 → 8 draft`, `2 → 3 deprecated`. ## Why `forgeplan health` was misleading: 7 supporting RFC/SPEC artifacts plus 1 muddled PRD sat in `draft` with parents that were long `active` with `R_eff > 0`. This was process debt from the `forgeplan activate` step being skipped at the end of the multi-instance + citty waves. Pure artifact-lifecycle hygiene — no code change, no behaviour change. ### Cluster A (activated) | Artifact | Title | Parent (active) | |---|---|---| | RFC-020 | citty integration architecture — bin/ subcommand split | PRD-024 | | RFC-021 | Scope resolver + user-scope path conventions for init | PRD-025 | | RFC-022 | start scaffold-resolution algorithm + error UX | multi-instance wave | | RFC-023 | Port allocator, pid-liveness probe, atomic registry writes | PRD-027 | | RFC-024 | Combobox primitive — bits-ui wrapper, /playground showcase | PRD-029 | | RFC-025 | HealthBar instance switcher + /api/instances readonly endpoint | PRD-029 | | SPEC-003 | Instance registry JSON schema (`~/.forgeplan-web/instances.json`) | PRD-027 + ADR-004 | ### PRD-001 (deprecated) Title says "Show forgeplan + forgeplan-web versions in UI footer" — long shipped (`__FORGEPLAN_WEB_VERSION__` + `/api/health` integration). Body says "Bootstrap CLAUDE.md baseline + add methodological guides" — also shipped (Red lines, routing table, hook table, `guides/INDEX.md`/`CLAUDE-MD-GUIDE.ru.md`/`GIT-FLOW-GUIDE.ru.md` all present). The artifact's markdown was Lance-only (never in git history); deprecation surfaced it as a new file at `prds/PRD-001-…md` with `status: deprecated`. EVID-004/EVID-005 still link to it — they're frozen evidence, no rework needed. ## Test plan - [x] `forgeplan health` runs clean — `verdict: healthy`, no new orphans. - [x] Front-matter `status:` correct on all 8 files (`active` × 7, `deprecated` × 1). - [x] No code touched (verify via `git diff --stat origin/develop...HEAD`). - [ ] Reviewer: confirm no parent PRD lost evidence pointer (relations are append-only in markdown, but worth a glance at the diff). - [ ] CI: smoke matrix green (no functional change but rule-21/22/23 verifications still run). ## Reversibility Fully reversible: edit `status:` line back to `draft` in front-matter, run `forgeplan scan-import` to refresh Lance. No git surgery needed. ## Drift risks - **Lock contention** — `forgeplan activate` ran in parallel hit the workspace lock 4× during this PR's prep. The CLI is not concurrency-safe for one workspace. Future cluster operations should serialise; not blocking for this PR. - **PRD-001 dependents** — EVID-004/005 still claim to "support" PRD-001. Since PRD-001 is now `deprecated`, those evidence packs are formally orphaned-by-status. No automatic cleanup; leaving them as historical record. Refs: PRD-024, PRD-025, PRD-027, PRD-029, ADR-004
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #109. Sub-issues #110, #111, #112, #113, #114, #115 all closed.
Summary
Refactor
@forgeplan/webfor multi-instance use:bin/forgeplan-web.mjsshrinks 367→5 lines; subcommands live inbin/commands/{init,update,start}.mjs; helpers inbin/lib/. Single allow-listed dep (citty@0.2.2) per amended rule 23.--scope user|projectflag — init writes to<cwd>/.forgeplan-web/or~/.forgeplan-web/.init -ykeeps defaulting to project (compat); interactive prompt picks user as the new default. Non-TTY without flag fails clearly.startresolution chain — project → user → prompt-init. Explicit--scopeshort-circuits the chain.~/.forgeplan-web/instances.json— incremental ports, pid + heartbeat liveness, atomic tmp+rename writes, stale sweep on every start. New/api/instancesreadonly endpoint mirrors the registry to the UI.template/src/shared/ui/combobox/(bits-ui 2.18.1; default + mono variants × sm/md sizes; showcased on /playground; rule-24 clean).http://host:port. Single-instance behavior unchanged.Why
Original issue: install
forgeplan-webonce per user (not per project), run several instances simultaneously, switch between them from any instance's UI. Refined plan keeps backwards compat (--scope projectis still theinit -ydefault) so existing setups don't break.Forgeplan artifacts (all
active, R_eff > 0)bin/--scopeflag + user-scope pathRules touched (each via dedicated PRD/ADR):
.claude/rules/20-init-host-isolation.md— user-scope writes are gitignore-silent (PRD-025).claude/rules/22-readonly-proxy.md—/api/instancesallow-list extension (PRD-027).claude/rules/23-bin-zero-deps.md— citty named exception (ADR-003)Test plan
npm run smoke— PASS at every wave (citty refactor, scope wiring, registry, HealthBar)npm run build— svelte-check 1056 files / 0 errors / 0 warningsinit -y --scope userwrites only to~/.forgeplan-web/, host.gitignoreuntouchedinit -y(no flag) defaults to project (compat preserved)/api/instancesreturns standard envelope{ ok, data: { instances }, cmd: "registry:read" }forgeplan healthclean post-Wave-7citty+node:*+ relative siblings inbin/):global()reaches into Combobox internals)Out of scope (deferred follow-ups)
.forgeplan-web/→ user-scopeXDG_CONFIG_HOMEetc.) — RFC-021 keeps~/.forgeplan-web/for predictability--scope userpath + multi-instance scenario (current smoke covers single project-scope instance only)🤖 Generated with Claude Code