agent-security-scanner-mcp 3.20.0 → 4.0.0

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.
Files changed (126) hide show
  1. package/README.md +144 -43
  2. package/code-review-agent/.env.example +8 -0
  3. package/code-review-agent/README.md +142 -0
  4. package/code-review-agent/TODO.md +149 -0
  5. package/code-review-agent/bin/cr-agent.ts +313 -0
  6. package/code-review-agent/dist/bin/cr-agent.d.ts +3 -0
  7. package/code-review-agent/dist/bin/cr-agent.d.ts.map +1 -0
  8. package/code-review-agent/dist/bin/cr-agent.js +299 -0
  9. package/code-review-agent/dist/bin/cr-agent.js.map +1 -0
  10. package/code-review-agent/dist/src/analyzer/engine.d.ts +16 -0
  11. package/code-review-agent/dist/src/analyzer/engine.d.ts.map +1 -0
  12. package/code-review-agent/dist/src/analyzer/engine.js +298 -0
  13. package/code-review-agent/dist/src/analyzer/engine.js.map +1 -0
  14. package/code-review-agent/dist/src/analyzer/intent.d.ts +10 -0
  15. package/code-review-agent/dist/src/analyzer/intent.d.ts.map +1 -0
  16. package/code-review-agent/dist/src/analyzer/intent.js +40 -0
  17. package/code-review-agent/dist/src/analyzer/intent.js.map +1 -0
  18. package/code-review-agent/dist/src/analyzer/semantic.d.ts +19 -0
  19. package/code-review-agent/dist/src/analyzer/semantic.d.ts.map +1 -0
  20. package/code-review-agent/dist/src/analyzer/semantic.js +150 -0
  21. package/code-review-agent/dist/src/analyzer/semantic.js.map +1 -0
  22. package/code-review-agent/dist/src/context/assembler.d.ts +16 -0
  23. package/code-review-agent/dist/src/context/assembler.d.ts.map +1 -0
  24. package/code-review-agent/dist/src/context/assembler.js +135 -0
  25. package/code-review-agent/dist/src/context/assembler.js.map +1 -0
  26. package/code-review-agent/dist/src/context/file.d.ts +6 -0
  27. package/code-review-agent/dist/src/context/file.d.ts.map +1 -0
  28. package/code-review-agent/dist/src/context/file.js +139 -0
  29. package/code-review-agent/dist/src/context/file.js.map +1 -0
  30. package/code-review-agent/dist/src/context/project.d.ts +4 -0
  31. package/code-review-agent/dist/src/context/project.d.ts.map +1 -0
  32. package/code-review-agent/dist/src/context/project.js +252 -0
  33. package/code-review-agent/dist/src/context/project.js.map +1 -0
  34. package/code-review-agent/dist/src/graph/dependency.d.ts +11 -0
  35. package/code-review-agent/dist/src/graph/dependency.d.ts.map +1 -0
  36. package/code-review-agent/dist/src/graph/dependency.js +102 -0
  37. package/code-review-agent/dist/src/graph/dependency.js.map +1 -0
  38. package/code-review-agent/dist/src/graph/resolver.d.ts +9 -0
  39. package/code-review-agent/dist/src/graph/resolver.d.ts.map +1 -0
  40. package/code-review-agent/dist/src/graph/resolver.js +124 -0
  41. package/code-review-agent/dist/src/graph/resolver.js.map +1 -0
  42. package/code-review-agent/dist/src/index.d.ts +21 -0
  43. package/code-review-agent/dist/src/index.d.ts.map +1 -0
  44. package/code-review-agent/dist/src/index.js +21 -0
  45. package/code-review-agent/dist/src/index.js.map +1 -0
  46. package/code-review-agent/dist/src/llm/anthropic.d.ts +13 -0
  47. package/code-review-agent/dist/src/llm/anthropic.d.ts.map +1 -0
  48. package/code-review-agent/dist/src/llm/anthropic.js +83 -0
  49. package/code-review-agent/dist/src/llm/anthropic.js.map +1 -0
  50. package/code-review-agent/dist/src/llm/claude-cli.d.ts +13 -0
  51. package/code-review-agent/dist/src/llm/claude-cli.d.ts.map +1 -0
  52. package/code-review-agent/dist/src/llm/claude-cli.js +142 -0
  53. package/code-review-agent/dist/src/llm/claude-cli.js.map +1 -0
  54. package/code-review-agent/dist/src/llm/openai.d.ts +13 -0
  55. package/code-review-agent/dist/src/llm/openai.d.ts.map +1 -0
  56. package/code-review-agent/dist/src/llm/openai.js +78 -0
  57. package/code-review-agent/dist/src/llm/openai.js.map +1 -0
  58. package/code-review-agent/dist/src/llm/provider.d.ts +18 -0
  59. package/code-review-agent/dist/src/llm/provider.d.ts.map +1 -0
  60. package/code-review-agent/dist/src/llm/provider.js +11 -0
  61. package/code-review-agent/dist/src/llm/provider.js.map +1 -0
  62. package/code-review-agent/dist/src/llm/router.d.ts +14 -0
  63. package/code-review-agent/dist/src/llm/router.d.ts.map +1 -0
  64. package/code-review-agent/dist/src/llm/router.js +67 -0
  65. package/code-review-agent/dist/src/llm/router.js.map +1 -0
  66. package/code-review-agent/dist/src/llm/schemas.d.ts +18 -0
  67. package/code-review-agent/dist/src/llm/schemas.d.ts.map +1 -0
  68. package/code-review-agent/dist/src/llm/schemas.js +91 -0
  69. package/code-review-agent/dist/src/llm/schemas.js.map +1 -0
  70. package/code-review-agent/dist/src/types/analysis.d.ts +56 -0
  71. package/code-review-agent/dist/src/types/analysis.d.ts.map +1 -0
  72. package/code-review-agent/dist/src/types/analysis.js +2 -0
  73. package/code-review-agent/dist/src/types/analysis.js.map +1 -0
  74. package/code-review-agent/dist/src/types/config.d.ts +24 -0
  75. package/code-review-agent/dist/src/types/config.d.ts.map +1 -0
  76. package/code-review-agent/dist/src/types/config.js +42 -0
  77. package/code-review-agent/dist/src/types/config.js.map +1 -0
  78. package/code-review-agent/dist/src/types/findings.d.ts +236 -0
  79. package/code-review-agent/dist/src/types/findings.d.ts.map +1 -0
  80. package/code-review-agent/dist/src/types/findings.js +64 -0
  81. package/code-review-agent/dist/src/types/findings.js.map +1 -0
  82. package/code-review-agent/package.json +36 -0
  83. package/code-review-agent/src/analyzer/engine.ts +374 -0
  84. package/code-review-agent/src/analyzer/intent.ts +49 -0
  85. package/code-review-agent/src/analyzer/semantic.ts +222 -0
  86. package/code-review-agent/src/context/assembler.ts +165 -0
  87. package/code-review-agent/src/context/file.ts +145 -0
  88. package/code-review-agent/src/context/project.ts +253 -0
  89. package/code-review-agent/src/graph/dependency.ts +116 -0
  90. package/code-review-agent/src/graph/resolver.ts +138 -0
  91. package/code-review-agent/src/index.ts +58 -0
  92. package/code-review-agent/src/llm/anthropic.ts +106 -0
  93. package/code-review-agent/src/llm/claude-cli.ts +188 -0
  94. package/code-review-agent/src/llm/openai.ts +95 -0
  95. package/code-review-agent/src/llm/provider.ts +33 -0
  96. package/code-review-agent/src/llm/router.ts +86 -0
  97. package/code-review-agent/src/llm/schemas.ts +125 -0
  98. package/code-review-agent/src/types/analysis.ts +62 -0
  99. package/code-review-agent/src/types/config.ts +72 -0
  100. package/code-review-agent/src/types/findings.ts +81 -0
  101. package/code-review-agent/tests/analyzer/engine.test.ts +194 -0
  102. package/code-review-agent/tests/analyzer/intent.test.ts +76 -0
  103. package/code-review-agent/tests/analyzer/semantic.test.ts +131 -0
  104. package/code-review-agent/tests/context/file.test.ts +21 -0
  105. package/code-review-agent/tests/context/project.test.ts +20 -0
  106. package/code-review-agent/tests/fixtures/safe-build-tool/README.md +19 -0
  107. package/code-review-agent/tests/fixtures/safe-build-tool/builder.js +52 -0
  108. package/code-review-agent/tests/fixtures/safe-file-manager/README.md +16 -0
  109. package/code-review-agent/tests/fixtures/safe-file-manager/organizer.py +70 -0
  110. package/code-review-agent/tests/fixtures/vuln-api-server/README.md +17 -0
  111. package/code-review-agent/tests/fixtures/vuln-api-server/server.js +52 -0
  112. package/code-review-agent/tests/fixtures/vuln-ecommerce/README.md +18 -0
  113. package/code-review-agent/tests/fixtures/vuln-ecommerce/checkout.js +63 -0
  114. package/code-review-agent/tests/graph/dependency.test.ts +136 -0
  115. package/code-review-agent/tests/helpers/mock-provider.ts +48 -0
  116. package/code-review-agent/tests/llm/claude-cli.test.ts +251 -0
  117. package/code-review-agent/tests/llm/router.test.ts +77 -0
  118. package/code-review-agent/tests/llm/schemas.test.ts +142 -0
  119. package/code-review-agent/tsconfig.json +20 -0
  120. package/code-review-agent/vitest.config.ts +11 -0
  121. package/index.js +18 -18
  122. package/openclaw.plugin.json +2 -2
  123. package/package.json +13 -3
  124. package/server.json +3 -3
  125. package/src/cli/init-hooks.js +3 -3
  126. package/src/cli/init.js +1 -1
@@ -0,0 +1,251 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { z } from 'zod';
3
+ import { SchemaValidationError } from '../../src/llm/provider.js';
4
+
5
+ // Mock child_process.spawn before importing the provider
6
+ const mockStdin = { write: vi.fn(), end: vi.fn(), on: vi.fn() };
7
+ const mockStdout = { on: vi.fn() };
8
+ const mockStderr = { on: vi.fn() };
9
+ const mockChild = {
10
+ stdin: mockStdin,
11
+ stdout: mockStdout,
12
+ stderr: mockStderr,
13
+ on: vi.fn(),
14
+ };
15
+
16
+ vi.mock('node:child_process', () => ({
17
+ spawn: vi.fn(() => mockChild),
18
+ }));
19
+
20
+ import { ClaudeCliProvider } from '../../src/llm/claude-cli.js';
21
+ import { spawn } from 'node:child_process';
22
+
23
+ function simulateClaudeResponse(result: string, isError = false) {
24
+ const jsonOutput = JSON.stringify({
25
+ type: 'result',
26
+ subtype: 'success',
27
+ is_error: isError,
28
+ result,
29
+ });
30
+
31
+ // When stdout.on('data', cb) is called, capture the callback
32
+ mockStdout.on.mockImplementation((event: string, cb: (data: Buffer) => void) => {
33
+ if (event === 'data') {
34
+ cb(Buffer.from(jsonOutput));
35
+ }
36
+ });
37
+
38
+ mockStderr.on.mockImplementation(() => {});
39
+
40
+ // When child.on('close', cb) is called, trigger immediately
41
+ mockChild.on.mockImplementation((event: string, cb: (code: number) => void) => {
42
+ if (event === 'close') {
43
+ cb(0);
44
+ }
45
+ });
46
+ }
47
+
48
+ function simulateClaudeError(code: number, stderrMsg = '') {
49
+ mockStdout.on.mockImplementation(() => {});
50
+ mockStderr.on.mockImplementation((event: string, cb: (data: Buffer) => void) => {
51
+ if (event === 'data' && stderrMsg) {
52
+ cb(Buffer.from(stderrMsg));
53
+ }
54
+ });
55
+ mockChild.on.mockImplementation((event: string, cb: (code: number) => void) => {
56
+ if (event === 'close') {
57
+ cb(code);
58
+ }
59
+ });
60
+ }
61
+
62
+ function simulateSpawnError(message: string) {
63
+ mockStdout.on.mockImplementation(() => {});
64
+ mockStderr.on.mockImplementation(() => {});
65
+ mockChild.on.mockImplementation((event: string, cb: unknown) => {
66
+ if (event === 'error') {
67
+ (cb as (err: Error) => void)(new Error(message));
68
+ }
69
+ });
70
+ }
71
+
72
+ describe('ClaudeCliProvider', () => {
73
+ beforeEach(() => {
74
+ vi.clearAllMocks();
75
+ });
76
+
77
+ describe('constructor', () => {
78
+ it('defaults to sonnet model', () => {
79
+ const provider = new ClaudeCliProvider();
80
+ expect(provider.modelId).toBe('sonnet');
81
+ expect(provider.providerName).toBe('claude-cli');
82
+ });
83
+
84
+ it('accepts custom model', () => {
85
+ const provider = new ClaudeCliProvider('haiku');
86
+ expect(provider.modelId).toBe('haiku');
87
+ });
88
+ });
89
+
90
+ describe('chat', () => {
91
+ it('sends prompt via stdin and returns result', async () => {
92
+ simulateClaudeResponse('Hello world');
93
+ const provider = new ClaudeCliProvider();
94
+ const result = await provider.chat([
95
+ { role: 'user', content: 'Say hello' },
96
+ ]);
97
+
98
+ expect(result).toBe('Hello world');
99
+ expect(spawn).toHaveBeenCalledWith('claude', [
100
+ '-p', '-',
101
+ '--output-format', 'json',
102
+ '--model', 'sonnet',
103
+ '--no-session-persistence',
104
+ ], expect.any(Object));
105
+ expect(mockStdin.write).toHaveBeenCalled();
106
+ expect(mockStdin.end).toHaveBeenCalled();
107
+ });
108
+
109
+ it('formats system messages correctly', async () => {
110
+ simulateClaudeResponse('ok');
111
+ const provider = new ClaudeCliProvider();
112
+ await provider.chat([
113
+ { role: 'system', content: 'You are a helper' },
114
+ { role: 'user', content: 'Help me' },
115
+ ]);
116
+
117
+ const writtenPrompt = mockStdin.write.mock.calls[0][0] as string;
118
+ expect(writtenPrompt).toContain('[System Instructions]');
119
+ expect(writtenPrompt).toContain('You are a helper');
120
+ expect(writtenPrompt).toContain('Help me');
121
+ });
122
+
123
+ it('formats assistant messages correctly', async () => {
124
+ simulateClaudeResponse('final answer');
125
+ const provider = new ClaudeCliProvider();
126
+ await provider.chat([
127
+ { role: 'user', content: 'Question 1' },
128
+ { role: 'assistant', content: 'Answer 1' },
129
+ { role: 'user', content: 'Question 2' },
130
+ ]);
131
+
132
+ const writtenPrompt = mockStdin.write.mock.calls[0][0] as string;
133
+ expect(writtenPrompt).toContain('[Previous response]');
134
+ expect(writtenPrompt).toContain('Answer 1');
135
+ });
136
+
137
+ it('handles large prompts via stdin', async () => {
138
+ const largeContent = 'x'.repeat(100_000);
139
+ simulateClaudeResponse('analyzed');
140
+ const provider = new ClaudeCliProvider();
141
+ const result = await provider.chat([
142
+ { role: 'user', content: largeContent },
143
+ ]);
144
+
145
+ expect(result).toBe('analyzed');
146
+ const writtenPrompt = mockStdin.write.mock.calls[0][0] as string;
147
+ expect(writtenPrompt.length).toBeGreaterThanOrEqual(100_000);
148
+ });
149
+
150
+ it('rejects on non-zero exit code', async () => {
151
+ simulateClaudeError(1, 'something went wrong');
152
+ const provider = new ClaudeCliProvider();
153
+
154
+ await expect(provider.chat([
155
+ { role: 'user', content: 'test' },
156
+ ])).rejects.toThrow('claude CLI exited with code 1');
157
+ });
158
+
159
+ it('rejects on spawn error', async () => {
160
+ simulateSpawnError('command not found');
161
+ const provider = new ClaudeCliProvider();
162
+
163
+ await expect(provider.chat([
164
+ { role: 'user', content: 'test' },
165
+ ])).rejects.toThrow('claude CLI failed to start');
166
+ });
167
+
168
+ it('rejects when CLI returns is_error', async () => {
169
+ simulateClaudeResponse('API rate limit exceeded', true);
170
+ const provider = new ClaudeCliProvider();
171
+
172
+ await expect(provider.chat([
173
+ { role: 'user', content: 'test' },
174
+ ])).rejects.toThrow('claude CLI error');
175
+ });
176
+ });
177
+
178
+ describe('chatStructured', () => {
179
+ const TestSchema = z.object({
180
+ name: z.string(),
181
+ value: z.number(),
182
+ });
183
+
184
+ it('parses valid JSON response', async () => {
185
+ simulateClaudeResponse('{"name": "test", "value": 42}');
186
+ const provider = new ClaudeCliProvider();
187
+ const result = await provider.chatStructured(
188
+ [{ role: 'user', content: 'Give me data' }],
189
+ TestSchema,
190
+ 'test_schema',
191
+ );
192
+
193
+ expect(result).toEqual({ name: 'test', value: 42 });
194
+ });
195
+
196
+ it('extracts JSON from markdown code blocks', async () => {
197
+ simulateClaudeResponse('```json\n{"name": "test", "value": 42}\n```');
198
+ const provider = new ClaudeCliProvider();
199
+ const result = await provider.chatStructured(
200
+ [{ role: 'user', content: 'Give me data' }],
201
+ TestSchema,
202
+ 'test_schema',
203
+ );
204
+
205
+ expect(result).toEqual({ name: 'test', value: 42 });
206
+ });
207
+
208
+ it('extracts JSON from surrounding text', async () => {
209
+ simulateClaudeResponse('Here is the result: {"name": "test", "value": 42} done.');
210
+ const provider = new ClaudeCliProvider();
211
+ const result = await provider.chatStructured(
212
+ [{ role: 'user', content: 'Give me data' }],
213
+ TestSchema,
214
+ 'test_schema',
215
+ );
216
+
217
+ expect(result).toEqual({ name: 'test', value: 42 });
218
+ });
219
+
220
+ it('throws SchemaValidationError after max retries with invalid JSON', async () => {
221
+ simulateClaudeResponse('not json at all');
222
+ const provider = new ClaudeCliProvider();
223
+
224
+ await expect(provider.chatStructured(
225
+ [{ role: 'user', content: 'Give me data' }],
226
+ TestSchema,
227
+ 'test_schema',
228
+ )).rejects.toThrow(SchemaValidationError);
229
+ });
230
+
231
+ it('throws SchemaValidationError after max retries with wrong schema', async () => {
232
+ simulateClaudeResponse('{"wrong": "shape"}');
233
+ const provider = new ClaudeCliProvider();
234
+
235
+ await expect(provider.chatStructured(
236
+ [{ role: 'user', content: 'Give me data' }],
237
+ TestSchema,
238
+ 'test_schema',
239
+ )).rejects.toThrow(SchemaValidationError);
240
+ });
241
+ });
242
+
243
+ describe('countTokens', () => {
244
+ it('approximates token count from text length', () => {
245
+ const provider = new ClaudeCliProvider();
246
+ expect(provider.countTokens('hello world')).toBe(3); // ceil(11/4)
247
+ expect(provider.countTokens('')).toBe(0);
248
+ expect(provider.countTokens('a'.repeat(100))).toBe(25);
249
+ });
250
+ });
251
+ });
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { ModelRouter } from '../../src/llm/router.js';
3
+ import type { AnalysisOptions } from '../../src/types/config.js';
4
+
5
+ function makeOptions(overrides: Partial<AnalysisOptions> = {}): AnalysisOptions {
6
+ return {
7
+ provider: 'anthropic',
8
+ confidenceThreshold: 0.7,
9
+ format: 'text',
10
+ verbose: false,
11
+ projectRoot: process.cwd(),
12
+ exclude: [],
13
+ concurrencyLimit: 5,
14
+ maxFileSize: 512 * 1024,
15
+ ...overrides,
16
+ };
17
+ }
18
+
19
+ describe('ModelRouter', () => {
20
+ beforeEach(() => {
21
+ vi.stubEnv('ANTHROPIC_API_KEY', 'test-key');
22
+ vi.stubEnv('OPENAI_API_KEY', 'test-key');
23
+ });
24
+
25
+ it('creates anthropic triage provider with haiku model', () => {
26
+ const router = new ModelRouter(makeOptions({ provider: 'anthropic' }));
27
+ const provider = router.getTriageProvider();
28
+ expect(provider.providerName).toBe('anthropic');
29
+ expect(provider.modelId).toBe('claude-haiku-4-5-20251001');
30
+ });
31
+
32
+ it('creates anthropic analysis provider with sonnet model', () => {
33
+ const router = new ModelRouter(makeOptions({ provider: 'anthropic' }));
34
+ const provider = router.getAnalysisProvider();
35
+ expect(provider.providerName).toBe('anthropic');
36
+ expect(provider.modelId).toBe('claude-sonnet-4-20250514');
37
+ });
38
+
39
+ it('creates openai triage provider with gpt-4o-mini', () => {
40
+ const router = new ModelRouter(makeOptions({ provider: 'openai' }));
41
+ const provider = router.getTriageProvider();
42
+ expect(provider.providerName).toBe('openai');
43
+ expect(provider.modelId).toBe('gpt-4o-mini');
44
+ });
45
+
46
+ it('creates openai analysis provider with gpt-4o', () => {
47
+ const router = new ModelRouter(makeOptions({ provider: 'openai' }));
48
+ const provider = router.getAnalysisProvider();
49
+ expect(provider.providerName).toBe('openai');
50
+ expect(provider.modelId).toBe('gpt-4o');
51
+ });
52
+
53
+ it('uses custom model override', () => {
54
+ const router = new ModelRouter(makeOptions({ provider: 'anthropic', model: 'claude-opus-4-20250514' }));
55
+ const provider = router.getAnalysisProvider();
56
+ expect(provider.modelId).toBe('claude-opus-4-20250514');
57
+ });
58
+
59
+ it('caches providers', () => {
60
+ const router = new ModelRouter(makeOptions());
61
+ const a = router.getTriageProvider();
62
+ const b = router.getTriageProvider();
63
+ expect(a).toBe(b);
64
+ });
65
+
66
+ it('estimates cost', () => {
67
+ const router = new ModelRouter(makeOptions());
68
+ const cost = router.estimateCost(1_000_000);
69
+ expect(cost).toBeGreaterThan(0);
70
+ });
71
+
72
+ it('throws on missing API key', () => {
73
+ vi.stubEnv('ANTHROPIC_API_KEY', '');
74
+ const router = new ModelRouter(makeOptions({ provider: 'anthropic' }));
75
+ expect(() => router.getAnalysisProvider()).toThrow('Missing API key');
76
+ });
77
+ });
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { z } from 'zod';
3
+ import {
4
+ zodToJsonSchema,
5
+ zodToAnthropicTool,
6
+ zodToOpenAIResponseFormat,
7
+ } from '../../src/llm/schemas.js';
8
+
9
+ describe('zodToJsonSchema', () => {
10
+ it('converts string schema', () => {
11
+ const result = zodToJsonSchema(z.string());
12
+ expect(result).toEqual({ type: 'string' });
13
+ });
14
+
15
+ it('converts number schema with min/max', () => {
16
+ const result = zodToJsonSchema(z.number().min(0).max(1));
17
+ expect(result).toEqual({ type: 'number', minimum: 0, maximum: 1 });
18
+ });
19
+
20
+ it('converts boolean schema', () => {
21
+ const result = zodToJsonSchema(z.boolean());
22
+ expect(result).toEqual({ type: 'boolean' });
23
+ });
24
+
25
+ it('converts enum schema', () => {
26
+ const result = zodToJsonSchema(z.enum(['a', 'b', 'c']));
27
+ expect(result).toEqual({ type: 'string', enum: ['a', 'b', 'c'] });
28
+ });
29
+
30
+ it('converts array schema', () => {
31
+ const result = zodToJsonSchema(z.array(z.string()));
32
+ expect(result).toEqual({ type: 'array', items: { type: 'string' } });
33
+ });
34
+
35
+ it('converts object schema', () => {
36
+ const schema = z.object({
37
+ name: z.string(),
38
+ age: z.number(),
39
+ });
40
+ const result = zodToJsonSchema(schema);
41
+ expect(result).toEqual({
42
+ type: 'object',
43
+ properties: {
44
+ name: { type: 'string' },
45
+ age: { type: 'number' },
46
+ },
47
+ required: ['name', 'age'],
48
+ additionalProperties: false,
49
+ });
50
+ });
51
+
52
+ it('handles optional fields', () => {
53
+ const schema = z.object({
54
+ name: z.string(),
55
+ bio: z.string().optional(),
56
+ });
57
+ const result = zodToJsonSchema(schema);
58
+ expect(result).toEqual({
59
+ type: 'object',
60
+ properties: {
61
+ name: { type: 'string' },
62
+ bio: { type: 'string' },
63
+ },
64
+ required: ['name'],
65
+ additionalProperties: false,
66
+ });
67
+ });
68
+
69
+ it('converts nested object schema', () => {
70
+ const schema = z.object({
71
+ location: z.object({
72
+ file: z.string(),
73
+ line: z.number(),
74
+ }),
75
+ });
76
+ const result = zodToJsonSchema(schema);
77
+ expect(result).toEqual({
78
+ type: 'object',
79
+ properties: {
80
+ location: {
81
+ type: 'object',
82
+ properties: {
83
+ file: { type: 'string' },
84
+ line: { type: 'number' },
85
+ },
86
+ required: ['file', 'line'],
87
+ additionalProperties: false,
88
+ },
89
+ },
90
+ required: ['location'],
91
+ additionalProperties: false,
92
+ });
93
+ });
94
+
95
+ it('converts literal schema', () => {
96
+ const result = zodToJsonSchema(z.literal('analyze'));
97
+ expect(result).toEqual({ type: 'string', const: 'analyze' });
98
+ });
99
+
100
+ it('throws for unsupported zod types instead of returning an empty schema', () => {
101
+ expect(() => zodToJsonSchema(z.tuple([z.string()]))).toThrow(
102
+ 'unsupported Zod type',
103
+ );
104
+ });
105
+ });
106
+
107
+ describe('zodToAnthropicTool', () => {
108
+ it('wraps schema as Anthropic tool definition', () => {
109
+ const schema = z.object({ name: z.string() });
110
+ const result = zodToAnthropicTool(schema, 'test_tool', 'A test tool');
111
+ expect(result).toEqual({
112
+ name: 'test_tool',
113
+ description: 'A test tool',
114
+ input_schema: {
115
+ type: 'object',
116
+ properties: { name: { type: 'string' } },
117
+ required: ['name'],
118
+ additionalProperties: false,
119
+ },
120
+ });
121
+ });
122
+ });
123
+
124
+ describe('zodToOpenAIResponseFormat', () => {
125
+ it('wraps schema as OpenAI response_format', () => {
126
+ const schema = z.object({ value: z.number() });
127
+ const result = zodToOpenAIResponseFormat(schema, 'test_format');
128
+ expect(result).toEqual({
129
+ type: 'json_schema',
130
+ json_schema: {
131
+ name: 'test_format',
132
+ strict: true,
133
+ schema: {
134
+ type: 'object',
135
+ properties: { value: { type: 'number' } },
136
+ required: ['value'],
137
+ additionalProperties: false,
138
+ },
139
+ },
140
+ });
141
+ });
142
+ });
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "lib": ["ES2022"],
7
+ "outDir": "dist",
8
+ "rootDir": ".",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*", "bin/**/*"],
19
+ "exclude": ["node_modules", "dist", "tests"]
20
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ testTimeout: 30_000,
8
+ include: ['tests/**/*.test.ts'],
9
+ exclude: ['node_modules', 'dist'],
10
+ },
11
+ });
package/index.js CHANGED
@@ -47,7 +47,7 @@ try {
47
47
  // Create MCP Server
48
48
  const server = new McpServer(
49
49
  {
50
- name: "agent-security-scanner-mcp",
50
+ name: "prooflayer-agent-security",
51
51
  version: _pkgVersion,
52
52
  },
53
53
  {
@@ -306,7 +306,7 @@ const cliArgs = process.argv.slice(2);
306
306
  // CLI mode: scan-prompt <text> [--verbosity minimal|compact|full]
307
307
  const text = cliArgs[1];
308
308
  if (!text) {
309
- console.error('Usage: agent-security-scanner-mcp scan-prompt <text> [--verbosity minimal|compact|full]');
309
+ console.error('Usage: prooflayer-agent-security scan-prompt <text> [--verbosity minimal|compact|full]');
310
310
  process.exit(1);
311
311
  }
312
312
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -326,7 +326,7 @@ const cliArgs = process.argv.slice(2);
326
326
  // CLI mode: scan-security <file> [--verbosity minimal|compact|full] [--format json|sarif]
327
327
  const filePath = cliArgs[1];
328
328
  if (!filePath) {
329
- console.error('Usage: agent-security-scanner-mcp scan-security <file> [--verbosity minimal|compact|full] [--format json|sarif]');
329
+ console.error('Usage: prooflayer-agent-security scan-security <file> [--verbosity minimal|compact|full] [--format json|sarif]');
330
330
  process.exit(1);
331
331
  }
332
332
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -348,7 +348,7 @@ const cliArgs = process.argv.slice(2);
348
348
  const packageName = cliArgs[1];
349
349
  const ecosystem = cliArgs[2];
350
350
  if (!packageName || !ecosystem) {
351
- console.error('Usage: agent-security-scanner-mcp check-package <name> <ecosystem>');
351
+ console.error('Usage: prooflayer-agent-security check-package <name> <ecosystem>');
352
352
  console.error('Ecosystems: npm, pypi, rubygems, crates, dart, perl, raku');
353
353
  process.exit(1);
354
354
  }
@@ -367,7 +367,7 @@ const cliArgs = process.argv.slice(2);
367
367
  const filePath = cliArgs[1];
368
368
  const ecosystem = cliArgs[2];
369
369
  if (!filePath || !ecosystem) {
370
- console.error('Usage: agent-security-scanner-mcp scan-packages <file> <ecosystem> [--verbosity minimal|compact|full]');
370
+ console.error('Usage: prooflayer-agent-security scan-packages <file> <ecosystem> [--verbosity minimal|compact|full]');
371
371
  console.error('Ecosystems: npm, pypi, rubygems, crates, dart, perl, raku');
372
372
  process.exit(1);
373
373
  }
@@ -387,7 +387,7 @@ const cliArgs = process.argv.slice(2);
387
387
  // CLI mode: scan-project <dir> [--recursive] [--diff-only] [--cross-file] [--include '*.py'] [--exclude '*.test.js'] [--verbosity minimal|compact|full]
388
388
  const dirPath = cliArgs[1];
389
389
  if (!dirPath || dirPath.startsWith('--')) {
390
- console.error('Usage: agent-security-scanner-mcp scan-project <directory> [--recursive] [--diff-only] [--cross-file] [--include <pattern>] [--exclude <pattern>] [--verbosity minimal|compact|full]');
390
+ console.error('Usage: prooflayer-agent-security scan-project <directory> [--recursive] [--diff-only] [--cross-file] [--include <pattern>] [--exclude <pattern>] [--verbosity minimal|compact|full]');
391
391
  process.exit(1);
392
392
  }
393
393
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -455,7 +455,7 @@ const cliArgs = process.argv.slice(2);
455
455
  // CLI mode: scan-mcp <path> [--verbosity minimal|compact|full]
456
456
  const serverPath = cliArgs[1];
457
457
  if (!serverPath) {
458
- console.error('Usage: agent-security-scanner-mcp scan-mcp <server-path> [--verbosity minimal|compact|full]');
458
+ console.error('Usage: prooflayer-agent-security scan-mcp <server-path> [--verbosity minimal|compact|full]');
459
459
  process.exit(1);
460
460
  }
461
461
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -474,7 +474,7 @@ const cliArgs = process.argv.slice(2);
474
474
  const actionType = cliArgs[1];
475
475
  const actionValue = cliArgs[2];
476
476
  if (!actionType || !actionValue) {
477
- console.error('Usage: agent-security-scanner-mcp scan-action <type> <value> [--verbosity minimal|compact|full]');
477
+ console.error('Usage: prooflayer-agent-security scan-action <type> <value> [--verbosity minimal|compact|full]');
478
478
  console.error('Types: bash, file_write, file_read, http_request, file_delete, cron, process_spawn, git, docker');
479
479
  process.exit(1);
480
480
  }
@@ -492,7 +492,7 @@ const cliArgs = process.argv.slice(2);
492
492
  } else if (cliArgs[0] === 'scan-skill') {
493
493
  const skillPath = cliArgs[1];
494
494
  if (!skillPath) {
495
- console.error('Usage: agent-security-scanner-mcp scan-skill <skill-path> [--verbosity minimal|compact|full] [--baseline]');
495
+ console.error('Usage: prooflayer-agent-security scan-skill <skill-path> [--verbosity minimal|compact|full] [--baseline]');
496
496
  process.exit(1);
497
497
  }
498
498
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -529,7 +529,7 @@ const cliArgs = process.argv.slice(2);
529
529
  await import('./src/cli/scan-clawhub-safe.js');
530
530
  // Exit is handled by scan-clawhub-safe.js
531
531
  } else if (cliArgs[0] === '--help' || cliArgs[0] === '-h' || cliArgs[0] === 'help') {
532
- console.log('\n agent-security-scanner-mcp\n');
532
+ console.log('\n prooflayer-agent-security\n');
533
533
  console.log(' Commands:');
534
534
  console.log(' init [client] Set up MCP config for a client');
535
535
  console.log(' init-hooks Install Claude Code hooks for auto-scanning');
@@ -557,14 +557,14 @@ const cliArgs = process.argv.slice(2);
557
557
  console.log(' --include <pattern> Include only matching files (scan-project)');
558
558
  console.log(' --exclude <pattern> Exclude matching files (scan-project)\n');
559
559
  console.log(' Examples:');
560
- console.log(' npx agent-security-scanner-mcp init');
561
- console.log(' npx agent-security-scanner-mcp scan-prompt "ignore previous instructions"');
562
- console.log(' npx agent-security-scanner-mcp scan-security ./app.py --verbosity minimal');
563
- console.log(' npx agent-security-scanner-mcp check-package flask pypi');
564
- console.log(' npx agent-security-scanner-mcp scan-project ./src --verbosity minimal');
565
- console.log(' npx agent-security-scanner-mcp scan-diff HEAD~1');
566
- console.log(' npx agent-security-scanner-mcp report ./src --json');
567
- console.log(' npx agent-security-scanner-mcp benchmark --save --compare-latest\n');
560
+ console.log(' npx prooflayer-agent-security init');
561
+ console.log(' npx prooflayer-agent-security scan-prompt "ignore previous instructions"');
562
+ console.log(' npx prooflayer-agent-security scan-security ./app.py --verbosity minimal');
563
+ console.log(' npx prooflayer-agent-security check-package flask pypi');
564
+ console.log(' npx prooflayer-agent-security scan-project ./src --verbosity minimal');
565
+ console.log(' npx prooflayer-agent-security scan-diff HEAD~1');
566
+ console.log(' npx prooflayer-agent-security report ./src --json');
567
+ console.log(' npx prooflayer-agent-security benchmark --save --compare-latest\n');
568
568
  process.exit(0);
569
569
  } else {
570
570
  // Normal MCP server mode
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "agent-security-scanner-mcp",
3
- "version": "3.10.3",
2
+ "name": "prooflayer-agent-security",
3
+ "version": "4.0.0",
4
4
  "description": "Security scanner for OpenClaw: prompt injection firewall, package hallucination detection, code vulnerability scanning, auto-fix",
5
5
  "author": "Sinewave AI",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "agent-security-scanner-mcp",
3
- "version": "3.20.0",
3
+ "version": "4.0.0",
4
4
  "mcpName": "io.github.sinewaveai/agent-security-scanner-mcp",
5
- "description": "Security scanner MCP server for AI coding agents. Prompt injection firewall, package hallucination detection (4.3M+ packages), 1000+ vulnerability rules with AST & taint analysis, auto-fix. For Claude Code, Cursor, Windsurf, Cline, OpenClaw.",
5
+ "description": "Security scanner MCP server for AI coding agents. Prompt injection firewall, package hallucination detection (4.3M+ packages), 1700+ vulnerability rules with AST & taint analysis, LLM-powered semantic code review, auto-fix. For Claude Code, Cursor, Windsurf, Cline, OpenClaw.",
6
6
  "main": "index.js",
7
7
  "type": "module",
8
8
  "bin": {
@@ -112,7 +112,17 @@
112
112
  "daemon.py",
113
113
  "python_taint_fallback.py",
114
114
  "src/lib/*.js",
115
- "compliance/**"
115
+ "compliance/**",
116
+ "code-review-agent/README.md",
117
+ "code-review-agent/TODO.md",
118
+ "code-review-agent/package.json",
119
+ "code-review-agent/tsconfig.json",
120
+ "code-review-agent/vitest.config.ts",
121
+ "code-review-agent/.env.example",
122
+ "code-review-agent/bin/**",
123
+ "code-review-agent/src/**",
124
+ "code-review-agent/dist/**",
125
+ "code-review-agent/tests/**"
116
126
  ],
117
127
  "devDependencies": {
118
128
  "all-the-package-names": "^2.0.2349",
package/server.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.sinewaveai/agent-security-scanner-mcp",
4
- "description": "MCP security scanner with prompt injection firewall, package hallucination detection, and auto-fix.",
5
- "version": "2.0.4",
3
+ "name": "io.github.sinewaveai/prooflayer-agent-security",
4
+ "description": "MCP security scanner with prompt injection firewall, package hallucination detection, LLM-powered code review, and auto-fix.",
5
+ "version": "4.0.0",
6
6
  "transport": "stdio",
7
7
  "registry": "npm"
8
8
  }