Files
Auree Aubert 55d2eaf660 Ship the OpenAI package without the surfaces its review rejected (#120)
* Hold the sending skill out of the OpenAI package

The directory review rejected two skills under "Spam mass abuse".

cargo-mailbox-management is a true positive on category: it provisions
sending mailboxes and runs a 5-to-40/day warm-up ramp, which is the shape
of cold-email infrastructure whatever the gates around it. So the package
now ships without it — EXCLUDED_SKILLS — and says so by shipping eighteen
skills rather than by rewording the nineteenth. The alternative, deleting
the refusal lists and ramp documentation until the keywords stop matching,
would ship a less guarded skill in order to pass a safety review.

cargo-gtm looks like a false positive on negation: its description ended
"bulk unsolicited messaging, purchased or scraped lists, and consumer
targeting are refused", which hands a keyword classifier four spam nouns
in one clause. The packaged description now states the same policy as what
the skill does — documented lawful basis, suppression screening,
per-recipient relevance, sends nothing itself — and keeps every routing
trigger. That did not clear review — gtm was rejected again with the new
card live, so the trigger is the skill body or the capability itself, not
the description. The reframed card is kept because it is the more accurate
of the two, not because it fixed anything.

Excluding a skill is not just skipping a directory. Fifteen of the
remaining skills link to it: a router table row, an ASCII diagram box, a
recap section, the domain list, defineMailbox/defineDomain in cargo-cdk,
a routing row in cargo-mcp, three passages in cargo-gtm's acceptable-use.
PACKAGE_EDITS removes each one in the staged copy, and every find must
match exactly once or the build fails — they are anchored to prose that
will drift, and a silent no-op would ship a dangling link. Skill counts
renumber from the real packaged total, so the router reads "18 skills ...
fifteen capability skills" without anyone maintaining the number.

Backstopping all of that, zipgrep asserts after zipping that no packaged
file names an excluded skill anywhere. That check does not care whether
the edits above were written correctly.

Every edit applies to the staging copy under dist/. The repo files are
read-only inputs, so Claude Code, Cursor, skills.sh and Hermes still
install all nineteen skills with every mailbox reference intact.

Also fixes the packaged manifest description, which claimed 17 skills —
stale since two were added. It derives from the packaged count now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

* Narrow cargo-gtm's person-level surface for the OpenAI package

Second rejection, with the reframed description already live in the
listing — so the card was never the trigger. Reading the skill body as a
policy reviewer would, five capabilities carry most of the risk, and on
those the reviewer is not wrong:

  snitcher              de-anonymising website visitors
  aiArk.analyzePersonality  OCEAN/DISC inference from a profile
  forager               personal email and personal phone from a URL
  linkedin.extractEventAttendees   harvesting a platform's attendee list
  x                     reading a social platform's posts and profiles

"We gate it" is a weak answer to visitor de-anonymisation and personality
inference. So the packaged skill no longer offers them: three playbooks
leave the tree, and the actions are removed from the stack table, the
cost table, the stage map, the alternatives table, the finding guide's
decision tree, two recipes, the aiArk playbook and the router's glossary.
Thirty-four edits, each asserted to match exactly once.

What remains is the same skill minus a person-level edge: company
research, work-email enrichment from licensed providers, verification,
scoring, signal monitoring, CRM sync. That is a real narrowing of what
the package can do rather than a rewording of what it says it does, which
is also what makes the appeal behind it checkable.

REMOVED_TERMS asserts against the built archive that nothing survived,
scoped by path — extractEventAttendees stays in cargo-connection, which
documents the catalog rather than recommending the action, and which the
directory already approved.

Metadata is regenerated with the repo's own skills-metadata.mjs over the
staged tree: three files left and a dozen changed, so every packaged
skill-metadata.json was stale by construction, content hash included.

Repo copies are untouched, as before. Every other channel still ships all
nineteen skills with the full provider stack.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

* Give the packaged quickstart a company-level demo

Third rejection, same category: the quickstart's headline pull is 25 named
people with titles and a "recently hired" flag. Sixteen skills that never
touch a person passed on the first try; the three that were rejected are
the three that do.

Rather than drop the onboarding skill, the package now ships a variant
that runs the same two-minute arc against company-level hiring intent —
theirStack.searchCompanies on the persona's role, postings inside 30 days.
This is the skill's own fallback rung 2, which it already describes as
"same wow, different angle". The receipt is unchanged at ~0.5 credits,
because searchCompanies bills per call rather than per row, so `limit` is
free to set. The demo names no individuals.

It ships as a whole file rather than as edits. The spine is rewritten —
action, filter, output fields, headline, fallback ladder, next steps — and
a dozen brittle anchors through a rewritten section would fail on the next
upstream wording change with nothing useful to say about it.

The copy is tied to the repo skill's frontmatter version instead. Bump
cargo-quickstart and this build fails, naming both versions and asking for
the port. That is the maintenance cost of an override, made loud on
purpose.

Step 3 reads the first row's keys and renders from them rather than
hardcoding a shape: the payload of this action is not something I have run,
and a demo that dies on line 3 is worse than one that spends a second
looking. Worth one 0.5-credit run against a live workspace before the
package is resubmitted.

Repo copies untouched, as with the two commits before this. Every other
channel still ships the leads demo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

* Rebuild the quickstart demo on aiArk, against a live run

theirStack was the wrong pick and the reason is not taste: it needs its
own API-key connector, so on the fresh workspace a quickstart exists for
it dead-ends on rung 1. It got chosen because the fallback ladder named
it, not because anything checked it.

aiArk.searchCompanies is better on every axis that matters here. It runs
on cargo's managed connection, so a workspace with nothing wired up can
still complete the demo. At 0.01 per returned record, 25 rows cost 0.25
credits — half what the leads demo cost. And it answers the persona
question with accounts rather than people.

The demo also opens on countCompanies, which is fixed-cost 0. Sizing the
whole addressable market for free and then sampling it for a quarter of a
credit is a better first beat than 25 rows with no denominator, and it
puts the pack's own count-first rule in the first thing a new user sees.

Everything here is verified against a real run rather than a playbook:

  countCompanies      free, returns {"count": N} — 1,904 on the example
  searchCompanies     25 rows for 0.25 credits
  employeeRole        employee_title_or / employee_seniority_or /
                      employee_department_or
  industry            industry_or takes plain lowercase strings
                      ("software development") — no autocomplete needed,
                      and it halved the pool to 840
  row fields          name, industry, employee_count, city, country,
                      domain, linkedin_url, revenue, founded_year,
                      technologies

So step 4 renders named columns now instead of printing the keys and
hoping. theirStack keeps a place as rung 3, where its own-key connector
is a known cost rather than a surprise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

* Remove only the mailbox bullet from the CDK critical rules

The drop_block helper takes the blank-line-delimited block around a
marker, which is right for a standalone diagram and wrong for a markdown
bullet list: consecutive bullets have no blank line between them, so the
whole list is one block. The cargo-cdk edit anchored on the defineMailbox
bullet and took all of "## 6) Critical rules" with it — 71 lines instead
of 12. The packaged CDK skill shipped an empty heading, losing the
state-file, secrets, handle-wiring, --yes, paid-play sample and alert
cost-gate rules.

Caught by Bugbot on #120, not by the build: every assertion here checks
that a find matched exactly once and that removed things left no trace.
Nothing checked that what remained was still there.

A repo-vs-package line-count diff over the whole archive now reconciles:
every SKILL.md is -12 for the dropped metadata block, and each further
delta matches a specific edit. cargo-cdk is the only file that was wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

* Document aiArk's three undocumented actions

Bugbot flagged the quickstart override for calling aiArk.countCompanies,
an action absent from the packaged playbook. The action is real — the demo
was verified against a live run — so the gap is in the documentation, and
it is a repo gap rather than a packaging one.

The playbook described six actions and asserted "all six are credits-based".
The integration exposes nine:

  enrichCompany    0.01   the cheapest company enrich in the catalog
  countCompanies   free   {"count": N} for a searchCompanies filter
  countPeople      free   same, for searchPeople

The count pair is the more useful omission. Cost discipline tells an agent
to size a pool before paying per record, and on this provider that check
costs nothing — but only if the agent knows the actions exist. Added
alongside the per-record billing warning, where the question actually comes
up.

Also corrects the override's claim that industry_or takes ordinary
lowercase strings, which contradicted the same playbook two files away.
Both are true and neither said so: the enum members are LinkedIn-style
lowercase names, identical to the strings each row returns in its own
industry field, so a value copied off a result matches while a guess
empties the pool. The override now says that, and points at listIndustries
for anything less obvious.

Repo change, so every channel gets it, not just the OpenAI package.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

* Remove the personality pitch, not just its slug

REMOVED_TERMS asserted on "analyzePersonality" and passed, while the
packaged playbook went on selling the capability in prose that named no
action: an intro pitching "a personality-analysis action nothing else in
the catalog has", and a reason-to-reach-for-it clause offering
"personality/selling guidance". A slug-only check waved both through.

The counts were wrong too. The repo playbook says nine actions, seven
credits-based; the package removes one, so it ships eight and six. Now
stated as such, and the packaged action table has exactly eight rows to
match.

The assertion now looks for "personality" itself. It cannot collide with
"personalization", which is a different word, and the sweep it flagged on
the first run is what found the second clause — the fix caught its own
gap before the archive shipped.

Also adds .github/package-overrides/** to the Codex package workflow's
path filter. The package depends on that file now, so a change touching
only the override needs to run the build and the version guard, and did
not.

Both found by Bugbot on #120.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

* Fetch the count, drop the attendee pitch, fail a broken sweep

Three from Bugbot on #120, all correct.

The quickstart's headline number was never fetched. Step 1 wrote the
countCompanies run object to a file and step 4 told the agent to lead with
the pool size, but the run object holds no results — the same skill says so
two paragraphs earlier — and nothing ever called download-outputs for that
run. The demo would have opened on an empty variable. Both steps now go
through one fetch_output helper, so the retrieval that was already correct
for the search is correct for the count too, and the headline reads $POOL.

The linkedin playbook still pitched event-attendee harvesting in prose
after the action row was removed: an engagement-sourcing bullet and the
per-item billing note. Same failure as the personality pitch — the slug
left, the capability stayed. The new assertion is scoped to that one file
on purpose: "event attendees" appears four more times in cargo-gtm as a
lawful BASIS for contacting someone, and that must stay exactly where it is.

And the sweeps themselves could not fail. Every throw was read as "nothing
matched", so a missing binary or an unreadable archive reported a clean
package. grep-family exit codes distinguish these: 1 is no match, anything
else is an error. Verified by putting a zipgrep that exits 2 on PATH — the
build now stops with "could not verify ... zipgrep exited 2" where it
previously passed.

That last one is the pattern worth naming: three of the five findings on
this PR were assertions that were true and conclusions that were wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YL6YdzRFUf27A2WChKxr1C

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 13:26:47 -07:00

86 lines
3.0 KiB
YAML

name: Codex package
# The OpenAI Plugins Directory has no submission API — the portal is the only
# path and every version is human-reviewed, so the upload itself stays manual.
# Everything before it does not have to be:
#
# - on a release: build the archive and attach it, so publishing is
# "download the asset, drop it in the portal" rather than "find whoever
# built the zip last time".
# - on a PR touching skills or the builder: run the same build, so a change
# that would fail OpenAI's validator fails here instead — months before
# someone spends a review cycle discovering it.
on:
release:
types: [published]
pull_request:
paths:
- "*/SKILL.md"
- "assets/**"
- ".claude-plugin/plugin.json"
- ".github/scripts/build-codex-package.mjs"
- ".github/package-overrides/**"
- ".github/workflows/codex-package.yml"
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "20"
# The builder measures icons with `sips`, which is macOS-only. Everything
# else is portable, so shim it with ImageMagick on the runner rather than
# paying for a macOS runner to check two PNGs.
- name: Provide a sips shim
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq imagemagick
sudo tee /usr/local/bin/sips >/dev/null <<'SHIM'
#!/usr/bin/env bash
# Minimal stand-in for macOS sips: only -g pixelWidth/pixelHeight.
file="${@: -1}"
read -r w h < <(identify -format "%w %h" "$file[0]" 2>/dev/null)
[ -z "$w" ] && exit 1
echo "$file"
echo " pixelWidth: $w"
echo " pixelHeight: $h"
SHIM
sudo chmod +x /usr/local/bin/sips
- name: Build and validate the package
run: node .github/scripts/build-codex-package.mjs
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cargo-skills-codex
path: dist/cargo-skills-codex.zip
if-no-files-found: error
attach:
needs: build
if: github.event_name == 'release'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: cargo-skills-codex
- name: Attach to the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ github.event.release.tag_name }}" \
cargo-skills-codex.zip --clobber \
--repo "${{ github.repository }}"
echo "::notice::Attached to ${{ github.event.release.tag_name }}. Upload it at https://platform.openai.com/plugins — the directory has no API."