From 70380edd80b261a192836b00ea713ba9682b24b4 Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Tue, 25 Aug 2026 13:25:14 +0300 Subject: [PATCH 1/7] AX-2162 - Add Codex web doc source shared install guidance and doc checks --- .github/workflows/pr.yml | 2 + CONTRIBUTING.md | 8 ++- README.md | 21 +++++++ docs/install-jfrog-plugin-for-codex.md | 81 ++++++++++++++++++++++++++ package.json | 2 +- scripts/validate-install-docs.mjs | 76 ++++++++++++++++++++++++ scripts/validate-install-docs.test.mjs | 50 ++++++++++++++++ 7 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 docs/install-jfrog-plugin-for-codex.md create mode 100644 scripts/validate-install-docs.mjs create mode 100644 scripts/validate-install-docs.test.mjs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index dc53687..0db5dfa 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,6 +19,8 @@ jobs: run: node --test scripts/validate.test.mjs - name: Validate manifests + skills run: node scripts/validate.mjs + - name: Validate install/recovery docs + run: node scripts/validate-install-docs.mjs - name: Version consistency (plugin.json == package.json) run: | PJ=$(jq -r .version .codex-plugin/plugin.json) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55111fb..43bec0e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,8 +13,9 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri 3. **Validate** locally: ```bash -npm test # unit tests for the manifest/skill validator -npm run validate # lint the plugin manifests + skill frontmatter +npm test # unit tests for the manifest/skill validator +npm run validate # lint the plugin manifests + skill frontmatter +node scripts/validate-install-docs.mjs # check install/verify docs stay aligned ``` 1. **Test** by loading your clone as the plugin. The repo root is the marketplace root (`.agents/plugins/marketplace.json` registers the `jfrog` plugin): @@ -41,7 +42,8 @@ This downloads the pinned upstream tarball and replaces the contents of `skills/ ## Pre-release checklist -- [ ] `npm test` and `npm run validate` pass. +- [ ] `npm test`, `npm run validate`, and `node scripts/validate-install-docs.mjs` pass. +- [ ] Install and recovery docs stay aligned: [`README.md`](README.md), [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md), and the [shared guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md) agree on initialization, env vars, restart, verification, and recovery. - [ ] `version` bumped and **identical** in both [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json) and [`package.json`](package.json) (CI enforces the match). - [ ] No secrets, credentials, or API keys committed. - [ ] If the skill tree changed: `pin` in [`scripts/sync-skills-vendor.json`](scripts/sync-skills-vendor.json) matches the upstream tag the new tree was generated from, and the README Prerequisites link points at that tag. diff --git a/README.md b/README.md index 5bf79a9..3af6a50 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ JFrog plugin for [OpenAI Codex](https://developers.openai.com/codex/): artifact management, security scanning, supply-chain best practices, and Agent Guard. +> **Install flow:** Follow the [shared install, verify, and recovery guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md) for the cross-harness rules on initialization, environment variables, restart, verification, and recovery. This README documents **Codex-only** differences. +> +> **Web doc source:** [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md) is the source for the published Codex install page. + ## Features The JFrog plugin provides the following capabilities, grouped by component: @@ -90,6 +94,23 @@ Restart Codex; the `jfrog` MCP server and its tools are now available (verify wi --- +## Verify + +Verification is a required install step, not a troubleshooting fallback. After +restarting Codex, confirm all four: + +1. `codex plugin list` — `jfrog@codex-plugin` is installed and enabled. +2. `/plugins` in the Codex TUI — the JFrog plugin lists its skills (`jfrog` and others). +3. `codex mcp list` — `jfrog` is connected (after `codex mcp login jfrog`). +4. `jf rt ping` — succeeds against your configured JFrog server. + +If any check fails, see the recovery playbook in the +[shared guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). +Setting MCP environment variables by hand does not repair a failed +initialization — re-run initialization instead. + +--- + ## Usage Once configured, interact with the JFrog plugin through natural language. diff --git a/docs/install-jfrog-plugin-for-codex.md b/docs/install-jfrog-plugin-for-codex.md new file mode 100644 index 0000000..3f19deb --- /dev/null +++ b/docs/install-jfrog-plugin-for-codex.md @@ -0,0 +1,81 @@ +# Install JFrog Agent Plugin for OpenAI Codex + +> **Web publication source.** Publish to `https://docs.jfrog.com/ai-ml/docs/codex` (and add to the JFrog Agent Plugins index). Canonical shared flow: [Shared install, verify, and recovery](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md). + +Install and configure the JFrog Agent Plugin for [OpenAI Codex](https://developers.openai.com/codex/), including JFrog Agent Skills, the bundled JFrog Platform MCP server, and Agent Guard. + +## What's included + +| Component | Description | +| --- | --- | +| **JFrog Skills** | Vendored from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills): platform operations, package safety, and AI Catalog workflows. | +| **`jfrog-init`** | Guided setup and readiness check — detects the JFrog CLI, config, MCP, and project, and reports what is missing. | +| **JFrog Platform MCP** | Bundled remote MCP at `https:///mcp` (OAuth via `codex mcp login jfrog`). | +| **Agent Guard** | Discover, install, and manage MCP servers approved in the JFrog AI Catalog. | + +## Prerequisites + +See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#common-prerequisites-all-harnesses). Codex-specific additions: + +- **OpenAI Codex CLI** with plugin support (`codex plugin` commands available). +- **Node.js ≥ 18** with `npx` on `PATH`. + +## Install the JFrog Agent Plugin for Codex + +The plugin is served from the public GitHub repo as a **Git marketplace** (`jfrog/codex-plugin`). + +1. In a regular terminal (not inside the Codex TUI), add the marketplace and install: + + ```bash + codex plugin marketplace add jfrog/codex-plugin + codex plugin add jfrog@codex-plugin + ``` + +2. **Restart Codex** so the plugin and bundled MCP load. + +3. **Run initialization.** Invoke the `jfrog-init` skill in Codex. It detects Node.js, + the JFrog CLI, your server config, the MCP registration, and project resolution, + then walks you through whatever is missing. This is the same initialization step + described in the [shared guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md). + + If you prefer to configure manually, the equivalent steps are: + + ```bash + jf config add + ``` + + then find the install path with `codex plugin list` (row `jfrog@codex-plugin`), edit + `/.mcp.json` to replace `` with your platform host + (for example `mycompany.jfrog.io`), and run `codex mcp login jfrog` to complete + browser OAuth. + +4. **Restart Codex again** after initialization or MCP edits. + +## Verify (required) + +Do not skip verification — treat this as part of installation. + +1. `codex plugin list` — `jfrog@codex-plugin` is installed and enabled. +2. In the Codex TUI, run `/plugins` — the JFrog plugin lists its skills (`jfrog` and others). +3. `codex mcp list` — server `jfrog` is listed and connected after OAuth. +4. `jf rt ping` — succeeds for your configured JFrog server. + +## Codex-specific notes + +- Run `codex plugin list` from a **regular terminal**, not from inside the Codex session, when you need filesystem paths for `.mcp.json`. +- Browse installed plugins and their skills in the Codex TUI with `/plugins`. +- The bundled MCP host is set by editing the plugin's `.mcp.json`, not by exporting a shell variable — Codex reads `` from that file. + +## Recovery + +If MCP tools are missing after install, follow the [shared recovery playbook](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). Setting `JFROG_URL` alone after a partial setup does **not** replace editing the bundled `.mcp.json` and completing `codex mcp login jfrog`. + +## Related topics + +- [JFrog Agent Plugins](https://docs.jfrog.com/ai-ml/docs/jfrog-plugins) +- [Claude Code](https://docs.jfrog.com/ai-ml/docs/claude-code) +- [VS Code](https://docs.jfrog.com/ai-ml/docs/vs-code) +- [Cursor](https://docs.jfrog.com/ai-ml/docs/cursor) +- [OpenCode](https://docs.jfrog.com/ai-ml/docs/opencode) +- [Troubleshoot Plugins](https://docs.jfrog.com/ai-ml/docs/troubleshoot-plugins) +- [Using the MCP Registry with Agent Guard](https://docs.jfrog.com/ai-ml/docs/configure-coding-agents) diff --git a/package.json b/package.json index 32d168e..aa35673 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,6 @@ "scripts": { "sync-skills": "node scripts/sync-skills.mjs", "validate": "node scripts/validate.mjs", - "test": "node --test scripts/validate.test.mjs" + "test": "node --test scripts/validate.test.mjs scripts/validate-install-docs.test.mjs" } } diff --git a/scripts/validate-install-docs.mjs b/scripts/validate-install-docs.mjs new file mode 100644 index 0000000..a5c581f --- /dev/null +++ b/scripts/validate-install-docs.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env node +// Copyright (c) JFrog Ltd. 2026 +// Licensed under the Apache License, Version 2.0 +// Validates install/recovery documentation invariants for AX-2162. + +import { readFileSync, existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = process.cwd(); + +const REQUIRED_README_MARKERS = [ + 'shared-install-and-verify', + '## Verify', +]; + +const FORBIDDEN_PATTERNS = [ + { + re: /setting\s+(?:the\s+)?environment\s+variables?\s+after\s+a\s+failed\s+init\s+may\s+repair/i, + message: 'README must not claim env vars repair failed init', + }, + { + re: /set\s+JFROG_URL.*after.*failed.*init.*fix/i, + message: 'README must not claim JFROG_URL fixes failed init', + }, +]; + +export function validateInstallDocs({ repoRoot: root, harness }) { + const errors = []; + const readmePath = join(root, 'README.md'); + if (!existsSync(readmePath)) { + return [`${harness}: missing README.md`]; + } + const readme = readFileSync(readmePath, 'utf8'); + for (const marker of REQUIRED_README_MARKERS) { + if (!readme.includes(marker)) { + errors.push(`${harness}: README.md missing required marker: ${marker}`); + } + } + for (const { re, message } of FORBIDDEN_PATTERNS) { + if (re.test(readme)) errors.push(`${harness}: ${message}`); + } + + if (harness === 'codex') { + const webDoc = join(root, 'docs', 'install-jfrog-plugin-for-codex.md'); + if (!existsSync(webDoc)) errors.push('codex: missing docs/install-jfrog-plugin-for-codex.md'); + } + if (harness === 'devin') { + const webDoc = join(root, 'docs', 'install-jfrog-plugin-for-devin.md'); + if (!existsSync(webDoc)) errors.push('devin: missing docs/install-jfrog-plugin-for-devin.md'); + } + return errors; +} + +function main() { + const harness = process.env.JFROG_PLUGIN_HARNESS ?? inferHarness(repoRoot); + const errors = validateInstallDocs({ repoRoot, harness }); + if (errors.length) { + console.error('install-docs validation failed:'); + for (const e of errors) console.error(` - ${e}`); + process.exit(1); + } + console.log('install-docs validation passed'); +} + +function inferHarness(root) { + if (existsSync(join(root, '.codex-plugin'))) return 'codex'; + if (existsSync(join(root, '.devin-plugin'))) return 'devin'; + if (existsSync(join(root, '.claude-plugin'))) return 'claude'; + if (existsSync(join(root, 'plugins', 'jfrog', '.cursor-plugin'))) return 'cursor'; + if (existsSync(join(root, 'plugin', '.claude-plugin'))) return 'vscode'; + if (existsSync(join(root, 'package.json')) && root.endsWith('opencode-jfrog-plugin')) return 'opencode'; + return 'unknown'; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) main(); diff --git a/scripts/validate-install-docs.test.mjs b/scripts/validate-install-docs.test.mjs new file mode 100644 index 0000000..2b2166f --- /dev/null +++ b/scripts/validate-install-docs.test.mjs @@ -0,0 +1,50 @@ +// Copyright (c) JFrog Ltd. 2026 +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { mkdtempSync, writeFileSync, mkdirSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { validateInstallDocs } from './validate-install-docs.mjs'; + +function writeReadme(root, body) { + writeFileSync(join(root, 'README.md'), body); +} + +test('validateInstallDocs passes when README links shared guide and has Verify section', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme( + root, + '# Codex\n\n[shared flow](shared-install-and-verify.md)\n\n## Verify\n\n1. list plugins\n' + ); + mkdirSync(join(root, 'docs'), { recursive: true }); + writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-codex.md'), '# web doc'); + assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'codex' }), []); +}); + +test('validateInstallDocs flags missing shared guide link', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme(root, '# Codex\n\n## Verify\n\n1. ok\n'); + mkdirSync(join(root, 'docs'), { recursive: true }); + writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-codex.md'), '# web doc'); + const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + assert.ok(errors.some((e) => e.includes('shared-install-and-verify'))); +}); + +test('validateInstallDocs requires codex web doc source file', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme(root, '# Codex\n\nshared-install-and-verify\n\n## Verify\n'); + const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + assert.ok(errors.some((e) => e.includes('install-jfrog-plugin-for-codex.md'))); +}); + +test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme( + root, + '# x\nshared-install-and-verify\n## Verify\nSetting environment variables after a failed init may repair MCP registration.' + ); + mkdirSync(join(root, 'docs'), { recursive: true }); + writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-codex.md'), '# web doc'); + const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + assert.ok(errors.some((e) => e.includes('env vars repair failed init'))); +}); From 3a7383644d0aaf54f7cf8ba5d22f7adb2732e7ee Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Tue, 25 Aug 2026 18:33:52 +0300 Subject: [PATCH 2/7] AX-2162 - Keep Codex install docs self-contained Drop the Claude plugin GitHub links. Install, verify, and recovery live in this repo, and the checker forbids pointing at other plugin repositories. Co-authored-by: Cursor --- CONTRIBUTING.md | 2 +- README.md | 21 ++++++---- docs/install-jfrog-plugin-for-codex.md | 34 +++++++++------- scripts/validate-install-docs.mjs | 54 +++++++++++++++++++------- scripts/validate-install-docs.test.mjs | 42 ++++++++++++-------- 5 files changed, 100 insertions(+), 53 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43bec0e..a74b963 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ This downloads the pinned upstream tarball and replaces the contents of `skills/ ## Pre-release checklist - [ ] `npm test`, `npm run validate`, and `node scripts/validate-install-docs.mjs` pass. -- [ ] Install and recovery docs stay aligned: [`README.md`](README.md), [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md), and the [shared guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md) agree on initialization, env vars, restart, verification, and recovery. +- [ ] Install and recovery docs stay aligned: [`README.md`](README.md) and [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md) agree on initialization, env vars, restart, verification, and recovery. Do not send readers to another plugin repository for those steps. - [ ] `version` bumped and **identical** in both [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json) and [`package.json`](package.json) (CI enforces the match). - [ ] No secrets, credentials, or API keys committed. - [ ] If the skill tree changed: `pin` in [`scripts/sync-skills-vendor.json`](scripts/sync-skills-vendor.json) matches the upstream tag the new tree was generated from, and the README Prerequisites link points at that tag. diff --git a/README.md b/README.md index 3af6a50..d2ceab1 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,7 @@ JFrog plugin for [OpenAI Codex](https://developers.openai.com/codex/): artifact management, security scanning, supply-chain best practices, and Agent Guard. -> **Install flow:** Follow the [shared install, verify, and recovery guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md) for the cross-harness rules on initialization, environment variables, restart, verification, and recovery. This README documents **Codex-only** differences. -> -> **Web doc source:** [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md) is the source for the published Codex install page. +> **Install, verify, and recovery:** [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md) is the source for the published Codex install page. ## Features @@ -104,10 +102,19 @@ restarting Codex, confirm all four: 3. `codex mcp list` — `jfrog` is connected (after `codex mcp login jfrog`). 4. `jf rt ping` — succeeds against your configured JFrog server. -If any check fails, see the recovery playbook in the -[shared guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). -Setting MCP environment variables by hand does not repair a failed -initialization — re-run initialization instead. +If any check fails, see [Recovery](#recovery). Setting MCP environment variables +by hand does not repair a failed initialization — re-run `jfrog-init` instead. + +--- + +## Recovery + +| Symptom | Do this | Do **not** do this | +| --- | --- | --- | +| MCP missing after install | Run `jfrog-init`, edit the plugin `.mcp.json` host if needed, run `codex mcp login jfrog`, **restart Codex**, then `codex mcp list`. | Assume exporting `JFROG_URL` or `JFROG_PLATFORM_URL` will register MCP. Codex reads the host from `.mcp.json`. | +| `jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `jfrog-init`**. | Skip init and only export env vars. | +| Placeholder still in `.mcp.json` | Set the host in `/.mcp.json`, run `codex mcp login jfrog`, restart Codex. | Reinstall the plugin when only the host placeholder is wrong. | +| Plugin not listed | Re-run `codex plugin add jfrog@codex-plugin` from a regular terminal, then restart Codex. | Run install commands from inside the Codex TUI. | --- diff --git a/docs/install-jfrog-plugin-for-codex.md b/docs/install-jfrog-plugin-for-codex.md index 3f19deb..6a6a1fc 100644 --- a/docs/install-jfrog-plugin-for-codex.md +++ b/docs/install-jfrog-plugin-for-codex.md @@ -1,6 +1,6 @@ # Install JFrog Agent Plugin for OpenAI Codex -> **Web publication source.** Publish to `https://docs.jfrog.com/ai-ml/docs/codex` (and add to the JFrog Agent Plugins index). Canonical shared flow: [Shared install, verify, and recovery](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md). +> **Web publication source.** Publish to `https://docs.jfrog.com/ai-ml/docs/codex` (and add to the JFrog Agent Plugins index). Install and configure the JFrog Agent Plugin for [OpenAI Codex](https://developers.openai.com/codex/), including JFrog Agent Skills, the bundled JFrog Platform MCP server, and Agent Guard. @@ -15,10 +15,13 @@ Install and configure the JFrog Agent Plugin for [OpenAI Codex](https://develope ## Prerequisites -See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#common-prerequisites-all-harnesses). Codex-specific additions: - -- **OpenAI Codex CLI** with plugin support (`codex plugin` commands available). -- **Node.js ≥ 18** with `npx` on `PATH`. +| Requirement | Notes | +| --- | --- | +| JFrog Platform instance | You can authenticate against it (URL + token or browser login). | +| OpenAI Codex CLI | Plugin support (`codex plugin` commands) must be available. | +| Node.js ≥ 18 | With `npx` on `PATH` (used by Agent Guard and `jfrog-init`). | +| `jf`, `jq`, `curl` on `PATH` | Required for JFrog skills at runtime. Configure the CLI with `jf config add` or `jf login`. | +| JFrog AI Catalog (optional) | Required only for Agent Guard MCP catalog features. | ## Install the JFrog Agent Plugin for Codex @@ -35,8 +38,7 @@ The plugin is served from the public GitHub repo as a **Git marketplace** (`jfro 3. **Run initialization.** Invoke the `jfrog-init` skill in Codex. It detects Node.js, the JFrog CLI, your server config, the MCP registration, and project resolution, - then walks you through whatever is missing. This is the same initialization step - described in the [shared guide](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md). + then walks you through whatever is missing. If you prefer to configure manually, the equivalent steps are: @@ -51,6 +53,10 @@ The plugin is served from the public GitHub repo as a **Git marketplace** (`jfro 4. **Restart Codex again** after initialization or MCP edits. +The bundled MCP host is set by editing the plugin's `.mcp.json`, not by exporting a +shell variable. Setting `JFROG_URL` or `JFROG_PLATFORM_URL` in the environment does +not substitute that placeholder, and does not repair a failed `jfrog-init`. + ## Verify (required) Do not skip verification — treat this as part of installation. @@ -60,22 +66,22 @@ Do not skip verification — treat this as part of installation. 3. `codex mcp list` — server `jfrog` is listed and connected after OAuth. 4. `jf rt ping` — succeeds for your configured JFrog server. -## Codex-specific notes +## Codex notes - Run `codex plugin list` from a **regular terminal**, not from inside the Codex session, when you need filesystem paths for `.mcp.json`. - Browse installed plugins and their skills in the Codex TUI with `/plugins`. -- The bundled MCP host is set by editing the plugin's `.mcp.json`, not by exporting a shell variable — Codex reads `` from that file. ## Recovery -If MCP tools are missing after install, follow the [shared recovery playbook](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). Setting `JFROG_URL` alone after a partial setup does **not** replace editing the bundled `.mcp.json` and completing `codex mcp login jfrog`. +| Symptom | Do this | Do **not** do this | +| --- | --- | --- | +| MCP missing after install | Run `jfrog-init`, edit `/.mcp.json` if the host is still a placeholder, run `codex mcp login jfrog`, **restart Codex**, then `codex mcp list`. | Assume exporting `JFROG_URL` will register MCP. | +| `jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `jfrog-init`**. | Skip init and only export env vars. | +| Placeholder still in `.mcp.json` | Replace `` with your host, run `codex mcp login jfrog`, restart. | Reinstall the plugin when only the host placeholder is wrong. | +| Plugin not listed | Re-run `codex plugin add jfrog@codex-plugin` from a regular terminal, then restart Codex. | Run install commands from inside the Codex TUI. | ## Related topics - [JFrog Agent Plugins](https://docs.jfrog.com/ai-ml/docs/jfrog-plugins) -- [Claude Code](https://docs.jfrog.com/ai-ml/docs/claude-code) -- [VS Code](https://docs.jfrog.com/ai-ml/docs/vs-code) -- [Cursor](https://docs.jfrog.com/ai-ml/docs/cursor) -- [OpenCode](https://docs.jfrog.com/ai-ml/docs/opencode) - [Troubleshoot Plugins](https://docs.jfrog.com/ai-ml/docs/troubleshoot-plugins) - [Using the MCP Registry with Agent Guard](https://docs.jfrog.com/ai-ml/docs/configure-coding-agents) diff --git a/scripts/validate-install-docs.mjs b/scripts/validate-install-docs.mjs index a5c581f..f215352 100644 --- a/scripts/validate-install-docs.mjs +++ b/scripts/validate-install-docs.mjs @@ -9,19 +9,27 @@ import { fileURLToPath } from 'node:url'; const repoRoot = process.cwd(); -const REQUIRED_README_MARKERS = [ - 'shared-install-and-verify', - '## Verify', -]; +const HARNESS_OWN_REPO = { + claude: 'claude-plugin', + codex: 'codex-plugin', + cursor: 'cursor-plugin', + devin: 'devin-plugin', + opencode: 'opencode-jfrog-plugin', + vscode: 'vscode-plugin', +}; + +const ALL_PLUGIN_REPOS = Object.values(HARNESS_OWN_REPO); + +const REQUIRED_README_MARKERS = ['## Verify']; const FORBIDDEN_PATTERNS = [ { re: /setting\s+(?:the\s+)?environment\s+variables?\s+after\s+a\s+failed\s+init\s+may\s+repair/i, - message: 'README must not claim env vars repair failed init', + message: 'must not claim env vars repair failed init', }, { re: /set\s+JFROG_URL.*after.*failed.*init.*fix/i, - message: 'README must not claim JFROG_URL fixes failed init', + message: 'must not claim JFROG_URL fixes failed init', }, ]; @@ -31,24 +39,40 @@ export function validateInstallDocs({ repoRoot: root, harness }) { if (!existsSync(readmePath)) { return [`${harness}: missing README.md`]; } - const readme = readFileSync(readmePath, 'utf8'); - for (const marker of REQUIRED_README_MARKERS) { - if (!readme.includes(marker)) { - errors.push(`${harness}: README.md missing required marker: ${marker}`); - } - } - for (const { re, message } of FORBIDDEN_PATTERNS) { - if (re.test(readme)) errors.push(`${harness}: ${message}`); - } + const files = [{ label: 'README.md', text: readFileSync(readmePath, 'utf8') }]; if (harness === 'codex') { const webDoc = join(root, 'docs', 'install-jfrog-plugin-for-codex.md'); if (!existsSync(webDoc)) errors.push('codex: missing docs/install-jfrog-plugin-for-codex.md'); + else files.push({ label: 'docs/install-jfrog-plugin-for-codex.md', text: readFileSync(webDoc, 'utf8') }); } if (harness === 'devin') { const webDoc = join(root, 'docs', 'install-jfrog-plugin-for-devin.md'); if (!existsSync(webDoc)) errors.push('devin: missing docs/install-jfrog-plugin-for-devin.md'); + else files.push({ label: 'docs/install-jfrog-plugin-for-devin.md', text: readFileSync(webDoc, 'utf8') }); } + + const readme = files[0].text; + for (const marker of REQUIRED_README_MARKERS) { + if (!readme.includes(marker)) { + errors.push(`${harness}: README.md missing required marker: ${marker}`); + } + } + + const ownRepo = HARNESS_OWN_REPO[harness]; + const otherRepos = ALL_PLUGIN_REPOS.filter((name) => name !== ownRepo); + + for (const { label, text } of files) { + for (const { re, message } of FORBIDDEN_PATTERNS) { + if (re.test(text)) errors.push(`${harness}: ${label} ${message}`); + } + for (const other of otherRepos) { + if (text.includes(`github.com/jfrog/${other}`)) { + errors.push(`${harness}: ${label} must not link to github.com/jfrog/${other}`); + } + } + } + return errors; } diff --git a/scripts/validate-install-docs.test.mjs b/scripts/validate-install-docs.test.mjs index 2b2166f..114a26d 100644 --- a/scripts/validate-install-docs.test.mjs +++ b/scripts/validate-install-docs.test.mjs @@ -10,29 +10,29 @@ function writeReadme(root, body) { writeFileSync(join(root, 'README.md'), body); } -test('validateInstallDocs passes when README links shared guide and has Verify section', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme( - root, - '# Codex\n\n[shared flow](shared-install-and-verify.md)\n\n## Verify\n\n1. list plugins\n' - ); +function withWebDoc(root, name = 'install-jfrog-plugin-for-codex.md') { mkdirSync(join(root, 'docs'), { recursive: true }); - writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-codex.md'), '# web doc'); + writeFileSync(join(root, 'docs', name), '# web doc\n'); +} + +test('validateInstallDocs passes when README has Verify and no other-plugin links', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme(root, '# Codex\n\n## Verify\n\n1. list plugins\n'); + withWebDoc(root); assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'codex' }), []); }); -test('validateInstallDocs flags missing shared guide link', () => { +test('validateInstallDocs flags missing Verify section', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n\n## Verify\n\n1. ok\n'); - mkdirSync(join(root, 'docs'), { recursive: true }); - writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-codex.md'), '# web doc'); + writeReadme(root, '# Codex\n\nInstall the plugin.\n'); + withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('shared-install-and-verify'))); + assert.ok(errors.some((e) => e.includes('## Verify'))); }); test('validateInstallDocs requires codex web doc source file', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n\nshared-install-and-verify\n\n## Verify\n'); + writeReadme(root, '# Codex\n\n## Verify\n'); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('install-jfrog-plugin-for-codex.md'))); }); @@ -41,10 +41,20 @@ test('validateInstallDocs rejects contradictory failed-init env-var recovery cla const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme( root, - '# x\nshared-install-and-verify\n## Verify\nSetting environment variables after a failed init may repair MCP registration.' + '# x\n## Verify\nSetting environment variables after a failed init may repair MCP registration.' ); - mkdirSync(join(root, 'docs'), { recursive: true }); - writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-codex.md'), '# web doc'); + withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('env vars repair failed init'))); }); + +test('validateInstallDocs rejects links to other plugin GitHub repos', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme( + root, + '# Codex\n## Verify\nSee https://github.com/jfrog/claude-plugin/blob/main/docs/install-and-verify.md\n' + ); + withWebDoc(root); + const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + assert.ok(errors.some((e) => e.includes('claude-plugin'))); +}); From e043ca03d3b2a560381a9238b6e23aa964f17f6c Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Tue, 25 Aug 2026 19:01:41 +0300 Subject: [PATCH 3/7] AX-2162 - Document JFROG_PLATFORM_URL instead of JFROG_URL Drop the legacy env var from install docs. The checker now rejects JFROG_URL in README and the web doc source. Co-authored-by: Cursor --- README.md | 2 +- docs/install-jfrog-plugin-for-codex.md | 6 +++--- scripts/validate-install-docs.mjs | 4 ++-- scripts/validate-install-docs.test.mjs | 8 ++++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d2ceab1..85849c6 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ by hand does not repair a failed initialization — re-run `jfrog-init` instead. | Symptom | Do this | Do **not** do this | | --- | --- | --- | -| MCP missing after install | Run `jfrog-init`, edit the plugin `.mcp.json` host if needed, run `codex mcp login jfrog`, **restart Codex**, then `codex mcp list`. | Assume exporting `JFROG_URL` or `JFROG_PLATFORM_URL` will register MCP. Codex reads the host from `.mcp.json`. | +| MCP missing after install | Run `jfrog-init`, edit the plugin `.mcp.json` host if needed, run `codex mcp login jfrog`, **restart Codex**, then `codex mcp list`. | Assume exporting `JFROG_PLATFORM_URL` will register MCP. Codex reads the host from `.mcp.json`. | | `jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `jfrog-init`**. | Skip init and only export env vars. | | Placeholder still in `.mcp.json` | Set the host in `/.mcp.json`, run `codex mcp login jfrog`, restart Codex. | Reinstall the plugin when only the host placeholder is wrong. | | Plugin not listed | Re-run `codex plugin add jfrog@codex-plugin` from a regular terminal, then restart Codex. | Run install commands from inside the Codex TUI. | diff --git a/docs/install-jfrog-plugin-for-codex.md b/docs/install-jfrog-plugin-for-codex.md index 6a6a1fc..639e381 100644 --- a/docs/install-jfrog-plugin-for-codex.md +++ b/docs/install-jfrog-plugin-for-codex.md @@ -54,8 +54,8 @@ The plugin is served from the public GitHub repo as a **Git marketplace** (`jfro 4. **Restart Codex again** after initialization or MCP edits. The bundled MCP host is set by editing the plugin's `.mcp.json`, not by exporting a -shell variable. Setting `JFROG_URL` or `JFROG_PLATFORM_URL` in the environment does -not substitute that placeholder, and does not repair a failed `jfrog-init`. +shell variable. Setting `JFROG_PLATFORM_URL` in the environment does not substitute +that placeholder, and does not repair a failed `jfrog-init`. ## Verify (required) @@ -75,7 +75,7 @@ Do not skip verification — treat this as part of installation. | Symptom | Do this | Do **not** do this | | --- | --- | --- | -| MCP missing after install | Run `jfrog-init`, edit `/.mcp.json` if the host is still a placeholder, run `codex mcp login jfrog`, **restart Codex**, then `codex mcp list`. | Assume exporting `JFROG_URL` will register MCP. | +| MCP missing after install | Run `jfrog-init`, edit `/.mcp.json` if the host is still a placeholder, run `codex mcp login jfrog`, **restart Codex**, then `codex mcp list`. | Assume exporting `JFROG_PLATFORM_URL` will register MCP. | | `jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `jfrog-init`**. | Skip init and only export env vars. | | Placeholder still in `.mcp.json` | Replace `` with your host, run `codex mcp login jfrog`, restart. | Reinstall the plugin when only the host placeholder is wrong. | | Plugin not listed | Re-run `codex plugin add jfrog@codex-plugin` from a regular terminal, then restart Codex. | Run install commands from inside the Codex TUI. | diff --git a/scripts/validate-install-docs.mjs b/scripts/validate-install-docs.mjs index f215352..712ad2a 100644 --- a/scripts/validate-install-docs.mjs +++ b/scripts/validate-install-docs.mjs @@ -28,8 +28,8 @@ const FORBIDDEN_PATTERNS = [ message: 'must not claim env vars repair failed init', }, { - re: /set\s+JFROG_URL.*after.*failed.*init.*fix/i, - message: 'must not claim JFROG_URL fixes failed init', + re: /JFROG_URL/, + message: 'must not document the legacy JFROG_URL env var; use JFROG_PLATFORM_URL', }, ]; diff --git a/scripts/validate-install-docs.test.mjs b/scripts/validate-install-docs.test.mjs index 114a26d..826d627 100644 --- a/scripts/validate-install-docs.test.mjs +++ b/scripts/validate-install-docs.test.mjs @@ -48,6 +48,14 @@ test('validateInstallDocs rejects contradictory failed-init env-var recovery cla assert.ok(errors.some((e) => e.includes('env vars repair failed init'))); }); +test('validateInstallDocs rejects the legacy JFROG_URL env var', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme(root, '# Codex\n## Verify\nSet `JFROG_URL` to your platform.\n'); + withWebDoc(root); + const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + assert.ok(errors.some((e) => e.includes('JFROG_URL'))); +}); + test('validateInstallDocs rejects links to other plugin GitHub repos', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme( From 890d51a36dbc4f91f677aef4cbe431b586f1368c Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Tue, 25 Aug 2026 19:06:55 +0300 Subject: [PATCH 4/7] Reject Jira hosts and ticket keys in install docs Install/recovery markdown is public and must not name internal tickets. Co-authored-by: Cursor --- scripts/validate-install-docs.mjs | 10 +++++++++- scripts/validate-install-docs.test.mjs | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/validate-install-docs.mjs b/scripts/validate-install-docs.mjs index 712ad2a..0636f3a 100644 --- a/scripts/validate-install-docs.mjs +++ b/scripts/validate-install-docs.mjs @@ -1,7 +1,7 @@ #!/usr/bin/env node // Copyright (c) JFrog Ltd. 2026 // Licensed under the Apache License, Version 2.0 -// Validates install/recovery documentation invariants for AX-2162. +// Validates install/recovery documentation invariants. import { readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; @@ -31,6 +31,14 @@ const FORBIDDEN_PATTERNS = [ re: /JFROG_URL/, message: 'must not document the legacy JFROG_URL env var; use JFROG_PLATFORM_URL', }, + { + re: /atlassian\.net/i, + message: 'must not reference JFrog Jira (atlassian.net) in repo files', + }, + { + re: /\b(?:AX|MLD)-\d+\b/, + message: 'must not include Jira ticket keys in repo files', + }, ]; export function validateInstallDocs({ repoRoot: root, harness }) { diff --git a/scripts/validate-install-docs.test.mjs b/scripts/validate-install-docs.test.mjs index 826d627..75fee1f 100644 --- a/scripts/validate-install-docs.test.mjs +++ b/scripts/validate-install-docs.test.mjs @@ -66,3 +66,14 @@ test('validateInstallDocs rejects links to other plugin GitHub repos', () => { const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('claude-plugin'))); }); + +test('validateInstallDocs rejects Jira URLs and ticket keys', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + const host = ['jfrog-int', 'atlassian', 'net'].join('.'); + const key = ['AX', '1780'].join('-'); + writeReadme(root, `# Codex\n## Verify\nSee [${key}](https://${host}/browse/${key}).\n`); + withWebDoc(root); + const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + assert.ok(errors.some((e) => e.includes('atlassian.net'))); + assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); +}); From 2a159e0f6f84f579bd04680d372cdd25d0bfe7cf Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Wed, 26 Aug 2026 14:53:51 +0300 Subject: [PATCH 5/7] Drop the extra Codex install markdown file Keep install, verify, and recovery in the README so the repo is not a second copy of the public JFrog docs. Co-authored-by: Cursor --- CONTRIBUTING.md | 2 +- README.md | 2 - docs/install-jfrog-plugin-for-codex.md | 87 -------------------------- scripts/validate-install-docs.mjs | 11 ---- scripts/validate-install-docs.test.mjs | 22 +------ 5 files changed, 3 insertions(+), 121 deletions(-) delete mode 100644 docs/install-jfrog-plugin-for-codex.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a74b963..8e44249 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ This downloads the pinned upstream tarball and replaces the contents of `skills/ ## Pre-release checklist - [ ] `npm test`, `npm run validate`, and `node scripts/validate-install-docs.mjs` pass. -- [ ] Install and recovery docs stay aligned: [`README.md`](README.md) and [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md) agree on initialization, env vars, restart, verification, and recovery. Do not send readers to another plugin repository for those steps. +- [ ] README includes **Verify** and **Recovery** and does not send readers to another plugin repository for those steps. - [ ] `version` bumped and **identical** in both [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json) and [`package.json`](package.json) (CI enforces the match). - [ ] No secrets, credentials, or API keys committed. - [ ] If the skill tree changed: `pin` in [`scripts/sync-skills-vendor.json`](scripts/sync-skills-vendor.json) matches the upstream tag the new tree was generated from, and the README Prerequisites link points at that tag. diff --git a/README.md b/README.md index 85849c6..c2531f1 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ JFrog plugin for [OpenAI Codex](https://developers.openai.com/codex/): artifact management, security scanning, supply-chain best practices, and Agent Guard. -> **Install, verify, and recovery:** [`docs/install-jfrog-plugin-for-codex.md`](docs/install-jfrog-plugin-for-codex.md) is the source for the published Codex install page. - ## Features The JFrog plugin provides the following capabilities, grouped by component: diff --git a/docs/install-jfrog-plugin-for-codex.md b/docs/install-jfrog-plugin-for-codex.md deleted file mode 100644 index 639e381..0000000 --- a/docs/install-jfrog-plugin-for-codex.md +++ /dev/null @@ -1,87 +0,0 @@ -# Install JFrog Agent Plugin for OpenAI Codex - -> **Web publication source.** Publish to `https://docs.jfrog.com/ai-ml/docs/codex` (and add to the JFrog Agent Plugins index). - -Install and configure the JFrog Agent Plugin for [OpenAI Codex](https://developers.openai.com/codex/), including JFrog Agent Skills, the bundled JFrog Platform MCP server, and Agent Guard. - -## What's included - -| Component | Description | -| --- | --- | -| **JFrog Skills** | Vendored from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills): platform operations, package safety, and AI Catalog workflows. | -| **`jfrog-init`** | Guided setup and readiness check — detects the JFrog CLI, config, MCP, and project, and reports what is missing. | -| **JFrog Platform MCP** | Bundled remote MCP at `https:///mcp` (OAuth via `codex mcp login jfrog`). | -| **Agent Guard** | Discover, install, and manage MCP servers approved in the JFrog AI Catalog. | - -## Prerequisites - -| Requirement | Notes | -| --- | --- | -| JFrog Platform instance | You can authenticate against it (URL + token or browser login). | -| OpenAI Codex CLI | Plugin support (`codex plugin` commands) must be available. | -| Node.js ≥ 18 | With `npx` on `PATH` (used by Agent Guard and `jfrog-init`). | -| `jf`, `jq`, `curl` on `PATH` | Required for JFrog skills at runtime. Configure the CLI with `jf config add` or `jf login`. | -| JFrog AI Catalog (optional) | Required only for Agent Guard MCP catalog features. | - -## Install the JFrog Agent Plugin for Codex - -The plugin is served from the public GitHub repo as a **Git marketplace** (`jfrog/codex-plugin`). - -1. In a regular terminal (not inside the Codex TUI), add the marketplace and install: - - ```bash - codex plugin marketplace add jfrog/codex-plugin - codex plugin add jfrog@codex-plugin - ``` - -2. **Restart Codex** so the plugin and bundled MCP load. - -3. **Run initialization.** Invoke the `jfrog-init` skill in Codex. It detects Node.js, - the JFrog CLI, your server config, the MCP registration, and project resolution, - then walks you through whatever is missing. - - If you prefer to configure manually, the equivalent steps are: - - ```bash - jf config add - ``` - - then find the install path with `codex plugin list` (row `jfrog@codex-plugin`), edit - `/.mcp.json` to replace `` with your platform host - (for example `mycompany.jfrog.io`), and run `codex mcp login jfrog` to complete - browser OAuth. - -4. **Restart Codex again** after initialization or MCP edits. - -The bundled MCP host is set by editing the plugin's `.mcp.json`, not by exporting a -shell variable. Setting `JFROG_PLATFORM_URL` in the environment does not substitute -that placeholder, and does not repair a failed `jfrog-init`. - -## Verify (required) - -Do not skip verification — treat this as part of installation. - -1. `codex plugin list` — `jfrog@codex-plugin` is installed and enabled. -2. In the Codex TUI, run `/plugins` — the JFrog plugin lists its skills (`jfrog` and others). -3. `codex mcp list` — server `jfrog` is listed and connected after OAuth. -4. `jf rt ping` — succeeds for your configured JFrog server. - -## Codex notes - -- Run `codex plugin list` from a **regular terminal**, not from inside the Codex session, when you need filesystem paths for `.mcp.json`. -- Browse installed plugins and their skills in the Codex TUI with `/plugins`. - -## Recovery - -| Symptom | Do this | Do **not** do this | -| --- | --- | --- | -| MCP missing after install | Run `jfrog-init`, edit `/.mcp.json` if the host is still a placeholder, run `codex mcp login jfrog`, **restart Codex**, then `codex mcp list`. | Assume exporting `JFROG_PLATFORM_URL` will register MCP. | -| `jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `jfrog-init`**. | Skip init and only export env vars. | -| Placeholder still in `.mcp.json` | Replace `` with your host, run `codex mcp login jfrog`, restart. | Reinstall the plugin when only the host placeholder is wrong. | -| Plugin not listed | Re-run `codex plugin add jfrog@codex-plugin` from a regular terminal, then restart Codex. | Run install commands from inside the Codex TUI. | - -## Related topics - -- [JFrog Agent Plugins](https://docs.jfrog.com/ai-ml/docs/jfrog-plugins) -- [Troubleshoot Plugins](https://docs.jfrog.com/ai-ml/docs/troubleshoot-plugins) -- [Using the MCP Registry with Agent Guard](https://docs.jfrog.com/ai-ml/docs/configure-coding-agents) diff --git a/scripts/validate-install-docs.mjs b/scripts/validate-install-docs.mjs index 0636f3a..c6c74b8 100644 --- a/scripts/validate-install-docs.mjs +++ b/scripts/validate-install-docs.mjs @@ -49,17 +49,6 @@ export function validateInstallDocs({ repoRoot: root, harness }) { } const files = [{ label: 'README.md', text: readFileSync(readmePath, 'utf8') }]; - if (harness === 'codex') { - const webDoc = join(root, 'docs', 'install-jfrog-plugin-for-codex.md'); - if (!existsSync(webDoc)) errors.push('codex: missing docs/install-jfrog-plugin-for-codex.md'); - else files.push({ label: 'docs/install-jfrog-plugin-for-codex.md', text: readFileSync(webDoc, 'utf8') }); - } - if (harness === 'devin') { - const webDoc = join(root, 'docs', 'install-jfrog-plugin-for-devin.md'); - if (!existsSync(webDoc)) errors.push('devin: missing docs/install-jfrog-plugin-for-devin.md'); - else files.push({ label: 'docs/install-jfrog-plugin-for-devin.md', text: readFileSync(webDoc, 'utf8') }); - } - const readme = files[0].text; for (const marker of REQUIRED_README_MARKERS) { if (!readme.includes(marker)) { diff --git a/scripts/validate-install-docs.test.mjs b/scripts/validate-install-docs.test.mjs index 75fee1f..5a2b2b4 100644 --- a/scripts/validate-install-docs.test.mjs +++ b/scripts/validate-install-docs.test.mjs @@ -1,7 +1,7 @@ // Copyright (c) JFrog Ltd. 2026 import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { mkdtempSync, writeFileSync, mkdirSync } from 'node:fs'; +import { mkdtempSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { validateInstallDocs } from './validate-install-docs.mjs'; @@ -10,40 +10,25 @@ function writeReadme(root, body) { writeFileSync(join(root, 'README.md'), body); } -function withWebDoc(root, name = 'install-jfrog-plugin-for-codex.md') { - mkdirSync(join(root, 'docs'), { recursive: true }); - writeFileSync(join(root, 'docs', name), '# web doc\n'); -} - test('validateInstallDocs passes when README has Verify and no other-plugin links', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme(root, '# Codex\n\n## Verify\n\n1. list plugins\n'); - withWebDoc(root); assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'codex' }), []); }); test('validateInstallDocs flags missing Verify section', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme(root, '# Codex\n\nInstall the plugin.\n'); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('## Verify'))); }); -test('validateInstallDocs requires codex web doc source file', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n\n## Verify\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('install-jfrog-plugin-for-codex.md'))); -}); - test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme( root, '# x\n## Verify\nSetting environment variables after a failed init may repair MCP registration.' ); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('env vars repair failed init'))); }); @@ -51,7 +36,6 @@ test('validateInstallDocs rejects contradictory failed-init env-var recovery cla test('validateInstallDocs rejects the legacy JFROG_URL env var', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme(root, '# Codex\n## Verify\nSet `JFROG_URL` to your platform.\n'); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('JFROG_URL'))); }); @@ -60,9 +44,8 @@ test('validateInstallDocs rejects links to other plugin GitHub repos', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme( root, - '# Codex\n## Verify\nSee https://github.com/jfrog/claude-plugin/blob/main/docs/install-and-verify.md\n' + '# Codex\n## Verify\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' ); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('claude-plugin'))); }); @@ -72,7 +55,6 @@ test('validateInstallDocs rejects Jira URLs and ticket keys', () => { const host = ['jfrog-int', 'atlassian', 'net'].join('.'); const key = ['AX', '1780'].join('-'); writeReadme(root, `# Codex\n## Verify\nSee [${key}](https://${host}/browse/${key}).\n`); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('atlassian.net'))); assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); From 9ab56a70c6d9837dbfff0422f273f71696c0ebc1 Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Thu, 27 Aug 2026 15:40:31 +0300 Subject: [PATCH 6/7] Require a Recovery heading in the install-docs checker. --- scripts/validate-install-docs.mjs | 2 +- scripts/validate-install-docs.test.mjs | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/scripts/validate-install-docs.mjs b/scripts/validate-install-docs.mjs index c6c74b8..33f5268 100644 --- a/scripts/validate-install-docs.mjs +++ b/scripts/validate-install-docs.mjs @@ -20,7 +20,7 @@ const HARNESS_OWN_REPO = { const ALL_PLUGIN_REPOS = Object.values(HARNESS_OWN_REPO); -const REQUIRED_README_MARKERS = ['## Verify']; +const REQUIRED_README_MARKERS = ['## Verify', '## Recovery']; const FORBIDDEN_PATTERNS = [ { diff --git a/scripts/validate-install-docs.test.mjs b/scripts/validate-install-docs.test.mjs index 5a2b2b4..0338ddf 100644 --- a/scripts/validate-install-docs.test.mjs +++ b/scripts/validate-install-docs.test.mjs @@ -10,9 +10,9 @@ function writeReadme(root, body) { writeFileSync(join(root, 'README.md'), body); } -test('validateInstallDocs passes when README has Verify and no other-plugin links', () => { +test('validateInstallDocs passes when README has Verify, Recovery, and no other-plugin links', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n\n## Verify\n\n1. list plugins\n'); + writeReadme(root, '# Codex\n\n## Verify\n\n1. list plugins\n\n## Recovery\n\n'); assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'codex' }), []); }); @@ -23,11 +23,18 @@ test('validateInstallDocs flags missing Verify section', () => { assert.ok(errors.some((e) => e.includes('## Verify'))); }); +test('validateInstallDocs flags missing Recovery section', () => { + const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + writeReadme(root, '# Codex\n\n## Verify\n\n1. list plugins\n'); + const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + assert.ok(errors.some((e) => e.includes('## Recovery'))); +}); + test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme( root, - '# x\n## Verify\nSetting environment variables after a failed init may repair MCP registration.' + '# x\n## Verify\n## Recovery\nSetting environment variables after a failed init may repair MCP registration.' ); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('env vars repair failed init'))); @@ -35,7 +42,7 @@ test('validateInstallDocs rejects contradictory failed-init env-var recovery cla test('validateInstallDocs rejects the legacy JFROG_URL env var', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n## Verify\nSet `JFROG_URL` to your platform.\n'); + writeReadme(root, '# Codex\n## Verify\n## Recovery\nSet `JFROG_URL` to your platform.\n'); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('JFROG_URL'))); }); @@ -44,7 +51,7 @@ test('validateInstallDocs rejects links to other plugin GitHub repos', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); writeReadme( root, - '# Codex\n## Verify\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' + '# Codex\n## Verify\n## Recovery\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' ); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('claude-plugin'))); @@ -54,7 +61,7 @@ test('validateInstallDocs rejects Jira URLs and ticket keys', () => { const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); const host = ['jfrog-int', 'atlassian', 'net'].join('.'); const key = ['AX', '1780'].join('-'); - writeReadme(root, `# Codex\n## Verify\nSee [${key}](https://${host}/browse/${key}).\n`); + writeReadme(root, `# Codex\n## Verify\n## Recovery\nSee [${key}](https://${host}/browse/${key}).\n`); const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); assert.ok(errors.some((e) => e.includes('atlassian.net'))); assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); From 2c12f95ea51bacdc8f987300fd534339f5e840e4 Mon Sep 17 00:00:00 2001 From: Yoni Melki Date: Thu, 27 Aug 2026 16:22:34 +0300 Subject: [PATCH 7/7] Drop the install-docs README checker. --- .github/workflows/pr.yml | 2 - CONTRIBUTING.md | 3 +- package.json | 2 +- scripts/validate-install-docs.mjs | 97 -------------------------- scripts/validate-install-docs.test.mjs | 68 ------------------ 5 files changed, 2 insertions(+), 170 deletions(-) delete mode 100644 scripts/validate-install-docs.mjs delete mode 100644 scripts/validate-install-docs.test.mjs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 0db5dfa..dc53687 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,8 +19,6 @@ jobs: run: node --test scripts/validate.test.mjs - name: Validate manifests + skills run: node scripts/validate.mjs - - name: Validate install/recovery docs - run: node scripts/validate-install-docs.mjs - name: Version consistency (plugin.json == package.json) run: | PJ=$(jq -r .version .codex-plugin/plugin.json) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e44249..2a7cc3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,6 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri ```bash npm test # unit tests for the manifest/skill validator npm run validate # lint the plugin manifests + skill frontmatter -node scripts/validate-install-docs.mjs # check install/verify docs stay aligned ``` 1. **Test** by loading your clone as the plugin. The repo root is the marketplace root (`.agents/plugins/marketplace.json` registers the `jfrog` plugin): @@ -42,7 +41,7 @@ This downloads the pinned upstream tarball and replaces the contents of `skills/ ## Pre-release checklist -- [ ] `npm test`, `npm run validate`, and `node scripts/validate-install-docs.mjs` pass. +- [ ] `npm test` and `npm run validate` pass. - [ ] README includes **Verify** and **Recovery** and does not send readers to another plugin repository for those steps. - [ ] `version` bumped and **identical** in both [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json) and [`package.json`](package.json) (CI enforces the match). - [ ] No secrets, credentials, or API keys committed. diff --git a/package.json b/package.json index aa35673..32d168e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,6 @@ "scripts": { "sync-skills": "node scripts/sync-skills.mjs", "validate": "node scripts/validate.mjs", - "test": "node --test scripts/validate.test.mjs scripts/validate-install-docs.test.mjs" + "test": "node --test scripts/validate.test.mjs" } } diff --git a/scripts/validate-install-docs.mjs b/scripts/validate-install-docs.mjs deleted file mode 100644 index 33f5268..0000000 --- a/scripts/validate-install-docs.mjs +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env node -// Copyright (c) JFrog Ltd. 2026 -// Licensed under the Apache License, Version 2.0 -// Validates install/recovery documentation invariants. - -import { readFileSync, existsSync } from 'node:fs'; -import { join } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const repoRoot = process.cwd(); - -const HARNESS_OWN_REPO = { - claude: 'claude-plugin', - codex: 'codex-plugin', - cursor: 'cursor-plugin', - devin: 'devin-plugin', - opencode: 'opencode-jfrog-plugin', - vscode: 'vscode-plugin', -}; - -const ALL_PLUGIN_REPOS = Object.values(HARNESS_OWN_REPO); - -const REQUIRED_README_MARKERS = ['## Verify', '## Recovery']; - -const FORBIDDEN_PATTERNS = [ - { - re: /setting\s+(?:the\s+)?environment\s+variables?\s+after\s+a\s+failed\s+init\s+may\s+repair/i, - message: 'must not claim env vars repair failed init', - }, - { - re: /JFROG_URL/, - message: 'must not document the legacy JFROG_URL env var; use JFROG_PLATFORM_URL', - }, - { - re: /atlassian\.net/i, - message: 'must not reference JFrog Jira (atlassian.net) in repo files', - }, - { - re: /\b(?:AX|MLD)-\d+\b/, - message: 'must not include Jira ticket keys in repo files', - }, -]; - -export function validateInstallDocs({ repoRoot: root, harness }) { - const errors = []; - const readmePath = join(root, 'README.md'); - if (!existsSync(readmePath)) { - return [`${harness}: missing README.md`]; - } - const files = [{ label: 'README.md', text: readFileSync(readmePath, 'utf8') }]; - - const readme = files[0].text; - for (const marker of REQUIRED_README_MARKERS) { - if (!readme.includes(marker)) { - errors.push(`${harness}: README.md missing required marker: ${marker}`); - } - } - - const ownRepo = HARNESS_OWN_REPO[harness]; - const otherRepos = ALL_PLUGIN_REPOS.filter((name) => name !== ownRepo); - - for (const { label, text } of files) { - for (const { re, message } of FORBIDDEN_PATTERNS) { - if (re.test(text)) errors.push(`${harness}: ${label} ${message}`); - } - for (const other of otherRepos) { - if (text.includes(`github.com/jfrog/${other}`)) { - errors.push(`${harness}: ${label} must not link to github.com/jfrog/${other}`); - } - } - } - - return errors; -} - -function main() { - const harness = process.env.JFROG_PLUGIN_HARNESS ?? inferHarness(repoRoot); - const errors = validateInstallDocs({ repoRoot, harness }); - if (errors.length) { - console.error('install-docs validation failed:'); - for (const e of errors) console.error(` - ${e}`); - process.exit(1); - } - console.log('install-docs validation passed'); -} - -function inferHarness(root) { - if (existsSync(join(root, '.codex-plugin'))) return 'codex'; - if (existsSync(join(root, '.devin-plugin'))) return 'devin'; - if (existsSync(join(root, '.claude-plugin'))) return 'claude'; - if (existsSync(join(root, 'plugins', 'jfrog', '.cursor-plugin'))) return 'cursor'; - if (existsSync(join(root, 'plugin', '.claude-plugin'))) return 'vscode'; - if (existsSync(join(root, 'package.json')) && root.endsWith('opencode-jfrog-plugin')) return 'opencode'; - return 'unknown'; -} - -if (process.argv[1] === fileURLToPath(import.meta.url)) main(); diff --git a/scripts/validate-install-docs.test.mjs b/scripts/validate-install-docs.test.mjs deleted file mode 100644 index 0338ddf..0000000 --- a/scripts/validate-install-docs.test.mjs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) JFrog Ltd. 2026 -import { test } from 'node:test'; -import assert from 'node:assert/strict'; -import { mkdtempSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { validateInstallDocs } from './validate-install-docs.mjs'; - -function writeReadme(root, body) { - writeFileSync(join(root, 'README.md'), body); -} - -test('validateInstallDocs passes when README has Verify, Recovery, and no other-plugin links', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n\n## Verify\n\n1. list plugins\n\n## Recovery\n\n'); - assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'codex' }), []); -}); - -test('validateInstallDocs flags missing Verify section', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n\nInstall the plugin.\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('## Verify'))); -}); - -test('validateInstallDocs flags missing Recovery section', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n\n## Verify\n\n1. list plugins\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('## Recovery'))); -}); - -test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme( - root, - '# x\n## Verify\n## Recovery\nSetting environment variables after a failed init may repair MCP registration.' - ); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('env vars repair failed init'))); -}); - -test('validateInstallDocs rejects the legacy JFROG_URL env var', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme(root, '# Codex\n## Verify\n## Recovery\nSet `JFROG_URL` to your platform.\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('JFROG_URL'))); -}); - -test('validateInstallDocs rejects links to other plugin GitHub repos', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - writeReadme( - root, - '# Codex\n## Verify\n## Recovery\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' - ); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('claude-plugin'))); -}); - -test('validateInstallDocs rejects Jira URLs and ticket keys', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); - const host = ['jfrog-int', 'atlassian', 'net'].join('.'); - const key = ['AX', '1780'].join('-'); - writeReadme(root, `# Codex\n## Verify\n## Recovery\nSee [${key}](https://${host}/browse/${key}).\n`); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('atlassian.net'))); - assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); -});