Visual Stack is a Claude Code and Codex plugin: skills, prompts, and the HTML workspaces they open. A change here changes what runs on an installer's machine, so scope and permissions matter more than line count.
- Work on a short-lived branch off
mainand open a PR.mainrequires one. - Keep a skill's footprint inside the user's project. Nothing writes outside it, and nothing transmits anywhere, unless that is the skill's stated purpose.
- Per-machine state belongs under
.vstack/local/<tool>/, which is gitignored whole — never in the repo, and never elsewhere in.vstack/, which is the pipeline. Resolve the path withlib/workdir.mjsrather than joining it by hand. - Workspaces are self-contained HTML. No external requests at runtime — inline what a page needs.
- If you add or rename a plugin, update
.claude-plugin/marketplace.jsonin the same commit.
Install the plugin from your branch and drive the skill end to end in a real project. A skill that has only been read is untested. For Codex changes, validate the plugin manifest and review skill, then run node plugins/vstack/skills/review/tests/host-profiles.mjs.
CI runs on every PR and repeats what you can run locally:
node plugins/vstack/skills/review/tests/review-lifecycle.mjs
node plugins/vstack/skills/review/tests/host-profiles.mjs
node plugins/vstack/skills/review/tests/workdir.mjs
node plugins/vstack/skills/review/tests/round-gate.mjs
node plugins/vstack/lib/build-shell.mjs check
claude plugin validate . --strict
claude plugin validate ./plugins/vstack --strictThe Gherkin suite in e2e/ drives the review server and CLI end to end with a mock agent in the agent's seat, under both hosts:
cd e2e && npm ci
npx playwright install chromium
npx cucumber-jsIt exercises the protocol, not a model, so a green build is still not a skill you have driven yourself.
CI also runs the scans listed in SECURITY.md, and a merge is blocked until all of them pass. Two of them you can run before you push:
gitleaks git --no-banner --redact --verbose # every commit, not the working tree
uvx zizmor@1.29.0 .github/workflows/ # only if you touched a workflowCodeQL and SonarQube Cloud report in the pull request itself. Read the finding before assuming it is noise — the servers read from disk and the pages build DOM from stored comments, which is exactly where a real one would appear.
When you add a step that uses an action, pin it by commit SHA and put the version in a trailing comment, the way the existing steps do. Copy the SHA from the release you intend to use:
gh api repos/<owner>/<action>/commits/<tag> --jq .shaCI rehearses the install a user performs. Run the same thing locally when you touch a manifest, with CLAUDE_CONFIG_DIR pointed at a throwaway directory so the local-path marketplace is not written to your real settings, where it would shadow the published cavalry-collective:
SANDBOX=$(mktemp -d)
CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin marketplace add ./
CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin install vstack@cavalry-collective
CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin details vstack
rm -rf $SANDBOXdetails prints the name, version, description and skill inventory a user sees. The source must be ./ and not ..
There is nothing to cut. Merging to main is the release, because this repository is what a user installs — the code is live for everyone as soon as it lands.
So the version travels with the change rather than following it. A pull request that touches plugins/ must also carry:
versionraised to the same value inplugins/vstack/.claude-plugin/plugin.jsonandplugins/vstack/.codex-plugin/plugin.json.- The matching entry in
CHANGELOG.md, newest first. It is published verbatim as the release notes, so write it for a user rather than for a reviewer.
CI fails the PR when either is missing. Nothing downstream can catch it: a merge that leaves the version alone publishes your code and tells nobody, because a host decides an update exists by comparing the version it installed against the one main declares. The only repair is a second release.
Version to semantic versioning: MAJOR for a breaking change to a skill name, an on-disk path, or a protocol; MINOR for new behaviour; PATCH for a fix.
Orphaning a user's in-flight state is a MAJOR change, and it needs a LEGACY entry in lib/workdir.mjs rather than a migration.
Once it merges, .github/workflows/release.yml tags main as vX.Y.Z and publishes the GitHub release with your changelog entry as its notes. Do not tag by hand and do not write a release by hand. A merge that changed no version publishes nothing, which is what a docs or CI change should do.
- A bug or an unclear skill: open an issue.
- Anything you would rather not post publicly: email adam@cavalry.sg.
- A security concern: follow
SECURITY.mdinstead.