@h-rig/hook-kit 0.0.6-alpha.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/README.md ADDED
@@ -0,0 +1 @@
1
+ # @h-rig/hook-kit
@@ -0,0 +1,240 @@
1
+ // @bun
2
+ // packages/hook-kit/src/context.ts
3
+ import { existsSync, readFileSync } from "fs";
4
+ import { resolve } from "path";
5
+ var RIG_DEFINITION_DIRNAME = "rig";
6
+ var RIG_STATE_DIRNAME = ".rig";
7
+ function normalizeBuildConfig(value) {
8
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9
+ return {};
10
+ }
11
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
12
+ }
13
+ function readBuildConfigForKit() {
14
+ if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
15
+ return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
16
+ }
17
+ const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
18
+ if (!raw) {
19
+ return {};
20
+ }
21
+ try {
22
+ return normalizeBuildConfig(JSON.parse(raw));
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+ var BUILD_CONFIG = readBuildConfigForKit();
28
+ var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
29
+ var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
30
+ var BAKED_STATE_DIR = BUILD_CONFIG.AGENT_STATE_DIR ?? "";
31
+ var BAKED_BUN_PATH = BUILD_CONFIG.AGENT_BUN_PATH ?? "";
32
+ var BAKED_TASK_CONFIG = BUILD_CONFIG.AGENT_TASK_CONFIG ?? "";
33
+ var BAKED_POLICY_CONTENT = BUILD_CONFIG.AGENT_POLICY_CONTENT ?? "";
34
+ var BAKED_TASK_SCOPES = BUILD_CONFIG.AGENT_TASK_SCOPES ?? "";
35
+ var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
36
+ function isPlainObject(value) {
37
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38
+ }
39
+ function isStringArray(value) {
40
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
41
+ }
42
+ function loadHookContextFromEnv() {
43
+ const contextFile = process.env[RUNTIME_CONTEXT_ENV]?.trim();
44
+ let filePath = contextFile || "";
45
+ if (!filePath) {
46
+ let current = resolve(process.cwd());
47
+ while (true) {
48
+ const candidate = resolve(current, "runtime-context.json");
49
+ if (existsSync(candidate)) {
50
+ filePath = candidate;
51
+ break;
52
+ }
53
+ const parent = resolve(current, "..");
54
+ if (parent === current) {
55
+ break;
56
+ }
57
+ current = parent;
58
+ }
59
+ }
60
+ if (!filePath || !existsSync(filePath)) {
61
+ return null;
62
+ }
63
+ try {
64
+ const raw = JSON.parse(readFileSync(filePath, "utf-8"));
65
+ if (!isPlainObject(raw)) {
66
+ return null;
67
+ }
68
+ const taskId = typeof raw.taskId === "string" ? raw.taskId : "";
69
+ const role = typeof raw.role === "string" ? raw.role : "";
70
+ const scopes = isStringArray(raw.scopes) ? raw.scopes : [];
71
+ const validation = isStringArray(raw.validation) ? raw.validation : [];
72
+ const hostProjectRoot = typeof raw.hostProjectRoot === "string" ? raw.hostProjectRoot : undefined;
73
+ const monorepoMainRoot = typeof raw.monorepoMainRoot === "string" ? raw.monorepoMainRoot : undefined;
74
+ const stateDir = typeof raw.stateDir === "string" ? raw.stateDir : "";
75
+ const policyFile = typeof raw.policyFile === "string" ? raw.policyFile : "";
76
+ if (!taskId || !stateDir) {
77
+ return null;
78
+ }
79
+ return {
80
+ taskId,
81
+ role,
82
+ scopes,
83
+ validation,
84
+ hostProjectRoot,
85
+ monorepoMainRoot,
86
+ stateDir,
87
+ policyFile
88
+ };
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+ var cachedContext;
94
+ function getContext() {
95
+ if (cachedContext !== undefined) {
96
+ return cachedContext;
97
+ }
98
+ cachedContext = loadHookContextFromEnv();
99
+ return cachedContext;
100
+ }
101
+ function resolveProjectRoot() {
102
+ const ctx = getContext();
103
+ if (ctx?.hostProjectRoot)
104
+ return ctx.hostProjectRoot;
105
+ if (BAKED_PROJECT_ROOT) {
106
+ return BAKED_PROJECT_ROOT;
107
+ }
108
+ if (process.env.PROJECT_RIG_ROOT) {
109
+ return process.env.PROJECT_RIG_ROOT;
110
+ }
111
+ const candidates = [process.cwd(), resolve(import.meta.dirname, "../..")];
112
+ for (const candidate of candidates) {
113
+ if (existsSync(resolve(candidate, RIG_DEFINITION_DIRNAME)) || existsSync(resolve(candidate, RIG_STATE_DIRNAME))) {
114
+ return candidate;
115
+ }
116
+ }
117
+ return resolve(import.meta.dirname, "../..");
118
+ }
119
+ function resolveTaskIdForHook(_projectRoot) {
120
+ const ctx = getContext();
121
+ if (ctx)
122
+ return ctx.taskId;
123
+ if (BAKED_TASK_ID) {
124
+ return BAKED_TASK_ID;
125
+ }
126
+ const fromEnv = (process.env.RIG_TASK_ID || "").trim();
127
+ if (fromEnv) {
128
+ return fromEnv;
129
+ }
130
+ const runtimeId = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
131
+ if (runtimeId.startsWith("task-") && runtimeId.length > "task-".length) {
132
+ return runtimeId.slice("task-".length);
133
+ }
134
+ return "";
135
+ }
136
+ function isTaskConfigEntry(value) {
137
+ if (!isPlainObject(value)) {
138
+ return false;
139
+ }
140
+ if ("role" in value && value.role !== undefined && typeof value.role !== "string") {
141
+ return false;
142
+ }
143
+ if ("scope" in value && value.scope !== undefined && !isStringArray(value.scope)) {
144
+ return false;
145
+ }
146
+ if ("validation" in value && value.validation !== undefined && !isStringArray(value.validation)) {
147
+ return false;
148
+ }
149
+ return true;
150
+ }
151
+ async function readTaskConfigFromDisk(configPath) {
152
+ if (!existsSync(configPath)) {
153
+ return null;
154
+ }
155
+ try {
156
+ const content = typeof Bun !== "undefined" ? await Bun.file(configPath).json() : JSON.parse(readFileSync(configPath, "utf-8"));
157
+ return isPlainObject(content) ? content : null;
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+ function taskConfigCandidates(ctx) {
163
+ const taskWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
164
+ return [
165
+ ctx?.monorepoMainRoot ? resolve(ctx.monorepoMainRoot, ".rig", "task-config.json") : "",
166
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
167
+ taskWorkspace ? resolve(taskWorkspace, ".rig", "task-config.json") : ""
168
+ ].filter(Boolean);
169
+ }
170
+ async function resolveTaskConfig(_projectRoot, _taskId) {
171
+ const ctx = getContext();
172
+ if (ctx) {
173
+ return {
174
+ scope: ctx.scopes,
175
+ validation: ctx.validation,
176
+ role: ctx.role
177
+ };
178
+ }
179
+ if (BAKED_TASK_CONFIG) {
180
+ try {
181
+ const parsed = JSON.parse(BAKED_TASK_CONFIG);
182
+ if (isTaskConfigEntry(parsed) && Object.keys(parsed).length > 0) {
183
+ return parsed;
184
+ }
185
+ } catch {}
186
+ }
187
+ for (const configPath of taskConfigCandidates(ctx)) {
188
+ const config = await readTaskConfigFromDisk(configPath);
189
+ if (!config) {
190
+ continue;
191
+ }
192
+ const taskEntry = config[_taskId];
193
+ if (isTaskConfigEntry(taskEntry)) {
194
+ return taskEntry;
195
+ }
196
+ }
197
+ return {};
198
+ }
199
+ async function resolveTaskScopes(projectRoot, taskId) {
200
+ const ctx = getContext();
201
+ if (ctx && ctx.scopes.length > 0)
202
+ return ctx.scopes;
203
+ if (BAKED_TASK_SCOPES) {
204
+ try {
205
+ const parsed = JSON.parse(BAKED_TASK_SCOPES);
206
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((item) => typeof item === "string")) {
207
+ return parsed;
208
+ }
209
+ } catch {}
210
+ }
211
+ return (await resolveTaskConfig(projectRoot, taskId)).scope || [];
212
+ }
213
+ function resolvePolicyContent(projectRoot) {
214
+ const ctx = getContext();
215
+ if (ctx?.policyFile && existsSync(ctx.policyFile)) {
216
+ return readFileSync(ctx.policyFile, "utf-8");
217
+ }
218
+ if (BAKED_POLICY_CONTENT)
219
+ return BAKED_POLICY_CONTENT;
220
+ const policyPath = resolve(projectRoot, "rig/policy/policy.json");
221
+ if (existsSync(policyPath))
222
+ return readFileSync(policyPath, "utf-8");
223
+ return "{}";
224
+ }
225
+ export {
226
+ resolveTaskScopes,
227
+ resolveTaskIdForHook,
228
+ resolveTaskConfig,
229
+ resolveProjectRoot,
230
+ resolvePolicyContent,
231
+ readBuildConfigForKit,
232
+ loadHookContextFromEnv,
233
+ BAKED_TASK_SCOPES,
234
+ BAKED_TASK_ID,
235
+ BAKED_TASK_CONFIG,
236
+ BAKED_STATE_DIR,
237
+ BAKED_PROJECT_ROOT,
238
+ BAKED_POLICY_CONTENT,
239
+ BAKED_BUN_PATH
240
+ };
@@ -0,0 +1,79 @@
1
+ // @bun
2
+ // packages/hook-kit/src/guard.ts
3
+ import {
4
+ appendFileSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ writeSync
9
+ } from "fs";
10
+ import { resolve } from "path";
11
+
12
+ // packages/hook-kit/src/utils.ts
13
+ function escapeRegExp(value) {
14
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15
+ }
16
+
17
+ // packages/hook-kit/src/context.ts
18
+ function normalizeBuildConfig(value) {
19
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
20
+ return {};
21
+ }
22
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
23
+ }
24
+ function readBuildConfigForKit() {
25
+ if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
26
+ return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
27
+ }
28
+ const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
29
+ if (!raw) {
30
+ return {};
31
+ }
32
+ try {
33
+ return normalizeBuildConfig(JSON.parse(raw));
34
+ } catch {
35
+ return {};
36
+ }
37
+ }
38
+ var BUILD_CONFIG = readBuildConfigForKit();
39
+ var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
40
+ var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
41
+ var BAKED_STATE_DIR = BUILD_CONFIG.AGENT_STATE_DIR ?? "";
42
+ var BAKED_BUN_PATH = BUILD_CONFIG.AGENT_BUN_PATH ?? "";
43
+ var BAKED_TASK_CONFIG = BUILD_CONFIG.AGENT_TASK_CONFIG ?? "";
44
+ var BAKED_POLICY_CONTENT = BUILD_CONFIG.AGENT_POLICY_CONTENT ?? "";
45
+ var BAKED_TASK_SCOPES = BUILD_CONFIG.AGENT_TASK_SCOPES ?? "";
46
+
47
+ // packages/hook-kit/src/guard.ts
48
+ function block(hookName, message, projectRoot) {
49
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
50
+ const stateDir = BAKED_STATE_DIR || (runtimeWorkspace ? resolve(runtimeWorkspace, ".rig", "state") : resolve(projectRoot, ".rig", "state"));
51
+ mkdirSync(stateDir, { recursive: true });
52
+ const tripLog = resolve(stateDir, "hook_trips.log");
53
+ let count = 0;
54
+ if (existsSync(tripLog)) {
55
+ const content = readFileSync(tripLog, "utf-8");
56
+ count = (content.match(new RegExp(`^${escapeRegExp(hookName)}\\s`, "gm")) || []).length;
57
+ }
58
+ appendFileSync(tripLog, `${hookName} ${new Date().toISOString()}
59
+ `, "utf-8");
60
+ count += 1;
61
+ const lines = [
62
+ `BLOCKED: ${message}`,
63
+ "",
64
+ "Re-read CLAUDE.md safety rules before your next action."
65
+ ];
66
+ if (count >= 3) {
67
+ lines.push("");
68
+ lines.push(`REPEATED VIOLATION (${count}x ${hookName}).`);
69
+ lines.push("STOP. Read the FULL CLAUDE.md from top to bottom.");
70
+ lines.push("If stuck, write the problem to .rig/state/failed_approaches.md and request help.");
71
+ }
72
+ writeSync(1, `${lines.join(`
73
+ `)}
74
+ `);
75
+ process.exit(1);
76
+ }
77
+ export {
78
+ block
79
+ };
@@ -0,0 +1,423 @@
1
+ // @bun
2
+ // packages/hook-kit/src/io.ts
3
+ import { readFileSync } from "fs";
4
+ async function readHookInput() {
5
+ let text = "";
6
+ const inputFile = process.env.RIG_HOOK_INPUT_FILE?.trim();
7
+ if (inputFile) {
8
+ text = readFileSync(inputFile, "utf-8");
9
+ } else {
10
+ try {
11
+ text = readFileSync("/dev/stdin", "utf-8");
12
+ } catch {
13
+ text = readFileSync(0, "utf-8");
14
+ }
15
+ }
16
+ if (!text.trim()) {
17
+ return { input: {}, valid: true, hadPayload: false };
18
+ }
19
+ try {
20
+ return {
21
+ input: JSON.parse(text),
22
+ valid: true,
23
+ hadPayload: true
24
+ };
25
+ } catch {
26
+ return { input: {}, valid: false, hadPayload: true };
27
+ }
28
+ }
29
+ // packages/hook-kit/src/context.ts
30
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
31
+ import { resolve } from "path";
32
+ var RIG_DEFINITION_DIRNAME = "rig";
33
+ var RIG_STATE_DIRNAME = ".rig";
34
+ function normalizeBuildConfig(value) {
35
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
36
+ return {};
37
+ }
38
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
39
+ }
40
+ function readBuildConfigForKit() {
41
+ if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
42
+ return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
43
+ }
44
+ const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
45
+ if (!raw) {
46
+ return {};
47
+ }
48
+ try {
49
+ return normalizeBuildConfig(JSON.parse(raw));
50
+ } catch {
51
+ return {};
52
+ }
53
+ }
54
+ var BUILD_CONFIG = readBuildConfigForKit();
55
+ var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
56
+ var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
57
+ var BAKED_STATE_DIR = BUILD_CONFIG.AGENT_STATE_DIR ?? "";
58
+ var BAKED_BUN_PATH = BUILD_CONFIG.AGENT_BUN_PATH ?? "";
59
+ var BAKED_TASK_CONFIG = BUILD_CONFIG.AGENT_TASK_CONFIG ?? "";
60
+ var BAKED_POLICY_CONTENT = BUILD_CONFIG.AGENT_POLICY_CONTENT ?? "";
61
+ var BAKED_TASK_SCOPES = BUILD_CONFIG.AGENT_TASK_SCOPES ?? "";
62
+ var RUNTIME_CONTEXT_ENV = "RIG_RUNTIME_CONTEXT_FILE";
63
+ function isPlainObject(value) {
64
+ return typeof value === "object" && value !== null && !Array.isArray(value);
65
+ }
66
+ function isStringArray(value) {
67
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
68
+ }
69
+ function loadHookContextFromEnv() {
70
+ const contextFile = process.env[RUNTIME_CONTEXT_ENV]?.trim();
71
+ let filePath = contextFile || "";
72
+ if (!filePath) {
73
+ let current = resolve(process.cwd());
74
+ while (true) {
75
+ const candidate = resolve(current, "runtime-context.json");
76
+ if (existsSync(candidate)) {
77
+ filePath = candidate;
78
+ break;
79
+ }
80
+ const parent = resolve(current, "..");
81
+ if (parent === current) {
82
+ break;
83
+ }
84
+ current = parent;
85
+ }
86
+ }
87
+ if (!filePath || !existsSync(filePath)) {
88
+ return null;
89
+ }
90
+ try {
91
+ const raw = JSON.parse(readFileSync2(filePath, "utf-8"));
92
+ if (!isPlainObject(raw)) {
93
+ return null;
94
+ }
95
+ const taskId = typeof raw.taskId === "string" ? raw.taskId : "";
96
+ const role = typeof raw.role === "string" ? raw.role : "";
97
+ const scopes = isStringArray(raw.scopes) ? raw.scopes : [];
98
+ const validation = isStringArray(raw.validation) ? raw.validation : [];
99
+ const hostProjectRoot = typeof raw.hostProjectRoot === "string" ? raw.hostProjectRoot : undefined;
100
+ const monorepoMainRoot = typeof raw.monorepoMainRoot === "string" ? raw.monorepoMainRoot : undefined;
101
+ const stateDir = typeof raw.stateDir === "string" ? raw.stateDir : "";
102
+ const policyFile = typeof raw.policyFile === "string" ? raw.policyFile : "";
103
+ if (!taskId || !stateDir) {
104
+ return null;
105
+ }
106
+ return {
107
+ taskId,
108
+ role,
109
+ scopes,
110
+ validation,
111
+ hostProjectRoot,
112
+ monorepoMainRoot,
113
+ stateDir,
114
+ policyFile
115
+ };
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+ var cachedContext;
121
+ function getContext() {
122
+ if (cachedContext !== undefined) {
123
+ return cachedContext;
124
+ }
125
+ cachedContext = loadHookContextFromEnv();
126
+ return cachedContext;
127
+ }
128
+ function resolveProjectRoot() {
129
+ const ctx = getContext();
130
+ if (ctx?.hostProjectRoot)
131
+ return ctx.hostProjectRoot;
132
+ if (BAKED_PROJECT_ROOT) {
133
+ return BAKED_PROJECT_ROOT;
134
+ }
135
+ if (process.env.PROJECT_RIG_ROOT) {
136
+ return process.env.PROJECT_RIG_ROOT;
137
+ }
138
+ const candidates = [process.cwd(), resolve(import.meta.dirname, "../..")];
139
+ for (const candidate of candidates) {
140
+ if (existsSync(resolve(candidate, RIG_DEFINITION_DIRNAME)) || existsSync(resolve(candidate, RIG_STATE_DIRNAME))) {
141
+ return candidate;
142
+ }
143
+ }
144
+ return resolve(import.meta.dirname, "../..");
145
+ }
146
+ function resolveTaskIdForHook(_projectRoot) {
147
+ const ctx = getContext();
148
+ if (ctx)
149
+ return ctx.taskId;
150
+ if (BAKED_TASK_ID) {
151
+ return BAKED_TASK_ID;
152
+ }
153
+ const fromEnv = (process.env.RIG_TASK_ID || "").trim();
154
+ if (fromEnv) {
155
+ return fromEnv;
156
+ }
157
+ const runtimeId = (process.env.RIG_TASK_RUNTIME_ID || "").trim();
158
+ if (runtimeId.startsWith("task-") && runtimeId.length > "task-".length) {
159
+ return runtimeId.slice("task-".length);
160
+ }
161
+ return "";
162
+ }
163
+ function isTaskConfigEntry(value) {
164
+ if (!isPlainObject(value)) {
165
+ return false;
166
+ }
167
+ if ("role" in value && value.role !== undefined && typeof value.role !== "string") {
168
+ return false;
169
+ }
170
+ if ("scope" in value && value.scope !== undefined && !isStringArray(value.scope)) {
171
+ return false;
172
+ }
173
+ if ("validation" in value && value.validation !== undefined && !isStringArray(value.validation)) {
174
+ return false;
175
+ }
176
+ return true;
177
+ }
178
+ async function readTaskConfigFromDisk(configPath) {
179
+ if (!existsSync(configPath)) {
180
+ return null;
181
+ }
182
+ try {
183
+ const content = typeof Bun !== "undefined" ? await Bun.file(configPath).json() : JSON.parse(readFileSync2(configPath, "utf-8"));
184
+ return isPlainObject(content) ? content : null;
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+ function taskConfigCandidates(ctx) {
190
+ const taskWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
191
+ return [
192
+ ctx?.monorepoMainRoot ? resolve(ctx.monorepoMainRoot, ".rig", "task-config.json") : "",
193
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
194
+ taskWorkspace ? resolve(taskWorkspace, ".rig", "task-config.json") : ""
195
+ ].filter(Boolean);
196
+ }
197
+ async function resolveTaskConfig(_projectRoot, _taskId) {
198
+ const ctx = getContext();
199
+ if (ctx) {
200
+ return {
201
+ scope: ctx.scopes,
202
+ validation: ctx.validation,
203
+ role: ctx.role
204
+ };
205
+ }
206
+ if (BAKED_TASK_CONFIG) {
207
+ try {
208
+ const parsed = JSON.parse(BAKED_TASK_CONFIG);
209
+ if (isTaskConfigEntry(parsed) && Object.keys(parsed).length > 0) {
210
+ return parsed;
211
+ }
212
+ } catch {}
213
+ }
214
+ for (const configPath of taskConfigCandidates(ctx)) {
215
+ const config = await readTaskConfigFromDisk(configPath);
216
+ if (!config) {
217
+ continue;
218
+ }
219
+ const taskEntry = config[_taskId];
220
+ if (isTaskConfigEntry(taskEntry)) {
221
+ return taskEntry;
222
+ }
223
+ }
224
+ return {};
225
+ }
226
+ async function resolveTaskScopes(projectRoot, taskId) {
227
+ const ctx = getContext();
228
+ if (ctx && ctx.scopes.length > 0)
229
+ return ctx.scopes;
230
+ if (BAKED_TASK_SCOPES) {
231
+ try {
232
+ const parsed = JSON.parse(BAKED_TASK_SCOPES);
233
+ if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((item) => typeof item === "string")) {
234
+ return parsed;
235
+ }
236
+ } catch {}
237
+ }
238
+ return (await resolveTaskConfig(projectRoot, taskId)).scope || [];
239
+ }
240
+ function resolvePolicyContent(projectRoot) {
241
+ const ctx = getContext();
242
+ if (ctx?.policyFile && existsSync(ctx.policyFile)) {
243
+ return readFileSync2(ctx.policyFile, "utf-8");
244
+ }
245
+ if (BAKED_POLICY_CONTENT)
246
+ return BAKED_POLICY_CONTENT;
247
+ const policyPath = resolve(projectRoot, "rig/policy/policy.json");
248
+ if (existsSync(policyPath))
249
+ return readFileSync2(policyPath, "utf-8");
250
+ return "{}";
251
+ }
252
+ // packages/hook-kit/src/guard.ts
253
+ import {
254
+ appendFileSync,
255
+ existsSync as existsSync2,
256
+ mkdirSync,
257
+ readFileSync as readFileSync3,
258
+ writeSync
259
+ } from "fs";
260
+ import { resolve as resolve2 } from "path";
261
+
262
+ // packages/hook-kit/src/utils.ts
263
+ function escapeRegExp(value) {
264
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
265
+ }
266
+
267
+ // packages/hook-kit/src/guard.ts
268
+ function block(hookName, message, projectRoot) {
269
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
270
+ const stateDir = BAKED_STATE_DIR || (runtimeWorkspace ? resolve2(runtimeWorkspace, ".rig", "state") : resolve2(projectRoot, ".rig", "state"));
271
+ mkdirSync(stateDir, { recursive: true });
272
+ const tripLog = resolve2(stateDir, "hook_trips.log");
273
+ let count = 0;
274
+ if (existsSync2(tripLog)) {
275
+ const content = readFileSync3(tripLog, "utf-8");
276
+ count = (content.match(new RegExp(`^${escapeRegExp(hookName)}\\s`, "gm")) || []).length;
277
+ }
278
+ appendFileSync(tripLog, `${hookName} ${new Date().toISOString()}
279
+ `, "utf-8");
280
+ count += 1;
281
+ const lines = [
282
+ `BLOCKED: ${message}`,
283
+ "",
284
+ "Re-read CLAUDE.md safety rules before your next action."
285
+ ];
286
+ if (count >= 3) {
287
+ lines.push("");
288
+ lines.push(`REPEATED VIOLATION (${count}x ${hookName}).`);
289
+ lines.push("STOP. Read the FULL CLAUDE.md from top to bottom.");
290
+ lines.push("If stuck, write the problem to .rig/state/failed_approaches.md and request help.");
291
+ }
292
+ writeSync(1, `${lines.join(`
293
+ `)}
294
+ `);
295
+ process.exit(1);
296
+ }
297
+ // packages/hook-kit/src/tools.ts
298
+ function extractToolFilePaths(toolName, input) {
299
+ const paths = [];
300
+ const add = (value) => {
301
+ if (typeof value === "string" && value.trim()) {
302
+ paths.push(value.trim());
303
+ }
304
+ };
305
+ if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
306
+ add(input.file_path);
307
+ add(input.path);
308
+ } else if (toolName === "Glob") {
309
+ add(input.path);
310
+ } else if (toolName === "Grep") {
311
+ add(input.path);
312
+ } else {
313
+ add(input.file_path);
314
+ add(input.path);
315
+ }
316
+ return paths;
317
+ }
318
+ function isTestFilePath(path) {
319
+ return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
320
+ }
321
+ // packages/hook-kit/src/runtime.ts
322
+ import { existsSync as existsSync3, realpathSync } from "fs";
323
+ import { resolve as resolve3 } from "path";
324
+ function normalizeExecutablePath(candidate) {
325
+ if (!candidate) {
326
+ return "";
327
+ }
328
+ const normalized = resolve3(candidate);
329
+ if (!existsSync3(normalized)) {
330
+ return "";
331
+ }
332
+ try {
333
+ return realpathSync(normalized);
334
+ } catch {
335
+ return normalized;
336
+ }
337
+ }
338
+ function looksLikeRuntimeGateway(candidate) {
339
+ const normalized = resolve3(candidate).replace(/\\/g, "/");
340
+ return normalized.includes("/.rig/bin/") || normalized.endsWith("/rig-shell") || normalized.endsWith("/rig-agent");
341
+ }
342
+ function resolveBunBinaryPath() {
343
+ const explicit = normalizeExecutablePath(process.env.RIG_BUN_PATH?.trim());
344
+ if (explicit) {
345
+ return explicit;
346
+ }
347
+ const bunWhich = typeof Bun !== "undefined" ? Bun.which?.("bun") : undefined;
348
+ const pathBun = normalizeExecutablePath(bunWhich?.trim());
349
+ if (pathBun && !looksLikeRuntimeGateway(pathBun)) {
350
+ return pathBun;
351
+ }
352
+ const home = process.env.HOME?.trim();
353
+ const fallbackCandidates = [
354
+ home ? resolve3(home, ".bun/bin/bun") : "",
355
+ "/opt/homebrew/bin/bun",
356
+ "/usr/local/bin/bun",
357
+ "/usr/bin/bun"
358
+ ];
359
+ for (const candidate of fallbackCandidates) {
360
+ const normalized = normalizeExecutablePath(candidate);
361
+ if (normalized) {
362
+ return normalized;
363
+ }
364
+ }
365
+ const execPath = normalizeExecutablePath(process.execPath?.trim());
366
+ if (execPath && !looksLikeRuntimeGateway(execPath)) {
367
+ return execPath;
368
+ }
369
+ throw new Error("bun not found in PATH");
370
+ }
371
+ function resolveBunCli() {
372
+ return resolveBunCliInvocation().command;
373
+ }
374
+ function resolveBunCliInvocation() {
375
+ const bakedBunPath = readBuildConfigForKit().AGENT_BUN_PATH ?? "";
376
+ if (bakedBunPath) {
377
+ return {
378
+ command: bakedBunPath,
379
+ env: {}
380
+ };
381
+ }
382
+ if (process.env.RIG_BUN_PATH?.trim()) {
383
+ return {
384
+ command: process.env.RIG_BUN_PATH.trim(),
385
+ env: {}
386
+ };
387
+ }
388
+ try {
389
+ const systemBun = resolveBunBinaryPath();
390
+ return {
391
+ command: systemBun,
392
+ env: {}
393
+ };
394
+ } catch {}
395
+ if (process.execPath?.trim()) {
396
+ return {
397
+ command: process.execPath,
398
+ env: { BUN_BE_BUN: "1" }
399
+ };
400
+ }
401
+ return { command: "bun", env: {} };
402
+ }
403
+ export {
404
+ resolveTaskScopes,
405
+ resolveTaskIdForHook,
406
+ resolveTaskConfig,
407
+ resolveProjectRoot,
408
+ resolvePolicyContent,
409
+ resolveBunCliInvocation,
410
+ resolveBunCli,
411
+ readHookInput,
412
+ isTestFilePath,
413
+ extractToolFilePaths,
414
+ escapeRegExp,
415
+ block,
416
+ BAKED_TASK_SCOPES,
417
+ BAKED_TASK_ID,
418
+ BAKED_TASK_CONFIG,
419
+ BAKED_STATE_DIR,
420
+ BAKED_PROJECT_ROOT,
421
+ BAKED_POLICY_CONTENT,
422
+ BAKED_BUN_PATH
423
+ };
package/dist/src/io.js ADDED
@@ -0,0 +1,31 @@
1
+ // @bun
2
+ // packages/hook-kit/src/io.ts
3
+ import { readFileSync } from "fs";
4
+ async function readHookInput() {
5
+ let text = "";
6
+ const inputFile = process.env.RIG_HOOK_INPUT_FILE?.trim();
7
+ if (inputFile) {
8
+ text = readFileSync(inputFile, "utf-8");
9
+ } else {
10
+ try {
11
+ text = readFileSync("/dev/stdin", "utf-8");
12
+ } catch {
13
+ text = readFileSync(0, "utf-8");
14
+ }
15
+ }
16
+ if (!text.trim()) {
17
+ return { input: {}, valid: true, hadPayload: false };
18
+ }
19
+ try {
20
+ return {
21
+ input: JSON.parse(text),
22
+ valid: true,
23
+ hadPayload: true
24
+ };
25
+ } catch {
26
+ return { input: {}, valid: false, hadPayload: true };
27
+ }
28
+ }
29
+ export {
30
+ readHookInput
31
+ };
@@ -0,0 +1,119 @@
1
+ // @bun
2
+ // packages/hook-kit/src/runtime.ts
3
+ import { existsSync, realpathSync } from "fs";
4
+ import { resolve } from "path";
5
+
6
+ // packages/hook-kit/src/context.ts
7
+ function normalizeBuildConfig(value) {
8
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
9
+ return {};
10
+ }
11
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
12
+ }
13
+ function readBuildConfigForKit() {
14
+ if (typeof __RIG_BUILD_CONFIG__ !== "undefined") {
15
+ return normalizeBuildConfig(__RIG_BUILD_CONFIG__);
16
+ }
17
+ const raw = process.env.RIG_BUILD_CONFIG_JSON?.trim();
18
+ if (!raw) {
19
+ return {};
20
+ }
21
+ try {
22
+ return normalizeBuildConfig(JSON.parse(raw));
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+ var BUILD_CONFIG = readBuildConfigForKit();
28
+ var BAKED_PROJECT_ROOT = BUILD_CONFIG.AGENT_PROJECT_ROOT ?? "";
29
+ var BAKED_TASK_ID = BUILD_CONFIG.AGENT_TASK_ID ?? "";
30
+ var BAKED_STATE_DIR = BUILD_CONFIG.AGENT_STATE_DIR ?? "";
31
+ var BAKED_BUN_PATH = BUILD_CONFIG.AGENT_BUN_PATH ?? "";
32
+ var BAKED_TASK_CONFIG = BUILD_CONFIG.AGENT_TASK_CONFIG ?? "";
33
+ var BAKED_POLICY_CONTENT = BUILD_CONFIG.AGENT_POLICY_CONTENT ?? "";
34
+ var BAKED_TASK_SCOPES = BUILD_CONFIG.AGENT_TASK_SCOPES ?? "";
35
+
36
+ // packages/hook-kit/src/runtime.ts
37
+ function normalizeExecutablePath(candidate) {
38
+ if (!candidate) {
39
+ return "";
40
+ }
41
+ const normalized = resolve(candidate);
42
+ if (!existsSync(normalized)) {
43
+ return "";
44
+ }
45
+ try {
46
+ return realpathSync(normalized);
47
+ } catch {
48
+ return normalized;
49
+ }
50
+ }
51
+ function looksLikeRuntimeGateway(candidate) {
52
+ const normalized = resolve(candidate).replace(/\\/g, "/");
53
+ return normalized.includes("/.rig/bin/") || normalized.endsWith("/rig-shell") || normalized.endsWith("/rig-agent");
54
+ }
55
+ function resolveBunBinaryPath() {
56
+ const explicit = normalizeExecutablePath(process.env.RIG_BUN_PATH?.trim());
57
+ if (explicit) {
58
+ return explicit;
59
+ }
60
+ const bunWhich = typeof Bun !== "undefined" ? Bun.which?.("bun") : undefined;
61
+ const pathBun = normalizeExecutablePath(bunWhich?.trim());
62
+ if (pathBun && !looksLikeRuntimeGateway(pathBun)) {
63
+ return pathBun;
64
+ }
65
+ const home = process.env.HOME?.trim();
66
+ const fallbackCandidates = [
67
+ home ? resolve(home, ".bun/bin/bun") : "",
68
+ "/opt/homebrew/bin/bun",
69
+ "/usr/local/bin/bun",
70
+ "/usr/bin/bun"
71
+ ];
72
+ for (const candidate of fallbackCandidates) {
73
+ const normalized = normalizeExecutablePath(candidate);
74
+ if (normalized) {
75
+ return normalized;
76
+ }
77
+ }
78
+ const execPath = normalizeExecutablePath(process.execPath?.trim());
79
+ if (execPath && !looksLikeRuntimeGateway(execPath)) {
80
+ return execPath;
81
+ }
82
+ throw new Error("bun not found in PATH");
83
+ }
84
+ function resolveBunCli() {
85
+ return resolveBunCliInvocation().command;
86
+ }
87
+ function resolveBunCliInvocation() {
88
+ const bakedBunPath = readBuildConfigForKit().AGENT_BUN_PATH ?? "";
89
+ if (bakedBunPath) {
90
+ return {
91
+ command: bakedBunPath,
92
+ env: {}
93
+ };
94
+ }
95
+ if (process.env.RIG_BUN_PATH?.trim()) {
96
+ return {
97
+ command: process.env.RIG_BUN_PATH.trim(),
98
+ env: {}
99
+ };
100
+ }
101
+ try {
102
+ const systemBun = resolveBunBinaryPath();
103
+ return {
104
+ command: systemBun,
105
+ env: {}
106
+ };
107
+ } catch {}
108
+ if (process.execPath?.trim()) {
109
+ return {
110
+ command: process.execPath,
111
+ env: { BUN_BE_BUN: "1" }
112
+ };
113
+ }
114
+ return { command: "bun", env: {} };
115
+ }
116
+ export {
117
+ resolveBunCliInvocation,
118
+ resolveBunCli
119
+ };
@@ -0,0 +1,29 @@
1
+ // @bun
2
+ // packages/hook-kit/src/tools.ts
3
+ function extractToolFilePaths(toolName, input) {
4
+ const paths = [];
5
+ const add = (value) => {
6
+ if (typeof value === "string" && value.trim()) {
7
+ paths.push(value.trim());
8
+ }
9
+ };
10
+ if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
11
+ add(input.file_path);
12
+ add(input.path);
13
+ } else if (toolName === "Glob") {
14
+ add(input.path);
15
+ } else if (toolName === "Grep") {
16
+ add(input.path);
17
+ } else {
18
+ add(input.file_path);
19
+ add(input.path);
20
+ }
21
+ return paths;
22
+ }
23
+ function isTestFilePath(path) {
24
+ return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
25
+ }
26
+ export {
27
+ isTestFilePath,
28
+ extractToolFilePaths
29
+ };
@@ -0,0 +1,8 @@
1
+ // @bun
2
+ // packages/hook-kit/src/utils.ts
3
+ function escapeRegExp(value) {
4
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ }
6
+ export {
7
+ escapeRegExp
8
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@h-rig/hook-kit",
3
+ "version": "0.0.6-alpha.0",
4
+ "type": "module",
5
+ "description": "Rig package",
6
+ "license": "UNLICENSED",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/src/index.js"
14
+ }
15
+ },
16
+ "engines": {
17
+ "bun": ">=1.3.11"
18
+ },
19
+ "main": "./dist/src/index.js",
20
+ "module": "./dist/src/index.js",
21
+ "dependencies": {
22
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.0",
23
+ "effect": "4.0.0-beta.78"
24
+ }
25
+ }