@h-rig/hook-kit 0.0.6-alpha.64 → 0.0.6-alpha.65

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/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,
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/hook-kit",
3
- "version": "0.0.6-alpha.64",
3
+ "version": "0.0.6-alpha.65",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -19,7 +19,7 @@
19
19
  "main": "./dist/src/index.js",
20
20
  "module": "./dist/src/index.js",
21
21
  "dependencies": {
22
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.64",
22
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.65",
23
23
  "effect": "4.0.0-beta.78"
24
24
  }
25
25
  }