From 873c74818d5a8cc70057cc4b25b7d75d6c27e0e8 Mon Sep 17 00:00:00 2001 From: cgasgarth <64235119+cgasgarth@users.noreply.github.com> Date: Sat, 21 Mar 2026 22:44:21 -0400 Subject: [PATCH] Add live OpenCode smoke skill --- skills/opencode-live-smoke/SKILL.md | 62 +++++++++ .../scripts/run-live-smoke.sh | 122 ++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 skills/opencode-live-smoke/SKILL.md create mode 100755 skills/opencode-live-smoke/scripts/run-live-smoke.sh diff --git a/skills/opencode-live-smoke/SKILL.md b/skills/opencode-live-smoke/SKILL.md new file mode 100644 index 0000000..1b7434f --- /dev/null +++ b/skills/opencode-live-smoke/SKILL.md @@ -0,0 +1,62 @@ +--- +name: opencode-live-smoke +description: Run live OpenCode smoke tests for opencode-dynamic-subagents against a real project by temporarily switching the local OpenCode plugin source to either the local dist build or the published npm package, executing a subagent-read prompt, and restoring config afterward. +--- + +# OpenCode Live Smoke + +Use this skill when you need to verify the plugin in a real OpenCode run, not just unit tests. + +## When To Use + +- Before publishing a plugin change +- After publishing a new npm version +- When debugging real subagent behavior in OpenCode +- When checking whether a project path is treated as internal or external + +## Workflow + +1. Choose the target project root and a file inside that project. +2. Choose `local` before publish or `npm` after publish. +3. Run the helper script: + +```bash +skills/opencode-live-smoke/scripts/run-live-smoke.sh \ + --project /absolute/project/root \ + --file /absolute/project/root/path/to/file.tsx \ + --source local +``` + +For npm validation: + +```bash +skills/opencode-live-smoke/scripts/run-live-smoke.sh \ + --project /absolute/project/root \ + --file /absolute/project/root/path/to/file.tsx \ + --source npm +``` + +## What The Script Does + +- Validates the project root and target file +- Uses the local built entrypoint for `local`, or `opencode-dynamic-subagents@` for `npm` +- Temporarily rewrites `~/.config/opencode/opencode.jsonc` +- Runs: + +```bash +opencode run --print-logs --agent build "Use a subagent to read and return only the first non-empty line." +``` + +- Restores the original OpenCode config on exit + +## Expected Result + +- OpenCode loads the plugin successfully +- A subagent task is launched +- The response returns the first non-empty line from the target file + +## Notes + +- Prefer a file inside the exact project root passed to `--project` +- `local` mode runs `npm run build` in this repo before testing +- If the run hangs or prompts unexpectedly, inspect the emitted logs before retrying diff --git a/skills/opencode-live-smoke/scripts/run-live-smoke.sh b/skills/opencode-live-smoke/scripts/run-live-smoke.sh new file mode 100755 index 0000000..f43dd01 --- /dev/null +++ b/skills/opencode-live-smoke/scripts/run-live-smoke.sh @@ -0,0 +1,122 @@ +#!/bin/sh +set -eu + +usage() { + cat <<'EOF' +Usage: + run-live-smoke.sh --project /abs/project/root --file /abs/file/in/project --source local|npm +EOF +} + +PROJECT="" +TARGET_FILE="" +SOURCE="" + +while [ "$#" -gt 0 ]; do + case "$1" in + --project) + PROJECT="${2:-}" + shift 2 + ;; + --file) + TARGET_FILE="${2:-}" + shift 2 + ;; + --source) + SOURCE="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [ -z "$PROJECT" ] || [ -z "$TARGET_FILE" ] || [ -z "$SOURCE" ]; then + usage >&2 + exit 1 +fi + +case "$SOURCE" in + local|npm) ;; + *) + echo "--source must be 'local' or 'npm'" >&2 + exit 1 + ;; +esac + +if ! command -v opencode >/dev/null 2>&1; then + echo "opencode is not installed or not on PATH" >&2 + exit 1 +fi + +SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +SKILL_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)" +REPO_ROOT="$(CDPATH= cd -- "$SKILL_DIR/../.." && pwd)" +CONFIG_PATH="$HOME/.config/opencode/opencode.jsonc" + +if [ ! -d "$PROJECT" ]; then + echo "Project root not found: $PROJECT" >&2 + exit 1 +fi + +if [ ! -f "$TARGET_FILE" ]; then + echo "Target file not found: $TARGET_FILE" >&2 + exit 1 +fi + +case "$TARGET_FILE" in + "$PROJECT"/*) ;; + *) + echo "Target file must live under the provided project root" >&2 + exit 1 + ;; +esac + +if [ ! -f "$CONFIG_PATH" ]; then + echo "OpenCode config not found: $CONFIG_PATH" >&2 + exit 1 +fi + +BACKUP_PATH="$(mktemp "${TMPDIR:-/tmp}/opencode.jsonc.XXXXXX")" +cp "$CONFIG_PATH" "$BACKUP_PATH" +cleanup() { + cp "$BACKUP_PATH" "$CONFIG_PATH" + rm -f "$BACKUP_PATH" +} +trap cleanup EXIT INT TERM + +if [ "$SOURCE" = "local" ]; then + (cd "$REPO_ROOT" && npm run build >/dev/null) + PLUGIN_SPEC="$REPO_ROOT/dist/entrypoint.js" +else + VERSION="$(node -p "require('$REPO_ROOT/package.json').version")" + PLUGIN_SPEC="opencode-dynamic-subagents@$VERSION" +fi + +node <<'EOF' "$CONFIG_PATH" "$PLUGIN_SPEC" +const fs = require("node:fs") + +const [configPath, pluginSpec] = process.argv.slice(1) +const text = fs.readFileSync(configPath, "utf8") +const next = text.replace( + /"(opencode-dynamic-subagents@[^"]+|\/Users\/cgas\/Documents\/Projects\/OpenCodePlugins\/opencode-dynamic-subagents\/dist\/entrypoint\.js)"/g, + JSON.stringify(pluginSpec), +) + +if (next === text) { + throw new Error("Could not find an opencode-dynamic-subagents plugin entry in opencode.jsonc") +} + +fs.writeFileSync(configPath, next) +EOF + +PROMPT="Use a subagent to read $TARGET_FILE and return only the first non-empty line." +cd "$PROJECT" +opencode run --print-logs --agent build "$PROMPT"