@mmnto/cli 1.35.0 → 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/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/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,91 @@
|
|
|
1
|
+
import { type ToolCallPayload } from '../hook/runtime.js';
|
|
2
|
+
/**
|
|
3
|
+
* `totem hook run --tool <name> --args <args>` — the PreToolUse runtime
|
|
4
|
+
* entrypoint (ADR-104 § Decisions 1, 2, 3 + § Convergence).
|
|
5
|
+
*
|
|
6
|
+
* Loads `.totem/compiled-hooks.json`, evaluates each compiled hook against
|
|
7
|
+
* the tool-call payload, and emits a structured `[totem:hook-block]`
|
|
8
|
+
* rejection to stderr (exit code 2) on the first match. Allow path is exit
|
|
9
|
+
* code 0 with no output.
|
|
10
|
+
*
|
|
11
|
+
* The runtime is deterministic Node.js — no LLM calls in this path
|
|
12
|
+
* (Tenet 15 corollary, ADR-103 § 8). The engine bootstrap (AST/language
|
|
13
|
+
* registration via `loadInstalledPacks`) is intentionally NOT invoked here
|
|
14
|
+
* because hook evaluation is regex-only in V1 and the bootstrap pulls
|
|
15
|
+
* heavy runtime deps that we cannot afford on every tool call.
|
|
16
|
+
*
|
|
17
|
+
* Failure modes are best-effort per ADR-104 § Decision 3 + Tenet 4 carve-out:
|
|
18
|
+
* - Missing manifest (fresh repo, no installed pack hooks): exit 0 silently.
|
|
19
|
+
* - Stale pack versions, schemaVersion mismatch, corrupt JSON: emit
|
|
20
|
+
* structured warnings/errors to stderr, allow the tool call.
|
|
21
|
+
* - Only an explicit `reject` decision from `evaluateHook` blocks.
|
|
22
|
+
*/
|
|
23
|
+
export interface HookRunCommandOptions {
|
|
24
|
+
tool: string;
|
|
25
|
+
args: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Side-effect-free result for the command driver. Returns the exit code
|
|
29
|
+
* the CLI wrapper should propagate, plus the stderr lines that should
|
|
30
|
+
* accompany it. Keeps `executeHookRun` testable without spying on
|
|
31
|
+
* `process.exit` or `console.error`.
|
|
32
|
+
*/
|
|
33
|
+
export interface HookRunResult {
|
|
34
|
+
exitCode: 0 | 2;
|
|
35
|
+
stderr: string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Injectable inputs for `executeHookRun`. The CLI wrapper resolves these
|
|
39
|
+
* from the working directory; tests construct them directly to avoid
|
|
40
|
+
* touching the real filesystem or `process.cwd()`.
|
|
41
|
+
*/
|
|
42
|
+
export interface HookRunInputs {
|
|
43
|
+
manifestPath: string;
|
|
44
|
+
installedPackVersions: Record<string, string>;
|
|
45
|
+
payload: ToolCallPayload;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The exit code Claude Code's PreToolUse hook convention treats as "block
|
|
49
|
+
* the tool call." Exported so test suites assert against the same constant
|
|
50
|
+
* rather than against a magic number that could drift.
|
|
51
|
+
*/
|
|
52
|
+
export declare const EXIT_ALLOW: 0;
|
|
53
|
+
export declare const PRE_TOOL_USE_BLOCK_EXIT_CODE: 2;
|
|
54
|
+
export declare function hookRunCommand(opts: HookRunCommandOptions): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Pure synchronous core. Given resolved inputs, returns the exit code and
|
|
57
|
+
* stderr lines the wrapper should emit. No process state is touched.
|
|
58
|
+
*
|
|
59
|
+
* Two-phase contract:
|
|
60
|
+
* 1. Load + emit diagnostics (warnings, structured errors) regardless of
|
|
61
|
+
* whether any hooks loaded successfully.
|
|
62
|
+
* 2. Walk loaded hooks until the first reject; allow when none reject.
|
|
63
|
+
*
|
|
64
|
+
* Order in `stderr` is preserved: load-time diagnostics come before any
|
|
65
|
+
* rejection line, so an operator inspecting stderr sees the staleness or
|
|
66
|
+
* schema-mismatch context that may explain why a rejection fired.
|
|
67
|
+
*/
|
|
68
|
+
export declare function executeHookRun(inputs: HookRunInputs): HookRunResult;
|
|
69
|
+
/**
|
|
70
|
+
* Scan `<projectRoot>/node_modules/@mmnto/pack-*` and read each pack's
|
|
71
|
+
* `package.json` version field. Returns a `packName → version` map for
|
|
72
|
+
* the compiled-hooks loader's staleness check.
|
|
73
|
+
*
|
|
74
|
+
* Bounded cost: one `readdirSync` plus N `readFileSync` calls (N = number
|
|
75
|
+
* of installed `@mmnto/pack-*` packages, typically <5). Expected per-pack
|
|
76
|
+
* read/parse failures (ENOENT/ENOTDIR/EPERM/EACCES on the package.json,
|
|
77
|
+
* SyntaxError on malformed JSON) are skipped so the loader can emit
|
|
78
|
+
* `[totem:hook-stale]` warnings for missing/invalid packs — the correct
|
|
79
|
+
* signal for an operator who has uninstalled a pack but not re-run
|
|
80
|
+
* `totem sync`. Unexpected exceptions (IO timeout, OOM, etc.) are
|
|
81
|
+
* rethrown to surface real faults. The outer `readdirSync` on the
|
|
82
|
+
* `@mmnto` scope dir applies the same discipline: ENOENT returns empty;
|
|
83
|
+
* any other errno surfaces as a thrown error.
|
|
84
|
+
*
|
|
85
|
+
* Workspace setups (pnpm workspace, yarn workspaces) symlink the pack
|
|
86
|
+
* directory into `node_modules/@mmnto/`, so the `readdirSync` + JSON
|
|
87
|
+
* read traverses the symlink transparently and reads the source-of-truth
|
|
88
|
+
* `package.json`. No special case needed.
|
|
89
|
+
*/
|
|
90
|
+
export declare function resolveInstalledPackVersions(projectRoot: string): Record<string, string>;
|
|
91
|
+
//# sourceMappingURL=hook-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-run.d.ts","sourceRoot":"","sources":["../../src/commands/hook-run.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiC,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEzF;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAG,CAAU,CAAC;AACrC,eAAO,MAAM,4BAA4B,EAAG,CAAU,CAAC;AAEvD,wBAAsB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC/E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAmBnE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmDxF"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadCompiledHooks } from '../hook/loader.js';
|
|
4
|
+
import { evaluateHook, formatRejection } from '../hook/runtime.js';
|
|
5
|
+
/**
|
|
6
|
+
* The exit code Claude Code's PreToolUse hook convention treats as "block
|
|
7
|
+
* the tool call." Exported so test suites assert against the same constant
|
|
8
|
+
* rather than against a magic number that could drift.
|
|
9
|
+
*/
|
|
10
|
+
export const EXIT_ALLOW = 0;
|
|
11
|
+
export const PRE_TOOL_USE_BLOCK_EXIT_CODE = 2;
|
|
12
|
+
export async function hookRunCommand(opts) {
|
|
13
|
+
const { loadConfig, resolveConfigPath } = await import('../utils.js');
|
|
14
|
+
const { sanitize } = await import('@mmnto/totem');
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const configPath = resolveConfigPath(cwd);
|
|
17
|
+
const config = await loadConfig(configPath);
|
|
18
|
+
const configRoot = path.dirname(configPath);
|
|
19
|
+
const manifestPath = path.join(configRoot, config.totemDir, 'compiled-hooks.json');
|
|
20
|
+
const installedPackVersions = resolveInstalledPackVersions(configRoot);
|
|
21
|
+
const result = executeHookRun({
|
|
22
|
+
manifestPath,
|
|
23
|
+
installedPackVersions,
|
|
24
|
+
payload: { tool: opts.tool, args: opts.args },
|
|
25
|
+
});
|
|
26
|
+
// Sanitize every stderr line at the terminal boundary — the lines flow
|
|
27
|
+
// through `executeHookRun` carrying pack-supplied content (rejection
|
|
28
|
+
// messages, recovery hints, hook ids, loader warnings interpolating pack
|
|
29
|
+
// names). Sanitizing once at the wrapper is simpler than instrumenting
|
|
30
|
+
// every emitter, and the pure-function tests already assert structural
|
|
31
|
+
// shape on the unsanitized form.
|
|
32
|
+
for (const line of result.stderr) {
|
|
33
|
+
console.error(sanitize(line));
|
|
34
|
+
}
|
|
35
|
+
if (result.exitCode !== 0) {
|
|
36
|
+
// Set process.exitCode rather than calling process.exit() so the top-level
|
|
37
|
+
// handler stays in control of shutdown ordering (per the codebase guideline
|
|
38
|
+
// against process.exit() inside command bodies). PreToolUse semantics are
|
|
39
|
+
// preserved — Node exits with the assigned code once the event loop drains.
|
|
40
|
+
process.exitCode = result.exitCode;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Pure synchronous core. Given resolved inputs, returns the exit code and
|
|
46
|
+
* stderr lines the wrapper should emit. No process state is touched.
|
|
47
|
+
*
|
|
48
|
+
* Two-phase contract:
|
|
49
|
+
* 1. Load + emit diagnostics (warnings, structured errors) regardless of
|
|
50
|
+
* whether any hooks loaded successfully.
|
|
51
|
+
* 2. Walk loaded hooks until the first reject; allow when none reject.
|
|
52
|
+
*
|
|
53
|
+
* Order in `stderr` is preserved: load-time diagnostics come before any
|
|
54
|
+
* rejection line, so an operator inspecting stderr sees the staleness or
|
|
55
|
+
* schema-mismatch context that may explain why a rejection fired.
|
|
56
|
+
*/
|
|
57
|
+
export function executeHookRun(inputs) {
|
|
58
|
+
const { hooks, warnings, errors } = loadCompiledHooks({
|
|
59
|
+
manifestPath: inputs.manifestPath,
|
|
60
|
+
installedPackVersions: inputs.installedPackVersions,
|
|
61
|
+
});
|
|
62
|
+
const stderr = [];
|
|
63
|
+
for (const w of warnings)
|
|
64
|
+
stderr.push(w);
|
|
65
|
+
for (const e of errors)
|
|
66
|
+
stderr.push(`[totem:hook-error] ${e.message}`);
|
|
67
|
+
for (const rule of hooks) {
|
|
68
|
+
const decision = evaluateHook(rule, inputs.payload);
|
|
69
|
+
if (decision.decision === 'reject') {
|
|
70
|
+
stderr.push(formatRejection(decision));
|
|
71
|
+
return { exitCode: PRE_TOOL_USE_BLOCK_EXIT_CODE, stderr };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { exitCode: 0, stderr };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Scan `<projectRoot>/node_modules/@mmnto/pack-*` and read each pack's
|
|
78
|
+
* `package.json` version field. Returns a `packName → version` map for
|
|
79
|
+
* the compiled-hooks loader's staleness check.
|
|
80
|
+
*
|
|
81
|
+
* Bounded cost: one `readdirSync` plus N `readFileSync` calls (N = number
|
|
82
|
+
* of installed `@mmnto/pack-*` packages, typically <5). Expected per-pack
|
|
83
|
+
* read/parse failures (ENOENT/ENOTDIR/EPERM/EACCES on the package.json,
|
|
84
|
+
* SyntaxError on malformed JSON) are skipped so the loader can emit
|
|
85
|
+
* `[totem:hook-stale]` warnings for missing/invalid packs — the correct
|
|
86
|
+
* signal for an operator who has uninstalled a pack but not re-run
|
|
87
|
+
* `totem sync`. Unexpected exceptions (IO timeout, OOM, etc.) are
|
|
88
|
+
* rethrown to surface real faults. The outer `readdirSync` on the
|
|
89
|
+
* `@mmnto` scope dir applies the same discipline: ENOENT returns empty;
|
|
90
|
+
* any other errno surfaces as a thrown error.
|
|
91
|
+
*
|
|
92
|
+
* Workspace setups (pnpm workspace, yarn workspaces) symlink the pack
|
|
93
|
+
* directory into `node_modules/@mmnto/`, so the `readdirSync` + JSON
|
|
94
|
+
* read traverses the symlink transparently and reads the source-of-truth
|
|
95
|
+
* `package.json`. No special case needed.
|
|
96
|
+
*/
|
|
97
|
+
export function resolveInstalledPackVersions(projectRoot) {
|
|
98
|
+
const result = {};
|
|
99
|
+
const scopeDir = path.join(projectRoot, 'node_modules', '@mmnto');
|
|
100
|
+
let entries;
|
|
101
|
+
try {
|
|
102
|
+
entries = fs.readdirSync(scopeDir, { withFileTypes: true });
|
|
103
|
+
// totem-context: intentional — ENOENT on the scope dir is the valid
|
|
104
|
+
// fresh-repo state (no `@mmnto` packs installed). Any other errno
|
|
105
|
+
// (EACCES, permission denied, etc.) is a real fault that should surface.
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
if (err.code === 'ENOENT') {
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
// Don't pre-filter on `entry.isDirectory()`: in pnpm/yarn workspaces a
|
|
115
|
+
// pack is symlinked into `node_modules/@mmnto/`, and a Dirent for a
|
|
116
|
+
// symlink reports `isDirectory()` false even when the target is a
|
|
117
|
+
// directory. The `readFileSync` below transparently traverses the
|
|
118
|
+
// symlink and the surrounding `try/catch` discards any path that does
|
|
119
|
+
// not resolve to a readable package.json.
|
|
120
|
+
if (!entry.name.startsWith('pack-'))
|
|
121
|
+
continue;
|
|
122
|
+
const packName = `@mmnto/${entry.name}`;
|
|
123
|
+
try {
|
|
124
|
+
const pkgRaw = fs.readFileSync(path.join(scopeDir, entry.name, 'package.json'), 'utf8');
|
|
125
|
+
const pkg = JSON.parse(pkgRaw);
|
|
126
|
+
if (typeof pkg.version === 'string') {
|
|
127
|
+
result[packName] = pkg.version;
|
|
128
|
+
}
|
|
129
|
+
// totem-context: intentional — the expected per-pack failure modes
|
|
130
|
+
// (missing/corrupt package.json, permission-quirk on individual pack
|
|
131
|
+
// dirs) are non-fatal; the loader emits a stale warning for any pack
|
|
132
|
+
// referenced in the manifest but missing from this map. Unusual
|
|
133
|
+
// errors (IO timeout, OOM, etc.) are not in the expected set and
|
|
134
|
+
// surface as a real fault so debug consumers can find them.
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
const code = err.code;
|
|
138
|
+
if (code === 'ENOENT' || code === 'ENOTDIR' || code === 'EPERM' || code === 'EACCES') {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (err instanceof SyntaxError) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=hook-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-run.js","sourceRoot":"","sources":["../../src/commands/hook-run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-run.test.d.ts","sourceRoot":"","sources":["../../src/commands/hook-run.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,264 @@
|
|
|
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, vi } from 'vitest';
|
|
5
|
+
import { executeHookRun, EXIT_ALLOW, PRE_TOOL_USE_BLOCK_EXIT_CODE as EXIT_BLOCK, resolveInstalledPackVersions, } from './hook-run.js';
|
|
6
|
+
/** Schema version no live runner supports — used to assert warn-and-skip on unknown manifest versions. */
|
|
7
|
+
const UNSUPPORTED_SCHEMA_VERSION = 999;
|
|
8
|
+
let workDir;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-hook-run-'));
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
function writeManifest(content) {
|
|
16
|
+
const manifestPath = path.join(workDir, 'compiled-hooks.json');
|
|
17
|
+
fs.writeFileSync(manifestPath, JSON.stringify(content), 'utf8');
|
|
18
|
+
return manifestPath;
|
|
19
|
+
}
|
|
20
|
+
const baseRule = {
|
|
21
|
+
id: 'gca-tag-xor-command',
|
|
22
|
+
packId: '@mmnto/pack-bot-gemini-code-assist',
|
|
23
|
+
trigger: { tool: 'bash', pattern: 'gh\\s+(pr|issue)\\s+comment' },
|
|
24
|
+
check: {
|
|
25
|
+
pattern: '(?=.*@gemini-code-assist)(?=.*\\/gemini review)',
|
|
26
|
+
type: 'reject-if-match',
|
|
27
|
+
},
|
|
28
|
+
message: 'GCA tag XOR command — never both; doubling wastes GCA quota.',
|
|
29
|
+
recoveryHint: 'Choose one: @-mention to comment, /gemini review for fresh review.',
|
|
30
|
+
};
|
|
31
|
+
function baseManifest(hooks = [baseRule]) {
|
|
32
|
+
return {
|
|
33
|
+
schemaVersion: 1,
|
|
34
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
35
|
+
sourcePackVersions: {
|
|
36
|
+
'@mmnto/pack-bot-gemini-code-assist': '1.0.0',
|
|
37
|
+
},
|
|
38
|
+
hooks,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
describe('executeHookRun — allow path', () => {
|
|
42
|
+
it('returns exit 0 with no stderr when the manifest file is absent (fresh repo)', () => {
|
|
43
|
+
const result = executeHookRun({
|
|
44
|
+
manifestPath: path.join(workDir, 'missing.json'),
|
|
45
|
+
installedPackVersions: {},
|
|
46
|
+
payload: { tool: 'bash', args: 'whatever' },
|
|
47
|
+
});
|
|
48
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
49
|
+
expect(result.stderr).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
it('returns exit 0 when no hook trigger matches the payload', () => {
|
|
52
|
+
const manifestPath = writeManifest(baseManifest());
|
|
53
|
+
const result = executeHookRun({
|
|
54
|
+
manifestPath,
|
|
55
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
56
|
+
payload: { tool: 'write', args: 'console.log("hi")' },
|
|
57
|
+
});
|
|
58
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
59
|
+
expect(result.stderr).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
it('returns exit 0 when the trigger matches but the check does not (reject-if-match)', () => {
|
|
62
|
+
const manifestPath = writeManifest(baseManifest());
|
|
63
|
+
const result = executeHookRun({
|
|
64
|
+
manifestPath,
|
|
65
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
66
|
+
payload: {
|
|
67
|
+
tool: 'bash',
|
|
68
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist take a look"',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
72
|
+
expect(result.stderr).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('executeHookRun — reject path (ADR-104 § Decision 1)', () => {
|
|
76
|
+
it('exits 2 with the structured [totem:hook-block] line on the first matching hook', () => {
|
|
77
|
+
const manifestPath = writeManifest(baseManifest());
|
|
78
|
+
const result = executeHookRun({
|
|
79
|
+
manifestPath,
|
|
80
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
81
|
+
payload: {
|
|
82
|
+
tool: 'bash',
|
|
83
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review please"',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
87
|
+
const rejectLine = result.stderr[result.stderr.length - 1];
|
|
88
|
+
expect(rejectLine).toContain('[totem:hook-block]');
|
|
89
|
+
expect(rejectLine).toContain('@mmnto/pack-bot-gemini-code-assist/gca-tag-xor-command');
|
|
90
|
+
expect(rejectLine).toContain('GCA tag XOR command');
|
|
91
|
+
expect(rejectLine).toContain('→ Choose one');
|
|
92
|
+
});
|
|
93
|
+
it('first-match-wins: a later hook that also matches is not evaluated', () => {
|
|
94
|
+
const second = {
|
|
95
|
+
...baseRule,
|
|
96
|
+
id: 'never-reached',
|
|
97
|
+
message: 'should not appear in stderr',
|
|
98
|
+
};
|
|
99
|
+
const manifestPath = writeManifest(baseManifest([baseRule, second]));
|
|
100
|
+
const result = executeHookRun({
|
|
101
|
+
manifestPath,
|
|
102
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
103
|
+
payload: {
|
|
104
|
+
tool: 'bash',
|
|
105
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review"',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
109
|
+
expect(result.stderr.some((l) => l.includes('never-reached'))).toBe(false);
|
|
110
|
+
expect(result.stderr.some((l) => l.includes('should not appear'))).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
it('omits the recoveryHint line when the rule has no recoveryHint', () => {
|
|
113
|
+
const ruleWithoutHint = { ...baseRule, recoveryHint: undefined };
|
|
114
|
+
const manifestPath = writeManifest(baseManifest([ruleWithoutHint]));
|
|
115
|
+
const result = executeHookRun({
|
|
116
|
+
manifestPath,
|
|
117
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
118
|
+
payload: {
|
|
119
|
+
tool: 'bash',
|
|
120
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review"',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
124
|
+
const rejectLine = result.stderr[result.stderr.length - 1];
|
|
125
|
+
expect(rejectLine).not.toContain('→');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('executeHookRun — diagnostics ordering', () => {
|
|
129
|
+
it('emits [totem:hook-stale] warnings before evaluating hooks (operator sees context for any rejection)', () => {
|
|
130
|
+
const manifestPath = writeManifest(baseManifest());
|
|
131
|
+
const result = executeHookRun({
|
|
132
|
+
manifestPath,
|
|
133
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.1.0' },
|
|
134
|
+
payload: {
|
|
135
|
+
tool: 'bash',
|
|
136
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review"',
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
140
|
+
const staleIdx = result.stderr.findIndex((l) => l.includes('[totem:hook-stale]'));
|
|
141
|
+
const rejectIdx = result.stderr.findIndex((l) => l.includes('[totem:hook-block]'));
|
|
142
|
+
expect(staleIdx).toBeGreaterThanOrEqual(0);
|
|
143
|
+
expect(rejectIdx).toBeGreaterThan(staleIdx);
|
|
144
|
+
});
|
|
145
|
+
it('emits [totem:hook-schema] warning and returns allow when manifest schemaVersion is unsupported', () => {
|
|
146
|
+
const manifestPath = writeManifest({
|
|
147
|
+
...baseManifest(),
|
|
148
|
+
schemaVersion: UNSUPPORTED_SCHEMA_VERSION,
|
|
149
|
+
});
|
|
150
|
+
const result = executeHookRun({
|
|
151
|
+
manifestPath,
|
|
152
|
+
installedPackVersions: {},
|
|
153
|
+
payload: { tool: 'bash', args: 'whatever' },
|
|
154
|
+
});
|
|
155
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
156
|
+
expect(result.stderr.some((l) => l.includes('[totem:hook-schema]'))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
it('emits [totem:hook-error] prefix on structural manifest errors (corrupt JSON)', () => {
|
|
159
|
+
const manifestPath = path.join(workDir, 'compiled-hooks.json');
|
|
160
|
+
fs.writeFileSync(manifestPath, '{ not valid json', 'utf8');
|
|
161
|
+
const result = executeHookRun({
|
|
162
|
+
manifestPath,
|
|
163
|
+
installedPackVersions: {},
|
|
164
|
+
payload: { tool: 'bash', args: 'whatever' },
|
|
165
|
+
});
|
|
166
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
167
|
+
expect(result.stderr.some((l) => l.startsWith('[totem:hook-error]'))).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe('resolveInstalledPackVersions', () => {
|
|
171
|
+
it('returns an empty map when no @mmnto scope directory exists', () => {
|
|
172
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
173
|
+
expect(result).toEqual({});
|
|
174
|
+
});
|
|
175
|
+
it('reads version from each @mmnto/pack-* package.json under node_modules', () => {
|
|
176
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
177
|
+
const packDir = path.join(scopeDir, 'pack-bot-coderabbit');
|
|
178
|
+
fs.mkdirSync(packDir, { recursive: true });
|
|
179
|
+
fs.writeFileSync(path.join(packDir, 'package.json'), JSON.stringify({ name: '@mmnto/pack-bot-coderabbit', version: '1.2.3' }), 'utf8');
|
|
180
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
181
|
+
expect(result).toEqual({ '@mmnto/pack-bot-coderabbit': '1.2.3' });
|
|
182
|
+
});
|
|
183
|
+
it('skips non-pack-* @mmnto packages (e.g. @mmnto/totem itself)', () => {
|
|
184
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
185
|
+
const corePkg = path.join(scopeDir, 'totem');
|
|
186
|
+
fs.mkdirSync(corePkg, { recursive: true });
|
|
187
|
+
fs.writeFileSync(path.join(corePkg, 'package.json'), JSON.stringify({ name: '@mmnto/totem', version: '1.36.0' }), 'utf8');
|
|
188
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
189
|
+
expect(result).toEqual({});
|
|
190
|
+
});
|
|
191
|
+
it('rethrows non-ENOENT readdirSync errors (e.g. scope-dir-is-a-file)', () => {
|
|
192
|
+
// Make `<projectRoot>/node_modules/@mmnto` a regular file so readdirSync
|
|
193
|
+
// fails with a non-ENOENT errno (ENOTDIR on POSIX, varies on Windows but
|
|
194
|
+
// never ENOENT). The function must surface that fault rather than mask
|
|
195
|
+
// it as "no packs installed."
|
|
196
|
+
const scopePath = path.join(workDir, 'node_modules', '@mmnto');
|
|
197
|
+
fs.mkdirSync(path.dirname(scopePath), { recursive: true });
|
|
198
|
+
fs.writeFileSync(scopePath, 'not a directory', 'utf8');
|
|
199
|
+
expect(() => resolveInstalledPackVersions(workDir)).toThrow();
|
|
200
|
+
});
|
|
201
|
+
it('reads from symlinked pack directories (pnpm/yarn workspaces case)', () => {
|
|
202
|
+
// Regression: Dirent.isDirectory() returns false for symlinks even when
|
|
203
|
+
// the target is a directory. Workspace setups symlink packs into
|
|
204
|
+
// `node_modules/@mmnto/`, so a pre-filter on `entry.isDirectory()` would
|
|
205
|
+
// have silently skipped every workspace-linked pack. The function now
|
|
206
|
+
// drops the type pre-filter and lets the `readFileSync` traversal +
|
|
207
|
+
// try/catch handle it.
|
|
208
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
209
|
+
fs.mkdirSync(scopeDir, { recursive: true });
|
|
210
|
+
// Target directory (the "source" of the pack).
|
|
211
|
+
const sourceDir = path.join(workDir, 'workspace-pack-source');
|
|
212
|
+
fs.mkdirSync(sourceDir);
|
|
213
|
+
fs.writeFileSync(path.join(sourceDir, 'package.json'), JSON.stringify({ name: '@mmnto/pack-workspace', version: '2.0.0' }), 'utf8');
|
|
214
|
+
// Symlink it into node_modules/@mmnto/pack-workspace. Skip the test on
|
|
215
|
+
// platforms that do not grant the calling process permission to create
|
|
216
|
+
// directory symlinks (Windows without developer mode / admin).
|
|
217
|
+
try {
|
|
218
|
+
fs.symlinkSync(sourceDir, path.join(scopeDir, 'pack-workspace'), 'dir');
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
if (err.code === 'EPERM')
|
|
222
|
+
return;
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
226
|
+
expect(result).toEqual({ '@mmnto/pack-workspace': '2.0.0' });
|
|
227
|
+
});
|
|
228
|
+
it('rethrows unusual per-pack errors (not in the expected errno set, not SyntaxError)', () => {
|
|
229
|
+
// The per-pack catch suppresses ENOENT/ENOTDIR/EPERM/EACCES (expected
|
|
230
|
+
// dir/permission quirks) and SyntaxError (malformed package.json), but
|
|
231
|
+
// any other exception class is treated as a real fault that surfaces.
|
|
232
|
+
// Mock readFileSync to throw a synthetic non-errno error and assert the
|
|
233
|
+
// function propagates it rather than silently skipping the pack.
|
|
234
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
235
|
+
const packDir = path.join(scopeDir, 'pack-unusual');
|
|
236
|
+
fs.mkdirSync(packDir, { recursive: true });
|
|
237
|
+
fs.writeFileSync(path.join(packDir, 'package.json'), JSON.stringify({ name: '@mmnto/pack-unusual', version: '1.0.0' }), 'utf8');
|
|
238
|
+
// `resolveInstalledPackVersions` only calls `readFileSync` for pack
|
|
239
|
+
// package.json files, so a blanket-throw mock exercises the inner catch
|
|
240
|
+
// without affecting other code paths.
|
|
241
|
+
const unusual = new Error('synthetic IO timeout');
|
|
242
|
+
const spy = vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
|
|
243
|
+
throw unusual;
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
expect(() => resolveInstalledPackVersions(workDir)).toThrow(unusual);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
spy.mockRestore();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
it('skips directory entries with unreadable or malformed package.json without failing the whole scan', () => {
|
|
253
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
254
|
+
const broken = path.join(scopeDir, 'pack-broken');
|
|
255
|
+
fs.mkdirSync(broken, { recursive: true });
|
|
256
|
+
fs.writeFileSync(path.join(broken, 'package.json'), '{ not json', 'utf8');
|
|
257
|
+
const good = path.join(scopeDir, 'pack-good');
|
|
258
|
+
fs.mkdirSync(good, { recursive: true });
|
|
259
|
+
fs.writeFileSync(path.join(good, 'package.json'), JSON.stringify({ name: '@mmnto/pack-good', version: '1.0.0' }), 'utf8');
|
|
260
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
261
|
+
expect(result).toEqual({ '@mmnto/pack-good': '1.0.0' });
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
//# sourceMappingURL=hook-run.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-run.test.js","sourceRoot":"","sources":["../../src/commands/hook-run.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HookTestResult, HookTestSummary } from '../hook/test-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* `totem hook test [--filter <term>]` — run fixture-based verification for
|
|
4
|
+
* compiled bot-pack hooks (ADR-104 § Convergence).
|
|
5
|
+
*
|
|
6
|
+
* Loads `surface: hooks` fixtures from `.totem/tests/`, evaluates each
|
|
7
|
+
* against the matching compiled hook, and reports per-line failures so
|
|
8
|
+
* authors can iterate on specific payloads. The runner is deterministic
|
|
9
|
+
* Node.js — no LLM calls — mirroring the `totem hook run` contract.
|
|
10
|
+
*
|
|
11
|
+
* `--filter <term>` matches against hook id (case-insensitive substring).
|
|
12
|
+
*/
|
|
13
|
+
export interface HookTestCommandOptions {
|
|
14
|
+
filter?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Apply `--filter` to a hook-test summary, failing loud when the filter
|
|
18
|
+
* matches nothing despite fixtures being present. Pure helper extracted
|
|
19
|
+
* from `hookTestCommand` so the filter contract is unit-testable without
|
|
20
|
+
* driving the command end-to-end through `process.cwd()` + `loadConfig`.
|
|
21
|
+
*
|
|
22
|
+
* Returns the filtered result slice when the filter matches (or is absent).
|
|
23
|
+
* Throws TEST_FAILED when `--filter` is set, `summary.total > 0`, and the
|
|
24
|
+
* filter matches no fixtures — a typoed filter must never look like a
|
|
25
|
+
* successful zero-test run.
|
|
26
|
+
*/
|
|
27
|
+
export declare function applyFilter(summary: HookTestSummary, filter: string | undefined): HookTestResult[];
|
|
28
|
+
export declare function hookTestCommand(opts: HookTestCommandOptions): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=hook-test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-test.d.ts","sourceRoot":"","sources":["../../src/commands/hook-test.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAW9E;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,cAAc,EAAE,CAYlB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwHjF"}
|