opscale-setup 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PreToolUse / Bash — intercepts npm/pip/cargo install commands and runs
4
+ * a lightweight audit before allowing the install to proceed.
5
+ *
6
+ * For npm: checks `npm audit` after hypothetical install.
7
+ * For pip: warns if package is not on a known allowlist (informational only).
8
+ */
9
+ import { readFileSync } from 'fs';
10
+ import { execSync } from 'child_process';
11
+
12
+ let input;
13
+ try {
14
+ input = JSON.parse(readFileSync('/dev/stdin', 'utf8'));
15
+ } catch {
16
+ process.exit(0);
17
+ }
18
+
19
+ const command = (input?.tool_input?.command ?? '').trim();
20
+
21
+ // Only handle install commands
22
+ if (!/^(npm\s+install|npm\s+i|pip\s+install|cargo\s+add|yarn\s+add|pnpm\s+add)/.test(command)) {
23
+ process.exit(0);
24
+ }
25
+
26
+ // Extract package names (simple heuristic — flags start with -)
27
+ const tokens = command.split(/\s+/).filter(t => !t.startsWith('-') && !/^(npm|pip|cargo|yarn|pnpm|install|add|i)$/.test(t));
28
+
29
+ if (tokens.length === 0) process.exit(0);
30
+
31
+ // Flag known typosquat-prone names
32
+ const SUSPICIOUS = [
33
+ /^cros$/, /^reqeusts$/, /^lodahs$/, /^expres$/, /^djano$/,
34
+ ];
35
+ for (const pkg of tokens) {
36
+ for (const pattern of SUSPICIOUS) {
37
+ if (pattern.test(pkg)) {
38
+ console.error(
39
+ `[dep-audit] BLOCKED: package name "${pkg}" looks like a typosquat.\n` +
40
+ ` Did you mean a different package?`
41
+ );
42
+ process.exit(1);
43
+ }
44
+ }
45
+ }
46
+
47
+ // For npm, run audit after install (informational)
48
+ if (/^(npm|yarn|pnpm)/.test(command)) {
49
+ try {
50
+ execSync('npm audit --audit-level=critical --json', { stdio: 'pipe' });
51
+ } catch (err) {
52
+ try {
53
+ const report = JSON.parse(err.stdout?.toString() ?? '{}');
54
+ const critical = report?.metadata?.vulnerabilities?.critical ?? 0;
55
+ if (critical > 0) {
56
+ console.error(
57
+ `[dep-audit] WARNING: npm audit found ${critical} critical vulnerabilities.\n` +
58
+ ` Run \`npm audit fix\` after install.`
59
+ );
60
+ // Warn only — don't block
61
+ }
62
+ } catch { /* ignore parse errors */ }
63
+ }
64
+ }
65
+
66
+ process.exit(0);
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PreToolUse / Bash — intercepts deploy commands and enforces a pre-flight
4
+ * checklist: tests must pass, no uncommitted changes, branch must be correct.
5
+ */
6
+ import { readFileSync } from 'fs';
7
+ import { execSync } from 'child_process';
8
+
9
+ const DEPLOY_PATTERNS = [
10
+ /\bdeploy\b/,
11
+ /\bserverless\s+deploy\b/,
12
+ /\beb\s+deploy\b/,
13
+ /\bkubectl\s+apply\b/,
14
+ /\bhelm\s+upgrade\b/,
15
+ /\bdocker\s+push\b/,
16
+ /\bvercel\b/,
17
+ /\bflyctl\s+deploy\b/,
18
+ ];
19
+
20
+ let input;
21
+ try {
22
+ input = JSON.parse(readFileSync('/dev/stdin', 'utf8'));
23
+ } catch {
24
+ process.exit(0);
25
+ }
26
+
27
+ const command = input?.tool_input?.command ?? '';
28
+
29
+ if (!DEPLOY_PATTERNS.some(p => p.test(command))) process.exit(0);
30
+
31
+ const errors = [];
32
+
33
+ // Uncommitted changes check
34
+ try {
35
+ const status = execSync('git status --porcelain', { stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
36
+ if (status.length > 0) {
37
+ errors.push('There are uncommitted changes. Commit or stash before deploying.');
38
+ }
39
+ } catch { /* not a git repo — skip */ }
40
+
41
+ // Check tests exist and pass (node projects)
42
+ const testScript = (() => {
43
+ try {
44
+ const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
45
+ return pkg?.scripts?.test;
46
+ } catch { return null; }
47
+ })();
48
+
49
+ if (testScript && testScript !== 'echo \\"Error: no test specified\\" && exit 1') {
50
+ try {
51
+ execSync('npm test --if-present', { stdio: 'pipe', timeout: 120_000 });
52
+ } catch {
53
+ errors.push('Test suite failed. Fix all tests before deploying.');
54
+ }
55
+ }
56
+
57
+ if (errors.length > 0) {
58
+ console.error('[deploy-check] BLOCKED — pre-flight checklist failed:\n' +
59
+ errors.map(e => ` • ${e}`).join('\n')
60
+ );
61
+ process.exit(1);
62
+ }
63
+
64
+ console.error('[deploy-check] Pre-flight checks passed.');
65
+ process.exit(0);
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PostToolUse / Write — runs the project linter on the file Claude just wrote.
4
+ * Supports ESLint, Biome, Ruff (Python), and gofmt. Advisory only — never blocks.
5
+ */
6
+ import { readFileSync, existsSync } from 'fs';
7
+ import { execSync } from 'child_process';
8
+ import path from 'path';
9
+
10
+ let input;
11
+ try {
12
+ input = JSON.parse(readFileSync('/dev/stdin', 'utf8'));
13
+ } catch {
14
+ process.exit(0);
15
+ }
16
+
17
+ const filePath = input?.tool_input?.file_path ?? '';
18
+ if (!filePath || !existsSync(filePath)) process.exit(0);
19
+
20
+ const ext = path.extname(filePath);
21
+
22
+ function run(cmd) {
23
+ try {
24
+ execSync(cmd, { stdio: 'pipe', timeout: 30_000 });
25
+ console.error(`[lint-check] Lint passed: ${path.basename(filePath)}`);
26
+ } catch (err) {
27
+ console.error(
28
+ `[lint-check] Lint issues in ${path.basename(filePath)}:\n` +
29
+ (err.stdout?.toString() ?? '') +
30
+ (err.stderr?.toString() ?? '')
31
+ );
32
+ }
33
+ }
34
+
35
+ if (['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'].includes(ext)) {
36
+ if (existsSync('biome.json') || existsSync('biome.jsonc')) {
37
+ run(`npx biome check --no-errors-on-unmatched ${filePath}`);
38
+ } else if (existsSync('.eslintrc.js') || existsSync('.eslintrc.json') || existsSync('eslint.config.js') || existsSync('eslint.config.mjs')) {
39
+ run(`npx eslint --max-warnings=0 ${filePath}`);
40
+ }
41
+ } else if (ext === '.py') {
42
+ if (existsSync('ruff.toml') || existsSync('pyproject.toml')) {
43
+ run(`ruff check ${filePath}`);
44
+ }
45
+ } else if (ext === '.go') {
46
+ run(`gofmt -l ${filePath}`);
47
+ } else if (ext === '.rs') {
48
+ run(`cargo clippy --quiet 2>&1`);
49
+ }
50
+
51
+ process.exit(0);
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PreToolUse / Bash + Write — scans command strings and file content for
4
+ * patterns that look like secrets, API keys, or credentials before they
5
+ * are written to disk or executed.
6
+ */
7
+ import { readFileSync } from 'fs';
8
+
9
+ const SECRET_PATTERNS = [
10
+ { name: 'AWS Access Key', re: /AKIA[0-9A-Z]{16}/ },
11
+ { name: 'AWS Secret Key', re: /aws_secret_access_key\s*=\s*\S{40}/i },
12
+ { name: 'GitHub Token', re: /ghp_[a-zA-Z0-9]{36}/ },
13
+ { name: 'GitHub Fine-grained', re: /github_pat_[a-zA-Z0-9_]{82}/ },
14
+ { name: 'Stripe Secret Key', re: /sk_live_[a-zA-Z0-9]{24,}/ },
15
+ { name: 'SendGrid Key', re: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/ },
16
+ { name: 'Anthropic API Key', re: /sk-ant-[a-zA-Z0-9\-_]{90,}/ },
17
+ { name: 'OpenAI API Key', re: /sk-[a-zA-Z0-9]{48}/ },
18
+ { name: 'Slack Token', re: /xox[baprs]-[a-zA-Z0-9\-]+/ },
19
+ { name: 'Private key block', re: /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
20
+ { name: 'Hardcoded password', re: /password\s*=\s*['"][^'"]{8,}['"]/i },
21
+ ];
22
+
23
+ let input;
24
+ try {
25
+ input = JSON.parse(readFileSync('/dev/stdin', 'utf8'));
26
+ } catch {
27
+ process.exit(0);
28
+ }
29
+
30
+ const haystack = [
31
+ input?.tool_input?.command,
32
+ input?.tool_input?.content,
33
+ input?.tool_input?.new_string,
34
+ ].filter(Boolean).join('\n');
35
+
36
+ for (const { name, re } of SECRET_PATTERNS) {
37
+ if (re.test(haystack)) {
38
+ console.error(
39
+ `[secret-guard] BLOCKED: possible ${name} detected in tool input.\n` +
40
+ ` If this is intentional, move the value to an environment variable.`
41
+ );
42
+ process.exit(1);
43
+ }
44
+ }
45
+
46
+ process.exit(0);
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PostToolUse / Write — when Claude writes a source file, auto-discovers and
4
+ * runs the related test file if one exists. Exits 0 always (advisory only),
5
+ * but prints test output so Claude sees failures and can fix them.
6
+ */
7
+ import { readFileSync, existsSync } from 'fs';
8
+ import { execSync } from 'child_process';
9
+ import path from 'path';
10
+
11
+ const TEST_CANDIDATES = (file) => {
12
+ const { dir, name, ext } = path.parse(file);
13
+ return [
14
+ path.join(dir, `${name}.test${ext}`),
15
+ path.join(dir, `${name}.spec${ext}`),
16
+ path.join(dir, '__tests__', `${name}.test${ext}`),
17
+ path.join('tests', `${name}.test${ext}`),
18
+ path.join('test', `${name}_test${ext}`),
19
+ // Python
20
+ path.join(dir, `test_${name}${ext}`),
21
+ path.join('tests', `test_${name}.py`),
22
+ ];
23
+ };
24
+
25
+ let input;
26
+ try {
27
+ input = JSON.parse(readFileSync('/dev/stdin', 'utf8'));
28
+ } catch {
29
+ process.exit(0);
30
+ }
31
+
32
+ const filePath = input?.tool_input?.file_path ?? input?.tool_response?.file_path ?? '';
33
+ if (!filePath) process.exit(0);
34
+
35
+ // Don't run tests when the test file itself was written
36
+ if (/\.(test|spec)\.(js|ts|jsx|tsx|py|go|rs)$/.test(filePath) || /test_.*\.py$/.test(filePath)) {
37
+ process.exit(0);
38
+ }
39
+
40
+ const testFile = TEST_CANDIDATES(filePath).find(f => existsSync(f));
41
+ if (!testFile) process.exit(0);
42
+
43
+ const ext = path.extname(testFile);
44
+
45
+ let cmd;
46
+ if (['.js', '.ts', '.jsx', '.tsx', '.mjs'].includes(ext)) {
47
+ cmd = `node --test ${testFile}`;
48
+ } else if (ext === '.py') {
49
+ cmd = `python -m pytest ${testFile} -q`;
50
+ } else if (ext === '.go') {
51
+ cmd = `go test ./${path.dirname(testFile)}/...`;
52
+ } else {
53
+ process.exit(0);
54
+ }
55
+
56
+ try {
57
+ const out = execSync(cmd, { stdio: 'pipe', timeout: 60_000 }).toString();
58
+ console.error(`[test-runner] Tests passed for ${path.basename(filePath)}:\n${out}`);
59
+ } catch (err) {
60
+ console.error(
61
+ `[test-runner] Tests FAILED for ${path.basename(filePath)}:\n` +
62
+ (err.stdout?.toString() ?? '') +
63
+ (err.stderr?.toString() ?? '')
64
+ );
65
+ }
66
+
67
+ process.exit(0);
@@ -0,0 +1,128 @@
1
+ export function renderSettings({ projectType, teamSize, deployTarget }) {
2
+ const hooksBase = '.claude/hooks';
3
+
4
+ // Larger teams get stricter guardrails
5
+ const strictMode = teamSize === 'medium' || teamSize === 'large';
6
+
7
+ const dangerousPatterns = [
8
+ 'rm -rf',
9
+ 'DROP TABLE',
10
+ 'DROP DATABASE',
11
+ 'DELETE FROM',
12
+ 'truncate',
13
+ '--force-with-lease',
14
+ 'git push --force',
15
+ 'chmod 777',
16
+ 'curl.*sh.*bash',
17
+ 'wget.*sh.*bash',
18
+ ];
19
+
20
+ if (deployTarget === 'aws' || deployTarget === 'gcp' || deployTarget === 'azure') {
21
+ dangerousPatterns.push('aws.*delete', 'gcloud.*delete', 'az.*delete');
22
+ }
23
+
24
+ return {
25
+ version: 1,
26
+ permissions: {
27
+ allow: [
28
+ 'Bash(git:*)',
29
+ 'Bash(npm:*)',
30
+ 'Bash(node:*)',
31
+ ...(projectType === 'python' ? ['Bash(python:*)', 'Bash(pip:*)', 'Bash(pytest:*)'] : []),
32
+ ...(projectType === 'go' ? ['Bash(go:*)'] : []),
33
+ ...(projectType === 'rust' ? ['Bash(cargo:*)'] : []),
34
+ 'Bash(ls:*)',
35
+ 'Bash(cat:*)',
36
+ 'Bash(echo:*)',
37
+ 'Bash(find:*)',
38
+ 'Bash(grep:*)',
39
+ ],
40
+ deny: [
41
+ 'Bash(rm -rf /*)',
42
+ 'Bash(curl * | bash:*)',
43
+ 'Bash(wget * | bash:*)',
44
+ ],
45
+ },
46
+ env: {
47
+ OPSCALE_PROJECT_TYPE: projectType,
48
+ OPSCALE_TEAM_SIZE: teamSize,
49
+ OPSCALE_DEPLOY_TARGET: deployTarget,
50
+ },
51
+ hooks: {
52
+ PreToolUse: [
53
+ {
54
+ matcher: 'Bash',
55
+ hooks: [
56
+ {
57
+ type: 'command',
58
+ command: `node ${hooksBase}/block-dangerous.js`,
59
+ },
60
+ {
61
+ type: 'command',
62
+ command: `node ${hooksBase}/secret-guard.js`,
63
+ },
64
+ {
65
+ type: 'command',
66
+ command: `node ${hooksBase}/branch-guard.js`,
67
+ },
68
+ {
69
+ type: 'command',
70
+ command: `node ${hooksBase}/dep-audit.js`,
71
+ },
72
+ {
73
+ type: 'command',
74
+ command: `node ${hooksBase}/deploy-check.js`,
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ matcher: 'Write',
80
+ hooks: [
81
+ {
82
+ type: 'command',
83
+ command: `node ${hooksBase}/secret-guard.js`,
84
+ },
85
+ ],
86
+ },
87
+ ],
88
+ PostToolUse: [
89
+ {
90
+ matcher: 'Write',
91
+ hooks: [
92
+ {
93
+ type: 'command',
94
+ command: `node ${hooksBase}/test-runner.js`,
95
+ },
96
+ {
97
+ type: 'command',
98
+ command: `node ${hooksBase}/lint-check.js`,
99
+ },
100
+ {
101
+ type: 'command',
102
+ command: `node ${hooksBase}/build-validator.js`,
103
+ },
104
+ ],
105
+ },
106
+ {
107
+ matcher: 'Bash',
108
+ hooks: [
109
+ {
110
+ type: 'command',
111
+ command: `node ${hooksBase}/auto-commit-msg.js`,
112
+ },
113
+ ],
114
+ },
115
+ ],
116
+ Stop: [
117
+ {
118
+ hooks: [
119
+ {
120
+ type: 'command',
121
+ command: `node ${hooksBase}/cost-tracker.js`,
122
+ },
123
+ ],
124
+ },
125
+ ],
126
+ },
127
+ };
128
+ }
@@ -0,0 +1,61 @@
1
+ # code-review
2
+
3
+ Review the current diff (or a specified file/PR) for correctness bugs, security
4
+ issues, and efficiency problems. Return actionable findings only — no praise,
5
+ no summaries of what the code does.
6
+
7
+ ## How to use
8
+
9
+ ```
10
+ /code-review # review staged + unstaged changes
11
+ /code-review src/auth.ts # review a specific file
12
+ /code-review --fix # apply safe fixes automatically
13
+ /code-review --security # focus only on security issues
14
+ ```
15
+
16
+ ## Review checklist
17
+
18
+ ### Correctness
19
+ - Off-by-one errors in loops and slices
20
+ - Null / undefined dereferences before safety checks
21
+ - Race conditions in async code (missing `await`, shared mutable state)
22
+ - Incorrect error handling (swallowed errors, wrong error types)
23
+ - Logic inversions (`!` in wrong place, wrong comparison operator)
24
+
25
+ ### Security (OWASP Top 10 + extras)
26
+ - SQL / NoSQL injection via string interpolation
27
+ - Command injection via `exec`/`spawn` with user input
28
+ - XSS — unescaped user content rendered as HTML
29
+ - Insecure direct object reference (IDOR)
30
+ - Missing authentication or authorization checks
31
+ - Secrets hardcoded or logged
32
+ - Unsafe deserialization
33
+ - Path traversal (`../` in user-supplied filenames)
34
+ - SSRF — user-controlled URLs fetched server-side
35
+
36
+ ### Performance
37
+ - N+1 query patterns
38
+ - Missing database indexes implied by query patterns
39
+ - Unnecessary re-renders or re-computations in hot paths
40
+ - Large payloads sent over the wire without pagination
41
+
42
+ ### Maintainability
43
+ - Functions doing more than one thing (flag for refactor, don't refactor)
44
+ - Magic numbers / strings that should be named constants
45
+ - Dead code paths
46
+ - Inconsistent error handling patterns vs. rest of codebase
47
+
48
+ ## Output format
49
+
50
+ For each finding:
51
+
52
+ ```
53
+ [SEVERITY] file.ts:42 — Short description
54
+ Why: <why this is a problem>
55
+ Fix: <concrete fix, code snippet if helpful>
56
+ ```
57
+
58
+ Severity levels: CRITICAL · HIGH · MEDIUM · LOW · INFO
59
+
60
+ Only report findings you are confident about. If uncertain, say so.
61
+ Prefer fewer high-confidence findings over many speculative ones.
@@ -0,0 +1,61 @@
1
+ # debugging
2
+
3
+ Systematically diagnose and fix a bug. Works for runtime errors, wrong output,
4
+ performance regressions, and flaky tests.
5
+
6
+ ## How to use
7
+
8
+ ```
9
+ /debugging "TypeError: Cannot read properties of undefined"
10
+ /debugging "checkout is 3x slower after the last deploy"
11
+ /debugging "test suite fails intermittently on CI"
12
+ ```
13
+
14
+ ## Debugging protocol
15
+
16
+ Follow these steps in order. Do not skip ahead.
17
+
18
+ ### 1. Reproduce
19
+ - Find or write the minimal command / test that reliably triggers the bug.
20
+ - If flaky: run it 10 times and record the failure rate.
21
+ - State the reproduction case explicitly before proceeding.
22
+
23
+ ### 2. Isolate
24
+ - Narrow scope: which module, function, or line is responsible?
25
+ - Use `git bisect` if the bug is a regression: `git bisect start HEAD <last-good-sha>`.
26
+ - Add temporary `console.error` / `print` / `fmt.Println` at key points — remove after.
27
+ - Read the stack trace from bottom (the root call) to top (where it blew up).
28
+
29
+ ### 3. Hypothesize
30
+ - State 2-3 hypotheses for the root cause, ranked by likelihood.
31
+ - Design one test per hypothesis that would confirm or rule it out.
32
+ - Test hypotheses cheapest-first.
33
+
34
+ ### 4. Fix
35
+ - Fix the root cause, not a symptom.
36
+ - If fixing requires a workaround (e.g., third-party bug), document why in a comment.
37
+ - Remove all debug logging added in step 2.
38
+ - Confirm reproduction case now passes.
39
+
40
+ ### 5. Prevent
41
+ - Write a regression test that would have caught this bug.
42
+ - Check if the same pattern exists elsewhere: `grep -r "pattern" src/`.
43
+ - If the bug was caused by a missing validation, add validation at the boundary.
44
+
45
+ ## Common root causes by symptom
46
+
47
+ | Symptom | Check first |
48
+ |---------|-------------|
49
+ | `undefined` / `null` dereference | Async timing, missing await, API response shape change |
50
+ | Wrong calculation | Unit mismatch, integer overflow, floating-point precision |
51
+ | Only fails in production | Missing env var, different data shape, caching layer |
52
+ | Only fails on CI | Time-dependent test, missing seed data, parallelism |
53
+ | Memory leak | Event listeners not removed, circular references, unbounded cache |
54
+ | Slow after deploy | N+1 query introduced, missing index, cache invalidated |
55
+
56
+ ## Rules
57
+
58
+ - Never blame the framework or library until you have ruled out your own code.
59
+ - "It works on my machine" means the environments differ — find the difference.
60
+ - A bug that cannot be reproduced reliably cannot be safely fixed.
61
+ - Fix one thing at a time; revert and try again if it doesn't help.
@@ -0,0 +1,89 @@
1
+ # deployment
2
+
3
+ Run a deployment checklist, generate deployment commands, or assist with
4
+ rollback procedures for any cloud target.
5
+
6
+ ## How to use
7
+
8
+ ```
9
+ /deployment # run full pre-deploy checklist
10
+ /deployment --rollback # generate rollback commands for last deploy
11
+ /deployment --diff # show what will change vs. production
12
+ /deployment --status # check current deployment status
13
+ ```
14
+
15
+ ## Pre-deployment checklist
16
+
17
+ Work through each item before running the deploy command.
18
+
19
+ ### Code quality
20
+ - [ ] All tests pass locally (`npm test` / `pytest` / `go test ./...`)
21
+ - [ ] No linting errors (`npm run lint` / `ruff check .`)
22
+ - [ ] TypeScript compiles without errors (`tsc --noEmit`)
23
+ - [ ] No `console.log` / `print` debug statements left in code
24
+ - [ ] Feature branch merged to main via PR, not committed directly
25
+
26
+ ### Configuration
27
+ - [ ] Environment variables verified in target environment
28
+ - [ ] `.env.example` updated if new vars were added
29
+ - [ ] Secrets rotated if they were accidentally committed (even briefly)
30
+ - [ ] Feature flags set correctly for target environment
31
+
32
+ ### Database
33
+ - [ ] Migration is backwards-compatible (can run with old code)
34
+ - [ ] Migration tested on a copy of production data
35
+ - [ ] Rollback SQL prepared for schema changes
36
+ - [ ] No long-lock migrations (adding NOT NULL without default on large tables)
37
+
38
+ ### Infrastructure
39
+ - [ ] Resource limits (CPU, memory) checked for new workloads
40
+ - [ ] Auto-scaling configured if expected traffic increase
41
+ - [ ] Health check endpoint responds correctly
42
+ - [ ] Deployment target (cluster/region) confirmed
43
+
44
+ ### Observability
45
+ - [ ] New code paths have log statements at appropriate levels
46
+ - [ ] Metrics / dashboards updated for new features
47
+ - [ ] Alerts configured for new failure modes
48
+ - [ ] On-call team notified if risky deploy
49
+
50
+ ## Deploy command templates
51
+
52
+ ### Vercel
53
+ ```bash
54
+ vercel --prod
55
+ ```
56
+
57
+ ### AWS ECS
58
+ ```bash
59
+ aws ecs update-service --cluster <cluster> --service <service> --force-new-deployment
60
+ ```
61
+
62
+ ### Kubernetes
63
+ ```bash
64
+ kubectl set image deployment/<name> <container>=<image>:<tag>
65
+ kubectl rollout status deployment/<name>
66
+ ```
67
+
68
+ ### Docker Compose
69
+ ```bash
70
+ docker compose pull && docker compose up -d --remove-orphans
71
+ ```
72
+
73
+ ## Rollback procedure
74
+
75
+ 1. Identify the last stable image/commit: `git log --oneline -10`
76
+ 2. Revert the deployment (do NOT delete data):
77
+ - Kubernetes: `kubectl rollout undo deployment/<name>`
78
+ - ECS: redeploy previous task definition revision
79
+ - Vercel: `vercel rollback`
80
+ 3. Verify health check returns 200.
81
+ 4. Roll back the database migration **only if** it is backwards-incompatible.
82
+ 5. File an incident report within 24 hours.
83
+
84
+ ## Rules
85
+
86
+ - Never deploy on a Friday afternoon or before a holiday without explicit sign-off.
87
+ - Always have a rollback plan before deploying schema changes.
88
+ - Deploy to staging first; wait 10 minutes; then promote to production.
89
+ - One deploy at a time — wait for the previous deploy to stabilize.