You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reduce packages/skilllint/plugin_validator.py from a multi-responsibility central module into a compatibility facade over smaller owner modules, without breaking current public/internal imports during the migration.
Current evidence
On main, plugin_validator.py is roughly 4,930 lines and contains dozens of top-level functions and classes spanning:
shared validation models/contracts;
policy/config/suppression;
frontmatter/YAML repair;
validators;
fix orchestration;
adapter/platform routing;
CLI commands;
runtime orchestration;
compatibility exports.
Many tests, rule modules, scripts, and examples import types or behavior from skilllint.plugin_validator, so a big-bang move would create unnecessary churn.
Current-main reconciliation — 2026-09-29
Current main has already completed the contract-first foundation:
the import/dependency inventory and compatibility requirements have been exercised by the earlier decomposition work;
shared validation contracts now originate in packages/skilllint/models.py;
skilllint.plugin_validator re-exports those contracts for compatibility;
scan_runtime.py and reporting.py already own their extracted runtime/reporting seams.
Policy/config ownership is complete via PR #301. PR #302 then made explicit-platform routing declaration-driven without combining a #283 extraction slice: adapter rule routing still lives in plugin_validator.py, while discovery remains in scan_runtime.py.
Current-main reconciliation — 2026-09-30 after #302
The next coherent #283 slice is fix authorization and execution orchestration (Slice D1), not validation routing or CLI extraction.
Current ownership evidence on main:
FIXER_TRIGGER_CODES and get_fixer_trigger_codes() live in plugin_validator.py and define fail-closed rule-to-fixer authorization.
validate_single_path() owns the generic fix loop: trigger intersection, can_fix() gating, ordered fix() calls, AppliedFix recording, and revalidation.
_get_fixers_for_path() still selects the reporting validators plus the fix-only NameFormatValidator, preserving an important ordering contract.
safe_load_yaml_with_colon_fix() is also still in the legacy module and has lazy imports from rule/boundary code; moving it casually with the coordinator would create unnecessary dependency/cycle risk.
Recommended next slice:
Add a dependency-light fixing.py owner for FIXER_TRIGGER_CODES, get_fixer_trigger_codes(), and a generic coordinator that receives an already ordered fixer sequence plus raw_codes, executes authorized fixes, records AppliedFix, and returns whether revalidation is needed.
Keep _get_fixers_for_path() and concrete validator fix() implementations in place for this slice. That avoids importing validator classes back into fixing.py and keeps validator selection for Slice E.
Preserve plugin_validator compatibility re-exports for moved names.
Keep the existing fixer-gating tests as the primary contract; add only the import-identity/coordinator evidence needed to prove the new owner is active.
A later Slice D2 can assess frontmatter mutation helpers separately once the generic coordinator is no longer embedded in validation dispatch. Slice E can then extract validator selection/collection and validate_file / validate_single_path around a smaller, already-separated fixing seam.
Current-main reconciliation — 2026-09-30 after #303/#304
PR #303 completed Slice D1: fixing.py now owns fail-closed fixer authorization and generic ordered execution while plugin_validator preserves compatibility re-exports. PR #304 completed the coherent D2 syntax seam: frontmatter_yaml.py owns YAML parsing/repair primitives and rule/boundary YAML recovery no longer reaches into the legacy validator.
A direct Slice E move of _get_validators_for_path, _collect_validator_results, validate_single_path, and validate_file is still premature. Concrete validator classes remain owned by plugin_validator.py, and several rule modules still perform deferred reverse imports from that module for unrelated utilities. Moving validation orchestration now would create or conceal a validation -> plugin_validator -> rules -> validation/plugin_validator dependency cycle.
The next coherent prerequisite is Slice E0 — remove rule-layer reverse dependencies on the legacy module without changing behavior:
Move SKILL.md document parsing to the existing frontmatter parsing owner and preserve the plugin_validator.parse_skill_md compatibility alias.
Move plugin-root/marketplace-root ancestry lookup to scan_runtime.py, which already owns path discovery, and preserve legacy aliases.
Put the frontmatter-exempt filename constant with the frontmatter contract rather than importing it from the CLI/validator module.
Let PA rules use rule_registry.rule_reference and their own registry IDs rather than the legacy ErrorCode wrapper.
Put HK005's Git execute-bit observation beside HK005 detection, while keeping the legacy private alias for tests/callers.
Repoint LK/MCP/PA/PR/AS rule modules to those owners and verify no product rule module imports plugin_validator afterwards.
Once E0 lands, reassess Slice E against the now one-way dependency graph before moving validator selection/collection.
Current-main reconciliation — 2026-09-30 after #305
PR #305 completed Slice E0. Product rule modules now have a one-way dependency on domain owners and an AST contract test rejects any rule import of skilllint.plugin_validator. Compatibility aliases remain available from the legacy module.
A direct validation-orchestration move is still blocked by one smaller ownership seam: FileType, frontmatter requirement classification, and the name-bearing file-type set still originate in plugin_validator.py. Any extracted concrete validator that needs file-type context would otherwise have to import the legacy module and recreate the cycle from a different direction.
The next coherent prerequisite is Slice E1 — extract file/capability classification:
Add a dependency-light file_types.py owner for FileType and frontmatter requirement classification.
Move the name-bearing file-type set and the quick frontmatter-presence helper with that classification contract.
Depend only on scan_runtime for scan context/manifest lookup and frontmatter_core for the exemption contract; do not import validators or the legacy facade.
Preserve plugin_validator.FileType, _FrontmatterRequirement, _frontmatter_requirement, _file_has_frontmatter, and _NAME_BEARING_FILE_TYPES as compatibility aliases while callers migrate.
Use existing file-type/frontmatter behavior tests as primary evidence and add only compatibility identity/architecture proof needed for the new owner.
After E1, concrete validators can be extracted by domain without importing plugin_validator; reassess the smallest validator-owner slice before moving validate_single_path itself.
Current-main reconciliation — 2026-09-30 after #306
PR #306 completed Slice E1. file_types.py now owns ScanContext, FileType, and frontmatter-requirement classification; plugin_manifest.py owns the dependency-light cached Claude plugin manifest reader. plugin_validator and scan_runtime preserve their previous symbol paths as compatibility aliases.
The next coherent slice is Slice E2 — extract rule-series validator adapters, not a generic validators.py move.
Current-main evidence:
ProgressiveDisclosureValidator, InternalLinkValidator, NamespaceReferenceValidator, and AsSeriesValidator primarily adapt rule-series functions into the shared Validator protocol and do not own mutation, subprocess execution, plugin-tree orchestration, or schema repair.
Their rule logic already lives in rules/pd_series.py, rules/lk_series.py, rules/nr_series.py, and rules/as_series.py.
Moving the adapter classes directly into those rule modules would make the eagerly loaded rule registry pull additional policy/frontmatter dependencies; keeping adapter packaging in a separate validators/rule_series.py preserves the lightweight rule-registration path.
DescriptionValidator, ComplexityValidator, and MarkdownTokenCounter still own additional parsing/counting behavior and should be assessed separately rather than swept into E2.
Fixable SymlinkTargetValidator, NameFormatValidator, FrontmatterValidator, and HookValidator, plus plugin-tree/subprocess validators, remain out of scope.
Recommended E2:
Establish a validators/ package and a focused rule_series.py owner for the four rule-series adapters.
Use dependency-light domain owners (models, policy, frontmatter_yaml, rule_registry, rule modules) only; never import plugin_validator or scan/CLI orchestration.
Preserve the four legacy class imports from plugin_validator by identity.
Keep existing validator behavior tests as the primary proof; add only architecture/identity checks needed to protect the new seam.
Reassess the remaining concrete validators after E2 before extracting validation orchestration.
Current-main reconciliation — 2026-09-30 after #307
PR #307 completed Slice E2. validators/rule_series.py now owns the read-only protocol adapters for AS, LK001, NR, and PD while rules/ remains the lightweight rule-truth/registration layer.
The next coherent slice is Slice E3 — extract read-only content quality/token validators:
Add a focused validators/content.py owner for DescriptionValidator, ComplexityValidator, and MarkdownTokenCounter.
Preserve their existing parsing/counting and policy-threshold behavior exactly; this is ownership movement, not a rewrite onto new helpers.
Depend only on file_types, frontmatter parsing, models, policy, rule functions, rule_registry, and token counting. Do not import the legacy facade, scan orchestration, fixing, or CLI.
Preserve all three plugin_validator class imports by identity.
Keep ComplexityMetrics in place for now: it has no current consumers, and moving an unused compatibility type merely to reduce line count is not a coherent extraction.
Keep frontmatter-specific issue normalization with frontmatter validation for this slice; do not broaden E3 into the mutation/schema seam.
After E3, reassess fixable validators, plugin-tree/subprocess validators, and validator ownership/selection metadata before extracting validate_single_path.
Current-main reconciliation — 2026-09-30 after #308
PR #308 completed Slice E3. validators/content.py now owns DescriptionValidator, ComplexityValidator, and MarkdownTokenCounter; the legacy facade only re-exports those active classes.
The next coherent prerequisite is Slice E4 — extract validator routing metadata:
Add validators/metadata.py as the dependency-light owner of ValidatorOwnership, VALIDATOR_OWNERSHIP, VALIDATOR_CONSTRAINT_SCOPES, get_validator_ownership(), get_validator_constraint_scopes(), and filter_validators_by_constraint_scopes().
Depend only on the shared Validator protocol and stdlib; do not import concrete validators, the legacy facade, scan runtime, rules, fixing, or CLI.
Preserve every existing plugin_validator metadata symbol by identity.
Keep class-name keyed metadata unchanged for this slice. Converting it to concrete-type registration would couple the metadata owner back to all validator implementations and defeat the extraction.
Keep existing ownership/routing tests as the behavioral proof and add only compatibility/dependency-boundary evidence.
After E4, reassess concrete fixable/plugin validators and then validator selection/collection. validate_single_path should move only after selection no longer requires implementation ownership from the legacy module.
Current-main reconciliation — 2026-09-30 after #309
PR #309 completed Slice E4. validators/metadata.py now owns validator ownership and provider constraint-scope routing without depending on concrete implementations.
The next coherent slice is Slice E5 — extract non-frontmatter filesystem-mutation validators:
Move SymlinkTargetValidator to a focused validators/symlinks.py owner. SL001 detection remains in rules/sl_series.py; the validator owns the safe symlink rewrite.
Move HookValidator to validators/hooks.py. HK detection remains in rules/hk_series.py; the validator owns execute-bit repair and rule-result packaging.
Preserve both legacy plugin_validator class imports by identity.
Do not combine FrontmatterValidator / NameFormatValidator: their schema/YAML/name mutation seam is substantially larger and should be reviewed as its own slice.
Do not combine plugin structure/registration/link-escape validators: those own plugin-tree/subprocess semantics and form a separate responsibility group.
Keep existing hook/symlink/fixer-gating tests as primary behavior proof; add only compatibility/dependency-boundary evidence.
After E5, reassess the plugin validator group and frontmatter validator group. Once concrete classes no longer originate in the legacy module, validator selection/collection can move without reverse implementation ownership.
Current-main reconciliation — 2026-09-30 after #310
PR #310 completed Slice E5. validators/hooks.py and validators/symlinks.py now own the non-frontmatter filesystem mutation validators; the legacy facade preserves both class identities.
The next coherent slice is Slice E6 — extract the frontmatter validation/mutation subsystem:
Add validators/frontmatter.py as the owner of FrontmatterValidator, NameFormatValidator, and their schema/YAML/name-specific helper functions.
Move the frontmatter-local naming constants/helpers (NAME_PATTERN, _normalize_skill_name, skill-directory validation) with that subsystem.
Depend only on file_types, frontmatter_core, frontmatter_yaml, shared models, rule metadata/functions, and validators/hooks.py for embedded hook-reference checks. Do not import the legacy facade, scan runtime, fixing orchestration, CLI, or reporting.
Use canonical rule IDs / rule_reference inside the new owner rather than depending on the legacy ErrorCode enum. Keep ErrorCode and its aliases in the compatibility facade.
Preserve existing plugin_validator.FrontmatterValidator, NameFormatValidator, NAME_PATTERN, _normalize_skill_name, and moved private helper names as compatibility imports where practical.
Keep existing frontmatter/name/fixer-gating/rule-truth tests as primary behavior proof. Add only owner/identity dependency-boundary evidence.
Do not combine plugin registration, plugin link-escape, or Claude CLI structure validation; those are a separate plugin-tree/subprocess responsibility.
After E6, the only concrete validator implementations left in the legacy module should be the plugin-tree/subprocess group. Extract that group before moving validator selection/collection and validate_single_path.
Current-main reconciliation — 2026-09-30 after #311
PR #311 completed Slice E6. validators/frontmatter.py now owns frontmatter schema validation, normalization, FM009 state, FM010/name repair, and the frontmatter-specific helper graph. The legacy facade preserves class/helper/model aliases.
The only concrete validators still implemented in plugin_validator.py are now the plugin-tree/subprocess group, so the next coherent slice is Slice E7 — extract plugin validation and Claude CLI integration:
Add validators/plugins.py as the owner of PluginLinkEscapeValidator, PluginRegistrationValidator, and PluginStructureValidator.
Move the plugin-only helper graph with them: LK004 plugin-root markers/scope helper, Claude subprocess invocation, Git-Bash resolution, nested-session skip detection, is_claude_available, and validate_with_claude.
Depend on existing domain seams (rules/lk_series.py, rules/pl_series.py, rules/pr_series.py, policy.py, and scan_runtime.py) rather than the legacy facade. scan_runtime.run_validation_loop already uses callback injection and does not import validation, so this dependency does not recreate the cycle.
Use canonical rule IDs / rule_reference in the new owner; keep legacy ErrorCode aliases in the facade.
Preserve legacy class/constants/helper imports by identity where practical.
Existing tests that monkeypatch private Claude helpers through plugin_validator should patch the new actual owner after extraction. Preserving replacement side effects of monkeypatching a private compatibility alias would require a reverse dependency and is not a product contract.
Keep existing plugin-link/registration/structure/external-tool/CLI tests as the primary behavior proof; add only owner/identity architecture evidence.
After E7, plugin_validator.py should contain no concrete validator class implementations. Reassess validator selection/collection and validate_single_path against that state before moving them.
Compatibility facade:plugin_validator.py continues to expose the moved policy/config names used by existing tests/consumers. Token threshold constants remain imported from token_counter there because validation still consumes them and existing callers import them through the legacy module.
Unmoved dependency:msgspec.json remains a direct plugin_validator.py dependency because plugin-manifest validation outside the policy block still decodes JSON.
Dependency direction:policy.py -> models.py + token_counter.py + msgspec + stdlib; plugin_validator.py -> policy.py. No policy-to-validator dependency or local-import cycle is introduced.
Behavioral proof: existing policy/config and ignore/suppression tests remain the primary contract; one import-identity characterization verifies the compatibility facade for the moved owner objects.
Performance proof: the PR benchmark supplies paired base/compare startup/import measurements; any material regression must be reconciled before merge.
Names are illustrative; preserve or adjust them based on actual responsibility boundaries discovered during implementation.
Migration plan
Slice A — dependency inventory and contract
Inventory imports of skilllint.plugin_validator across product, tests, scripts, rules, and documentation.
Classify imported symbols as public compatibility, internal compatibility, or implementation detail.
Add characterization/import-identity tests where compatibility is required.
Slice B — extract shared models/contracts first
Move stable types such as the validation issue/result/fix/file-type contracts into a small dependency-light module.
This should remove the need for rule modules to TYPE_CHECKING-import fundamental result types from the central validator and should reduce circular/deferred imports.
Keep re-exports from plugin_validator.py while consumers migrate.
Slice C — extract policy/config
Move suppression, thresholds, severity/config discovery and related policy models to an owner module. Preserve behavior and public compatibility.
Slice D — extract fixing/frontmatter mutation orchestration
Separate mutation/fixer eligibility and YAML repair orchestration from validation dispatch where the existing behavior permits a clean boundary.
Slice E — extract validation orchestration
Move validator selection/collection and validate_file / validate_single_path ownership into a focused validation module while keeping the compatibility facade.
Slice F — extract CLI wiring
Make the Typer application/commands depend on the smaller domain seams rather than making the domain depend on the CLI module.
Slice G — retire compatibility exports only when proven safe
Do not remove legacy imports merely because internal code no longer needs them. Removal requires an explicit compatibility decision and release treatment.
Architectural constraints
Preserve existing rule, adapter, schema, scan-runtime and reporting seams where they already work.
Parent: #280
Outcome
Reduce
packages/skilllint/plugin_validator.pyfrom a multi-responsibility central module into a compatibility facade over smaller owner modules, without breaking current public/internal imports during the migration.Current evidence
On
main,plugin_validator.pyis roughly 4,930 lines and contains dozens of top-level functions and classes spanning:Many tests, rule modules, scripts, and examples import types or behavior from
skilllint.plugin_validator, so a big-bang move would create unnecessary churn.Current-main reconciliation — 2026-09-29
Current
mainhas already completed the contract-first foundation:packages/skilllint/models.py;skilllint.plugin_validatorre-exports those contracts for compatibility;scan_runtime.pyandreporting.pyalready own their extracted runtime/reporting seams.Policy/config ownership is complete via PR #301. PR #302 then made explicit-platform routing declaration-driven without combining a #283 extraction slice: adapter rule routing still lives in
plugin_validator.py, while discovery remains inscan_runtime.py.Current-main reconciliation — 2026-09-30 after #302
The next coherent #283 slice is fix authorization and execution orchestration (Slice D1), not validation routing or CLI extraction.
Current ownership evidence on
main:FIXER_TRIGGER_CODESandget_fixer_trigger_codes()live inplugin_validator.pyand define fail-closed rule-to-fixer authorization.validate_single_path()owns the generic fix loop: trigger intersection,can_fix()gating, orderedfix()calls,AppliedFixrecording, and revalidation._get_fixers_for_path()still selects the reporting validators plus the fix-onlyNameFormatValidator, preserving an important ordering contract.safe_load_yaml_with_colon_fix()is also still in the legacy module and has lazy imports from rule/boundary code; moving it casually with the coordinator would create unnecessary dependency/cycle risk.Recommended next slice:
fixing.pyowner forFIXER_TRIGGER_CODES,get_fixer_trigger_codes(), and a generic coordinator that receives an already ordered fixer sequence plusraw_codes, executes authorized fixes, recordsAppliedFix, and returns whether revalidation is needed._get_fixers_for_path()and concrete validatorfix()implementations in place for this slice. That avoids importing validator classes back intofixing.pyand keeps validator selection for Slice E.plugin_validatorcompatibility re-exports for moved names.A later Slice D2 can assess frontmatter mutation helpers separately once the generic coordinator is no longer embedded in validation dispatch. Slice E can then extract validator selection/collection and
validate_file/validate_single_patharound a smaller, already-separated fixing seam.Current-main reconciliation — 2026-09-30 after #303/#304
PR #303 completed Slice D1:
fixing.pynow owns fail-closed fixer authorization and generic ordered execution whileplugin_validatorpreserves compatibility re-exports. PR #304 completed the coherent D2 syntax seam:frontmatter_yaml.pyowns YAML parsing/repair primitives and rule/boundary YAML recovery no longer reaches into the legacy validator.A direct Slice E move of
_get_validators_for_path,_collect_validator_results,validate_single_path, andvalidate_fileis still premature. Concrete validator classes remain owned byplugin_validator.py, and several rule modules still perform deferred reverse imports from that module for unrelated utilities. Moving validation orchestration now would create or conceal avalidation -> plugin_validator -> rules -> validation/plugin_validatordependency cycle.The next coherent prerequisite is Slice E0 — remove rule-layer reverse dependencies on the legacy module without changing behavior:
plugin_validator.parse_skill_mdcompatibility alias.scan_runtime.py, which already owns path discovery, and preserve legacy aliases.rule_registry.rule_referenceand their own registry IDs rather than the legacyErrorCodewrapper.plugin_validatorafterwards.import skilllint.rulesstartup cost; perf(tests): suite spends ~two thirds of its wall clock on subprocess startup #148 remains the performance-measurement owner.Once E0 lands, reassess Slice E against the now one-way dependency graph before moving validator selection/collection.
Current-main reconciliation — 2026-09-30 after #305
PR #305 completed Slice E0. Product rule modules now have a one-way dependency on domain owners and an AST contract test rejects any rule import of
skilllint.plugin_validator. Compatibility aliases remain available from the legacy module.A direct validation-orchestration move is still blocked by one smaller ownership seam:
FileType, frontmatter requirement classification, and the name-bearing file-type set still originate inplugin_validator.py. Any extracted concrete validator that needs file-type context would otherwise have to import the legacy module and recreate the cycle from a different direction.The next coherent prerequisite is Slice E1 — extract file/capability classification:
file_types.pyowner forFileTypeand frontmatter requirement classification.scan_runtimefor scan context/manifest lookup andfrontmatter_corefor the exemption contract; do not import validators or the legacy facade.plugin_validator.FileType,_FrontmatterRequirement,_frontmatter_requirement,_file_has_frontmatter, and_NAME_BEARING_FILE_TYPESas compatibility aliases while callers migrate.After E1, concrete validators can be extracted by domain without importing
plugin_validator; reassess the smallest validator-owner slice before movingvalidate_single_pathitself.Current-main reconciliation — 2026-09-30 after #306
PR #306 completed Slice E1.
file_types.pynow ownsScanContext,FileType, and frontmatter-requirement classification;plugin_manifest.pyowns the dependency-light cached Claude plugin manifest reader.plugin_validatorandscan_runtimepreserve their previous symbol paths as compatibility aliases.The next coherent slice is Slice E2 — extract rule-series validator adapters, not a generic
validators.pymove.Current-main evidence:
ProgressiveDisclosureValidator,InternalLinkValidator,NamespaceReferenceValidator, andAsSeriesValidatorprimarily adapt rule-series functions into the sharedValidatorprotocol and do not own mutation, subprocess execution, plugin-tree orchestration, or schema repair.rules/pd_series.py,rules/lk_series.py,rules/nr_series.py, andrules/as_series.py.validators/rule_series.pypreserves the lightweight rule-registration path.DescriptionValidator,ComplexityValidator, andMarkdownTokenCounterstill own additional parsing/counting behavior and should be assessed separately rather than swept into E2.SymlinkTargetValidator,NameFormatValidator,FrontmatterValidator, andHookValidator, plus plugin-tree/subprocess validators, remain out of scope.Recommended E2:
validators/package and a focusedrule_series.pyowner for the four rule-series adapters.models,policy,frontmatter_yaml,rule_registry, rule modules) only; never importplugin_validatoror scan/CLI orchestration.plugin_validatorby identity.Current-main reconciliation — 2026-09-30 after #307
PR #307 completed Slice E2.
validators/rule_series.pynow owns the read-only protocol adapters for AS, LK001, NR, and PD whilerules/remains the lightweight rule-truth/registration layer.The next coherent slice is Slice E3 — extract read-only content quality/token validators:
validators/content.pyowner forDescriptionValidator,ComplexityValidator, andMarkdownTokenCounter.file_types, frontmatter parsing,models,policy, rule functions,rule_registry, and token counting. Do not import the legacy facade, scan orchestration, fixing, or CLI.plugin_validatorclass imports by identity.ComplexityMetricsin place for now: it has no current consumers, and moving an unused compatibility type merely to reduce line count is not a coherent extraction.After E3, reassess fixable validators, plugin-tree/subprocess validators, and validator ownership/selection metadata before extracting
validate_single_path.Current-main reconciliation — 2026-09-30 after #308
PR #308 completed Slice E3.
validators/content.pynow ownsDescriptionValidator,ComplexityValidator, andMarkdownTokenCounter; the legacy facade only re-exports those active classes.The next coherent prerequisite is Slice E4 — extract validator routing metadata:
validators/metadata.pyas the dependency-light owner ofValidatorOwnership,VALIDATOR_OWNERSHIP,VALIDATOR_CONSTRAINT_SCOPES,get_validator_ownership(),get_validator_constraint_scopes(), andfilter_validators_by_constraint_scopes().Validatorprotocol and stdlib; do not import concrete validators, the legacy facade, scan runtime, rules, fixing, or CLI.plugin_validatormetadata symbol by identity.After E4, reassess concrete fixable/plugin validators and then validator selection/collection.
validate_single_pathshould move only after selection no longer requires implementation ownership from the legacy module.Current-main reconciliation — 2026-09-30 after #309
PR #309 completed Slice E4.
validators/metadata.pynow owns validator ownership and provider constraint-scope routing without depending on concrete implementations.The next coherent slice is Slice E5 — extract non-frontmatter filesystem-mutation validators:
SymlinkTargetValidatorto a focusedvalidators/symlinks.pyowner. SL001 detection remains inrules/sl_series.py; the validator owns the safe symlink rewrite.HookValidatortovalidators/hooks.py. HK detection remains inrules/hk_series.py; the validator owns execute-bit repair and rule-result packaging.plugin_validatorclass imports by identity.FrontmatterValidator/NameFormatValidator: their schema/YAML/name mutation seam is substantially larger and should be reviewed as its own slice.After E5, reassess the plugin validator group and frontmatter validator group. Once concrete classes no longer originate in the legacy module, validator selection/collection can move without reverse implementation ownership.
Current-main reconciliation — 2026-09-30 after #310
PR #310 completed Slice E5.
validators/hooks.pyandvalidators/symlinks.pynow own the non-frontmatter filesystem mutation validators; the legacy facade preserves both class identities.The next coherent slice is Slice E6 — extract the frontmatter validation/mutation subsystem:
validators/frontmatter.pyas the owner ofFrontmatterValidator,NameFormatValidator, and their schema/YAML/name-specific helper functions.NAME_PATTERN,_normalize_skill_name, skill-directory validation) with that subsystem.file_types,frontmatter_core,frontmatter_yaml, shared models, rule metadata/functions, andvalidators/hooks.pyfor embedded hook-reference checks. Do not import the legacy facade, scan runtime, fixing orchestration, CLI, or reporting.rule_referenceinside the new owner rather than depending on the legacyErrorCodeenum. KeepErrorCodeand its aliases in the compatibility facade.plugin_validator.FrontmatterValidator,NameFormatValidator,NAME_PATTERN,_normalize_skill_name, and moved private helper names as compatibility imports where practical.After E6, the only concrete validator implementations left in the legacy module should be the plugin-tree/subprocess group. Extract that group before moving validator selection/collection and
validate_single_path.Current-main reconciliation — 2026-09-30 after #311
PR #311 completed Slice E6.
validators/frontmatter.pynow owns frontmatter schema validation, normalization, FM009 state, FM010/name repair, and the frontmatter-specific helper graph. The legacy facade preserves class/helper/model aliases.The only concrete validators still implemented in
plugin_validator.pyare now the plugin-tree/subprocess group, so the next coherent slice is Slice E7 — extract plugin validation and Claude CLI integration:validators/plugins.pyas the owner ofPluginLinkEscapeValidator,PluginRegistrationValidator, andPluginStructureValidator.is_claude_available, andvalidate_with_claude.rules/lk_series.py,rules/pl_series.py,rules/pr_series.py,policy.py, andscan_runtime.py) rather than the legacy facade.scan_runtime.run_validation_loopalready uses callback injection and does not import validation, so this dependency does not recreate the cycle.rule_referencein the new owner; keep legacyErrorCodealiases in the facade.plugin_validatorshould patch the new actual owner after extraction. Preserving replacement side effects of monkeypatching a private compatibility alias would require a reverse dependency and is not a product contract.After E7,
plugin_validator.pyshould contain no concrete validator class implementations. Reassess validator selection/collection andvalidate_single_pathagainst that state before moving them.Dependency inventory — policy/config slice (2026-09-30)
Observed before finalizing Slice C:
policy.pyownsValidationPolicy,IgnoreConfig, config parsing/loading/discovery, threshold/severity configuration, policy/ignore caches, suppression matching, and suppression filtering.plugin_validator.pyconsumes the policy objects/resolvers/filter but retains finding reclassification, validator dispatch, fixing, adapter routing, and CLI wiring for later refactor(core): decompose plugin_validator.py behind stable compatibility exports #283 slices.plugin_validator.pycontinues to expose the moved policy/config names used by existing tests/consumers. Token threshold constants remain imported fromtoken_counterthere because validation still consumes them and existing callers import them through the legacy module.msgspec.jsonremains a directplugin_validator.pydependency because plugin-manifest validation outside the policy block still decodes JSON.policy.py -> models.py + token_counter.py + msgspec + stdlib;plugin_validator.py -> policy.py. No policy-to-validator dependency or local-import cycle is introduced.Target direction
A likely end state is approximately:
Names are illustrative; preserve or adjust them based on actual responsibility boundaries discovered during implementation.
Migration plan
Slice A — dependency inventory and contract
skilllint.plugin_validatoracross product, tests, scripts, rules, and documentation.Slice B — extract shared models/contracts first
Move stable types such as the validation issue/result/fix/file-type contracts into a small dependency-light module.
This should remove the need for rule modules to TYPE_CHECKING-import fundamental result types from the central validator and should reduce circular/deferred imports.
Keep re-exports from
plugin_validator.pywhile consumers migrate.Slice C — extract policy/config
Move suppression, thresholds, severity/config discovery and related policy models to an owner module. Preserve behavior and public compatibility.
Slice D — extract fixing/frontmatter mutation orchestration
Separate mutation/fixer eligibility and YAML repair orchestration from validation dispatch where the existing behavior permits a clean boundary.
Slice E — extract validation orchestration
Move validator selection/collection and
validate_file/validate_single_pathownership into a focused validation module while keeping the compatibility facade.Slice F — extract CLI wiring
Make the Typer application/commands depend on the smaller domain seams rather than making the domain depend on the CLI module.
Slice G — retire compatibility exports only when proven safe
Do not remove legacy imports merely because internal code no longer needs them. Removal requires an explicit compatibility decision and release treatment.
Architectural constraints
plugin_validator.py; it should enter through an explicit analysis subsystem.Acceptance criteria
skilllint.plugin_validatorimports continue working through compatibility exports during migration.