From b1c15fd9f825be54c10da655245ec7dd67f88575 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Mon, 27 Apr 2026 14:23:54 -0700 Subject: [PATCH] Preserve Codex marketplace metadata --- .codex-plugin/plugin.json | 3 ++ scripts/sync-to-codex-plugin.sh | 40 +++++++++++++++-- .../test-sync-to-codex-plugin.sh | 44 +++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index d4f3f7a3..6583feac 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -36,6 +36,9 @@ "I've got an idea for something I'd like to build.", "Let's add a feature to this project." ], + "websiteURL": "https://github.com/obra/superpowers", + "privacyPolicyURL": "https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement", + "termsOfServiceURL": "https://docs.github.com/en/site-policy/github-terms/github-terms-of-service", "brandColor": "#F59E0B", "composerIcon": "./assets/superpowers-small.svg", "logo": "./assets/app-icon.png", diff --git a/scripts/sync-to-codex-plugin.sh b/scripts/sync-to-codex-plugin.sh index 16fd89ae..fc0a8e85 100755 --- a/scripts/sync-to-codex-plugin.sh +++ b/scripts/sync-to-codex-plugin.sh @@ -4,7 +4,8 @@ # # Sync this superpowers checkout → prime-radiant-inc/openai-codex-plugins. # Clones the fork fresh into a temp dir, rsyncs tracked upstream plugin content -# (including committed Codex files under .codex-plugin/ and assets/), commits, +# (including committed Codex files under .codex-plugin/ and assets/), preserves +# OpenAI-owned marketplace metadata already in the destination plugin, commits, # pushes a sync branch, and opens a PR. # Path/user agnostic — auto-detects upstream from script location. # @@ -223,6 +224,7 @@ fi DEST="$DEST_REPO/$DEST_REL" PREVIEW_REPO="$DEST_REPO" PREVIEW_DEST="$DEST" +SYNC_SOURCE="" overlay_destination_paths() { local repo="$1" @@ -291,7 +293,7 @@ apply_to_preview_checkout() { mkdir -p "$PREVIEW_DEST" fi - rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$PREVIEW_DEST/" + rsync "${RSYNC_ARGS[@]}" "$SYNC_SOURCE/" "$PREVIEW_DEST/" } preview_checkout_has_changes() { @@ -316,6 +318,36 @@ for pat in "${EXCLUDES[@]}"; do RSYNC_ARGS+=(--exclude="$pat"); done append_git_ignored_directory_excludes append_git_ignored_file_excludes +copy_preserved_destination_metadata() { + local destination="$1" + local source="$2" + local path + local rel + + [[ -d "$destination/skills" ]] || return 0 + + while IFS= read -r -d '' path; do + rel="${path#"$destination"/}" + mkdir -p "$source/$(dirname "$rel")" + cp -p "$path" "$source/$rel" + done < <(find "$destination/skills" -path '*/agents/openai.yaml' -type f -print0) +} + +prepare_sync_source() { + local destination="$1" + + [[ -n "$CLEANUP_DIR" ]] || CLEANUP_DIR="$(mktemp -d)" + + SYNC_SOURCE="$CLEANUP_DIR/source-overlay" + rm -rf "$SYNC_SOURCE" + mkdir -p "$SYNC_SOURCE" + + rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$SYNC_SOURCE/" >/dev/null + copy_preserved_destination_metadata "$destination" "$SYNC_SOURCE" +} + +prepare_sync_source "$PREVIEW_DEST" + # ============================================================================= # Dry run preview (always shown) # ============================================================================= @@ -331,7 +363,7 @@ if [[ $BOOTSTRAP -eq 1 ]]; then fi echo "" echo "=== Preview (rsync --dry-run) ===" -rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$UPSTREAM/" "$PREVIEW_DEST/" +rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$SYNC_SOURCE/" "$PREVIEW_DEST/" echo "=== End preview ===" echo "" @@ -368,7 +400,7 @@ echo "Syncing upstream content..." if [[ $BOOTSTRAP -eq 1 ]]; then mkdir -p "$DEST" fi -rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$DEST/" +rsync "${RSYNC_ARGS[@]}" "$SYNC_SOURCE/" "$DEST/" # Bail early if nothing actually changed cd "$DEST_REPO" diff --git a/tests/codex-plugin-sync/test-sync-to-codex-plugin.sh b/tests/codex-plugin-sync/test-sync-to-codex-plugin.sh index a8fc245b..441230e1 100755 --- a/tests/codex-plugin-sync/test-sync-to-codex-plugin.sh +++ b/tests/codex-plugin-sync/test-sync-to-codex-plugin.sh @@ -73,6 +73,19 @@ assert_matches() { fi } +assert_not_matches() { + local haystack="$1" + local pattern="$2" + local description="$3" + + if printf '%s' "$haystack" | grep -Eq -- "$pattern"; then + fail "$description" + echo " did not expect to match: $pattern" + else + pass "$description" + fi +} + assert_path_absent() { local path="$1" local description="$2" @@ -244,6 +257,22 @@ EOF commit_fixture "$repo" "Initial destination fixture" } +add_openai_agent_metadata_fixture() { + local repo="$1" + + mkdir -p "$repo/plugins/superpowers/skills/example/agents" + + cat > "$repo/plugins/superpowers/skills/example/agents/openai.yaml" <<'EOF' +interface: + display_name: "Example" + short_description: "Destination-owned OpenAI metadata" +EOF + + git -C "$repo" add plugins/superpowers/skills/example/agents/openai.yaml + + commit_fixture "$repo" "Add OpenAI agent metadata fixture" +} + dirty_tracked_destination_skill() { local repo="$1" @@ -261,6 +290,7 @@ write_synced_destination_fixture() { "$repo/plugins/superpowers/.codex-plugin" \ "$repo/plugins/superpowers/.private-journal" \ "$repo/plugins/superpowers/assets" \ + "$repo/plugins/superpowers/skills/example/agents" \ "$repo/plugins/superpowers/skills/example" cat > "$repo/plugins/superpowers/.codex-plugin/plugin.json" < "$repo/plugins/superpowers/skills/example/agents/openai.yaml" <<'EOF' +interface: + display_name: "Example" + short_description: "Destination-owned OpenAI metadata" +EOF + printf 'tracked keep\n' > "$repo/plugins/superpowers/.private-journal/keep.txt" git -C "$repo" add \ plugins/superpowers/.codex-plugin/plugin.json \ plugins/superpowers/assets/app-icon.png \ plugins/superpowers/assets/superpowers-small.svg \ + plugins/superpowers/skills/example/agents/openai.yaml \ plugins/superpowers/skills/example/SKILL.md \ plugins/superpowers/.private-journal/keep.txt @@ -415,6 +452,7 @@ main() { local help_output local script_source local dirty_skill_path + local noop_openai_metadata_path echo "=== Test: sync-to-codex-plugin dry-run regression ===" @@ -443,6 +481,7 @@ main() { init_repo "$dest" write_destination_fixture "$dest" + add_openai_agent_metadata_fixture "$dest" checkout_fixture_branch "$dest" "$dest_branch" dirty_tracked_destination_skill "$dest" @@ -490,6 +529,7 @@ main() { preview_section="$(printf '%s\n' "$preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')" stale_preview_section="$(printf '%s\n' "$stale_preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')" dirty_skill_path="$dirty_apply_dest/plugins/superpowers/skills/example/SKILL.md" + noop_openai_metadata_path="$noop_apply_dest/plugins/superpowers/skills/example/agents/openai.yaml" echo "" echo "Preview assertions..." @@ -505,6 +545,7 @@ main() { assert_not_contains "$preview_output" "Overlay file (.codex-plugin/plugin.json) will be regenerated" "Preview omits overlay regeneration note" assert_not_contains "$preview_output" "Assets (superpowers-small.svg, app-icon.png) will be seeded from" "Preview omits assets seeding note" assert_contains "$preview_section" "skills/example/SKILL.md" "Preview reflects dirty tracked destination file" + assert_not_matches "$preview_section" "\\*deleting +skills/example/agents/openai\\.yaml" "Preview preserves destination-owned OpenAI agent metadata" assert_current_branch "$dest" "$dest_branch" "Preview leaves destination checkout on its original branch" assert_branch_absent "$dest" "sync/superpowers-*" "Preview does not create sync branch in destination checkout" @@ -542,6 +583,9 @@ Locally modified fixture content." "Dirty local apply preserves tracked working- assert_contains "$noop_apply_output" "No changes — embedded plugin was already in sync with upstream" "Clean no-op local apply reports no changes" assert_current_branch "$noop_apply_dest" "$noop_apply_dest_branch" "Clean no-op local apply leaves destination checkout on its original branch" assert_branch_absent "$noop_apply_dest" "sync/superpowers-*" "Clean no-op local apply does not create sync branch in destination checkout" + assert_file_equals "$noop_openai_metadata_path" "interface: + display_name: \"Example\" + short_description: \"Destination-owned OpenAI metadata\"" "Clean no-op local apply preserves OpenAI agent metadata" echo "" echo "Missing manifest assertions..."