feat(enforcement): typed OutputMode and EnforcementStage enum taxonomy (ENF-MODE)#20
Merged
Conversation
…e_kinds Extends the enforcement-plane enum taxonomy with two new str enums: OutputMode (5 values) to classify what artifact family an adapter emits, and EnforcementStage (3 values) to identify the gate at which enforcement runs. Placed alongside ClauseKind in clause_kinds.py — the shared enum module for the enforcement plane — so all pipeline and reporting code can import from one location. str,Enum idiom preserves backward-compatible string comparisons throughout the codebase.
The two optional properties on BaseAdapter previously returned list[str] with a comment noting ENF-MODE would formalise the values. Now that OutputMode and EnforcementStage exist in clause_kinds, replace the provisional string returns with the proper enum types. Because both are str-enum subclasses, all downstream string comparisons remain valid without modification.
…enums Replace list[str] returns with list[OutputMode] / list[EnforcementStage] across all 5 concrete adapters (ESLint, Ruff, Mypy, Tsconfig, ImportLinter) to surface strong types at the adapter boundary instead of raw strings. Also corrects ImportLinterAdapter.output_modes from NATIVE_CONFIG to NATIVE_RULES: import-linter appends contract entries to existing config rather than owning a whole config file, matching the spec table definition of native_rules. Adds TestAdapterOutputModes and TestAdapterEnforcementStages test classes (12 new assertions) covering the typed returns and confirming enum values remain str-compatible for downstream serialisation.
…dFragment ConfigFragment (dataclass in base.py) gains output_mode: OutputMode with default NATIVE_CONFIG. AppliedFragment (Pydantic model in pipeline.py) gains output_mode: str with default "native_config" — kept as str to avoid importing enforcement enums into the result model and to keep JSON serialization trivial. _write_fragment bridges them via fragment.output_mode.value. Tests cover default construction, explicit NATIVE_RULES, and script_fallback for both classes (453 passed, 5 new).
Extracts fallback promptlet generation into a proper BaseAdapter subclass (output_modes=[OutputMode.SCRIPT_FALLBACK]) so the output mode taxonomy is complete and the logic is testable in isolation. Pipeline Stage 4.5 now delegates to FallbackAdapter.generate_fragments instead of the private _build_fallback_promptlet side path. Backward compat preserved: result.fallback_promptlets is still populated; unroutable keys also appear in result.fragments_applied with output_mode='script_fallback'. _build_fallback_promptlet is kept in place for backward compat.
…d_stages with enum types Closes the type chain started in earlier steps: EnforcementStage and OutputMode are now reflected as typed lists in RoutingDecision rather than list[str]. No runtime behavior changes since both enums inherit from str. Tests verify that ESLintAdapter decisions carry OutputMode/EnforcementStage instances and that ImportLinterAdapter returns NATIVE_RULES in its output_modes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The enforcement plane lacked a formal vocabulary for what kind of artifact an adapter emits and at what gate it runs. Downstream code (pipeline, reporter, routing) was using plain strings with no type safety, making it easy to introduce typos or drift from the spec taxonomy silently.
Approach
Introduced two
str,Enumsubclasses —OutputMode(5 values: native_config, native_rules, ci_config, git_hook, script_fallback) andEnforcementStage(3 values: pre_commit, ci, runtime) — in the existingclause_kinds.pymodule so all enforcement code imports from one place.All five concrete adapters (ESLint, Ruff, Mypy, Tsconfig, ImportLinter) now return typed lists instead of
list[str].ConfigFragmentandAppliedFragmentcarryoutput_modeat the fragment level so the output type flows through the pipeline into results.RoutingDecisioncloses the chain at the routing layer.FallbackAdapteris extracted as a properBaseAdaptersubclass (replacing an inline private method) so theSCRIPT_FALLBACKoutput mode is fully represented in the taxonomy and testable in isolation. The_build_fallback_promptletprivate path is kept for backward compat.str,Enuminheritance means all existing string comparisons remain valid without modification — this is purely additive typing.One correctness fix:
ImportLinterAdapter.output_modeswas returningNATIVE_CONFIG; corrected toNATIVE_RULES(import-linter appends contracts to config rather than owning a whole file).What Was Tested
ConfigFragmentandAppliedFragmenttested for default construction, explicitNATIVE_RULES, andscript_fallbackFallbackAdaptercovered in isolation via the existing adapter test classesRoutingDecisionverified to carryOutputMode/EnforcementStageinstances for ESLint and ImportLinterRisks
Additive changes only — no existing public API modified. The
str,Enumapproach guarantees no downstream breakage. TheImportLinterAdapter.output_modescorrection is technically a behavior change but was previously wrong per the spec; no code depended on the incorrect value.