@mutmutco/kimi-plugin 4.1.1 → 4.1.3
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/.kimi-plugin/plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ import { decide as decideCommandLadder, matchedVerb } from './command-ladder-gat
|
|
|
11
11
|
import { handleGateCrash, handleMissingHookInput, recordGateSuccess } from './deny-gate-crash.mjs';
|
|
12
12
|
import { readHookInput } from './hook-io.mjs';
|
|
13
13
|
import { appendHookActivity } from './hook-trace.mjs';
|
|
14
|
+
import { evaluateTestCommandPolicy } from './test-command-policy-core.mjs';
|
|
14
15
|
|
|
15
16
|
// Secret echoes are blocked before execution on every active host.
|
|
16
17
|
const SECRET_ECHO_MODE = process.env.MMI_SECRET_ECHO_LINT || 'block';
|
|
@@ -358,34 +359,14 @@ function repositoryRoot(input) {
|
|
|
358
359
|
return null;
|
|
359
360
|
}
|
|
360
361
|
|
|
361
|
-
function
|
|
362
|
+
function policyMandatoryEntries(root) {
|
|
362
363
|
const path = resolve(root, 'test-policy.json');
|
|
363
364
|
if (!existsSync(path)) return null;
|
|
364
365
|
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
365
366
|
if (!Array.isArray(parsed.mandatory) || !parsed.mandatory.every((entry) => entry && typeof entry.glob === 'string')) {
|
|
366
367
|
throw new Error('test-policy.json mandatory entries are invalid');
|
|
367
368
|
}
|
|
368
|
-
return parsed.mandatory
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function policyGlobToRegExp(glob) {
|
|
372
|
-
let out = '';
|
|
373
|
-
for (let i = 0; i < glob.length; i += 1) {
|
|
374
|
-
const char = glob[i];
|
|
375
|
-
if (char === '*') {
|
|
376
|
-
if (glob[i + 1] === '*') {
|
|
377
|
-
if (glob[i + 2] === '/') { out += '(?:.*/)?'; i += 2; } else { out += '.*'; i += 1; }
|
|
378
|
-
} else out += '[^/]*';
|
|
379
|
-
} else if (char === '{') {
|
|
380
|
-
const close = glob.indexOf('}', i);
|
|
381
|
-
if (close === -1) out += '\\{';
|
|
382
|
-
else {
|
|
383
|
-
out += `(?:${glob.slice(i + 1, close).split(',').map(policyGlobToRegExp).join('|')})`;
|
|
384
|
-
i = close;
|
|
385
|
-
}
|
|
386
|
-
} else out += /[.+?^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
387
|
-
}
|
|
388
|
-
return out;
|
|
369
|
+
return parsed.mandatory;
|
|
389
370
|
}
|
|
390
371
|
|
|
391
372
|
function taskDiffPaths(root) {
|
|
@@ -410,12 +391,18 @@ function taskDiffPaths(root) {
|
|
|
410
391
|
function runTestCommandPolicy(input, { stdout = process.stdout } = {}) {
|
|
411
392
|
if (!requestedTestCommand(input?.tool_input?.command)) return { denied: false };
|
|
412
393
|
const root = repositoryRoot(input);
|
|
413
|
-
let
|
|
394
|
+
let mandatory;
|
|
414
395
|
try {
|
|
415
396
|
// No declaration is the estate default: this hook does not regulate test execution there.
|
|
416
|
-
if (!root || (
|
|
417
|
-
|
|
418
|
-
|
|
397
|
+
if (!root || (mandatory = policyMandatoryEntries(root)) === null) return { denied: false };
|
|
398
|
+
// #5519: same evaluator `mmi-cli tests policy` attaches to its OK summary — matched globs and
|
|
399
|
+
// command classes cannot disagree with the CLI on the same path set.
|
|
400
|
+
const decision = evaluateTestCommandPolicy({
|
|
401
|
+
paths: taskDiffPaths(root),
|
|
402
|
+
mandatory,
|
|
403
|
+
regulated: true,
|
|
404
|
+
});
|
|
405
|
+
if (decision.testCommandsAllowed) return { denied: false, decision };
|
|
419
406
|
} catch (error) {
|
|
420
407
|
const reason = 'TEST-POLICY TEST COMMAND REFUSED [test-command-policy-unresolvable]: '
|
|
421
408
|
+ `a test-policy.json applies but its repository, policy, or task diff could not be established (${error.message}). `
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// test-command-policy-core.mjs — shared verdict for "may this diff run tests?" (#5519).
|
|
2
|
+
//
|
|
3
|
+
// `mmi-cli tests policy` and the PreToolUse test-command gate both answer that question. Before
|
|
4
|
+
// #5519 they answered it separately: the CLI summary printed the repository's CONFIGURED mandatory
|
|
5
|
+
// glob count on an OK line, while the gate independently matched the task diff against those globs
|
|
6
|
+
// and refused a focused test when none hit. Agents read "8 mandatory glob(s)" as permission, then
|
|
7
|
+
// hit TEST-POLICY TEST COMMAND REFUSED on the next line. Delegated workers and the parent hook also
|
|
8
|
+
// diverged when they did not share an evaluator.
|
|
9
|
+
//
|
|
10
|
+
// This module is the one evaluator. It reports matched globs separately from configured totals and
|
|
11
|
+
// the exact allowed/refused command classes. Pure: no IO, no git.
|
|
12
|
+
|
|
13
|
+
/** Command class the PreToolUse gate regulates. Non-test verification is never refused here. */
|
|
14
|
+
export const TEST_COMMAND_CLASS = 'test';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Glob body → regex body. Supports `**`, `*`, and `{a,b}` (including wildcards inside braces).
|
|
18
|
+
* Kept byte-compatible with {@link globToRegExp} in cli/src/test-policy-core.ts so rule matching
|
|
19
|
+
* and the command guard cannot drift on the same policy file.
|
|
20
|
+
*/
|
|
21
|
+
export function translateGlob(glob) {
|
|
22
|
+
let out = '';
|
|
23
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
24
|
+
const char = glob[i];
|
|
25
|
+
if (char === '*') {
|
|
26
|
+
if (glob[i + 1] === '*') {
|
|
27
|
+
if (glob[i + 2] === '/') {
|
|
28
|
+
out += '(?:.*/)?';
|
|
29
|
+
i += 2;
|
|
30
|
+
} else {
|
|
31
|
+
out += '.*';
|
|
32
|
+
i += 1;
|
|
33
|
+
}
|
|
34
|
+
} else out += '[^/]*';
|
|
35
|
+
} else if (char === '{') {
|
|
36
|
+
const close = glob.indexOf('}', i);
|
|
37
|
+
if (close === -1) out += '\\{';
|
|
38
|
+
else {
|
|
39
|
+
out += `(?:${glob.slice(i + 1, close).split(',').map(translateGlob).join('|')})`;
|
|
40
|
+
i = close;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
out += /[.+?^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function globToRegExp(glob) {
|
|
50
|
+
return new RegExp(`^${translateGlob(glob)}$`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function mandatoryGlobList(mandatory) {
|
|
54
|
+
if (!Array.isArray(mandatory)) return [];
|
|
55
|
+
return mandatory.map((entry) => (typeof entry === 'string' ? entry : entry?.glob)).filter((glob) => typeof glob === 'string');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Mandatory globs that match at least one path in `paths`. Order follows the policy declaration. */
|
|
59
|
+
export function matchedMandatoryGlobs(paths, mandatory) {
|
|
60
|
+
const globs = mandatoryGlobList(mandatory);
|
|
61
|
+
const list = Array.isArray(paths) ? paths : [];
|
|
62
|
+
return globs.filter((glob) => {
|
|
63
|
+
const re = globToRegExp(glob);
|
|
64
|
+
return list.some((path) => re.test(path));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Decide whether test commands are allowed against a path set and a mandatory zone.
|
|
70
|
+
*
|
|
71
|
+
* @param {{ paths: string[], mandatory?: unknown, regulated?: boolean }} input
|
|
72
|
+
* `regulated: false` — no test-policy.json (estate default); tests are not gated.
|
|
73
|
+
* `regulated: true` (default) — a declared policy applies; zero matched globs refuses `test`.
|
|
74
|
+
*/
|
|
75
|
+
export function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {}) {
|
|
76
|
+
const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
|
|
77
|
+
if (!regulated) {
|
|
78
|
+
return {
|
|
79
|
+
configuredMandatoryCount: 0,
|
|
80
|
+
matchedMandatoryGlobs: [],
|
|
81
|
+
matchedMandatoryCount: 0,
|
|
82
|
+
testCommandsAllowed: true,
|
|
83
|
+
reasonId: null,
|
|
84
|
+
commandClasses: { allowed: [TEST_COMMAND_CLASS], refused: [] },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const matched = matchedMandatoryGlobs(paths, mandatory);
|
|
88
|
+
const testCommandsAllowed = matched.length > 0;
|
|
89
|
+
return {
|
|
90
|
+
configuredMandatoryCount,
|
|
91
|
+
matchedMandatoryGlobs: matched,
|
|
92
|
+
matchedMandatoryCount: matched.length,
|
|
93
|
+
testCommandsAllowed,
|
|
94
|
+
reasonId: testCommandsAllowed ? null : 'test-command-outside-mandatory-zone',
|
|
95
|
+
commandClasses: {
|
|
96
|
+
allowed: testCommandsAllowed ? [TEST_COMMAND_CLASS] : [],
|
|
97
|
+
refused: testCommandsAllowed ? [] : [TEST_COMMAND_CLASS],
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|