diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3220297b..12828bfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,8 +143,12 @@ jobs: - name: Report generated drift run: | - managed_files=$(node tools/scripts/generated_files.js --shell --include-mixed) - drift_files=$(git diff --name-only -- $managed_files) + mapfile -t managed_files < <(node tools/scripts/generated_files.js --include-mixed) + if [ "${#managed_files[@]}" -eq 0 ]; then + echo "::error::No managed files resolved from generated_files contract." + exit 1 + fi + drift_files=$(git diff --name-only -- "${managed_files[@]}") { echo "## Artifact Preview" @@ -232,11 +236,15 @@ jobs: - name: Auto-commit canonical artifacts if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: | - managed_files=$(node tools/scripts/generated_files.js --shell --include-mixed) + mapfile -t managed_files < <(node tools/scripts/generated_files.js --include-mixed) + if [ "${#managed_files[@]}" -eq 0 ]; then + echo "No managed files resolved from generated_files contract." + exit 1 + fi git diff --quiet && exit 0 - git add $managed_files || true + git add -- "${managed_files[@]}" || true git diff --cached --quiet && exit 0 diff --git a/.github/workflows/repo-hygiene.yml b/.github/workflows/repo-hygiene.yml index 3d17a850..68e1c99c 100644 --- a/.github/workflows/repo-hygiene.yml +++ b/.github/workflows/repo-hygiene.yml @@ -40,7 +40,11 @@ jobs: run: | set -euo pipefail - managed_files=$(node tools/scripts/generated_files.js --shell --include-mixed) + mapfile -t managed_files < <(node tools/scripts/generated_files.js --include-mixed) + if [ "${#managed_files[@]}" -eq 0 ]; then + echo "No managed files resolved from generated_files contract." + exit 1 + fi if git diff --quiet; then echo "No repo-state drift detected." @@ -49,7 +53,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add $managed_files || true + git add -- "${managed_files[@]}" || true if git diff --cached --quiet; then echo "Repo hygiene produced unmanaged drift." diff --git a/plugins/antigravity-awesome-skills-claude/skills/claude-monitor/scripts/api_bench.py b/plugins/antigravity-awesome-skills-claude/skills/claude-monitor/scripts/api_bench.py index 5fe46d80..ce01cee4 100644 --- a/plugins/antigravity-awesome-skills-claude/skills/claude-monitor/scripts/api_bench.py +++ b/plugins/antigravity-awesome-skills-claude/skills/claude-monitor/scripts/api_bench.py @@ -50,7 +50,6 @@ def test_tls_handshake(host, port=443, timeout=5): """Testa tempo do handshake TLS.""" try: context = ssl.create_default_context() - context.minimum_version = ssl.TLSVersion.TLSv1_2 start = time.time() with socket.create_connection((host, port), timeout=timeout) as sock: with context.wrap_socket(sock, server_hostname=host) as ssock: diff --git a/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package-lock.json b/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package-lock.json index 4d91dee8..2a4b1d15 100644 --- a/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package-lock.json +++ b/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package-lock.json @@ -1527,9 +1527,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { diff --git a/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package.json b/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package.json index d853c5af..9b0fcdae 100644 --- a/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package.json +++ b/plugins/antigravity-awesome-skills-claude/skills/loki-mode/examples/todo-app-generated/frontend/package.json @@ -23,7 +23,6 @@ "react-dom": "^19.2.3" }, "overrides": { - "picomatch": "^4.0.4", "rollup": "^4.59.0" } } diff --git a/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/send_test_message.py b/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/send_test_message.py index 882a0432..7f121a1f 100644 --- a/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/send_test_message.py +++ b/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/send_test_message.py @@ -10,6 +10,7 @@ Usage: """ import argparse +import json import os import sys @@ -77,11 +78,17 @@ def send_test(to: str, message: str) -> None: error = data.get("error", {}) print(f"Error sending message:") print(f" Code: {error.get('code', '?')}") - print(f" Status: {response.status_code}") - print(" Message: Request rejected by WhatsApp Cloud API.") + print(f" Message: {error.get('message', 'Unknown error')}") + if error.get("error_data"): + print(f" Details: {error['error_data'].get('details', '')}") print() - print("Response details omitted to avoid exposing sensitive API data.") + print("Full response:") + # Mask token in response output to prevent credential leakage + response_str = json.dumps(data, indent=2) + if token and token in response_str: + response_str = response_str.replace(token, _mask_secret(token)) + print(response_str) except httpx.ConnectError: print("Error: Connection failed. Check your internet connection.") @@ -89,8 +96,10 @@ def send_test(to: str, message: str) -> None: except httpx.TimeoutException: print("Error: Request timed out.") sys.exit(1) - except Exception: - print("Error: Unexpected failure while sending the message.") + except Exception as e: + # Mask token in error output to prevent credential leakage + safe_err = str(e).replace(token, _mask_secret(token)) if token else str(e) + print(f"Error: {safe_err}") sys.exit(1) diff --git a/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/validate_config.py b/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/validate_config.py index 66c3362e..ab6a90e1 100644 --- a/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/validate_config.py +++ b/plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/validate_config.py @@ -54,15 +54,6 @@ def _mask_secret(value: str) -> str: return f"{value[:6]}...masked" -def _extract_error_code(response: httpx.Response) -> str: - """Return an API error code without logging response details.""" - try: - error = response.json().get("error", {}) - return str(error.get("code", "?")) - except Exception: - return "?" - - def test_api_connection() -> tuple[bool, str]: """Test connection to WhatsApp Cloud API.""" token = os.environ.get("WHATSAPP_TOKEN", "") @@ -79,18 +70,23 @@ def test_api_connection() -> tuple[bool, str]: if response.status_code == 200: data = response.json() return True, ( + f"Phone: {data.get('display_phone_number', 'N/A')}\n" + f" Name: {data.get('verified_name', 'N/A')}\n" f" Status: {data.get('code_verification_status', 'N/A')}\n" f" Quality: {data.get('quality_rating', 'N/A')}" ) else: - return False, f"API request failed with status {response.status_code} (code {_extract_error_code(response)})" + error = response.json().get("error", {}) + return False, f"API Error {error.get('code', '?')}: {error.get('message', 'Unknown')}" except httpx.ConnectError: return False, "Connection failed. Check your internet connection." except httpx.TimeoutException: return False, "Request timed out after 10 seconds." - except Exception: - return False, "Unexpected error while checking phone number access." + except Exception as e: + # Mask token in error output to prevent credential leakage + safe_err = str(e).replace(token, _mask_secret(token)) if token else str(e) + return False, f"Unexpected error: {safe_err}" def test_waba_access() -> tuple[bool, str]: @@ -110,10 +106,13 @@ def test_waba_access() -> tuple[bool, str]: count = len(data.get("data", [])) return True, f"WABA accessible. {count} phone number(s) found." else: - return False, f"API request failed with status {response.status_code} (code {_extract_error_code(response)})" + error = response.json().get("error", {}) + return False, f"API Error {error.get('code', '?')}: {error.get('message', 'Unknown')}" - except Exception: - return False, "Unexpected error while checking WABA access." + except Exception as e: + # Mask token in error output to prevent credential leakage + safe_err = str(e).replace(token, _mask_secret(token)) if token else str(e) + return False, f"Error: {safe_err}" def main(): diff --git a/plugins/antigravity-awesome-skills/skills/claude-monitor/scripts/api_bench.py b/plugins/antigravity-awesome-skills/skills/claude-monitor/scripts/api_bench.py index 5fe46d80..ce01cee4 100644 --- a/plugins/antigravity-awesome-skills/skills/claude-monitor/scripts/api_bench.py +++ b/plugins/antigravity-awesome-skills/skills/claude-monitor/scripts/api_bench.py @@ -50,7 +50,6 @@ def test_tls_handshake(host, port=443, timeout=5): """Testa tempo do handshake TLS.""" try: context = ssl.create_default_context() - context.minimum_version = ssl.TLSVersion.TLSv1_2 start = time.time() with socket.create_connection((host, port), timeout=timeout) as sock: with context.wrap_socket(sock, server_hostname=host) as ssock: diff --git a/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/send_test_message.py b/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/send_test_message.py index 882a0432..7f121a1f 100644 --- a/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/send_test_message.py +++ b/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/send_test_message.py @@ -10,6 +10,7 @@ Usage: """ import argparse +import json import os import sys @@ -77,11 +78,17 @@ def send_test(to: str, message: str) -> None: error = data.get("error", {}) print(f"Error sending message:") print(f" Code: {error.get('code', '?')}") - print(f" Status: {response.status_code}") - print(" Message: Request rejected by WhatsApp Cloud API.") + print(f" Message: {error.get('message', 'Unknown error')}") + if error.get("error_data"): + print(f" Details: {error['error_data'].get('details', '')}") print() - print("Response details omitted to avoid exposing sensitive API data.") + print("Full response:") + # Mask token in response output to prevent credential leakage + response_str = json.dumps(data, indent=2) + if token and token in response_str: + response_str = response_str.replace(token, _mask_secret(token)) + print(response_str) except httpx.ConnectError: print("Error: Connection failed. Check your internet connection.") @@ -89,8 +96,10 @@ def send_test(to: str, message: str) -> None: except httpx.TimeoutException: print("Error: Request timed out.") sys.exit(1) - except Exception: - print("Error: Unexpected failure while sending the message.") + except Exception as e: + # Mask token in error output to prevent credential leakage + safe_err = str(e).replace(token, _mask_secret(token)) if token else str(e) + print(f"Error: {safe_err}") sys.exit(1) diff --git a/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/validate_config.py b/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/validate_config.py index 66c3362e..ab6a90e1 100644 --- a/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/validate_config.py +++ b/plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/validate_config.py @@ -54,15 +54,6 @@ def _mask_secret(value: str) -> str: return f"{value[:6]}...masked" -def _extract_error_code(response: httpx.Response) -> str: - """Return an API error code without logging response details.""" - try: - error = response.json().get("error", {}) - return str(error.get("code", "?")) - except Exception: - return "?" - - def test_api_connection() -> tuple[bool, str]: """Test connection to WhatsApp Cloud API.""" token = os.environ.get("WHATSAPP_TOKEN", "") @@ -79,18 +70,23 @@ def test_api_connection() -> tuple[bool, str]: if response.status_code == 200: data = response.json() return True, ( + f"Phone: {data.get('display_phone_number', 'N/A')}\n" + f" Name: {data.get('verified_name', 'N/A')}\n" f" Status: {data.get('code_verification_status', 'N/A')}\n" f" Quality: {data.get('quality_rating', 'N/A')}" ) else: - return False, f"API request failed with status {response.status_code} (code {_extract_error_code(response)})" + error = response.json().get("error", {}) + return False, f"API Error {error.get('code', '?')}: {error.get('message', 'Unknown')}" except httpx.ConnectError: return False, "Connection failed. Check your internet connection." except httpx.TimeoutException: return False, "Request timed out after 10 seconds." - except Exception: - return False, "Unexpected error while checking phone number access." + except Exception as e: + # Mask token in error output to prevent credential leakage + safe_err = str(e).replace(token, _mask_secret(token)) if token else str(e) + return False, f"Unexpected error: {safe_err}" def test_waba_access() -> tuple[bool, str]: @@ -110,10 +106,13 @@ def test_waba_access() -> tuple[bool, str]: count = len(data.get("data", [])) return True, f"WABA accessible. {count} phone number(s) found." else: - return False, f"API request failed with status {response.status_code} (code {_extract_error_code(response)})" + error = response.json().get("error", {}) + return False, f"API Error {error.get('code', '?')}: {error.get('message', 'Unknown')}" - except Exception: - return False, "Unexpected error while checking WABA access." + except Exception as e: + # Mask token in error output to prevent credential leakage + safe_err = str(e).replace(token, _mask_secret(token)) if token else str(e) + return False, f"Error: {safe_err}" def main(): diff --git a/tools/bin/install.js b/tools/bin/install.js index 43cf333c..68bf7773 100755 --- a/tools/bin/install.js +++ b/tools/bin/install.js @@ -8,6 +8,7 @@ const { resolveSafeRealPath } = require("../lib/symlink-safety"); const REPO = "https://github.com/sickn33/antigravity-awesome-skills.git"; const HOME = process.env.HOME || process.env.USERPROFILE || ""; +const INSTALL_MANIFEST_FILE = ".antigravity-install-manifest.json"; function resolveDir(p) { if (!p) return null; @@ -170,23 +171,114 @@ function copyRecursiveSync(src, dest, rootDir = src, skipGit = true) { } /** Copy contents of repo's skills/ into target so each skill is target/skill-name/ (for Claude Code etc.). */ -function installSkillsIntoTarget(tempDir, target) { +function getInstallEntries(tempDir) { const repoSkills = path.join(tempDir, "skills"); if (!fs.existsSync(repoSkills)) { console.error("Cloned repo has no skills/ directory."); process.exit(1); } - fs.readdirSync(repoSkills).forEach((name) => { + const entries = fs.readdirSync(repoSkills); + if (fs.existsSync(path.join(tempDir, "docs"))) { + entries.push("docs"); + } + return entries; +} + +function installSkillsIntoTarget(tempDir, target, installEntries) { + const repoSkills = path.join(tempDir, "skills"); + installEntries.forEach((name) => { + if (name === "docs") { + const repoDocs = path.join(tempDir, "docs"); + const docsDest = path.join(target, "docs"); + if (!fs.existsSync(docsDest)) fs.mkdirSync(docsDest, { recursive: true }); + copyRecursiveSync(repoDocs, docsDest, repoDocs); + return; + } const src = path.join(repoSkills, name); const dest = path.join(target, name); copyRecursiveSync(src, dest, repoSkills); }); - const repoDocs = path.join(tempDir, "docs"); - if (fs.existsSync(repoDocs)) { - const docsDest = path.join(target, "docs"); - if (!fs.existsSync(docsDest)) fs.mkdirSync(docsDest, { recursive: true }); - copyRecursiveSync(repoDocs, docsDest, repoDocs); +} + +function resolveManagedPath(targetPath, entry) { + const resolvedTargetPath = path.resolve(targetPath); + const candidate = path.resolve(targetPath, entry); + const relative = path.relative(resolvedTargetPath, candidate); + if (relative.startsWith("..") || path.isAbsolute(relative)) { + return null; } + return candidate; +} + +function readInstallManifest(targetPath) { + const manifestPath = path.join(targetPath, INSTALL_MANIFEST_FILE); + if (!fs.existsSync(manifestPath)) { + return []; + } + try { + const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8")); + if (!parsed || !Array.isArray(parsed.entries)) { + return []; + } + return parsed.entries.filter((entry) => typeof entry === "string"); + } catch (error) { + console.warn(` Ignoring invalid install manifest at ${manifestPath}`); + return []; + } +} + +function writeInstallManifest(targetPath, installEntries) { + const manifestPath = path.join(targetPath, INSTALL_MANIFEST_FILE); + fs.writeFileSync( + manifestPath, + JSON.stringify( + { + schemaVersion: 1, + updatedAt: new Date().toISOString(), + entries: installEntries.slice().sort(), + }, + null, + 2, + ) + "\n", + "utf8", + ); +} + +function pruneRemovedEntries(targetPath, previousEntries, installEntries) { + const next = new Set(installEntries); + for (const entry of previousEntries) { + if (next.has(entry)) { + continue; + } + const candidate = resolveManagedPath(targetPath, entry); + if (!candidate) { + console.warn(` Skipping unsafe managed entry path from manifest: ${entry}`); + continue; + } + fs.rmSync(candidate, { recursive: true, force: true }); + console.log(` Removed stale managed entry: ${entry}`); + } +} + +function ensureTargetIsDirectory(targetPath) { + if (!fs.existsSync(targetPath)) { + return; + } + const stats = fs.lstatSync(targetPath); + if (stats.isDirectory()) { + return; + } + if (stats.isSymbolicLink()) { + try { + if (fs.statSync(targetPath).isDirectory()) { + return; + } + } catch (error) { + // Fall through to the error below for dangling links or non-directory targets. + } + } + console.error(` Install path exists but is not a directory: ${targetPath}`); + process.exit(1); } function run(cmd, args, opts = {}) { @@ -196,6 +288,7 @@ function run(cmd, args, opts = {}) { function installForTarget(tempDir, target) { if (fs.existsSync(target.path)) { + ensureTargetIsDirectory(target.path); const gitDir = path.join(target.path, ".git"); if (fs.existsSync(gitDir)) { console.log(` Migrating from full-repo install to skills-only layout…`); @@ -232,7 +325,11 @@ function installForTarget(tempDir, target) { fs.mkdirSync(target.path, { recursive: true }); } - installSkillsIntoTarget(tempDir, target.path); + const installEntries = getInstallEntries(tempDir); + const previousEntries = readInstallManifest(target.path); + pruneRemovedEntries(target.path, previousEntries, installEntries); + installSkillsIntoTarget(tempDir, target.path, installEntries); + writeInstallManifest(target.path, installEntries); console.log(` ✓ Installed to ${target.path}`); } @@ -322,7 +419,11 @@ if (require.main === module) { module.exports = { copyRecursiveSync, getPostInstallMessages, + getInstallEntries, installSkillsIntoTarget, installForTarget, main, + pruneRemovedEntries, + readInstallManifest, + writeInstallManifest, }; diff --git a/tools/scripts/tests/automation_workflows.test.js b/tools/scripts/tests/automation_workflows.test.js index a623534c..62abd3e2 100644 --- a/tools/scripts/tests/automation_workflows.test.js +++ b/tools/scripts/tests/automation_workflows.test.js @@ -130,8 +130,8 @@ assert.match( ); assert.match( hygieneWorkflow, - /generated_files\.js --shell --include-mixed/, - "repo hygiene workflow should stage the mixed generated files contract", + /generated_files\.js --include-mixed/, + "repo hygiene workflow should resolve and stage the mixed generated files contract", ); assert.match(publishWorkflow, /run: npm ci/, "npm publish workflow should install dependencies"); diff --git a/tools/scripts/tests/installer_update_sync.test.js b/tools/scripts/tests/installer_update_sync.test.js new file mode 100644 index 00000000..0c9f2bc5 --- /dev/null +++ b/tools/scripts/tests/installer_update_sync.test.js @@ -0,0 +1,85 @@ +const assert = require("assert"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const { spawnSync } = require("child_process"); + +const installer = require(path.resolve(__dirname, "..", "..", "bin", "install.js")); + +function writeSkill(repoRoot, skillName, content = "# Skill\n") { + const skillDir = path.join(repoRoot, "skills", skillName); + fs.mkdirSync(skillDir, { recursive: true }); + fs.writeFileSync(path.join(skillDir, "SKILL.md"), content, "utf8"); +} + +function createFakeRepo(rootDir, skills) { + fs.mkdirSync(path.join(rootDir, "skills"), { recursive: true }); + for (const skillName of skills) { + writeSkill(rootDir, skillName, `# ${skillName}\n`); + } +} + +function readManifestEntries(targetDir) { + const manifestPath = path.join(targetDir, ".antigravity-install-manifest.json"); + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); + return manifest.entries; +} + +const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "installer-update-sync-")); + +try { + const repoV1 = path.join(tmpRoot, "repo-v1"); + const repoV2 = path.join(tmpRoot, "repo-v2"); + const targetDir = path.join(tmpRoot, "target"); + fs.mkdirSync(targetDir, { recursive: true }); + + createFakeRepo(repoV1, ["skill-a", "skill-b"]); + createFakeRepo(repoV2, ["skill-a"]); + + installer.installForTarget(repoV1, { name: "Test", path: targetDir }); + assert.ok(fs.existsSync(path.join(targetDir, "skill-a", "SKILL.md"))); + assert.ok(fs.existsSync(path.join(targetDir, "skill-b", "SKILL.md"))); + + installer.installForTarget(repoV2, { name: "Test", path: targetDir }); + assert.ok(fs.existsSync(path.join(targetDir, "skill-a", "SKILL.md"))); + assert.strictEqual( + fs.existsSync(path.join(targetDir, "skill-b")), + false, + "stale managed skill should be pruned during updates", + ); + assert.deepStrictEqual( + readManifestEntries(targetDir), + ["skill-a"], + "install manifest should mirror the latest installed entries", + ); + + const badTargetPath = path.join(tmpRoot, "bad-target"); + fs.writeFileSync(badTargetPath, "not-a-directory", "utf8"); + const badTargetCheck = spawnSync( + process.execPath, + [ + "-e", + ` +const installer = require(${JSON.stringify(path.resolve(__dirname, "..", "..", "bin", "install.js"))}); +installer.installForTarget(${JSON.stringify(repoV2)}, { name: "BadTarget", path: ${JSON.stringify(badTargetPath)} }); +`, + ], + { + stdio: "pipe", + encoding: "utf8", + }, + ); + + assert.notStrictEqual( + badTargetCheck.status, + 0, + "installer should fail fast when target path exists as a non-directory", + ); + assert.match( + `${badTargetCheck.stdout}\n${badTargetCheck.stderr}`, + /not a directory/i, + "installer should print a clear error for non-directory targets", + ); +} finally { + fs.rmSync(tmpRoot, { recursive: true, force: true }); +} diff --git a/tools/scripts/tests/run-test-suite.js b/tools/scripts/tests/run-test-suite.js index 966ae35e..50ffa433 100644 --- a/tools/scripts/tests/run-test-suite.js +++ b/tools/scripts/tests/run-test-suite.js @@ -19,6 +19,7 @@ const LOCAL_TEST_COMMANDS = [ [path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_editorial_bundles.py")], [path.join(TOOL_SCRIPTS, "run-python.js"), path.join(TOOL_TESTS, "test_plugin_compatibility.py")], [path.join(TOOL_TESTS, "installer_antigravity_guidance.test.js")], + [path.join(TOOL_TESTS, "installer_update_sync.test.js")], [path.join(TOOL_TESTS, "jetski_gemini_loader.test.cjs")], [path.join(TOOL_TESTS, "npm_package_contents.test.js")], [path.join(TOOL_TESTS, "setup_web_sync.test.js")],