@h-rig/hook-kit 0.0.6-alpha.8 → 0.0.6-alpha.81
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/src/context.d.ts +35 -0
- package/dist/src/guard.d.ts +11 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +60 -0
- package/dist/src/io.d.ts +14 -0
- package/dist/src/runtime.d.ts +7 -0
- package/dist/src/tools.d.ts +10 -0
- package/dist/src/typed.d.ts +33 -0
- package/dist/src/typed.js +247 -0
- package/dist/src/utils.d.ts +8 -0
- package/package.json +4 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Read build-time baked config. Exported for use by sibling modules in hook-kit. */
|
|
2
|
+
export declare function readBuildConfigForKit(): Record<string, string>;
|
|
3
|
+
export declare const BAKED_PROJECT_ROOT: string;
|
|
4
|
+
export declare const BAKED_TASK_ID: string;
|
|
5
|
+
export declare const BAKED_STATE_DIR: string;
|
|
6
|
+
export declare const BAKED_BUN_PATH: string;
|
|
7
|
+
export declare const BAKED_TASK_CONFIG: string;
|
|
8
|
+
export declare const BAKED_POLICY_CONTENT: string;
|
|
9
|
+
export declare const BAKED_TASK_SCOPES: string;
|
|
10
|
+
export type HookContext = {
|
|
11
|
+
taskId: string;
|
|
12
|
+
role: string;
|
|
13
|
+
scopes: string[];
|
|
14
|
+
validation: string[];
|
|
15
|
+
hostProjectRoot?: string | undefined;
|
|
16
|
+
monorepoMainRoot?: string | undefined;
|
|
17
|
+
stateDir: string;
|
|
18
|
+
policyFile: string;
|
|
19
|
+
};
|
|
20
|
+
/** Load HookContext from the runtime context JSON file, or return null. */
|
|
21
|
+
export declare function loadHookContextFromEnv(): HookContext | null;
|
|
22
|
+
/** Resolve the project root from HookContext, baked constant, env, or CWD heuristic. */
|
|
23
|
+
export declare function resolveProjectRoot(): string;
|
|
24
|
+
/** Resolve the current task ID from HookContext, baked constant, or env vars. */
|
|
25
|
+
export declare function resolveTaskIdForHook(_projectRoot: string): string;
|
|
26
|
+
/** Get task config from HookContext, baked constant, or disk. */
|
|
27
|
+
export declare function resolveTaskConfig(_projectRoot: string, _taskId: string): Promise<{
|
|
28
|
+
scope?: string[];
|
|
29
|
+
validation?: string[];
|
|
30
|
+
role?: string;
|
|
31
|
+
}>;
|
|
32
|
+
/** Get scope globs from HookContext, baked constant, or disk config. */
|
|
33
|
+
export declare function resolveTaskScopes(projectRoot: string, taskId: string): Promise<string[]>;
|
|
34
|
+
/** Get policy content from HookContext, baked constant, or disk. */
|
|
35
|
+
export declare function resolvePolicyContent(projectRoot: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block the current action, log the trip, and exit non-zero.
|
|
3
|
+
*
|
|
4
|
+
* Writes a BLOCKED message to stdout (so Claude Code sees it immediately),
|
|
5
|
+
* appends to the hook_trips.log in stateDir, and calls `process.exit(1)`.
|
|
6
|
+
*
|
|
7
|
+
* After 3 trips for the same hookName an escalation line is appended to the
|
|
8
|
+
* message. The escalation text is intentionally generic so Rig plugins can
|
|
9
|
+
* override the CLAUDE.md reference.
|
|
10
|
+
*/
|
|
11
|
+
export declare function block(hookName: string, message: string, projectRoot: string): never;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { HookInput, ParsedHookInput } from "./io";
|
|
2
|
+
export { readHookInput } from "./io";
|
|
3
|
+
export type { HookContext } from "./context";
|
|
4
|
+
export { BAKED_PROJECT_ROOT, BAKED_TASK_ID, BAKED_STATE_DIR, BAKED_BUN_PATH, BAKED_TASK_CONFIG, BAKED_POLICY_CONTENT, BAKED_TASK_SCOPES, resolveProjectRoot, resolveTaskIdForHook, resolveTaskConfig, resolveTaskScopes, resolvePolicyContent, } from "./context";
|
|
5
|
+
export { block } from "./guard";
|
|
6
|
+
export type { TypedHookOptions } from "./typed";
|
|
7
|
+
export { buildPluginHookContext, hookResultToProtocol, runTypedHook, } from "./typed";
|
|
8
|
+
export { extractToolFilePaths, isTestFilePath } from "./tools";
|
|
9
|
+
export { resolveBunCli, resolveBunCliInvocation } from "./runtime";
|
|
10
|
+
export { escapeRegExp } from "./utils";
|
package/dist/src/index.js
CHANGED
|
@@ -294,6 +294,9 @@ function block(hookName, message, projectRoot) {
|
|
|
294
294
|
`);
|
|
295
295
|
process.exit(1);
|
|
296
296
|
}
|
|
297
|
+
// packages/hook-kit/src/typed.ts
|
|
298
|
+
import { writeSync as writeSync2 } from "fs";
|
|
299
|
+
|
|
297
300
|
// packages/hook-kit/src/tools.ts
|
|
298
301
|
function extractToolFilePaths(toolName, input) {
|
|
299
302
|
const paths = [];
|
|
@@ -318,6 +321,60 @@ function extractToolFilePaths(toolName, input) {
|
|
|
318
321
|
function isTestFilePath(path) {
|
|
319
322
|
return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
|
|
320
323
|
}
|
|
324
|
+
|
|
325
|
+
// packages/hook-kit/src/typed.ts
|
|
326
|
+
function buildPluginHookContext(parsed, opts) {
|
|
327
|
+
const toolName = parsed.input.tool_name;
|
|
328
|
+
const toolInput = parsed.input.tool_input ?? {};
|
|
329
|
+
return {
|
|
330
|
+
event: opts.event,
|
|
331
|
+
toolName,
|
|
332
|
+
toolInput,
|
|
333
|
+
filePaths: toolName ? extractToolFilePaths(toolName, toolInput) : [],
|
|
334
|
+
projectRoot: opts.projectRoot,
|
|
335
|
+
taskId: opts.taskId
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function hookResultToProtocol(result) {
|
|
339
|
+
if (result.decision === "block") {
|
|
340
|
+
const lines = [`BLOCKED: ${result.reason ?? "blocked by plugin hook"}`];
|
|
341
|
+
if (result.systemMessage) {
|
|
342
|
+
lines.push(result.systemMessage);
|
|
343
|
+
}
|
|
344
|
+
return { exitCode: 1, stdout: `${lines.join(`
|
|
345
|
+
`)}
|
|
346
|
+
` };
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
exitCode: 0,
|
|
350
|
+
stdout: result.systemMessage ? `${result.systemMessage}
|
|
351
|
+
` : ""
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
async function runTypedHook(fn, opts) {
|
|
355
|
+
const parsed = await readHookInput();
|
|
356
|
+
const projectRoot = opts.projectRoot ?? resolveProjectRoot();
|
|
357
|
+
const taskId = opts.taskId ?? resolveTaskIdForHook(projectRoot);
|
|
358
|
+
const ctx = buildPluginHookContext(parsed, {
|
|
359
|
+
event: opts.event,
|
|
360
|
+
projectRoot,
|
|
361
|
+
taskId
|
|
362
|
+
});
|
|
363
|
+
let result;
|
|
364
|
+
try {
|
|
365
|
+
result = await fn(ctx);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
368
|
+
writeSync2(2, `[rig hook] typed hook threw: ${message}
|
|
369
|
+
`);
|
|
370
|
+
process.exit(0);
|
|
371
|
+
}
|
|
372
|
+
const { exitCode, stdout } = hookResultToProtocol(result);
|
|
373
|
+
if (stdout) {
|
|
374
|
+
writeSync2(1, stdout);
|
|
375
|
+
}
|
|
376
|
+
process.exit(exitCode);
|
|
377
|
+
}
|
|
321
378
|
// packages/hook-kit/src/runtime.ts
|
|
322
379
|
import { existsSync as existsSync3, realpathSync } from "fs";
|
|
323
380
|
import { resolve as resolve3 } from "path";
|
|
@@ -401,6 +458,7 @@ function resolveBunCliInvocation() {
|
|
|
401
458
|
return { command: "bun", env: {} };
|
|
402
459
|
}
|
|
403
460
|
export {
|
|
461
|
+
runTypedHook,
|
|
404
462
|
resolveTaskScopes,
|
|
405
463
|
resolveTaskIdForHook,
|
|
406
464
|
resolveTaskConfig,
|
|
@@ -410,8 +468,10 @@ export {
|
|
|
410
468
|
resolveBunCli,
|
|
411
469
|
readHookInput,
|
|
412
470
|
isTestFilePath,
|
|
471
|
+
hookResultToProtocol,
|
|
413
472
|
extractToolFilePaths,
|
|
414
473
|
escapeRegExp,
|
|
474
|
+
buildPluginHookContext,
|
|
415
475
|
block,
|
|
416
476
|
BAKED_TASK_SCOPES,
|
|
417
477
|
BAKED_TASK_ID,
|
package/dist/src/io.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** JSON shape of a Claude Code hook payload. */
|
|
2
|
+
export type HookInput = {
|
|
3
|
+
tool_name?: string;
|
|
4
|
+
tool_input?: Record<string, unknown>;
|
|
5
|
+
command?: string;
|
|
6
|
+
};
|
|
7
|
+
/** Wrapper around `HookInput` with parse-success flags. */
|
|
8
|
+
export type ParsedHookInput = {
|
|
9
|
+
input: HookInput;
|
|
10
|
+
valid: boolean;
|
|
11
|
+
hadPayload: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** Read and parse the JSON hook payload from stdin or `RIG_HOOK_INPUT_FILE`. */
|
|
14
|
+
export declare function readHookInput(): Promise<ParsedHookInput>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-input helpers for hook binaries.
|
|
3
|
+
*
|
|
4
|
+
* Moved from packages/runtime/src/control-plane/hooks/shared.ts
|
|
5
|
+
* in Phase 5 Task 5.3 of the Rig extraction.
|
|
6
|
+
*/
|
|
7
|
+
/** Extract file path candidates from tool input for pre-guard worktree checks. */
|
|
8
|
+
export declare function extractToolFilePaths(toolName: string, input: Record<string, unknown>): string[];
|
|
9
|
+
/** Check whether a file path looks like a test file. */
|
|
10
|
+
export declare function isTestFilePath(path: string): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HookContext as PluginHookContext, HookEvent, HookImplementation, HookResult } from "@rig/contracts";
|
|
2
|
+
import { type ParsedHookInput } from "./io";
|
|
3
|
+
export interface TypedHookOptions {
|
|
4
|
+
/** The lifecycle event this hook is registered for (from its metadata). */
|
|
5
|
+
event: HookEvent;
|
|
6
|
+
/** Project root override; resolved via `resolveProjectRoot()` when absent. */
|
|
7
|
+
projectRoot?: string;
|
|
8
|
+
/** Task id override; resolved via `resolveTaskIdForHook()` when absent. */
|
|
9
|
+
taskId?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build the typed-hook input from a parsed Claude Code hook payload.
|
|
13
|
+
* Pure — exported for tests and for the plugin-testkit helpers.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildPluginHookContext(parsed: ParsedHookInput, opts: {
|
|
16
|
+
event: HookEvent;
|
|
17
|
+
projectRoot: string;
|
|
18
|
+
taskId: string;
|
|
19
|
+
}): PluginHookContext;
|
|
20
|
+
/**
|
|
21
|
+
* Serialize a HookResult to the Claude Code hook protocol. Pure — the
|
|
22
|
+
* exit/stdout side effects happen in `runTypedHook`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function hookResultToProtocol(result: HookResult): {
|
|
25
|
+
exitCode: 0 | 1;
|
|
26
|
+
stdout: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Run a typed hook implementation against the current process's hook
|
|
30
|
+
* invocation: parse stdin into a HookContext, call the function, write the
|
|
31
|
+
* protocol output, and exit. Never returns.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runTypedHook(fn: HookImplementation, opts: TypedHookOptions): Promise<never>;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/hook-kit/src/typed.ts
|
|
3
|
+
import { writeSync } from "fs";
|
|
4
|
+
|
|
5
|
+
// packages/hook-kit/src/io.ts
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
async function readHookInput() {
|
|
8
|
+
let text = "";
|
|
9
|
+
const inputFile = process.env.RIG_HOOK_INPUT_FILE?.trim();
|
|
10
|
+
if (inputFile) {
|
|
11
|
+
text = readFileSync(inputFile, "utf-8");
|
|
12
|
+
} else {
|
|
13
|
+
try {
|
|
14
|
+
text = readFileSync("/dev/stdin", "utf-8");
|
|
15
|
+
} catch {
|
|
16
|
+
text = readFileSync(0, "utf-8");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (!text.trim()) {
|
|
20
|
+
return { input: {}, valid: true, hadPayload: false };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return {
|
|
24
|
+
input: JSON.parse(text),
|
|
25
|
+
valid: true,
|
|
26
|
+
hadPayload: true
|
|
27
|
+
};
|
|
28
|
+
} catch {
|
|
29
|
+
return { input: {}, valid: false, hadPayload: true };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// packages/hook-kit/src/tools.ts
|
|
34
|
+
function extractToolFilePaths(toolName, input) {
|
|
35
|
+
const paths = [];
|
|
36
|
+
const add = (value) => {
|
|
37
|
+
if (typeof value === "string" && value.trim()) {
|
|
38
|
+
paths.push(value.trim());
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
|
|
42
|
+
add(input.file_path);
|
|
43
|
+
add(input.path);
|
|
44
|
+
} else if (toolName === "Glob") {
|
|
45
|
+
add(input.path);
|
|
46
|
+
} else if (toolName === "Grep") {
|
|
47
|
+
add(input.path);
|
|
48
|
+
} else {
|
|
49
|
+
add(input.file_path);
|
|
50
|
+
add(input.path);
|
|
51
|
+
}
|
|
52
|
+
return paths;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// packages/hook-kit/src/context.ts
|
|
56
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
57
|
+
import { resolve } from "path";
|
|
58
|
+
var RIG_DEFINITION_DIRNAME = "rig";
|
|
59
|
+
var RIG_STATE_DIRNAME = ".rig";
|
|
60
|
+
function normalizeBuildConfig(value) {
|
|
61
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
65
|
+
}
|
|
66
|
+
function readBuildConfigForKit() {
|
|
67
|
+
if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
|
|
68
|
+
return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
|
|
69
|
+
}
|
|
70
|
+
const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
|
|
71
|
+
if (!raw) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return normalizeBuildConfig(JSON.parse(raw));
|
|
76
|
+
} catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
var BUILD_CONFIG = readBuildConfigForKit();
|
|
81
|
+
var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
|
|
82
|
+
var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
|
|
83
|
+
var BAKED_STATE_DIR = BUILD_CONFIG.AGENT_STATE_DIR ?? "";
|
|
84
|
+
var BAKED_BUN_PATH = BUILD_CONFIG.AGENT_BUN_PATH ?? "";
|
|
85
|
+
var BAKED_TASK_CONFIG = BUILD_CONFIG.AGENT_TASK_CONFIG ?? "";
|
|
86
|
+
var BAKED_POLICY_CONTENT = BUILD_CONFIG.AGENT_POLICY_CONTENT ?? "";
|
|
87
|
+
var BAKED_TASK_SCOPES = BUILD_CONFIG.AGENT_TASK_SCOPES ?? "";
|
|
88
|
+
var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
|
|
89
|
+
function isPlainObject(value) {
|
|
90
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91
|
+
}
|
|
92
|
+
function isStringArray(value) {
|
|
93
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
94
|
+
}
|
|
95
|
+
function loadHookContextFromEnv() {
|
|
96
|
+
const contextFile = process.env[RUNTIME_CONTEXT_ENV]?.trim();
|
|
97
|
+
let filePath = contextFile || "";
|
|
98
|
+
if (!filePath) {
|
|
99
|
+
let current = resolve(process.cwd());
|
|
100
|
+
while (true) {
|
|
101
|
+
const candidate = resolve(current, "runtime-context.json");
|
|
102
|
+
if (existsSync(candidate)) {
|
|
103
|
+
filePath = candidate;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
const parent = resolve(current, "..");
|
|
107
|
+
if (parent === current) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
current = parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!filePath || !existsSync(filePath)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
118
|
+
if (!isPlainObject(raw)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const taskId = typeof raw.taskId === "string" ? raw.taskId : "";
|
|
122
|
+
const role = typeof raw.role === "string" ? raw.role : "";
|
|
123
|
+
const scopes = isStringArray(raw.scopes) ? raw.scopes : [];
|
|
124
|
+
const validation = isStringArray(raw.validation) ? raw.validation : [];
|
|
125
|
+
const hostProjectRoot = typeof raw.hostProjectRoot === "string" ? raw.hostProjectRoot : undefined;
|
|
126
|
+
const monorepoMainRoot = typeof raw.monorepoMainRoot === "string" ? raw.monorepoMainRoot : undefined;
|
|
127
|
+
const stateDir = typeof raw.stateDir === "string" ? raw.stateDir : "";
|
|
128
|
+
const policyFile = typeof raw.policyFile === "string" ? raw.policyFile : "";
|
|
129
|
+
if (!taskId || !stateDir) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
taskId,
|
|
134
|
+
role,
|
|
135
|
+
scopes,
|
|
136
|
+
validation,
|
|
137
|
+
hostProjectRoot,
|
|
138
|
+
monorepoMainRoot,
|
|
139
|
+
stateDir,
|
|
140
|
+
policyFile
|
|
141
|
+
};
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
var cachedContext;
|
|
147
|
+
function getContext() {
|
|
148
|
+
if (cachedContext !== undefined) {
|
|
149
|
+
return cachedContext;
|
|
150
|
+
}
|
|
151
|
+
cachedContext = loadHookContextFromEnv();
|
|
152
|
+
return cachedContext;
|
|
153
|
+
}
|
|
154
|
+
function resolveProjectRoot() {
|
|
155
|
+
const ctx = getContext();
|
|
156
|
+
if (ctx?.hostProjectRoot)
|
|
157
|
+
return ctx.hostProjectRoot;
|
|
158
|
+
if (BAKED_PROJECT_ROOT) {
|
|
159
|
+
return BAKED_PROJECT_ROOT;
|
|
160
|
+
}
|
|
161
|
+
if (process.env.PROJECT_RIG_ROOT) {
|
|
162
|
+
return process.env.PROJECT_RIG_ROOT;
|
|
163
|
+
}
|
|
164
|
+
const candidates = [process.cwd(), resolve(import.meta.dirname, "../..")];
|
|
165
|
+
for (const candidate of candidates) {
|
|
166
|
+
if (existsSync(resolve(candidate, RIG_DEFINITION_DIRNAME)) || existsSync(resolve(candidate, RIG_STATE_DIRNAME))) {
|
|
167
|
+
return candidate;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return resolve(import.meta.dirname, "../..");
|
|
171
|
+
}
|
|
172
|
+
function resolveTaskIdForHook(_projectRoot) {
|
|
173
|
+
const ctx = getContext();
|
|
174
|
+
if (ctx)
|
|
175
|
+
return ctx.taskId;
|
|
176
|
+
if (BAKED_TASK_ID) {
|
|
177
|
+
return BAKED_TASK_ID;
|
|
178
|
+
}
|
|
179
|
+
const fromEnv = (process.env.RIG_TASK_ID || "").trim();
|
|
180
|
+
if (fromEnv) {
|
|
181
|
+
return fromEnv;
|
|
182
|
+
}
|
|
183
|
+
const runtimeId = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
|
|
184
|
+
if (runtimeId.startsWith("task-") && runtimeId.length > "task-".length) {
|
|
185
|
+
return runtimeId.slice("task-".length);
|
|
186
|
+
}
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// packages/hook-kit/src/typed.ts
|
|
191
|
+
function buildPluginHookContext(parsed, opts) {
|
|
192
|
+
const toolName = parsed.input.tool_name;
|
|
193
|
+
const toolInput = parsed.input.tool_input ?? {};
|
|
194
|
+
return {
|
|
195
|
+
event: opts.event,
|
|
196
|
+
toolName,
|
|
197
|
+
toolInput,
|
|
198
|
+
filePaths: toolName ? extractToolFilePaths(toolName, toolInput) : [],
|
|
199
|
+
projectRoot: opts.projectRoot,
|
|
200
|
+
taskId: opts.taskId
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function hookResultToProtocol(result) {
|
|
204
|
+
if (result.decision === "block") {
|
|
205
|
+
const lines = [`BLOCKED: ${result.reason ?? "blocked by plugin hook"}`];
|
|
206
|
+
if (result.systemMessage) {
|
|
207
|
+
lines.push(result.systemMessage);
|
|
208
|
+
}
|
|
209
|
+
return { exitCode: 1, stdout: `${lines.join(`
|
|
210
|
+
`)}
|
|
211
|
+
` };
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
exitCode: 0,
|
|
215
|
+
stdout: result.systemMessage ? `${result.systemMessage}
|
|
216
|
+
` : ""
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async function runTypedHook(fn, opts) {
|
|
220
|
+
const parsed = await readHookInput();
|
|
221
|
+
const projectRoot = opts.projectRoot ?? resolveProjectRoot();
|
|
222
|
+
const taskId = opts.taskId ?? resolveTaskIdForHook(projectRoot);
|
|
223
|
+
const ctx = buildPluginHookContext(parsed, {
|
|
224
|
+
event: opts.event,
|
|
225
|
+
projectRoot,
|
|
226
|
+
taskId
|
|
227
|
+
});
|
|
228
|
+
let result;
|
|
229
|
+
try {
|
|
230
|
+
result = await fn(ctx);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
233
|
+
writeSync(2, `[rig hook] typed hook threw: ${message}
|
|
234
|
+
`);
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
const { exitCode, stdout } = hookResultToProtocol(result);
|
|
238
|
+
if (stdout) {
|
|
239
|
+
writeSync(1, stdout);
|
|
240
|
+
}
|
|
241
|
+
process.exit(exitCode);
|
|
242
|
+
}
|
|
243
|
+
export {
|
|
244
|
+
runTypedHook,
|
|
245
|
+
hookResultToProtocol,
|
|
246
|
+
buildPluginHookContext
|
|
247
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* General utility helpers for hook binaries.
|
|
3
|
+
*
|
|
4
|
+
* Moved from packages/runtime/src/control-plane/hooks/shared.ts
|
|
5
|
+
* in Phase 5 Task 5.3 of the Rig extraction.
|
|
6
|
+
*/
|
|
7
|
+
/** Escape special regex characters in a string. */
|
|
8
|
+
export declare function escapeRegExp(value: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@h-rig/hook-kit",
|
|
3
|
-
"version": "0.0.6-alpha.
|
|
3
|
+
"version": "0.0.6-alpha.81",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rig package",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
],
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
+
"types": "./dist/src/index.d.ts",
|
|
13
14
|
"import": "./dist/src/index.js"
|
|
14
15
|
}
|
|
15
16
|
},
|
|
@@ -18,8 +19,9 @@
|
|
|
18
19
|
},
|
|
19
20
|
"main": "./dist/src/index.js",
|
|
20
21
|
"module": "./dist/src/index.js",
|
|
22
|
+
"types": "./dist/src/index.d.ts",
|
|
21
23
|
"dependencies": {
|
|
22
|
-
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.
|
|
24
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.81",
|
|
23
25
|
"effect": "4.0.0-beta.78"
|
|
24
26
|
}
|
|
25
27
|
}
|