@devrik-tools/claude-gates 0.7.2 → 0.9.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 +3 -3
- package/README.es.md +129 -11
- package/README.md +120 -10
- package/cli/doctor.mjs +132 -0
- package/cli/evidence.mjs +78 -0
- package/cli/hooks-manifest.mjs +89 -0
- package/cli/index.mjs +124 -6
- package/cli/init.mjs +97 -3
- package/cli/log.mjs +70 -0
- package/cli/materialize.mjs +36 -2
- package/cli/registry.mjs +12 -1
- package/cli/selection.mjs +18 -0
- package/cli/smoke-fixtures.json +159 -25
- package/cli/task.mjs +104 -11
- package/cli/toggle.mjs +125 -0
- package/package.json +4 -3
- package/plugins/gates/.claude-plugin/plugin.json +1 -1
- package/plugins/gates/hooks/doctor.mjs +47 -1
- package/plugins/gates/hooks/gates/atomic-commit/index.mjs +92 -119
- package/plugins/gates/hooks/gates/audit-before-build/index.mjs +101 -66
- package/plugins/gates/hooks/gates/autonomous-mode/index.mjs +8 -8
- package/plugins/gates/hooks/gates/autonomous-mode/stop.mjs +13 -64
- package/plugins/gates/hooks/gates/bash-commands/index.mjs +186 -163
- package/plugins/gates/hooks/gates/block-remote-publish/index.mjs +100 -101
- package/plugins/gates/hooks/gates/brief-approved/index.mjs +71 -140
- package/plugins/gates/hooks/gates/brief-before-delegate/index.mjs +56 -137
- package/plugins/gates/hooks/gates/capability-map/index.mjs +280 -506
- package/plugins/gates/hooks/gates/circuit-breaker/index.mjs +94 -215
- package/plugins/gates/hooks/gates/circuit-breaker/track.mjs +285 -0
- package/plugins/gates/hooks/gates/dependency-skills/index.mjs +127 -46
- package/plugins/gates/hooks/gates/diagnosis-before-patch/index.mjs +81 -19
- package/plugins/gates/hooks/gates/engram-first/index.mjs +41 -0
- package/plugins/gates/hooks/gates/engram-first/session-start.mjs +64 -0
- package/plugins/gates/hooks/gates/engram-first/shared.mjs +90 -0
- package/plugins/gates/hooks/gates/engram-first/stop.mjs +23 -0
- package/plugins/gates/hooks/gates/engram-first/track.mjs +97 -0
- package/plugins/gates/hooks/gates/feature-catalog/index.mjs +156 -50
- package/plugins/gates/hooks/gates/force-parallel/index.mjs +53 -95
- package/plugins/gates/hooks/gates/forge-flow/index.mjs +51 -83
- package/plugins/gates/hooks/gates/implementation-pipeline/index.mjs +40 -80
- package/plugins/gates/hooks/gates/intent-flow/index.mjs +23 -149
- package/plugins/gates/hooks/gates/library-docs/index.mjs +338 -0
- package/plugins/gates/hooks/gates/library-docs/track.mjs +95 -0
- package/plugins/gates/hooks/gates/lint-commit/index.mjs +88 -97
- package/plugins/gates/hooks/gates/mandatory-flow/index.mjs +48 -95
- package/plugins/gates/hooks/gates/neutral-spanish/index.mjs +69 -31
- package/plugins/gates/hooks/gates/never-assume/index.mjs +23 -18
- package/plugins/gates/hooks/gates/no-blocking/index.mjs +127 -86
- package/plugins/gates/hooks/gates/no-coauthor/index.mjs +108 -88
- package/plugins/gates/hooks/gates/no-explanatory-comments/index.mjs +281 -0
- package/plugins/gates/hooks/gates/no-lint-suppression/index.mjs +170 -110
- package/plugins/gates/hooks/gates/no-memory-dependency/index.mjs +46 -77
- package/plugins/gates/hooks/gates/no-reconfirm/index.mjs +85 -47
- package/plugins/gates/hooks/gates/no-trivial-scripts/index.mjs +114 -0
- package/plugins/gates/hooks/gates/protected-paths/index.mjs +137 -90
- package/plugins/gates/hooks/gates/recurrence-lock/index.mjs +95 -51
- package/plugins/gates/hooks/gates/require-monitor/index.mjs +126 -0
- package/plugins/gates/hooks/gates/require-task-split/index.mjs +88 -0
- package/plugins/gates/hooks/gates/reuse-before-build/index.mjs +72 -216
- package/plugins/gates/hooks/gates/risk-level/index.mjs +51 -204
- package/plugins/gates/hooks/gates/root-cause-first/index.mjs +34 -17
- package/plugins/gates/hooks/gates/root-whitelist/index.mjs +160 -95
- package/plugins/gates/hooks/gates/rule-skill-autodiscovery/index.mjs +196 -97
- package/plugins/gates/hooks/gates/sdd-specs/index.mjs +90 -143
- package/plugins/gates/hooks/gates/staged-lint/index.mjs +98 -106
- package/plugins/gates/hooks/gates/stop-pending/index.mjs +47 -148
- package/plugins/gates/hooks/gates/test-after-implementation/index.mjs +78 -68
- package/plugins/gates/hooks/gates/test-matrix/index.mjs +53 -88
- package/plugins/gates/hooks/gates/tool-map/index.mjs +80 -110
- package/plugins/gates/hooks/hooks.json +152 -42
- package/plugins/gates/hooks/lib/config.mjs +6 -3
- package/plugins/gates/hooks/lib/delegation.mjs +352 -0
- package/plugins/gates/hooks/lib/gate-log.mjs +141 -0
- package/plugins/gates/hooks/lib/git.mjs +262 -0
- package/plugins/gates/hooks/lib/hook-io.mjs +451 -83
- package/plugins/gates/hooks/lib/session-state.mjs +150 -0
- package/plugins/gates/hooks/lib/signals.mjs +18 -13
- package/plugins/gates/hooks/lib/testing.mjs +148 -0
- package/plugins/gates/hooks/lib/tools.mjs +144 -0
- package/plugins/tasks/.claude-plugin/plugin.json +1 -1
- package/plugins/tasks/hooks/lib/task-store.mjs +12 -6
- package/plugins/tasks/hooks/register-requests.mjs +37 -10
- package/registry.json +249 -38
|
@@ -1,15 +1,29 @@
|
|
|
1
|
+
// rule-skill-autodiscovery — runs the project's own sub-gates (rules/<gate file> and
|
|
2
|
+
// .claude/skills/*/<gate file>) on every execution/delegation call, feeding each the hook
|
|
3
|
+
// payload on stdin. A sub-gate denies by printing `{"permissionDecision":"deny",...}` or by
|
|
4
|
+
// exiting 2; a sub-gate that crashes or times out only WARNS, so one broken script cannot
|
|
5
|
+
// block all work. Deliberate restrictions, because a discovered script is arbitrary code:
|
|
6
|
+
// - it runs only when THIS project's .ai/config.json enables the gate (a global setting
|
|
7
|
+
// must never grant code execution to a freshly cloned repo);
|
|
8
|
+
// - it gets a minimal environment (PATH/HOME/USERPROFILE/TEMP/TMP/SystemRoot/CLAUDE_*);
|
|
9
|
+
// - all scripts share one 8 s budget;
|
|
10
|
+
// - the gate config files are hashed before and after each script; any change is
|
|
11
|
+
// reverted and denied, so a script cannot disable the gates that govern it.
|
|
12
|
+
|
|
1
13
|
import { execFileSync } from 'node:child_process';
|
|
2
14
|
import { createHash } from 'node:crypto';
|
|
3
15
|
import {
|
|
4
16
|
existsSync,
|
|
5
17
|
readdirSync,
|
|
6
18
|
readFileSync,
|
|
7
|
-
|
|
19
|
+
statSync,
|
|
8
20
|
unlinkSync,
|
|
21
|
+
writeFileSync,
|
|
9
22
|
} from 'node:fs';
|
|
10
23
|
import { homedir } from 'node:os';
|
|
11
24
|
import { join, relative } from 'node:path';
|
|
12
|
-
import {
|
|
25
|
+
import { projectRootOf, readJsonOrNull } from '../../lib/config.mjs';
|
|
26
|
+
import { runGate, deny, warn, toolInGroups } from '../../lib/hook-io.mjs';
|
|
13
27
|
|
|
14
28
|
const GATE_ID = 'rule-skill-autodiscovery';
|
|
15
29
|
const CONFIG_KEY = 'autodiscoverRulesAndSkills';
|
|
@@ -21,72 +35,92 @@ const DEFAULT_GATE_FILE_NAMES = [
|
|
|
21
35
|
'check.mjs',
|
|
22
36
|
'verify.mjs',
|
|
23
37
|
];
|
|
38
|
+
const TOTAL_BUDGET_MS = 8000;
|
|
39
|
+
const KIB = 1024;
|
|
40
|
+
const MAX_OUTPUT_BYTES = KIB * KIB;
|
|
41
|
+
const DENY_EXIT_CODE = 2;
|
|
42
|
+
const ALLOWED_ENVIRONMENT = new Set([
|
|
43
|
+
'PATH',
|
|
44
|
+
'HOME',
|
|
45
|
+
'USERPROFILE',
|
|
46
|
+
'TEMP',
|
|
47
|
+
'TMP',
|
|
48
|
+
'SYSTEMROOT',
|
|
49
|
+
]);
|
|
50
|
+
const ENVIRONMENT_PREFIX = 'CLAUDE_';
|
|
51
|
+
const PROJECT_CONFIG_RELATIVE_PATH = join('.ai', 'config.json');
|
|
52
|
+
const GLOBAL_CONFIG_PATH = join(
|
|
53
|
+
homedir(),
|
|
54
|
+
'.claude',
|
|
55
|
+
'claude-gates',
|
|
56
|
+
'config.json',
|
|
57
|
+
);
|
|
24
58
|
|
|
25
|
-
function
|
|
26
|
-
|
|
59
|
+
function projectEnablesGate(root) {
|
|
60
|
+
const entry = readJsonOrNull(join(root, PROJECT_CONFIG_RELATIVE_PATH))
|
|
61
|
+
?.gates?.[CONFIG_KEY];
|
|
62
|
+
return entry === true || entry?.enabled === true;
|
|
27
63
|
}
|
|
28
64
|
|
|
29
|
-
function
|
|
30
|
-
if (!existsSync(scriptsDirectory)) return [];
|
|
31
|
-
let entries;
|
|
65
|
+
function isDirectory(path) {
|
|
32
66
|
try {
|
|
33
|
-
|
|
67
|
+
return statSync(path).isDirectory();
|
|
34
68
|
} catch {
|
|
35
|
-
return
|
|
69
|
+
return false;
|
|
36
70
|
}
|
|
37
|
-
return entries
|
|
38
|
-
.filter((entry) => entry.isFile())
|
|
39
|
-
.filter((entry) => /\.(mjs|js)$/.test(entry.name))
|
|
40
|
-
.filter((entry) => isGateFile(entry.name, gateFileNames))
|
|
41
|
-
.map((entry) => join(scriptsDirectory, entry.name));
|
|
42
71
|
}
|
|
43
72
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const skillsRoot = join(projectRoot, '.claude', 'skills');
|
|
50
|
-
if (existsSync(skillsRoot)) {
|
|
51
|
-
let skillDirectories;
|
|
52
|
-
try {
|
|
53
|
-
skillDirectories = readdirSync(skillsRoot, {
|
|
54
|
-
withFileTypes: true,
|
|
55
|
-
}).filter((entry) => entry.isDirectory());
|
|
56
|
-
} catch {
|
|
57
|
-
skillDirectories = [];
|
|
58
|
-
}
|
|
59
|
-
for (const skillDirectory of skillDirectories) {
|
|
60
|
-
scripts.push(
|
|
61
|
-
...findScriptsIn(join(skillsRoot, skillDirectory.name), gateFileNames),
|
|
62
|
-
);
|
|
63
|
-
}
|
|
73
|
+
function isFile(path) {
|
|
74
|
+
try {
|
|
75
|
+
return statSync(path).isFile();
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
64
78
|
}
|
|
65
|
-
|
|
66
|
-
return scripts;
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
function findScriptsIn(directory, gateFileNames) {
|
|
82
|
+
let names;
|
|
83
|
+
try {
|
|
84
|
+
names = readdirSync(directory);
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
return names
|
|
89
|
+
.filter((name) => /\.(mjs|js)$/.test(name))
|
|
90
|
+
.filter(
|
|
91
|
+
(name) => gateFileNames.includes(name) || name.endsWith('.gate.mjs'),
|
|
92
|
+
)
|
|
93
|
+
.map((name) => join(directory, name))
|
|
94
|
+
.filter(isFile);
|
|
95
|
+
}
|
|
80
96
|
|
|
81
|
-
function
|
|
82
|
-
|
|
97
|
+
function discoverScripts(root, rulesDirectoryName, gateFileNames) {
|
|
98
|
+
const scripts = findScriptsIn(join(root, rulesDirectoryName), gateFileNames);
|
|
99
|
+
const skillsRoot = join(root, '.claude', 'skills');
|
|
100
|
+
let skillNames;
|
|
101
|
+
try {
|
|
102
|
+
skillNames = readdirSync(skillsRoot);
|
|
103
|
+
} catch {
|
|
104
|
+
skillNames = [];
|
|
105
|
+
}
|
|
106
|
+
for (const name of skillNames) {
|
|
107
|
+
const skillDirectory = join(skillsRoot, name);
|
|
108
|
+
if (isDirectory(skillDirectory))
|
|
109
|
+
scripts.push(...findScriptsIn(skillDirectory, gateFileNames));
|
|
110
|
+
}
|
|
111
|
+
return scripts;
|
|
83
112
|
}
|
|
84
113
|
|
|
85
|
-
function
|
|
86
|
-
|
|
114
|
+
function restrictedEnvironment() {
|
|
115
|
+
const environment = {};
|
|
116
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
117
|
+
const upper = key.toUpperCase();
|
|
118
|
+
if (ALLOWED_ENVIRONMENT.has(upper) || upper.startsWith(ENVIRONMENT_PREFIX))
|
|
119
|
+
environment[key] = value;
|
|
120
|
+
}
|
|
121
|
+
return environment;
|
|
87
122
|
}
|
|
88
123
|
|
|
89
|
-
/** Reads raw bytes (or null if absent) and their hash, per watched config path. */
|
|
90
124
|
function snapshotConfigs(paths) {
|
|
91
125
|
return paths.map((path) => {
|
|
92
126
|
let content = null;
|
|
@@ -102,7 +136,6 @@ function snapshotConfigs(paths) {
|
|
|
102
136
|
});
|
|
103
137
|
}
|
|
104
138
|
|
|
105
|
-
/** Restores every watched config file to its pre-execution content, best-effort. */
|
|
106
139
|
function revertConfigs(snapshots) {
|
|
107
140
|
for (const snapshot of snapshots) {
|
|
108
141
|
try {
|
|
@@ -112,18 +145,114 @@ function revertConfigs(snapshots) {
|
|
|
112
145
|
writeFileSync(snapshot.path, snapshot.content);
|
|
113
146
|
}
|
|
114
147
|
} catch {
|
|
115
|
-
// Best-effort revert:
|
|
116
|
-
// beyond having already denied the call.
|
|
148
|
+
// Best-effort revert: the call is denied regardless.
|
|
117
149
|
}
|
|
118
150
|
}
|
|
119
151
|
}
|
|
120
152
|
|
|
121
|
-
/** Whether any watched config file's content changed since `before`. */
|
|
122
153
|
function configsTampered(before) {
|
|
123
154
|
const after = snapshotConfigs(before.map((snapshot) => snapshot.path));
|
|
124
155
|
return before.some((snapshot, index) => snapshot.hash !== after[index].hash);
|
|
125
156
|
}
|
|
126
157
|
|
|
158
|
+
function runScript(script, root, rawPayload, timeoutMs) {
|
|
159
|
+
try {
|
|
160
|
+
const stdout = execFileSync(process.execPath, [script], {
|
|
161
|
+
cwd: root,
|
|
162
|
+
input: rawPayload,
|
|
163
|
+
env: restrictedEnvironment(),
|
|
164
|
+
encoding: 'utf8',
|
|
165
|
+
timeout: timeoutMs,
|
|
166
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
167
|
+
});
|
|
168
|
+
return { status: 0, stdout, stderr: '' };
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return {
|
|
171
|
+
status: error?.status ?? null,
|
|
172
|
+
stdout: String(error?.stdout ?? ''),
|
|
173
|
+
stderr: String(error?.stderr ?? ''),
|
|
174
|
+
message:
|
|
175
|
+
error?.code === 'ETIMEDOUT'
|
|
176
|
+
? 'timed out'
|
|
177
|
+
: String(error?.message ?? error),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function parseJsonOrNull(text) {
|
|
183
|
+
try {
|
|
184
|
+
return JSON.parse(text);
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const NO_REASON = 'no reason given';
|
|
191
|
+
|
|
192
|
+
function jsonDecisionOf(parsed) {
|
|
193
|
+
const specific = parsed?.hookSpecificOutput ?? {};
|
|
194
|
+
return {
|
|
195
|
+
decision:
|
|
196
|
+
specific.permissionDecision ??
|
|
197
|
+
parsed?.permissionDecision ??
|
|
198
|
+
parsed?.decision,
|
|
199
|
+
reason:
|
|
200
|
+
specific.permissionDecisionReason ??
|
|
201
|
+
parsed?.permissionDecisionReason ??
|
|
202
|
+
parsed?.reason ??
|
|
203
|
+
NO_REASON,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function denialReasonOf(result) {
|
|
208
|
+
const output = result.stdout.trim();
|
|
209
|
+
const { decision, reason } = jsonDecisionOf(parseJsonOrNull(output));
|
|
210
|
+
if (decision === 'deny' || decision === 'block') return reason;
|
|
211
|
+
if (result.status === DENY_EXIT_CODE)
|
|
212
|
+
return result.stderr.trim() || output || NO_REASON;
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function failureOf(result, scriptLabel) {
|
|
217
|
+
if (result.status === 0 || result.status === DENY_EXIT_CODE) return null;
|
|
218
|
+
const detail = result.stderr.trim() || result.message || 'unknown error';
|
|
219
|
+
return `'${scriptLabel}' failed (exit ${result.status ?? 'none'}): ${detail}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function runDiscoveredScripts(scripts, root, rawPayload) {
|
|
223
|
+
const watched = [
|
|
224
|
+
join(root, PROJECT_CONFIG_RELATIVE_PATH),
|
|
225
|
+
GLOBAL_CONFIG_PATH,
|
|
226
|
+
];
|
|
227
|
+
const started = Date.now();
|
|
228
|
+
const warnings = [];
|
|
229
|
+
for (const [index, script] of scripts.entries()) {
|
|
230
|
+
const remaining = TOTAL_BUDGET_MS - (Date.now() - started);
|
|
231
|
+
if (remaining <= 0) {
|
|
232
|
+
warnings.push(
|
|
233
|
+
`${scripts.length - index} script(s) skipped: the ${TOTAL_BUDGET_MS} ms budget for all sub-gates was exhausted`,
|
|
234
|
+
);
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
const label = relative(root, script);
|
|
238
|
+
const before = snapshotConfigs(watched);
|
|
239
|
+
const result = runScript(script, root, rawPayload, remaining);
|
|
240
|
+
if (configsTampered(before)) {
|
|
241
|
+
revertConfigs(before);
|
|
242
|
+
deny(
|
|
243
|
+
CONFIG_KEY,
|
|
244
|
+
`Discovered rule/skill script ${label} attempted to modify gate security configuration (.ai/config.json or the global config). The change was reverted and the action is blocked.`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const reason = denialReasonOf(result);
|
|
248
|
+
if (reason !== null)
|
|
249
|
+
deny(CONFIG_KEY, `Sub-gate '${label}' denied: ${reason}`);
|
|
250
|
+
const failure = failureOf(result, label);
|
|
251
|
+
if (failure) warnings.push(failure);
|
|
252
|
+
}
|
|
253
|
+
return warnings;
|
|
254
|
+
}
|
|
255
|
+
|
|
127
256
|
runGate(
|
|
128
257
|
{
|
|
129
258
|
id: GATE_ID,
|
|
@@ -134,54 +263,24 @@ runGate(
|
|
|
134
263
|
gateFileNames: DEFAULT_GATE_FILE_NAMES,
|
|
135
264
|
},
|
|
136
265
|
},
|
|
137
|
-
({ toolName, parameters }) => {
|
|
266
|
+
({ toolName, parameters, cwd, rawPayload }) => {
|
|
138
267
|
if (!toolInGroups(toolName, ['execution', 'delegation'])) return;
|
|
268
|
+
const root = projectRootOf(cwd) ?? cwd;
|
|
269
|
+
if (!projectEnablesGate(root)) return;
|
|
139
270
|
|
|
140
|
-
const projectRoot = process.cwd();
|
|
141
271
|
const scripts = discoverScripts(
|
|
142
|
-
|
|
272
|
+
root,
|
|
143
273
|
parameters.rulesDir,
|
|
144
274
|
parameters.gateFileNames,
|
|
145
275
|
);
|
|
146
276
|
if (scripts.length === 0) return;
|
|
147
277
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
execFileSync(process.execPath, [script], {
|
|
156
|
-
stdio: ['ignore', 'ignore', 'pipe'],
|
|
157
|
-
timeout: 5000,
|
|
158
|
-
});
|
|
159
|
-
} catch (error) {
|
|
160
|
-
failure = error;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (configsTampered(before)) {
|
|
164
|
-
revertConfigs(before);
|
|
165
|
-
deny(
|
|
166
|
-
CONFIG_KEY,
|
|
167
|
-
`Discovered rule/skill script ${relative(projectRoot, script)} attempted to modify gate security configuration (.ai/config.json or the global config). The change was reverted and the action is blocked.`,
|
|
168
|
-
);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (failure) {
|
|
173
|
-
const stderr = String(failure.stderr ?? '').trim();
|
|
174
|
-
const detail = stderr || failure.message || String(failure);
|
|
175
|
-
deny(
|
|
176
|
-
CONFIG_KEY,
|
|
177
|
-
`Sub-gate '${relative(projectRoot, script)}' failed (exit ` +
|
|
178
|
-
`${failure.status ?? 'unknown'}): ${detail}\n` +
|
|
179
|
-
'No filesystem exploration is needed — fix that script (open it at the path ' +
|
|
180
|
-
'above and address the error shown), or remove it from ' +
|
|
181
|
-
`${parameters.rulesDir}/ if it should not run as a gate.`,
|
|
182
|
-
);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
278
|
+
const warnings = runDiscoveredScripts(scripts, root, rawPayload);
|
|
279
|
+
if (warnings.length === 0) return;
|
|
280
|
+
warn(
|
|
281
|
+
CONFIG_KEY,
|
|
282
|
+
`Sub-gate(s) could not be evaluated, so they did not judge this call: ${warnings.join('; ')}. ` +
|
|
283
|
+
`Fix the script (open it at the path above and address the error shown) or remove it from ${parameters.rulesDir}/ or the skill directory if it should not run as a gate.`,
|
|
284
|
+
);
|
|
186
285
|
},
|
|
187
286
|
);
|