@h-rig/guard-plugin 0.0.6-alpha.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/src/hooks/audit-trail.d.ts +4 -0
- package/dist/src/hooks/audit-trail.js +38 -0
- package/dist/src/hooks/import-guard.d.ts +9 -0
- package/dist/src/hooks/import-guard.js +51 -0
- package/dist/src/hooks/post-edit-lint.d.ts +4 -0
- package/dist/src/hooks/post-edit-lint.js +49 -0
- package/dist/src/hooks/safety-guard.d.ts +13 -0
- package/dist/src/hooks/safety-guard.js +70 -0
- package/dist/src/hooks/scope-guard.d.ts +9 -0
- package/dist/src/hooks/scope-guard.js +69 -0
- package/dist/src/hooks/test-integrity-guard.d.ts +9 -0
- package/dist/src/hooks/test-integrity-guard.js +52 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +368 -0
- package/dist/src/plugin.d.ts +3 -0
- package/dist/src/plugin.js +356 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @h-rig/guard-plugin
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/guard-plugin/src/hooks/audit-trail.ts
|
|
5
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
6
|
+
import { resolve } from "path";
|
|
7
|
+
import { runTypedHook } from "@rig/hook-kit";
|
|
8
|
+
import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
|
|
9
|
+
var AUDIT_TRAIL_HOOK_ID = "@rig/guard-plugin:audit-trail";
|
|
10
|
+
function auditTrailHandler(ctx) {
|
|
11
|
+
const projectRoot = ctx.projectRoot;
|
|
12
|
+
const toolInput = ctx.toolInput;
|
|
13
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
14
|
+
mkdirSync(paths.logsDir, { recursive: true });
|
|
15
|
+
const taskId = ctx.taskId || "unknown";
|
|
16
|
+
const tool = ctx.toolName || "unknown";
|
|
17
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "none");
|
|
18
|
+
const command = String(toolInput.command ?? toolInput.cmd ?? "").slice(0, 180);
|
|
19
|
+
const payload = {
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
task: taskId,
|
|
22
|
+
tool,
|
|
23
|
+
path: filePath
|
|
24
|
+
};
|
|
25
|
+
if (tool === "Bash" && command) {
|
|
26
|
+
payload.command_preview = command;
|
|
27
|
+
}
|
|
28
|
+
appendFileSync(resolve(paths.logsDir, "audit.jsonl"), `${JSON.stringify(payload)}
|
|
29
|
+
`, "utf-8");
|
|
30
|
+
return { decision: "allow" };
|
|
31
|
+
}
|
|
32
|
+
if (import.meta.main || process.env.RIG_HOOK_ROLE === "audit-trail") {
|
|
33
|
+
runTypedHook(auditTrailHandler, { event: "PreToolUse" });
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
auditTrailHandler,
|
|
37
|
+
AUDIT_TRAIL_HOOK_ID
|
|
38
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* import-guard hook — blocks cross-module internal imports.
|
|
4
|
+
*
|
|
5
|
+
* Single-channel plugin hook (see safety-guard.ts for the pattern).
|
|
6
|
+
*/
|
|
7
|
+
import type { HookContext, HookResult } from "@rig/contracts";
|
|
8
|
+
export declare const IMPORT_GUARD_HOOK_ID = "@rig/guard-plugin:import-guard";
|
|
9
|
+
export declare function importGuardHandler(ctx: HookContext): Promise<HookResult>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/guard-plugin/src/hooks/import-guard.ts
|
|
5
|
+
import { resolveTaskScopes, resolvePolicyContent, runTypedHook } from "@rig/hook-kit";
|
|
6
|
+
import { evaluate, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
|
|
7
|
+
var IMPORT_GUARD_HOOK_ID = "@rig/guard-plugin:import-guard";
|
|
8
|
+
async function importGuardHandler(ctx) {
|
|
9
|
+
const projectRoot = ctx.projectRoot;
|
|
10
|
+
seedPolicyFromContent(resolvePolicyContent(projectRoot));
|
|
11
|
+
const toolInput = ctx.toolInput;
|
|
12
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
13
|
+
if (!content) {
|
|
14
|
+
return { decision: "allow" };
|
|
15
|
+
}
|
|
16
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
17
|
+
const evaluation = {
|
|
18
|
+
type: "content-write",
|
|
19
|
+
file_path: filePath,
|
|
20
|
+
content
|
|
21
|
+
};
|
|
22
|
+
const taskId = ctx.taskId || "";
|
|
23
|
+
const scopes = taskId ? await resolveTaskScopes(projectRoot, taskId) : [];
|
|
24
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
25
|
+
const decision = evaluate({
|
|
26
|
+
projectRoot,
|
|
27
|
+
taskScopes: scopes,
|
|
28
|
+
evaluation,
|
|
29
|
+
...taskId ? { taskId } : {},
|
|
30
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
31
|
+
});
|
|
32
|
+
const importViolations = decision.matchedRules.filter((r) => r.category === "import");
|
|
33
|
+
if (importViolations.length > 0 && decision.action === "block") {
|
|
34
|
+
const reasons = importViolations.map((r) => r.reason).join(`
|
|
35
|
+
`);
|
|
36
|
+
return {
|
|
37
|
+
decision: "block",
|
|
38
|
+
reason: `Cross-module internal import detected:
|
|
39
|
+
${reasons}
|
|
40
|
+
Only import from a module public API (index.ts) or npm packages.`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { decision: "allow" };
|
|
44
|
+
}
|
|
45
|
+
if (import.meta.main || process.env.RIG_HOOK_ROLE === "import-guard") {
|
|
46
|
+
runTypedHook(importGuardHandler, { event: "PreToolUse" });
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
importGuardHandler,
|
|
50
|
+
IMPORT_GUARD_HOOK_ID
|
|
51
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/guard-plugin/src/hooks/post-edit-lint.ts
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { runTypedHook } from "@rig/hook-kit";
|
|
7
|
+
var POST_EDIT_LINT_HOOK_ID = "@rig/guard-plugin:post-edit-lint";
|
|
8
|
+
function postEditLintHandler(ctx) {
|
|
9
|
+
const toolInput = ctx.toolInput;
|
|
10
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
11
|
+
if (!filePath || !/\.(ts|tsx|js|jsx)$/.test(filePath) || !existsSync(filePath)) {
|
|
12
|
+
return { decision: "allow" };
|
|
13
|
+
}
|
|
14
|
+
const content = readFileSync(filePath, "utf-8");
|
|
15
|
+
const warnings = [];
|
|
16
|
+
const consoleCount = (content.match(/console\.(log|debug)\(/g) || []).length;
|
|
17
|
+
if (consoleCount > 3) {
|
|
18
|
+
warnings.push(`${filePath}: ${consoleCount} console.log/debug calls. Remove before completion.`);
|
|
19
|
+
}
|
|
20
|
+
if (!/\.(test|spec)\./.test(filePath)) {
|
|
21
|
+
const markers = content.split(/\r?\n/).map((line, index) => ({ line, index: index + 1 })).filter((item) => /TODO|FIXME|HACK|\[STUB\]/.test(item.line)).slice(0, 5);
|
|
22
|
+
if (markers.length > 0) {
|
|
23
|
+
warnings.push(`${filePath}: contains TODO/FIXME/HACK/STUB markers:
|
|
24
|
+
${markers.map((m) => `${m.index}: ${m.line}`).join(`
|
|
25
|
+
`)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(content)) {
|
|
29
|
+
warnings.push(`${filePath}: Empty catch block detected.`);
|
|
30
|
+
}
|
|
31
|
+
if (warnings.length > 0) {
|
|
32
|
+
return {
|
|
33
|
+
decision: "allow",
|
|
34
|
+
systemMessage: `POST-EDIT WARNINGS:
|
|
35
|
+
- ${warnings.join(`
|
|
36
|
+
- `)}
|
|
37
|
+
|
|
38
|
+
These are not blocking, but must be resolved before task completion.`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return { decision: "allow" };
|
|
42
|
+
}
|
|
43
|
+
if (import.meta.main || process.env.RIG_HOOK_ROLE === "post-edit-lint") {
|
|
44
|
+
runTypedHook(postEditLintHandler, { event: "PostToolUse" });
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
postEditLintHandler,
|
|
48
|
+
POST_EDIT_LINT_HOOK_ID
|
|
49
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* safety-guard hook — blocks dangerous commands and content patterns.
|
|
4
|
+
*
|
|
5
|
+
* Single-channel plugin hook: the logic lives in `safetyGuardHandler`
|
|
6
|
+
* (a typed HookImplementation contributed by @rig/guard-plugin). The
|
|
7
|
+
* self-exec guard at the bottom keeps the standalone per-run binary and
|
|
8
|
+
* the seed's basename-role dispatch working by running the same handler
|
|
9
|
+
* through @rig/hook-kit's `runTypedHook`.
|
|
10
|
+
*/
|
|
11
|
+
import type { HookContext, HookResult } from "@rig/contracts";
|
|
12
|
+
export declare const SAFETY_GUARD_HOOK_ID = "@rig/guard-plugin:safety-guard";
|
|
13
|
+
export declare function safetyGuardHandler(ctx: HookContext): Promise<HookResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/guard-plugin/src/hooks/safety-guard.ts
|
|
5
|
+
import { resolveTaskScopes, resolvePolicyContent, runTypedHook } from "@rig/hook-kit";
|
|
6
|
+
import { evaluate, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
|
|
7
|
+
var SAFETY_GUARD_HOOK_ID = "@rig/guard-plugin:safety-guard";
|
|
8
|
+
async function safetyGuardHandler(ctx) {
|
|
9
|
+
const projectRoot = ctx.projectRoot;
|
|
10
|
+
seedPolicyFromContent(resolvePolicyContent(projectRoot));
|
|
11
|
+
const tool = ctx.toolName ?? "";
|
|
12
|
+
const toolInput = ctx.toolInput;
|
|
13
|
+
const command = String(toolInput.command ?? toolInput.cmd ?? "");
|
|
14
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
15
|
+
if (tool === "Bash" && !command) {
|
|
16
|
+
return { decision: "block", reason: "Bash tool payload did not include a command field." };
|
|
17
|
+
}
|
|
18
|
+
const taskId = ctx.taskId || "";
|
|
19
|
+
const scopes = taskId ? await resolveTaskScopes(projectRoot, taskId) : [];
|
|
20
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
21
|
+
const baseContext = {
|
|
22
|
+
projectRoot,
|
|
23
|
+
taskScopes: scopes,
|
|
24
|
+
evaluation: { type: "command", command: "" },
|
|
25
|
+
...taskId ? { taskId } : {},
|
|
26
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
27
|
+
};
|
|
28
|
+
if (tool === "Bash" && command) {
|
|
29
|
+
const decision = evaluate({
|
|
30
|
+
...baseContext,
|
|
31
|
+
evaluation: { type: "command", command }
|
|
32
|
+
});
|
|
33
|
+
if (!decision.allowed && decision.action === "block") {
|
|
34
|
+
const reasons = decision.matchedRules.map((r) => r.reason).join(`
|
|
35
|
+
`);
|
|
36
|
+
return { decision: "block", reason: reasons };
|
|
37
|
+
}
|
|
38
|
+
if (taskWorkspace && taskWorkspace !== projectRoot) {
|
|
39
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT || projectRoot;
|
|
40
|
+
const rootReposPrefix = `${hostProjectRoot}/repos/`;
|
|
41
|
+
if (command.includes(rootReposPrefix)) {
|
|
42
|
+
return {
|
|
43
|
+
decision: "block",
|
|
44
|
+
reason: `Absolute root repo path detected (${rootReposPrefix}). Use relative paths or worktree paths under ${taskWorkspace}.`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (content) {
|
|
50
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
51
|
+
const contentDecision = evaluate({
|
|
52
|
+
...baseContext,
|
|
53
|
+
evaluation: { type: "content-write", file_path: filePath, content }
|
|
54
|
+
});
|
|
55
|
+
const contentRules = contentDecision.matchedRules.filter((r) => r.category === "content");
|
|
56
|
+
if (contentRules.length > 0 && contentDecision.action === "block") {
|
|
57
|
+
const reasons = contentRules.map((r) => r.reason).join(`
|
|
58
|
+
`);
|
|
59
|
+
return { decision: "block", reason: reasons };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { decision: "allow" };
|
|
63
|
+
}
|
|
64
|
+
if (import.meta.main || process.env.RIG_HOOK_ROLE === "safety-guard") {
|
|
65
|
+
runTypedHook(safetyGuardHandler, { event: "PreToolUse" });
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
safetyGuardHandler,
|
|
69
|
+
SAFETY_GUARD_HOOK_ID
|
|
70
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* scope-guard hook — enforces task scope boundaries.
|
|
4
|
+
*
|
|
5
|
+
* Single-channel plugin hook (see safety-guard.ts for the pattern).
|
|
6
|
+
*/
|
|
7
|
+
import type { HookContext, HookResult } from "@rig/contracts";
|
|
8
|
+
export declare const SCOPE_GUARD_HOOK_ID = "@rig/guard-plugin:scope-guard";
|
|
9
|
+
export declare function scopeGuardHandler(ctx: HookContext): Promise<HookResult>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/guard-plugin/src/hooks/scope-guard.ts
|
|
5
|
+
import { resolveTaskScopes, resolvePolicyContent, runTypedHook } from "@rig/hook-kit";
|
|
6
|
+
import { evaluate, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
|
|
7
|
+
var SCOPE_GUARD_HOOK_ID = "@rig/guard-plugin:scope-guard";
|
|
8
|
+
async function scopeGuardHandler(ctx) {
|
|
9
|
+
const projectRoot = ctx.projectRoot;
|
|
10
|
+
seedPolicyFromContent(resolvePolicyContent(projectRoot));
|
|
11
|
+
const tool = ctx.toolName ?? "";
|
|
12
|
+
const toolInput = ctx.toolInput;
|
|
13
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
14
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT || projectRoot;
|
|
15
|
+
const runtimeMode = (process.env.RIG_TASK_RUNTIME_MODE || "").trim().toLowerCase();
|
|
16
|
+
const runtimeIsolationActive = runtimeMode !== "" && runtimeMode !== "off" || taskWorkspace !== "" && taskWorkspace !== projectRoot;
|
|
17
|
+
const rootReposPrefix = `${hostProjectRoot}/repos/`;
|
|
18
|
+
for (const filePath of ctx.filePaths) {
|
|
19
|
+
if (!filePath) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const resolvesInsideTaskWorkspace = taskWorkspace !== "" && (filePath === taskWorkspace || filePath.startsWith(`${taskWorkspace}/`));
|
|
23
|
+
if (filePath.startsWith(rootReposPrefix) && !resolvesInsideTaskWorkspace && (taskWorkspace !== "" || runtimeIsolationActive)) {
|
|
24
|
+
return {
|
|
25
|
+
decision: "block",
|
|
26
|
+
reason: `Absolute root repo path detected (${rootReposPrefix}). Use task-runtime paths under ${taskWorkspace || "$RIG_TASK_WORKSPACE"}.`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if ((tool === "Read" || tool === "Glob" || tool === "Grep") && /^repos\//.test(filePath) && (taskWorkspace !== "" || runtimeIsolationActive)) {
|
|
30
|
+
return {
|
|
31
|
+
decision: "block",
|
|
32
|
+
reason: `Relative repo path '${filePath}' is blocked in runtime mode. Use absolute paths under ${taskWorkspace || "$RIG_TASK_WORKSPACE"}.`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const evaluation = {
|
|
37
|
+
type: "tool-call",
|
|
38
|
+
tool_name: tool,
|
|
39
|
+
tool_input: toolInput
|
|
40
|
+
};
|
|
41
|
+
const taskId = ctx.taskId || "";
|
|
42
|
+
if (!taskId) {
|
|
43
|
+
return { decision: "allow" };
|
|
44
|
+
}
|
|
45
|
+
const scopes = await resolveTaskScopes(projectRoot, taskId);
|
|
46
|
+
if (scopes.length === 0) {
|
|
47
|
+
return { decision: "allow" };
|
|
48
|
+
}
|
|
49
|
+
const decision = evaluate({
|
|
50
|
+
projectRoot,
|
|
51
|
+
taskScopes: scopes,
|
|
52
|
+
evaluation,
|
|
53
|
+
...taskId ? { taskId } : {},
|
|
54
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
55
|
+
});
|
|
56
|
+
if (!decision.allowed && decision.action === "block") {
|
|
57
|
+
const reasons = decision.matchedRules.map((r) => r.reason).join(`
|
|
58
|
+
`);
|
|
59
|
+
return { decision: "block", reason: reasons };
|
|
60
|
+
}
|
|
61
|
+
return { decision: "allow" };
|
|
62
|
+
}
|
|
63
|
+
if (import.meta.main || process.env.RIG_HOOK_ROLE === "scope-guard") {
|
|
64
|
+
runTypedHook(scopeGuardHandler, { event: "PreToolUse" });
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
scopeGuardHandler,
|
|
68
|
+
SCOPE_GUARD_HOOK_ID
|
|
69
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* test-integrity-guard hook — prevents test tampering (.skip, .only, etc.).
|
|
4
|
+
*
|
|
5
|
+
* Single-channel plugin hook (see safety-guard.ts for the pattern).
|
|
6
|
+
*/
|
|
7
|
+
import type { HookContext, HookResult } from "@rig/contracts";
|
|
8
|
+
export declare const TEST_INTEGRITY_GUARD_HOOK_ID = "@rig/guard-plugin:test-integrity-guard";
|
|
9
|
+
export declare function testIntegrityGuardHandler(ctx: HookContext): Promise<HookResult>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// packages/guard-plugin/src/hooks/test-integrity-guard.ts
|
|
5
|
+
import { resolveTaskScopes, resolvePolicyContent, isTestFilePath, runTypedHook } from "@rig/hook-kit";
|
|
6
|
+
import { evaluate, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
|
|
7
|
+
var TEST_INTEGRITY_GUARD_HOOK_ID = "@rig/guard-plugin:test-integrity-guard";
|
|
8
|
+
async function testIntegrityGuardHandler(ctx) {
|
|
9
|
+
const projectRoot = ctx.projectRoot;
|
|
10
|
+
seedPolicyFromContent(resolvePolicyContent(projectRoot));
|
|
11
|
+
const toolInput = ctx.toolInput;
|
|
12
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
13
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
14
|
+
if (!filePath || !content) {
|
|
15
|
+
return { decision: "allow" };
|
|
16
|
+
}
|
|
17
|
+
const evaluation = {
|
|
18
|
+
type: "content-write",
|
|
19
|
+
file_path: filePath,
|
|
20
|
+
content
|
|
21
|
+
};
|
|
22
|
+
const taskId = ctx.taskId || "";
|
|
23
|
+
const scopes = taskId ? await resolveTaskScopes(projectRoot, taskId) : [];
|
|
24
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
25
|
+
const decision = evaluate({
|
|
26
|
+
projectRoot,
|
|
27
|
+
taskScopes: scopes,
|
|
28
|
+
evaluation,
|
|
29
|
+
...taskId ? { taskId } : {},
|
|
30
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
31
|
+
});
|
|
32
|
+
const testViolations = decision.matchedRules.filter((r) => r.category === "test-integrity");
|
|
33
|
+
if (testViolations.length > 0 && decision.action === "block") {
|
|
34
|
+
const reasons = testViolations.map((r) => r.reason).join(`
|
|
35
|
+
`);
|
|
36
|
+
return { decision: "block", reason: reasons };
|
|
37
|
+
}
|
|
38
|
+
if (isTestFilePath(filePath) && /(test|it)\s*\(/.test(content) && !/expect\s*\(|assert/.test(content)) {
|
|
39
|
+
return {
|
|
40
|
+
decision: "allow",
|
|
41
|
+
systemMessage: `WARNING: Test block in ${filePath} may have no assertions.`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return { decision: "allow" };
|
|
45
|
+
}
|
|
46
|
+
if (import.meta.main || process.env.RIG_HOOK_ROLE === "test-integrity-guard") {
|
|
47
|
+
runTypedHook(testIntegrityGuardHandler, { event: "PreToolUse" });
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
testIntegrityGuardHandler,
|
|
51
|
+
TEST_INTEGRITY_GUARD_HOOK_ID
|
|
52
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./plugin";
|
|
2
|
+
export { safetyGuardHandler, SAFETY_GUARD_HOOK_ID } from "./hooks/safety-guard";
|
|
3
|
+
export { scopeGuardHandler, SCOPE_GUARD_HOOK_ID } from "./hooks/scope-guard";
|
|
4
|
+
export { importGuardHandler, IMPORT_GUARD_HOOK_ID } from "./hooks/import-guard";
|
|
5
|
+
export { testIntegrityGuardHandler, TEST_INTEGRITY_GUARD_HOOK_ID } from "./hooks/test-integrity-guard";
|
|
6
|
+
export { auditTrailHandler, AUDIT_TRAIL_HOOK_ID } from "./hooks/audit-trail";
|
|
7
|
+
export { postEditLintHandler, POST_EDIT_LINT_HOOK_ID } from "./hooks/post-edit-lint";
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/guard-plugin/src/plugin.ts
|
|
3
|
+
import { definePlugin } from "@rig/core/config";
|
|
4
|
+
|
|
5
|
+
// packages/guard-plugin/src/hooks/safety-guard.ts
|
|
6
|
+
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
|
+
async function safetyGuardHandler(ctx) {
|
|
10
|
+
const projectRoot = ctx.projectRoot;
|
|
11
|
+
seedPolicyFromContent(resolvePolicyContent(projectRoot));
|
|
12
|
+
const tool = ctx.toolName ?? "";
|
|
13
|
+
const toolInput = ctx.toolInput;
|
|
14
|
+
const command = String(toolInput.command ?? toolInput.cmd ?? "");
|
|
15
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
16
|
+
if (tool === "Bash" && !command) {
|
|
17
|
+
return { decision: "block", reason: "Bash tool payload did not include a command field." };
|
|
18
|
+
}
|
|
19
|
+
const taskId = ctx.taskId || "";
|
|
20
|
+
const scopes = taskId ? await resolveTaskScopes(projectRoot, taskId) : [];
|
|
21
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
22
|
+
const baseContext = {
|
|
23
|
+
projectRoot,
|
|
24
|
+
taskScopes: scopes,
|
|
25
|
+
evaluation: { type: "command", command: "" },
|
|
26
|
+
...taskId ? { taskId } : {},
|
|
27
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
28
|
+
};
|
|
29
|
+
if (tool === "Bash" && command) {
|
|
30
|
+
const decision = evaluate({
|
|
31
|
+
...baseContext,
|
|
32
|
+
evaluation: { type: "command", command }
|
|
33
|
+
});
|
|
34
|
+
if (!decision.allowed && decision.action === "block") {
|
|
35
|
+
const reasons = decision.matchedRules.map((r) => r.reason).join(`
|
|
36
|
+
`);
|
|
37
|
+
return { decision: "block", reason: reasons };
|
|
38
|
+
}
|
|
39
|
+
if (taskWorkspace && taskWorkspace !== projectRoot) {
|
|
40
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT || projectRoot;
|
|
41
|
+
const rootReposPrefix = `${hostProjectRoot}/repos/`;
|
|
42
|
+
if (command.includes(rootReposPrefix)) {
|
|
43
|
+
return {
|
|
44
|
+
decision: "block",
|
|
45
|
+
reason: `Absolute root repo path detected (${rootReposPrefix}). Use relative paths or worktree paths under ${taskWorkspace}.`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (content) {
|
|
51
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
52
|
+
const contentDecision = evaluate({
|
|
53
|
+
...baseContext,
|
|
54
|
+
evaluation: { type: "content-write", file_path: filePath, content }
|
|
55
|
+
});
|
|
56
|
+
const contentRules = contentDecision.matchedRules.filter((r) => r.category === "content");
|
|
57
|
+
if (contentRules.length > 0 && contentDecision.action === "block") {
|
|
58
|
+
const reasons = contentRules.map((r) => r.reason).join(`
|
|
59
|
+
`);
|
|
60
|
+
return { decision: "block", reason: reasons };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { decision: "allow" };
|
|
64
|
+
}
|
|
65
|
+
if (process.env.RIG_HOOK_ROLE === "safety-guard") {
|
|
66
|
+
runTypedHook(safetyGuardHandler, { event: "PreToolUse" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// packages/guard-plugin/src/hooks/scope-guard.ts
|
|
70
|
+
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
|
+
async function scopeGuardHandler(ctx) {
|
|
74
|
+
const projectRoot = ctx.projectRoot;
|
|
75
|
+
seedPolicyFromContent2(resolvePolicyContent2(projectRoot));
|
|
76
|
+
const tool = ctx.toolName ?? "";
|
|
77
|
+
const toolInput = ctx.toolInput;
|
|
78
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
79
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT || projectRoot;
|
|
80
|
+
const runtimeMode = (process.env.RIG_TASK_RUNTIME_MODE || "").trim().toLowerCase();
|
|
81
|
+
const runtimeIsolationActive = runtimeMode !== "" && runtimeMode !== "off" || taskWorkspace !== "" && taskWorkspace !== projectRoot;
|
|
82
|
+
const rootReposPrefix = `${hostProjectRoot}/repos/`;
|
|
83
|
+
for (const filePath of ctx.filePaths) {
|
|
84
|
+
if (!filePath) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const resolvesInsideTaskWorkspace = taskWorkspace !== "" && (filePath === taskWorkspace || filePath.startsWith(`${taskWorkspace}/`));
|
|
88
|
+
if (filePath.startsWith(rootReposPrefix) && !resolvesInsideTaskWorkspace && (taskWorkspace !== "" || runtimeIsolationActive)) {
|
|
89
|
+
return {
|
|
90
|
+
decision: "block",
|
|
91
|
+
reason: `Absolute root repo path detected (${rootReposPrefix}). Use task-runtime paths under ${taskWorkspace || "$RIG_TASK_WORKSPACE"}.`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if ((tool === "Read" || tool === "Glob" || tool === "Grep") && /^repos\//.test(filePath) && (taskWorkspace !== "" || runtimeIsolationActive)) {
|
|
95
|
+
return {
|
|
96
|
+
decision: "block",
|
|
97
|
+
reason: `Relative repo path '${filePath}' is blocked in runtime mode. Use absolute paths under ${taskWorkspace || "$RIG_TASK_WORKSPACE"}.`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const evaluation = {
|
|
102
|
+
type: "tool-call",
|
|
103
|
+
tool_name: tool,
|
|
104
|
+
tool_input: toolInput
|
|
105
|
+
};
|
|
106
|
+
const taskId = ctx.taskId || "";
|
|
107
|
+
if (!taskId) {
|
|
108
|
+
return { decision: "allow" };
|
|
109
|
+
}
|
|
110
|
+
const scopes = await resolveTaskScopes2(projectRoot, taskId);
|
|
111
|
+
if (scopes.length === 0) {
|
|
112
|
+
return { decision: "allow" };
|
|
113
|
+
}
|
|
114
|
+
const decision = evaluate2({
|
|
115
|
+
projectRoot,
|
|
116
|
+
taskScopes: scopes,
|
|
117
|
+
evaluation,
|
|
118
|
+
...taskId ? { taskId } : {},
|
|
119
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
120
|
+
});
|
|
121
|
+
if (!decision.allowed && decision.action === "block") {
|
|
122
|
+
const reasons = decision.matchedRules.map((r) => r.reason).join(`
|
|
123
|
+
`);
|
|
124
|
+
return { decision: "block", reason: reasons };
|
|
125
|
+
}
|
|
126
|
+
return { decision: "allow" };
|
|
127
|
+
}
|
|
128
|
+
if (process.env.RIG_HOOK_ROLE === "scope-guard") {
|
|
129
|
+
runTypedHook2(scopeGuardHandler, { event: "PreToolUse" });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// packages/guard-plugin/src/hooks/import-guard.ts
|
|
133
|
+
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
|
+
async function importGuardHandler(ctx) {
|
|
137
|
+
const projectRoot = ctx.projectRoot;
|
|
138
|
+
seedPolicyFromContent3(resolvePolicyContent3(projectRoot));
|
|
139
|
+
const toolInput = ctx.toolInput;
|
|
140
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
141
|
+
if (!content) {
|
|
142
|
+
return { decision: "allow" };
|
|
143
|
+
}
|
|
144
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
145
|
+
const evaluation = {
|
|
146
|
+
type: "content-write",
|
|
147
|
+
file_path: filePath,
|
|
148
|
+
content
|
|
149
|
+
};
|
|
150
|
+
const taskId = ctx.taskId || "";
|
|
151
|
+
const scopes = taskId ? await resolveTaskScopes3(projectRoot, taskId) : [];
|
|
152
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
153
|
+
const decision = evaluate3({
|
|
154
|
+
projectRoot,
|
|
155
|
+
taskScopes: scopes,
|
|
156
|
+
evaluation,
|
|
157
|
+
...taskId ? { taskId } : {},
|
|
158
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
159
|
+
});
|
|
160
|
+
const importViolations = decision.matchedRules.filter((r) => r.category === "import");
|
|
161
|
+
if (importViolations.length > 0 && decision.action === "block") {
|
|
162
|
+
const reasons = importViolations.map((r) => r.reason).join(`
|
|
163
|
+
`);
|
|
164
|
+
return {
|
|
165
|
+
decision: "block",
|
|
166
|
+
reason: `Cross-module internal import detected:
|
|
167
|
+
${reasons}
|
|
168
|
+
Only import from a module public API (index.ts) or npm packages.`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { decision: "allow" };
|
|
172
|
+
}
|
|
173
|
+
if (process.env.RIG_HOOK_ROLE === "import-guard") {
|
|
174
|
+
runTypedHook3(importGuardHandler, { event: "PreToolUse" });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// packages/guard-plugin/src/hooks/test-integrity-guard.ts
|
|
178
|
+
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
|
+
async function testIntegrityGuardHandler(ctx) {
|
|
182
|
+
const projectRoot = ctx.projectRoot;
|
|
183
|
+
seedPolicyFromContent4(resolvePolicyContent4(projectRoot));
|
|
184
|
+
const toolInput = ctx.toolInput;
|
|
185
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
186
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
187
|
+
if (!filePath || !content) {
|
|
188
|
+
return { decision: "allow" };
|
|
189
|
+
}
|
|
190
|
+
const evaluation = {
|
|
191
|
+
type: "content-write",
|
|
192
|
+
file_path: filePath,
|
|
193
|
+
content
|
|
194
|
+
};
|
|
195
|
+
const taskId = ctx.taskId || "";
|
|
196
|
+
const scopes = taskId ? await resolveTaskScopes4(projectRoot, taskId) : [];
|
|
197
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
198
|
+
const decision = evaluate4({
|
|
199
|
+
projectRoot,
|
|
200
|
+
taskScopes: scopes,
|
|
201
|
+
evaluation,
|
|
202
|
+
...taskId ? { taskId } : {},
|
|
203
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
204
|
+
});
|
|
205
|
+
const testViolations = decision.matchedRules.filter((r) => r.category === "test-integrity");
|
|
206
|
+
if (testViolations.length > 0 && decision.action === "block") {
|
|
207
|
+
const reasons = testViolations.map((r) => r.reason).join(`
|
|
208
|
+
`);
|
|
209
|
+
return { decision: "block", reason: reasons };
|
|
210
|
+
}
|
|
211
|
+
if (isTestFilePath(filePath) && /(test|it)\s*\(/.test(content) && !/expect\s*\(|assert/.test(content)) {
|
|
212
|
+
return {
|
|
213
|
+
decision: "allow",
|
|
214
|
+
systemMessage: `WARNING: Test block in ${filePath} may have no assertions.`
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return { decision: "allow" };
|
|
218
|
+
}
|
|
219
|
+
if (process.env.RIG_HOOK_ROLE === "test-integrity-guard") {
|
|
220
|
+
runTypedHook4(testIntegrityGuardHandler, { event: "PreToolUse" });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// packages/guard-plugin/src/hooks/audit-trail.ts
|
|
224
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
225
|
+
import { resolve } from "path";
|
|
226
|
+
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";
|
|
229
|
+
function auditTrailHandler(ctx) {
|
|
230
|
+
const projectRoot = ctx.projectRoot;
|
|
231
|
+
const toolInput = ctx.toolInput;
|
|
232
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
233
|
+
mkdirSync(paths.logsDir, { recursive: true });
|
|
234
|
+
const taskId = ctx.taskId || "unknown";
|
|
235
|
+
const tool = ctx.toolName || "unknown";
|
|
236
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "none");
|
|
237
|
+
const command = String(toolInput.command ?? toolInput.cmd ?? "").slice(0, 180);
|
|
238
|
+
const payload = {
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
task: taskId,
|
|
241
|
+
tool,
|
|
242
|
+
path: filePath
|
|
243
|
+
};
|
|
244
|
+
if (tool === "Bash" && command) {
|
|
245
|
+
payload.command_preview = command;
|
|
246
|
+
}
|
|
247
|
+
appendFileSync(resolve(paths.logsDir, "audit.jsonl"), `${JSON.stringify(payload)}
|
|
248
|
+
`, "utf-8");
|
|
249
|
+
return { decision: "allow" };
|
|
250
|
+
}
|
|
251
|
+
if (process.env.RIG_HOOK_ROLE === "audit-trail") {
|
|
252
|
+
runTypedHook5(auditTrailHandler, { event: "PreToolUse" });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// packages/guard-plugin/src/hooks/post-edit-lint.ts
|
|
256
|
+
import { existsSync, readFileSync } from "fs";
|
|
257
|
+
import { runTypedHook as runTypedHook6 } from "@rig/hook-kit";
|
|
258
|
+
var POST_EDIT_LINT_HOOK_ID = "@rig/guard-plugin:post-edit-lint";
|
|
259
|
+
function postEditLintHandler(ctx) {
|
|
260
|
+
const toolInput = ctx.toolInput;
|
|
261
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
262
|
+
if (!filePath || !/\.(ts|tsx|js|jsx)$/.test(filePath) || !existsSync(filePath)) {
|
|
263
|
+
return { decision: "allow" };
|
|
264
|
+
}
|
|
265
|
+
const content = readFileSync(filePath, "utf-8");
|
|
266
|
+
const warnings = [];
|
|
267
|
+
const consoleCount = (content.match(/console\.(log|debug)\(/g) || []).length;
|
|
268
|
+
if (consoleCount > 3) {
|
|
269
|
+
warnings.push(`${filePath}: ${consoleCount} console.log/debug calls. Remove before completion.`);
|
|
270
|
+
}
|
|
271
|
+
if (!/\.(test|spec)\./.test(filePath)) {
|
|
272
|
+
const markers = content.split(/\r?\n/).map((line, index) => ({ line, index: index + 1 })).filter((item) => /TODO|FIXME|HACK|\[STUB\]/.test(item.line)).slice(0, 5);
|
|
273
|
+
if (markers.length > 0) {
|
|
274
|
+
warnings.push(`${filePath}: contains TODO/FIXME/HACK/STUB markers:
|
|
275
|
+
${markers.map((m) => `${m.index}: ${m.line}`).join(`
|
|
276
|
+
`)}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(content)) {
|
|
280
|
+
warnings.push(`${filePath}: Empty catch block detected.`);
|
|
281
|
+
}
|
|
282
|
+
if (warnings.length > 0) {
|
|
283
|
+
return {
|
|
284
|
+
decision: "allow",
|
|
285
|
+
systemMessage: `POST-EDIT WARNINGS:
|
|
286
|
+
- ${warnings.join(`
|
|
287
|
+
- `)}
|
|
288
|
+
|
|
289
|
+
These are not blocking, but must be resolved before task completion.`
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
return { decision: "allow" };
|
|
293
|
+
}
|
|
294
|
+
if (process.env.RIG_HOOK_ROLE === "post-edit-lint") {
|
|
295
|
+
runTypedHook6(postEditLintHandler, { event: "PostToolUse" });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// packages/guard-plugin/src/plugin.ts
|
|
299
|
+
var GUARD_PLUGIN_NAME = "@rig/guard-plugin";
|
|
300
|
+
var GUARD_HOOKS = [
|
|
301
|
+
{
|
|
302
|
+
id: SAFETY_GUARD_HOOK_ID,
|
|
303
|
+
event: "PreToolUse",
|
|
304
|
+
matcher: { kind: "all" },
|
|
305
|
+
description: "Blocks dangerous commands and content patterns.",
|
|
306
|
+
handler: safetyGuardHandler
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: SCOPE_GUARD_HOOK_ID,
|
|
310
|
+
event: "PreToolUse",
|
|
311
|
+
matcher: { kind: "all" },
|
|
312
|
+
description: "Enforces task scope boundaries.",
|
|
313
|
+
handler: scopeGuardHandler
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: IMPORT_GUARD_HOOK_ID,
|
|
317
|
+
event: "PreToolUse",
|
|
318
|
+
matcher: { kind: "all" },
|
|
319
|
+
description: "Blocks cross-module internal imports.",
|
|
320
|
+
handler: importGuardHandler
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
id: TEST_INTEGRITY_GUARD_HOOK_ID,
|
|
324
|
+
event: "PreToolUse",
|
|
325
|
+
matcher: { kind: "all" },
|
|
326
|
+
description: "Prevents test tampering (.skip, .only, missing assertions).",
|
|
327
|
+
handler: testIntegrityGuardHandler
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: AUDIT_TRAIL_HOOK_ID,
|
|
331
|
+
event: "PreToolUse",
|
|
332
|
+
matcher: { kind: "all" },
|
|
333
|
+
description: "Logs tool invocations to audit.jsonl.",
|
|
334
|
+
handler: auditTrailHandler
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: POST_EDIT_LINT_HOOK_ID,
|
|
338
|
+
event: "PostToolUse",
|
|
339
|
+
matcher: { kind: "all" },
|
|
340
|
+
description: "Warns about console.log spam, TODO markers, and empty catches.",
|
|
341
|
+
handler: postEditLintHandler
|
|
342
|
+
}
|
|
343
|
+
];
|
|
344
|
+
function createGuardPlugin() {
|
|
345
|
+
return definePlugin({
|
|
346
|
+
name: GUARD_PLUGIN_NAME,
|
|
347
|
+
version: "0.0.0-alpha.1",
|
|
348
|
+
contributes: {
|
|
349
|
+
hooks: GUARD_HOOKS
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
export {
|
|
354
|
+
testIntegrityGuardHandler,
|
|
355
|
+
scopeGuardHandler,
|
|
356
|
+
safetyGuardHandler,
|
|
357
|
+
postEditLintHandler,
|
|
358
|
+
importGuardHandler,
|
|
359
|
+
createGuardPlugin,
|
|
360
|
+
auditTrailHandler,
|
|
361
|
+
TEST_INTEGRITY_GUARD_HOOK_ID,
|
|
362
|
+
SCOPE_GUARD_HOOK_ID,
|
|
363
|
+
SAFETY_GUARD_HOOK_ID,
|
|
364
|
+
POST_EDIT_LINT_HOOK_ID,
|
|
365
|
+
IMPORT_GUARD_HOOK_ID,
|
|
366
|
+
GUARD_PLUGIN_NAME,
|
|
367
|
+
AUDIT_TRAIL_HOOK_ID
|
|
368
|
+
};
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/guard-plugin/src/plugin.ts
|
|
3
|
+
import { definePlugin } from "@rig/core/config";
|
|
4
|
+
|
|
5
|
+
// packages/guard-plugin/src/hooks/safety-guard.ts
|
|
6
|
+
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
|
+
async function safetyGuardHandler(ctx) {
|
|
10
|
+
const projectRoot = ctx.projectRoot;
|
|
11
|
+
seedPolicyFromContent(resolvePolicyContent(projectRoot));
|
|
12
|
+
const tool = ctx.toolName ?? "";
|
|
13
|
+
const toolInput = ctx.toolInput;
|
|
14
|
+
const command = String(toolInput.command ?? toolInput.cmd ?? "");
|
|
15
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
16
|
+
if (tool === "Bash" && !command) {
|
|
17
|
+
return { decision: "block", reason: "Bash tool payload did not include a command field." };
|
|
18
|
+
}
|
|
19
|
+
const taskId = ctx.taskId || "";
|
|
20
|
+
const scopes = taskId ? await resolveTaskScopes(projectRoot, taskId) : [];
|
|
21
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
22
|
+
const baseContext = {
|
|
23
|
+
projectRoot,
|
|
24
|
+
taskScopes: scopes,
|
|
25
|
+
evaluation: { type: "command", command: "" },
|
|
26
|
+
...taskId ? { taskId } : {},
|
|
27
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
28
|
+
};
|
|
29
|
+
if (tool === "Bash" && command) {
|
|
30
|
+
const decision = evaluate({
|
|
31
|
+
...baseContext,
|
|
32
|
+
evaluation: { type: "command", command }
|
|
33
|
+
});
|
|
34
|
+
if (!decision.allowed && decision.action === "block") {
|
|
35
|
+
const reasons = decision.matchedRules.map((r) => r.reason).join(`
|
|
36
|
+
`);
|
|
37
|
+
return { decision: "block", reason: reasons };
|
|
38
|
+
}
|
|
39
|
+
if (taskWorkspace && taskWorkspace !== projectRoot) {
|
|
40
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT || projectRoot;
|
|
41
|
+
const rootReposPrefix = `${hostProjectRoot}/repos/`;
|
|
42
|
+
if (command.includes(rootReposPrefix)) {
|
|
43
|
+
return {
|
|
44
|
+
decision: "block",
|
|
45
|
+
reason: `Absolute root repo path detected (${rootReposPrefix}). Use relative paths or worktree paths under ${taskWorkspace}.`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (content) {
|
|
51
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
52
|
+
const contentDecision = evaluate({
|
|
53
|
+
...baseContext,
|
|
54
|
+
evaluation: { type: "content-write", file_path: filePath, content }
|
|
55
|
+
});
|
|
56
|
+
const contentRules = contentDecision.matchedRules.filter((r) => r.category === "content");
|
|
57
|
+
if (contentRules.length > 0 && contentDecision.action === "block") {
|
|
58
|
+
const reasons = contentRules.map((r) => r.reason).join(`
|
|
59
|
+
`);
|
|
60
|
+
return { decision: "block", reason: reasons };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { decision: "allow" };
|
|
64
|
+
}
|
|
65
|
+
if (process.env.RIG_HOOK_ROLE === "safety-guard") {
|
|
66
|
+
runTypedHook(safetyGuardHandler, { event: "PreToolUse" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// packages/guard-plugin/src/hooks/scope-guard.ts
|
|
70
|
+
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
|
+
async function scopeGuardHandler(ctx) {
|
|
74
|
+
const projectRoot = ctx.projectRoot;
|
|
75
|
+
seedPolicyFromContent2(resolvePolicyContent2(projectRoot));
|
|
76
|
+
const tool = ctx.toolName ?? "";
|
|
77
|
+
const toolInput = ctx.toolInput;
|
|
78
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
79
|
+
const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT || projectRoot;
|
|
80
|
+
const runtimeMode = (process.env.RIG_TASK_RUNTIME_MODE || "").trim().toLowerCase();
|
|
81
|
+
const runtimeIsolationActive = runtimeMode !== "" && runtimeMode !== "off" || taskWorkspace !== "" && taskWorkspace !== projectRoot;
|
|
82
|
+
const rootReposPrefix = `${hostProjectRoot}/repos/`;
|
|
83
|
+
for (const filePath of ctx.filePaths) {
|
|
84
|
+
if (!filePath) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const resolvesInsideTaskWorkspace = taskWorkspace !== "" && (filePath === taskWorkspace || filePath.startsWith(`${taskWorkspace}/`));
|
|
88
|
+
if (filePath.startsWith(rootReposPrefix) && !resolvesInsideTaskWorkspace && (taskWorkspace !== "" || runtimeIsolationActive)) {
|
|
89
|
+
return {
|
|
90
|
+
decision: "block",
|
|
91
|
+
reason: `Absolute root repo path detected (${rootReposPrefix}). Use task-runtime paths under ${taskWorkspace || "$RIG_TASK_WORKSPACE"}.`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if ((tool === "Read" || tool === "Glob" || tool === "Grep") && /^repos\//.test(filePath) && (taskWorkspace !== "" || runtimeIsolationActive)) {
|
|
95
|
+
return {
|
|
96
|
+
decision: "block",
|
|
97
|
+
reason: `Relative repo path '${filePath}' is blocked in runtime mode. Use absolute paths under ${taskWorkspace || "$RIG_TASK_WORKSPACE"}.`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const evaluation = {
|
|
102
|
+
type: "tool-call",
|
|
103
|
+
tool_name: tool,
|
|
104
|
+
tool_input: toolInput
|
|
105
|
+
};
|
|
106
|
+
const taskId = ctx.taskId || "";
|
|
107
|
+
if (!taskId) {
|
|
108
|
+
return { decision: "allow" };
|
|
109
|
+
}
|
|
110
|
+
const scopes = await resolveTaskScopes2(projectRoot, taskId);
|
|
111
|
+
if (scopes.length === 0) {
|
|
112
|
+
return { decision: "allow" };
|
|
113
|
+
}
|
|
114
|
+
const decision = evaluate2({
|
|
115
|
+
projectRoot,
|
|
116
|
+
taskScopes: scopes,
|
|
117
|
+
evaluation,
|
|
118
|
+
...taskId ? { taskId } : {},
|
|
119
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
120
|
+
});
|
|
121
|
+
if (!decision.allowed && decision.action === "block") {
|
|
122
|
+
const reasons = decision.matchedRules.map((r) => r.reason).join(`
|
|
123
|
+
`);
|
|
124
|
+
return { decision: "block", reason: reasons };
|
|
125
|
+
}
|
|
126
|
+
return { decision: "allow" };
|
|
127
|
+
}
|
|
128
|
+
if (process.env.RIG_HOOK_ROLE === "scope-guard") {
|
|
129
|
+
runTypedHook2(scopeGuardHandler, { event: "PreToolUse" });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// packages/guard-plugin/src/hooks/import-guard.ts
|
|
133
|
+
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
|
+
async function importGuardHandler(ctx) {
|
|
137
|
+
const projectRoot = ctx.projectRoot;
|
|
138
|
+
seedPolicyFromContent3(resolvePolicyContent3(projectRoot));
|
|
139
|
+
const toolInput = ctx.toolInput;
|
|
140
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
141
|
+
if (!content) {
|
|
142
|
+
return { decision: "allow" };
|
|
143
|
+
}
|
|
144
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
145
|
+
const evaluation = {
|
|
146
|
+
type: "content-write",
|
|
147
|
+
file_path: filePath,
|
|
148
|
+
content
|
|
149
|
+
};
|
|
150
|
+
const taskId = ctx.taskId || "";
|
|
151
|
+
const scopes = taskId ? await resolveTaskScopes3(projectRoot, taskId) : [];
|
|
152
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
153
|
+
const decision = evaluate3({
|
|
154
|
+
projectRoot,
|
|
155
|
+
taskScopes: scopes,
|
|
156
|
+
evaluation,
|
|
157
|
+
...taskId ? { taskId } : {},
|
|
158
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
159
|
+
});
|
|
160
|
+
const importViolations = decision.matchedRules.filter((r) => r.category === "import");
|
|
161
|
+
if (importViolations.length > 0 && decision.action === "block") {
|
|
162
|
+
const reasons = importViolations.map((r) => r.reason).join(`
|
|
163
|
+
`);
|
|
164
|
+
return {
|
|
165
|
+
decision: "block",
|
|
166
|
+
reason: `Cross-module internal import detected:
|
|
167
|
+
${reasons}
|
|
168
|
+
Only import from a module public API (index.ts) or npm packages.`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { decision: "allow" };
|
|
172
|
+
}
|
|
173
|
+
if (process.env.RIG_HOOK_ROLE === "import-guard") {
|
|
174
|
+
runTypedHook3(importGuardHandler, { event: "PreToolUse" });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// packages/guard-plugin/src/hooks/test-integrity-guard.ts
|
|
178
|
+
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
|
+
async function testIntegrityGuardHandler(ctx) {
|
|
182
|
+
const projectRoot = ctx.projectRoot;
|
|
183
|
+
seedPolicyFromContent4(resolvePolicyContent4(projectRoot));
|
|
184
|
+
const toolInput = ctx.toolInput;
|
|
185
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
186
|
+
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
187
|
+
if (!filePath || !content) {
|
|
188
|
+
return { decision: "allow" };
|
|
189
|
+
}
|
|
190
|
+
const evaluation = {
|
|
191
|
+
type: "content-write",
|
|
192
|
+
file_path: filePath,
|
|
193
|
+
content
|
|
194
|
+
};
|
|
195
|
+
const taskId = ctx.taskId || "";
|
|
196
|
+
const scopes = taskId ? await resolveTaskScopes4(projectRoot, taskId) : [];
|
|
197
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
198
|
+
const decision = evaluate4({
|
|
199
|
+
projectRoot,
|
|
200
|
+
taskScopes: scopes,
|
|
201
|
+
evaluation,
|
|
202
|
+
...taskId ? { taskId } : {},
|
|
203
|
+
...taskWorkspace ? { taskWorkspace } : {}
|
|
204
|
+
});
|
|
205
|
+
const testViolations = decision.matchedRules.filter((r) => r.category === "test-integrity");
|
|
206
|
+
if (testViolations.length > 0 && decision.action === "block") {
|
|
207
|
+
const reasons = testViolations.map((r) => r.reason).join(`
|
|
208
|
+
`);
|
|
209
|
+
return { decision: "block", reason: reasons };
|
|
210
|
+
}
|
|
211
|
+
if (isTestFilePath(filePath) && /(test|it)\s*\(/.test(content) && !/expect\s*\(|assert/.test(content)) {
|
|
212
|
+
return {
|
|
213
|
+
decision: "allow",
|
|
214
|
+
systemMessage: `WARNING: Test block in ${filePath} may have no assertions.`
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return { decision: "allow" };
|
|
218
|
+
}
|
|
219
|
+
if (process.env.RIG_HOOK_ROLE === "test-integrity-guard") {
|
|
220
|
+
runTypedHook4(testIntegrityGuardHandler, { event: "PreToolUse" });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// packages/guard-plugin/src/hooks/audit-trail.ts
|
|
224
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
225
|
+
import { resolve } from "path";
|
|
226
|
+
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";
|
|
229
|
+
function auditTrailHandler(ctx) {
|
|
230
|
+
const projectRoot = ctx.projectRoot;
|
|
231
|
+
const toolInput = ctx.toolInput;
|
|
232
|
+
const paths = resolveHarnessPaths(projectRoot);
|
|
233
|
+
mkdirSync(paths.logsDir, { recursive: true });
|
|
234
|
+
const taskId = ctx.taskId || "unknown";
|
|
235
|
+
const tool = ctx.toolName || "unknown";
|
|
236
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "none");
|
|
237
|
+
const command = String(toolInput.command ?? toolInput.cmd ?? "").slice(0, 180);
|
|
238
|
+
const payload = {
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
task: taskId,
|
|
241
|
+
tool,
|
|
242
|
+
path: filePath
|
|
243
|
+
};
|
|
244
|
+
if (tool === "Bash" && command) {
|
|
245
|
+
payload.command_preview = command;
|
|
246
|
+
}
|
|
247
|
+
appendFileSync(resolve(paths.logsDir, "audit.jsonl"), `${JSON.stringify(payload)}
|
|
248
|
+
`, "utf-8");
|
|
249
|
+
return { decision: "allow" };
|
|
250
|
+
}
|
|
251
|
+
if (process.env.RIG_HOOK_ROLE === "audit-trail") {
|
|
252
|
+
runTypedHook5(auditTrailHandler, { event: "PreToolUse" });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// packages/guard-plugin/src/hooks/post-edit-lint.ts
|
|
256
|
+
import { existsSync, readFileSync } from "fs";
|
|
257
|
+
import { runTypedHook as runTypedHook6 } from "@rig/hook-kit";
|
|
258
|
+
var POST_EDIT_LINT_HOOK_ID = "@rig/guard-plugin:post-edit-lint";
|
|
259
|
+
function postEditLintHandler(ctx) {
|
|
260
|
+
const toolInput = ctx.toolInput;
|
|
261
|
+
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
262
|
+
if (!filePath || !/\.(ts|tsx|js|jsx)$/.test(filePath) || !existsSync(filePath)) {
|
|
263
|
+
return { decision: "allow" };
|
|
264
|
+
}
|
|
265
|
+
const content = readFileSync(filePath, "utf-8");
|
|
266
|
+
const warnings = [];
|
|
267
|
+
const consoleCount = (content.match(/console\.(log|debug)\(/g) || []).length;
|
|
268
|
+
if (consoleCount > 3) {
|
|
269
|
+
warnings.push(`${filePath}: ${consoleCount} console.log/debug calls. Remove before completion.`);
|
|
270
|
+
}
|
|
271
|
+
if (!/\.(test|spec)\./.test(filePath)) {
|
|
272
|
+
const markers = content.split(/\r?\n/).map((line, index) => ({ line, index: index + 1 })).filter((item) => /TODO|FIXME|HACK|\[STUB\]/.test(item.line)).slice(0, 5);
|
|
273
|
+
if (markers.length > 0) {
|
|
274
|
+
warnings.push(`${filePath}: contains TODO/FIXME/HACK/STUB markers:
|
|
275
|
+
${markers.map((m) => `${m.index}: ${m.line}`).join(`
|
|
276
|
+
`)}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(content)) {
|
|
280
|
+
warnings.push(`${filePath}: Empty catch block detected.`);
|
|
281
|
+
}
|
|
282
|
+
if (warnings.length > 0) {
|
|
283
|
+
return {
|
|
284
|
+
decision: "allow",
|
|
285
|
+
systemMessage: `POST-EDIT WARNINGS:
|
|
286
|
+
- ${warnings.join(`
|
|
287
|
+
- `)}
|
|
288
|
+
|
|
289
|
+
These are not blocking, but must be resolved before task completion.`
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
return { decision: "allow" };
|
|
293
|
+
}
|
|
294
|
+
if (process.env.RIG_HOOK_ROLE === "post-edit-lint") {
|
|
295
|
+
runTypedHook6(postEditLintHandler, { event: "PostToolUse" });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// packages/guard-plugin/src/plugin.ts
|
|
299
|
+
var GUARD_PLUGIN_NAME = "@rig/guard-plugin";
|
|
300
|
+
var GUARD_HOOKS = [
|
|
301
|
+
{
|
|
302
|
+
id: SAFETY_GUARD_HOOK_ID,
|
|
303
|
+
event: "PreToolUse",
|
|
304
|
+
matcher: { kind: "all" },
|
|
305
|
+
description: "Blocks dangerous commands and content patterns.",
|
|
306
|
+
handler: safetyGuardHandler
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: SCOPE_GUARD_HOOK_ID,
|
|
310
|
+
event: "PreToolUse",
|
|
311
|
+
matcher: { kind: "all" },
|
|
312
|
+
description: "Enforces task scope boundaries.",
|
|
313
|
+
handler: scopeGuardHandler
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: IMPORT_GUARD_HOOK_ID,
|
|
317
|
+
event: "PreToolUse",
|
|
318
|
+
matcher: { kind: "all" },
|
|
319
|
+
description: "Blocks cross-module internal imports.",
|
|
320
|
+
handler: importGuardHandler
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
id: TEST_INTEGRITY_GUARD_HOOK_ID,
|
|
324
|
+
event: "PreToolUse",
|
|
325
|
+
matcher: { kind: "all" },
|
|
326
|
+
description: "Prevents test tampering (.skip, .only, missing assertions).",
|
|
327
|
+
handler: testIntegrityGuardHandler
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: AUDIT_TRAIL_HOOK_ID,
|
|
331
|
+
event: "PreToolUse",
|
|
332
|
+
matcher: { kind: "all" },
|
|
333
|
+
description: "Logs tool invocations to audit.jsonl.",
|
|
334
|
+
handler: auditTrailHandler
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: POST_EDIT_LINT_HOOK_ID,
|
|
338
|
+
event: "PostToolUse",
|
|
339
|
+
matcher: { kind: "all" },
|
|
340
|
+
description: "Warns about console.log spam, TODO markers, and empty catches.",
|
|
341
|
+
handler: postEditLintHandler
|
|
342
|
+
}
|
|
343
|
+
];
|
|
344
|
+
function createGuardPlugin() {
|
|
345
|
+
return definePlugin({
|
|
346
|
+
name: GUARD_PLUGIN_NAME,
|
|
347
|
+
version: "0.0.0-alpha.1",
|
|
348
|
+
contributes: {
|
|
349
|
+
hooks: GUARD_HOOKS
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
export {
|
|
354
|
+
createGuardPlugin,
|
|
355
|
+
GUARD_PLUGIN_NAME
|
|
356
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@h-rig/guard-plugin",
|
|
3
|
+
"version": "0.0.6-alpha.156",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "First-party runtime policy-guard hooks contributed as a single-channel Rig plugin.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/src/index.d.ts",
|
|
14
|
+
"import": "./dist/src/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./plugin": {
|
|
17
|
+
"types": "./dist/src/plugin.d.ts",
|
|
18
|
+
"import": "./dist/src/plugin.js"
|
|
19
|
+
},
|
|
20
|
+
"./hooks/safety-guard": {
|
|
21
|
+
"types": "./dist/src/hooks/safety-guard.d.ts",
|
|
22
|
+
"import": "./dist/src/hooks/safety-guard.js"
|
|
23
|
+
},
|
|
24
|
+
"./hooks/scope-guard": {
|
|
25
|
+
"types": "./dist/src/hooks/scope-guard.d.ts",
|
|
26
|
+
"import": "./dist/src/hooks/scope-guard.js"
|
|
27
|
+
},
|
|
28
|
+
"./hooks/import-guard": {
|
|
29
|
+
"types": "./dist/src/hooks/import-guard.d.ts",
|
|
30
|
+
"import": "./dist/src/hooks/import-guard.js"
|
|
31
|
+
},
|
|
32
|
+
"./hooks/test-integrity-guard": {
|
|
33
|
+
"types": "./dist/src/hooks/test-integrity-guard.d.ts",
|
|
34
|
+
"import": "./dist/src/hooks/test-integrity-guard.js"
|
|
35
|
+
},
|
|
36
|
+
"./hooks/audit-trail": {
|
|
37
|
+
"types": "./dist/src/hooks/audit-trail.d.ts",
|
|
38
|
+
"import": "./dist/src/hooks/audit-trail.js"
|
|
39
|
+
},
|
|
40
|
+
"./hooks/post-edit-lint": {
|
|
41
|
+
"types": "./dist/src/hooks/post-edit-lint.d.ts",
|
|
42
|
+
"import": "./dist/src/hooks/post-edit-lint.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"bun": ">=1.3.11"
|
|
47
|
+
},
|
|
48
|
+
"main": "./dist/src/index.js",
|
|
49
|
+
"module": "./dist/src/index.js",
|
|
50
|
+
"types": "./dist/src/index.d.ts",
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.156",
|
|
53
|
+
"@rig/core": "npm:@h-rig/core@0.0.6-alpha.156",
|
|
54
|
+
"@rig/hook-kit": "npm:@h-rig/hook-kit@0.0.6-alpha.156",
|
|
55
|
+
"@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.156"
|
|
56
|
+
}
|
|
57
|
+
}
|