diff --git a/.github/workflows/index.yml b/.github/workflows/index.yml index d72fa4d..a0aaa00 100644 --- a/.github/workflows/index.yml +++ b/.github/workflows/index.yml @@ -13,6 +13,7 @@ on: - 'schema/**' - 'scripts/build_comparison_search.py' - 'scripts/build_comparisons.py' + - 'scripts/build_depth_discovery.py' - 'scripts/build_index.py' - 'scripts/catalog_utils.py' - 'scripts/coverage_metadata.py' @@ -39,6 +40,7 @@ on: - 'schema/**' - 'scripts/build_comparison_search.py' - 'scripts/build_comparisons.py' + - 'scripts/build_depth_discovery.py' - 'scripts/build_index.py' - 'scripts/catalog_utils.py' - 'scripts/coverage_metadata.py' @@ -103,6 +105,13 @@ jobs: --output-dir dist/index \ --comparison-index dist/comparisons/module-comparisons.json + - name: Build reviewed depth discovery artifacts + run: | + python scripts/build_depth_discovery.py \ + --catalog-dir catalog \ + --maturity-manifest quality/module-maturity.yaml \ + --output-dir dist/depth + - name: Validate comparison discovery graph run: | python scripts/validate_comparison_discovery.py \ @@ -141,10 +150,12 @@ jobs: comparison_search = json.loads(Path('dist/comparisons/search.json').read_text(encoding='utf-8')) module_comparisons = json.loads(Path('dist/comparisons/module-comparisons.json').read_text(encoding='utf-8')) discovery = json.loads(Path('dist/comparisons/discovery-validation.json').read_text(encoding='utf-8')) + depth = json.loads(Path('dist/depth/depth.json').read_text(encoding='utf-8')) coverage = json.loads(Path('dist/coverage/coverage.json').read_text(encoding='utf-8')) print('# OpenDevIndex Search Index') print() print(f"Generated **{data['module_count']}** validated modules.") + print(f"Reviewed depth discovery: **{depth['maturity_counts'].get('deep-dive', 0)}** deep dive(s), **{depth['maturity_counts'].get('guide', 0)}** guide(s).") print(f"Generated **{comparisons['comparison_count']}** curated comparison view(s).") print(f"Searchable comparison records: **{comparison_search['comparison_count']}**.") print(f"Comparison-linked modules: **{module_comparisons['module_count']}**.") @@ -162,6 +173,8 @@ jobs: for kind, count in data['kind_counts'].items(): print(f'| `{kind}` | {count} |') print() + print(Path('dist/depth/depth.md').read_text(encoding='utf-8')) + print() print(Path('dist/coverage/coverage.md').read_text(encoding='utf-8')) print() print(Path('dist/quality/quality-report.md').read_text(encoding='utf-8')) @@ -174,6 +187,7 @@ jobs: path: | dist/index dist/comparisons + dist/depth dist/coverage dist/quality if-no-files-found: error diff --git a/.github/workflows/public-index.yml b/.github/workflows/public-index.yml index 2509c32..f6ba53b 100644 --- a/.github/workflows/public-index.yml +++ b/.github/workflows/public-index.yml @@ -11,6 +11,7 @@ on: - 'quality/module-maturity.yaml' - 'taxonomy/**' - 'scripts/build_comparisons.py' + - 'scripts/build_depth_discovery.py' - 'scripts/build_index.py' - 'scripts/catalog_utils.py' - 'scripts/module_maturity.py' @@ -53,6 +54,14 @@ jobs: --output-dir dist/comparisons \ --public-dir docs/comparisons + - name: Generate reviewed depth view + run: | + python scripts/build_depth_discovery.py \ + --catalog-dir catalog \ + --maturity-manifest quality/module-maturity.yaml \ + --output-dir dist/depth \ + --public-file docs/depth.md + - name: Generate comparison-aware public module index run: | python scripts/build_index.py \ @@ -63,13 +72,13 @@ jobs: - name: Publish only when discovery views changed run: | - if [ -z "$(git status --porcelain -- INDEX.md docs/comparisons)" ]; then - echo 'Public index and comparison views are already current.' >> "$GITHUB_STEP_SUMMARY" + if [ -z "$(git status --porcelain -- INDEX.md docs/comparisons docs/depth.md)" ]; then + echo 'Public index, comparison views, and reviewed depth view are already current.' >> "$GITHUB_STEP_SUMMARY" exit 0 fi git config user.name "OpenDevIndex Publisher" git config user.email "207100624+BLCCoreStudio@users.noreply.github.com" - git add INDEX.md docs/comparisons + git add INDEX.md docs/comparisons docs/depth.md git commit -m "docs: refresh public discovery views [skip ci]" git push origin HEAD:main - echo 'Published updated INDEX.md and comparison views.' >> "$GITHUB_STEP_SUMMARY" + echo 'Published updated INDEX.md, comparison views, and reviewed depth view.' >> "$GITHUB_STEP_SUMMARY" diff --git a/README.md b/README.md index 340a00d..c9cec1e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@

Browse the Index · + Browse Reviewed Depth · Compare Technologies · Explore Technology Areas · Module Standard · @@ -31,7 +32,7 @@ A module can grow from a concise source-backed overview into a full technical de ## Start exploring -The generated [`INDEX.md`](INDEX.md) is the main directory. It groups modules by what they are and shows the domains they belong to. +The generated [`INDEX.md`](INDEX.md) is the main directory. It groups modules by what they are and shows the domains they belong to. The generated [`docs/depth.md`](docs/depth.md) view focuses specifically on reviewed guide and deep-dive modules, with kind and domain facets for faster depth-oriented browsing. A few examples: @@ -180,11 +181,12 @@ OpenDevIndex generates machine-readable search and comparison artifacts alongsid ```bash python scripts/build_index.py --catalog-dir catalog --output-dir dist/index --public-index INDEX.md +python scripts/build_depth_discovery.py --catalog-dir catalog --maturity-manifest quality/module-maturity.yaml --output-dir dist/depth python scripts/search_index.py "local ai" --index dist/index/search.json python scripts/build_comparisons.py --comparisons-dir comparisons --catalog-dir catalog --output-dir dist/comparisons ``` -Search can use taxonomy-aware fields including maturity, kind, domains, tags, deployment type, licensing metadata, and Technology Universe coverage facets. Comparison artifacts expose reviewed dimensions, module metadata, and requirement-oriented decision guidance for downstream interfaces. +Search can use taxonomy-aware fields including maturity, kind, domains, tags, deployment type, licensing metadata, and Technology Universe coverage facets. Reviewed-depth artifacts provide a focused machine-readable and Markdown view of guide/deep-dive modules without redefining maturity. Comparison artifacts expose reviewed dimensions, module metadata, and requirement-oriented decision guidance for downstream interfaces. The structured model is intended to work for: @@ -262,6 +264,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`docs/EDITORIAL_POLICY.md`](docs/E ## Documentation - [`docs/MODULE_STANDARD.md`](docs/MODULE_STANDARD.md) — module depth and editorial expectations +- [`docs/DEPTH_DISCOVERY.md`](docs/DEPTH_DISCOVERY.md) — reviewed guide/deep-dive discovery generation and source-of-truth rules - [`docs/COMPARISONS.md`](docs/COMPARISONS.md) — curated comparison manifests, validation, and publication model - [`docs/COVERAGE.md`](docs/COVERAGE.md) — technology-area coverage model - [`docs/TAXONOMY.md`](docs/TAXONOMY.md) — stable addresses, kinds, domains, and relationships diff --git a/comparisons/_meta/search-smoke.yaml b/comparisons/_meta/search-smoke.yaml index 379cc69..47049cd 100644 --- a/comparisons/_meta/search-smoke.yaml +++ b/comparisons/_meta/search-smoke.yaml @@ -1,5 +1,7 @@ schema_version: 1 cases: +- comparison: containers-docker-containerd + query: image build responsibility - comparison: github-actions-argocd query: runner jobs drift reconciliation - comparison: kubernetes-helm-argocd diff --git a/docs/DEPTH_DISCOVERY.md b/docs/DEPTH_DISCOVERY.md new file mode 100644 index 0000000..aec3931 --- /dev/null +++ b/docs/DEPTH_DISCOVERY.md @@ -0,0 +1,51 @@ +# Reviewed depth discovery + +OpenDevIndex keeps module coverage separate from editorial depth. The reviewed-depth discovery view exposes only modules that have been explicitly classified as `guide` or `deep-dive` in `quality/module-maturity.yaml`; overview modules remain valid catalog entries and are not treated as lower quality. + +## Source of truth + +The generator does not infer maturity from page length, schema version, branch history, or catalog wording. It joins validated catalog records with the existing maturity manifest and uses that reviewed metadata as the only maturity source of truth. + +This keeps discovery output derived and reproducible instead of introducing a second hand-maintained list of mature modules. + +## Build locally + +```bash +python scripts/build_depth_discovery.py \ + --catalog-dir catalog \ + --maturity-manifest quality/module-maturity.yaml \ + --output-dir dist/depth +``` + +The command writes: + +- `dist/depth/depth.json` — machine-readable reviewed-depth records and maturity/kind/domain counts; +- `dist/depth/depth.md` — deterministic human-readable discovery view. + +To render the same Markdown to a public path, pass `--public-file`: + +```bash +python scripts/build_depth_discovery.py \ + --catalog-dir catalog \ + --maturity-manifest quality/module-maturity.yaml \ + --output-dir dist/depth \ + --public-file docs/depth.md +``` + +## Discovery fields + +Each reviewed record includes the stable module ref, display name, kind, domains, maturity, review date, summary, and canonical module URL. The Markdown view groups modules by maturity and also exposes kind/domain facets with direct module links. + +Output ordering is deterministic so identical validated inputs produce identical files. + +## CI and publication + +The **Build Search Index** workflow builds the reviewed-depth artifacts, reports guide/deep-dive counts in the Actions summary, and includes `dist/depth` in the discovery artifact bundle. + +The trusted **Publish Public Index** workflow renders `docs/depth.md` from the same generator and only commits public discovery output when validated generated views actually change. + +Generated public Markdown is therefore a projection of reviewed catalog and maturity data. It must not become an independent editorial source of truth. + +## Editorial boundary + +A module enters this view only after its maturity metadata is deliberately reviewed. Adding a module to the catalog, increasing its word count, or changing generated discovery files must never promote maturity by itself. diff --git a/docs/SEARCH.md b/docs/SEARCH.md index bc83378..67517f2 100644 --- a/docs/SEARCH.md +++ b/docs/SEARCH.md @@ -23,6 +23,26 @@ The index builder also joins `quality/module-maturity.yaml`, so generated record The generator intentionally avoids timestamps in generated artifacts so the same validated inputs produce stable output. +## Build reviewed-depth discovery + +To generate the focused guide/deep-dive discovery view from the same reviewed maturity source of truth: + +```bash +python scripts/build_depth_discovery.py \ + --catalog-dir catalog \ + --maturity-manifest quality/module-maturity.yaml \ + --output-dir dist/depth +``` + +Generated files: + +- `dist/depth/depth.json` — machine-readable reviewed guide/deep-dive records plus deterministic maturity, kind, and domain facets; +- `dist/depth/depth.md` — human-readable reviewed-depth view with direct module navigation. + +The trusted public publisher uses the same renderer to produce `docs/depth.md`. Overview modules are deliberately excluded from this focused view; that is a discovery filter, not a lower-quality classification. Maturity remains owned by `quality/module-maturity.yaml`, and generated depth files are never an editorial source of truth. + +See [`DEPTH_DISCOVERY.md`](DEPTH_DISCOVERY.md) for the generation contract and source-of-truth boundary. + ## Search modules locally ```bash @@ -185,7 +205,7 @@ Successful validation writes `dist/comparisons/discovery-validation.json` with c ## Public discovery build -The public publisher renders comparison views first, then the comparison-aware module index: +The public publisher renders comparison views first, then the comparison-aware module index and reviewed-depth view: ```bash python scripts/build_comparisons.py \ @@ -199,11 +219,17 @@ python scripts/build_index.py \ --output-dir dist/index \ --public-index INDEX.md \ --comparison-index dist/comparisons/module-comparisons.json + +python scripts/build_depth_discovery.py \ + --catalog-dir catalog \ + --maturity-manifest quality/module-maturity.yaml \ + --output-dir dist/depth \ + --public-markdown docs/depth.md ``` -Compact comparison search and discovery-validation JSON are CI/downstream artifacts rather than committed public Markdown. Public comparison pages remain generated under `docs/comparisons/` from the same reviewed manifests. +Compact comparison search and discovery-validation JSON are CI/downstream artifacts rather than committed public Markdown. Public comparison pages remain generated under `docs/comparisons/`, while `docs/depth.md` is generated from the reviewed maturity manifest and catalog through the same deterministic depth renderer used by CI. -See [`COMPARISONS.md`](COMPARISONS.md) for the editorial, bidirectional-discovery, semantic-search, and validation model. +See [`COMPARISONS.md`](COMPARISONS.md) for the editorial, bidirectional-discovery, semantic-search, and validation model, and [`DEPTH_DISCOVERY.md`](DEPTH_DISCOVERY.md) for the reviewed-depth publication contract. ## Coverage progress @@ -223,22 +249,24 @@ The report compares real mapped modules with the 10,000-module Technology Univer The **Build Search Index** workflow: -1. runs unit tests for module search, curated comparisons, reverse indexes, joins/filters, comparison search, and discovery validation; +1. runs unit tests for module search, curated comparisons, reverse indexes, joins/filters, comparison search, discovery validation, and reviewed-depth generation; 2. builds full comparison records and the compact comparison-search artifact; 3. builds module catalog/search artifacts with comparison backlinks joined in; 4. runs the generic comparison discovery validator against all generated graph surfaces; 5. executes every semantic rank-1 query registered in `comparisons/_meta/search-smoke.yaml`; -6. builds coverage-progress artifacts and audits editorial quality; -7. smoke-tests representative non-comparison module facets; -8. uploads module, comparison, validation, quality, and coverage discovery output as workflow artifacts. +6. builds the reviewed-depth JSON/Markdown artifacts from the reviewed maturity manifest; +7. builds coverage-progress artifacts and audits editorial quality; +8. smoke-tests representative non-comparison module facets; +9. uploads module, comparison, depth, validation, quality, and coverage discovery output as workflow artifacts. -The **Publish Public Index** workflow rebuilds `INDEX.md` plus generated `docs/comparisons/` views when their validated inputs change. +The **Publish Public Index** workflow rebuilds `INDEX.md`, generated `docs/comparisons/` views, and `docs/depth.md` when their validated inputs change. -OpenDevIndex therefore exposes two complementary deterministic search surfaces: +OpenDevIndex therefore exposes three complementary deterministic discovery surfaces: ```text module search -> technologies + comparison backlinks comparison search -> curated trade-off views + participating technologies +reviewed depth -> reviewed guides/deep dives + kind/domain facets ``` -A separate end-to-end validator ensures those surfaces remain one consistent graph rather than drifting independent indexes. \ No newline at end of file +Separate validation and generation contracts keep those surfaces consistent with their reviewed source data rather than allowing generated discovery files to become independent sources of truth. diff --git a/scripts/build_depth_discovery.py b/scripts/build_depth_discovery.py new file mode 100644 index 0000000..b9bc079 --- /dev/null +++ b/scripts/build_depth_discovery.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +"""Build deterministic discovery artifacts for reviewed guide and deep-dive modules.""" + +from __future__ import annotations + +import argparse +import json +from collections import Counter +from pathlib import Path + +from catalog_utils import collect_entries, discover_catalogs +from module_maturity import load_maturity_manifest, module_level + +REPOSITORY_URL = "https://github.com/BLCCoreStudio/OpenDevIndex" +DISCOVERABLE_LEVELS = ("deep-dive", "guide") + + +def module_url(module_ref: str) -> str: + return f"{REPOSITORY_URL}/tree/{module_ref}/entry" + + +def build_records(catalog_dir: Path, maturity_manifest_path: Path) -> list[dict]: + entries, _ = collect_entries(discover_catalogs(catalog_dir)) + known_refs = {entry["module_ref"] for entry in entries} + manifest = load_maturity_manifest(maturity_manifest_path, known_refs) + + records: list[dict] = [] + for entry in entries: + ref = entry["module_ref"] + level = module_level(ref, manifest) + if level not in DISCOVERABLE_LEVELS: + continue + maturity = manifest["modules"].get(ref, {}) + records.append( + { + "ref": ref, + "name": entry["name"], + "kind": entry["kind"], + "domains": sorted(entry.get("domains", [])), + "maturity": level, + "reviewed_at": maturity.get("reviewed_at"), + "summary": entry["summary"], + "url": module_url(ref), + } + ) + + level_rank = {level: index for index, level in enumerate(DISCOVERABLE_LEVELS)} + records.sort( + key=lambda item: ( + level_rank[item["maturity"]], + item["kind"].casefold(), + item["name"].casefold(), + item["ref"], + ) + ) + return records + + +def discovery_counts(records: list[dict]) -> tuple[Counter, Counter, Counter]: + maturity_counts = Counter(record["maturity"] for record in records) + kind_counts = Counter(record["kind"] for record in records) + domain_counts = Counter( + domain for record in records for domain in record.get("domains", []) + ) + return maturity_counts, kind_counts, domain_counts + + +def module_links(records: list[dict]) -> str: + ordered = sorted( + records, + key=lambda item: (item["name"].casefold(), item["ref"]), + ) + return ", ".join( + f"[{record['name'].replace('|', '\\|')}]({record['url']})" + for record in ordered + ) + + +def render_markdown(records: list[dict]) -> str: + counts, kind_counts, domain_counts = discovery_counts(records) + lines = [ + "# OpenDevIndex — Reviewed Depth", + "", + "This generated view highlights modules that have progressed beyond overview maturity after explicit editorial review.", + "", + f"**[Deep dives](#deep-dives):** {counts['deep-dive']}", + "", + f"**[Guides](#guides):** {counts['guide']}", + ] + + if kind_counts: + lines.extend( + [ + "", + "## Reviewed depth by kind", + "", + "| Kind | Modules | Browse |", + "| --- | ---: | --- |", + ] + ) + for kind, count in sorted(kind_counts.items()): + matching = [record for record in records if record["kind"] == kind] + lines.append(f"| `{kind}` | {count} | {module_links(matching)} |") + + if domain_counts: + lines.extend( + [ + "", + "## Reviewed depth by domain", + "", + "| Domain | Modules | Browse |", + "| --- | ---: | --- |", + ] + ) + for domain, count in sorted(domain_counts.items()): + matching = [record for record in records if domain in record.get("domains", [])] + lines.append(f"| `{domain}` | {count} | {module_links(matching)} |") + + for level, heading in (("deep-dive", "Deep dives"), ("guide", "Guides")): + level_records = [record for record in records if record["maturity"] == level] + lines.extend(["", f"## {heading}", ""]) + if not level_records: + lines.append("_No reviewed modules at this depth yet._") + continue + lines.extend( + [ + "| Module | Kind | Domains | Reviewed | Summary |", + "| --- | --- | --- | --- | --- |", + ] + ) + for record in level_records: + name = record["name"].replace("|", "\\|") + summary = record["summary"].replace("|", "\\|") + domains = ", ".join(f"`{domain}`" for domain in record["domains"]) or "—" + reviewed_at = record["reviewed_at"] or "—" + lines.append( + f"| [{name}]({record['url']}) (`{record['ref']}`) | `{record['kind']}` | {domains} | {reviewed_at} | {summary} |" + ) + + lines.extend( + [ + "", + "---", + "", + "Maturity is independently reviewed metadata. An overview module is not considered lower quality; it simply has a narrower editorial scope.", + "", + ] + ) + return "\n".join(lines) + + +def build( + catalog_dir: Path, + maturity_manifest_path: Path, + output_dir: Path, + public_file: Path | None = None, +) -> dict: + records = build_records(catalog_dir, maturity_manifest_path) + counts, kind_counts, domain_counts = discovery_counts(records) + payload = { + "schema_version": 1, + "module_count": len(records), + "maturity_counts": { + level: counts[level] for level in DISCOVERABLE_LEVELS if counts[level] + }, + "kind_counts": dict(sorted(kind_counts.items())), + "domain_counts": dict(sorted(domain_counts.items())), + "entries": records, + } + + rendered = render_markdown(records) + output_dir.mkdir(parents=True, exist_ok=True) + (output_dir / "depth.json").write_text( + json.dumps(payload, indent=2, ensure_ascii=False, sort_keys=True) + "\n", + encoding="utf-8", + ) + (output_dir / "depth.md").write_text(rendered, encoding="utf-8") + if public_file is not None: + public_file.parent.mkdir(parents=True, exist_ok=True) + public_file.write_text(rendered, encoding="utf-8") + return payload + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--catalog-dir", default="catalog") + parser.add_argument("--maturity-manifest", default="quality/module-maturity.yaml") + parser.add_argument("--output-dir", default="dist/depth") + parser.add_argument( + "--public-file", + help="Optional generated Markdown path for the public reviewed-depth view", + ) + args = parser.parse_args() + + try: + payload = build( + Path(args.catalog_dir), + Path(args.maturity_manifest), + Path(args.output_dir), + Path(args.public_file) if args.public_file else None, + ) + except (OSError, ValueError) as exc: + parser.error(str(exc)) + + counts = payload["maturity_counts"] + print( + "Built reviewed-depth discovery: " + f"{counts.get('deep-dive', 0)} deep dive(s), {counts.get('guide', 0)} guide(s)" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_depth_discovery.py b/tests/test_depth_discovery.py new file mode 100644 index 0000000..b40afd4 --- /dev/null +++ b/tests/test_depth_discovery.py @@ -0,0 +1,186 @@ +from __future__ import annotations + +import json +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCRIPTS = ROOT / "scripts" +if str(SCRIPTS) not in sys.path: + sys.path.insert(0, str(SCRIPTS)) + +from build_depth_discovery import build # noqa: E402 + + +CATALOG = """schema_version: 3 +milestone: test +verified_at: '2026-09-01' +target_modules: 3 +expected_categories: + tool: 3 +entries: +- category: tool + id: alpha + name: Alpha + kind: tool + domains: [developer-tools] + coverage: + area: developer-tools + topics: [developer-experience] + summary: Alpha is a reviewed deep-dive fixture used for deterministic discovery tests. + homepage: https://example.com/alpha + repository: https://github.com/example/alpha + tags: [alpha, discovery, testing] + sources: + - title: Alpha docs + url: https://example.com/alpha/docs + type: documentation + - title: Alpha repository + url: https://github.com/example/alpha + type: repository + use_cases: + - Exercise reviewed deep-dive discovery behavior + - Verify deterministic deep-dive report ordering + - Confirm reviewed maturity metadata is preserved + key_points: + - Represents an explicitly reviewed deep-dive module + - Supplies valid schema version three catalog metadata + - Remains stable across repeated discovery report builds +- category: tool + id: beta + name: Beta + kind: tool + domains: [developer-tools, testing] + coverage: + area: developer-tools + topics: [developer-experience] + summary: Beta is a reviewed guide fixture used to verify focused discovery output behavior. + homepage: https://example.com/beta + repository: https://github.com/example/beta + tags: [beta, discovery, testing] + sources: + - title: Beta docs + url: https://example.com/beta/docs + type: documentation + - title: Beta repository + url: https://github.com/example/beta + type: repository + use_cases: + - Exercise reviewed guide discovery behavior + - Verify guides remain visible beside deep dives + - Confirm guide review metadata is preserved + key_points: + - Represents an explicitly reviewed guide module + - Supplies valid schema version three catalog metadata + - Remains stable across repeated discovery report builds +- category: tool + id: gamma + name: Gamma + kind: tool + domains: [developer-tools] + coverage: + area: developer-tools + topics: [developer-experience] + summary: Gamma is an overview fixture used to verify focused depth discovery exclusions. + homepage: https://example.com/gamma + repository: https://github.com/example/gamma + tags: [gamma, discovery, testing] + sources: + - title: Gamma docs + url: https://example.com/gamma/docs + type: documentation + - title: Gamma repository + url: https://github.com/example/gamma + type: repository + use_cases: + - Exercise overview exclusion from depth discovery + - Verify overview modules remain outside focused output + - Confirm default maturity behavior stays unchanged + key_points: + - Represents a valid overview module fixture + - Supplies valid schema version three catalog metadata + - Must not appear in reviewed depth discovery output +""" + +MATURITY = """schema_version: 1 +default_level: overview +levels: [overview, guide, deep-dive] +modules: + tool/alpha: + level: deep-dive + reviewed_at: '2026-09-01' + tool/beta: + level: guide + reviewed_at: '2026-08-31' +""" + + +class DepthDiscoveryTests(unittest.TestCase): + def test_build_lists_only_reviewed_non_overview_modules(self) -> None: + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + catalog_dir = root / "catalog" + output_dir = root / "dist" + manifest = root / "maturity.yaml" + catalog_dir.mkdir() + (catalog_dir / "test.yaml").write_text(CATALOG, encoding="utf-8") + manifest.write_text(MATURITY, encoding="utf-8") + + payload = build(catalog_dir, manifest, output_dir) + + self.assertEqual(payload["module_count"], 2) + self.assertEqual(payload["maturity_counts"], {"deep-dive": 1, "guide": 1}) + self.assertEqual([entry["ref"] for entry in payload["entries"]], ["tool/alpha", "tool/beta"]) + self.assertEqual(payload["entries"][0]["reviewed_at"], "2026-09-01") + self.assertNotIn("tool/gamma", {entry["ref"] for entry in payload["entries"]}) + + rendered = (output_dir / "depth.md").read_text(encoding="utf-8") + self.assertIn("## Deep dives", rendered) + self.assertIn("## Guides", rendered) + self.assertIn("[Alpha]", rendered) + self.assertIn("[Beta]", rendered) + self.assertNotIn("Gamma", rendered) + + json_payload = json.loads((output_dir / "depth.json").read_text(encoding="utf-8")) + self.assertEqual(json_payload, payload) + + def test_build_can_write_public_markdown_view(self) -> None: + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + catalog_dir = root / "catalog" + output_dir = root / "dist" + public_file = root / "docs" / "depth.md" + manifest = root / "maturity.yaml" + catalog_dir.mkdir() + (catalog_dir / "test.yaml").write_text(CATALOG, encoding="utf-8") + manifest.write_text(MATURITY, encoding="utf-8") + + build(catalog_dir, manifest, output_dir, public_file) + + self.assertEqual( + public_file.read_text(encoding="utf-8"), + (output_dir / "depth.md").read_text(encoding="utf-8"), + ) + + def test_build_is_deterministic(self) -> None: + with tempfile.TemporaryDirectory() as temp: + root = Path(temp) + catalog_dir = root / "catalog" + output_dir = root / "dist" + manifest = root / "maturity.yaml" + catalog_dir.mkdir() + (catalog_dir / "test.yaml").write_text(CATALOG, encoding="utf-8") + manifest.write_text(MATURITY, encoding="utf-8") + + build(catalog_dir, manifest, output_dir) + first_json = (output_dir / "depth.json").read_text(encoding="utf-8") + first_md = (output_dir / "depth.md").read_text(encoding="utf-8") + build(catalog_dir, manifest, output_dir) + self.assertEqual((output_dir / "depth.json").read_text(encoding="utf-8"), first_json) + self.assertEqual((output_dir / "depth.md").read_text(encoding="utf-8"), first_md) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_depth_discovery_facets.py b/tests/test_depth_discovery_facets.py new file mode 100644 index 0000000..9b484df --- /dev/null +++ b/tests/test_depth_discovery_facets.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCRIPTS = ROOT / "scripts" +if str(SCRIPTS) not in sys.path: + sys.path.insert(0, str(SCRIPTS)) + +from build_depth_discovery import discovery_counts, render_markdown # noqa: E402 + + +RECORDS = [ + { + "ref": "tool/alpha", + "name": "Alpha", + "kind": "tool", + "domains": ["developer-tools", "testing"], + "maturity": "deep-dive", + "reviewed_at": "2026-09-01", + "summary": "Alpha summary.", + "url": "https://github.com/BLCCoreStudio/OpenDevIndex/tree/tool/alpha/entry", + }, + { + "ref": "framework/beta", + "name": "Beta", + "kind": "framework", + "domains": ["developer-tools"], + "maturity": "guide", + "reviewed_at": "2026-08-31", + "summary": "Beta summary.", + "url": "https://github.com/BLCCoreStudio/OpenDevIndex/tree/framework/beta/entry", + }, +] + + +class DepthDiscoveryFacetTests(unittest.TestCase): + def test_counts_include_kind_and_domain_facets(self) -> None: + maturity, kinds, domains = discovery_counts(RECORDS) + + self.assertEqual(maturity, {"deep-dive": 1, "guide": 1}) + self.assertEqual(kinds, {"tool": 1, "framework": 1}) + self.assertEqual(domains, {"developer-tools": 2, "testing": 1}) + + def test_markdown_facets_link_to_matching_modules(self) -> None: + rendered = render_markdown(RECORDS) + + self.assertIn("## Reviewed depth by kind", rendered) + self.assertIn("## Reviewed depth by domain", rendered) + self.assertIn("| `tool` | 1 | [Alpha](https://github.com/BLCCoreStudio/OpenDevIndex/tree/tool/alpha/entry) |", rendered) + self.assertIn("| `framework` | 1 | [Beta](https://github.com/BLCCoreStudio/OpenDevIndex/tree/framework/beta/entry) |", rendered) + self.assertIn( + "| `developer-tools` | 2 | [Alpha](https://github.com/BLCCoreStudio/OpenDevIndex/tree/tool/alpha/entry), [Beta](https://github.com/BLCCoreStudio/OpenDevIndex/tree/framework/beta/entry) |", + rendered, + ) + self.assertIn("**[Deep dives](#deep-dives):** 1", rendered) + self.assertIn("**[Guides](#guides):** 1", rendered) + + +if __name__ == "__main__": + unittest.main()