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 thevibelangcompiler on selected.tsand.vibeinputs, 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
.vibethe 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
.vibeitself (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 linttakes the compiler's options (-p/--projectand files) and no others.vibelang --fixstays 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 stableEK38xxcode, in a table generated from the pinned effect-tsgo version. Severity resolves fromvibe.severities, then the repository's Effect language-service configuration, then the rule's default; everyEK38xxcode 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, commit999942fa41462eb46dfcb2d718e7671db8e14156) is the normative pin;sync.sh <tag>is the only update path, and a VIP-32 bump runs it and regenerates theEK38xxtable, 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.jsonis generated from theUPSTREAMpin, currently@effect/tsgo@0.57.0. KeepEK3801forfloatingEffect; 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 theEK38prefix and a minimum-two-digit decimal ordinal: afterEK3899comesEK38100. This amends VIP-34's capacity rule for imported lint without reservingEK39xx(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 resolvedcompilerOptions.pluginsentry named@effect/language-serviceactivates the imported rules. Parse inheritance, options, native disable settings and file eligibility as the pinned upstream does;diagnostics: falseordiagnosticSeverity: nulldisables its diagnostics. Avibe.severitiesentry does not re-enable disabled diagnostics. Until theEK3012migration below retires the legacy shortcut, an assignedEK38xxkey in a config without a resolved plugin entry still activates the whole default plugin and reportsEK3012; 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 lintactivates, 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*InTscExitCodeoptions do not apply to it. This clarification follows the agent derivationdecide-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 decisiondecide-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: falseandignoreEffectSuggestionsInTscExitCode: true. Severity overrides leave these options unchanged. Before retiring severity-key activation, migration warningEK3012identifies a resolved config with an assignedEK38xxseverity key,"off"included, and no resolved@effect/language-serviceplugin entry. Ordinaryvibelang --fixand 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 --onlyremains 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 warningEK3012, 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, nativeTS377108): no plugin and no imported severity entry reports no imported diagnostics; no plugin with an imported severity entry reports the default plugin's diagnostics andEK3012before 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
noEmitOnErrorpolicy under the same effective rule, category, inclusion and ignore options; imported warnings affect both by default. The standalonevibelang lintcommand 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.