Skip to content

Effect-ification: Instance deleted, 0 ALS fallbacks, 81 TUI tests#21

Merged
e6qu merged 33 commits intodevfrom
effect/complete-effectification
Mar 20, 2026
Merged

Effect-ification: Instance deleted, 0 ALS fallbacks, 81 TUI tests#21
e6qu merged 33 commits intodevfrom
effect/complete-effectification

Conversation

@e6qu
Copy link
Copy Markdown
Owner

@e6qu e6qu commented Mar 20, 2026

Summary

Complete Effect-ification of the Instance module — 33 commits across 181 files.

  • Delete src/project/instance.ts — zero src/ imports. Test-only shim at test/fixture/instance-shim.ts
  • Eliminate all 59 ?? InstanceALS.x fallback patterns — every module now receives directory/worktree/projectID explicitly
  • Add 81 TUI component tests — helpers, 5 dialog tests, 3 standalone tests, plus tmux integration harness
  • 1447 tests passing, 0 TS errors, 0 failures

Instance decoupling (B1-B10g)

Instance split into three modules:

  • InstanceALS — AsyncLocalStorage context propagation (directory, worktree, project)
  • InstanceLifecycle — boot/dispose/reload with caching
  • InstanceContext — Effect layer bridge

ALS fallback elimination (59/59)

Batch Modules Fallbacks
Leaf state() 15 modules + runPromiseInstance 15
Non-state leaf command, mcp, status, config 8
Session core system, instruction, compaction, llm, index, prompt 20
Worktree + tools worktree, pty, bash 6
Wide-caller env (25 callers), plugin (31), bus (78) 10

TUI tests

  • test/cli/tui/helpers.tsx — reusable mock factories for all TUI contexts
  • 5 dialog UI tests: agent, model, theme-list, session-list, skill
  • 3 standalone UI tests: logo, spinner, tips
  • test/cli/tui/tmux-tui-test.ts — tmux-based integration harness (5 flows)

Manual testing (2026-03-20)

All TUI flows verified: home screen, command palette, agent cycling, message submission, cost dialog.

Test plan

  • bun test — 1447 pass, 0 fail, 8 skipped
  • npx tsc --noEmit — 0 errors
  • grep '?? InstanceALS' src/ — 0 matches
  • grep 'project/instance"' src/ — 0 matches
  • Manual TUI testing via tmux — all flows pass

Adrian Mârza added 30 commits March 18, 2026 15:00
…e Instance.* from tool layer (B2)

Tool.Context and Tool.InitContext now carry directory/worktree/projectID/containsPath
fields. All 17 tool execute() handlers read from ctx instead of Instance singleton.
Construction sites in prompt.ts and registry.ts populate the new fields.
12 test files updated with mock context. New service-layers.ts and tui-service.ts
support the Effect layer infrastructure.

~20 Instance.* occurrences removed from tool layer.
1423 tests pass, 0 failures.
…ading (B3)

Add optional `directory` parameter to internal state() functions in env,
bus, command, provider, plugin, mcp, pty, and agent modules. Each state()
now accepts explicit directory with ALS fallback (directory ?? Instance.directory).

- scripts.ts: fully Instance-free, uses ctx.directory from tool context
- bus: publish/subscribe/once/subscribeAll gain optional directory param
- env: get/set/all/remove gain optional directory param
- command: get/list gain optional directory param, worktree captured at init
- agent: worktree captured at init time instead of lazy ALS read
- plugin: state/initPlugins/trigger/list/init gain optional directory param
- mcp: state/create gain directory param, threaded through initialization
- pty: state() parameterized (bind sites deferred to B4)
- EnvService/BusService layers: read from InstanceContext instead of Instance

7 direct Instance reads removed, 17 expected remnants (fallbacks, worktree, bind).
1423 tests pass, 0 failures.
…(B4)

Replace all 5 Instance.bind() call sites with plain closures that capture
directory from the surrounding scope (InstanceContext or local variable).
Callbacks now pass directory explicitly to Bus.publish/subscribe.

- file/watcher.ts: capture directory from InstanceContext, pass to Bus.publish
- project/vcs.ts: capture directory, pass to Bus.subscribe and Bus.publish
- format/index.ts: capture directory, pass to Bus.subscribe
- pty/index.ts: capture directory in create(), pass to Bus.publish and remove()
- file/index.ts: replace Instance.containsPath with local containsPath using
  instance.directory and instance.project.worktree from InstanceContext

Instance import removed from watcher.ts, vcs.ts, format/index.ts, file/index.ts.
1423 tests pass, 0 failures.
…s (B5)

Change Formatter.Info.enabled() signature to accept (directory, worktree)
parameters. All 25 formatter enabled() functions updated. Callers in
FormatService.layer pass instance.directory and instance.project.worktree
from InstanceContext.

Instance import removed from formatter.ts.
8 Instance.* occurrences eliminated.
1423 tests pass, 0 failures.
Add directory/worktree parameters to LSPServer.Info.root() and spawn()
interfaces. All 37 server definitions updated. NearestRoot helper takes
directory param instead of reading Instance.directory.

- server.ts: Instance import removed, all 11 Instance refs eliminated
- index.ts: state() parameterized, getClients/hasClients capture
  directory/worktree from Instance (ALS fallback for now)
- client.ts: create() takes directory param, Instance import removed

16 Instance occurrences eliminated across 3 files.
1423 tests pass, 0 failures.
…atus, compaction, llm (B7)

Add optional directory/worktree/project parameters to session module
leaf helpers with ALS fallback:

- system.ts: environment() takes optional ctx with directory/worktree/project
- instruction.ts: state(), systemPaths(), resolve(), resolveRelative() parameterized
  InstructionService.layer reads from InstanceContext
- status.ts: state() parameterized, SessionStatusService reads InstanceContext
- compaction.ts: process() takes optional directory/worktree
- llm.ts: StreamInput gains projectID field, header uses it with ALS fallback

prompt.ts and index.ts construction sites deferred to B10.
14 Instance.* occurrences parameterized with ALS fallback.
1423 tests pass, 0 failures.
Worktree module: capture Instance.worktree/project at function entry
in makeWorktreeInfo, createFromInfo, remove, reset. Internal candidate()
takes explicit worktree param. 21 → 9 Instance refs (all ALS fallbacks
or Instance.provide for B10).

Config module: state() parameterized, initConfig() captures directory/worktree
at entry. TUI config: state() parameterized, initTuiConfig() captures at entry.
migrate-tui-config: opencodeFiles() takes optional directory/worktree.

~25 direct Instance reads replaced with captured values or ALS fallbacks.
1423 tests pass, 0 failures.
STATUS.md: added Effect-ification progress table (B1-B8 done),
  list of Instance-free and ALS-fallback modules
PLAN.md: updated status line, added B2-B10 to completed/next table
WHAT_WE_DID.md: added Phase 7 section with B1-B8 commit table
DO_NEXT.md: replaced stale backport items with B9-B10 checklist
…on/tool APIs (B9)

Session: createNext, plan, list accept optional directory/project/worktree
params with ALS fallback. Removed dead Instance.project ref in remove().
Server routes: capture Instance values at handler entry.
CLI commands: capture Instance values inside provide callbacks.
tool/registry: parameterize state() with optional directory.
project/bootstrap: capture directory and projectID at entry.
debug/agent: add missing context fields (directory, worktree, projectID).
…nce ALS (B10a-b)

service-layers.ts: all 10 layer constructors use yield* InstanceContext
instead of Instance.directory. runtime.ts: runPromiseInstance accepts
optional directory param. instances.ts: lookup uses side-map with
Instance.current fallback; Instances.get accepts optional context shape.
… (B10c)

Prompt functions (loop, resolveTools, createUserMessage, shell) capture
Instance values at entry and use locals throughout. resolveTools accepts
optional directory/worktree/projectID params with ALS fallback. Deep tool
context construction sites use captured locals instead of Instance directly.
Also adds pre-existing-failures policy to AGENTS.md.
Separate context propagation (ALS) from bootstrap/lifecycle concerns.
InstanceALS provides directory, worktree, project, current, containsPath,
run, and bind — no cache, no bootstrap, no disposal. Instance delegates
all ALS reads to InstanceALS internally. Zero behavioral change.
Pass explicit directory to Bus.publish calls inside Database.effect
closures where Session.Info.directory is in scope. Eliminates ~13 ALS
fallbacks in session CRUD operations (touch, create, share, setTitle,
setArchived, setPermission, setRevert, clearRevert, setSummary, remove).
Add optional directory param to SessionStatus.set/get/list so callers
with captured directory can bypass ALS. Thread _dir through Bus.publish
and SessionStatus.set calls in prompt.ts loop and createUserMessage.
Pass input.directory to Bus.publish in compaction.ts.
Extract boot/cache/dispose/disposeAll/reload logic from Instance into
InstanceLifecycle. Instance.provide becomes a thin wrapper calling
InstanceLifecycle.boot + InstanceALS.run. Zero behavioral change.
…ecycle (B10f-2)

Replace Instance.provide with InstanceLifecycle.boot + InstanceALS.run
across all entry points: CLI commands (agent, mcp, github, pr, models,
providers), TUI (worker, attach, thread), server (server.ts, routes),
control-plane workspace server, worktree, config, and cli/bootstrap.

Replace Instance.dispose with InstanceLifecycle.dispose(directory),
Instance.disposeAll with InstanceLifecycle.disposeAll(), and
Instance.reload with InstanceLifecycle.reload. Instance module now
has zero callers for lifecycle operations.
Replace all Instance.directory/worktree/project/current/containsPath
reads with InstanceALS equivalents across 40 files. Zero imports of
Instance remain — the module is now unused and ready for deletion.
Instance module is now a thin shim delegating to InstanceALS + InstanceLifecycle.
Zero src/ code imports Instance — only test fixtures use it. The three
concerns are fully decoupled:
- InstanceALS: context propagation (ALS reads)
- InstanceLifecycle: boot/dispose/reload (cache + lifecycle)
- InstanceContext: Effect service layer bridge

Fix duplicate InstanceALS import in worktree/index.ts.
…ce.ts

Move Instance compatibility shim from src/project/instance.ts to
test/fixture/instance-shim.ts. Update all 58 test file imports.
Delete the src/ copy — zero src/ code references Instance now.

The shim delegates to InstanceALS (context) + InstanceLifecycle
(boot/dispose/reload) and is kept only because 58 test files use
Instance.provide({ directory, init?, fn }) which would be a
fragile mechanical rewrite to inline.
Remove ?? InstanceALS.directory fallbacks from 15 leaf module
state() functions. Callers now pass directory explicitly.

Also makes runPromiseInstance() directory param required and
updates all 10 call sites in src/ to pass InstanceALS.directory.
…te-tui-config)

Make directory required in Command.get/list, MCP local transport,
SessionStatus.get/list/set, and MigrateInput. Update all callers.

36 fallbacks remain (session core, worktree, env, bus, plugin, pty, bash).
New test infrastructure:
- test/cli/tui/helpers.tsx: reusable mock factories for all TUI contexts
  (theme, dialog, sync, sdk, local, route, keybind, kv, toast)

Dialog tests (5 files, 15 tests):
- dialog-agent, dialog-model, dialog-theme-list, dialog-session-list,
  dialog-skill — each with 3 test cases

Standalone component tests (3 files, 9 tests):
- logo, spinner, tips — verify render output

Total: 81 TUI tests across 13 files (was 56 across 5).
Launches frankencode in a tmux session, sends keystrokes, captures
terminal frames, and asserts on visible content.

Flows tested: home splash, command palette, agent cycling,
message submission + LLM response, cost dialog.

Usage: bun test/cli/tui/tmux-tui-test.ts [--flow <name>]
PLAN.md: mark Effect-ification complete, note 36 deferred fallbacks
STATUS.md: accurate counts (1447 tests, 123 files, 36 fallbacks,
  150 entry-point reads, 0 TS errors, 27 commits on branch)
DO_NEXT.md: itemized remaining fallbacks by module with caller counts
BUGS.md: add manual TUI testing results (2026-03-20, all pass)
Make directory/worktree/projectID/ctx required in:
- session/system.ts (3): environment() ctx parameter
- session/instruction.ts (5): resolveRelative, state, systemPaths, resolve
- session/compaction.ts (2): process() directory/worktree
- session/llm.ts (1): stream() projectID
- session/index.ts (4): createNext, plan, list project param
- session/prompt.ts (5): resolvePromptParts, cancel, resolveTools

16 fallbacks remain (worktree, pty, bash, env, bus, plugin).
Adrian Mârza added 3 commits March 20, 2026 10:50
- worktree/index.ts: make ctx required in makeWorktreeInfo, createFromInfo
- pty/index.ts: make directory required in remove()
- tool/bash.ts: guard initCtx.directory, remove InstanceALS import

10 fallbacks remain (env: 4, plugin: 4, bus: 2).
env/index.ts (4): make directory required in get/all/set/remove
plugin/index.ts (4): make directory required in trigger/list/init
bus/index.ts (2): make directory required in publish/subscribe

Updated 25 Env callers, 31 Plugin callers, 78 Bus callers across
29 src/ files and 9 test files.

Total: 59 of 59 ALS fallback patterns eliminated.
@e6qu e6qu merged commit c151041 into dev Mar 20, 2026
1 check passed
@e6qu e6qu deleted the effect/complete-effectification branch March 20, 2026 09:50
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