From 2964cbf2b1ae0c1184b7ac4a8392c5da74ea7ed8 Mon Sep 17 00:00:00 2001 From: Kirill Morozov Date: Tue, 28 Apr 2026 22:03:50 +0200 Subject: [PATCH 1/4] feat(codex): add plugin manifest --- .codex-plugin/plugin.json | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .codex-plugin/plugin.json diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..44fc274 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,37 @@ +{ + "author": { + "name": "Anton Babenko", + "url": "https://github.com/antonbabenko" + }, + "description": "Terraform and OpenTofu best-practices skill for writing, reviewing, debugging, testing, CI, scans, and state operations.", + "homepage": "https://github.com/antonbabenko/terraform-skill", + "interface": { + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "category": "Development", + "developerName": "Anton Babenko", + "displayName": "Terraform Skill", + "longDescription": "Diagnose-first Terraform and OpenTofu guidance with version-aware guardrails for module design, testing strategy, CI/CD workflows, security scanning, compliance checks, and state management.", + "shortDescription": "Terraform and OpenTofu guidance for modules, tests, CI, scans, and state operations.", + "websiteURL": "https://github.com/antonbabenko/terraform-skill" + }, + "keywords": [ + "ci-cd", + "iac", + "infrastructure-as-code", + "modules", + "opentofu", + "security-scanning", + "state-management", + "terraform", + "testing" + ], + "license": "Apache-2.0", + "name": "terraform-skill", + "repository": "https://github.com/antonbabenko/terraform-skill", + "skills": "./skills", + "version": "1.8.0" +} From 0938f834708ace22b430a811fb16b987760d92f1 Mon Sep 17 00:00:00 2001 From: Kirill Morozov Date: Tue, 28 Apr 2026 22:04:26 +0200 Subject: [PATCH 2/4] docs: update codex section --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 0c0a145..f19e8af 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,24 @@ git clone https://github.com/antonbabenko/terraform-skill.git ~/.agents/skills/t Codex auto-discovers skills from `~/.agents/skills/` and `.agents/skills/`. Update with `cd ~/.agents/skills/terraform-skill && git pull`. +Or add the following lines to your `~/.agents/plugins/marketplace.json` file under `plugins[]` to install this skill as a plugin: + +```json +{ + "category": "Productivity", + "name": "terraform-skill", + "policy": { + "authentication": "ON_INSTALL", + "installation": "AVAILABLE" + }, + "source": { + "ref": "master", + "source": "url", + "url": "git@github.com:antonbabenko/terraform-skill.git" + } +} +``` +
From b2629db453dbda4323d10378b1f81fb2df8dde81 Mon Sep 17 00:00:00 2001 From: Anton Babenko Date: Mon, 18 May 2026 10:42:10 +0200 Subject: [PATCH 3/4] fix(codex): version-sync manifest via CI, drop manual marketplace README block The hand-set manifest version (1.8.0) would go stale immediately: this repo's policy is a single CI-owned version source (SKILL.md frontmatter), no hand-edited version numbers. Set it to the current 1.12.0 and wire .codex-plugin/plugin.json into automated-release.yml so every release syncs it alongside SKILL.md, with a validate.yml cross-check that fails the PR on drift. Drop the README block instructing users to hand-add a url-source entry pinned to master in ~/.agents/plugins/marketplace.json: it is unversioned and conflicts with the repo's single-marketplace setup (README already warns that adding antonbabenko/terraform-skill as its own marketplace clashes by name with agent-plugins). The manifest alone enables the supported managed path via the antonbabenko/agent-plugins Codex marketplace; replace the block with a pointer to that path. --- .codex-plugin/plugin.json | 2 +- .github/workflows/automated-release.yml | 48 ++++++++++++++++++++++--- .github/workflows/validate.yml | 32 +++++++++++++++++ README.md | 21 +++-------- 4 files changed, 81 insertions(+), 22 deletions(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 44fc274..95743ec 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -33,5 +33,5 @@ "name": "terraform-skill", "repository": "https://github.com/antonbabenko/terraform-skill", "skills": "./skills", - "version": "1.8.0" + "version": "1.12.0" } diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index 909585f..970b3f3 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -48,11 +48,11 @@ jobs: skip-version-file: 'false' skip-commit: 'false' - # 2b. Sync plugin version, SKILL.md version, and git ref - # This ensures all three version fields stay synchronized: - # - root version (updated by conventional-changelog-action above) - # - plugins[0].version (synced here) + # 2b. Sync version fields and git ref + # This ensures all version fields stay synchronized: + # - root version.json (updated by conventional-changelog-action above) # - SKILL.md metadata.version (synced here) + # - .codex-plugin/plugin.json version (synced here, if present) - name: Sync Plugin Version and SKILL.md if: steps.changelog.outputs.skipped == 'false' env: @@ -90,6 +90,39 @@ jobs: return skill_path + def update_codex_manifest(version): + """Mirror the version into .codex-plugin/plugin.json if present. + + The Codex plugin host reads this manifest when the + agent-plugins marketplace resolves terraform-skill as a + url-source plugin. Keep its version in lockstep with the + SKILL.md single version source so it never goes stale. + """ + manifest_path = '.codex-plugin/plugin.json' + + if not os.path.exists(manifest_path): + return None + + with open(manifest_path, 'r') as f: + lines = f.readlines() + + updated = False + for i, line in enumerate(lines): + # Top-level " "version": "X.Y.Z"" (2 spaces indent) + if line.lstrip().startswith('"version":'): + lines[i] = f' "version": "{version}"\n' + updated = True + break + + if not updated: + raise ValueError( + "Could not find version field in .codex-plugin/plugin.json") + + with open(manifest_path, 'w') as f: + f.writelines(lines) + + return manifest_path + try: version = os.environ['VERSION'] @@ -97,6 +130,12 @@ jobs: skill_path = update_skill_version(version) print(f"✅ Synced {skill_path} metadata.version to {version}") + manifest_path = update_codex_manifest(version) + if manifest_path: + print(f"✅ Synced {manifest_path} version to {version}") + else: + print("ℹ️ No .codex-plugin/plugin.json; skipped") + except Exception as e: print(f"❌ ERROR: Failed to sync versions: {e}") sys.exit(1) @@ -106,6 +145,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add version.json skills/terraform-skill/SKILL.md + [ -f .codex-plugin/plugin.json ] && git add .codex-plugin/plugin.json || true git commit --amend --no-edit git push --force-with-lease diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ac6eec3..73b3c49 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -5,11 +5,13 @@ on: paths: - 'skills/**' - '.claude-plugin/**' + - '.codex-plugin/**' push: branches: [master, main] paths: - 'skills/**' - '.claude-plugin/**' + - '.codex-plugin/**' workflow_dispatch: jobs: @@ -89,6 +91,36 @@ jobs: print(f"✅ Frontmatter valid ({desc_len} chars)") EOF + - name: Check Codex Manifest Version Sync + run: | + python3 << 'EOF' + import json + import os + import sys + import yaml + + manifest_path = '.codex-plugin/plugin.json' + if not os.path.exists(manifest_path): + print("ℹ️ No .codex-plugin/plugin.json; skipping sync check") + sys.exit(0) + + with open('skills/terraform-skill/SKILL.md') as f: + fm = yaml.safe_load(f.read().split('---', 2)[1]) + skill_version = (fm.get('metadata') or {}).get('version') + + with open(manifest_path) as f: + manifest_version = json.load(f).get('version') + + if skill_version != manifest_version: + print( + f"❌ ERROR: version mismatch - SKILL.md metadata.version=" + f"{skill_version!r} vs {manifest_path}={manifest_version!r}. " + f"CI owns these; do not hand-edit (see CLAUDE.md).") + sys.exit(1) + + print(f"✅ Codex manifest version in sync ({manifest_version})") + EOF + - name: Check File Size run: | LINES=$(wc -l < skills/terraform-skill/SKILL.md) diff --git a/README.md b/README.md index f19e8af..eff37cd 100644 --- a/README.md +++ b/README.md @@ -125,23 +125,10 @@ git clone https://github.com/antonbabenko/terraform-skill.git ~/.agents/skills/t Codex auto-discovers skills from `~/.agents/skills/` and `.agents/skills/`. Update with `cd ~/.agents/skills/terraform-skill && git pull`. -Or add the following lines to your `~/.agents/plugins/marketplace.json` file under `plugins[]` to install this skill as a plugin: - -```json -{ - "category": "Productivity", - "name": "terraform-skill", - "policy": { - "authentication": "ON_INSTALL", - "installation": "AVAILABLE" - }, - "source": { - "ref": "master", - "source": "url", - "url": "git@github.com:antonbabenko/terraform-skill.git" - } -} -``` +For a managed Codex plugin install, use the `antonbabenko/agent-plugins` +marketplace (`codex plugin marketplace add antonbabenko/agent-plugins`, then +install `terraform-skill`). Do not add `antonbabenko/terraform-skill` as a +separate marketplace - it clashes by name with `agent-plugins`.
From 479651c6db4a252868a1ac265019ae254eb975bd Mon Sep 17 00:00:00 2001 From: Anton Babenko Date: Mon, 18 May 2026 10:56:24 +0200 Subject: [PATCH 4/4] fix(codex): harden release git-add guard and version validation - automated-release.yml: replace `[ -f ] && git add || true` with an explicit if block so a real `git add` failure is no longer masked under `bash -e`; absent-manifest case still skipped cleanly. - automated-release.yml: anchor the manifest version line match to a 2-space indent (top-level key only) so a nested "version" key can never be matched. - validate.yml: the sync check now requires both versions to exist and be valid semver, not merely equal (equality alone passed when both were missing/empty). Addresses independent GPT and Gemini review feedback on PR #23. Does not fix the separate pre-existing release-tag staleness (tracked separately). --- .github/workflows/automated-release.yml | 9 ++++++--- .github/workflows/validate.yml | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index 970b3f3..aac8818 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -108,8 +108,9 @@ jobs: updated = False for i, line in enumerate(lines): - # Top-level " "version": "X.Y.Z"" (2 spaces indent) - if line.lstrip().startswith('"version":'): + # Top-level key only: exactly 2-space indent. Anchored so a + # nested "version" key could never be matched by accident. + if line.startswith(' "version":'): lines[i] = f' "version": "{version}"\n' updated = True break @@ -145,7 +146,9 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add version.json skills/terraform-skill/SKILL.md - [ -f .codex-plugin/plugin.json ] && git add .codex-plugin/plugin.json || true + if [ -f .codex-plugin/plugin.json ]; then + git add .codex-plugin/plugin.json + fi git commit --amend --no-edit git push --force-with-lease diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 73b3c49..4f711e2 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -96,9 +96,12 @@ jobs: python3 << 'EOF' import json import os + import re import sys import yaml + SEMVER = re.compile(r'^\d+\.\d+\.\d+$') + manifest_path = '.codex-plugin/plugin.json' if not os.path.exists(manifest_path): print("ℹ️ No .codex-plugin/plugin.json; skipping sync check") @@ -111,6 +114,18 @@ jobs: with open(manifest_path) as f: manifest_version = json.load(f).get('version') + # Both must exist and be real semver, not merely equal: equality + # alone passes when both are missing/empty. + for label, value in ( + ('SKILL.md metadata.version', skill_version), + (f'{manifest_path} version', manifest_version), + ): + if not (isinstance(value, str) and SEMVER.match(value)): + print( + f"❌ ERROR: {label} is not a valid semver: {value!r}. " + f"CI owns these; do not hand-edit (see CLAUDE.md).") + sys.exit(1) + if skill_version != manifest_version: print( f"❌ ERROR: version mismatch - SKILL.md metadata.version="