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