+ Add to your service repo's apm.yml. Pinned to
+ {TAG} so consumers are reproducible.
+
+ Then in CI / locally:
+
+ + Install directly from the org/repo path. Useful for one-off experiments. +
+
+ + Register the marketplace once, then install any package by short name. + See the + {' '}APM marketplace guide. +
+
+ {plugin.marketplaceDescription}
+ diff --git a/docs/src/components/PluginGrid.astro b/docs/src/components/PluginGrid.astro new file mode 100644 index 0000000..2c37737 --- /dev/null +++ b/docs/src/components/PluginGrid.astro @@ -0,0 +1,18 @@ +--- +import { plugins } from '../data/plugins'; +import PluginCard from './PluginCard.astro'; +const { tier, phase } = Astro.props as { + tier?: 'foundation' | 'phase' | 'accelerator'; + phase?: string; +}; +let list = plugins; +if (tier) list = list.filter((p) => p.tier === tier); +if (phase) list = list.filter((p) => p.phase === phase); +--- +{list.length === 0 ? ( +No plugins ship for this scope yet — roadmap.
+) : ( ++ {tierLabel} + {plugin.phase && {plugin.phase}} + v{plugin.version} +
+ +{plugin.description} + +{prim.kind}
+ {prim.path}
+ + {tierLabel} + {plugin.phase && {plugin.phase}} + v{plugin.version} +
+ +{plugin.description} + +{prim.kind}
+ {prim.path}
+ v{VERSION} — {plugins.length} plugins in
+three tiers.
+
++ {tierLabel} + {plugin.phase && {plugin.phase}} + v{plugin.version} +
+ +{plugin.description} + +{prim.kind}
+ {prim.path}
+ + {tierLabel} + {plugin.phase && {plugin.phase}} + v{plugin.version} +
+ +{plugin.description} + +{prim.kind}
+ {prim.path}
+ + {tierLabel} + {plugin.phase && {plugin.phase}} + v{plugin.version} +
+ +{plugin.description} + +{prim.kind}
+ {prim.path}
+ + {tierLabel} + {plugin.phase && {plugin.phase}} + v{plugin.version} +
+ +{plugin.description} + +{prim.kind}
+ {prim.path}
+ + {tierLabel} + {plugin.phase && {plugin.phase}} + v{plugin.version} +
+ +{plugin.description} + +{prim.kind}
+ {prim.path}
+ {POLICY_REPO}/apm-policy.yml
+
+ + The single source of truth. Edit there to change the rules for every Zava repo. +
+
+ Upstream APM docs explaining how apm audit --ci discovers and applies the policy.
+
+
+If the command exits non-zero, your repo is out of compliance. The output
+points at the offending file and rule id; cross-reference with
+`DevExpGbb/.github/apm-policy.yml`.
+
+## Changing the policy
+
+The policy is org-wide — changes affect every Zava repo on the next CI run.
+Open a PR against [`{POLICY_REPO}`]({POLICY_FILE_URL}) and follow the
+governance review process documented there.
+
+apm.yml. One change here propagates to every IDE, every PR, every coding-agent run.
+ image:
+ file: ../../assets/zava.svg
+ actions:
+ - text: Browse the catalog
+ link: /catalog/
+ icon: right-arrow
+ variant: primary
+ - text: Quick start
+ link: /quick-start/
+ icon: rocket
+ - text: View on GitHub
+ link: https://github.com/DevExpGbb/zava-agent-config
+ icon: external
+ variant: minimal
+---
+
+import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components';
+import PolicyBanner from '../../components/PolicyBanner.astro';
+import SDLCRibbon from '../../components/SDLCRibbon.astro';
+import PluginGrid from '../../components/PluginGrid.astro';
+import { VERSION } from '../../data/plugins';
+
+v{VERSION} · tag pattern v{version} · pinned by every consumer.
diff --git a/docs/src/content/docs/install.mdx b/docs/src/content/docs/install.mdx
new file mode 100644
index 0000000..68806c4
--- /dev/null
+++ b/docs/src/content/docs/install.mdx
@@ -0,0 +1,67 @@
+---
+title: Install patterns
+description: Three supported ways to install Zava marketplace plugins — apm.yml pin, one-shot CLI, or marketplace registration.
+---
+
+import { Code, Tabs, TabItem, LinkCard } from '@astrojs/starlight/components';
+import { REPO, TAG, APM_MARKETPLACE_DOCS_URL, APM_QUICKSTART_URL } from '../../data/plugins';
+
+Three supported install flows. **`apm.yml` pinning is the recommended path for
+service repos** — it's the only flow CI can audit deterministically.
+
+## 1. Pin in `apm.yml` (recommended)
+
+Pin individual plugins to the current marketplace tag (`v6.1.0` ships today).
+Reproducible across every developer's IDE, every PR check, and every
+coding-agent run.
+
+/apm.yml
+dependencies:
+ apm:
+ - ${REPO}/plugins/secure-baseline#${TAG}
+ - ${REPO}/plugins/code-kit#${TAG}
+ - ${REPO}/plugins/review-kit#${TAG}
+ - ${REPO}/plugins/release-kit#${TAG}
+ # operate-kit, ideate-kit not needed → not pinned
+`} />
+
+Then:
+
+
+
+[APM quick start →]({APM_QUICKSTART_URL})
+
+## 2. One-shot CLI install
+
+Install a plugin directly from the org/repo path. Good for spikes &
+experiments; **not** the path for production service repos because the pin
+isn't recorded anywhere.
+
+
+
+## 3. Register the marketplace, then install by name
+
+Register `zava-agent-config` as a marketplace once, then install any package
+by short name. Useful when you'll consume several packages and want compact
+commands.
+
+
+
+Marketplace registration & install syntax is documented upstream:
+
+
+
+## Which one should I pick?
+
+| You are… | Use |
+| ------------------------------------------- | ------------------------- |
+| A Zava service repo (storefront, checkout…) | **`apm.yml` pin** |
+| Trying a plugin in a throwaway sandbox | One-shot CLI |
+| Onboarding a new repo to the whole catalog | Marketplace + named pins |
diff --git a/docs/src/content/docs/quick-start.mdx b/docs/src/content/docs/quick-start.mdx
new file mode 100644
index 0000000..6f352e6
--- /dev/null
+++ b/docs/src/content/docs/quick-start.mdx
@@ -0,0 +1,56 @@
+---
+title: Quick start
+description: Install your first plugin from the Zava marketplace in under a minute.
+---
+
+import { LinkCard } from '@astrojs/starlight/components';
+import PolicyBanner from '../../components/PolicyBanner.astro';
+
+
+
+## 1. Install the APM CLI
+
+If you haven't already — see the
+[APM quick start](https://microsoft.github.io/apm/getting-started/quick-start/).
+
+## 2. Pin the plugins you need in `apm.yml`
+
+Pin `secure-baseline` (mandatory) plus the phase kits your repo exercises.
+Pinned to the current marketplace tag so installs are reproducible.
+
+```yaml
+# zava-storefront/apm.yml
+dependencies:
+ apm:
+ - DevExpGbb/zava-agent-config/plugins/secure-baseline#v6.1.0
+ - DevExpGbb/zava-agent-config/plugins/code-kit#v6.1.0
+ - DevExpGbb/zava-agent-config/plugins/review-kit#v6.1.0
+ - DevExpGbb/zava-agent-config/plugins/release-kit#v6.1.0
+```
+
+## 3. Install (locally and in CI)
+
+```bash
+apm install
+apm audit --ci
+```
+
+## That's it
+
+Your repo now inherits the pinned plugins' skills, personas and instructions.
+Layered service-local instructions in your own `.apm/` always win over
+inherited ones.
+
+## Other ways to install
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/build.mdx b/docs/src/content/docs/sdlc/build.mdx
new file mode 100644
index 0000000..a3d84eb
--- /dev/null
+++ b/docs/src/content/docs/sdlc/build.mdx
@@ -0,0 +1,19 @@
+---
+title: Build
+description: Compilation, packaging and artefact production.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — BUILD.** Compilation, packaging and artefact production.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/code.mdx b/docs/src/content/docs/sdlc/code.mdx
new file mode 100644
index 0000000..31a4f41
--- /dev/null
+++ b/docs/src/content/docs/sdlc/code.mdx
@@ -0,0 +1,19 @@
+---
+title: Code
+description: Local authoring with design-intent guidance.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — CODE.** Local authoring with design-intent guidance.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/ideate.mdx b/docs/src/content/docs/sdlc/ideate.mdx
new file mode 100644
index 0000000..bcc3585
--- /dev/null
+++ b/docs/src/content/docs/sdlc/ideate.mdx
@@ -0,0 +1,19 @@
+---
+title: Ideate
+description: Turning unstructured input into well-formed work items.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — IDEATE.** Turning unstructured input into well-formed work items.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/operate.mdx b/docs/src/content/docs/sdlc/operate.mdx
new file mode 100644
index 0000000..9d4dad2
--- /dev/null
+++ b/docs/src/content/docs/sdlc/operate.mdx
@@ -0,0 +1,19 @@
+---
+title: Operate
+description: Production telemetry, incident response, remediation PRs.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — OPERATE.** Production telemetry, incident response, remediation PRs.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/plan.mdx b/docs/src/content/docs/sdlc/plan.mdx
new file mode 100644
index 0000000..31885fe
--- /dev/null
+++ b/docs/src/content/docs/sdlc/plan.mdx
@@ -0,0 +1,19 @@
+---
+title: Plan
+description: Breaking work into shaped issues, milestones and tickets.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — PLAN.** Breaking work into shaped issues, milestones and tickets.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/release.mdx b/docs/src/content/docs/sdlc/release.mdx
new file mode 100644
index 0000000..dbd9be3
--- /dev/null
+++ b/docs/src/content/docs/sdlc/release.mdx
@@ -0,0 +1,19 @@
+---
+title: Release
+description: CI/CD golden paths, deploy gates, environment promotion.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — RELEASE.** CI/CD golden paths, deploy gates, environment promotion.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/review.mdx b/docs/src/content/docs/sdlc/review.mdx
new file mode 100644
index 0000000..a995bb8
--- /dev/null
+++ b/docs/src/content/docs/sdlc/review.mdx
@@ -0,0 +1,19 @@
+---
+title: Review
+description: Pre-PR self-review and human-readable diffs.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — REVIEW.** Pre-PR self-review and human-readable diffs.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/sdlc/test.mdx b/docs/src/content/docs/sdlc/test.mdx
new file mode 100644
index 0000000..6c49898
--- /dev/null
+++ b/docs/src/content/docs/sdlc/test.mdx
@@ -0,0 +1,19 @@
+---
+title: Test
+description: Automated testing and coverage gates.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+**SDLC phase — TEST.** Automated testing and coverage gates.
+
+## Kits available
+
+
+
+
diff --git a/docs/src/content/docs/tiers/accelerators.mdx b/docs/src/content/docs/tiers/accelerators.mdx
new file mode 100644
index 0000000..31ffef0
--- /dev/null
+++ b/docs/src/content/docs/tiers/accelerators.mdx
@@ -0,0 +1,13 @@
+---
+title: Accelerators
+description: Episodic, opt-in plugins. Pin during a planned migration, remove afterwards.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+
+Accelerators don't map to a single SDLC phase. Their value is **bursty** — you
+pin them during a planned migration and remove them once the migration
+completes. Leaving an unused accelerator pinned isn't dangerous, but it
+muddies the "what is this repo asking the agent to do today" signal.
+
+
diff --git a/docs/src/content/docs/tiers/foundation.mdx b/docs/src/content/docs/tiers/foundation.mdx
new file mode 100644
index 0000000..a705994
--- /dev/null
+++ b/docs/src/content/docs/tiers/foundation.mdx
@@ -0,0 +1,18 @@
+---
+title: Foundation
+description: The cross-cutting baseline — explicitly pinned by every Zava service repo.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import { LinkCard } from '@astrojs/starlight/components';
+
+Cross-cutting plugins that sit **under** the SDLC ribbon. Every Zava service
+pins these explicitly — they are never satisfied transitively by phase kits.
+
+
+
+
diff --git a/docs/src/content/docs/tiers/phase-kits.mdx b/docs/src/content/docs/tiers/phase-kits.mdx
new file mode 100644
index 0000000..374b20e
--- /dev/null
+++ b/docs/src/content/docs/tiers/phase-kits.mdx
@@ -0,0 +1,14 @@
+---
+title: Phase kits
+description: One kit per covered SDLC phase. Pin only the phases your repo actually exercises.
+---
+
+import PluginGrid from '../../../components/PluginGrid.astro';
+import SDLCRibbon from '../../../components/SDLCRibbon.astro';
+
+Phase kits compound in lockstep with developer flow — one per phase covered,
+independently versioned.
+
+
+
+
diff --git a/docs/src/data/plugins.ts b/docs/src/data/plugins.ts
new file mode 100644
index 0000000..5031b22
--- /dev/null
+++ b/docs/src/data/plugins.ts
@@ -0,0 +1,287 @@
+/**
+ * Build-time loader for the Zava marketplace catalog.
+ *
+ * Source of truth (in priority order):
+ * 1. ../.claude-plugin/marketplace.json — the plugin list & order
+ * 2. ../apm.yml — top-level marketplace version
+ * 3. ../plugins//apm.yml — per-plugin version + description
+ * 4. ../plugins//.claude-plugin/plugin.json — keywords (drive tier/phase)
+ * 5. ../plugins//.apm/{agents,skills,instructions,commands,hooks}
+ * — primitive inventory
+ *
+ * Editorial overrides for "Pin if you…" + "Pairs with" live in PLUGIN_NOTES
+ * below; everything else is derived. Add a new plugin to the marketplace and
+ * tag its keywords correctly and it will appear in the site automatically.
+ */
+
+import fs from 'node:fs';
+import path from 'node:path';
+import yaml from 'js-yaml';
+
+/* The site lives under /docs and Astro always runs from that directory,
+ * so the marketplace lives one level up. We walk a couple of levels just in
+ * case (e.g. someone runs `astro` from the repo root via `npm --prefix`). */
+function findRepoRoot(): string {
+ let cur = process.cwd();
+ for (let i = 0; i < 4; i++) {
+ if (fs.existsSync(path.join(cur, '.claude-plugin', 'marketplace.json'))) {
+ return cur;
+ }
+ cur = path.dirname(cur);
+ }
+ throw new Error(
+ 'Could not locate .claude-plugin/marketplace.json walking up from ' +
+ process.cwd(),
+ );
+}
+
+const REPO_ROOT = findRepoRoot();
+
+export const REPO = 'DevExpGbb/zava-agent-config';
+export const REPO_URL = `https://github.com/${REPO}`;
+export const POLICY_REPO = 'DevExpGbb/.github';
+export const POLICY_FILE_URL =
+ 'https://github.com/DevExpGbb/.github/blob/main/apm-policy.yml';
+export const POLICY_DOCS_URL =
+ 'https://microsoft.github.io/apm/guides/ci-policy-setup/';
+export const APM_MARKETPLACE_DOCS_URL =
+ 'https://microsoft.github.io/apm/guides/marketplaces/';
+export const APM_QUICKSTART_URL =
+ 'https://microsoft.github.io/apm/getting-started/quick-start/';
+
+export type Tier = 'foundation' | 'phase' | 'accelerator';
+export type Phase =
+ | 'IDEATE'
+ | 'PLAN'
+ | 'CODE'
+ | 'BUILD'
+ | 'TEST'
+ | 'REVIEW'
+ | 'RELEASE'
+ | 'OPERATE';
+
+export interface Primitive {
+ name: string;
+ kind: 'persona' | 'skill' | 'instructions' | 'command' | 'hook';
+ /** Repo-relative path. */
+ path: string;
+}
+
+export interface Plugin {
+ id: string;
+ name: string;
+ version: string;
+ tier: Tier;
+ phase?: Phase;
+ description: string;
+ /** Marketplace-level description (often shorter than apm.yml's). */
+ marketplaceDescription: string;
+ keywords: string[];
+ /** Repo-relative source path. */
+ source: string;
+ primitives: Primitive[];
+ pinIf: string;
+ pairsWith: string[];
+}
+
+const PHASES_LIST: Phase[] = [
+ 'IDEATE', 'PLAN', 'CODE', 'BUILD', 'TEST', 'REVIEW', 'RELEASE', 'OPERATE',
+];
+
+export const PHASES: { id: Phase; label: string }[] = PHASES_LIST.map((p) => ({
+ id: p,
+ label: p[0] + p.slice(1).toLowerCase(),
+}));
+
+export const TIER_LABEL: Record = {
+ foundation: 'Foundation',
+ phase: 'Phase kit',
+ accelerator: 'Accelerator',
+};
+
+/* --------------------------------------------------------------------------
+ * Editorial overrides — kept tiny so most of the catalog stays auto-derived.
+ * Add an entry only when "Pin if you…" / "Pairs with" can't be inferred.
+ * -------------------------------------------------------------------------- */
+const PLUGIN_NOTES: Record<
+ string,
+ { pinIf?: string; pairsWith?: string[] }
+> = {
+ 'secure-baseline': {
+ pinIf:
+ 'You ship anything. Mandatory explicit pin for every Zava service — never satisfied transitively.',
+ pairsWith: ['code-kit', 'review-kit', 'release-kit'],
+ },
+ 'ideate-kit': {
+ pinIf:
+ 'Your repo receives unstructured input (transcripts, voice notes, tickets) that should become tracked work.',
+ pairsWith: ['secure-baseline'],
+ },
+ 'code-kit': {
+ pinIf: 'You want design-intent guidance during local authoring.',
+ pairsWith: ['secure-baseline', 'review-kit'],
+ },
+ 'review-kit': {
+ pinIf:
+ 'You want a multi-perspective self-review of staged changes before push.',
+ pairsWith: ['secure-baseline', 'code-kit'],
+ },
+ 'release-kit': {
+ pinIf:
+ 'You operate any CI/CD pipeline — reusable workflows, deploy gates, env promotion.',
+ pairsWith: ['secure-baseline'],
+ },
+ 'operate-kit': {
+ pinIf:
+ 'You wire Azure SRE Agent findings into remediation pull requests.',
+ pairsWith: ['secure-baseline'],
+ },
+ 'modernize-kit': {
+ pinIf:
+ 'You have a planned framework migration. Episodic — remove the pin once the migration completes.',
+ pairsWith: ['secure-baseline'],
+ },
+};
+
+/* --------------------------------------------------------------------------
+ * Loaders
+ * -------------------------------------------------------------------------- */
+
+interface MarketplaceManifest {
+ name: string;
+ owner: { name: string; url?: string };
+ plugins: { name: string; description: string; source: string }[];
+}
+
+function readJSON(rel: string): T {
+ return JSON.parse(
+ fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8'),
+ ) as T;
+}
+
+function readYAML(rel: string): T {
+ return yaml.load(
+ fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8'),
+ ) as T;
+}
+
+function tryReadJSON(rel: string): T | undefined {
+ const abs = path.join(REPO_ROOT, rel);
+ if (!fs.existsSync(abs)) return undefined;
+ return JSON.parse(fs.readFileSync(abs, 'utf8')) as T;
+}
+
+function inferTierAndPhase(
+ keywords: string[],
+): { tier: Tier; phase?: Phase } {
+ const kw = keywords.map((k) => k.toLowerCase());
+ if (kw.includes('accelerator')) return { tier: 'accelerator' };
+ if (kw.includes('cross-cutting')) return { tier: 'foundation' };
+ for (const phase of PHASES_LIST) {
+ if (kw.includes(phase.toLowerCase())) return { tier: 'phase', phase };
+ }
+ return { tier: 'foundation' };
+}
+
+function listPrimitives(pluginRelPath: string): Primitive[] {
+ const apmDir = path.join(REPO_ROOT, pluginRelPath, '.apm');
+ if (!fs.existsSync(apmDir)) return [];
+ const out: Primitive[] = [];
+
+ const buckets: { dir: string; kind: Primitive['kind'] }[] = [
+ { dir: 'agents', kind: 'persona' },
+ { dir: 'skills', kind: 'skill' },
+ { dir: 'instructions', kind: 'instructions' },
+ { dir: 'commands', kind: 'command' },
+ { dir: 'hooks', kind: 'hook' },
+ ];
+
+ for (const { dir, kind } of buckets) {
+ const bucketPath = path.join(apmDir, dir);
+ if (!fs.existsSync(bucketPath)) continue;
+ for (const entry of fs.readdirSync(bucketPath, { withFileTypes: true })) {
+ let name = entry.name;
+ if (entry.isFile()) {
+ // architect.agent.md → architect ; foo.instructions.md → foo
+ name = name.replace(/\.(agent|instructions|command|hook)\.md$/, '');
+ name = name.replace(/\.md$/, '');
+ }
+ out.push({
+ name,
+ kind,
+ path: path.posix.join(pluginRelPath, '.apm', dir, entry.name),
+ });
+ }
+ }
+
+ return out;
+}
+
+/* --------------------------------------------------------------------------
+ * Build the catalog
+ * -------------------------------------------------------------------------- */
+
+const marketplace = readJSON(
+ '.claude-plugin/marketplace.json',
+);
+const topLevelApm = readYAML<{ version: string }>('apm.yml');
+
+export const VERSION = topLevelApm.version;
+export const TAG = `v${VERSION}`;
+export const MARKETPLACE_OWNER = marketplace.owner;
+
+export const plugins: Plugin[] = marketplace.plugins.map((entry) => {
+ const id = entry.name;
+ const sourceRel = entry.source.replace(/^\.\//, '');
+
+ const pluginApm = readYAML<{
+ name: string;
+ version: string;
+ description: string;
+ }>(path.posix.join(sourceRel, 'apm.yml'));
+
+ const pluginJson = tryReadJSON<{ keywords?: string[] }>(
+ path.posix.join(sourceRel, '.claude-plugin', 'plugin.json'),
+ );
+ const keywords = pluginJson?.keywords ?? [];
+
+ const { tier, phase } = inferTierAndPhase(keywords);
+ const notes = PLUGIN_NOTES[id] ?? {};
+
+ return {
+ id,
+ name: pluginApm.name,
+ version: pluginApm.version,
+ description: pluginApm.description,
+ marketplaceDescription: entry.description,
+ keywords,
+ tier,
+ phase,
+ source: sourceRel,
+ primitives: listPrimitives(sourceRel),
+ pinIf: notes.pinIf ?? entry.description,
+ pairsWith: notes.pairsWith ?? [],
+ };
+});
+
+/* --------------------------------------------------------------------------
+ * Convenience selectors
+ * -------------------------------------------------------------------------- */
+
+export function getPlugin(id: string): Plugin {
+ const p = plugins.find((x) => x.id === id);
+ if (!p) throw new Error(`Unknown plugin: ${id}`);
+ return p;
+}
+
+export function pluginsByPhase(phase: Phase): Plugin[] {
+ return plugins.filter((p) => p.phase === phase);
+}
+
+export function pluginsByTier(tier: Tier): Plugin[] {
+ return plugins.filter((p) => p.tier === tier);
+}
+
+export function phaseStatus(phase: Phase): 'covered' | 'roadmap' {
+ return pluginsByPhase(phase).length > 0 ? 'covered' : 'roadmap';
+}
diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css
new file mode 100644
index 0000000..9a74477
--- /dev/null
+++ b/docs/src/styles/custom.css
@@ -0,0 +1,118 @@
+/* Zava IDP — Starlight theming.
+ Brand: deep indigo + warm amber accent. */
+
+:root {
+ --sl-font: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI',
+ Roboto, Helvetica, Arial, sans-serif;
+ --sl-font-mono: ui-monospace, 'JetBrains Mono', 'SF Mono', Menlo,
+ 'Cascadia Mono', Consolas, monospace;
+}
+
+:root[data-theme='dark'] {
+ --sl-color-accent-low: #1e1b4b;
+ --sl-color-accent: #818cf8;
+ --sl-color-accent-high: #c7d2fe;
+ --sl-color-bg-nav: #0b0a18;
+}
+
+:root[data-theme='light'] {
+ --sl-color-accent-low: #eef2ff;
+ --sl-color-accent: #4338ca;
+ --sl-color-accent-high: #1e1b4b;
+}
+
+.tier-pill,
+.phase-pill,
+.version-pill {
+ display: inline-block;
+ padding: 0.1rem 0.55rem;
+ border-radius: 999px;
+ font-size: 0.72rem;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ text-transform: uppercase;
+ border: 1px solid transparent;
+ vertical-align: middle;
+ margin-right: 0.35rem;
+}
+
+.tier-pill[data-tier='foundation'] { background: #fff7d6; color: #7a5b00; border-color: #d8b95b; }
+.tier-pill[data-tier='phase'] { background: #d4ecd5; color: #15532b; border-color: #5fa56a; }
+.tier-pill[data-tier='accelerator'] { background: #cfe3f7; color: #163d6c; border-color: #5d92cf; }
+
+.phase-pill { background: #ede4f7; color: #3b1d6b; border-color: #a98ad6; }
+.version-pill { background: #1f2937; color: #e5e7eb; border-color: #374151; }
+
+:root[data-theme='dark'] .tier-pill[data-tier='foundation'] { background: #3a2d05; color: #fde68a; border-color: #a07c1a; }
+:root[data-theme='dark'] .tier-pill[data-tier='phase'] { background: #0f3320; color: #bbf7d0; border-color: #2d7a3a; }
+:root[data-theme='dark'] .tier-pill[data-tier='accelerator'] { background: #0d2545; color: #bfdbfe; border-color: #2563eb; }
+:root[data-theme='dark'] .phase-pill { background: #2a1854; color: #ddd6fe; border-color: #7c3aed; }
+
+/* Catalog grid cards */
+.catalog-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1rem;
+ margin-top: 1.25rem;
+}
+
+.catalog-card {
+ display: block;
+ padding: 1rem 1.1rem;
+ border: 1px solid var(--sl-color-gray-5);
+ border-radius: 12px;
+ background: var(--sl-color-bg-sidebar);
+ text-decoration: none !important;
+ color: inherit;
+ transition: transform 120ms ease, border-color 120ms ease,
+ box-shadow 120ms ease;
+}
+.catalog-card:hover {
+ transform: translateY(-2px);
+ border-color: var(--sl-color-accent);
+ box-shadow: 0 6px 20px -10px var(--sl-color-accent);
+}
+.catalog-card h3 {
+ margin: 0 0 0.35rem;
+ font-size: 1.05rem;
+}
+.catalog-card p {
+ margin: 0.5rem 0 0;
+ font-size: 0.88rem;
+ color: var(--sl-color-gray-2);
+}
+
+/* SDLC ribbon on landing */
+.sdlc-ribbon {
+ display: grid;
+ grid-template-columns: repeat(8, minmax(0, 1fr));
+ gap: 0.4rem;
+ margin: 1.5rem 0 0.5rem;
+}
+.sdlc-cell {
+ text-align: center;
+ padding: 0.6rem 0.3rem;
+ border-radius: 10px;
+ border: 1px solid var(--sl-color-gray-5);
+ background: var(--sl-color-bg-sidebar);
+ font-size: 0.78rem;
+ text-decoration: none !important;
+ color: inherit;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ transition: border-color 120ms ease, transform 120ms ease;
+}
+.sdlc-cell:hover { transform: translateY(-1px); border-color: var(--sl-color-accent); }
+.sdlc-cell[data-status='covered'] { border-color: #5fa56a; }
+.sdlc-cell[data-status='roadmap'] { border-style: dashed; opacity: 0.7; }
+.sdlc-cell .sdlc-name {
+ font-weight: 700;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+}
+.sdlc-cell .sdlc-count { font-size: 0.7rem; color: var(--sl-color-gray-2); }
+
+@media (max-width: 800px) {
+ .sdlc-ribbon { grid-template-columns: repeat(4, minmax(0, 1fr)); }
+}