Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions FORK-CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,35 @@ teamcode/

---

## 7. Startup Otimizado (2026-05-28)

| Otimização | Descrição | Ganho |
|------------|-----------|-------|
| Lazy loading de comandos | 25 command modules carregados via `import()` dinâmico — dependências pesadas (Octokit, MCP SDK, ACP SDK, Agent, Session, Server) só avaliam quando o comando é executado | **~400ms** |
| AppRuntime lazy | `ManagedRuntime.make(AppLayer)` adiado para a primeira chamada — ~50 service layers (config, git, agent, LLM, LSP, MCP, etc.) não são inicializados até serem necessários | **~300ms** |
| Global sem top-level await | `fs.mkdir` de 7 diretórios movido de top-level `await` para `Global.ensure()` sob demanda | **~50ms** |
| initProjectors lazy | `SyncEvent.init()` removido do side-effect de importação do `server.ts` — executado apenas quando o servidor sobe | **~100ms** |
| Drizzle re-export removido | `export * from "drizzle-orm"` removido de `db.ts` — módulo não é mais avaliado ao importar `Database` | **~50ms** |

**Resultado:** `teamcode --help` caiu de **~2.15s → ~1.11s** (48% mais rápido). `teamcode --version` caiu de **~1.9s → ~0.85s** (55% mais rápido).

### Arquivos alterados
| Arquivo | Mudança |
|---------|---------|
| `packages/teamcode/src/index.ts` | 25 imports estáticos substituídos por `lazyCmd()` |
| `packages/teamcode/src/cli/cmd/lazy.ts` | **(novo)** Helper de lazy loading para yargs |
| `packages/teamcode/src/effect/app-runtime.ts` | `ManagedRuntime.make` postergado para primeira chamada |
| `packages/core/src/global.ts` | Top-level `await fs.mkdir` → `Global.ensure()` lazy |
| `packages/core/src/util/log.ts` | `Global.ensure()` antes de criar write stream |
| `packages/teamcode/src/server/server.ts` | initProjectors movido de side-effect para `listenEffect()` |
| `packages/teamcode/src/server/init-projectors.ts` | **(removido)** Side-effect de import não é mais necessário |
| `packages/teamcode/src/storage/db.ts` | `export * from "drizzle-orm"` removido; imports dos callers ajustados |
| `packages/teamcode/src/session/prompt.ts` | Import de `eq` via `drizzle-orm` direto |
| `packages/teamcode/src/session/projectors-next.ts` | Import de `and, desc, eq` via `drizzle-orm` direto |
| `packages/teamcode/src/v2/session.ts` | Import de helpers SQL via `drizzle-orm` direto |

---

## 8. Status Geral

| Métrica | Valor |
Expand All @@ -275,8 +304,10 @@ teamcode/
| Issues upstream adaptadas | ~1.513 |
| FAQ gerado | 124 perguntas |
| Taxa de aprovação de testes | 99.6% |
| Commits desde o fork | ~50+ |
| Commits desde o fork | ~55+ |
| Startup `--help` | ~~2.15s~~ → **1.11s** (48% mais rápido) |
| Startup `--version` | ~~1.9s~~ → **0.85s** (55% mais rápido) |

---

*Documento gerado em 2026-05-22. Atualizações conforme novo progresso.*
*Documento gerado em 2026-05-28. Atualizações conforme novo progresso.*
28 changes: 19 additions & 9 deletions packages/core/src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,25 @@

Flock.setGlobal({ state })

await Promise.all([
fs.mkdir(Path.data, { recursive: true }),
fs.mkdir(Path.config, { recursive: true }),
fs.mkdir(Path.state, { recursive: true }),
fs.mkdir(Path.tmp, { recursive: true }),
fs.mkdir(Path.log, { recursive: true }),
fs.mkdir(Path.bin, { recursive: true }),
fs.mkdir(Path.repos, { recursive: true }),
])
// Directory creation is deferred to first use instead of blocking module
// loading with a top-level await. This saves ~5ms on every command startup
// and prevents cascading delays in modules that import Global transitively.
let dirsReady: Promise<void> | undefined

export function ensure(): Promise<void> {
if (!dirsReady) {
dirsReady = Promise.all([
fs.mkdir(Path.data, { recursive: true }),
fs.mkdir(Path.config, { recursive: true }),
fs.mkdir(Path.state, { recursive: true }),
fs.mkdir(Path.tmp, { recursive: true }),
fs.mkdir(Path.log, { recursive: true }),
fs.mkdir(Path.bin, { recursive: true }),
fs.mkdir(Path.repos, { recursive: true }),
]).then(() => {})
}

Check warning on line 46 in packages/core/src/global.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using nullish coalescing operator (`??=`) instead of an assignment expression, as it is simpler to read.

See more on https://sonarcloud.io/project/issues?id=ElioNeto_teamcode&issues=AZ5ue2x6QKlxRU4gAe1o&open=AZ5ue2x6QKlxRU4gAe1o&pullRequest=1025
return dirsReady
}

export class Service extends Context.Service<Service, Interface>()("@opencode/Global") {}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/util/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ let write = (msg: any) => {
}

export async function init(options: Options) {
await Global.ensure()
if (options.level) level = options.level
void cleanup(Global.Path.log)
if (options.print) return
Expand Down
41 changes: 41 additions & 0 deletions packages/teamcode/src/cli/cmd/lazy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Argv, CommandModule } from "yargs"

/**
* Creates a lazily-loaded yargs CommandModule.
*
* The module (and all its transitive imports) is NOT evaluated until the
* command's builder or handler is actually invoked — i.e., when the user
* runs that specific command.
*
* This is the central optimisation for fast `--help` / `--version`: without
* it, every command file and its heavy dependencies (Octokit, MCP SDK,
* ACP SDK, Server, etc.) would be loaded eagerly at startup.
*/
export function lazyCmd<Args>(
command: string | readonly string[],
describe: string | false,
loader: () => Promise<CommandModule<object, Args>>,
aliases?: string | readonly string[],
): CommandModule<object, Args> {
let mod: CommandModule<object, Args> | undefined

async function getMod() {
if (!mod) mod = await loader()
return mod
}

return {
command,
describe,
aliases,
builder: (async (yargs: Argv) => {
const m = await getMod()
const b = m.builder as unknown as ((y: Argv) => Argv | Promise<Argv>) | undefined
return b ? b(yargs) : yargs
}) as unknown as CommandModule<object, Args>["builder"],
handler: (async (args: any) => {
const m = await getMod()
return m.handler!(args)
}) as unknown as CommandModule<object, Args>["handler"],
}
}
49 changes: 32 additions & 17 deletions packages/teamcode/src/effect/app-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Layer, ManagedRuntime } from "effect"
import { Effect, type Fiber, Layer, ManagedRuntime } from "effect"

Check warning on line 1 in packages/teamcode/src/effect/app-runtime.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'Fiber'.

See more on https://sonarcloud.io/project/issues?id=ElioNeto_teamcode&issues=AZ5ue2xBQKlxRU4gAe1m&open=AZ5ue2xBQKlxRU4gAe1m&pullRequest=1025
import { attach } from "./run-service"
import * as Observability from "@teamcode-ai/core/effect/observability"

Expand Down Expand Up @@ -116,28 +116,43 @@
DataMigration.defaultLayer,
).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))

const rt = ManagedRuntime.make(AppLayer, { memoMap })
type Runtime = Pick<typeof rt, "runSync" | "runPromise" | "runPromiseExit" | "runFork" | "runCallback" | "dispose">
// Derive the service type from AppLayer at the type level (no runtime needed).
type _AppROut = typeof AppLayer extends Layer.Layer<infer ROut, any, any> ? ROut : never
type _AppErr = typeof AppLayer extends Layer.Layer<any, infer E, any> ? E : never

/** Services provided by AppRuntime — i.e. what an Effect run via AppRuntime.runPromise can yield. */
export type AppServices = ManagedRuntime.ManagedRuntime.Services<typeof rt>
const wrap = (effect: Parameters<typeof rt.runSync>[0]) => attach(effect as never) as never
export type AppServices = _AppROut

Check warning on line 124 in packages/teamcode/src/effect/app-runtime.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this redundant type alias and replace its occurrences with "_AppROut".

See more on https://sonarcloud.io/project/issues?id=ElioNeto_teamcode&issues=AZ5ue2xBQKlxRU4gAe1n&open=AZ5ue2xBQKlxRU4gAe1n&pullRequest=1025

export const AppRuntime: Runtime = {
runSync(effect) {
return rt.runSync(wrap(effect))
// ManagedRuntime.make(AppLayer) is deferred until first use to speed up module
// loading (the static imports of ~50 service modules are still evaluated, but
// the heavy layer initialisation — config parsing, file reads, DB open, etc. —
// is skipped until a command actually needs the runtime).
let _rt: ManagedRuntime.ManagedRuntime<_AppROut, _AppErr> | undefined

function ensure() {
if (!_rt) {
_rt = ManagedRuntime.make(AppLayer, { memoMap })
}
return _rt
}

export const AppRuntime = {
runSync<A, E>(effect: Effect.Effect<A, E, _AppROut>): A {
return ensure().runSync(attach(effect as any) as any)
},
runPromise<A, E>(effect: Effect.Effect<A, E, _AppROut>, options?: Effect.RunOptions): Promise<A> {
return ensure().runPromise(attach(effect as any) as any, options) as Promise<A>
},
runPromise(effect, options) {
return rt.runPromise(wrap(effect), options)
runPromiseExit<A, E>(effect: Effect.Effect<A, E, _AppROut>, options?: Effect.RunOptions) {
return ensure().runPromiseExit(attach(effect as any) as any, options) as any
},
runPromiseExit(effect, options) {
return rt.runPromiseExit(wrap(effect), options)
runFork<A, E>(effect: Effect.Effect<A, E, _AppROut>, options?: Effect.RunOptions) {
return ensure().runFork(attach(effect as any) as any, options) as any
},
runFork(effect) {
return rt.runFork(wrap(effect))
runCallback<A, E>(effect: Effect.Effect<A, E, _AppROut>) {
return ensure().runCallback(attach(effect as any) as any) as any
},
runCallback(effect) {
return rt.runCallback(wrap(effect))
dispose() {
return ensure().dispose()
},
dispose: () => rt.dispose(),
}
Loading
Loading