@devrik-tools/claude-gates 0.4.0 → 0.7.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 (57) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.es.md +39 -4
  3. package/README.md +34 -5
  4. package/cli/config.mjs +126 -124
  5. package/cli/init.mjs +303 -276
  6. package/cli/install.mjs +281 -175
  7. package/cli/materialize.mjs +103 -102
  8. package/cli/registry.mjs +139 -136
  9. package/cli/smoke-fixtures.json +65 -0
  10. package/cli/task.mjs +140 -140
  11. package/package.json +1 -1
  12. package/plugins/gates/.claude-plugin/plugin.json +1 -1
  13. package/plugins/gates/hooks/ask-adoption.mjs +147 -147
  14. package/plugins/gates/hooks/doctor.mjs +207 -207
  15. package/plugins/gates/hooks/gates/atomic-commit/index.mjs +229 -0
  16. package/plugins/gates/hooks/gates/audit-before-build/index.mjs +110 -88
  17. package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +50 -50
  18. package/plugins/gates/hooks/gates/bash-commands/index.mjs +215 -215
  19. package/plugins/gates/hooks/gates/brief-approved/index.mjs +216 -0
  20. package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +269 -265
  21. package/plugins/gates/hooks/gates/capability-map/index.mjs +701 -0
  22. package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +527 -501
  23. package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +48 -43
  24. package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -83
  25. package/plugins/gates/hooks/gates/force-parallel/index.mjs +134 -119
  26. package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -134
  27. package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -187
  28. package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -260
  29. package/plugins/gates/hooks/gates/lint-commit/index.mjs +152 -149
  30. package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -180
  31. package/plugins/gates/hooks/gates/never-assume/index.mjs +59 -58
  32. package/plugins/gates/hooks/gates/no-blocking/index.mjs +163 -148
  33. package/plugins/gates/hooks/gates/no-coauthor/index.mjs +127 -0
  34. package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +183 -0
  35. package/plugins/gates/hooks/gates/protected-paths/index.mjs +149 -144
  36. package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +91 -89
  37. package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +263 -159
  38. package/plugins/gates/hooks/gates/risk-level/index.mjs +265 -263
  39. package/plugins/gates/hooks/gates/root-cause-first/index.mjs +57 -56
  40. package/plugins/gates/hooks/gates/root-whitelist/index.mjs +211 -131
  41. package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +181 -184
  42. package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -256
  43. package/plugins/gates/hooks/gates/staged-lint/index.mjs +187 -0
  44. package/plugins/gates/hooks/gates/stop-pending/index.mjs +169 -164
  45. package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -187
  46. package/plugins/gates/hooks/gates/tool-map/index.mjs +168 -143
  47. package/plugins/gates/hooks/hooks.json +61 -0
  48. package/plugins/gates/hooks/lib/config.mjs +179 -172
  49. package/plugins/gates/hooks/lib/hook-io.mjs +367 -357
  50. package/plugins/gates/hooks/lib/signals.mjs +172 -127
  51. package/plugins/gates/hooks/wiring-check.mjs +227 -227
  52. package/plugins/tasks/.claude-plugin/plugin.json +1 -1
  53. package/plugins/tasks/hooks/hooks.json +26 -26
  54. package/plugins/tasks/hooks/lib/task-store.mjs +217 -197
  55. package/plugins/tasks/hooks/register-requests.mjs +145 -145
  56. package/plugins/tasks/hooks/session-tasks.mjs +108 -108
  57. package/registry.json +192 -1
@@ -1,131 +1,211 @@
1
- // root-whitelist — denies creating a new file or folder at the project's ROOT unless it
2
- // is on the declared whitelist. Migrated from ~/.claude/hooks/guard-root-whitelist.mjs.
3
- // A root grows orphaned files silently: nothing else stops a stray file from landing next
4
- // to package.json. Only the top level of the project is governed — anything nested is
5
- // this gate's business only insofar as its first path segment is the new thing at the root.
6
- //
7
- // ── What a project can configure (params) ───────────────────────────────────────────
8
- // rootFilesWhitelist root-level file names allowed to be created. Replaces the
9
- // built-in list wholesale.
10
- // rootFoldersWhitelist root-level folder names allowed to be created. Replaces the
11
- // built-in list wholesale.
12
- // The defaults live here, in the source, so a project reads them and knows exactly what
13
- // its override replaces. A dotfile/dotfolder (.gitignore, .claude) is always allowed —
14
- // dotfiles are a different, well-known convention this gate does not police.
15
-
16
- import { basename, isAbsolute, resolve, sep } from 'node:path';
17
- import {
18
- runGate,
19
- deny,
20
- toolInGroups,
21
- writtenPathOf,
22
- shellWrittenPaths,
23
- } from '../../lib/hook-io.mjs';
24
-
25
- const GATE_ID = 'root-whitelist';
26
- const CONFIG_KEY = 'blockPathsOutsideRootWhitelist';
27
-
28
- const DEFAULT_ROOT_FILES_WHITELIST = [
29
- 'AGENTS.md',
30
- 'CLAUDE.md',
31
- 'README.md',
32
- 'LICENSE',
33
- 'package.json',
34
- 'package-lock.json',
35
- 'tsconfig.json',
36
- 'eslint.config.mjs',
37
- 'eslint.config.js',
38
- 'eslint.config.cjs',
39
- '.prettierrc',
40
- '.prettierrc.json',
41
- '.prettierignore',
42
- '.eslintignore',
43
- 'cspell.json',
44
- '.cspell.json',
45
- '.gitignore',
46
- '.env',
47
- '.env.example',
48
- ];
49
-
50
- const DEFAULT_ROOT_FOLDERS_WHITELIST = [
51
- 'src',
52
- 'tests',
53
- 'docs',
54
- 'scripts',
55
- 'plugins',
56
- ];
57
-
58
- // The whitelist verdict for one target path. Returns a deny reason string when the path is a
59
- // non-whitelisted new thing at the project root, or null when it is allowed (outside the root,
60
- // a dotfile, or whitelisted). Shared by the write-tool path and every shell-created path, so a
61
- // `printf x > basura.txt` is judged by the same rule as a Write to basura.txt.
62
- function rootWhitelistViolation(target, filesWhitelist, foldersWhitelist) {
63
- if (!target) return null;
64
-
65
- // Some tools send a path relative to cwd rather than absolute. resolve() leaves an
66
- // already-absolute path merely normalized ('..' collapsed, separators fixed), and resolves
67
- // a relative one against process.cwd() — so either shape lands on the same absolute path.
68
- const normalized = isAbsolute(target)
69
- ? resolve(target)
70
- : resolve(process.cwd(), target);
71
- const projectRoot = process.cwd() + sep;
72
-
73
- // A path outside the project (scratchpad, temp, another repo) is not at its root — not this
74
- // rule's business. Judged AFTER resolving, so a relative path is judged by where it lands.
75
- if (!normalized.startsWith(projectRoot)) return null;
76
-
77
- const relativePath = normalized.slice(projectRoot.length);
78
-
79
- if (!relativePath.includes(sep)) {
80
- const fileName = basename(relativePath);
81
- if (fileName.startsWith('.') || filesWhitelist.has(fileName.toLowerCase())) return null;
82
- return `'${fileName}' at the project root is not on the whitelist (${[...filesWhitelist].join(', ')}).`;
83
- }
84
-
85
- const topDirectory = relativePath.split(sep)[0];
86
- if (topDirectory.startsWith('.') || foldersWhitelist.has(topDirectory.toLowerCase())) {
87
- return null;
88
- }
89
- return `Folder '${topDirectory}' at the project root is not on the whitelist (${[...foldersWhitelist].join(', ')}).`;
90
- }
91
-
92
- runGate(
93
- {
94
- id: GATE_ID,
95
- configKey: CONFIG_KEY,
96
- enabledByDefault: true,
97
- defaultParams: {
98
- rootFilesWhitelist: DEFAULT_ROOT_FILES_WHITELIST,
99
- rootFoldersWhitelist: DEFAULT_ROOT_FOLDERS_WHITELIST,
100
- },
101
- },
102
- ({ toolName, toolInput, parameters }) => {
103
- const isWrite = toolInGroups(toolName, ['write']);
104
- const isShell = toolInGroups(toolName, ['shell']);
105
- if (!isWrite && !isShell) return;
106
-
107
- // Windows filesystems are case-insensitive: 'Package.json' and 'package.json' are the same
108
- // file. Comparing lowercase on both sides avoids a false positive on a case difference.
109
- const filesWhitelist = new Set(
110
- (parameters.rootFilesWhitelist ?? []).map((name) => name.toLowerCase()),
111
- );
112
- const foldersWhitelist = new Set(
113
- (parameters.rootFoldersWhitelist ?? []).map((name) => name.toLowerCase()),
114
- );
115
-
116
- // The paths to judge: a write tool's target, OR every path a shell command creates via a
117
- // redirection / touch / tee / cp / mv. The latter closes the hole where `> basura.txt`
118
- // created a root file that a Write to the same name would have been denied. This does not
119
- // resolve a dynamically built path (`> "$f"`) — that honest limitation is the boundary of
120
- // a regex, and is why the write-tool path (which carries a concrete file_path) stays the
121
- // primary, most reliable surface.
122
- const targets = isShell
123
- ? shellWrittenPaths(String(toolInput?.command ?? toolInput?.CommandLine ?? ''))
124
- : [writtenPathOf(toolInput)];
125
-
126
- for (const target of targets) {
127
- const reason = rootWhitelistViolation(target, filesWhitelist, foldersWhitelist);
128
- if (reason) deny(GATE_ID, reason);
129
- }
130
- },
131
- );
1
+ // root-whitelist — denies creating a new file or folder at the project's ROOT unless it
2
+ // is on the declared whitelist. Migrated from ~/.claude/hooks/guard-root-whitelist.mjs.
3
+ // A root grows orphaned files silently: nothing else stops a stray file from landing next
4
+ // to package.json. Only the top level of the project is governed — anything nested is
5
+ // this gate's business only insofar as its first path segment is the new thing at the root.
6
+ //
7
+ // ── What a project can configure (params) ───────────────────────────────────────────
8
+ // rootFilesWhitelist root-level file names allowed to be created. Replaces the
9
+ // built-in list wholesale.
10
+ // rootFoldersWhitelist root-level folder names allowed to be created. Replaces the
11
+ // built-in list wholesale.
12
+ // The defaults live here, in the source, so a project reads them and knows exactly what
13
+ // its override replaces. A dotfile/dotfolder (.gitignore, .claude) is always allowed —
14
+ // dotfiles are a different, well-known convention this gate does not police.
15
+
16
+ import { basename, isAbsolute, resolve, sep } from 'node:path';
17
+ import {
18
+ runGate,
19
+ deny,
20
+ toolInGroups,
21
+ writtenPathOf,
22
+ shellWrittenPaths,
23
+ } from '../../lib/hook-io.mjs';
24
+
25
+ const GATE_ID = 'root-whitelist';
26
+ const CONFIG_KEY = 'blockPathsOutsideRootWhitelist';
27
+
28
+ const DEFAULT_ROOT_FILES_WHITELIST = [
29
+ 'AGENTS.md',
30
+ 'CLAUDE.md',
31
+ 'README.md',
32
+ 'LICENSE',
33
+ 'package.json',
34
+ 'package-lock.json',
35
+ 'tsconfig.json',
36
+ 'eslint.config.mjs',
37
+ 'eslint.config.js',
38
+ 'eslint.config.cjs',
39
+ '.prettierrc',
40
+ '.prettierrc.json',
41
+ '.prettierignore',
42
+ '.eslintignore',
43
+ 'cspell.json',
44
+ '.cspell.json',
45
+ '.gitignore',
46
+ '.env',
47
+ '.env.example',
48
+ ];
49
+
50
+ // Root folders that real projects legitimately create. The old list (src/tests/docs/
51
+ // scripts/plugins) was calibrated to THIS repo and wrongly blocked the root folders every
52
+ // common framework creates — app (Next/Nuxt/Laravel/Expo), lib, public, dist, components,
53
+ // pages, api, and so on. A root-level folder is a deliberate, structural choice; the value
54
+ // of this gate is catching a stray FILE dropped next to package.json, not second-guessing a
55
+ // project's directory layout. So the folder whitelist is broad (real framework/tooling
56
+ // conventions) while the file whitelist stays the strict part. A project still overrides
57
+ // either list wholesale via config.
58
+ const DEFAULT_ROOT_FOLDERS_WHITELIST = [
59
+ // source / app code
60
+ 'src',
61
+ 'app',
62
+ 'lib',
63
+ 'pkg',
64
+ 'packages',
65
+ 'apps',
66
+ 'libs',
67
+ 'components',
68
+ 'pages',
69
+ 'api',
70
+ 'routes',
71
+ 'server',
72
+ 'client',
73
+ 'shared',
74
+ 'core',
75
+ 'modules',
76
+ 'features',
77
+ // web / framework conventions
78
+ 'public',
79
+ 'static',
80
+ 'assets',
81
+ 'styles',
82
+ 'templates',
83
+ 'views',
84
+ 'layouts',
85
+ 'middleware',
86
+ 'plugins',
87
+ 'store',
88
+ 'stores',
89
+ 'hooks',
90
+ 'composables',
91
+ 'utils',
92
+ 'helpers',
93
+ 'services',
94
+ 'config',
95
+ 'configs',
96
+ 'i18n',
97
+ 'locales',
98
+ // tests / docs / tooling
99
+ 'tests',
100
+ 'test',
101
+ '__tests__',
102
+ 'e2e',
103
+ 'cypress',
104
+ 'docs',
105
+ 'doc',
106
+ 'examples',
107
+ 'example',
108
+ 'scripts',
109
+ 'tools',
110
+ 'bin',
111
+ // build / generated (present in many repos, not worth blocking)
112
+ 'dist',
113
+ 'build',
114
+ 'out',
115
+ 'coverage',
116
+ 'node_modules',
117
+ 'vendor',
118
+ 'target',
119
+ // infra / ops
120
+ 'migrations',
121
+ 'prisma',
122
+ 'db',
123
+ 'database',
124
+ 'docker',
125
+ '.github',
126
+ ];
127
+
128
+ // The whitelist verdict for one target path. Returns a deny reason string when the path is a
129
+ // non-whitelisted new thing at the project root, or null when it is allowed (outside the root,
130
+ // a dotfile, or whitelisted). Shared by the write-tool path and every shell-created path, so a
131
+ // `printf x > basura.txt` is judged by the same rule as a Write to basura.txt.
132
+ function rootWhitelistViolation(target, filesWhitelist, foldersWhitelist) {
133
+ if (!target) return null;
134
+
135
+ // Some tools send a path relative to cwd rather than absolute. resolve() leaves an
136
+ // already-absolute path merely normalized ('..' collapsed, separators fixed), and resolves
137
+ // a relative one against process.cwd() — so either shape lands on the same absolute path.
138
+ const normalized = isAbsolute(target)
139
+ ? resolve(target)
140
+ : resolve(process.cwd(), target);
141
+ const projectRoot = process.cwd() + sep;
142
+
143
+ // A path outside the project (scratchpad, temp, another repo) is not at its root — not this
144
+ // rule's business. Judged AFTER resolving, so a relative path is judged by where it lands.
145
+ if (!normalized.startsWith(projectRoot)) return null;
146
+
147
+ const relativePath = normalized.slice(projectRoot.length);
148
+
149
+ if (!relativePath.includes(sep)) {
150
+ const fileName = basename(relativePath);
151
+ if (fileName.startsWith('.') || filesWhitelist.has(fileName.toLowerCase()))
152
+ return null;
153
+ return `'${fileName}' at the project root is not on the whitelist (${[...filesWhitelist].join(', ')}).`;
154
+ }
155
+
156
+ const topDirectory = relativePath.split(sep)[0];
157
+ if (
158
+ topDirectory.startsWith('.') ||
159
+ foldersWhitelist.has(topDirectory.toLowerCase())
160
+ ) {
161
+ return null;
162
+ }
163
+ return `Folder '${topDirectory}' at the project root is not on the whitelist (${[...foldersWhitelist].join(', ')}).`;
164
+ }
165
+
166
+ runGate(
167
+ {
168
+ id: GATE_ID,
169
+ configKey: CONFIG_KEY,
170
+ enabledByDefault: true,
171
+ defaultParams: {
172
+ rootFilesWhitelist: DEFAULT_ROOT_FILES_WHITELIST,
173
+ rootFoldersWhitelist: DEFAULT_ROOT_FOLDERS_WHITELIST,
174
+ },
175
+ },
176
+ ({ toolName, toolInput, parameters }) => {
177
+ const isWrite = toolInGroups(toolName, ['write']);
178
+ const isShell = toolInGroups(toolName, ['shell']);
179
+ if (!isWrite && !isShell) return;
180
+
181
+ // Windows filesystems are case-insensitive: 'Package.json' and 'package.json' are the same
182
+ // file. Comparing lowercase on both sides avoids a false positive on a case difference.
183
+ const filesWhitelist = new Set(
184
+ (parameters.rootFilesWhitelist ?? []).map((name) => name.toLowerCase()),
185
+ );
186
+ const foldersWhitelist = new Set(
187
+ (parameters.rootFoldersWhitelist ?? []).map((name) => name.toLowerCase()),
188
+ );
189
+
190
+ // The paths to judge: a write tool's target, OR every path a shell command creates via a
191
+ // redirection / touch / tee / cp / mv. The latter closes the hole where `> basura.txt`
192
+ // created a root file that a Write to the same name would have been denied. This does not
193
+ // resolve a dynamically built path (`> "$f"`) — that honest limitation is the boundary of
194
+ // a regex, and is why the write-tool path (which carries a concrete file_path) stays the
195
+ // primary, most reliable surface.
196
+ const targets = isShell
197
+ ? shellWrittenPaths(
198
+ String(toolInput?.command ?? toolInput?.CommandLine ?? ''),
199
+ )
200
+ : [writtenPathOf(toolInput)];
201
+
202
+ for (const target of targets) {
203
+ const reason = rootWhitelistViolation(
204
+ target,
205
+ filesWhitelist,
206
+ foldersWhitelist,
207
+ );
208
+ if (reason) deny(GATE_ID, reason);
209
+ }
210
+ },
211
+ );