Skip to content

Reference Generation Sample

Purpose: Reference document: Reference Generation Sample Detail Level: Full reference


Invariant: The product-area tag uses one of 7 canonical values. Each value represents a reader-facing documentation section, not a source module.

Rationale: Without canonical values, organic drift (e.g., Generator vs Generators) produces inconsistent grouping in generated documentation and fragmented product area pages.

ValueReader QuestionCovers
AnnotationHow do I annotate code?Scanning, extraction, tag parsing, dual-source
ConfigurationHow do I configure the tool?Config loading, presets, resolution
GenerationHow does code become docs?Codecs, generators, rendering, diagrams
ValidationHow is the workflow enforced?FSM, DoD, anti-patterns, process guard, lint
DataAPIHow do I query process state?Process state API, stubs, context assembly, CLI
CoreTypesWhat foundational types exist?Result monad, error factories, string utils
ProcessHow does the session workflow work?Session lifecycle, handoffs, conventions

Invariant: The adr-category tag uses one of 4 values.

Rationale: Unbounded category values prevent meaningful grouping of architecture decisions and make cross-cutting queries unreliable.

ValuePurpose
architectureSystem structure, component design, data flow
processWorkflow, conventions, annotation rules
testingTest strategy, verification approach
documentationDocumentation generation, content structure

Invariant: Pattern status uses exactly 4 values with defined protection levels. These are enforced by Process Guard at commit time.

Rationale: Without protection levels, active specs accumulate scope creep and completed specs get silently modified, undermining delivery process integrity.

StatusProtectionCan Add DeliverablesAllowed Actions
roadmapNoneYesFull editing
activeScope-lockedNoEdit existing deliverables only
completedHard-lockedNoRequires unlock-reason tag
deferredNoneYesFull editing

Invariant: Only these transitions are valid. All others are rejected by Process Guard.

Rationale: Allowing arbitrary transitions (e.g., roadmap to completed) bypasses the active phase where scope-lock and deliverable tracking provide quality assurance. Completed is a terminal state. Modifications require @libar-docs-unlock-reason escape hatch.

FromToTrigger
roadmapactiveStart work
roadmapdeferredPostpone
activecompletedAll deliverables done
activeroadmapBlocked/regressed
deferredroadmapResume planning

Invariant: Every tag has one of 6 format types that determines how its value is parsed.

Rationale: Without explicit format types, parsers must guess value structure, leading to silent data corruption when CSV values are treated as single strings or numbers are treated as text.

FormatParsingExample
flagBoolean presence, no value@libar-docs-core
valueSimple string@libar-docs-pattern MyPattern
enumConstrained to predefined list@libar-docs-status completed
csvComma-separated values@libar-docs-uses A, B, C
numberNumeric value@libar-docs-phase 15
quoted-valuePreserves spaces@libar-docs-brief:‘Multi word’

Invariant: Relationship tags have defined ownership by source type. Anti-pattern detection enforces these boundaries.

Rationale: Cross-domain tag placement (e.g., runtime dependencies in Gherkin) creates conflicting sources of truth and breaks the dual-source architecture ownership model.

TagCorrect SourceWrong SourceRationale
usesTypeScriptFeature filesTS owns runtime dependencies
depends-onFeature filesTypeScriptGherkin owns planning dependencies
quarterFeature filesTypeScriptGherkin owns timeline metadata
teamFeature filesTypeScriptGherkin owns ownership metadata

Invariant: The quarter tag uses YYYY-QN format (e.g., 2026-Q1). ISO-year-first sorting works lexicographically.

Rationale: Non-standard formats (e.g., Q1-2026) break lexicographic sorting, which roadmap generation and timeline queries depend on for correct ordering.


Canonical phase definitions (6-phase USDP standard)

Section titled “Canonical phase definitions (6-phase USDP standard)”

Invariant: The default workflow defines exactly 6 phases in fixed order. These are the canonical phase names and ordinals used by all generated documentation.

Rationale: Ad-hoc phase names and ordering produce inconsistent roadmap grouping across packages and make cross-package progress tracking impossible.

OrderPhasePurpose
1InceptionProblem framing, scope definition
2ElaborationDesign decisions, architecture exploration
3SessionPlanning and design session work
4ConstructionImplementation, testing, integration
5ValidationVerification, acceptance criteria confirmation
6RetrospectiveReview, lessons learned, documentation

Invariant: Deliverable status (distinct from pattern FSM status) uses exactly 6 values, enforced by Zod schema at parse time.

Rationale: Freeform status strings bypass Zod validation and break DoD checks, which rely on terminal status classification to determine pattern completeness.

ValueMeaning
completeWork is done
in-progressWork is ongoing
pendingWork has not started
deferredWork postponed
supersededReplaced by another
n/aNot applicable

Scoped architecture diagram showing component relationships:

graph TB
    subgraph config["Config"]
        DeliveryProcessFactory("DeliveryProcessFactory")
        DefineConfig[/"DefineConfig"/]
    end
    ConfigBasedWorkflowDefinition["ConfigBasedWorkflowDefinition"]
    ProcessGuardTesting["ProcessGuardTesting"]
    subgraph related["Related"]
        AntiPatternDetector["AntiPatternDetector"]:::neighbor
        ConfigurationTypes["ConfigurationTypes"]:::neighbor
        RegexBuilders["RegexBuilders"]:::neighbor
        ProjectConfigTypes["ProjectConfigTypes"]:::neighbor
        ConfigurationPresets["ConfigurationPresets"]:::neighbor
        ProcessGuardLinter["ProcessGuardLinter"]:::neighbor
        PhaseStateMachineValidation["PhaseStateMachineValidation"]:::neighbor
        MvpWorkflowImplementation["MvpWorkflowImplementation"]:::neighbor
    end
    DeliveryProcessFactory -->|uses| ConfigurationTypes
    DeliveryProcessFactory -->|uses| ConfigurationPresets
    DeliveryProcessFactory -->|uses| RegexBuilders
    DefineConfig -->|uses| ProjectConfigTypes
    ConfigBasedWorkflowDefinition -.->|depends on| MvpWorkflowImplementation
    ProcessGuardTesting -.->|depends on| PhaseStateMachineValidation
    ProcessGuardTesting -.->|depends on| AntiPatternDetector
    ProcessGuardTesting ..->|implements| ProcessGuardLinter
    RegexBuilders -->|uses| ConfigurationTypes
    ProjectConfigTypes -->|uses| ConfigurationTypes
    ProjectConfigTypes -->|uses| ConfigurationPresets
    ConfigurationPresets -->|uses| ConfigurationTypes
    classDef neighbor stroke-dasharray: 5 5

Temporal flow of the documentation generation pipeline:

sequenceDiagram
    participant CLI
    participant Orchestrator
    participant Scanner
    participant Extractor
    participant Transformer
    participant Codec
    participant Renderer
    CLI ->> Orchestrator: generate(config)
    Orchestrator ->> Scanner: scanPatterns(globs)
    Scanner -->> Orchestrator: TypeScript ASTs
    Orchestrator ->> Scanner: scanGherkinFiles(globs)
    Scanner -->> Orchestrator: Gherkin documents
    Orchestrator ->> Extractor: extractPatterns(files)
    Extractor -->> Orchestrator: ExtractedPattern[]
    Orchestrator ->> Extractor: extractFromGherkin(docs)
    Extractor -->> Orchestrator: ExtractedPattern[]
    Orchestrator ->> Orchestrator: mergePatterns(ts, gherkin)
    Orchestrator ->> Transformer: transformToMasterDataset(patterns)
    Transformer -->> Orchestrator: MasterDataset
    Orchestrator ->> Codec: codec.decode(dataset)
    Codec -->> Orchestrator: RenderableDocument
    Orchestrator ->> Renderer: render(document)
    Renderer -->> Orchestrator: markdown string

Scoped architecture diagram showing component relationships:

classDiagram
    class SourceMapper {
        <<infrastructure>>
    }
    class Documentation_Generation_Orchestrator {
        <<service>>
    }
    class TransformDataset {
        <<service>>
    }
    class DecisionDocGenerator {
        <<service>>
    }
    class MasterDataset
    class Pattern_Scanner
    class GherkinASTParser
    class ShapeExtractor
    class DecisionDocCodec
    class PatternRelationshipModel
    SourceMapper ..> DecisionDocCodec : depends on
    SourceMapper ..> ShapeExtractor : depends on
    SourceMapper ..> GherkinASTParser : depends on
    Documentation_Generation_Orchestrator ..> Pattern_Scanner : uses
    TransformDataset ..> MasterDataset : uses
    TransformDataset ..|> PatternRelationshipModel : implements
    DecisionDocGenerator ..> DecisionDocCodec : depends on
    DecisionDocGenerator ..> SourceMapper : depends on

FSM lifecycle showing valid state transitions and protection levels:

stateDiagram-v2
    [*] --> roadmap
    roadmap --> active : Start work
    roadmap --> deferred : Postpone
    roadmap --> roadmap : Stay in planning
    active --> completed : All deliverables done
    active --> roadmap : Blocked / regressed
    completed --> [*]
    deferred --> roadmap : Resume planning
    note right of roadmap
        Protection: none
    end note
    note right of active
        Protection: scope-locked
    end note
    note right of completed
        Protection: hard-locked
    end note
    note right of deferred
        Protection: none
    end note

Scoped architecture diagram showing component relationships:

C4Context
    title Scanning & Extraction Boundary
    Boundary(extractor, "Extractor") {
        System(GherkinExtractor, "GherkinExtractor")
        System(DualSourceExtractor, "DualSourceExtractor")
        System(Document_Extractor, "Document Extractor")
    }
    Boundary(scanner, "Scanner") {
        System(Pattern_Scanner, "Pattern Scanner")
        System(GherkinScanner, "GherkinScanner")
        System(GherkinASTParser, "GherkinASTParser")
        System(TypeScript_AST_Parser, "TypeScript AST Parser")
    }
    System_Ext(DocDirectiveSchema, "DocDirectiveSchema")
    System_Ext(GherkinRulesSupport, "GherkinRulesSupport")
    Rel(GherkinScanner, GherkinASTParser, "uses")
    Rel(GherkinScanner, GherkinRulesSupport, "implements")
    Rel(GherkinASTParser, GherkinRulesSupport, "implements")
    Rel(TypeScript_AST_Parser, DocDirectiveSchema, "uses")
    Rel(GherkinExtractor, GherkinASTParser, "uses")
    Rel(GherkinExtractor, GherkinRulesSupport, "implements")
    Rel(DualSourceExtractor, GherkinExtractor, "uses")
    Rel(DualSourceExtractor, GherkinScanner, "uses")
    Rel(Document_Extractor, Pattern_Scanner, "uses")

Scoped architecture diagram showing component relationships:

graph LR
    subgraph api["Api"]
        MasterDataset[/"MasterDataset"/]
        PatternHelpers["PatternHelpers"]
        ArchQueriesImpl("ArchQueriesImpl")
    end
    subgraph config["Config"]
        ConfigurationTypes["ConfigurationTypes"]
        ProjectConfigTypes["ProjectConfigTypes"]
        ConfigurationPresets["ConfigurationPresets"]
    end
    subgraph taxonomy["Taxonomy"]
        TagRegistryBuilder("TagRegistryBuilder")
    end
    subgraph validation["Validation"]
        FSMTransitions[/"FSMTransitions"/]
        FSMStates[/"FSMStates"/]
    end
    subgraph related["Related"]
        ProcessStateAPI["ProcessStateAPI"]:::neighbor
        TypeScriptTaxonomyImplementation["TypeScriptTaxonomyImplementation"]:::neighbor
        PhaseStateMachineValidation["PhaseStateMachineValidation"]:::neighbor
        DataAPIOutputShaping["DataAPIOutputShaping"]:::neighbor
        DataAPIArchitectureQueries["DataAPIArchitectureQueries"]:::neighbor
    end
    TagRegistryBuilder ..->|implements| TypeScriptTaxonomyImplementation
    ProjectConfigTypes -->|uses| ConfigurationTypes
    ProjectConfigTypes -->|uses| ConfigurationPresets
    ConfigurationPresets -->|uses| ConfigurationTypes
    PatternHelpers ..->|implements| DataAPIOutputShaping
    ArchQueriesImpl -->|uses| ProcessStateAPI
    ArchQueriesImpl -->|uses| MasterDataset
    ArchQueriesImpl ..->|implements| DataAPIArchitectureQueries
    FSMTransitions ..->|implements| PhaseStateMachineValidation
    FSMStates ..->|implements| PhaseStateMachineValidation
    ProcessStateAPI -->|uses| MasterDataset
    ProcessStateAPI ..->|implements| PhaseStateMachineValidation
    DataAPIArchitectureQueries -.->|depends on| DataAPIOutputShaping
    classDef neighbor stroke-dasharray: 5 5

type SectionBlock =
| HeadingBlock
| ParagraphBlock
| SeparatorBlock
| TableBlock
| ListBlock
| CodeBlock
| MermaidBlock
| CollapsibleBlock
| LinkOutBlock;
/**
* Normalize any status string to a display bucket
*
* Maps status values to three canonical display states:
* - "completed": completed
* - "active": active
* - "planned": roadmap, deferred, planned, or any unknown value
*
* Per PDR-005: deferred items are treated as planned (not actively worked on)
*
* @param status - Raw status from pattern (case-insensitive)
* @returns "completed" | "active" | "planned"
*
* @example
* ```typescript
* normalizeStatus("completed") // → "completed"
* normalizeStatus("active") // → "active"
* normalizeStatus("roadmap") // → "planned"
* normalizeStatus("deferred") // → "planned"
* normalizeStatus(undefined) // → "planned"
* ```
*/
function normalizeStatus(status: string | undefined): NormalizedStatus;
ParameterTypeDescription
statusRaw status from pattern (case-insensitive)

Returns: “completed” | “active” | “planned”

/**
* Canonical deliverable status values
*
* These are the ONLY accepted values for the Status column in
* Gherkin Background deliverable tables. Values are lowercased
* at extraction time before schema validation.
*
* - complete: Work is done
* - in-progress: Work is ongoing
* - pending: Work hasn't started
* - deferred: Work postponed
* - superseded: Replaced by another deliverable
* - n/a: Not applicable
*
*/
DELIVERABLE_STATUS_VALUES = [
'complete',
'in-progress',
'pending',
'deferred',
'superseded',
'n/a',
] as const
interface CategoryDefinition {
/** Category tag name without prefix (e.g., "core", "api", "ddd", "saga") */
readonly tag: string;
/** Human-readable domain name for display (e.g., "Strategic DDD", "Event Sourcing") */
readonly domain: string;
/** Display order priority - lower values appear first in sorted output */
readonly priority: number;
/** Brief description of the category's purpose and typical patterns */
readonly description: string;
/** Alternative tag names that map to this category (e.g., "es" for "event-sourcing") */
readonly aliases: readonly string[];
}
PropertyDescription
tagCategory tag name without prefix (e.g., “core”, “api”, “ddd”, “saga”)
domainHuman-readable domain name for display (e.g., “Strategic DDD”, “Event Sourcing”)
priorityDisplay order priority - lower values appear first in sorted output
descriptionBrief description of the category’s purpose and typical patterns
aliasesAlternative tag names that map to this category (e.g., “es” for “event-sourcing”)

View DeliveryProcessFactory source

Main factory function for creating configured delivery process instances. Supports presets, custom configuration, and configuration overrides.

  • At application startup to create a configured instance
  • When switching between different tag prefixes
  • When customizing the taxonomy for a specific project

View DefineConfig source

Identity function for type-safe project configuration. Follows the Vite/Vitest defineConfig() convention: returns the input unchanged, providing only TypeScript type checking.

Validation happens later at load time via Zod schema in loadProjectConfig().

In delivery-process.config.ts at project root:

import { defineConfig } from '@libar-dev/delivery-process/config';
export default defineConfig({
preset: 'ddd-es-cqrs',
sources: { typescript: ['src/** /*.ts'] },
});

View ADR005CodecBasedMarkdownRendering source

Context: The documentation generator needs to transform structured pattern data (MasterDataset) into markdown files. The initial approach used direct string concatenation in generator functions, mixing data selection, formatting logic, and output assembly in a single pass. This made generators hard to test, difficult to compose, and impossible to render the same data in different formats (e.g., full docs vs compact AI context).

Decision: Adopt a codec architecture inspired by serialization codecs (encode/decode). Each document type has a codec that decodes a MasterDataset into a RenderableDocument — an intermediate representation of sections, headings, tables, paragraphs, and code blocks. A separate renderer transforms the RenderableDocument into markdown. This separates data selection (what to include) from formatting (how it looks) from serialization (markdown syntax).

Consequences: | Type | Impact | | Positive | Codecs are pure functions: dataset in, document out — trivially testable | | Positive | RenderableDocument is an inspectable IR — tests assert on structure, not strings | | Positive | Composable via CompositeCodec — reference docs assemble from child codecs | | Positive | Same dataset can produce different outputs (full doc, compact doc, AI context) | | Negative | Extra abstraction layer between data and output | | Negative | RenderableDocument vocabulary must cover all needed output patterns |

Benefits: | Benefit | Before (String Concat) | After (Codec) | | Testability | Assert on markdown strings | Assert on typed section blocks | | Composability | Copy-paste between generators | CompositeCodec assembles children | | Format variants | Duplicate generator logic | Same codec, different renderer | | Progressive disclosure | Manual heading management | Heading depth auto-calculated |

Codecs implement a decode-only contract (2 scenarios)

Invariant: Every codec is a pure function that accepts a MasterDataset and returns a RenderableDocument. Codecs do not perform side effects, do not write files, and do not access the filesystem. The codec contract is decode-only because the transformation is one-directional: structured data becomes a document, never the reverse.

Rationale: Pure functions are deterministic and trivially testable. For the same MasterDataset, a codec always produces the same RenderableDocument. This makes snapshot testing reliable and enables codec output comparison across versions.

Codec call signature:

interface DocumentCodec {
decode(dataset: MasterDataset): RenderableDocument;
}

Verified by:

  • Codec produces deterministic output
  • Codec has no side effects
RenderableDocument is a typed intermediate representation (2 scenarios)

RenderableDocument is a typed intermediate representation

Section titled “RenderableDocument is a typed intermediate representation”

Invariant: RenderableDocument contains a title, an ordered array of SectionBlock elements, and an optional record of additional files. Each SectionBlock is a discriminated union: heading, paragraph, table, code, list, separator, or metaRow. The renderer consumes this IR without needing to know which codec produced it.

Rationale: A typed IR decouples codecs from rendering. Codecs express intent (“this is a table with these rows”) and the renderer handles syntax (“pipe-delimited markdown with separator row”). This means switching output format (e.g., HTML instead of markdown) requires only a new renderer, not changes to every codec.

Section block types:

Block TypePurposeMarkdown Output
headingSection title with depth## Title (depth-adjusted)
paragraphProse textPlain text with blank lines
tableStructured dataPipe-delimited table
codeCode sample with languageFenced code block
listOrdered or unordered items- item or 1. item
separatorVisual break between sections---
metaRowKey-value metadataKey: Value

Verified by:

  • All block types render to markdown
  • Unknown block type is rejected
CompositeCodec assembles documents from child codecs (2 scenarios)

CompositeCodec assembles documents from child codecs

Section titled “CompositeCodec assembles documents from child codecs”

Invariant: CompositeCodec accepts an array of child codecs and produces a single RenderableDocument by concatenating their sections. Child codec order determines section order in the output. Separators are inserted between children by default.

Rationale: Reference documents combine content from multiple domains (patterns, conventions, shapes, diagrams). Rather than building a monolithic codec that knows about all content types, CompositeCodec lets each domain own its codec and composes them declaratively.

Composition example:

const referenceDoc = CompositeCodec.create({
title: 'Architecture Reference',
codecs: [
behaviorCodec, // patterns with rules
conventionCodec, // decision records
shapeCodec, // type definitions
diagramCodec, // mermaid diagrams
],
});

Verified by:

  • Child sections appear in codec array order
  • Empty children are skipped without separators
ADR content comes from both Feature description and Rule prefixes (3 scenarios)

ADR content comes from both Feature description and Rule prefixes

Section titled “ADR content comes from both Feature description and Rule prefixes”

Invariant: ADR structured content (Context, Decision, Consequences) can appear in two locations within a feature file. Both sources must be rendered. Silently dropping either source causes content loss.

Rationale: Early ADRs used name prefixes like “Context - …” and “Decision - …” on Rule blocks to structure content. Later ADRs placed Context, Decision, and Consequences as bold-annotated prose in the Feature description, reserving Rule: blocks for invariants and design rules. Both conventions are valid. The ADR codec must handle both because the codebase contains ADRs authored in each style. The Feature description lives in pattern.directive.description. If the codec only renders Rules (via partitionRulesByPrefix), then Feature description content is silently dropped — no error, no warning. This caused confusion across two repos where ADR content appeared in the feature file but was missing from generated docs. The fix renders pattern.directive.description in buildSingleAdrDocument between the Overview metadata table and the partitioned Rules section, using renderFeatureDescription() which walks content linearly and handles prose, tables, and DocStrings with correct interleaving.

SourceLocationExampleRendered Via
Rule prefixRule: Context - …ADR-001 (taxonomy)partitionRulesByPrefix()
Feature descriptionContext: prose in Feature blockADR-005 (codec rendering)renderFeatureDescription()

Verified by:

  • Feature description content is rendered
  • Rule prefix content is rendered
  • Both sources combine in single ADR
The markdown renderer is codec-agnostic (2 scenarios)

Invariant: The renderer accepts any RenderableDocument regardless of which codec produced it. Rendering depends only on block types, not on document origin. This enables testing codecs and renderers independently.

Rationale: If the renderer knew about specific codecs, adding a new codec would require renderer changes. By operating purely on the SectionBlock discriminated union, the renderer is closed for modification but open for extension via new block types.

Verified by:

  • Same renderer handles different codec outputs
  • Renderer and codec are tested independently

View ADR001TaxonomyCanonicalValues source

Context: The annotation system requires well-defined canonical values for taxonomy tags, FSM status lifecycle, and source ownership rules. Without canonical values, organic growth produces drift (Generator vs Generators, Process vs DeliveryProcess) and inconsistent grouping in generated documentation.

Decision: Define canonical values for all taxonomy enums, FSM states with protection levels, valid transitions, tag format types, and source ownership rules. These are the durable constants of the delivery process.

Consequences: | Type | Impact | | Positive | Generated docs group into coherent sections | | Positive | FSM enforcement has clear, auditable state definitions | | Positive | Source ownership prevents cross-domain tag confusion | | Negative | Migration effort for existing specs with non-canonical values |

Product area canonical values

Invariant: The product-area tag uses one of 7 canonical values. Each value represents a reader-facing documentation section, not a source module.

Rationale: Without canonical values, organic drift (e.g., Generator vs Generators) produces inconsistent grouping in generated documentation and fragmented product area pages.

ValueReader QuestionCovers
AnnotationHow do I annotate code?Scanning, extraction, tag parsing, dual-source
ConfigurationHow do I configure the tool?Config loading, presets, resolution
GenerationHow does code become docs?Codecs, generators, rendering, diagrams
ValidationHow is the workflow enforced?FSM, DoD, anti-patterns, process guard, lint
DataAPIHow do I query process state?Process state API, stubs, context assembly, CLI
CoreTypesWhat foundational types exist?Result monad, error factories, string utils
ProcessHow does the session workflow work?Session lifecycle, handoffs, conventions
ADR category canonical values

Invariant: The adr-category tag uses one of 4 values.

Rationale: Unbounded category values prevent meaningful grouping of architecture decisions and make cross-cutting queries unreliable.

ValuePurpose
architectureSystem structure, component design, data flow
processWorkflow, conventions, annotation rules
testingTest strategy, verification approach
documentationDocumentation generation, content structure
FSM status values and protection levels

Invariant: Pattern status uses exactly 4 values with defined protection levels. These are enforced by Process Guard at commit time.

Rationale: Without protection levels, active specs accumulate scope creep and completed specs get silently modified, undermining delivery process integrity.

StatusProtectionCan Add DeliverablesAllowed Actions
roadmapNoneYesFull editing
activeScope-lockedNoEdit existing deliverables only
completedHard-lockedNoRequires unlock-reason tag
deferredNoneYesFull editing
Valid FSM transitions

Invariant: Only these transitions are valid. All others are rejected by Process Guard.

Rationale: Allowing arbitrary transitions (e.g., roadmap to completed) bypasses the active phase where scope-lock and deliverable tracking provide quality assurance. Completed is a terminal state. Modifications require @libar-docs-unlock-reason escape hatch.

FromToTrigger
roadmapactiveStart work
roadmapdeferredPostpone
activecompletedAll deliverables done
activeroadmapBlocked/regressed
deferredroadmapResume planning
Tag format types

Invariant: Every tag has one of 6 format types that determines how its value is parsed.

Rationale: Without explicit format types, parsers must guess value structure, leading to silent data corruption when CSV values are treated as single strings or numbers are treated as text.

FormatParsingExample
flagBoolean presence, no value@libar-docs-core
valueSimple string@libar-docs-pattern MyPattern
enumConstrained to predefined list@libar-docs-status completed
csvComma-separated values@libar-docs-uses A, B, C
numberNumeric value@libar-docs-phase 15
quoted-valuePreserves spaces@libar-docs-brief:‘Multi word’
Source ownership

Invariant: Relationship tags have defined ownership by source type. Anti-pattern detection enforces these boundaries.

Rationale: Cross-domain tag placement (e.g., runtime dependencies in Gherkin) creates conflicting sources of truth and breaks the dual-source architecture ownership model.

TagCorrect SourceWrong SourceRationale
usesTypeScriptFeature filesTS owns runtime dependencies
depends-onFeature filesTypeScriptGherkin owns planning dependencies
quarterFeature filesTypeScriptGherkin owns timeline metadata
teamFeature filesTypeScriptGherkin owns ownership metadata
Quarter format convention

Invariant: The quarter tag uses YYYY-QN format (e.g., 2026-Q1). ISO-year-first sorting works lexicographically.

Rationale: Non-standard formats (e.g., Q1-2026) break lexicographic sorting, which roadmap generation and timeline queries depend on for correct ordering.

Canonical phase definitions (6-phase USDP standard)

Canonical phase definitions (6-phase USDP standard)

Section titled “Canonical phase definitions (6-phase USDP standard)”

Invariant: The default workflow defines exactly 6 phases in fixed order. These are the canonical phase names and ordinals used by all generated documentation.

Rationale: Ad-hoc phase names and ordering produce inconsistent roadmap grouping across packages and make cross-package progress tracking impossible.

OrderPhasePurpose
1InceptionProblem framing, scope definition
2ElaborationDesign decisions, architecture exploration
3SessionPlanning and design session work
4ConstructionImplementation, testing, integration
5ValidationVerification, acceptance criteria confirmation
6RetrospectiveReview, lessons learned, documentation
Deliverable status canonical values (1 scenarios)

Invariant: Deliverable status (distinct from pattern FSM status) uses exactly 6 values, enforced by Zod schema at parse time.

Rationale: Freeform status strings bypass Zod validation and break DoD checks, which rely on terminal status classification to determine pattern completeness.

ValueMeaning
completeWork is done
in-progressWork is ongoing
pendingWork has not started
deferredWork postponed
supersededReplaced by another
n/aNot applicable

Verified by:

  • Canonical values are enforced

View ConfigBasedWorkflowDefinition source

Problem: Every pnpm process:query and pnpm docs:* invocation prints: Failed to load default workflow (6-phase-standard): Workflow file not found

The loadDefaultWorkflow() function resolves to catalogue/workflows/ which does not exist. The directory was deleted during monorepo extraction. The system already degrades gracefully (workflow = undefined), but the warning is noise for both human CLI use and future hook consumers (HUD).

The old 6-phase-standard.json conflated three concerns:

  • Taxonomy vocabulary (status names) — already in src/taxonomy/
  • FSM behavior (transitions) — already in src/validation/fsm/
  • Workflow structure (phases) — orphaned, no proper home

Solution: Inline the default workflow as a constant in workflow-loader.ts, built from canonical taxonomy values. Make loadDefaultWorkflow() synchronous. Preserve loadWorkflowFromPath() for custom --workflow <file> overrides.

The workflow definition uses only the 4 canonical statuses from ADR-001 (roadmap, active, completed, deferred) — not the stale 5-status set from the deleted JSON (which included non-canonical implemented and partial).

Phase definitions (Inception, Elaboration, Session, Construction, Validation, Retrospective) move from a missing JSON file to an inline constant, making the default workflow always available without file I/O.

Design Decisions (DS-1, 2026-02-15):

| ID | Decision | Rationale | | DD-1 | Inline constant in workflow-loader.ts, not preset integration | Minimal correct fix, zero type regression risk. Preset integration deferred. | | DD-2 | Constant satisfies existing WorkflowConfig type | Reuse createLoadedWorkflow() from workflow-config.ts. No new types needed. | | DD-3 | Remove dead code: getCatalogueWorkflowsPath, loadWorkflowConfig, DEFAULT_WORKFLOW_NAME | Dead since monorepo extraction. Public API break is safe (function always threw). | | DD-4 | loadDefaultWorkflow() returns LoadedWorkflow synchronously | Infallible constant needs no async or error handling. | | DD-5 | Amend ADR-001 with canonical phase definitions | Phase names are canonical values; fits existing governance in ADR-001. |

Default workflow is built from an inline constant (2 scenarios)

Default workflow is built from an inline constant

Section titled “Default workflow is built from an inline constant”

Invariant: loadDefaultWorkflow() returns a LoadedWorkflow without file system access. It cannot fail. The default workflow constant uses only canonical status values from src/taxonomy/status-values.ts.

Rationale: The file-based loading path (catalogue/workflows/) has been dead code since monorepo extraction. Both callers (orchestrator, process-api) already handle the failure gracefully, proving the system works without it. Making the function synchronous and infallible removes the try-catch ceremony and the warning noise.

StepChangeImpact
Add DEFAULT_WORKFLOW_CONFIG constantWorkflowConfig literal with 4 statuses, 6 phasesNew code in workflow-loader.ts
Change loadDefaultWorkflow() to syncReturns createLoadedWorkflow(DEFAULT_WORKFLOW_CONFIG)Signature: Promise to sync
Remove dead code pathsDelete getCatalogueWorkflowsPath, loadWorkflowConfig, DEFAULT_WORKFLOW_NAME, dead importsworkflow-loader.ts cleanup
Remove loadWorkflowConfig from public APIUpdate src/config/index.ts exportsBreaking change (safe: function always threw)
Update orchestrator call siteRemove await and try-catch (lines 410-418)orchestrator.ts
Update process-api call siteRemove await and try-catch (lines 549-555)process-api.ts

Verified by:

  • Default workflow loads without warning

  • Workflow constant uses canonical statuses only

  • Workflow constant uses canonical statuses only

    Implementation approach:

Custom workflow files still work via --workflow flag (1 scenarios)

Custom workflow files still work via —workflow flag

Section titled “Custom workflow files still work via —workflow flag”

Invariant: loadWorkflowFromPath() remains available for projects that need custom workflow definitions. The --workflow <file> CLI flag and workflowPath config field continue to work.

Rationale: The inline default replaces file-based default loading, not file-based custom loading. Projects may define custom phases or additional statuses via JSON files.

Verified by:

  • Custom workflow file overrides default
FSM validation and Process Guard are not affected

FSM validation and Process Guard are not affected

Section titled “FSM validation and Process Guard are not affected”

Invariant: The FSM transition matrix, protection levels, and Process Guard rules remain hardcoded in src/validation/fsm/ and src/lint/process-guard/. They do not read from LoadedWorkflow.

Rationale: FSM and workflow are separate concerns. FSM enforces status transitions (4-state model from PDR-005). Workflow defines phase structure (6-phase USDP). The workflow JSON declared transitionsTo on its statuses, but no code ever read those values — the FSM uses its own VALID_TRANSITIONS constant. This separation is correct and intentional. Blast radius analysis confirmed zero workflow imports in: - src/validation/fsm/ (4 files) - src/lint/process-guard/ (5 files) - src/taxonomy/ (all files)

Workflow as a configurable preset field is deferred

Workflow as a configurable preset field is deferred

Section titled “Workflow as a configurable preset field is deferred”

Invariant: The inline default workflow constant is the only workflow source until preset integration is implemented. No preset or project config field exposes workflow customization.

Rationale: Coupling workflow into the preset/config system before the inline fix ships would widen the blast radius and risk type regressions across all config consumers. Adding workflow as a field on DeliveryProcessConfig (presets) and DeliveryProcessProjectConfig (project config) is a natural next step but NOT required for the MVP fix. The inline constant in workflow-loader.ts resolves the warning. Moving workflow into the preset/config system enables: - Different presets with different default phases (e.g., 3-phase generic) - Per-project phase customization in delivery-process.config.ts - Phase definitions appearing in generated documentation See ideation artifact for design options: delivery-process/ideations/2026-02-15-workflow-config-and-fsm-extensibility.feature

View ProcessGuardTesting source

Pure validation functions for enforcing delivery process rules per PDR-005. All validation follows the Decider pattern: (state, changes, options) => result.

Problem:

  • Completed specs modified without explicit unlock reason
  • Invalid status transitions bypass FSM rules
  • Active specs expand scope unexpectedly with new deliverables
  • Changes occur outside session boundaries

Solution:

  • checkProtectionLevel() enforces unlock-reason for completed (hard) files
  • checkStatusTransitions() validates transitions against FSM matrix
  • checkScopeCreep() prevents deliverable addition to active (scope) specs
  • checkSessionScope() warns about files outside session scope
  • checkSessionExcluded() errors on explicitly excluded files
Completed files require unlock-reason to modify (4 scenarios)

Completed files require unlock-reason to modify

Section titled “Completed files require unlock-reason to modify”

Invariant: A completed spec file cannot be modified unless it carries an @libar-docs-unlock-reason tag.

Rationale: Completed work represents validated, shipped functionality — accidental modification risks regression.

Verified by:

  • Completed file with unlock-reason passes validation
  • Completed file without unlock-reason fails validation
  • Protection levels and unlock requirement
  • File transitioning to completed does not require unlock-reason
Status transitions must follow PDR-005 FSM (2 scenarios)

Status transitions must follow PDR-005 FSM

Section titled “Status transitions must follow PDR-005 FSM”

Invariant: Status changes must follow the directed graph: roadmap->active->completed, roadmap<->deferred, active->roadmap.

Rationale: The FSM prevents skipping required stages (e.g., roadmap->completed bypasses implementation).

Verified by:

  • Valid transitions pass validation
  • Invalid transitions fail validation
Active specs cannot add new deliverables (6 scenarios)

Invariant: A spec in active status cannot have deliverables added that were not present when it entered active.

Rationale: Scope-locking active work prevents mid-sprint scope creep that derails delivery commitments.

Verified by:

  • Active spec with no deliverable changes passes
  • Active spec adding deliverable fails validation
  • Roadmap spec can add deliverables freely
  • Removing deliverable produces warning
  • Deliverable status change does not trigger scope-creep
  • Multiple deliverable status changes pass validation
Files outside active session scope trigger warnings (4 scenarios)

Files outside active session scope trigger warnings

Section titled “Files outside active session scope trigger warnings”

Invariant: Files modified outside the active session’s declared scope produce a session-scope warning.

Rationale: Session scoping keeps focus on planned work and makes accidental cross-cutting changes visible.

Verified by:

  • File in session scope passes validation
  • File outside session scope triggers warning
  • No active session means all files in scope
  • ignoreSession flag suppresses session warnings
Explicitly excluded files trigger errors (3 scenarios)

Invariant: Files explicitly excluded from a session cannot be modified, producing a session-excluded error.

Rationale: Exclusion is stronger than scope — it marks files that must NOT be touched during this session.

Verified by:

  • Excluded file triggers error
  • Non-excluded file passes validation
  • ignoreSession flag suppresses excluded errors
Multiple rules validate independently (3 scenarios)

Invariant: Each validation rule evaluates independently — a single file can produce violations from multiple rules.

Rationale: Independent evaluation ensures no rule masks another, giving complete diagnostic output.

Verified by:

  • Multiple violations from different rules
  • Strict mode promotes warnings to errors
  • Clean change produces empty violations