diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 763c795..f867075 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -42,6 +42,7 @@ jobs:
pip install -e packages/datasets_pdf[dev]
pip install -e packages/adapters/collector_gh[dev]
pip install -e packages/services/normalize_issues[dev]
+ pip install -e packages/services/coverage_matrix[dev]
pip install -e "apps/cli[dev]"
- name: Run golden verification
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d68aceb..73450dc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -47,6 +47,7 @@ jobs:
pip install -e packages/datasets_pdf[dev]
pip install -e packages/adapters/collector_gh[dev]
pip install -e packages/services/normalize_issues[dev]
+ pip install -e packages/services/coverage_matrix[dev]
pip install -e "apps/cli[dev]"
- name: Run QA checks (Black, Pylint, mypy, Pytest)
diff --git a/.gitignore b/.gitignore
index 267521d..37ed922 100644
--- a/.gitignore
+++ b/.gitignore
@@ -211,3 +211,7 @@ outputs/
.DS_Store
doc-issues.json
pdf_ready.json
+ui-tests.json
+doc-source.json
+spec.md
+coverage-matrix.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 6028021..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [Unreleased]
-
-### Added
-- Initial project scaffolding and directory structure
-- Root pyproject.toml with Black, Mypy, and pytest configuration
-- CHANGELOG.md following Keep a Changelog format
-- Copyright header template for Python files
-- Updated README.md with project overview and quickstart
-
-### Changed
-
-### Fixed
-
-### Removed
diff --git a/DEVELOPER.md b/DEVELOPER.md
index 9d303b7..c0a84e8 100644
--- a/DEVELOPER.md
+++ b/DEVELOPER.md
@@ -39,6 +39,7 @@ pip install -e packages/core[dev]
pip install -e packages/datasets_pdf[dev]
pip install -e packages/adapters/collector_gh[dev]
pip install -e packages/services/normalize_issues[dev]
+pip install -e packages/services/coverage_matrix[dev]
pip install -e "apps/cli[dev]"
```
@@ -53,6 +54,7 @@ pip install -e "apps/cli[dev]"
| `packages/datasets_pdf` | `living-doc-datasets-pdf` | Pydantic models and JSON schemas for PDF contracts |
| `packages/adapters/collector_gh` | `living-doc-adapter-collector-gh` | Detector and parser for collector-gh output |
| `packages/services/normalize_issues` | `living-doc-service-normalize-issues` | Issue normalization service |
+| `packages/services/coverage_matrix` | `living-doc-service-coverage-matrix` | AC-level test coverage matrix generator |
| `apps/cli` | `living-doc-cli` | CLI entry point (`living-doc` command) |
Each package has its own `pyproject.toml`, `src/` layout, and `tests/` directory.
@@ -103,6 +105,7 @@ make py-qa-core # packages/core
make py-qa-datasets-pdf # packages/datasets_pdf
make py-qa-collector-gh # packages/adapters/collector_gh
make py-qa-normalize # packages/services/normalize_issues
+make py-qa-coverage # packages/services/coverage_matrix
make py-qa-cli # apps/cli
```
@@ -151,7 +154,7 @@ pylint src/living_doc_core/json_utils.py
From the **repository root**:
```shell
-for pkg in packages/core packages/datasets_pdf packages/adapters/collector_gh packages/services/normalize_issues apps/cli; do
+for pkg in packages/core packages/datasets_pdf packages/adapters/collector_gh packages/services/normalize_issues packages/services/coverage_matrix apps/cli; do
echo "=== Pylint: $pkg ==="
(cd "$pkg" && pylint $(git ls-files '*.py' | grep -v '^tests/'))
done
@@ -211,7 +214,7 @@ mypy .
From the **repository root**:
```shell
-for pkg in packages/core packages/datasets_pdf packages/adapters/collector_gh packages/services/normalize_issues apps/cli; do
+for pkg in packages/core packages/datasets_pdf packages/adapters/collector_gh packages/services/normalize_issues packages/services/coverage_matrix apps/cli; do
echo "=== mypy: $pkg ==="
(cd "$pkg" && mypy .)
done
@@ -242,7 +245,7 @@ pytest --cov=src -v tests/ --cov-fail-under=80
From the **repository root**:
```shell
-for pkg in packages/core packages/datasets_pdf packages/adapters/collector_gh packages/services/normalize_issues apps/cli; do
+for pkg in packages/core packages/datasets_pdf packages/adapters/collector_gh packages/services/normalize_issues packages/services/coverage_matrix apps/cli; do
echo "=== Tests: $pkg ==="
(cd "$pkg" && pytest --cov=src -v tests/ --cov-fail-under=80)
done
@@ -304,6 +307,12 @@ living-doc normalize-issues \
--source auto \
--document-title "Sprint 42 Report" \
--document-version "1.0.0"
+
+living-doc coverage-matrix \
+ --doc-input doc-source.json \
+ --tests-input ui-tests.json \
+ --output coverage-matrix.json \
+ --fail-under 80
```
## Branch Naming Convention (PID:H-1)
diff --git a/Makefile b/Makefile
index 7d081c5..758ebe2 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,8 @@
PACKAGES := packages/core \
packages/datasets_pdf \
packages/adapters/collector_gh \
- packages/services/normalize_issues
+ packages/services/normalize_issues \
+ packages/services/coverage_matrix
APPS := apps/cli
ALL_TARGETS := $(PACKAGES) $(APPS)
@@ -30,6 +31,7 @@ install: ## Install all packages with [dev] dependencies
$(PYTHON) -m pip install -e packages/datasets_pdf[dev]
$(PYTHON) -m pip install -e packages/adapters/collector_gh[dev]
$(PYTHON) -m pip install -e packages/services/normalize_issues[dev]
+ $(PYTHON) -m pip install -e packages/services/coverage_matrix[dev]
$(PYTHON) -m pip install -e "apps/cli[dev]"
@echo "$(GREEN)✓ All packages installed$(NC)"
@@ -53,6 +55,7 @@ help: ## Show this help message
@echo " make py-qa-datasets-pdf Run all QA on packages/datasets_pdf"
@echo " make py-qa-collector-gh Run all QA on packages/adapters/collector_gh"
@echo " make py-qa-normalize Run all QA on packages/services/normalize_issues"
+ @echo " make py-qa-coverage Run all QA on packages/services/coverage_matrix"
@echo " make py-qa-cli Run all QA on apps/cli"
@echo ""
@echo "$(YELLOW)Run individual checks by package:$(NC)"
@@ -62,7 +65,7 @@ help: ## Show this help message
@echo " make pytest-unit-core Run tests on packages/core"
@echo ""
@echo "$(YELLOW)Package shortcuts (replace 'core' with any package):$(NC)"
- @echo " - datasets_pdf, collector_gh, normalize, cli"
+ @echo " - datasets_pdf, collector_gh, normalize, coverage, cli"
# ============================================================================
# ALL PACKAGES: Aggregated QA targets
@@ -111,6 +114,9 @@ py-qa-collector-gh: black-packages/adapters/collector_gh pylint-packages/adapter
py-qa-normalize: black-packages/services/normalize_issues pylint-packages/services/normalize_issues mypy-packages/services/normalize_issues pytest-unit-packages/services/normalize_issues
@echo "$(GREEN)✓ All QA checks passed for packages/services/normalize_issues$(NC)"
+py-qa-coverage: black-packages/services/coverage_matrix pylint-packages/services/coverage_matrix mypy-packages/services/coverage_matrix pytest-unit-packages/services/coverage_matrix
+ @echo "$(GREEN)✓ All QA checks passed for packages/services/coverage_matrix$(NC)"
+
py-qa-cli: black-apps/cli pylint-apps/cli mypy-apps/cli pytest-unit-apps/cli
@echo "$(GREEN)✓ All QA checks passed for apps/cli$(NC)"
diff --git a/README.md b/README.md
index 9c68b8a..7264f60 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,13 @@ living-doc normalize-issues \
--source auto \
--document-title "Sprint 42 Report" \
--document-version "1.0.0"
+
+# Generate an AC-level test coverage matrix
+living-doc coverage-matrix \
+ --doc-input doc-source.json \
+ --tests-input ui-tests.json \
+ --output coverage-matrix.json \
+ --fail-under 80
```
## Documentation
@@ -78,6 +85,11 @@ Converts collector output (`doc-issues.json`) into PDF-ready canonical JSON (`pd
- [Recipe: Local usage](docs/recipes/local-normalize-issues.md) — Run the CLI on your machine
- [Recipe: GitHub Actions](docs/recipes/github-actions-normalize-issues.md) — CI/CD workflow integration
+### `coverage-matrix`
+Cross-references a `doc-source.json` (User Stories + acceptance criteria) with a `ui-tests.json` (E2E test scenarios) and produces `coverage-matrix.json`: an AC-level test coverage matrix per User Story.
+
+- [Package README](packages/services/coverage_matrix/README.md) — Matching logic, CLI reference, module layout
+
## License
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for full details.
diff --git a/apps/cli/pyproject.toml b/apps/cli/pyproject.toml
index 4833794..1ae5c5d 100644
--- a/apps/cli/pyproject.toml
+++ b/apps/cli/pyproject.toml
@@ -15,6 +15,7 @@ requires-python = ">=3.14"
dependencies = [
"living-doc-core",
"living-doc-service-normalize-issues",
+ "living-doc-service-coverage-matrix",
"click>=8.1.0,<9.0.0"
]
diff --git a/apps/cli/src/living_doc_cli/commands/coverage_matrix.py b/apps/cli/src/living_doc_cli/commands/coverage_matrix.py
new file mode 100644
index 0000000..f0d9bef
--- /dev/null
+++ b/apps/cli/src/living_doc_cli/commands/coverage_matrix.py
@@ -0,0 +1,76 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+coverage-matrix CLI command.
+"""
+
+import sys
+
+import click
+
+from living_doc_core.errors import ToolkitError # type: ignore[import-untyped]
+from living_doc_service_coverage_matrix.service import run_service # type: ignore[import-untyped]
+
+
+@click.command("coverage-matrix")
+@click.option(
+ "--doc-input",
+ "doc_input",
+ required=True,
+ type=click.Path(exists=False),
+ help="Path to the US+AC doc JSON file (doc-source.json / doc-issues.json)",
+)
+@click.option(
+ "--tests-input",
+ "tests_input",
+ required=True,
+ type=click.Path(exists=False),
+ help="Path to the ui-tests JSON file",
+)
+@click.option(
+ "--output",
+ "output_path",
+ required=True,
+ type=click.Path(),
+ help="Destination path for coverage-matrix.json",
+)
+@click.option(
+ "--fail-under",
+ "fail_under",
+ type=float,
+ default=None,
+ help="Exit with code 1 if coverage_pct is below this threshold",
+)
+@click.option("--verbose", is_flag=True, help="Enable verbose logging")
+@click.pass_context
+def coverage_matrix( # pylint: disable=too-many-arguments,too-many-positional-arguments
+ ctx: click.Context,
+ doc_input: str,
+ tests_input: str,
+ output_path: str,
+ fail_under: float | None,
+ verbose: bool,
+) -> None:
+ """
+ Generate an AC-level test coverage matrix.
+
+ Cross-references doc-source output (User Stories + acceptance criteria) with
+ ui-tests output (test scenarios) into coverage-matrix.json.
+ """
+ global_verbose = ctx.obj.get("verbose", False) if ctx.obj else False
+ options = {
+ "verbose": verbose or global_verbose,
+ "fail_under": fail_under,
+ }
+
+ try:
+ run_service(doc_input, tests_input, output_path, options)
+ click.echo(f"Successfully generated coverage matrix -> {output_path}")
+
+ except ToolkitError as e:
+ click.echo(f"Error: {e.message}", err=True)
+ sys.exit(1)
+
+ except Exception as e: # pylint: disable=broad-exception-caught
+ click.echo(f"Error: Unexpected error: {e}", err=True)
+ sys.exit(1)
diff --git a/apps/cli/src/living_doc_cli/main.py b/apps/cli/src/living_doc_cli/main.py
index 1ba05ab..d64c7ac 100644
--- a/apps/cli/src/living_doc_cli/main.py
+++ b/apps/cli/src/living_doc_cli/main.py
@@ -7,6 +7,7 @@
import click
from living_doc_cli import __version__
+from living_doc_cli.commands.coverage_matrix import coverage_matrix
from living_doc_cli.commands.normalize_issues import normalize_issues
@@ -27,6 +28,7 @@ def cli(ctx: click.Context, verbose: bool) -> None:
cli.add_command(normalize_issues)
+cli.add_command(coverage_matrix)
if __name__ == "__main__":
diff --git a/apps/cli/tests/integration/test_cli_invocation.py b/apps/cli/tests/integration/test_cli_invocation.py
index 55904e9..afde20a 100644
--- a/apps/cli/tests/integration/test_cli_invocation.py
+++ b/apps/cli/tests/integration/test_cli_invocation.py
@@ -40,7 +40,7 @@ def valid_input_data():
"enterprise": None,
},
},
- "items": [
+ "user_stories": [
{
"id": "github:owner/repo#1",
"title": "Test Issue",
@@ -48,7 +48,11 @@ def valid_input_data():
"tags": ["enhancement"],
"url": "https://github.com/owner/repo/issues/1",
"timestamps": {"created": "2026-01-01T00:00:00Z", "updated": "2026-01-02T00:00:00Z"},
- "body": "## Description\nThis is a test issue.\n\n## Acceptance Criteria\n- Criterion 1\n- Criterion 2",
+ "description": "This is a test issue.",
+ "acceptance_criteria": [
+ {"id": "GH-1-01", "state": "Active", "version": "v1.0.0", "description": "Criterion 1"},
+ {"id": "GH-1-02", "state": "Active", "version": "v1.0.0", "description": "Criterion 2"},
+ ],
}
],
}
diff --git a/apps/cli/tests/test_cli.py b/apps/cli/tests/test_cli.py
index 7ac41f9..aa0d204 100644
--- a/apps/cli/tests/test_cli.py
+++ b/apps/cli/tests/test_cli.py
@@ -221,3 +221,114 @@ def test_normalize_issues_unexpected_error(mock_run_service, runner):
assert result.exit_code == 1
assert "Unexpected error:" in result.output
assert "Unexpected failure" in result.output
+
+
+def test_cli_help_lists_coverage_matrix(runner):
+ """Test that coverage-matrix appears in the top-level help."""
+ result = runner.invoke(cli, ["--help"])
+ assert result.exit_code == 0
+ assert "coverage-matrix" in result.output
+
+
+def test_coverage_matrix_help(runner):
+ """Test that coverage-matrix --help displays usage information."""
+ result = runner.invoke(cli, ["coverage-matrix", "--help"])
+ assert result.exit_code == 0
+ assert "--doc-input" in result.output
+ assert "--tests-input" in result.output
+ assert "--output" in result.output
+ assert "--fail-under" in result.output
+
+
+def test_coverage_matrix_missing_required_args(runner):
+ """Test that missing required arguments shows an error."""
+ result = runner.invoke(cli, ["coverage-matrix"])
+ assert result.exit_code != 0
+ assert "Missing option" in result.output or "Error" in result.output
+
+
+@patch("living_doc_cli.commands.coverage_matrix.run_service")
+def test_coverage_matrix_success(mock_run_service, runner):
+ """Test successful execution of coverage-matrix command."""
+ mock_run_service.return_value = None
+
+ result = runner.invoke(
+ cli,
+ [
+ "coverage-matrix",
+ "--doc-input",
+ "doc.json",
+ "--tests-input",
+ "tests.json",
+ "--output",
+ "out.json",
+ ],
+ )
+
+ assert result.exit_code == 0
+ assert "Successfully generated coverage matrix" in result.output
+ mock_run_service.assert_called_once()
+
+ call_args = mock_run_service.call_args
+ assert call_args[0][0] == "doc.json"
+ assert call_args[0][1] == "tests.json"
+ assert call_args[0][2] == "out.json"
+ options = call_args[0][3]
+ assert options["verbose"] is False
+ assert options["fail_under"] is None
+
+
+@patch("living_doc_cli.commands.coverage_matrix.run_service")
+def test_coverage_matrix_with_fail_under_and_verbose(mock_run_service, runner):
+ """Test coverage-matrix with --fail-under and --verbose options."""
+ mock_run_service.return_value = None
+
+ result = runner.invoke(
+ cli,
+ [
+ "coverage-matrix",
+ "--doc-input",
+ "doc.json",
+ "--tests-input",
+ "tests.json",
+ "--output",
+ "out.json",
+ "--fail-under",
+ "75",
+ "--verbose",
+ ],
+ )
+
+ assert result.exit_code == 0
+ options = mock_run_service.call_args[0][3]
+ assert options["verbose"] is True
+ assert options["fail_under"] == 75.0
+
+
+@patch("living_doc_cli.commands.coverage_matrix.run_service")
+def test_coverage_matrix_toolkit_error_exits_1(mock_run_service, runner):
+ """Test exit code 1 when the service raises a ToolkitError."""
+ mock_run_service.side_effect = InvalidInputError("Doc input must contain an 'items' array")
+
+ result = runner.invoke(
+ cli,
+ ["coverage-matrix", "--doc-input", "doc.json", "--tests-input", "tests.json", "--output", "out.json"],
+ )
+
+ assert result.exit_code == 1
+ assert "Doc input must contain an 'items' array" in result.output
+
+
+@patch("living_doc_cli.commands.coverage_matrix.run_service")
+def test_coverage_matrix_unexpected_error_exits_1(mock_run_service, runner):
+ """Test exit code 1 for unexpected errors."""
+ mock_run_service.side_effect = RuntimeError("boom")
+
+ result = runner.invoke(
+ cli,
+ ["coverage-matrix", "--doc-input", "doc.json", "--tests-input", "tests.json", "--output", "out.json"],
+ )
+
+ assert result.exit_code == 1
+ assert "Unexpected error:" in result.output
+ assert "boom" in result.output
diff --git a/docs/architecture.md b/docs/architecture.md
index e14a311..1f6d126 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -51,17 +51,16 @@ graph LR
1. **Collector Action** (`AbsaOSS/living-doc-collector-gh`)
- Collects issues from GitHub
- - Outputs: `doc-issues.json`
+ - Outputs: `doc-issues.json`, `doc-source.json`, `ui-tests.json`
2. **Adapter** (`packages/adapters/collector_gh`)
- Detects producer format
- Parses input into internal representation
- Outputs: `AdapterResult`
-3. **Service** (`packages/services/normalize_issues`)
- - Normalizes markdown sections
- - Builds canonical JSON structure
- - Outputs: `pdf_ready.json`
+3. **Services** (`packages/services/*`)
+ - `normalize_issues`: Normalizes markdown sections into PDF-ready JSON
+ - `coverage_matrix`: Cross-references User Stories with UI tests into an AC-level coverage matrix
4. **Dataset** (`packages/datasets_pdf`)
- Validates output against schema
@@ -140,6 +139,7 @@ graph TD
Adapters --> CollectorGH[collector_gh/]
Services --> NormalizeIssues[normalize_issues/]
+ Services --> CoverageMatrix[coverage_matrix/]
Apps --> CLI[cli/]
@@ -165,10 +165,18 @@ graph TD
ServiceSrc --> Service[service.py]
ServiceSrc --> Normalizer[normalizer.py]
ServiceSrc --> Builder[builder.py]
-
+
+ CoverageMatrix --> CovSrc[src/living_doc_service_coverage_matrix/]
+ CovSrc --> CovService[service.py]
+ CovSrc --> CovLoader[loader.py]
+ CovSrc --> CovMatcher[matcher.py]
+ CovSrc --> CovSummary[summary.py]
+ CovSrc --> CovModel[model/coverage_item.py]
+
CLI --> CLISrc[src/living_doc_cli/]
CLISrc --> Main[main.py]
CLISrc --> Commands[commands/normalize_issues.py]
+ CLISrc --> CovCmd[commands/coverage_matrix.py]
style Root fill:#e1f5ff,stroke:#0288d1
style Core fill:#fff9c4,stroke:#f57f17
@@ -183,15 +191,18 @@ graph TD
```mermaid
graph TD
CLI[apps/cli] --> NormalizeService[packages/services/normalize_issues]
+ CLI --> CoverageService[packages/services/coverage_matrix]
NormalizeService --> Core[packages/core]
NormalizeService --> Datasets[packages/datasets_pdf]
NormalizeService --> CollectorGH[packages/adapters/collector_gh]
-
+ CoverageService --> Core
+
CollectorGH --> Core
Datasets --> Core
-
+
style CLI fill:#fce4ec,stroke:#c2185b
style NormalizeService fill:#ffe0b2,stroke:#e64a19
+ style CoverageService fill:#ffe0b2,stroke:#e64a19
style Core fill:#fff9c4,stroke:#f57f17
style Datasets fill:#f3e5f5,stroke:#7b1fa2
style CollectorGH fill:#e8f5e9,stroke:#388e3c
@@ -201,8 +212,8 @@ graph TD
- **Core** has no dependencies (lowest level)
- **Datasets** depends only on Core
- **Adapters** depend on Core
-- **Services** depend on Core, Datasets, and specific Adapters
-- **CLI** depends on Core and specific Services
+- **Services** depend on Core, and optionally Datasets and specific Adapters
+- **CLI** depends on Core and all Services
---
@@ -267,6 +278,55 @@ graph TB
---
+### Coverage-Matrix Service Components
+
+```mermaid
+graph TB
+ subgraph "Service Package (packages/services/coverage_matrix)"
+ CovService[service.py
Orchestration]
+ CovLoader[loader.py
I/O Boundary]
+ CovMatcher[matcher.py
Pure Matching Logic]
+ CovSummary[summary.py
Pure Tallying]
+ CovModel[model/coverage_item.py
Output Dataclasses]
+ end
+
+ subgraph "Core Package (packages/core)"
+ JsonUtils2[json_utils.py]
+ LoggingConfig2[logging_config.py]
+ Errors2[errors.py]
+ end
+
+ CovService --> CovLoader
+ CovService --> CovMatcher
+ CovMatcher --> CovSummary
+ CovMatcher --> CovModel
+ CovSummary --> CovModel
+ CovService --> JsonUtils2
+ CovService --> LoggingConfig2
+ CovService --> Errors2
+
+ style CovService fill:#ffe0b2,stroke:#e64a19,stroke-width:3px
+ style CovLoader fill:#e3f2fd,stroke:#1976d2
+ style CovMatcher fill:#f3e5f5,stroke:#7b1fa2
+ style CovSummary fill:#e8f5e9,stroke:#388e3c
+```
+
+**Component Responsibilities:**
+
+- **service.py**: Orchestration — loads inputs, calls matcher, writes output, enforces `--fail-under`
+- **loader.py**: Pure I/O — reads `doc-source.json` (bare array, legacy `items` envelope, or `user_stories`/`functionalities`/`features` envelope) and `ui-tests.json`
+- **matcher.py**: Pure transformation — builds the `CoverageMatrix` from parsed doc groups (no I/O)
+- **summary.py**: Pure tallying — `compute_us_summary()` and `compute_summary()` with deprecated-AC exclusion
+- **model/coverage_item.py**: Output dataclasses that serialize to `coverage-matrix.json`
+
+**Key design constraints:**
+- `matcher.py` is a pure function: no I/O, no logging, fully testable without mocks
+- User Stories and Functionalities are coverage-scored; Features are emitted as a registry group without ACs
+- Deprecated ACs are included in the matrix but excluded from `coverage_pct` computation
+- Unresolved `us_id` / `func_id` values land in `unlinked_tests`; unknown `ac_id` references land in `stale_ac_refs`
+
+---
+
## Adapter Pattern
### Adapter Selection and Execution
diff --git a/docs/contracts.md b/docs/contracts.md
index 254417d..d7fa992 100644
--- a/docs/contracts.md
+++ b/docs/contracts.md
@@ -7,6 +7,7 @@ Quick reference for all external-facing contracts. Changes to items below requir
- [CLI Interface](#cli-interface)
- [Input Contract: `doc-issues.json`](#input-contract-doc-issuesjson)
- [Output Contract: `pdf_ready.json`](#output-contract-pdf_readyjson)
+- [Output Contract: `coverage-matrix.json`](#output-contract-coverage-matrixjson)
- [Audit Envelope (v1.0)](#audit-envelope-v10)
- [JSON Schemas](#json-schemas)
- [Change Control](#change-control)
@@ -16,7 +17,7 @@ Quick reference for all external-facing contracts. Changes to items below requir
## CLI Interface
-**Command:** `living-doc normalize-issues`
+### `living-doc normalize-issues`
| Argument | Type | Required | Default | Description |
|----------|------|----------|---------|-------------|
@@ -27,7 +28,7 @@ Quick reference for all external-facing contracts. Changes to items below requir
| `--document-version` | string | No | from input | Override `meta.document_version` |
| `--verbose` | flag | No | `false` | Enable verbose logging |
-### Exit Codes
+### Exit Codes (`normalize-issues`)
| Code | Condition | Error Prefix |
|------|-----------|--------------|
@@ -42,6 +43,25 @@ Error format: `{prefix} {detail}. {guidance}`
---
+### `living-doc coverage-matrix`
+
+| Argument | Type | Required | Default | Description |
+|----------|------|----------|---------|-------------|
+| `--doc-input` | path | Yes | — | Path to US+AC doc JSON (`doc-source.json` / `doc-issues.json`) |
+| `--tests-input` | path | Yes | — | Path to ui-tests JSON (`ui-tests.json`) |
+| `--output` | path | Yes | — | Destination path for `coverage-matrix.json` |
+| `--fail-under` | float | No | disabled | Exit code 1 if `coverage_pct < N` |
+| `--verbose` | flag | No | `false` | Enable verbose logging |
+
+### Exit Codes (`coverage-matrix`)
+
+| Code | Condition |
+|------|-----------|
+| 0 | Success |
+| 1 | Any error (invalid input, I/O failure, coverage below `--fail-under`) |
+
+---
+
## Input Contract: `doc-issues.json`
Produced by [living-doc-collector-gh](https://github.com/AbsaOSS/living-doc-collector-gh).
@@ -122,6 +142,51 @@ Issue body `##` headings map to canonical section keys (case-insensitive):
---
+## Output Contract: `coverage-matrix.json`
+
+**Schema version:** `"coverage-matrix-v1.0.0"` (field `schema_version`)
+
+Produced by `living-doc coverage-matrix`. Consumed by downstream PDF / reporting generators.
+
+### Structure
+
+```
+coverage-matrix.json
+├── schema_version: "coverage-matrix-v1.0.0"
+├── generated_at: ISO-8601 timestamp
+├── summary { total_user_stories, total_functionalities, total_features, total_acs, active_acs, covered_acs, coverage_pct }
+├── user_stories[]
+│ ├── id, full_id, title, state
+│ ├── summary { total_acs, active_acs, covered_acs, coverage_pct }
+│ └── acceptance_criteria[]
+│ ├── id, state, version, description
+│ └── coverage { status, test_count, tests[] }
+├── functionalities[]
+│ ├── id, full_id, title, state, parent, func_type
+│ ├── summary { total_acs, active_acs, covered_acs, coverage_pct }
+│ └── acceptance_criteria[] ← same shape as user_stories
+├── features[] ← registry surfaces (no acceptance criteria)
+│ └── id, full_id, title, state, surface_type, route, owners, purpose,
+│ user_stories[], functionalities[], external_dependencies, page_object
+├── unlinked_tests[] ← scenarios with null/unresolved us_id and func_id
+└── stale_ac_refs[] ← ac_ids that don't exist on the resolved US/Functionality
+```
+
+### Coverage Status
+
+| Status | Condition |
+|--------|-----------|
+| `covered` | ≥1 scenario references this `ac_id` |
+| `not_covered` | 0 scenarios reference this `ac_id` |
+
+### Coverage Percentage
+
+`coverage_pct = covered_active_acs / active_acs * 100` rounded to 1 dp.
+Deprecated ACs (`state != "Active"`) are included in the matrix but **excluded from `coverage_pct`** so they cannot inflate scores.
+`coverage_pct` is `null` when `active_acs == 0`.
+
+---
+
## Audit Envelope (v1.0)
Lives at `meta.audit`. Preserves upstream provenance and tracks transformation steps.
@@ -170,18 +235,23 @@ Each pipeline stage appends a trace entry:
Machine-readable schemas are at:
- `packages/datasets_pdf/schemas/pdf_ready_v1.schema.json`
- `packages/datasets_pdf/schemas/audit_envelope_v1.schema.json`
+- `packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/doc-source-v1.0.0-schema.json` (validates the `doc-source.json` input)
+- `packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/coverage-matrix-v1.0.0-schema.json`
-Pydantic models (source of truth):
+Pydantic models (source of truth for PDF contracts):
- `packages/datasets_pdf/src/living_doc_datasets_pdf/pdf_ready/v1/models.py`
- `packages/datasets_pdf/src/living_doc_datasets_pdf/audit/v1/models.py`
+Dataclasses (source of truth for coverage-matrix contract):
+- `packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/model/coverage_item.py`
+
---
## Change Control
### Stable (breaking changes require major version bump)
-- Schema field names, types, and meanings (v1.0)
+- Schema field names, types, and meanings (`pdf_ready` v1.0, `coverage-matrix` v1.0.0)
- `AdapterResult` model signature
- CLI argument names and defaults
- Exit codes and error message prefixes
diff --git a/packages/adapters/collector_gh/README.md b/packages/adapters/collector_gh/README.md
index d5a85d6..926851c 100644
--- a/packages/adapters/collector_gh/README.md
+++ b/packages/adapters/collector_gh/README.md
@@ -50,8 +50,8 @@ if can_handle(payload):
# Parse the input
result = parse(payload)
- # Access parsed items
- for item in result.items:
+ # Access parsed user stories
+ for item in result.user_stories:
print(f"ID: {item.id}")
print(f"Title: {item.title}")
print(f"State: {item.state}")
@@ -102,7 +102,7 @@ Complete output from the adapter:
```python
@dataclass
class AdapterResult:
- items: list[AdapterItem] # Parsed items/issues
+ user_stories: list[AdapterItem] # Parsed user stories
metadata: AdapterMetadata # Producer metadata
warnings: list[CompatibilityWarning] # Compatibility warnings
```
@@ -120,7 +120,10 @@ class AdapterItem:
tags: list[str] # Labels/tags
url: str # GitHub issue URL
timestamps: AdapterItemTimestamps # Created/updated times
- body: str | None # Issue body content
+ description: str | None # User story description
+ business_value: list[str] | None # Business value statements
+ preconditions: list[str] | None # Preconditions
+ acceptance_criteria: list[AcceptanceCriterion] | None # Structured ACs
```
### AdapterMetadata
diff --git a/packages/adapters/collector_gh/schemas/doc-issues-v1.0.0-schema.json b/packages/adapters/collector_gh/schemas/doc-issues-v1.0.0-schema.json
index 6ad5131..b3708c2 100644
--- a/packages/adapters/collector_gh/schemas/doc-issues-v1.0.0-schema.json
+++ b/packages/adapters/collector_gh/schemas/doc-issues-v1.0.0-schema.json
@@ -1,7 +1,7 @@
{
"$defs": {
"AcceptanceCriterion": {
- "description": "A single acceptance criterion parsed from the issue body table.",
+ "description": "A single acceptance criterion attached to a User Story.",
"properties": {
"id": {
"title": "Id",
@@ -30,7 +30,7 @@
"type": "object"
},
"AdapterItem": {
- "description": "Represents a single item (issue) from the collector output.",
+ "description": "Represents a single User Story from the collector output.",
"properties": {
"id": {
"title": "Id",
@@ -368,11 +368,11 @@
},
"description": "Complete result from adapter parsing.",
"properties": {
- "items": {
+ "user_stories": {
"items": {
"$ref": "#/$defs/AdapterItem"
},
- "title": "Items",
+ "title": "User Stories",
"type": "array"
},
"metadata": {
@@ -387,7 +387,7 @@
}
},
"required": [
- "items",
+ "user_stories",
"metadata",
"warnings"
],
diff --git a/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/models.py b/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/models.py
index 664623f..6522374 100644
--- a/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/models.py
+++ b/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/models.py
@@ -43,17 +43,17 @@ class AdapterItemTimestamps(BaseModel):
updated: str
-class AdapterItemAcceptanceCriterion(BaseModel):
- """A single acceptance criterion from the structured input format."""
+class AcceptanceCriterion(BaseModel):
+ """A single acceptance criterion attached to a User Story."""
- id: str | None = None
- state: str | None = None
- version: str | None = None
+ id: str
+ state: str
+ version: str
description: str
class AdapterItem(BaseModel):
- """Represents a single item (issue) from the collector output."""
+ """Represents a single User Story from the collector output."""
id: str
title: str
@@ -61,11 +61,10 @@ class AdapterItem(BaseModel):
tags: list[str]
url: str
timestamps: AdapterItemTimestamps
- body: str | None = None
- # Structured fields present in the new items-array format
- structured_business_value: list[str] | None = None
- structured_preconditions: list[str] | None = None
- structured_acceptance_criteria: list[AdapterItemAcceptanceCriterion] | None = None
+ description: str | None = None
+ business_value: list[str] | None = None
+ preconditions: list[str] | None = None
+ acceptance_criteria: list[AcceptanceCriterion] | None = None
class AdapterMetadataProducer(BaseModel):
@@ -108,6 +107,6 @@ class AdapterMetadata(BaseModel):
class AdapterResult(BaseModel):
"""Complete result from adapter parsing."""
- items: list[AdapterItem]
+ user_stories: list[AdapterItem]
metadata: AdapterMetadata
warnings: list[CompatibilityWarning]
diff --git a/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/parser.py b/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/parser.py
index 186208a..70d0cea 100644
--- a/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/parser.py
+++ b/packages/adapters/collector_gh/src/living_doc_adapter_collector_gh/parser.py
@@ -15,8 +15,8 @@
from living_doc_adapter_collector_gh.compatibility import check_compatibility
from living_doc_adapter_collector_gh.detector import extract_version
from living_doc_adapter_collector_gh.models import (
+ AcceptanceCriterion,
AdapterItem,
- AdapterItemAcceptanceCriterion,
AdapterItemTimestamps,
AdapterMetadata,
AdapterMetadataProducer,
@@ -28,26 +28,6 @@
logger = logging.getLogger(__name__)
-def _build_body_from_structured(raw_item: dict) -> str | None:
- """
- Reconstruct a markdown body from structured item fields.
-
- Handles items that carry explicit JSON fields (description, business_value,
- preconditions, acceptance_criteria) instead of a pre-rendered markdown body.
-
- Args:
- raw_item: Raw item dict from the payload.
-
- Returns:
- Markdown string assembled from present structured fields, or None when no
- structured fields are found.
- """
- description = raw_item.get("description")
- if not description:
- return None
- return f"## Description\n{description}"
-
-
def _validate_metadata(metadata: dict, errors: list[str]) -> None:
"""Validate the metadata section of the payload."""
for key in ["producer", "run", "source"]:
@@ -75,20 +55,20 @@ def _validate_metadata(metadata: dict, errors: list[str]) -> None:
def _validate_items(items: list, errors: list[str]) -> None:
- """Validate the items section of the payload."""
+ """Validate the user_stories section of the payload."""
item_count = len(items)
for idx, item in enumerate(items[:5]):
if not isinstance(item, dict):
- errors.append(f"Item {idx} must be a dict, got {type(item).__name__}")
+ errors.append(f"User story {idx} must be a dict, got {type(item).__name__}")
continue
for field in ["id", "title", "state", "url", "timestamps"]:
if field not in item:
item_id = item.get("id", f"[{idx}]")
- errors.append(f"Item {item_id} missing required field: '{field}'")
+ errors.append(f"User story {item_id} missing required field: '{field}'")
if item_count > 5:
logger.info(
- "Schema validation checked first 5 of %d items; full validation deferred to item parsing",
+ "Schema validation checked first 5 of %d user stories; full validation deferred to item parsing",
item_count,
)
@@ -114,8 +94,8 @@ def _validate_schema(payload: dict) -> list[str]:
if "metadata" not in payload:
errors.append("Missing required key: 'metadata'")
- if "items" not in payload:
- errors.append("Missing required key: 'items'")
+ if "user_stories" not in payload:
+ errors.append("Missing required key: 'user_stories'")
if "metadata" in payload:
metadata = payload["metadata"]
@@ -124,10 +104,10 @@ def _validate_schema(payload: dict) -> list[str]:
else:
_validate_metadata(metadata, errors)
- if "items" in payload:
- items = payload["items"]
+ if "user_stories" in payload:
+ items = payload["user_stories"]
if not isinstance(items, list):
- errors.append(f"'items' must be a list, got {type(items).__name__}")
+ errors.append(f"'user_stories' must be a list, got {type(items).__name__}")
return errors
_validate_items(items, errors)
@@ -167,27 +147,23 @@ def _build_metadata(payload: dict) -> AdapterMetadata:
def _parse_single_item(raw_item: dict) -> AdapterItem:
- """Parse a single raw item dict into an AdapterItem."""
+ """Parse a single raw User Story dict into an AdapterItem."""
missing_fields = [f for f in ["id", "title", "state", "url", "timestamps"] if f not in raw_item]
if missing_fields:
raise KeyError(f"Missing required fields: {', '.join(missing_fields)}")
- raw_body = raw_item.get("body")
- if raw_body is None:
- raw_body = _build_body_from_structured(raw_item)
-
raw_bv = raw_item.get("business_value")
raw_pc = raw_item.get("preconditions")
raw_ac = raw_item.get("acceptance_criteria")
- structured_ac = None
+ acceptance_criteria = None
if raw_ac is not None:
- structured_ac = [
- AdapterItemAcceptanceCriterion(
- id=ac.get("id"),
- state=ac.get("state"),
- version=ac.get("version"),
- description=ac.get("description", ""),
+ acceptance_criteria = [
+ AcceptanceCriterion(
+ id=ac["id"],
+ state=ac["state"],
+ version=ac["version"],
+ description=ac["description"],
)
for ac in raw_ac
]
@@ -202,10 +178,10 @@ def _parse_single_item(raw_item: dict) -> AdapterItem:
created=raw_item["timestamps"]["created"],
updated=raw_item["timestamps"]["updated"],
),
- body=raw_body,
- structured_business_value=raw_bv if isinstance(raw_bv, list) else None,
- structured_preconditions=raw_pc if isinstance(raw_pc, list) else None,
- structured_acceptance_criteria=structured_ac,
+ description=raw_item.get("description"),
+ business_value=raw_bv if isinstance(raw_bv, list) else None,
+ preconditions=raw_pc if isinstance(raw_pc, list) else None,
+ acceptance_criteria=acceptance_criteria,
)
@@ -220,7 +196,7 @@ def parse(payload: dict) -> AdapterResult:
payload: Input payload from collector-gh
Returns:
- AdapterResult with parsed items and metadata
+ AdapterResult with parsed user stories and metadata
Raises:
AdapterError: If validation or parsing fails
@@ -253,11 +229,11 @@ def parse(payload: dict) -> AdapterResult:
logger.debug("Extracting metadata")
metadata = _build_metadata(payload)
- logger.debug("Parsing items")
- items_data = payload.get("items", [])
+ logger.debug("Parsing user stories")
+ items_data = payload.get("user_stories", [])
if not isinstance(items_data, list):
items_data = []
- logger.debug("Items provided as array with %d items", len(items_data))
+ logger.debug("User stories provided as array with %d items", len(items_data))
items = []
parse_errors = []
@@ -266,24 +242,24 @@ def parse(payload: dict) -> AdapterResult:
try:
item = _parse_single_item(raw_item)
items.append(item)
- logger.debug("Parsed item %s: %s", raw_item["id"], raw_item["title"][:50])
+ logger.debug("Parsed user story %s: %s", raw_item["id"], raw_item["title"][:50])
except (KeyError, TypeError, PydanticValidationError) as e:
item_id = raw_item.get("id", f"[{idx}]")
- error_msg = f"Failed to parse item {item_id}: {e}"
+ error_msg = f"Failed to parse user story {item_id}: {e}"
logger.error(error_msg)
parse_errors.append(error_msg)
if parse_errors:
- error_summary = f"Failed to parse {len(parse_errors)} item(s):\n"
+ error_summary = f"Failed to parse {len(parse_errors)} user story(ies):\n"
for error in parse_errors:
error_summary += f" - {error}\n"
logger.error(error_summary.strip())
raise AdapterError(error_summary.strip())
logger.info("Parsing input with collector-gh adapter...")
- logger.info("Parsed %d items", len(items))
+ logger.info("Parsed %d user stories", len(items))
- return AdapterResult(items=items, metadata=metadata, warnings=warnings)
+ return AdapterResult(user_stories=items, metadata=metadata, warnings=warnings)
except AdapterError:
raise
diff --git a/packages/adapters/collector_gh/tests/fixtures/collector_v1.0.0/input/doc-issues.json b/packages/adapters/collector_gh/tests/fixtures/collector_v1.0.0/input/doc-issues.json
index 11938c5..0cca8ff 100644
--- a/packages/adapters/collector_gh/tests/fixtures/collector_v1.0.0/input/doc-issues.json
+++ b/packages/adapters/collector_gh/tests/fixtures/collector_v1.0.0/input/doc-issues.json
@@ -1,45 +1,39 @@
{
- "metadata": {
- "producer": {
- "name": "AbsaOSS/living-doc-collector-gh",
- "version": "1.0.0",
- "build": "sha-abc123"
- },
- "run": {
- "run_id": "123456789",
- "run_attempt": "1",
- "actor": "john.doe@example.com",
- "workflow": "collect-documentation",
- "ref": "refs/heads/main",
- "sha": "abc123def456789"
- },
- "source": {
- "systems": [
- "GitHub"
- ],
- "repositories": [
- "AbsaOSS/example-project"
- ],
- "organization": "AbsaOSS",
- "enterprise": null
- }
- },
- "items": [
+ "user_stories": [
{
"id": "github:AbsaOSS/example-project#1",
"title": "User Authentication with OAuth2",
"state": "open",
"tags": [
"documentation",
- "priority:high",
- "authentication"
+ "priority:high"
],
"url": "https://github.com/AbsaOSS/example-project/issues/1",
"timestamps": {
"created": "2026-01-10T08:00:00Z",
"updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a user, I want to authenticate using OAuth2 providers so that I can securely access the application without creating a separate account.\n\n## Business Value\nReduces user friction during onboarding and improves security by leveraging trusted identity providers.\n\n## Acceptance Criteria\n- User can select OAuth2 provider (Google, Microsoft, GitHub)\n- Successful authentication redirects to application dashboard\n- Failed authentication shows appropriate error message\n- User profile is created/updated from OAuth2 data"
+ "description": "As a user, I want to authenticate using OAuth2 providers so that I can securely access the application.",
+ "business_value": [
+ "Business value for issue 1."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-1-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 1."
+ },
+ {
+ "id": "GH-1-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 1."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#2",
@@ -47,15 +41,34 @@
"state": "open",
"tags": [
"documentation",
- "feature",
"priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/2",
"timestamps": {
- "created": "2026-01-12T09:15:00Z",
- "updated": "2026-01-22T10:45:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a data analyst, I want to export report data to CSV format so that I can perform further analysis in Excel or other tools.\n\n## Preconditions\n- User has at least one report generated\n- User has permission to export data\n\n## Acceptance Criteria\n- Export button is visible on report page\n- CSV file includes all report columns\n- CSV file respects user's date/time format preferences\n- Export completes within 30 seconds for reports up to 10,000 rows\n\n## User Guide\n1. Navigate to the report page\n2. Click the \"Export to CSV\" button in the top-right corner\n3. Wait for download to complete\n4. Open the CSV file in your preferred tool"
+ "description": "As a user, I want feature 2 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 2."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-2-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 2."
+ },
+ {
+ "id": "GH-2-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 2."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#3",
@@ -63,15 +76,34 @@
"state": "closed",
"tags": [
"documentation",
- "enhancement",
- "notifications"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/3",
"timestamps": {
- "created": "2026-01-05T11:20:00Z",
- "updated": "2026-01-25T16:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Overview\nImplement a real-time notification system that alerts users about important events without requiring page refresh.\n\n## Business Value\nImproves user engagement and ensures timely awareness of critical updates.\n\n## Done Criteria\n| Criteria | Status |\n|----------|--------|\n| WebSocket connection established | \u2713 |\n| Notifications appear as toast messages | \u2713 |\n| Users can dismiss notifications | \u2713 |\n| Notification history is accessible | \u2713 |\n\n## Connections\n- Related to #5 (Push Notification Support)\n- Depends on #12 (WebSocket Infrastructure)"
+ "description": "As a user, I want feature 3 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 3."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-3-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 3."
+ },
+ {
+ "id": "GH-3-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 3."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#4",
@@ -79,15 +111,34 @@
"state": "open",
"tags": [
"documentation",
- "i18n",
- "priority:low"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/4",
"timestamps": {
- "created": "2026-01-14T13:30:00Z",
- "updated": "2026-01-23T09:20:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Summary\nAdd internationalization support to allow users to switch between English, French, and German languages.\n\n## Prerequisites\n- Translation keys are defined in locale files\n- Language selector is added to user preferences\n\n## AC\n- User can select preferred language from profile settings\n- UI text updates immediately after language change\n- Selected language persists across sessions\n- All user-facing text is translatable (no hardcoded strings)\n\n## Last Edited\nUpdated by alice.smith@example.com on 2026-01-23"
+ "description": "As a user, I want feature 4 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 4."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-4-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 4."
+ },
+ {
+ "id": "GH-4-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 4."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#5",
@@ -95,15 +146,34 @@
"state": "open",
"tags": [
"documentation",
- "search",
- "ux"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/5",
"timestamps": {
- "created": "2026-01-16T10:00:00Z",
- "updated": "2026-01-24T11:30:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a power user, I want to use advanced search filters to quickly find specific records based on multiple criteria.\n\n## Why\nReduces time spent manually browsing through large datasets and improves productivity.\n\n## Acceptance Criteria\n- Search supports text, date range, and category filters\n- Filters can be combined with AND/OR logic\n- Search results update in real-time as filters are applied\n- Search history is saved for quick access\n- Export search results to CSV"
+ "description": "As a user, I want feature 5 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 5."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-5-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 5."
+ },
+ {
+ "id": "GH-5-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 5."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#6",
@@ -111,15 +181,34 @@
"state": "open",
"tags": [
"documentation",
- "dashboard",
- "personalization"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/6",
"timestamps": {
- "created": "2026-01-17T14:45:00Z",
- "updated": "2026-01-25T08:15:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAllow users to customize their dashboard by adding, removing, and rearranging widgets.\n\n## Business Value\nEnhances user experience by providing personalized views tailored to individual workflows.\n\n## Preconditions\n- User is logged in\n- At least 3 widget types are available\n\n## Acceptance Criteria\n- Users can drag and drop widgets to reorder them\n- Users can add widgets from a widget library\n- Users can remove widgets they don't need\n- Dashboard layout is saved automatically\n- Layout persists across devices\n\n## Instructions\n### Adding a Widget\n1. Click \"Add Widget\" button\n2. Select widget type from dropdown\n3. Configure widget settings\n4. Click \"Save\"\n\n### Removing a Widget\n1. Hover over widget\n2. Click the X icon in the top-right corner\n3. Confirm deletion"
+ "description": "As a user, I want feature 6 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 6."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-6-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 6."
+ },
+ {
+ "id": "GH-6-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 6."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#7",
@@ -127,95 +216,249 @@
"state": "closed",
"tags": [
"documentation",
- "compliance",
- "security"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/7",
"timestamps": {
- "created": "2026-01-08T09:00:00Z",
- "updated": "2026-01-26T12:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nImplement comprehensive audit logging to track all user actions for compliance and security purposes.\n\n## Business Value\nEnsures regulatory compliance (GDPR, SOX) and provides security incident investigation capabilities.\n\n## Acceptance Criteria\n- All CRUD operations are logged with timestamp, user ID, and action details\n- Logs are immutable and stored in a tamper-proof format\n- Logs are retained for 7 years\n- Administrators can search and filter audit logs\n- Failed login attempts are logged\n\n## Related\n- Links to compliance documentation: https://example.com/compliance\n- See also: #15 (Role-Based Access Control)"
+ "description": "As a user, I want feature 7 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 7."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-7-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 7."
+ },
+ {
+ "id": "GH-7-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 7."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#8",
- "title": "Email Notification Templates",
+ "title": "Bulk Import of Records",
"state": "open",
"tags": [
"documentation",
- "notifications",
- "email"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/8",
"timestamps": {
- "created": "2026-01-18T11:30:00Z",
- "updated": "2026-01-24T15:45:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Overview\nCreate customizable email templates for various notification types (welcome, password reset, report ready, etc.).\n\n## Value\nProvides consistent branding and messaging across all email communications.\n\n## Setup\n- Email service (SendGrid/Mailgun) is configured\n- Template variables are documented\n\n## Done Criteria\n- At least 5 email templates are available\n- Templates support variable substitution (user name, links, etc.)\n- Templates are responsive (mobile-friendly)\n- Administrators can preview templates before sending\n- Template changes are version-controlled"
+ "description": "As a user, I want feature 8 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 8."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-8-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 8."
+ },
+ {
+ "id": "GH-8-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 8."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#9",
- "title": "Two-Factor Authentication (2FA)",
+ "title": "Two-factor Authentication",
"state": "open",
"tags": [
"documentation",
- "security",
- "priority:high"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/9",
"timestamps": {
- "created": "2026-01-11T08:30:00Z",
- "updated": "2026-01-26T10:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a security-conscious user, I want to enable two-factor authentication to add an extra layer of protection to my account.\n\n## Business Value\nSignificantly reduces the risk of unauthorized account access and data breaches.\n\n## Acceptance Criteria\n- Users can enable 2FA via authenticator apps (Google Authenticator, Authy)\n- Backup codes are generated and displayed once\n- 2FA can be disabled with password confirmation\n- Failed 2FA attempts are rate-limited\n- SMS-based 2FA is supported as fallback\n\n## User Guide\n### Enabling 2FA\n1. Go to Account Settings > Security\n2. Click \"Enable Two-Factor Authentication\"\n3. Scan QR code with authenticator app\n4. Enter verification code to confirm\n5. Save backup codes in a secure location\n\n### Disabling 2FA\n1. Go to Account Settings > Security\n2. Click \"Disable Two-Factor Authentication\"\n3. Enter current password\n4. Enter 2FA code\n5. Click \"Confirm\"\n\n## History\nInitially proposed in Q4 2025, prioritized for Q1 2026 release."
+ "description": "As a user, I want feature 9 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 9."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-9-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 9."
+ },
+ {
+ "id": "GH-9-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 9."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#10",
- "title": "API Rate Limiting",
- "state": "closed",
+ "title": "Role-Based Access Control",
+ "state": "open",
"tags": [
"documentation",
- "api",
- "performance"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/10",
"timestamps": {
- "created": "2026-01-09T14:00:00Z",
- "updated": "2026-01-27T09:30:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nImplement rate limiting for all API endpoints to prevent abuse and ensure fair resource allocation.\n\n## Business Value\nProtects system availability and prevents denial-of-service attacks.\n\n## Acceptance Criteria\n- Rate limit is 100 requests per minute per API key\n- Exceeding rate limit returns 429 status code with Retry-After header\n- Rate limit counters reset every minute\n- Premium users have higher rate limits (500 requests/minute)\n- Rate limit status is visible in API response headers\n\n## Connections\n- Related to API documentation: https://api.example.com/docs\n- See also: #18 (API Key Management)"
+ "description": "As a user, I want feature 10 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 10."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-10-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 10."
+ },
+ {
+ "id": "GH-10-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 10."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#11",
- "title": "Scheduled Report Generation",
+ "title": "Scheduled Report Delivery",
"state": "open",
"tags": [
"documentation",
- "reports",
- "automation"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/11",
"timestamps": {
- "created": "2026-01-19T10:15:00Z",
- "updated": "2026-01-26T14:20:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAllow users to schedule automatic report generation and delivery via email at specified intervals.\n\n## Business Value\nSaves time by automating repetitive reporting tasks and ensures stakeholders receive timely updates.\n\n## Preconditions\n- User has created at least one report template\n- Email service is configured and operational\n\n## AC\n- Users can schedule reports (daily, weekly, monthly)\n- Reports are generated at the specified time\n- Generated reports are emailed as PDF attachments\n- Users can pause or cancel scheduled reports\n- Failed report generations trigger alerts\n\n## How To\n1. Open the report you want to schedule\n2. Click \"Schedule\" button\n3. Select frequency (daily/weekly/monthly)\n4. Choose delivery time\n5. Enter recipient email addresses\n6. Click \"Save Schedule\"\n\n## Changes\nScheduling logic was refactored in v2.3 to improve reliability."
+ "description": "As a user, I want feature 11 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 11."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-11-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 11."
+ },
+ {
+ "id": "GH-11-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 11."
+ }
+ ]
},
{
"id": "github:AbsaOSS/example-project#12",
- "title": "Dark Mode Theme",
+ "title": "In-app Feedback Widget",
"state": "open",
"tags": [
"documentation",
- "ui",
- "accessibility"
+ "priority:medium"
],
"url": "https://github.com/AbsaOSS/example-project/issues/12",
"timestamps": {
- "created": "2026-01-20T09:45:00Z",
- "updated": "2026-01-27T11:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
+ },
+ "description": "As a user, I want feature 12 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 12."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-12-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 12."
+ },
+ {
+ "id": "GH-12-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 12."
+ }
+ ]
+ }
+ ],
+ "metadata": {
+ "producer": {
+ "name": "AbsaOSS/living-doc-collector-gh",
+ "version": "1.0.0",
+ "build": "sha-abc123"
+ },
+ "run": {
+ "run_id": "123456789",
+ "run_attempt": "1",
+ "actor": "john.doe@example.com",
+ "workflow": "collect-documentation",
+ "ref": "refs/heads/main",
+ "sha": "abc123def456789"
+ },
+ "source": {
+ "systems": [
+ "GitHub"
+ ],
+ "repositories": [
+ "AbsaOSS/example-project"
+ ],
+ "organization": "AbsaOSS",
+ "enterprise": null
+ },
+ "original_metadata": {
+ "producer": {
+ "name": "AbsaOSS/living-doc-collector-gh",
+ "version": "1.0.0"
+ },
+ "run": {
+ "run_id": "123456789"
},
- "body": "## Summary\nImplement a dark mode theme option to reduce eye strain and improve accessibility for users in low-light environments.\n\n## Value\nEnhances user comfort and accessibility, especially for users who work extended hours.\n\n## Acceptance Criteria\n- Theme toggle is available in user preferences\n- Dark mode applies to all UI components\n- Theme preference persists across sessions\n- System theme preference is detected and applied automatically\n- Contrast ratios meet WCAG AA standards\n\n## Links\n- Design mockups: https://figma.com/darkmode\n- WCAG guidelines: https://www.w3.org/WAI/WCAG21/quickref/"
+ "source": {
+ "repositories": [
+ "AbsaOSS/example-project"
+ ]
+ }
}
- ]
+ },
+ "warnings": []
}
\ No newline at end of file
diff --git a/packages/adapters/collector_gh/tests/fixtures/collector_v1.2.0/input/doc-issues.json b/packages/adapters/collector_gh/tests/fixtures/collector_v1.2.0/input/doc-issues.json
index eb067a7..ffbed98 100644
--- a/packages/adapters/collector_gh/tests/fixtures/collector_v1.2.0/input/doc-issues.json
+++ b/packages/adapters/collector_gh/tests/fixtures/collector_v1.2.0/input/doc-issues.json
@@ -1,221 +1,464 @@
{
- "metadata": {
- "producer": {
- "name": "AbsaOSS/living-doc-collector-gh",
- "version": "1.2.0",
- "build": "sha-xyz789"
- },
- "run": {
- "run_id": "987654321",
- "run_attempt": "2",
- "actor": "jane.smith@example.com",
- "workflow": "documentation-pipeline",
- "ref": "refs/heads/develop",
- "sha": "xyz789abc456def"
- },
- "source": {
- "systems": [
- "GitHub"
- ],
- "repositories": [
- "AbsaOSS/example-project"
- ],
- "organization": "AbsaOSS",
- "enterprise": "AbsaCorp"
- }
- },
- "items": [
+ "user_stories": [
{
- "id": "github:AbsaOSS/example-project#20",
- "title": "Shopping Cart Persistence",
+ "id": "github:AbsaOSS/example-project#1",
+ "title": "User Authentication with OAuth2",
"state": "open",
"tags": [
"documentation",
- "e-commerce",
"priority:high"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/20",
+ "url": "https://github.com/AbsaOSS/example-project/issues/1",
"timestamps": {
- "created": "2026-01-21T08:00:00Z",
- "updated": "2026-01-27T15:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a customer, I want my shopping cart to persist across sessions so that I don't lose my selected items if I close the browser.\n\n## Business Value\nReduces cart abandonment rates and improves conversion by preserving user shopping intent.\n\n## Acceptance Criteria\n- Cart items are saved to database after each addition/removal\n- Cart is restored when user logs in\n- Cart items expire after 30 days of inactivity\n- Guest carts are converted to user carts upon registration\n- Out-of-stock items show appropriate messaging"
+ "description": "As a user, I want to authenticate using OAuth2 providers so that I can securely access the application.",
+ "business_value": [
+ "Business value for issue 1."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-1-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 1."
+ },
+ {
+ "id": "GH-1-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 1."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#21",
- "title": "Product Recommendation Engine",
+ "id": "github:AbsaOSS/example-project#2",
+ "title": "Export Data to CSV Format",
"state": "open",
"tags": [
"documentation",
- "ml",
- "personalization"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/21",
+ "url": "https://github.com/AbsaOSS/example-project/issues/2",
"timestamps": {
- "created": "2026-01-22T09:30:00Z",
- "updated": "2026-01-28T10:45:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Overview\nImplement a machine learning-based product recommendation system that suggests relevant items based on browsing history and purchase patterns.\n\n## Why\nIncreases average order value and improves customer satisfaction through personalized shopping experiences.\n\n## Prerequisites\n- User behavior tracking is implemented\n- Product catalog is indexed and searchable\n- ML model training pipeline is established\n\n## Done Criteria\n- Recommendations appear on product pages and homepage\n- Recommendations update in real-time based on browsing behavior\n- Click-through rate on recommendations is measured\n- Recommendation quality improves over time\n- Users can dismiss recommendations"
+ "description": "As a user, I want feature 2 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 2."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-2-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 2."
+ },
+ {
+ "id": "GH-2-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 2."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#22",
- "title": "Payment Gateway Integration",
+ "id": "github:AbsaOSS/example-project#3",
+ "title": "Real-time Notification System",
"state": "closed",
"tags": [
"documentation",
- "payments",
- "integration"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/22",
+ "url": "https://github.com/AbsaOSS/example-project/issues/3",
"timestamps": {
- "created": "2026-01-15T11:00:00Z",
- "updated": "2026-01-29T14:30:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nIntegrate Stripe payment gateway to support credit card, debit card, and digital wallet payments.\n\n## Business Value\nEnables secure online transactions and supports multiple payment methods for customer convenience.\n\n## Acceptance Criteria\n| Payment Method | Status | Notes |\n|----------------|--------|-------|\n| Credit Cards | \u2713 | Visa, Mastercard, Amex |\n| Debit Cards | \u2713 | All major providers |\n| Apple Pay | \u2713 | iOS only |\n| Google Pay | \u2713 | Android and Web |\n| PayPal | Pending | Integration in progress |\n\n## User Guide\n### Making a Payment\n1. Add items to cart\n2. Proceed to checkout\n3. Select payment method\n4. Enter payment details\n5. Review and confirm order\n6. Receive confirmation email\n\n## Related\n- Stripe API documentation: https://stripe.com/docs\n- PCI compliance: #45"
+ "description": "As a user, I want feature 3 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 3."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-3-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 3."
+ },
+ {
+ "id": "GH-3-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 3."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#23",
- "title": "Inventory Management Dashboard",
+ "id": "github:AbsaOSS/example-project#4",
+ "title": "Multi-language Support for UI",
"state": "open",
"tags": [
"documentation",
- "inventory",
- "admin"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/23",
+ "url": "https://github.com/AbsaOSS/example-project/issues/4",
"timestamps": {
- "created": "2026-01-23T13:15:00Z",
- "updated": "2026-01-28T09:20:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Summary\nCreate an admin dashboard for managing product inventory, tracking stock levels, and receiving low-stock alerts.\n\n## Value\nPrevents stockouts and overstocking by providing real-time inventory visibility.\n\n## AC\n- Dashboard displays current stock levels for all products\n- Low-stock alerts are sent when quantity falls below threshold\n- Inventory can be updated manually or via bulk import\n- Stock movement history is tracked and auditable\n- Dashboard is accessible to users with 'inventory_manager' role"
+ "description": "As a user, I want feature 4 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 4."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-4-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 4."
+ },
+ {
+ "id": "GH-4-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 4."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#24",
- "title": "Customer Review and Rating System",
+ "id": "github:AbsaOSS/example-project#5",
+ "title": "Advanced Search with Filters",
"state": "open",
"tags": [
"documentation",
- "reviews",
- "social"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/24",
+ "url": "https://github.com/AbsaOSS/example-project/issues/5",
"timestamps": {
- "created": "2026-01-24T10:00:00Z",
- "updated": "2026-01-29T11:30:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a customer, I want to read and write product reviews to make informed purchasing decisions and share my experiences.\n\n## Business Value\nBuilds trust and credibility, provides social proof, and increases conversion rates.\n\n## Preconditions\n- User has purchased the product\n- User is logged in\n\n## Acceptance Criteria\n- Customers can rate products from 1 to 5 stars\n- Customers can write text reviews (up to 1000 characters)\n- Reviews can include photos (up to 5 images)\n- Reviews are moderated before publication\n- Average rating is displayed on product page\n- Reviews can be sorted by date, rating, or helpfulness\n\n## Instructions\n### Writing a Review\n1. Navigate to product page\n2. Click \"Write a Review\"\n3. Select star rating\n4. Write review text\n5. Optionally upload photos\n6. Click \"Submit Review\"\n7. Review enters moderation queue\n\n### Flagging Inappropriate Reviews\n1. Click \"Report\" on the review\n2. Select reason (spam, offensive, etc.)\n3. Provide additional details\n4. Submit report"
+ "description": "As a user, I want feature 5 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 5."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-5-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 5."
+ },
+ {
+ "id": "GH-5-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 5."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#25",
- "title": "Order Tracking and Notifications",
- "state": "closed",
+ "id": "github:AbsaOSS/example-project#6",
+ "title": "Dashboard Customization",
+ "state": "open",
"tags": [
"documentation",
- "orders",
- "notifications"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/25",
+ "url": "https://github.com/AbsaOSS/example-project/issues/6",
"timestamps": {
- "created": "2026-01-16T14:30:00Z",
- "updated": "2026-01-30T08:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nProvide customers with real-time order tracking and automated notifications at each stage of the fulfillment process.\n\n## Business Value\nReduces customer support inquiries and improves customer satisfaction through transparency.\n\n## Acceptance Criteria\n- Order status is updated in real-time (processing, shipped, delivered)\n- Customers receive email notifications at each status change\n- Tracking link is provided for shipped orders\n- Estimated delivery date is displayed\n- Customers can view order history\n\n## Connections\n- Integration with shipping providers: #48\n- Email notification templates: #8"
+ "description": "As a user, I want feature 6 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 6."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-6-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 6."
+ },
+ {
+ "id": "GH-6-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 6."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#26",
- "title": "Wishlist Functionality",
- "state": "open",
+ "id": "github:AbsaOSS/example-project#7",
+ "title": "Audit Log for Compliance",
+ "state": "closed",
"tags": [
"documentation",
- "wishlist",
- "feature"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/26",
+ "url": "https://github.com/AbsaOSS/example-project/issues/7",
"timestamps": {
- "created": "2026-01-25T09:45:00Z",
- "updated": "2026-01-29T15:45:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Overview\nAllow customers to save products to a wishlist for future purchase consideration.\n\n## Why\nEncourages repeat visits and provides insights into customer preferences.\n\n## Setup\n- User authentication is implemented\n- Product catalog is available\n\n## Done Criteria\n- Users can add/remove products to wishlist\n- Wishlist is accessible from user profile\n- Wishlist can be shared via unique URL\n- Price drop notifications for wishlist items\n- Move to cart functionality available"
+ "description": "As a user, I want feature 7 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 7."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-7-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 7."
+ },
+ {
+ "id": "GH-7-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 7."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#27",
- "title": "Coupon and Discount System",
+ "id": "github:AbsaOSS/example-project#8",
+ "title": "Bulk Import of Records",
"state": "open",
"tags": [
"documentation",
- "promotions",
- "pricing"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/27",
+ "url": "https://github.com/AbsaOSS/example-project/issues/8",
"timestamps": {
- "created": "2026-01-26T11:00:00Z",
- "updated": "2026-01-30T10:15:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a marketing manager, I want to create and manage promotional coupons to drive sales and customer engagement.\n\n## Business Value\nIncreases sales during promotional periods and enables targeted marketing campaigns.\n\n## Acceptance Criteria\n- Coupons can be percentage-based or fixed amount\n- Coupons have start and end dates\n- Coupons can have usage limits (per customer and total)\n- Coupons can be restricted to specific products or categories\n- Invalid or expired coupons show appropriate error messages\n- Coupon usage is tracked and reportable\n\n## User Guide\n### Creating a Coupon\n1. Navigate to Admin > Promotions > Coupons\n2. Click \"Create New Coupon\"\n3. Enter coupon code (e.g., SUMMER2026)\n4. Select discount type and amount\n5. Set validity period\n6. Configure usage limits\n7. Click \"Save\"\n\n### Applying a Coupon (Customer)\n1. Add items to cart\n2. Proceed to checkout\n3. Enter coupon code in the promo field\n4. Click \"Apply\"\n5. Discount is reflected in order total"
+ "description": "As a user, I want feature 8 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 8."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-8-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 8."
+ },
+ {
+ "id": "GH-8-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 8."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#28",
- "title": "Gift Card System",
+ "id": "github:AbsaOSS/example-project#9",
+ "title": "Two-factor Authentication",
"state": "open",
"tags": [
"documentation",
- "gift-cards",
- "payments"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/28",
+ "url": "https://github.com/AbsaOSS/example-project/issues/9",
"timestamps": {
- "created": "2026-01-27T08:30:00Z",
- "updated": "2026-01-30T12:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Summary\nImplement a gift card system allowing customers to purchase, send, and redeem gift cards.\n\n## Value\nGenerates additional revenue and provides gifting options for customers.\n\n## Prerequisites\n- Payment gateway is integrated\n- Email notification system is functional\n\n## AC\n- Customers can purchase gift cards in denominations of $25, $50, $100, or custom amounts\n- Gift cards can be sent via email with personalized messages\n- Gift cards have unique codes and are validated at checkout\n- Gift card balance is tracked and displayed\n- Partial redemptions are supported\n- Gift cards expire after 2 years"
+ "description": "As a user, I want feature 9 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 9."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-9-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 9."
+ },
+ {
+ "id": "GH-9-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 9."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#29",
- "title": "Multi-Currency Support",
- "state": "closed",
+ "id": "github:AbsaOSS/example-project#10",
+ "title": "Role-Based Access Control",
+ "state": "open",
"tags": [
"documentation",
- "i18n",
- "pricing"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/29",
+ "url": "https://github.com/AbsaOSS/example-project/issues/10",
"timestamps": {
- "created": "2026-01-18T10:15:00Z",
- "updated": "2026-01-31T09:30:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nSupport multiple currencies to enable international sales and provide localized pricing.\n\n## Business Value\nExpands market reach and improves conversion rates for international customers.\n\n## Acceptance Criteria\n- Support for USD, EUR, GBP, and JPY\n- Currency is detected based on user location\n- Users can manually switch currency\n- Exchange rates are updated daily\n- Prices are displayed in selected currency throughout the site\n- Orders are processed in customer's selected currency\n\n## Links\n- Currency conversion API: https://exchangerate-api.com\n- Pricing strategy documentation"
+ "description": "As a user, I want feature 10 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 10."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-10-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 10."
+ },
+ {
+ "id": "GH-10-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 10."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#30",
- "title": "Customer Loyalty Program",
+ "id": "github:AbsaOSS/example-project#11",
+ "title": "Scheduled Report Delivery",
"state": "open",
"tags": [
"documentation",
- "loyalty",
- "gamification"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/30",
+ "url": "https://github.com/AbsaOSS/example-project/issues/11",
"timestamps": {
- "created": "2026-01-28T13:45:00Z",
- "updated": "2026-01-31T11:00:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\nAs a returning customer, I want to earn loyalty points for purchases that can be redeemed for discounts on future orders.\n\n## Business Value\nIncreases customer retention and lifetime value through rewards and incentives.\n\n## Acceptance Criteria\n- Customers earn 1 point per $1 spent\n- Points can be redeemed at a rate of 100 points = $5 discount\n- Points balance is visible in user profile\n- Points transactions are logged (earned, redeemed, expired)\n- Bonus point promotions can be configured\n- Points expire after 12 months of inactivity\n\n## How To\n### Earning Points\n- Complete purchases (automatic)\n- Refer friends (bonus points)\n- Write product reviews (bonus points)\n- Complete profile information (one-time bonus)\n\n### Redeeming Points\n1. Go to checkout\n2. View available points balance\n3. Select points to redeem\n4. Discount is applied to order total\n\n## History\nLoyalty program launched in Q2 2026 with initial tier structure."
+ "description": "As a user, I want feature 11 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 11."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-11-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 11."
+ },
+ {
+ "id": "GH-11-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 11."
+ }
+ ]
},
{
- "id": "github:AbsaOSS/example-project#31",
- "title": "Product Comparison Feature",
+ "id": "github:AbsaOSS/example-project#12",
+ "title": "In-app Feedback Widget",
"state": "open",
"tags": [
"documentation",
- "comparison",
- "ux"
+ "priority:medium"
],
- "url": "https://github.com/AbsaOSS/example-project/issues/31",
+ "url": "https://github.com/AbsaOSS/example-project/issues/12",
"timestamps": {
- "created": "2026-01-29T09:00:00Z",
- "updated": "2026-01-31T14:20:00Z"
+ "created": "2026-01-10T08:00:00Z",
+ "updated": "2026-01-20T14:30:00Z"
+ },
+ "description": "As a user, I want feature 12 so that I can be more productive.",
+ "business_value": [
+ "Business value for issue 12."
+ ],
+ "preconditions": [
+ "User is logged in."
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-12-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "First criterion for issue 12."
+ },
+ {
+ "id": "GH-12-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Second criterion for issue 12."
+ }
+ ]
+ }
+ ],
+ "metadata": {
+ "producer": {
+ "name": "AbsaOSS/living-doc-collector-gh",
+ "version": "1.2.0",
+ "build": "sha-xyz789"
+ },
+ "run": {
+ "run_id": "123456789",
+ "run_attempt": "1",
+ "actor": "john.doe@example.com",
+ "workflow": "collect-documentation",
+ "ref": "refs/heads/main",
+ "sha": "abc123def456789"
+ },
+ "source": {
+ "systems": [
+ "GitHub"
+ ],
+ "repositories": [
+ "AbsaOSS/example-project"
+ ],
+ "organization": "AbsaOSS",
+ "enterprise": null
+ },
+ "original_metadata": {
+ "producer": {
+ "name": "AbsaOSS/living-doc-collector-gh",
+ "version": "1.2.0"
},
- "body": "## Overview\nAllow customers to compare specifications and features of up to 4 products side-by-side.\n\n## Why\nHelps customers make informed decisions and reduces decision fatigue.\n\n## Setup\n- Product specifications are standardized in the database\n- Comparison UI is designed and approved\n\n## Done Criteria\n- Users can add products to comparison list from any page\n- Comparison view shows products in a table format\n- Key specifications are highlighted\n- Differences are visually emphasized\n- Comparison can be shared via URL\n- Comparison list is limited to 4 products"
+ "run": {
+ "run_id": "123456789"
+ },
+ "source": {
+ "repositories": [
+ "AbsaOSS/example-project"
+ ]
+ }
}
- ]
+ },
+ "warnings": []
}
\ No newline at end of file
diff --git a/packages/adapters/collector_gh/tests/test_models.py b/packages/adapters/collector_gh/tests/test_models.py
index 6244eb2..3503dd6 100644
--- a/packages/adapters/collector_gh/tests/test_models.py
+++ b/packages/adapters/collector_gh/tests/test_models.py
@@ -57,16 +57,16 @@ def test_create_with_all_fields(self):
tags=["tag1", "tag2"],
url="https://github.com/owner/repo/issues/42",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-02T00:00:00Z"),
- body="Issue body content",
+ description="Issue description content",
)
assert item.id == "github:owner/repo#42"
assert item.title == "Test Issue"
assert item.state == "open"
assert item.tags == ["tag1", "tag2"]
- assert item.body == "Issue body content"
+ assert item.description == "Issue description content"
- def test_create_without_body(self):
- """Test creating AdapterItem without body field."""
+ def test_create_without_description(self):
+ """Test creating AdapterItem without optional structured fields."""
item = AdapterItem(
id="github:owner/repo#42",
title="Test Issue",
@@ -75,7 +75,10 @@ def test_create_without_body(self):
url="https://github.com/owner/repo/issues/42",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-02T00:00:00Z"),
)
- assert item.body is None
+ assert item.description is None
+ assert item.business_value is None
+ assert item.preconditions is None
+ assert item.acceptance_criteria is None
def test_create_with_empty_tags(self):
"""Test creating AdapterItem with empty tags list."""
@@ -132,9 +135,9 @@ class TestAdapterResult:
"""Tests for the AdapterResult model."""
def test_create_result_with_items_and_warnings(self):
- """Test creating AdapterResult with items and warnings."""
+ """Test creating AdapterResult with user stories and warnings."""
result = AdapterResult(
- items=[
+ user_stories=[
AdapterItem(
id="github:owner/repo#1",
title="Test",
@@ -152,14 +155,14 @@ def test_create_result_with_items_and_warnings(self):
),
warnings=[CompatibilityWarning(code="TEST_WARNING", message="Test warning message")],
)
- assert len(result.items) == 1
+ assert len(result.user_stories) == 1
assert len(result.warnings) == 1
assert result.warnings[0].code == "TEST_WARNING"
def test_create_result_with_empty_warnings(self):
"""Test creating AdapterResult with empty warnings list."""
result = AdapterResult(
- items=[],
+ user_stories=[],
metadata=AdapterMetadata(
producer=AdapterMetadataProducer(name="test", version="1.0.0", build=None),
run=AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
@@ -173,4 +176,4 @@ def test_create_result_with_empty_warnings(self):
def test_validation_fails_with_invalid_data(self):
"""Test that validation fails with invalid data."""
with pytest.raises(ValidationError):
- AdapterResult(items="not a list", metadata={}, warnings=[]) # Should be a list # Should be AdapterMetadata
+ AdapterResult(user_stories="not a list", metadata={}, warnings=[])
diff --git a/packages/adapters/collector_gh/tests/test_parser.py b/packages/adapters/collector_gh/tests/test_parser.py
index 5b37e96..05990c0 100644
--- a/packages/adapters/collector_gh/tests/test_parser.py
+++ b/packages/adapters/collector_gh/tests/test_parser.py
@@ -49,7 +49,7 @@ def minimal_payload(self):
"enterprise": None,
},
},
- "items": [
+ "user_stories": [
{
"id": "github:owner/repo#1",
"title": "Test Issue",
@@ -60,7 +60,7 @@ def minimal_payload(self):
"created": "2026-01-01T00:00:00Z",
"updated": "2026-01-02T00:00:00Z",
},
- "body": "Test body",
+ "description": "Test description",
}
],
}
@@ -69,18 +69,14 @@ def test_parse_v1_0_0_fixture(self, fixture_v1_0_0):
"""Test parsing with v1.0.0 fixture."""
result = parse(fixture_v1_0_0)
- # Check that we have the correct number of items
- assert len(result.items) == 12
+ assert len(result.user_stories) == 12
- # Check metadata
assert result.metadata.producer.name == "AbsaOSS/living-doc-collector-gh"
assert result.metadata.producer.version == "1.0.0"
assert result.metadata.producer.build == "sha-abc123"
- # Check no warnings for version 1.0.0
assert len(result.warnings) == 0
- # Check original metadata is preserved
assert "producer" in result.metadata.original_metadata
assert result.metadata.original_metadata["producer"]["version"] == "1.0.0"
@@ -88,34 +84,26 @@ def test_parse_v1_2_0_fixture(self, fixture_v1_2_0):
"""Test parsing with v1.2.0 fixture."""
result = parse(fixture_v1_2_0)
- # Check that we have the correct number of items
- assert len(result.items) == 12
+ assert len(result.user_stories) == 12
- # Check metadata
assert result.metadata.producer.name == "AbsaOSS/living-doc-collector-gh"
assert result.metadata.producer.version == "1.2.0"
assert result.metadata.producer.build == "sha-xyz789"
- # Check no warnings for version 1.2.0
assert len(result.warnings) == 0
def test_adapter_item_id_format(self, fixture_v1_0_0):
"""Test that AdapterItem ID has correct format."""
result = parse(fixture_v1_0_0)
- # Check first item
- first_item = result.items[0]
- assert first_item.id == "github:AbsaOSS/example-project#1"
-
- # Check another item
- second_item = result.items[1]
- assert second_item.id == "github:AbsaOSS/example-project#2"
+ assert result.user_stories[0].id == "github:AbsaOSS/example-project#1"
+ assert result.user_stories[1].id == "github:AbsaOSS/example-project#2"
def test_adapter_item_fields_mapped(self, fixture_v1_0_0):
"""Test that AdapterItem fields are correctly mapped."""
result = parse(fixture_v1_0_0)
- first_item = result.items[0]
+ first_item = result.user_stories[0]
assert first_item.title == "User Authentication with OAuth2"
assert first_item.state == "open"
assert "documentation" in first_item.tags
@@ -123,8 +111,22 @@ def test_adapter_item_fields_mapped(self, fixture_v1_0_0):
assert first_item.url == "https://github.com/AbsaOSS/example-project/issues/1"
assert first_item.timestamps.created == "2026-01-10T08:00:00Z"
assert first_item.timestamps.updated == "2026-01-20T14:30:00Z"
- assert first_item.body is not None
- assert "OAuth2" in first_item.body
+ assert first_item.description is not None
+ assert "OAuth2" in first_item.description
+
+ def test_adapter_item_structured_fields(self, fixture_v1_0_0):
+ """Test that structured fields are parsed into the item."""
+ result = parse(fixture_v1_0_0)
+
+ first_item = result.user_stories[0]
+ assert first_item.business_value == ["Business value for issue 1."]
+ assert first_item.preconditions == ["User is logged in."]
+ assert first_item.acceptance_criteria is not None
+ ac = first_item.acceptance_criteria[0]
+ assert ac.id == "GH-1-01"
+ assert ac.state == "Active"
+ assert ac.version == "v1.0.0"
+ assert ac.description == "First criterion for issue 1."
def test_metadata_producer_mapping(self, fixture_v1_0_0):
"""Test that producer metadata is correctly mapped."""
@@ -168,25 +170,25 @@ def test_parse_minimal_payload(self, minimal_payload):
"""Test parsing with minimal payload."""
result = parse(minimal_payload)
- assert len(result.items) == 1
- assert result.items[0].id == "github:owner/repo#1"
- assert result.items[0].title == "Test Issue"
+ assert len(result.user_stories) == 1
+ assert result.user_stories[0].id == "github:owner/repo#1"
+ assert result.user_stories[0].title == "Test Issue"
def test_parse_with_missing_labels(self, minimal_payload):
"""Test parsing when tags are missing from item."""
- minimal_payload["items"][0].pop("tags")
+ minimal_payload["user_stories"][0].pop("tags")
result = parse(minimal_payload)
- assert len(result.items) == 1
- assert result.items[0].tags == []
+ assert len(result.user_stories) == 1
+ assert result.user_stories[0].tags == []
- def test_parse_with_missing_body(self, minimal_payload):
- """Test parsing when body is missing from item."""
- minimal_payload["items"][0].pop("body")
+ def test_parse_with_missing_description(self, minimal_payload):
+ """Test parsing when description is missing from item."""
+ minimal_payload["user_stories"][0].pop("description")
result = parse(minimal_payload)
- assert len(result.items) == 1
- assert result.items[0].body is None
+ assert len(result.user_stories) == 1
+ assert result.user_stories[0].description is None
def test_parse_with_no_repositories(self, minimal_payload):
"""Test that empty repositories list raises AdapterError (required by schema)."""
@@ -209,16 +211,13 @@ def test_parse_with_closed_issue(self, fixture_v1_0_0):
"""Test parsing includes closed issues."""
result = parse(fixture_v1_0_0)
- # Find a closed issue
- closed_items = [item for item in result.items if item.state == "closed"]
+ closed_items = [item for item in result.user_stories if item.state == "closed"]
assert len(closed_items) > 0
-
- closed_item = closed_items[0]
- assert closed_item.state == "closed"
+ assert closed_items[0].state == "closed"
def test_parse_missing_item_field_raises_error(self, minimal_payload):
"""Test that missing required item field raises AdapterError."""
- del minimal_payload["items"][0]["title"]
+ del minimal_payload["user_stories"][0]["title"]
with pytest.raises(AdapterError) as exc_info:
parse(minimal_payload)
@@ -226,66 +225,51 @@ def test_parse_missing_item_field_raises_error(self, minimal_payload):
def test_parse_missing_metadata_raises_error(self):
"""Test that missing metadata raises AdapterError."""
- payload = {"items": []}
+ payload = {"user_stories": []}
with pytest.raises(AdapterError):
parse(payload)
- def test_parse_empty_items_list(self, minimal_payload):
- """Test parsing with empty items list."""
- minimal_payload["items"] = []
- result = parse(minimal_payload)
-
- assert len(result.items) == 0
- assert len(result.warnings) == 0
-
-
-class TestBuildBodyFromStructured:
- """Tests for the _build_body_from_structured helper."""
-
- def test_all_structured_fields_present(self):
- """Test markdown body is built only from description when all structured fields are present."""
- from living_doc_adapter_collector_gh.parser import _build_body_from_structured
-
- raw_item = {
- "description": "As a user, I want to view domain details.",
- "business_value": ["Streamlines domain visibility.", "Improves clarity."],
- "preconditions": ["User is logged in.", "At least one domain exists."],
- "acceptance_criteria": [
- {"id": "GH-28-01", "state": "Active", "version": "v1.5.0", "description": "User can access details."},
- {"id": "GH-28-02", "state": "Active", "version": "v1.5.0", "description": "Domain card is visible."},
- ],
+ def test_parse_missing_user_stories_raises_error(self):
+ """Test that a missing user_stories key raises AdapterError."""
+ payload = {
+ "metadata": {
+ "producer": {"name": "AbsaOSS/living-doc-collector-gh", "version": "1.0.0", "build": None},
+ "run": {"run_id": None, "run_attempt": None, "actor": None, "workflow": None, "ref": None, "sha": None},
+ "source": {
+ "systems": ["GitHub"],
+ "repositories": ["owner/repo"],
+ "organization": "owner",
+ "enterprise": None,
+ },
+ }
}
- body = _build_body_from_structured(raw_item)
-
- # body now only contains description
- assert body == "## Description\nAs a user, I want to view domain details."
- # bv/pc/ac are NOT in the body; they flow through structured fields
- assert "Business Value" not in body
- assert "Preconditions" not in body
- assert "Acceptance Criteria" not in body
+ with pytest.raises(AdapterError) as exc_info:
+ parse(payload)
+ assert "user_stories" in str(exc_info.value)
- def test_only_description_present(self):
- """Test body built with description only."""
- from living_doc_adapter_collector_gh.parser import _build_body_from_structured
+ def test_parse_empty_user_stories_list(self, minimal_payload):
+ """Test parsing with empty user_stories list."""
+ minimal_payload["user_stories"] = []
+ result = parse(minimal_payload)
- body = _build_body_from_structured({"description": "Simple description."})
+ assert len(result.user_stories) == 0
+ assert len(result.warnings) == 0
- assert body == "## Description\nSimple description."
+ def test_parse_null_acceptance_criteria(self, minimal_payload):
+ """Test parsing an item whose acceptance_criteria is null."""
+ minimal_payload["user_stories"][0]["acceptance_criteria"] = None
+ result = parse(minimal_payload)
- def test_no_structured_fields_returns_none(self):
- """Test that None is returned when no structured fields are present."""
- from living_doc_adapter_collector_gh.parser import _build_body_from_structured
+ assert result.user_stories[0].acceptance_criteria is None
- assert _build_body_from_structured({}) is None
- assert _build_body_from_structured({"title": "No structured content"}) is None
- def test_acceptance_criteria_without_meta(self):
- """Test that structured AC entries without state/version still parse cleanly."""
- from living_doc_adapter_collector_gh.parser import parse
+class TestParseAcceptanceCriteria:
+ """Tests for acceptance-criteria parsing behavior."""
- payload = {
+ def _payload(self, item):
+ return {
"metadata": {
"producer": {"name": "AbsaOSS/living-doc-collector-gh", "version": "0.1.1", "build": None},
"run": {"run_id": None, "run_attempt": None, "actor": None, "workflow": None, "ref": None, "sha": None},
@@ -297,123 +281,59 @@ def test_acceptance_criteria_without_meta(self):
},
"original_metadata": {},
},
- "items": [
- {
- "id": "owner/repo/1",
- "title": "Test",
- "state": "open",
- "tags": [],
- "url": "https://github.com/owner/repo/issues/1",
- "timestamps": {"created": "2025-01-01T00:00:00+00:00", "updated": "2025-01-01T00:00:00+00:00"},
- "acceptance_criteria": [
- {"id": None, "state": None, "version": None, "description": "Something happens."}
- ],
- }
- ],
+ "user_stories": [item],
"warnings": [],
}
- result = parse(payload)
- ac = result.items[0].structured_acceptance_criteria
- assert ac is not None
- assert ac[0].description == "Something happens."
- assert ac[0].id is None
-
- def test_structured_fields_used_when_body_absent(self):
- """Test that parse populates structured fields from item when body is missing."""
- payload = {
- "metadata": {
- "producer": {"name": "AbsaOSS/living-doc-collector-gh", "version": "0.1.1", "build": None},
- "run": {
- "run_id": None,
- "run_attempt": None,
- "actor": None,
- "workflow": None,
- "ref": None,
- "sha": None,
- },
- "source": {
- "systems": ["GitHub"],
- "repositories": ["owner/repo"],
- "organization": "owner",
- "enterprise": None,
- },
- "original_metadata": {},
- },
- "items": [
- {
- "id": "owner/repo/1",
- "title": "View domain",
- "state": "open",
- "tags": ["DocumentedUserStory"],
- "url": "https://github.com/owner/repo/issues/1",
- "timestamps": {"created": "2025-12-17T11:31:16+00:00", "updated": "2026-04-09T07:10:01+00:00"},
- "description": "As a user, I want to view the details of a selected domain.",
- "business_value": ["Streamlines domain details visibility."],
- "preconditions": ["The user has logged in."],
- "acceptance_criteria": [
- {
- "id": "GH-28-01",
- "state": "Active",
- "version": "v1.5.0",
- "description": "User can access details.",
- },
- ],
- }
+
+ def test_structured_fields_populated(self):
+ """Test that parse populates the structured fields from the item."""
+ item = {
+ "id": "owner/repo/1",
+ "title": "View domain",
+ "state": "open",
+ "tags": ["DocumentedUserStory"],
+ "url": "https://github.com/owner/repo/issues/1",
+ "timestamps": {"created": "2025-12-17T11:31:16+00:00", "updated": "2026-04-09T07:10:01+00:00"},
+ "description": "As a user, I want to view the details of a selected domain.",
+ "business_value": ["Streamlines domain details visibility."],
+ "preconditions": ["The user has logged in."],
+ "acceptance_criteria": [
+ {"id": "GH-28-01", "state": "Active", "version": "v1.5.0", "description": "User can access details."},
],
- "warnings": [],
}
- result = parse(payload)
-
- assert len(result.items) == 1
- item = result.items[0]
- # body carries only the description
- assert item.body == "## Description\nAs a user, I want to view the details of a selected domain."
- # structured fields are populated directly
- assert item.structured_business_value == ["Streamlines domain details visibility."]
- assert item.structured_preconditions == ["The user has logged in."]
- assert item.structured_acceptance_criteria is not None
- assert item.structured_acceptance_criteria[0].id == "GH-28-01"
- assert item.structured_acceptance_criteria[0].state == "Active"
- assert item.structured_acceptance_criteria[0].version == "v1.5.0"
- assert item.structured_acceptance_criteria[0].description == "User can access details."
-
- def test_explicit_body_not_overridden_by_structured_fields(self):
- """Test that an explicit body field takes precedence over structured fields."""
- payload = {
- "metadata": {
- "producer": {"name": "AbsaOSS/living-doc-collector-gh", "version": "0.1.1", "build": None},
- "run": {
- "run_id": None,
- "run_attempt": None,
- "actor": None,
- "workflow": None,
- "ref": None,
- "sha": None,
- },
- "source": {
- "systems": ["GitHub"],
- "repositories": ["owner/repo"],
- "organization": "owner",
- "enterprise": None,
- },
- "original_metadata": {},
- },
- "items": [
- {
- "id": "owner/repo/2",
- "title": "Edit domain",
- "state": "open",
- "tags": [],
- "url": "https://github.com/owner/repo/issues/2",
- "timestamps": {"created": "2025-12-17T11:31:16+00:00", "updated": "2026-04-09T07:10:01+00:00"},
- "body": "## Description\nExplicit markdown body.",
- "description": "Should be ignored.",
- }
- ],
- "warnings": [],
+ result = parse(self._payload(item))
+
+ assert len(result.user_stories) == 1
+ story = result.user_stories[0]
+ assert story.description == "As a user, I want to view the details of a selected domain."
+ assert story.business_value == ["Streamlines domain details visibility."]
+ assert story.preconditions == ["The user has logged in."]
+ assert story.acceptance_criteria is not None
+ assert story.acceptance_criteria[0].id == "GH-28-01"
+ assert story.acceptance_criteria[0].state == "Active"
+ assert story.acceptance_criteria[0].version == "v1.5.0"
+ assert story.acceptance_criteria[0].description == "User can access details."
+
+ def test_null_structured_fields(self):
+ """Test that an item with null structured fields parses cleanly."""
+ item = {
+ "id": "owner/repo/15",
+ "title": "Add data feeds",
+ "state": "closed",
+ "tags": ["DocumentedUserStory"],
+ "url": "https://github.com/owner/repo/issues/15",
+ "timestamps": {"created": "2025-11-21T10:27:16+00:00", "updated": "2026-03-30T08:41:28+00:00"},
+ "description": None,
+ "business_value": None,
+ "preconditions": None,
+ "acceptance_criteria": None,
}
- result = parse(payload)
+ result = parse(self._payload(item))
- assert result.items[0].body == "## Description\nExplicit markdown body."
+ story = result.user_stories[0]
+ assert story.description is None
+ assert story.business_value is None
+ assert story.preconditions is None
+ assert story.acceptance_criteria is None
diff --git a/packages/services/coverage_matrix/README.md b/packages/services/coverage_matrix/README.md
new file mode 100644
index 0000000..a306f4a
--- /dev/null
+++ b/packages/services/coverage_matrix/README.md
@@ -0,0 +1,60 @@
+# living-doc-service-coverage-matrix
+
+Coverage matrix generator for the Living Documentation Toolkit.
+
+Cross-references two collector outputs to produce an AC-level test coverage matrix
+per User Story, ready for PDF report generation.
+
+## Inputs
+
+| Input | Source | Description |
+|---|---|---|
+| `doc-source.json` | living-doc-collector-gh `doc-source` mode | User Stories with acceptance criteria |
+| `ui-tests.json` | living-doc-collector-gh `ui-tests` mode | UI/E2E test scenarios with `us_id` / `ac_ids` |
+
+The doc input may be a bare JSON array of User Story objects, a legacy collector
+envelope exposing an `items` array, or a `doc-source` envelope exposing
+`user_stories`, `functionalities`, and `features` arrays. The tests input must be an
+envelope with an `items` array.
+
+## Output
+
+`coverage-matrix.json`, conforming to
+[`coverage-matrix-v1.0.0-schema.json`](src/living_doc_service_coverage_matrix/schema/coverage-matrix-v1.0.0-schema.json).
+It groups results into `user_stories`, `functionalities`, and `features`.
+
+## CLI
+
+```
+living-doc coverage-matrix \
+ --doc-input doc-source.json \
+ --tests-input ui-tests.json \
+ --output coverage-matrix.json \
+ [--fail-under 80]
+```
+
+| Argument | Required | Description |
+|---|---|---|
+| `--doc-input` | yes | Path to the US+AC doc JSON file |
+| `--tests-input` | yes | Path to the ui-tests JSON file |
+| `--output` | yes | Destination path for the coverage matrix JSON |
+| `--fail-under` | no | Exit with code 1 if `coverage_pct < N` |
+
+## Matching logic
+
+- A scenario links to a User Story when `scenario.us_id` equals the US short id
+ (the numeric suffix of `us.id`, e.g. `org/repo/US-27` -> `US-27`).
+- A scenario covers an AC when the `ac_id` appears in `scenario.ac_ids` and matches a
+ known `acceptance_criteria[].id` on the resolved User Story.
+- `coverage_pct` counts only Active ACs, so deprecated ACs never inflate it.
+- Scenarios with an unresolved `us_id` land in `unlinked_tests`; `ac_ids` that do not
+ exist on the resolved US land in `stale_ac_refs`.
+
+## Module layout
+
+- `loader.py` — pure I/O: `load_doc_input()`, `load_tests_input()`
+- `matcher.py` — pure: `build_coverage_matrix()`
+- `summary.py` — pure: `compute_summary()`, `compute_us_summary()`
+- `service.py` — orchestration: `run_service()`
+- `model/coverage_item.py` — output dataclasses
+- `schema/` — shipped JSON Schema
diff --git a/packages/services/coverage_matrix/pyproject.toml b/packages/services/coverage_matrix/pyproject.toml
new file mode 100644
index 0000000..dbd99cc
--- /dev/null
+++ b/packages/services/coverage_matrix/pyproject.toml
@@ -0,0 +1,55 @@
+[build-system]
+requires = ["setuptools>=61.0", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "living-doc-service-coverage-matrix"
+version = "1.0.0"
+description = "Coverage matrix generator for the Living Documentation Toolkit"
+readme = "README.md"
+license = {text = "Apache-2.0"}
+authors = [
+ {name = "AbsaOSS", email = "opensource@absa.africa"}
+]
+requires-python = ">=3.14"
+dependencies = [
+ "living-doc-core",
+ "jsonschema>=4.20.0,<5.0.0"
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=7.0.0",
+ "pytest-cov>=4.0.0",
+ "black>=23.0.0",
+ "pylint>=3.0.0",
+ "mypy>=1.0.0"
+]
+
+[tool.setuptools.packages.find]
+where = ["src"]
+
+[tool.setuptools.package-data]
+"living_doc_service_coverage_matrix" = ["schema/*.json"]
+
+[tool.black]
+line-length = 120
+target-version = ['py314']
+extend-exclude = '''
+^(
+ tests/
+ | verifications/
+)
+'''
+
+[tool.pylint.format]
+max-line-length = 120
+
+[tool.mypy]
+check_untyped_defs = true
+python_version = "3.14"
+exclude = '^(tests/|verifications/)'
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+addopts = "--cov=src --cov-report=term --cov-report=html --cov-fail-under=80"
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/__init__.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/__init__.py
new file mode 100644
index 0000000..d824fd4
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/__init__.py
@@ -0,0 +1,10 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+Living Documentation Toolkit - Coverage Matrix Service.
+
+This service cross-references collector doc-source output with ui-tests output to
+produce an AC-level test coverage matrix per User Story.
+"""
+
+__version__ = "1.0.0"
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/loader.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/loader.py
new file mode 100644
index 0000000..e3f8a3e
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/loader.py
@@ -0,0 +1,74 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+Input loaders for the coverage-matrix service.
+
+Pure I/O boundary: read JSON files and extract the item arrays. No matching logic.
+"""
+
+from pathlib import Path
+
+from living_doc_core.errors import InvalidInputError # type: ignore[import-untyped]
+from living_doc_core.json_utils import read_json # type: ignore[import-untyped]
+
+from living_doc_service_coverage_matrix.schema_validation import is_doc_source_envelope, validate_doc_source
+
+
+def load_doc_input(filepath: str | Path) -> dict:
+ """
+ Load the doc-source input and split it into its object groups.
+
+ Accepts the doc-source envelope (an object exposing ``user_stories``,
+ ``functionalities`` and ``features`` arrays), a legacy collector envelope with an
+ ``items`` array, or a bare JSON array of User Story objects. Full doc-source
+ envelopes (carrying ``metadata`` and ``warnings``) are validated against the
+ bundled ``doc-source-v1.0.0`` JSON Schema.
+
+ Args:
+ filepath: Path to the doc JSON file (doc-source.json / doc-issues.json)
+
+ Returns:
+ Dict with ``user_stories``, ``functionalities`` and ``features`` lists
+
+ Raises:
+ FileIOError: If the file is missing or unreadable
+ InvalidInputError: If the JSON is malformed, fails schema validation, or is
+ not a US array/envelope
+ """
+ payload = read_json(filepath)
+ if isinstance(payload, list):
+ return {"user_stories": payload, "functionalities": [], "features": []}
+ if isinstance(payload, dict):
+ if is_doc_source_envelope(payload):
+ validate_doc_source(payload)
+ if isinstance(payload.get("user_stories"), list):
+ return {
+ "user_stories": payload["user_stories"],
+ "functionalities": payload.get("functionalities") or [],
+ "features": payload.get("features") or [],
+ }
+ if isinstance(payload.get("items"), list):
+ return {"user_stories": payload["items"], "functionalities": [], "features": []}
+ raise InvalidInputError(
+ f"Doc input '{filepath}' must be a JSON array or an object with a " "'user_stories' (or legacy 'items') array"
+ )
+
+
+def load_tests_input(filepath: str | Path) -> list[dict]:
+ """
+ Load the ui-tests input.
+
+ Args:
+ filepath: Path to the ui-tests JSON file
+
+ Returns:
+ List of test scenario dictionaries
+
+ Raises:
+ FileIOError: If the file is missing or unreadable
+ InvalidInputError: If the JSON is malformed or has no ``items`` array
+ """
+ payload = read_json(filepath)
+ if isinstance(payload, dict) and isinstance(payload.get("items"), list):
+ return payload["items"]
+ raise InvalidInputError(f"Tests input '{filepath}' must contain an 'items' array")
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/matcher.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/matcher.py
new file mode 100644
index 0000000..247405d
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/matcher.py
@@ -0,0 +1,212 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+Coverage matcher.
+
+Pure transformation: takes parsed doc groups (user stories, functionalities, features)
+and test scenarios and returns a :class:`CoverageMatrix`. No file I/O and no logging —
+fully unit-testable.
+"""
+
+from collections import defaultdict
+
+from living_doc_service_coverage_matrix.model.coverage_item import (
+ AcCoverage,
+ Coverage,
+ CoverageMatrix,
+ FeatureEntry,
+ FunctionalityCoverage,
+ StaleAcRef,
+ TestRef,
+ UnlinkedTest,
+ UserStoryCoverage,
+)
+from living_doc_service_coverage_matrix.summary import compute_summary, compute_us_summary
+
+SCHEMA_VERSION = "coverage-matrix-v1.0.0"
+COVERED = "covered"
+NOT_COVERED = "not_covered"
+
+
+def _short_id(full_id: str) -> str:
+ """Extract the short id (e.g. ``US-27`` / ``FUNC-001``) from ``org/repo/US-27``."""
+ return full_id.split("/")[-1]
+
+
+def _test_ref(scenario: dict) -> TestRef:
+ """Build a TestRef from a scenario dictionary."""
+ return TestRef(
+ id=scenario.get("id"),
+ scenario_name=scenario.get("scenario_name"),
+ tags=scenario.get("tags") or [],
+ source=scenario.get("source"),
+ )
+
+
+def _ac_ids_of(entity: dict) -> set[str]:
+ """Collect the set of AC ids declared on an entity."""
+ return {ac.get("id") for ac in entity.get("acceptance_criteria") or []}
+
+
+def _resolve_tests(
+ test_items: list[dict],
+ us_by_num: dict[str, dict],
+ func_by_num: dict[str, dict],
+) -> tuple[
+ dict[str, dict[str, list[TestRef]]],
+ dict[str, dict[str, list[TestRef]]],
+ list[UnlinkedTest],
+ list[StaleAcRef],
+]:
+ """Split scenarios into per-US and per-Functionality coverage maps, unlinked, and stale refs."""
+ us_map: dict[str, dict[str, list[TestRef]]] = defaultdict(lambda: defaultdict(list))
+ func_map: dict[str, dict[str, list[TestRef]]] = defaultdict(lambda: defaultdict(list))
+ unlinked: list[UnlinkedTest] = []
+ stale: list[StaleAcRef] = []
+
+ for scenario in test_items:
+ us_id = scenario.get("us_id")
+ func_id = scenario.get("func_id")
+ us = us_by_num.get(us_id) if us_id else None
+ func = func_by_num.get(func_id) if func_id else None
+
+ if us is None and func is None:
+ unlinked.append(
+ UnlinkedTest(
+ id=scenario.get("id"),
+ scenario_name=scenario.get("scenario_name"),
+ us_id=us_id,
+ func_id=func_id,
+ ac_ids=scenario.get("ac_ids") or [],
+ source=scenario.get("source"),
+ )
+ )
+ continue
+
+ us_ac_ids = _ac_ids_of(us) if us is not None else set()
+ func_ac_ids = _ac_ids_of(func) if func is not None else set()
+ us_key = _short_id(us["id"]) if us is not None else None
+ func_key = _short_id(func["id"]) if func is not None else None
+
+ for ac_id in scenario.get("ac_ids") or []:
+ matched = False
+ if us_key is not None and ac_id in us_ac_ids:
+ us_map[us_key][ac_id].append(_test_ref(scenario))
+ matched = True
+ if func_key is not None and ac_id in func_ac_ids:
+ func_map[func_key][ac_id].append(_test_ref(scenario))
+ matched = True
+ if not matched:
+ stale.append(
+ StaleAcRef(
+ scenario_id=scenario.get("id"),
+ scenario_name=scenario.get("scenario_name"),
+ us_id=us_id,
+ func_id=func_id,
+ stale_ac_id=ac_id,
+ source=scenario.get("source"),
+ )
+ )
+
+ return us_map, func_map, unlinked, stale
+
+
+def _build_ac_coverages(entity: dict, ac_tests: dict[str, list[TestRef]]) -> list[AcCoverage]:
+ """Build the per-AC coverage list for an entity."""
+ ac_coverages: list[AcCoverage] = []
+ for ac in entity.get("acceptance_criteria") or []:
+ tests = ac_tests.get(ac.get("id"), [])
+ status = COVERED if tests else NOT_COVERED
+ ac_coverages.append(
+ AcCoverage(
+ id=ac.get("id"),
+ state=ac.get("state"),
+ version=ac.get("version"),
+ description=ac.get("description"),
+ coverage=Coverage(status=status, test_count=len(tests), tests=tests),
+ )
+ )
+ return ac_coverages
+
+
+def _build_user_story(us: dict, ac_tests: dict[str, list[TestRef]]) -> UserStoryCoverage:
+ """Build a single UserStoryCoverage from a US dict and its resolved AC tests."""
+ ac_coverages = _build_ac_coverages(us, ac_tests)
+ return UserStoryCoverage(
+ id=_short_id(us["id"]),
+ full_id=us["id"],
+ title=us.get("title"),
+ state=us.get("state"),
+ summary=compute_us_summary(ac_coverages),
+ acceptance_criteria=ac_coverages,
+ )
+
+
+def _build_functionality(func: dict, ac_tests: dict[str, list[TestRef]]) -> FunctionalityCoverage:
+ """Build a single FunctionalityCoverage from a functionality dict and its resolved AC tests."""
+ ac_coverages = _build_ac_coverages(func, ac_tests)
+ return FunctionalityCoverage(
+ id=_short_id(func["id"]),
+ full_id=func["id"],
+ title=func.get("title"),
+ state=func.get("state"),
+ parent=func.get("parent"),
+ func_type=func.get("func_type"),
+ summary=compute_us_summary(ac_coverages),
+ acceptance_criteria=ac_coverages,
+ )
+
+
+def _build_feature(feature: dict) -> FeatureEntry:
+ """Build a single FeatureEntry from a feature dict (registry surface, no ACs)."""
+ return FeatureEntry(
+ id=_short_id(feature["id"]),
+ full_id=feature["id"],
+ title=feature.get("title"),
+ state=feature.get("state"),
+ surface_type=feature.get("surface_type"),
+ route=feature.get("route"),
+ owners=feature.get("owners"),
+ purpose=feature.get("purpose"),
+ user_stories=feature.get("user_stories") or [],
+ functionalities=feature.get("functionalities") or [],
+ external_dependencies=feature.get("external_dependencies"),
+ page_object=feature.get("page_object"),
+ )
+
+
+def build_coverage_matrix(doc: dict, test_items: list[dict], generated_at: str) -> CoverageMatrix:
+ """
+ Cross-reference User Stories, Functionalities, Features and UI tests into a coverage matrix.
+
+ Args:
+ doc: Doc groups with ``user_stories``, ``functionalities`` and ``features`` lists
+ test_items: UI test scenario dictionaries
+ generated_at: ISO-8601 timestamp recorded on the matrix
+
+ Returns:
+ A fully populated :class:`CoverageMatrix`.
+ """
+ doc_us = doc.get("user_stories") or []
+ doc_func = doc.get("functionalities") or []
+ doc_feat = doc.get("features") or []
+
+ us_by_num = {_short_id(us["id"]): us for us in doc_us}
+ func_by_num = {_short_id(func["id"]): func for func in doc_func}
+
+ us_map, func_map, unlinked, stale = _resolve_tests(test_items, us_by_num, func_by_num)
+
+ user_stories = [_build_user_story(us, us_map.get(_short_id(us["id"]), {})) for us in doc_us]
+ functionalities = [_build_functionality(func, func_map.get(_short_id(func["id"]), {})) for func in doc_func]
+ features = [_build_feature(feature) for feature in doc_feat]
+
+ return CoverageMatrix(
+ schema_version=SCHEMA_VERSION,
+ generated_at=generated_at,
+ summary=compute_summary(user_stories, functionalities, len(features)),
+ user_stories=user_stories,
+ functionalities=functionalities,
+ features=features,
+ unlinked_tests=unlinked,
+ stale_ac_refs=stale,
+ )
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/model/__init__.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/model/__init__.py
new file mode 100644
index 0000000..5f860b8
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/model/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Dataclasses describing the coverage-matrix output schema."""
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/model/coverage_item.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/model/coverage_item.py
new file mode 100644
index 0000000..587b5ee
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/model/coverage_item.py
@@ -0,0 +1,150 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+Dataclasses for the coverage-matrix output document.
+
+These types mirror ``coverage-matrix-v1.0.0-schema.json`` and serialize to plain
+dictionaries via :meth:`CoverageMatrix.to_dict`.
+"""
+
+from dataclasses import asdict, dataclass
+from typing import Any, Optional
+
+
+@dataclass
+class Summary:
+ """AC coverage tallies scoped to a single User Story or Functionality."""
+
+ total_acs: int
+ active_acs: int
+ covered_acs: int
+ coverage_pct: Optional[float]
+
+
+@dataclass
+class TopSummary:
+ """AC coverage tallies aggregated across User Stories and Functionalities."""
+
+ total_user_stories: int
+ total_functionalities: int
+ total_features: int
+ total_acs: int
+ active_acs: int
+ covered_acs: int
+ coverage_pct: Optional[float]
+
+
+@dataclass
+class TestRef:
+ """A reference to a UI test scenario covering an acceptance criterion."""
+
+ id: Optional[str]
+ scenario_name: Optional[str]
+ tags: list[str]
+ source: Optional[dict[str, Any]]
+
+
+@dataclass
+class Coverage:
+ """Coverage status and linked tests for a single acceptance criterion."""
+
+ status: str
+ test_count: int
+ tests: list[TestRef]
+
+
+@dataclass
+class AcCoverage:
+ """An acceptance criterion with its resolved coverage."""
+
+ id: Optional[str]
+ state: Optional[str]
+ version: Optional[str]
+ description: Optional[str]
+ coverage: Coverage
+
+
+@dataclass
+class UserStoryCoverage:
+ """A User Story with per-AC coverage and a scoped summary."""
+
+ id: str
+ full_id: str
+ title: Optional[str]
+ state: Optional[str]
+ summary: Summary
+ acceptance_criteria: list[AcCoverage]
+
+
+@dataclass
+class FunctionalityCoverage:
+ """A Functionality with per-AC coverage and a scoped summary."""
+
+ id: str
+ full_id: str
+ title: Optional[str]
+ state: Optional[str]
+ parent: Optional[str]
+ func_type: Optional[str]
+ summary: Summary
+ acceptance_criteria: list[AcCoverage]
+
+
+@dataclass
+class FeatureEntry:
+ """A Feature surface registry entry (no acceptance criteria of its own)."""
+
+ id: str
+ full_id: str
+ title: Optional[str]
+ state: Optional[str]
+ surface_type: Optional[str]
+ route: Optional[str]
+ owners: Optional[str]
+ purpose: Optional[str]
+ user_stories: list[str]
+ functionalities: list[str]
+ external_dependencies: Optional[str]
+ page_object: Optional[str]
+
+
+@dataclass
+class UnlinkedTest:
+ """A scenario whose ``us_id`` / ``func_id`` is null or does not resolve."""
+
+ id: Optional[str]
+ scenario_name: Optional[str]
+ us_id: Optional[str]
+ func_id: Optional[str]
+ ac_ids: list[str]
+ source: Optional[dict[str, Any]]
+
+
+@dataclass
+class StaleAcRef:
+ """An ``ac_id`` referenced by a scenario that does not exist on the resolved entity."""
+
+ scenario_id: Optional[str]
+ scenario_name: Optional[str]
+ us_id: Optional[str]
+ func_id: Optional[str]
+ stale_ac_id: str
+ source: Optional[dict[str, Any]]
+
+
+@dataclass
+class CoverageMatrix:
+ """Top-level coverage-matrix document."""
+
+ schema_version: str
+ generated_at: str
+ summary: TopSummary
+ user_stories: list[UserStoryCoverage]
+ functionalities: list[FunctionalityCoverage]
+ features: list[FeatureEntry]
+ unlinked_tests: list[UnlinkedTest]
+ stale_ac_refs: list[StaleAcRef]
+
+ def to_dict(self) -> dict[str, Any]:
+ """Serialize the matrix to a plain dictionary suitable for JSON output."""
+ return asdict(self)
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/coverage-matrix-v1.0.0-schema.json b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/coverage-matrix-v1.0.0-schema.json
new file mode 100644
index 0000000..5c93f8c
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/coverage-matrix-v1.0.0-schema.json
@@ -0,0 +1,406 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://absaoss.github.io/living-doc-toolkit/schemas/coverage-matrix-v1.0.0-schema.json",
+ "title": "Coverage Matrix",
+ "description": "AC-level test coverage matrix per User Story and Functionality, plus a Feature registry, produced by the coverage-matrix generator.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "schema_version",
+ "generated_at",
+ "summary",
+ "user_stories",
+ "functionalities",
+ "features",
+ "unlinked_tests",
+ "stale_ac_refs"
+ ],
+ "properties": {
+ "schema_version": {
+ "type": "string",
+ "const": "coverage-matrix-v1.0.0"
+ },
+ "generated_at": {
+ "type": "string",
+ "description": "ISO-8601 timestamp of generation."
+ },
+ "summary": {
+ "$ref": "#/$defs/topSummary"
+ },
+ "user_stories": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/userStory"
+ }
+ },
+ "functionalities": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/functionality"
+ }
+ },
+ "features": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/feature"
+ }
+ },
+ "unlinked_tests": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/unlinkedTest"
+ }
+ },
+ "stale_ac_refs": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/staleAcRef"
+ }
+ }
+ },
+ "$defs": {
+ "source": {
+ "type": ["object", "null"],
+ "properties": {
+ "org": {
+ "type": ["string", "null"]
+ },
+ "repo": {
+ "type": ["string", "null"]
+ },
+ "file": {
+ "type": ["string", "null"]
+ }
+ }
+ },
+ "summary": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["total_acs", "active_acs", "covered_acs", "coverage_pct"],
+ "properties": {
+ "total_acs": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "active_acs": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "covered_acs": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "coverage_pct": {
+ "type": ["number", "null"]
+ }
+ }
+ },
+ "topSummary": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "total_user_stories",
+ "total_functionalities",
+ "total_features",
+ "total_acs",
+ "active_acs",
+ "covered_acs",
+ "coverage_pct"
+ ],
+ "properties": {
+ "total_user_stories": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "total_functionalities": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "total_features": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "total_acs": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "active_acs": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "covered_acs": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "coverage_pct": {
+ "type": ["number", "null"]
+ }
+ }
+ },
+ "testRef": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["id", "scenario_name", "tags", "source"],
+ "properties": {
+ "id": {
+ "type": ["string", "null"]
+ },
+ "scenario_name": {
+ "type": ["string", "null"]
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "source": {
+ "$ref": "#/$defs/source"
+ }
+ }
+ },
+ "coverage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["status", "test_count", "tests"],
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": ["covered", "not_covered"]
+ },
+ "test_count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "tests": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/testRef"
+ }
+ }
+ }
+ },
+ "acceptanceCriterion": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["id", "state", "version", "description", "coverage"],
+ "properties": {
+ "id": {
+ "type": ["string", "null"]
+ },
+ "state": {
+ "type": ["string", "null"]
+ },
+ "version": {
+ "type": ["string", "null"]
+ },
+ "description": {
+ "type": ["string", "null"]
+ },
+ "coverage": {
+ "$ref": "#/$defs/coverage"
+ }
+ }
+ },
+ "userStory": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "full_id",
+ "title",
+ "state",
+ "summary",
+ "acceptance_criteria"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "full_id": {
+ "type": "string"
+ },
+ "title": {
+ "type": ["string", "null"]
+ },
+ "state": {
+ "type": ["string", "null"]
+ },
+ "summary": {
+ "$ref": "#/$defs/summary"
+ },
+ "acceptance_criteria": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/acceptanceCriterion"
+ }
+ }
+ }
+ },
+ "functionality": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "full_id",
+ "title",
+ "state",
+ "parent",
+ "func_type",
+ "summary",
+ "acceptance_criteria"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "full_id": {
+ "type": "string"
+ },
+ "title": {
+ "type": ["string", "null"]
+ },
+ "state": {
+ "type": ["string", "null"]
+ },
+ "parent": {
+ "type": ["string", "null"]
+ },
+ "func_type": {
+ "type": ["string", "null"]
+ },
+ "summary": {
+ "$ref": "#/$defs/summary"
+ },
+ "acceptance_criteria": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/acceptanceCriterion"
+ }
+ }
+ }
+ },
+ "feature": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "full_id",
+ "title",
+ "state",
+ "surface_type",
+ "route",
+ "owners",
+ "purpose",
+ "user_stories",
+ "functionalities",
+ "external_dependencies",
+ "page_object"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "full_id": {
+ "type": "string"
+ },
+ "title": {
+ "type": ["string", "null"]
+ },
+ "state": {
+ "type": ["string", "null"]
+ },
+ "surface_type": {
+ "type": ["string", "null"]
+ },
+ "route": {
+ "type": ["string", "null"]
+ },
+ "owners": {
+ "type": ["string", "null"]
+ },
+ "purpose": {
+ "type": ["string", "null"]
+ },
+ "user_stories": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "functionalities": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "external_dependencies": {
+ "type": ["string", "null"]
+ },
+ "page_object": {
+ "type": ["string", "null"]
+ }
+ }
+ },
+ "unlinkedTest": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["id", "scenario_name", "us_id", "func_id", "ac_ids", "source"],
+ "properties": {
+ "id": {
+ "type": ["string", "null"]
+ },
+ "scenario_name": {
+ "type": ["string", "null"]
+ },
+ "us_id": {
+ "type": ["string", "null"]
+ },
+ "func_id": {
+ "type": ["string", "null"]
+ },
+ "ac_ids": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "source": {
+ "$ref": "#/$defs/source"
+ }
+ }
+ },
+ "staleAcRef": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "scenario_id",
+ "scenario_name",
+ "us_id",
+ "func_id",
+ "stale_ac_id",
+ "source"
+ ],
+ "properties": {
+ "scenario_id": {
+ "type": ["string", "null"]
+ },
+ "scenario_name": {
+ "type": ["string", "null"]
+ },
+ "us_id": {
+ "type": ["string", "null"]
+ },
+ "func_id": {
+ "type": ["string", "null"]
+ },
+ "stale_ac_id": {
+ "type": "string"
+ },
+ "source": {
+ "$ref": "#/$defs/source"
+ }
+ }
+ }
+ }
+}
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/doc-source-v1.0.0-schema.json b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/doc-source-v1.0.0-schema.json
new file mode 100644
index 0000000..773d47c
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema/doc-source-v1.0.0-schema.json
@@ -0,0 +1,126 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$schema_version": "1.0.0",
+ "$id": "https://absaoss.github.io/living-doc-toolkit/schemas/doc-source-v1.0.0-schema.json",
+ "$defs": {
+ "AcceptanceCriterion": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "state": { "type": "string" },
+ "version": { "type": "string" },
+ "description": { "type": "string" }
+ },
+ "required": ["id", "state", "version", "description"]
+ },
+ "AdapterMetadataProducer": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "version": { "type": "string" },
+ "build": { "type": ["string", "null"] }
+ },
+ "required": ["name", "version", "build"]
+ },
+ "AdapterMetadataRun": {
+ "type": "object",
+ "properties": {
+ "run_id": { "type": ["string", "null"] },
+ "run_attempt": { "type": ["string", "null"] },
+ "actor": { "type": ["string", "null"] },
+ "workflow": { "type": ["string", "null"] },
+ "ref": { "type": ["string", "null"] },
+ "sha": { "type": ["string", "null"] }
+ },
+ "required": ["run_id", "run_attempt", "actor", "workflow", "ref", "sha"]
+ },
+ "AdapterMetadataSource": {
+ "type": "object",
+ "properties": {
+ "systems": { "type": "array", "items": { "type": "string" } },
+ "repositories": { "type": "array", "items": { "type": "string" } },
+ "organization": { "type": ["string", "null"] },
+ "enterprise": { "type": ["string", "null"] }
+ },
+ "required": ["systems", "repositories", "organization", "enterprise"]
+ },
+ "AdapterMetadata": {
+ "type": "object",
+ "properties": {
+ "producer": { "$ref": "#/$defs/AdapterMetadataProducer" },
+ "run": { "$ref": "#/$defs/AdapterMetadataRun" },
+ "source": { "$ref": "#/$defs/AdapterMetadataSource" },
+ "original_metadata": { "type": "object", "additionalProperties": true }
+ },
+ "required": ["producer", "run", "source", "original_metadata"]
+ },
+ "CompatibilityWarning": {
+ "type": "object",
+ "properties": {
+ "code": { "type": "string" },
+ "message": { "type": "string" },
+ "context": { "type": ["string", "null"], "default": null }
+ },
+ "required": ["code", "message"]
+ },
+ "UserStoryItem": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "repository_name": { "type": "string" },
+ "title": { "type": "string" },
+ "state": { "type": ["string", "null"] },
+ "tags": { "type": "array", "items": { "type": "string" } },
+ "url": { "type": ["string", "null"] },
+ "timestamps": { "type": "null" },
+ "description": { "type": ["string", "null"], "default": null },
+ "business_value": { "anyOf": [{ "type": "array", "items": { "type": "string" } }, { "type": "null" }], "default": null },
+ "preconditions": { "anyOf": [{ "type": "array", "items": { "type": "string" } }, { "type": "null" }], "default": null },
+ "acceptance_criteria": { "anyOf": [{ "type": "array", "items": { "$ref": "#/$defs/AcceptanceCriterion" } }, { "type": "null" }], "default": null }
+ },
+ "required": ["id", "repository_name", "title", "state", "tags", "url", "timestamps"]
+ },
+ "FunctionalityItem": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "repository_name": { "type": "string" },
+ "title": { "type": "string" },
+ "state": { "type": ["string", "null"] },
+ "parent": { "type": ["string", "null"] },
+ "func_type": { "type": ["string", "null"] },
+ "acceptance_criteria": { "anyOf": [{ "type": "array", "items": { "$ref": "#/$defs/AcceptanceCriterion" } }, { "type": "null" }], "default": null }
+ },
+ "required": ["id", "repository_name", "title"]
+ },
+ "FeatureItem": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "string" },
+ "repository_name": { "type": "string" },
+ "title": { "type": "string" },
+ "state": { "type": ["string", "null"] },
+ "surface_type": { "type": ["string", "null"] },
+ "route": { "type": ["string", "null"] },
+ "owners": { "type": ["string", "null"] },
+ "purpose": { "type": ["string", "null"] },
+ "user_stories": { "type": "array", "items": { "type": "string" } },
+ "functionalities": { "type": "array", "items": { "type": "string" } },
+ "external_dependencies":{ "type": ["string", "null"] },
+ "page_object": { "type": ["string", "null"] }
+ },
+ "required": ["id", "repository_name", "title"]
+ }
+ },
+ "title": "DocSourceResult",
+ "description": "Complete result from doc-source collector parsing.",
+ "type": "object",
+ "properties": {
+ "user_stories": { "type": "array", "items": { "$ref": "#/$defs/UserStoryItem" } },
+ "functionalities": { "type": "array", "items": { "$ref": "#/$defs/FunctionalityItem" } },
+ "features": { "type": "array", "items": { "$ref": "#/$defs/FeatureItem" } },
+ "metadata": { "$ref": "#/$defs/AdapterMetadata" },
+ "warnings": { "type": "array", "items": { "$ref": "#/$defs/CompatibilityWarning" } }
+ },
+ "required": ["user_stories", "functionalities", "features", "metadata", "warnings"]
+}
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema_validation.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema_validation.py
new file mode 100644
index 0000000..f5dac8f
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/schema_validation.py
@@ -0,0 +1,55 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+Schema validation for coverage-matrix inputs.
+
+Validates the doc-source envelope against the bundled ``doc-source-v1.0.0`` JSON
+Schema before the matcher consumes it.
+"""
+
+import json
+from functools import lru_cache
+from importlib import resources
+
+from jsonschema import Draft7Validator # type: ignore[import-untyped]
+from living_doc_core.errors import InvalidInputError # type: ignore[import-untyped]
+
+_DOC_SOURCE_SCHEMA_FILE = "doc-source-v1.0.0-schema.json"
+
+
+@lru_cache(maxsize=1)
+def _doc_source_validator() -> Draft7Validator:
+ """Build (and cache) the Draft-07 validator for the doc-source schema."""
+ schema_text = (
+ resources.files("living_doc_service_coverage_matrix")
+ .joinpath("schema", _DOC_SOURCE_SCHEMA_FILE)
+ .read_text(encoding="utf-8")
+ )
+ return Draft7Validator(json.loads(schema_text))
+
+
+def is_doc_source_envelope(payload: object) -> bool:
+ """Return ``True`` when the payload is a full doc-source envelope."""
+ return (
+ isinstance(payload, dict)
+ and isinstance(payload.get("user_stories"), list)
+ and "metadata" in payload
+ and "warnings" in payload
+ )
+
+
+def validate_doc_source(payload: dict) -> None:
+ """
+ Validate a doc-source envelope against the bundled JSON Schema.
+
+ Args:
+ payload: The parsed doc-source JSON object
+
+ Raises:
+ InvalidInputError: If the payload violates the doc-source schema
+ """
+ validator = _doc_source_validator()
+ errors = sorted(validator.iter_errors(payload), key=lambda err: list(err.path))
+ if errors:
+ details = "; ".join(f"{'/'.join(str(p) for p in err.path) or ''}: {err.message}" for err in errors)
+ raise InvalidInputError(f"doc-source input failed schema validation: {details}")
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/service.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/service.py
new file mode 100644
index 0000000..f1e4b4e
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/service.py
@@ -0,0 +1,112 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+Service orchestration for the coverage-matrix generator.
+
+Loads the doc and tests inputs, builds the coverage matrix, writes the output JSON,
+and enforces an optional ``--fail-under`` threshold.
+"""
+
+from datetime import datetime, timezone
+
+from living_doc_core.errors import ToolkitError # type: ignore[import-untyped]
+from living_doc_core.json_utils import write_json # type: ignore[import-untyped]
+from living_doc_core.logging_config import setup_logging # type: ignore[import-untyped]
+
+from living_doc_service_coverage_matrix.loader import load_doc_input, load_tests_input
+from living_doc_service_coverage_matrix.matcher import build_coverage_matrix
+from living_doc_service_coverage_matrix.model.coverage_item import CoverageMatrix
+
+
+class CoverageThresholdError(ToolkitError):
+ """Coverage percentage below the configured ``--fail-under`` threshold. Exit code 1."""
+
+ exit_code = 1
+
+
+def _filter_valid_entities(items: list[dict], kind: str, logger) -> list[dict]:
+ """Drop entities missing ``id`` or ``acceptance_criteria``, logging each skip."""
+ valid: list[dict] = []
+ for entity in items:
+ if not isinstance(entity, dict) or not entity.get("id") or entity.get("acceptance_criteria") is None:
+ skipped_id = entity.get("id") if isinstance(entity, dict) else entity
+ logger.warning("Skipping %s missing 'id' or 'acceptance_criteria': %s", kind, skipped_id)
+ continue
+ valid.append(entity)
+ return valid
+
+
+def _filter_valid_features(items: list[dict], logger) -> list[dict]:
+ """Drop features missing ``id``, logging each skip."""
+ valid: list[dict] = []
+ for feature in items:
+ if not isinstance(feature, dict) or not feature.get("id"):
+ skipped_id = feature.get("id") if isinstance(feature, dict) else feature
+ logger.warning("Skipping feature missing 'id': %s", skipped_id)
+ continue
+ valid.append(feature)
+ return valid
+
+
+def run_service(doc_input: str, tests_input: str, output_path: str, options: dict) -> CoverageMatrix:
+ """
+ Run the coverage-matrix generation pipeline.
+
+ Args:
+ doc_input: Path to the doc JSON file (doc-source.json / doc-issues.json)
+ tests_input: Path to the ui-tests JSON file
+ output_path: Destination path for coverage-matrix.json
+ options: Configuration options (``verbose``, ``fail_under``)
+
+ Returns:
+ The generated :class:`CoverageMatrix`.
+
+ Raises:
+ FileIOError: If an input file is missing or the output cannot be written
+ InvalidInputError: If an input file is malformed or has the wrong shape
+ CoverageThresholdError: If coverage is below ``--fail-under``
+ """
+ verbose = options.get("verbose", False)
+ fail_under = options.get("fail_under")
+ logger = setup_logging(verbose=verbose)
+
+ logger.info("Starting coverage matrix generation")
+ logger.info("Doc input: %s", doc_input)
+ logger.info("Tests input: %s", tests_input)
+ logger.info("Output: %s", output_path)
+
+ doc_groups = load_doc_input(doc_input)
+ test_items = load_tests_input(tests_input)
+
+ valid_doc = {
+ "user_stories": _filter_valid_entities(doc_groups["user_stories"], "user story", logger),
+ "functionalities": _filter_valid_entities(doc_groups["functionalities"], "functionality", logger),
+ "features": _filter_valid_features(doc_groups["features"], logger),
+ }
+
+ generated_at = datetime.now(timezone.utc).isoformat()
+ matrix = build_coverage_matrix(valid_doc, test_items, generated_at)
+
+ logger.info("Writing coverage matrix JSON...")
+ write_json(output_path, matrix.to_dict(), indent=2, sort_keys=True)
+
+ summary = matrix.summary
+ logger.info("Coverage matrix written successfully")
+ logger.info(" - User stories: %d", summary.total_user_stories)
+ logger.info(" - Functionalities: %d", summary.total_functionalities)
+ logger.info(" - Features: %d", summary.total_features)
+ logger.info(" - Total ACs: %d", summary.total_acs)
+ logger.info(" - Active ACs: %d", summary.active_acs)
+ logger.info(" - Covered ACs: %d", summary.covered_acs)
+ logger.info(" - Coverage: %s%%", summary.coverage_pct)
+ if matrix.unlinked_tests:
+ logger.warning("Unlinked tests: %d", len(matrix.unlinked_tests))
+ if matrix.stale_ac_refs:
+ logger.warning("Stale AC references: %d", len(matrix.stale_ac_refs))
+
+ if fail_under is not None:
+ effective_pct = summary.coverage_pct if summary.coverage_pct is not None else 0.0
+ if effective_pct < fail_under:
+ raise CoverageThresholdError(f"Coverage {effective_pct}% is below threshold {fail_under}%")
+
+ return matrix
diff --git a/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/summary.py b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/summary.py
new file mode 100644
index 0000000..dcd5acd
--- /dev/null
+++ b/packages/services/coverage_matrix/src/living_doc_service_coverage_matrix/summary.py
@@ -0,0 +1,66 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""
+Summary computation for the coverage matrix.
+
+Pure functions that tally AC coverage. An AC contributes to ``covered_acs`` only when
+it is Active and covered, so deprecated ACs never inflate ``coverage_pct``.
+"""
+
+from living_doc_service_coverage_matrix.model.coverage_item import (
+ AcCoverage,
+ FunctionalityCoverage,
+ Summary,
+ TopSummary,
+ UserStoryCoverage,
+)
+
+ACTIVE_STATE = "Active"
+COVERED = "covered"
+
+
+def _coverage_pct(covered: int, active: int) -> float | None:
+ """Return covered/active as a percentage rounded to 1 dp, or None when active is 0."""
+ if active == 0:
+ return None
+ return round(covered / active * 100, 1)
+
+
+def _is_active_covered(ac: AcCoverage) -> bool:
+ """True when an AC is Active and has at least one covering test."""
+ return ac.state == ACTIVE_STATE and ac.coverage.status == COVERED
+
+
+def compute_us_summary(acceptance_criteria: list[AcCoverage]) -> Summary:
+ """Compute the coverage summary for a single entity's acceptance criteria."""
+ total = len(acceptance_criteria)
+ active = sum(1 for ac in acceptance_criteria if ac.state == ACTIVE_STATE)
+ covered = sum(1 for ac in acceptance_criteria if _is_active_covered(ac))
+ return Summary(
+ total_acs=total,
+ active_acs=active,
+ covered_acs=covered,
+ coverage_pct=_coverage_pct(covered, active),
+ )
+
+
+def compute_summary(
+ user_stories: list[UserStoryCoverage],
+ functionalities: list[FunctionalityCoverage],
+ feature_count: int,
+) -> TopSummary:
+ """Compute the top-level coverage summary across User Stories and Functionalities."""
+ all_acs = [ac for us in user_stories for ac in us.acceptance_criteria]
+ all_acs += [ac for func in functionalities for ac in func.acceptance_criteria]
+ total = len(all_acs)
+ active = sum(1 for ac in all_acs if ac.state == ACTIVE_STATE)
+ covered = sum(1 for ac in all_acs if _is_active_covered(ac))
+ return TopSummary(
+ total_user_stories=len(user_stories),
+ total_functionalities=len(functionalities),
+ total_features=feature_count,
+ total_acs=total,
+ active_acs=active,
+ covered_acs=covered,
+ coverage_pct=_coverage_pct(covered, active),
+ )
diff --git a/packages/services/coverage_matrix/tests/__init__.py b/packages/services/coverage_matrix/tests/__init__.py
new file mode 100644
index 0000000..f8d2e07
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Unit tests for the coverage-matrix service."""
diff --git a/packages/services/coverage_matrix/tests/fixtures/golden/doc_source.json b/packages/services/coverage_matrix/tests/fixtures/golden/doc_source.json
new file mode 100644
index 0000000..348263f
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/fixtures/golden/doc_source.json
@@ -0,0 +1,90 @@
+{
+ "items": [
+ {
+ "id": "absa-group/aul-ui/US-1",
+ "title": "User Login",
+ "state": "active",
+ "tags": [],
+ "url": "https://github.com/absa-group/aul-ui/issues/2",
+ "timestamps": null,
+ "description": "As a user, I want to log in so that I can access the application.",
+ "business_value": [],
+ "preconditions": [],
+ "acceptance_criteria": [
+ {
+ "id": "US-1-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "User can log in with valid credentials."
+ },
+ {
+ "id": "US-1-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Login button is disabled until both fields are filled."
+ },
+ {
+ "id": "US-1-03",
+ "state": "Deprecated",
+ "version": "v0.9.0",
+ "description": "Legacy basic-auth login (removed)."
+ }
+ ]
+ },
+ {
+ "id": "absa-group/aul-ui/US-2",
+ "title": "View Dashboard",
+ "state": "active",
+ "tags": [],
+ "url": null,
+ "timestamps": null,
+ "description": "As a user, I want a dashboard overview.",
+ "business_value": [],
+ "preconditions": [],
+ "acceptance_criteria": [
+ {
+ "id": "US-2-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "User can see accessible domains on the dashboard."
+ },
+ {
+ "id": "US-2-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Dashboard shows an empty state when no domains are accessible."
+ }
+ ]
+ },
+ {
+ "id": "absa-group/aul-ui/US-7",
+ "title": "Delete Domain",
+ "state": "active",
+ "tags": [],
+ "url": null,
+ "timestamps": null,
+ "description": "As a user, I want to delete a domain.",
+ "business_value": [],
+ "preconditions": [],
+ "acceptance_criteria": []
+ }
+ ],
+ "metadata": {
+ "producer": {
+ "name": "AbsaOSS/living-doc-collector-gh",
+ "version": "0.1.1",
+ "build": null
+ },
+ "source": {
+ "systems": [
+ "GitHub"
+ ],
+ "repositories": [
+ "absa-group/aul-ui"
+ ],
+ "organization": "absa-group",
+ "enterprise": null
+ }
+ },
+ "warnings": []
+}
diff --git a/packages/services/coverage_matrix/tests/fixtures/golden/expected_coverage_matrix.json b/packages/services/coverage_matrix/tests/fixtures/golden/expected_coverage_matrix.json
new file mode 100644
index 0000000..f1d2f8d
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/fixtures/golden/expected_coverage_matrix.json
@@ -0,0 +1,220 @@
+{
+ "features": [],
+ "functionalities": [],
+ "generated_at": "PLACEHOLDER",
+ "schema_version": "coverage-matrix-v1.0.0",
+ "stale_ac_refs": [
+ {
+ "func_id": null,
+ "scenario_id": "absa-group/aul-ui/user_login.feature/references-unknown-ac",
+ "scenario_name": "Scenario referencing an AC that no longer exists",
+ "source": {
+ "file": "user_login.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "stale_ac_id": "US-1-99",
+ "us_id": "US-1"
+ }
+ ],
+ "summary": {
+ "active_acs": 4,
+ "coverage_pct": 75.0,
+ "covered_acs": 3,
+ "total_acs": 5,
+ "total_features": 0,
+ "total_functionalities": 0,
+ "total_user_stories": 3
+ },
+ "unlinked_tests": [
+ {
+ "ac_ids": [],
+ "func_id": null,
+ "id": "absa-group/aul-ui/orphan.feature/scenario-without-us",
+ "scenario_name": "Scenario not linked to any user story",
+ "source": {
+ "file": "orphan.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "us_id": null
+ },
+ {
+ "ac_ids": [
+ "US-99-01"
+ ],
+ "func_id": null,
+ "id": "absa-group/aul-ui/unknown.feature/scenario-for-unknown-us",
+ "scenario_name": "Scenario for a user story not in the doc input",
+ "source": {
+ "file": "unknown.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "us_id": "US-99"
+ }
+ ],
+ "user_stories": [
+ {
+ "acceptance_criteria": [
+ {
+ "coverage": {
+ "status": "covered",
+ "test_count": 2,
+ "tests": [
+ {
+ "id": "absa-group/aul-ui/user_login.feature/user-logs-in-with-valid-credentials",
+ "scenario_name": "User logs in with valid credentials",
+ "source": {
+ "file": "user_login.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "tags": [
+ "Regression"
+ ]
+ },
+ {
+ "id": "absa-group/aul-ui/user_login.feature/login-button-disabled",
+ "scenario_name": "Login button stays disabled until fields are filled",
+ "source": {
+ "file": "user_login.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "tags": [
+ "Smoke"
+ ]
+ }
+ ]
+ },
+ "description": "User can log in with valid credentials.",
+ "id": "US-1-01",
+ "state": "Active",
+ "version": "v1.0.0"
+ },
+ {
+ "coverage": {
+ "status": "covered",
+ "test_count": 1,
+ "tests": [
+ {
+ "id": "absa-group/aul-ui/user_login.feature/user-logs-in-with-valid-credentials",
+ "scenario_name": "User logs in with valid credentials",
+ "source": {
+ "file": "user_login.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "tags": [
+ "Regression"
+ ]
+ }
+ ]
+ },
+ "description": "Login button is disabled until both fields are filled.",
+ "id": "US-1-02",
+ "state": "Active",
+ "version": "v1.0.0"
+ },
+ {
+ "coverage": {
+ "status": "covered",
+ "test_count": 1,
+ "tests": [
+ {
+ "id": "absa-group/aul-ui/user_login.feature/legacy-basic-auth-login",
+ "scenario_name": "Legacy basic-auth login",
+ "source": {
+ "file": "user_login.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "tags": [
+ "Regression",
+ "skip"
+ ]
+ }
+ ]
+ },
+ "description": "Legacy basic-auth login (removed).",
+ "id": "US-1-03",
+ "state": "Deprecated",
+ "version": "v0.9.0"
+ }
+ ],
+ "full_id": "absa-group/aul-ui/US-1",
+ "id": "US-1",
+ "state": "active",
+ "summary": {
+ "active_acs": 2,
+ "coverage_pct": 100.0,
+ "covered_acs": 2,
+ "total_acs": 3
+ },
+ "title": "User Login"
+ },
+ {
+ "acceptance_criteria": [
+ {
+ "coverage": {
+ "status": "covered",
+ "test_count": 1,
+ "tests": [
+ {
+ "id": "absa-group/aul-ui/dashboard.feature/user-sees-accessible-domains",
+ "scenario_name": "User sees accessible domains on the dashboard",
+ "source": {
+ "file": "dashboard.feature",
+ "org": "absa-group",
+ "repo": "aul-ui"
+ },
+ "tags": [
+ "Regression"
+ ]
+ }
+ ]
+ },
+ "description": "User can see accessible domains on the dashboard.",
+ "id": "US-2-01",
+ "state": "Active",
+ "version": "v1.0.0"
+ },
+ {
+ "coverage": {
+ "status": "not_covered",
+ "test_count": 0,
+ "tests": []
+ },
+ "description": "Dashboard shows an empty state when no domains are accessible.",
+ "id": "US-2-02",
+ "state": "Active",
+ "version": "v1.0.0"
+ }
+ ],
+ "full_id": "absa-group/aul-ui/US-2",
+ "id": "US-2",
+ "state": "active",
+ "summary": {
+ "active_acs": 2,
+ "coverage_pct": 50.0,
+ "covered_acs": 1,
+ "total_acs": 2
+ },
+ "title": "View Dashboard"
+ },
+ {
+ "acceptance_criteria": [],
+ "full_id": "absa-group/aul-ui/US-7",
+ "id": "US-7",
+ "state": "active",
+ "summary": {
+ "active_acs": 0,
+ "coverage_pct": null,
+ "covered_acs": 0,
+ "total_acs": 0
+ },
+ "title": "Delete Domain"
+ }
+ ]
+}
diff --git a/packages/services/coverage_matrix/tests/fixtures/golden/ui_tests.json b/packages/services/coverage_matrix/tests/fixtures/golden/ui_tests.json
new file mode 100644
index 0000000..0f60d79
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/fixtures/golden/ui_tests.json
@@ -0,0 +1,146 @@
+{
+ "items": [
+ {
+ "id": "absa-group/aul-ui/user_login.feature/user-logs-in-with-valid-credentials",
+ "us_id": "US-1",
+ "ac_ids": [
+ "US-1-01",
+ "US-1-02"
+ ],
+ "scenario_name": "User logs in with valid credentials",
+ "scenario_type": "Scenario",
+ "tags": [
+ "Regression"
+ ],
+ "steps": [],
+ "source": {
+ "org": "absa-group",
+ "repo": "aul-ui",
+ "file": "user_login.feature"
+ }
+ },
+ {
+ "id": "absa-group/aul-ui/user_login.feature/legacy-basic-auth-login",
+ "us_id": "US-1",
+ "ac_ids": [
+ "US-1-03"
+ ],
+ "scenario_name": "Legacy basic-auth login",
+ "scenario_type": "Scenario",
+ "tags": [
+ "Regression",
+ "skip"
+ ],
+ "steps": [],
+ "source": {
+ "org": "absa-group",
+ "repo": "aul-ui",
+ "file": "user_login.feature"
+ }
+ },
+ {
+ "id": "absa-group/aul-ui/user_login.feature/login-button-disabled",
+ "us_id": "US-1",
+ "ac_ids": [
+ "US-1-01"
+ ],
+ "scenario_name": "Login button stays disabled until fields are filled",
+ "scenario_type": "Scenario",
+ "tags": [
+ "Smoke"
+ ],
+ "steps": [],
+ "source": {
+ "org": "absa-group",
+ "repo": "aul-ui",
+ "file": "user_login.feature"
+ }
+ },
+ {
+ "id": "absa-group/aul-ui/user_login.feature/references-unknown-ac",
+ "us_id": "US-1",
+ "ac_ids": [
+ "US-1-99"
+ ],
+ "scenario_name": "Scenario referencing an AC that no longer exists",
+ "scenario_type": "Scenario",
+ "tags": [
+ "Regression"
+ ],
+ "steps": [],
+ "source": {
+ "org": "absa-group",
+ "repo": "aul-ui",
+ "file": "user_login.feature"
+ }
+ },
+ {
+ "id": "absa-group/aul-ui/dashboard.feature/user-sees-accessible-domains",
+ "us_id": "US-2",
+ "ac_ids": [
+ "US-2-01"
+ ],
+ "scenario_name": "User sees accessible domains on the dashboard",
+ "scenario_type": "Scenario",
+ "tags": [
+ "Regression"
+ ],
+ "steps": [],
+ "source": {
+ "org": "absa-group",
+ "repo": "aul-ui",
+ "file": "dashboard.feature"
+ }
+ },
+ {
+ "id": "absa-group/aul-ui/orphan.feature/scenario-without-us",
+ "us_id": null,
+ "ac_ids": [],
+ "scenario_name": "Scenario not linked to any user story",
+ "scenario_type": "Scenario",
+ "tags": [],
+ "steps": [],
+ "source": {
+ "org": "absa-group",
+ "repo": "aul-ui",
+ "file": "orphan.feature"
+ }
+ },
+ {
+ "id": "absa-group/aul-ui/unknown.feature/scenario-for-unknown-us",
+ "us_id": "US-99",
+ "ac_ids": [
+ "US-99-01"
+ ],
+ "scenario_name": "Scenario for a user story not in the doc input",
+ "scenario_type": "Scenario",
+ "tags": [
+ "Regression"
+ ],
+ "steps": [],
+ "source": {
+ "org": "absa-group",
+ "repo": "aul-ui",
+ "file": "unknown.feature"
+ }
+ }
+ ],
+ "metadata": {
+ "producer": {
+ "name": "AbsaOSS/living-doc-collector-gh",
+ "version": "0.1.1",
+ "build": null
+ },
+ "source": {
+ "systems": [
+ "GitHub"
+ ],
+ "repositories": [
+ "absa-group/aul-ui"
+ ],
+ "organization": "absa-group",
+ "enterprise": null
+ }
+ },
+ "warnings": []
+}
diff --git a/packages/services/coverage_matrix/tests/integration/__init__.py b/packages/services/coverage_matrix/tests/integration/__init__.py
new file mode 100644
index 0000000..939961c
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/integration/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Integration tests for the coverage-matrix service."""
diff --git a/packages/services/coverage_matrix/tests/integration/test_golden_files.py b/packages/services/coverage_matrix/tests/integration/test_golden_files.py
new file mode 100644
index 0000000..caea573
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/integration/test_golden_files.py
@@ -0,0 +1,75 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Integration test for the coverage-matrix service using the golden fixtures."""
+
+import json
+from pathlib import Path
+
+from living_doc_service_coverage_matrix.service import run_service
+
+FIXTURES = Path(__file__).parent.parent / "fixtures" / "golden"
+
+
+def test_golden_coverage_matrix(tmp_path):
+ """Run the full pipeline and compare against the golden expected output."""
+ doc_file = FIXTURES / "doc_source.json"
+ tests_file = FIXTURES / "ui_tests.json"
+ expected_file = FIXTURES / "expected_coverage_matrix.json"
+ out_file = tmp_path / "coverage-matrix.json"
+
+ run_service(str(doc_file), str(tests_file), str(out_file), {})
+
+ with open(expected_file, "r", encoding="utf-8") as f:
+ expected = json.load(f)
+ with open(out_file, "r", encoding="utf-8") as f:
+ actual = json.load(f)
+
+ # generated_at is dynamic; normalise before comparing.
+ assert actual["generated_at"]
+ actual["generated_at"] = "PLACEHOLDER"
+
+ assert actual == expected, "Output does not match the golden expected_coverage_matrix.json"
+
+
+def test_golden_summary_and_buckets(tmp_path):
+ """Assert the key coverage facts produced from the golden fixtures."""
+ doc_file = FIXTURES / "doc_source.json"
+ tests_file = FIXTURES / "ui_tests.json"
+ out_file = tmp_path / "coverage-matrix.json"
+
+ matrix = run_service(str(doc_file), str(tests_file), str(out_file), {})
+
+ summary = matrix.summary
+ assert summary.total_user_stories == 3
+ assert summary.total_acs == 5
+ assert summary.active_acs == 4
+ assert summary.covered_acs == 3
+ assert summary.coverage_pct == 75.0
+
+ us_by_id = {us.id: us for us in matrix.user_stories}
+
+ # US-1: the deprecated AC is covered but excluded from coverage_pct.
+ us1 = us_by_id["US-1"]
+ us1_cov = {ac.id: ac.coverage for ac in us1.acceptance_criteria}
+ assert us1_cov["US-1-01"].status == "covered"
+ assert us1_cov["US-1-01"].test_count == 2
+ assert us1_cov["US-1-02"].status == "covered"
+ assert us1_cov["US-1-03"].status == "covered"
+ assert us1.summary.coverage_pct == 100.0
+
+ # US-2: one covered, one not covered.
+ us2 = us_by_id["US-2"]
+ us2_cov = {ac.id: ac.coverage.status for ac in us2.acceptance_criteria}
+ assert us2_cov == {"US-2-01": "covered", "US-2-02": "not_covered"}
+
+ # US-7: no ACs -> null coverage_pct.
+ us7 = us_by_id["US-7"]
+ assert us7.acceptance_criteria == []
+ assert us7.summary.coverage_pct is None
+
+ # Two unlinked scenarios (null us_id and unresolved US-99) and one stale AC ref.
+ unlinked_us_ids = sorted(str(t.us_id) for t in matrix.unlinked_tests)
+ assert unlinked_us_ids == ["None", "US-99"]
+ assert len(matrix.stale_ac_refs) == 1
+ assert matrix.stale_ac_refs[0].stale_ac_id == "US-1-99"
+ assert matrix.stale_ac_refs[0].us_id == "US-1"
diff --git a/packages/services/coverage_matrix/tests/test_loader.py b/packages/services/coverage_matrix/tests/test_loader.py
new file mode 100644
index 0000000..008ff8e
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/test_loader.py
@@ -0,0 +1,166 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Unit tests for the loader module."""
+
+import json
+
+import pytest
+
+from living_doc_core.errors import FileIOError, InvalidInputError
+
+from living_doc_service_coverage_matrix.loader import load_doc_input, load_tests_input
+
+
+def _write(path, payload):
+ with open(path, "w", encoding="utf-8") as f:
+ json.dump(payload, f)
+
+
+def test_load_doc_input_bare_array(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ _write(doc_file, [{"id": "org/repo/US-1", "acceptance_criteria": []}])
+
+ result = load_doc_input(str(doc_file))
+
+ assert len(result["user_stories"]) == 1
+ assert result["user_stories"][0]["id"] == "org/repo/US-1"
+ assert result["functionalities"] == []
+ assert result["features"] == []
+
+
+def test_load_doc_input_legacy_items_envelope(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ _write(doc_file, {"items": [{"id": "org/repo/US-1", "acceptance_criteria": []}], "metadata": {}})
+
+ result = load_doc_input(str(doc_file))
+
+ assert len(result["user_stories"]) == 1
+ assert result["user_stories"][0]["id"] == "org/repo/US-1"
+ assert result["functionalities"] == []
+ assert result["features"] == []
+
+
+def test_load_doc_input_doc_source_envelope(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ _write(
+ doc_file,
+ {
+ "user_stories": [{"id": "org/repo/US-1", "acceptance_criteria": []}],
+ "functionalities": [{"id": "org/repo/FUNC-001", "acceptance_criteria": []}],
+ "features": [{"id": "org/repo/FEAT-001"}],
+ "metadata": {},
+ },
+ )
+
+ result = load_doc_input(str(doc_file))
+
+ assert result["user_stories"][0]["id"] == "org/repo/US-1"
+ assert result["functionalities"][0]["id"] == "org/repo/FUNC-001"
+ assert result["features"][0]["id"] == "org/repo/FEAT-001"
+
+
+def test_load_doc_input_invalid_shape(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ _write(doc_file, {"no_items": True})
+
+ with pytest.raises(InvalidInputError):
+ load_doc_input(str(doc_file))
+
+
+def _valid_metadata():
+ return {
+ "producer": {"name": "collector-gh", "version": "1.0.0", "build": None},
+ "run": {
+ "run_id": None,
+ "run_attempt": None,
+ "actor": None,
+ "workflow": None,
+ "ref": None,
+ "sha": None,
+ },
+ "source": {"systems": [], "repositories": [], "organization": None, "enterprise": None},
+ "original_metadata": {},
+ }
+
+
+def _valid_user_story():
+ return {
+ "id": "org/repo/US-1",
+ "repository_name": "org/repo",
+ "title": "A story",
+ "state": "open",
+ "tags": [],
+ "url": None,
+ "timestamps": None,
+ "acceptance_criteria": [],
+ }
+
+
+def test_load_doc_input_doc_source_envelope_schema_valid(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ _write(
+ doc_file,
+ {
+ "user_stories": [_valid_user_story()],
+ "functionalities": [{"id": "org/repo/FUNC-001", "repository_name": "org/repo", "title": "F"}],
+ "features": [{"id": "org/repo/FEAT-001", "repository_name": "org/repo", "title": "Feat"}],
+ "metadata": _valid_metadata(),
+ "warnings": [],
+ },
+ )
+
+ result = load_doc_input(str(doc_file))
+
+ assert result["user_stories"][0]["id"] == "org/repo/US-1"
+ assert result["functionalities"][0]["id"] == "org/repo/FUNC-001"
+ assert result["features"][0]["id"] == "org/repo/FEAT-001"
+
+
+def test_load_doc_input_doc_source_envelope_schema_invalid(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ bad_story = {"id": "org/repo/US-1"} # missing required fields
+ _write(
+ doc_file,
+ {
+ "user_stories": [bad_story],
+ "functionalities": [],
+ "features": [],
+ "metadata": _valid_metadata(),
+ "warnings": [],
+ },
+ )
+
+ with pytest.raises(InvalidInputError, match="schema validation"):
+ load_doc_input(str(doc_file))
+
+
+def test_load_doc_input_missing_file(tmp_path):
+ with pytest.raises(FileIOError):
+ load_doc_input(str(tmp_path / "missing.json"))
+
+
+def test_load_tests_input_envelope(tmp_path):
+ tests_file = tmp_path / "tests.json"
+ _write(tests_file, {"items": [{"id": "s1", "us_id": "US-1"}]})
+
+ items = load_tests_input(str(tests_file))
+
+ assert len(items) == 1
+ assert items[0]["us_id"] == "US-1"
+
+
+def test_load_tests_input_missing_items(tmp_path):
+ tests_file = tmp_path / "tests.json"
+ _write(tests_file, [{"id": "s1"}])
+
+ with pytest.raises(InvalidInputError):
+ load_tests_input(str(tests_file))
+
+
+def test_load_tests_input_malformed_json(tmp_path):
+ tests_file = tmp_path / "tests.json"
+ with open(tests_file, "w", encoding="utf-8") as f:
+ f.write("{ not valid json ")
+
+ with pytest.raises(InvalidInputError):
+ load_tests_input(str(tests_file))
diff --git a/packages/services/coverage_matrix/tests/test_matcher.py b/packages/services/coverage_matrix/tests/test_matcher.py
new file mode 100644
index 0000000..0d9fafa
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/test_matcher.py
@@ -0,0 +1,268 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Unit tests for the matcher module."""
+
+from living_doc_service_coverage_matrix.matcher import build_coverage_matrix
+
+GENERATED_AT = "2026-06-30T00:00:00+00:00"
+
+
+def _us(num, acs, state="active", title="Title"):
+ return {
+ "id": f"org/repo/{num}",
+ "title": title,
+ "state": state,
+ "acceptance_criteria": acs,
+ }
+
+
+def _func(num, acs, parent="org/repo/US-1", func_type="unit", state="active", title="Func"):
+ return {
+ "id": f"org/repo/{num}",
+ "title": title,
+ "state": state,
+ "parent": parent,
+ "func_type": func_type,
+ "acceptance_criteria": acs,
+ }
+
+
+def _feature(num, title="Feature", surface_type="ui", route="/x"):
+ return {
+ "id": f"org/repo/{num}",
+ "title": title,
+ "state": "active",
+ "surface_type": surface_type,
+ "route": route,
+ "owners": "team-a",
+ "purpose": "purpose",
+ "user_stories": ["org/repo/US-1"],
+ "functionalities": ["org/repo/FUNC-001"],
+ "external_dependencies": None,
+ "page_object": "LoginPage",
+ }
+
+
+def _ac(ac_id, state="Active", version="v1.0.0", description="desc"):
+ return {"id": ac_id, "state": state, "version": version, "description": description}
+
+
+def _scenario(scenario_id, us_id, ac_ids, name="scenario", tags=None, source=None, func_id=None):
+ return {
+ "id": scenario_id,
+ "us_id": us_id,
+ "func_id": func_id,
+ "ac_ids": ac_ids,
+ "scenario_name": name,
+ "tags": tags if tags is not None else ["Regression"],
+ "source": source if source is not None else {"org": "org", "repo": "repo", "file": "f.feature"},
+ }
+
+
+def _doc(user_stories=None, functionalities=None, features=None):
+ return {
+ "user_stories": user_stories or [],
+ "functionalities": functionalities or [],
+ "features": features or [],
+ }
+
+
+def test_covered_and_not_covered():
+ doc = _doc([_us("US-1", [_ac("US-1-01"), _ac("US-1-02")])])
+ tests = [_scenario("s1", "US-1", ["US-1-01"])]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ us = matrix.user_stories[0]
+ assert us.id == "US-1"
+ assert us.full_id == "org/repo/US-1"
+ ac1, ac2 = us.acceptance_criteria
+ assert ac1.coverage.status == "covered"
+ assert ac1.coverage.test_count == 1
+ assert ac1.coverage.tests[0].id == "s1"
+ assert ac2.coverage.status == "not_covered"
+ assert ac2.coverage.test_count == 0
+ assert ac2.coverage.tests == []
+
+
+def test_us_summary_counts():
+ doc = _doc([_us("US-1", [_ac("US-1-01"), _ac("US-1-02")])])
+ tests = [_scenario("s1", "US-1", ["US-1-01"])]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ summary = matrix.user_stories[0].summary
+ assert summary.total_acs == 2
+ assert summary.active_acs == 2
+ assert summary.covered_acs == 1
+ assert summary.coverage_pct == 50.0
+
+
+def test_deprecated_ac_excluded_from_pct():
+ doc = _doc([_us("US-1", [_ac("US-1-01"), _ac("US-1-02", state="Deprecated")])])
+ tests = [_scenario("s1", "US-1", ["US-1-01", "US-1-02"])]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ summary = matrix.user_stories[0].summary
+ assert summary.total_acs == 2
+ assert summary.active_acs == 1
+ assert summary.covered_acs == 1
+ assert summary.coverage_pct == 100.0
+
+
+def test_unlinked_when_us_id_null():
+ doc = _doc([_us("US-1", [_ac("US-1-01")])])
+ tests = [_scenario("s1", None, ["US-1-01"])]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ assert matrix.user_stories[0].acceptance_criteria[0].coverage.status == "not_covered"
+ assert len(matrix.unlinked_tests) == 1
+ assert matrix.unlinked_tests[0].id == "s1"
+ assert matrix.unlinked_tests[0].us_id is None
+
+
+def test_unlinked_when_us_id_unresolved():
+ doc = _doc([_us("US-1", [_ac("US-1-01")])])
+ tests = [_scenario("s1", "US-99", ["US-99-01"])]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ assert len(matrix.unlinked_tests) == 1
+ assert matrix.unlinked_tests[0].us_id == "US-99"
+ assert matrix.stale_ac_refs == []
+
+
+def test_stale_ac_ref_recorded():
+ doc = _doc([_us("US-1", [_ac("US-1-01")])])
+ tests = [_scenario("s1", "US-1", ["US-1-01", "US-1-99"])]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ assert matrix.user_stories[0].acceptance_criteria[0].coverage.status == "covered"
+ assert len(matrix.stale_ac_refs) == 1
+ stale = matrix.stale_ac_refs[0]
+ assert stale.scenario_id == "s1"
+ assert stale.us_id == "US-1"
+ assert stale.stale_ac_id == "US-1-99"
+
+
+def test_multiple_tests_for_one_ac():
+ doc = _doc([_us("US-1", [_ac("US-1-01")])])
+ tests = [
+ _scenario("s1", "US-1", ["US-1-01"]),
+ _scenario("s2", "US-1", ["US-1-01"]),
+ ]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ coverage = matrix.user_stories[0].acceptance_criteria[0].coverage
+ assert coverage.test_count == 2
+ assert {t.id for t in coverage.tests} == {"s1", "s2"}
+
+
+def test_functionality_coverage():
+ doc = _doc(
+ functionalities=[_func("FUNC-001", [_ac("FUNC-001-01"), _ac("FUNC-001-02")])],
+ )
+ tests = [_scenario("s1", None, ["FUNC-001-01"], func_id="FUNC-001")]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ func = matrix.functionalities[0]
+ assert func.id == "FUNC-001"
+ assert func.full_id == "org/repo/FUNC-001"
+ assert func.parent == "org/repo/US-1"
+ assert func.func_type == "unit"
+ ac1, ac2 = func.acceptance_criteria
+ assert ac1.coverage.status == "covered"
+ assert ac1.coverage.tests[0].id == "s1"
+ assert ac2.coverage.status == "not_covered"
+ assert func.summary.total_acs == 2
+ assert func.summary.covered_acs == 1
+ assert func.summary.coverage_pct == 50.0
+
+
+def test_functionality_stale_ac_ref():
+ doc = _doc(functionalities=[_func("FUNC-001", [_ac("FUNC-001-01")])])
+ tests = [_scenario("s1", None, ["FUNC-001-99"], func_id="FUNC-001")]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ assert len(matrix.stale_ac_refs) == 1
+ stale = matrix.stale_ac_refs[0]
+ assert stale.func_id == "FUNC-001"
+ assert stale.stale_ac_id == "FUNC-001-99"
+
+
+def test_scenario_links_us_and_func():
+ doc = _doc(
+ user_stories=[_us("US-1", [_ac("US-1-01")])],
+ functionalities=[_func("FUNC-001", [_ac("FUNC-001-01")])],
+ )
+ tests = [_scenario("s1", "US-1", ["US-1-01", "FUNC-001-01"], func_id="FUNC-001")]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ assert matrix.user_stories[0].acceptance_criteria[0].coverage.status == "covered"
+ assert matrix.functionalities[0].acceptance_criteria[0].coverage.status == "covered"
+ assert matrix.stale_ac_refs == []
+
+
+def test_feature_registry_entry():
+ doc = _doc(features=[_feature("FEAT-001")])
+
+ matrix = build_coverage_matrix(doc, [], GENERATED_AT)
+
+ feature = matrix.features[0]
+ assert feature.id == "FEAT-001"
+ assert feature.full_id == "org/repo/FEAT-001"
+ assert feature.surface_type == "ui"
+ assert feature.route == "/x"
+ assert feature.owners == "team-a"
+ assert feature.page_object == "LoginPage"
+ assert feature.user_stories == ["org/repo/US-1"]
+ assert feature.functionalities == ["org/repo/FUNC-001"]
+
+
+def test_top_summary_aggregation():
+ doc = _doc(
+ user_stories=[
+ _us("US-1", [_ac("US-1-01"), _ac("US-1-02")]),
+ _us("US-2", [_ac("US-2-01")]),
+ ],
+ functionalities=[_func("FUNC-001", [_ac("FUNC-001-01")])],
+ features=[_feature("FEAT-001")],
+ )
+ tests = [_scenario("s1", "US-1", ["US-1-01"])]
+
+ matrix = build_coverage_matrix(doc, tests, GENERATED_AT)
+
+ summary = matrix.summary
+ assert summary.total_user_stories == 2
+ assert summary.total_functionalities == 1
+ assert summary.total_features == 1
+ assert summary.total_acs == 4
+ assert summary.active_acs == 4
+ assert summary.covered_acs == 1
+ assert summary.coverage_pct == 25.0
+
+
+def test_coverage_pct_null_when_no_active_acs():
+ doc = _doc([_us("US-1", [_ac("US-1-01", state="Deprecated")])])
+
+ matrix = build_coverage_matrix(doc, [], GENERATED_AT)
+
+ assert matrix.summary.coverage_pct is None
+ assert matrix.user_stories[0].summary.coverage_pct is None
+
+
+def test_schema_version_and_timestamp():
+ matrix = build_coverage_matrix(_doc(), [], GENERATED_AT)
+
+ assert matrix.schema_version == "coverage-matrix-v1.0.0"
+ assert matrix.generated_at == GENERATED_AT
+ assert matrix.summary.total_user_stories == 0
+ assert matrix.summary.total_functionalities == 0
+ assert matrix.summary.total_features == 0
diff --git a/packages/services/coverage_matrix/tests/test_service.py b/packages/services/coverage_matrix/tests/test_service.py
new file mode 100644
index 0000000..39cc862
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/test_service.py
@@ -0,0 +1,106 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Unit tests for the service orchestration module."""
+
+import json
+
+import pytest
+
+from living_doc_service_coverage_matrix.service import CoverageThresholdError, run_service
+
+
+def _write(path, payload):
+ with open(path, "w", encoding="utf-8") as f:
+ json.dump(payload, f)
+
+
+def _doc_payload():
+ return {
+ "items": [
+ {
+ "id": "org/repo/US-1",
+ "title": "User Login",
+ "state": "active",
+ "acceptance_criteria": [
+ {"id": "US-1-01", "state": "Active", "version": "v1.0.0", "description": "a"},
+ {"id": "US-1-02", "state": "Active", "version": "v1.0.0", "description": "b"},
+ ],
+ }
+ ]
+ }
+
+
+def _tests_payload():
+ return {
+ "items": [
+ {
+ "id": "s1",
+ "us_id": "US-1",
+ "ac_ids": ["US-1-01"],
+ "scenario_name": "scenario one",
+ "tags": ["Regression"],
+ "source": {"org": "org", "repo": "repo", "file": "f.feature"},
+ }
+ ]
+ }
+
+
+def test_run_service_writes_output(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ tests_file = tmp_path / "tests.json"
+ out_file = tmp_path / "coverage-matrix.json"
+ _write(doc_file, _doc_payload())
+ _write(tests_file, _tests_payload())
+
+ run_service(str(doc_file), str(tests_file), str(out_file), {})
+
+ assert out_file.exists()
+ with open(out_file, "r", encoding="utf-8") as f:
+ data = json.load(f)
+
+ assert data["schema_version"] == "coverage-matrix-v1.0.0"
+ assert data["summary"]["total_user_stories"] == 1
+ assert data["summary"]["covered_acs"] == 1
+ assert data["summary"]["coverage_pct"] == 50.0
+ assert data["user_stories"][0]["id"] == "US-1"
+
+
+def test_run_service_skips_invalid_user_story(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ tests_file = tmp_path / "tests.json"
+ out_file = tmp_path / "out.json"
+ payload = _doc_payload()
+ payload["items"].append({"title": "no id"})
+ payload["items"].append({"id": "org/repo/US-2"}) # missing acceptance_criteria
+ _write(doc_file, payload)
+ _write(tests_file, _tests_payload())
+
+ matrix = run_service(str(doc_file), str(tests_file), str(out_file), {})
+
+ assert matrix.summary.total_user_stories == 1
+
+
+def test_run_service_fail_under_raises(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ tests_file = tmp_path / "tests.json"
+ out_file = tmp_path / "out.json"
+ _write(doc_file, _doc_payload())
+ _write(tests_file, _tests_payload())
+
+ with pytest.raises(CoverageThresholdError):
+ run_service(str(doc_file), str(tests_file), str(out_file), {"fail_under": 80.0})
+
+ # Output is still written before the threshold check.
+ assert out_file.exists()
+
+
+def test_run_service_fail_under_passes(tmp_path):
+ doc_file = tmp_path / "doc.json"
+ tests_file = tmp_path / "tests.json"
+ out_file = tmp_path / "out.json"
+ _write(doc_file, _doc_payload())
+ _write(tests_file, _tests_payload())
+
+ matrix = run_service(str(doc_file), str(tests_file), str(out_file), {"fail_under": 50.0})
+
+ assert matrix.summary.coverage_pct == 50.0
diff --git a/packages/services/coverage_matrix/tests/test_summary.py b/packages/services/coverage_matrix/tests/test_summary.py
new file mode 100644
index 0000000..6a6f686
--- /dev/null
+++ b/packages/services/coverage_matrix/tests/test_summary.py
@@ -0,0 +1,105 @@
+# Copyright 2026 ABSA Group Limited. Apache License, Version 2.0.
+
+"""Unit tests for the summary module."""
+
+from living_doc_service_coverage_matrix.model.coverage_item import (
+ AcCoverage,
+ Coverage,
+ FunctionalityCoverage,
+ UserStoryCoverage,
+)
+from living_doc_service_coverage_matrix.summary import compute_summary, compute_us_summary
+
+
+def _ac(state, status):
+ return AcCoverage(
+ id="US-1-01",
+ state=state,
+ version="v1.0.0",
+ description="desc",
+ coverage=Coverage(status=status, test_count=1 if status == "covered" else 0, tests=[]),
+ )
+
+
+def test_compute_us_summary_mixed():
+ acs = [
+ _ac("Active", "covered"),
+ _ac("Active", "not_covered"),
+ _ac("Deprecated", "covered"),
+ ]
+
+ summary = compute_us_summary(acs)
+
+ assert summary.total_acs == 3
+ assert summary.active_acs == 2
+ assert summary.covered_acs == 1
+ assert summary.coverage_pct == 50.0
+
+
+def test_compute_us_summary_empty():
+ summary = compute_us_summary([])
+
+ assert summary.total_acs == 0
+ assert summary.active_acs == 0
+ assert summary.covered_acs == 0
+ assert summary.coverage_pct is None
+
+
+def test_compute_summary_aggregates_user_stories():
+ us1 = UserStoryCoverage(
+ id="US-1",
+ full_id="org/repo/US-1",
+ title="t",
+ state="active",
+ summary=compute_us_summary([_ac("Active", "covered")]),
+ acceptance_criteria=[_ac("Active", "covered")],
+ )
+ us2 = UserStoryCoverage(
+ id="US-2",
+ full_id="org/repo/US-2",
+ title="t",
+ state="active",
+ summary=compute_us_summary([_ac("Active", "not_covered")]),
+ acceptance_criteria=[_ac("Active", "not_covered")],
+ )
+
+ summary = compute_summary([us1, us2], [], 0)
+
+ assert summary.total_user_stories == 2
+ assert summary.total_functionalities == 0
+ assert summary.total_features == 0
+ assert summary.total_acs == 2
+ assert summary.active_acs == 2
+ assert summary.covered_acs == 1
+ assert summary.coverage_pct == 50.0
+
+
+def test_compute_summary_aggregates_functionalities():
+ us = UserStoryCoverage(
+ id="US-1",
+ full_id="org/repo/US-1",
+ title="t",
+ state="active",
+ summary=compute_us_summary([_ac("Active", "covered")]),
+ acceptance_criteria=[_ac("Active", "covered")],
+ )
+ func = FunctionalityCoverage(
+ id="FUNC-001",
+ full_id="org/repo/FUNC-001",
+ title="t",
+ state="active",
+ parent="org/repo/US-1",
+ func_type="unit",
+ summary=compute_us_summary([_ac("Active", "not_covered")]),
+ acceptance_criteria=[_ac("Active", "not_covered")],
+ )
+
+ summary = compute_summary([us], [func], 3)
+
+ assert summary.total_user_stories == 1
+ assert summary.total_functionalities == 1
+ assert summary.total_features == 3
+ assert summary.total_acs == 2
+ assert summary.active_acs == 2
+ assert summary.covered_acs == 1
+ assert summary.coverage_pct == 50.0
diff --git a/packages/services/normalize_issues/src/living_doc_service_normalize_issues/builder.py b/packages/services/normalize_issues/src/living_doc_service_normalize_issues/builder.py
index c0b13be..b5d9d81 100644
--- a/packages/services/normalize_issues/src/living_doc_service_normalize_issues/builder.py
+++ b/packages/services/normalize_issues/src/living_doc_service_normalize_issues/builder.py
@@ -29,82 +29,26 @@
UserStory,
)
-from living_doc_service_normalize_issues.normalizer import normalize_sections
-
-
-def _parse_md_to_list(text: str | None) -> list[str] | None:
- """Convert a markdown bullet list to a plain list of strings.
-
- If the text contains no bullet lines, wraps the whole text as a single-item list.
- Returns None when text is absent or empty.
- """
- if not text or not text.strip():
- return None
-
- result: list[str] = []
- for line in text.splitlines():
- stripped = line.strip()
- if not stripped:
- continue
- if stripped.startswith(("- [ ] ", "- [x] ", "- [X] ")):
- result.append(stripped[6:].strip())
- elif stripped.startswith(("- ", "* ")):
- result.append(stripped[2:].strip())
- else:
- # Non-bullet content — return whole text as one item
- return [text.strip()]
-
- return result if result else [text.strip()]
-
-
-def _parse_md_to_ac_list(text: str | None) -> list[AcceptanceCriterion] | None:
- """Convert a markdown bullet list to a list of AcceptanceCriterion objects.
-
- Each bullet item becomes a criterion with only the description populated.
- Returns None when text is absent or empty.
- """
- items = _parse_md_to_list(text)
- if items is None:
- return None
- return [AcceptanceCriterion(id=None, state=None, version=None, description=item) for item in items]
-
def build_pdf_ready(adapter_result: AdapterResult, options: dict) -> PdfReadyV1: # pylint: disable=too-many-locals
"""
Build PDF-ready JSON from adapter result.
- This function transforms AdapterResult into PdfReadyV1 format, normalizing markdown
- sections, populating metadata, and building the audit trail.
+ This function transforms AdapterResult into PdfReadyV1 format from the structured
+ User Story fields, populating metadata, and building the audit trail.
Args:
- adapter_result: Parsed adapter result with items and metadata
+ adapter_result: Parsed adapter result with user stories and metadata
options: Configuration options (document_title, document_version, etc.)
Returns:
PdfReadyV1 object ready for serialization
"""
- # Build user stories from adapter items
+ # Build user stories from adapter user stories
user_stories = []
- for item in adapter_result.items:
- # Normalize markdown sections (description, user_guide, connections, last_edited)
- normalized = normalize_sections(item.body or "")
-
- # Structured fields: use explicit values when available, fall back to markdown parsing
- business_value: list[str] | None
- preconditions: list[str] | None
- acceptance_criteria: list[AcceptanceCriterion] | None
-
- if item.structured_business_value is not None:
- business_value = item.structured_business_value
- else:
- business_value = _parse_md_to_list(normalized.get("business_value"))
-
- if item.structured_preconditions is not None:
- preconditions = item.structured_preconditions
- else:
- preconditions = _parse_md_to_list(normalized.get("preconditions"))
-
- if item.structured_acceptance_criteria is not None:
+ for item in adapter_result.user_stories:
+ acceptance_criteria: list[AcceptanceCriterion] | None = None
+ if item.acceptance_criteria is not None:
acceptance_criteria = [
AcceptanceCriterion(
id=ac.id,
@@ -112,20 +56,18 @@ def build_pdf_ready(adapter_result: AdapterResult, options: dict) -> PdfReadyV1:
version=ac.version,
description=ac.description,
)
- for ac in item.structured_acceptance_criteria
+ for ac in item.acceptance_criteria
]
- else:
- acceptance_criteria = _parse_md_to_ac_list(normalized.get("acceptance_criteria"))
- # Build Sections object
+ # Build Sections object from structured fields
sections = Sections(
- description=normalized.get("description"),
- business_value=business_value,
- preconditions=preconditions,
+ description=item.description,
+ business_value=item.business_value,
+ preconditions=item.preconditions,
acceptance_criteria=acceptance_criteria,
- user_guide=normalized.get("user_guide"),
- connections=normalized.get("connections"),
- last_edited=normalized.get("last_edited"),
+ user_guide=None,
+ connections=None,
+ last_edited=None,
)
# Build UserStory object
@@ -147,7 +89,7 @@ def build_pdf_ready(adapter_result: AdapterResult, options: dict) -> PdfReadyV1:
content = Content(user_stories=user_stories)
# Build SelectionSummary
- total_items = len(adapter_result.items)
+ total_items = len(adapter_result.user_stories)
selection_summary = SelectionSummary(
total_items=total_items,
included_items=total_items,
diff --git a/packages/services/normalize_issues/src/living_doc_service_normalize_issues/service.py b/packages/services/normalize_issues/src/living_doc_service_normalize_issues/service.py
index d073db4..6e316cf 100644
--- a/packages/services/normalize_issues/src/living_doc_service_normalize_issues/service.py
+++ b/packages/services/normalize_issues/src/living_doc_service_normalize_issues/service.py
@@ -76,7 +76,7 @@ def run_service(input_path: str, output_path: str, options: dict) -> None:
except Exception as e:
raise NormalizationError(f"Failed to parse input: {e}") from e
- logger.info("Parsed %d items", len(adapter_result.items))
+ logger.info("Parsed %d user stories", len(adapter_result.user_stories))
if adapter_result.warnings:
logger.warning("Adapter reported %d warnings", len(adapter_result.warnings))
for warning in adapter_result.warnings:
diff --git a/packages/services/normalize_issues/tests/fixtures/golden/expected_output.json b/packages/services/normalize_issues/tests/fixtures/golden/expected_output.json
index 1f33614..b1f8d02 100644
--- a/packages/services/normalize_issues/tests/fixtures/golden/expected_output.json
+++ b/packages/services/normalize_issues/tests/fixtures/golden/expected_output.json
@@ -7,27 +7,15 @@
"acceptance_criteria": [
{
"description": "User can log in with email and password",
- "id": null,
- "state": null,
- "version": null
+ "id": "GH-1-01",
+ "state": "Active",
+ "version": "v1.0.0"
},
{
"description": "User can log in with OAuth (Google, GitHub)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Session management is secure",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Password reset functionality works",
- "id": null,
- "state": null,
- "version": null
+ "id": "GH-1-02",
+ "state": "Active",
+ "version": "v1.0.0"
}
],
"business_value": [
@@ -60,21 +48,9 @@
"acceptance_criteria": [
{
"description": "Theme toggle button in settings",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "All pages support dark mode",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "User preference is saved",
- "id": null,
- "state": null,
- "version": null
+ "id": "GH-2-01",
+ "state": "Active",
+ "version": "v1.0.0"
}
],
"business_value": [
@@ -123,13 +99,22 @@
{
"id": "github:AbsaOSS/living-doc-toolkit#4",
"sections": {
- "acceptance_criteria": null,
- "business_value": null,
- "connections": "- #1 (authentication)\n- API specification document",
- "description": "Create comprehensive API documentation.\n\n### Custom Section\nThis is a custom section that should be appended to description.",
+ "acceptance_criteria": [
+ {
+ "description": "All endpoints listed with examples",
+ "id": "GH-4-01",
+ "state": "Active",
+ "version": "v1.0.0"
+ }
+ ],
+ "business_value": [
+ "Improves developer onboarding."
+ ],
+ "connections": null,
+ "description": "Create comprehensive API documentation.",
"last_edited": null,
"preconditions": null,
- "user_guide": "1. List all endpoints with examples\n2. Document request/response formats\n3. Add authentication requirements"
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -248,4 +233,4 @@
]
},
"schema_version": "1.0"
-}
\ No newline at end of file
+}
diff --git a/packages/services/normalize_issues/tests/fixtures/golden/input.json b/packages/services/normalize_issues/tests/fixtures/golden/input.json
index bbf30ca..5417bb2 100644
--- a/packages/services/normalize_issues/tests/fixtures/golden/input.json
+++ b/packages/services/normalize_issues/tests/fixtures/golden/input.json
@@ -23,7 +23,7 @@
"enterprise": null
}
},
- "items": [
+ "user_stories": [
{
"id": "github:AbsaOSS/living-doc-toolkit#1",
"title": "Implement user authentication",
@@ -37,7 +37,28 @@
"created": "2026-01-15T10:00:00Z",
"updated": "2026-01-20T15:30:00Z"
},
- "body": "## Overview\nImplement secure user authentication for the application.\n\n## Business Value\nProvides secure access control and user management capabilities.\n\n## Prerequisites\n- Database schema updated\n- OAuth provider configured\n\n## Acceptance Criteria\n- User can log in with email and password\n- User can log in with OAuth (Google, GitHub)\n- Session management is secure\n- Password reset functionality works"
+ "description": "Implement secure user authentication for the application.",
+ "business_value": [
+ "Provides secure access control and user management capabilities."
+ ],
+ "preconditions": [
+ "Database schema updated",
+ "OAuth provider configured"
+ ],
+ "acceptance_criteria": [
+ {
+ "id": "GH-1-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "User can log in with email and password"
+ },
+ {
+ "id": "GH-1-02",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "User can log in with OAuth (Google, GitHub)"
+ }
+ ]
},
{
"id": "github:AbsaOSS/living-doc-toolkit#2",
@@ -52,7 +73,19 @@
"created": "2026-01-10T09:00:00Z",
"updated": "2026-01-25T14:00:00Z"
},
- "body": "## Summary\nAdd dark mode theme support to improve user experience.\n\n## Why\nMany users prefer dark mode for reduced eye strain.\n\n## Done Criteria\n- Theme toggle button in settings\n- All pages support dark mode\n- User preference is saved"
+ "description": "Add dark mode theme support to improve user experience.",
+ "business_value": [
+ "Many users prefer dark mode for reduced eye strain."
+ ],
+ "preconditions": null,
+ "acceptance_criteria": [
+ {
+ "id": "GH-2-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "Theme toggle button in settings"
+ }
+ ]
},
{
"id": "github:AbsaOSS/living-doc-toolkit#3",
@@ -66,7 +99,10 @@
"created": "2026-01-18T11:30:00Z",
"updated": "2026-01-18T11:30:00Z"
},
- "body": "Pagination is not working correctly on the users list page. When clicking page 2, it shows page 1 content."
+ "description": "Pagination is not working correctly on the users list page. When clicking page 2, it shows page 1 content.",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-toolkit#4",
@@ -80,7 +116,19 @@
"created": "2026-01-22T08:00:00Z",
"updated": "2026-01-23T16:45:00Z"
},
- "body": "## Description\nCreate comprehensive API documentation.\n\n## How To\n1. List all endpoints with examples\n2. Document request/response formats\n3. Add authentication requirements\n\n## Related\n- #1 (authentication)\n- API specification document\n\n## Custom Section\nThis is a custom section that should be appended to description."
+ "description": "Create comprehensive API documentation.",
+ "business_value": [
+ "Improves developer onboarding."
+ ],
+ "preconditions": null,
+ "acceptance_criteria": [
+ {
+ "id": "GH-4-01",
+ "state": "Active",
+ "version": "v1.0.0",
+ "description": "All endpoints listed with examples"
+ }
+ ]
},
{
"id": "github:AbsaOSS/living-doc-toolkit#5",
@@ -91,7 +139,11 @@
"timestamps": {
"created": "2026-01-24T12:00:00Z",
"updated": "2026-01-24T12:00:00Z"
- }
+ },
+ "description": null,
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
}
]
-}
\ No newline at end of file
+}
diff --git a/packages/services/normalize_issues/tests/integration/test_golden_files.py b/packages/services/normalize_issues/tests/integration/test_golden_files.py
index 56e13b1..1dab6e4 100644
--- a/packages/services/normalize_issues/tests/integration/test_golden_files.py
+++ b/packages/services/normalize_issues/tests/integration/test_golden_files.py
@@ -75,13 +75,12 @@ def test_golden_files(tmp_path):
== "Pagination is not working correctly on the users list page. When clicking page 2, it shows page 1 content."
)
- # Verify fourth user story has custom section appended to description
+ # Verify fourth user story maps structured fields
story4 = actual["content"]["user_stories"][3]
- assert "Create comprehensive API documentation." in story4["sections"]["description"]
- assert "### Custom Section" in story4["sections"]["description"]
- assert "This is a custom section that should be appended to description." in story4["sections"]["description"]
- assert story4["sections"]["user_guide"] is not None
- assert story4["sections"]["connections"] is not None
+ assert story4["sections"]["description"] == "Create comprehensive API documentation."
+ assert story4["sections"]["business_value"] == ["Improves developer onboarding."]
+ assert story4["sections"]["user_guide"] is None
+ assert story4["sections"]["connections"] is None
# Verify fifth user story with null body
story5 = actual["content"]["user_stories"][4]
diff --git a/packages/services/normalize_issues/tests/test_builder.py b/packages/services/normalize_issues/tests/test_builder.py
index 84f8717..373e0f5 100644
--- a/packages/services/normalize_issues/tests/test_builder.py
+++ b/packages/services/normalize_issues/tests/test_builder.py
@@ -5,6 +5,7 @@
"""
from living_doc_adapter_collector_gh.models import (
+ AcceptanceCriterion,
AdapterItem,
AdapterItemTimestamps,
AdapterMetadata,
@@ -18,9 +19,31 @@
from living_doc_service_normalize_issues.builder import build_pdf_ready
+def _make_metadata(
+ *,
+ build=None,
+ run=None,
+ repositories=None,
+ organization=None,
+ enterprise=None,
+ original_metadata=None,
+):
+ """Build an AdapterMetadata with sensible defaults for tests."""
+ return AdapterMetadata(
+ producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=build),
+ run=run or AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
+ source=AdapterMetadataSource(
+ systems=["github"],
+ repositories=repositories or ["github:owner/repo"],
+ organization=organization,
+ enterprise=enterprise,
+ ),
+ original_metadata=original_metadata if original_metadata is not None else {},
+ )
+
+
def test_build_pdf_ready_basic():
"""Test basic PDF-ready building from adapter result."""
- # Create a simple adapter result
item = AdapterItem(
id="github:owner/repo#123",
title="Test Issue",
@@ -28,34 +51,27 @@ def test_build_pdf_ready_basic():
tags=["enhancement"],
url="https://github.com/owner/repo/issues/123",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-02T00:00:00Z"),
- body="## Description\nThis is a test issue.",
+ description="This is a test issue.",
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
+ metadata = _make_metadata(
run=AdapterMetadataRun(
run_id="123456", run_attempt="1", actor="testuser", workflow="test-workflow", ref="main", sha="abc123"
),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/repo"], organization="owner", enterprise=None
- ),
- original_metadata={},
+ organization="owner",
)
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[])
options = {"document_title": "Test Document", "document_version": "1.0.0"}
- # Build PDF-ready
pdf_ready = build_pdf_ready(adapter_result, options)
- # Verify structure
assert pdf_ready.schema_version == "1.0"
assert pdf_ready.meta.document_title == "Test Document"
assert pdf_ready.meta.document_version == "1.0.0"
assert len(pdf_ready.content.user_stories) == 1
- # Verify user story
story = pdf_ready.content.user_stories[0]
assert story.id == "github:owner/repo#123"
assert story.title == "Test Issue"
@@ -68,7 +84,7 @@ def test_build_pdf_ready_basic():
def test_build_pdf_ready_normalized_sections():
- """Test that sections are normalized correctly."""
+ """Test that structured sections are mapped correctly."""
item = AdapterItem(
id="github:owner/repo#456",
title="Test Issue with Sections",
@@ -76,33 +92,22 @@ def test_build_pdf_ready_normalized_sections():
tags=[],
url="https://github.com/owner/repo/issues/456",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body="""## Overview
-This is the overview.
-
-## Business Value
-High value feature.
-
-## AC
-- Criterion 1
-- Criterion 2""",
+ description="This is the overview.",
+ business_value=["High value feature."],
+ acceptance_criteria=[
+ AcceptanceCriterion(id="GH-456-01", state="Active", version="v1.0.0", description="Criterion 1"),
+ AcceptanceCriterion(id="GH-456-02", state="Active", version="v1.0.0", description="Criterion 2"),
+ ],
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
- run=AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/repo"], organization=None, enterprise=None
- ),
- original_metadata={},
- )
+ metadata = _make_metadata(organization=None)
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[])
options = {}
pdf_ready = build_pdf_ready(adapter_result, options)
story = pdf_ready.content.user_stories[0]
- # Verify normalized sections
assert story.sections.description == "This is the overview."
assert story.sections.business_value == ["High value feature."]
assert story.sections.acceptance_criteria is not None
@@ -119,34 +124,22 @@ def test_build_pdf_ready_meta_fields():
tags=[],
url="https://github.com/owner/repo/issues/1",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body=None,
+ description=None,
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
- run=AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
- source=AdapterMetadataSource(
- systems=["github"],
- repositories=["github:owner/repo1", "github:owner/repo2"],
- organization=None,
- enterprise=None,
- ),
- original_metadata={},
- )
+ metadata = _make_metadata(repositories=["github:owner/repo1", "github:owner/repo2"])
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[])
options = {"document_title": "My Doc", "document_version": "2.0.0"}
pdf_ready = build_pdf_ready(adapter_result, options)
- # Verify meta fields
assert pdf_ready.meta.document_title == "My Doc"
assert pdf_ready.meta.document_version == "2.0.0"
assert pdf_ready.meta.generated_at is not None
assert "T" in pdf_ready.meta.generated_at # ISO 8601 format
assert pdf_ready.meta.source_set == ["github:owner/repo1", "github:owner/repo2"]
- # Verify selection summary
assert pdf_ready.meta.selection_summary.total_items == 1
assert pdf_ready.meta.selection_summary.included_items == 1
assert pdf_ready.meta.selection_summary.excluded_items == 0
@@ -161,24 +154,16 @@ def test_build_pdf_ready_fallback_document_title():
tags=[],
url="https://github.com/owner/repo/issues/1",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body=None,
+ description=None,
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
- run=AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/myrepo"], organization=None, enterprise=None
- ),
- original_metadata={},
- )
+ metadata = _make_metadata(repositories=["github:owner/myrepo"])
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[])
options = {} # No document_title provided
pdf_ready = build_pdf_ready(adapter_result, options)
- # Should derive title from repository
assert "owner/myrepo" in pdf_ready.meta.document_title
@@ -191,36 +176,32 @@ def test_build_pdf_ready_audit_envelope():
tags=[],
url="https://github.com/owner/repo/issues/1",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body=None,
+ description=None,
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build="build123"),
+ metadata = _make_metadata(
+ build="build123",
run=AdapterMetadataRun(
run_id="123", run_attempt="1", actor="user1", workflow="workflow1", ref="main", sha="abc123"
),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/repo"], organization="myorg", enterprise="myenterprise"
- ),
+ organization="myorg",
+ enterprise="myenterprise",
original_metadata={"some": "data"},
)
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[])
options = {}
pdf_ready = build_pdf_ready(adapter_result, options)
audit = pdf_ready.meta.audit
- # Verify audit envelope structure
assert audit is not None
assert audit.schema_version == "1.0"
- # Verify producer mapping
assert audit.producer.name == "living-doc-collector-gh"
assert audit.producer.version == "1.0.0"
assert audit.producer.build == "build123"
- # Verify run mapping
assert audit.run.run_id == "123"
assert audit.run.run_attempt == "1"
assert audit.run.actor == "user1"
@@ -228,13 +209,11 @@ def test_build_pdf_ready_audit_envelope():
assert audit.run.ref == "main"
assert audit.run.sha == "abc123"
- # Verify source mapping
assert audit.source.systems == ["github"]
assert audit.source.repositories == ["github:owner/repo"]
assert audit.source.organization == "myorg"
assert audit.source.enterprise == "myenterprise"
- # Verify extensions
assert "collector-gh" in audit.extensions
assert audit.extensions["collector-gh"]["original_metadata"] == {"some": "data"}
@@ -248,25 +227,17 @@ def test_build_pdf_ready_audit_trace_normalization_step():
tags=[],
url="https://github.com/owner/repo/issues/1",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body=None,
+ description=None,
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
- run=AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/repo"], organization=None, enterprise=None
- ),
- original_metadata={},
- )
+ metadata = _make_metadata()
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[])
options = {}
pdf_ready = build_pdf_ready(adapter_result, options)
audit = pdf_ready.meta.audit
- # Verify trace step
assert len(audit.trace) == 1
trace_step = audit.trace[0]
assert trace_step.step == "normalization"
@@ -285,27 +256,19 @@ def test_build_pdf_ready_warnings_in_audit():
tags=[],
url="https://github.com/owner/repo/issues/1",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body=None,
+ description=None,
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
- run=AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/repo"], organization=None, enterprise=None
- ),
- original_metadata={},
- )
+ metadata = _make_metadata()
warning = CompatibilityWarning(code="VERSION_MISMATCH", message="Version mismatch detected", context="v0.9.0")
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[warning])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[warning])
options = {}
pdf_ready = build_pdf_ready(adapter_result, options)
audit = pdf_ready.meta.audit
- # Verify warning in trace
trace_step = audit.trace[0]
assert len(trace_step.warnings) == 1
assert trace_step.warnings[0].code == "VERSION_MISMATCH"
@@ -322,26 +285,20 @@ def test_build_pdf_ready_run_context():
tags=[],
url="https://github.com/owner/repo/issues/1",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body=None,
+ description=None,
)
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
+ metadata = _make_metadata(
run=AdapterMetadataRun(
run_id="789", run_attempt="2", actor="testuser", workflow="ci", ref="feature-branch", sha="def456"
),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/repo"], organization=None, enterprise=None
- ),
- original_metadata={},
)
- adapter_result = AdapterResult(items=[item], metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=[item], metadata=metadata, warnings=[])
options = {}
pdf_ready = build_pdf_ready(adapter_result, options)
- # Verify run context
assert pdf_ready.meta.run_context is not None
assert pdf_ready.meta.run_context.ci_run_id == "789"
assert pdf_ready.meta.run_context.triggered_by == "testuser"
@@ -359,31 +316,22 @@ def test_build_pdf_ready_multiple_items():
tags=["tag1"],
url=f"https://github.com/owner/repo/issues/{i}",
timestamps=AdapterItemTimestamps(created="2026-01-01T00:00:00Z", updated="2026-01-01T00:00:00Z"),
- body=f"## Description\nIssue {i} content.",
+ description=f"Issue {i} content.",
)
for i in range(1, 6)
]
- metadata = AdapterMetadata(
- producer=AdapterMetadataProducer(name="living-doc-collector-gh", version="1.0.0", build=None),
- run=AdapterMetadataRun(run_id=None, run_attempt=None, actor=None, workflow=None, ref=None, sha=None),
- source=AdapterMetadataSource(
- systems=["github"], repositories=["github:owner/repo"], organization=None, enterprise=None
- ),
- original_metadata={},
- )
+ metadata = _make_metadata()
- adapter_result = AdapterResult(items=items, metadata=metadata, warnings=[])
+ adapter_result = AdapterResult(user_stories=items, metadata=metadata, warnings=[])
options = {}
pdf_ready = build_pdf_ready(adapter_result, options)
- # Verify all items were processed
assert len(pdf_ready.content.user_stories) == 5
assert pdf_ready.meta.selection_summary.total_items == 5
assert pdf_ready.meta.selection_summary.included_items == 5
- # Verify each story
for i, story in enumerate(pdf_ready.content.user_stories, start=1):
assert story.id == f"github:owner/repo#{i}"
assert story.title == f"Issue {i}"
diff --git a/packages/services/normalize_issues/tests/test_service.py b/packages/services/normalize_issues/tests/test_service.py
index d143220..f1e99e2 100644
--- a/packages/services/normalize_issues/tests/test_service.py
+++ b/packages/services/normalize_issues/tests/test_service.py
@@ -35,7 +35,7 @@ def test_run_service_valid_input(tmp_path):
"enterprise": None,
},
},
- "items": [
+ "user_stories": [
{
"id": "github:owner/repo#123",
"title": "Test Issue",
@@ -43,7 +43,7 @@ def test_run_service_valid_input(tmp_path):
"tags": ["enhancement"],
"url": "https://github.com/owner/repo/issues/123",
"timestamps": {"created": "2026-01-01T00:00:00Z", "updated": "2026-01-02T00:00:00Z"},
- "body": "## Description\nThis is a test issue.",
+ "description": "This is a test issue.",
}
],
}
@@ -137,7 +137,7 @@ def test_run_service_explicit_adapter(tmp_path):
},
"source": {"systems": ["github"], "repositories": ["owner/repo"], "organization": None, "enterprise": None},
},
- "items": [
+ "user_stories": [
{
"id": "github:owner/repo#1",
"title": "Test",
@@ -194,7 +194,7 @@ def test_run_service_empty_items(tmp_path):
},
"source": {"systems": ["github"], "repositories": ["owner/repo"], "organization": None, "enterprise": None},
},
- "items": [],
+ "user_stories": [],
}
input_file = tmp_path / "input.json"
@@ -230,7 +230,7 @@ def test_run_service_multiple_items(tmp_path):
},
"source": {"systems": ["github"], "repositories": ["owner/repo"], "organization": None, "enterprise": None},
},
- "items": [
+ "user_stories": [
{
"id": f"github:owner/repo#{i}",
"title": f"Issue {i}",
@@ -238,7 +238,7 @@ def test_run_service_multiple_items(tmp_path):
"tags": [],
"url": f"https://github.com/owner/repo/issues/{i}",
"timestamps": {"created": "2026-01-01T00:00:00Z", "updated": "2026-01-01T00:00:00Z"},
- "body": f"## Description\nIssue {i} content.",
+ "description": f"Issue {i} content.",
}
for i in range(1, 4)
],
diff --git a/tests/fixtures/collector_gh/v0.9.0/input/doc-issues.json b/tests/fixtures/collector_gh/v0.9.0/input/doc-issues.json
index 77e4783..ab03ac4 100644
--- a/tests/fixtures/collector_gh/v0.9.0/input/doc-issues.json
+++ b/tests/fixtures/collector_gh/v0.9.0/input/doc-issues.json
@@ -24,7 +24,7 @@
"enterprise": null
}
},
- "items": [
+ "user_stories": [
{
"id": "github:AbsaOSS/test-project#1",
"title": "Implement basic feature",
@@ -37,7 +37,10 @@
"created": "2026-01-01T10:00:00Z",
"updated": "2026-01-02T12:00:00Z"
},
- "body": "## Description\nImplement a basic feature for testing."
+ "description": "## Description\nImplement a basic feature for testing.",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/test-project#2",
@@ -51,7 +54,10 @@
"created": "2026-01-03T14:00:00Z",
"updated": "2026-01-04T16:00:00Z"
},
- "body": "## Summary\nFix a simple bug in the system."
+ "description": "## Summary\nFix a simple bug in the system.",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
}
]
-}
\ No newline at end of file
+}
diff --git a/tests/fixtures/collector_gh/v1.0.0/input/doc-issues.json b/tests/fixtures/collector_gh/v1.0.0/input/doc-issues.json
index e2cea19..5893b9a 100644
--- a/tests/fixtures/collector_gh/v1.0.0/input/doc-issues.json
+++ b/tests/fixtures/collector_gh/v1.0.0/input/doc-issues.json
@@ -24,7 +24,7 @@
"enterprise": null
}
},
- "items": [
+ "user_stories": [
{
"id": "github:AbsaOSS/living-doc-project#101",
"title": "Implement user authentication with SSO",
@@ -39,7 +39,10 @@
"created": "2026-01-10T08:00:00Z",
"updated": "2026-01-20T14:30:00Z"
},
- "body": "## Description\n\nAs a user, I want to authenticate using Single Sign-On (SSO) so that I can access the application securely using my corporate credentials.\n\n## Business Value\n\n- Reduces password fatigue for users\n- Improves security through centralized authentication\n- Simplifies user onboarding and offboarding\n- Enables enterprise-grade access control\n\n## Preconditions\n\n- SSO provider (Azure AD, Okta, or similar) must be configured\n- OAuth 2.0 client credentials must be obtained\n- Callback URLs must be registered with the provider\n\n## Acceptance Criteria\n\n- [ ] User can click \"Sign in with SSO\" button on login page\n- [ ] User is redirected to SSO provider login page\n- [ ] After successful authentication, user is redirected back with valid session\n- [ ] User profile information is populated from SSO claims\n- [ ] Session expires after 8 hours of inactivity\n- [ ] Failed authentication shows appropriate error message\n\n## User Guide\n\n### For End Users\n\n1. Navigate to the login page\n2. Click the \"Sign in with SSO\" button\n3. Enter your corporate email on the SSO provider page\n4. Complete two-factor authentication if required\n5. You will be automatically redirected to the dashboard\n\n### For Administrators\n\nConfiguration steps:\n```yaml\nsso:\n provider: azure-ad\n client_id: ${SSO_CLIENT_ID}\n client_secret: ${SSO_CLIENT_SECRET}\n tenant_id: ${TENANT_ID}\n```\n\n## Connections\n\n- Related to #102 (Multi-factor authentication)\n- Depends on #103 (User profile management)\n- See also: [Security Architecture](https://docs.example.com/security)\n\n## Last Edited\n\nLast updated by alice@example.com on 2026-01-20"
+ "description": "## Description\n\nAs a user, I want to authenticate using Single Sign-On (SSO) so that I can access the application securely using my corporate credentials.\n\n## Business Value\n\n- Reduces password fatigue for users\n- Improves security through centralized authentication\n- Simplifies user onboarding and offboarding\n- Enables enterprise-grade access control\n\n## Preconditions\n\n- SSO provider (Azure AD, Okta, or similar) must be configured\n- OAuth 2.0 client credentials must be obtained\n- Callback URLs must be registered with the provider\n\n## Acceptance Criteria\n\n- [ ] User can click \"Sign in with SSO\" button on login page\n- [ ] User is redirected to SSO provider login page\n- [ ] After successful authentication, user is redirected back with valid session\n- [ ] User profile information is populated from SSO claims\n- [ ] Session expires after 8 hours of inactivity\n- [ ] Failed authentication shows appropriate error message\n\n## User Guide\n\n### For End Users\n\n1. Navigate to the login page\n2. Click the \"Sign in with SSO\" button\n3. Enter your corporate email on the SSO provider page\n4. Complete two-factor authentication if required\n5. You will be automatically redirected to the dashboard\n\n### For Administrators\n\nConfiguration steps:\n```yaml\nsso:\n provider: azure-ad\n client_id: ${SSO_CLIENT_ID}\n client_secret: ${SSO_CLIENT_SECRET}\n tenant_id: ${TENANT_ID}\n```\n\n## Connections\n\n- Related to #102 (Multi-factor authentication)\n- Depends on #103 (User profile management)\n- See also: [Security Architecture](https://docs.example.com/security)\n\n## Last Edited\n\nLast updated by alice@example.com on 2026-01-20",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#102",
@@ -55,7 +58,10 @@
"created": "2026-01-11T09:30:00Z",
"updated": "2026-01-21T10:15:00Z"
},
- "body": "## Overview\n\nEnhance security by implementing multi-factor authentication (MFA) for all user accounts.\n\n## Why\n\nMFA significantly reduces the risk of unauthorized access even if passwords are compromised. Industry best practice and compliance requirement for enterprise applications.\n\n## Setup\n\n| Component | Requirement | Status |\n|-----------|-------------|--------|\n| TOTP Library | pyotp >= 2.8.0 | ✅ Available |\n| QR Code Generator | qrcode >= 7.4.0 | ✅ Available |\n| SMS Gateway | Twilio API | ⚠️ Pending setup |\n\n## AC\n\n1. **TOTP Setup**\n - User can enable MFA from account settings\n - QR code is displayed for authenticator app setup\n - Backup codes are generated (10 codes)\n \n2. **Login Flow**\n - After password validation, MFA code prompt appears\n - User enters 6-digit code from authenticator app\n - Code must be valid within 30-second window\n - 3 failed attempts lock account for 15 minutes\n\n3. **Recovery Options**\n - User can use backup codes when authenticator unavailable\n - Admin can reset MFA for user accounts\n - SMS fallback option for verified phone numbers\n\n## Instructions\n\nEnabling MFA:\n1. Go to Profile → Security Settings\n2. Click \"Enable Two-Factor Authentication\"\n3. Scan QR code with Google Authenticator, Authy, or similar app\n4. Enter verification code to confirm\n5. Save backup codes in secure location\n\n## Related\n\n- #101 - SSO authentication (works together with MFA)\n- #104 - Session management\n- External: [OWASP MFA Guidelines](https://owasp.org/mfa)\n\n## Changes\n\nModified by bob@example.com on 2026-01-21 - Added SMS fallback requirement"
+ "description": "## Overview\n\nEnhance security by implementing multi-factor authentication (MFA) for all user accounts.\n\n## Why\n\nMFA significantly reduces the risk of unauthorized access even if passwords are compromised. Industry best practice and compliance requirement for enterprise applications.\n\n## Setup\n\n| Component | Requirement | Status |\n|-----------|-------------|--------|\n| TOTP Library | pyotp >= 2.8.0 | \u2705 Available |\n| QR Code Generator | qrcode >= 7.4.0 | \u2705 Available |\n| SMS Gateway | Twilio API | \u26a0\ufe0f Pending setup |\n\n## AC\n\n1. **TOTP Setup**\n - User can enable MFA from account settings\n - QR code is displayed for authenticator app setup\n - Backup codes are generated (10 codes)\n \n2. **Login Flow**\n - After password validation, MFA code prompt appears\n - User enters 6-digit code from authenticator app\n - Code must be valid within 30-second window\n - 3 failed attempts lock account for 15 minutes\n\n3. **Recovery Options**\n - User can use backup codes when authenticator unavailable\n - Admin can reset MFA for user accounts\n - SMS fallback option for verified phone numbers\n\n## Instructions\n\nEnabling MFA:\n1. Go to Profile \u2192 Security Settings\n2. Click \"Enable Two-Factor Authentication\"\n3. Scan QR code with Google Authenticator, Authy, or similar app\n4. Enter verification code to confirm\n5. Save backup codes in secure location\n\n## Related\n\n- #101 - SSO authentication (works together with MFA)\n- #104 - Session management\n- External: [OWASP MFA Guidelines](https://owasp.org/mfa)\n\n## Changes\n\nModified by bob@example.com on 2026-01-21 - Added SMS fallback requirement",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#103",
@@ -70,7 +76,10 @@
"created": "2026-01-05T14:00:00Z",
"updated": "2026-01-25T16:00:00Z"
},
- "body": "## Summary\n\nCreate a user profile management dashboard where users can view and edit their account information.\n\n## Value\n\nEmpowers users to manage their own information, reducing support burden and improving user satisfaction.\n\n## Prerequisites\n\n- User authentication must be implemented (#101)\n- Database schema for user profiles must be ready\n- Frontend framework (React) must be set up\n\n## Done Criteria\n\n- User can view current profile information (name, email, avatar)\n- User can edit profile fields with inline validation\n- Changes are saved immediately with visual feedback\n- Profile picture upload supports JPEG, PNG (max 5MB)\n- Audit log tracks all profile changes\n\n## How To\n\n**Accessing Your Profile:**\n- Click your avatar in top-right corner\n- Select \"Profile Settings\" from dropdown menu\n\n**Editing Information:**\n- Click the edit icon next to any field\n- Make your changes\n- Changes save automatically when you click outside the field\n\n**Profile Picture:**\n- Click \"Change Photo\" button\n- Select image file (JPEG or PNG, under 5MB)\n- Crop if desired, then confirm\n\n## Links\n\nRelated issues: #101, #105\nDesign mockups: [Figma Link](https://figma.com/file/xyz)"
+ "description": "## Summary\n\nCreate a user profile management dashboard where users can view and edit their account information.\n\n## Value\n\nEmpowers users to manage their own information, reducing support burden and improving user satisfaction.\n\n## Prerequisites\n\n- User authentication must be implemented (#101)\n- Database schema for user profiles must be ready\n- Frontend framework (React) must be set up\n\n## Done Criteria\n\n- User can view current profile information (name, email, avatar)\n- User can edit profile fields with inline validation\n- Changes are saved immediately with visual feedback\n- Profile picture upload supports JPEG, PNG (max 5MB)\n- Audit log tracks all profile changes\n\n## How To\n\n**Accessing Your Profile:**\n- Click your avatar in top-right corner\n- Select \"Profile Settings\" from dropdown menu\n\n**Editing Information:**\n- Click the edit icon next to any field\n- Make your changes\n- Changes save automatically when you click outside the field\n\n**Profile Picture:**\n- Click \"Change Photo\" button\n- Select image file (JPEG or PNG, under 5MB)\n- Crop if desired, then confirm\n\n## Links\n\nRelated issues: #101, #105\nDesign mockups: [Figma Link](https://figma.com/file/xyz)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#104",
@@ -85,7 +94,10 @@
"created": "2026-01-12T11:00:00Z",
"updated": "2026-01-22T13:45:00Z"
},
- "body": "## Description\n\nImplement secure session management with configurable timeout and automatic cleanup.\n\nKey requirements:\n- Session tokens stored securely (httpOnly, secure, sameSite cookies)\n- Configurable session timeout (default: 8 hours)\n- Automatic session extension on user activity\n- Session cleanup job runs every hour\n- Multiple concurrent sessions per user (max 5)\n\n## Business Value\n\nBalances security and user convenience. Automatic timeout prevents unauthorized access from unattended sessions while activity tracking minimizes disruption to active users.\n\n## Acceptance Criteria\n\n- [ ] Session tokens use UUID v4 format\n- [ ] Tokens are stored with expiration timestamp in Redis\n- [ ] Cookie flags: httpOnly=true, secure=true, sameSite=strict\n- [ ] Activity within 30 minutes extends session by configured timeout\n- [ ] Expired sessions return 401 Unauthorized\n- [ ] User can view and revoke active sessions from settings\n- [ ] Session data includes: IP address, user agent, last activity timestamp\n\n## Connections\n\nBlocks: #102 (MFA needs session management)\nRelated: #101 (SSO creates sessions)"
+ "description": "## Description\n\nImplement secure session management with configurable timeout and automatic cleanup.\n\nKey requirements:\n- Session tokens stored securely (httpOnly, secure, sameSite cookies)\n- Configurable session timeout (default: 8 hours)\n- Automatic session extension on user activity\n- Session cleanup job runs every hour\n- Multiple concurrent sessions per user (max 5)\n\n## Business Value\n\nBalances security and user convenience. Automatic timeout prevents unauthorized access from unattended sessions while activity tracking minimizes disruption to active users.\n\n## Acceptance Criteria\n\n- [ ] Session tokens use UUID v4 format\n- [ ] Tokens are stored with expiration timestamp in Redis\n- [ ] Cookie flags: httpOnly=true, secure=true, sameSite=strict\n- [ ] Activity within 30 minutes extends session by configured timeout\n- [ ] Expired sessions return 401 Unauthorized\n- [ ] User can view and revoke active sessions from settings\n- [ ] Session data includes: IP address, user agent, last activity timestamp\n\n## Connections\n\nBlocks: #102 (MFA needs session management)\nRelated: #101 (SSO creates sessions)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#105",
@@ -100,7 +112,10 @@
"created": "2026-01-13T10:30:00Z",
"updated": "2026-01-23T11:20:00Z"
},
- "body": "## Overview\n\nAllow users to configure their notification preferences for email, in-app, and mobile push notifications.\n\n## Value\n\nUsers can control communication frequency and channels, reducing notification fatigue and improving engagement.\n\n## Done Criteria\n\n- Settings page with notification preferences UI\n- Toggle switches for each notification type:\n - Email notifications (on/off)\n - In-app notifications (on/off) \n - Push notifications (on/off)\n- Granular controls:\n - Security alerts (always on)\n - Account updates (optional)\n - Marketing emails (optional)\n - Weekly digest (optional)\n- Preferences saved per user in database\n- Notification service respects user preferences before sending\n\n## User Guide\n\n1. Navigate to Settings → Notifications\n2. Toggle switches for desired notification types\n3. Expand \"Advanced Options\" for granular controls\n4. Click \"Save Preferences\" (auto-saves enabled by default)"
+ "description": "## Overview\n\nAllow users to configure their notification preferences for email, in-app, and mobile push notifications.\n\n## Value\n\nUsers can control communication frequency and channels, reducing notification fatigue and improving engagement.\n\n## Done Criteria\n\n- Settings page with notification preferences UI\n- Toggle switches for each notification type:\n - Email notifications (on/off)\n - In-app notifications (on/off) \n - Push notifications (on/off)\n- Granular controls:\n - Security alerts (always on)\n - Account updates (optional)\n - Marketing emails (optional)\n - Weekly digest (optional)\n- Preferences saved per user in database\n- Notification service respects user preferences before sending\n\n## User Guide\n\n1. Navigate to Settings \u2192 Notifications\n2. Toggle switches for desired notification types\n3. Expand \"Advanced Options\" for granular controls\n4. Click \"Save Preferences\" (auto-saves enabled by default)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#106",
@@ -115,7 +130,10 @@
"created": "2026-01-14T13:15:00Z",
"updated": "2026-01-26T09:00:00Z"
},
- "body": "## Description\n\nPagination offset calculation is incorrect when page size is not 10. This causes wrong results to be displayed on pages 2+.\n\n**Bug Details:**\n```python\n# Current (wrong):\noffset = page * page_size\n\n# Should be:\noffset = (page - 1) * page_size\n```\n\n## Acceptance Criteria\n\n- [ ] Fix offset calculation in all paginated endpoints\n- [ ] Add unit tests for pagination edge cases\n- [ ] Test with page sizes: 5, 10, 20, 50, 100\n- [ ] Verify page 1 shows items 1-N\n- [ ] Verify page 2 shows items N+1 to 2N\n\n## Last Edited\n\nFixed and deployed by charlie@example.com on 2026-01-26"
+ "description": "## Description\n\nPagination offset calculation is incorrect when page size is not 10. This causes wrong results to be displayed on pages 2+.\n\n**Bug Details:**\n```python\n# Current (wrong):\noffset = page * page_size\n\n# Should be:\noffset = (page - 1) * page_size\n```\n\n## Acceptance Criteria\n\n- [ ] Fix offset calculation in all paginated endpoints\n- [ ] Add unit tests for pagination edge cases\n- [ ] Test with page sizes: 5, 10, 20, 50, 100\n- [ ] Verify page 1 shows items 1-N\n- [ ] Verify page 2 shows items N+1 to 2N\n\n## Last Edited\n\nFixed and deployed by charlie@example.com on 2026-01-26",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#107",
@@ -130,7 +148,10 @@
"created": "2026-01-15T09:00:00Z",
"updated": "2026-01-24T14:30:00Z"
},
- "body": "## Summary\n\nGenerate comprehensive API documentation using OpenAPI 3.0 specification and Swagger UI.\n\n## Why\n\nAPI documentation improves developer experience and reduces integration time for external teams.\n\n## Preconditions\n\n- API endpoints must be stable and versioned (v1)\n- FastAPI/Flask framework must support OpenAPI decorators\n- Hosting environment for Swagger UI docs\n\n## Acceptance Criteria\n\nOpenAPI Specification:\n- [ ] All endpoints documented with descriptions\n- [ ] Request/response schemas defined\n- [ ] Authentication requirements specified\n- [ ] Example requests and responses included\n- [ ] Error responses documented (4xx, 5xx)\n\nSwagger UI:\n- [ ] Accessible at /api/docs endpoint\n- [ ] Interactive \"Try it out\" functionality works\n- [ ] Supports authentication token input\n- [ ] Auto-generated from code annotations\n\n## Instructions\n\nAccessing API Docs:\n- Navigate to https://api.example.com/docs\n- Click \"Authorize\" button to enter API token\n- Expand any endpoint to see details\n- Click \"Try it out\" to test directly from browser\n\n## Related\n\n- #108 - API versioning strategy\n- #109 - Rate limiting\n- External docs: [OpenAPI Specification](https://swagger.io/specification/)"
+ "description": "## Summary\n\nGenerate comprehensive API documentation using OpenAPI 3.0 specification and Swagger UI.\n\n## Why\n\nAPI documentation improves developer experience and reduces integration time for external teams.\n\n## Preconditions\n\n- API endpoints must be stable and versioned (v1)\n- FastAPI/Flask framework must support OpenAPI decorators\n- Hosting environment for Swagger UI docs\n\n## Acceptance Criteria\n\nOpenAPI Specification:\n- [ ] All endpoints documented with descriptions\n- [ ] Request/response schemas defined\n- [ ] Authentication requirements specified\n- [ ] Example requests and responses included\n- [ ] Error responses documented (4xx, 5xx)\n\nSwagger UI:\n- [ ] Accessible at /api/docs endpoint\n- [ ] Interactive \"Try it out\" functionality works\n- [ ] Supports authentication token input\n- [ ] Auto-generated from code annotations\n\n## Instructions\n\nAccessing API Docs:\n- Navigate to https://api.example.com/docs\n- Click \"Authorize\" button to enter API token\n- Expand any endpoint to see details\n- Click \"Try it out\" to test directly from browser\n\n## Related\n\n- #108 - API versioning strategy\n- #109 - Rate limiting\n- External docs: [OpenAPI Specification](https://swagger.io/specification/)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#108",
@@ -145,7 +166,10 @@
"created": "2026-01-16T10:45:00Z",
"updated": "2026-01-25T12:00:00Z"
},
- "body": "## Description\n\nEstablish API versioning strategy to support backward compatibility and smooth migrations.\n\n**Proposed Approach:**\n- URL path versioning: `/api/v1/`, `/api/v2/`\n- Major version in path, minor/patch in header\n- Deprecation notices 3 months before removal\n- Support N-1 versions (current + previous)\n\n## Business Value\n\nEnables API evolution without breaking existing integrations. Reduces coordination overhead for external teams consuming our API.\n\n## Setup\n\n- Update API gateway routing rules\n- Implement version detection middleware\n- Create deprecation warning mechanism\n- Update CI/CD to deploy multiple versions\n\n## AC\n\n1. **Version Detection**\n - Requests to `/api/v1/users` route to v1 handler\n - Requests to `/api/v2/users` route to v2 handler\n - Missing version defaults to latest stable\n \n2. **Deprecation Handling**\n - Deprecated endpoints return `Deprecation` header\n - Header includes sunset date and migration guide URL\n - Deprecated endpoints logged for usage analytics\n\n3. **Documentation**\n - Version-specific documentation at /docs/v1, /docs/v2\n - Migration guide between versions\n - Changelog with breaking changes highlighted\n\n## Connections\n\nRelated to #107 (API documentation)"
+ "description": "## Description\n\nEstablish API versioning strategy to support backward compatibility and smooth migrations.\n\n**Proposed Approach:**\n- URL path versioning: `/api/v1/`, `/api/v2/`\n- Major version in path, minor/patch in header\n- Deprecation notices 3 months before removal\n- Support N-1 versions (current + previous)\n\n## Business Value\n\nEnables API evolution without breaking existing integrations. Reduces coordination overhead for external teams consuming our API.\n\n## Setup\n\n- Update API gateway routing rules\n- Implement version detection middleware\n- Create deprecation warning mechanism\n- Update CI/CD to deploy multiple versions\n\n## AC\n\n1. **Version Detection**\n - Requests to `/api/v1/users` route to v1 handler\n - Requests to `/api/v2/users` route to v2 handler\n - Missing version defaults to latest stable\n \n2. **Deprecation Handling**\n - Deprecated endpoints return `Deprecation` header\n - Header includes sunset date and migration guide URL\n - Deprecated endpoints logged for usage analytics\n\n3. **Documentation**\n - Version-specific documentation at /docs/v1, /docs/v2\n - Migration guide between versions\n - Changelog with breaking changes highlighted\n\n## Connections\n\nRelated to #107 (API documentation)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#109",
@@ -160,7 +184,10 @@
"created": "2026-01-17T11:30:00Z",
"updated": "2026-01-27T10:00:00Z"
},
- "body": "## Description\n\nImplement rate limiting to prevent API abuse and ensure fair resource allocation.\n\n**Rate Limits:**\n- Anonymous: 100 requests/hour\n- Authenticated: 1000 requests/hour\n- Premium tier: 5000 requests/hour\n\n## Value\n\nProtects API availability and performance. Prevents denial-of-service attacks and ensures quality of service for all users.\n\n## Acceptance Criteria\n\n- [ ] Rate limiting based on IP address for anonymous users\n- [ ] Rate limiting based on API token for authenticated users\n- [ ] Redis backend for distributed rate limit tracking\n- [ ] Response headers include rate limit info:\n - `X-RateLimit-Limit`: Total requests allowed\n - `X-RateLimit-Remaining`: Requests remaining\n - `X-RateLimit-Reset`: Unix timestamp when limit resets\n- [ ] 429 Too Many Requests response when limit exceeded\n- [ ] Retry-After header indicates wait time in seconds\n\n## History\n\nImplemented by dave@example.com on 2026-01-27"
+ "description": "## Description\n\nImplement rate limiting to prevent API abuse and ensure fair resource allocation.\n\n**Rate Limits:**\n- Anonymous: 100 requests/hour\n- Authenticated: 1000 requests/hour\n- Premium tier: 5000 requests/hour\n\n## Value\n\nProtects API availability and performance. Prevents denial-of-service attacks and ensures quality of service for all users.\n\n## Acceptance Criteria\n\n- [ ] Rate limiting based on IP address for anonymous users\n- [ ] Rate limiting based on API token for authenticated users\n- [ ] Redis backend for distributed rate limit tracking\n- [ ] Response headers include rate limit info:\n - `X-RateLimit-Limit`: Total requests allowed\n - `X-RateLimit-Remaining`: Requests remaining\n - `X-RateLimit-Reset`: Unix timestamp when limit resets\n- [ ] 429 Too Many Requests response when limit exceeded\n- [ ] Retry-After header indicates wait time in seconds\n\n## History\n\nImplemented by dave@example.com on 2026-01-27",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#110",
@@ -175,7 +202,10 @@
"created": "2026-01-18T08:00:00Z",
"updated": "2026-01-26T16:30:00Z"
},
- "body": "## Overview\n\nMigrate production database from PostgreSQL 12 to PostgreSQL 15 to leverage performance improvements and new features.\n\n## Business Value\n\n- 30% faster query performance (benchmark results)\n- Improved JSON query capabilities\n- Better index management\n- Extended support lifecycle\n\n## Prerequisites\n\n- Complete backup of production database\n- Test migration on staging environment\n- Verify application compatibility with PG 15\n- Plan maintenance window (estimated 4 hours)\n- Rollback procedure documented and tested\n\n## Acceptance Criteria\n\nPre-Migration:\n- [ ] Full database backup completed and verified\n- [ ] Staging migration successful with zero data loss\n- [ ] All application tests pass against PG 15\n- [ ] Migration runbook reviewed by team\n- [ ] Stakeholders notified of maintenance window\n\nMigration:\n- [ ] Enable read-only mode on current database\n- [ ] Dump database using pg_dump\n- [ ] Provision PostgreSQL 15 instance\n- [ ] Restore data to new instance\n- [ ] Run ANALYZE to update statistics\n- [ ] Update application connection strings\n- [ ] Verify data integrity checksums\n\nPost-Migration:\n- [ ] All application endpoints responding correctly\n- [ ] Monitor query performance for 24 hours\n- [ ] Keep PG 12 instance running for 1 week (rollback option)\n- [ ] Update documentation with new connection details\n\n## User Guide\n\n**During Maintenance:**\n- Application will be read-only from 02:00-06:00 UTC on 2026-02-01\n- No action required from end users\n- Service will automatically reconnect after migration\n\n**For Developers:**\n- Update local connection strings to new instance\n- Connection pooling parameters may need adjustment\n- Review [PostgreSQL 15 release notes](https://www.postgresql.org/docs/15/release-15.html)\n\n## Connections\n\n- Blocks #111 (performance optimization depends on PG 15 features)\n- Related: Infrastructure upgrade initiative\n\n## Changes\n\nMigration date updated by sre@example.com on 2026-01-26"
+ "description": "## Overview\n\nMigrate production database from PostgreSQL 12 to PostgreSQL 15 to leverage performance improvements and new features.\n\n## Business Value\n\n- 30% faster query performance (benchmark results)\n- Improved JSON query capabilities\n- Better index management\n- Extended support lifecycle\n\n## Prerequisites\n\n- Complete backup of production database\n- Test migration on staging environment\n- Verify application compatibility with PG 15\n- Plan maintenance window (estimated 4 hours)\n- Rollback procedure documented and tested\n\n## Acceptance Criteria\n\nPre-Migration:\n- [ ] Full database backup completed and verified\n- [ ] Staging migration successful with zero data loss\n- [ ] All application tests pass against PG 15\n- [ ] Migration runbook reviewed by team\n- [ ] Stakeholders notified of maintenance window\n\nMigration:\n- [ ] Enable read-only mode on current database\n- [ ] Dump database using pg_dump\n- [ ] Provision PostgreSQL 15 instance\n- [ ] Restore data to new instance\n- [ ] Run ANALYZE to update statistics\n- [ ] Update application connection strings\n- [ ] Verify data integrity checksums\n\nPost-Migration:\n- [ ] All application endpoints responding correctly\n- [ ] Monitor query performance for 24 hours\n- [ ] Keep PG 12 instance running for 1 week (rollback option)\n- [ ] Update documentation with new connection details\n\n## User Guide\n\n**During Maintenance:**\n- Application will be read-only from 02:00-06:00 UTC on 2026-02-01\n- No action required from end users\n- Service will automatically reconnect after migration\n\n**For Developers:**\n- Update local connection strings to new instance\n- Connection pooling parameters may need adjustment\n- Review [PostgreSQL 15 release notes](https://www.postgresql.org/docs/15/release-15.html)\n\n## Connections\n\n- Blocks #111 (performance optimization depends on PG 15 features)\n- Related: Infrastructure upgrade initiative\n\n## Changes\n\nMigration date updated by sre@example.com on 2026-01-26",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#111",
@@ -190,7 +220,10 @@
"created": "2026-01-19T09:30:00Z",
"updated": "2026-01-27T11:15:00Z"
},
- "body": "## Description\n\nOptimize slow database queries identified in performance monitoring.\n\n**Problem Queries:**\n1. User search: 2.5s average response time\n2. Dashboard analytics: 4.1s average response time\n3. Report generation: 8.3s average response time\n\n**Target Performance:**\n1. User search: < 200ms\n2. Dashboard analytics: < 500ms\n3. Report generation: < 2s\n\n## Value\n\nImproved application responsiveness leads to better user experience and higher satisfaction scores.\n\n## Setup\n\n- PostgreSQL 15 migration must be completed (#110)\n- Enable query execution plan logging\n- Set up performance monitoring dashboard\n\n## Done Criteria\n\n- [ ] Add composite index on users(email, active, created_at)\n- [ ] Add materialized view for dashboard analytics\n- [ ] Implement query result caching (Redis, 5 min TTL)\n- [ ] Add database connection pooling (pgbouncer)\n- [ ] All target performance metrics achieved\n- [ ] Performance monitoring alerts configured\n\n## Related\n\nDependency: #110 (PostgreSQL 15 migration)"
+ "description": "## Description\n\nOptimize slow database queries identified in performance monitoring.\n\n**Problem Queries:**\n1. User search: 2.5s average response time\n2. Dashboard analytics: 4.1s average response time\n3. Report generation: 8.3s average response time\n\n**Target Performance:**\n1. User search: < 200ms\n2. Dashboard analytics: < 500ms\n3. Report generation: < 2s\n\n## Value\n\nImproved application responsiveness leads to better user experience and higher satisfaction scores.\n\n## Setup\n\n- PostgreSQL 15 migration must be completed (#110)\n- Enable query execution plan logging\n- Set up performance monitoring dashboard\n\n## Done Criteria\n\n- [ ] Add composite index on users(email, active, created_at)\n- [ ] Add materialized view for dashboard analytics\n- [ ] Implement query result caching (Redis, 5 min TTL)\n- [ ] Add database connection pooling (pgbouncer)\n- [ ] All target performance metrics achieved\n- [ ] Performance monitoring alerts configured\n\n## Related\n\nDependency: #110 (PostgreSQL 15 migration)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#112",
@@ -205,7 +238,10 @@
"created": "2026-01-20T10:00:00Z",
"updated": "2026-01-28T09:00:00Z"
},
- "body": "## Overview\n\nImplement comprehensive audit logging for compliance and security monitoring.\n\n## Why\n\nRegulatory compliance requires detailed audit trails. Security teams need visibility into user actions for threat detection and incident investigation.\n\n## Preconditions\n\n- Log aggregation infrastructure (ELK or similar) must be ready\n- Data retention policy must be approved (7 years for financial data)\n- Log storage capacity planned and provisioned\n\n## AC\n\n**Events to Log:**\n- User authentication (login, logout, failed attempts)\n- Authorization changes (role assignments, permission changes)\n- Data access (view, export of sensitive data)\n- Data modifications (create, update, delete)\n- Administrative actions (user management, configuration changes)\n- Security events (password resets, MFA changes)\n\n**Log Format:**\n```json\n{\n \"timestamp\": \"2026-01-28T09:00:00Z\",\n \"event_type\": \"data_access\",\n \"actor\": \"user@example.com\",\n \"action\": \"export_report\",\n \"resource\": \"financial_report_2025\",\n \"ip_address\": \"192.168.1.100\",\n \"user_agent\": \"Mozilla/5.0...\",\n \"result\": \"success\",\n \"metadata\": {}\n}\n```\n\n**Requirements:**\n- [ ] Logs written to append-only storage\n- [ ] Tamper-proof audit trail (checksums)\n- [ ] Centralized log collection\n- [ ] 7-year retention for compliance logs\n- [ ] 90-day retention for operational logs\n- [ ] Search and reporting UI for security team\n- [ ] Automated alerts for suspicious patterns\n\n## User Guide\n\n**For Security Team:**\n- Access audit logs at /admin/audit\n- Search by user, event type, or date range\n- Export results to CSV for compliance reports\n\n**For Administrators:**\n- Configure retention policies in admin panel\n- Set up email alerts for critical events\n- Review daily summary reports\n\n## Connections\n\n- Related to #101, #102 (authentication/authorization events)\n- Related to compliance initiative\n- External: [OWASP Logging Guide](https://owasp.org/logging)"
+ "description": "## Overview\n\nImplement comprehensive audit logging for compliance and security monitoring.\n\n## Why\n\nRegulatory compliance requires detailed audit trails. Security teams need visibility into user actions for threat detection and incident investigation.\n\n## Preconditions\n\n- Log aggregation infrastructure (ELK or similar) must be ready\n- Data retention policy must be approved (7 years for financial data)\n- Log storage capacity planned and provisioned\n\n## AC\n\n**Events to Log:**\n- User authentication (login, logout, failed attempts)\n- Authorization changes (role assignments, permission changes)\n- Data access (view, export of sensitive data)\n- Data modifications (create, update, delete)\n- Administrative actions (user management, configuration changes)\n- Security events (password resets, MFA changes)\n\n**Log Format:**\n```json\n{\n \"timestamp\": \"2026-01-28T09:00:00Z\",\n \"event_type\": \"data_access\",\n \"actor\": \"user@example.com\",\n \"action\": \"export_report\",\n \"resource\": \"financial_report_2025\",\n \"ip_address\": \"192.168.1.100\",\n \"user_agent\": \"Mozilla/5.0...\",\n \"result\": \"success\",\n \"metadata\": {}\n}\n```\n\n**Requirements:**\n- [ ] Logs written to append-only storage\n- [ ] Tamper-proof audit trail (checksums)\n- [ ] Centralized log collection\n- [ ] 7-year retention for compliance logs\n- [ ] 90-day retention for operational logs\n- [ ] Search and reporting UI for security team\n- [ ] Automated alerts for suspicious patterns\n\n## User Guide\n\n**For Security Team:**\n- Access audit logs at /admin/audit\n- Search by user, event type, or date range\n- Export results to CSV for compliance reports\n\n**For Administrators:**\n- Configure retention policies in admin panel\n- Set up email alerts for critical events\n- Review daily summary reports\n\n## Connections\n\n- Related to #101, #102 (authentication/authorization events)\n- Related to compliance initiative\n- External: [OWASP Logging Guide](https://owasp.org/logging)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#113",
@@ -220,7 +256,10 @@
"created": "2026-01-21T11:00:00Z",
"updated": "2026-01-28T14:00:00Z"
},
- "body": "## Summary\n\nEnable offline mode in mobile app to allow users to work without internet connectivity.\n\n## Business Value\n\nUsers can continue working during travel or in areas with poor connectivity. Improves user satisfaction and productivity.\n\n## Prerequisites\n\n- Mobile app architecture supports local storage\n- Conflict resolution strategy defined\n- Background sync mechanism implemented\n\n## Done Criteria\n\nOffline Capabilities:\n- [ ] View cached content (last 7 days)\n- [ ] Create new items (queued for sync)\n- [ ] Edit existing items (queued for sync)\n- [ ] Delete items (queued for sync)\n- [ ] Search within cached content\n\nSync Behavior:\n- [ ] Automatic sync when connectivity restored\n- [ ] Manual sync trigger in app\n- [ ] Conflict detection and resolution UI\n- [ ] Progress indicator during sync\n- [ ] Failed sync items highlighted for user review\n\nStorage:\n- [ ] Max 100MB local storage per user\n- [ ] Automatic cache cleanup (30 days)\n- [ ] User can manually clear cache\n\n## How To\n\n**Using Offline Mode:**\n1. Ensure app is synced while online\n2. App automatically detects loss of connectivity\n3. Offline indicator appears in header\n4. Continue working - changes saved locally\n5. When back online, app syncs automatically\n6. Review \"Sync Status\" to see pending changes\n\n**Handling Conflicts:**\n- If conflict detected, app shows both versions\n- User chooses which version to keep\n- Option to merge changes manually\n\n## Links\n\nDesign: [Offline UX Mockups](https://figma.com/file/offline)\nRelated: #114 (sync optimization)"
+ "description": "## Summary\n\nEnable offline mode in mobile app to allow users to work without internet connectivity.\n\n## Business Value\n\nUsers can continue working during travel or in areas with poor connectivity. Improves user satisfaction and productivity.\n\n## Prerequisites\n\n- Mobile app architecture supports local storage\n- Conflict resolution strategy defined\n- Background sync mechanism implemented\n\n## Done Criteria\n\nOffline Capabilities:\n- [ ] View cached content (last 7 days)\n- [ ] Create new items (queued for sync)\n- [ ] Edit existing items (queued for sync)\n- [ ] Delete items (queued for sync)\n- [ ] Search within cached content\n\nSync Behavior:\n- [ ] Automatic sync when connectivity restored\n- [ ] Manual sync trigger in app\n- [ ] Conflict detection and resolution UI\n- [ ] Progress indicator during sync\n- [ ] Failed sync items highlighted for user review\n\nStorage:\n- [ ] Max 100MB local storage per user\n- [ ] Automatic cache cleanup (30 days)\n- [ ] User can manually clear cache\n\n## How To\n\n**Using Offline Mode:**\n1. Ensure app is synced while online\n2. App automatically detects loss of connectivity\n3. Offline indicator appears in header\n4. Continue working - changes saved locally\n5. When back online, app syncs automatically\n6. Review \"Sync Status\" to see pending changes\n\n**Handling Conflicts:**\n- If conflict detected, app shows both versions\n- User chooses which version to keep\n- Option to merge changes manually\n\n## Links\n\nDesign: [Offline UX Mockups](https://figma.com/file/offline)\nRelated: #114 (sync optimization)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#114",
@@ -234,7 +273,10 @@
"created": "2026-01-22T12:00:00Z",
"updated": "2026-01-22T12:00:00Z"
},
- "body": null
+ "description": null,
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-project#115",
@@ -248,7 +290,10 @@
"created": "2026-01-23T13:30:00Z",
"updated": "2026-01-29T10:00:00Z"
},
- "body": "This issue has no markdown headings, just plain text content. It should be treated as description content. This tests the normalizer's handling of plain text without any markdown structure."
+ "description": "This issue has no markdown headings, just plain text content. It should be treated as description content. This tests the normalizer's handling of plain text without any markdown structure.",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
}
]
-}
\ No newline at end of file
+}
diff --git a/tests/fixtures/collector_gh/v1.2.0/input/doc-issues.json b/tests/fixtures/collector_gh/v1.2.0/input/doc-issues.json
index 74d8cee..4be06cc 100644
--- a/tests/fixtures/collector_gh/v1.2.0/input/doc-issues.json
+++ b/tests/fixtures/collector_gh/v1.2.0/input/doc-issues.json
@@ -24,7 +24,7 @@
"enterprise": null
}
},
- "items": [
+ "user_stories": [
{
"id": "github:AbsaOSS/living-doc-v2#201",
"title": "Implement real-time notifications",
@@ -39,7 +39,10 @@
"created": "2026-02-01T08:00:00Z",
"updated": "2026-02-10T15:30:00Z"
},
- "body": "## Description\n\nImplement real-time push notifications using WebSocket connections to provide instant updates to users.\n\n## Business Value\n\n- Eliminates need for page refreshing\n- Improves user engagement and responsiveness\n- Reduces server load from polling\n- Enables collaborative features\n\n## Preconditions\n\n- WebSocket infrastructure must be available (Socket.io or similar)\n- Redis pub/sub for message distribution across servers\n- Load balancer must support WebSocket connections\n- Client library for WebSocket management\n\n## Acceptance Criteria\n\n**Connection Management:**\n- [ ] Client establishes WebSocket connection on login\n- [ ] Connection survives network hiccups (auto-reconnect)\n- [ ] Heartbeat ping/pong every 30 seconds\n- [ ] Graceful fallback to polling if WebSocket unavailable\n\n**Notification Types:**\n- [ ] New message received\n- [ ] Task assignment\n- [ ] Status change on watched items\n- [ ] System announcements\n- [ ] Collaboration updates (user joined, user typing)\n\n**Performance:**\n- [ ] Support 10,000 concurrent connections per server\n- [ ] Message delivery latency < 100ms\n- [ ] CPU usage < 20% at max capacity\n\n## User Guide\n\n**For End Users:**\nNotifications appear automatically - no configuration needed!\n- Toast notification appears in bottom-right corner\n- Click notification to navigate to relevant item\n- Bell icon shows notification count\n- Notification center lists all recent notifications\n\n**For Developers:**\n```javascript\n// Subscribe to notifications\nconst socket = io();\nsocket.on('notification', (data) => {\n showToast(data.message, data.type);\n});\n```"
+ "description": "## Description\n\nImplement real-time push notifications using WebSocket connections to provide instant updates to users.\n\n## Business Value\n\n- Eliminates need for page refreshing\n- Improves user engagement and responsiveness\n- Reduces server load from polling\n- Enables collaborative features\n\n## Preconditions\n\n- WebSocket infrastructure must be available (Socket.io or similar)\n- Redis pub/sub for message distribution across servers\n- Load balancer must support WebSocket connections\n- Client library for WebSocket management\n\n## Acceptance Criteria\n\n**Connection Management:**\n- [ ] Client establishes WebSocket connection on login\n- [ ] Connection survives network hiccups (auto-reconnect)\n- [ ] Heartbeat ping/pong every 30 seconds\n- [ ] Graceful fallback to polling if WebSocket unavailable\n\n**Notification Types:**\n- [ ] New message received\n- [ ] Task assignment\n- [ ] Status change on watched items\n- [ ] System announcements\n- [ ] Collaboration updates (user joined, user typing)\n\n**Performance:**\n- [ ] Support 10,000 concurrent connections per server\n- [ ] Message delivery latency < 100ms\n- [ ] CPU usage < 20% at max capacity\n\n## User Guide\n\n**For End Users:**\nNotifications appear automatically - no configuration needed!\n- Toast notification appears in bottom-right corner\n- Click notification to navigate to relevant item\n- Bell icon shows notification count\n- Notification center lists all recent notifications\n\n**For Developers:**\n```javascript\n// Subscribe to notifications\nconst socket = io();\nsocket.on('notification', (data) => {\n showToast(data.message, data.type);\n});\n```",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#202",
@@ -54,7 +57,10 @@
"created": "2026-02-02T09:15:00Z",
"updated": "2026-02-11T10:45:00Z"
},
- "body": "## Overview\n\nEnable users to export data in multiple formats: CSV, Excel, JSON, and PDF.\n\n## Why\n\nUsers need data in different formats for reporting, analysis, and integration with other tools.\n\n## Setup\n\n| Format | Library | Version |\n|--------|---------|--------|\n| CSV | csv | stdlib |\n| Excel | openpyxl | 3.1.0 |\n| JSON | json | stdlib |\n| PDF | reportlab | 4.0.0 |\n\n## AC\n\n**Supported Formats:**\n- [ ] CSV - comma-separated values\n- [ ] XLSX - Excel workbook with formatting\n- [ ] JSON - structured data export\n- [ ] PDF - formatted report with branding\n\n**Export Options:**\n- [ ] Select fields to include/exclude\n- [ ] Date range filter\n- [ ] Sort order selection\n- [ ] Page orientation for PDF (portrait/landscape)\n\n**Implementation:**\n- [ ] Export button in list views\n- [ ] Format selection dropdown\n- [ ] Background job for large exports\n- [ ] Email notification when export ready\n- [ ] Download link expires after 24 hours\n\n## Instructions\n\n1. Navigate to any list view (users, reports, etc.)\n2. Click \"Export\" button in toolbar\n3. Select desired format from dropdown\n4. Configure export options (optional)\n5. Click \"Generate Export\"\n6. For small exports: Download starts immediately\n7. For large exports: Email sent when ready\n\n## Related\n\n- #203 - Report scheduling\n- #204 - Data import"
+ "description": "## Overview\n\nEnable users to export data in multiple formats: CSV, Excel, JSON, and PDF.\n\n## Why\n\nUsers need data in different formats for reporting, analysis, and integration with other tools.\n\n## Setup\n\n| Format | Library | Version |\n|--------|---------|--------|\n| CSV | csv | stdlib |\n| Excel | openpyxl | 3.1.0 |\n| JSON | json | stdlib |\n| PDF | reportlab | 4.0.0 |\n\n## AC\n\n**Supported Formats:**\n- [ ] CSV - comma-separated values\n- [ ] XLSX - Excel workbook with formatting\n- [ ] JSON - structured data export\n- [ ] PDF - formatted report with branding\n\n**Export Options:**\n- [ ] Select fields to include/exclude\n- [ ] Date range filter\n- [ ] Sort order selection\n- [ ] Page orientation for PDF (portrait/landscape)\n\n**Implementation:**\n- [ ] Export button in list views\n- [ ] Format selection dropdown\n- [ ] Background job for large exports\n- [ ] Email notification when export ready\n- [ ] Download link expires after 24 hours\n\n## Instructions\n\n1. Navigate to any list view (users, reports, etc.)\n2. Click \"Export\" button in toolbar\n3. Select desired format from dropdown\n4. Configure export options (optional)\n5. Click \"Generate Export\"\n6. For small exports: Download starts immediately\n7. For large exports: Email sent when ready\n\n## Related\n\n- #203 - Report scheduling\n- #204 - Data import",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#203",
@@ -69,7 +75,10 @@
"created": "2026-02-03T10:30:00Z",
"updated": "2026-02-12T14:00:00Z"
},
- "body": "## Summary\n\nAllow users to schedule automatic report generation and delivery.\n\n## Value\n\nSaves time and ensures stakeholders receive regular updates without manual intervention.\n\n## Prerequisites\n\n- Celery or similar job scheduler configured\n- Email delivery system operational\n- Report templates defined\n- Cloud storage for report archives\n\n## Done Criteria\n\n**Scheduling Options:**\n- [ ] Daily, weekly, monthly schedules\n- [ ] Custom cron expressions for advanced users\n- [ ] Timezone selection\n- [ ] Start date and optional end date\n\n**Delivery Methods:**\n- [ ] Email with PDF attachment\n- [ ] Email with download link\n- [ ] Save to cloud storage (S3, GCS)\n- [ ] Webhook notification\n\n**Report Types:**\n- [ ] User activity summary\n- [ ] Performance metrics\n- [ ] Compliance audit report\n- [ ] Custom report builder\n\n## How To\n\n**Creating a Scheduled Report:**\n1. Go to Reports → Scheduled Reports\n2. Click \"New Schedule\"\n3. Select report type and parameters\n4. Choose frequency (daily/weekly/monthly)\n5. Add email recipients\n6. Click \"Create Schedule\"\n\n**Managing Schedules:**\n- View all schedules in dashboard\n- Pause/resume schedules\n- Edit schedule parameters\n- View execution history\n- Download previous reports\n\n## History\n\nImplemented by emma@example.com on 2026-02-12"
+ "description": "## Summary\n\nAllow users to schedule automatic report generation and delivery.\n\n## Value\n\nSaves time and ensures stakeholders receive regular updates without manual intervention.\n\n## Prerequisites\n\n- Celery or similar job scheduler configured\n- Email delivery system operational\n- Report templates defined\n- Cloud storage for report archives\n\n## Done Criteria\n\n**Scheduling Options:**\n- [ ] Daily, weekly, monthly schedules\n- [ ] Custom cron expressions for advanced users\n- [ ] Timezone selection\n- [ ] Start date and optional end date\n\n**Delivery Methods:**\n- [ ] Email with PDF attachment\n- [ ] Email with download link\n- [ ] Save to cloud storage (S3, GCS)\n- [ ] Webhook notification\n\n**Report Types:**\n- [ ] User activity summary\n- [ ] Performance metrics\n- [ ] Compliance audit report\n- [ ] Custom report builder\n\n## How To\n\n**Creating a Scheduled Report:**\n1. Go to Reports \u2192 Scheduled Reports\n2. Click \"New Schedule\"\n3. Select report type and parameters\n4. Choose frequency (daily/weekly/monthly)\n5. Add email recipients\n6. Click \"Create Schedule\"\n\n**Managing Schedules:**\n- View all schedules in dashboard\n- Pause/resume schedules\n- Edit schedule parameters\n- View execution history\n- Download previous reports\n\n## History\n\nImplemented by emma@example.com on 2026-02-12",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#204",
@@ -84,7 +93,10 @@
"created": "2026-02-04T11:00:00Z",
"updated": "2026-02-13T09:30:00Z"
},
- "body": "## Description\n\nEnable bulk import of data from CSV and Excel files with validation and error handling.\n\n**Supported Entities:**\n- Users (bulk user provisioning)\n- Products (inventory updates)\n- Transactions (bulk data entry)\n- Configuration settings\n\n## Business Value\n\nAccelerates data migration and initial setup. Reduces manual data entry errors and saves hours of work.\n\n## Acceptance Criteria\n\n**File Upload:**\n- [ ] Drag-and-drop file upload\n- [ ] Support CSV and XLSX formats\n- [ ] Max file size: 10MB\n- [ ] Sample templates available for download\n\n**Validation:**\n- [ ] Column header mapping (automatic + manual)\n- [ ] Data type validation\n- [ ] Required field checking\n- [ ] Duplicate detection\n- [ ] Preview before import (first 10 rows)\n\n**Processing:**\n- [ ] Background processing for large files\n- [ ] Progress bar with percentage complete\n- [ ] Partial import on errors (continue processing valid rows)\n- [ ] Detailed error report for failed rows\n- [ ] Transaction rollback option\n\n**Error Handling:**\n- [ ] Download error report as CSV\n- [ ] Highlight problematic rows and columns\n- [ ] Suggested fixes for common errors\n- [ ] Ability to fix and re-upload\n\n## User Guide\n\n**Import Process:**\n1. Click \"Import\" button\n2. Select entity type (Users, Products, etc.)\n3. Download template file (optional)\n4. Upload your populated CSV/Excel file\n5. Map columns if automatic mapping fails\n6. Review preview of first 10 rows\n7. Click \"Import\" to process\n8. Monitor progress bar\n9. Review completion summary\n10. Download error report if needed\n\n**Template Format:**\nDownload templates include:\n- Required column headers\n- Example data rows\n- Data type comments\n- Validation rules\n\n## Connections\n\nRelated to #202 (data export)"
+ "description": "## Description\n\nEnable bulk import of data from CSV and Excel files with validation and error handling.\n\n**Supported Entities:**\n- Users (bulk user provisioning)\n- Products (inventory updates)\n- Transactions (bulk data entry)\n- Configuration settings\n\n## Business Value\n\nAccelerates data migration and initial setup. Reduces manual data entry errors and saves hours of work.\n\n## Acceptance Criteria\n\n**File Upload:**\n- [ ] Drag-and-drop file upload\n- [ ] Support CSV and XLSX formats\n- [ ] Max file size: 10MB\n- [ ] Sample templates available for download\n\n**Validation:**\n- [ ] Column header mapping (automatic + manual)\n- [ ] Data type validation\n- [ ] Required field checking\n- [ ] Duplicate detection\n- [ ] Preview before import (first 10 rows)\n\n**Processing:**\n- [ ] Background processing for large files\n- [ ] Progress bar with percentage complete\n- [ ] Partial import on errors (continue processing valid rows)\n- [ ] Detailed error report for failed rows\n- [ ] Transaction rollback option\n\n**Error Handling:**\n- [ ] Download error report as CSV\n- [ ] Highlight problematic rows and columns\n- [ ] Suggested fixes for common errors\n- [ ] Ability to fix and re-upload\n\n## User Guide\n\n**Import Process:**\n1. Click \"Import\" button\n2. Select entity type (Users, Products, etc.)\n3. Download template file (optional)\n4. Upload your populated CSV/Excel file\n5. Map columns if automatic mapping fails\n6. Review preview of first 10 rows\n7. Click \"Import\" to process\n8. Monitor progress bar\n9. Review completion summary\n10. Download error report if needed\n\n**Template Format:**\nDownload templates include:\n- Required column headers\n- Example data rows\n- Data type comments\n- Validation rules\n\n## Connections\n\nRelated to #202 (data export)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#205",
@@ -100,7 +112,10 @@
"created": "2026-02-05T08:30:00Z",
"updated": "2026-02-14T11:00:00Z"
},
- "body": "## Overview\n\nReplace basic SQL LIKE queries with Elasticsearch for powerful full-text search capabilities.\n\n## Why\n\n- Current search is slow on large datasets (3+ seconds)\n- No support for fuzzy matching or typo tolerance\n- Cannot search across multiple fields efficiently\n- No relevance ranking\n\n## Setup\n\n**Infrastructure:**\n- Elasticsearch 8.x cluster (3 nodes)\n- Kibana for monitoring and debugging\n- Logstash for data synchronization\n- 100GB storage allocation\n\n**Data Synchronization:**\n- Full index rebuild: Weekly (Sunday 2 AM)\n- Incremental updates: Real-time via change data capture\n- Fallback: Hourly delta sync job\n\n## AC\n\n**Search Features:**\n- [ ] Full-text search across all text fields\n- [ ] Fuzzy matching (handle typos)\n- [ ] Phrase matching with quotes\n- [ ] Boolean operators (AND, OR, NOT)\n- [ ] Wildcard search (* and ?)\n- [ ] Field-specific search (title:\"example\")\n- [ ] Relevance-based ranking\n- [ ] Search suggestions (autocomplete)\n\n**Performance:**\n- [ ] Search response time < 200ms (p95)\n- [ ] Support 100 concurrent searches\n- [ ] Index size < 5GB\n\n**Indexing:**\n- [ ] Index users, documents, messages, products\n- [ ] Custom analyzers for each language\n- [ ] Configurable field weights for ranking\n\n## Instructions\n\n**Basic Search:**\n- Enter search terms in global search box\n- Press Enter or click search icon\n- Results appear with highlighting\n\n**Advanced Search:**\n- Use quotes for exact phrases: \"product manager\"\n- Combine terms: python AND django\n- Exclude terms: java NOT javascript \n- Field search: author:\"john doe\"\n- Wildcard: test*\n\n## Related\n\n- #206 - Search analytics\n- External: [Elasticsearch Guide](https://elastic.co/guide)"
+ "description": "## Overview\n\nReplace basic SQL LIKE queries with Elasticsearch for powerful full-text search capabilities.\n\n## Why\n\n- Current search is slow on large datasets (3+ seconds)\n- No support for fuzzy matching or typo tolerance\n- Cannot search across multiple fields efficiently\n- No relevance ranking\n\n## Setup\n\n**Infrastructure:**\n- Elasticsearch 8.x cluster (3 nodes)\n- Kibana for monitoring and debugging\n- Logstash for data synchronization\n- 100GB storage allocation\n\n**Data Synchronization:**\n- Full index rebuild: Weekly (Sunday 2 AM)\n- Incremental updates: Real-time via change data capture\n- Fallback: Hourly delta sync job\n\n## AC\n\n**Search Features:**\n- [ ] Full-text search across all text fields\n- [ ] Fuzzy matching (handle typos)\n- [ ] Phrase matching with quotes\n- [ ] Boolean operators (AND, OR, NOT)\n- [ ] Wildcard search (* and ?)\n- [ ] Field-specific search (title:\"example\")\n- [ ] Relevance-based ranking\n- [ ] Search suggestions (autocomplete)\n\n**Performance:**\n- [ ] Search response time < 200ms (p95)\n- [ ] Support 100 concurrent searches\n- [ ] Index size < 5GB\n\n**Indexing:**\n- [ ] Index users, documents, messages, products\n- [ ] Custom analyzers for each language\n- [ ] Configurable field weights for ranking\n\n## Instructions\n\n**Basic Search:**\n- Enter search terms in global search box\n- Press Enter or click search icon\n- Results appear with highlighting\n\n**Advanced Search:**\n- Use quotes for exact phrases: \"product manager\"\n- Combine terms: python AND django\n- Exclude terms: java NOT javascript \n- Field search: author:\"john doe\"\n- Wildcard: test*\n\n## Related\n\n- #206 - Search analytics\n- External: [Elasticsearch Guide](https://elastic.co/guide)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#206",
@@ -115,7 +130,10 @@
"created": "2026-02-06T09:45:00Z",
"updated": "2026-02-15T10:15:00Z"
},
- "body": "## Description\n\nTrack and analyze search behavior to improve search relevance and understand user needs.\n\n## Value\n\nInsights from search data help improve content organization, identify gaps, and optimize search algorithms.\n\n## Done Criteria\n\n**Tracked Metrics:**\n- Search queries (text, filters, results count)\n- Click-through rate (which results users clicked)\n- Zero-result searches (queries with no results)\n- Search refinements (query modifications)\n- Time to click (search duration)\n- Abandoned searches\n\n**Analytics Dashboard:**\n- [ ] Top 100 search queries\n- [ ] Most clicked results\n- [ ] Zero-result query report\n- [ ] Search success rate\n- [ ] Average results per query\n- [ ] Search trends over time\n\n**Actionable Insights:**\n- [ ] Recommend synonyms for common searches\n- [ ] Identify content gaps (frequent zero-result queries)\n- [ ] Suggest popular content for homepage\n- [ ] Alert on broken links in search results"
+ "description": "## Description\n\nTrack and analyze search behavior to improve search relevance and understand user needs.\n\n## Value\n\nInsights from search data help improve content organization, identify gaps, and optimize search algorithms.\n\n## Done Criteria\n\n**Tracked Metrics:**\n- Search queries (text, filters, results count)\n- Click-through rate (which results users clicked)\n- Zero-result searches (queries with no results)\n- Search refinements (query modifications)\n- Time to click (search duration)\n- Abandoned searches\n\n**Analytics Dashboard:**\n- [ ] Top 100 search queries\n- [ ] Most clicked results\n- [ ] Zero-result query report\n- [ ] Search success rate\n- [ ] Average results per query\n- [ ] Search trends over time\n\n**Actionable Insights:**\n- [ ] Recommend synonyms for common searches\n- [ ] Identify content gaps (frequent zero-result queries)\n- [ ] Suggest popular content for homepage\n- [ ] Alert on broken links in search results",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#207",
@@ -130,7 +148,10 @@
"created": "2026-02-07T10:00:00Z",
"updated": "2026-02-16T14:30:00Z"
},
- "body": "## Summary\n\nImplement comprehensive caching strategy using Redis to improve application performance.\n\n## Business Value\n\n- 70% reduction in database load\n- 5x faster page load times for cached content\n- Better scalability and reduced infrastructure costs\n\n## Prerequisites\n\n- Redis cluster deployed (3 nodes, master-replica setup)\n- Cache warming strategy defined\n- Cache invalidation events identified\n\n## Acceptance Criteria\n\n**Cache Layers:**\n\n1. **Page Cache** (TTL: 5 minutes)\n - Full HTML responses for public pages\n - Vary by user role for personalized pages\n \n2. **API Response Cache** (TTL: 2 minutes)\n - GET endpoints only\n - Cache key includes query parameters\n - Skip for user-specific data\n \n3. **Database Query Cache** (TTL: 10 minutes)\n - Expensive queries (joins, aggregations)\n - Reference data (countries, categories)\n \n4. **Session Cache** (TTL: 8 hours)\n - User sessions and authentication tokens\n - Shopping cart data\n\n**Cache Invalidation:**\n- [ ] Automatic invalidation on data updates\n- [ ] Manual cache clear for administrators\n- [ ] Tag-based invalidation (e.g., clear all user-related caches)\n- [ ] LRU eviction when memory limit reached\n\n**Monitoring:**\n- [ ] Cache hit/miss rate metrics\n- [ ] Memory usage dashboard\n- [ ] Slow cache operations alerts\n\n## History\n\nDeployed by frank@example.com on 2026-02-16"
+ "description": "## Summary\n\nImplement comprehensive caching strategy using Redis to improve application performance.\n\n## Business Value\n\n- 70% reduction in database load\n- 5x faster page load times for cached content\n- Better scalability and reduced infrastructure costs\n\n## Prerequisites\n\n- Redis cluster deployed (3 nodes, master-replica setup)\n- Cache warming strategy defined\n- Cache invalidation events identified\n\n## Acceptance Criteria\n\n**Cache Layers:**\n\n1. **Page Cache** (TTL: 5 minutes)\n - Full HTML responses for public pages\n - Vary by user role for personalized pages\n \n2. **API Response Cache** (TTL: 2 minutes)\n - GET endpoints only\n - Cache key includes query parameters\n - Skip for user-specific data\n \n3. **Database Query Cache** (TTL: 10 minutes)\n - Expensive queries (joins, aggregations)\n - Reference data (countries, categories)\n \n4. **Session Cache** (TTL: 8 hours)\n - User sessions and authentication tokens\n - Shopping cart data\n\n**Cache Invalidation:**\n- [ ] Automatic invalidation on data updates\n- [ ] Manual cache clear for administrators\n- [ ] Tag-based invalidation (e.g., clear all user-related caches)\n- [ ] LRU eviction when memory limit reached\n\n**Monitoring:**\n- [ ] Cache hit/miss rate metrics\n- [ ] Memory usage dashboard\n- [ ] Slow cache operations alerts\n\n## History\n\nDeployed by frank@example.com on 2026-02-16",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#208",
@@ -145,7 +166,10 @@
"created": "2026-02-08T11:15:00Z",
"updated": "2026-02-17T09:00:00Z"
},
- "body": "## Overview\n\nImplement comprehensive server-side input validation to prevent injection attacks and data corruption.\n\n## Why\n\nSecurity best practice. Prevents SQL injection, XSS, command injection, and ensures data integrity.\n\n## Setup\n\nValidation library setup:\n- Backend: Pydantic for Python, Joi for Node.js\n- Sanitization: bleach for HTML, validator.js for strings\n- Content Security Policy headers\n\n## AC\n\n**Validation Rules:**\n\nEmail fields:\n- [ ] Valid email format\n- [ ] Domain whitelist (optional)\n- [ ] Max length: 254 characters\n\nText inputs:\n- [ ] HTML tag stripping or escaping\n- [ ] Max length enforcement\n- [ ] No control characters\n- [ ] Unicode normalization\n\nNumeric inputs:\n- [ ] Type validation (int vs float)\n- [ ] Range checking (min/max)\n- [ ] Precision limits\n\nFile uploads:\n- [ ] File extension whitelist\n- [ ] MIME type verification\n- [ ] File size limits\n- [ ] Malware scanning\n\n**Error Messages:**\n- [ ] User-friendly messages (not technical)\n- [ ] Field-specific errors\n- [ ] Multiple errors displayed together\n- [ ] No sensitive information in errors\n\n## User Guide\n\nWhen validation fails:\n- Red border appears around invalid field\n- Error message appears below field\n- Submit button remains disabled\n- Fix all errors to enable submission"
+ "description": "## Overview\n\nImplement comprehensive server-side input validation to prevent injection attacks and data corruption.\n\n## Why\n\nSecurity best practice. Prevents SQL injection, XSS, command injection, and ensures data integrity.\n\n## Setup\n\nValidation library setup:\n- Backend: Pydantic for Python, Joi for Node.js\n- Sanitization: bleach for HTML, validator.js for strings\n- Content Security Policy headers\n\n## AC\n\n**Validation Rules:**\n\nEmail fields:\n- [ ] Valid email format\n- [ ] Domain whitelist (optional)\n- [ ] Max length: 254 characters\n\nText inputs:\n- [ ] HTML tag stripping or escaping\n- [ ] Max length enforcement\n- [ ] No control characters\n- [ ] Unicode normalization\n\nNumeric inputs:\n- [ ] Type validation (int vs float)\n- [ ] Range checking (min/max)\n- [ ] Precision limits\n\nFile uploads:\n- [ ] File extension whitelist\n- [ ] MIME type verification\n- [ ] File size limits\n- [ ] Malware scanning\n\n**Error Messages:**\n- [ ] User-friendly messages (not technical)\n- [ ] Field-specific errors\n- [ ] Multiple errors displayed together\n- [ ] No sensitive information in errors\n\n## User Guide\n\nWhen validation fails:\n- Red border appears around invalid field\n- Error message appears below field\n- Submit button remains disabled\n- Fix all errors to enable submission",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#209",
@@ -160,7 +184,10 @@
"created": "2026-02-09T08:00:00Z",
"updated": "2026-02-18T10:30:00Z"
},
- "body": "## Description\n\nImplement features required for GDPR compliance including consent management, data portability, and right to be forgotten.\n\n## Business Value\n\nLegal requirement for EU users. Builds trust and demonstrates commitment to privacy. Avoids hefty fines (up to 4% of revenue).\n\n## Preconditions\n\n- Legal review of privacy policy\n- Data processing agreements with third parties\n- Data classification and inventory complete\n- Privacy impact assessment approved\n\n## Acceptance Criteria\n\n**Consent Management:**\n- [ ] Cookie consent banner on first visit\n- [ ] Granular consent options (necessary, functional, marketing)\n- [ ] Easy withdrawal of consent\n- [ ] Consent records stored with timestamp\n- [ ] Opt-out links in all marketing emails\n\n**Data Subject Rights:**\n\n1. **Right to Access**\n - [ ] User can download all personal data (JSON format)\n - [ ] Data export includes: profile, activity logs, communications\n - [ ] Export generated within 24 hours\n \n2. **Right to Rectification** \n - [ ] User can edit profile information\n - [ ] Request form for data corrections\n \n3. **Right to Erasure (Right to be Forgotten)**\n - [ ] Account deletion request form\n - [ ] 30-day grace period before permanent deletion\n - [ ] Anonymize user data instead of deletion (where legally required to retain)\n - [ ] Email confirmation of deletion\n \n4. **Right to Data Portability**\n - [ ] Export data in machine-readable format (JSON/CSV)\n - [ ] Transfer data directly to another service (if possible)\n\n**Privacy by Design:**\n- [ ] Data minimization (collect only necessary data)\n- [ ] Pseudonymization of personal data in logs\n- [ ] Encryption at rest and in transit\n- [ ] Access controls and audit logging\n- [ ] Automated data retention policies\n\n## User Guide\n\n**Accessing Your Data:**\n1. Go to Settings → Privacy\n2. Click \"Download My Data\"\n3. Email sent when export ready (usually < 1 hour)\n4. Download link valid for 48 hours\n\n**Deleting Your Account:**\n1. Go to Settings → Privacy\n2. Click \"Delete My Account\"\n3. Enter password to confirm\n4. Account enters 30-day deletion queue\n5. Cancel deletion anytime within 30 days\n6. After 30 days, deletion is permanent\n\n## Connections\n\n- Related to #112 (audit logging)\n- Related to legal compliance initiative"
+ "description": "## Description\n\nImplement features required for GDPR compliance including consent management, data portability, and right to be forgotten.\n\n## Business Value\n\nLegal requirement for EU users. Builds trust and demonstrates commitment to privacy. Avoids hefty fines (up to 4% of revenue).\n\n## Preconditions\n\n- Legal review of privacy policy\n- Data processing agreements with third parties\n- Data classification and inventory complete\n- Privacy impact assessment approved\n\n## Acceptance Criteria\n\n**Consent Management:**\n- [ ] Cookie consent banner on first visit\n- [ ] Granular consent options (necessary, functional, marketing)\n- [ ] Easy withdrawal of consent\n- [ ] Consent records stored with timestamp\n- [ ] Opt-out links in all marketing emails\n\n**Data Subject Rights:**\n\n1. **Right to Access**\n - [ ] User can download all personal data (JSON format)\n - [ ] Data export includes: profile, activity logs, communications\n - [ ] Export generated within 24 hours\n \n2. **Right to Rectification** \n - [ ] User can edit profile information\n - [ ] Request form for data corrections\n \n3. **Right to Erasure (Right to be Forgotten)**\n - [ ] Account deletion request form\n - [ ] 30-day grace period before permanent deletion\n - [ ] Anonymize user data instead of deletion (where legally required to retain)\n - [ ] Email confirmation of deletion\n \n4. **Right to Data Portability**\n - [ ] Export data in machine-readable format (JSON/CSV)\n - [ ] Transfer data directly to another service (if possible)\n\n**Privacy by Design:**\n- [ ] Data minimization (collect only necessary data)\n- [ ] Pseudonymization of personal data in logs\n- [ ] Encryption at rest and in transit\n- [ ] Access controls and audit logging\n- [ ] Automated data retention policies\n\n## User Guide\n\n**Accessing Your Data:**\n1. Go to Settings \u2192 Privacy\n2. Click \"Download My Data\"\n3. Email sent when export ready (usually < 1 hour)\n4. Download link valid for 48 hours\n\n**Deleting Your Account:**\n1. Go to Settings \u2192 Privacy\n2. Click \"Delete My Account\"\n3. Enter password to confirm\n4. Account enters 30-day deletion queue\n5. Cancel deletion anytime within 30 days\n6. After 30 days, deletion is permanent\n\n## Connections\n\n- Related to #112 (audit logging)\n- Related to legal compliance initiative",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#210",
@@ -175,7 +202,10 @@
"created": "2026-02-10T09:30:00Z",
"updated": "2026-02-18T11:45:00Z"
},
- "body": "## Overview\n\nAdd internationalization support to enable multi-language user interface.\n\n## Value\n\nExpands market reach. Improves user experience for non-English speakers. Requirement for international expansion.\n\n## Prerequisites\n\n- Translation management system (e.g., Crowdin, Lokalise)\n- Professional translation service engaged\n- Language selection UI/UX designed\n- Right-to-left (RTL) layout considerations\n\n## Done Criteria\n\n**Supported Languages (Phase 1):**\n- [ ] English (default)\n- [ ] Spanish\n- [ ] French\n- [ ] German\n- [ ] Japanese\n\n**Implementation:**\n- [ ] Extract all UI strings to translation files\n- [ ] Use i18n library (react-i18next, Flask-Babel, etc.)\n- [ ] Language selector in header\n- [ ] User language preference saved\n- [ ] Browser language auto-detection\n- [ ] Fallback to English for missing translations\n\n**Content Types:**\n- [ ] UI labels and buttons\n- [ ] Error messages\n- [ ] Email templates\n- [ ] Help documentation\n- [ ] Date/time formatting per locale\n- [ ] Number formatting (decimals, thousands separators)\n- [ ] Currency formatting\n\n**Quality:**\n- [ ] 100% translation coverage for supported languages\n- [ ] Professional translation review\n- [ ] Test all workflows in each language\n- [ ] No hardcoded strings in code\n\n## How To\n\n**Changing Language:**\n1. Click language selector in header (globe icon)\n2. Select desired language from dropdown\n3. Page refreshes with new language\n4. Preference saved for future visits\n\n**For Developers:**\n```javascript\n// Before\n\n\n// After \n\n```\n\n## Related\n\n- #211 - Right-to-left (RTL) language support"
+ "description": "## Overview\n\nAdd internationalization support to enable multi-language user interface.\n\n## Value\n\nExpands market reach. Improves user experience for non-English speakers. Requirement for international expansion.\n\n## Prerequisites\n\n- Translation management system (e.g., Crowdin, Lokalise)\n- Professional translation service engaged\n- Language selection UI/UX designed\n- Right-to-left (RTL) layout considerations\n\n## Done Criteria\n\n**Supported Languages (Phase 1):**\n- [ ] English (default)\n- [ ] Spanish\n- [ ] French\n- [ ] German\n- [ ] Japanese\n\n**Implementation:**\n- [ ] Extract all UI strings to translation files\n- [ ] Use i18n library (react-i18next, Flask-Babel, etc.)\n- [ ] Language selector in header\n- [ ] User language preference saved\n- [ ] Browser language auto-detection\n- [ ] Fallback to English for missing translations\n\n**Content Types:**\n- [ ] UI labels and buttons\n- [ ] Error messages\n- [ ] Email templates\n- [ ] Help documentation\n- [ ] Date/time formatting per locale\n- [ ] Number formatting (decimals, thousands separators)\n- [ ] Currency formatting\n\n**Quality:**\n- [ ] 100% translation coverage for supported languages\n- [ ] Professional translation review\n- [ ] Test all workflows in each language\n- [ ] No hardcoded strings in code\n\n## How To\n\n**Changing Language:**\n1. Click language selector in header (globe icon)\n2. Select desired language from dropdown\n3. Page refreshes with new language\n4. Preference saved for future visits\n\n**For Developers:**\n```javascript\n// Before\n\n\n// After \n\n```\n\n## Related\n\n- #211 - Right-to-left (RTL) language support",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#211",
@@ -190,7 +220,10 @@
"created": "2026-02-11T10:00:00Z",
"updated": "2026-02-18T13:00:00Z"
},
- "body": "## Description\n\nAdd support for right-to-left languages (Arabic, Hebrew) including layout mirroring and text direction.\n\n## Value\n\nEnables product usage in Middle East and Israel markets. Shows cultural sensitivity and attention to detail.\n\n## Setup\n\n- RTL CSS framework or utility classes\n- Bi-directional text handling\n- Icon flipping for directional icons\n\n## Acceptance Criteria\n\n**Layout:**\n- [ ] Entire UI mirrors for RTL languages\n- [ ] Sidebar moves to right side\n- [ ] Text alignment: right for RTL, left for LTR\n- [ ] Directional icons flipped (arrows, chevrons)\n- [ ] Margins and padding reversed\n\n**Text Direction:**\n- [ ] HTML dir attribute set correctly (rtl/ltr)\n- [ ] Bidirectional text handled correctly (mixed RTL/LTR)\n- [ ] Form inputs right-aligned for RTL\n\n**Testing:**\n- [ ] Test with Arabic language\n- [ ] Test with Hebrew language\n- [ ] Test mixed content (English + RTL)\n- [ ] Verify all pages render correctly\n\n## Connections\n\nDepends on #210 (i18n support)"
+ "description": "## Description\n\nAdd support for right-to-left languages (Arabic, Hebrew) including layout mirroring and text direction.\n\n## Value\n\nEnables product usage in Middle East and Israel markets. Shows cultural sensitivity and attention to detail.\n\n## Setup\n\n- RTL CSS framework or utility classes\n- Bi-directional text handling\n- Icon flipping for directional icons\n\n## Acceptance Criteria\n\n**Layout:**\n- [ ] Entire UI mirrors for RTL languages\n- [ ] Sidebar moves to right side\n- [ ] Text alignment: right for RTL, left for LTR\n- [ ] Directional icons flipped (arrows, chevrons)\n- [ ] Margins and padding reversed\n\n**Text Direction:**\n- [ ] HTML dir attribute set correctly (rtl/ltr)\n- [ ] Bidirectional text handled correctly (mixed RTL/LTR)\n- [ ] Form inputs right-aligned for RTL\n\n**Testing:**\n- [ ] Test with Arabic language\n- [ ] Test with Hebrew language\n- [ ] Test mixed content (English + RTL)\n- [ ] Verify all pages render correctly\n\n## Connections\n\nDepends on #210 (i18n support)",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#212",
@@ -205,7 +238,10 @@
"created": "2026-02-12T11:30:00Z",
"updated": "2026-02-17T15:00:00Z"
},
- "body": "## Summary\n\nImplement feature flags system for gradual rollouts and A/B testing.\n\n## Why\n\nEnables:\n- Safe deployment of new features\n- Gradual rollout to subset of users\n- Quick rollback without code deployment\n- A/B testing for feature validation\n- Different features for different user segments\n\n## Prerequisites\n\n- Feature flag service (LaunchDarkly, Unleash, or custom)\n- Admin UI for flag management\n- SDK integration in application code\n\n## Acceptance Criteria\n\n**Flag Types:**\n- [ ] Boolean flags (on/off)\n- [ ] Percentage rollouts (10%, 25%, 50%, etc.)\n- [ ] User segment targeting (beta users, premium, etc.)\n- [ ] A/B test variants (variant A vs B vs C)\n\n**Flag Management:**\n- [ ] Create/edit/delete flags via admin UI\n- [ ] Flag overrides for testing (per-user, per-session)\n- [ ] Flag change history and audit log\n- [ ] Schedule flag changes (enable at specific time)\n\n**Integration:**\n- [ ] Backend flag evaluation\n- [ ] Frontend flag evaluation\n- [ ] Real-time flag updates (no deployment needed)\n- [ ] Default values when flag service unavailable\n\n**Examples:**\n```python\nif feature_flags.is_enabled('new_dashboard', user):\n return render_new_dashboard()\nelse:\n return render_old_dashboard()\n```\n\n## Changes\n\nRolled out by grace@example.com on 2026-02-17"
+ "description": "## Summary\n\nImplement feature flags system for gradual rollouts and A/B testing.\n\n## Why\n\nEnables:\n- Safe deployment of new features\n- Gradual rollout to subset of users\n- Quick rollback without code deployment\n- A/B testing for feature validation\n- Different features for different user segments\n\n## Prerequisites\n\n- Feature flag service (LaunchDarkly, Unleash, or custom)\n- Admin UI for flag management\n- SDK integration in application code\n\n## Acceptance Criteria\n\n**Flag Types:**\n- [ ] Boolean flags (on/off)\n- [ ] Percentage rollouts (10%, 25%, 50%, etc.)\n- [ ] User segment targeting (beta users, premium, etc.)\n- [ ] A/B test variants (variant A vs B vs C)\n\n**Flag Management:**\n- [ ] Create/edit/delete flags via admin UI\n- [ ] Flag overrides for testing (per-user, per-session)\n- [ ] Flag change history and audit log\n- [ ] Schedule flag changes (enable at specific time)\n\n**Integration:**\n- [ ] Backend flag evaluation\n- [ ] Frontend flag evaluation\n- [ ] Real-time flag updates (no deployment needed)\n- [ ] Default values when flag service unavailable\n\n**Examples:**\n```python\nif feature_flags.is_enabled('new_dashboard', user):\n return render_new_dashboard()\nelse:\n return render_old_dashboard()\n```\n\n## Changes\n\nRolled out by grace@example.com on 2026-02-17",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#213",
@@ -219,7 +255,10 @@
"created": "2026-02-13T12:00:00Z",
"updated": "2026-02-13T12:00:00Z"
},
- "body": "## Description\n\nTest case with markdown table:\n\n| Column 1 | Column 2 | Column 3 |\n|----------|----------|----------|\n| Value A | Value B | Value C |\n| Value D | Value E | Value F |\n\nThis tests table preservation in normalization."
+ "description": "## Description\n\nTest case with markdown table:\n\n| Column 1 | Column 2 | Column 3 |\n|----------|----------|----------|\n| Value A | Value B | Value C |\n| Value D | Value E | Value F |\n\nThis tests table preservation in normalization.",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/living-doc-v2#214",
@@ -233,7 +272,10 @@
"created": "2026-02-14T13:30:00Z",
"updated": "2026-02-14T14:00:00Z"
},
- "body": "## Description\n\nTest complex markdown:\n\n```python\ndef hello_world():\n print(\"Hello, World!\")\n return 42\n```\n\n## Acceptance Criteria\n\n1. First criterion\n2. Second criterion \n - Nested item A\n - Nested item B\n3. Third criterion\n\n## User Guide\n\n- **Bold item**: description\n- *Italic item*: description\n- `Code item`: description"
+ "description": "## Description\n\nTest complex markdown:\n\n```python\ndef hello_world():\n print(\"Hello, World!\")\n return 42\n```\n\n## Acceptance Criteria\n\n1. First criterion\n2. Second criterion \n - Nested item A\n - Nested item B\n3. Third criterion\n\n## User Guide\n\n- **Bold item**: description\n- *Italic item*: description\n- `Code item`: description",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
}
]
-}
\ No newline at end of file
+}
diff --git a/tests/fixtures/collector_gh/v2.0.0/input/doc-issues.json b/tests/fixtures/collector_gh/v2.0.0/input/doc-issues.json
index 482a292..53d7850 100644
--- a/tests/fixtures/collector_gh/v2.0.0/input/doc-issues.json
+++ b/tests/fixtures/collector_gh/v2.0.0/input/doc-issues.json
@@ -24,7 +24,7 @@
"enterprise": null
}
},
- "items": [
+ "user_stories": [
{
"id": "github:AbsaOSS/future-project#301",
"title": "AI-powered code review assistant",
@@ -39,7 +39,10 @@
"created": "2026-03-01T08:00:00Z",
"updated": "2026-03-10T16:30:00Z"
},
- "body": "## Description\n\nIntegrate AI-powered code review assistant that automatically reviews pull requests and provides intelligent feedback.\n\n## Business Value\n\n- Reduces code review time by 60%\n- Catches common bugs and security issues automatically\n- Improves code quality consistency\n- Frees developers to focus on architectural reviews\n\n## Preconditions\n\n- AI model trained on company codebase\n- API integration with OpenAI or custom model\n- Pull request webhook configuration\n- Review comment posting permissions\n\n## Acceptance Criteria\n\n**Automated Checks:**\n- [ ] Code style and formatting issues\n- [ ] Potential bugs and logic errors\n- [ ] Security vulnerabilities (SQL injection, XSS)\n- [ ] Performance anti-patterns\n- [ ] Missing test coverage\n- [ ] Documentation gaps\n\n**Review Comments:**\n- [ ] Posted as PR comments with line numbers\n- [ ] Severity levels: critical, warning, suggestion\n- [ ] Explanation and suggested fix for each issue\n- [ ] Links to style guide or documentation\n\n**Intelligence:**\n- [ ] Learn from accepted/rejected suggestions\n- [ ] Adapt to team coding style\n- [ ] Context-aware (understands project architecture)\n\n## User Guide\n\n**For Developers:**\n1. Create pull request as usual\n2. AI assistant reviews within 2 minutes\n3. Review comments appear on specific lines\n4. Address critical issues before merging\n5. Accept or dismiss suggestions\n\n**For Team Leads:**\n- Configure AI review rules in settings\n- Set severity thresholds for blocking\n- Review AI performance metrics\n- Provide feedback on AI suggestions"
+ "description": "## Description\n\nIntegrate AI-powered code review assistant that automatically reviews pull requests and provides intelligent feedback.\n\n## Business Value\n\n- Reduces code review time by 60%\n- Catches common bugs and security issues automatically\n- Improves code quality consistency\n- Frees developers to focus on architectural reviews\n\n## Preconditions\n\n- AI model trained on company codebase\n- API integration with OpenAI or custom model\n- Pull request webhook configuration\n- Review comment posting permissions\n\n## Acceptance Criteria\n\n**Automated Checks:**\n- [ ] Code style and formatting issues\n- [ ] Potential bugs and logic errors\n- [ ] Security vulnerabilities (SQL injection, XSS)\n- [ ] Performance anti-patterns\n- [ ] Missing test coverage\n- [ ] Documentation gaps\n\n**Review Comments:**\n- [ ] Posted as PR comments with line numbers\n- [ ] Severity levels: critical, warning, suggestion\n- [ ] Explanation and suggested fix for each issue\n- [ ] Links to style guide or documentation\n\n**Intelligence:**\n- [ ] Learn from accepted/rejected suggestions\n- [ ] Adapt to team coding style\n- [ ] Context-aware (understands project architecture)\n\n## User Guide\n\n**For Developers:**\n1. Create pull request as usual\n2. AI assistant reviews within 2 minutes\n3. Review comments appear on specific lines\n4. Address critical issues before merging\n5. Accept or dismiss suggestions\n\n**For Team Leads:**\n- Configure AI review rules in settings\n- Set severity thresholds for blocking\n- Review AI performance metrics\n- Provide feedback on AI suggestions",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#302",
@@ -54,7 +57,10 @@
"created": "2026-03-02T09:15:00Z",
"updated": "2026-03-11T10:45:00Z"
},
- "body": "## Overview\n\nImplement immutable blockchain-based audit trail for critical operations.\n\n## Why\n\nProvides cryptographically verifiable audit trail that cannot be tampered with. Meets highest regulatory compliance standards.\n\n## Setup\n\n- Private blockchain network (Hyperledger Fabric)\n- Smart contracts for audit logging\n- Consensus mechanism (RAFT)\n- 5 validator nodes across regions\n\n## AC\n\n**Logged Operations:**\n- [ ] Financial transactions\n- [ ] Data access and modifications\n- [ ] Administrative actions\n- [ ] Security events\n\n**Blockchain Features:**\n- [ ] Immutable append-only ledger\n- [ ] Cryptographic hash chain\n- [ ] Multi-party validation\n- [ ] Timestamp verification\n\n**Verification:**\n- [ ] Audit trail verification API\n- [ ] Cryptographic proof generation\n- [ ] Chain integrity checking\n- [ ] Export for regulatory audits"
+ "description": "## Overview\n\nImplement immutable blockchain-based audit trail for critical operations.\n\n## Why\n\nProvides cryptographically verifiable audit trail that cannot be tampered with. Meets highest regulatory compliance standards.\n\n## Setup\n\n- Private blockchain network (Hyperledger Fabric)\n- Smart contracts for audit logging\n- Consensus mechanism (RAFT)\n- 5 validator nodes across regions\n\n## AC\n\n**Logged Operations:**\n- [ ] Financial transactions\n- [ ] Data access and modifications\n- [ ] Administrative actions\n- [ ] Security events\n\n**Blockchain Features:**\n- [ ] Immutable append-only ledger\n- [ ] Cryptographic hash chain\n- [ ] Multi-party validation\n- [ ] Timestamp verification\n\n**Verification:**\n- [ ] Audit trail verification API\n- [ ] Cryptographic proof generation\n- [ ] Chain integrity checking\n- [ ] Export for regulatory audits",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#303",
@@ -69,7 +75,10 @@
"created": "2026-03-03T10:30:00Z",
"updated": "2026-03-12T14:00:00Z"
},
- "body": "## Summary\n\nImplement quantum-resistant cryptographic algorithms to future-proof data security.\n\n## Value\n\nProtects against future quantum computing attacks. Ensures long-term data confidentiality.\n\n## Prerequisites\n\n- NIST-approved post-quantum algorithms\n- Library support (liboqs or similar)\n- Performance testing and benchmarks\n- Migration plan for existing encrypted data\n\n## Done Criteria\n\n**Algorithms:**\n- [ ] CRYSTALS-Kyber for key encapsulation\n- [ ] CRYSTALS-Dilithium for digital signatures\n- [ ] Hybrid mode (classical + quantum-resistant)\n\n**Migration:**\n- [ ] Gradual rollout over 6 months\n- [ ] Backward compatibility during transition\n- [ ] Re-encryption of critical data\n\n**Performance:**\n- [ ] Encryption overhead < 20% vs AES\n- [ ] Key generation time < 100ms\n- [ ] Signature verification < 10ms"
+ "description": "## Summary\n\nImplement quantum-resistant cryptographic algorithms to future-proof data security.\n\n## Value\n\nProtects against future quantum computing attacks. Ensures long-term data confidentiality.\n\n## Prerequisites\n\n- NIST-approved post-quantum algorithms\n- Library support (liboqs or similar)\n- Performance testing and benchmarks\n- Migration plan for existing encrypted data\n\n## Done Criteria\n\n**Algorithms:**\n- [ ] CRYSTALS-Kyber for key encapsulation\n- [ ] CRYSTALS-Dilithium for digital signatures\n- [ ] Hybrid mode (classical + quantum-resistant)\n\n**Migration:**\n- [ ] Gradual rollout over 6 months\n- [ ] Backward compatibility during transition\n- [ ] Re-encryption of critical data\n\n**Performance:**\n- [ ] Encryption overhead < 20% vs AES\n- [ ] Key generation time < 100ms\n- [ ] Signature verification < 10ms",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#304",
@@ -84,7 +93,10 @@
"created": "2026-03-04T11:00:00Z",
"updated": "2026-03-13T09:30:00Z"
},
- "body": "## Description\n\nDeploy application components to edge locations for ultra-low latency.\n\n## Business Value\n\n- Sub-10ms response times for users\n- Reduced bandwidth costs\n- Improved user experience globally\n- Enables real-time applications\n\n## Acceptance Criteria\n\n**Edge Deployment:**\n- [ ] Static assets on CDN edge\n- [ ] API endpoints on edge functions\n- [ ] Regional data caching\n- [ ] Geographic routing\n\n**Performance:**\n- [ ] p95 latency < 50ms worldwide\n- [ ] Cache hit rate > 90%\n- [ ] Auto-scaling at edge\n\n## History\n\nDeployed by henry@example.com on 2026-03-13"
+ "description": "## Description\n\nDeploy application components to edge locations for ultra-low latency.\n\n## Business Value\n\n- Sub-10ms response times for users\n- Reduced bandwidth costs\n- Improved user experience globally\n- Enables real-time applications\n\n## Acceptance Criteria\n\n**Edge Deployment:**\n- [ ] Static assets on CDN edge\n- [ ] API endpoints on edge functions\n- [ ] Regional data caching\n- [ ] Geographic routing\n\n**Performance:**\n- [ ] p95 latency < 50ms worldwide\n- [ ] Cache hit rate > 90%\n- [ ] Auto-scaling at edge\n\n## History\n\nDeployed by henry@example.com on 2026-03-13",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#305",
@@ -99,7 +111,10 @@
"created": "2026-03-05T08:30:00Z",
"updated": "2026-03-14T11:00:00Z"
},
- "body": "## Overview\n\nReplace keyword-based search with semantic search using neural embeddings.\n\n## Why\n\n- Understands user intent, not just keywords\n- Finds conceptually similar content\n- Handles multi-language queries\n- Better relevance than traditional search\n\n## Setup\n\n- Embedding model (OpenAI, Sentence-BERT)\n- Vector database (Pinecone, Weaviate)\n- Batch embedding generation pipeline\n- Real-time embedding updates\n\n## AC\n\n**Semantic Search:**\n- [ ] Understands synonyms and related concepts\n- [ ] Cross-language search capability\n- [ ] Handles typos and misspellings\n- [ ] Context-aware results\n\n**Vector Operations:**\n- [ ] Generate embeddings for all content\n- [ ] Similarity search with cosine distance\n- [ ] Hybrid search (keyword + semantic)\n- [ ] Re-ranking with ML model\n\n**Performance:**\n- [ ] Search response time < 100ms\n- [ ] Support millions of vectors\n- [ ] Batch embedding updates in < 1 hour"
+ "description": "## Overview\n\nReplace keyword-based search with semantic search using neural embeddings.\n\n## Why\n\n- Understands user intent, not just keywords\n- Finds conceptually similar content\n- Handles multi-language queries\n- Better relevance than traditional search\n\n## Setup\n\n- Embedding model (OpenAI, Sentence-BERT)\n- Vector database (Pinecone, Weaviate)\n- Batch embedding generation pipeline\n- Real-time embedding updates\n\n## AC\n\n**Semantic Search:**\n- [ ] Understands synonyms and related concepts\n- [ ] Cross-language search capability\n- [ ] Handles typos and misspellings\n- [ ] Context-aware results\n\n**Vector Operations:**\n- [ ] Generate embeddings for all content\n- [ ] Similarity search with cosine distance\n- [ ] Hybrid search (keyword + semantic)\n- [ ] Re-ranking with ML model\n\n**Performance:**\n- [ ] Search response time < 100ms\n- [ ] Support millions of vectors\n- [ ] Batch embedding updates in < 1 hour",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#306",
@@ -114,7 +129,10 @@
"created": "2026-03-06T09:45:00Z",
"updated": "2026-03-15T10:15:00Z"
},
- "body": "## Description\n\nAI-powered monitoring system that detects anomalies and auto-remediates issues.\n\n## Value\n\nReduces downtime and mean-time-to-recovery. Prevents issues before they impact users.\n\n## Done Criteria\n\n**Anomaly Detection:**\n- [ ] ML model trained on historical metrics\n- [ ] Real-time anomaly detection\n- [ ] Predictive failure warnings\n- [ ] False positive rate < 5%\n\n**Auto-Remediation:**\n- [ ] Restart unhealthy services\n- [ ] Scale resources automatically\n- [ ] Clear cache when needed\n- [ ] Trigger failover on critical failures\n\n**Alerting:**\n- [ ] Smart alert grouping\n- [ ] Context-rich notifications\n- [ ] Suggested remediation actions\n- [ ] Escalation policies"
+ "description": "## Description\n\nAI-powered monitoring system that detects anomalies and auto-remediates issues.\n\n## Value\n\nReduces downtime and mean-time-to-recovery. Prevents issues before they impact users.\n\n## Done Criteria\n\n**Anomaly Detection:**\n- [ ] ML model trained on historical metrics\n- [ ] Real-time anomaly detection\n- [ ] Predictive failure warnings\n- [ ] False positive rate < 5%\n\n**Auto-Remediation:**\n- [ ] Restart unhealthy services\n- [ ] Scale resources automatically\n- [ ] Clear cache when needed\n- [ ] Trigger failover on critical failures\n\n**Alerting:**\n- [ ] Smart alert grouping\n- [ ] Context-rich notifications\n- [ ] Suggested remediation actions\n- [ ] Escalation policies",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#307",
@@ -129,7 +147,10 @@
"created": "2026-03-07T10:00:00Z",
"updated": "2026-03-16T14:30:00Z"
},
- "body": "## Summary\n\nCreate VR interface for immersive data visualization and collaboration.\n\n## Business Value\n\nEarly adopter advantage in metaverse space. Novel user experience attracts media attention.\n\n## Prerequisites\n\n- WebXR API support\n- 3D rendering engine (Three.js, Babylon.js)\n- VR headset testing (Quest, Vive)\n- Avatar system\n\n## Done Criteria\n\n**VR Features:**\n- [ ] 3D data visualization\n- [ ] Virtual collaboration spaces\n- [ ] Avatar representation\n- [ ] Hand gesture controls\n- [ ] Spatial audio\n\n**Compatibility:**\n- [ ] Works on Meta Quest\n- [ ] Works on HTC Vive\n- [ ] Fallback to desktop view\n\n## How To\n\n1. Put on VR headset\n2. Launch application\n3. Enter virtual workspace\n4. Use hand gestures to interact\n5. Voice commands for navigation"
+ "description": "## Summary\n\nCreate VR interface for immersive data visualization and collaboration.\n\n## Business Value\n\nEarly adopter advantage in metaverse space. Novel user experience attracts media attention.\n\n## Prerequisites\n\n- WebXR API support\n- 3D rendering engine (Three.js, Babylon.js)\n- VR headset testing (Quest, Vive)\n- Avatar system\n\n## Done Criteria\n\n**VR Features:**\n- [ ] 3D data visualization\n- [ ] Virtual collaboration spaces\n- [ ] Avatar representation\n- [ ] Hand gesture controls\n- [ ] Spatial audio\n\n**Compatibility:**\n- [ ] Works on Meta Quest\n- [ ] Works on HTC Vive\n- [ ] Fallback to desktop view\n\n## How To\n\n1. Put on VR headset\n2. Launch application\n3. Enter virtual workspace\n4. Use hand gestures to interact\n5. Voice commands for navigation",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#308",
@@ -144,7 +165,10 @@
"created": "2026-03-08T11:15:00Z",
"updated": "2026-03-17T09:00:00Z"
},
- "body": "## Overview\n\nOptimize application for energy efficiency and carbon footprint reduction.\n\n## Why\n\nCorporate sustainability goals. Reduces operating costs. Positive environmental impact.\n\n## Setup\n\n- Carbon-aware computing library\n- Green energy datacenter migration\n- Energy monitoring tools\n\n## AC\n\n**Optimizations:**\n- [ ] Shift batch jobs to off-peak hours\n- [ ] Use carbon-aware scheduling\n- [ ] Optimize database queries for energy\n- [ ] Compress data transfers\n- [ ] Use renewable energy regions\n\n**Metrics:**\n- [ ] 40% reduction in energy usage\n- [ ] Carbon footprint dashboard\n- [ ] Energy efficiency score\n\n## History\n\nImplemented by sustainability-team@example.com on 2026-03-17"
+ "description": "## Overview\n\nOptimize application for energy efficiency and carbon footprint reduction.\n\n## Why\n\nCorporate sustainability goals. Reduces operating costs. Positive environmental impact.\n\n## Setup\n\n- Carbon-aware computing library\n- Green energy datacenter migration\n- Energy monitoring tools\n\n## AC\n\n**Optimizations:**\n- [ ] Shift batch jobs to off-peak hours\n- [ ] Use carbon-aware scheduling\n- [ ] Optimize database queries for energy\n- [ ] Compress data transfers\n- [ ] Use renewable energy regions\n\n**Metrics:**\n- [ ] 40% reduction in energy usage\n- [ ] Carbon footprint dashboard\n- [ ] Energy efficiency score\n\n## History\n\nImplemented by sustainability-team@example.com on 2026-03-17",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#309",
@@ -159,7 +183,10 @@
"created": "2026-03-09T08:00:00Z",
"updated": "2026-03-18T10:30:00Z"
},
- "body": "## Description\n\nImplement federated learning for privacy-preserving machine learning.\n\n## Value\n\nTrain ML models without centralizing sensitive data. Meets strict privacy regulations.\n\n## Acceptance Criteria\n\n**Federated Training:**\n- [ ] Client-side model training\n- [ ] Encrypted gradient aggregation\n- [ ] Differential privacy guarantees\n- [ ] Byzantine-robust aggregation\n\n**Infrastructure:**\n- [ ] Coordination server\n- [ ] Client SDK for edge devices\n- [ ] Secure aggregation protocol\n- [ ] Model versioning"
+ "description": "## Description\n\nImplement federated learning for privacy-preserving machine learning.\n\n## Value\n\nTrain ML models without centralizing sensitive data. Meets strict privacy regulations.\n\n## Acceptance Criteria\n\n**Federated Training:**\n- [ ] Client-side model training\n- [ ] Encrypted gradient aggregation\n- [ ] Differential privacy guarantees\n- [ ] Byzantine-robust aggregation\n\n**Infrastructure:**\n- [ ] Coordination server\n- [ ] Client SDK for edge devices\n- [ ] Secure aggregation protocol\n- [ ] Model versioning",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#310",
@@ -174,7 +201,10 @@
"created": "2026-03-10T09:30:00Z",
"updated": "2026-03-18T11:45:00Z"
},
- "body": "## Overview\n\nImplement zero-knowledge proof authentication for privacy-preserving identity verification.\n\n## Value\n\nProves identity without revealing credentials. Ultimate privacy protection.\n\n## Prerequisites\n\n- ZK-SNARK library\n- Trusted setup ceremony\n- Browser extension or SDK\n\n## Done Criteria\n\n**Authentication:**\n- [ ] Generate ZK proof of password knowledge\n- [ ] Verify proof without seeing password\n- [ ] No credentials stored on server\n- [ ] Quantum-resistant variant\n\n**User Experience:**\n- [ ] Seamless login flow\n- [ ] Hardware wallet support\n- [ ] Biometric integration\n\n## How To\n\n1. Install identity wallet\n2. Generate proof locally\n3. Submit proof to server\n4. Server verifies without learning secret\n5. Access granted"
+ "description": "## Overview\n\nImplement zero-knowledge proof authentication for privacy-preserving identity verification.\n\n## Value\n\nProves identity without revealing credentials. Ultimate privacy protection.\n\n## Prerequisites\n\n- ZK-SNARK library\n- Trusted setup ceremony\n- Browser extension or SDK\n\n## Done Criteria\n\n**Authentication:**\n- [ ] Generate ZK proof of password knowledge\n- [ ] Verify proof without seeing password\n- [ ] No credentials stored on server\n- [ ] Quantum-resistant variant\n\n**User Experience:**\n- [ ] Seamless login flow\n- [ ] Hardware wallet support\n- [ ] Biometric integration\n\n## How To\n\n1. Install identity wallet\n2. Generate proof locally\n3. Submit proof to server\n4. Server verifies without learning secret\n5. Access granted",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#311",
@@ -189,7 +219,10 @@
"created": "2026-03-11T10:00:00Z",
"updated": "2026-03-18T13:00:00Z"
},
- "body": "## Description\n\nAdd support for holographic displays and 3D interfaces.\n\n## Business Value\n\nFuture-proof interface. Competitive advantage with emerging display technology.\n\n## Setup\n\n- Looking Glass display hardware\n- Holographic rendering library\n- Depth map generation\n\n## Acceptance Criteria\n\n**3D Interface:**\n- [ ] Multi-layer depth rendering\n- [ ] Perspective-correct views\n- [ ] Interactive 3D objects\n- [ ] Gesture-based navigation\n\n**Content:**\n- [ ] 3D charts and graphs\n- [ ] Spatial data visualization\n- [ ] 3D model viewing"
+ "description": "## Description\n\nAdd support for holographic displays and 3D interfaces.\n\n## Business Value\n\nFuture-proof interface. Competitive advantage with emerging display technology.\n\n## Setup\n\n- Looking Glass display hardware\n- Holographic rendering library\n- Depth map generation\n\n## Acceptance Criteria\n\n**3D Interface:**\n- [ ] Multi-layer depth rendering\n- [ ] Perspective-correct views\n- [ ] Interactive 3D objects\n- [ ] Gesture-based navigation\n\n**Content:**\n- [ ] 3D charts and graphs\n- [ ] Spatial data visualization\n- [ ] 3D model viewing",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#312",
@@ -204,7 +237,10 @@
"created": "2026-03-12T11:30:00Z",
"updated": "2026-03-17T15:00:00Z"
},
- "body": "## Summary\n\nImplement satellite communication as backup for internet outages.\n\n## Why\n\nEnsures service availability during natural disasters or infrastructure failures.\n\n## Prerequisites\n\n- Starlink or similar satellite service\n- Ground station equipment\n- Automatic failover system\n\n## Acceptance Criteria\n\n**Backup Communication:**\n- [ ] Automatic detection of primary internet failure\n- [ ] Seamless failover to satellite\n- [ ] Critical services remain available\n- [ ] Fallback within 60 seconds\n\n**Monitoring:**\n- [ ] Connection health dashboard\n- [ ] Failover alerts\n- [ ] Bandwidth usage tracking\n\n## Changes\n\nDeployed by infrastructure@example.com on 2026-03-17"
+ "description": "## Summary\n\nImplement satellite communication as backup for internet outages.\n\n## Why\n\nEnsures service availability during natural disasters or infrastructure failures.\n\n## Prerequisites\n\n- Starlink or similar satellite service\n- Ground station equipment\n- Automatic failover system\n\n## Acceptance Criteria\n\n**Backup Communication:**\n- [ ] Automatic detection of primary internet failure\n- [ ] Seamless failover to satellite\n- [ ] Critical services remain available\n- [ ] Fallback within 60 seconds\n\n**Monitoring:**\n- [ ] Connection health dashboard\n- [ ] Failover alerts\n- [ ] Bandwidth usage tracking\n\n## Changes\n\nDeployed by infrastructure@example.com on 2026-03-17",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
},
{
"id": "github:AbsaOSS/future-project#313",
@@ -218,7 +254,10 @@
"created": "2026-03-13T12:00:00Z",
"updated": "2026-03-13T12:00:00Z"
},
- "body": "Just plain text without any headings or markdown formatting."
+ "description": "Just plain text without any headings or markdown formatting.",
+ "business_value": null,
+ "preconditions": null,
+ "acceptance_criteria": null
}
]
-}
\ No newline at end of file
+}
diff --git a/tests/fixtures/golden/v1.0.0/expected_output.json b/tests/fixtures/golden/v1.0.0/expected_output.json
index 8fdd2ac..23f2d6b 100644
--- a/tests/fixtures/golden/v1.0.0/expected_output.json
+++ b/tests/fixtures/golden/v1.0.0/expected_output.json
@@ -4,59 +4,13 @@
{
"id": "github:AbsaOSS/living-doc-project#101",
"sections": {
- "acceptance_criteria": [
- {
- "description": "User can click \"Sign in with SSO\" button on login page",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "User is redirected to SSO provider login page",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "After successful authentication, user is redirected back with valid session",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "User profile information is populated from SSO claims",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Session expires after 8 hours of inactivity",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Failed authentication shows appropriate error message",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Reduces password fatigue for users",
- "Improves security through centralized authentication",
- "Simplifies user onboarding and offboarding",
- "Enables enterprise-grade access control"
- ],
- "connections": "- Related to #102 (Multi-factor authentication)\n- Depends on #103 (User profile management)\n- See also: [Security Architecture](https://docs.example.com/security)",
- "description": "As a user, I want to authenticate using Single Sign-On (SSO) so that I can access the application securely using my corporate credentials.",
- "last_edited": "Last updated by alice@example.com on 2026-01-20",
- "preconditions": [
- "SSO provider (Azure AD, Okta, or similar) must be configured",
- "OAuth 2.0 client credentials must be obtained",
- "Callback URLs must be registered with the provider"
- ],
- "user_guide": "### For End Users\n\n1. Navigate to the login page\n2. Click the \"Sign in with SSO\" button\n3. Enter your corporate email on the SSO provider page\n4. Complete two-factor authentication if required\n5. You will be automatically redirected to the dashboard\n\n### For Administrators\n\nConfiguration steps:\n```yaml\nsso:\n provider: azure-ad\n client_id: ${SSO_CLIENT_ID}\n client_secret: ${SSO_CLIENT_SECRET}\n tenant_id: ${TENANT_ID}\n```"
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Description\n\nAs a user, I want to authenticate using Single Sign-On (SSO) so that I can access the application securely using my corporate credentials.\n\n## Business Value\n\n- Reduces password fatigue for users\n- Improves security through centralized authentication\n- Simplifies user onboarding and offboarding\n- Enables enterprise-grade access control\n\n## Preconditions\n\n- SSO provider (Azure AD, Okta, or similar) must be configured\n- OAuth 2.0 client credentials must be obtained\n- Callback URLs must be registered with the provider\n\n## Acceptance Criteria\n\n- [ ] User can click \"Sign in with SSO\" button on login page\n- [ ] User is redirected to SSO provider login page\n- [ ] After successful authentication, user is redirected back with valid session\n- [ ] User profile information is populated from SSO claims\n- [ ] Session expires after 8 hours of inactivity\n- [ ] Failed authentication shows appropriate error message\n\n## User Guide\n\n### For End Users\n\n1. Navigate to the login page\n2. Click the \"Sign in with SSO\" button\n3. Enter your corporate email on the SSO provider page\n4. Complete two-factor authentication if required\n5. You will be automatically redirected to the dashboard\n\n### For Administrators\n\nConfiguration steps:\n```yaml\nsso:\n provider: azure-ad\n client_id: ${SSO_CLIENT_ID}\n client_secret: ${SSO_CLIENT_SECRET}\n tenant_id: ${TENANT_ID}\n```\n\n## Connections\n\n- Related to #102 (Multi-factor authentication)\n- Depends on #103 (User profile management)\n- See also: [Security Architecture](https://docs.example.com/security)\n\n## Last Edited\n\nLast updated by alice@example.com on 2026-01-20",
+ "last_edited": null,
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -74,24 +28,13 @@
{
"id": "github:AbsaOSS/living-doc-project#102",
"sections": {
- "acceptance_criteria": [
- {
- "description": "1. **TOTP Setup**\n - User can enable MFA from account settings\n - QR code is displayed for authenticator app setup\n - Backup codes are generated (10 codes)\n \n2. **Login Flow**\n - After password validation, MFA code prompt appears\n - User enters 6-digit code from authenticator app\n - Code must be valid within 30-second window\n - 3 failed attempts lock account for 15 minutes\n\n3. **Recovery Options**\n - User can use backup codes when authenticator unavailable\n - Admin can reset MFA for user accounts\n - SMS fallback option for verified phone numbers",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "MFA significantly reduces the risk of unauthorized access even if passwords are compromised. Industry best practice and compliance requirement for enterprise applications."
- ],
- "connections": "- #101 - SSO authentication (works together with MFA)\n- #104 - Session management\n- External: [OWASP MFA Guidelines](https://owasp.org/mfa)",
- "description": "Enhance security by implementing multi-factor authentication (MFA) for all user accounts.",
- "last_edited": "Modified by bob@example.com on 2026-01-21 - Added SMS fallback requirement",
- "preconditions": [
- "| Component | Requirement | Status |\n|-----------|-------------|--------|\n| TOTP Library | pyotp >= 2.8.0 | ✅ Available |\n| QR Code Generator | qrcode >= 7.4.0 | ✅ Available |\n| SMS Gateway | Twilio API | ⚠️ Pending setup |"
- ],
- "user_guide": "Enabling MFA:\n1. Go to Profile → Security Settings\n2. Click \"Enable Two-Factor Authentication\"\n3. Scan QR code with Google Authenticator, Authy, or similar app\n4. Enter verification code to confirm\n5. Save backup codes in secure location"
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Overview\n\nEnhance security by implementing multi-factor authentication (MFA) for all user accounts.\n\n## Why\n\nMFA significantly reduces the risk of unauthorized access even if passwords are compromised. Industry best practice and compliance requirement for enterprise applications.\n\n## Setup\n\n| Component | Requirement | Status |\n|-----------|-------------|--------|\n| TOTP Library | pyotp >= 2.8.0 | \u2705 Available |\n| QR Code Generator | qrcode >= 7.4.0 | \u2705 Available |\n| SMS Gateway | Twilio API | \u26a0\ufe0f Pending setup |\n\n## AC\n\n1. **TOTP Setup**\n - User can enable MFA from account settings\n - QR code is displayed for authenticator app setup\n - Backup codes are generated (10 codes)\n \n2. **Login Flow**\n - After password validation, MFA code prompt appears\n - User enters 6-digit code from authenticator app\n - Code must be valid within 30-second window\n - 3 failed attempts lock account for 15 minutes\n\n3. **Recovery Options**\n - User can use backup codes when authenticator unavailable\n - Admin can reset MFA for user accounts\n - SMS fallback option for verified phone numbers\n\n## Instructions\n\nEnabling MFA:\n1. Go to Profile \u2192 Security Settings\n2. Click \"Enable Two-Factor Authentication\"\n3. Scan QR code with Google Authenticator, Authy, or similar app\n4. Enter verification code to confirm\n5. Save backup codes in secure location\n\n## Related\n\n- #101 - SSO authentication (works together with MFA)\n- #104 - Session management\n- External: [OWASP MFA Guidelines](https://owasp.org/mfa)\n\n## Changes\n\nModified by bob@example.com on 2026-01-21 - Added SMS fallback requirement",
+ "last_edited": null,
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -109,50 +52,13 @@
{
"id": "github:AbsaOSS/living-doc-project#103",
"sections": {
- "acceptance_criteria": [
- {
- "description": "User can view current profile information (name, email, avatar)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "User can edit profile fields with inline validation",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Changes are saved immediately with visual feedback",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Profile picture upload supports JPEG, PNG (max 5MB)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Audit log tracks all profile changes",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Empowers users to manage their own information, reducing support burden and improving user satisfaction."
- ],
- "connections": "Related issues: #101, #105\nDesign mockups: [Figma Link](https://figma.com/file/xyz)",
- "description": "Create a user profile management dashboard where users can view and edit their account information.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Summary\n\nCreate a user profile management dashboard where users can view and edit their account information.\n\n## Value\n\nEmpowers users to manage their own information, reducing support burden and improving user satisfaction.\n\n## Prerequisites\n\n- User authentication must be implemented (#101)\n- Database schema for user profiles must be ready\n- Frontend framework (React) must be set up\n\n## Done Criteria\n\n- User can view current profile information (name, email, avatar)\n- User can edit profile fields with inline validation\n- Changes are saved immediately with visual feedback\n- Profile picture upload supports JPEG, PNG (max 5MB)\n- Audit log tracks all profile changes\n\n## How To\n\n**Accessing Your Profile:**\n- Click your avatar in top-right corner\n- Select \"Profile Settings\" from dropdown menu\n\n**Editing Information:**\n- Click the edit icon next to any field\n- Make your changes\n- Changes save automatically when you click outside the field\n\n**Profile Picture:**\n- Click \"Change Photo\" button\n- Select image file (JPEG or PNG, under 5MB)\n- Crop if desired, then confirm\n\n## Links\n\nRelated issues: #101, #105\nDesign mockups: [Figma Link](https://figma.com/file/xyz)",
"last_edited": null,
- "preconditions": [
- "User authentication must be implemented (#101)",
- "Database schema for user profiles must be ready",
- "Frontend framework (React) must be set up"
- ],
- "user_guide": "**Accessing Your Profile:**\n- Click your avatar in top-right corner\n- Select \"Profile Settings\" from dropdown menu\n\n**Editing Information:**\n- Click the edit icon next to any field\n- Make your changes\n- Changes save automatically when you click outside the field\n\n**Profile Picture:**\n- Click \"Change Photo\" button\n- Select image file (JPEG or PNG, under 5MB)\n- Crop if desired, then confirm"
+ "preconditions": null,
+ "user_guide": null
},
"state": "closed",
"tags": [
@@ -169,55 +75,10 @@
{
"id": "github:AbsaOSS/living-doc-project#104",
"sections": {
- "acceptance_criteria": [
- {
- "description": "Session tokens use UUID v4 format",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Tokens are stored with expiration timestamp in Redis",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Cookie flags: httpOnly=true, secure=true, sameSite=strict",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Activity within 30 minutes extends session by configured timeout",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Expired sessions return 401 Unauthorized",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "User can view and revoke active sessions from settings",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Session data includes: IP address, user agent, last activity timestamp",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Balances security and user convenience. Automatic timeout prevents unauthorized access from unattended sessions while activity tracking minimizes disruption to active users."
- ],
- "connections": "Blocks: #102 (MFA needs session management)\nRelated: #101 (SSO creates sessions)",
- "description": "Implement secure session management with configurable timeout and automatic cleanup.\n\nKey requirements:\n- Session tokens stored securely (httpOnly, secure, sameSite cookies)\n- Configurable session timeout (default: 8 hours)\n- Automatic session extension on user activity\n- Session cleanup job runs every hour\n- Multiple concurrent sessions per user (max 5)",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Description\n\nImplement secure session management with configurable timeout and automatic cleanup.\n\nKey requirements:\n- Session tokens stored securely (httpOnly, secure, sameSite cookies)\n- Configurable session timeout (default: 8 hours)\n- Automatic session extension on user activity\n- Session cleanup job runs every hour\n- Multiple concurrent sessions per user (max 5)\n\n## Business Value\n\nBalances security and user convenience. Automatic timeout prevents unauthorized access from unattended sessions while activity tracking minimizes disruption to active users.\n\n## Acceptance Criteria\n\n- [ ] Session tokens use UUID v4 format\n- [ ] Tokens are stored with expiration timestamp in Redis\n- [ ] Cookie flags: httpOnly=true, secure=true, sameSite=strict\n- [ ] Activity within 30 minutes extends session by configured timeout\n- [ ] Expired sessions return 401 Unauthorized\n- [ ] User can view and revoke active sessions from settings\n- [ ] Session data includes: IP address, user agent, last activity timestamp\n\n## Connections\n\nBlocks: #102 (MFA needs session management)\nRelated: #101 (SSO creates sessions)",
"last_edited": null,
"preconditions": null,
"user_guide": null
@@ -237,88 +98,13 @@
{
"id": "github:AbsaOSS/living-doc-project#105",
"sections": {
- "acceptance_criteria": [
- {
- "description": "Settings page with notification preferences UI",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Toggle switches for each notification type:",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Email notifications (on/off)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "In-app notifications (on/off)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Push notifications (on/off)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Granular controls:",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Security alerts (always on)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Account updates (optional)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Marketing emails (optional)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Weekly digest (optional)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Preferences saved per user in database",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Notification service respects user preferences before sending",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Users can control communication frequency and channels, reducing notification fatigue and improving engagement."
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Allow users to configure their notification preferences for email, in-app, and mobile push notifications.",
+ "description": "## Overview\n\nAllow users to configure their notification preferences for email, in-app, and mobile push notifications.\n\n## Value\n\nUsers can control communication frequency and channels, reducing notification fatigue and improving engagement.\n\n## Done Criteria\n\n- Settings page with notification preferences UI\n- Toggle switches for each notification type:\n - Email notifications (on/off)\n - In-app notifications (on/off) \n - Push notifications (on/off)\n- Granular controls:\n - Security alerts (always on)\n - Account updates (optional)\n - Marketing emails (optional)\n - Weekly digest (optional)\n- Preferences saved per user in database\n- Notification service respects user preferences before sending\n\n## User Guide\n\n1. Navigate to Settings \u2192 Notifications\n2. Toggle switches for desired notification types\n3. Expand \"Advanced Options\" for granular controls\n4. Click \"Save Preferences\" (auto-saves enabled by default)",
"last_edited": null,
"preconditions": null,
- "user_guide": "1. Navigate to Settings → Notifications\n2. Toggle switches for desired notification types\n3. Expand \"Advanced Options\" for granular controls\n4. Click \"Save Preferences\" (auto-saves enabled by default)"
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -335,42 +121,11 @@
{
"id": "github:AbsaOSS/living-doc-project#106",
"sections": {
- "acceptance_criteria": [
- {
- "description": "Fix offset calculation in all paginated endpoints",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Add unit tests for pagination edge cases",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Test with page sizes: 5, 10, 20, 50, 100",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Verify page 1 shows items 1-N",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Verify page 2 shows items N+1 to 2N",
- "id": null,
- "state": null,
- "version": null
- }
- ],
+ "acceptance_criteria": null,
"business_value": null,
"connections": null,
- "description": "Pagination offset calculation is incorrect when page size is not 10. This causes wrong results to be displayed on pages 2+.\n\n**Bug Details:**\n```python\n# Current (wrong):\noffset = page * page_size\n\n# Should be:\noffset = (page - 1) * page_size\n```",
- "last_edited": "Fixed and deployed by charlie@example.com on 2026-01-26",
+ "description": "## Description\n\nPagination offset calculation is incorrect when page size is not 10. This causes wrong results to be displayed on pages 2+.\n\n**Bug Details:**\n```python\n# Current (wrong):\noffset = page * page_size\n\n# Should be:\noffset = (page - 1) * page_size\n```\n\n## Acceptance Criteria\n\n- [ ] Fix offset calculation in all paginated endpoints\n- [ ] Add unit tests for pagination edge cases\n- [ ] Test with page sizes: 5, 10, 20, 50, 100\n- [ ] Verify page 1 shows items 1-N\n- [ ] Verify page 2 shows items N+1 to 2N\n\n## Last Edited\n\nFixed and deployed by charlie@example.com on 2026-01-26",
+ "last_edited": null,
"preconditions": null,
"user_guide": null
},
@@ -389,26 +144,13 @@
{
"id": "github:AbsaOSS/living-doc-project#107",
"sections": {
- "acceptance_criteria": [
- {
- "description": "OpenAPI Specification:\n- [ ] All endpoints documented with descriptions\n- [ ] Request/response schemas defined\n- [ ] Authentication requirements specified\n- [ ] Example requests and responses included\n- [ ] Error responses documented (4xx, 5xx)\n\nSwagger UI:\n- [ ] Accessible at /api/docs endpoint\n- [ ] Interactive \"Try it out\" functionality works\n- [ ] Supports authentication token input\n- [ ] Auto-generated from code annotations",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "API documentation improves developer experience and reduces integration time for external teams."
- ],
- "connections": "- #108 - API versioning strategy\n- #109 - Rate limiting\n- External docs: [OpenAPI Specification](https://swagger.io/specification/)",
- "description": "Generate comprehensive API documentation using OpenAPI 3.0 specification and Swagger UI.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Summary\n\nGenerate comprehensive API documentation using OpenAPI 3.0 specification and Swagger UI.\n\n## Why\n\nAPI documentation improves developer experience and reduces integration time for external teams.\n\n## Preconditions\n\n- API endpoints must be stable and versioned (v1)\n- FastAPI/Flask framework must support OpenAPI decorators\n- Hosting environment for Swagger UI docs\n\n## Acceptance Criteria\n\nOpenAPI Specification:\n- [ ] All endpoints documented with descriptions\n- [ ] Request/response schemas defined\n- [ ] Authentication requirements specified\n- [ ] Example requests and responses included\n- [ ] Error responses documented (4xx, 5xx)\n\nSwagger UI:\n- [ ] Accessible at /api/docs endpoint\n- [ ] Interactive \"Try it out\" functionality works\n- [ ] Supports authentication token input\n- [ ] Auto-generated from code annotations\n\n## Instructions\n\nAccessing API Docs:\n- Navigate to https://api.example.com/docs\n- Click \"Authorize\" button to enter API token\n- Expand any endpoint to see details\n- Click \"Try it out\" to test directly from browser\n\n## Related\n\n- #108 - API versioning strategy\n- #109 - Rate limiting\n- External docs: [OpenAPI Specification](https://swagger.io/specification/)",
"last_edited": null,
- "preconditions": [
- "API endpoints must be stable and versioned (v1)",
- "FastAPI/Flask framework must support OpenAPI decorators",
- "Hosting environment for Swagger UI docs"
- ],
- "user_guide": "Accessing API Docs:\n- Navigate to https://api.example.com/docs\n- Click \"Authorize\" button to enter API token\n- Expand any endpoint to see details\n- Click \"Try it out\" to test directly from browser"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -425,26 +167,12 @@
{
"id": "github:AbsaOSS/living-doc-project#108",
"sections": {
- "acceptance_criteria": [
- {
- "description": "1. **Version Detection**\n - Requests to `/api/v1/users` route to v1 handler\n - Requests to `/api/v2/users` route to v2 handler\n - Missing version defaults to latest stable\n \n2. **Deprecation Handling**\n - Deprecated endpoints return `Deprecation` header\n - Header includes sunset date and migration guide URL\n - Deprecated endpoints logged for usage analytics\n\n3. **Documentation**\n - Version-specific documentation at /docs/v1, /docs/v2\n - Migration guide between versions\n - Changelog with breaking changes highlighted",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Enables API evolution without breaking existing integrations. Reduces coordination overhead for external teams consuming our API."
- ],
- "connections": "Related to #107 (API documentation)",
- "description": "Establish API versioning strategy to support backward compatibility and smooth migrations.\n\n**Proposed Approach:**\n- URL path versioning: `/api/v1/`, `/api/v2/`\n- Major version in path, minor/patch in header\n- Deprecation notices 3 months before removal\n- Support N-1 versions (current + previous)",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Description\n\nEstablish API versioning strategy to support backward compatibility and smooth migrations.\n\n**Proposed Approach:**\n- URL path versioning: `/api/v1/`, `/api/v2/`\n- Major version in path, minor/patch in header\n- Deprecation notices 3 months before removal\n- Support N-1 versions (current + previous)\n\n## Business Value\n\nEnables API evolution without breaking existing integrations. Reduces coordination overhead for external teams consuming our API.\n\n## Setup\n\n- Update API gateway routing rules\n- Implement version detection middleware\n- Create deprecation warning mechanism\n- Update CI/CD to deploy multiple versions\n\n## AC\n\n1. **Version Detection**\n - Requests to `/api/v1/users` route to v1 handler\n - Requests to `/api/v2/users` route to v2 handler\n - Missing version defaults to latest stable\n \n2. **Deprecation Handling**\n - Deprecated endpoints return `Deprecation` header\n - Header includes sunset date and migration guide URL\n - Deprecated endpoints logged for usage analytics\n\n3. **Documentation**\n - Version-specific documentation at /docs/v1, /docs/v2\n - Migration guide between versions\n - Changelog with breaking changes highlighted\n\n## Connections\n\nRelated to #107 (API documentation)",
"last_edited": null,
- "preconditions": [
- "Update API gateway routing rules",
- "Implement version detection middleware",
- "Create deprecation warning mechanism",
- "Update CI/CD to deploy multiple versions"
- ],
+ "preconditions": null,
"user_guide": null
},
"state": "open",
@@ -462,68 +190,11 @@
{
"id": "github:AbsaOSS/living-doc-project#109",
"sections": {
- "acceptance_criteria": [
- {
- "description": "Rate limiting based on IP address for anonymous users",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Rate limiting based on API token for authenticated users",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Redis backend for distributed rate limit tracking",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Response headers include rate limit info:",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "`X-RateLimit-Limit`: Total requests allowed",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "`X-RateLimit-Remaining`: Requests remaining",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "`X-RateLimit-Reset`: Unix timestamp when limit resets",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "429 Too Many Requests response when limit exceeded",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Retry-After header indicates wait time in seconds",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Protects API availability and performance. Prevents denial-of-service attacks and ensures quality of service for all users."
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Implement rate limiting to prevent API abuse and ensure fair resource allocation.\n\n**Rate Limits:**\n- Anonymous: 100 requests/hour\n- Authenticated: 1000 requests/hour\n- Premium tier: 5000 requests/hour",
- "last_edited": "Implemented by dave@example.com on 2026-01-27",
+ "description": "## Description\n\nImplement rate limiting to prevent API abuse and ensure fair resource allocation.\n\n**Rate Limits:**\n- Anonymous: 100 requests/hour\n- Authenticated: 1000 requests/hour\n- Premium tier: 5000 requests/hour\n\n## Value\n\nProtects API availability and performance. Prevents denial-of-service attacks and ensures quality of service for all users.\n\n## Acceptance Criteria\n\n- [ ] Rate limiting based on IP address for anonymous users\n- [ ] Rate limiting based on API token for authenticated users\n- [ ] Redis backend for distributed rate limit tracking\n- [ ] Response headers include rate limit info:\n - `X-RateLimit-Limit`: Total requests allowed\n - `X-RateLimit-Remaining`: Requests remaining\n - `X-RateLimit-Reset`: Unix timestamp when limit resets\n- [ ] 429 Too Many Requests response when limit exceeded\n- [ ] Retry-After header indicates wait time in seconds\n\n## History\n\nImplemented by dave@example.com on 2026-01-27",
+ "last_edited": null,
"preconditions": null,
"user_guide": null
},
@@ -542,31 +213,13 @@
{
"id": "github:AbsaOSS/living-doc-project#110",
"sections": {
- "acceptance_criteria": [
- {
- "description": "Pre-Migration:\n- [ ] Full database backup completed and verified\n- [ ] Staging migration successful with zero data loss\n- [ ] All application tests pass against PG 15\n- [ ] Migration runbook reviewed by team\n- [ ] Stakeholders notified of maintenance window\n\nMigration:\n- [ ] Enable read-only mode on current database\n- [ ] Dump database using pg_dump\n- [ ] Provision PostgreSQL 15 instance\n- [ ] Restore data to new instance\n- [ ] Run ANALYZE to update statistics\n- [ ] Update application connection strings\n- [ ] Verify data integrity checksums\n\nPost-Migration:\n- [ ] All application endpoints responding correctly\n- [ ] Monitor query performance for 24 hours\n- [ ] Keep PG 12 instance running for 1 week (rollback option)\n- [ ] Update documentation with new connection details",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "30% faster query performance (benchmark results)",
- "Improved JSON query capabilities",
- "Better index management",
- "Extended support lifecycle"
- ],
- "connections": "- Blocks #111 (performance optimization depends on PG 15 features)\n- Related: Infrastructure upgrade initiative",
- "description": "Migrate production database from PostgreSQL 12 to PostgreSQL 15 to leverage performance improvements and new features.",
- "last_edited": "Migration date updated by sre@example.com on 2026-01-26",
- "preconditions": [
- "Complete backup of production database",
- "Test migration on staging environment",
- "Verify application compatibility with PG 15",
- "Plan maintenance window (estimated 4 hours)",
- "Rollback procedure documented and tested"
- ],
- "user_guide": "**During Maintenance:**\n- Application will be read-only from 02:00-06:00 UTC on 2026-02-01\n- No action required from end users\n- Service will automatically reconnect after migration\n\n**For Developers:**\n- Update local connection strings to new instance\n- Connection pooling parameters may need adjustment\n- Review [PostgreSQL 15 release notes](https://www.postgresql.org/docs/15/release-15.html)"
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Overview\n\nMigrate production database from PostgreSQL 12 to PostgreSQL 15 to leverage performance improvements and new features.\n\n## Business Value\n\n- 30% faster query performance (benchmark results)\n- Improved JSON query capabilities\n- Better index management\n- Extended support lifecycle\n\n## Prerequisites\n\n- Complete backup of production database\n- Test migration on staging environment\n- Verify application compatibility with PG 15\n- Plan maintenance window (estimated 4 hours)\n- Rollback procedure documented and tested\n\n## Acceptance Criteria\n\nPre-Migration:\n- [ ] Full database backup completed and verified\n- [ ] Staging migration successful with zero data loss\n- [ ] All application tests pass against PG 15\n- [ ] Migration runbook reviewed by team\n- [ ] Stakeholders notified of maintenance window\n\nMigration:\n- [ ] Enable read-only mode on current database\n- [ ] Dump database using pg_dump\n- [ ] Provision PostgreSQL 15 instance\n- [ ] Restore data to new instance\n- [ ] Run ANALYZE to update statistics\n- [ ] Update application connection strings\n- [ ] Verify data integrity checksums\n\nPost-Migration:\n- [ ] All application endpoints responding correctly\n- [ ] Monitor query performance for 24 hours\n- [ ] Keep PG 12 instance running for 1 week (rollback option)\n- [ ] Update documentation with new connection details\n\n## User Guide\n\n**During Maintenance:**\n- Application will be read-only from 02:00-06:00 UTC on 2026-02-01\n- No action required from end users\n- Service will automatically reconnect after migration\n\n**For Developers:**\n- Update local connection strings to new instance\n- Connection pooling parameters may need adjustment\n- Review [PostgreSQL 15 release notes](https://www.postgresql.org/docs/15/release-15.html)\n\n## Connections\n\n- Blocks #111 (performance optimization depends on PG 15 features)\n- Related: Infrastructure upgrade initiative\n\n## Changes\n\nMigration date updated by sre@example.com on 2026-01-26",
+ "last_edited": null,
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -583,55 +236,12 @@
{
"id": "github:AbsaOSS/living-doc-project#111",
"sections": {
- "acceptance_criteria": [
- {
- "description": "Add composite index on users(email, active, created_at)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Add materialized view for dashboard analytics",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Implement query result caching (Redis, 5 min TTL)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Add database connection pooling (pgbouncer)",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "All target performance metrics achieved",
- "id": null,
- "state": null,
- "version": null
- },
- {
- "description": "Performance monitoring alerts configured",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Improved application responsiveness leads to better user experience and higher satisfaction scores."
- ],
- "connections": "Dependency: #110 (PostgreSQL 15 migration)",
- "description": "Optimize slow database queries identified in performance monitoring.\n\n**Problem Queries:**\n1. User search: 2.5s average response time\n2. Dashboard analytics: 4.1s average response time\n3. Report generation: 8.3s average response time\n\n**Target Performance:**\n1. User search: < 200ms\n2. Dashboard analytics: < 500ms\n3. Report generation: < 2s",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Description\n\nOptimize slow database queries identified in performance monitoring.\n\n**Problem Queries:**\n1. User search: 2.5s average response time\n2. Dashboard analytics: 4.1s average response time\n3. Report generation: 8.3s average response time\n\n**Target Performance:**\n1. User search: < 200ms\n2. Dashboard analytics: < 500ms\n3. Report generation: < 2s\n\n## Value\n\nImproved application responsiveness leads to better user experience and higher satisfaction scores.\n\n## Setup\n\n- PostgreSQL 15 migration must be completed (#110)\n- Enable query execution plan logging\n- Set up performance monitoring dashboard\n\n## Done Criteria\n\n- [ ] Add composite index on users(email, active, created_at)\n- [ ] Add materialized view for dashboard analytics\n- [ ] Implement query result caching (Redis, 5 min TTL)\n- [ ] Add database connection pooling (pgbouncer)\n- [ ] All target performance metrics achieved\n- [ ] Performance monitoring alerts configured\n\n## Related\n\nDependency: #110 (PostgreSQL 15 migration)",
"last_edited": null,
- "preconditions": [
- "PostgreSQL 15 migration must be completed (#110)",
- "Enable query execution plan logging",
- "Set up performance monitoring dashboard"
- ],
+ "preconditions": null,
"user_guide": null
},
"state": "open",
@@ -649,26 +259,13 @@
{
"id": "github:AbsaOSS/living-doc-project#112",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Events to Log:**\n- User authentication (login, logout, failed attempts)\n- Authorization changes (role assignments, permission changes)\n- Data access (view, export of sensitive data)\n- Data modifications (create, update, delete)\n- Administrative actions (user management, configuration changes)\n- Security events (password resets, MFA changes)\n\n**Log Format:**\n```json\n{\n \"timestamp\": \"2026-01-28T09:00:00Z\",\n \"event_type\": \"data_access\",\n \"actor\": \"user@example.com\",\n \"action\": \"export_report\",\n \"resource\": \"financial_report_2025\",\n \"ip_address\": \"192.168.1.100\",\n \"user_agent\": \"Mozilla/5.0...\",\n \"result\": \"success\",\n \"metadata\": {}\n}\n```\n\n**Requirements:**\n- [ ] Logs written to append-only storage\n- [ ] Tamper-proof audit trail (checksums)\n- [ ] Centralized log collection\n- [ ] 7-year retention for compliance logs\n- [ ] 90-day retention for operational logs\n- [ ] Search and reporting UI for security team\n- [ ] Automated alerts for suspicious patterns",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Regulatory compliance requires detailed audit trails. Security teams need visibility into user actions for threat detection and incident investigation."
- ],
- "connections": "- Related to #101, #102 (authentication/authorization events)\n- Related to compliance initiative\n- External: [OWASP Logging Guide](https://owasp.org/logging)",
- "description": "Implement comprehensive audit logging for compliance and security monitoring.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Overview\n\nImplement comprehensive audit logging for compliance and security monitoring.\n\n## Why\n\nRegulatory compliance requires detailed audit trails. Security teams need visibility into user actions for threat detection and incident investigation.\n\n## Preconditions\n\n- Log aggregation infrastructure (ELK or similar) must be ready\n- Data retention policy must be approved (7 years for financial data)\n- Log storage capacity planned and provisioned\n\n## AC\n\n**Events to Log:**\n- User authentication (login, logout, failed attempts)\n- Authorization changes (role assignments, permission changes)\n- Data access (view, export of sensitive data)\n- Data modifications (create, update, delete)\n- Administrative actions (user management, configuration changes)\n- Security events (password resets, MFA changes)\n\n**Log Format:**\n```json\n{\n \"timestamp\": \"2026-01-28T09:00:00Z\",\n \"event_type\": \"data_access\",\n \"actor\": \"user@example.com\",\n \"action\": \"export_report\",\n \"resource\": \"financial_report_2025\",\n \"ip_address\": \"192.168.1.100\",\n \"user_agent\": \"Mozilla/5.0...\",\n \"result\": \"success\",\n \"metadata\": {}\n}\n```\n\n**Requirements:**\n- [ ] Logs written to append-only storage\n- [ ] Tamper-proof audit trail (checksums)\n- [ ] Centralized log collection\n- [ ] 7-year retention for compliance logs\n- [ ] 90-day retention for operational logs\n- [ ] Search and reporting UI for security team\n- [ ] Automated alerts for suspicious patterns\n\n## User Guide\n\n**For Security Team:**\n- Access audit logs at /admin/audit\n- Search by user, event type, or date range\n- Export results to CSV for compliance reports\n\n**For Administrators:**\n- Configure retention policies in admin panel\n- Set up email alerts for critical events\n- Review daily summary reports\n\n## Connections\n\n- Related to #101, #102 (authentication/authorization events)\n- Related to compliance initiative\n- External: [OWASP Logging Guide](https://owasp.org/logging)",
"last_edited": null,
- "preconditions": [
- "Log aggregation infrastructure (ELK or similar) must be ready",
- "Data retention policy must be approved (7 years for financial data)",
- "Log storage capacity planned and provisioned"
- ],
- "user_guide": "**For Security Team:**\n- Access audit logs at /admin/audit\n- Search by user, event type, or date range\n- Export results to CSV for compliance reports\n\n**For Administrators:**\n- Configure retention policies in admin panel\n- Set up email alerts for critical events\n- Review daily summary reports"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -685,26 +282,13 @@
{
"id": "github:AbsaOSS/living-doc-project#113",
"sections": {
- "acceptance_criteria": [
- {
- "description": "Offline Capabilities:\n- [ ] View cached content (last 7 days)\n- [ ] Create new items (queued for sync)\n- [ ] Edit existing items (queued for sync)\n- [ ] Delete items (queued for sync)\n- [ ] Search within cached content\n\nSync Behavior:\n- [ ] Automatic sync when connectivity restored\n- [ ] Manual sync trigger in app\n- [ ] Conflict detection and resolution UI\n- [ ] Progress indicator during sync\n- [ ] Failed sync items highlighted for user review\n\nStorage:\n- [ ] Max 100MB local storage per user\n- [ ] Automatic cache cleanup (30 days)\n- [ ] User can manually clear cache",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Users can continue working during travel or in areas with poor connectivity. Improves user satisfaction and productivity."
- ],
- "connections": "Design: [Offline UX Mockups](https://figma.com/file/offline)\nRelated: #114 (sync optimization)",
- "description": "Enable offline mode in mobile app to allow users to work without internet connectivity.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Summary\n\nEnable offline mode in mobile app to allow users to work without internet connectivity.\n\n## Business Value\n\nUsers can continue working during travel or in areas with poor connectivity. Improves user satisfaction and productivity.\n\n## Prerequisites\n\n- Mobile app architecture supports local storage\n- Conflict resolution strategy defined\n- Background sync mechanism implemented\n\n## Done Criteria\n\nOffline Capabilities:\n- [ ] View cached content (last 7 days)\n- [ ] Create new items (queued for sync)\n- [ ] Edit existing items (queued for sync)\n- [ ] Delete items (queued for sync)\n- [ ] Search within cached content\n\nSync Behavior:\n- [ ] Automatic sync when connectivity restored\n- [ ] Manual sync trigger in app\n- [ ] Conflict detection and resolution UI\n- [ ] Progress indicator during sync\n- [ ] Failed sync items highlighted for user review\n\nStorage:\n- [ ] Max 100MB local storage per user\n- [ ] Automatic cache cleanup (30 days)\n- [ ] User can manually clear cache\n\n## How To\n\n**Using Offline Mode:**\n1. Ensure app is synced while online\n2. App automatically detects loss of connectivity\n3. Offline indicator appears in header\n4. Continue working - changes saved locally\n5. When back online, app syncs automatically\n6. Review \"Sync Status\" to see pending changes\n\n**Handling Conflicts:**\n- If conflict detected, app shows both versions\n- User chooses which version to keep\n- Option to merge changes manually\n\n## Links\n\nDesign: [Offline UX Mockups](https://figma.com/file/offline)\nRelated: #114 (sync optimization)",
"last_edited": null,
- "preconditions": [
- "Mobile app architecture supports local storage",
- "Conflict resolution strategy defined",
- "Background sync mechanism implemented"
- ],
- "user_guide": "**Using Offline Mode:**\n1. Ensure app is synced while online\n2. App automatically detects loss of connectivity\n3. Offline indicator appears in header\n4. Continue working - changes saved locally\n5. When back online, app syncs automatically\n6. Review \"Sync Status\" to see pending changes\n\n**Handling Conflicts:**\n- If conflict detected, app shows both versions\n- User chooses which version to keep\n- Option to merge changes manually"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
diff --git a/tests/fixtures/golden/v1.2.0/expected_output.json b/tests/fixtures/golden/v1.2.0/expected_output.json
index ed741a5..afe2339 100644
--- a/tests/fixtures/golden/v1.2.0/expected_output.json
+++ b/tests/fixtures/golden/v1.2.0/expected_output.json
@@ -4,30 +4,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#201",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Connection Management:**\n- [ ] Client establishes WebSocket connection on login\n- [ ] Connection survives network hiccups (auto-reconnect)\n- [ ] Heartbeat ping/pong every 30 seconds\n- [ ] Graceful fallback to polling if WebSocket unavailable\n\n**Notification Types:**\n- [ ] New message received\n- [ ] Task assignment\n- [ ] Status change on watched items\n- [ ] System announcements\n- [ ] Collaboration updates (user joined, user typing)\n\n**Performance:**\n- [ ] Support 10,000 concurrent connections per server\n- [ ] Message delivery latency < 100ms\n- [ ] CPU usage < 20% at max capacity",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Eliminates need for page refreshing",
- "Improves user engagement and responsiveness",
- "Reduces server load from polling",
- "Enables collaborative features"
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Implement real-time push notifications using WebSocket connections to provide instant updates to users.",
+ "description": "## Description\n\nImplement real-time push notifications using WebSocket connections to provide instant updates to users.\n\n## Business Value\n\n- Eliminates need for page refreshing\n- Improves user engagement and responsiveness\n- Reduces server load from polling\n- Enables collaborative features\n\n## Preconditions\n\n- WebSocket infrastructure must be available (Socket.io or similar)\n- Redis pub/sub for message distribution across servers\n- Load balancer must support WebSocket connections\n- Client library for WebSocket management\n\n## Acceptance Criteria\n\n**Connection Management:**\n- [ ] Client establishes WebSocket connection on login\n- [ ] Connection survives network hiccups (auto-reconnect)\n- [ ] Heartbeat ping/pong every 30 seconds\n- [ ] Graceful fallback to polling if WebSocket unavailable\n\n**Notification Types:**\n- [ ] New message received\n- [ ] Task assignment\n- [ ] Status change on watched items\n- [ ] System announcements\n- [ ] Collaboration updates (user joined, user typing)\n\n**Performance:**\n- [ ] Support 10,000 concurrent connections per server\n- [ ] Message delivery latency < 100ms\n- [ ] CPU usage < 20% at max capacity\n\n## User Guide\n\n**For End Users:**\nNotifications appear automatically - no configuration needed!\n- Toast notification appears in bottom-right corner\n- Click notification to navigate to relevant item\n- Bell icon shows notification count\n- Notification center lists all recent notifications\n\n**For Developers:**\n```javascript\n// Subscribe to notifications\nconst socket = io();\nsocket.on('notification', (data) => {\n showToast(data.message, data.type);\n});\n```",
"last_edited": null,
- "preconditions": [
- "WebSocket infrastructure must be available (Socket.io or similar)",
- "Redis pub/sub for message distribution across servers",
- "Load balancer must support WebSocket connections",
- "Client library for WebSocket management"
- ],
- "user_guide": "**For End Users:**\nNotifications appear automatically - no configuration needed!\n- Toast notification appears in bottom-right corner\n- Click notification to navigate to relevant item\n- Bell icon shows notification count\n- Notification center lists all recent notifications\n\n**For Developers:**\n```javascript\n// Subscribe to notifications\nconst socket = io();\nsocket.on('notification', (data) => {\n showToast(data.message, data.type);\n});\n```"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -45,24 +28,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#202",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Supported Formats:**\n- [ ] CSV - comma-separated values\n- [ ] XLSX - Excel workbook with formatting\n- [ ] JSON - structured data export\n- [ ] PDF - formatted report with branding\n\n**Export Options:**\n- [ ] Select fields to include/exclude\n- [ ] Date range filter\n- [ ] Sort order selection\n- [ ] Page orientation for PDF (portrait/landscape)\n\n**Implementation:**\n- [ ] Export button in list views\n- [ ] Format selection dropdown\n- [ ] Background job for large exports\n- [ ] Email notification when export ready\n- [ ] Download link expires after 24 hours",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Users need data in different formats for reporting, analysis, and integration with other tools."
- ],
- "connections": "- #203 - Report scheduling\n- #204 - Data import",
- "description": "Enable users to export data in multiple formats: CSV, Excel, JSON, and PDF.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Overview\n\nEnable users to export data in multiple formats: CSV, Excel, JSON, and PDF.\n\n## Why\n\nUsers need data in different formats for reporting, analysis, and integration with other tools.\n\n## Setup\n\n| Format | Library | Version |\n|--------|---------|--------|\n| CSV | csv | stdlib |\n| Excel | openpyxl | 3.1.0 |\n| JSON | json | stdlib |\n| PDF | reportlab | 4.0.0 |\n\n## AC\n\n**Supported Formats:**\n- [ ] CSV - comma-separated values\n- [ ] XLSX - Excel workbook with formatting\n- [ ] JSON - structured data export\n- [ ] PDF - formatted report with branding\n\n**Export Options:**\n- [ ] Select fields to include/exclude\n- [ ] Date range filter\n- [ ] Sort order selection\n- [ ] Page orientation for PDF (portrait/landscape)\n\n**Implementation:**\n- [ ] Export button in list views\n- [ ] Format selection dropdown\n- [ ] Background job for large exports\n- [ ] Email notification when export ready\n- [ ] Download link expires after 24 hours\n\n## Instructions\n\n1. Navigate to any list view (users, reports, etc.)\n2. Click \"Export\" button in toolbar\n3. Select desired format from dropdown\n4. Configure export options (optional)\n5. Click \"Generate Export\"\n6. For small exports: Download starts immediately\n7. For large exports: Email sent when ready\n\n## Related\n\n- #203 - Report scheduling\n- #204 - Data import",
"last_edited": null,
- "preconditions": [
- "| Format | Library | Version |\n|--------|---------|--------|\n| CSV | csv | stdlib |\n| Excel | openpyxl | 3.1.0 |\n| JSON | json | stdlib |\n| PDF | reportlab | 4.0.0 |"
- ],
- "user_guide": "1. Navigate to any list view (users, reports, etc.)\n2. Click \"Export\" button in toolbar\n3. Select desired format from dropdown\n4. Configure export options (optional)\n5. Click \"Generate Export\"\n6. For small exports: Download starts immediately\n7. For large exports: Email sent when ready"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -79,27 +51,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#203",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Scheduling Options:**\n- [ ] Daily, weekly, monthly schedules\n- [ ] Custom cron expressions for advanced users\n- [ ] Timezone selection\n- [ ] Start date and optional end date\n\n**Delivery Methods:**\n- [ ] Email with PDF attachment\n- [ ] Email with download link\n- [ ] Save to cloud storage (S3, GCS)\n- [ ] Webhook notification\n\n**Report Types:**\n- [ ] User activity summary\n- [ ] Performance metrics\n- [ ] Compliance audit report\n- [ ] Custom report builder",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Saves time and ensures stakeholders receive regular updates without manual intervention."
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Allow users to schedule automatic report generation and delivery.",
- "last_edited": "Implemented by emma@example.com on 2026-02-12",
- "preconditions": [
- "Celery or similar job scheduler configured",
- "Email delivery system operational",
- "Report templates defined",
- "Cloud storage for report archives"
- ],
- "user_guide": "**Creating a Scheduled Report:**\n1. Go to Reports → Scheduled Reports\n2. Click \"New Schedule\"\n3. Select report type and parameters\n4. Choose frequency (daily/weekly/monthly)\n5. Add email recipients\n6. Click \"Create Schedule\"\n\n**Managing Schedules:**\n- View all schedules in dashboard\n- Pause/resume schedules\n- Edit schedule parameters\n- View execution history\n- Download previous reports"
+ "description": "## Summary\n\nAllow users to schedule automatic report generation and delivery.\n\n## Value\n\nSaves time and ensures stakeholders receive regular updates without manual intervention.\n\n## Prerequisites\n\n- Celery or similar job scheduler configured\n- Email delivery system operational\n- Report templates defined\n- Cloud storage for report archives\n\n## Done Criteria\n\n**Scheduling Options:**\n- [ ] Daily, weekly, monthly schedules\n- [ ] Custom cron expressions for advanced users\n- [ ] Timezone selection\n- [ ] Start date and optional end date\n\n**Delivery Methods:**\n- [ ] Email with PDF attachment\n- [ ] Email with download link\n- [ ] Save to cloud storage (S3, GCS)\n- [ ] Webhook notification\n\n**Report Types:**\n- [ ] User activity summary\n- [ ] Performance metrics\n- [ ] Compliance audit report\n- [ ] Custom report builder\n\n## How To\n\n**Creating a Scheduled Report:**\n1. Go to Reports \u2192 Scheduled Reports\n2. Click \"New Schedule\"\n3. Select report type and parameters\n4. Choose frequency (daily/weekly/monthly)\n5. Add email recipients\n6. Click \"Create Schedule\"\n\n**Managing Schedules:**\n- View all schedules in dashboard\n- Pause/resume schedules\n- Edit schedule parameters\n- View execution history\n- Download previous reports\n\n## History\n\nImplemented by emma@example.com on 2026-02-12",
+ "last_edited": null,
+ "preconditions": null,
+ "user_guide": null
},
"state": "closed",
"tags": [
@@ -116,22 +74,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#204",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**File Upload:**\n- [ ] Drag-and-drop file upload\n- [ ] Support CSV and XLSX formats\n- [ ] Max file size: 10MB\n- [ ] Sample templates available for download\n\n**Validation:**\n- [ ] Column header mapping (automatic + manual)\n- [ ] Data type validation\n- [ ] Required field checking\n- [ ] Duplicate detection\n- [ ] Preview before import (first 10 rows)\n\n**Processing:**\n- [ ] Background processing for large files\n- [ ] Progress bar with percentage complete\n- [ ] Partial import on errors (continue processing valid rows)\n- [ ] Detailed error report for failed rows\n- [ ] Transaction rollback option\n\n**Error Handling:**\n- [ ] Download error report as CSV\n- [ ] Highlight problematic rows and columns\n- [ ] Suggested fixes for common errors\n- [ ] Ability to fix and re-upload",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Accelerates data migration and initial setup. Reduces manual data entry errors and saves hours of work."
- ],
- "connections": "Related to #202 (data export)",
- "description": "Enable bulk import of data from CSV and Excel files with validation and error handling.\n\n**Supported Entities:**\n- Users (bulk user provisioning)\n- Products (inventory updates)\n- Transactions (bulk data entry)\n- Configuration settings",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Description\n\nEnable bulk import of data from CSV and Excel files with validation and error handling.\n\n**Supported Entities:**\n- Users (bulk user provisioning)\n- Products (inventory updates)\n- Transactions (bulk data entry)\n- Configuration settings\n\n## Business Value\n\nAccelerates data migration and initial setup. Reduces manual data entry errors and saves hours of work.\n\n## Acceptance Criteria\n\n**File Upload:**\n- [ ] Drag-and-drop file upload\n- [ ] Support CSV and XLSX formats\n- [ ] Max file size: 10MB\n- [ ] Sample templates available for download\n\n**Validation:**\n- [ ] Column header mapping (automatic + manual)\n- [ ] Data type validation\n- [ ] Required field checking\n- [ ] Duplicate detection\n- [ ] Preview before import (first 10 rows)\n\n**Processing:**\n- [ ] Background processing for large files\n- [ ] Progress bar with percentage complete\n- [ ] Partial import on errors (continue processing valid rows)\n- [ ] Detailed error report for failed rows\n- [ ] Transaction rollback option\n\n**Error Handling:**\n- [ ] Download error report as CSV\n- [ ] Highlight problematic rows and columns\n- [ ] Suggested fixes for common errors\n- [ ] Ability to fix and re-upload\n\n## User Guide\n\n**Import Process:**\n1. Click \"Import\" button\n2. Select entity type (Users, Products, etc.)\n3. Download template file (optional)\n4. Upload your populated CSV/Excel file\n5. Map columns if automatic mapping fails\n6. Review preview of first 10 rows\n7. Click \"Import\" to process\n8. Monitor progress bar\n9. Review completion summary\n10. Download error report if needed\n\n**Template Format:**\nDownload templates include:\n- Required column headers\n- Example data rows\n- Data type comments\n- Validation rules\n\n## Connections\n\nRelated to #202 (data export)",
"last_edited": null,
"preconditions": null,
- "user_guide": "**Import Process:**\n1. Click \"Import\" button\n2. Select entity type (Users, Products, etc.)\n3. Download template file (optional)\n4. Upload your populated CSV/Excel file\n5. Map columns if automatic mapping fails\n6. Review preview of first 10 rows\n7. Click \"Import\" to process\n8. Monitor progress bar\n9. Review completion summary\n10. Download error report if needed\n\n**Template Format:**\nDownload templates include:\n- Required column headers\n- Example data rows\n- Data type comments\n- Validation rules"
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -148,27 +97,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#205",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Search Features:**\n- [ ] Full-text search across all text fields\n- [ ] Fuzzy matching (handle typos)\n- [ ] Phrase matching with quotes\n- [ ] Boolean operators (AND, OR, NOT)\n- [ ] Wildcard search (* and ?)\n- [ ] Field-specific search (title:\"example\")\n- [ ] Relevance-based ranking\n- [ ] Search suggestions (autocomplete)\n\n**Performance:**\n- [ ] Search response time < 200ms (p95)\n- [ ] Support 100 concurrent searches\n- [ ] Index size < 5GB\n\n**Indexing:**\n- [ ] Index users, documents, messages, products\n- [ ] Custom analyzers for each language\n- [ ] Configurable field weights for ranking",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Current search is slow on large datasets (3+ seconds)",
- "No support for fuzzy matching or typo tolerance",
- "Cannot search across multiple fields efficiently",
- "No relevance ranking"
- ],
- "connections": "- #206 - Search analytics\n- External: [Elasticsearch Guide](https://elastic.co/guide)",
- "description": "Replace basic SQL LIKE queries with Elasticsearch for powerful full-text search capabilities.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Overview\n\nReplace basic SQL LIKE queries with Elasticsearch for powerful full-text search capabilities.\n\n## Why\n\n- Current search is slow on large datasets (3+ seconds)\n- No support for fuzzy matching or typo tolerance\n- Cannot search across multiple fields efficiently\n- No relevance ranking\n\n## Setup\n\n**Infrastructure:**\n- Elasticsearch 8.x cluster (3 nodes)\n- Kibana for monitoring and debugging\n- Logstash for data synchronization\n- 100GB storage allocation\n\n**Data Synchronization:**\n- Full index rebuild: Weekly (Sunday 2 AM)\n- Incremental updates: Real-time via change data capture\n- Fallback: Hourly delta sync job\n\n## AC\n\n**Search Features:**\n- [ ] Full-text search across all text fields\n- [ ] Fuzzy matching (handle typos)\n- [ ] Phrase matching with quotes\n- [ ] Boolean operators (AND, OR, NOT)\n- [ ] Wildcard search (* and ?)\n- [ ] Field-specific search (title:\"example\")\n- [ ] Relevance-based ranking\n- [ ] Search suggestions (autocomplete)\n\n**Performance:**\n- [ ] Search response time < 200ms (p95)\n- [ ] Support 100 concurrent searches\n- [ ] Index size < 5GB\n\n**Indexing:**\n- [ ] Index users, documents, messages, products\n- [ ] Custom analyzers for each language\n- [ ] Configurable field weights for ranking\n\n## Instructions\n\n**Basic Search:**\n- Enter search terms in global search box\n- Press Enter or click search icon\n- Results appear with highlighting\n\n**Advanced Search:**\n- Use quotes for exact phrases: \"product manager\"\n- Combine terms: python AND django\n- Exclude terms: java NOT javascript \n- Field search: author:\"john doe\"\n- Wildcard: test*\n\n## Related\n\n- #206 - Search analytics\n- External: [Elasticsearch Guide](https://elastic.co/guide)",
"last_edited": null,
- "preconditions": [
- "**Infrastructure:**\n- Elasticsearch 8.x cluster (3 nodes)\n- Kibana for monitoring and debugging\n- Logstash for data synchronization\n- 100GB storage allocation\n\n**Data Synchronization:**\n- Full index rebuild: Weekly (Sunday 2 AM)\n- Incremental updates: Real-time via change data capture\n- Fallback: Hourly delta sync job"
- ],
- "user_guide": "**Basic Search:**\n- Enter search terms in global search box\n- Press Enter or click search icon\n- Results appear with highlighting\n\n**Advanced Search:**\n- Use quotes for exact phrases: \"product manager\"\n- Combine terms: python AND django\n- Exclude terms: java NOT javascript \n- Field search: author:\"john doe\"\n- Wildcard: test*"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -186,19 +121,10 @@
{
"id": "github:AbsaOSS/living-doc-v2#206",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Tracked Metrics:**\n- Search queries (text, filters, results count)\n- Click-through rate (which results users clicked)\n- Zero-result searches (queries with no results)\n- Search refinements (query modifications)\n- Time to click (search duration)\n- Abandoned searches\n\n**Analytics Dashboard:**\n- [ ] Top 100 search queries\n- [ ] Most clicked results\n- [ ] Zero-result query report\n- [ ] Search success rate\n- [ ] Average results per query\n- [ ] Search trends over time\n\n**Actionable Insights:**\n- [ ] Recommend synonyms for common searches\n- [ ] Identify content gaps (frequent zero-result queries)\n- [ ] Suggest popular content for homepage\n- [ ] Alert on broken links in search results",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Insights from search data help improve content organization, identify gaps, and optimize search algorithms."
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Track and analyze search behavior to improve search relevance and understand user needs.",
+ "description": "## Description\n\nTrack and analyze search behavior to improve search relevance and understand user needs.\n\n## Value\n\nInsights from search data help improve content organization, identify gaps, and optimize search algorithms.\n\n## Done Criteria\n\n**Tracked Metrics:**\n- Search queries (text, filters, results count)\n- Click-through rate (which results users clicked)\n- Zero-result searches (queries with no results)\n- Search refinements (query modifications)\n- Time to click (search duration)\n- Abandoned searches\n\n**Analytics Dashboard:**\n- [ ] Top 100 search queries\n- [ ] Most clicked results\n- [ ] Zero-result query report\n- [ ] Search success rate\n- [ ] Average results per query\n- [ ] Search trends over time\n\n**Actionable Insights:**\n- [ ] Recommend synonyms for common searches\n- [ ] Identify content gaps (frequent zero-result queries)\n- [ ] Suggest popular content for homepage\n- [ ] Alert on broken links in search results",
"last_edited": null,
"preconditions": null,
"user_guide": null
@@ -218,27 +144,12 @@
{
"id": "github:AbsaOSS/living-doc-v2#207",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Cache Layers:**\n\n1. **Page Cache** (TTL: 5 minutes)\n - Full HTML responses for public pages\n - Vary by user role for personalized pages\n \n2. **API Response Cache** (TTL: 2 minutes)\n - GET endpoints only\n - Cache key includes query parameters\n - Skip for user-specific data\n \n3. **Database Query Cache** (TTL: 10 minutes)\n - Expensive queries (joins, aggregations)\n - Reference data (countries, categories)\n \n4. **Session Cache** (TTL: 8 hours)\n - User sessions and authentication tokens\n - Shopping cart data\n\n**Cache Invalidation:**\n- [ ] Automatic invalidation on data updates\n- [ ] Manual cache clear for administrators\n- [ ] Tag-based invalidation (e.g., clear all user-related caches)\n- [ ] LRU eviction when memory limit reached\n\n**Monitoring:**\n- [ ] Cache hit/miss rate metrics\n- [ ] Memory usage dashboard\n- [ ] Slow cache operations alerts",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "70% reduction in database load",
- "5x faster page load times for cached content",
- "Better scalability and reduced infrastructure costs"
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Implement comprehensive caching strategy using Redis to improve application performance.",
- "last_edited": "Deployed by frank@example.com on 2026-02-16",
- "preconditions": [
- "Redis cluster deployed (3 nodes, master-replica setup)",
- "Cache warming strategy defined",
- "Cache invalidation events identified"
- ],
+ "description": "## Summary\n\nImplement comprehensive caching strategy using Redis to improve application performance.\n\n## Business Value\n\n- 70% reduction in database load\n- 5x faster page load times for cached content\n- Better scalability and reduced infrastructure costs\n\n## Prerequisites\n\n- Redis cluster deployed (3 nodes, master-replica setup)\n- Cache warming strategy defined\n- Cache invalidation events identified\n\n## Acceptance Criteria\n\n**Cache Layers:**\n\n1. **Page Cache** (TTL: 5 minutes)\n - Full HTML responses for public pages\n - Vary by user role for personalized pages\n \n2. **API Response Cache** (TTL: 2 minutes)\n - GET endpoints only\n - Cache key includes query parameters\n - Skip for user-specific data\n \n3. **Database Query Cache** (TTL: 10 minutes)\n - Expensive queries (joins, aggregations)\n - Reference data (countries, categories)\n \n4. **Session Cache** (TTL: 8 hours)\n - User sessions and authentication tokens\n - Shopping cart data\n\n**Cache Invalidation:**\n- [ ] Automatic invalidation on data updates\n- [ ] Manual cache clear for administrators\n- [ ] Tag-based invalidation (e.g., clear all user-related caches)\n- [ ] LRU eviction when memory limit reached\n\n**Monitoring:**\n- [ ] Cache hit/miss rate metrics\n- [ ] Memory usage dashboard\n- [ ] Slow cache operations alerts\n\n## History\n\nDeployed by frank@example.com on 2026-02-16",
+ "last_edited": null,
+ "preconditions": null,
"user_guide": null
},
"state": "closed",
@@ -256,24 +167,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#208",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Validation Rules:**\n\nEmail fields:\n- [ ] Valid email format\n- [ ] Domain whitelist (optional)\n- [ ] Max length: 254 characters\n\nText inputs:\n- [ ] HTML tag stripping or escaping\n- [ ] Max length enforcement\n- [ ] No control characters\n- [ ] Unicode normalization\n\nNumeric inputs:\n- [ ] Type validation (int vs float)\n- [ ] Range checking (min/max)\n- [ ] Precision limits\n\nFile uploads:\n- [ ] File extension whitelist\n- [ ] MIME type verification\n- [ ] File size limits\n- [ ] Malware scanning\n\n**Error Messages:**\n- [ ] User-friendly messages (not technical)\n- [ ] Field-specific errors\n- [ ] Multiple errors displayed together\n- [ ] No sensitive information in errors",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Security best practice. Prevents SQL injection, XSS, command injection, and ensures data integrity."
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Implement comprehensive server-side input validation to prevent injection attacks and data corruption.",
+ "description": "## Overview\n\nImplement comprehensive server-side input validation to prevent injection attacks and data corruption.\n\n## Why\n\nSecurity best practice. Prevents SQL injection, XSS, command injection, and ensures data integrity.\n\n## Setup\n\nValidation library setup:\n- Backend: Pydantic for Python, Joi for Node.js\n- Sanitization: bleach for HTML, validator.js for strings\n- Content Security Policy headers\n\n## AC\n\n**Validation Rules:**\n\nEmail fields:\n- [ ] Valid email format\n- [ ] Domain whitelist (optional)\n- [ ] Max length: 254 characters\n\nText inputs:\n- [ ] HTML tag stripping or escaping\n- [ ] Max length enforcement\n- [ ] No control characters\n- [ ] Unicode normalization\n\nNumeric inputs:\n- [ ] Type validation (int vs float)\n- [ ] Range checking (min/max)\n- [ ] Precision limits\n\nFile uploads:\n- [ ] File extension whitelist\n- [ ] MIME type verification\n- [ ] File size limits\n- [ ] Malware scanning\n\n**Error Messages:**\n- [ ] User-friendly messages (not technical)\n- [ ] Field-specific errors\n- [ ] Multiple errors displayed together\n- [ ] No sensitive information in errors\n\n## User Guide\n\nWhen validation fails:\n- Red border appears around invalid field\n- Error message appears below field\n- Submit button remains disabled\n- Fix all errors to enable submission",
"last_edited": null,
- "preconditions": [
- "Validation library setup:\n- Backend: Pydantic for Python, Joi for Node.js\n- Sanitization: bleach for HTML, validator.js for strings\n- Content Security Policy headers"
- ],
- "user_guide": "When validation fails:\n- Red border appears around invalid field\n- Error message appears below field\n- Submit button remains disabled\n- Fix all errors to enable submission"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -290,27 +190,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#209",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Consent Management:**\n- [ ] Cookie consent banner on first visit\n- [ ] Granular consent options (necessary, functional, marketing)\n- [ ] Easy withdrawal of consent\n- [ ] Consent records stored with timestamp\n- [ ] Opt-out links in all marketing emails\n\n**Data Subject Rights:**\n\n1. **Right to Access**\n - [ ] User can download all personal data (JSON format)\n - [ ] Data export includes: profile, activity logs, communications\n - [ ] Export generated within 24 hours\n \n2. **Right to Rectification** \n - [ ] User can edit profile information\n - [ ] Request form for data corrections\n \n3. **Right to Erasure (Right to be Forgotten)**\n - [ ] Account deletion request form\n - [ ] 30-day grace period before permanent deletion\n - [ ] Anonymize user data instead of deletion (where legally required to retain)\n - [ ] Email confirmation of deletion\n \n4. **Right to Data Portability**\n - [ ] Export data in machine-readable format (JSON/CSV)\n - [ ] Transfer data directly to another service (if possible)\n\n**Privacy by Design:**\n- [ ] Data minimization (collect only necessary data)\n- [ ] Pseudonymization of personal data in logs\n- [ ] Encryption at rest and in transit\n- [ ] Access controls and audit logging\n- [ ] Automated data retention policies",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Legal requirement for EU users. Builds trust and demonstrates commitment to privacy. Avoids hefty fines (up to 4% of revenue)."
- ],
- "connections": "- Related to #112 (audit logging)\n- Related to legal compliance initiative",
- "description": "Implement features required for GDPR compliance including consent management, data portability, and right to be forgotten.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Description\n\nImplement features required for GDPR compliance including consent management, data portability, and right to be forgotten.\n\n## Business Value\n\nLegal requirement for EU users. Builds trust and demonstrates commitment to privacy. Avoids hefty fines (up to 4% of revenue).\n\n## Preconditions\n\n- Legal review of privacy policy\n- Data processing agreements with third parties\n- Data classification and inventory complete\n- Privacy impact assessment approved\n\n## Acceptance Criteria\n\n**Consent Management:**\n- [ ] Cookie consent banner on first visit\n- [ ] Granular consent options (necessary, functional, marketing)\n- [ ] Easy withdrawal of consent\n- [ ] Consent records stored with timestamp\n- [ ] Opt-out links in all marketing emails\n\n**Data Subject Rights:**\n\n1. **Right to Access**\n - [ ] User can download all personal data (JSON format)\n - [ ] Data export includes: profile, activity logs, communications\n - [ ] Export generated within 24 hours\n \n2. **Right to Rectification** \n - [ ] User can edit profile information\n - [ ] Request form for data corrections\n \n3. **Right to Erasure (Right to be Forgotten)**\n - [ ] Account deletion request form\n - [ ] 30-day grace period before permanent deletion\n - [ ] Anonymize user data instead of deletion (where legally required to retain)\n - [ ] Email confirmation of deletion\n \n4. **Right to Data Portability**\n - [ ] Export data in machine-readable format (JSON/CSV)\n - [ ] Transfer data directly to another service (if possible)\n\n**Privacy by Design:**\n- [ ] Data minimization (collect only necessary data)\n- [ ] Pseudonymization of personal data in logs\n- [ ] Encryption at rest and in transit\n- [ ] Access controls and audit logging\n- [ ] Automated data retention policies\n\n## User Guide\n\n**Accessing Your Data:**\n1. Go to Settings \u2192 Privacy\n2. Click \"Download My Data\"\n3. Email sent when export ready (usually < 1 hour)\n4. Download link valid for 48 hours\n\n**Deleting Your Account:**\n1. Go to Settings \u2192 Privacy\n2. Click \"Delete My Account\"\n3. Enter password to confirm\n4. Account enters 30-day deletion queue\n5. Cancel deletion anytime within 30 days\n6. After 30 days, deletion is permanent\n\n## Connections\n\n- Related to #112 (audit logging)\n- Related to legal compliance initiative",
"last_edited": null,
- "preconditions": [
- "Legal review of privacy policy",
- "Data processing agreements with third parties",
- "Data classification and inventory complete",
- "Privacy impact assessment approved"
- ],
- "user_guide": "**Accessing Your Data:**\n1. Go to Settings → Privacy\n2. Click \"Download My Data\"\n3. Email sent when export ready (usually < 1 hour)\n4. Download link valid for 48 hours\n\n**Deleting Your Account:**\n1. Go to Settings → Privacy\n2. Click \"Delete My Account\"\n3. Enter password to confirm\n4. Account enters 30-day deletion queue\n5. Cancel deletion anytime within 30 days\n6. After 30 days, deletion is permanent"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -327,27 +213,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#210",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Supported Languages (Phase 1):**\n- [ ] English (default)\n- [ ] Spanish\n- [ ] French\n- [ ] German\n- [ ] Japanese\n\n**Implementation:**\n- [ ] Extract all UI strings to translation files\n- [ ] Use i18n library (react-i18next, Flask-Babel, etc.)\n- [ ] Language selector in header\n- [ ] User language preference saved\n- [ ] Browser language auto-detection\n- [ ] Fallback to English for missing translations\n\n**Content Types:**\n- [ ] UI labels and buttons\n- [ ] Error messages\n- [ ] Email templates\n- [ ] Help documentation\n- [ ] Date/time formatting per locale\n- [ ] Number formatting (decimals, thousands separators)\n- [ ] Currency formatting\n\n**Quality:**\n- [ ] 100% translation coverage for supported languages\n- [ ] Professional translation review\n- [ ] Test all workflows in each language\n- [ ] No hardcoded strings in code",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Expands market reach. Improves user experience for non-English speakers. Requirement for international expansion."
- ],
- "connections": "- #211 - Right-to-left (RTL) language support",
- "description": "Add internationalization support to enable multi-language user interface.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Overview\n\nAdd internationalization support to enable multi-language user interface.\n\n## Value\n\nExpands market reach. Improves user experience for non-English speakers. Requirement for international expansion.\n\n## Prerequisites\n\n- Translation management system (e.g., Crowdin, Lokalise)\n- Professional translation service engaged\n- Language selection UI/UX designed\n- Right-to-left (RTL) layout considerations\n\n## Done Criteria\n\n**Supported Languages (Phase 1):**\n- [ ] English (default)\n- [ ] Spanish\n- [ ] French\n- [ ] German\n- [ ] Japanese\n\n**Implementation:**\n- [ ] Extract all UI strings to translation files\n- [ ] Use i18n library (react-i18next, Flask-Babel, etc.)\n- [ ] Language selector in header\n- [ ] User language preference saved\n- [ ] Browser language auto-detection\n- [ ] Fallback to English for missing translations\n\n**Content Types:**\n- [ ] UI labels and buttons\n- [ ] Error messages\n- [ ] Email templates\n- [ ] Help documentation\n- [ ] Date/time formatting per locale\n- [ ] Number formatting (decimals, thousands separators)\n- [ ] Currency formatting\n\n**Quality:**\n- [ ] 100% translation coverage for supported languages\n- [ ] Professional translation review\n- [ ] Test all workflows in each language\n- [ ] No hardcoded strings in code\n\n## How To\n\n**Changing Language:**\n1. Click language selector in header (globe icon)\n2. Select desired language from dropdown\n3. Page refreshes with new language\n4. Preference saved for future visits\n\n**For Developers:**\n```javascript\n// Before\n\n\n// After \n\n```\n\n## Related\n\n- #211 - Right-to-left (RTL) language support",
"last_edited": null,
- "preconditions": [
- "Translation management system (e.g., Crowdin, Lokalise)",
- "Professional translation service engaged",
- "Language selection UI/UX designed",
- "Right-to-left (RTL) layout considerations"
- ],
- "user_guide": "**Changing Language:**\n1. Click language selector in header (globe icon)\n2. Select desired language from dropdown\n3. Page refreshes with new language\n4. Preference saved for future visits\n\n**For Developers:**\n```javascript\n// Before\n\n\n// After \n\n```"
+ "preconditions": null,
+ "user_guide": null
},
"state": "open",
"tags": [
@@ -364,25 +236,12 @@
{
"id": "github:AbsaOSS/living-doc-v2#211",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Layout:**\n- [ ] Entire UI mirrors for RTL languages\n- [ ] Sidebar moves to right side\n- [ ] Text alignment: right for RTL, left for LTR\n- [ ] Directional icons flipped (arrows, chevrons)\n- [ ] Margins and padding reversed\n\n**Text Direction:**\n- [ ] HTML dir attribute set correctly (rtl/ltr)\n- [ ] Bidirectional text handled correctly (mixed RTL/LTR)\n- [ ] Form inputs right-aligned for RTL\n\n**Testing:**\n- [ ] Test with Arabic language\n- [ ] Test with Hebrew language\n- [ ] Test mixed content (English + RTL)\n- [ ] Verify all pages render correctly",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Enables product usage in Middle East and Israel markets. Shows cultural sensitivity and attention to detail."
- ],
- "connections": "Depends on #210 (i18n support)",
- "description": "Add support for right-to-left languages (Arabic, Hebrew) including layout mirroring and text direction.",
+ "acceptance_criteria": null,
+ "business_value": null,
+ "connections": null,
+ "description": "## Description\n\nAdd support for right-to-left languages (Arabic, Hebrew) including layout mirroring and text direction.\n\n## Value\n\nEnables product usage in Middle East and Israel markets. Shows cultural sensitivity and attention to detail.\n\n## Setup\n\n- RTL CSS framework or utility classes\n- Bi-directional text handling\n- Icon flipping for directional icons\n\n## Acceptance Criteria\n\n**Layout:**\n- [ ] Entire UI mirrors for RTL languages\n- [ ] Sidebar moves to right side\n- [ ] Text alignment: right for RTL, left for LTR\n- [ ] Directional icons flipped (arrows, chevrons)\n- [ ] Margins and padding reversed\n\n**Text Direction:**\n- [ ] HTML dir attribute set correctly (rtl/ltr)\n- [ ] Bidirectional text handled correctly (mixed RTL/LTR)\n- [ ] Form inputs right-aligned for RTL\n\n**Testing:**\n- [ ] Test with Arabic language\n- [ ] Test with Hebrew language\n- [ ] Test mixed content (English + RTL)\n- [ ] Verify all pages render correctly\n\n## Connections\n\nDepends on #210 (i18n support)",
"last_edited": null,
- "preconditions": [
- "RTL CSS framework or utility classes",
- "Bi-directional text handling",
- "Icon flipping for directional icons"
- ],
+ "preconditions": null,
"user_guide": null
},
"state": "open",
@@ -400,25 +259,12 @@
{
"id": "github:AbsaOSS/living-doc-v2#212",
"sections": {
- "acceptance_criteria": [
- {
- "description": "**Flag Types:**\n- [ ] Boolean flags (on/off)\n- [ ] Percentage rollouts (10%, 25%, 50%, etc.)\n- [ ] User segment targeting (beta users, premium, etc.)\n- [ ] A/B test variants (variant A vs B vs C)\n\n**Flag Management:**\n- [ ] Create/edit/delete flags via admin UI\n- [ ] Flag overrides for testing (per-user, per-session)\n- [ ] Flag change history and audit log\n- [ ] Schedule flag changes (enable at specific time)\n\n**Integration:**\n- [ ] Backend flag evaluation\n- [ ] Frontend flag evaluation\n- [ ] Real-time flag updates (no deployment needed)\n- [ ] Default values when flag service unavailable\n\n**Examples:**\n```python\nif feature_flags.is_enabled('new_dashboard', user):\n return render_new_dashboard()\nelse:\n return render_old_dashboard()\n```",
- "id": null,
- "state": null,
- "version": null
- }
- ],
- "business_value": [
- "Enables:\n- Safe deployment of new features\n- Gradual rollout to subset of users\n- Quick rollback without code deployment\n- A/B testing for feature validation\n- Different features for different user segments"
- ],
+ "acceptance_criteria": null,
+ "business_value": null,
"connections": null,
- "description": "Implement feature flags system for gradual rollouts and A/B testing.",
- "last_edited": "Rolled out by grace@example.com on 2026-02-17",
- "preconditions": [
- "Feature flag service (LaunchDarkly, Unleash, or custom)",
- "Admin UI for flag management",
- "SDK integration in application code"
- ],
+ "description": "## Summary\n\nImplement feature flags system for gradual rollouts and A/B testing.\n\n## Why\n\nEnables:\n- Safe deployment of new features\n- Gradual rollout to subset of users\n- Quick rollback without code deployment\n- A/B testing for feature validation\n- Different features for different user segments\n\n## Prerequisites\n\n- Feature flag service (LaunchDarkly, Unleash, or custom)\n- Admin UI for flag management\n- SDK integration in application code\n\n## Acceptance Criteria\n\n**Flag Types:**\n- [ ] Boolean flags (on/off)\n- [ ] Percentage rollouts (10%, 25%, 50%, etc.)\n- [ ] User segment targeting (beta users, premium, etc.)\n- [ ] A/B test variants (variant A vs B vs C)\n\n**Flag Management:**\n- [ ] Create/edit/delete flags via admin UI\n- [ ] Flag overrides for testing (per-user, per-session)\n- [ ] Flag change history and audit log\n- [ ] Schedule flag changes (enable at specific time)\n\n**Integration:**\n- [ ] Backend flag evaluation\n- [ ] Frontend flag evaluation\n- [ ] Real-time flag updates (no deployment needed)\n- [ ] Default values when flag service unavailable\n\n**Examples:**\n```python\nif feature_flags.is_enabled('new_dashboard', user):\n return render_new_dashboard()\nelse:\n return render_old_dashboard()\n```\n\n## Changes\n\nRolled out by grace@example.com on 2026-02-17",
+ "last_edited": null,
+ "preconditions": null,
"user_guide": null
},
"state": "closed",
@@ -439,7 +285,7 @@
"acceptance_criteria": null,
"business_value": null,
"connections": null,
- "description": "Test case with markdown table:\n\n| Column 1 | Column 2 | Column 3 |\n|----------|----------|----------|\n| Value A | Value B | Value C |\n| Value D | Value E | Value F |\n\nThis tests table preservation in normalization.",
+ "description": "## Description\n\nTest case with markdown table:\n\n| Column 1 | Column 2 | Column 3 |\n|----------|----------|----------|\n| Value A | Value B | Value C |\n| Value D | Value E | Value F |\n\nThis tests table preservation in normalization.",
"last_edited": null,
"preconditions": null,
"user_guide": null
@@ -458,20 +304,13 @@
{
"id": "github:AbsaOSS/living-doc-v2#214",
"sections": {
- "acceptance_criteria": [
- {
- "description": "1. First criterion\n2. Second criterion \n - Nested item A\n - Nested item B\n3. Third criterion",
- "id": null,
- "state": null,
- "version": null
- }
- ],
+ "acceptance_criteria": null,
"business_value": null,
"connections": null,
- "description": "Test complex markdown:\n\n```python\ndef hello_world():\n print(\"Hello, World!\")\n return 42\n```",
+ "description": "## Description\n\nTest complex markdown:\n\n```python\ndef hello_world():\n print(\"Hello, World!\")\n return 42\n```\n\n## Acceptance Criteria\n\n1. First criterion\n2. Second criterion \n - Nested item A\n - Nested item B\n3. Third criterion\n\n## User Guide\n\n- **Bold item**: description\n- *Italic item*: description\n- `Code item`: description",
"last_edited": null,
"preconditions": null,
- "user_guide": "- **Bold item**: description\n- *Italic item*: description\n- `Code item`: description"
+ "user_guide": null
},
"state": "closed",
"tags": [