@mmnto/cli 1.34.3 → 1.36.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/dist/commands/eject.d.ts.map +1 -1
- package/dist/commands/eject.js +64 -2
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +51 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/hook-run.d.ts +91 -0
- package/dist/commands/hook-run.d.ts.map +1 -0
- package/dist/commands/hook-run.js +149 -0
- package/dist/commands/hook-run.js.map +1 -0
- package/dist/commands/hook-run.test.d.ts +2 -0
- package/dist/commands/hook-run.test.d.ts.map +1 -0
- package/dist/commands/hook-run.test.js +264 -0
- package/dist/commands/hook-run.test.js.map +1 -0
- package/dist/commands/hook-test.d.ts +29 -0
- package/dist/commands/hook-test.d.ts.map +1 -0
- package/dist/commands/hook-test.js +132 -0
- package/dist/commands/hook-test.js.map +1 -0
- package/dist/commands/init-templates.d.ts +11 -0
- package/dist/commands/init-templates.d.ts.map +1 -1
- package/dist/commands/init-templates.js +119 -0
- package/dist/commands/init-templates.js.map +1 -1
- package/dist/commands/init.d.ts +21 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +90 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +91 -2
- package/dist/commands/init.test.js.map +1 -1
- package/dist/hook/classification.d.ts +45 -0
- package/dist/hook/classification.d.ts.map +1 -0
- package/dist/hook/classification.js +24 -0
- package/dist/hook/classification.js.map +1 -0
- package/dist/hook/classification.test.d.ts +2 -0
- package/dist/hook/classification.test.d.ts.map +1 -0
- package/dist/hook/classification.test.js +40 -0
- package/dist/hook/classification.test.js.map +1 -0
- package/dist/hook/loader.d.ts +47 -0
- package/dist/hook/loader.d.ts.map +1 -0
- package/dist/hook/loader.js +66 -0
- package/dist/hook/loader.js.map +1 -0
- package/dist/hook/loader.test.d.ts +2 -0
- package/dist/hook/loader.test.d.ts.map +1 -0
- package/dist/hook/loader.test.js +205 -0
- package/dist/hook/loader.test.js.map +1 -0
- package/dist/hook/runtime.d.ts +47 -0
- package/dist/hook/runtime.d.ts.map +1 -0
- package/dist/hook/runtime.js +85 -0
- package/dist/hook/runtime.js.map +1 -0
- package/dist/hook/runtime.test.d.ts +2 -0
- package/dist/hook/runtime.test.d.ts.map +1 -0
- package/dist/hook/runtime.test.js +135 -0
- package/dist/hook/runtime.test.js.map +1 -0
- package/dist/hook/schema.d.ts +385 -0
- package/dist/hook/schema.d.ts.map +1 -0
- package/dist/hook/schema.js +164 -0
- package/dist/hook/schema.js.map +1 -0
- package/dist/hook/schema.test.d.ts +2 -0
- package/dist/hook/schema.test.d.ts.map +1 -0
- package/dist/hook/schema.test.js +233 -0
- package/dist/hook/schema.test.js.map +1 -0
- package/dist/hook/test-runner.d.ts +64 -0
- package/dist/hook/test-runner.d.ts.map +1 -0
- package/dist/hook/test-runner.js +57 -0
- package/dist/hook/test-runner.js.map +1 -0
- package/dist/hook/test-runner.test.d.ts +2 -0
- package/dist/hook/test-runner.test.d.ts.map +1 -0
- package/dist/hook/test-runner.test.js +237 -0
- package/dist/hook/test-runner.test.js.map +1 -0
- package/dist/index.js +57 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { COMPILED_HOOKS_SCHEMA_VERSION, CompiledHooksManifestSchema, HookRuleSchema, HOOKS_YAML_SCHEMA_VERSION, HooksYamlSchema, } from './schema.js';
|
|
3
|
+
describe('hook schema', () => {
|
|
4
|
+
describe('HookRuleSchema', () => {
|
|
5
|
+
it('accepts a minimal rule with required fields only', () => {
|
|
6
|
+
const minimal = {
|
|
7
|
+
id: 'gca-tag-xor-command',
|
|
8
|
+
trigger: { tool: 'bash', pattern: 'gh\\s+(pr|issue)\\s+comment.*' },
|
|
9
|
+
check: {
|
|
10
|
+
pattern: '(?=.*@gemini-code-assist)(?=.*\\/gemini review)',
|
|
11
|
+
type: 'reject-if-match',
|
|
12
|
+
},
|
|
13
|
+
message: 'GCA tag XOR — never both.',
|
|
14
|
+
};
|
|
15
|
+
const parsed = HookRuleSchema.parse(minimal);
|
|
16
|
+
expect(parsed.recoveryHint).toBeUndefined();
|
|
17
|
+
expect(parsed.verification_shadow).toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
it('accepts a rule with the optional recoveryHint field', () => {
|
|
20
|
+
const withHint = {
|
|
21
|
+
id: 'r1',
|
|
22
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
23
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
24
|
+
message: 'm',
|
|
25
|
+
recoveryHint: 'try y instead',
|
|
26
|
+
};
|
|
27
|
+
const parsed = HookRuleSchema.parse(withHint);
|
|
28
|
+
expect(parsed.recoveryHint).toBe('try y instead');
|
|
29
|
+
});
|
|
30
|
+
it('accepts verification_shadow permissively (V1 warn-and-ignore at runtime)', () => {
|
|
31
|
+
const withShadow = {
|
|
32
|
+
id: 'r1',
|
|
33
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
34
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
35
|
+
message: 'm',
|
|
36
|
+
verification_shadow: { rego: 'package x' },
|
|
37
|
+
};
|
|
38
|
+
// V1 contract: schema accepts the block so a future Spine-Rule
|
|
39
|
+
// promotion doesn't break the parser. Runtime drops/warns on it.
|
|
40
|
+
expect(() => HookRuleSchema.parse(withShadow)).not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
it('rejects rules with empty required strings', () => {
|
|
43
|
+
const empty = {
|
|
44
|
+
id: '',
|
|
45
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
46
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
47
|
+
message: 'm',
|
|
48
|
+
};
|
|
49
|
+
expect(() => HookRuleSchema.parse(empty)).toThrow();
|
|
50
|
+
});
|
|
51
|
+
it('rejects an unknown check.type value', () => {
|
|
52
|
+
const badType = {
|
|
53
|
+
id: 'r1',
|
|
54
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
55
|
+
check: { pattern: 'x', type: 'maybe-reject' },
|
|
56
|
+
message: 'm',
|
|
57
|
+
};
|
|
58
|
+
expect(() => HookRuleSchema.parse(badType)).toThrow();
|
|
59
|
+
});
|
|
60
|
+
it('rejects rules whose trigger.pattern is not a valid regex (parse-time)', () => {
|
|
61
|
+
// Unterminated character class — `new RegExp('[')` throws SyntaxError.
|
|
62
|
+
// Schema must catch this before any `evaluateHook` invocation so a
|
|
63
|
+
// malformed pack rule surfaces at parse time rather than at first-fire.
|
|
64
|
+
const badRegex = {
|
|
65
|
+
id: 'r1',
|
|
66
|
+
trigger: { tool: 'bash', pattern: '[' },
|
|
67
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
68
|
+
message: 'm',
|
|
69
|
+
};
|
|
70
|
+
expect(() => HookRuleSchema.parse(badRegex)).toThrow();
|
|
71
|
+
});
|
|
72
|
+
it('rejects rules whose check.pattern is not a valid regex (parse-time)', () => {
|
|
73
|
+
const badRegex = {
|
|
74
|
+
id: 'r1',
|
|
75
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
76
|
+
check: { pattern: '*invalid', type: 'reject-if-match' },
|
|
77
|
+
message: 'm',
|
|
78
|
+
};
|
|
79
|
+
expect(() => HookRuleSchema.parse(badRegex)).toThrow();
|
|
80
|
+
});
|
|
81
|
+
it('rejects rules whose check.pattern has ReDoS risk (parse-time)', () => {
|
|
82
|
+
// `(a+)+$` is the canonical exponential-backtracking ReDoS shape — runs
|
|
83
|
+
// exponentially in the input length on near-matches. Pack-supplied
|
|
84
|
+
// patterns get a parse-time safety check via safe-regex2 to keep the
|
|
85
|
+
// engine's availability guarantee independent of pack quality.
|
|
86
|
+
const redosRule = {
|
|
87
|
+
id: 'r1',
|
|
88
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
89
|
+
check: { pattern: '(a+)+$', type: 'reject-if-match' },
|
|
90
|
+
message: 'm',
|
|
91
|
+
};
|
|
92
|
+
const result = HookRuleSchema.safeParse(redosRule);
|
|
93
|
+
expect(result.success).toBe(false);
|
|
94
|
+
if (!result.success) {
|
|
95
|
+
const issue = result.error.issues.find((i) => i.message.includes('catastrophic-backtracking'));
|
|
96
|
+
expect(issue).toBeDefined();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it('rejects rules whose pattern exceeds the length cap', () => {
|
|
100
|
+
const oversized = {
|
|
101
|
+
id: 'r1',
|
|
102
|
+
trigger: { tool: 'bash', pattern: 'a'.repeat(600) },
|
|
103
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
104
|
+
message: 'm',
|
|
105
|
+
};
|
|
106
|
+
const result = HookRuleSchema.safeParse(oversized);
|
|
107
|
+
expect(result.success).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('HooksYamlSchema', () => {
|
|
111
|
+
it('accepts a pack-level hooks.yaml with version 1 and an empty hooks array', () => {
|
|
112
|
+
const empty = { version: HOOKS_YAML_SCHEMA_VERSION, hooks: [] };
|
|
113
|
+
expect(() => HooksYamlSchema.parse(empty)).not.toThrow();
|
|
114
|
+
});
|
|
115
|
+
it('accepts version 2 — forward-compat path (runtime warn-and-skip per Decision 4)', () => {
|
|
116
|
+
// Schema accepts any positive integer version. The warn-and-skip on
|
|
117
|
+
// unknown-version is enforced at the load layer, not the schema layer,
|
|
118
|
+
// so a future bot-pack publishing version 2 doesn't crash the parser.
|
|
119
|
+
const future = { version: 2, hooks: [] };
|
|
120
|
+
expect(() => HooksYamlSchema.parse(future)).not.toThrow();
|
|
121
|
+
});
|
|
122
|
+
it('rejects non-positive version values', () => {
|
|
123
|
+
expect(() => HooksYamlSchema.parse({ version: 0, hooks: [] })).toThrow();
|
|
124
|
+
expect(() => HooksYamlSchema.parse({ version: -1, hooks: [] })).toThrow();
|
|
125
|
+
});
|
|
126
|
+
it('accepts hooks with distinct ids', () => {
|
|
127
|
+
const distinct = {
|
|
128
|
+
version: HOOKS_YAML_SCHEMA_VERSION,
|
|
129
|
+
hooks: [
|
|
130
|
+
{
|
|
131
|
+
id: 'r1',
|
|
132
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
133
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
134
|
+
message: 'm1',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'r2',
|
|
138
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
139
|
+
check: { pattern: 'y', type: 'reject-if-match' },
|
|
140
|
+
message: 'm2',
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
expect(() => HooksYamlSchema.parse(distinct)).not.toThrow();
|
|
145
|
+
});
|
|
146
|
+
it('rejects duplicate hook ids within a pack (provenance must stay deterministic)', () => {
|
|
147
|
+
// Two rules with the same id within one pack would make
|
|
148
|
+
// `<packId>/<ruleId>` rejection trails ambiguous (ADR-104 § Decision 1).
|
|
149
|
+
const dup = {
|
|
150
|
+
version: HOOKS_YAML_SCHEMA_VERSION,
|
|
151
|
+
hooks: [
|
|
152
|
+
{
|
|
153
|
+
id: 'r1',
|
|
154
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
155
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
156
|
+
message: 'first',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: 'r1',
|
|
160
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
161
|
+
check: { pattern: 'y', type: 'reject-if-match' },
|
|
162
|
+
message: 'second',
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
const result = HooksYamlSchema.safeParse(dup);
|
|
167
|
+
expect(result.success).toBe(false);
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
const issue = result.error.issues.find((i) => i.message === 'Duplicate hook id within pack: r1');
|
|
170
|
+
expect(issue).toBeDefined();
|
|
171
|
+
expect(issue?.path).toEqual(['hooks', 1, 'id']);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe('CompiledHooksManifestSchema', () => {
|
|
176
|
+
it('accepts a manifest with the required staleness metadata fields', () => {
|
|
177
|
+
const manifest = {
|
|
178
|
+
schemaVersion: COMPILED_HOOKS_SCHEMA_VERSION,
|
|
179
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
180
|
+
sourcePackVersions: {
|
|
181
|
+
'@mmnto/pack-bot-coderabbit': '1.0.0',
|
|
182
|
+
},
|
|
183
|
+
hooks: [],
|
|
184
|
+
};
|
|
185
|
+
expect(() => CompiledHooksManifestSchema.parse(manifest)).not.toThrow();
|
|
186
|
+
});
|
|
187
|
+
it('rejects a manifest missing sourcePackVersions (staleness check requires it)', () => {
|
|
188
|
+
const bad = {
|
|
189
|
+
schemaVersion: COMPILED_HOOKS_SCHEMA_VERSION,
|
|
190
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
191
|
+
hooks: [],
|
|
192
|
+
};
|
|
193
|
+
expect(() => CompiledHooksManifestSchema.parse(bad)).toThrow();
|
|
194
|
+
});
|
|
195
|
+
it('rejects a non-ISO compiledAt string', () => {
|
|
196
|
+
const bad = {
|
|
197
|
+
schemaVersion: COMPILED_HOOKS_SCHEMA_VERSION,
|
|
198
|
+
compiledAt: 'yesterday',
|
|
199
|
+
sourcePackVersions: {},
|
|
200
|
+
hooks: [],
|
|
201
|
+
};
|
|
202
|
+
expect(() => CompiledHooksManifestSchema.parse(bad)).toThrow();
|
|
203
|
+
});
|
|
204
|
+
it('rejects a future schemaVersion (uses z.literal — caller must handle upgrades)', () => {
|
|
205
|
+
const future = {
|
|
206
|
+
schemaVersion: 2,
|
|
207
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
208
|
+
sourcePackVersions: {},
|
|
209
|
+
hooks: [],
|
|
210
|
+
};
|
|
211
|
+
expect(() => CompiledHooksManifestSchema.parse(future)).toThrow();
|
|
212
|
+
});
|
|
213
|
+
it('preserves provenance via packId on each compiled rule', () => {
|
|
214
|
+
const manifest = {
|
|
215
|
+
schemaVersion: COMPILED_HOOKS_SCHEMA_VERSION,
|
|
216
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
217
|
+
sourcePackVersions: { '@mmnto/pack-bot-coderabbit': '1.0.0' },
|
|
218
|
+
hooks: [
|
|
219
|
+
{
|
|
220
|
+
id: 'r1',
|
|
221
|
+
packId: '@mmnto/pack-bot-coderabbit',
|
|
222
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
223
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
224
|
+
message: 'm',
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
const parsed = CompiledHooksManifestSchema.parse(manifest);
|
|
229
|
+
expect(parsed.hooks[0].packId).toBe('@mmnto/pack-bot-coderabbit');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
//# sourceMappingURL=schema.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.test.js","sourceRoot":"","sources":["../../src/hook/schema.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks-surface fixture test runner (ADR-104 § Convergence).
|
|
3
|
+
*
|
|
4
|
+
* Walks fixtures with `surface: hooks` from `.totem/tests/`, looks up the
|
|
5
|
+
* matching compiled hook by id, and evaluates each fixture line against
|
|
6
|
+
* the hook. Failures are reported per-line so authors can iterate on
|
|
7
|
+
* specific examples.
|
|
8
|
+
*
|
|
9
|
+
* Fixture semantics for hooks (corpus-driven):
|
|
10
|
+
* - `corpus: fail` (or `## Should fail` block) — each line is a tool-call
|
|
11
|
+
* payload that MUST cause the hook to reject; lines that allow are
|
|
12
|
+
* `missedFails`.
|
|
13
|
+
* - `corpus: pass` (or `## Should pass` block) — each line is a tool-call
|
|
14
|
+
* payload that MUST allow; lines that reject are `falsePositives`.
|
|
15
|
+
*
|
|
16
|
+
* Both blocks may appear in a single fixture (matches the rules-surface
|
|
17
|
+
* dual-section pattern). The corpus frontmatter field is informational
|
|
18
|
+
* for hooks fixtures — the section names carry the same intent and the
|
|
19
|
+
* runner exercises both.
|
|
20
|
+
*
|
|
21
|
+
* The tool for each payload is sourced from the matched hook's
|
|
22
|
+
* `trigger.tool`. Fixtures do not encode the tool explicitly; the hook
|
|
23
|
+
* declares which tool it gates, so the fixture body is just the args
|
|
24
|
+
* payload the hook would see at runtime.
|
|
25
|
+
*
|
|
26
|
+
* The runner is deterministic Node.js — no LLM calls — mirroring the
|
|
27
|
+
* `totem hook run` contract. Loader warnings and structured errors flow
|
|
28
|
+
* through to the summary so the CLI surface can echo them to stderr.
|
|
29
|
+
*/
|
|
30
|
+
export interface HookTestFailure {
|
|
31
|
+
line: string;
|
|
32
|
+
expected: 'allow' | 'reject';
|
|
33
|
+
actual: 'allow' | 'reject';
|
|
34
|
+
}
|
|
35
|
+
export interface HookTestResult {
|
|
36
|
+
hookId: string;
|
|
37
|
+
packId: string;
|
|
38
|
+
fixturePath: string;
|
|
39
|
+
failures: HookTestFailure[];
|
|
40
|
+
passed: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface HookTestSummary {
|
|
43
|
+
total: number;
|
|
44
|
+
passed: number;
|
|
45
|
+
failed: number;
|
|
46
|
+
/** Fixtures referencing a hook id not present in the loaded manifest. */
|
|
47
|
+
unknownHooks: {
|
|
48
|
+
fixturePath: string;
|
|
49
|
+
hookId: string;
|
|
50
|
+
}[];
|
|
51
|
+
results: HookTestResult[];
|
|
52
|
+
loadWarnings: string[];
|
|
53
|
+
loadErrors: {
|
|
54
|
+
code: string;
|
|
55
|
+
message: string;
|
|
56
|
+
}[];
|
|
57
|
+
}
|
|
58
|
+
export interface RunHookTestsOptions {
|
|
59
|
+
manifestPath: string;
|
|
60
|
+
testsDir: string;
|
|
61
|
+
installedPackVersions: Record<string, string>;
|
|
62
|
+
}
|
|
63
|
+
export declare function runHookTests(options: RunHookTestsOptions): HookTestSummary;
|
|
64
|
+
//# sourceMappingURL=test-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-runner.d.ts","sourceRoot":"","sources":["../../src/hook/test-runner.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC7B,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,YAAY,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxD,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,eAAe,CA+B1E"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { loadFixtures } from '@mmnto/totem';
|
|
2
|
+
import { loadCompiledHooks } from './loader.js';
|
|
3
|
+
import { evaluateHook } from './runtime.js';
|
|
4
|
+
export function runHookTests(options) {
|
|
5
|
+
const { hooks, warnings, errors } = loadCompiledHooks({
|
|
6
|
+
manifestPath: options.manifestPath,
|
|
7
|
+
installedPackVersions: options.installedPackVersions,
|
|
8
|
+
});
|
|
9
|
+
const fixtures = loadFixtures(options.testsDir).filter((f) => f.surface === 'hooks');
|
|
10
|
+
const hooksById = new Map(hooks.map((h) => [h.id, h]));
|
|
11
|
+
const results = [];
|
|
12
|
+
const unknownHooks = [];
|
|
13
|
+
for (const fixture of fixtures) {
|
|
14
|
+
const hook = hooksById.get(fixture.ruleHash);
|
|
15
|
+
if (!hook) {
|
|
16
|
+
unknownHooks.push({ fixturePath: fixture.fixturePath, hookId: fixture.ruleHash });
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
results.push(testHook(hook, fixture));
|
|
20
|
+
}
|
|
21
|
+
const passed = results.filter((r) => r.passed).length;
|
|
22
|
+
return {
|
|
23
|
+
total: results.length,
|
|
24
|
+
passed,
|
|
25
|
+
failed: results.length - passed,
|
|
26
|
+
unknownHooks,
|
|
27
|
+
results,
|
|
28
|
+
loadWarnings: warnings,
|
|
29
|
+
loadErrors: errors.map((e) => ({ code: e.code, message: e.message })),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function testHook(hook, fixture) {
|
|
33
|
+
const failures = [];
|
|
34
|
+
const tool = hook.trigger.tool;
|
|
35
|
+
for (const args of fixture.failLines) {
|
|
36
|
+
const payload = { tool, args };
|
|
37
|
+
const decision = evaluateHook(hook, payload).decision;
|
|
38
|
+
if (decision !== 'reject') {
|
|
39
|
+
failures.push({ line: args, expected: 'reject', actual: decision });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const args of fixture.passLines) {
|
|
43
|
+
const payload = { tool, args };
|
|
44
|
+
const decision = evaluateHook(hook, payload).decision;
|
|
45
|
+
if (decision !== 'allow') {
|
|
46
|
+
failures.push({ line: args, expected: 'allow', actual: decision });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
hookId: hook.id,
|
|
51
|
+
packId: hook.packId,
|
|
52
|
+
fixturePath: fixture.fixturePath,
|
|
53
|
+
failures,
|
|
54
|
+
passed: failures.length === 0,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=test-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-runner.js","sourceRoot":"","sources":["../../src/hook/test-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-runner.test.d.ts","sourceRoot":"","sources":["../../src/hook/test-runner.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { applyFilter } from '../commands/hook-test.js';
|
|
6
|
+
import { runHookTests } from './test-runner.js';
|
|
7
|
+
let workDir;
|
|
8
|
+
let testsDir;
|
|
9
|
+
let manifestPath;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-hook-test-runner-'));
|
|
12
|
+
testsDir = path.join(workDir, 'tests');
|
|
13
|
+
fs.mkdirSync(testsDir);
|
|
14
|
+
manifestPath = path.join(workDir, 'compiled-hooks.json');
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
const xorHook = {
|
|
20
|
+
id: 'gca-tag-xor-command',
|
|
21
|
+
packId: '@mmnto/pack-bot-gemini-code-assist',
|
|
22
|
+
trigger: { tool: 'bash', pattern: 'gh\\s+(pr|issue)\\s+comment' },
|
|
23
|
+
check: {
|
|
24
|
+
pattern: '(?=.*@gemini-code-assist)(?=.*\\/gemini review)',
|
|
25
|
+
type: 'reject-if-match',
|
|
26
|
+
},
|
|
27
|
+
message: 'GCA tag XOR command — never both; doubling wastes GCA quota.',
|
|
28
|
+
};
|
|
29
|
+
function writeManifest(hooks = [xorHook]) {
|
|
30
|
+
fs.writeFileSync(manifestPath, JSON.stringify({
|
|
31
|
+
schemaVersion: 1,
|
|
32
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
33
|
+
sourcePackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
34
|
+
hooks,
|
|
35
|
+
}), 'utf8');
|
|
36
|
+
}
|
|
37
|
+
function writeFixture(filename, content) {
|
|
38
|
+
fs.writeFileSync(path.join(testsDir, filename), content, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
describe('runHookTests', () => {
|
|
41
|
+
it('returns empty results when no fixtures exist', () => {
|
|
42
|
+
writeManifest();
|
|
43
|
+
const summary = runHookTests({
|
|
44
|
+
manifestPath,
|
|
45
|
+
testsDir,
|
|
46
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
47
|
+
});
|
|
48
|
+
expect(summary.total).toBe(0);
|
|
49
|
+
expect(summary.results).toEqual([]);
|
|
50
|
+
expect(summary.unknownHooks).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
it('skips fixtures whose surface defaults to rules (backwards-compat)', () => {
|
|
53
|
+
writeManifest();
|
|
54
|
+
writeFixture('rule-fixture.md', `---
|
|
55
|
+
rule: some-rule-hash
|
|
56
|
+
file: src/app.ts
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Should fail
|
|
60
|
+
|
|
61
|
+
\`\`\`ts
|
|
62
|
+
unsafe_pattern
|
|
63
|
+
\`\`\`
|
|
64
|
+
`);
|
|
65
|
+
const summary = runHookTests({
|
|
66
|
+
manifestPath,
|
|
67
|
+
testsDir,
|
|
68
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
69
|
+
});
|
|
70
|
+
expect(summary.total).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
it('reports passed=true when every fail-section line rejects', () => {
|
|
73
|
+
writeManifest();
|
|
74
|
+
writeFixture('xor-fail.md', `---
|
|
75
|
+
rule: gca-tag-xor-command
|
|
76
|
+
file: hook-fixtures/gca-fail.txt
|
|
77
|
+
surface: hooks
|
|
78
|
+
corpus: fail
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Should fail
|
|
82
|
+
|
|
83
|
+
\`\`\`text
|
|
84
|
+
gh pr comment 1 -b "@gemini-code-assist /gemini review please"
|
|
85
|
+
gh issue comment 2 -b "@gemini-code-assist /gemini review now"
|
|
86
|
+
\`\`\`
|
|
87
|
+
`);
|
|
88
|
+
const summary = runHookTests({
|
|
89
|
+
manifestPath,
|
|
90
|
+
testsDir,
|
|
91
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
92
|
+
});
|
|
93
|
+
expect(summary.total).toBe(1);
|
|
94
|
+
expect(summary.passed).toBe(1);
|
|
95
|
+
expect(summary.failed).toBe(0);
|
|
96
|
+
expect(summary.results[0].failures).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
it('reports failed when a fail-section line does not reject (missed reject)', () => {
|
|
99
|
+
writeManifest();
|
|
100
|
+
writeFixture('xor-fail.md', `---
|
|
101
|
+
rule: gca-tag-xor-command
|
|
102
|
+
file: hook-fixtures/gca-fail.txt
|
|
103
|
+
surface: hooks
|
|
104
|
+
corpus: fail
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Should fail
|
|
108
|
+
|
|
109
|
+
\`\`\`text
|
|
110
|
+
gh pr comment 1 -b "@gemini-code-assist take a look"
|
|
111
|
+
\`\`\`
|
|
112
|
+
`);
|
|
113
|
+
const summary = runHookTests({
|
|
114
|
+
manifestPath,
|
|
115
|
+
testsDir,
|
|
116
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
117
|
+
});
|
|
118
|
+
expect(summary.failed).toBe(1);
|
|
119
|
+
expect(summary.results[0].failures.length).toBe(1);
|
|
120
|
+
expect(summary.results[0].failures[0]).toMatchObject({
|
|
121
|
+
expected: 'reject',
|
|
122
|
+
actual: 'allow',
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it('reports failed when a pass-section line rejects (false positive)', () => {
|
|
126
|
+
writeManifest();
|
|
127
|
+
writeFixture('xor-pass.md', `---
|
|
128
|
+
rule: gca-tag-xor-command
|
|
129
|
+
file: hook-fixtures/gca-pass.txt
|
|
130
|
+
surface: hooks
|
|
131
|
+
corpus: pass
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Should pass
|
|
135
|
+
|
|
136
|
+
\`\`\`text
|
|
137
|
+
gh pr comment 1 -b "@gemini-code-assist /gemini review please"
|
|
138
|
+
\`\`\`
|
|
139
|
+
`);
|
|
140
|
+
const summary = runHookTests({
|
|
141
|
+
manifestPath,
|
|
142
|
+
testsDir,
|
|
143
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
144
|
+
});
|
|
145
|
+
expect(summary.failed).toBe(1);
|
|
146
|
+
expect(summary.results[0].failures[0]).toMatchObject({
|
|
147
|
+
expected: 'allow',
|
|
148
|
+
actual: 'reject',
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
it('flags fixtures referencing a hook id absent from the manifest under unknownHooks', () => {
|
|
152
|
+
writeManifest();
|
|
153
|
+
writeFixture('orphan.md', `---
|
|
154
|
+
rule: does-not-exist
|
|
155
|
+
file: hook-fixtures/orphan.txt
|
|
156
|
+
surface: hooks
|
|
157
|
+
corpus: fail
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Should fail
|
|
161
|
+
|
|
162
|
+
\`\`\`text
|
|
163
|
+
git push --force origin main
|
|
164
|
+
\`\`\`
|
|
165
|
+
`);
|
|
166
|
+
const summary = runHookTests({
|
|
167
|
+
manifestPath,
|
|
168
|
+
testsDir,
|
|
169
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
170
|
+
});
|
|
171
|
+
expect(summary.total).toBe(0);
|
|
172
|
+
expect(summary.unknownHooks).toEqual([expect.objectContaining({ hookId: 'does-not-exist' })]);
|
|
173
|
+
});
|
|
174
|
+
it('surfaces loadWarnings and loadErrors from the manifest loader', () => {
|
|
175
|
+
fs.writeFileSync(manifestPath, '{ not valid json', 'utf8');
|
|
176
|
+
const summary = runHookTests({
|
|
177
|
+
manifestPath,
|
|
178
|
+
testsDir,
|
|
179
|
+
installedPackVersions: {},
|
|
180
|
+
});
|
|
181
|
+
expect(summary.loadErrors.length).toBe(1);
|
|
182
|
+
expect(summary.loadErrors[0].code).toBe('HOOKS_LOAD_FAILED');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe('applyFilter (hookTestCommand --filter contract)', () => {
|
|
186
|
+
function fakeResult(hookId, passed = true) {
|
|
187
|
+
return {
|
|
188
|
+
hookId,
|
|
189
|
+
packId: '@mmnto/pack-bot-coderabbit',
|
|
190
|
+
fixturePath: `tests/${hookId}.md`,
|
|
191
|
+
failures: [],
|
|
192
|
+
passed,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function fakeSummary(overrides = {}) {
|
|
196
|
+
const results = [
|
|
197
|
+
fakeResult('alpha'),
|
|
198
|
+
fakeResult('beta'),
|
|
199
|
+
fakeResult('gamma'),
|
|
200
|
+
];
|
|
201
|
+
return {
|
|
202
|
+
total: results.length,
|
|
203
|
+
passed: results.length,
|
|
204
|
+
failed: 0,
|
|
205
|
+
unknownHooks: [],
|
|
206
|
+
results,
|
|
207
|
+
loadWarnings: [],
|
|
208
|
+
loadErrors: [],
|
|
209
|
+
...overrides,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
it('returns all results when filter is undefined', () => {
|
|
213
|
+
const summary = fakeSummary();
|
|
214
|
+
expect(applyFilter(summary, undefined).map((r) => r.hookId)).toEqual([
|
|
215
|
+
'alpha',
|
|
216
|
+
'beta',
|
|
217
|
+
'gamma',
|
|
218
|
+
]);
|
|
219
|
+
});
|
|
220
|
+
it('returns only matching results when filter matches a substring', () => {
|
|
221
|
+
const summary = fakeSummary();
|
|
222
|
+
expect(applyFilter(summary, 'eta').map((r) => r.hookId)).toEqual(['beta']);
|
|
223
|
+
});
|
|
224
|
+
it('matches case-insensitively', () => {
|
|
225
|
+
const summary = fakeSummary();
|
|
226
|
+
expect(applyFilter(summary, 'ALPHA').map((r) => r.hookId)).toEqual(['alpha']);
|
|
227
|
+
});
|
|
228
|
+
it('throws TEST_FAILED when --filter matches nothing AND fixtures exist (typoed filter guard)', () => {
|
|
229
|
+
const summary = fakeSummary();
|
|
230
|
+
expect(() => applyFilter(summary, 'no-such-hook')).toThrow(/No hook tests matched/);
|
|
231
|
+
});
|
|
232
|
+
it('does NOT throw when filter is set but no fixtures exist (the placeholder branch handles that)', () => {
|
|
233
|
+
const summary = fakeSummary({ total: 0, results: [] });
|
|
234
|
+
expect(applyFilter(summary, 'anything')).toEqual([]);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
//# sourceMappingURL=test-runner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-runner.test.js","sourceRoot":"","sources":["../../src/hook/test-runner.test.ts"],"names":[],"mappings":"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"}
|