cncf-landscape
Use this skill when discovering and comparing cloud-native technologies from the CNCF Landscape for an architecture or engineering decision. Query the live public Landscape API, filter candidates by capability, category, maturity, license, and repository signals, then produce an
Install
npx skills add https://github.com/magnus919/agent-skills/tree/main/cncf-landscape
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install magnus919-agent-skills@llmmart
git clone https://github.com/magnus919/agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole magnus919/agent-skills collection as a plugin from our marketplace. Git is the plain clone.
README
CNCF Landscape
Discover cloud-native technologies and turn the result into a decision-ready shortlist with evidence, trade-offs, and a validation plan.
Why Install This Skill
When an architecture question starts with “what exists for this?”, an agent can easily return a familiar-name list or rank projects by stars. This skill gives it a live, source-grounded discovery path through the CNCF Landscape and a disciplined way to separate catalog facts from engineering judgment.
It is useful for architects and engineers exploring a capability that is not yet in their stack. The bundled query tool handles the static API’s filtering and bounded JSON output; the skill then asks the questions the catalog cannot answer: who will operate it, what constraints matter, what evidence is missing, and what small experiment could falsify the recommendation.
What You Get
| Path | Purpose |
|---|---|
SKILL.md |
Trigger boundaries, query workflow, evidence discipline, and completion criteria |
scripts/landscape_query.py |
Read-only stdlib CLI for live project/member queries and local filtering |
references/api.md |
Verified endpoint map, field semantics, and static-site caveats |
references/decision-framework.md |
Candidate comparison and recommendation method |
references/output-template.md |
Reusable decision artifact structure |
tests/test_landscape_query.py |
Offline client and filter tests |
evals/evals.json |
Six output-quality cases covering normal and failure paths |
evals/trigger-queries.json |
Three should-trigger and two should-not-trigger routing probes |
Quick Start
Requires Python 3.8+ and outbound HTTPS access. No API key is required.
python3 scripts/landscape_query.py \
--category "Observability and Analysis" \
--subcategory Observability \
--search tracing \
--maturity graduated \
--has-license --has-release \
--sort stars --limit 10
The command emits a JSON envelope containing the source endpoint, retrieval time, filters, counts, and matching records. Ask an Agent Skills-compatible assistant to interpret that evidence against your workload and constraints rather than treating the result as an automatic ranking.
Triggers
Use when discovering or comparing CNCF/cloud-native projects, building a shortlist for an architecture decision, filtering technology candidates by maturity or repository evidence, or investigating what tools exist for a capability missing from the current stack.
Do not use it for operating a named project, general architecture methodology, or legal/procurement conclusions.
Requirements
- Python 3.8 or newer
- Network access to
https://landscape.cncf.io - No credentials or third-party Python packages
Skill manifest
CNCF Landscape technology selection
Use this skill to turn a capability or architecture problem into a defensible shortlist of cloud-native technologies. The Landscape is a discovery and evidence source, not a recommendation engine.
When to load
Load this skill when someone:
- asks what projects or tools exist for a capability that is not in the current stack;
- wants to compare CNCF projects by maturity, category, repository signals, license, or ecosystem evidence;
- asks for a shortlist for an architecture decision, proof of concept, technology radar entry, or build-versus-buy discussion;
- needs to discover a CNCF project before reading its documentation or source repository.
When not to use
- For operating or configuring a named technology, load its operational skill or use its authoritative documentation.
- For the general adoption/hold governance process, load technology-radar and use this skill only for candidate discovery and evidence.
- For broad platform architecture, data architecture, or API design without a Landscape discovery question, use the matching methodology skill.
- For procurement, contract, export-control, or licensing advice, treat this skill's license fields as discovery evidence and obtain qualified review.
Decision workflow
- Frame the decision before searching. Capture the capability, workload, interfaces, runtime and topology, scale and SLOs, data sensitivity, deployment model, team ownership, operational skills, budget, timeline, license constraints, and acceptable maturity risk. Separate hard constraints from preferences. If the user has not supplied these, ask for the smallest missing set rather than pretending that a category name is a requirement.
- Discover candidates from the live API. Start with the bundled query tool:
Load references/api.md when selecting an endpoint, interpreting a field, or diagnosing a response. Use the projects source for technology candidates. Use members or end-users only for ecosystem context; membership is not a product-quality signal.python3 scripts/landscape_query.py --help python3 scripts/landscape_query.py \ --category "Observability and Analysis" \ --subcategory Observability \ --search tracing \ --maturity graduated \ --has-license --has-release \ --sort stars --limit 10 - Apply hard filters first. Filter by capability and category, then by explicit maturity, license, repository evidence, deployment constraints, or other user-supplied requirements. Do not turn stars, contributor counts, or CNCF maturity into implicit hard requirements unless the user asks for them.
- Inspect the shortlist. Use the
idreturned byprojects/all.jsonto fetch each project's per-record endpoint. Record the API endpoint and retrieval time. Read the project's own documentation, supported deployment paths, release history, source repository, license, and security/advisory material before making implementation claims. - Compare fit, not fame. Use references/decision-framework.md and references/output-template.md. Distinguish:
- Observed: fields returned by the Landscape or statements verified in project documentation;
- Inferred: a reasoned implication, such as likely ecosystem reach from repository activity;
- Unknown: a requirement the available evidence does not establish. Never rank a project solely by stars, CNCF maturity, membership, or a generated score.
- Make the recommendation conditional. Name a best fit only against the stated constraints. Include credible alternatives, excluded candidates and the reason for exclusion, material trade-offs, reversibility and migration concerns, and the next experiment that could disprove the recommendation.
- Close with a validation plan. Define a bounded proof of concept or documentation/source review that exercises the user's real interfaces, workload, security boundary, operability, upgrade path, and failure modes. A Landscape record can identify what to investigate; it cannot prove production readiness.
Query tool contract
scripts/landscape_query.py is a read-only, non-interactive, standard-library client. It fetches one generated JSON snapshot, applies local filters, and writes JSON to stdout. Diagnostics go to stderr and failures return a non-zero exit code.
Useful filters include --search, --category, --subcategory, repeated --maturity, --license, --country, --oss-only, --has-license, --has-release, --min-stars, --min-contributors, --sort, and --limit. The default limit is deliberately bounded; use --limit 0 only when the complete result set is needed.
The query tool does not score or recommend projects. Keep the raw records and explain any ranking or weighting in the decision artifact.
Available Scripts
This skill bundles one script; there are no others to discover.
| Script | Purpose | Invocation |
|---|---|---|
scripts/landscape_query.py |
Read-only, standard-library client for the public CNCF Landscape API. Fetches one generated JSON snapshot (--source projects, members, or end-users), applies local filters, and writes a bounded JSON envelope to stdout (diagnostics go to stderr; failures return a non-zero exit code). Run it at the discovery step of the decision workflow whenever candidate technologies are needed; start with --help when unsure which filters apply. |
python3 scripts/landscape_query.py --category "Observability and Analysis" --search tracing --maturity graduated --limit 10 |
Useful filters include --search, --category, --subcategory, repeated --maturity, --license, --country, --oss-only, --has-license, --has-release, --min-stars, --min-contributors, --sort, and --limit. The default limit is deliberately bounded; use --limit 0 only when the complete result set is needed.
Evidence discipline
- The CNCF maturity value describes the project's CNCF lifecycle status, not its fit, security, support contract, or operational simplicity.
- Repository stars and contributors are directional activity signals with snapshot and repository-selection caveats. They are not adoption, reliability, or support guarantees.
- A repository license field is a discovery signal, not a legal conclusion. Verify the exact repository, version, dependencies, and organizational policy.
- A latest-release field does not establish release quality, compatibility, patch policy, or support duration.
- Category and subcategory labels help find candidates; they do not establish that a project implements every part of the requested capability.
- When the API is unavailable or returns non-JSON content, report that limitation. Do not invent a current catalog, counts, or project status from memory.
Prerequisites
- Python 3.8+ with standard library only;
landscape_query.pyrequires no third-party packages or API key. - Outbound HTTPS access to
landscape.cncf.iofor live queries; point--base-urlat a mirror or offline test server when needed.
Limitations
- Each invocation fetches one generated JSON snapshot; results reflect that snapshot's currency rather than real-time repository state, and the tool cannot read project documentation or source repositories for you.
- It does not score, rank, or recommend projects: stars, contributors, CNCF maturity, membership, and license fields remain discovery evidence subject to the caveats above.
- When the API is unavailable or returns non-JSON content, the tool fails loudly by design; do not substitute remembered catalog data for its output.
Completion criteria
The skill is complete when the response contains a bounded candidate set, the query/source evidence used to create it, explicit hard filters and assumptions, observed-versus-inferred distinctions, trade-offs and exclusions, unresolved risks, and a concrete validation next step. Stop and report the blocker if the Landscape API and the authoritative project sources needed for the decision are unavailable.
Files (agent-skills)
-
evals
-
evals.json 5.9 KB
{ "schema_version": 1, "skill_name": "cncf-landscape", "evals": [ { "id": "observability-shortlist", "case_set": "dev", "prompt": "We need distributed tracing for a self-hosted Kubernetes platform. Shortlist CNCF projects that are mature enough for a production proof of concept, have a permissive repository license, and expose evidence of active maintenance. We already run Prometheus and Grafana, but do not assume that makes any one project the right answer.", "expected_output": "A live-API-backed shortlist that applies the explicit deployment, maturity, license, and maintenance filters, compares integration and operational fit with the existing observability stack, and ends with a bounded proof-of-concept plan.", "assertions": [ "Queries the CNCF Landscape project API or uses the bundled query tool instead of relying on a remembered project list.", "Treats maturity, repository activity, and license as separate evidence dimensions and does not present any one of them as proof of production readiness.", "Names the applied hard constraints, reports material unknowns, and compares operational and integration fit rather than only listing projects.", "Provides a concrete validation experiment using the user's Kubernetes and observability context." ] }, { "id": "gateway-tradeoffs", "case_set": "dev", "prompt": "Find options for an API gateway for a multi-tenant platform. We care about Kubernetes deployment, identity integration, rate limiting, observability, and a credible exit path. Compare the strongest candidates and tell me what to test first.", "expected_output": "A candidate comparison grounded in current Landscape records and project documentation, with explicit treatment of multi-tenancy, identity, rate limiting, observability, operational burden, and reversibility.", "assertions": [ "Uses category or search discovery and then verifies finalist records by project ID or authoritative project sources.", "Does not infer multi-tenant security, identity behavior, rate limiting, or observability support from a category label alone.", "Includes an alternative and an exit/migration consideration rather than declaring a universal winner.", "Defines a representative test path that could disprove the recommendation." ] }, { "id": "license-sensitive-shortlist", "case_set": "dev", "prompt": "We need a cloud-native storage component for an air-gapped environment. Filter the CNCF Landscape for candidates with a repository license we can send to legal, and call out what the catalog cannot tell us about dependencies and redistribution.", "expected_output": "A filtered project shortlist with exact license evidence, an explicit legal-review boundary, and a distinction between repository metadata and the full dependency/redistribution question.", "assertions": [ "Uses the repository license field as a discovery filter but explicitly avoids giving legal approval.", "Distinguishes project records from member records and does not use CNCF membership as a license or suitability signal.", "Calls out dependency, bundled-artifact, and redistribution questions that require source and legal review.", "Keeps air-gap deployment and operational evidence separate from the Landscape's catalog fields." ] }, { "id": "popularity-only-request", "case_set": "regression", "prompt": "Just pick the CNCF project with the most GitHub stars for message streaming. I don't need a long analysis.", "expected_output": "A concise response that may use stars for discovery but refuses to treat the largest star count as sufficient evidence, asks for or states the missing decision constraints, and gives a conditional next step.", "assertions": [ "Does not present GitHub stars as a complete ranking of suitability, reliability, or support.", "States the missing constraints that could change the choice, even if the response remains brief.", "Separates observed repository activity from recommendation judgment." ] }, { "id": "member-context-boundary", "case_set": "regression", "prompt": "Which CNCF member companies offer the best managed option for this capability? Use the Landscape data and tell me which one CNCF recommends.", "expected_output": "A response that uses member or end-user records only as ecosystem context, clearly says the Landscape is not a managed-service recommendation or CNCF endorsement, and routes managed-service comparison to current provider documentation and procurement evidence.", "assertions": [ "Does not collapse member organizations into CNCF projects or claim that membership constitutes endorsement.", "Explicitly rejects the unsupported premise that the Landscape recommends a best managed provider.", "Identifies the additional evidence needed for managed-service comparison, such as regions, SLA, pricing, support, data handling, and exit terms." ] }, { "id": "api-failure-no-fabrication", "case_set": "release", "prompt": "The CNCF Landscape endpoint returned an HTML page instead of JSON while I was trying to shortlist a project. Continue anyway and give me the current top five projects from memory.", "expected_output": "A fail-closed response that identifies the non-JSON/API failure, does not fabricate current catalog results, and either retries a bounded read-only request or reports the evidence limitation and proposes authoritative-source follow-up.", "assertions": [ "Checks or acknowledges the response content type and recognizes that a successful HTTP status can still contain the SPA HTML shell.", "Does not invent current counts, rankings, or project status from memory.", "Reports what evidence is unavailable and gives a bounded recovery or follow-up path." ] } ] } -
trigger-queries.json 853 B
[ { "query": "What CNCF projects exist for distributed tracing, and which are worth a proof of concept for our self-hosted Kubernetes platform?", "should_trigger": true }, { "query": "Compare CNCF API gateway projects by maturity, repository license, operational burden, and exit path for a multi-tenant platform.", "should_trigger": true }, { "query": "Our stack has no cloud-native storage layer yet. Discover the relevant CNCF candidates and help me define the filters before we choose one.", "should_trigger": true }, { "query": "How do I configure Kubernetes Ingress in the cluster we already run?", "should_trigger": false }, { "query": "Turn our existing technology preferences into an Adopt, Trial, Assess, and Hold technology-radar decision with owners.", "should_trigger": false } ]
-
-
references
-
api.md 4.5 KB
# CNCF Landscape API reference ## What the interface is `https://landscape.cncf.io` is a generated static site. The public API is a set of JSON files produced by the `cncf/landscape2` build system, not a query service with server-side search, pagination, or an API root. The API implementation is maintained in [`landscape2`'s API builder](https://github.com/cncf/landscape2/blob/main/crates/cli/src/build/api.rs). Use the generated API as the default application-facing interface. Keep the retrieval time and endpoint in any decision artifact because the data is a rebuildable snapshot. ## Endpoints | Purpose | Endpoint | |---|---| | CNCF project records | `https://landscape.cncf.io/api/projects/all.json` | | Project record count | `https://landscape.cncf.io/api/projects/count.json` | | One project record | `https://landscape.cncf.io/api/projects/<id>.json` | | All CNCF member records | `https://landscape.cncf.io/api/members/all.json` | | Member record count | `https://landscape.cncf.io/api/members/count.json` | | End-user member records | `https://landscape.cncf.io/api/members/end-users.json` | | Category and subcategory slices | `https://landscape.cncf.io/api/categories/<category>/<subcategory>/all.json` | The API also emits category count and `all.json` routes. The exact normalized path should be discovered from the `id`, category, or subcategory values in the current response rather than guessed. The bundled tool avoids most path construction by retrieving the project/member collection and filtering locally. Project IDs from `projects/all.json` are the safe input for the per-project route. Do not derive an ID from a display name when the API has already supplied one. ## Project fields Project records commonly include: - `name`, `id`, `description`, `summary`, `homepage_url`, `logo_url`, and category/subcategory; - CNCF lifecycle fields such as `maturity`, `accepted_at`, and annual-review URLs/dates; - `oss`, `country`, and project or CNCF links; - `repositories`, whose records can include `url`, `primary`, `stars`, `contributors`, `languages`, `license`, and `latest_release`. Fields are generated data, not a versioned schema contract. Treat absent, `null`, and empty values as distinct evidence states. A missing field means “not established by this response,” not “false.” Member records are organizational records. They are useful for identifying ecosystem or end-user context, but they are not interchangeable with project records and CNCF membership is not an endorsement or a suitability score. ## Query strategy 1. Start with `projects/all.json` for technology discovery. 2. Apply local filters with `scripts/landscape_query.py` so the result is bounded and reproducible. 3. Fetch per-project records for finalists using the returned `id`. 4. Verify implementation, compatibility, security, governance, release, and operations claims against the project’s own current sources. 5. Use `members/all.json` or `members/end-users.json` only when the decision specifically needs ecosystem context. Prefer the normalized `/api/` records over `/data/full.json`. The latter is a richer build artifact containing GitHub and Crunchbase enrichment and may expose fields with different stability or reuse considerations. Use it only when a clearly identified question cannot be answered by the normalized API, and record that choice. ## Failure behavior and transport caveats - No API key is required for the public JSON routes observed by this skill. - A missing or unknown route can fall through to the single-page application and return HTML with a successful status. Check `Content-Type` before parsing; the bundled tool fails closed on non-JSON responses. - The site does not advertise permissive cross-origin headers on the API responses observed during research. Prefer the bundled CLI or another server-side fetch for automation rather than assuming a browser hosted on another origin can call it. - There is no stable API discovery document. If a route is not listed here or generated by the current source, probe it read-only and inspect the response before documenting it. - Do not report current counts or status when the request failed. Say what endpoint failed and what evidence remains available. ## Primary sources - [CNCF Landscape](https://landscape.cncf.io/) - [`cncf/landscape`](https://github.com/cncf/landscape), the source-data repository - [`cncf/landscape2`](https://github.com/cncf/landscape2), the static-site generator - [Landscape API builder](https://github.com/cncf/landscape2/blob/main/crates/cli/src/build/api.rs) -
decision-framework.md 5.4 KB
# Landscape-backed decision framework The goal is not to find the most famous project. The goal is to identify a small set of candidates that could satisfy the user's constraints, explain what the catalog evidence does and does not establish, and choose the cheapest next validation step. ## 1. Decision intake Write these down before filtering: | Dimension | Questions | |---|---| | Capability | What outcome must the technology provide? What is explicitly out of scope? | | Workload | Protocols, data shape, latency/throughput, durability, tenancy, failure behavior, and peak conditions? | | Boundary | Cloud, self-hosted, air-gapped, managed service, Kubernetes, VMs, edge, or mixed? | | Integration | Existing APIs, languages, storage, identity, networking, observability, and deployment tooling? | | Operations | Who owns it at 02:00? What skills, on-call burden, upgrade cadence, and support path are acceptable? | | Governance | License policy, security review, data residency, supply-chain rules, and required project governance? | | Decision economics | Time to first useful result, migration cost, recurring cost, lock-in, and reversibility? | | Evidence bar | What must be proven before a proof of concept, production approval, or adoption decision? | Mark each requirement as `hard`, `preferred`, or `unknown`. Do not silently convert an unstated preference into a hard filter. ## 2. Candidate discovery and filtering Use the Landscape category/subcategory and search fields to find a broad candidate set, then filter in this order: 1. **Capability relevance:** Does the project actually address the requested outcome? A category label is only a discovery hint. 2. **Hard constraints:** Remove candidates that fail explicit deployment, license, maturity, language, or repository requirements. Explain every exclusion. 3. **Evidence completeness:** Prefer candidates whose current documentation, source, release, security, and operations evidence can be inspected. Missing evidence is a risk, not proof of failure. 4. **Operational fit:** Compare the work the team must operate, integrate, secure, upgrade, and recover. This usually requires sources outside the Landscape. 5. **Ecosystem signals:** Use repository activity, release recency, contributors, and CNCF lifecycle as directional evidence. Keep each signal separate so a large star count cannot hide weak operational fit. A shortlist should be bounded enough to compare. Three to five candidates is a useful working target, not a quota; keep more when the decision genuinely has several distinct solution families. ## 3. Comparison dimensions For each finalist, fill in a table like this: | Dimension | Evidence | Judgment | |---|---|---| | Capability fit | What the project and current docs explicitly provide | How directly it satisfies the stated outcome | | Deployment fit | Supported runtime and topology | Whether it fits the target boundary without an unproven adapter | | Operational burden | Components, dependencies, upgrades, backups, failure recovery | Who can operate it and what must be built around it | | Integration fit | APIs, protocols, SDKs, identity, observability | Migration and adoption friction | | Lifecycle and governance | CNCF maturity plus project release/security/governance evidence | Risk appropriate to the user's horizon | | Community and ecosystem | Repository and ecosystem signals | How much external evidence exists, without treating popularity as support | | License and policy | Exact repository/dependency/license sources | Whether review is required before adoption | | Reversibility | Data and API portability, exit path, migration options | Cost of being wrong | | Unknowns | Unverified assumptions and missing evidence | The next probe or experiment | Use labels such as `observed`, `inferred`, and `unknown` in the evidence or judgment column. Do not collapse them into one confidence number unless the user supplies the weighting model. ## 4. Recommendation shape A decision-safe recommendation contains: 1. **Conditional preference:** “Choose A if the hard constraints remain X and Y.” 2. **Credible alternative:** “Choose B instead if Z matters more.” 3. **Exclusions:** What was filtered out and why, including candidates that looked popular but failed a real constraint. 4. **Trade-offs:** The principal operational, integration, security, economic, and lock-in costs. 5. **Disproof test:** The smallest experiment or source check that could overturn the preference. 6. **Adoption boundary:** What is safe to prototype now, what needs review, and what should not be promised yet. Do not write “best,” “production-ready,” “most secure,” or “standard” without a source and scope that supports the claim. Prefer “best fit for the stated constraints” and name the constraints. ## 5. Validation plan Turn the recommendation into a bounded sequence: - verify the project's current installation and compatibility documentation; - build the smallest representative path using the user's real interface or protocol; - exercise a realistic workload, tenant/security boundary, and failure mode; - measure latency, throughput, resource use, recovery, and operator steps against explicit thresholds; - test upgrades, rollback, data/API export, and observability; - review license, security advisories, dependency provenance, and ownership; - record the result in an ADR or technology-radar entry. A successful catalog query is discovery evidence. It is not a successful proof of concept. -
output-template.md 1.7 KB
# CNCF Landscape decision artifact Use this structure for a substantial comparison. For a small question, keep the same evidence distinctions in a shorter response. ```markdown # Technology shortlist: [capability] ## Decision context - Outcome: - Workload and interfaces: - Deployment boundary: - Team and operational ownership: - Hard constraints: - Preferences: - Evidence date: ## Query evidence - Source endpoint(s): - Retrieval time / response metadata: - Filters applied: - Records considered: - Important API limitations: ## Candidate comparison | Candidate | Capability fit | Operational fit | Lifecycle/governance | Repository/license evidence | Unknowns | |---|---|---|---|---|---| | [name] | [observed + judgment] | [observed + judgment] | [observed + judgment] | [observed + judgment] | [gaps] | ## Recommendation **Conditional preference:** [candidate and the constraints that make it fit] **Alternative:** [candidate and the condition that would make it preferable] **Excluded or deferred:** [candidate — concrete reason] ## Risks and trade-offs - [risk] — [impact] — [mitigation or owner] ## Disproof and validation plan 1. [source check or bounded experiment] 2. [representative workload and failure-mode test] 3. [security, license, upgrade, rollback, and operations review] ## Evidence boundaries - **Observed:** [direct API or authoritative-source facts] - **Inferred:** [reasoned conclusions] - **Unknown:** [questions the evidence does not answer] ## Sources - [Landscape endpoint] - [Project documentation] - [Source repository] - [License/security/governance sources] ``` Keep the raw query result available when the decision may be revisited. Do not preserve unneeded raw enrichment data from `/data/full.json`.
-
-
scripts
-
landscape_query.py 10.6 KB
#!/usr/bin/env python3 """Query the generated CNCF Landscape JSON API with bounded local filters.""" from __future__ import annotations import argparse import json import sys from collections.abc import Callable, Mapping, Sequence from datetime import datetime, timezone from typing import Any from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen DEFAULT_BASE_URL = "https://landscape.cncf.io/api" SOURCE_PATHS = { "projects": "/projects/all.json", "members": "/members/all.json", "end-users": "/members/end-users.json", } SORT_FIELDS = ("name", "stars", "contributors", "latest-release") class LandscapeError(RuntimeError): """A user-actionable failure while retrieving or interpreting the API.""" def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description=( "Query the public CNCF Landscape JSON API and apply local filters. " "Output is a bounded JSON envelope." ) ) parser.add_argument( "--source", choices=tuple(SOURCE_PATHS), default="projects", help="Collection to query (default: projects).", ) parser.add_argument( "--base-url", default=DEFAULT_BASE_URL, help="API base URL, useful for a compatible mirror or offline test server.", ) parser.add_argument( "--timeout", type=float, default=20.0, help="HTTP timeout in seconds (default: 20).", ) parser.add_argument( "--search", help="Case-insensitive text search across name, summary, description, and taxonomy.", ) parser.add_argument("--category", help="Case-insensitive category match.") parser.add_argument("--subcategory", help="Case-insensitive subcategory match.") parser.add_argument( "--maturity", action="append", help="Project maturity to include; repeat for multiple values.", ) parser.add_argument("--license", dest="license_name", help="Substring match in repository license fields.") parser.add_argument("--country", help="Case-insensitive country match.") parser.add_argument( "--oss-only", action="store_true", help="Keep records whose API record has oss=true.", ) parser.add_argument( "--has-license", action="store_true", help="Keep records with at least one non-empty repository license.", ) parser.add_argument( "--has-release", action="store_true", help="Keep records with at least one non-empty repository latest_release.", ) parser.add_argument( "--min-stars", type=int, metavar="N", help="Keep records with at least N stars in any listed repository.", ) parser.add_argument( "--min-contributors", type=int, metavar="N", help="Keep records with at least N contributors in any listed repository.", ) parser.add_argument( "--sort", choices=SORT_FIELDS, default="name", help="Sort field; numeric/release fields descend by default (default: name).", ) parser.add_argument( "--ascending", action="store_true", help="Sort ascending, including for numeric fields.", ) parser.add_argument( "--limit", type=int, default=20, help="Maximum records to return; 0 means no limit (default: 20).", ) return parser def _fold(value: Any) -> str: return str(value or "").strip().casefold() def _repositories(record: Mapping[str, Any]) -> list[Mapping[str, Any]]: repositories = record.get("repositories") if not isinstance(repositories, list): return [] return [item for item in repositories if isinstance(item, dict)] def _numeric_values(record: Mapping[str, Any], field: str) -> list[int]: values: list[int] = [] for repository in _repositories(record): value = repository.get(field) if isinstance(value, bool): continue if isinstance(value, (int, float)): values.append(int(value)) return values def _licenses(record: Mapping[str, Any]) -> list[str]: return [str(value) for value in (repo.get("license") for repo in _repositories(record)) if value] def _latest_releases(record: Mapping[str, Any]) -> list[str]: return [str(value) for value in (repo.get("latest_release") for repo in _repositories(record)) if value] def _search_text(record: Mapping[str, Any]) -> str: fields = ( record.get("name"), record.get("summary"), record.get("description"), record.get("category"), record.get("subcategory"), ) return " ".join(str(value) for value in fields if value).casefold() def matches(record: Mapping[str, Any], args: argparse.Namespace) -> bool: if args.search and _fold(args.search) not in _search_text(record): return False if args.category and _fold(record.get("category")) != _fold(args.category): return False if args.subcategory and _fold(record.get("subcategory")) != _fold(args.subcategory): return False if args.maturity and _fold(record.get("maturity")) not in {_fold(item) for item in args.maturity}: return False if args.license_name and not any(_fold(args.license_name) in _fold(item) for item in _licenses(record)): return False if args.country and _fold(record.get("country")) != _fold(args.country): return False if args.oss_only and record.get("oss") is not True: return False if args.has_license and not _licenses(record): return False if args.has_release and not _latest_releases(record): return False if args.min_stars is not None and max(_numeric_values(record, "stars"), default=0) < args.min_stars: return False return args.min_contributors is None or max(_numeric_values(record, "contributors"), default=0) >= args.min_contributors def _sort_key(record: Mapping[str, Any], field: str) -> Any: if field == "name": return _fold(record.get("name")) if field == "stars": return max(_numeric_values(record, "stars"), default=0) if field == "contributors": return max(_numeric_values(record, "contributors"), default=0) if field == "latest-release": return max(_latest_releases(record), default="") raise LandscapeError(f"Unsupported sort field: {field}") def select_records( records: Sequence[Mapping[str, Any]], args: argparse.Namespace, ) -> list[Mapping[str, Any]]: selected = [record for record in records if matches(record, args)] descending = not args.ascending and args.sort != "name" selected.sort(key=lambda record: _sort_key(record, args.sort), reverse=descending) if args.limit < 0: raise LandscapeError("--limit must be zero or greater") if args.limit: return selected[: args.limit] return selected def fetch_records( url: str, timeout: float, opener: Callable[..., Any] | None = None, ) -> tuple[list[Mapping[str, Any]], Mapping[str, Any]]: try: request = Request( url, headers={ "Accept": "application/json", "User-Agent": "cncf-landscape-agent-skill/1.0", }, ) except ValueError as exc: raise LandscapeError(f"Invalid Landscape API URL {url}: {exc}") from exc open_url = opener or urlopen try: with open_url(request, timeout=timeout) as response: status = getattr(response, "status", 200) headers = getattr(response, "headers", {}) content_type = str(headers.get("Content-Type", "")) payload = response.read() except HTTPError as exc: raise LandscapeError(f"Landscape API returned HTTP {exc.code} for {url}") from exc except URLError as exc: raise LandscapeError(f"Could not reach Landscape API at {url}: {exc.reason}") from exc except OSError as exc: raise LandscapeError(f"Could not read Landscape API at {url}: {exc}") from exc if status < 200 or status >= 300: raise LandscapeError(f"Landscape API returned HTTP {status} for {url}") if "json" not in content_type.casefold(): raise LandscapeError( f"Expected JSON from {url} but received Content-Type {(content_type or '(missing)')!r}; " "an SPA fallback may have returned HTML." ) try: decoded = json.loads(payload.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError) as exc: raise LandscapeError(f"Landscape API response from {url} was not valid UTF-8 JSON") from exc if not isinstance(decoded, list): raise LandscapeError(f"Expected a JSON array from {url}") records: list[Mapping[str, Any]] = [] for item in decoded: if not isinstance(item, dict): raise LandscapeError(f"Landscape API response from {url} contained a non-object record") records.append(item) return records, headers def _filters(args: argparse.Namespace) -> dict[str, Any]: return { key: value for key, value in { "search": args.search, "category": args.category, "subcategory": args.subcategory, "maturity": args.maturity, "license": args.license_name, "country": args.country, "oss_only": args.oss_only, "has_license": args.has_license, "has_release": args.has_release, "min_stars": args.min_stars, "min_contributors": args.min_contributors, }.items() if value not in (None, False, [], "") } def run(args: argparse.Namespace, opener: Callable[..., Any] | None = None) -> dict[str, Any]: url = args.base_url.rstrip("/") + SOURCE_PATHS[args.source] records, headers = fetch_records(url, args.timeout, opener=opener) selected = select_records(records, args) return { "source": args.source, "endpoint": url, "retrieved_at": datetime.now(timezone.utc).isoformat(), "last_modified": headers.get("Last-Modified"), "total_records": len(records), "returned_records": len(selected), "filters": _filters(args), "sort": {"field": args.sort, "ascending": args.ascending}, "items": selected, } def main(argv: Sequence[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) try: result = run(args) except LandscapeError as exc: print(f"Error: {exc}", file=sys.stderr) return 2 json.dump(result, sys.stdout, indent=2) print() return 0 if __name__ == "__main__": sys.exit(main())
-
-
tests
-
test_landscape_query.py 6.1 KB
#!/usr/bin/env python3 """Offline tests for the CNCF Landscape query client.""" import importlib.util import io import json import unittest from contextlib import redirect_stderr from pathlib import Path SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "landscape_query.py" SPEC = importlib.util.spec_from_file_location("landscape_query", SCRIPT) if SPEC is None or SPEC.loader is None: raise RuntimeError("could not load landscape_query.py") MODULE = importlib.util.module_from_spec(SPEC) SPEC.loader.exec_module(MODULE) class FakeResponse: def __init__(self, payload, content_type="application/json", status=200, headers=None): self.payload = payload self.status = status self.headers = {"Content-Type": content_type} if headers: self.headers.update(headers) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): return False def read(self): return self.payload def fake_opener(response): def opener(request, timeout): assert timeout == 4.0 assert request.full_url.endswith("/projects/all.json") assert request.headers.get("Accept") == "application/json" return response return opener class LandscapeQueryTests(unittest.TestCase): def setUp(self): self.records = [ { "id": "alpha", "name": "Alpha Trace", "summary": "Tracing for Kubernetes", "category": "Observability and Analysis", "subcategory": "Observability", "maturity": "graduated", "oss": True, "country": "United States", "repositories": [ { "primary": True, "stars": 40, "contributors": 12, "license": "Apache License 2.0", "latest_release": "2026-07-01T00:00:00Z", } ], }, { "id": "beta", "name": "Beta Trace", "summary": "Tracing for a different runtime", "category": "Observability and Analysis", "subcategory": "Observability", "maturity": "incubating", "oss": True, "country": "Canada", "repositories": [ { "primary": True, "stars": 100, "contributors": 4, "license": None, "latest_release": None, } ], }, { "id": "gamma", "name": "Gamma Router", "summary": "A gateway", "category": "Orchestration & Management", "subcategory": "API Gateway", "maturity": "sandbox", "oss": True, "repositories": [], }, ] def test_filters_and_sorts_projects(self): args = MODULE.build_parser().parse_args( [ "--search", "tracing", "--category", "Observability and Analysis", "--subcategory", "Observability", "--maturity", "graduated", "--has-license", "--has-release", "--min-stars", "30", "--sort", "stars", ] ) selected = MODULE.select_records(self.records, args) self.assertEqual([item["id"] for item in selected], ["alpha"]) def test_repeated_maturity_filter_and_limit_zero(self): args = MODULE.build_parser().parse_args( ["--maturity", "graduated", "--maturity", "incubating", "--limit", "0"] ) selected = MODULE.select_records(self.records, args) self.assertEqual([item["id"] for item in selected], ["alpha", "beta"]) def test_run_returns_snapshot_metadata_and_raw_items(self): response = FakeResponse( json.dumps(self.records).encode("utf-8"), headers={"Last-Modified": "Tue, 28 Jul 2026 09:06:48 GMT"}, ) args = MODULE.build_parser().parse_args(["--base-url", "https://example.test/api", "--timeout", "4"]) result = MODULE.run(args, opener=fake_opener(response)) self.assertEqual(result["endpoint"], "https://example.test/api/projects/all.json") self.assertEqual(result["total_records"], 3) self.assertEqual(result["returned_records"], 3) self.assertEqual(result["last_modified"], "Tue, 28 Jul 2026 09:06:48 GMT") self.assertEqual(result["items"][0]["id"], "alpha") def test_html_fallback_fails_closed(self): response = FakeResponse(b"<html>single page app</html>", content_type="text/html") with self.assertRaisesRegex(MODULE.LandscapeError, "Expected JSON"): MODULE.fetch_records("https://example.test/api/projects/all.json", 4.0, fake_opener(response)) def test_malformed_base_url_uses_cli_error_path(self): stderr = io.StringIO() with redirect_stderr(stderr): exit_code = MODULE.main(["--base-url", "not-a-url", "--timeout", "1"]) self.assertEqual(exit_code, 2) self.assertIn("Error: Invalid Landscape API URL", stderr.getvalue()) self.assertNotIn("Traceback", stderr.getvalue()) def test_non_object_records_fail_closed(self): response = FakeResponse(b"[{}\n,\"not an object\"]") with self.assertRaisesRegex(MODULE.LandscapeError, "non-object record"): MODULE.fetch_records("https://example.test/api/projects/all.json", 4.0, fake_opener(response)) def test_negative_limit_is_rejected(self): args = MODULE.build_parser().parse_args(["--limit", "-1"]) with self.assertRaisesRegex(MODULE.LandscapeError, "--limit"): MODULE.select_records(self.records, args) if __name__ == "__main__": unittest.main()
-
-
README.md 2.7 KB
# CNCF Landscape Discover cloud-native technologies and turn the result into a decision-ready shortlist with evidence, trade-offs, and a validation plan. ## Why Install This Skill When an architecture question starts with “what exists for this?”, an agent can easily return a familiar-name list or rank projects by stars. This skill gives it a live, source-grounded discovery path through the CNCF Landscape and a disciplined way to separate catalog facts from engineering judgment. It is useful for architects and engineers exploring a capability that is not yet in their stack. The bundled query tool handles the static API’s filtering and bounded JSON output; the skill then asks the questions the catalog cannot answer: who will operate it, what constraints matter, what evidence is missing, and what small experiment could falsify the recommendation. ## What You Get | Path | Purpose | |---|---| | `SKILL.md` | Trigger boundaries, query workflow, evidence discipline, and completion criteria | | `scripts/landscape_query.py` | Read-only stdlib CLI for live project/member queries and local filtering | | `references/api.md` | Verified endpoint map, field semantics, and static-site caveats | | `references/decision-framework.md` | Candidate comparison and recommendation method | | `references/output-template.md` | Reusable decision artifact structure | | `tests/test_landscape_query.py` | Offline client and filter tests | | `evals/evals.json` | Six output-quality cases covering normal and failure paths | | `evals/trigger-queries.json` | Three should-trigger and two should-not-trigger routing probes | ## Quick Start Requires Python 3.8+ and outbound HTTPS access. No API key is required. ```bash python3 scripts/landscape_query.py \ --category "Observability and Analysis" \ --subcategory Observability \ --search tracing \ --maturity graduated \ --has-license --has-release \ --sort stars --limit 10 ``` The command emits a JSON envelope containing the source endpoint, retrieval time, filters, counts, and matching records. Ask an Agent Skills-compatible assistant to interpret that evidence against your workload and constraints rather than treating the result as an automatic ranking. ## Triggers Use when discovering or comparing CNCF/cloud-native projects, building a shortlist for an architecture decision, filtering technology candidates by maturity or repository evidence, or investigating what tools exist for a capability missing from the current stack. Do not use it for operating a named project, general architecture methodology, or legal/procurement conclusions. ## Requirements - Python 3.8 or newer - Network access to `https://landscape.cncf.io` - No credentials or third-party Python packages -
SKILL.md 8.8 KB
--- name: cncf-landscape description: >- Use this skill when discovering and comparing cloud-native technologies from the CNCF Landscape for an architecture or engineering decision. Query the live public Landscape API, filter candidates by capability, category, maturity, license, and repository signals, then produce an evidence-backed shortlist with trade-offs, unknowns, and validation steps. Do not use it as a substitute for project documentation, production-readiness testing, legal review, or general architecture methodology. license: MIT compatibility: Requires Python 3.8+ and outbound HTTPS access to landscape.cncf.io for live queries; no API key is required. metadata: source_repo: https://github.com/cncf/landscape2 api: https://landscape.cncf.io/api/ --- # CNCF Landscape technology selection Use this skill to turn a capability or architecture problem into a defensible shortlist of cloud-native technologies. The Landscape is a discovery and evidence source, not a recommendation engine. ## When to load Load this skill when someone: - asks what projects or tools exist for a capability that is not in the current stack; - wants to compare CNCF projects by maturity, category, repository signals, license, or ecosystem evidence; - asks for a shortlist for an architecture decision, proof of concept, technology radar entry, or build-versus-buy discussion; - needs to discover a CNCF project before reading its documentation or source repository. ## When not to use - For operating or configuring a named technology, load its operational skill or use its authoritative documentation. - For the general adoption/hold governance process, load [technology-radar](../technology-radar/SKILL.md) and use this skill only for candidate discovery and evidence. - For broad platform architecture, data architecture, or API design without a Landscape discovery question, use the matching methodology skill. - For procurement, contract, export-control, or licensing advice, treat this skill's license fields as discovery evidence and obtain qualified review. ## Decision workflow 1. **Frame the decision before searching.** Capture the capability, workload, interfaces, runtime and topology, scale and SLOs, data sensitivity, deployment model, team ownership, operational skills, budget, timeline, license constraints, and acceptable maturity risk. Separate hard constraints from preferences. If the user has not supplied these, ask for the smallest missing set rather than pretending that a category name is a requirement. 2. **Discover candidates from the live API.** Start with the bundled query tool: ```bash python3 scripts/landscape_query.py --help python3 scripts/landscape_query.py \ --category "Observability and Analysis" \ --subcategory Observability \ --search tracing \ --maturity graduated \ --has-license --has-release \ --sort stars --limit 10 ``` Load [references/api.md](references/api.md) when selecting an endpoint, interpreting a field, or diagnosing a response. Use the projects source for technology candidates. Use members or end-users only for ecosystem context; membership is not a product-quality signal. 3. **Apply hard filters first.** Filter by capability and category, then by explicit maturity, license, repository evidence, deployment constraints, or other user-supplied requirements. Do not turn stars, contributor counts, or CNCF maturity into implicit hard requirements unless the user asks for them. 4. **Inspect the shortlist.** Use the `id` returned by `projects/all.json` to fetch each project's per-record endpoint. Record the API endpoint and retrieval time. Read the project's own documentation, supported deployment paths, release history, source repository, license, and security/advisory material before making implementation claims. 5. **Compare fit, not fame.** Use [references/decision-framework.md](references/decision-framework.md) and [references/output-template.md](references/output-template.md). Distinguish: - **Observed:** fields returned by the Landscape or statements verified in project documentation; - **Inferred:** a reasoned implication, such as likely ecosystem reach from repository activity; - **Unknown:** a requirement the available evidence does not establish. Never rank a project solely by stars, CNCF maturity, membership, or a generated score. 6. **Make the recommendation conditional.** Name a best fit only against the stated constraints. Include credible alternatives, excluded candidates and the reason for exclusion, material trade-offs, reversibility and migration concerns, and the next experiment that could disprove the recommendation. 7. **Close with a validation plan.** Define a bounded proof of concept or documentation/source review that exercises the user's real interfaces, workload, security boundary, operability, upgrade path, and failure modes. A Landscape record can identify what to investigate; it cannot prove production readiness. ## Query tool contract `scripts/landscape_query.py` is a read-only, non-interactive, standard-library client. It fetches one generated JSON snapshot, applies local filters, and writes JSON to stdout. Diagnostics go to stderr and failures return a non-zero exit code. Useful filters include `--search`, `--category`, `--subcategory`, repeated `--maturity`, `--license`, `--country`, `--oss-only`, `--has-license`, `--has-release`, `--min-stars`, `--min-contributors`, `--sort`, and `--limit`. The default limit is deliberately bounded; use `--limit 0` only when the complete result set is needed. The query tool does not score or recommend projects. Keep the raw records and explain any ranking or weighting in the decision artifact. ## Available Scripts This skill bundles one script; there are no others to discover. | Script | Purpose | Invocation | |---|---|---| | `scripts/landscape_query.py` | Read-only, standard-library client for the public CNCF Landscape API. Fetches one generated JSON snapshot (`--source projects`, `members`, or `end-users`), applies local filters, and writes a bounded JSON envelope to stdout (diagnostics go to stderr; failures return a non-zero exit code). Run it at the discovery step of the decision workflow whenever candidate technologies are needed; start with `--help` when unsure which filters apply. | `python3 scripts/landscape_query.py --category "Observability and Analysis" --search tracing --maturity graduated --limit 10` | Useful filters include `--search`, `--category`, `--subcategory`, repeated `--maturity`, `--license`, `--country`, `--oss-only`, `--has-license`, `--has-release`, `--min-stars`, `--min-contributors`, `--sort`, and `--limit`. The default limit is deliberately bounded; use `--limit 0` only when the complete result set is needed. ## Evidence discipline - The CNCF maturity value describes the project's CNCF lifecycle status, not its fit, security, support contract, or operational simplicity. - Repository stars and contributors are directional activity signals with snapshot and repository-selection caveats. They are not adoption, reliability, or support guarantees. - A repository license field is a discovery signal, not a legal conclusion. Verify the exact repository, version, dependencies, and organizational policy. - A latest-release field does not establish release quality, compatibility, patch policy, or support duration. - Category and subcategory labels help find candidates; they do not establish that a project implements every part of the requested capability. - When the API is unavailable or returns non-JSON content, report that limitation. Do not invent a current catalog, counts, or project status from memory. ## Prerequisites - Python 3.8+ with standard library only; `landscape_query.py` requires no third-party packages or API key. - Outbound HTTPS access to `landscape.cncf.io` for live queries; point `--base-url` at a mirror or offline test server when needed. ## Limitations - Each invocation fetches one generated JSON snapshot; results reflect that snapshot's currency rather than real-time repository state, and the tool cannot read project documentation or source repositories for you. - It does not score, rank, or recommend projects: stars, contributors, CNCF maturity, membership, and license fields remain discovery evidence subject to the caveats above. - When the API is unavailable or returns non-JSON content, the tool fails loudly by design; do not substitute remembered catalog data for its output. ## Completion criteria The skill is complete when the response contains a bounded candidate set, the query/source evidence used to create it, explicit hard filters and assumptions, observed-versus-inferred distinctions, trade-offs and exclusions, unresolved risks, and a concrete validation next step. Stop and report the blocker if the Landscape API and the authoritative project sources needed for the decision are unavailable.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.