Skip to content
LogoLogo

VIP-38: Effect's type-aware lint is built into VibeLang

Status: accepted by Will, 2026-10-29.

Decision

  • The Effect language service's type-aware rules (effect-tsgo's effecttsgo/* rules) run inside the vibelang compiler on selected .ts and .vibe inputs, keeping the pinned upstream rule/file eligibility and declaration/library exclusions. VibeLang-specific language checks remain confined to .vibe. Both are typescript-go forks, so the rule runner is built in rather than run beside it.

  • They run wherever VibeLang reports diagnostics: the CLI, the language server, and a lint command for CI. A repo's existing Effect language-service configuration selects and configures them, so a repo's lint script covers .vibe the same way it covers .ts.

  • Tools that only analyze code (cycle checkers such as madge, and dependency graphs) may run on VibeLang's compiled output. That cannot work for formatters, which must read .vibe itself (VIP-34).

  • Amended 2026-10-29: the command. The lint command is vibelang lint. Sent to Will on Telegram 2026-10-29 11:28 under his standing rule 'assume I accept'; no objection by 12:00.

  • Amended 2026-10-29: lint options. vibelang lint takes the compiler's options (-p/--project and files) and no others. vibelang --fix stays the one codemod entry point. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 58).

  • Amended 2026-10-29: codes for the imported rules. Each imported effecttsgo/* rule gets one stable EK38xx code, in a table generated from the pinned effect-tsgo version. Severity resolves from vibe.severities, then the repository's Effect language-service configuration, then the rule's default; every EK38xx code may be set to any value, "off" included. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 77).

  • Amended 2026-10-29: the imported rules are pinned to @effect/tsgo@0.57.0. typescript/tsc/internal/effecttsgo/UPSTREAM (tag @effect/tsgo@0.57.0, commit 999942fa41462eb46dfcb2d718e7671db8e14156) is the normative pin; sync.sh <tag> is the only update path, and a VIP-32 bump runs it and regenerates the EK38xx table, keeping every assigned code. Decided by the agent on 2026-10-29 under Will's rule "when I state a principle, the agent derives the concrete rule and acts; I can override later" (decided detail 98).

  • Amended 2026-10-30: stable imported codes. The normative lint-codes.json is generated from the UPSTREAM pin, currently @effect/tsgo@0.57.0. Keep EK3801 for floatingEffect; allocate the other initial rule names in ascending ASCII order from ordinal 2. Future new names append in sorted order after the highest reserved ordinal, and removed names keep their slots. Codes use the EK38 prefix and a minimum-two-digit decimal ordinal: after EK3899 comes EK38100. This amends VIP-34's capacity rule for imported lint without reserving EK39xx (decided details 98, 106 and 142).

  • Amended 2026-11-03: compiler activation follows the pinned native plugin. In compiler builds, the language server and vibelang lint, the resolved compilerOptions.plugins entry named @effect/language-service activates the imported rules. Parse inheritance, options, native disable settings and file eligibility as the pinned upstream does; diagnostics: false or diagnosticSeverity: null disables its diagnostics. A vibe.severities entry does not re-enable disabled diagnostics. Until the EK3012 migration below retires the legacy shortcut, an assigned EK38xx key in a config without a resolved plugin entry still activates the whole default plugin and reports EK3012; after that retirement, such a key alone does not activate the plugin. Once the plugin's diagnostics are enabled, it overrides rule categories and may enable a default-off rule. vibelang lint activates, selects and configures the imported rules exactly as the compiler does for the same project and files; it emits no files, fails on errors, and exits 0 for warnings or suggestions alone. Native options retain their documented host scope; the compiler’s *InTscExitCode options do not apply to it. This clarification follows the agent derivation decide-live-r31-lint-unified-config.md, 2026-11-03, from this VIP’s one-configuration and compiler-options-only rules. This applies accepted VIP-38's native configuration contract and Will's standing TypeScript rule, "It is already implied we match typescript" (WILL-ANSWERS.md, 2026-11-03), as derived in the agent decision decide-live-r30-lint-activation.md; it is not a new personal answer from Will.

    The pinned defaults are diagnostics: true, includeSuggestionsInTsc: true, ignoreEffectWarningsInTscExitCode: false, ignoreEffectErrorsInTscExitCode: false and ignoreEffectSuggestionsInTscExitCode: true. Severity overrides leave these options unchanged. Before retiring severity-key activation, migration warning EK3012 identifies a resolved config with an assigned EK38xx severity key, "off" included, and no resolved @effect/language-service plugin entry. Ordinary vibelang --fix and both migration scope flags write the plain default plugin entry in the selected project’s own config, retaining inherited plugin entries, explicit disables, severities, JSONC comments and unrelated settings. The legacy shortcut enabled the whole default plugin even for an "off" key; add no per-rule suppression. Selected-file --fix --only remains unable to write configs. Follow VIP-34’s ownership and refusal rules and chapter 7’s migration procedure. This operational clarification and configuration-warning allocation derive from this VIP’s preserving-transition requirement under VIP-34, 2026-11-03; they are not a new personal answer from Will. Apart from the migration warning EK3012, prove equal diagnostic categories and inclusion, exit status and emission before and after; never add ignore flags to make migration pass.

    Conformance uses an actual pinned rule witness, such as promiseInEffectSuccess (EK3897, native TS377108): no plugin and no imported severity entry reports no imported diagnostics; no plugin with an imported severity entry reports the default plugin's diagnostics and EK3012 before the retirement, and no imported diagnostics after it; an explicit default plugin reports the warning; native disable settings plus a severity override remain disabled. Controls cover inherited configuration, per-rule "off", native suggestion/error/warning options, the same imported diagnostics under standalone lint, with and without the plugin and the migration above.

  • Amended 2026-11-03: imported Effect diagnostics keep the pinned compiler's exit policy. VibeLang's own warnings do not change compiler exit status unless configured as errors. Compiler diagnostics imported under VIP-38 retain the pinned native Effect TypeScript compiler's exit and noEmitOnError policy under the same effective rule, category, inclusion and ignore options; imported warnings affect both by default. The standalone vibelang lint command fails only on errors; warnings or suggestions alone exit 0. This narrows the unqualified 2026-10-30 warning rule in VIP-34 (decided detail 71); it does not restate that old rule as already limited to VibeLang diagnostics. The agent's R3 decision (decide-live-r3-warning-scope.md) applies Will's standing direction, "It is already implied we match typescript" (WILL-ANSWERS.md, 2026-11-03), to the measured pinned compiler. It is an agent derivation from that direction, not a new personal answer from Will.

Will's words

"1. The effect ts go should absolutely be built into Vibelang in fact this was always supposed to be part of the spec"

"Some of these can work via just compiling vibe and then running the tool. Obv that doesn't work for formatters though"

Context

The tool-risk report (2026-10-29, ~/migrate/TOOL-RISK.md on nyc-02) found type-aware Effect lint was the one tool with no workaround: it blocks alchemy's lint and foldkit's lint:effect.