@shomra/agent 0.3.16 → 0.3.18
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/NOTICE +1 -1
- package/README.md +57 -57
- package/package.json +3 -9
- package/shomra.mjs +9 -7168
- package/src/agents/hook-command.mjs +19 -0
- package/src/agents/hook-files.mjs +41 -0
- package/src/agents/installers.mjs +203 -0
- package/src/artifacts/matchers.mjs +59 -0
- package/src/artifacts/report.mjs +50 -0
- package/src/cli/flags.mjs +68 -0
- package/src/cli/help-sections.mjs +309 -0
- package/src/cli/help.mjs +27 -0
- package/src/cli/main.mjs +55 -0
- package/src/cli/registry.mjs +80 -0
- package/src/cli/suggestions.mjs +33 -0
- package/src/commands/add.mjs +149 -0
- package/src/commands/agent-identity.mjs +46 -0
- package/src/commands/check.mjs +194 -0
- package/src/commands/corpus.mjs +126 -0
- package/src/commands/design.mjs +168 -0
- package/src/commands/doctor.mjs +209 -0
- package/src/commands/fix.mjs +115 -0
- package/src/commands/gate.mjs +154 -0
- package/src/commands/git-hooks.mjs +163 -0
- package/src/commands/init.mjs +36 -0
- package/src/commands/install-hook.mjs +51 -0
- package/src/commands/llm-proxy.mjs +153 -0
- package/src/commands/mcp-add.mjs +185 -0
- package/src/commands/mcp.mjs +143 -0
- package/src/commands/memory-scan.mjs +181 -0
- package/src/commands/model-scan.mjs +99 -0
- package/src/commands/models.mjs +145 -0
- package/src/commands/new.mjs +64 -0
- package/src/commands/plan.mjs +87 -0
- package/src/commands/pr.mjs +249 -0
- package/src/commands/protect.mjs +38 -0
- package/src/commands/provenance.mjs +91 -0
- package/src/commands/redteam.mjs +166 -0
- package/src/commands/rules.mjs +220 -0
- package/src/commands/run.mjs +128 -0
- package/src/commands/scan-zip.mjs +118 -0
- package/src/commands/scan.mjs +102 -0
- package/src/commands/secrets.mjs +99 -0
- package/src/commands/status.mjs +50 -0
- package/src/commands/why.mjs +88 -0
- package/src/core/api-client.mjs +66 -0
- package/src/core/api-key.mjs +6 -0
- package/src/core/circuit-breaker.mjs +42 -0
- package/src/core/config.mjs +37 -0
- package/src/core/exit-codes.mjs +9 -0
- package/src/core/json-file.mjs +13 -0
- package/src/core/numbers.mjs +4 -0
- package/src/core/package-root.mjs +10 -0
- package/src/core/terminal.mjs +16 -0
- package/src/core/version.mjs +14 -0
- package/src/core/wire-limits.mjs +53 -0
- package/src/corpus/screening.mjs +127 -0
- package/{ai-usage.mjs → src/detect/ai-usage.mjs} +0 -27
- package/src/detect/code-sast.mjs +2 -0
- package/{design.mjs → src/detect/design.mjs} +18 -107
- package/src/detect/guard-signals.mjs +18 -0
- package/{model-refs.mjs → src/detect/model-refs.mjs} +18 -77
- package/src/detect/sast/chains.mjs +30 -0
- package/src/detect/sast/path-expressions.mjs +76 -0
- package/src/detect/sast/rules-chains.mjs +33 -0
- package/src/detect/sast/rules-config.mjs +51 -0
- package/src/detect/sast/rules-javascript.mjs +109 -0
- package/src/detect/sast/rules-python.mjs +292 -0
- package/src/detect/sast/scanner.mjs +104 -0
- package/src/detect/sast/source-lines.mjs +115 -0
- package/src/detect/sast/taint.mjs +71 -0
- package/src/detect/signals/artifacts.mjs +113 -0
- package/src/detect/signals/autonomy.mjs +55 -0
- package/src/detect/signals/config-markers.mjs +28 -0
- package/src/detect/signals/credential-harvest.mjs +64 -0
- package/src/detect/signals/durable-claims.mjs +73 -0
- package/src/detect/signals/egress.mjs +56 -0
- package/src/detect/signals/execution-hijack.mjs +128 -0
- package/src/detect/signals/gate.mjs +91 -0
- package/src/detect/signals/injection.mjs +55 -0
- package/src/detect/signals/lines.mjs +42 -0
- package/src/detect/signals/masking.mjs +99 -0
- package/src/detect/signals/memory.mjs +357 -0
- package/src/detect/signals/packages.mjs +45 -0
- package/src/detect/signals/propagation.mjs +86 -0
- package/src/detect/signals/prose-context.mjs +82 -0
- package/src/detect/signals/scan.mjs +91 -0
- package/src/detect/signals/secrets.mjs +85 -0
- package/src/detect/signals/sensitive.mjs +9 -0
- package/src/detect/signals/severity.mjs +10 -0
- package/src/detect/signals/shell.mjs +96 -0
- package/src/detect/signals/staged-fetch.mjs +66 -0
- package/src/detect/signals/text-match.mjs +35 -0
- package/src/gate/batch.mjs +157 -0
- package/src/gate/environment.mjs +122 -0
- package/src/gate/repo-policy.mjs +65 -0
- package/src/gate/result.mjs +53 -0
- package/src/gate/sarif.mjs +33 -0
- package/src/gate/sast.mjs +64 -0
- package/src/gate/suppressions.mjs +0 -0
- package/src/guard/classify.mjs +50 -0
- package/src/guard/emit.mjs +51 -0
- package/src/guard/ignore.mjs +24 -0
- package/src/guard/ledger.mjs +112 -0
- package/src/guard/model-load.mjs +50 -0
- package/src/guard/normalize.mjs +77 -0
- package/src/guard/options.mjs +10 -0
- package/src/guard/prompt-guard.mjs +184 -0
- package/src/guard/report.mjs +35 -0
- package/src/guard/result-guard.mjs +140 -0
- package/src/guard/tool-guard.mjs +166 -0
- package/src/inventory/agent-artifacts.mjs +5 -0
- package/src/inventory/agent-posture.mjs +249 -0
- package/src/inventory/artifacts/classify.mjs +27 -0
- package/src/inventory/artifacts/discover.mjs +187 -0
- package/src/inventory/artifacts/file-read.mjs +42 -0
- package/src/inventory/artifacts/hooks.mjs +14 -0
- package/src/inventory/artifacts/limits.mjs +37 -0
- package/src/inventory/artifacts/marketplaces.mjs +45 -0
- package/src/inventory/artifacts/roots.mjs +20 -0
- package/src/inventory/artifacts/walk.mjs +36 -0
- package/src/inventory/discovery/ai-dependencies.mjs +161 -0
- package/src/inventory/discovery/ai-tools.mjs +23 -0
- package/src/inventory/discovery/all.mjs +40 -0
- package/src/inventory/discovery/coding-agents.mjs +77 -0
- package/src/inventory/discovery/fs-read.mjs +36 -0
- package/src/inventory/discovery/local-runtimes.mjs +53 -0
- package/src/inventory/discovery/mcp-clients.mjs +67 -0
- package/src/inventory/discovery/mcp-servers.mjs +78 -0
- package/src/inventory/discovery/model-keys.mjs +97 -0
- package/src/inventory/discovery/platform.mjs +16 -0
- package/src/inventory/discovery/rules-files.mjs +25 -0
- package/src/inventory/discovery/vector-stores.mjs +176 -0
- package/src/inventory/discovery/workspace.mjs +124 -0
- package/src/inventory/discovery.mjs +10 -0
- package/src/mcp/child-process.mjs +50 -0
- package/src/mcp/config-wrapping.mjs +75 -0
- package/src/mcp/connect-gate.mjs +45 -0
- package/src/mcp/hosts.mjs +16 -0
- package/src/mcp/jsonrpc.mjs +48 -0
- package/src/mcp/lookup.mjs +50 -0
- package/src/mcp/screening.mjs +103 -0
- package/src/mcp/server-tools.mjs +97 -0
- package/src/mcp/server.mjs +102 -0
- package/src/mcp/shim.mjs +205 -0
- package/src/models/lookup.mjs +79 -0
- package/src/models/references.mjs +103 -0
- package/src/rules/context.mjs +98 -0
- package/src/rules/generate.mjs +103 -0
- package/src/rules/sections.mjs +145 -0
- package/src/scaffold/agent-project.mjs +185 -0
- package/src/scaffold/artifact-templates.mjs +35 -0
- package/code-sast.mjs +0 -1063
- package/discovery.mjs +0 -977
- package/guard-ledger.mjs +0 -239
- package/guard-signals.mjs +0 -1268
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CLI_ENTRY_PATH } from '../core/package-root.mjs';
|
|
2
|
+
|
|
3
|
+
export const LLM_PROXY_BASE = process.env.SHOMRA_LLM_PROXY_BASE || 'http://127.0.0.1:4141/openai/v1';
|
|
4
|
+
|
|
5
|
+
export const SELF_PATH = CLI_ENTRY_PATH;
|
|
6
|
+
|
|
7
|
+
function quoteArg(argument) {
|
|
8
|
+
return /\s/.test(argument) ? `"${argument}"` : argument;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function hookCommand(args) {
|
|
12
|
+
return `${quoteArg(process.execPath)} ${quoteArg(SELF_PATH)} ${args}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function shomraHookRe(verb) {
|
|
16
|
+
return new RegExp(`shomra(\\.mjs"?)?\\s+${verb}`, 'i');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SHOMRA_ANY_HOOK_RE = /shomra(\.mjs"?)?\s+(tool-guard|result-guard|prompt-guard|plan-guard)/i;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { SHOMRA_ANY_HOOK_RE, shomraHookRe } from './hook-command.mjs';
|
|
5
|
+
|
|
6
|
+
function agentHookFiles(agent) {
|
|
7
|
+
const home = os.homedir();
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
switch (agent) {
|
|
10
|
+
case 'claude': return [path.join(home, '.claude', 'settings.json'), path.join(cwd, '.claude', 'settings.json')];
|
|
11
|
+
case 'codex': return [path.join(home, '.codex', 'hooks.json'), path.join(cwd, '.codex', 'hooks.json')];
|
|
12
|
+
case 'gemini': return [path.join(home, '.gemini', 'settings.json'), path.join(cwd, '.gemini', 'settings.json')];
|
|
13
|
+
case 'cursor': return [path.join(home, '.cursor', 'hooks.json'), path.join(cwd, '.cursor', 'hooks.json')];
|
|
14
|
+
case 'windsurf': return [path.join(home, '.codeium', 'windsurf', 'hooks.json'), path.join(cwd, '.windsurf', 'hooks.json')];
|
|
15
|
+
case 'copilot': return [path.join(home, '.copilot', 'hooks', 'shomra.json'), path.join(cwd, '.github', 'hooks', 'shomra.json')];
|
|
16
|
+
case 'cline': return [path.join(home, '.cline', 'hooks.json'), path.join(cwd, '.cline', 'hooks.json')];
|
|
17
|
+
case 'aider': return [path.join(home, '.aider.conf.yml'), path.join(cwd, '.aider.conf.yml')];
|
|
18
|
+
default: return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function agentHookInstalled(agent) {
|
|
23
|
+
return agentHookFiles(agent).filter((f) => {
|
|
24
|
+
try {
|
|
25
|
+
const text = fs.readFileSync(f, 'utf8');
|
|
26
|
+
if (agent === 'aider') return /shomra llm guard/i.test(text);
|
|
27
|
+
return SHOMRA_ANY_HOOK_RE.test(text);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function hasGroupedHook(list, verb) {
|
|
35
|
+
const re = shomraHookRe(verb);
|
|
36
|
+
return Array.isArray(list) && list.some((g) => Array.isArray(g.hooks) && g.hooks.some((h) => re.test(String(h.command || ''))));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function hasFlatHook(list) {
|
|
40
|
+
return Array.isArray(list) && list.some((h) => SHOMRA_ANY_HOOK_RE.test(String(h.command || '')));
|
|
41
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { readJsonFile } from '../core/json-file.mjs';
|
|
5
|
+
import { LLM_PROXY_BASE, hookCommand } from './hook-command.mjs';
|
|
6
|
+
import { hasFlatHook, hasGroupedHook } from './hook-files.mjs';
|
|
7
|
+
|
|
8
|
+
export const AGENT_LABELS = {
|
|
9
|
+
claude: 'Claude Code',
|
|
10
|
+
cursor: 'Cursor',
|
|
11
|
+
windsurf: 'Windsurf',
|
|
12
|
+
gemini: 'Gemini CLI',
|
|
13
|
+
codex: 'OpenAI Codex CLI',
|
|
14
|
+
copilot: 'GitHub Copilot CLI',
|
|
15
|
+
cline: 'Cline',
|
|
16
|
+
aider: 'Aider',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const AGENT_KEYS = Object.keys(AGENT_LABELS);
|
|
20
|
+
|
|
21
|
+
export const AGENT_INSTALLERS = {
|
|
22
|
+
claude(global) {
|
|
23
|
+
const dir = global ? path.join(os.homedir(), '.claude') : path.join(process.cwd(), '.claude');
|
|
24
|
+
const file = path.join(dir, 'settings.json');
|
|
25
|
+
const settings = readJsonFile(file);
|
|
26
|
+
settings.hooks = settings.hooks || {};
|
|
27
|
+
const pre = (settings.hooks.PreToolUse = settings.hooks.PreToolUse || []);
|
|
28
|
+
const post = (settings.hooks.PostToolUse = settings.hooks.PostToolUse || []);
|
|
29
|
+
let changed = false;
|
|
30
|
+
if (!hasGroupedHook(pre, 'tool-guard')) {
|
|
31
|
+
pre.push({ matcher: 'Bash|Write|Edit|MultiEdit|NotebookEdit|mcp__.*', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent claude') }] });
|
|
32
|
+
changed = true;
|
|
33
|
+
}
|
|
34
|
+
if (!hasGroupedHook(post, 'result-guard')) {
|
|
35
|
+
post.push({ matcher: 'WebFetch|WebSearch|Read|NotebookRead|mcp__.*', hooks: [{ type: 'command', command: hookCommand('result-guard --agent claude') }] });
|
|
36
|
+
changed = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const prompt = (settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit || []);
|
|
40
|
+
if (!hasGroupedHook(prompt, 'prompt-guard')) {
|
|
41
|
+
prompt.push({ hooks: [{ type: 'command', command: hookCommand('prompt-guard --agent claude') }] });
|
|
42
|
+
changed = true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!hasGroupedHook(pre, 'plan-guard')) {
|
|
46
|
+
pre.push({ matcher: 'ExitPlanMode', hooks: [{ type: 'command', command: hookCommand('plan-guard --agent claude') }] });
|
|
47
|
+
changed = true;
|
|
48
|
+
}
|
|
49
|
+
if (changed) {
|
|
50
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
51
|
+
fs.writeFileSync(file, JSON.stringify(settings, null, 2));
|
|
52
|
+
}
|
|
53
|
+
return { file, changed };
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
codex(global) {
|
|
57
|
+
const dir = global ? path.join(os.homedir(), '.codex') : path.join(process.cwd(), '.codex');
|
|
58
|
+
const file = path.join(dir, 'hooks.json');
|
|
59
|
+
const settings = readJsonFile(file);
|
|
60
|
+
const pre = (settings.PreToolUse = settings.PreToolUse || []);
|
|
61
|
+
const post = (settings.PostToolUse = settings.PostToolUse || []);
|
|
62
|
+
let changed = false;
|
|
63
|
+
if (!hasGroupedHook(pre, 'tool-guard')) {
|
|
64
|
+
pre.push({ matcher: 'Bash|Write|Edit|mcp__.*', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent codex') }] });
|
|
65
|
+
changed = true;
|
|
66
|
+
}
|
|
67
|
+
if (!hasGroupedHook(post, 'result-guard')) {
|
|
68
|
+
post.push({ matcher: 'WebFetch|WebSearch|Read|mcp__.*', hooks: [{ type: 'command', command: hookCommand('result-guard --agent codex') }] });
|
|
69
|
+
changed = true;
|
|
70
|
+
}
|
|
71
|
+
if (changed) {
|
|
72
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
73
|
+
fs.writeFileSync(file, JSON.stringify(settings, null, 2));
|
|
74
|
+
}
|
|
75
|
+
return { file, changed };
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
gemini(global) {
|
|
79
|
+
const dir = global ? path.join(os.homedir(), '.gemini') : path.join(process.cwd(), '.gemini');
|
|
80
|
+
const file = path.join(dir, 'settings.json');
|
|
81
|
+
const settings = readJsonFile(file);
|
|
82
|
+
settings.hooks = settings.hooks || {};
|
|
83
|
+
const before = (settings.hooks.BeforeTool = settings.hooks.BeforeTool || []);
|
|
84
|
+
const after = (settings.hooks.AfterTool = settings.hooks.AfterTool || []);
|
|
85
|
+
let changed = false;
|
|
86
|
+
if (!hasGroupedHook(before, 'tool-guard')) {
|
|
87
|
+
before.push({ matcher: '.*', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent gemini') }] });
|
|
88
|
+
changed = true;
|
|
89
|
+
}
|
|
90
|
+
if (!hasGroupedHook(after, 'result-guard')) {
|
|
91
|
+
after.push({ matcher: '.*', hooks: [{ type: 'command', command: hookCommand('result-guard --agent gemini') }] });
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
if (changed) {
|
|
95
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
96
|
+
fs.writeFileSync(file, JSON.stringify(settings, null, 2));
|
|
97
|
+
}
|
|
98
|
+
return { file, changed };
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
cursor(global) {
|
|
102
|
+
const dir = global ? path.join(os.homedir(), '.cursor') : path.join(process.cwd(), '.cursor');
|
|
103
|
+
const file = path.join(dir, 'hooks.json');
|
|
104
|
+
const cfg = readJsonFile(file);
|
|
105
|
+
if (cfg.version === undefined) cfg.version = 1;
|
|
106
|
+
cfg.hooks = cfg.hooks || {};
|
|
107
|
+
let changed = false;
|
|
108
|
+
const wire = (event, command) => {
|
|
109
|
+
const list = (cfg.hooks[event] = cfg.hooks[event] || []);
|
|
110
|
+
if (!hasFlatHook(list)) {
|
|
111
|
+
list.push({ command });
|
|
112
|
+
changed = true;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
wire('beforeShellExecution', hookCommand('tool-guard --agent cursor'));
|
|
116
|
+
wire('beforeMCPExecution', hookCommand('tool-guard --agent cursor'));
|
|
117
|
+
wire('afterFileEdit', hookCommand('result-guard --agent cursor'));
|
|
118
|
+
wire('afterMCPExecution', hookCommand('result-guard --agent cursor'));
|
|
119
|
+
|
|
120
|
+
wire('beforeSubmitPrompt', hookCommand('prompt-guard --agent cursor'));
|
|
121
|
+
if (changed) {
|
|
122
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
123
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
|
|
124
|
+
}
|
|
125
|
+
return { file, changed };
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
windsurf(global) {
|
|
129
|
+
const dir = global ? path.join(os.homedir(), '.codeium', 'windsurf') : path.join(process.cwd(), '.windsurf');
|
|
130
|
+
const file = path.join(dir, 'hooks.json');
|
|
131
|
+
const cfg = readJsonFile(file);
|
|
132
|
+
cfg.hooks = cfg.hooks || {};
|
|
133
|
+
let changed = false;
|
|
134
|
+
const wire = (event, command) => {
|
|
135
|
+
const list = (cfg.hooks[event] = cfg.hooks[event] || []);
|
|
136
|
+
if (!hasFlatHook(list)) {
|
|
137
|
+
list.push({ command });
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
wire('pre_run_command', hookCommand('tool-guard --agent windsurf'));
|
|
142
|
+
wire('pre_write_code', hookCommand('tool-guard --agent windsurf'));
|
|
143
|
+
wire('pre_mcp_tool_use', hookCommand('tool-guard --agent windsurf'));
|
|
144
|
+
wire('post_mcp_tool_use', hookCommand('result-guard --agent windsurf'));
|
|
145
|
+
if (changed) {
|
|
146
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
147
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
|
|
148
|
+
}
|
|
149
|
+
return { file, changed };
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
copilot(global) {
|
|
153
|
+
const dir = global ? path.join(os.homedir(), '.copilot', 'hooks') : path.join(process.cwd(), '.github', 'hooks');
|
|
154
|
+
const file = path.join(dir, 'shomra.json');
|
|
155
|
+
if (fs.existsSync(file)) return { file, changed: false };
|
|
156
|
+
const cfg = {
|
|
157
|
+
preToolUse: [{ command: hookCommand('tool-guard --agent copilot') }],
|
|
158
|
+
postToolUse: [{ command: hookCommand('result-guard --agent copilot') }],
|
|
159
|
+
};
|
|
160
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
161
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
|
|
162
|
+
return { file, changed: true };
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
cline(global) {
|
|
166
|
+
const dir = global ? path.join(os.homedir(), '.cline') : path.join(process.cwd(), '.cline');
|
|
167
|
+
const file = path.join(dir, 'hooks.json');
|
|
168
|
+
const settings = readJsonFile(file);
|
|
169
|
+
settings.hooks = settings.hooks || {};
|
|
170
|
+
const pre = (settings.hooks.PreToolUse = settings.hooks.PreToolUse || []);
|
|
171
|
+
const post = (settings.hooks.PostToolUse = settings.hooks.PostToolUse || []);
|
|
172
|
+
let changed = false;
|
|
173
|
+
if (!hasGroupedHook(pre, 'tool-guard')) {
|
|
174
|
+
pre.push({ matcher: 'execute_command|write_to_file|replace_in_file|new_rule|use_mcp_tool', hooks: [{ type: 'command', command: hookCommand('tool-guard --agent cline') }] });
|
|
175
|
+
changed = true;
|
|
176
|
+
}
|
|
177
|
+
if (!hasGroupedHook(post, 'result-guard')) {
|
|
178
|
+
post.push({ matcher: 'read_file|web_fetch|use_mcp_tool', hooks: [{ type: 'command', command: hookCommand('result-guard --agent cline') }] });
|
|
179
|
+
changed = true;
|
|
180
|
+
}
|
|
181
|
+
if (changed) {
|
|
182
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
183
|
+
fs.writeFileSync(file, JSON.stringify(settings, null, 2));
|
|
184
|
+
}
|
|
185
|
+
return { file, changed };
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
aider(global) {
|
|
189
|
+
const file = global ? path.join(os.homedir(), '.aider.conf.yml') : path.join(process.cwd(), '.aider.conf.yml');
|
|
190
|
+
let text = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
191
|
+
if (/#\s*shomra llm guard/i.test(text) || text.includes(LLM_PROXY_BASE)) {
|
|
192
|
+
return { file, changed: false };
|
|
193
|
+
}
|
|
194
|
+
const block =
|
|
195
|
+
`\n# --- shomra llm guard ---\n` +
|
|
196
|
+
`# Routes Aider's model traffic through the Shomra LLM Guard proxy so every\n` +
|
|
197
|
+
`# prompt/response is policy-screened. Requires: shomra llm-proxy (running).\n` +
|
|
198
|
+
`openai-api-base: ${LLM_PROXY_BASE}\n` +
|
|
199
|
+
`# --- end shomra ---\n`;
|
|
200
|
+
fs.writeFileSync(file, (text.endsWith('\n') || !text ? text : text + '\n') + block);
|
|
201
|
+
return { file, changed: true };
|
|
202
|
+
},
|
|
203
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const ARTIFACT_MATCHERS = [
|
|
5
|
+
{ kind: 'mcp', re: /(^|\/)\.?mcp\.json$/i },
|
|
6
|
+
{ kind: 'mcp', re: /(^|\/)\.(vscode|cursor)\/mcp\.json$/i },
|
|
7
|
+
{ kind: 'skill', re: /(^|\/)SKILL\.md$/i },
|
|
8
|
+
{ kind: 'command', re: /(^|\/)\.claude\/commands\/[^/]+\.md$/i },
|
|
9
|
+
{ kind: 'subagent', re: /(^|\/)\.claude\/agents\/[^/]+\.md$/i },
|
|
10
|
+
{ kind: 'hook', re: /(^|\/)\.claude\/settings(\.local)?\.json$/i },
|
|
11
|
+
|
|
12
|
+
{ kind: 'hook', re: /(^|\/)\.cursor\/hooks\.json$/i },
|
|
13
|
+
{ kind: 'hook', re: /(^|\/)(\.windsurf|\.codeium\/windsurf)\/hooks\.json$/i },
|
|
14
|
+
{ kind: 'hook', re: /(^|\/)\.gemini\/settings\.json$/i },
|
|
15
|
+
{ kind: 'hook', re: /(^|\/)\.cline\/hooks\.json$/i },
|
|
16
|
+
{ kind: 'hook', re: /(^|\/)\.codex\/hooks\.json$/i },
|
|
17
|
+
{ kind: 'hook', re: /(^|\/)(\.github|\.copilot)\/hooks\/[^/]+\.json$/i },
|
|
18
|
+
{ kind: 'hook', re: /(^|\/)\.aider\.conf\.yml$/i },
|
|
19
|
+
{ kind: 'agent-card', re: /(^|\/)\.well-known\/agent(-card)?\.json$/i },
|
|
20
|
+
{ kind: 'agent-card', re: /(^|\/)agent[-_]card\.json$/i },
|
|
21
|
+
{ kind: 'rules', re: /(^|\/)(CLAUDE|AGENTS|GEMINI|CONVENTIONS)\.md$/i },
|
|
22
|
+
{ kind: 'rules', re: /(^|\/)\.(cursorrules|windsurfrules|clinerules|aiderrules|continuerules|goosehints)$/i },
|
|
23
|
+
{ kind: 'rules', re: /(^|\/)\.github\/copilot-instructions\.md$/i },
|
|
24
|
+
{ kind: 'rules', re: /(^|\/)\.cursor\/rules\/[^/]+\.mdc$/i },
|
|
25
|
+
|
|
26
|
+
{ kind: 'env', re: /(^|\/)\.env(\.(local|development|dev|production|prod|staging|stage|test))?$/i },
|
|
27
|
+
{ kind: 'memory', re: /(^|\/)MEMORY\.md$/i },
|
|
28
|
+
{ kind: 'memory', re: /(^|\/)(mem0|letta_memory|memgpt_memory)\.json$/i },
|
|
29
|
+
{ kind: 'memory', re: /(^|\/)(\.mem0|\.letta|\.memgpt|memory)\/[^/]+\.(md|json)$/i },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'out', 'vendor', '.venv', '__pycache__']);
|
|
33
|
+
|
|
34
|
+
export const MAX_ARTIFACT_BYTES = 1_000_000;
|
|
35
|
+
|
|
36
|
+
export function walkArtifacts(root) {
|
|
37
|
+
const found = [];
|
|
38
|
+
const stack = [root];
|
|
39
|
+
while (stack.length) {
|
|
40
|
+
const dir = stack.pop();
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
44
|
+
} catch {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
for (const ent of entries) {
|
|
48
|
+
const full = path.join(dir, ent.name);
|
|
49
|
+
if (ent.isDirectory()) {
|
|
50
|
+
if (!SKIP_DIRS.has(ent.name)) stack.push(full);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const rel = path.relative(root, full).split(path.sep).join('/');
|
|
54
|
+
const match = ARTIFACT_MATCHERS.find((m) => m.re.test(rel));
|
|
55
|
+
if (match) found.push({ full, rel, kind: match.kind });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { bold, cyan, dim, gray, yellow } from '../core/terminal.mjs';
|
|
3
|
+
import { rollupArtifacts } from '../inventory/agent-artifacts.mjs';
|
|
4
|
+
|
|
5
|
+
export function printAssets(assets) {
|
|
6
|
+
const byType = {};
|
|
7
|
+
for (const a of assets) byType[a.type] = (byType[a.type] ?? 0) + 1;
|
|
8
|
+
console.log(bold('\n Discovered AI assets'));
|
|
9
|
+
console.log(
|
|
10
|
+
' ' +
|
|
11
|
+
Object.entries(byType)
|
|
12
|
+
.map(([t, n]) => `${cyan(n)} ${dim(t.replace('_', ' ').toLowerCase())}`)
|
|
13
|
+
.join(dim(' · ')),
|
|
14
|
+
);
|
|
15
|
+
for (const a of assets) {
|
|
16
|
+
console.log(` ${gray('•')} ${bold(a.name)} ${dim(a.type)} ${a.vendor ? gray('(' + a.vendor + ')') : ''}`);
|
|
17
|
+
if (a.identifier && a.identifier !== a.name) console.log(` ${dim(a.identifier)}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function printArtifacts(artifacts, capped = [], available = []) {
|
|
22
|
+
if (!artifacts.length && !capped.length && !available.length) return;
|
|
23
|
+
const by = rollupArtifacts(artifacts);
|
|
24
|
+
console.log(bold('\n Installed agent artifacts'));
|
|
25
|
+
console.log(
|
|
26
|
+
' ' +
|
|
27
|
+
(Object.keys(by).length
|
|
28
|
+
? Object.entries(by)
|
|
29
|
+
.map(([k, n]) => `${cyan(n)} ${dim(k + (n === 1 ? '' : 's'))}`)
|
|
30
|
+
.join(dim(' · '))
|
|
31
|
+
: dim('none found')),
|
|
32
|
+
);
|
|
33
|
+
for (const a of artifacts) {
|
|
34
|
+
const scope = a.scope === 'user' ? yellow('user') : dim('project');
|
|
35
|
+
console.log(` ${gray('•')} ${bold(a.name)} ${dim(a.kind)} ${scope} ${a.vendor ? gray('(' + a.vendor + ')') : ''}`);
|
|
36
|
+
console.log(` ${dim(a.path)}`);
|
|
37
|
+
if (a.files?.length) console.log(` ${dim(a.files.length + ' bundled file(s)')}`);
|
|
38
|
+
}
|
|
39
|
+
if (available.length) {
|
|
40
|
+
const total = available.reduce((n, a) => n + a.count, 0);
|
|
41
|
+
|
|
42
|
+
console.log(
|
|
43
|
+
dim(`\n ${total} more available in ${available.length} checked-out catalogue(s), none enabled:`),
|
|
44
|
+
);
|
|
45
|
+
for (const a of available) console.log(` ${dim(a.marketplace)} ${gray(a.count)}`);
|
|
46
|
+
}
|
|
47
|
+
if (capped.length) {
|
|
48
|
+
console.log(dim(`\n Sweep capped (${capped.length}) - this list is a floor, not a total.`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const BOOLEAN_FLAGS = new Set([
|
|
2
|
+
'strict', 'json', 'sarif', 'fix', 'staged', 'changed', 'all', 'history', 'force',
|
|
3
|
+
'apply', 'dry-run', 'global', 'local', 'trailer', 'evolve', 'report', 'init',
|
|
4
|
+
'no-suppress', 'no-baseline', 'no-policy', 'no-index', 'adaptive',
|
|
5
|
+
'fail-on-regression', 'fail-on-blocked', 'write', 'yes', 'stdin', 'quiet', 'help',
|
|
6
|
+
'check', 'checklist', 'pre-receive', 'uninstall', 'save', 'list',
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
const VALUE_FLAGS = new Set([
|
|
10
|
+
'key', 'url', 'path', 'kind', 'name', 'project', 'agent', 'agent-id', 'min',
|
|
11
|
+
'scenarios', 'objectives', 'turns', 'target', 'run', 'port', 'config', 'env',
|
|
12
|
+
'command', 'base', 'repo', 'pr', 'token', 'sha', 'session', 'since', 'depth',
|
|
13
|
+
'scope', 'writer', 'type', 'slug', 'framework', 'chunk-size', 'manifest',
|
|
14
|
+
|
|
15
|
+
'fail-on',
|
|
16
|
+
|
|
17
|
+
'subject', 'title', 'note', 'actor',
|
|
18
|
+
|
|
19
|
+
'input',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export const KNOWN_FLAGS = new Set([...BOOLEAN_FLAGS, ...VALUE_FLAGS]);
|
|
23
|
+
|
|
24
|
+
const REPEATABLE_FLAGS = new Set(['input']);
|
|
25
|
+
|
|
26
|
+
function setFlag(flags, name, value) {
|
|
27
|
+
if (!REPEATABLE_FLAGS.has(name)) {
|
|
28
|
+
flags[name] = value;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const prev = flags[name];
|
|
32
|
+
flags[name] = prev === undefined ? value : [].concat(prev, value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseFlags(argv) {
|
|
36
|
+
const flags = {};
|
|
37
|
+
const positional = [];
|
|
38
|
+
const unknown = [];
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
const a = argv[i];
|
|
41
|
+
if (a.startsWith('--')) {
|
|
42
|
+
const body = a.slice(2);
|
|
43
|
+
|
|
44
|
+
const eq = body.indexOf('=');
|
|
45
|
+
if (eq !== -1) {
|
|
46
|
+
const name = body.slice(0, eq);
|
|
47
|
+
if (!KNOWN_FLAGS.has(name)) unknown.push(name);
|
|
48
|
+
setFlag(flags, name, body.slice(eq + 1));
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!KNOWN_FLAGS.has(body)) {
|
|
52
|
+
unknown.push(body);
|
|
53
|
+
flags[body] = true;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (BOOLEAN_FLAGS.has(body)) {
|
|
57
|
+
flags[body] = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const next = argv[i + 1];
|
|
61
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
62
|
+
setFlag(flags, body, next);
|
|
63
|
+
i++;
|
|
64
|
+
} else flags[body] = true;
|
|
65
|
+
} else positional.push(a);
|
|
66
|
+
}
|
|
67
|
+
return { flags, positional, unknown };
|
|
68
|
+
}
|