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": [