@thedecipherist/claude-packs 1.0.0 → 1.1.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/CHANGELOG.md +74 -0
- package/README.md +275 -18
- package/bin/claude-packs.mjs +18 -10
- package/configurations/base/config.json +1 -1
- package/configurations/cpp/config.json +2 -1
- package/configurations/full-stack/config.json +2 -1
- package/configurations/go/config.json +3 -1
- package/configurations/node/config.json +3 -1
- package/configurations/python/config.json +3 -1
- package/configurations/rust/config.json +106 -0
- package/configurations/synth-creator/config.json +1 -1
- package/global/hooks/lib/shell-writes.mjs +2 -293
- package/lib/build-queue.mjs +22 -0
- package/lib/claude-md.mjs +4 -2
- package/lib/config-cli.mjs +1 -1
- package/lib/coverage.mjs +10 -2
- package/lib/doctor.mjs +14 -3
- package/lib/fix-plan.mjs +2 -1
- package/lib/found-folders.mjs +25 -0
- package/lib/global.mjs +3 -3
- package/lib/hook-proof.mjs +24 -7
- package/lib/kit-command.mjs +14 -0
- package/lib/layout.mjs +27 -3
- package/lib/logs.mjs +45 -5
- package/lib/new.mjs +1 -1
- package/lib/packs.mjs +4 -2
- package/lib/rule-tests.mjs +42 -0
- package/lib/scan-detect.mjs +41 -5
- package/lib/scan.mjs +5 -2
- package/lib/suggest.mjs +16 -3
- package/lib/sync.mjs +5 -2
- package/lib/workorder.mjs +26 -0
- package/package.json +1 -1
- package/packs/audio-core/hooks/param-id-lock.mjs +59 -16
- package/packs/audio-core/pack.json +5 -0
- package/packs/audio-core/rules/a-control-is-proved-through-its-input.md +26 -0
- package/packs/audio-core/rules/cpu-worst-case-per-block.md +3 -1
- package/packs/audio-core/rules/house-control-behaviours.md +1 -1
- package/packs/audio-core/rules/scoped-no-denormals.md +1 -1
- package/packs/audio-core/rules/straight-on-and-aligned.md +1 -1
- package/packs/audio-core/rules/ui-matches-reference-look.md +2 -0
- package/packs/audio-core/skills/analyse-reference-look/SKILL.md +25 -11
- package/packs/audio-core/skills/prove-control/SKILL.md +2 -0
- package/packs/base/checks/one-walk-per-template.json +1 -1
- package/packs/base/hooks/code-shape.mjs +10 -6
- package/packs/base/hooks/dispatch-once.mjs +58 -21
- package/packs/base/hooks/folder-law.mjs +3 -0
- package/packs/base/hooks/git-guard.mjs +22 -109
- package/packs/base/hooks/lesson-checks.mjs +9 -1
- package/packs/base/hooks/lib/code-shapes.mjs +11 -6
- package/packs/base/hooks/lib/crash-policy.mjs +47 -0
- package/packs/base/hooks/lib/dup-index.mjs +6 -1
- package/packs/base/hooks/lib/dup.mjs +2 -2
- package/packs/base/hooks/lib/event-log.mjs +4 -3
- package/packs/base/hooks/lib/git-adds.mjs +2 -1
- package/packs/base/hooks/lib/git-auth.mjs +44 -0
- package/packs/base/hooks/lib/git-guard-selftest.mjs +93 -0
- package/packs/base/hooks/lib/languages.mjs +5 -2
- package/packs/base/hooks/lib/layout.mjs +6 -2
- package/packs/base/hooks/lib/making-check-selftest.mjs +3 -0
- package/packs/base/hooks/lib/near-copies.mjs +1 -1
- package/packs/base/hooks/lib/program-read.mjs +6 -3
- package/packs/base/hooks/lib/rust-scan.mjs +139 -0
- package/packs/base/hooks/lib/shape-lists.mjs +31 -0
- package/packs/base/hooks/lib/shape-prose.mjs +78 -0
- package/packs/base/hooks/lib/shape.mjs +18 -39
- package/packs/base/hooks/lib/turn.mjs +6 -1
- package/packs/base/hooks/lib/unread-writes.mjs +24 -0
- package/packs/base/hooks/making-check.mjs +12 -7
- package/packs/base/hooks/run-hooks.mjs +15 -7
- package/packs/base/hooks/shape-law.mjs +11 -4
- package/packs/base/hooks/suite-once.mjs +22 -6
- package/packs/base/rules/a-control-is-seen-to-fire.md +1 -1
- package/packs/base/rules/behaviour-lives-with-its-kind.md +1 -1
- package/packs/base/rules/bug-fix-starts-red.md +1 -1
- package/packs/base/rules/field-map-over-if-runs.md +1 -1
- package/packs/base/rules/instances-by-index-identities-by-id.md +2 -2
- package/packs/base/rules/lookup-over-if-chains.md +1 -1
- package/packs/base/rules/plan-and-logs.md +1 -1
- package/packs/base/rules/refusals-are-judged.md +1 -0
- package/packs/base/rules/test-the-template-once.md +2 -1
- package/packs/base/rules/unattended-runs.md +15 -0
- package/packs/base/skills/new-project/SKILL.md +23 -0
- package/packs/base/skills/write-a-workorder/SKILL.md +41 -0
- package/packs/cpp/checks/cpp-find-with-the-library.json +2 -2
- package/packs/go/pack.json +5 -0
- package/packs/hook-proof/rules/a-refusal-is-exit-2.md +2 -2
- package/packs/instrument-shell/rules/render-the-extremes.md +2 -2
- package/packs/midi/checks/position-not-integrated.json +5 -1
- package/packs/postgres/checks/pg-bind-values.json +33 -0
- package/packs/postgres/checks/pg-concurrently-in-txn.json +18 -0
- package/packs/postgres/checks/pg-fk-not-valid.json +18 -0
- package/packs/postgres/checks/pg-index-concurrently.json +18 -0
- package/packs/postgres/checks/pg-no-float-money.json +19 -0
- package/packs/postgres/checks/pg-no-money.json +18 -0
- package/packs/postgres/checks/pg-no-required-column.json +18 -0
- package/packs/postgres/checks/pg-no-volatile-default.json +18 -0
- package/packs/postgres/checks/pg-not-exists.json +18 -0
- package/packs/postgres/checks/pg-timestamptz.json +18 -0
- package/packs/postgres/hooks/db-wipe-guard.mjs +95 -0
- package/packs/postgres/pack.json +48 -0
- package/packs/postgres/rules/postgres-migrations-never-stop-traffic.md +28 -0
- package/packs/postgres/rules/postgres-one-pool-per-process.md +21 -0
- package/packs/postgres/rules/postgres-queries-are-null-safe-and-bounded.md +26 -0
- package/packs/postgres/rules/postgres-types-say-what-they-hold.md +22 -0
- package/packs/postgres/rules/postgres-values-are-bound.md +130 -0
- package/packs/postgres/skills/postgres-safe-migration/SKILL.md +45 -0
- package/packs/reverse-engineering/rules/addresses-name-their-space.md +1 -0
- package/packs/reverse-engineering/rules/reproduction-or-original.md +1 -1
- package/packs/reverse-engineering/skills/gather-sources/SKILL.md +1 -0
- package/packs/rust/checks/rust-allow-needs-reason.json +16 -0
- package/packs/rust/checks/rust-blocking-in-async.json +32 -0
- package/packs/rust/checks/rust-if-chain-is-a-match.json +32 -0
- package/packs/rust/checks/rust-static-mut.json +32 -0
- package/packs/rust/checks/rust-string-as-error.json +34 -0
- package/packs/rust/checks/rust-stub-left-in.json +32 -0
- package/packs/rust/checks/rust-unsafe-needs-safety.json +31 -0
- package/packs/rust/checks/rust-unwrap-in-library.json +34 -0
- package/packs/rust/checks/rust-wildcard-dependency.json +16 -0
- package/packs/rust/clippy.toml +5 -0
- package/packs/rust/pack.json +27 -0
- package/packs/rust/rules/rust-async-never-blocks.md +20 -0
- package/packs/rust/rules/rust-borrow-before-you-clone.md +19 -0
- package/packs/rust/rules/rust-errors-are-returned.md +22 -0
- package/packs/rust/rules/rust-lints-are-the-gate.md +23 -0
- package/packs/rust/rules/rust-repeats-become-templates.md +28 -0
- package/packs/rust/rules/rust-unsafe-is-proved.md +21 -0
- package/packs/rust/skills/rust-extract-template/SKILL.md +50 -0
- package/packs/rust/skills/rust-port/SKILL.md +22 -0
- package/packs/rust/skills/rust-review/SKILL.md +24 -0
- package/packs/rust/tools/rust-lints.toml +19 -0
- package/packs/sound-design/skills/bounce-a-plugin/SKILL.md +40 -0
- package/packs/sound-design/tools/vst3host/build.sh +73 -0
- package/packs/sound-design/tools/vst3host/host_events.h +101 -0
- package/packs/sound-design/tools/vst3host/options.h +172 -0
- package/packs/sound-design/tools/vst3host/render.cpp +245 -0
- package/packs/sound-design/tools/vst3host/session.h +241 -0
- package/packs/synth-creator/hooks/build-guard.mjs +7 -7
- package/packs/synth-creator/hooks/protect-vendor.mjs +2 -1
- package/packs/synth-creator/hooks/stage-guard.mjs +1 -1
- package/packs/synth-creator/rules/timing-from-synthcore.md +1 -1
- package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +4 -2
- package/packs/synth-creator/tools/buildq.mjs +74 -0
- package/prompts/workorder/PROMPT.md +53 -0
- package/prompts/workorder/run.sh +36 -0
- package/prompts/workorder/workorder.md +59 -0
- package/global/hooks/lib/program-read.mjs +0 -196
- package/global/hooks/lib/program-writes.mjs +0 -177
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// db-wipe-guard (PreToolUse, Bash): a command that wipes a database runs only against a database this machine holds. Two public reports of an agent
|
|
3
|
+
// running a forced reset against a live database lost 60 and 87 tables; in the second, .env pointed at production. A reset is ordinary work on a local
|
|
4
|
+
// database, so the guard judges the target, in this order: the host named in the command, then DATABASE_URL or POSTGRES_URL in the checkout's .env
|
|
5
|
+
// files. A target it cannot find is refused, because a guard that cannot judge must not pass a wipe: name a local host in the command and it can.
|
|
6
|
+
// Only the host is read and shown, never a password. `--selftest` proves it.
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join, resolve } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { checkoutRoot } from './lib/project.mjs';
|
|
11
|
+
import { bashOf, runHook, scratchProject, verdicts } from './lib/selftest.mjs';
|
|
12
|
+
import { judgeEvent } from './lib/writes.mjs';
|
|
13
|
+
import { asHook, isMain } from './lib/hook-log.mjs';
|
|
14
|
+
|
|
15
|
+
const WIPES = [
|
|
16
|
+
[/\bprisma\s+migrate\s+reset\b/, 'prisma migrate reset'],
|
|
17
|
+
[/\bprisma\s+db\s+push\b[^\n;&|]*--(?:force-reset|accept-data-loss)\b/, 'prisma db push with a forced reset'],
|
|
18
|
+
[/\bdrizzle-kit\s+push\b[^\n;&|]*--force\b/, 'drizzle-kit push --force'],
|
|
19
|
+
[/\b(?:rails|rake)\s+db:(?:drop|reset)\b/, 'db:drop or db:reset'],
|
|
20
|
+
[/\bmanage\.py\s+flush\b/, 'manage.py flush'],
|
|
21
|
+
[/(?:^|[\s;&|])dropdb\s/, 'dropdb'],
|
|
22
|
+
[/\bsqlx\s+database\s+(?:drop|reset)\b/, 'sqlx database drop or reset'],
|
|
23
|
+
[/\bdiesel\s+database\s+reset\b/, 'diesel database reset'],
|
|
24
|
+
];
|
|
25
|
+
const RAW_WIPE = /\bpsql\b[^\n|]*\bDROP\s+(?:DATABASE|SCHEMA)\b/i;
|
|
26
|
+
const LOCAL = new Set(['localhost', '127.0.0.1', '::1', '[::1]', 'host.docker.internal', 'db', 'postgres', 'database']);
|
|
27
|
+
const URL_HOST = /\b(?:postgres(?:ql)?|mysql|mariadb):\/\/(?:[^@\s/'"]*@)?(\[[^\]]+\]|[^:/?\s'"]+)/i;
|
|
28
|
+
const ENV_URL = /^\s*(?:export\s+)?(?:DATABASE_URL|POSTGRES_URL|POSTGRESQL_URL|DB_URL)\s*=\s*['"]?([^\s'"]+)/m;
|
|
29
|
+
const HOST_FLAG = /(?:\s-h\s*|\s--host[=\s])(\S+)/;
|
|
30
|
+
const ENV_FILES = ['.env.local', '.env.development', '.env'];
|
|
31
|
+
|
|
32
|
+
const hostIn = (text) => text.match(URL_HOST)?.[1] ?? null;
|
|
33
|
+
|
|
34
|
+
function hostOfEnv(root) {
|
|
35
|
+
for (const f of ENV_FILES) {
|
|
36
|
+
const file = join(root, f);
|
|
37
|
+
if (!existsSync(file)) continue;
|
|
38
|
+
const url = readFileSync(file, 'utf8').match(ENV_URL)?.[1];
|
|
39
|
+
if (url) return { host: hostIn(url), from: f };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The host a command names, then the host its checkout's .env names.
|
|
45
|
+
function targetOf(command, cwd) {
|
|
46
|
+
const named = hostIn(command) ?? command.match(HOST_FLAG)?.[1];
|
|
47
|
+
if (named) return { host: named, from: 'the command' };
|
|
48
|
+
return hostOfEnv(checkoutRoot(resolve(cwd, 'x'))) ?? { host: null, from: null };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function refusal(command, cwd) {
|
|
52
|
+
const body = command.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n\1\b/g, '').replace(/'[^']*'|"(?:\\.|[^"\\])*"/g, "''");
|
|
53
|
+
const wipe = WIPES.find(([re]) => re.test(body))?.[1] ?? (RAW_WIPE.test(command) ? 'psql with DROP' : null);
|
|
54
|
+
if (!wipe) return null;
|
|
55
|
+
const { host, from } = targetOf(command, cwd);
|
|
56
|
+
if (host && LOCAL.has(host.toLowerCase())) return null;
|
|
57
|
+
const where = host ? `its target is ${host} (from ${from}), which is not this machine` : 'nothing says which database it would hit';
|
|
58
|
+
return `\`${wipe}\` wipes a database, and ${where}.\n` +
|
|
59
|
+
'A wipe runs only against a local database. If this one is local, name it in the command: `DATABASE_URL=postgres://localhost/<db> <command>`, or `-h localhost`.\n' +
|
|
60
|
+
'If it is not, this is the user\'s to run, not Claude\'s: stop and ask.\n';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const main = (text) => judgeEvent(text, { name: 'db-wipe-guard', command: refusal });
|
|
64
|
+
|
|
65
|
+
function selftest() {
|
|
66
|
+
const hook = fileURLToPath(import.meta.url);
|
|
67
|
+
const live = scratchProject({ '.env': 'DATABASE_URL=postgres://app@db.prod.example.com:5432/app\n' });
|
|
68
|
+
const local = scratchProject({ '.env': 'DATABASE_URL="postgresql://app@localhost:5432/app"\n' });
|
|
69
|
+
const compose = scratchProject({ '.env.local': 'export POSTGRES_URL=postgres://app@db/app\n' });
|
|
70
|
+
const unknown = scratchProject({});
|
|
71
|
+
const sh = (dir, cmd) => runHook(hook, dir, bashOf(cmd, dir));
|
|
72
|
+
return verdicts('db-wipe-guard', [
|
|
73
|
+
['a reset, with .env on a live host', sh(live, 'npx prisma migrate reset --force'), 2],
|
|
74
|
+
['a forced push there', sh(live, 'npx prisma db push --force-reset'), 2],
|
|
75
|
+
['data loss accepted there', sh(live, 'prisma db push --accept-data-loss'), 2],
|
|
76
|
+
['drizzle there', sh(live, 'npx drizzle-kit push --force'), 2],
|
|
77
|
+
['rails there', sh(live, 'bin/rails db:drop'), 2],
|
|
78
|
+
['django there', sh(live, 'python manage.py flush --no-input'), 2],
|
|
79
|
+
['psql dropping a database', sh(live, 'psql "$URL" -c "DROP DATABASE app"'), 2],
|
|
80
|
+
['after other steps', sh(live, 'git status && dropdb app'), 2],
|
|
81
|
+
['a live host named in the command beats a local .env', sh(local, 'DATABASE_URL=postgres://u@db.prod.example.com/app npx prisma migrate reset'), 2],
|
|
82
|
+
['a reset with .env on localhost', sh(local, 'npx prisma migrate reset --force'), 0],
|
|
83
|
+
['a compose service in .env.local', sh(compose, 'npx prisma migrate reset --force'), 0],
|
|
84
|
+
['no target anywhere', sh(unknown, 'dropdb app'), 2],
|
|
85
|
+
['a local host named in the command', sh(unknown, 'dropdb -h localhost app'), 0],
|
|
86
|
+
['a local url named in the command', sh(live, 'DATABASE_URL=postgres://localhost/app npx prisma migrate reset'), 0],
|
|
87
|
+
['a safe migrate', sh(live, 'npx prisma migrate deploy'), 0],
|
|
88
|
+
['a push with no force', sh(live, 'npx drizzle-kit push'), 0],
|
|
89
|
+
['words about a wipe', sh(live, "echo 'npx prisma migrate reset'"), 0],
|
|
90
|
+
['a heredoc body', sh(live, "cat <<'EOF'\nprisma migrate reset\nEOF"), 0],
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const hook = main; // run-hooks.mjs calls it with the event's text (KWB-24)
|
|
95
|
+
if (isMain(import.meta.url)) process.exit(process.argv.includes('--selftest') ? selftest() : await asHook('db-wipe-guard', main));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postgres",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "PostgreSQL: migrations that never stop traffic, values always bound, types that say what they hold, and no database wiped by mistake.",
|
|
5
|
+
"type": "stock",
|
|
6
|
+
"author": "claude-packs",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/TheDecipherist/claude-packs",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/TheDecipherist/claude-packs.git",
|
|
12
|
+
"directory": "packs/postgres"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"postgres",
|
|
16
|
+
"postgresql",
|
|
17
|
+
"sql",
|
|
18
|
+
"migrations"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"claude-packs": ">=1.1.0"
|
|
22
|
+
},
|
|
23
|
+
"concepts": {
|
|
24
|
+
"postgres": [
|
|
25
|
+
"**/*.sql",
|
|
26
|
+
"**/*.prisma"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"optIn": [
|
|
30
|
+
"checks/pg-index-concurrently.json",
|
|
31
|
+
"checks/pg-fk-not-valid.json",
|
|
32
|
+
"checks/pg-timestamptz.json"
|
|
33
|
+
],
|
|
34
|
+
"hooks": {
|
|
35
|
+
"PreToolUse": [
|
|
36
|
+
{
|
|
37
|
+
"matcher": "Bash",
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/db-wipe-guard.mjs\"",
|
|
42
|
+
"timeout": 10
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.sql"
|
|
4
|
+
- "**/migrations/**"
|
|
5
|
+
- "**/migrate/**"
|
|
6
|
+
- "**/*.prisma"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# A migration never stops traffic
|
|
10
|
+
|
|
11
|
+
A new table is free. A live table is not: a lock held for one long statement queues every query behind it.
|
|
12
|
+
|
|
13
|
+
<constraints>
|
|
14
|
+
Always do:
|
|
15
|
+
- On a live table, build an index with `CONCURRENTLY`, outside a transaction. Check how the migration tool leaves its transaction first.
|
|
16
|
+
- Add a foreign key or a check `NOT VALID`, then `VALIDATE` it in a later transaction.
|
|
17
|
+
- Add a required column in three steps: nullable, backfill in batches, then tighten. The `postgres-safe-migration` skill walks it.
|
|
18
|
+
- Change a column by expand and contract: a new column, both written, a backfill, then the old one dropped.
|
|
19
|
+
- Set `lock_timeout` on a migration that takes a strong lock.
|
|
20
|
+
|
|
21
|
+
Never do:
|
|
22
|
+
- Add a column with a volatile default (`gen_random_uuid()`, `clock_timestamp()`, `random()`, `nextval()`) to a live table. `now()` and a constant are safe.
|
|
23
|
+
- Add a `NOT NULL` column with no default to a table with rows.
|
|
24
|
+
- Run a wipe (`prisma migrate reset`, `db push --force-reset`, `drizzle-kit push --force`, `db:drop`, `flush`, `dropdb`) on anything but a local database. `db-wipe-guard` refuses it.
|
|
25
|
+
</constraints>
|
|
26
|
+
|
|
27
|
+
## Check
|
|
28
|
+
`pg-no-volatile-default`, `pg-no-required-column`, `pg-concurrently-in-txn`, and the hook `db-wipe-guard`. On a project with large live tables, name these in `"enable"`: `checks/pg-index-concurrently.json`, `checks/pg-fk-not-valid.json`. A line that is right anyway says so at its end: `-- pg: allow <reason>`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ts"
|
|
4
|
+
- "**/*.py"
|
|
5
|
+
- "**/*.go"
|
|
6
|
+
- "**/*.rs"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# One pool per process, in one place
|
|
10
|
+
|
|
11
|
+
<constraints>
|
|
12
|
+
Always do:
|
|
13
|
+
- Make one pool per process, in one module, and hand it to everything that needs it.
|
|
14
|
+
- Size it so every process together stays under `max_connections`, with room for a migration.
|
|
15
|
+
- Give back every client you take, on every path.
|
|
16
|
+
- Keep a transaction short, and do no network call inside one.
|
|
17
|
+
|
|
18
|
+
Never do:
|
|
19
|
+
- Open a connection per request.
|
|
20
|
+
- Use session state (`SET`, `LISTEN`, session advisory locks, `PREPARE`) through PgBouncer in transaction mode.
|
|
21
|
+
</constraints>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.sql"
|
|
4
|
+
- "**/*.ts"
|
|
5
|
+
- "**/*.py"
|
|
6
|
+
- "**/*.go"
|
|
7
|
+
- "**/*.rs"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# A query survives NULLs, and reads a bounded amount
|
|
11
|
+
|
|
12
|
+
<constraints>
|
|
13
|
+
Always do:
|
|
14
|
+
- Write `NOT EXISTS` in place of `NOT IN (SELECT ...)`: one NULL in the subquery returns no rows at all.
|
|
15
|
+
- Page with a key (`WHERE id > $1 ORDER BY id LIMIT n`). `OFFSET` still computes every row it skips.
|
|
16
|
+
- Index a foreign key's referencing column when rows are looked up or deleted through it. Postgres does not do it for you.
|
|
17
|
+
- Name the columns a query needs in code that ships.
|
|
18
|
+
- Read `EXPLAIN (ANALYZE, BUFFERS)` on a query a hot path runs.
|
|
19
|
+
|
|
20
|
+
Never do:
|
|
21
|
+
- Run a query with no `LIMIT` on a table that grows, from a request.
|
|
22
|
+
- Use `BETWEEN` on a `timestamptz`: write `>=` and `<`.
|
|
23
|
+
</constraints>
|
|
24
|
+
|
|
25
|
+
## Check
|
|
26
|
+
`pg-not-exists`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.sql"
|
|
4
|
+
- "**/*.prisma"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# A column's type says what it holds
|
|
8
|
+
|
|
9
|
+
<constraints>
|
|
10
|
+
Always do:
|
|
11
|
+
- Use `timestamptz` for a moment in time, and `timestamp` only for a wall-clock time with no zone. Prisma and Rails default to `timestamp`: say `@db.Timestamptz` or `timestamptz` yourself.
|
|
12
|
+
- Use `text`, with a `CHECK` where a length is a real rule.
|
|
13
|
+
- Use `GENERATED ALWAYS AS IDENTITY` in place of `serial`.
|
|
14
|
+
- Use `numeric`, or an integer count of the smallest unit, for money.
|
|
15
|
+
|
|
16
|
+
Never do:
|
|
17
|
+
- Use `money`, or a float for an amount.
|
|
18
|
+
- Use `char(n)`.
|
|
19
|
+
</constraints>
|
|
20
|
+
|
|
21
|
+
## Check
|
|
22
|
+
`pg-no-money`, `pg-no-float-money`. `checks/pg-timestamptz.json` is opt-in, through `"enable"`.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ts"
|
|
4
|
+
- "**/*.tsx"
|
|
5
|
+
- "**/*.js"
|
|
6
|
+
- "bin/claude-packs.mjs"
|
|
7
|
+
- "lib/backup.mjs"
|
|
8
|
+
- "lib/build-queue.mjs"
|
|
9
|
+
- "lib/claude-md.mjs"
|
|
10
|
+
- "lib/config-cli.mjs"
|
|
11
|
+
- "lib/configs.mjs"
|
|
12
|
+
- "lib/coverage.mjs"
|
|
13
|
+
- "lib/custom-packs.mjs"
|
|
14
|
+
- "lib/doctor.mjs"
|
|
15
|
+
- "lib/fix-plan.mjs"
|
|
16
|
+
- "lib/git-tokens.mjs"
|
|
17
|
+
- "lib/global.mjs"
|
|
18
|
+
- "lib/hook-proof.mjs"
|
|
19
|
+
- "lib/hook-timing.mjs"
|
|
20
|
+
- "lib/hover-gate.mjs"
|
|
21
|
+
- "lib/kit-command.mjs"
|
|
22
|
+
- "lib/kit-lock.mjs"
|
|
23
|
+
- "lib/layout.mjs"
|
|
24
|
+
- "lib/logs.mjs"
|
|
25
|
+
- "lib/manifest.mjs"
|
|
26
|
+
- "lib/new.mjs"
|
|
27
|
+
- "lib/once-only-report.mjs"
|
|
28
|
+
- "lib/pack-manifest.mjs"
|
|
29
|
+
- "lib/packs.mjs"
|
|
30
|
+
- "lib/planner.mjs"
|
|
31
|
+
- "lib/repeats.mjs"
|
|
32
|
+
- "lib/repo-weight.mjs"
|
|
33
|
+
- "lib/rule-tests.mjs"
|
|
34
|
+
- "lib/rules.mjs"
|
|
35
|
+
- "lib/scan-detect.mjs"
|
|
36
|
+
- "lib/scan.mjs"
|
|
37
|
+
- "lib/selftest-cache.mjs"
|
|
38
|
+
- "lib/settings.mjs"
|
|
39
|
+
- "lib/shape-cli.mjs"
|
|
40
|
+
- "lib/shape.mjs"
|
|
41
|
+
- "lib/suggest.mjs"
|
|
42
|
+
- "lib/switches.mjs"
|
|
43
|
+
- "lib/symmetry-doctor.mjs"
|
|
44
|
+
- "lib/sync.mjs"
|
|
45
|
+
- "scripts/test-changed.mjs"
|
|
46
|
+
- "test/audio-packs.test.mjs"
|
|
47
|
+
- "test/base-laws.test.mjs"
|
|
48
|
+
- "test/base-neutral.test.mjs"
|
|
49
|
+
- "test/check-speed.test.mjs"
|
|
50
|
+
- "test/configs.test.mjs"
|
|
51
|
+
- "test/configurations.test.mjs"
|
|
52
|
+
- "test/content-sites.test.mjs"
|
|
53
|
+
- "test/cpp-pack.test.mjs"
|
|
54
|
+
- "test/custom-packs.test.mjs"
|
|
55
|
+
- "test/doctor-cache.test.mjs"
|
|
56
|
+
- "test/doctor-timeout.test.mjs"
|
|
57
|
+
- "test/every-language.test.mjs"
|
|
58
|
+
- "test/example-line.test.mjs"
|
|
59
|
+
- "test/file-size.test.mjs"
|
|
60
|
+
- "test/fix-plan.test.mjs"
|
|
61
|
+
- "test/frame-bench.test.mjs"
|
|
62
|
+
- "test/fresh-tmp.mjs"
|
|
63
|
+
- "test/full-stack.test.mjs"
|
|
64
|
+
- "test/gitignore.test.mjs"
|
|
65
|
+
- "test/global.test.mjs"
|
|
66
|
+
- "test/hook-proof.test.mjs"
|
|
67
|
+
- "test/hook-timing.test.mjs"
|
|
68
|
+
- "test/house.test.mjs"
|
|
69
|
+
- "test/hover.test.mjs"
|
|
70
|
+
- "test/install-fixes.test.mjs"
|
|
71
|
+
- "test/install.test.mjs"
|
|
72
|
+
- "test/instances.test.mjs"
|
|
73
|
+
- "test/kf-build-laws.test.mjs"
|
|
74
|
+
- "test/kf-fail-open.test.mjs"
|
|
75
|
+
- "test/kf-new-projects.test.mjs"
|
|
76
|
+
- "test/kf-refusals.test.mjs"
|
|
77
|
+
- "test/kit-cli.mjs"
|
|
78
|
+
- "test/kit-guards.test.mjs"
|
|
79
|
+
- "test/kit-lock.test.mjs"
|
|
80
|
+
- "test/layout-owned.test.mjs"
|
|
81
|
+
- "test/lock-release.test.mjs"
|
|
82
|
+
- "test/logging.test.mjs"
|
|
83
|
+
- "test/lookup.test.mjs"
|
|
84
|
+
- "test/micro.test.mjs"
|
|
85
|
+
- "test/names-sweep.test.mjs"
|
|
86
|
+
- "test/new.test.mjs"
|
|
87
|
+
- "test/no-personal-name.test.mjs"
|
|
88
|
+
- "test/noop.test.mjs"
|
|
89
|
+
- "test/npx-name.test.mjs"
|
|
90
|
+
- "test/once-only.test.mjs"
|
|
91
|
+
- "test/other-projects.test.mjs"
|
|
92
|
+
- "test/pack-manifest.test.mjs"
|
|
93
|
+
- "test/readme.test.mjs"
|
|
94
|
+
- "test/review-packs.test.mjs"
|
|
95
|
+
- "test/routes.test.mjs"
|
|
96
|
+
- "test/rule-tests.test.mjs"
|
|
97
|
+
- "test/rust-pack.test.mjs"
|
|
98
|
+
- "test/scan.test.mjs"
|
|
99
|
+
- "test/shape.test.mjs"
|
|
100
|
+
- "test/stored-text.test.mjs"
|
|
101
|
+
- "test/structure.test.mjs"
|
|
102
|
+
- "test/sub37-wb.test.mjs"
|
|
103
|
+
- "test/symmetry.test.mjs"
|
|
104
|
+
- "test/test-changed.test.mjs"
|
|
105
|
+
- "test/ui-craft.test.mjs"
|
|
106
|
+
- "test/ui-depth.test.mjs"
|
|
107
|
+
- "test/what-the-kit-finds.test.mjs"
|
|
108
|
+
- "test/worktrees.test.mjs"
|
|
109
|
+
- "test/writing.test.mjs"
|
|
110
|
+
- "**/*.py"
|
|
111
|
+
- "**/*.go"
|
|
112
|
+
- "**/*.rs"
|
|
113
|
+
- "**/*.rb"
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
# A value is bound, never written into the SQL text
|
|
117
|
+
|
|
118
|
+
<constraints>
|
|
119
|
+
Always do:
|
|
120
|
+
- Pass every value as a parameter: `$1`, `%s`, or the driver's tagged form (`` sql`...` ``, `$queryRaw`).
|
|
121
|
+
- Take a table or column name from an allow-list in the code, and quote it with the driver's identifier helper.
|
|
122
|
+
- Connect read-only work as a database role that can only read. A wrapper that promises read-only is no boundary.
|
|
123
|
+
|
|
124
|
+
Never do:
|
|
125
|
+
- Build SQL from a value with a template string, an f-string, `%`, `.format`, `Sprintf` or `format!`.
|
|
126
|
+
- Trust a value because it came from your own database or a form with validation.
|
|
127
|
+
</constraints>
|
|
128
|
+
|
|
129
|
+
## Check
|
|
130
|
+
`pg-bind-values`. A line that is right anyway says so: `// pg: allow <reason>`.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: postgres-safe-migration
|
|
3
|
+
description: Use when writing a migration for a table that holds data, when changing a column, adding a required column, an index or a constraint, or when a pg- check refuses a migration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Change a live Postgres table (one job: the change lands, and no query waits for it)
|
|
7
|
+
|
|
8
|
+
<constraints>
|
|
9
|
+
Always do:
|
|
10
|
+
1. Find out how big the table is and how busy, or ask. A new or empty table needs none of this.
|
|
11
|
+
2. Find out how the migration tool leaves a transaction, before writing `CONCURRENTLY`. Each has its own way:
|
|
12
|
+
| Tool | How |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Rails | `disable_ddl_transaction!` |
|
|
15
|
+
| Django | `atomic = False` |
|
|
16
|
+
| Diesel | `run_in_transaction = false` in `metadata.toml` |
|
|
17
|
+
| goose | `-- +goose NO TRANSACTION` |
|
|
18
|
+
| dbmate | `-- migrate:up transaction:false` |
|
|
19
|
+
| sqlx | `-- no-transaction` on the first line |
|
|
20
|
+
| Alembic | `autocommit_block()` |
|
|
21
|
+
| Knex | `exports.config = { transaction: false }` |
|
|
22
|
+
| node-pg-migrate | `pgm.noTransaction()` |
|
|
23
|
+
|
|
24
|
+
Drizzle runs every pending migration in one transaction: take the index out of its migrations and run it apart.
|
|
25
|
+
3. Add a required column:
|
|
26
|
+
```sql
|
|
27
|
+
ALTER TABLE t ADD COLUMN owner_id bigint; -- 1. nullable, instant
|
|
28
|
+
-- 2. the code writes it, and a backfill runs in batches
|
|
29
|
+
ALTER TABLE t ADD CONSTRAINT t_owner CHECK (owner_id IS NOT NULL) NOT VALID;
|
|
30
|
+
ALTER TABLE t VALIDATE CONSTRAINT t_owner; -- 3. a later transaction, no write lock
|
|
31
|
+
ALTER TABLE t ALTER COLUMN owner_id SET NOT NULL; -- 4. the valid check skips the scan
|
|
32
|
+
ALTER TABLE t DROP CONSTRAINT t_owner;
|
|
33
|
+
```
|
|
34
|
+
4. Add a foreign key: `ADD CONSTRAINT ... NOT VALID`, then `VALIDATE CONSTRAINT` in a later transaction. In one transaction it gains nothing.
|
|
35
|
+
5. Add a unique constraint: `CREATE UNIQUE INDEX CONCURRENTLY`, then `ADD CONSTRAINT ... UNIQUE USING INDEX`.
|
|
36
|
+
6. Change a type: check first whether it rewrites. `varchar` to `text` does not. Most others do: add a new column, write both, backfill, switch reads, drop the old one.
|
|
37
|
+
7. A failed `CONCURRENTLY` leaves an invalid index: drop it, then build it again.
|
|
38
|
+
8. Set a short `lock_timeout` on a statement that takes a strong lock, and retry it.
|
|
39
|
+
9. Deploy the code that can read both shapes first, and drop what is old only after nothing reads it.
|
|
40
|
+
|
|
41
|
+
Never do:
|
|
42
|
+
- Backfill a large table in one `UPDATE`.
|
|
43
|
+
- Rename a column in one step while old code still runs.
|
|
44
|
+
- Refuse `now()` or a constant default as unsafe: since PostgreSQL 11 both are instant.
|
|
45
|
+
</constraints>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<constraints>
|
|
8
8
|
Always do:
|
|
9
9
|
- Say what each address is: a file offset, a CPU address, a physical address, `seg:off`, a DSP `P:`, `X:` or `Y:` address, or a word index. Record the load map, and prove a base by scoring.
|
|
10
|
+
- Read a strictly rising run of 16-bit values as a pointer table or paired bytes until shown otherwise, never as a curve: check whether its values are CPU addresses in its own bank first.
|
|
10
11
|
- Find a table's own start and end before reading it. A `[FW]` tag covers the bytes, the boundary and the role separately.
|
|
11
12
|
- Index records by their marker over the whole image, and state the count and the gaps. A scan of one range found 47 of 174.
|
|
12
13
|
- Make an unpacked image answer "was this address ever written?". A zero read past where the loader stopped is unknown, and one blocked a law for a whole phase.
|
|
@@ -14,5 +14,5 @@ Always do:
|
|
|
14
14
|
|
|
15
15
|
Never do:
|
|
16
16
|
- Invent a global control on a reproduction. It rewrites every preset.
|
|
17
|
-
- Copy the machine's artwork: same sections, positions and labels, drawn your own way.
|
|
17
|
+
- Copy the machine's artwork, unless the brief asks for it and `"guards": { "instrument": { "artwork": "verbatim" } }` says so. By default a reproduction has the same sections, positions and labels, drawn your own way. With `"artwork": "verbatim"` the picture is the spec, and whoever ships it decides whether they may: log that as an open question.
|
|
18
18
|
</constraints>
|
|
@@ -15,6 +15,7 @@ Always do:
|
|
|
15
15
|
6. Read the disk OS's own strings.
|
|
16
16
|
7. Check the tool table before writing a disassembler.
|
|
17
17
|
8. Take ratios from a redrawn trace, never absolutes.
|
|
18
|
+
9. A source that exists and cannot be fetched (the host refuses automated access, the file is behind a login): record its name, where it is, what refused, and what it would have settled, in the project's decisions. Mark what rests on its absence as a `[gap]`, ask the user for the file when a person is there, and go on with the next source.
|
|
18
19
|
|
|
19
20
|
Never do:
|
|
20
21
|
- Start the disassembly before steps 1 to 4.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "rust-allow-needs-reason",
|
|
3
|
+
"why": "A lint turned off with no reason stays off long after the reason is gone.",
|
|
4
|
+
"rule": ".claude/rules/rust-lints-are-the-gate.md",
|
|
5
|
+
"globs": [
|
|
6
|
+
"**/*.rs"
|
|
7
|
+
],
|
|
8
|
+
"exclude": [
|
|
9
|
+
"**/target/**",
|
|
10
|
+
"**/vendor/**"
|
|
11
|
+
],
|
|
12
|
+
"language": "rust",
|
|
13
|
+
"pattern": "#!?\\[\\s*(?:allow|expect)\\s*\\((?![^\\]]*\\breason\\s*=)",
|
|
14
|
+
"bad": "#[allow(dead_code)]\nfn old() {}",
|
|
15
|
+
"good": "#[expect(dead_code, reason = \"called from the FFI side\")]\nfn old() {}"
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "rust-blocking-in-async",
|
|
3
|
+
"why": "A blocking call in an async fn stops every task on that thread. Hand it to spawn_blocking, or use the runtime's own version.",
|
|
4
|
+
"rule": ".claude/rules/rust-async-never-blocks.md",
|
|
5
|
+
"globs": [
|
|
6
|
+
"**/*.rs"
|
|
7
|
+
],
|
|
8
|
+
"exclude": [
|
|
9
|
+
"**/tests/**",
|
|
10
|
+
"**/test/**",
|
|
11
|
+
"**/benches/**",
|
|
12
|
+
"**/examples/**",
|
|
13
|
+
"**/fixtures/**",
|
|
14
|
+
"**/test_data/**",
|
|
15
|
+
"**/testdata/**",
|
|
16
|
+
"**/fuzz/**",
|
|
17
|
+
"**/xtask/**",
|
|
18
|
+
"**/target/**",
|
|
19
|
+
"**/vendor/**",
|
|
20
|
+
"**/build.rs",
|
|
21
|
+
"**/tests.rs",
|
|
22
|
+
"**/test.rs",
|
|
23
|
+
"**/*_test.rs",
|
|
24
|
+
"**/*_tests.rs",
|
|
25
|
+
"**/test_*.rs"
|
|
26
|
+
],
|
|
27
|
+
"language": "rust",
|
|
28
|
+
"notInTests": true,
|
|
29
|
+
"pattern": "\\basync\\s+fn\\b[^{;]*\\{(?:(?!\\bfn\\b|\\|\\||\\bmove\\s*\\||\\bspawn\\w*\\s*\\(|\\bunblock\\s*\\()[^}])*?\\b(?:thread::sleep|std::fs::\\w+|std::process::Command::new)\\s*\\(",
|
|
30
|
+
"bad": "async fn wait() {\n std::thread::sleep(ONE_SECOND);\n}",
|
|
31
|
+
"good": "async fn wait() {\n tokio::time::sleep(ONE_SECOND).await;\n}"
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "rust-if-chain-is-a-match",
|
|
3
|
+
"why": "Three branches on one value are a match, which the compiler can hold to every case. A comparison a pattern cannot express is left alone.",
|
|
4
|
+
"rule": ".claude/rules/rust-repeats-become-templates.md",
|
|
5
|
+
"globs": [
|
|
6
|
+
"**/*.rs"
|
|
7
|
+
],
|
|
8
|
+
"exclude": [
|
|
9
|
+
"**/tests/**",
|
|
10
|
+
"**/test/**",
|
|
11
|
+
"**/benches/**",
|
|
12
|
+
"**/examples/**",
|
|
13
|
+
"**/fixtures/**",
|
|
14
|
+
"**/test_data/**",
|
|
15
|
+
"**/testdata/**",
|
|
16
|
+
"**/fuzz/**",
|
|
17
|
+
"**/xtask/**",
|
|
18
|
+
"**/target/**",
|
|
19
|
+
"**/vendor/**",
|
|
20
|
+
"**/build.rs",
|
|
21
|
+
"**/tests.rs",
|
|
22
|
+
"**/test.rs",
|
|
23
|
+
"**/*_test.rs",
|
|
24
|
+
"**/*_tests.rs",
|
|
25
|
+
"**/test_*.rs"
|
|
26
|
+
],
|
|
27
|
+
"language": "rust",
|
|
28
|
+
"notInTests": true,
|
|
29
|
+
"pattern": "\\bif\\s+([\\w.]+)\\s*==\\s*(?:[A-Z]\\w*::[A-Z]\\w*|\"\\s*\"|-?\\d[\\w.]*)\\s*\\{(?:[^{}]|\\{[^{}]*\\})*\\}\\s*else\\s+if\\s+\\1\\s*==\\s*(?:[A-Z]\\w*::[A-Z]\\w*|\"\\s*\"|-?\\d[\\w.]*)\\s*\\{(?:[^{}]|\\{[^{}]*\\})*\\}\\s*else\\s+if\\s+\\1\\s*==\\s*(?:[A-Z]\\w*::[A-Z]\\w*|\"\\s*\"|-?\\d[\\w.]*)\\s*\\{",
|
|
30
|
+
"bad": "if kind == Kind::A {\n 1\n} else if kind == Kind::B {\n 2\n} else if kind == Kind::C {\n 3\n}",
|
|
31
|
+
"good": "match kind {\n Kind::A => 1,\n Kind::B => 2,\n Kind::C => 3,\n _ => 0,\n}"
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "rust-static-mut",
|
|
3
|
+
"why": "A static mut is a data race the compiler stopped checking. An extern declaration has no initializer and is left alone.",
|
|
4
|
+
"rule": ".claude/rules/rust-unsafe-is-proved.md",
|
|
5
|
+
"globs": [
|
|
6
|
+
"**/*.rs"
|
|
7
|
+
],
|
|
8
|
+
"exclude": [
|
|
9
|
+
"**/tests/**",
|
|
10
|
+
"**/test/**",
|
|
11
|
+
"**/benches/**",
|
|
12
|
+
"**/examples/**",
|
|
13
|
+
"**/fixtures/**",
|
|
14
|
+
"**/test_data/**",
|
|
15
|
+
"**/testdata/**",
|
|
16
|
+
"**/fuzz/**",
|
|
17
|
+
"**/xtask/**",
|
|
18
|
+
"**/target/**",
|
|
19
|
+
"**/vendor/**",
|
|
20
|
+
"**/build.rs",
|
|
21
|
+
"**/tests.rs",
|
|
22
|
+
"**/test.rs",
|
|
23
|
+
"**/*_test.rs",
|
|
24
|
+
"**/*_tests.rs",
|
|
25
|
+
"**/test_*.rs"
|
|
26
|
+
],
|
|
27
|
+
"language": "rust",
|
|
28
|
+
"notInTests": true,
|
|
29
|
+
"pattern": "(?<!')\\bstatic\\s+mut\\s+\\w+\\s*:[^;=]*=",
|
|
30
|
+
"bad": "static mut COUNT: u32 = 0;",
|
|
31
|
+
"good": "static COUNT: AtomicU32 = AtomicU32::new(0);"
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "rust-string-as-error",
|
|
3
|
+
"why": "An error the caller cannot match on can only be printed. A public function returns a type of its own.",
|
|
4
|
+
"rule": ".claude/rules/rust-errors-are-returned.md",
|
|
5
|
+
"globs": [
|
|
6
|
+
"**/*.rs"
|
|
7
|
+
],
|
|
8
|
+
"exclude": [
|
|
9
|
+
"**/tests/**",
|
|
10
|
+
"**/test/**",
|
|
11
|
+
"**/benches/**",
|
|
12
|
+
"**/examples/**",
|
|
13
|
+
"**/fixtures/**",
|
|
14
|
+
"**/test_data/**",
|
|
15
|
+
"**/testdata/**",
|
|
16
|
+
"**/fuzz/**",
|
|
17
|
+
"**/xtask/**",
|
|
18
|
+
"**/target/**",
|
|
19
|
+
"**/vendor/**",
|
|
20
|
+
"**/build.rs",
|
|
21
|
+
"**/tests.rs",
|
|
22
|
+
"**/test.rs",
|
|
23
|
+
"**/*_test.rs",
|
|
24
|
+
"**/*_tests.rs",
|
|
25
|
+
"**/test_*.rs",
|
|
26
|
+
"**/main.rs",
|
|
27
|
+
"**/bin/**"
|
|
28
|
+
],
|
|
29
|
+
"language": "rust",
|
|
30
|
+
"notInTests": true,
|
|
31
|
+
"pattern": "\\bpub\\s+(?:async\\s+)?fn\\b[^{;]*?->\\s*(?:std::result::)?Result<(?:[^<>(),;{]|\\([^()]*\\)|<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)*,\\s*(?:String|&\\'static\\s+str|Box<dyn\\s+(?:std::error::)?Error\\b[^>]*>)\\s*>",
|
|
32
|
+
"bad": "pub fn parse(s: &str) -> Result<Port, String> { Err(s.to_string()) }",
|
|
33
|
+
"good": "pub fn parse(s: &str) -> Result<Port, ParseError> { Err(ParseError::Empty) }"
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "rust-stub-left-in",
|
|
3
|
+
"why": "A stub or a debug print compiles, so it ships. `unimplemented!` stays allowed: it is a decision, not a leftover.",
|
|
4
|
+
"rule": ".claude/rules/rust-lints-are-the-gate.md",
|
|
5
|
+
"globs": [
|
|
6
|
+
"**/*.rs"
|
|
7
|
+
],
|
|
8
|
+
"exclude": [
|
|
9
|
+
"**/tests/**",
|
|
10
|
+
"**/test/**",
|
|
11
|
+
"**/benches/**",
|
|
12
|
+
"**/examples/**",
|
|
13
|
+
"**/fixtures/**",
|
|
14
|
+
"**/test_data/**",
|
|
15
|
+
"**/testdata/**",
|
|
16
|
+
"**/fuzz/**",
|
|
17
|
+
"**/xtask/**",
|
|
18
|
+
"**/target/**",
|
|
19
|
+
"**/vendor/**",
|
|
20
|
+
"**/build.rs",
|
|
21
|
+
"**/tests.rs",
|
|
22
|
+
"**/test.rs",
|
|
23
|
+
"**/*_test.rs",
|
|
24
|
+
"**/*_tests.rs",
|
|
25
|
+
"**/test_*.rs"
|
|
26
|
+
],
|
|
27
|
+
"language": "rust",
|
|
28
|
+
"notInTests": true,
|
|
29
|
+
"pattern": "\\b(?:todo|dbg)!\\s*[(\\[{]",
|
|
30
|
+
"bad": "fn load() -> Config { todo!() }",
|
|
31
|
+
"good": "fn load() -> Result<Config> { read_config() }"
|
|
32
|
+
}
|