agent-guardrails 0.3.5 → 0.3.7
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/lib/commands/daemon.js +65 -0
- package/lib/daemon/hooks/daemon-check.js +36 -0
- package/lib/daemon/worker.js +13 -0
- package/package.json +1 -1
package/lib/commands/daemon.js
CHANGED
|
@@ -55,6 +55,69 @@ function removeDaemonRule(repoRoot) {
|
|
|
55
55
|
fs.writeFileSync(agentsFile, content, "utf8");
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* 注入/移除 Claude Code PostToolUse hook(daemon-check)
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
const DAEMON_HOOK_ID = "agent-guardrails:daemon-check";
|
|
63
|
+
const DAEMON_HOOK_SCRIPT = ".claude/hooks/daemon-check.js";
|
|
64
|
+
|
|
65
|
+
function injectClaudeHook(repoRoot) {
|
|
66
|
+
const settingsPath = path.join(repoRoot, ".claude", "settings.json");
|
|
67
|
+
const hookScriptPath = path.join(repoRoot, DAEMON_HOOK_SCRIPT);
|
|
68
|
+
const scriptSource = path.resolve(__dirname, "..", "daemon", "hooks", "daemon-check.js");
|
|
69
|
+
|
|
70
|
+
// 复制 hook 脚本到项目
|
|
71
|
+
const hookDir = path.dirname(hookScriptPath);
|
|
72
|
+
if (!fs.existsSync(hookDir)) fs.mkdirSync(hookDir, { recursive: true });
|
|
73
|
+
fs.copyFileSync(scriptSource, hookScriptPath);
|
|
74
|
+
|
|
75
|
+
// 合并到 settings.json
|
|
76
|
+
let settings = {};
|
|
77
|
+
if (fs.existsSync(settingsPath)) {
|
|
78
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!settings.hooks) settings.hooks = {};
|
|
82
|
+
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
83
|
+
|
|
84
|
+
// 幂等检查
|
|
85
|
+
if (settings.hooks.PostToolUse.some(h => h.id === DAEMON_HOOK_ID)) return;
|
|
86
|
+
|
|
87
|
+
settings.hooks.PostToolUse.push({
|
|
88
|
+
id: DAEMON_HOOK_ID,
|
|
89
|
+
matcher: "Edit|Write|MultiEdit",
|
|
90
|
+
hooks: [{
|
|
91
|
+
type: "command",
|
|
92
|
+
command: `node "${hookScriptPath}"`,
|
|
93
|
+
timeout: 10
|
|
94
|
+
}]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const settingsDir = path.dirname(settingsPath);
|
|
98
|
+
if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
|
|
99
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function removeClaudeHook(repoRoot) {
|
|
103
|
+
const settingsPath = path.join(repoRoot, ".claude", "settings.json");
|
|
104
|
+
const hookScriptPath = path.join(repoRoot, DAEMON_HOOK_SCRIPT);
|
|
105
|
+
|
|
106
|
+
// 删除 hook 脚本
|
|
107
|
+
if (fs.existsSync(hookScriptPath)) fs.unlinkSync(hookScriptPath);
|
|
108
|
+
|
|
109
|
+
// 从 settings.json 移除
|
|
110
|
+
if (!fs.existsSync(settingsPath)) return;
|
|
111
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
112
|
+
if (!settings.hooks?.PostToolUse) return;
|
|
113
|
+
|
|
114
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(h => h.id !== DAEMON_HOOK_ID);
|
|
115
|
+
if (settings.hooks.PostToolUse.length === 0) delete settings.hooks.PostToolUse;
|
|
116
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
119
|
+
}
|
|
120
|
+
|
|
58
121
|
/**
|
|
59
122
|
* 默认守护配置
|
|
60
123
|
*/
|
|
@@ -252,6 +315,7 @@ export async function startDaemon(repoRoot, options = {}) {
|
|
|
252
315
|
|
|
253
316
|
if (pid) {
|
|
254
317
|
injectDaemonRule(repoRoot);
|
|
318
|
+
injectClaudeHook(repoRoot);
|
|
255
319
|
console.log(`${t("daemon.started")}`);
|
|
256
320
|
console.log(` PID: ${pid}`);
|
|
257
321
|
console.log(`\n${t("daemon.logFile")}: ${DAEMON_LOG_FILE}`);
|
|
@@ -310,6 +374,7 @@ export async function stopDaemon(repoRoot, options = {}) {
|
|
|
310
374
|
}
|
|
311
375
|
|
|
312
376
|
removeDaemonRule(repoRoot);
|
|
377
|
+
removeClaudeHook(repoRoot);
|
|
313
378
|
|
|
314
379
|
console.log(`\n${t("daemon.stopped")}`);
|
|
315
380
|
return { success: true };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code PostToolUse Hook — daemon-check
|
|
4
|
+
*
|
|
5
|
+
* Reads the latest daemon check result. If error-level findings exist,
|
|
6
|
+
* outputs them to stderr with exit code 2 so Claude Code sees them.
|
|
7
|
+
* Otherwise exits silently with code 0.
|
|
8
|
+
*
|
|
9
|
+
* Triggered by: Edit, Write, MultiEdit tool usage
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
16
|
+
const resultFile = path.join(projectDir, ".agent-guardrails", "daemon-result.json");
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(resultFile)) process.exit(0);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const data = JSON.parse(fs.readFileSync(resultFile, "utf8"));
|
|
22
|
+
|
|
23
|
+
if (!data.ok && data.result?.findings?.length > 0) {
|
|
24
|
+
const errors = data.result.findings
|
|
25
|
+
.filter(f => f.severity === "error")
|
|
26
|
+
.map(f => `[${f.code}] ${f.message}`)
|
|
27
|
+
.join("\n");
|
|
28
|
+
|
|
29
|
+
if (errors) {
|
|
30
|
+
process.stderr.write(`Guardrails daemon detected issues:\n${errors}\n`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch { /* ignore parse errors */ }
|
|
35
|
+
|
|
36
|
+
process.exit(0);
|
package/lib/daemon/worker.js
CHANGED
|
@@ -276,3 +276,16 @@ export async function run({ repoRoot, config, foreground = false, locale = null
|
|
|
276
276
|
// Keep process alive
|
|
277
277
|
return new Promise(() => { /* never resolve */ });
|
|
278
278
|
}
|
|
279
|
+
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Self-executing entry (when spawned as background process)
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
const args = parseArgs();
|
|
285
|
+
if (args.repoRoot && !args.foreground) {
|
|
286
|
+
const config = args.config || {};
|
|
287
|
+
run({ repoRoot: args.repoRoot, config, foreground: false }).catch((err) => {
|
|
288
|
+
process.stderr.write(`Worker error: ${err.message}\n`);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
});
|
|
291
|
+
}
|