Skip to content

ADR-021: Doc Generation Proof Of Concept

Purpose: Architecture decision record for Doc Generation Proof Of Concept


PropertyValue
Statussuperseded
Categorydocumentation
Phase27

Status: SUPERSEDED - This POC has been implemented. See:

  • Convention-tagged decision records (ADR/PDR) with @libar-docs-convention tags
  • docs-generated/ANNOTATION-GUIDE.md - Comprehensive guide for fixing generated docs

This decision establishes the pattern for generating technical documentation from annotated source files. It serves as both the DECISION (why/how) and the PROOF OF CONCEPT (demonstrating the pattern works).

Invariant: Documentation must be generated from annotated source code, never manually maintained as a separate artifact.

Rationale: Manual documentation drifts from source as the codebase evolves, creating stale references that mislead both humans and AI coding sessions.

DocumentLinesMaintenance Burden
docs/PROCESS-GUARD.md~300High - duplicates code behavior
docs/METHODOLOGY.md~400Medium - conceptual, changes less
_claude-md/validation/*.md~50 eachHigh - must match detailed docs
CLAUDE.md~800Very High - aggregates everything
GapImpactSolution
Shape extraction from TypeScriptHighNew @extract-shapes tag
Convention-tagged contentMediumDecision records as convention sources
Durable intro/context contentMediumDecision Rule: Context sections

The Problem:

Common technical documentation is the hardest part to maintain in a repository.
The volume constantly grows, and AI coding sessions are drastically less effective
at updating documentation compared to code. Documentation drifts from source.
Current state in this package:
**Root Causes:**
1. **Duplication** - Same information exists in code comments, feature files,
and markdown docs. Changes require updating multiple places.
2. **No Single Source** - Documentation is authored separately from the code
it describes. There's no compilation step to catch drift.
3. **Detail Level Mismatch** - Compact docs for AI context and detailed docs
for humans are maintained separately despite sharing content.
**What We Have:**
The delivery-process package already has the required ingredients:
- Pattern extraction from TypeScript JSDoc and Gherkin tags
- Rich content support (DocStrings, tables, code blocks in features)
- Multi-source aggregation via tag taxonomy
- Progressive disclosure via codec detail levels
- Relationship tags for cross-references
**What's Missing:**

Invariant: Each content type (intro/rationale, rules/examples, API types) is owned by exactly one source type (decision, behavior spec, or code).

Rationale: Shared ownership leads to conflicting updates and ambiguous authority over what the “correct” version is.

Source TypeDurabilityContent Ownership
Decision documents (ADR/PDR)PermanentIntro, context, rationale, conventions
Behavior specs (.feature)PermanentRules, examples, acceptance criteria
Implementation code (.ts)CompiledAPI types, error messages, signatures
Rule PrefixADR SectionDoc Section
Context...context## Background / Introduction
Decision...decision## How It Works
Consequence...consequences## Trade-offs
Other rulesother (warning logged)Custom sections
Target DocumentSourcesDetail LevelEffect
docs/PROCESS-GUARD.mdThis decision + behavior specs + codedetailedAll sections, full JSDoc
_claude-md/validation/process-guard.mdThis decision + behavior specs + codesummaryRules table, types only
LevelContent IncludedRendering Style
summaryEssential tables, type names onlyCompact - lists vs code blocks
standardTables, types, key descriptionsBalanced
detailedEverything including JSDoc, examplesFull - code blocks with JSDoc
SourceWhat’s ExtractedHow
Decision Rule: ContextIntro/background sectionRule description text
Decision Rule: DecisionHow it works sectionRule description text
Decision Rule: ConsequencesTrade-offs sectionRule description text
Decision DocStringsCode examples (Husky, API)Fenced code blocks
Behavior spec RulesValidation rules, business rulesRule names + descriptions
Behavior spec Scenario OutlinesDecision tables, lookup tablesExamples tables
TypeScript @extract-shapesAPI types, interfacesAST extraction
TypeScript JSDocImplementation notesMarkdown in comments

The Pattern:

Documentation is generated from three source types with different durability:
**Why Decisions Own Intro Content:**
Tier 1 specs (roadmap features) become clutter after implementation - their
deliverables are done, status is completed, they pile up. Behavior specs stay
current because tests must pass. But neither is appropriate for intro content.
Decisions (ADR/PDR) are durable by design - they remain valid until explicitly
superseded. The `Rule: Context` section of a decision IS the background/intro
for any documentation about that topic.
**Extends Existing ADR Codec:**
The doc-from-decision generator extends the existing `AdrDocumentCodec` which
already parses Rule: prefixes via `partitionAdrRules()` (see adr.ts:627-663):
**Source Mapping Pattern:**
Each documentation decision declares its target documents and source mapping:
**Detail Level Mapping:**
Uses existing `DetailLevel` enum from `renderable/codecs/types/base.ts`:
**Extraction by Source Type:**
**The Generator Command:**
Terminal window
generate-docs --decisions 'specs/**/*.feature' --features 'tests/**/*.feature' --typescript 'src/**/*.ts' --generators doc-from-decision --output docs

Invariant: The source mapping table in a decision document defines how documentation sections are assembled from multiple source files.

Rationale: Without a declarative mapping, generators must hard-code source-to-section relationships, making the system brittle to new document types.

ColumnPurposeExample
SectionTarget section heading in generated doc”Intro & Context”, “API Types”
Source FilePath to source file or self-reference marker”src/types.ts”, “THIS DECISION”
Extraction MethodHow to extract content from source”@extract-shapes”, “Rule blocks”
MarkerMeaning
THIS DECISIONExtract from the current decision document
THIS DECISION (Rule: X)Extract specific Rule: block from current document
THIS DECISION (DocString)Extract fenced code blocks from current document
Extraction MethodSource TypeAction
Decision rule descriptionDecision (.feature)Extract Rule: block content (Invariant, Rationale)
@extract-shapes tagTypeScript (.ts)Invoke shape extractor for @libar-docs-extract-shapes
Rule blocksBehavior spec (.feature)Extract Rule: names and descriptions
Scenario Outline ExamplesBehavior spec (.feature)Extract Examples tables as markdown
JSDoc sectionTypeScript (.ts)Extract markdown from JSDoc comments
createViolation() patternsTypeScript (.ts)Extract error message literals
Fenced code blockDecision (.feature)Extract DocString code blocks with language

Table Format:

**Self-Reference Markers:**
**Extraction Method Dispatch:**
**Path Resolution:**
- Relative paths are resolved from project root
- `THIS DECISION` resolves to the current decision document
- Missing files produce warnings but generation continues

Invariant: Decision documents remain the authoritative source for intro, rationale, and convention content until explicitly superseded.

Rationale: Without durable ownership, documentation sections lose their authoritative source and degrade into unattributed prose that no one updates.

BenefitHow
Single source of truthEach content type owned by one source
Always-current docsGenerated from tested/compiled sources
Reduced maintenanceChange source once, docs regenerate
Progressive disclosureSame sources → compact + detailed outputs
Clear ownershipDecisions own “why”, code owns “what”
Trade-offMitigation
Decisions must be updated for fundamental changesAppropriate - fundamentals ARE decisions
New @extract-shapes capability requiredSpec created (shape-extraction.feature)
Initial annotation effort on existing codeOne-time migration, then maintained
Generated docs in git historySame as current manual approach
Content TypeOwnerUpdate Trigger
Intro, rationale, contextDecision documentFundamental change to approach
Rules, examples, edge casesBehavior specsBehavior change (tests fail)
API types, signaturesCode with @extract-shapesInterface change (compile fail)
Error messagesCode patternsMessage text change
Code examplesDecision DocStringsExample needs update

Benefits:

**Trade-offs:**
**Ownership Boundaries:**

Invariant: Pre-implementation design stubs must reside in delivery-process/stubs/, never in src/.

Rationale: Stubs in src/ require ESLint exceptions, create confusion between production and design code, and risk accidental imports of unimplemented functions.

IssueImpact
ESLint exceptions neededRules relaxed for “not-yet-real” code
ConfusionWhat’s production vs. what’s design?
PollutionStubs mixed with implemented code
Import accidentsOther code might import unimplemented stubs
Maintenance burdenMust track which files are stubs
LocationContentWhen Moved to src/
delivery-process/stubs/{pattern}/*.tsAPI shapes, interfaces, throw-not-implementedImplementation session
src/*/.tsProduction code onlyAlready there
BenefitHow
No ESLint exceptionsStubs aren’t in src/, no relaxation needed
Clear separationdelivery-process/stubs/ = design, src/ = production
Documentation sourceStubs with @extract-shapes generate API docs
Safe iterationCan refine stub APIs without breaking anything
Implementation signalMoving from delivery-process/stubs/ to src/ = implementation started
DocumentDecision Source
docs/METHODOLOGY.mdADR for delivery process methodology
docs/TAXONOMY.mdPDR-006 TypeScript Taxonomy (exists)
docs/VALIDATION.mdADR for validation approach
docs/SESSION-GUIDES.mdADR for session workflows
_claude-md/*/.mdCorresponding decisions with compact extraction

The Problem:

Design stubs (pre-implementation API shapes) placed in `src/` cause issues:
Example of the anti-pattern (from monorepo eslint.config.js):
// TODO: Delivery process design artifacts: Relax unused-vars
{
files: [
"**/packages/platform-core/src/durability/durableAppend.ts",
"**/packages/platform-core/src/durability/intentCompletion.ts",
// ... more stubs in src/ ...
],
rules: {
"@typescript-eslint/no-unused-vars": "off",
},
}

The Solution:

Design stubs live in `delivery-process/stubs/`:
**Design Stub Pattern:**
delivery-process/stubs/shape-extractor/shape-extractor.ts
/**
* @libar-docs
* @libar-docs-pattern ShapeExtractorStub
* @libar-docs-status roadmap
*
* ## Shape Extractor - Design Stub
*
* API design for extracting TypeScript types from source files.
*/
export interface ExtractedShape {
name: string;
kind: 'interface' | 'type' | 'enum' | 'function';
sourceText: string;
}
export function extractShapes(
sourceCode: string,
shapeNames: string[]
): Map<string, ExtractedShape> {
throw new Error('ShapeExtractor not yet implemented - roadmap pattern');
}

Benefits:

**Workflow:**
1. **Design session:** Create stub in `delivery-process/stubs/{pattern-name}/`
2. **Iterate:** Refine API shapes, add JSDoc, test with docs generation
3. **Implementation session:** Move/copy to `src/`, implement real logic
4. **Stub becomes example:** Original stub stays as reference (optional)
**What This Enables:**
Once proven with Process Guard, the pattern applies to all documentation:

Proof of Concept - Self-documentation validates the pattern

Section titled “Proof of Concept - Self-documentation validates the pattern”

Invariant: The documentation generation pattern must be validated by generating documentation about itself from its own annotated sources.

Rationale: A self-referential proof of concept exposes extraction gaps and source mapping issues that synthetic test data would miss. This POC demonstrates the doc-from-decision pattern by generating docs about ITSELF. The DocGenerationProofOfConcept pattern produces:

OutputPurposeDetail Level
docs/DOC-GENERATION-PROOF-OF-CONCEPT.mdDetailed referencedetailed
_claude-md/generated/doc-generation-proof-of-concept.mdAI contextsummary
SectionSource FileExtraction Method
Intro & ContextTHIS DECISION (Rule: Context above)Decision rule description
How It WorksTHIS DECISION (Rule: Decision above)Decision rule description
Validation Rulestests/features/validation/process-guard.featureRule blocks
Protection Levelsdelivery-process/specs/process-guard-linter.featureScenario Outline Examples
Valid Transitionsdelivery-process/specs/process-guard-linter.featureScenario Outline Examples
API Typessrc/lint/process-guard/types.ts@extract-shapes tag
Decider APIsrc/lint/process-guard/decider.ts@extract-shapes tag
CLI Optionssrc/cli/lint-process.tsJSDoc section
Error Messagessrc/lint/process-guard/decider.tscreateViolation() patterns
Pre-commit SetupTHIS DECISION (DocString)Fenced code block
Programmatic APITHIS DECISION (DocString)Fenced code block
SituationSolutionExample
Fix bug in completed specAdd unlock reason tag@libar-docs-unlock-reason:'Fix-typo'
Modify outside session scopeUse ignore flaglint-process --staged --ignore-session
CI treats warnings as errorsUse strict flaglint-process --all --strict
Skip workflow (legacy import)Multiple transitionsSet roadmap then completed in same commit

Process Guard docs are generated separately from adr-006-process-guard.feature.

**Source Mapping for POC Self-Documentation:**
This source mapping demonstrates all extraction methods by extracting content
from this POC's own sources. The table serves as both documentation AND test data.
**Pre-commit Hook Setup:**
File: `.husky/pre-commit`
Terminal window
npx lint-process --staged

Package.json Scripts:

{
"scripts": {
"lint:process": "lint-process --staged",
"lint:process:ci": "lint-process --all --strict"
}
}

Programmatic API Example:

import {
deriveProcessState,
detectStagedChanges,
validateChanges,
hasErrors,
summarizeResult,
} from '@libar-dev/delivery-process/lint';
// 1. Derive state from annotations
const state = (await deriveProcessState({ baseDir: '.' })).value;
// 2. Detect changes
const changes = detectStagedChanges('.').value;
// 3. Validate
const { result } = validateChanges({
state,
changes,
options: { strict: false, ignoreSession: false },
});
// 4. Handle results
if (hasErrors(result)) {
console.log(summarizeResult(result));
process.exit(1);
}

Escape Hatches:

Expected Output - Compact claude module structure

Section titled “Expected Output - Compact claude module structure”

Invariant: Compact output must contain only essential tables and type names, with no JSDoc comments or implementation details.

Rationale: AI context windows are finite; including non-essential content displaces actionable information and degrades session effectiveness.

SectionContent
Header + IntroPattern name, problem/solution summary
API TypesCore interface definitions (DeciderInput, ValidationResult)
7 Validation RulesRule table with severity and description
Protection LevelsStatus-to-protection mapping table
CLIEssential command examples
LinkReference to full documentation

File: _claude-md/validation/process-guard.md

The compact module extracts only essential content for AI context.
Output size depends on source mapping entries - there is no artificial line limit.
**Expected Sections:**
**Key Characteristics:**
- Summary detail level (essential tables only)
- No JSDoc comments or implementation details
- Tables for structured data (rules, protection levels)
- Inline code blocks for CLI examples
- Cross-reference to detailed documentation

← Back to All Decisions