@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.es.md +39 -4
- package/README.md +34 -5
- package/cli/config.mjs +126 -124
- package/cli/init.mjs +303 -276
- package/cli/install.mjs +281 -175
- package/cli/materialize.mjs +103 -102
- package/cli/registry.mjs +139 -136
- package/cli/smoke-fixtures.json +65 -0
- package/cli/task.mjs +140 -140
- package/package.json +1 -1
- package/plugins/gates/.claude-plugin/plugin.json +1 -1
- package/plugins/gates/hooks/ask-adoption.mjs +147 -147
- package/plugins/gates/hooks/doctor.mjs +207 -207
- package/plugins/gates/hooks/gates/atomic-commit/index.mjs +229 -0
- package/plugins/gates/hooks/gates/audit-before-build/index.mjs +110 -88
- package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +50 -50
- package/plugins/gates/hooks/gates/bash-commands/index.mjs +215 -215
- package/plugins/gates/hooks/gates/brief-approved/index.mjs +216 -0
- package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +269 -265
- package/plugins/gates/hooks/gates/capability-map/index.mjs +701 -0
- package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +527 -501
- package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +48 -43
- package/plugins/gates/hooks/gates/feature-catalog/index.mjs +83 -83
- package/plugins/gates/hooks/gates/force-parallel/index.mjs +134 -119
- package/plugins/gates/hooks/gates/forge-flow/index.mjs +134 -134
- package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +187 -187
- package/plugins/gates/hooks/gates/intent-flow/index.mjs +260 -260
- package/plugins/gates/hooks/gates/lint-commit/index.mjs +152 -149
- package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +180 -180
- package/plugins/gates/hooks/gates/never-assume/index.mjs +59 -58
- package/plugins/gates/hooks/gates/no-blocking/index.mjs +163 -148
- package/plugins/gates/hooks/gates/no-coauthor/index.mjs +127 -0
- package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +183 -0
- package/plugins/gates/hooks/gates/protected-paths/index.mjs +149 -144
- package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +91 -89
- package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +263 -159
- package/plugins/gates/hooks/gates/risk-level/index.mjs +265 -263
- package/plugins/gates/hooks/gates/root-cause-first/index.mjs +57 -56
- package/plugins/gates/hooks/gates/root-whitelist/index.mjs +211 -131
- package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +181 -184
- package/plugins/gates/hooks/gates/sdd-specs/index.mjs +256 -256
- package/plugins/gates/hooks/gates/staged-lint/index.mjs +187 -0
- package/plugins/gates/hooks/gates/stop-pending/index.mjs +169 -164
- package/plugins/gates/hooks/gates/test-matrix/index.mjs +187 -187
- package/plugins/gates/hooks/gates/tool-map/index.mjs +168 -143
- package/plugins/gates/hooks/hooks.json +61 -0
- package/plugins/gates/hooks/lib/config.mjs +179 -172
- package/plugins/gates/hooks/lib/hook-io.mjs +367 -357
- package/plugins/gates/hooks/lib/signals.mjs +172 -127
- package/plugins/gates/hooks/wiring-check.mjs +227 -227
- package/plugins/tasks/.claude-plugin/plugin.json +1 -1
- package/plugins/tasks/hooks/hooks.json +26 -26
- package/plugins/tasks/hooks/lib/task-store.mjs +217 -197
- package/plugins/tasks/hooks/register-requests.mjs +145 -145
- package/plugins/tasks/hooks/session-tasks.mjs +108 -108
- package/registry.json +192 -1
|
@@ -1,159 +1,263 @@
|
|
|
1
|
-
// reuse-before-build — do not reinvent the wheel. Before building a new script, gate,
|
|
2
|
-
// verifier
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
if (!
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
);
|
|
1
|
+
// reuse-before-build — do not reinvent the wheel. Before building a new script, gate,
|
|
2
|
+
// verifier, helper, composable, component or generic utility, consult what already solves
|
|
3
|
+
// the need: the per-project tool map (a record of past findings), the installed dependencies
|
|
4
|
+
// (package.json), and — declared by the author — a local→cloud audit. This gate is the
|
|
5
|
+
// deterministic half: it objects when a build is starting without evidence of a check.
|
|
6
|
+
// It never reaches the network: the cloud search (npm, marketplaces) is the assistant's or
|
|
7
|
+
// the CLI's job, so the hook stays self-contained.
|
|
8
|
+
//
|
|
9
|
+
// ── What a project can configure (params) ───────────────────────────────────────────
|
|
10
|
+
// toolMapFile path to the per-project tool map, relative to the project root.
|
|
11
|
+
// Default .ai/tool-map.json.
|
|
12
|
+
// toolFolders folder names that mark a file as a "tool/helper". Replaces the built-in
|
|
13
|
+
// list wholesale.
|
|
14
|
+
// toolNamePatterns regex sources; a new file whose BASENAME matches any is treated as a
|
|
15
|
+
// generic helper regardless of its folder (useFoo, fooHelper, fooUtil...).
|
|
16
|
+
// toolExtensions file extensions considered code that can reinvent a wheel.
|
|
17
|
+
//
|
|
18
|
+
// ── How it decides (deterministic, no judgment) ─────────────────────────────────────
|
|
19
|
+
// A write that creates a tool/helper (by folder, by name pattern, or by a helper-ish
|
|
20
|
+
// extension), or a delegation prompt that asks to build one, must carry evidence the wheel
|
|
21
|
+
// was checked: (a) an audit phrase in the text, OR (b) the tool's own name already recorded
|
|
22
|
+
// in the tool map, OR (c) an installed dependency whose name matches the tool's name (there
|
|
23
|
+
// is already a package that plausibly covers it — reuse it or justify not to). Absent all
|
|
24
|
+
// three, the gate blocks with an actionable message.
|
|
25
|
+
//
|
|
26
|
+
// ── What changed vs the first version (closed holes) ────────────────────────────────
|
|
27
|
+
// · Map match was a lax "any 4+-letter word of the target appears anywhere in the JSON",
|
|
28
|
+
// which produced false "already covered" (a `parser.mjs` cleared by any unrelated entry
|
|
29
|
+
// mentioning "parser"). Now it matches the tool's own basename as a whole token against
|
|
30
|
+
// the recorded tool paths — structural, not substring-of-prose.
|
|
31
|
+
// · Scope was only scripts/hooks/tools/gates + executable extensions. Now it also covers
|
|
32
|
+
// lib/utils/helpers/composables/components/services AND name patterns (useX, XHelper,
|
|
33
|
+
// XUtil, XService) anywhere, which is where a wheel is most often reinvented.
|
|
34
|
+
// · Build-intent detection was English-only prose; now it uses the bilingual BUILD_INTENT.
|
|
35
|
+
// · Adds an installed-dependency check: a matching package.json dep also clears the build.
|
|
36
|
+
|
|
37
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
38
|
+
import { basename, dirname, extname, join } from 'node:path';
|
|
39
|
+
import {
|
|
40
|
+
runGate,
|
|
41
|
+
deny,
|
|
42
|
+
toolInGroups,
|
|
43
|
+
writtenContentOf,
|
|
44
|
+
writtenPathOf,
|
|
45
|
+
delegationPromptOf,
|
|
46
|
+
} from '../../lib/hook-io.mjs';
|
|
47
|
+
import { isBuildIntent } from '../../lib/signals.mjs';
|
|
48
|
+
|
|
49
|
+
const GATE_ID = 'reuse-before-build';
|
|
50
|
+
const CONFIG_KEY = 'requireReuseCheckBeforeBuilding';
|
|
51
|
+
|
|
52
|
+
const DEFAULT_TOOL_MAP_FILE = join('.ai', 'tool-map.json');
|
|
53
|
+
|
|
54
|
+
// Code that can reinvent a wheel — broad, because a helper is a helper in any language.
|
|
55
|
+
const DEFAULT_TOOL_EXTENSIONS = [
|
|
56
|
+
'.js',
|
|
57
|
+
'.mjs',
|
|
58
|
+
'.cjs',
|
|
59
|
+
'.ts',
|
|
60
|
+
'.tsx',
|
|
61
|
+
'.jsx',
|
|
62
|
+
'.vue',
|
|
63
|
+
'.py',
|
|
64
|
+
'.sh',
|
|
65
|
+
'.ps1',
|
|
66
|
+
'.rb',
|
|
67
|
+
'.go',
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Folders whose files are "tools/helpers" worth a reuse check. Beyond the original four,
|
|
71
|
+
// this now includes the places generic reusable code actually lives.
|
|
72
|
+
const DEFAULT_TOOL_FOLDERS = [
|
|
73
|
+
'scripts',
|
|
74
|
+
'hooks',
|
|
75
|
+
'tools',
|
|
76
|
+
'gates',
|
|
77
|
+
'lib',
|
|
78
|
+
'libs',
|
|
79
|
+
'utils',
|
|
80
|
+
'util',
|
|
81
|
+
'helpers',
|
|
82
|
+
'helper',
|
|
83
|
+
'composables',
|
|
84
|
+
'components',
|
|
85
|
+
'services',
|
|
86
|
+
'shared',
|
|
87
|
+
'common',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// A file whose BASENAME matches one of these is a generic helper wherever it lives —
|
|
91
|
+
// useDebounce.ts in features/x/ is still a candidate for "does this already exist?".
|
|
92
|
+
const DEFAULT_TOOL_NAME_PATTERNS = [
|
|
93
|
+
String.raw`^use[A-Z]`, // React/Vue hook/composable: useFoo
|
|
94
|
+
String.raw`[-.]?helpers?\.`, // foo.helper.ts, foo-helpers.js
|
|
95
|
+
String.raw`[-.]?utils?\.`, // foo.util.ts, string-utils.js
|
|
96
|
+
String.raw`[-.]?service\.`, // foo.service.ts
|
|
97
|
+
String.raw`[-.]?wrapper\.`, // foo.wrapper.ts
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
// Evidence the wheel was already checked: any phrase stating the audit result. Bilingual.
|
|
101
|
+
const AUDIT_DONE_PATTERN =
|
|
102
|
+
/(already exists|no existing tool|there is no plugin|audited|checked whether|nothing does this|justification:|ya existe|no existe (?:una |la )?herramienta|no hay plugin|audit[eé]|verifiqu[eé] que no)/i;
|
|
103
|
+
|
|
104
|
+
function projectRootOf(startDirectory) {
|
|
105
|
+
let current = startDirectory;
|
|
106
|
+
while (true) {
|
|
107
|
+
if (existsSync(join(current, '.git'))) return current;
|
|
108
|
+
const parent = dirname(current);
|
|
109
|
+
if (parent === current) return null;
|
|
110
|
+
current = parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether the tool map already records THIS tool, matched structurally: the new file's
|
|
116
|
+
* basename (without extension) is compared as a whole `-`/`.`-delimited token against the
|
|
117
|
+
* basenames of the recorded tool paths. This replaces the old "any 4+-letter word appears
|
|
118
|
+
* anywhere in the JSON blob", which cleared unrelated builds by coincidental word overlap.
|
|
119
|
+
*/
|
|
120
|
+
function toolAlreadyInMap(startDirectory, toolMapFile, toolBaseName) {
|
|
121
|
+
const root = projectRootOf(startDirectory);
|
|
122
|
+
if (!root) return false;
|
|
123
|
+
const path = join(root, toolMapFile);
|
|
124
|
+
if (!existsSync(path)) return false;
|
|
125
|
+
let parsed;
|
|
126
|
+
try {
|
|
127
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (!Array.isArray(parsed.tools)) return false;
|
|
132
|
+
const target = toolBaseName.toLowerCase();
|
|
133
|
+
return parsed.tools.some((tool) => {
|
|
134
|
+
const recorded = basename(String(tool.path ?? ''))
|
|
135
|
+
.replace(/\.[^.]+$/, '')
|
|
136
|
+
.toLowerCase();
|
|
137
|
+
return recorded === target;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Whether an installed dependency's name matches the tool's name (a package likely covers
|
|
142
|
+
* it). Normalizes scoped names (@scope/foo → foo) and matches whole, so `parser` is cleared
|
|
143
|
+
* by a `parser`/`@x/parser` dep but not by an unrelated `body-parser`. */
|
|
144
|
+
function installedDependencyCovers(startDirectory, toolBaseName) {
|
|
145
|
+
const root = projectRootOf(startDirectory);
|
|
146
|
+
if (!root) return false;
|
|
147
|
+
const path = join(root, 'package.json');
|
|
148
|
+
if (!existsSync(path)) return false;
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const names = [
|
|
156
|
+
...Object.keys(parsed.dependencies ?? {}),
|
|
157
|
+
...Object.keys(parsed.devDependencies ?? {}),
|
|
158
|
+
];
|
|
159
|
+
const target = toolBaseName.toLowerCase();
|
|
160
|
+
return names.some((name) => {
|
|
161
|
+
const bare = name.replace(/^@[^/]+\//, '').toLowerCase();
|
|
162
|
+
return bare === target;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function hasToolExtension(filePath, toolExtensions) {
|
|
167
|
+
return toolExtensions.includes(extname(filePath).toLowerCase());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** A real path SEGMENT equal to one of the tool-folder names (not a mere substring, so
|
|
171
|
+
* `src/mytools/x` does not match `tools`). */
|
|
172
|
+
function inToolFolder(filePath, toolFolders) {
|
|
173
|
+
const segments = filePath.replace(/\\/g, '/').split('/');
|
|
174
|
+
return segments.some((segment) =>
|
|
175
|
+
toolFolders.includes(segment.toLowerCase()),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function matchesToolNamePattern(fileName, toolNamePatterns) {
|
|
180
|
+
return toolNamePatterns.some((source) => {
|
|
181
|
+
try {
|
|
182
|
+
return new RegExp(source).test(fileName);
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Whether a new file at this path should be treated as a reusable tool/helper. */
|
|
190
|
+
function isToolLikePath(filePath, parameters) {
|
|
191
|
+
if (!hasToolExtension(filePath, parameters.toolExtensions)) return false;
|
|
192
|
+
const fileName = basename(filePath);
|
|
193
|
+
return (
|
|
194
|
+
inToolFolder(filePath, parameters.toolFolders) ||
|
|
195
|
+
matchesToolNamePattern(fileName, parameters.toolNamePatterns)
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const DENY_MESSAGE =
|
|
200
|
+
'Do not reinvent the wheel. Before building this, confirm nothing already covers it, ' +
|
|
201
|
+
'auditing in this order: (1) LOCAL — the repo and installed deps (search by name and ' +
|
|
202
|
+
'usage, read the manifest); (2) CONTEXT7 — resolve the candidate library and read its ' +
|
|
203
|
+
'docs to confirm whether it truly covers the need; (3) WEB — search npm and plugin ' +
|
|
204
|
+
'marketplaces for a maintained package. If it is genuinely absent, record the finding ' +
|
|
205
|
+
'(e.g. `claude-gates tool-map add`) and state the audit in the text, so the exploration ' +
|
|
206
|
+
'is not repeated next time.';
|
|
207
|
+
|
|
208
|
+
/** A build is cleared when its text declares an audit, its name is already recorded in the
|
|
209
|
+
* map, or an installed dependency covers it. `toolBaseName` is used for the last two. */
|
|
210
|
+
function buildIsCleared(text, cwd, parameters, toolBaseName) {
|
|
211
|
+
if (AUDIT_DONE_PATTERN.test(text)) return true;
|
|
212
|
+
if (
|
|
213
|
+
toolBaseName &&
|
|
214
|
+
toolAlreadyInMap(cwd, parameters.toolMapFile, toolBaseName)
|
|
215
|
+
)
|
|
216
|
+
return true;
|
|
217
|
+
if (toolBaseName && installedDependencyCovers(cwd, toolBaseName)) return true;
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Delegation: block a build-a-tool prompt that is not cleared. No file name is known from a
|
|
222
|
+
* prompt, so only the audit-phrase evidence applies there. */
|
|
223
|
+
function checkDelegation(toolInput, cwd, parameters) {
|
|
224
|
+
const prompt = delegationPromptOf(toolInput);
|
|
225
|
+
if (!isBuildIntent(prompt)) return;
|
|
226
|
+
if (buildIsCleared(prompt, cwd, parameters, null)) return;
|
|
227
|
+
deny(GATE_ID, DENY_MESSAGE);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Write: block a new tool-like file that is not cleared by content, map, or an installed dep. */
|
|
231
|
+
function checkWrite(toolInput, cwd, parameters) {
|
|
232
|
+
const rawPath = writtenPathOf(toolInput);
|
|
233
|
+
const filePath = rawPath.replace(/\\/g, '/');
|
|
234
|
+
if (!isToolLikePath(filePath, parameters)) return;
|
|
235
|
+
// Editing an EXISTING file is not building a new tool — only creation triggers the audit.
|
|
236
|
+
if (rawPath && existsSync(rawPath)) return;
|
|
237
|
+
const content = writtenContentOf(toolInput);
|
|
238
|
+
const toolBaseName = basename(filePath).replace(/\.[^.]+$/, '');
|
|
239
|
+
if (buildIsCleared(content, cwd, parameters, toolBaseName)) return;
|
|
240
|
+
deny(GATE_ID, DENY_MESSAGE);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
runGate(
|
|
244
|
+
{
|
|
245
|
+
id: GATE_ID,
|
|
246
|
+
configKey: CONFIG_KEY,
|
|
247
|
+
enabledByDefault: false,
|
|
248
|
+
defaultParams: {
|
|
249
|
+
toolMapFile: DEFAULT_TOOL_MAP_FILE,
|
|
250
|
+
toolFolders: DEFAULT_TOOL_FOLDERS,
|
|
251
|
+
toolNamePatterns: DEFAULT_TOOL_NAME_PATTERNS,
|
|
252
|
+
toolExtensions: DEFAULT_TOOL_EXTENSIONS,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
({ toolName, toolInput, parameters }) => {
|
|
256
|
+
const cwd = process.cwd();
|
|
257
|
+
if (toolInGroups(toolName, ['delegation'])) {
|
|
258
|
+
checkDelegation(toolInput, cwd, parameters);
|
|
259
|
+
} else if (toolInGroups(toolName, ['write'])) {
|
|
260
|
+
checkWrite(toolInput, cwd, parameters);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
);
|