SLOPSHOPPER

prod-guard

Holds every write call to the Dokploy MCP server (deploy, save, update, delete, stop...) until you press Proceed. Reads pass through.

panebandguardtoast
★ 21v0.1.0GPL-3.0updated 2026-10-18fcsouza/agent-skills/mods/prod-guard
A shopper browsing a rack in a slop shop
README

Agent Skills

Production-grade skills for AI coding agents (OpenClaw, Claude Code, and others).

Built by Fabricio Cavalcante — Software Engineer & AI Specialist.

Available Plugins

Plugins are complete workflow bundles — skills + agents + hooks working together as an integrated system. Install a plugin to get the full experience.

PluginDescriptionPlatform
game-dev26 skills, 7 commands, 3 agents, 5 hooks — complete game development workflow with engineering, design, narrative, and infrastructureGame Dev

Claude Code Mods

Mods are plugins built on function hooks: they run inside Claude Code, guard tool calls and draw above the prompt. See mods/ for install steps.

ModDescription
commit-guardBlocks commits with machine paths or .env files, git add -A, and pushes to main
prod-guardHolds Dokploy MCP writes until you press Proceed
bun-onlyRewrites npm, npx, yarn and pnpm to bun
done-gateShows edited files and the checks still owed
worktreesShows git worktrees, their changes, and the count
usage-ringsContext and plan usage, coloured by level
pr-statusThe current branch's PR: CI checks and review
sql-guardHolds writes to non-local databases until you press Proceed
pino-guardBlocks pino transports that break bundled logging

Available Skills

Skills are individual, cherry-pickable modules. Install only what you need.

Core

SkillDescriptionPlatform
claude-agent-sdkBuild production AI agents with the Claude Agent SDK — query(), hooks, subagents, custom tools, MCP servers, sessions, permissions, hostingClaude Code / API
database-performance-tuningDiagnose and fix slow databases like a DBA — pg_stat_statements, EXPLAIN plans, index design, query rewrites, vacuum and bloat, safe online rollout. Ships a read-only diagnostic scriptPostgreSQL / MySQL
foundry-vtt-module-devBuild, extend, and maintain Foundry VTT v14 modules — TypeDataModel, ApplicationV2 (detached windows), ActorSheetV2, hooks, sockets, dice, canvas/PIXI, Scene Levels, Regions as templates, ActiveEffects V2, v13→v14 migration, boilerplate filesFoundry VTT
foundry-vtt-system-devBuild Foundry VTT v14 game systems — system.json documentTypes, Actor/Item TypeDataModel, dice mechanics, combat/initiative, ActiveEffect V2 subtypes, data migration, character sheets, status effects, macros, enrichersFoundry VTT
openclaw-genieComprehensive OpenClaw gateway skill — installation, configuration, 38+ channels, memory, tools, hooks, deployment, multi-agentOpenClaw
nuvemshop-sdkPrepare, audit, implement, homologate, and publish Nuvemshop/Tiendanube apps — OAuth, API, NubeSDK, Nexo, Nimbus, webhooks, and App Store approvalNuvemshop
react-animationsReact animation patterns and utilitiesReact

Game Dev — Design

SkillDescription
game-design-fundamentalsCore loops, MDA framework, progression curves
game-economy-designVirtual economies, currencies, loot tables
level-designLevels, environments, difficulty curves
procedural-genDungeon gen, noise terrain, wave function collapse
quest-mission-designQuests, objectives, quest trees
skill-progression-treesSkill trees, talent trees, unlock graphs
ui-ux-gameHUD, menus, onboarding, accessibility

Game Dev — Engineering

SkillDescription
betterauth-integrationAuth, OAuth, JWT, RBAC
bullmq-game-queuesJob queues, async events, scheduled tasks
elevenlabs-sound-musicSFX generation, adaptive music, voice acting
game-backend-architectureGame servers, WebSocket, rooms, tick loops
game-state-syncClient-server sync, rollback netcode, delta compression
gameplay-analyticsRetention analytics, funnels, D1/D7/D30
matchmaking-systemMatchmaking, lobbies, ELO, skill-based queues
postgres-game-schemaGame DB schemas, Drizzle ORM, inventory
redis-game-patternsCaching, leaderboards, pub/sub, rate limiting
stripe-game-paymentsIn-app purchases, subscriptions, webhooks

Game Dev — Infrastructure

SkillDescription
ci-cd-gameCI/CD pipelines, GitHub Actions, deployment
claude-code-game-workflowEntry point for AI agents on game projects
cursor-codex-integrationCursor IDE, .cursorrules, Codex setup
monitoring-game-opsMonitoring, logging, telemetry, alerts

Game Dev — Narrative

SkillDescription
character-design-narrativeCharacters, NPCs, dialogue, archetypes
quest-narrative-coherenceNarrative coherence check, lore validation
story-structure-gameBranching narratives, player agency, story arcs
worldbuildingLore, factions, geography, world bibles

Structure

All skills follow a flat layout:

skills/<skill-name>/
├── README.md           # Overview and installation instructions
├── SKILL.md            # Main skill file (YAML frontmatter + body)
├── boilerplate/        # Starter code to copy and customize (optional)
├── templates/          # Document and config templates (optional)
├── references/         # Deep-dive reference files (optional)
└── ARCHITECTURE.md     # System design decisions (optional)
  • SKILL.md is always the entry point — read this first
  • boilerplate/ files are starter code, copy and customize
  • templates/ are document/config templates to fill in
  • ARCHITECTURE.md explains system design decisions

Installation

game-dev plugin (Claude Code)

/plugin marketplace add fcsouza/agent-skills
/plugin install game-dev@fcsouza-agent-skills

See plugins/game-dev/README.md for the full skill index, commands, and agents.

Individual skills (cherry-pick)

# Install a specific skill
npx skills add fcsouza/agent-skills --skill game-design-fundamentals

# Install all game-dev engineering skills at once
npx skills add fcsouza/agent-skills --skill betterauth-integration --skill postgres-game-schema --skill game-backend-architecture

# List all available skills
npx skills add fcsouza/agent-skills --list

Manual

# Plugin (full bundle)
cp -r plugins/game-dev/ .claude/plugins/game-dev/

# Individual skill
cp -r skills/<skill-name>/ .claude/skills/<skill-name>/

# For OpenClaw
cp -r skills/<skill-name>/ ~/.openclaw/workspace/skills/<skill-name>/

License

GPL-3.0

Source 2 files
hooks/register.tsx 103 lines
1import type { EngineInterface, Register, RenderInput } from 'claude-code';
2
3import { actionOf, describeInput, isWrite } from './rules';
4
5const PANE_ID = 'prod-guard';
6const POLL_MS = 250;
7const HOLD_LIMIT_MS = 10 * 60 * 1000;
8
9type Held = {
10  action: string;
11  lines: string[];
12  decision: 'proceed' | 'cancel' | null;
13  where: 'pane' | 'band';
14};
15
16// The call waiting for an answer, or null. One at a time.
17let held: Held | null = null;
18
19export const register: Register = on => {
20  on('tool.call', { tool: /^mcp__dokploy__/ }, async ($, e, next) => {
21    if (!isWrite(e.tool)) {
22      return next(e);
23    }
24
25    while (held !== null) {
26      if (next.signal.aborted) {
27        return { deny: `${$.plugin.name}: the turn was interrupted while another production call waited. Nothing ran.` };
28      }
29      await $.clock.sleep(POLL_MS);
30    }
31    const mine: Held = { action: actionOf(e.tool), lines: describeInput(e as Record<string, unknown>), decision: null, where: 'pane' };
32    held = mine;
33
34    let opened = { isPlaced: false };
35    let outcome: 'proceed' | 'cancel' | 'timeout' | 'interrupted' | 'error' = 'error';
36    try {
37      opened = await $.ui.open({ id: PANE_ID, title: 'Production write', focus: true });
38      if (!opened.isPlaced) {
39        mine.where = 'band';
40      }
41      $.ui.invalidate('ui.render');
42      $.ui.toast(`${$.plugin.name}: ${mine.action} waits for your OK`);
43
44      const startedAt = await $.clock.now();
45      while (mine.decision === null) {
46        if (next.signal.aborted) {
47          break;
48        }
49        if ((await $.clock.now()) - startedAt > HOLD_LIMIT_MS) {
50          break;
51        }
52        await $.clock.sleep(POLL_MS);
53      }
54      outcome = mine.decision ?? (next.signal.aborted ? 'interrupted' : 'timeout');
55    } finally {
56      if (opened.isPlaced) {
57        await $.ui.close({ id: PANE_ID }).catch(() => undefined);
58      }
59      if (held === mine) {
60        held = null;
61      }
62      $.ui.invalidate('ui.render');
63    }
64
65    if (outcome === 'proceed') {
66      return next(e);
67    }
68    const why = {
69      cancel: 'the user pressed Cancel',
70      timeout: 'no answer within 10 minutes',
71      interrupted: 'the turn was interrupted',
72      error: 'the guard hit an error',
73    }[outcome];
74    return { deny: `${$.plugin.name}: ${mine.action} did not run: ${why}. Do not retry it unless the user asks.` };
75  }).catch(($, e, next) => (next.called ? next(e) : { deny: `${$.plugin.name}: the guard failed, so the production call did not run.` }));
76
77  on('ui.render', { component: 'Pane', requestId: PANE_ID }, ($, e, next) => (held === null ? next(e) : draw($, e, held)));
78
79  on('ui.render', { component: 'AbovePrompt' }, ($, e, next) => (held === null || held.where !== 'band' ? next(e) : draw($, e, held)));
80};
81
82function draw($: EngineInterface, e: RenderInput<'Pane' | 'AbovePrompt'>, call: Held) {
83  const { Box, Button, Text } = $.ui.resolve(e);
84  const decide = (decision: 'proceed' | 'cancel') => () => {
85    call.decision = decision;
86    $.ui.invalidate('ui.render');
87  };
88  return (
89    <Box flexDirection="column" borderStyle="round" borderColor="#f97316" paddingX={1}>
90      <Text bold color="#f97316">
91        Dokploy write: {call.action}
92      </Text>
93      {call.lines.map(line => (
94        <Text dimColor>{line}</Text>
95      ))}
96      <Box gap={1} marginTop={1}>
97        <Button key="proceed" label="Proceed" onPress={decide('proceed')} />
98        <Button key="cancel" label="Cancel" onPress={decide('cancel')} />
99      </Box>
100    </Box>
101  );
102}
103
hooks/rules.ts 40 lines
1// Which Dokploy MCP calls only read. Everything else under the server is held:
2// a new endpoint that writes is held by default, never let through by default.
3
4export const SERVER = /^mcp__dokploy__/;
5
6const READ_ACTION =
7  /-(one|search|all\w*|by[A-Z]\w*|read\w*|get[A-Z]\w*|list\w*|load[A-Z]\w*|preview\w*|fetch\w*|templates|canGenerate\w*|homeStats|onboardingStatus|queueList|validateDomain)$/;
8
9export function isWrite(tool: string): boolean {
10  return SERVER.test(tool) && !READ_ACTION.test(tool);
11}
12
13/** `mcp__dokploy__application-deploy` → `application-deploy`. */
14export const actionOf = (tool: string): string => tool.replace(SERVER, '');
15
16// Fields the engine adds to every call: not the call's own arguments.
17const ENGINE_FIELDS = new Set(['tool', 'tool_use_id', 'consent', 'agentId', 'requestMeta', 'signal']);
18
19// Field names whose values never go on screen: env blocks, passwords, tokens.
20const SECRET_KEY = /env|password|secret|token|key|credential/i;
21
22/** The call's arguments as short lines, secrets shown by name only. */
23export function describeInput(input: Record<string, unknown>): string[] {
24  const lines: string[] = [];
25  for (const [key, value] of Object.entries(input)) {
26    if (ENGINE_FIELDS.has(key) || value === undefined || typeof value === 'function') {
27      continue;
28    }
29    if (SECRET_KEY.test(key)) {
30      lines.push(`${key}: (hidden${typeof value === 'string' ? `, ${countLines(value)} lines` : ''})`);
31      continue;
32    }
33    const text = typeof value === 'string' ? value : JSON.stringify(value);
34    lines.push(`${key}: ${text.length > 120 ? `${text.slice(0, 117)}...` : text}`);
35  }
36  return lines.slice(0, 12);
37}
38
39const countLines = (text: string): number => text.split('\n').filter(line => line.trim() !== '').length;
40