From d09db66aac2de0ceab16c9309f437f457a1264b9 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 Devin web doc source shared install guidance and doc checks Co-authored-by: Cursor --- .github/workflows/validate.yml | 5 ++ CONTRIBUTING.md | 3 +- README.md | 28 ++++++++++ docs/install-jfrog-plugin-for-devin.md | 76 ++++++++++++++++++++++++++ scripts/validate-install-docs.mjs | 76 ++++++++++++++++++++++++++ scripts/validate-install-docs.test.mjs | 50 +++++++++++++++++ 6 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 docs/install-jfrog-plugin-for-devin.md create mode 100644 scripts/validate-install-docs.mjs create mode 100644 scripts/validate-install-docs.test.mjs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index a115d20..fdf9ebf 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,3 +22,8 @@ jobs: - name: Validate plugin layout run: node scripts/validate-devin-plugin.mjs + + - name: Validate install/recovery docs + run: | + node --test scripts/validate-install-docs.test.mjs + JFROG_PLUGIN_HARNESS=devin node scripts/validate-install-docs.mjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 90e98d2..0edd55e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,9 +14,10 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri ```bash node scripts/validate-devin-plugin.mjs +node scripts/validate-install-docs.mjs ``` -This checks `.devin-plugin/plugin.json` and walks every `skills/*/SKILL.md` for required YAML frontmatter. +This checks `.devin-plugin/plugin.json`, skill frontmatter, and install/recovery doc invariants (shared guide link, Verify section, web doc source). 4. **Test** by installing the plugin from the repository root (the repo root **is** the plugin root): diff --git a/README.md b/README.md index a63bba3..ede8307 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ JFrog plugin for [Devin](https://devin.ai/): JFrog Platform skills for artifact > **Current version:** `0.3.0` — skills from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) (pinned at `v0.25.0`) and a bundled JFrog MCP entry. +> **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 **Devin-only** differences. +> +> **Web doc source:** [`docs/install-jfrog-plugin-for-devin.md`](docs/install-jfrog-plugin-for-devin.md) is the source for the published Devin install page. + ## Skills | Skill | Description | @@ -52,6 +56,22 @@ The plugin registers this MCP server (declared in `mcp.json` and referenced from devin plugins install jfrog/devin-plugin -y ``` +Then, in a Devin session, run initialization: + +```text +/jfrog:jfrog-init +``` + +`jfrog-init` checks the JFrog CLI, server config, MCP registration, project +resolution, and AI Catalog entitlement, and walks you through anything missing. +Restart Devin afterwards so the MCP entry reloads. + +`JFROG_PLATFORM_URL` must be set in the environment that launches Devin **before** +you start the session, because `mcp.json` resolves `${env:JFROG_PLATFORM_URL}` at +launch. Setting it mid-session, or setting other JFrog variables afterwards, does +not repair a failed initialization — fix the reported step and re-run +`/jfrog:jfrog-init`. + ## Verify ```bash @@ -69,6 +89,14 @@ In a Devin CLI or Devin Local session: Confirm `jfrog` is listed (and Connected after OAuth). Ask the agent to list tools for `jfrog` — it should expose at least one tool. +Verification is a required install step, not a troubleshooting fallback. + +## Recovery + +If a check above fails, re-run `/jfrog:jfrog-init` after fixing the step it +reports, then restart Devin. For symptom-by-symptom guidance shared across +harnesses, see the [recovery playbook](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). + ## Repository layout ``` diff --git a/docs/install-jfrog-plugin-for-devin.md b/docs/install-jfrog-plugin-for-devin.md new file mode 100644 index 0000000..01d5bdc --- /dev/null +++ b/docs/install-jfrog-plugin-for-devin.md @@ -0,0 +1,76 @@ +# Install JFrog Agent Plugin for Devin + +> **Web publication source.** Publish to `https://docs.jfrog.com/ai-ml/docs/devin` (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 [Devin](https://devin.ai/) CLI and Devin Local sessions, including JFrog Agent Skills and the bundled JFrog Platform MCP server. + +## What's included + +| Component | Description | +| --- | --- | +| **JFrog Skills** | Including `jfrog`, `jfrog-init`, `jfrog-mcp-management`, package safety, AI Catalog, package-manager setup, and reference architecture skills. | +| **JFrog Platform MCP** | Remote MCP entry (`https://${JFROG_PLATFORM_URL}/mcp`) with OAuth (`devin mcp login jfrog`). | + +## Prerequisites + +See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#common-prerequisites-all-harnesses). Devin-specific additions: + +- **Devin CLI** with plugins enabled for your organization (`devin plugins install` allowed). +- **`JFROG_PLATFORM_URL`** — platform host only (for example `mycompany.jfrog.io`, no scheme, no trailing slash) in the environment that launches Devin. + +## Install the JFrog Agent Plugin for Devin + +1. Install the plugin: + + ```bash + devin plugins install jfrog/devin-plugin -y + ``` + +2. Export the platform host for the bundled MCP (shell profile, Devin launch environment, or session env): + + ```bash + export JFROG_PLATFORM_URL=mycompany.jfrog.io + ``` + +3. **Configure the JFrog CLI** for skills: + + ```bash + jf config add + ``` + +4. Start a Devin CLI or Devin Local session and run **`/jfrog:jfrog-init`** to walk the shared readiness checklist (Node, CLI, server, MCP file, project, AI Catalog). + +5. **Restart Devin** after plugin install or MCP config changes. + +6. Authenticate the platform MCP when prompted, or run: + + ```bash + devin mcp login jfrog + ``` + +## Verify (required) + +1. `devin plugins list` and `devin plugins info jfrog` — plugin installed; skills listed. +2. In session: `/mcp` — `jfrog` appears and shows Connected after OAuth. +3. Ask the agent to list tools for `jfrog` — at least one tool is returned. +4. `jf rt ping` — succeeds for your configured server. + +## Devin-specific notes + +- Skills are invoked as `/jfrog:` (for example `/jfrog:jfrog-init`, `/jfrog:jfrog-mcp-management`). +- The plugin ships skills **and** declares MCP in-repo; Devin loads skills on install and resolves `${env:JFROG_PLATFORM_URL}` at MCP startup — the host env var must be set **before** Devin starts. +- If `/jfrog-init` cannot find helper scripts, ensure you invoke the namespaced skill (`/jfrog:jfrog-init`) so Devin resolves the vendored skill directory correctly. + +## Recovery + +Follow the [shared recovery playbook](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). After a failed `/jfrog-init`, fix the reported step and **re-run `/jfrog:jfrog-init`** — do not assume exporting `JFROG_URL` alone repairs MCP registration. + +## 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) +- [Codex web source](https://github.com/jfrog/codex-plugin/blob/main/docs/install-jfrog-plugin-for-codex.md) +- [Troubleshoot Plugins](https://docs.jfrog.com/ai-ml/docs/troubleshoot-plugins) 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 0100bd25b7260e2fed8018b4a81a939e192d661b 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 Devin install docs self-contained Drop the Claude and Codex 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 | 13 +++--- docs/install-jfrog-plugin-for-devin.md | 37 +++++++++------- scripts/validate-install-docs.mjs | 54 ++++++++++++++++------- scripts/validate-install-docs.test.mjs | 60 +++++++++++++++----------- 5 files changed, 104 insertions(+), 62 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0edd55e..6594e21 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ node scripts/validate-devin-plugin.mjs node scripts/validate-install-docs.mjs ``` -This checks `.devin-plugin/plugin.json`, skill frontmatter, and install/recovery doc invariants (shared guide link, Verify section, web doc source). +This checks `.devin-plugin/plugin.json`, skill frontmatter, and install/recovery doc invariants (Verify section, web doc source, no links to other plugin repositories). 4. **Test** by installing the plugin from the repository root (the repo root **is** the plugin root): diff --git a/README.md b/README.md index ede8307..9ae9d14 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ JFrog plugin for [Devin](https://devin.ai/): JFrog Platform skills for artifact > **Current version:** `0.3.0` — skills from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) (pinned at `v0.25.0`) and a bundled JFrog MCP entry. -> **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 **Devin-only** differences. -> -> **Web doc source:** [`docs/install-jfrog-plugin-for-devin.md`](docs/install-jfrog-plugin-for-devin.md) is the source for the published Devin install page. +> **Install, verify, and recovery:** [`docs/install-jfrog-plugin-for-devin.md`](docs/install-jfrog-plugin-for-devin.md) is the source for the published Devin install page. ## Skills @@ -94,8 +92,13 @@ Verification is a required install step, not a troubleshooting fallback. ## Recovery If a check above fails, re-run `/jfrog:jfrog-init` after fixing the step it -reports, then restart Devin. For symptom-by-symptom guidance shared across -harnesses, see the [recovery playbook](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). +reports, then restart Devin. + +| Symptom | Do this | Do **not** do this | +| --- | --- | --- | +| MCP missing after install | Confirm `JFROG_PLATFORM_URL` is set in the **launch** environment, re-run `/jfrog:jfrog-init`, complete `devin mcp login jfrog`, restart Devin, then `/mcp`. | Assume exporting `JFROG_URL` mid-session will register MCP. | +| `/jfrog:jfrog-init` stopped at CLI/auth | Follow the skill prompt, then **re-run `/jfrog:jfrog-init`**. | Skip init and only export env vars. | +| Host placeholder unresolved | Set `JFROG_PLATFORM_URL` before starting Devin, restart, re-run init. | Change the variable after Devin is already running. | ## Repository layout diff --git a/docs/install-jfrog-plugin-for-devin.md b/docs/install-jfrog-plugin-for-devin.md index 01d5bdc..c25b31c 100644 --- a/docs/install-jfrog-plugin-for-devin.md +++ b/docs/install-jfrog-plugin-for-devin.md @@ -1,6 +1,6 @@ # Install JFrog Agent Plugin for Devin -> **Web publication source.** Publish to `https://docs.jfrog.com/ai-ml/docs/devin` (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/devin` (and add to the JFrog Agent Plugins index). Install and configure the JFrog Agent Plugin for [Devin](https://devin.ai/) CLI and Devin Local sessions, including JFrog Agent Skills and the bundled JFrog Platform MCP server. @@ -13,10 +13,13 @@ Install and configure the JFrog Agent Plugin for [Devin](https://devin.ai/) CLI ## Prerequisites -See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#common-prerequisites-all-harnesses). Devin-specific additions: - -- **Devin CLI** with plugins enabled for your organization (`devin plugins install` allowed). -- **`JFROG_PLATFORM_URL`** — platform host only (for example `mycompany.jfrog.io`, no scheme, no trailing slash) in the environment that launches Devin. +| Requirement | Notes | +| --- | --- | +| JFrog Platform instance | You can authenticate against it (URL + token or browser login). | +| Devin CLI | Plugins must be enabled for your organization (`devin plugins install` allowed). | +| `JFROG_PLATFORM_URL` | Platform host only (for example `mycompany.jfrog.io`, no scheme, no trailing slash) in the environment that **launches** Devin. | +| `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 Devin @@ -26,7 +29,7 @@ See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/ devin plugins install jfrog/devin-plugin -y ``` -2. Export the platform host for the bundled MCP (shell profile, Devin launch environment, or session env): +2. Export the platform host for the bundled MCP (shell profile, Devin launch environment, or session env) **before** starting Devin: ```bash export JFROG_PLATFORM_URL=mycompany.jfrog.io @@ -38,7 +41,7 @@ See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/ jf config add ``` -4. Start a Devin CLI or Devin Local session and run **`/jfrog:jfrog-init`** to walk the shared readiness checklist (Node, CLI, server, MCP file, project, AI Catalog). +4. Start a Devin CLI or Devin Local session and run **`/jfrog:jfrog-init`**. It checks Node, the JFrog CLI, server reachability, the MCP file, project resolution, and AI Catalog entitlement, and walks you through anything missing. 5. **Restart Devin** after plugin install or MCP config changes. @@ -48,6 +51,8 @@ See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/ devin mcp login jfrog ``` +`mcp.json` resolves `${env:JFROG_PLATFORM_URL}` at launch. Setting it mid-session, or setting other JFrog variables afterwards, does not repair a failed `/jfrog:jfrog-init` — fix the reported step and re-run the skill. + ## Verify (required) 1. `devin plugins list` and `devin plugins info jfrog` — plugin installed; skills listed. @@ -55,22 +60,22 @@ See the [shared prerequisites](https://github.com/jfrog/claude-plugin/blob/main/ 3. Ask the agent to list tools for `jfrog` — at least one tool is returned. 4. `jf rt ping` — succeeds for your configured server. -## Devin-specific notes +## Devin notes - Skills are invoked as `/jfrog:` (for example `/jfrog:jfrog-init`, `/jfrog:jfrog-mcp-management`). -- The plugin ships skills **and** declares MCP in-repo; Devin loads skills on install and resolves `${env:JFROG_PLATFORM_URL}` at MCP startup — the host env var must be set **before** Devin starts. -- If `/jfrog-init` cannot find helper scripts, ensure you invoke the namespaced skill (`/jfrog:jfrog-init`) so Devin resolves the vendored skill directory correctly. +- The plugin ships skills **and** declares MCP in-repo; Devin loads skills on install and resolves `${env:JFROG_PLATFORM_URL}` at MCP startup. +- If `/jfrog-init` cannot find helper scripts, invoke the namespaced skill (`/jfrog:jfrog-init`) so Devin resolves the vendored skill directory correctly. ## Recovery -Follow the [shared recovery playbook](https://github.com/jfrog/claude-plugin/blob/main/docs/shared-install-and-verify.md#recovery-playbook). After a failed `/jfrog-init`, fix the reported step and **re-run `/jfrog:jfrog-init`** — do not assume exporting `JFROG_URL` alone repairs MCP registration. +| Symptom | Do this | Do **not** do this | +| --- | --- | --- | +| MCP missing after install | Confirm `JFROG_PLATFORM_URL` is set in the **launch** environment, re-run `/jfrog:jfrog-init`, complete `devin mcp login jfrog`, **restart Devin**, then `/mcp`. | Assume exporting `JFROG_URL` mid-session will register MCP. | +| `/jfrog:jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `/jfrog:jfrog-init`**. | Skip init and only export env vars. | +| Host placeholder unresolved | Set `JFROG_PLATFORM_URL` before starting Devin, restart, re-run `/jfrog:jfrog-init`. | Change the variable after Devin is already running and expect MCP to pick it up. | +| Init cannot find helper scripts | Invoke `/jfrog:jfrog-init` (namespaced), not a bare `/jfrog-init`. | Reinstall before checking the skill name. | ## 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) -- [Codex web source](https://github.com/jfrog/codex-plugin/blob/main/docs/install-jfrog-plugin-for-codex.md) - [Troubleshoot Plugins](https://docs.jfrog.com/ai-ml/docs/troubleshoot-plugins) 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..86c8733 100644 --- a/scripts/validate-install-docs.test.mjs +++ b/scripts/validate-install-docs.test.mjs @@ -10,41 +10,51 @@ 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) { 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' }), []); + writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-devin.md'), '# web doc\n'); +} + +test('validateInstallDocs passes when README has Verify and no other-plugin links', () => { + const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); + writeReadme(root, '# Devin\n\n## Verify\n\n1. list plugins\n'); + withWebDoc(root); + assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'devin' }), []); }); -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 flags missing Verify section', () => { + const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); + writeReadme(root, '# Devin\n\nInstall the plugin.\n'); + withWebDoc(root); + const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); + 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'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); - assert.ok(errors.some((e) => e.includes('install-jfrog-plugin-for-codex.md'))); +test('validateInstallDocs requires Devin web doc source file', () => { + const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); + writeReadme(root, '# Devin\n\n## Verify\n'); + const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); + assert.ok(errors.some((e) => e.includes('install-jfrog-plugin-for-devin.md'))); }); test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { - const root = mkdtempSync(join(tmpdir(), 'codex-docs-')); + const root = mkdtempSync(join(tmpdir(), 'devin-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'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'codex' }); + withWebDoc(root); + const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); 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(), 'devin-docs-')); + writeReadme( + root, + '# Devin\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: 'devin' }); + assert.ok(errors.some((e) => e.includes('claude-plugin'))); +}); From c8601b7555e52d5cfe5a5f3bfad8e4fac11b8b84 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-devin.md | 2 +- scripts/validate-install-docs.mjs | 4 ++-- scripts/validate-install-docs.test.mjs | 8 ++++++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ae9d14..6e40b7d 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ reports, then restart Devin. | Symptom | Do this | Do **not** do this | | --- | --- | --- | -| MCP missing after install | Confirm `JFROG_PLATFORM_URL` is set in the **launch** environment, re-run `/jfrog:jfrog-init`, complete `devin mcp login jfrog`, restart Devin, then `/mcp`. | Assume exporting `JFROG_URL` mid-session will register MCP. | +| MCP missing after install | Confirm `JFROG_PLATFORM_URL` is set in the **launch** environment, re-run `/jfrog:jfrog-init`, complete `devin mcp login jfrog`, restart Devin, then `/mcp`. | Assume changing env vars mid-session will register MCP. | | `/jfrog:jfrog-init` stopped at CLI/auth | Follow the skill prompt, then **re-run `/jfrog:jfrog-init`**. | Skip init and only export env vars. | | Host placeholder unresolved | Set `JFROG_PLATFORM_URL` before starting Devin, restart, re-run init. | Change the variable after Devin is already running. | diff --git a/docs/install-jfrog-plugin-for-devin.md b/docs/install-jfrog-plugin-for-devin.md index c25b31c..9293b56 100644 --- a/docs/install-jfrog-plugin-for-devin.md +++ b/docs/install-jfrog-plugin-for-devin.md @@ -70,7 +70,7 @@ Install and configure the JFrog Agent Plugin for [Devin](https://devin.ai/) CLI | Symptom | Do this | Do **not** do this | | --- | --- | --- | -| MCP missing after install | Confirm `JFROG_PLATFORM_URL` is set in the **launch** environment, re-run `/jfrog:jfrog-init`, complete `devin mcp login jfrog`, **restart Devin**, then `/mcp`. | Assume exporting `JFROG_URL` mid-session will register MCP. | +| MCP missing after install | Confirm `JFROG_PLATFORM_URL` is set in the **launch** environment, re-run `/jfrog:jfrog-init`, complete `devin mcp login jfrog`, **restart Devin**, then `/mcp`. | Assume changing env vars mid-session will register MCP. | | `/jfrog:jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `/jfrog:jfrog-init`**. | Skip init and only export env vars. | | Host placeholder unresolved | Set `JFROG_PLATFORM_URL` before starting Devin, restart, re-run `/jfrog:jfrog-init`. | Change the variable after Devin is already running and expect MCP to pick it up. | | Init cannot find helper scripts | Invoke `/jfrog:jfrog-init` (namespaced), not a bare `/jfrog-init`. | Reinstall before checking the skill name. | 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 86c8733..7f46692 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(), 'devin-docs-')); + writeReadme(root, '# Devin\n## Verify\nSet `JFROG_URL` to your platform.\n'); + withWebDoc(root); + const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); + assert.ok(errors.some((e) => e.includes('JFROG_URL'))); +}); + test('validateInstallDocs rejects links to other plugin GitHub repos', () => { const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); writeReadme( From c34fec360820012febcee0a136167e2edfc7067d 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 7f46692..c932b00 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: 'devin' }); assert.ok(errors.some((e) => e.includes('claude-plugin'))); }); + +test('validateInstallDocs rejects Jira URLs and ticket keys', () => { + const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); + const host = ['jfrog-int', 'atlassian', 'net'].join('.'); + const key = ['AX', '1780'].join('-'); + writeReadme(root, `# Devin\n## Verify\nSee [${key}](https://${host}/browse/${key}).\n`); + withWebDoc(root); + const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); + assert.ok(errors.some((e) => e.includes('atlassian.net'))); + assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); +}); From 6160137a95b754194c13c2d174c0fad45b9d95ca 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 Devin 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-devin.md | 81 -------------------------- scripts/validate-install-docs.mjs | 11 ---- scripts/validate-install-docs.test.mjs | 22 +------ 5 files changed, 3 insertions(+), 115 deletions(-) delete mode 100644 docs/install-jfrog-plugin-for-devin.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6594e21..c77aa6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ node scripts/validate-devin-plugin.mjs node scripts/validate-install-docs.mjs ``` -This checks `.devin-plugin/plugin.json`, skill frontmatter, and install/recovery doc invariants (Verify section, web doc source, no links to other plugin repositories). +This checks `.devin-plugin/plugin.json`, skill frontmatter, and install/recovery doc invariants (Verify section, no links to other plugin repositories). 4. **Test** by installing the plugin from the repository root (the repo root **is** the plugin root): diff --git a/README.md b/README.md index 6e40b7d..5bb295b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ JFrog plugin for [Devin](https://devin.ai/): JFrog Platform skills for artifact > **Current version:** `0.3.0` — skills from [jfrog/jfrog-skills](https://github.com/jfrog/jfrog-skills) (pinned at `v0.25.0`) and a bundled JFrog MCP entry. -> **Install, verify, and recovery:** [`docs/install-jfrog-plugin-for-devin.md`](docs/install-jfrog-plugin-for-devin.md) is the source for the published Devin install page. - ## Skills | Skill | Description | diff --git a/docs/install-jfrog-plugin-for-devin.md b/docs/install-jfrog-plugin-for-devin.md deleted file mode 100644 index 9293b56..0000000 --- a/docs/install-jfrog-plugin-for-devin.md +++ /dev/null @@ -1,81 +0,0 @@ -# Install JFrog Agent Plugin for Devin - -> **Web publication source.** Publish to `https://docs.jfrog.com/ai-ml/docs/devin` (and add to the JFrog Agent Plugins index). - -Install and configure the JFrog Agent Plugin for [Devin](https://devin.ai/) CLI and Devin Local sessions, including JFrog Agent Skills and the bundled JFrog Platform MCP server. - -## What's included - -| Component | Description | -| --- | --- | -| **JFrog Skills** | Including `jfrog`, `jfrog-init`, `jfrog-mcp-management`, package safety, AI Catalog, package-manager setup, and reference architecture skills. | -| **JFrog Platform MCP** | Remote MCP entry (`https://${JFROG_PLATFORM_URL}/mcp`) with OAuth (`devin mcp login jfrog`). | - -## Prerequisites - -| Requirement | Notes | -| --- | --- | -| JFrog Platform instance | You can authenticate against it (URL + token or browser login). | -| Devin CLI | Plugins must be enabled for your organization (`devin plugins install` allowed). | -| `JFROG_PLATFORM_URL` | Platform host only (for example `mycompany.jfrog.io`, no scheme, no trailing slash) in the environment that **launches** Devin. | -| `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 Devin - -1. Install the plugin: - - ```bash - devin plugins install jfrog/devin-plugin -y - ``` - -2. Export the platform host for the bundled MCP (shell profile, Devin launch environment, or session env) **before** starting Devin: - - ```bash - export JFROG_PLATFORM_URL=mycompany.jfrog.io - ``` - -3. **Configure the JFrog CLI** for skills: - - ```bash - jf config add - ``` - -4. Start a Devin CLI or Devin Local session and run **`/jfrog:jfrog-init`**. It checks Node, the JFrog CLI, server reachability, the MCP file, project resolution, and AI Catalog entitlement, and walks you through anything missing. - -5. **Restart Devin** after plugin install or MCP config changes. - -6. Authenticate the platform MCP when prompted, or run: - - ```bash - devin mcp login jfrog - ``` - -`mcp.json` resolves `${env:JFROG_PLATFORM_URL}` at launch. Setting it mid-session, or setting other JFrog variables afterwards, does not repair a failed `/jfrog:jfrog-init` — fix the reported step and re-run the skill. - -## Verify (required) - -1. `devin plugins list` and `devin plugins info jfrog` — plugin installed; skills listed. -2. In session: `/mcp` — `jfrog` appears and shows Connected after OAuth. -3. Ask the agent to list tools for `jfrog` — at least one tool is returned. -4. `jf rt ping` — succeeds for your configured server. - -## Devin notes - -- Skills are invoked as `/jfrog:` (for example `/jfrog:jfrog-init`, `/jfrog:jfrog-mcp-management`). -- The plugin ships skills **and** declares MCP in-repo; Devin loads skills on install and resolves `${env:JFROG_PLATFORM_URL}` at MCP startup. -- If `/jfrog-init` cannot find helper scripts, invoke the namespaced skill (`/jfrog:jfrog-init`) so Devin resolves the vendored skill directory correctly. - -## Recovery - -| Symptom | Do this | Do **not** do this | -| --- | --- | --- | -| MCP missing after install | Confirm `JFROG_PLATFORM_URL` is set in the **launch** environment, re-run `/jfrog:jfrog-init`, complete `devin mcp login jfrog`, **restart Devin**, then `/mcp`. | Assume changing env vars mid-session will register MCP. | -| `/jfrog:jfrog-init` stopped at CLI/auth | Follow the skill prompt (`jf config add`, web login, or token path), then **re-run `/jfrog:jfrog-init`**. | Skip init and only export env vars. | -| Host placeholder unresolved | Set `JFROG_PLATFORM_URL` before starting Devin, restart, re-run `/jfrog:jfrog-init`. | Change the variable after Devin is already running and expect MCP to pick it up. | -| Init cannot find helper scripts | Invoke `/jfrog:jfrog-init` (namespaced), not a bare `/jfrog-init`. | Reinstall before checking the skill name. | - -## 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) 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 c932b00..1595a86 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) { - mkdirSync(join(root, 'docs'), { recursive: true }); - writeFileSync(join(root, 'docs', 'install-jfrog-plugin-for-devin.md'), '# web doc\n'); -} - test('validateInstallDocs passes when README has Verify and no other-plugin links', () => { const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); writeReadme(root, '# Devin\n\n## Verify\n\n1. list plugins\n'); - withWebDoc(root); assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'devin' }), []); }); test('validateInstallDocs flags missing Verify section', () => { const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); writeReadme(root, '# Devin\n\nInstall the plugin.\n'); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); assert.ok(errors.some((e) => e.includes('## Verify'))); }); -test('validateInstallDocs requires Devin web doc source file', () => { - const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); - writeReadme(root, '# Devin\n\n## Verify\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); - assert.ok(errors.some((e) => e.includes('install-jfrog-plugin-for-devin.md'))); -}); - test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { const root = mkdtempSync(join(tmpdir(), 'devin-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: 'devin' }); 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(), 'devin-docs-')); writeReadme(root, '# Devin\n## Verify\nSet `JFROG_URL` to your platform.\n'); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); 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(), 'devin-docs-')); writeReadme( root, - '# Devin\n## Verify\nSee https://github.com/jfrog/claude-plugin/blob/main/docs/install-and-verify.md\n' + '# Devin\n## Verify\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' ); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); 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, `# Devin\n## Verify\nSee [${key}](https://${host}/browse/${key}).\n`); - withWebDoc(root); const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); assert.ok(errors.some((e) => e.includes('atlassian.net'))); assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); From 544e9211f2f3bca6022da1c1525e340fbd5015ce 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 1595a86..e92830d 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(), 'devin-docs-')); - writeReadme(root, '# Devin\n\n## Verify\n\n1. list plugins\n'); + writeReadme(root, '# Devin\n\n## Verify\n\n1. list plugins\n\n## Recovery\n\n'); assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'devin' }), []); }); @@ -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(), 'devin-docs-')); + writeReadme(root, '# Devin\n\n## Verify\n\n1. list plugins\n'); + const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); + assert.ok(errors.some((e) => e.includes('## Recovery'))); +}); + test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { const root = mkdtempSync(join(tmpdir(), 'devin-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: 'devin' }); 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(), 'devin-docs-')); - writeReadme(root, '# Devin\n## Verify\nSet `JFROG_URL` to your platform.\n'); + writeReadme(root, '# Devin\n## Verify\n## Recovery\nSet `JFROG_URL` to your platform.\n'); const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); 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(), 'devin-docs-')); writeReadme( root, - '# Devin\n## Verify\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' + '# Devin\n## Verify\n## Recovery\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' ); const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); 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(), 'devin-docs-')); const host = ['jfrog-int', 'atlassian', 'net'].join('.'); const key = ['AX', '1780'].join('-'); - writeReadme(root, `# Devin\n## Verify\nSee [${key}](https://${host}/browse/${key}).\n`); + writeReadme(root, `# Devin\n## Verify\n## Recovery\nSee [${key}](https://${host}/browse/${key}).\n`); const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); assert.ok(errors.some((e) => e.includes('atlassian.net'))); assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); From 4ae2ab053e9e19a05894e6abeb9bc6c52be0f9b4 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/validate.yml | 5 -- CONTRIBUTING.md | 3 +- scripts/validate-install-docs.mjs | 97 -------------------------- scripts/validate-install-docs.test.mjs | 68 ------------------ 4 files changed, 1 insertion(+), 172 deletions(-) delete mode 100644 scripts/validate-install-docs.mjs delete mode 100644 scripts/validate-install-docs.test.mjs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index fdf9ebf..a115d20 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -22,8 +22,3 @@ jobs: - name: Validate plugin layout run: node scripts/validate-devin-plugin.mjs - - - name: Validate install/recovery docs - run: | - node --test scripts/validate-install-docs.test.mjs - JFROG_PLUGIN_HARNESS=devin node scripts/validate-install-docs.mjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c77aa6b..e71ef2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,10 +14,9 @@ All contributors must sign the [JFrog CLA](https://jfrog.com/cla/) before contri ```bash node scripts/validate-devin-plugin.mjs -node scripts/validate-install-docs.mjs ``` -This checks `.devin-plugin/plugin.json`, skill frontmatter, and install/recovery doc invariants (Verify section, no links to other plugin repositories). +This checks `.devin-plugin/plugin.json` and skill frontmatter. 4. **Test** by installing the plugin from the repository root (the repo root **is** the plugin root): 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 e92830d..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(), 'devin-docs-')); - writeReadme(root, '# Devin\n\n## Verify\n\n1. list plugins\n\n## Recovery\n\n'); - assert.deepEqual(validateInstallDocs({ repoRoot: root, harness: 'devin' }), []); -}); - -test('validateInstallDocs flags missing Verify section', () => { - const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); - writeReadme(root, '# Devin\n\nInstall the plugin.\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); - assert.ok(errors.some((e) => e.includes('## Verify'))); -}); - -test('validateInstallDocs flags missing Recovery section', () => { - const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); - writeReadme(root, '# Devin\n\n## Verify\n\n1. list plugins\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); - assert.ok(errors.some((e) => e.includes('## Recovery'))); -}); - -test('validateInstallDocs rejects contradictory failed-init env-var recovery claims', () => { - const root = mkdtempSync(join(tmpdir(), 'devin-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: 'devin' }); - 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(), 'devin-docs-')); - writeReadme(root, '# Devin\n## Verify\n## Recovery\nSet `JFROG_URL` to your platform.\n'); - const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); - assert.ok(errors.some((e) => e.includes('JFROG_URL'))); -}); - -test('validateInstallDocs rejects links to other plugin GitHub repos', () => { - const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); - writeReadme( - root, - '# Devin\n## Verify\n## Recovery\nSee https://github.com/jfrog/claude-plugin/blob/main/README.md\n' - ); - const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); - assert.ok(errors.some((e) => e.includes('claude-plugin'))); -}); - -test('validateInstallDocs rejects Jira URLs and ticket keys', () => { - const root = mkdtempSync(join(tmpdir(), 'devin-docs-')); - const host = ['jfrog-int', 'atlassian', 'net'].join('.'); - const key = ['AX', '1780'].join('-'); - writeReadme(root, `# Devin\n## Verify\n## Recovery\nSee [${key}](https://${host}/browse/${key}).\n`); - const errors = validateInstallDocs({ repoRoot: root, harness: 'devin' }); - assert.ok(errors.some((e) => e.includes('atlassian.net'))); - assert.ok(errors.some((e) => e.includes('Jira ticket keys'))); -});