Purpose: Validation product area overview
Detail Level: Full reference
How is the workflow enforced? Validation is the enforcement boundary — it ensures that every change to annotated source files respects the delivery lifecycle rules defined by the FSM, protection levels, and scope constraints. The system operates in three layers: the FSM validator checks status transitions against a 4-state directed graph, the Process Guard orchestrates commit-time validation using a Decider pattern (state derived from annotations, not stored separately), and the lint engine provides pluggable rule execution with pretty and JSON output. Anti-pattern detection enforces dual-source ownership boundaries — @libar-docs-uses belongs on TypeScript, @libar-docs-depends-on belongs on Gherkin — preventing cross-domain tag confusion that causes documentation drift. Definition of Done validation ensures completed patterns have all deliverables marked done and at least one acceptance-criteria scenario.
Protection levels: roadmap/deferred = none (fully editable), active = scope-locked (no new deliverables), completed = hard-locked (requires @libar-docs-unlock-reason)
Valid FSM transitions: Only roadmap→active, roadmap→deferred, active→completed, active→roadmap, deferred→roadmap. Completed is terminal
Decider pattern: All validation is (state, changes, options) → result. State is derived from annotations, not maintained separately
Dual-source ownership: Anti-pattern detection enforces tag boundaries — uses on TypeScript (runtime deps), depends-on/quarter/team on Gherkin (planning metadata). Violations are flagged before they cause documentation drift
Process metadata should not appear in TypeScript code
Process metadata tags (@libar-docs-status, @libar-docs-phase, etc.) must only appear in Gherkin feature files, never in TypeScript source code.
TypeScript owns runtime behavior while Gherkin owns delivery process metadata — mixing them creates dual-source conflicts and validation ambiguity.
Generator hints should not appear in feature files
Feature files must not contain generator magic comments beyond a configurable threshold.
Generator hints are implementation details that belong in TypeScript — excessive magic comments in specs indicate leaking implementation concerns into business requirements.
Feature files should not have excessive scenarios
A single feature file must not exceed the configured maximum scenario count.
Oversized feature files indicate missing decomposition — they become hard to maintain and slow to execute.
Feature files should not exceed size thresholds
A single feature file must not exceed the configured maximum line count.
Excessively large files indicate a feature that should be split into focused, independently testable specifications.
All anti-patterns can be detected in one pass
The anti-pattern detector must evaluate all registered rules in a single scan pass over the source files.
Single-pass detection ensures consistent results and avoids O(n*m) performance degradation with multiple file traversals.
Violations can be formatted for console output
Anti-pattern violations must be renderable as grouped, human-readable console output.
Developers need actionable feedback at commit time — ungrouped or unformatted violations are hard to triage and fix.
Generator configuration must use a .json registry file and an output directory that does not escape the project root via parent traversal.
Non-JSON registry files could introduce parsing vulnerabilities, and unrestricted output paths could overwrite files outside the project.
isScannerConfig type guard narrows unknown values
isScannerConfig returns true only for objects that have a non-empty patterns array and a string baseDir.
Without a reliable type guard, callers cannot safely narrow unknown config objects and risk accessing properties on incompatible types at runtime.
isGeneratorConfig type guard narrows unknown values
isGeneratorConfig returns true only for objects that have a string outputDir and a .json registryPath.
Without a reliable type guard, callers cannot safely narrow unknown config objects and risk passing malformed generator configs that bypass schema validation.
Status changes are detected as modifications not additions
When a deliverable’s status value changes between versions, the change detector must classify it as a modification, not an addition or removal.
Correct change classification drives scope-creep detection — misclassifying a status change as an addition would trigger false scope-creep violations on active specs.
New deliverables are detected as additions
Deliverables present in the new version but absent in the old version must be classified as additions.
Addition detection powers the scope-creep rule — new deliverables added to active specs must be flagged as violations.
Removed deliverables are detected as removals
Deliverables present in the old version but absent in the new version must be classified as removals.
Removal detection enables the deliverable-removed warning — silently dropping deliverables could hide incomplete work.
Mixed changes are correctly categorized
When a single diff contains additions, removals, and modifications simultaneously, each change must be independently categorized.
Real-world commits often contain mixed changes — incorrect categorization of any single change cascades into wrong validation decisions.
Non-deliverable tables are ignored
Changes to non-deliverable tables (e.g., ScenarioOutline Examples tables) must not be detected as deliverable changes.
Feature files contain many table structures — only the Background deliverables table is semantically relevant to process guard validation.
Every pattern status value must be one of the states defined in the PDR-005 finite state machine (roadmap, active, completed, deferred).
Invalid status values bypass FSM transition validation and produce undefined behavior in process guard enforcement.
Status transitions must follow FSM rules
Every status change must follow a valid edge in the PDR-005 state machine graph — no skipping states or invalid paths.
The FSM encodes the delivery workflow contract — invalid transitions indicate process violations that could corrupt delivery tracking.
Completed patterns should have proper metadata
Patterns in completed status must carry completion date and actual effort metadata to pass validation without warnings.
Completion metadata enables retrospective analysis and effort estimation — missing metadata degrades project planning accuracy over time.
Protection levels match FSM state definitions
Each FSM state must map to exactly one protection level (none, scope-locked, or hard-locked) as defined in PDR-005.
Protection levels enforce edit constraints per state — mismatched protection would allow prohibited modifications to active or completed specs.
Combined validation provides complete results
The FSM validator must return a combined result including status validity, transition validity, metadata warnings, and protection level in a single call.
Callers need a complete validation picture — requiring multiple separate calls risks partial validation and inconsistent error reporting.
Single directive linting validates annotations against rules
Every directive is checked against all provided rules and violations include source location.
Skipping rules or omitting source locations makes violations unactionable, as developers cannot locate or understand the issue.
Multi-file batch linting aggregates results across files
All files and directives are scanned, violations are collected per file, and severity counts are accurate.
Missing files or inaccurate severity counts cause silent rule violations in CI and undermine trust in the linting pipeline.
Failure detection respects strict mode for severity escalation
Errors always indicate failure. Warnings only indicate failure in strict mode. Info never indicates failure.
Without correct severity-to-exit-code mapping, CI pipelines either miss real errors or block on informational messages, eroding developer trust in the linter.
Violation sorting orders by severity then by line number
Sorted output places errors first, then warnings, then info, with stable line-number ordering within each severity. Sorting does not mutate the original array.
Unsorted output forces developers to manually scan for critical errors among lower-severity noise, and mutating the original array would break callers that hold a reference to it.
Pretty formatting produces human-readable output with severity counts
Pretty output includes file paths, line numbers, severity labels, rule IDs, and summary counts. Quiet mode suppresses non-error violations.
Incomplete formatting (missing file paths or line numbers) prevents developers from navigating directly to violations, and noisy output in quiet mode defeats its purpose.
JSON formatting produces machine-readable output with full details
JSON output is valid, includes all summary fields, and preserves violation details including file, line, severity, rule, and message.
Machine consumers (CI pipelines, IDE integrations) depend on valid JSON with complete fields; missing or malformed output breaks automated tooling downstream.
Every file’s modification restrictions are determined solely by its @libar-docs-status tag, with completed requiring an explicit unlock reason for any change.
Without status-derived protection, completed and approved work can be silently overwritten by bulk edits or accidental modifications. Files inherit protection from their @libar-docs-status tag. Higher protection levels require explicit unlock to modify.
Session definition files scope what can be modified
When an active session exists, only specs explicitly listed in the session definition may be modified without warning, and excluded specs cannot be modified at all.
Without session scoping, bulk operations and context switches cause unintended modifications to specs outside the current work focus. Optional session files (delivery-process/sessions/*.feature) explicitly declare which specs are in-scope for modification during a work session. If active, modifications outside scope trigger warnings or errors.
Status transitions follow PDR-005 FSM
Every status change must follow a valid edge in the PDR-005 finite state machine; no transition may skip intermediate states.
Skipping states (e.g., roadmap directly to completed) bypasses scope-locking and review gates, allowing incomplete work to be marked as done. Status changes in a file must follow a valid transition per PDR-005. This extends phase-state-machine.feature to the linter context.
Active specs cannot add new deliverables
The deliverables table of an active spec is immutable with respect to new rows; only existing deliverable statuses may change.
Adding deliverables after work has begun constitutes scope creep, undermining effort estimates and blocking completion. Once a spec transitions to active, its deliverables table is considered scope-locked. Adding new rows indicates scope creep.
CLI provides flexible validation modes
The CLI must support both pre-commit (staged-only) and CI (all-files) validation modes with deterministic exit codes reflecting violation severity.
Without flexible modes, teams cannot integrate process guard into both local developer workflows and CI pipelines with appropriate strictness levels.
Integrates with existing lint infrastructure
Process guard output format and exit code semantics must be consistent with the existing lint-patterns tool.
Inconsistent output formats force consumers to maintain separate parsers, and inconsistent exit codes break combined lint pipelines.
New tags support process guard functionality
Session and protection tags must be registered in the TypeScript taxonomy with defined formats before use in feature files.
Unregistered tags bypass schema validation and are silently ignored by the scanner, causing process guard rules to fail without diagnostics. The following tags are defined in the TypeScript taxonomy to support process guard:
Files with @libar-docs-status roadmap or deferred have relaxed unused-vars rules. Files with active, completed, or no status have strict enforcement.
Design artifacts (roadmap stubs) define API shapes that are intentionally unused until implementation. Relaxing rules for these files prevents false positives while ensuring implemented code (active/completed) remains strictly checked.
Reuses deriveProcessState for status extraction
Status extraction logic must be shared with Process Guard Linter. No duplicate parsing or status-to-protection mapping.
DRY principle - the Process Guard already has battle-tested status extraction from JSDoc comments. Duplicating this logic creates maintenance burden and potential inconsistencies between tools.
ESLint Processor filters messages based on status
The processor uses ESLint’s postprocess hook to filter or downgrade messages. Source code is never modified. No eslint-disable comments are injected.
ESLint processors can inspect and filter linting messages after rules run. This approach: - Requires no source code modification - Works with any ESLint rule (not just no-unused-vars) - Can be extended to other status-based behaviors
CLI can generate static ESLint ignore list
Running pnpm lint:process --eslint-ignores outputs a list of files that should have relaxed linting, suitable for inclusion in eslint.config.js.
For CI environments or users preferring static configuration, a generated list provides an alternative to runtime processing. The list can be regenerated whenever status annotations change.
Replaces directory-based ESLint exclusions
After implementation, the directory-based exclusions in eslint.config.js (lines 30-57) are removed. All suppression is driven by @libar-docs-status annotations.
Directory-based exclusions are tech debt: - They don’t account for file lifecycle (roadmap -> completed) - They require manual updates when new roadmap directories are added - They persist even after files are implemented
Rule relaxation is configurable
The set of rules relaxed for roadmap/deferred files is configurable, defaulting to @typescript-eslint/no-unused-vars.
Different projects may want to relax different rules for design artifacts. The default covers the common case (unused exports in API stubs).
A hash character in the middle of a Gherkin step line can be interpreted as a comment by some parsers, silently truncating the step text. This differs from hash-in-description (which catches hash inside description pseudo-code-blocks).
We encountered this exact trap while writing the lint-steps test suite. Step text like “Given a file with # inside” was silently truncated to “Given a file with”.
Gherkin keywords in description text are detected
A Feature or Rule description line that starts with Given, When, Then, And, or But breaks the Gherkin parser because it interprets the line as a step definition rather than description text.
This is documented in vitest-cucumber quirks but has no static detection. Authors writing natural language descriptions accidentally start sentences with these keywords.
Scenario Outline steps with quoted values are detected
When a feature file has a Scenario Outline and its steps use quoted values instead of angle-bracket placeholders, this indicates the author may be using the Scenario pattern (function params) instead of the ScenarioOutline pattern (variables object). This is the feature-file side of the Two-Pattern Problem.
The existing scenario-outline-function-params rule catches the step-file side. This rule catches the feature-file side where quoted values in Scenario Outline steps suggest the author expects Cucumber expression matching rather than variable substitution.
Repeated step patterns in the same scenario are detected
Registering the same step pattern twice in one Scenario block causes vitest-cucumber to overwrite the first registration. Only the last callback runs, causing silent test failures where assertions appear to pass but the setup was wrong.
This happens when authors copy-paste step definitions within a scenario and forget to change the pattern. The failure is silent — tests pass but with wrong assertions.
Hash comments inside description pseudo-code-blocks are detected
A # at the start of a line inside a """ block within a Feature or Rule description terminates the description context, because the Gherkin parser treats # as a comment even inside descriptions. The """ delimiters in descriptions are NOT real DocStrings.
This is the most confusing Gherkin parser trap. Authors embed code examples using """ and expect # comments to be protected. The resulting parse error gives no hint about the actual cause.
Duplicate And steps in the same scenario are detected
Multiple And steps with identical text in the same scenario cause vitest-cucumber step matching failures. The fix is to consolidate into a single step with a DataTable.
Duplicate step text silently overwrites step registrations, causing the second And to match the first handler and produce wrong or undefined behavior at runtime.
Dollar sign in step text is detected
The $ character in step text causes matching issues in vitest-cucumber’s expression parser.
The dollar sign is interpreted as a special character in expression parsing, causing steps to silently fail to match and producing confusing StepAbleUnknowStepError messages.
Regex step patterns are detected
vitest-cucumber only supports string patterns with {string} and {int}. Regex patterns throw StepAbleStepExpressionError.
Regex patterns are a common Cucumber.js habit that compiles without error but throws at runtime in vitest-cucumber, wasting debugging time.
Unsupported phrase type is detected
vitest-cucumber does not support {phrase}. Use {string} with quoted values in the feature file.
The {phrase} type is valid in standard Cucumber but unsupported in vitest-cucumber, causing silent parameter capture failures that are difficult to trace.
ScenarioOutline function params are detected
ScenarioOutline step callbacks must use the variables object, not function params. Using (_ctx, value: string) means value will be undefined at runtime.
This is the most common vitest-cucumber trap. Function params compile and even type-check, but the values are always undefined at runtime because ScenarioOutline injects data through the variables object, not positional arguments.
Missing And destructuring is detected
If a feature file has And steps, the step definition must destructure And from the scenario callback.
Without destructuring And, vitest-cucumber cannot bind And steps and throws StepAbleUnknowStepError at runtime with no indication that a missing destructure is the cause.
Missing Rule wrapper is detected
If a feature file has Rule: blocks, the step definition must destructure Rule from describeFeature.
Without the Rule() wrapper, scenarios inside Rule: blocks are invisible to vitest-cucumber and silently never execute, giving a false green test suite.
Feature-to-step pairing resolves both loadFeature patterns
Step files use two loadFeature patterns: simple string paths and resolve(__dirname, relative) paths. Both must be paired.
Unpaired feature files cannot be cross-checked for compatibility issues, leaving ScenarioOutline param misuse and missing destructures undetected.
Validator queries the read model for cross-source matching
Pattern identity resolution — including implements relationships in both directions — uses MasterDataset.relationshipIndex rather than ad-hoc name-equality maps built from raw scanner output.
The MasterDataset computes implementedBy reverse lookups in transform-dataset.ts (second pass, lines 488-546). The validator’s current name-equality Map cannot resolve ShapeExtractor -> ShapeExtraction or DecisionDocGeneratorTesting -> DecisionDocGenerator because these are implements relationships, not name matches.
No lossy local types in the validator
The validator operates on ExtractedPattern from the MasterDataset, not a consumer-local DTO that discards fields.
GherkinPatternInfo keeps only name, phase, status, file, and deliverables — discarding uses, dependsOn, implementsPatterns, include, productArea, rules, and 20+ other fields. When the validator needs relationship data, it cannot access it through the lossy type.
Utility patterns without specs are not false positives
Internal utility patterns that have a @libar-docs-phase but will never have a Gherkin spec should not carry phase metadata. Phase tags signal roadmap participation.
Five utility patterns (ContentDeduplicator, FileCache, WarningCollector, SourceMappingValidator, SourceMapper) have phase tags from the phase when they were built. They are infrastructure, not roadmap features. The validator correctly reports missing Gherkin for patterns with phases — the fix is removing the phase tag, not suppressing the warning.