From 12b5d830911a5ce943c9663c64fe406a87438509 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 24 Jun 2026 18:25:13 +0200 Subject: [PATCH] feat(cli): add open-reuse validate and register portfolio integrations Implement Integration Definition validator CLI with schema and index checks, pytest suite, and CI workflow. Register open-cmis-tck and issue-core-gitea in the integration index. Closes OPEN-WP-0003 and OPEN-WP-0004. --- .claude/rules/stack-and-commands.md | 11 +- .gitea/workflows/ci.yml | 28 +++ AGENTS.md | 16 ++ open_reuse/__init__.py | 3 + open_reuse/cli.py | 74 +++++++ open_reuse/registry.py | 91 ++++++++ open_reuse/validate.py | 209 ++++++++++++++++++ pyproject.toml | 26 +++ registry/README.md | 40 +++- registry/indexes/integrations.yaml | 32 ++- tests/fixtures/invalid.integration.yaml | 19 ++ tests/fixtures/valid.integration.yaml | 26 +++ tests/test_validate.py | 120 ++++++++++ .../OPEN-WP-0003-integration-cli-validator.md | 35 ++- ...0004-integration-portfolio-registration.md | 39 ++-- 15 files changed, 714 insertions(+), 55 deletions(-) create mode 100644 .gitea/workflows/ci.yml create mode 100644 open_reuse/__init__.py create mode 100644 open_reuse/cli.py create mode 100644 open_reuse/registry.py create mode 100644 open_reuse/validate.py create mode 100644 pyproject.toml create mode 100644 tests/fixtures/invalid.integration.yaml create mode 100644 tests/fixtures/valid.integration.yaml create mode 100644 tests/test_validate.py diff --git a/.claude/rules/stack-and-commands.md b/.claude/rules/stack-and-commands.md index 9379bea..ec3632d 100644 --- a/.claude/rules/stack-and-commands.md +++ b/.claude/rules/stack-and-commands.md @@ -1,7 +1,8 @@ ## Stack -- **Language:** Markdown-first registry and planning repo (no application runtime yet) -- **Key deps:** State Hub ADR-001 workplans, `registry/indexes/capabilities.yaml` +- **Language:** Python CLI + Markdown/YAML registry artifacts +- **Key deps:** State Hub ADR-001 workplans, `jsonschema`, `pyyaml`, + `registry/indexes/integrations.yaml` ## Dev Commands @@ -12,6 +13,12 @@ cat INTENT.md cat SCOPE.md ls workplans/ +# Install and validate +python -m pip install -e ".[dev]" +open-reuse validate +open-reuse validate --repos-base /home/worsch +pytest -q + # After workplan or registry edits — from ~/state-hub make fix-consistency REPO=open-reuse diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..10546bd --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,28 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + validate-registry: + runs-on: ubuntu-latest + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install package + run: python -m pip install -e ".[dev]" + + - name: Validate integration registry index + run: open-reuse validate --indexed-only + + - name: Run tests + run: pytest -q \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index e55a058..7fa72b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -156,6 +156,22 @@ get wrong. +## Integration Registry CLI + +Install and validate integration definitions: + +```bash +python -m pip install -e ".[dev]" +open-reuse validate +open-reuse validate --repos-base /home/worsch --fail-on-warnings +pytest -q +``` + +- Schema: `schemas/integration.schema.yaml` +- Index: `registry/indexes/integrations.yaml` +- Template: `templates/integration-entry.template.yaml` +- Authoring guide: `registry/README.md` + --- ## Workplan Convention (ADR-001) diff --git a/open_reuse/__init__.py b/open_reuse/__init__.py new file mode 100644 index 0000000..7ffb907 --- /dev/null +++ b/open_reuse/__init__.py @@ -0,0 +1,3 @@ +"""open-reuse integration registry tooling.""" + +__version__ = "0.1.0" \ No newline at end of file diff --git a/open_reuse/cli.py b/open_reuse/cli.py new file mode 100644 index 0000000..fa2d144 --- /dev/null +++ b/open_reuse/cli.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +from open_reuse.validate import run_validate + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(prog="open-reuse") + subparsers = parser.add_subparsers(dest="command", required=True) + + validate = subparsers.add_parser( + "validate", + help="Validate integration definitions and registry index", + ) + validate.add_argument( + "paths", + nargs="*", + type=Path, + help="Integration definition files (default: registry/integrations/*.integration.yaml)", + ) + validate.add_argument( + "--root", + type=Path, + default=None, + help="open-reuse repository root (auto-detected when omitted)", + ) + validate.add_argument( + "--repos-base", + type=Path, + default=None, + help="Base directory containing consuming repos for external definition checks", + ) + validate.add_argument( + "--no-index", + action="store_true", + help="Skip indexes/integrations.yaml checks", + ) + validate.add_argument( + "--indexed-only", + action="store_true", + help="When checking index, skip local definition drift warnings", + ) + validate.add_argument( + "--fail-on-warnings", + action="store_true", + help="Exit non-zero when promotion-gate warnings are present", + ) + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = _build_parser() + args = parser.parse_args(argv) + + if args.command == "validate": + targets = args.paths or None + return run_validate( + root=args.root, + targets=targets, + repos_base=args.repos_base, + fail_on_warnings=args.fail_on_warnings, + check_index=not args.no_index, + indexed_only=args.indexed_only, + ) + + parser.error(f"unknown command: {args.command}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/open_reuse/registry.py b/open_reuse/registry.py new file mode 100644 index 0000000..2eaf767 --- /dev/null +++ b/open_reuse/registry.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import yaml + +PACKAGE_ROOT = Path(__file__).resolve().parent.parent + +INDEX_REQUIRED_FIELDS = ( + "id", + "name", + "status", + "owner", + "reuse_mode", + "path", + "repo", + "upstream", +) + + +def resolve_repo_root(root: Path | None = None) -> Path: + if root is not None: + return root.resolve() + candidate = PACKAGE_ROOT + markers = ( + candidate / "schemas" / "integration.schema.yaml", + candidate / "registry" / "indexes" / "integrations.yaml", + ) + if all(path.exists() for path in markers): + return candidate + raise FileNotFoundError( + "Could not resolve open-reuse repo root; pass --root explicitly." + ) + + +def registry_paths(repo_root: Path) -> dict[str, Path]: + registry = repo_root / "registry" + return { + "registry": registry, + "integrations": registry / "integrations", + "index": registry / "indexes" / "integrations.yaml", + "schema": repo_root / "schemas" / "integration.schema.yaml", + } + + +def load_schema(repo_root: Path) -> dict[str, Any]: + schema_path = registry_paths(repo_root)["schema"] + with schema_path.open(encoding="utf-8") as handle: + return yaml.safe_load(handle) + + +def load_yaml(path: Path) -> dict[str, Any]: + with path.open(encoding="utf-8") as handle: + data = yaml.safe_load(handle) + if not isinstance(data, dict): + raise ValueError(f"{path}: expected YAML mapping at root") + return data + + +def load_index(repo_root: Path) -> dict[str, Any]: + index_path = registry_paths(repo_root)["index"] + if not index_path.exists(): + return {"integrations": []} + return load_yaml(index_path) + + +def integration_paths(repo_root: Path, targets: list[Path] | None = None) -> list[Path]: + if targets: + return [path.resolve() for path in targets] + integrations_dir = registry_paths(repo_root)["integrations"] + if not integrations_dir.exists(): + return [] + return sorted( + path + for path in integrations_dir.glob("*.integration.yaml") + if path.is_file() + ) + + +def resolve_external_definition( + repo_slug: str, + relative_path: str, + repos_base: Path | None, +) -> Path | None: + if repos_base is None: + return None + candidate = (repos_base / repo_slug / relative_path).resolve() + if candidate.is_file(): + return candidate + return None \ No newline at end of file diff --git a/open_reuse/validate.py b/open_reuse/validate.py new file mode 100644 index 0000000..c4243a6 --- /dev/null +++ b/open_reuse/validate.py @@ -0,0 +1,209 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any + +import yaml +from jsonschema import Draft202012Validator + +from open_reuse.registry import ( + INDEX_REQUIRED_FIELDS, + integration_paths, + load_index, + load_schema, + load_yaml, + registry_paths, + resolve_external_definition, + resolve_repo_root, +) + + +def _format_schema_error(path: Path, error: Any) -> str: + location = ".".join(str(part) for part in error.path) or "(root)" + return f"{path}: schema error at {location}: {error.message}" + + +def validate_definition( + path: Path, + schema: dict[str, Any], +) -> tuple[list[str], list[str]]: + errors: list[str] = [] + warnings: list[str] = [] + try: + data = load_yaml(path) + except (OSError, yaml.YAMLError, ValueError) as exc: + return [f"{path}: failed to load YAML: {exc}"], warnings + + validator = Draft202012Validator(schema) + schema_errors = sorted(validator.iter_errors(data), key=lambda item: list(item.path)) + if schema_errors: + errors.extend(_format_schema_error(path, item) for item in schema_errors) + return errors, warnings + + warnings.extend(_promotion_gate_warnings(path, data)) + return errors, warnings + + +def _promotion_gate_warnings(path: Path, data: dict[str, Any]) -> list[str]: + warnings: list[str] = [] + status = data.get("status", "draft") + maintenance = data.get("maintenance", {}) + + if status == "active" and not maintenance.get("maintainers"): + warnings.append( + f"{path}: active integration missing maintenance.maintainers" + ) + if status in {"registered", "active"}: + if not data.get("boundary"): + warnings.append(f"{path}: {status} integration missing boundary block") + if not data.get("validation", {}).get("harness"): + warnings.append(f"{path}: {status} integration missing validation.harness") + return warnings + + +def validate_index( + repo_root: Path, + *, + repos_base: Path | None, + indexed_only: bool = False, +) -> tuple[list[str], list[str]]: + errors: list[str] = [] + warnings: list[str] = [] + index = load_index(repo_root) + entries = index.get("integrations", []) + if not isinstance(entries, list): + return ["indexes/integrations.yaml: integrations must be a list"], warnings + + seen_ids: set[str] = set() + indexed_ids: set[str] = set() + for row in entries: + if not isinstance(row, dict): + errors.append("indexes/integrations.yaml: integration row must be a mapping") + continue + + integration_id = row.get("id") + if not integration_id: + errors.append("indexes/integrations.yaml: integration row missing id") + continue + if integration_id in seen_ids: + errors.append( + f"indexes/integrations.yaml: duplicate integration id '{integration_id}'" + ) + seen_ids.add(integration_id) + indexed_ids.add(integration_id) + + for field in INDEX_REQUIRED_FIELDS: + if field not in row: + errors.append( + f"indexes/integrations.yaml: '{integration_id}' missing required field '{field}'" + ) + + upstream = row.get("upstream") + if upstream is not None and not isinstance(upstream, dict): + errors.append( + f"indexes/integrations.yaml: '{integration_id}' upstream must be a mapping" + ) + elif isinstance(upstream, dict) and "name" not in upstream: + errors.append( + f"indexes/integrations.yaml: '{integration_id}' upstream missing name" + ) + + repo_slug = row.get("repo") + rel_path = row.get("path") + if not repo_slug or not rel_path: + continue + + definition_path = resolve_external_definition(repo_slug, rel_path, repos_base) + if definition_path is None: + if repos_base is None: + warnings.append( + f"indexes/integrations.yaml: '{integration_id}' definition not checked " + f"(pass --repos-base to verify {repo_slug}/{rel_path})" + ) + else: + warnings.append( + f"indexes/integrations.yaml: '{integration_id}' definition not found at " + f"{repos_base / repo_slug / rel_path}" + ) + continue + + try: + definition = load_yaml(definition_path) + except (OSError, yaml.YAMLError, ValueError) as exc: + errors.append( + f"indexes/integrations.yaml: '{integration_id}' definition load failed: {exc}" + ) + continue + + if definition.get("id") != integration_id: + errors.append( + f"indexes/integrations.yaml: '{integration_id}' id mismatch in " + f"{definition_path} (found '{definition.get('id')}')" + ) + index_mode = row.get("reuse_mode") + definition_mode = definition.get("reuse", {}).get("primary_reuse_mode") + if index_mode and definition_mode and index_mode != definition_mode: + errors.append( + f"indexes/integrations.yaml: '{integration_id}' reuse_mode '{index_mode}' " + f"does not match definition '{definition_mode}'" + ) + + if not indexed_only: + local_paths = integration_paths(repo_root) + for path in local_paths: + try: + definition = load_yaml(path) + except (OSError, yaml.YAMLError, ValueError) as exc: + errors.append(f"{path}: failed to load local definition: {exc}") + continue + integration_id = definition.get("id") + if integration_id and integration_id not in indexed_ids: + warnings.append( + f"{path}: local definition '{integration_id}' missing index row" + ) + + return errors, warnings + + +def run_validate( + *, + root: Path | None, + targets: list[Path] | None, + repos_base: Path | None, + fail_on_warnings: bool, + check_index: bool, + indexed_only: bool, +) -> int: + repo_root = resolve_repo_root(root) + schema = load_schema(repo_root) + errors: list[str] = [] + warnings: list[str] = [] + + definition_paths = integration_paths(repo_root, targets) + if targets is None and not definition_paths and not check_index: + definition_paths = [] + + for path in definition_paths: + file_errors, file_warnings = validate_definition(path, schema) + errors.extend(file_errors) + warnings.extend(file_warnings) + + if check_index: + index_errors, index_warnings = validate_index( + repo_root, + repos_base=repos_base, + indexed_only=indexed_only, + ) + errors.extend(index_errors) + warnings.extend(index_warnings) + + for warning in warnings: + print(f"warning: {warning}") + for error in errors: + print(f"error: {error}") + + if errors: + return 1 + if fail_on_warnings and warnings: + return 1 + return 0 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..95fbf07 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "open-reuse" +version = "0.1.0" +description = "Integration registry tooling for open-reuse" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "jsonschema>=4.0", + "pyyaml>=6.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", +] + +[project.scripts] +open-reuse = "open_reuse.cli:main" + +[tool.setuptools.packages.find] +where = ["."] +include = ["open_reuse*"] \ No newline at end of file diff --git a/registry/README.md b/registry/README.md index db7e717..f6555c2 100644 --- a/registry/README.md +++ b/registry/README.md @@ -132,14 +132,35 @@ Prove Value 5. Set `schema_version: open-reuse.integration.v0.1`. 6. Add a row to `registry/indexes/integrations.yaml` with `id`, `path`, `repo`, `reuse_mode`, and `upstream` summary. -7. Validate manually (checklist below) before setting `status: active`. +7. Run `open-reuse validate --repos-base ` before setting + `status: active`. Early adopters may use `schema_version: open-reuse.integration.v1`; the schema accepts both. New entries should use v0.1. +## Automated validation + +Primary validation path: + +```bash +# Install (from repo root) +python -m pip install -e ".[dev]" + +# Validate local definitions and registry index +open-reuse validate + +# Validate index rows against consuming-repo definitions +open-reuse validate --repos-base /home/worsch + +# Treat promotion-gate warnings as failures (CI default for strict checks) +open-reuse validate --repos-base /home/worsch --fail-on-warnings +``` + +CI runs `open-reuse validate --indexed-only` on every push to `main`. + ## Manual validation checklist -Use until an automated CLI validator ships. +Fallback when the CLI is unavailable. ### Required fields @@ -174,14 +195,17 @@ Update actions: `ignore`, `monitor-only`, `open-issue`, `open-update-proposal`, - [ ] `reuse_mode` matches `reuse.primary_reuse_mode` in the definition - [ ] `upstream.name` matches the definition -## Reference integration +## Reference integrations -`markitect-quarkdown` provides the first real-world adapter integration: +| ID | Repo | Reuse mode | Definition path | +| -- | ---- | ---------- | --------------- | +| `markitect-quarkdown` | markitect-quarkdown | adapter | `integration/quarkdown.integration.yaml` | +| `open-cmis-tck` | open-cmis-tck | adapter | `integration/opencmis-tck.integration.yaml` | +| `issue-core-gitea` | issue-core | adapter | `integration/gitea-backend.integration.yaml` | -- Definition: `markitect-quarkdown/integration/quarkdown.integration.yaml` -- Index row: `indexes/integrations.yaml` → `markitect-quarkdown` - -Use it as a worked example for adapter + cli-boundary reuse. +`markitect-quarkdown` is the primary worked example for adapter + cli-boundary +reuse. `open-cmis-tck` illustrates cli-boundary orchestration of an upstream +test harness. `issue-core-gitea` illustrates a remote API backend adapter. ## Capability registry diff --git a/registry/indexes/integrations.yaml b/registry/indexes/integrations.yaml index eb8e238..ddfc4c0 100644 --- a/registry/indexes/integrations.yaml +++ b/registry/indexes/integrations.yaml @@ -16,4 +16,34 @@ integrations: project_url: https://github.com/iamgio/quarkdown notes: > Reference integration for adapter reuse mode. Definition lives in the - consuming repository; this index row is the registry discovery surface. \ No newline at end of file + consuming repository; this index row is the registry discovery surface. + + - id: open-cmis-tck + name: Apache Chemistry OpenCMIS TCK Harness + status: registered + owner: open-cmis-tck + reuse_mode: adapter + risk_level: medium + path: integration/opencmis-tck.integration.yaml + repo: open-cmis-tck + upstream: + name: Apache Chemistry OpenCMIS TCK + project_url: https://github.com/apache/chemistry-opencmis + notes: > + Guide-board extension wrapping OpenCMIS TCK ConsoleRunner through a + cli-boundary adapter. + + - id: issue-core-gitea + name: issue-core Gitea Backend + status: registered + owner: issue-core + reuse_mode: adapter + risk_level: medium + path: integration/gitea-backend.integration.yaml + repo: issue-core + upstream: + name: Gitea + project_url: https://github.com/go-gitea/gitea + notes: > + RemoteBackend adapter mapping issue-core task lifecycle onto the Gitea + issues API. \ No newline at end of file diff --git a/tests/fixtures/invalid.integration.yaml b/tests/fixtures/invalid.integration.yaml new file mode 100644 index 0000000..b7aa1ef --- /dev/null +++ b/tests/fixtures/invalid.integration.yaml @@ -0,0 +1,19 @@ +schema_version: open-reuse.integration.v0.1 +id: INVALID_ID +name: Fixture Invalid Integration + +upstream: + name: Example Upstream + +reuse: + primary_reuse_mode: not-a-real-mode + +boundary: + type: adapter + +validation: + harness: echo ok + +maintenance: + escalation_conditions: + - validation failure \ No newline at end of file diff --git a/tests/fixtures/valid.integration.yaml b/tests/fixtures/valid.integration.yaml new file mode 100644 index 0000000..9f9e396 --- /dev/null +++ b/tests/fixtures/valid.integration.yaml @@ -0,0 +1,26 @@ +schema_version: open-reuse.integration.v0.1 +id: fixture-valid +name: Fixture Valid Integration +status: registered +owner: open-reuse + +upstream: + name: Example Upstream + project_url: https://example.com/upstream + +reuse: + primary_reuse_mode: adapter + +boundary: + type: adapter + local_adapter: fixture.adapter.Adapter + reused_surface: upstream API + +validation: + harness: python3 -m pytest tests/ + +maintenance: + maintainers: + - fixture-team + escalation_conditions: + - validation failure \ No newline at end of file diff --git a/tests/test_validate.py b/tests/test_validate.py new file mode 100644 index 0000000..82a41e0 --- /dev/null +++ b/tests/test_validate.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +from open_reuse.registry import PACKAGE_ROOT, resolve_repo_root +from open_reuse.validate import run_validate, validate_definition +from open_reuse.registry import load_schema + +FIXTURES = Path(__file__).parent / "fixtures" +REPO_ROOT = resolve_repo_root() +SCHEMA = load_schema(REPO_ROOT) +MARKITECT_QUARKDOWN = Path("/home/worsch/markitect-quarkdown/integration/quarkdown.integration.yaml") + + +def test_valid_fixture_passes_schema() -> None: + errors, warnings = validate_definition(FIXTURES / "valid.integration.yaml", SCHEMA) + assert errors == [] + assert warnings == [] + + +def test_invalid_fixture_fails_schema() -> None: + errors, warnings = validate_definition(FIXTURES / "invalid.integration.yaml", SCHEMA) + assert errors + assert any("schema error" in item for item in errors) + + +@pytest.mark.skipif(not MARKITECT_QUARKDOWN.is_file(), reason="markitect-quarkdown not present") +def test_markitect_quarkdown_reference_passes_schema() -> None: + errors, warnings = validate_definition(MARKITECT_QUARKDOWN, SCHEMA) + assert errors == [] + + +def test_validate_command_success_on_fixture() -> None: + code = run_validate( + root=REPO_ROOT, + targets=[FIXTURES / "valid.integration.yaml"], + repos_base=None, + fail_on_warnings=False, + check_index=False, + indexed_only=False, + ) + assert code == 0 + + +def test_validate_command_fails_on_invalid_fixture() -> None: + code = run_validate( + root=REPO_ROOT, + targets=[FIXTURES / "invalid.integration.yaml"], + repos_base=None, + fail_on_warnings=False, + check_index=False, + indexed_only=False, + ) + assert code == 1 + + +def test_index_requires_fields() -> None: + code = run_validate( + root=REPO_ROOT, + targets=[], + repos_base=None, + fail_on_warnings=False, + check_index=True, + indexed_only=True, + ) + assert code == 0 + + +@pytest.mark.skipif(not MARKITECT_QUARKDOWN.is_file(), reason="markitect-quarkdown not present") +def test_index_consistency_with_repos_base() -> None: + code = run_validate( + root=REPO_ROOT, + targets=[], + repos_base=Path("/home/worsch"), + fail_on_warnings=False, + check_index=True, + indexed_only=True, + ) + assert code == 0 + + +def test_active_without_maintainers_warns_with_fail_on_warnings() -> None: + active_fixture = FIXTURES / "active-missing-maintainers.integration.yaml" + active_fixture.write_text( + """ +schema_version: open-reuse.integration.v0.1 +id: active-missing-maintainers +name: Active Missing Maintainers +status: active +owner: open-reuse +upstream: + name: Example Upstream +reuse: + primary_reuse_mode: adapter +boundary: + type: adapter + local_adapter: fixture.adapter.Adapter +validation: + harness: python3 -m pytest +maintenance: + escalation_conditions: + - validation failure +""".strip() + + "\n", + encoding="utf-8", + ) + try: + code = run_validate( + root=REPO_ROOT, + targets=[active_fixture], + repos_base=None, + fail_on_warnings=True, + check_index=False, + indexed_only=False, + ) + assert code == 1 + finally: + active_fixture.unlink(missing_ok=True) \ No newline at end of file diff --git a/workplans/OPEN-WP-0003-integration-cli-validator.md b/workplans/OPEN-WP-0003-integration-cli-validator.md index d012063..2dcc8f6 100644 --- a/workplans/OPEN-WP-0003-integration-cli-validator.md +++ b/workplans/OPEN-WP-0003-integration-cli-validator.md @@ -4,7 +4,7 @@ type: workplan title: "Integration CLI validator" domain: infotech repo: open-reuse -status: ready +status: finished owner: codex topic_slug: infotech created: "2026-06-24" @@ -32,56 +32,47 @@ patterns from reuse-surface `reuse-surface validate`. ```task id: OPEN-WP-0003-T01 -status: todo +status: done priority: high state_hub_task_id: "70b8cead-3c16-4e48-ab8f-ee6f7cedf25e" ``` -Add `pyproject.toml`, `open_reuse/` package skeleton, and entry point -`open-reuse` with a `validate` subcommand stub. Include `jsonschema` and `pyyaml` -as dependencies. Document install and run commands in `AGENTS.md` and +Result 2026-06-24: Added `pyproject.toml`, `open_reuse/` package, and CLI entry +point. Documented install commands in `AGENTS.md` and `.claude/rules/stack-and-commands.md`. ## Implement validate command ```task id: OPEN-WP-0003-T02 -status: todo +status: done priority: high state_hub_task_id: "0f8c8e25-1c1f-4f94-8771-71bcc3402db2" ``` -Implement `open-reuse validate` with: - -- Schema validation of one or more `*.integration.yaml` files (default: scan - `registry/integrations/` if present). -- Index checks: every `indexes/integrations.yaml` row has required fields; `id` - and `reuse_mode` are consistent when the definition file is reachable. -- `--fail-on-warnings` for promotion-gate checks (missing maintainers on - `active` status, missing index row for local definitions). -- Exit code 0 on success, non-zero on errors. +Result 2026-06-24: Implemented schema validation, index consistency checks, +`--repos-base` external definition resolution, and `--fail-on-warnings`. ## Add tests and CI ```task id: OPEN-WP-0003-T03 -status: todo +status: done priority: medium state_hub_task_id: "e84e7b00-43ec-429e-afa0-42cb8eaa0074" ``` -Add `tests/test_validate.py` covering schema pass/fail, index consistency, and -the markitect-quarkdown reference fixture. Add `.gitea/workflows/ci.yml` running -pytest on push/PR to `main`. +Result 2026-06-24: Added `tests/test_validate.py` (8 tests) and +`.gitea/workflows/ci.yml`. ## Update registry documentation ```task id: OPEN-WP-0003-T04 -status: todo +status: done priority: medium state_hub_task_id: "cfd38f45-0c0a-4790-b666-c211983b3ee9" ``` -Update `registry/README.md` to reference `open-reuse validate` as the primary -validation path. Mark manual checklist as fallback until CI is green. \ No newline at end of file +Result 2026-06-24: Updated `registry/README.md` with CLI as primary validation +path; manual checklist retained as fallback. \ No newline at end of file diff --git a/workplans/OPEN-WP-0004-integration-portfolio-registration.md b/workplans/OPEN-WP-0004-integration-portfolio-registration.md index 6ba0a76..bbd690d 100644 --- a/workplans/OPEN-WP-0004-integration-portfolio-registration.md +++ b/workplans/OPEN-WP-0004-integration-portfolio-registration.md @@ -4,7 +4,7 @@ type: workplan title: "Integration portfolio registration" domain: infotech repo: open-reuse -status: ready +status: finished owner: codex topic_slug: infotech created: "2026-06-24" @@ -32,55 +32,50 @@ open-reuse. ```task id: OPEN-WP-0004-T01 -status: todo +status: done priority: high state_hub_task_id: "c4367626-a4a6-4c57-bf83-ba54df3e6df0" ``` -Scan the local repo portfolio (reuse-surface `local-repo-roster.yaml`, domain -`INTENT.md` files, and existing `*.integration.yaml` files) for proven -integrations not yet indexed. Produce a short candidate list with owner repo, -upstream project, reuse mode estimate, and registration readiness -(ready / needs-definition / needs-boundary-work). +Result 2026-06-24: Surveyed 60-repo roster. Top candidates with proven code + +tests: `open-cmis-tck` (OpenCMIS TCK), `issue-core` (Gitea API), `llm-connect` +(multi-provider, needs-boundary-work), `sand-boxer` (E2B/Modal), `tele-mcp` +(Prometheus/Loki/k8s). Only `markitect-quarkdown` had an existing definition. ## Prioritize and assign targets ```task id: OPEN-WP-0004-T02 -status: todo +status: done priority: high state_hub_task_id: "bd2c5dc8-814e-4045-86ed-2f15db4faf59" ``` -Select 2–3 candidates for registration in this workplan cycle. Record the -selection and rationale in the task result. Defer remaining candidates to -backlog with explicit blockers. +Result 2026-06-24: Selected `open-cmis-tck` and `issue-core-gitea` for this +cycle — clearest adapter boundaries and strongest test coverage. Deferred +`llm-connect` (multi-upstream boundary work), `sand-boxer` (multi-backend +registration shape), and `tele-mcp` (upstream version matrix) to backlog. ## Author integration definitions ```task id: OPEN-WP-0004-T03 -status: todo +status: done priority: high state_hub_task_id: "f12d9856-fd9f-44f0-b841-a82f521149c3" ``` -For each selected candidate, ensure a conforming Integration Definition exists -in the consuming repo at `integration/.integration.yaml` using -`templates/integration-entry.template.yaml`. Complete at minimum: upstream, -reuse classification, boundary, validation harness, and maintainers. Coordinate -PRs in owning repos where definitions are missing. +Result 2026-06-24: Added `integration/opencmis-tck.integration.yaml` in +open-cmis-tck and `integration/gitea-backend.integration.yaml` in issue-core. ## Expand registry index ```task id: OPEN-WP-0004-T04 -status: todo +status: done priority: medium state_hub_task_id: "c59c5fe2-6f55-4097-8419-a73593abd589" ``` -Add index rows to `registry/indexes/integrations.yaml` for each registered -integration. Update `registry/README.md` reference section. Run -`open-reuse validate` (or manual checklist) and confirm all new rows pass -promotion gates for `registered` or `active` status. \ No newline at end of file +Result 2026-06-24: Added index rows for `open-cmis-tck` and `issue-core-gitea`. +Verified with `open-reuse validate --repos-base /home/worsch` (all checks pass). \ No newline at end of file