@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.
Files changed (148) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +275 -18
  3. package/bin/claude-packs.mjs +18 -10
  4. package/configurations/base/config.json +1 -1
  5. package/configurations/cpp/config.json +2 -1
  6. package/configurations/full-stack/config.json +2 -1
  7. package/configurations/go/config.json +3 -1
  8. package/configurations/node/config.json +3 -1
  9. package/configurations/python/config.json +3 -1
  10. package/configurations/rust/config.json +106 -0
  11. package/configurations/synth-creator/config.json +1 -1
  12. package/global/hooks/lib/shell-writes.mjs +2 -293
  13. package/lib/build-queue.mjs +22 -0
  14. package/lib/claude-md.mjs +4 -2
  15. package/lib/config-cli.mjs +1 -1
  16. package/lib/coverage.mjs +10 -2
  17. package/lib/doctor.mjs +14 -3
  18. package/lib/fix-plan.mjs +2 -1
  19. package/lib/found-folders.mjs +25 -0
  20. package/lib/global.mjs +3 -3
  21. package/lib/hook-proof.mjs +24 -7
  22. package/lib/kit-command.mjs +14 -0
  23. package/lib/layout.mjs +27 -3
  24. package/lib/logs.mjs +45 -5
  25. package/lib/new.mjs +1 -1
  26. package/lib/packs.mjs +4 -2
  27. package/lib/rule-tests.mjs +42 -0
  28. package/lib/scan-detect.mjs +41 -5
  29. package/lib/scan.mjs +5 -2
  30. package/lib/suggest.mjs +16 -3
  31. package/lib/sync.mjs +5 -2
  32. package/lib/workorder.mjs +26 -0
  33. package/package.json +1 -1
  34. package/packs/audio-core/hooks/param-id-lock.mjs +59 -16
  35. package/packs/audio-core/pack.json +5 -0
  36. package/packs/audio-core/rules/a-control-is-proved-through-its-input.md +26 -0
  37. package/packs/audio-core/rules/cpu-worst-case-per-block.md +3 -1
  38. package/packs/audio-core/rules/house-control-behaviours.md +1 -1
  39. package/packs/audio-core/rules/scoped-no-denormals.md +1 -1
  40. package/packs/audio-core/rules/straight-on-and-aligned.md +1 -1
  41. package/packs/audio-core/rules/ui-matches-reference-look.md +2 -0
  42. package/packs/audio-core/skills/analyse-reference-look/SKILL.md +25 -11
  43. package/packs/audio-core/skills/prove-control/SKILL.md +2 -0
  44. package/packs/base/checks/one-walk-per-template.json +1 -1
  45. package/packs/base/hooks/code-shape.mjs +10 -6
  46. package/packs/base/hooks/dispatch-once.mjs +58 -21
  47. package/packs/base/hooks/folder-law.mjs +3 -0
  48. package/packs/base/hooks/git-guard.mjs +22 -109
  49. package/packs/base/hooks/lesson-checks.mjs +9 -1
  50. package/packs/base/hooks/lib/code-shapes.mjs +11 -6
  51. package/packs/base/hooks/lib/crash-policy.mjs +47 -0
  52. package/packs/base/hooks/lib/dup-index.mjs +6 -1
  53. package/packs/base/hooks/lib/dup.mjs +2 -2
  54. package/packs/base/hooks/lib/event-log.mjs +4 -3
  55. package/packs/base/hooks/lib/git-adds.mjs +2 -1
  56. package/packs/base/hooks/lib/git-auth.mjs +44 -0
  57. package/packs/base/hooks/lib/git-guard-selftest.mjs +93 -0
  58. package/packs/base/hooks/lib/languages.mjs +5 -2
  59. package/packs/base/hooks/lib/layout.mjs +6 -2
  60. package/packs/base/hooks/lib/making-check-selftest.mjs +3 -0
  61. package/packs/base/hooks/lib/near-copies.mjs +1 -1
  62. package/packs/base/hooks/lib/program-read.mjs +6 -3
  63. package/packs/base/hooks/lib/rust-scan.mjs +139 -0
  64. package/packs/base/hooks/lib/shape-lists.mjs +31 -0
  65. package/packs/base/hooks/lib/shape-prose.mjs +78 -0
  66. package/packs/base/hooks/lib/shape.mjs +18 -39
  67. package/packs/base/hooks/lib/turn.mjs +6 -1
  68. package/packs/base/hooks/lib/unread-writes.mjs +24 -0
  69. package/packs/base/hooks/making-check.mjs +12 -7
  70. package/packs/base/hooks/run-hooks.mjs +15 -7
  71. package/packs/base/hooks/shape-law.mjs +11 -4
  72. package/packs/base/hooks/suite-once.mjs +22 -6
  73. package/packs/base/rules/a-control-is-seen-to-fire.md +1 -1
  74. package/packs/base/rules/behaviour-lives-with-its-kind.md +1 -1
  75. package/packs/base/rules/bug-fix-starts-red.md +1 -1
  76. package/packs/base/rules/field-map-over-if-runs.md +1 -1
  77. package/packs/base/rules/instances-by-index-identities-by-id.md +2 -2
  78. package/packs/base/rules/lookup-over-if-chains.md +1 -1
  79. package/packs/base/rules/plan-and-logs.md +1 -1
  80. package/packs/base/rules/refusals-are-judged.md +1 -0
  81. package/packs/base/rules/test-the-template-once.md +2 -1
  82. package/packs/base/rules/unattended-runs.md +15 -0
  83. package/packs/base/skills/new-project/SKILL.md +23 -0
  84. package/packs/base/skills/write-a-workorder/SKILL.md +41 -0
  85. package/packs/cpp/checks/cpp-find-with-the-library.json +2 -2
  86. package/packs/go/pack.json +5 -0
  87. package/packs/hook-proof/rules/a-refusal-is-exit-2.md +2 -2
  88. package/packs/instrument-shell/rules/render-the-extremes.md +2 -2
  89. package/packs/midi/checks/position-not-integrated.json +5 -1
  90. package/packs/postgres/checks/pg-bind-values.json +33 -0
  91. package/packs/postgres/checks/pg-concurrently-in-txn.json +18 -0
  92. package/packs/postgres/checks/pg-fk-not-valid.json +18 -0
  93. package/packs/postgres/checks/pg-index-concurrently.json +18 -0
  94. package/packs/postgres/checks/pg-no-float-money.json +19 -0
  95. package/packs/postgres/checks/pg-no-money.json +18 -0
  96. package/packs/postgres/checks/pg-no-required-column.json +18 -0
  97. package/packs/postgres/checks/pg-no-volatile-default.json +18 -0
  98. package/packs/postgres/checks/pg-not-exists.json +18 -0
  99. package/packs/postgres/checks/pg-timestamptz.json +18 -0
  100. package/packs/postgres/hooks/db-wipe-guard.mjs +95 -0
  101. package/packs/postgres/pack.json +48 -0
  102. package/packs/postgres/rules/postgres-migrations-never-stop-traffic.md +28 -0
  103. package/packs/postgres/rules/postgres-one-pool-per-process.md +21 -0
  104. package/packs/postgres/rules/postgres-queries-are-null-safe-and-bounded.md +26 -0
  105. package/packs/postgres/rules/postgres-types-say-what-they-hold.md +22 -0
  106. package/packs/postgres/rules/postgres-values-are-bound.md +130 -0
  107. package/packs/postgres/skills/postgres-safe-migration/SKILL.md +45 -0
  108. package/packs/reverse-engineering/rules/addresses-name-their-space.md +1 -0
  109. package/packs/reverse-engineering/rules/reproduction-or-original.md +1 -1
  110. package/packs/reverse-engineering/skills/gather-sources/SKILL.md +1 -0
  111. package/packs/rust/checks/rust-allow-needs-reason.json +16 -0
  112. package/packs/rust/checks/rust-blocking-in-async.json +32 -0
  113. package/packs/rust/checks/rust-if-chain-is-a-match.json +32 -0
  114. package/packs/rust/checks/rust-static-mut.json +32 -0
  115. package/packs/rust/checks/rust-string-as-error.json +34 -0
  116. package/packs/rust/checks/rust-stub-left-in.json +32 -0
  117. package/packs/rust/checks/rust-unsafe-needs-safety.json +31 -0
  118. package/packs/rust/checks/rust-unwrap-in-library.json +34 -0
  119. package/packs/rust/checks/rust-wildcard-dependency.json +16 -0
  120. package/packs/rust/clippy.toml +5 -0
  121. package/packs/rust/pack.json +27 -0
  122. package/packs/rust/rules/rust-async-never-blocks.md +20 -0
  123. package/packs/rust/rules/rust-borrow-before-you-clone.md +19 -0
  124. package/packs/rust/rules/rust-errors-are-returned.md +22 -0
  125. package/packs/rust/rules/rust-lints-are-the-gate.md +23 -0
  126. package/packs/rust/rules/rust-repeats-become-templates.md +28 -0
  127. package/packs/rust/rules/rust-unsafe-is-proved.md +21 -0
  128. package/packs/rust/skills/rust-extract-template/SKILL.md +50 -0
  129. package/packs/rust/skills/rust-port/SKILL.md +22 -0
  130. package/packs/rust/skills/rust-review/SKILL.md +24 -0
  131. package/packs/rust/tools/rust-lints.toml +19 -0
  132. package/packs/sound-design/skills/bounce-a-plugin/SKILL.md +40 -0
  133. package/packs/sound-design/tools/vst3host/build.sh +73 -0
  134. package/packs/sound-design/tools/vst3host/host_events.h +101 -0
  135. package/packs/sound-design/tools/vst3host/options.h +172 -0
  136. package/packs/sound-design/tools/vst3host/render.cpp +245 -0
  137. package/packs/sound-design/tools/vst3host/session.h +241 -0
  138. package/packs/synth-creator/hooks/build-guard.mjs +7 -7
  139. package/packs/synth-creator/hooks/protect-vendor.mjs +2 -1
  140. package/packs/synth-creator/hooks/stage-guard.mjs +1 -1
  141. package/packs/synth-creator/rules/timing-from-synthcore.md +1 -1
  142. package/packs/synth-creator/skills/build-through-the-queue/SKILL.md +4 -2
  143. package/packs/synth-creator/tools/buildq.mjs +74 -0
  144. package/prompts/workorder/PROMPT.md +53 -0
  145. package/prompts/workorder/run.sh +36 -0
  146. package/prompts/workorder/workorder.md +59 -0
  147. package/global/hooks/lib/program-read.mjs +0 -196
  148. package/global/hooks/lib/program-writes.mjs +0 -177
@@ -11,20 +11,21 @@
11
11
  // - a selector for a concept (pickSlot, slotFor, firstSlot, ownerSlot) not listed in the manifest;
12
12
  // - a new type that holds instances (an index member, or made N times in an array) whose concept has no row.
13
13
  // No design/shape.json: nothing is declared, so nothing is judged (doctor fails a project of a configuration that needs one). `--selftest`.
14
- import { readFileSync } from 'node:fs';
15
14
  import { dirname, join } from 'node:path';
16
15
  import { fileURLToPath } from 'node:url';
17
16
  import { checkoutRoot, insideProject, relInCheckout, ROOT } from './lib/project.mjs';
18
17
  import { addedFindings, findInstanceLiterals, formatFinding, isExcluded, isTestFile, logRefusal, readShape } from './lib/shape.mjs';
19
18
  import { addedDefs, findSelectorDefs, findUnknownTypes } from './lib/shape-code.mjs';
19
+ import { declaredAmount } from './lib/shape-prose.mjs';
20
20
  import { bashOf, editOf, runHook, scratchProject, verdicts, writeOf } from './lib/selftest.mjs';
21
21
  import { writesOf } from './lib/writes.mjs';
22
22
  import { asHook, isMain } from './lib/hook-log.mjs';
23
23
 
24
24
  const TEST_NOTE = 'a test that drives one instance proves that instance only: loop over every instance, or take the instance as a parameter';
25
25
 
26
- function judgeText(rel, before, after, shape) {
27
- const lits = addedFindings(before, after, (t) => findInstanceLiterals(t, rel, shape));
26
+ function judgeText(rel, before, after, shape, root) {
27
+ // a call that takes a count looks like one that takes an index; the declaration tells them apart (KF-17)
28
+ const lits = addedFindings(before, after, (t) => findInstanceLiterals(t, rel, shape)).filter((f) => !(f.call && declaredAmount(root, f.call, after)));
28
29
  const test = isTestFile(rel);
29
30
  const out = lits.map((f) => `${formatFinding(f, rel)}${test ? `\n ${TEST_NOTE}` : ''}`);
30
31
  const concepts = lits.map((f) => f.concept);
@@ -45,7 +46,7 @@ function main(text) {
45
46
  if (!shape || shape.error) continue;
46
47
  const rel = relInCheckout(w.path);
47
48
  if (rel.startsWith('..') || isExcluded(rel, shape)) continue;
48
- const { out, concepts } = judgeText(rel, w.before, w.after, shape);
49
+ const { out, concepts } = judgeText(rel, w.before, w.after, shape, root);
49
50
  if (!out.length) continue;
50
51
  logRefusal(ROOT, 'shape-law', concepts);
51
52
  process.stderr.write(`BLOCKED by shape-law: this write names one instance of a concept that design/shape.json declares (KWB-1).\n${out.slice(0, 10).map((l) => ` ${l}`).join('\n')}${out.length > 10 ? `\n ... and ${out.length - 10} more` : ''}\n` +
@@ -119,6 +120,12 @@ function selftest() {
119
120
  ['allowed: a multi-word caps label\'s value ("AIM PLANETS 100", KWB-22)', w('src/ui/View.cpp', '// every planet off, AIM PLANETS 100: nothing heard\n'), 0],
120
121
  ['allowed: a multi-word caps label before the word ("a NOTES MONO slot", KWB-22)', w('src/engines/Engine.cpp', '// a NOTES MONO slot plays only its newest note\n'), 0],
121
122
  ['allowed: a number past the count is a value ("slot 40 ms", KWB-22)', w('docs/notes.md', 'Each slot 40 ms later than the last.\n'), 0],
123
+ ['allowed: a hex address after the word (KF-13)', w('docs/notes.md', 'Slot 0x18000 appears at window 0x8000.\n'), 0],
124
+ ['allowed: first as a search (KF-17)', w('src/world/Find.cpp', '// the first slot with this name, or nothing\n'), 0],
125
+ ['allowed: a sentence in a string (KF-17)', w('tests/msg_test.cpp', 'expect (done, "and on that bank has a second slot");\n'), 0],
126
+ ['allowed: a sum, not a compare (KF-20)', w('src/world/Sum.cpp', 'bool same = each + 1 == slot;\n'), 0],
127
+ ['a short key in a string is still an instance (KF-17)', w('tests/key_test.cpp', 'set ("slot 2", 3);\n'), 2],
128
+ ['the first of the concept is still an instance (KF-17)', w('docs/notes.md', 'The first slot holds the pad.\n'), 2],
122
129
  ['allowed: the whole range "slots 1-4"', w('docs/notes.md', 'Slots 1-4 all play every note.\n'), 0],
123
130
  ['allowed: a line already there, edited and moved (KWB-23)', runHook(hook, dir, editOf(join(dir, 'src/world/Old.cpp'), 'void f() { if (slot == 2) g(); }\n// PLACE', '// PLACE\nvoid f() { if (slot == 2) g(); } // closed: B-1')), 0],
124
131
  ['an edit adding a second copy of a literal already there (KWB-23)', runHook(hook, dir, editOf(join(dir, 'src/world/Old.cpp'), '// PLACE', 'void h() { if (slot == 2) g(); }')), 2],
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // suite-once (PreToolUse, Bash): the whole test suite runs once, as the gate before landing, never while working (one
3
3
  // agent ran all 425 tests on 1 core; KWB-41: a session ran the kit's suite again and again, 12 minutes each, on code that had not
4
- // changed). A whole-suite command (`npm test`, `pnpm|yarn|bun test`, a bare `ctest`, the project's "commands".test in layout.json) is
5
- // refused while the checkout it runs in has uncommitted tracked changes: uncommitted means still working, and working runs only the
4
+ // changed). A whole-suite command (`npm test`, `pnpm|yarn|bun test`, a bare `ctest`, `cargo test` with no name, the project's "commands".test in layout.json with any
5
+ // options after it, a build wrapper's `build.sh test`: KF-23, the wrapper is the route the kit's own build rule names) is refused while the checkout it runs in has uncommitted tracked changes: uncommitted means still working, and working runs only the
6
6
  // tests the change reaches (rule: test-only-what-changed; skill: pick-tests). Once the work is committed the gate passes. Named tests
7
7
  // (`npm test -- a.test.mjs`, `ctest -R x`) are always allowed. `--selftest` proves it.
8
8
  import { execFileSync } from 'node:child_process';
@@ -13,11 +13,20 @@ import { bashOf, git, put, runHook, scratchProject, verdicts } from './lib/selft
13
13
  import { judgeEvent } from './lib/writes.mjs';
14
14
  import { asHook, isMain } from './lib/hook-log.mjs';
15
15
 
16
- const WHOLE = /^(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?test|ctest(?:\s+(?!-R\b|--tests-regex\b|-L\b|-I\b)\S+)*)$/;
17
- const PREFIX = /^(?:[A-Za-z_]\w*=\S*\s+|(?:env|command|nohup|time|exec)\s+|timeout\s+\S+\s+)+/;
16
+ const WHOLE = /^(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?test|ctest(?:\s+(?!-R\b|--tests-regex\b|-L\b|-I\b)\S+)*|cargo\s+(?:test|nextest\s+run)(?:\s+-\S+)*)$/;
17
+ const PREFIX = /^(?:[A-Za-z_]\w*=\S*\s+|(?:env|command|nohup|time|exec|\S*buildq)\s+|timeout\s+\S+\s+)+/;
18
18
  const REDIRECT = /\s*(?:\d?>>?|&>)&?\s*\S+/g;
19
+ const WRAPPER = /^\S*\bbuild\.sh\s+test(?:\s+-\S+)*$/;
19
20
 
20
21
  const clean = (part) => part.trim().replace(PREFIX, '').replace(REDIRECT, '').trim();
22
+ const bare = (words) => words.replace(/^(?:bash|sh)\s+/, '').replace(/^\.\//, '');
23
+
24
+ // The project's own command, as it is or with options; a name after it (`build.sh test one_gate`) is a named test.
25
+ function ownSuite(words, test) {
26
+ if (!test) return false;
27
+ const [w, t] = [bare(words), bare(test)];
28
+ return w === t || (w.startsWith(`${t} `) && w.slice(t.length).trim().split(/\s+/).every((a) => a.startsWith('-')));
29
+ }
21
30
 
22
31
  function uncommitted(dir) {
23
32
  try {
@@ -30,12 +39,12 @@ function uncommitted(dir) {
30
39
  export function refusal(command, cwd) {
31
40
  const body = command.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n\1\b/g, '').replace(/'[^']*'|"(?:\\.|[^"\\])*"/g, "''");
32
41
  let here = cwd;
33
- for (const part of body.split(/&&|\|\||;|\||\n/)) {
42
+ for (const part of body.split(/&&|\|\||;|\||\n|(?<![&>])&(?![&>])/)) {
34
43
  const cd = part.trim().match(/^cd\s+(\S+)$/);
35
44
  if (cd) { here = resolve(here, cd[1]); continue; }
36
45
  const words = clean(part);
37
46
  const { commands = {} } = readLayout(claudeRoot(here));
38
- if (!WHOLE.test(words) && words !== commands.test) continue;
47
+ if (!WHOLE.test(words) && !WRAPPER.test(bare(words)) && !ownSuite(words, commands.test)) continue;
39
48
  const dirty = uncommitted(checkoutRoot(resolve(here, 'x')));
40
49
  if (!dirty.length) continue;
41
50
  return `\`${words}\` runs the whole suite, and this checkout has uncommitted work:\n ${dirty.slice(0, 8).join('\n ')}\n` +
@@ -65,7 +74,14 @@ function selftest() {
65
74
  ['pnpm, a run form and a prefix', sh('CI=1 timeout 300 pnpm run test'), 2],
66
75
  ['a redirect and a pipe change nothing', sh('npm test > /tmp/log 2>&1 | tail -3'), 2],
67
76
  ['a bare ctest', sh('ctest --output-on-failure'), 2],
77
+ ['cargo test of the whole workspace', sh('cargo test --workspace --all-features'), 2],
78
+ ['cargo nextest of everything', sh('cargo nextest run'), 2],
79
+ ['cargo test of one test', sh('cargo test parses_header'), 0],
80
+ ['cargo test of one crate', sh('cargo test -p core parses_header'), 0],
68
81
  ['the project\'s own whole-suite command', sh('bash tools/all.sh'), 2],
82
+ ['its command with an option, in the background', sh('nohup tools/all.sh --full-gate > /tmp/log 2>&1 &'), 2],
83
+ ['a build wrapper, with no command declared', sh('tools/build.sh test --full-gate'), 2],
84
+ ['a build wrapper with one test named', sh('tools/build.sh test one_gate'), 0],
69
85
  ['after other steps', sh('git status && npm test'), 2],
70
86
  ['named tests', sh('npm test -- a.test.mjs'), 0],
71
87
  ['the changed-only run', sh('npm run test:changed'), 0],
@@ -1,5 +1,5 @@
1
1
  ---
2
- # concepts: cpp, js, tests
2
+ # concepts: cpp, js, tests, python, go, rust
3
3
  paths:
4
4
  - "placeholder"
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- # concepts: cpp, js
2
+ # concepts: cpp, js, python, go, rust
3
3
  paths:
4
4
  - "placeholder"
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- # concepts: tests, cpp
2
+ # concepts: tests, cpp, python, go, rust, js
3
3
  paths:
4
4
  - "placeholder"
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- # concepts: cpp, js
2
+ # concepts: cpp, js, python, go, rust
3
3
  paths:
4
4
  - "placeholder"
5
5
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- # concepts: cpp, tests, notes, plan
2
+ # concepts: cpp, tests, notes, plan, python, go, rust, js
3
3
  paths:
4
4
  - "placeholder"
5
5
  ---
@@ -51,4 +51,4 @@ Never do:
51
51
  - `shape_symmetry` (generated at configure time by `npx --no-install @thedecipherist/claude-packs symmetry gen`, run by `.claude/tools/shape-symmetry.mjs`): for each many
52
52
  concept, the members of every instance are the same set, and every one of the N·(N-1)/2 pairs swaps the same. Its selftest plants a
53
53
  missing member and a swap difference.
54
- - The project's own tests: the generated IDs equal the frozen lock for every instance.
54
+ - The generated IDs equal the frozen lock for every instance, in the project's own tests.
@@ -1,5 +1,5 @@
1
1
  ---
2
- # concepts: cpp, js
2
+ # concepts: cpp, js, python, go, rust
3
3
  paths:
4
4
  - "placeholder"
5
5
  ---
@@ -11,7 +11,7 @@ Always do:
11
11
  - Tick a step in `build.md` only when its "done when" was measured, or seen in the opened app. Every step's start and finish time goes in the log (the project's step tool writes them).
12
12
  - Strike a step that is dropped through, with the reason and the date. A reopened step keeps its line, marked REOPENED.
13
13
  - Log a bug in `bugs.md` the moment it is found, before it is fixed. A fixed bug moves to Fixed with its cause, the fix, the date and the gate that now catches it.
14
- - Record in `docs/laws.md` every house law the project leaves, with the user's yes. Put a row in `docs/licences.md` before any file is copied in, with its source, commit and licence.
14
+ - Record in `docs/laws.md` every house law the project leaves, with the user's yes. Put a row in `docs/licences.md` before any file is copied in, with its source, commit and licence. Read what that row will say before choosing the source: a small fix taken from an installed package brings its licence with it, and only its copyright file says so.
15
15
  - Send a question the spec leaves open to the user as a `d-n`, with the spec's recommendation. Never decide it by guessing.
16
16
  - Write internal docs plainly. The `humanize` skill is for public writing only, not for specs, logs or briefs.
17
17
 
@@ -11,6 +11,7 @@ Always do:
11
11
  - Mark a refusal you met, once you know: `npx --no-install @thedecipherist/claude-packs logs mark <id> right|wrong "<why>"`. `logs refusals --open` lists them.
12
12
  - Mark it wrong when correct work was refused, and report the hook.
13
13
  - Read `logs` before changing or retiring a hook.
14
+ - Treat a crash as its own thing: it refused nothing, so it has no verdict. `logs crashes` lists them, and `logs clear <hook>|all fixed|superseded "<why>"` clears them, for doctor, once they are.
14
15
 
15
16
  Never do:
16
17
  - Delete or edit a log. Logs rotate, and `.claude/logs/` keeps them all.
@@ -1,5 +1,5 @@
1
1
  ---
2
- # concepts: cpp, js
2
+ # concepts: cpp, js, python, go, rust
3
3
  paths:
4
4
  - "placeholder"
5
5
  ---
@@ -14,6 +14,7 @@ Always do:
14
14
  - Test behaviour once, on the template, with an instance chosen as a parameter: any instance must do.
15
15
  - Prove the instances really are the template with one structural pass over the data: every instance is built by the same factory, with the same members. That is a read, not a run of the behaviour.
16
16
  - Test an instance's own data where it differs: its label, its range, its ID, as a table.
17
+ - Where one instance can be unhooked while its row, class and binding stay intact, add one cheap pass of every instance through the real input path: each moves something, and no more. It is not a behaviour walk: keep it apart from them, in its own every-instance pass, and name it so.
17
18
 
18
19
  Never do:
19
20
  - Loop a behaviour test over every instance of one template: `one-walk-per-template` refuses a loop that presses, clicks or drags them.
@@ -0,0 +1,15 @@
1
+ # An unattended run takes the recommended choice and records it
2
+
3
+ Where a brief says the user is away, nobody is there to ask. The rule to stop and report, and the rule to ask before a new folder, are for when someone is there.
4
+
5
+ <constraints>
6
+ Always do:
7
+ - Keep every law: an unattended run takes the recommended choice, never a way around a hook.
8
+ - Write each choice, and why, in the project's decisions file as it is made, so the user can overturn it.
9
+ - Report a hook that blocks correct work as you would with someone there: log it, mark the refusal, and fix it where the fix belongs.
10
+ - Stop only for what cannot be undone or leaves the project: publishing, deleting what you did not make, spending money, writing to another repo.
11
+
12
+ Never do:
13
+ - Wait for an answer that cannot come, or end a run with work left that you could have done.
14
+ - Take a brief's silence as permission to skip a gate.
15
+ </constraints>
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: new-project
3
+ description: Use when starting a new project, when the kit has just been added to a repo, or when code needs a folder the layout does not have.
4
+ ---
5
+
6
+ # Start a project so the laws reach its code (one job: every source file is in a folder the layout names)
7
+
8
+ A file can only be judged where the layout knows it. `folder-law` keeps new source inside the layout from the first write, and the rules for each language follow it there.
9
+
10
+ <constraints>
11
+ Always do:
12
+ 1. For a new project, run `new <name>`: the house files, a git repo on a work branch, and the kit installed. Name its kind with `--config <name>`; `configs` lists them.
13
+ 2. For a repo with code in it, run `suggest`, then `init --auto`. The install adds the top-level folders your source is in to `.claude/layout.json`, and says which.
14
+ 3. Read the install's last lines, then run `doctor --strict`. It fails on a source file no rule covers and a rule that matches nothing. Fix those now, while they are few.
15
+ 4. Decide where each kind of thing goes before the first file of that kind, and write it in `.claude/layout.json`: `folders` for where, `kinds` for what goes there.
16
+ 5. When code needs a new folder, add it to `layout.json` first, then run `update`, then write the file. `folder-law` refuses it the other way round.
17
+ 6. In a project that keeps code in several folders, run `survey` and give Claude the prompt, so the layout is fitted to the real tree.
18
+
19
+ Never do:
20
+ - Write source first and sort the folders out later: a file outside the layout gets no folder law, and a project that grows that way has to be moved.
21
+ - Put a new kind of code in an unrelated folder because that one is allowed.
22
+ - Add a broad folder like `.` or `**` to the layout to make a refusal go away.
23
+ </constraints>
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: write-a-workorder
3
+ description: Use when a project or a large piece of work should be built with nobody there, when planning work across many sessions, or when writing or changing a work order.
4
+ ---
5
+
6
+ # Write a work order (one job: a brief that needs nobody to answer anything)
7
+
8
+ `workorder <name>` writes `docs/workorders/<name>/`: `workorder.md` (the steps), `PROMPT.md` (how to run with nobody there) and `run.sh` (session after session). You write the steps.
9
+
10
+ <constraints>
11
+ Always do:
12
+ 1. Say what it is, and what finished looks like, in one paragraph a stranger could act on.
13
+ 2. State the honest position: what is known, what is not, and what cannot be proved. It stops a report claiming more than the work shows.
14
+ 3. List every source by path or URL, with what it gives and what is missing. A run cannot ask where something is.
15
+ 4. Name the foundation: what is reused, and must not be written again. Name existing work to learn from, and what not to take from it.
16
+ 5. Order the steps so proof comes before what depends on it: find out, then prove what you found, then build on it. A step that can fail badly goes early.
17
+ 6. End every step with **Done when:** a number or a test someone can run. "Works" and "looks right" cannot be measured, so they cannot finish.
18
+ 7. Say what is forbidden: what stays out of git, and what no document may claim.
19
+ 8. Make each new test show it can fail: a planted fault, seen red.
20
+ 9. Tell the run what to record: decisions as it makes them, bugs before fixing them, and one line of progress per item, so a new session can resume from files alone.
21
+ 10. Keep a last step that reads the finished work as a senior engineer would, and a report with counts.
22
+ 11. Before leaving it running, read `PROMPT.md` and `run.sh`, and check that `doctor --strict` is OK and the first step can start.
23
+ 12. To change a run in progress, edit `PROMPT.md` and commit it. A run should refuse an instruction it cannot trace to its order or to you, so a file dropped beside it is rightly ignored.
24
+
25
+ Never do:
26
+ - Leave a decision open. Give the recommended choice, or tell the run to take one and record it.
27
+ - Write a step that needs a person: a login, a purchase, a judgement of taste. Put it first, or list it as open for the end.
28
+ - Let a checkpoint read as the end. A run finishes only on the DONE file.
29
+ - Ask for a whole product in one step. A step is what can be proved in one go.
30
+ </constraints>
31
+
32
+ A step that works:
33
+
34
+ ```markdown
35
+ ## W2. The import
36
+
37
+ 1. Read every record in `data/orders.csv` into the schema of W1.
38
+ 2. Reject a bad row by name, with its line number. Never skip one silently.
39
+
40
+ **Done when:** all 41,206 rows of the sample load, the 12 known bad rows are each rejected with the right reason, and a planted bad row turns the test red.
41
+ ```
@@ -6,7 +6,7 @@
6
6
  "globs": [],
7
7
  "exclude": ["**/build/**"],
8
8
  "code": true,
9
- "pattern": "for\\s*\\(\\s*(?:const\\s+)?auto\\s*&&?\\s*(\\w+)\\s*:\\s*[^;{}\\n]+\\)\\s*\\{?\\s*if\\s*\\(\\s*\\1\\s*(?:\\.|->)\\s*\\w+\\s*==",
9
+ "pattern": "for\\s*\\(\\s*(?:const\\s+)?auto\\s*&&?\\s*(\\w+)\\s*:\\s*[^;{}\\n]+\\)\\s*\\{?\\s*if\\s*\\(\\s*\\1\\s*(?:\\.|->)\\s*\\w+\\s*==[^\\n]*\\)\\s*(?:\\{[^{}]{0,200}?)?\\b(?:return|break)\\b",
10
10
  "bad": "for (const auto& c : shown().controls) if (c.id == id) return &c;",
11
- "good": "const auto it = std::ranges::find (controls, id, &Control::id);\nfor (const auto& c : controls) total += c.width;\nfor (const auto& c : controls) if (c.width > limit) wide.push_back (c);"
11
+ "good": "const auto it = std::ranges::find (controls, id, &Control::id);\nfor (const auto& c : controls) total += c.width;\nfor (const auto& c : controls) if (c.width > limit) wide.push_back (c);\nfor (const auto& target : targets) if (target.kind == none) continue;"
12
12
  }
@@ -17,5 +17,10 @@
17
17
  ],
18
18
  "engines": {
19
19
  "claude-packs": ">=0.25.0"
20
+ },
21
+ "concepts": {
22
+ "go": [
23
+ "**/*.go"
24
+ ]
20
25
  }
21
26
  }
@@ -11,7 +11,7 @@ A guard that fails fails silently: the call goes ahead, and nothing says the gua
11
11
  <constraints>
12
12
  Always do:
13
13
  - Refuse with exit 2, the reason on stderr. Exit 2 is the only code that blocks: exit 1 is a non-blocking error, and a hook killed at its timeout does not block either.
14
- - Decide what a guard does when it cannot judge: say so, in the code. `doctor` turns a crash into a red check, but the call that crashed already went through.
14
+ - Decide what a guard does when it cannot judge: say so, in the code. Under `run-hooks` a crash on a tool call refuses that call once and says so; the same call again goes through, logged. A hook run on its own has no such net.
15
15
  - Keep a guard fast and small enough to finish inside its timeout.
16
16
  - Prove a new hook with the `prove-a-hook` skill: its bad case seen refused, through the real settings entry.
17
17
 
@@ -25,4 +25,4 @@ Never do:
25
25
  </constraints>
26
26
 
27
27
  ## Check
28
- `doctor` is red for a hook that crashed since it last changed (`.claude/hooks.log`) and for one no settings entry runs. It cannot see a hook killed at its timeout: that leaves no line.
28
+ `doctor` is red for a hook that crashed since it or a file in `hooks/lib/` last changed (`.claude/hooks.log`), until `logs clear <hook> fixed|superseded "<why>"`, and for one no settings entry runs. It cannot see a hook killed at its timeout: that leaves no line.
@@ -17,9 +17,9 @@ paths:
17
17
  Always do:
18
18
  - Render the widest and narrowest machine, at opening width, on both axes, against a declared minimum display.
19
19
  - Give each parameter exactly one page.
20
- - Build a reproduction with its own sections, fractional positions, control types and labels, drawn your own way.
20
+ - Build a reproduction with its own sections, fractional positions, control types and labels, drawn your own way, unless `"guards": { "instrument": { "artwork": "verbatim" } }` says the brief asks for the machine's own look.
21
21
  - Use pages only on machines designed with them.
22
22
 
23
23
  Never do:
24
- - Copy the artwork.
24
+ - Copy the artwork without that setting.
25
25
  </constraints>
@@ -16,7 +16,11 @@
16
16
  "**/JUCE/**",
17
17
  "**/node_modules/**",
18
18
  "**/*Clock*",
19
- "**/*clock*"
19
+ "**/*clock*",
20
+ "**/tests/**",
21
+ "**/test/**",
22
+ "**/*_test.*",
23
+ "**/*Test.*"
20
24
  ],
21
25
  "pattern": "\\b\\w*(?:[pP]os|[pP]pq|[bB]eat)\\w*\\s*\\+=[^;\\n]*(?:bpm|[tT]empo)",
22
26
  "bad": "posBeats += n * bpm / (60.0 * sr);",
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "pg-bind-values",
3
+ "why": "A value in the SQL text is an injection. Bind it as a parameter: $1, %s, or the driver's own tagged form.",
4
+ "rule": ".claude/rules/postgres-values-are-bound.md",
5
+ "globs": [
6
+ "**/*.ts",
7
+ "**/*.tsx",
8
+ "**/*.js",
9
+ "**/*.mjs",
10
+ "**/*.py",
11
+ "**/*.go",
12
+ "**/*.rs"
13
+ ],
14
+ "exclude": [
15
+ "**/node_modules/**",
16
+ "**/vendor/**",
17
+ "**/target/**",
18
+ "**/dist/**",
19
+ "**/tests/**",
20
+ "**/test/**",
21
+ "**/__tests__/**",
22
+ "**/migrations/**",
23
+ "**/*.test.*",
24
+ "**/*.spec.*",
25
+ "**/*_test.go",
26
+ "**/*_test.py",
27
+ "**/test_*.py"
28
+ ],
29
+ "pattern": "^(?![ \\t]*(?://|#))(?![^\\n]*pg: allow)[^\\n]*?(?:(?<!\\bsql|\\bSQL|\\$queryRaw|\\$executeRaw|\\braw|sql<[^`\\n]{0,60}>)`\\s*(?:SELECT|INSERT\\s+INTO|UPDATE|DELETE\\s+FROM)\\b[^`]*?(?:'%?\\$\\{|(?:=|<|>|LIKE|ILIKE)\\s*\\$\\{|\\bIN\\s*\\(\\s*\\$\\{(?!\\w*(?:placeholder|param)))|\\bf(?:\"\"\"|\\'\\'\\'|\"|\\')\\s*(?:SELECT|INSERT\\s+INTO|UPDATE|DELETE\\s+FROM)\\b[^\\n]*?(?:'%?\\{|(?:=|<|>|LIKE|ILIKE)\\s*\\{|\\bIN\\s*\\(\\s*\\{(?!\\w*(?:placeholder|param)))|Sprintf\\(\\s*[\"`]\\s*(?:SELECT|INSERT\\s+INTO|UPDATE|DELETE\\s+FROM)\\b[^\\n]*?(?:'%?%[sv]|(?:=|<|>|LIKE|ILIKE)\\s*%[sv])|format!\\(\\s*r?#*\"\\s*(?:SELECT|INSERT\\s+INTO|UPDATE|DELETE\\s+FROM)\\b[^\\n]*?(?:'%?\\{|(?:=|<|>|LIKE|ILIKE)\\s*\\{|\\bIN\\s*\\(\\s*\\{(?!\\w*(?:placeholder|param))))",
30
+ "flags": "im",
31
+ "bad": "await db.query(`SELECT * FROM users WHERE email = '${email}'`);",
32
+ "good": "await pool.query(\"SELECT * FROM users WHERE email = $1\", [email]);"
33
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-concurrently-in-txn",
3
+ "why": "CREATE INDEX CONCURRENTLY cannot run in a transaction: the migration fails when it runs.",
4
+ "rule": ".claude/rules/postgres-migrations-never-stop-traffic.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^[ \\t]*(?:BEGIN|START\\s+TRANSACTION)\\b[^\\n]*;(?:(?!^[ \\t]*(?:COMMIT|END|ROLLBACK)\\b)[\\s\\S])*?\\bCONCURRENTLY\\b",
15
+ "flags": "im",
16
+ "bad": "BEGIN;\nCREATE INDEX CONCURRENTLY i ON t (a);\nCOMMIT;",
17
+ "good": "CREATE INDEX CONCURRENTLY i ON t (a);"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-fk-not-valid",
3
+ "why": "A foreign key locks both tables while it scans. Add it NOT VALID, then VALIDATE it in another transaction.",
4
+ "rule": ".claude/rules/postgres-migrations-never-stop-traffic.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)[ \\t]*ALTER\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(?:ONLY\\s+)?(?:\"?\\w+\"?\\.)?\"?(\\w+)\\b\"?(?<!CREATE\\s+(?:UNLOGGED\\s+)?TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:\"?\\w+\"?\\.)?\"?\\1\"?[\\s(][\\s\\S]*)\\s+ADD\\s+(?:CONSTRAINT\\s+\\S+\\s+)?FOREIGN\\s+KEY\\b(?![^;]{0,400}\\bNOT\\s+VALID\\b)",
15
+ "flags": "im",
16
+ "bad": "ALTER TABLE t ADD FOREIGN KEY (a) REFERENCES u (id);",
17
+ "good": "ALTER TABLE t ADD FOREIGN KEY (a) REFERENCES u (id) NOT VALID;"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-index-concurrently",
3
+ "why": "A plain CREATE INDEX blocks every write to a live table until it finishes.",
4
+ "rule": ".claude/rules/postgres-migrations-never-stop-traffic.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)[ \\t]*CREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+(?!CONCURRENTLY\\b)(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:\"?\\w+\"?\\s+)?ON\\s+(?:ONLY\\s+)?(?:\"?\\w+\"?\\.)?\"?(\\w+)\\b\"?(?<!CREATE\\s+(?:UNLOGGED\\s+)?TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:\"?\\w+\"?\\.)?\"?\\1\"?[\\s(][\\s\\S]*)",
15
+ "flags": "im",
16
+ "bad": "CREATE INDEX i ON t (a);",
17
+ "good": "CREATE TABLE t (a int);\nCREATE INDEX i ON t (a);"
18
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "id": "pg-no-float-money",
3
+ "why": "A float cannot hold 0.10 exactly, so amounts drift.",
4
+ "rule": ".claude/rules/postgres-types-say-what-they-hold.md",
5
+ "globs": [
6
+ "**/*.sql",
7
+ "**/*.prisma"
8
+ ],
9
+ "exclude": [
10
+ "**/node_modules/**",
11
+ "**/vendor/**",
12
+ "**/target/**",
13
+ "**/dist/**"
14
+ ],
15
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)(?:[^\\n]*[(,\\s])?\"?(?:\\w*_)?(?:price|prices|amount|amounts|cost|costs|balance|balances|fee|fees|tax|taxes|total|totals)(?:_\\w*)?\"?\\s+(?:REAL|FLOAT\\d?|DOUBLE\\s+PRECISION|Float)\\b",
16
+ "flags": "im",
17
+ "bad": "unit_price double precision NOT NULL",
18
+ "good": "unit_price numeric(12, 2) NOT NULL"
19
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-no-money",
3
+ "why": "The money type rounds to one locale's fraction and holds no currency.",
4
+ "rule": ".claude/rules/postgres-types-say-what-they-hold.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)(?:[^\\n]*[(,\\s])?\"?\\w+\"?\\s+MONEY\\b",
15
+ "flags": "im",
16
+ "bad": "price money NOT NULL",
17
+ "good": "price_cents bigint NOT NULL"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-no-required-column",
3
+ "why": "A NOT NULL column with no default fails on any table that has rows. Add it nullable, backfill, then tighten.",
4
+ "rule": ".claude/rules/postgres-migrations-never-stop-traffic.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)[ \\t]*(?:ALTER\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(?:ONLY\\s+)?(?:\"?\\w+\"?\\.)?\"?(\\w+)\\b\"?(?<!CREATE\\s+(?:UNLOGGED\\s+)?TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(?:\"?\\w+\"?\\.)?\"?\\1\"?[\\s(][\\s\\S]*)\\s+)ADD\\s+(?:COLUMN\\s+)?(?:IF\\s+NOT\\s+EXISTS\\s+)?(?!CONSTRAINT\\b|PRIMARY\\b|UNIQUE\\b|FOREIGN\\b|CHECK\\b|EXCLUDE\\b)\"?\\w+\"?\\s+(?![^,;]*\\b(?:DEFAULT|GENERATED)\\b)[^,;]*\\bNOT\\s+NULL\\b",
15
+ "flags": "im",
16
+ "bad": "ALTER TABLE t ADD COLUMN owner_id bigint NOT NULL;",
17
+ "good": "ALTER TABLE t ADD COLUMN owner_id bigint NOT NULL DEFAULT 0;"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-no-volatile-default",
3
+ "why": "A volatile default rewrites the whole table under an exclusive lock. `now()` is not volatile, and stays allowed.",
4
+ "rule": ".claude/rules/postgres-migrations-never-stop-traffic.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)[ \\t]*(?:ALTER\\s+TABLE\\s+\\S+\\s+)?ADD\\s+(?:COLUMN\\s+)?(?:IF\\s+NOT\\s+EXISTS\\s+)?\"?\\w+\"?\\s+[^,;]*?\\bDEFAULT\\b[^,;]*?\\b(?:gen_random_uuid|uuid_generate_v\\d\\w*|uuidv[47]|clock_timestamp|random|nextval|timeofday)\\s*\\(",
15
+ "flags": "im",
16
+ "bad": "ALTER TABLE t ADD COLUMN token uuid DEFAULT gen_random_uuid();",
17
+ "good": "ALTER TABLE t ADD COLUMN created_at timestamptz DEFAULT now();"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-not-exists",
3
+ "why": "NOT IN with a subquery returns no rows at all when the subquery holds one NULL, and plans badly.",
4
+ "rule": ".claude/rules/postgres-queries-are-null-safe-and-bounded.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)[^\\n]*\\bNOT\\s+IN\\s*\\(\\s*SELECT\\b",
15
+ "flags": "im",
16
+ "bad": "DELETE FROM a WHERE id NOT IN (SELECT a_id FROM b);",
17
+ "good": "DELETE FROM a WHERE NOT EXISTS (SELECT 1 FROM b WHERE b.a_id = a.id);"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "pg-timestamptz",
3
+ "why": "timestamp keeps no time zone, so the same value means different moments on different servers.",
4
+ "rule": ".claude/rules/postgres-types-say-what-they-hold.md",
5
+ "globs": [
6
+ "**/*.sql"
7
+ ],
8
+ "exclude": [
9
+ "**/node_modules/**",
10
+ "**/vendor/**",
11
+ "**/target/**",
12
+ "**/dist/**"
13
+ ],
14
+ "pattern": "^(?![ \\t]*--)(?![^\\n]*pg: allow)(?:[^\\n]*[(,\\s])?\"?\\w+\"?\\s+TIMESTAMP\\b(?:\\s*\\(\\d\\))?(?!\\s+WITH\\s+TIME\\s+ZONE)",
15
+ "flags": "im",
16
+ "bad": "created_at timestamp NOT NULL",
17
+ "good": "created_at timestamptz NOT NULL"
18
+ }