Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,6 @@ setups.

## License

This repo does not yet have a finalized license (see the root
[README](./README.md#license)). Until one is added, treat any contribution
discussion as provisional - the license that ultimately covers this project will
also cover contributions made before it's added, and will be called out clearly
when it lands.
This repo is MIT licensed (see [LICENSE](./LICENSE)). By submitting a
contribution, you agree it is provided under the same MIT license as the rest
of the project.
27 changes: 16 additions & 11 deletions DISTILLING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@ Work through every file that's new or changed since the last distillation. For
each one:

**Strip entirely - these should never appear in this repo:**
- Internal Rails/app file paths and method names (`app/services/...`,
- Internal Rails/app file paths and method names (`app/<internal-paths>/...`,
`client.rb#some_method`, specific class/service names from the private repo's
codebase).
- Internal hostnames and URLs (`demo.butterstack.com`, `staging.butterstack.com`,
any `*.butterstack.com` subdomain that isn't the public marketing site,
internal admin panels).
- Internal hostnames and URLs (`<env>.example.com`-shaped internal subdomains,
any subdomain of the private repo's production domain that isn't the public
marketing site, internal admin panels).
- Docker container/service names tied to the private repo's compose setup
(`butter_stack-jenkins-1` and similar).
- Personal names (Kevin, Ryan, or any other individual) - reword to "the team",
"we", or drop the attribution entirely. First-person plural ("we found",
"we recommend") reads fine and is honest without naming anyone.
(`<project>-jenkins-1`-shaped names and similar).
- Personal names (individual names used internally, or any other individual) -
reword to "the team", "we", or drop the attribution entirely. First-person
plural ("we found", "we recommend") reads fine and is honest without naming
anyone.
- Internal tracking references: GitHub issue numbers, branch names
(`claude/issue-36-...`), PR numbers, internal doc filenames.
- Private repo URLs, including private forks (e.g. an org-internal fork of a
Expand All @@ -58,7 +59,7 @@ each one:
what make a playbook copy-pasteable and are never sensitive.

**Genericize - keep the substance, drop the specific internal-ish identifier:**
- Personal or team machine/rig names (`beast`, `wanda`, `beast-wsl`) → "the
- Personal or team machine/rig names (`rig-01`, `rig-02`, `workstation-b`) → "the
primary test machine," "a second machine on hand."
- An internal environment name used as a stand-in for "a real server" (e.g. "the
demo Perforce server") → "our own internal Perforce server" or similar - the
Expand All @@ -72,9 +73,13 @@ each one:
to disclose, genericize it; if it's just a label for "our second test game,"
it's fine.

Run a final grep sweep before considering a file done:
Run a final grep sweep before considering a file done. The pattern below is a
shape, not the real values - swap in the actual internal names, project
prefix, and production subdomains from the private repo before running it
(deliberately not spelled out here so this document doesn't itself become a
list of live internal identifiers):
```
grep -rniE 'Kevin|Ryan|butter_stack-|app/(services|clients|controllers)|demo\.butterstack|staging\.butterstack|issue #[0-9]|branch `claude/' <changed files>
grep -rniE '<individual names>|<project>-|app/(services|clients|controllers)|<env>\.<production-domain>|issue #[0-9]|branch `claude/' <changed files>
```
Zero hits, or every hit explained, is the bar.

Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 ButterStack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,20 @@ a standing basis - see [DISTILLING.md](./DISTILLING.md) for the process.

## License

**Not yet licensed for reuse.** This repo does not currently include a LICENSE file,
so all rights are reserved by default - treat it as read-only reference, not
licensed open-source code. A license decision (most likely MIT, matching
ButterStack's stated intent for this series) is still pending and will be made
before this repo is ever made public.
MIT. See [LICENSE](./LICENSE).

## Trademarks

Perforce, Helix Core, and P4 are trademarks or registered trademarks of Perforce
Software, Inc. Unreal Engine, Epic Games, and Lore are trademarks or registered
trademarks of Epic Games, Inc. Unity is a trademark or registered trademark of
Unity Technologies. Jenkins is a trademark of the Jenkins project (a
Continuous Delivery Foundation project).

ButterStack is not affiliated with, endorsed by, or sponsored by Perforce
Software, Epic Games, Unity Technologies, or the Jenkins project. These marks
are used only to identify the third-party tools these agents observe and
operate; naming a tool is not a claim of partnership or endorsement.

---

Expand Down
39 changes: 39 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Security Policy

## Reporting a vulnerability

If you find a security issue in one of these agents, skills, or guard hooks -
including a way to bypass a guard hook's "hard block," a prompt-injection
path, or anything else with a real security impact - please report it
privately rather than opening a public issue.

Email **hello@butterstack.com** with a description of the issue, the affected
plugin, and reproduction steps if you have them. We will acknowledge reports
within a few business days and keep you updated as we work through a fix.

Please do not open a public GitHub issue for a security report until we've
had a chance to address it.

## Scope

This repo ships Claude Code plugins: agent definitions, skills, slash
commands, and PreToolUse guard hooks (`*/scripts/guard-*.sh`) for Perforce,
Unreal Engine, Unity, Epic's Lore VCS, and Jenkins integrations. In scope:

- A guard hook that fails to block a command it documents as blocked.
- A prompt-injection or command-injection path in any agent, skill, or hook.
- Anything that could exfiltrate credentials or tokens from the environment
the agent runs in.

Out of scope: vulnerabilities in Perforce, Unreal Engine, Unity, Lore, or
Jenkins themselves - report those to the respective vendor.

## A note on the guard hooks

The guard hooks in this repo are shell-string matchers, not sandboxes. They
are a backstop against common invocation forms of catastrophic operations,
not a guarantee against every possible way to construct an equivalent
command. Each plugin's README says plainly what the actual safety boundary
is (the underlying account's own permissions, source control, or both) - the
hooks reduce the blast radius of an obvious mistake, they do not replace
least-privileged credentials.
1 change: 1 addition & 0 deletions jenkins/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"displayName": "Jenkins Agent",
"description": "Observe, operate, and safely diagnose a Jenkins CI/CD server. A read-first, gamedev-focused Claude Code agent that pairs with the Perforce agent's change-commit build loop.",
"version": "0.1.0",
"license": "MIT",
"author": {
"name": "ButterStack"
},
Expand Down
4 changes: 3 additions & 1 deletion jenkins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ Jenkins admin/`ADMINISTER`-holding account.
(`/script`, `/scriptText`, CLI `groovy`/`groovysh`) regardless of how your Claude
Code permissions are configured - but a least-privileged Jenkins account that
simply *can't* reach the Script Console (no `ADMINISTER`/`RUN_SCRIPTS` permission)
is the guardrail that actually matters.
is the guardrail that actually matters. It is a shell-string matcher, not a full
shell-grammar parser, so treat it as a backstop against the common mistake, not
a guarantee against every way to construct the same request.

## Quickstart

Expand Down
1 change: 1 addition & 0 deletions lore/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"displayName": "Lore Agent (Epic Games VCS)",
"description": "Observe, analyze, and safely operate an Epic Games Lore version control system (the `lore` CLI - formerly Unreal Revision Control). A read-first, gamedev-focused Claude Code agent with observability + operations skills and slash commands.",
"version": "0.1.0",
"license": "MIT",
"author": {
"name": "ButterStack"
},
Expand Down
4 changes: 3 additions & 1 deletion lore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ commit identity (`lore ... --identity you@example.com`, or `identity` in
**The real safety boundary is your Lore account and server permissions**, not Claude
Code's. As a backstop, this plugin ships a `guard-lore` hook that hard-blocks
`lore file obliterate` (irreversible fragment destruction) and `lore repository
delete` regardless of how your permissions are set.
delete` regardless of how your permissions are set. It is a shell-string matcher,
not a full shell-grammar parser - a backstop against the common mistake, not a
guarantee against every way to construct the same request.

Optionally reduce prompts for read-only inspection by allowlisting the safe commands
in your **own** `~/.claude/settings.json` or project `.claude/settings.json` (plugins
Expand Down
1 change: 1 addition & 0 deletions perforce/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"displayName": "Perforce Agent (Helix Core)",
"description": "Observe, analyze, and safely operate a Perforce / Helix Core (p4/p4d) system. A read-first, gamedev-focused Claude Code agent with observability + operations skills and slash commands.",
"version": "0.2.0",
"license": "MIT",
"author": {
"name": "ButterStack"
},
Expand Down
5 changes: 4 additions & 1 deletion perforce/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ read-only Perforce user - never as `super`/admin.** Perforce protections, not Cl
Code permissions, are what actually stop a destructive command. As a backstop, this
plugin also ships a `guard-p4` hook that hard-blocks `p4 obliterate`, `p4 admin`,
`archive`/`restore`, `dbverify`, and other server-lifecycle commands regardless of
how your permissions are set.
how your permissions are set, including when a benign `p4` call precedes the
destructive one in the same command (`p4 info && p4 obliterate ...`). It is a
shell-string matcher, not a full shell-grammar parser - the p4 account's own
permissions remain the control that cannot be talked around.

Two settings are worth adding to your **own** `~/.claude/settings.json` or project
`.claude/settings.json` (plugins can't ship `env`/`permissions` for you):
Expand Down
44 changes: 26 additions & 18 deletions perforce/scripts/guard-p4.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,34 @@ else
fi
[ -n "$cmd" ] || exit 0

# Extract the p4 SUBCOMMAND (verb): the first non-flag token after a `p4`/`.../p4`
# token, skipping global flags and the values of value-taking flags. Matching the
# verb position (not "anywhere in the string") avoids false positives like a depot
# path //depot/archive/... being read as the `archive` verb.
verb="$(printf '%s' "$cmd" | awk '{
s=0
for (i=1;i<=NF;i++) { if ($i ~ /(^|\/)p4$/) { s=i; break } }
if (!s) exit
for (i=s+1;i<=NF;i++) {
t=$i
if (t ~ /^-/) { if (t ~ /^-(p|u|c|C|d|H|P|Q|r|v|x|z|F|L|G|I)$/) i++; continue }
print t; exit
# Extract the p4 SUBCOMMAND (verb): for EVERY `p4`/`.../p4` token in the command,
# the first non-flag token after it, skipping global flags and the values of
# value-taking flags. Emitting a verb per occurrence (not just the first) catches
# forms like `p4 info && p4 obliterate ...` or `p4 -ztag info; p4 obliterate ...`,
# where a benign p4 call precedes the destructive one. Matching the verb POSITION
# (not "anywhere in the string") avoids false positives like a depot path
# //depot/archive/... being read as the `archive` verb.
verbs="$(printf '%s' "$cmd" | awk '{
for (s=1;s<=NF;s++) {
if ($s !~ /(^|\/)p4$/) continue
for (i=s+1;i<=NF;i++) {
t=$i
if (t ~ /^-/) { if (t ~ /^-(p|u|c|C|d|H|P|Q|r|v|x|z|F|L|G|I)$/) i++; continue }
print t; break
}
}
}')"
[ -n "$verbs" ] || exit 0

case "$verb" in
obliterate|admin|dbverify|dbpack|journalcopy|journaldbchecksums|archive|restore|unload|ldapsync|storage|unsubmit|duplicate|retype)
echo "guard-p4: refusing '$verb' - server-lifecycle / archive / history-rewriting operations are out of scope for this agent. Refer to the Perforce administrator." >&2
exit 2
;;
esac
while IFS= read -r verb; do
case "$verb" in
obliterate|admin|dbverify|dbpack|journalcopy|journaldbchecksums|archive|restore|unload|ldapsync|storage|unsubmit|duplicate|retype)
echo "guard-p4: refusing '$verb' - server-lifecycle / archive / history-rewriting operations are out of scope for this agent. Refer to the Perforce administrator." >&2
exit 2
;;
esac
done <<EOF
$verbs
EOF

exit 0
1 change: 1 addition & 0 deletions unity/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"displayName": "Unity Agent",
"description": "Observe, diagnose, and safely operate a Unity project's CLI build pipeline via the standalone Unity CLI. A read-first, gamedev-focused Claude Code agent with a project/Editor-version doctor that routes the CLI surface on Editor version (2022 LTS = batchmode; Unity 6 adds the verified Pipeline live-Editor surface and MCP), gated unity build/test/run, live-Editor control that honors Unity's own confirm/dry_run gates, license-vs-auth diagnosis, and a project-tree guard hook.",
"version": "0.2.0",
"license": "MIT",
"author": {
"name": "ButterStack"
},
Expand Down
10 changes: 10 additions & 0 deletions unity/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ reference). **Allowed**: deleting regenerable build state - `Library/`,
`Temp/`, `Logs/`, `obj/`, `Builds/` (the agent still confirms a `Library/`
wipe because of the reimport cost, but the hook does not block it).

**Honest limit**: this is a shell-string matcher, not a sandbox. It catches
the common direct forms (`rm x.meta`, `rm -rf Assets/...`) and chained
variants, but a path that never puts the protected filename or directory
directly next to `rm`/`mv` - `find Assets -name '*.meta' | xargs rm`,
`git clean -xfd Assets` - is not something a shell-string matcher can
reliably see and is not caught. The hook is a backstop against the common
mistake, not a guarantee against every route to the same deletion; treat
source control (commit before destructive local operations) as the real
safety net.

Soft gates the agent owns: `--allow-install` (multi-GB silent download);
`--allow-dirty-build` (defeats the CLI's own uncommitted-changes guard);
`unity license activate`/`return` (seat); `unity editors upgrade` and any
Expand Down
18 changes: 15 additions & 3 deletions unity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,15 @@ curl -fsSL https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.sh | UNITY_
```

```powershell
# Windows (the docs' `| bash` line is wrong for PowerShell; this installer is verified)
$env:UNITY_CLI_CHANNEL="beta"; irm https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.ps1 | iex
# Windows (the docs' `| bash` line is wrong for PowerShell). install.ps1 is an
# undocumented CDN script Unity does not version - download and inspect it
# rather than piping straight into `iex`:
Invoke-WebRequest -Uri https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.ps1 -OutFile install.ps1
Get-FileHash install.ps1 -Algorithm SHA256
# last verified 2026-08-21: 3b5b42c066f04a43aaa587cfa50c5873e0148b80138a8befc610f7a7477b58e6
# a different hash later isn't itself a red flag - Unity can update this file
# without notice - but re-read the script before running it if it changes.
$env:UNITY_CLI_CHANNEL="beta"; .\install.ps1
```

plus a Unity Editor matching the project's
Expand All @@ -96,7 +103,12 @@ destructive filesystem commands (`rm`/`mv`/truncating redirects/
and any `*.meta` file (those belong to source control - and a deleted `.meta`
orphans its asset's GUID), plus any write into a Unity Editor install tree -
regardless of how your permissions are set. Regenerable build-state cleanup
(`Library/`, `Temp/`, `Logs/`, `obj/`, `Builds/`) passes through.
(`Library/`, `Temp/`, `Logs/`, `obj/`, `Builds/`) passes through. **The hook is
a shell-string matcher, not a sandbox** - it catches the common direct and
chained forms, but a route that never puts the protected path next to
`rm`/`mv` (`find ... | xargs rm`, `git clean -xfd`) is not something a
shell-string matcher can reliably see. Source control, not this hook, is the
real backstop against data loss.

## Quickstart

Expand Down
16 changes: 12 additions & 4 deletions unity/skills/unity-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,22 @@ Installs to `$UNITY_CLI_HOME` (default `~/.unity`), binary in `~/.unity/bin`.

Windows - the docs give the same `| bash` line, which is **wrong for
PowerShell**. There is an **undocumented `install.ps1`** on the same CDN path
that is the correct Windows installer (verified working):
that is the correct Windows installer (verified working). Because it is
undocumented and Unity does not version it, download and inspect it before
running rather than piping straight into `iex`:

```powershell
$env:UNITY_CLI_CHANNEL="beta"; irm https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.ps1 | iex
Invoke-WebRequest -Uri https://public-cdn.cloud.unity3d.com/hub/prod/cli/install.ps1 -OutFile install.ps1
Get-FileHash install.ps1 -Algorithm SHA256
# last verified 2026-08-21: 3b5b42c066f04a43aaa587cfa50c5873e0148b80138a8befc610f7a7477b58e6
# Unity can change this file without notice - a different hash on a later pull
# is not itself a red flag, but means re-reading install.ps1 before running it.
$env:UNITY_CLI_CHANNEL="beta"; .\install.ps1
```

Installs to `%LOCALAPPDATA%\Unity\bin\unity.exe`, verifying SHA-256 first.
Requires a terminal restart (or the absolute path) before `unity` is on `PATH`.
Installs to `%LOCALAPPDATA%\Unity\bin\unity.exe`, which then verifies its own
SHA-256 against Unity's manifest at first run. Requires a terminal restart
(or the absolute path) before `unity` is on `PATH`.

Self-update: `unity upgrade`. Removal: `unity self-uninstall`.

Expand Down
1 change: 1 addition & 0 deletions unreal/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"displayName": "Unreal Agent",
"description": "Observe, diagnose, and safely operate an Unreal Engine project's build/cook/package pipeline and editor automation. A read-first, gamedev-focused Claude Code agent with an engine/project version doctor, cook-log diagnosis, gated BuildCookRun/UBT builds, first-party editor scripting, and runtime performance profiling (Unreal Insights, stat/obj triage, GC and Nanite heuristics).",
"version": "0.1.0",
"license": "MIT",
"author": {
"name": "ButterStack"
},
Expand Down
Loading
Loading