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.
- package/.github/workflows/publish.yml +33 -0
- package/README.md +111 -0
- package/bin/index.js +8 -0
- package/package.json +46 -0
- package/src/index.js +21 -0
- package/src/installer.js +75 -0
- package/src/logger.js +20 -0
- package/src/templates/CLAUDE.md.js +82 -0
- package/src/templates/hooks/auto-commit-msg.js +39 -0
- package/src/templates/hooks/block-dangerous.js +40 -0
- package/src/templates/hooks/branch-guard.js +48 -0
- package/src/templates/hooks/build-validator.js +49 -0
- package/src/templates/hooks/cost-tracker.js +63 -0
- package/src/templates/hooks/dep-audit.js +66 -0
- package/src/templates/hooks/deploy-check.js +65 -0
- package/src/templates/hooks/lint-check.js +51 -0
- package/src/templates/hooks/secret-guard.js +46 -0
- package/src/templates/hooks/test-runner.js +67 -0
- package/src/templates/settings.js +128 -0
- package/src/templates/skills/code-review.md +61 -0
- package/src/templates/skills/debugging.md +61 -0
- package/src/templates/skills/deployment.md +89 -0
- package/src/templates/skills/pr-description.md +61 -0
- package/src/templates/skills/test-generation.md +56 -0
- package/src/wizard.js +62 -0
|
@@ -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.
|