@h-rig/guard-plugin 0.0.6-alpha.157 → 0.0.6-alpha.158

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.
@@ -1,11 +1,679 @@
1
1
  // @bun
2
- // packages/guard-plugin/src/plugin.ts
3
- import { definePlugin } from "@rig/core/config";
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+ var __require = import.meta.require;
18
+
19
+ // packages/guard-plugin/src/hook-ids.ts
20
+ var SAFETY_GUARD_HOOK_ID = "@rig/guard-plugin:safety-guard", SCOPE_GUARD_HOOK_ID = "@rig/guard-plugin:scope-guard", IMPORT_GUARD_HOOK_ID = "@rig/guard-plugin:import-guard", TEST_INTEGRITY_GUARD_HOOK_ID = "@rig/guard-plugin:test-integrity-guard", AUDIT_TRAIL_HOOK_ID = "@rig/guard-plugin:audit-trail", POST_EDIT_LINT_HOOK_ID = "@rig/guard-plugin:post-edit-lint";
21
+
22
+ // packages/guard-plugin/src/scope.ts
23
+ import { getScopeRules } from "@rig/core/scope-rules";
24
+ function unique(values) {
25
+ return [...new Set(values)];
26
+ }
27
+ function normalizeRelativeScopePath(inputPath) {
28
+ let normalized = inputPath.replace(/^\.\//, "");
29
+ const rules = getScopeRules();
30
+ if (rules?.stripPrefixes) {
31
+ for (const prefix of rules.stripPrefixes) {
32
+ if (normalized.startsWith(prefix)) {
33
+ normalized = normalized.slice(prefix.length);
34
+ }
35
+ }
36
+ }
37
+ return normalized;
38
+ }
39
+ function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
40
+ let normalized = inputPath.replace(/^\.\//, "");
41
+ if (normalized.startsWith(projectRoot + "/")) {
42
+ normalized = normalized.slice(projectRoot.length + 1);
43
+ }
44
+ if (normalized.startsWith(monorepoRoot + "/")) {
45
+ normalized = normalized.slice(monorepoRoot.length + 1);
46
+ }
47
+ return normalizeRelativeScopePath(normalized);
48
+ }
49
+ function scopeGlobToRegex(glob) {
50
+ const cached = scopeRegexCache.get(glob);
51
+ if (cached) {
52
+ return cached;
53
+ }
54
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*");
55
+ const compiled = new RegExp(`^${escaped}$`);
56
+ scopeRegexCache.set(glob, compiled);
57
+ return compiled;
58
+ }
59
+ function scopeMatches(path, scopes) {
60
+ const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
61
+ for (const scope of scopes) {
62
+ const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
63
+ for (const candidatePath of pathVariants) {
64
+ for (const candidateScope of scopeVariants) {
65
+ if (candidatePath === candidateScope || scopeGlobToRegex(candidateScope).test(candidatePath)) {
66
+ return true;
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ var scopeRegexCache;
74
+ var init_scope = __esm(() => {
75
+ scopeRegexCache = new Map;
76
+ });
77
+
78
+ // packages/guard-plugin/src/guard.ts
79
+ import { optimizeNextInvocation } from "bun:jsc";
80
+ import { existsSync, readFileSync, statSync } from "fs";
81
+ import { resolve } from "path";
82
+ import {
83
+ POLICY_VERSION
84
+ } from "@rig/contracts";
85
+ function defaultPolicy() {
86
+ return {
87
+ version: POLICY_VERSION,
88
+ mode: "enforce",
89
+ scope: { ...DEFAULT_SCOPE },
90
+ rules: [],
91
+ sandbox: { ...DEFAULT_SANDBOX },
92
+ isolation: { ...DEFAULT_ISOLATION },
93
+ completion: { ...DEFAULT_COMPLETION },
94
+ runtime_image: {
95
+ deps: { ...DEFAULT_RUNTIME_IMAGE.deps },
96
+ plugins_require_binaries: DEFAULT_RUNTIME_IMAGE.plugins_require_binaries
97
+ },
98
+ runtime_snapshot: { ...DEFAULT_RUNTIME_SNAPSHOT }
99
+ };
100
+ }
101
+ function seedPolicyFromContent(rawJson) {
102
+ try {
103
+ const parsed = JSON.parse(rawJson);
104
+ seededPolicyConfig = mergeWithDefaults(parsed);
105
+ } catch {}
106
+ }
107
+ function loadPolicy(projectRoot) {
108
+ if (seededPolicyConfig) {
109
+ return seededPolicyConfig;
110
+ }
111
+ const configPath = resolve(projectRoot, "rig/policy/policy.json");
112
+ if (!existsSync(configPath)) {
113
+ return defaultPolicy();
114
+ }
115
+ let mtimeMs;
116
+ try {
117
+ mtimeMs = statSync(configPath).mtimeMs;
118
+ } catch {
119
+ return defaultPolicy();
120
+ }
121
+ if (policyCache && policyCachePath === configPath && policyCache.mtimeMs === mtimeMs) {
122
+ return policyCache.config;
123
+ }
124
+ let parsed;
125
+ try {
126
+ parsed = JSON.parse(readFileSync(configPath, "utf-8"));
127
+ } catch {
128
+ return defaultPolicy();
129
+ }
130
+ const config = mergeWithDefaults(parsed);
131
+ policyCache = { mtimeMs, config };
132
+ policyCachePath = configPath;
133
+ return config;
134
+ }
135
+ function mergeWithDefaults(parsed) {
136
+ const base = defaultPolicy();
137
+ if (typeof parsed.mode === "string" && isValidMode(parsed.mode)) {
138
+ base.mode = parsed.mode;
139
+ }
140
+ if (parsed.scope && typeof parsed.scope === "object" && !Array.isArray(parsed.scope)) {
141
+ const s = parsed.scope;
142
+ if (typeof s.fail_closed === "boolean")
143
+ base.scope.fail_closed = s.fail_closed;
144
+ if (typeof s.harness_paths_exempt === "boolean")
145
+ base.scope.harness_paths_exempt = s.harness_paths_exempt;
146
+ if (typeof s.runtime_paths_exempt === "boolean")
147
+ base.scope.runtime_paths_exempt = s.runtime_paths_exempt;
148
+ }
149
+ if (Array.isArray(parsed.rules)) {
150
+ base.rules = precompilePolicyRuleRegexes(parsed.rules.filter(isValidRule));
151
+ }
152
+ if (Array.isArray(parsed.deny) && base.rules.length === 0) {
153
+ base.rules = precompilePolicyRuleRegexes(migrateLegacyDeny(parsed.deny));
154
+ }
155
+ if (parsed.sandbox && typeof parsed.sandbox === "object" && !Array.isArray(parsed.sandbox)) {
156
+ const sb = parsed.sandbox;
157
+ if (typeof sb.mode === "string" && isValidMode(sb.mode))
158
+ base.sandbox.mode = sb.mode;
159
+ if (typeof sb.network === "boolean")
160
+ base.sandbox.network = sb.network;
161
+ if (Array.isArray(sb.read_deny))
162
+ base.sandbox.read_deny = sb.read_deny.filter((v) => typeof v === "string");
163
+ if (typeof sb.write_allow_from_runtime === "boolean")
164
+ base.sandbox.write_allow_from_runtime = sb.write_allow_from_runtime;
165
+ }
166
+ if (parsed.isolation && typeof parsed.isolation === "object" && !Array.isArray(parsed.isolation)) {
167
+ const iso = parsed.isolation;
168
+ if (iso.default_mode === "worktree")
169
+ base.isolation.default_mode = iso.default_mode;
170
+ if (typeof iso.repo_symlink_fallback === "boolean")
171
+ base.isolation.repo_symlink_fallback = iso.repo_symlink_fallback;
172
+ if (typeof iso.strict_provisioning === "boolean")
173
+ base.isolation.strict_provisioning = iso.strict_provisioning;
174
+ if (typeof iso.fail_closed_on_provision_error === "boolean")
175
+ base.isolation.fail_closed_on_provision_error = iso.fail_closed_on_provision_error;
176
+ }
177
+ if (parsed.completion && typeof parsed.completion === "object" && !Array.isArray(parsed.completion)) {
178
+ const comp = parsed.completion;
179
+ if (typeof comp.derive_checks_from_scope === "boolean")
180
+ base.completion.derive_checks_from_scope = comp.derive_checks_from_scope;
181
+ if (Array.isArray(comp.checks))
182
+ base.completion.checks = comp.checks.filter((v) => typeof v === "string");
183
+ if (Array.isArray(comp.typescript_config_probe))
184
+ base.completion.typescript_config_probe = comp.typescript_config_probe.filter((v) => typeof v === "string");
185
+ if (Array.isArray(comp.eslint_config_probe))
186
+ base.completion.eslint_config_probe = comp.eslint_config_probe.filter((v) => typeof v === "string");
187
+ }
188
+ if (parsed.runtime_image && typeof parsed.runtime_image === "object" && !Array.isArray(parsed.runtime_image)) {
189
+ const runtimeImage = parsed.runtime_image;
190
+ if (runtimeImage.deps && typeof runtimeImage.deps === "object" && !Array.isArray(runtimeImage.deps)) {
191
+ const deps = runtimeImage.deps;
192
+ if (typeof deps.monorepo_install === "boolean") {
193
+ base.runtime_image.deps.monorepo_install = deps.monorepo_install;
194
+ }
195
+ if (typeof deps.hp_next_install === "boolean") {
196
+ base.runtime_image.deps.hp_next_install = deps.hp_next_install;
197
+ }
198
+ }
199
+ if (typeof runtimeImage.plugins_require_binaries === "boolean") {
200
+ base.runtime_image.plugins_require_binaries = runtimeImage.plugins_require_binaries;
201
+ }
202
+ }
203
+ if (parsed.runtime_snapshot && typeof parsed.runtime_snapshot === "object" && !Array.isArray(parsed.runtime_snapshot)) {
204
+ const runtimeSnapshot = parsed.runtime_snapshot;
205
+ if (typeof runtimeSnapshot.enabled === "boolean") {
206
+ base.runtime_snapshot.enabled = runtimeSnapshot.enabled;
207
+ }
208
+ }
209
+ return base;
210
+ }
211
+ function isValidMode(value) {
212
+ return value === "off" || value === "observe" || value === "enforce";
213
+ }
214
+ function isValidRule(value) {
215
+ if (!value || typeof value !== "object" || Array.isArray(value))
216
+ return false;
217
+ const r = value;
218
+ return typeof r.id === "string" && typeof r.category === "string" && r.match != null && typeof r.match === "object";
219
+ }
220
+ function migrateLegacyDeny(deny) {
221
+ const rules = [];
222
+ for (const entry of deny) {
223
+ if (typeof entry.id !== "string")
224
+ continue;
225
+ const match = {};
226
+ if (typeof entry.pattern === "string")
227
+ match.pattern = entry.pattern;
228
+ if (typeof entry.regex === "string")
229
+ match.regex = entry.regex;
230
+ if (!match.pattern && !match.regex)
231
+ continue;
232
+ rules.push({
233
+ id: entry.id,
234
+ category: "command",
235
+ match,
236
+ action: "block",
237
+ ...typeof entry.description === "string" ? { description: entry.description } : {}
238
+ });
239
+ }
240
+ return rules;
241
+ }
242
+ function precompilePolicyRuleRegexes(rules) {
243
+ return rules.map((rule) => {
244
+ const compiledRegex = rule.match.regex ? compileSafeRegex(rule.match.regex, `rules.${rule.id}.match.regex`, true) : undefined;
245
+ const compiledUnlessRegex = rule.unless?.regex ? compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, true) : undefined;
246
+ return {
247
+ ...rule,
248
+ ...compiledRegex ? { compiledRegex } : {},
249
+ ...compiledUnlessRegex ? { compiledUnlessRegex } : {}
250
+ };
251
+ });
252
+ }
253
+ function getRegexUnsafeReason(pattern) {
254
+ if (pattern.length > 512) {
255
+ return "pattern exceeds max safe length (512 chars)";
256
+ }
257
+ if (/\\[1-9]/.test(pattern)) {
258
+ return "pattern uses backreferences";
259
+ }
260
+ if (/\((?:[^()\\]|\\.)*[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
261
+ return "pattern contains nested quantifiers";
262
+ }
263
+ if (/\((?:[^()\\]|\\.)*\.\\?[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
264
+ return "pattern contains nested broad quantifiers";
265
+ }
266
+ return null;
267
+ }
268
+ function compileSafeRegex(pattern, sourceLabel, logOnFailure) {
269
+ const cached = compiledRegexCache.get(pattern);
270
+ if (cached !== undefined) {
271
+ return cached ?? undefined;
272
+ }
273
+ const unsafeReason = getRegexUnsafeReason(pattern);
274
+ if (unsafeReason) {
275
+ if (logOnFailure) {
276
+ console.warn(`[policy] Skipping unsafe regex in ${sourceLabel}: ${unsafeReason}`);
277
+ }
278
+ compiledRegexCache.set(pattern, null);
279
+ return;
280
+ }
281
+ try {
282
+ const compiled = new RegExp(pattern);
283
+ compiledRegexCache.set(pattern, compiled);
284
+ return compiled;
285
+ } catch (error) {
286
+ if (logOnFailure) {
287
+ const message = error instanceof Error ? error.message : String(error);
288
+ console.warn(`[policy] Skipping invalid regex in ${sourceLabel}: ${message}`);
289
+ }
290
+ compiledRegexCache.set(pattern, null);
291
+ return;
292
+ }
293
+ }
294
+ function matchRule(rule, input) {
295
+ const { match } = rule;
296
+ if (match.pattern && input.includes(match.pattern)) {
297
+ return true;
298
+ }
299
+ if (match.regex) {
300
+ const compiled = rule.compiledRegex || compileSafeRegex(match.regex, `rules.${rule.id}.match.regex`, false);
301
+ if (!compiled) {
302
+ return false;
303
+ }
304
+ try {
305
+ return compiled.test(input);
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+ return false;
311
+ }
312
+ function matchRuleUnless(rule, command, taskId) {
313
+ if (!rule.unless)
314
+ return false;
315
+ if (rule.unless.regex) {
316
+ const compiled = rule.compiledUnlessRegex || compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, false);
317
+ if (!compiled) {
318
+ return false;
319
+ }
320
+ try {
321
+ if (compiled.test(command))
322
+ return true;
323
+ } catch {}
324
+ }
325
+ if (rule.unless.task_in && taskId) {
326
+ if (rule.unless.task_in.includes(taskId))
327
+ return true;
328
+ }
329
+ return false;
330
+ }
331
+ function resolveAction(mode, matched) {
332
+ if (matched.length === 0)
333
+ return "allow";
334
+ if (mode === "off")
335
+ return "allow";
336
+ if (mode === "observe")
337
+ return "warn";
338
+ return "block";
339
+ }
340
+ function resolveAbsolutePath(projectRoot, rawPath) {
341
+ if (rawPath.startsWith("/"))
342
+ return resolve(rawPath);
343
+ return resolve(projectRoot, rawPath);
344
+ }
345
+ function isHarnessPath(projectRoot, rawPath) {
346
+ const absPath = resolveAbsolutePath(projectRoot, rawPath);
347
+ const managedRoots = [
348
+ resolve(projectRoot, "rig"),
349
+ resolve(projectRoot, ".rig"),
350
+ resolve(projectRoot, "artifacts")
351
+ ];
352
+ return managedRoots.some((root) => absPath === root || absPath.startsWith(root + "/"));
353
+ }
354
+ function isRuntimePath(projectRoot, rawPath, taskWorkspace) {
355
+ const absPath = resolveAbsolutePath(projectRoot, rawPath);
356
+ if (taskWorkspace) {
357
+ const workspaceRigRoot = resolve(taskWorkspace, ".rig");
358
+ const workspaceArtifactsRoot = resolve(taskWorkspace, "artifacts");
359
+ if (absPath === workspaceRigRoot || absPath.startsWith(workspaceRigRoot + "/") || absPath === workspaceArtifactsRoot || absPath.startsWith(workspaceArtifactsRoot + "/")) {
360
+ return true;
361
+ }
362
+ }
363
+ const runtimeRoot = resolve(projectRoot, ".rig/runtime/agents");
364
+ return absPath === runtimeRoot || absPath.startsWith(runtimeRoot + "/");
365
+ }
366
+ function isTestFile(path) {
367
+ return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
368
+ }
369
+ function evaluate(context) {
370
+ const policy = loadPolicy(context.projectRoot);
371
+ switch (context.evaluation.type) {
372
+ case "tool-call":
373
+ return evaluateToolCall(policy, context);
374
+ case "command":
375
+ return evaluateCommand(policy, context);
376
+ case "content-write":
377
+ return evaluateContent(policy, context);
378
+ case "file-access":
379
+ return evaluateScope(policy, context, context.evaluation.file_path, context.evaluation.access);
380
+ }
381
+ }
382
+ function evaluateScope(policy, context, filePath, access) {
383
+ const allowed = () => ({
384
+ allowed: true,
385
+ matchedRules: [],
386
+ action: "allow",
387
+ failClosed: false
388
+ });
389
+ if (policy.scope.harness_paths_exempt && isHarnessPath(context.projectRoot, filePath)) {
390
+ return allowed();
391
+ }
392
+ if (policy.scope.runtime_paths_exempt && isRuntimePath(context.projectRoot, filePath, context.taskWorkspace)) {
393
+ return allowed();
394
+ }
395
+ if (!context.taskId) {
396
+ if (access === "write" && policy.scope.fail_closed) {
397
+ return {
398
+ allowed: false,
399
+ matchedRules: [],
400
+ action: resolveAction(policy.mode, [{ id: "scope:no-task", category: "command", reason: "No active task; fail-closed for write operations" }]),
401
+ failClosed: true
402
+ };
403
+ }
404
+ return allowed();
405
+ }
406
+ const scopes = context.taskScopes || [];
407
+ if (scopes.length === 0) {
408
+ return allowed();
409
+ }
410
+ if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith("/")) {
411
+ const absPath = resolve(filePath);
412
+ if (!absPath.startsWith(context.taskWorkspace + "/") && !isHarnessPath(context.projectRoot, filePath)) {
413
+ const reason2 = `Absolute path '${filePath}' is outside task runtime boundary. Allowed root: ${context.taskWorkspace}`;
414
+ const matched2 = [{ id: "scope:workspace-boundary", category: "command", reason: reason2 }];
415
+ return {
416
+ allowed: policy.mode !== "enforce",
417
+ matchedRules: matched2,
418
+ action: resolveAction(policy.mode, matched2),
419
+ failClosed: false
420
+ };
421
+ }
422
+ }
423
+ const monorepoRoot = context.monorepoRoot || process.env.MONOREPO_ROOT?.trim() || context.taskWorkspace || context.projectRoot;
424
+ let normalizedPath = filePath;
425
+ if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith(context.taskWorkspace + "/")) {
426
+ normalizedPath = filePath.slice(context.taskWorkspace.length + 1);
427
+ }
428
+ normalizedPath = normalizePathToScope(context.projectRoot, monorepoRoot, normalizedPath);
429
+ if (scopeMatches(filePath, scopes) || scopeMatches(normalizedPath, scopes)) {
430
+ return allowed();
431
+ }
432
+ const reason = `File '${filePath}' (normalized: '${normalizedPath}') is outside scope of task ${context.taskId}`;
433
+ const matched = [{ id: "scope:out-of-scope", category: "command", reason }];
434
+ return {
435
+ allowed: policy.mode !== "enforce",
436
+ matchedRules: matched,
437
+ action: resolveAction(policy.mode, matched),
438
+ failClosed: false
439
+ };
440
+ }
441
+ function evaluateCommand(policy, context) {
442
+ const evaluation = context.evaluation;
443
+ if (evaluation.type !== "command") {
444
+ return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
445
+ }
446
+ const command = evaluation.command;
447
+ const matchedRules = [];
448
+ for (const rule of policy.rules) {
449
+ if (rule.category !== "command")
450
+ continue;
451
+ if (!matchRule(rule, command))
452
+ continue;
453
+ if (matchRuleUnless(rule, command, context.taskId))
454
+ continue;
455
+ matchedRules.push({
456
+ id: rule.id,
457
+ category: rule.category,
458
+ ...rule.description !== undefined ? { description: rule.description } : {},
459
+ reason: rule.description || `Matched rule ${rule.id}`
460
+ });
461
+ }
462
+ const writeTarget = extractWriteTarget(command);
463
+ if (writeTarget && !/^\/dev\//.test(writeTarget) && !/^\/proc\//.test(writeTarget)) {
464
+ const scopeResult = evaluateScope(policy, context, writeTarget, "write");
465
+ if (!scopeResult.allowed || scopeResult.matchedRules.length > 0) {
466
+ matchedRules.push(...scopeResult.matchedRules);
467
+ }
468
+ }
469
+ const action = resolveAction(policy.mode, matchedRules);
470
+ return {
471
+ allowed: action !== "block",
472
+ matchedRules,
473
+ action,
474
+ failClosed: false
475
+ };
476
+ }
477
+ function extractWriteTarget(command) {
478
+ const redirect = command.match(/>>?\s+([^\s;|&]+)/);
479
+ if (redirect?.[1])
480
+ return redirect[1];
481
+ const tee = command.match(/tee\s+(-a\s+)?([^\s;|&]+)/);
482
+ if (tee?.[2])
483
+ return tee[2];
484
+ return "";
485
+ }
486
+ function evaluateContent(policy, context) {
487
+ const evaluation = context.evaluation;
488
+ if (evaluation.type !== "content-write") {
489
+ return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
490
+ }
491
+ const { content, file_path } = evaluation;
492
+ const matchedRules = [];
493
+ const scopeResult = evaluateScope(policy, context, file_path, "write");
494
+ if (scopeResult.matchedRules.length > 0) {
495
+ matchedRules.push(...scopeResult.matchedRules);
496
+ }
497
+ for (const rule of policy.rules) {
498
+ if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
499
+ continue;
500
+ if (rule.applies_to === "test-files" && !isTestFile(file_path))
501
+ continue;
502
+ if (!matchRule(rule, content))
503
+ continue;
504
+ if (matchRuleUnless(rule, content, context.taskId))
505
+ continue;
506
+ matchedRules.push({
507
+ id: rule.id,
508
+ category: rule.category,
509
+ ...rule.description !== undefined ? { description: rule.description } : {},
510
+ reason: rule.description || `Matched rule ${rule.id}`
511
+ });
512
+ }
513
+ const action = resolveAction(policy.mode, matchedRules);
514
+ return {
515
+ allowed: action !== "block",
516
+ matchedRules,
517
+ action,
518
+ failClosed: false
519
+ };
520
+ }
521
+ function evaluateToolCall(policy, context) {
522
+ const evaluation = context.evaluation;
523
+ if (evaluation.type !== "tool-call") {
524
+ return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
525
+ }
526
+ const { tool_name, tool_input } = evaluation;
527
+ const allMatched = [];
528
+ const filePaths = extractFilePathsFromToolInput(tool_name, tool_input);
529
+ for (const fp of filePaths) {
530
+ const access = isWriteTool(tool_name) ? "write" : "read";
531
+ const scopeResult = evaluateScope(policy, context, fp, access);
532
+ if (scopeResult.matchedRules.length > 0) {
533
+ allMatched.push(...scopeResult.matchedRules);
534
+ }
535
+ }
536
+ const content = extractContentFromToolInput(tool_input);
537
+ if (content) {
538
+ const filePath = filePaths[0] || "";
539
+ const contentContext = {
540
+ ...context,
541
+ evaluation: { type: "content-write", file_path: filePath, content }
542
+ };
543
+ const contentPolicy = loadPolicy(context.projectRoot);
544
+ for (const rule of contentPolicy.rules) {
545
+ if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
546
+ continue;
547
+ if (rule.applies_to === "test-files" && !isTestFile(filePath))
548
+ continue;
549
+ if (!matchRule(rule, content))
550
+ continue;
551
+ if (matchRuleUnless(rule, content, context.taskId))
552
+ continue;
553
+ allMatched.push({
554
+ id: rule.id,
555
+ category: rule.category,
556
+ ...rule.description !== undefined ? { description: rule.description } : {},
557
+ reason: rule.description || `Matched rule ${rule.id}`
558
+ });
559
+ }
560
+ }
561
+ if (tool_name === "Bash") {
562
+ const command = String(tool_input.command || tool_input.cmd || "");
563
+ if (command) {
564
+ const cmdContext = {
565
+ ...context,
566
+ evaluation: { type: "command", command }
567
+ };
568
+ const cmdResult = evaluateCommand(policy, cmdContext);
569
+ if (cmdResult.matchedRules.length > 0) {
570
+ allMatched.push(...cmdResult.matchedRules);
571
+ }
572
+ }
573
+ }
574
+ const seen = new Set;
575
+ const deduplicated = [];
576
+ for (const rule of allMatched) {
577
+ if (!seen.has(rule.id)) {
578
+ seen.add(rule.id);
579
+ deduplicated.push(rule);
580
+ }
581
+ }
582
+ const action = resolveAction(policy.mode, deduplicated);
583
+ return {
584
+ allowed: action !== "block",
585
+ matchedRules: deduplicated,
586
+ action,
587
+ failClosed: false
588
+ };
589
+ }
590
+ function isWriteTool(toolName) {
591
+ return toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit";
592
+ }
593
+ function extractFilePathsFromToolInput(toolName, input) {
594
+ const paths = [];
595
+ const add = (value) => {
596
+ if (typeof value === "string" && value.trim()) {
597
+ paths.push(value.trim());
598
+ }
599
+ };
600
+ if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
601
+ add(input.file_path);
602
+ add(input.path);
603
+ } else if (toolName === "Glob") {
604
+ add(input.path);
605
+ } else if (toolName === "Grep") {
606
+ add(input.path);
607
+ } else {
608
+ add(input.file_path);
609
+ add(input.path);
610
+ }
611
+ return paths;
612
+ }
613
+ function extractContentFromToolInput(input) {
614
+ if (typeof input.content === "string")
615
+ return input.content;
616
+ if (typeof input.new_string === "string")
617
+ return input.new_string;
618
+ return "";
619
+ }
620
+ function primeGuardHotPaths() {
621
+ if (guardHotPathPrimed) {
622
+ return;
623
+ }
624
+ guardHotPathPrimed = true;
625
+ try {
626
+ optimizeNextInvocation(matchRule);
627
+ optimizeNextInvocation(evaluate);
628
+ } catch {}
629
+ }
630
+ var DEFAULT_SCOPE, DEFAULT_SANDBOX, DEFAULT_ISOLATION, DEFAULT_COMPLETION, DEFAULT_RUNTIME_IMAGE, DEFAULT_RUNTIME_SNAPSHOT, policyCache = null, policyCachePath = null, seededPolicyConfig = null, compiledRegexCache, guardHotPathPrimed = false;
631
+ var init_guard = __esm(() => {
632
+ init_scope();
633
+ DEFAULT_SCOPE = {
634
+ fail_closed: true,
635
+ harness_paths_exempt: true,
636
+ runtime_paths_exempt: true
637
+ };
638
+ DEFAULT_SANDBOX = {
639
+ mode: "enforce",
640
+ network: true,
641
+ read_deny: [],
642
+ write_allow_from_runtime: true
643
+ };
644
+ DEFAULT_ISOLATION = {
645
+ default_mode: "worktree",
646
+ repo_symlink_fallback: false,
647
+ strict_provisioning: true,
648
+ fail_closed_on_provision_error: true
649
+ };
650
+ DEFAULT_COMPLETION = {
651
+ derive_checks_from_scope: true,
652
+ checks: [],
653
+ typescript_config_probe: ["tsconfig.json"],
654
+ eslint_config_probe: [".eslintrc.js", ".eslintrc.json", "eslint.config.js"]
655
+ };
656
+ DEFAULT_RUNTIME_IMAGE = {
657
+ deps: {
658
+ monorepo_install: false,
659
+ hp_next_install: false
660
+ },
661
+ plugins_require_binaries: true
662
+ };
663
+ DEFAULT_RUNTIME_SNAPSHOT = {
664
+ enabled: true
665
+ };
666
+ compiledRegexCache = new Map;
667
+ primeGuardHotPaths();
668
+ });
4
669
 
5
670
  // packages/guard-plugin/src/hooks/safety-guard.ts
671
+ var exports_safety_guard = {};
672
+ __export(exports_safety_guard, {
673
+ safetyGuardHandler: () => safetyGuardHandler,
674
+ SAFETY_GUARD_HOOK_ID: () => SAFETY_GUARD_HOOK_ID
675
+ });
6
676
  import { resolveTaskScopes, resolvePolicyContent, runTypedHook } from "@rig/hook-kit";
7
- import { evaluate, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
8
- var SAFETY_GUARD_HOOK_ID = "@rig/guard-plugin:safety-guard";
9
677
  async function safetyGuardHandler(ctx) {
10
678
  const projectRoot = ctx.projectRoot;
11
679
  seedPolicyFromContent(resolvePolicyContent(projectRoot));
@@ -62,17 +730,23 @@ async function safetyGuardHandler(ctx) {
62
730
  }
63
731
  return { decision: "allow" };
64
732
  }
65
- if (process.env.RIG_HOOK_ROLE === "safety-guard") {
66
- runTypedHook(safetyGuardHandler, { event: "PreToolUse" });
67
- }
733
+ var init_safety_guard = __esm(() => {
734
+ init_guard();
735
+ if (process.env.RIG_HOOK_ROLE === "safety-guard") {
736
+ runTypedHook(safetyGuardHandler, { event: "PreToolUse" });
737
+ }
738
+ });
68
739
 
69
740
  // packages/guard-plugin/src/hooks/scope-guard.ts
741
+ var exports_scope_guard = {};
742
+ __export(exports_scope_guard, {
743
+ scopeGuardHandler: () => scopeGuardHandler,
744
+ SCOPE_GUARD_HOOK_ID: () => SCOPE_GUARD_HOOK_ID
745
+ });
70
746
  import { resolveTaskScopes as resolveTaskScopes2, resolvePolicyContent as resolvePolicyContent2, runTypedHook as runTypedHook2 } from "@rig/hook-kit";
71
- import { evaluate as evaluate2, seedPolicyFromContent as seedPolicyFromContent2 } from "@rig/runtime/control-plane/runtime/guard";
72
- var SCOPE_GUARD_HOOK_ID = "@rig/guard-plugin:scope-guard";
73
747
  async function scopeGuardHandler(ctx) {
74
748
  const projectRoot = ctx.projectRoot;
75
- seedPolicyFromContent2(resolvePolicyContent2(projectRoot));
749
+ seedPolicyFromContent(resolvePolicyContent2(projectRoot));
76
750
  const tool = ctx.toolName ?? "";
77
751
  const toolInput = ctx.toolInput;
78
752
  const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
@@ -111,7 +785,7 @@ async function scopeGuardHandler(ctx) {
111
785
  if (scopes.length === 0) {
112
786
  return { decision: "allow" };
113
787
  }
114
- const decision = evaluate2({
788
+ const decision = evaluate({
115
789
  projectRoot,
116
790
  taskScopes: scopes,
117
791
  evaluation,
@@ -125,17 +799,23 @@ async function scopeGuardHandler(ctx) {
125
799
  }
126
800
  return { decision: "allow" };
127
801
  }
128
- if (process.env.RIG_HOOK_ROLE === "scope-guard") {
129
- runTypedHook2(scopeGuardHandler, { event: "PreToolUse" });
130
- }
802
+ var init_scope_guard = __esm(() => {
803
+ init_guard();
804
+ if (process.env.RIG_HOOK_ROLE === "scope-guard") {
805
+ runTypedHook2(scopeGuardHandler, { event: "PreToolUse" });
806
+ }
807
+ });
131
808
 
132
809
  // packages/guard-plugin/src/hooks/import-guard.ts
810
+ var exports_import_guard = {};
811
+ __export(exports_import_guard, {
812
+ importGuardHandler: () => importGuardHandler,
813
+ IMPORT_GUARD_HOOK_ID: () => IMPORT_GUARD_HOOK_ID
814
+ });
133
815
  import { resolveTaskScopes as resolveTaskScopes3, resolvePolicyContent as resolvePolicyContent3, runTypedHook as runTypedHook3 } from "@rig/hook-kit";
134
- import { evaluate as evaluate3, seedPolicyFromContent as seedPolicyFromContent3 } from "@rig/runtime/control-plane/runtime/guard";
135
- var IMPORT_GUARD_HOOK_ID = "@rig/guard-plugin:import-guard";
136
816
  async function importGuardHandler(ctx) {
137
817
  const projectRoot = ctx.projectRoot;
138
- seedPolicyFromContent3(resolvePolicyContent3(projectRoot));
818
+ seedPolicyFromContent(resolvePolicyContent3(projectRoot));
139
819
  const toolInput = ctx.toolInput;
140
820
  const content = String(toolInput.content ?? toolInput.new_string ?? "");
141
821
  if (!content) {
@@ -150,7 +830,7 @@ async function importGuardHandler(ctx) {
150
830
  const taskId = ctx.taskId || "";
151
831
  const scopes = taskId ? await resolveTaskScopes3(projectRoot, taskId) : [];
152
832
  const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
153
- const decision = evaluate3({
833
+ const decision = evaluate({
154
834
  projectRoot,
155
835
  taskScopes: scopes,
156
836
  evaluation,
@@ -170,17 +850,23 @@ Only import from a module public API (index.ts) or npm packages.`
170
850
  }
171
851
  return { decision: "allow" };
172
852
  }
173
- if (process.env.RIG_HOOK_ROLE === "import-guard") {
174
- runTypedHook3(importGuardHandler, { event: "PreToolUse" });
175
- }
853
+ var init_import_guard = __esm(() => {
854
+ init_guard();
855
+ if (process.env.RIG_HOOK_ROLE === "import-guard") {
856
+ runTypedHook3(importGuardHandler, { event: "PreToolUse" });
857
+ }
858
+ });
176
859
 
177
860
  // packages/guard-plugin/src/hooks/test-integrity-guard.ts
861
+ var exports_test_integrity_guard = {};
862
+ __export(exports_test_integrity_guard, {
863
+ testIntegrityGuardHandler: () => testIntegrityGuardHandler,
864
+ TEST_INTEGRITY_GUARD_HOOK_ID: () => TEST_INTEGRITY_GUARD_HOOK_ID
865
+ });
178
866
  import { resolveTaskScopes as resolveTaskScopes4, resolvePolicyContent as resolvePolicyContent4, isTestFilePath, runTypedHook as runTypedHook4 } from "@rig/hook-kit";
179
- import { evaluate as evaluate4, seedPolicyFromContent as seedPolicyFromContent4 } from "@rig/runtime/control-plane/runtime/guard";
180
- var TEST_INTEGRITY_GUARD_HOOK_ID = "@rig/guard-plugin:test-integrity-guard";
181
867
  async function testIntegrityGuardHandler(ctx) {
182
868
  const projectRoot = ctx.projectRoot;
183
- seedPolicyFromContent4(resolvePolicyContent4(projectRoot));
869
+ seedPolicyFromContent(resolvePolicyContent4(projectRoot));
184
870
  const toolInput = ctx.toolInput;
185
871
  const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
186
872
  const content = String(toolInput.content ?? toolInput.new_string ?? "");
@@ -195,7 +881,7 @@ async function testIntegrityGuardHandler(ctx) {
195
881
  const taskId = ctx.taskId || "";
196
882
  const scopes = taskId ? await resolveTaskScopes4(projectRoot, taskId) : [];
197
883
  const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
198
- const decision = evaluate4({
884
+ const decision = evaluate({
199
885
  projectRoot,
200
886
  taskScopes: scopes,
201
887
  evaluation,
@@ -216,21 +902,28 @@ async function testIntegrityGuardHandler(ctx) {
216
902
  }
217
903
  return { decision: "allow" };
218
904
  }
219
- if (process.env.RIG_HOOK_ROLE === "test-integrity-guard") {
220
- runTypedHook4(testIntegrityGuardHandler, { event: "PreToolUse" });
221
- }
905
+ var init_test_integrity_guard = __esm(() => {
906
+ init_guard();
907
+ if (process.env.RIG_HOOK_ROLE === "test-integrity-guard") {
908
+ runTypedHook4(testIntegrityGuardHandler, { event: "PreToolUse" });
909
+ }
910
+ });
222
911
 
223
912
  // packages/guard-plugin/src/hooks/audit-trail.ts
224
- import { appendFileSync, mkdirSync } from "fs";
225
- import { resolve } from "path";
913
+ var exports_audit_trail = {};
914
+ __export(exports_audit_trail, {
915
+ auditTrailHandler: () => auditTrailHandler,
916
+ AUDIT_TRAIL_HOOK_ID: () => AUDIT_TRAIL_HOOK_ID
917
+ });
918
+ import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
919
+ import { resolve as resolve4 } from "path";
226
920
  import { runTypedHook as runTypedHook5 } from "@rig/hook-kit";
227
- import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
228
- var AUDIT_TRAIL_HOOK_ID = "@rig/guard-plugin:audit-trail";
921
+ import { resolveRigLayout } from "@rig/core/layout";
229
922
  function auditTrailHandler(ctx) {
230
923
  const projectRoot = ctx.projectRoot;
231
924
  const toolInput = ctx.toolInput;
232
- const paths = resolveHarnessPaths(projectRoot);
233
- mkdirSync(paths.logsDir, { recursive: true });
925
+ const logsDir = process.env.RIG_LOGS_DIR?.trim() || resolveRigLayout(projectRoot).logsDir;
926
+ mkdirSync3(logsDir, { recursive: true });
234
927
  const taskId = ctx.taskId || "unknown";
235
928
  const tool = ctx.toolName || "unknown";
236
929
  const filePath = String(toolInput.file_path ?? toolInput.path ?? "none");
@@ -244,25 +937,31 @@ function auditTrailHandler(ctx) {
244
937
  if (tool === "Bash" && command) {
245
938
  payload.command_preview = command;
246
939
  }
247
- appendFileSync(resolve(paths.logsDir, "audit.jsonl"), `${JSON.stringify(payload)}
940
+ appendFileSync(resolve4(logsDir, "audit.jsonl"), `${JSON.stringify(payload)}
248
941
  `, "utf-8");
249
942
  return { decision: "allow" };
250
943
  }
251
- if (process.env.RIG_HOOK_ROLE === "audit-trail") {
252
- runTypedHook5(auditTrailHandler, { event: "PreToolUse" });
253
- }
944
+ var init_audit_trail = __esm(() => {
945
+ if (process.env.RIG_HOOK_ROLE === "audit-trail") {
946
+ runTypedHook5(auditTrailHandler, { event: "PreToolUse" });
947
+ }
948
+ });
254
949
 
255
950
  // packages/guard-plugin/src/hooks/post-edit-lint.ts
256
- import { existsSync, readFileSync } from "fs";
951
+ var exports_post_edit_lint = {};
952
+ __export(exports_post_edit_lint, {
953
+ postEditLintHandler: () => postEditLintHandler,
954
+ POST_EDIT_LINT_HOOK_ID: () => POST_EDIT_LINT_HOOK_ID
955
+ });
956
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
257
957
  import { runTypedHook as runTypedHook6 } from "@rig/hook-kit";
258
- var POST_EDIT_LINT_HOOK_ID = "@rig/guard-plugin:post-edit-lint";
259
958
  function postEditLintHandler(ctx) {
260
959
  const toolInput = ctx.toolInput;
261
960
  const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
262
- if (!filePath || !/\.(ts|tsx|js|jsx)$/.test(filePath) || !existsSync(filePath)) {
961
+ if (!filePath || !/\.(ts|tsx|js|jsx)$/.test(filePath) || !existsSync3(filePath)) {
263
962
  return { decision: "allow" };
264
963
  }
265
- const content = readFileSync(filePath, "utf-8");
964
+ const content = readFileSync2(filePath, "utf-8");
266
965
  const warnings = [];
267
966
  const consoleCount = (content.match(/console\.(log|debug)\(/g) || []).length;
268
967
  if (consoleCount > 3) {
@@ -291,8 +990,306 @@ These are not blocking, but must be resolved before task completion.`
291
990
  }
292
991
  return { decision: "allow" };
293
992
  }
294
- if (process.env.RIG_HOOK_ROLE === "post-edit-lint") {
295
- runTypedHook6(postEditLintHandler, { event: "PostToolUse" });
993
+ var init_post_edit_lint = __esm(() => {
994
+ if (process.env.RIG_HOOK_ROLE === "post-edit-lint") {
995
+ runTypedHook6(postEditLintHandler, { event: "PostToolUse" });
996
+ }
997
+ });
998
+
999
+ // packages/guard-plugin/src/plugin.ts
1000
+ import { definePlugin } from "@rig/core/config";
1001
+ import { defineCapability } from "@rig/core/capability";
1002
+ import { CLI_RUNNER, GUARD_TOOLCHAIN_SOURCES } from "@rig/contracts";
1003
+
1004
+ // packages/guard-plugin/src/agent-shell.ts
1005
+ init_guard();
1006
+ var {$ } = globalThis.Bun;
1007
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
1008
+ import { resolve as resolve3 } from "path";
1009
+ import { EventBus } from "@rig/core/runtime-events";
1010
+ import { CliError } from "@rig/contracts";
1011
+
1012
+ // packages/guard-plugin/src/agent-binary-build.ts
1013
+ import { chmodSync, copyFileSync, linkSync, mkdirSync, rmSync, writeFileSync } from "fs";
1014
+ import { basename, dirname, resolve as resolve2 } from "path";
1015
+ import { runtimeProvisioningEnv } from "@rig/core/runtime-provisioning-env";
1016
+
1017
+ // packages/guard-plugin/src/embedded-native-assets.ts
1018
+ var embeddedNatives = null;
1019
+
1020
+ // packages/guard-plugin/src/agent-binary-build.ts
1021
+ function materializeSelfExecRole(outputPath, define) {
1022
+ const selfPath = process.execPath;
1023
+ mkdirSync(dirname(outputPath), { recursive: true });
1024
+ rmSync(outputPath, { force: true });
1025
+ try {
1026
+ linkSync(selfPath, outputPath);
1027
+ } catch {
1028
+ copyFileSync(selfPath, outputPath);
1029
+ }
1030
+ chmodSync(outputPath, 493);
1031
+ const role = basename(outputPath).replace(/\.exe$/i, "");
1032
+ writeFileSync(`${outputPath}.rig-runconfig.json`, `${JSON.stringify({ role, define: define ?? {} }, null, 2)}
1033
+ `, { mode: 384 });
1034
+ }
1035
+ function hasEmbeddedNatives() {
1036
+ return embeddedNatives !== null;
1037
+ }
1038
+ async function buildAgentBinary(entrypoint, outputPath, cwd, defines) {
1039
+ if (hasEmbeddedNatives()) {
1040
+ materializeSelfExecRole(outputPath, defines);
1041
+ return;
1042
+ }
1043
+ await buildRuntimeBinary({
1044
+ entrypoint,
1045
+ outputPath,
1046
+ cwd,
1047
+ ...defines ? { define: defines } : {},
1048
+ env: runtimeProvisioningEnv()
1049
+ });
1050
+ }
1051
+ async function buildRuntimeBinary(options) {
1052
+ mkdirSync(dirname(options.outputPath), { recursive: true });
1053
+ await withTemporaryEnv({
1054
+ ...options.env,
1055
+ ...options.define ? { RIG_BUILD_CONFIG_JSON: JSON.stringify(options.define) } : {}
1056
+ }, async () => {
1057
+ const result = await Bun.build({
1058
+ entrypoints: [resolve2(options.cwd, options.entrypoint)],
1059
+ compile: { outfile: options.outputPath },
1060
+ target: "bun",
1061
+ format: "esm",
1062
+ minify: true,
1063
+ bytecode: true,
1064
+ ...options.define ? { define: { __RIG_BUILD_CONFIG__: JSON.stringify(options.define) } } : {}
1065
+ });
1066
+ if (!result.success) {
1067
+ throw new Error(result.logs.map((log) => log.message).join(`
1068
+ `) || `Failed to compile ${options.entrypoint}`);
1069
+ }
1070
+ });
1071
+ chmodSync(options.outputPath, 493);
1072
+ }
1073
+ async function withTemporaryEnv(env, callback) {
1074
+ const previous = {};
1075
+ for (const [key, value] of Object.entries(env)) {
1076
+ previous[key] = process.env[key];
1077
+ if (value === undefined) {
1078
+ delete process.env[key];
1079
+ } else {
1080
+ process.env[key] = value;
1081
+ }
1082
+ }
1083
+ try {
1084
+ return await callback();
1085
+ } finally {
1086
+ for (const [key, value] of Object.entries(previous)) {
1087
+ if (value === undefined) {
1088
+ delete process.env[key];
1089
+ } else {
1090
+ process.env[key] = value;
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ // packages/guard-plugin/src/agent-shell.ts
1097
+ init_guard();
1098
+ function withProjectRoot(projectRoot) {
1099
+ $.cwd(projectRoot);
1100
+ }
1101
+ function formatCommand(parts) {
1102
+ return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
1103
+ }
1104
+ var AGENT_DISPATCH_SOURCE = "packages/provider-plugin/src/agent-harness/agent-wrapper.ts";
1105
+ function hasAgentDispatchSource(root) {
1106
+ return existsSync2(resolve3(root, AGENT_DISPATCH_SOURCE));
1107
+ }
1108
+ function resolveAgentMaterializationSourceRoot(projectRoot, options = {}) {
1109
+ const env = options.env ?? process.env;
1110
+ const candidates = [
1111
+ env.RIG_HOST_PROJECT_ROOT?.trim(),
1112
+ env.PROJECT_RIG_ROOT?.trim(),
1113
+ options.cwd ?? process.cwd(),
1114
+ resolve3(options.moduleDir ?? import.meta.dir, "../../.."),
1115
+ projectRoot
1116
+ ].filter((value) => Boolean(value));
1117
+ for (const candidate of candidates) {
1118
+ const root = resolve3(candidate);
1119
+ if (hasAgentDispatchSource(root)) {
1120
+ return root;
1121
+ }
1122
+ }
1123
+ return resolve3(projectRoot);
1124
+ }
1125
+ async function ensureAgentShellBinary(projectRoot, options = {}) {
1126
+ const agentBinary = resolve3(projectRoot, ".rig", "bin", "rig-agent");
1127
+ if (existsSync2(agentBinary)) {
1128
+ return agentBinary;
1129
+ }
1130
+ const sourceRoot = resolveAgentMaterializationSourceRoot(projectRoot, options);
1131
+ if (!hasAgentDispatchSource(sourceRoot)) {
1132
+ throw new CliError(`Missing compiled agent dispatch binary at ${agentBinary}, and no Rig source checkout with ${AGENT_DISPATCH_SOURCE} was found to materialize it automatically.`, 2);
1133
+ }
1134
+ mkdirSync2(resolve3(agentBinary, ".."), { recursive: true });
1135
+ try {
1136
+ await (options.build ?? buildAgentBinary)(AGENT_DISPATCH_SOURCE, agentBinary, sourceRoot, {
1137
+ AGENT_PROJECT_ROOT: projectRoot
1138
+ });
1139
+ } catch (error) {
1140
+ throw new CliError(`Missing compiled agent dispatch binary at ${agentBinary}, and automatic materialization from ${sourceRoot} failed: ${error instanceof Error ? error.message : String(error)}`, 2);
1141
+ }
1142
+ if (!existsSync2(agentBinary)) {
1143
+ throw new CliError(`Automatic agent dispatch materialization from ${sourceRoot} did not create ${agentBinary}.`, 2);
1144
+ }
1145
+ return agentBinary;
1146
+ }
1147
+ async function initializeRuntime(options) {
1148
+ const eventBus = new EventBus({
1149
+ projectRoot: options.projectRoot,
1150
+ ...options.runId ? { runId: options.runId } : {}
1151
+ });
1152
+ let context;
1153
+ const baseContext = {
1154
+ projectRoot: options.projectRoot,
1155
+ dryRun: options.dryRun,
1156
+ outputMode: options.outputMode,
1157
+ runId: eventBus.getRunId(),
1158
+ ...options.policyMode ? { policyMode: options.policyMode } : {},
1159
+ eventBus,
1160
+ emitEvent: async (type, payload) => {
1161
+ await eventBus.emit(type, payload);
1162
+ }
1163
+ };
1164
+ context = {
1165
+ ...baseContext,
1166
+ runCommand: async (parts) => runCommand(context, parts)
1167
+ };
1168
+ await context.emitEvent("runtime.init", {
1169
+ runId: context.runId,
1170
+ outputMode: context.outputMode,
1171
+ dryRun: context.dryRun,
1172
+ policyMode: context.policyMode ?? loadPolicy(options.projectRoot).mode,
1173
+ policyFile: resolve3(options.projectRoot, "rig/policy/policy.json")
1174
+ });
1175
+ return context;
1176
+ }
1177
+ async function runCommand(context, parts) {
1178
+ if (parts.length === 0) {
1179
+ throw new CliError("Cannot execute an empty command.");
1180
+ }
1181
+ const envMode = process.env.RIG_BASH_MODE;
1182
+ const effectiveMode = context.policyMode || (envMode === "off" || envMode === "observe" || envMode === "enforce" ? envMode : loadPolicy(context.projectRoot).mode);
1183
+ const controlledPath = `${resolve3(context.projectRoot, ".rig", "bin")}:${context.projectRoot}/rig/tools:${process.env.PATH ?? ""}`;
1184
+ const usesInfrastructureBinary = parts[0] === "rig-server";
1185
+ const controlledBash = usesInfrastructureBinary ? null : await ensureAgentShellBinary(context.projectRoot);
1186
+ const commandEnv = [
1187
+ "env",
1188
+ `PATH=${controlledPath}`,
1189
+ `PROJECT_RIG_ROOT=${context.projectRoot}`,
1190
+ ...controlledBash ? [`BASH=${controlledBash}`] : [],
1191
+ `RIG_BASH_MODE=${effectiveMode}`,
1192
+ `RIG_POLICY_FILE=${resolve3(context.projectRoot, "rig/policy/policy.json")}`,
1193
+ ...context.eventBus.getEventsFile() ? [`RIG_EVENTS_FILE=${context.eventBus.getEventsFile()}`] : []
1194
+ ];
1195
+ const commandWithEnv = [...commandEnv, ...parts];
1196
+ const formatted = formatCommand(commandWithEnv);
1197
+ await context.emitEvent("command.received", {
1198
+ command: commandWithEnv,
1199
+ formattedCommand: formatted
1200
+ });
1201
+ const guardDecision = evaluate({
1202
+ projectRoot: context.projectRoot,
1203
+ evaluation: { type: "command", command: formatted }
1204
+ });
1205
+ const guardAction = resolveAction(effectiveMode, guardDecision.matchedRules);
1206
+ await context.emitEvent("policy.decision", {
1207
+ target: "command",
1208
+ command: formatted,
1209
+ mode: effectiveMode,
1210
+ allowed: guardAction !== "block",
1211
+ matchedRuleIds: guardDecision.matchedRules.map((r) => r.id),
1212
+ reasons: guardDecision.matchedRules.map((r) => r.reason)
1213
+ });
1214
+ if (guardAction === "block") {
1215
+ await context.emitEvent("command.failed", {
1216
+ command: commandWithEnv,
1217
+ formattedCommand: formatted,
1218
+ reason: "Policy denied command",
1219
+ matchedRuleIds: guardDecision.matchedRules.map((r) => r.id),
1220
+ mode: effectiveMode
1221
+ });
1222
+ throw new CliError(`Policy blocked command: ${formatted}`, 126, { hint: "Review rig/policy/policy.json, or re-run with `--policy-mode observe` to log instead of block." });
1223
+ }
1224
+ const startedAt = new Date;
1225
+ await context.emitEvent("command.started", {
1226
+ command: commandWithEnv,
1227
+ formattedCommand: formatted,
1228
+ startedAt: startedAt.toISOString(),
1229
+ dryRun: context.dryRun
1230
+ });
1231
+ if (context.dryRun) {
1232
+ const dryResult = {
1233
+ command: commandWithEnv,
1234
+ formattedCommand: formatted,
1235
+ exitCode: 0,
1236
+ durationMs: 0,
1237
+ startedAt: startedAt.toISOString(),
1238
+ finishedAt: startedAt.toISOString()
1239
+ };
1240
+ if (context.outputMode === "text") {
1241
+ console.log(`$ ${formatted}`);
1242
+ }
1243
+ await context.emitEvent("command.finished", {
1244
+ ...dryResult,
1245
+ dryRun: true
1246
+ });
1247
+ return dryResult;
1248
+ }
1249
+ let exitCode = 0;
1250
+ let stdout = "";
1251
+ let stderr = "";
1252
+ if (context.outputMode === "json") {
1253
+ const child = Bun.spawn(commandWithEnv, {
1254
+ cwd: context.projectRoot,
1255
+ stdin: "inherit",
1256
+ stdout: "pipe",
1257
+ stderr: "pipe"
1258
+ });
1259
+ exitCode = await child.exited;
1260
+ stdout = await new Response(child.stdout).text();
1261
+ stderr = await new Response(child.stderr).text();
1262
+ } else {
1263
+ const child = Bun.spawn(commandWithEnv, {
1264
+ cwd: context.projectRoot,
1265
+ stdin: "inherit",
1266
+ stdout: "inherit",
1267
+ stderr: "inherit"
1268
+ });
1269
+ exitCode = await child.exited;
1270
+ }
1271
+ const finishedAt = new Date;
1272
+ const result = {
1273
+ command: commandWithEnv,
1274
+ formattedCommand: formatted,
1275
+ exitCode,
1276
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
1277
+ startedAt: startedAt.toISOString(),
1278
+ finishedAt: finishedAt.toISOString(),
1279
+ ...context.outputMode === "json" ? { stdout, stderr } : {}
1280
+ };
1281
+ if (exitCode !== 0) {
1282
+ await context.emitEvent("command.failed", {
1283
+ ...result
1284
+ });
1285
+ const errorSuffix = context.outputMode === "json" && stderr ? `
1286
+ ${stderr.trim()}` : "";
1287
+ throw new CliError(`Command failed (${exitCode}): ${formatted}${errorSuffix}`, exitCode);
1288
+ }
1289
+ await context.emitEvent("command.finished", {
1290
+ ...result
1291
+ });
1292
+ return result;
296
1293
  }
297
1294
 
298
1295
  // packages/guard-plugin/src/plugin.ts
@@ -303,42 +1300,123 @@ var GUARD_HOOKS = [
303
1300
  event: "PreToolUse",
304
1301
  matcher: { kind: "all" },
305
1302
  description: "Blocks dangerous commands and content patterns.",
306
- handler: safetyGuardHandler
1303
+ handler: (input) => Promise.resolve().then(() => (init_safety_guard(), exports_safety_guard)).then((m) => m.safetyGuardHandler(input))
307
1304
  },
308
1305
  {
309
1306
  id: SCOPE_GUARD_HOOK_ID,
310
1307
  event: "PreToolUse",
311
1308
  matcher: { kind: "all" },
312
1309
  description: "Enforces task scope boundaries.",
313
- handler: scopeGuardHandler
1310
+ handler: (input) => Promise.resolve().then(() => (init_scope_guard(), exports_scope_guard)).then((m) => m.scopeGuardHandler(input))
314
1311
  },
315
1312
  {
316
1313
  id: IMPORT_GUARD_HOOK_ID,
317
1314
  event: "PreToolUse",
318
1315
  matcher: { kind: "all" },
319
1316
  description: "Blocks cross-module internal imports.",
320
- handler: importGuardHandler
1317
+ handler: (input) => Promise.resolve().then(() => (init_import_guard(), exports_import_guard)).then((m) => m.importGuardHandler(input))
321
1318
  },
322
1319
  {
323
1320
  id: TEST_INTEGRITY_GUARD_HOOK_ID,
324
1321
  event: "PreToolUse",
325
1322
  matcher: { kind: "all" },
326
1323
  description: "Prevents test tampering (.skip, .only, missing assertions).",
327
- handler: testIntegrityGuardHandler
1324
+ handler: (input) => Promise.resolve().then(() => (init_test_integrity_guard(), exports_test_integrity_guard)).then((m) => m.testIntegrityGuardHandler(input))
328
1325
  },
329
1326
  {
330
1327
  id: AUDIT_TRAIL_HOOK_ID,
331
1328
  event: "PreToolUse",
332
1329
  matcher: { kind: "all" },
333
1330
  description: "Logs tool invocations to audit.jsonl.",
334
- handler: auditTrailHandler
1331
+ handler: (input) => Promise.resolve().then(() => (init_audit_trail(), exports_audit_trail)).then((m) => m.auditTrailHandler(input))
335
1332
  },
336
1333
  {
337
1334
  id: POST_EDIT_LINT_HOOK_ID,
338
1335
  event: "PostToolUse",
339
1336
  matcher: { kind: "all" },
340
1337
  description: "Warns about console.log spam, TODO markers, and empty catches.",
341
- handler: postEditLintHandler
1338
+ handler: (input) => Promise.resolve().then(() => (init_post_edit_lint(), exports_post_edit_lint)).then((m) => m.postEditLintHandler(input))
1339
+ }
1340
+ ];
1341
+ var ToolchainSourcesCap = defineCapability(GUARD_TOOLCHAIN_SOURCES);
1342
+ var CliRunnerCap = defineCapability(CLI_RUNNER);
1343
+ var GUARD_TOOLCHAIN_SOURCE_CONTRIBUTION = {
1344
+ hookBinaries: [
1345
+ { name: "scope-guard", source: "packages/guard-plugin/src/hooks/scope-guard.ts" },
1346
+ { name: "import-guard", source: "packages/guard-plugin/src/hooks/import-guard.ts" },
1347
+ { name: "safety-guard", source: "packages/guard-plugin/src/hooks/safety-guard.ts" },
1348
+ { name: "test-integrity-guard", source: "packages/guard-plugin/src/hooks/test-integrity-guard.ts" },
1349
+ { name: "audit-trail", source: "packages/guard-plugin/src/hooks/audit-trail.ts" },
1350
+ { name: "post-edit-lint", source: "packages/guard-plugin/src/hooks/post-edit-lint.ts" }
1351
+ ],
1352
+ namedSources: {
1353
+ "controlled-bash": "packages/guard-plugin/src/controlled-bash.ts"
1354
+ }
1355
+ };
1356
+ var GUARD_CLI_RUNNER = {
1357
+ initializeRuntime: async (options) => initializeRuntime(options),
1358
+ runCommand: async (context, parts) => runCommand(context, parts),
1359
+ ensureAgentShellBinary,
1360
+ resolveAgentMaterializationSourceRoot,
1361
+ withProjectRoot,
1362
+ formatCommand,
1363
+ loadPolicy
1364
+ };
1365
+ var GUARD_CAPABILITIES = [
1366
+ ToolchainSourcesCap.provide(() => GUARD_TOOLCHAIN_SOURCE_CONTRIBUTION, {
1367
+ title: "Guard toolchain sources",
1368
+ description: "Source paths for guard hook binaries and the controlled-bash wrapper, compiled by the isolation runtime toolchain."
1369
+ }),
1370
+ CliRunnerCap.provide(() => GUARD_CLI_RUNNER, {
1371
+ title: "CLI guarded runner",
1372
+ description: "Policy-guarded command runner used by CLI-surface commands through the host seam."
1373
+ })
1374
+ ];
1375
+ async function runHookRunner(pluginName, hookId, role) {
1376
+ process.env.RIG_HOOK_ROLE = role;
1377
+ const { main } = await import("@rig/core/hook-runner");
1378
+ await main(["--plugin", pluginName, "--hook", hookId]);
1379
+ }
1380
+ var GUARD_SEED_ENTRYPOINTS = [
1381
+ {
1382
+ id: `${GUARD_PLUGIN_NAME}:controlled-bash`,
1383
+ basename: "controlled-bash",
1384
+ description: "Policy-guarded shell wrapper used by per-run toolchains.",
1385
+ run: async ({ argv, execPath }) => {
1386
+ const { runControlledBash } = await import("@rig/guard-plugin/controlled-bash");
1387
+ const { dirname: dirname2 } = await import("path");
1388
+ process.exit(await runControlledBash(argv.slice(2), { projectRootFallbackDir: dirname2(execPath ?? process.cwd()) }));
1389
+ }
1390
+ },
1391
+ {
1392
+ id: `${GUARD_PLUGIN_NAME}:scope-guard-entrypoint`,
1393
+ basename: "scope-guard",
1394
+ run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, SCOPE_GUARD_HOOK_ID, basename2 ?? "scope-guard")
1395
+ },
1396
+ {
1397
+ id: `${GUARD_PLUGIN_NAME}:import-guard-entrypoint`,
1398
+ basename: "import-guard",
1399
+ run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, IMPORT_GUARD_HOOK_ID, basename2 ?? "import-guard")
1400
+ },
1401
+ {
1402
+ id: `${GUARD_PLUGIN_NAME}:safety-guard-entrypoint`,
1403
+ basename: "safety-guard",
1404
+ run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, SAFETY_GUARD_HOOK_ID, basename2 ?? "safety-guard")
1405
+ },
1406
+ {
1407
+ id: `${GUARD_PLUGIN_NAME}:test-integrity-guard-entrypoint`,
1408
+ basename: "test-integrity-guard",
1409
+ run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, TEST_INTEGRITY_GUARD_HOOK_ID, basename2 ?? "test-integrity-guard")
1410
+ },
1411
+ {
1412
+ id: `${GUARD_PLUGIN_NAME}:audit-trail-entrypoint`,
1413
+ basename: "audit-trail",
1414
+ run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, AUDIT_TRAIL_HOOK_ID, basename2 ?? "audit-trail")
1415
+ },
1416
+ {
1417
+ id: `${GUARD_PLUGIN_NAME}:post-edit-lint-entrypoint`,
1418
+ basename: "post-edit-lint",
1419
+ run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, POST_EDIT_LINT_HOOK_ID, basename2 ?? "post-edit-lint")
342
1420
  }
343
1421
  ];
344
1422
  function createGuardPlugin() {
@@ -346,7 +1424,9 @@ function createGuardPlugin() {
346
1424
  name: GUARD_PLUGIN_NAME,
347
1425
  version: "0.0.0-alpha.1",
348
1426
  contributes: {
349
- hooks: GUARD_HOOKS
1427
+ hooks: GUARD_HOOKS,
1428
+ capabilities: GUARD_CAPABILITIES,
1429
+ seedEntrypoints: GUARD_SEED_ENTRYPOINTS
350
1430
  }
351
1431
  });
352
1432
  }