Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/community-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Community Entry Check

# Lightweight, non-executing gate for community showcase entries.
# It does NOT run contributor code. It validates entry structure, scans for
# committed secrets, and confirms hosted entries use Opik. Secrets-free, so it
# runs on forks. The showcase index (community/README.md) is regenerated
# separately on merge by community-index.yml — contributors run nothing.
#
# Community entries are intentionally exempt from the verified-bucket workflows
# (compliance / pr-test / test-notebooks / scheduled), which are path-scoped to
# examples|integrations|scripts|use-cases|guides and never match community/**.

on:
pull_request:
paths:
- "community/**"

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: true
python-version: "3.12"

- name: Install tooling
run: |
cd community/_ci
uv sync

- name: Unit-test the check tooling
run: |
cd community/_ci
uv run pytest -q

- name: Lint the check tooling
run: |
cd community/_ci
uv run ruff check .
uv run ruff format --check .

- name: Validate all community entries
run: |
cd community/_ci
uv run python check_entry.py
56 changes: 56 additions & 0 deletions .github/workflows/community-index.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Community Index

# Regenerates the community showcase index (community/README.md) from every
# entry's meta.yaml after changes land on main. This keeps the index current
# with zero manual action — contributors never run the generator, and no one
# hand-edits community/README.md.
#
# It runs build_index.py (which only parses YAML — it never executes contributor
# code) and commits the result back to main only if it changed. The path filter
# excludes community/README.md so this workflow's own index commit does not
# re-trigger it.

on:
push:
branches: [main]
paths:
- "community/**"
- "!community/README.md"

permissions:
contents: write

concurrency:
group: community-index
cancel-in-progress: false

jobs:
regenerate:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v7
with:
version: "latest"
enable-cache: true
python-version: "3.12"

- name: Regenerate showcase index
run: |
cd community/_ci
uv sync
uv run python build_index.py

- name: Commit index if it changed
run: |
if [[ -n "$(git status --porcelain community/README.md)" ]]; then
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add community/README.md
git commit -m "chore: regenerate community showcase index"
git push
else
echo "Showcase index already up to date."
fi
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ poetry.lock

# Local planning/process artifacts (superpowers brainstorm specs & implementation plans) — kept local, never committed
docs/superpowers/

# Community proof screenshots are intentionally committed (overrides the global *.png rule above)
!community/**/*.png
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Thank you for contributing. This repo is a reference library for Opik users — the goal is examples that are easy to find, easy to run, and easy to adapt.

> **Contributing community showcase work instead of a verified example?** The
> `community/` folder has its own, lighter-weight contract — see
> [community/CONTRIBUTING.md](community/CONTRIBUTING.md). The rules below apply
> to the verified `integrations/`, `guides/`, `use-cases/`, and `scripts/`
> buckets.

## Recommended workflow

This is the loop we follow for non-trivial contributions. The slash-commands in brackets come from Claude Code plugins (see below) and are optional but recommended. **Start every contribution with `/brainstorming`** to agree on scope, and **finish by reviewing your own PR with `/review`** before asking a human.
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ Standalone scripts for automating and managing Opik resources.
| [scripts/usage_stats](scripts/usage_stats/) | Fetch trace and span counts per project and visualise trends |
| [scripts/leaderboard_dashboard](scripts/leaderboard_dashboard/) | Create an Experiment Leaderboard dashboard via the REST API |

## Community

Work built with Opik by the open-source community. Unlike the buckets above,
these entries are **community-contributed and not maintainer-verified** — they
showcase what people are building. Standout real-world projects get hosted
in-repo and spotlighted in our community forums.

Browse them in [community/](community/), or add your own via
[community/CONTRIBUTING.md](community/CONTRIBUTING.md).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guide and example template.
48 changes: 48 additions & 0 deletions community/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Contributing to the Community folder

This folder showcases work the open-source community has built with Opik. It is
deliberately **lighter-weight than the main repo contract** — the strict
`run.sh` / dry-run / litellm / CI rules in the root
[CONTRIBUTING.md](../CONTRIBUTING.md) do **not** apply here. Community entries
are not executed by CI; a maintainer reviews (and, for hosted entries, runs)
them by hand.

There is one thing we always require: **proof you actually logged with Opik** —
either Comet cloud or the self-hosted open-source platform.

## Two kinds of entry

- **Listed** (default): a folder describing your work with links out to your own
repo/blog/notebook. No code needs to live here.
- **Hosted**: standout, real-world projects we promote into this repo with their
code included. You submit as *listed*; a maintainer sets `hosted: true` and
moves your code in when promoting. We also spotlight promoted work in our
community forums.

## Add your entry

1. Copy `templates/entry-template/` to `community/<your-handle>_<project>/`
(lowercase, underscores, e.g. `jane_support_agent`).
2. Fill in `meta.yaml` (all fields) and `README.md` (all four sections).
3. Replace `opik-proof.png` with a real screenshot of your Opik traces or
dashboard.
4. If you want it considered for hosting, include your code in the folder — it
must genuinely use Opik (`import opik`, `@opik.track`, ...).
5. Open a PR. A maintainer reviews it.

That's it — you don't need to run anything. The showcase index
(`community/README.md`) is regenerated automatically when your entry merges, so
your project appears there without any manual step on your part.

## What the automated check enforces

`community/_ci/check_entry.py` runs on your PR (a hard gate). It does **not**
run your code. It checks:

- `meta.yaml` has all required fields, at least one link, and a valid
`opik_platform`.
- `README.md` has all four sections filled in (no leftover `TODO`).
- `opik-proof.png` exists and is referenced from your README.
- No `.env` file or hardcoded API keys are committed.
- Hosted entries contain code that uses Opik.
- The folder name is `lowercase_with_underscores`.
12 changes: 12 additions & 0 deletions community/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Community

<!-- This file is generated by community/_ci/build_index.py. Do not edit by hand. -->

Work built with Opik by the open-source community. These entries are
**community-contributed and not maintainer-verified** — they are showcased here
to help people discover what others are building. Standout, real-world projects
are hosted in-repo (see the Hosted column).

To add your own, see [CONTRIBUTING.md](CONTRIBUTING.md).

_No community contributions yet — be the first!_
121 changes: 121 additions & 0 deletions community/_ci/build_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from __future__ import annotations

import sys
from pathlib import Path

from check_entry import COMMUNITY_DIR, discover_entries
from entry_rules import load_meta

_HEADER = """\
# Community

<!-- This file is generated by community/_ci/build_index.py. Do not edit by hand. -->

Work built with Opik by the open-source community. These entries are
**community-contributed and not maintainer-verified** — they are showcased here
to help people discover what others are building. Standout, real-world projects
are hosted in-repo (see the Hosted column).

To add your own, see [CONTRIBUTING.md](CONTRIBUTING.md).
"""

_TABLE_HEADER = (
"| Project | Author | Description | Platform | Links | Hosted | Tags |\n"
"|---|---|---|---|---|---|---|"
)


def load_entries(community_dir: Path) -> list[dict]:
entries: list[dict] = []
for entry in discover_entries(community_dir):
data, errors = load_meta(entry)
if errors:
continue
entries.append(
{
"slug": entry.name,
"title": str(data.get("title", entry.name)),
"description": str(data.get("description", "")),
"author": str(data.get("author", "")),
"links": data.get("links") or {},
"opik_platform": str(data.get("opik_platform", "")),
"tags": data.get("tags") or [],
"hosted": bool(data.get("hosted", False)),
}
)
entries.sort(key=lambda e: e["title"].casefold())
return entries


def _escape_pipes(value: str) -> str:
return value.replace("|", "\\|")


def _links_cell(links: dict) -> str:
parts = [
f"[{_escape_pipes(str(label))}]({_escape_pipes(str(url))})"
for label, url in links.items()
if str(url).startswith("http")
]
return "<br>".join(parts) if parts else ""


def _row(entry: dict) -> str:
project = f"[{_escape_pipes(entry['title'])}]({entry['slug']}/)"
author = (
f"[@{_escape_pipes(entry['author'])}](https://github.com/{_escape_pipes(entry['author'])})"
if entry["author"]
else ""
)
tags = _escape_pipes(", ".join(str(t) for t in entry["tags"]))
hosted = "hosted" if entry["hosted"] else ""
cells = [
project,
author,
_escape_pipes(entry["description"]),
_escape_pipes(entry["opik_platform"]),
_links_cell(entry["links"]),
hosted,
tags,
]
return "| " + " | ".join(cells) + " |"


def render_index(entries: list[dict]) -> str:
lines = [_HEADER]
if not entries:
lines.append("_No community contributions yet — be the first!_\n")
else:
lines.append(_TABLE_HEADER)
lines.extend(_row(e) for e in entries)
lines.append("")
return "\n".join(lines).rstrip("\n") + "\n"


def write_index(community_dir: Path) -> None:
(community_dir / "README.md").write_text(
render_index(load_entries(community_dir)), encoding="utf-8"
)


def check_index(community_dir: Path) -> bool:
readme = community_dir / "README.md"
current = readme.read_text(encoding="utf-8") if readme.is_file() else ""
return current == render_index(load_entries(community_dir))


def main(argv: list[str] | None = None) -> int:
args = sys.argv[1:] if argv is None else argv
if "--check" in args:
if check_index(COMMUNITY_DIR):
print("community/README.md is up to date.")
return 0
print("community/README.md is stale. Run: python build_index.py")
return 1
write_index(COMMUNITY_DIR)
print("Wrote community/README.md")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading