Starlight Effect v4 house conventions - namespace-wrapped service modules, layers, runtime assembly, typed errors, and ast-grep enforcement. Load when writing or reviewing any Effect TypeScript code in this repo.
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
House conventions for the Effect surfaces (apps/server/src/services, apps/server/src/ai, packages/api/src/services). For portable v4 API guidance (layers, HTTP client, concurrency, configuration), use the global effect-ts skill; this file only defines project-specific rules.
One service per file. All exports live inside one export namespace <CanonicalName> block that matches the consumer-facing name (usually the PascalCase file name); imports stay at module scope:
import { Context, Effect, Layer, Schema } from "effect";
import { FetchHttpClient, HttpClient } from "effect/unstable/http";
export namespace Name {
export interface Interface {
readonly method: (input: Input) => Effect.Effect<Output, ServiceError>;
}
export class Service extends Context.Service<Service, Interface>()("starlight/Name") {}
export const layer: Layer.Layer<Service, never, HttpClient.HttpClient> = Layer.effect(
Service,
Effect.gen(function* () {
const client = yield* HttpClient.HttpClient; // bind deps once, close over them
const helper = Effect.fn("Name.helper")(function* helper() {
/* ... */
});
return Service.of({
method: Effect.fn("Name.method")(function* method(input) {
/* ... */
}),
});
}),
);
export const defaultLayer: Layer.Layer<Service> = layer.pipe(Layer.provide(FetchHttpClient.layer));
}import { Exa } from "@/services/exa") and access Exa.Service, Exa.defaultLayer. Star imports (import * as X from "...") are forbidden and rejected by the oxlint import/no-namespace rule.await cannot live inside a namespace body; keep such bindings as module-scope private consts above the namespace and re-export them through it (ChatReply.systemPrompt).Schema.TaggedError classes with a static fromCause(input: { message; cause }).Effect.mapError; never leak raw HttpClient errors upward.Effect.fn("Module.methodName")(function* methodName(...)).(yield* SomeService).method(...) (ast-grep enforces this).ManagedRuntime per process: apps/server/src/services/runtime.ts merges every service's defaultLayer; packages/api/src/services/runtime.ts builds from EmbeddingsService.defaultLayer.Logger.formatStructured mapping; log with stable messages plus fields.bun run lint runs ast-grep over both rule sets alongside oxlint and typechecks.script/ast-grep/rules: no import aliases, no JSON.parse(x) as T, bind services before calling their methods, Effect.die only with new Error(...).script/ast-grep/effect-simplifications: Effect.orElseSucceed over catch + succeed, effect-form andThen over flatMap + suspend, Effect.as(undefined) over andThen + succeed(undefined).rule-tests/; verify with bun run test:ast-grep-rules.bun:test. Execute isolated effects with Effect.runPromise; use the shared app runtime's runPromise when integration wiring is the point.divaltor/starlight
3b609a5
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.