appsec-agent 2.8.0 → 3.0.1

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 (73) hide show
  1. package/README.md +0 -18
  2. package/dist/bin/agent-run.js +14 -17
  3. package/dist/bin/agent-run.js.map +1 -1
  4. package/dist/src/__tests__/mocks/codex_sdk.d.ts +53 -0
  5. package/dist/src/__tests__/mocks/codex_sdk.d.ts.map +1 -0
  6. package/dist/src/__tests__/mocks/codex_sdk.js +8 -0
  7. package/dist/src/__tests__/mocks/codex_sdk.js.map +1 -0
  8. package/dist/src/agent_actions.d.ts +1 -1
  9. package/dist/src/agent_actions.d.ts.map +1 -1
  10. package/dist/src/agent_actions.js +41 -27
  11. package/dist/src/agent_actions.js.map +1 -1
  12. package/dist/src/agent_options.d.ts +17 -93
  13. package/dist/src/agent_options.d.ts.map +1 -1
  14. package/dist/src/agent_options.js +191 -310
  15. package/dist/src/agent_options.js.map +1 -1
  16. package/dist/src/llm_query.d.ts +4 -43
  17. package/dist/src/llm_query.d.ts.map +1 -1
  18. package/dist/src/llm_query.js +4 -145
  19. package/dist/src/llm_query.js.map +1 -1
  20. package/dist/src/main.d.ts.map +1 -1
  21. package/dist/src/main.js +1 -7
  22. package/dist/src/main.js.map +1 -1
  23. package/dist/src/mcp_internal.d.ts +13 -0
  24. package/dist/src/mcp_internal.d.ts.map +1 -0
  25. package/dist/src/mcp_internal.js +34 -0
  26. package/dist/src/mcp_internal.js.map +1 -0
  27. package/dist/src/providers/claude_provider.d.ts +18 -0
  28. package/dist/src/providers/claude_provider.d.ts.map +1 -0
  29. package/dist/src/providers/claude_provider.js +27 -0
  30. package/dist/src/providers/claude_provider.js.map +1 -0
  31. package/dist/src/providers/claude_role_spec.d.ts +10 -0
  32. package/dist/src/providers/claude_role_spec.d.ts.map +1 -0
  33. package/dist/src/providers/claude_role_spec.js +85 -0
  34. package/dist/src/providers/claude_role_spec.js.map +1 -0
  35. package/dist/src/providers/codex_model.d.ts +12 -0
  36. package/dist/src/providers/codex_model.d.ts.map +1 -0
  37. package/dist/src/providers/codex_model.js +45 -0
  38. package/dist/src/providers/codex_model.js.map +1 -0
  39. package/dist/src/providers/codex_provider.d.ts +30 -0
  40. package/dist/src/providers/codex_provider.d.ts.map +1 -0
  41. package/dist/src/providers/codex_provider.js +170 -0
  42. package/dist/src/providers/codex_provider.js.map +1 -0
  43. package/dist/src/providers/codex_role_spec.d.ts +16 -0
  44. package/dist/src/providers/codex_role_spec.d.ts.map +1 -0
  45. package/dist/src/providers/codex_role_spec.js +63 -0
  46. package/dist/src/providers/codex_role_spec.js.map +1 -0
  47. package/dist/src/providers/query_message.d.ts +45 -0
  48. package/dist/src/providers/query_message.d.ts.map +1 -0
  49. package/dist/src/providers/query_message.js +8 -0
  50. package/dist/src/providers/query_message.js.map +1 -0
  51. package/dist/src/providers/resolve_provider.d.ts +10 -0
  52. package/dist/src/providers/resolve_provider.d.ts.map +1 -0
  53. package/dist/src/providers/resolve_provider.js +29 -0
  54. package/dist/src/providers/resolve_provider.js.map +1 -0
  55. package/dist/src/providers/role_spec.d.ts +39 -0
  56. package/dist/src/providers/role_spec.d.ts.map +1 -0
  57. package/dist/src/providers/role_spec.js +8 -0
  58. package/dist/src/providers/role_spec.js.map +1 -0
  59. package/dist/src/providers/structured_output.d.ts +21 -0
  60. package/dist/src/providers/structured_output.d.ts.map +1 -0
  61. package/dist/src/providers/structured_output.js +61 -0
  62. package/dist/src/providers/structured_output.js.map +1 -0
  63. package/dist/src/providers/types.d.ts +18 -0
  64. package/dist/src/providers/types.d.ts.map +1 -0
  65. package/dist/src/providers/types.js +15 -0
  66. package/dist/src/providers/types.js.map +1 -0
  67. package/dist/src/utils.js +1 -1
  68. package/dist/src/utils.js.map +1 -1
  69. package/package.json +3 -3
  70. package/dist/src/openai_tools.d.ts +0 -26
  71. package/dist/src/openai_tools.d.ts.map +0 -1
  72. package/dist/src/openai_tools.js +0 -194
  73. package/dist/src/openai_tools.js.map +0 -1
@@ -5,8 +5,7 @@
5
5
  * Author: Sam Li
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.AgentOptions = exports.MCP_INTERNAL_TOOL_NAMES = exports.MCP_INTERNAL_SERVER_NAME = exports.DEFAULT_MCP_SERVER_NAME = void 0;
9
- exports.buildMcpInternalToolNames = buildMcpInternalToolNames;
8
+ exports.AgentOptions = exports.buildMcpInternalToolNames = exports.MCP_INTERNAL_TOOL_NAMES = exports.MCP_INTERNAL_SERVER_NAME = exports.DEFAULT_MCP_SERVER_NAME = void 0;
10
9
  exports.buildPrReviewerMcpNudgeSystemPromptSuffix = buildPrReviewerMcpNudgeSystemPromptSuffix;
11
10
  const security_report_1 = require("./schemas/security_report");
12
11
  const threat_model_report_1 = require("./schemas/threat_model_report");
@@ -16,6 +15,12 @@ const finding_validator_1 = require("./schemas/finding_validator");
16
15
  const context_extraction_1 = require("./schemas/context_extraction");
17
16
  const learned_guidance_1 = require("./schemas/learned_guidance");
18
17
  const fp_adversary_pass_1 = require("./schemas/fp_adversary_pass");
18
+ const claude_role_spec_1 = require("./providers/claude_role_spec");
19
+ const mcp_internal_1 = require("./mcp_internal");
20
+ Object.defineProperty(exports, "DEFAULT_MCP_SERVER_NAME", { enumerable: true, get: function () { return mcp_internal_1.DEFAULT_MCP_SERVER_NAME; } });
21
+ Object.defineProperty(exports, "MCP_INTERNAL_SERVER_NAME", { enumerable: true, get: function () { return mcp_internal_1.MCP_INTERNAL_SERVER_NAME; } });
22
+ Object.defineProperty(exports, "MCP_INTERNAL_TOOL_NAMES", { enumerable: true, get: function () { return mcp_internal_1.MCP_INTERNAL_TOOL_NAMES; } });
23
+ Object.defineProperty(exports, "buildMcpInternalToolNames", { enumerable: true, get: function () { return mcp_internal_1.buildMcpInternalToolNames; } });
19
24
  const FIX_CODE_VS_OPTIONS_GUIDANCE = `
20
25
 
21
26
  FIXED CODE vs FIX OPTIONS:
@@ -24,54 +29,6 @@ FIXED CODE vs FIX OPTIONS:
24
29
  - Use "fix_options" when the fix requires architectural decisions, domain-specific knowledge,
25
30
  or when multiple valid remediation approaches exist. Each option needs an id, title, and description.
26
31
  - Provide either fixed_code OR fix_options per finding, not both.`;
27
- /**
28
- * Default identifier the agent uses to register a parent-app-managed MCP
29
- * server with the Claude Agent SDK. The resulting tool names exposed to
30
- * the LLM follow the SDK convention `mcp__<server>__<tool>`, so this
31
- * value becomes the literal prefix the model sees on its tool list.
32
- *
33
- * The default is intentionally generic (`appsec-internal`) so the
34
- * `appsec-agent` package is reusable across parent apps. Callers that
35
- * need a different identifier — e.g. to keep an existing prompt-nudge or
36
- * counter contract stable — pass `--mcp-server-name <name>` on the CLI
37
- * (or `mcpServerName` to the role builders directly), and the override
38
- * threads through to both `Options.mcpServers[<name>]` and the namespaced
39
- * tool names in the subagent whitelist.
40
- */
41
- exports.DEFAULT_MCP_SERVER_NAME = 'appsec-internal';
42
- /**
43
- * @deprecated since v2.4.2 — historical alias for the default server name.
44
- * Kept exported so existing imports keep type-checking; new code should
45
- * read `DEFAULT_MCP_SERVER_NAME` (and pass an override via
46
- * `mcpServerName` when a specific identifier is required).
47
- */
48
- exports.MCP_INTERNAL_SERVER_NAME = exports.DEFAULT_MCP_SERVER_NAME;
49
- /**
50
- * Tools exposed by the per-scan in-process MCP server the parent app
51
- * stands up. Pinned at this set (`queryFindingsHistory`,
52
- * `queryImportGraph`, `queryRuntimeEnrichment`, `queryCodebaseGraph`) so
53
- * the agent's tool whitelist is deterministic at the current version;
54
- * parent apps that expose a different surface should fork or extend this
55
- * list rather than rely on dynamic discovery (the SDK would otherwise have
56
- * to round-trip the server before constructing the whitelist).
57
- */
58
- exports.MCP_INTERNAL_TOOL_NAMES = [
59
- 'queryFindingsHistory',
60
- 'queryImportGraph',
61
- 'queryRuntimeEnrichment',
62
- 'queryCodebaseGraph',
63
- ];
64
- /**
65
- * SDK-namespaced tool names the LLM sees on its tool list when the server
66
- * is wired up. Exposed as a helper rather than a constant so callers in
67
- * `agent_options.ts` and the test suite share one source of truth.
68
- *
69
- * @param serverName - Override for the MCP server identifier. Defaults
70
- * to `DEFAULT_MCP_SERVER_NAME` (`appsec-internal`).
71
- */
72
- function buildMcpInternalToolNames(serverName = exports.DEFAULT_MCP_SERVER_NAME) {
73
- return exports.MCP_INTERNAL_TOOL_NAMES.map((tool) => `mcp__${serverName}__${tool}`);
74
- }
75
32
  /**
76
33
  * System-prompt suffix for `pr_reviewer` / `code_reviewer` when
77
34
  * `--mcp-server-url` is set. Steers the model toward all live parent-app
@@ -88,8 +45,8 @@ function buildMcpInternalToolNames(serverName = exports.DEFAULT_MCP_SERVER_NAME)
88
45
  * @param mcpServerName - Same override as `attachMcpServerToOptions`
89
46
  * (`DEFAULT_MCP_SERVER_NAME` when omitted).
90
47
  */
91
- function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName = exports.DEFAULT_MCP_SERVER_NAME) {
92
- const name = mcpServerName || exports.DEFAULT_MCP_SERVER_NAME;
48
+ function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName = mcp_internal_1.DEFAULT_MCP_SERVER_NAME) {
49
+ const name = mcpServerName || mcp_internal_1.DEFAULT_MCP_SERVER_NAME;
93
50
  const findingsTool = `mcp__${name}__queryFindingsHistory`;
94
51
  const importGraphTool = `mcp__${name}__queryImportGraph`;
95
52
  const runtimeTool = `mcp__${name}__queryRuntimeEnrichment`;
@@ -101,24 +58,13 @@ function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName = exports.DEFAU
101
58
  }
102
59
  /**
103
60
  * Mutate an already-built `Options` object to attach the MCP server config
104
- * (top-level `mcpServers`) and extend the named subagent's `tools`
105
- * whitelist with the backend-backed tool surface. No-op when
106
- * `mcpServerUrl` is falsy — preserves the pre-v2.4.0 behaviour for
107
- * callers that don't pass the new parameter.
108
- *
109
- * Mutating in place (rather than returning a fresh object) keeps the
110
- * existing role builders' return statements untouched and avoids a deep
111
- * clone of the (large) `Options` shape.
112
- *
113
- * @param mcpServerName - Override for the server identifier. Defaults
114
- * to `DEFAULT_MCP_SERVER_NAME` (`appsec-internal`); pass the parent
115
- * app's chosen name to keep tool-name contracts stable.
61
+ * @deprecated Use attachMcpToRoleSpec + roleSpecToClaudeOptions instead.
116
62
  */
117
- function attachMcpServerToOptions(options, mcpServerUrl, agentKey, mcpServerName = exports.DEFAULT_MCP_SERVER_NAME, mcpServerBearer) {
63
+ function attachMcpServerToOptions(options, mcpServerUrl, agentKey, mcpServerName = mcp_internal_1.DEFAULT_MCP_SERVER_NAME, mcpServerBearer) {
118
64
  if (!mcpServerUrl) {
119
65
  return;
120
66
  }
121
- const serverName = mcpServerName || exports.DEFAULT_MCP_SERVER_NAME;
67
+ const serverName = mcpServerName || mcp_internal_1.DEFAULT_MCP_SERVER_NAME;
122
68
  const httpEntry = {
123
69
  type: 'http',
124
70
  url: mcpServerUrl,
@@ -133,7 +79,7 @@ function attachMcpServerToOptions(options, mcpServerUrl, agentKey, mcpServerName
133
79
  const agent = options.agents?.[agentKey];
134
80
  if (agent) {
135
81
  const existingTools = agent.tools ?? [];
136
- agent.tools = [...existingTools, ...buildMcpInternalToolNames(serverName)];
82
+ agent.tools = [...existingTools, ...(0, mcp_internal_1.buildMcpInternalToolNames)(serverName)];
137
83
  }
138
84
  }
139
85
  class AgentOptions {
@@ -181,19 +127,26 @@ class AgentOptions {
181
127
  /**
182
128
  * Get options for simple query agent
183
129
  */
184
- getSimpleQueryAgentOptions(role = 'simple_query_agent', srcDir) {
130
+ getSimpleQueryAgentRoleSpec(role = 'simple_query_agent', srcDir) {
185
131
  const roleConfig = this.confDict[this.environment]?.[role];
186
132
  let systemPrompt = roleConfig?.options?.system_prompt ||
187
133
  'You are an Application Security (AppSec) expert assistant. You are responsible for providing security advice and guidance to the user.';
188
- // Add source directory context to system prompt if provided
189
134
  if (srcDir) {
190
135
  systemPrompt += ` You have access to a source code directory at ${srcDir} that you can search and read files from to answer questions.`;
191
136
  }
192
137
  return {
193
- systemPrompt: systemPrompt,
194
- maxTurns: roleConfig?.options?.max_turns || 1
138
+ roleId: 'simple_query_agent',
139
+ systemPrompt,
140
+ maxTurns: roleConfig?.options?.max_turns || 1,
141
+ capabilities: {},
142
+ noTools: true,
143
+ model: this.model,
144
+ workingDirectory: srcDir ?? undefined,
195
145
  };
196
146
  }
147
+ getSimpleQueryAgentOptions(role = 'simple_query_agent', srcDir) {
148
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getSimpleQueryAgentRoleSpec(role, srcDir));
149
+ }
197
150
  /**
198
151
  * Get options for security code reviewer
199
152
  *
@@ -213,7 +166,7 @@ class AgentOptions {
213
166
  * @param mcpServerName - Override for the MCP server identifier
214
167
  * @param mcpServerBearer - Bearer token for MCP HTTP requests
215
168
  */
216
- getCodeReviewerOptions(role = 'code_reviewer', outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer) {
169
+ getCodeReviewerRoleSpec(role = 'code_reviewer', outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer, workingDirectory) {
217
170
  const roleConfig = this.confDict[this.environment]?.[role];
218
171
  let systemPrompt = roleConfig?.options?.system_prompt ||
219
172
  'You are an Application Security (AppSec) expert assistant. You are responsible for performing a thorough code review. List out all the potential security and privacy issues found in the code.';
@@ -223,59 +176,60 @@ class AgentOptions {
223
176
  if (mcpServerUrl) {
224
177
  systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
225
178
  }
226
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 30;
227
- const options = {
228
- agents: {
229
- 'code-reviewer': {
230
- description: 'Reviews code for best practices and potential security issues only',
231
- prompt: systemPrompt,
232
- tools: ['Read', 'Grep', 'Write'],
233
- model: this.model,
234
- maxTurns: resolvedMaxTurns
235
- }
236
- },
237
- permissionMode: 'bypassPermissions'
179
+ const spec = {
180
+ roleId: 'code_reviewer',
181
+ systemPrompt,
182
+ maxTurns: roleConfig?.options?.max_turns ?? 30,
183
+ agentName: 'code-reviewer',
184
+ agentDescription: 'Reviews code for best practices and potential security issues only',
185
+ capabilities: { read: true, grep: true, write: true },
186
+ permissionMode: 'bypassPermissions',
187
+ model: this.model,
188
+ workingDirectory,
238
189
  };
239
- // Add JSON schema enforcement when output format is JSON
240
190
  if (outputFormat?.toLowerCase() === 'json') {
241
- options.outputFormat = {
242
- type: 'json_schema',
243
- schema: security_report_1.SECURITY_REPORT_SCHEMA
244
- };
191
+ spec.outputSchema = security_report_1.SECURITY_REPORT_SCHEMA;
245
192
  }
246
- attachMcpServerToOptions(options, mcpServerUrl, 'code-reviewer', mcpServerName, mcpServerBearer);
247
- return options;
193
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
194
+ return spec;
195
+ }
196
+ getCodeReviewerOptions(role = 'code_reviewer', outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer) {
197
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getCodeReviewerRoleSpec(role, outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer));
248
198
  }
249
199
  /**
250
- * Get options for threat modeler
251
- * @param role - The role configuration key
252
- * @param outputFormat - Output format (json, markdown, etc.)
200
+ * Provider-neutral spec for threat modeler (Phase 3 RoleSpec spike).
253
201
  */
254
- getThreatModelerOptions(role = 'threat_modeler', outputFormat) {
202
+ getThreatModelerRoleSpec(role = 'threat_modeler', outputFormat, workingDirectory) {
255
203
  const roleConfig = this.confDict[this.environment]?.[role];
256
204
  const systemPrompt = roleConfig?.options?.system_prompt ||
257
205
  'You are an Application Security (AppSec) expert assistant. You are responsible for performing risk assessment on the source code repository for SOC2 type 2 compliance audit using the STRIDE methodology.';
258
206
  const isJson = outputFormat?.toLowerCase() === 'json';
259
207
  const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 20;
260
- const options = {
261
- agents: {
262
- 'threat-modeler': {
263
- description: 'Performs threat modeling and risk assessment using STRIDE methodology',
264
- prompt: systemPrompt,
265
- tools: isJson ? ['Read', 'Grep'] : ['Read', 'Grep', 'Write', 'Graphviz'],
266
- model: this.model,
267
- maxTurns: resolvedMaxTurns
268
- }
269
- },
270
- permissionMode: 'bypassPermissions'
208
+ const spec = {
209
+ roleId: 'threat_modeler',
210
+ systemPrompt,
211
+ maxTurns: resolvedMaxTurns,
212
+ agentName: 'threat-modeler',
213
+ agentDescription: 'Performs threat modeling and risk assessment using STRIDE methodology',
214
+ capabilities: isJson
215
+ ? { read: true, grep: true }
216
+ : { read: true, grep: true, write: true, graphviz: true },
217
+ permissionMode: 'bypassPermissions',
218
+ model: this.model,
219
+ workingDirectory,
271
220
  };
272
221
  if (isJson) {
273
- options.outputFormat = {
274
- type: 'json_schema',
275
- schema: threat_model_report_1.THREAT_MODEL_REPORT_SCHEMA
276
- };
222
+ spec.outputSchema = threat_model_report_1.THREAT_MODEL_REPORT_SCHEMA;
277
223
  }
278
- return options;
224
+ return spec;
225
+ }
226
+ /**
227
+ * Get options for threat modeler
228
+ * @param role - The role configuration key
229
+ * @param outputFormat - Output format (json, markdown, etc.)
230
+ */
231
+ getThreatModelerOptions(role = 'threat_modeler', outputFormat) {
232
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getThreatModelerRoleSpec(role, outputFormat));
279
233
  }
280
234
  /**
281
235
  * Get options for PR diff-focused code reviewer
@@ -285,7 +239,7 @@ class AgentOptions {
285
239
  * @param srcDir - Optional source directory path
286
240
  * @param outputFormat - Output format (json, markdown, etc.)
287
241
  */
288
- getDiffReviewerOptions(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
242
+ getDiffReviewerRoleSpec(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
289
243
  const roleConfig = this.confDict[this.environment]?.[role];
290
244
  let systemPrompt;
291
245
  if (noTools) {
@@ -341,7 +295,6 @@ You have access to Read, Grep, and Write tools:
341
295
  if (srcDir) {
342
296
  systemPrompt += `\n\nSource directory available at: ${srcDir}`;
343
297
  }
344
- // Allow role config to override the system prompt
345
298
  if (roleConfig?.options?.diff_reviewer_system_prompt) {
346
299
  systemPrompt = roleConfig.options.diff_reviewer_system_prompt;
347
300
  }
@@ -356,76 +309,59 @@ You have access to Read, Grep, and Write tools:
356
309
  if (mcpServerUrl && role === 'pr_reviewer') {
357
310
  systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
358
311
  }
359
- const resolvedMaxTurns = maxTurns
360
- ?? roleConfig?.options?.max_turns
361
- ?? 10;
362
- const options = {
363
- agents: {
364
- 'diff-reviewer': {
365
- description: 'Reviews PR diff changes for security vulnerabilities',
366
- prompt: systemPrompt,
367
- tools: noTools ? ['Write'] : ['Read', 'Grep', 'Write'],
368
- model: this.model,
369
- maxTurns: resolvedMaxTurns
370
- }
371
- },
372
- permissionMode: 'bypassPermissions'
312
+ const spec = {
313
+ roleId: role === 'pr_reviewer' ? 'pr_reviewer' : 'code_reviewer',
314
+ systemPrompt,
315
+ maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 10,
316
+ agentName: 'diff-reviewer',
317
+ agentDescription: 'Reviews PR diff changes for security vulnerabilities',
318
+ capabilities: noTools ? {} : { read: true, grep: true, write: true },
319
+ allowedTools: noTools ? ['Write'] : undefined,
320
+ permissionMode: 'bypassPermissions',
321
+ model: this.model,
322
+ workingDirectory: srcDir ?? undefined,
373
323
  };
374
- // Add JSON schema enforcement when output format is JSON
375
324
  if (outputFormat?.toLowerCase() === 'json') {
376
- options.outputFormat = {
377
- type: 'json_schema',
378
- schema: security_report_1.SECURITY_REPORT_SCHEMA
379
- };
325
+ spec.outputSchema = security_report_1.SECURITY_REPORT_SCHEMA;
380
326
  }
381
- attachMcpServerToOptions(options, mcpServerUrl, 'diff-reviewer', mcpServerName, mcpServerBearer);
382
- return options;
327
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
328
+ return spec;
383
329
  }
384
- /**
385
- * Get options for code fixer agent
386
- * Uses structured JSON output to guarantee a well-formed fix response.
387
- * Has Read and Grep tools to explore the source directory for additional context.
388
- * @param role - The role configuration key
389
- * @param srcDir - Optional source directory path for additional context
390
- */
391
- getCodeFixerOptions(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
330
+ getDiffReviewerOptions(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
331
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getDiffReviewerRoleSpec(role, srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer));
332
+ }
333
+ getCodeFixerRoleSpec(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
392
334
  const roleConfig = this.confDict[this.environment]?.[role];
393
335
  let systemPrompt = roleConfig?.options?.system_prompt ||
394
336
  'You are an expert security engineer specializing in fixing vulnerabilities in code. ' +
395
337
  'You receive a finding with code context and must produce a precise, minimal fix that resolves ' +
396
- 'the security issue while preserving the original code\'s functionality and indentation. ' +
338
+ "the security issue while preserving the original code's functionality and indentation. " +
397
339
  'Only modify the affected lines. Always use the recommended secure alternatives when applicable.';
398
340
  if (srcDir) {
399
341
  systemPrompt += `\n\nSource directory available at: ${srcDir}. You may read files for additional context if needed.`;
400
342
  }
401
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 10;
402
- const options = {
403
- agents: {
404
- 'code-fixer': {
405
- description: 'Generates precise security fixes for code vulnerabilities',
406
- prompt: systemPrompt,
407
- tools: ['Read', 'Grep'],
408
- model: this.model,
409
- maxTurns: resolvedMaxTurns
410
- }
411
- },
343
+ const spec = {
344
+ roleId: 'code_fixer',
345
+ systemPrompt,
346
+ maxTurns: roleConfig?.options?.max_turns ?? 10,
347
+ agentName: 'code-fixer',
348
+ agentDescription: 'Generates precise security fixes for code vulnerabilities',
349
+ capabilities: { read: true, grep: true },
412
350
  permissionMode: 'bypassPermissions',
413
- outputFormat: {
414
- type: 'json_schema',
415
- schema: security_fix_1.FIX_OUTPUT_SCHEMA
416
- }
351
+ model: this.model,
352
+ outputSchema: security_fix_1.FIX_OUTPUT_SCHEMA,
353
+ workingDirectory: srcDir ?? undefined,
417
354
  };
418
- attachMcpServerToOptions(options, mcpServerUrl, 'code-fixer', mcpServerName, mcpServerBearer);
419
- return options;
355
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
356
+ return spec;
420
357
  }
421
- /**
422
- * Get options for the QA verifier agent
423
- * Uses Read, Grep, and Bash tools for test execution and analysis
424
- */
425
- getQaVerifierOptions(role = 'qa_verifier', srcDir) {
358
+ getCodeFixerOptions(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
359
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getCodeFixerRoleSpec(role, srcDir, mcpServerUrl, mcpServerName, mcpServerBearer));
360
+ }
361
+ getQaVerifierRoleSpec(role = 'qa_verifier', srcDir) {
426
362
  const roleConfig = this.confDict[this.environment]?.[role];
427
363
  let systemPrompt = roleConfig?.options?.system_prompt ||
428
- 'You are a QA verification engineer. Your task is to verify security fixes by running the project\'s test suite ' +
364
+ "You are a QA verification engineer. Your task is to verify security fixes by running the project's test suite " +
429
365
  'and analyzing the results. You have access to the project source code and can execute shell commands to run tests. ' +
430
366
  'First, set up the environment (install dependencies if needed), then run the test suite. ' +
431
367
  'If tests fail, analyze the failures to determine if they are caused by the security fix or are pre-existing issues. ' +
@@ -433,30 +369,23 @@ You have access to Read, Grep, and Write tools:
433
369
  if (srcDir) {
434
370
  systemPrompt += `\n\nProject source code is available at: ${srcDir}. Use Read and Grep to inspect files, and Bash to execute commands.`;
435
371
  }
436
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 15;
437
- const options = {
438
- agents: {
439
- 'qa-verifier': {
440
- description: 'Verifies security fixes by running project tests and analyzing results',
441
- prompt: systemPrompt,
442
- tools: ['Read', 'Grep', 'Bash'],
443
- model: this.model,
444
- maxTurns: resolvedMaxTurns
445
- }
446
- },
372
+ return {
373
+ roleId: 'qa_verifier',
374
+ systemPrompt,
375
+ maxTurns: roleConfig?.options?.max_turns ?? 15,
376
+ agentName: 'qa-verifier',
377
+ agentDescription: 'Verifies security fixes by running project tests and analyzing results',
378
+ capabilities: { read: true, grep: true, shell: true },
447
379
  permissionMode: 'bypassPermissions',
448
- outputFormat: {
449
- type: 'json_schema',
450
- schema: qa_context_1.QA_VERDICT_SCHEMA
451
- }
380
+ model: this.model,
381
+ outputSchema: qa_context_1.QA_VERDICT_SCHEMA,
382
+ workingDirectory: srcDir ?? undefined,
452
383
  };
453
- return options;
454
384
  }
455
- /**
456
- * Get options for the finding validator agent
457
- * Uses Read and Grep tools (read-only) to analyze code for vulnerability presence.
458
- */
459
- getContextExtractorOptions(role = 'context_extractor') {
385
+ getQaVerifierOptions(role = 'qa_verifier', srcDir) {
386
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getQaVerifierRoleSpec(role, srcDir));
387
+ }
388
+ getContextExtractorRoleSpec(role = 'context_extractor') {
460
389
  const roleConfig = this.confDict[this.environment]?.[role];
461
390
  const systemPrompt = roleConfig?.options?.system_prompt ||
462
391
  'You are a security-aware software analyst. Your task is to analyze repository files and metadata ' +
@@ -470,25 +399,23 @@ You have access to Read, Grep, and Write tools:
470
399
  '(logs, uploads, work-dir, data), IDE config (.cursor, .vscode), utility scripts, and documentation. Use ' +
471
400
  'specific paths from the tree (e.g., "backend/scripts/**" not just "scripts/**"). Only suggest patterns NOT ' +
472
401
  'already in the standard preset. If a field has no relevant information, return an empty string.';
473
- const options = {
474
- agents: {
475
- 'context-extractor': {
476
- description: 'Extracts structured project intelligence from repository files',
477
- prompt: systemPrompt,
478
- tools: [],
479
- model: this.model,
480
- maxTurns: 1,
481
- },
482
- },
402
+ return {
403
+ roleId: 'context_extractor',
404
+ systemPrompt,
405
+ maxTurns: 1,
406
+ agentName: 'context-extractor',
407
+ agentDescription: 'Extracts structured project intelligence from repository files',
408
+ capabilities: {},
409
+ allowedTools: [],
483
410
  permissionMode: 'bypassPermissions',
484
- outputFormat: {
485
- type: 'json_schema',
486
- schema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
487
- },
411
+ model: this.model,
412
+ outputSchema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
488
413
  };
489
- return options;
490
414
  }
491
- getFindingValidatorOptions(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
415
+ getContextExtractorOptions(role = 'context_extractor') {
416
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getContextExtractorRoleSpec(role));
417
+ }
418
+ getFindingValidatorRoleSpec(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
492
419
  const roleConfig = this.confDict[this.environment]?.[role];
493
420
  let systemPrompt = roleConfig?.options?.system_prompt ||
494
421
  'You are a security expert specializing in vulnerability validation. ' +
@@ -498,45 +425,25 @@ You have access to Read, Grep, and Write tools:
498
425
  if (srcDir) {
499
426
  systemPrompt += `\n\nSource code is available at: ${srcDir}. Use Read and Grep to inspect files for additional context if needed.`;
500
427
  }
501
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 5;
502
- const options = {
503
- agents: {
504
- 'finding-validator': {
505
- description: 'Validates whether a previously detected security vulnerability is still present in code',
506
- prompt: systemPrompt,
507
- tools: ['Read', 'Grep'],
508
- model: this.model,
509
- maxTurns: resolvedMaxTurns
510
- }
511
- },
428
+ const spec = {
429
+ roleId: 'finding_validator',
430
+ systemPrompt,
431
+ maxTurns: roleConfig?.options?.max_turns ?? 5,
432
+ agentName: 'finding-validator',
433
+ agentDescription: 'Validates whether a previously detected security vulnerability is still present in code',
434
+ capabilities: { read: true, grep: true },
512
435
  permissionMode: 'bypassPermissions',
513
- outputFormat: {
514
- type: 'json_schema',
515
- schema: finding_validator_1.RETEST_VERDICT_SCHEMA
516
- }
436
+ model: this.model,
437
+ outputSchema: finding_validator_1.RETEST_VERDICT_SCHEMA,
438
+ workingDirectory: srcDir ?? undefined,
517
439
  };
518
- attachMcpServerToOptions(options, mcpServerUrl, 'finding-validator', mcpServerName, mcpServerBearer);
519
- return options;
440
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
441
+ return spec;
520
442
  }
521
- /**
522
- * learned_guidance_synthesizer (v2.5.0 / parent-app plan §3.8): pure-transform
523
- * role that condenses bucketed dismissal/outcome/feedback signals into a short
524
- * list of class-level policy bullets the pr_reviewer reads next scan.
525
- *
526
- * Tools: NONE — the agent must work from the provided buckets only. No
527
- * source-tree access, no MCP server. The parent app's `runSynthesizerAgent`
528
- * spawns this from a temp working directory anyway, so even if a tool were
529
- * attached it would have nothing to read.
530
- *
531
- * Output schema (LEARNED_GUIDANCE_OUTPUT_SCHEMA):
532
- * { bullets: [{ cwe, bullet (≤300 chars), confidence (0..1) }] }
533
- *
534
- * Confidence floor and active-bullet cap are enforced by the parent app
535
- * (`MIN_CONFIDENCE = 0.6`, `MAX_ACTIVE_BULLETS_PER_PROJECT = 12`); this
536
- * role is allowed to return up to 50 bullets and the parent ranks +
537
- * truncates.
538
- */
539
- getLearnedGuidanceSynthesizerOptions(role = 'learned_guidance_synthesizer') {
443
+ getFindingValidatorOptions(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
444
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getFindingValidatorRoleSpec(role, srcDir, mcpServerUrl, mcpServerName, mcpServerBearer));
445
+ }
446
+ getLearnedGuidanceSynthesizerRoleSpec(role = 'learned_guidance_synthesizer') {
540
447
  const roleConfig = this.confDict[this.environment]?.[role];
541
448
  const systemPrompt = roleConfig?.options?.system_prompt ||
542
449
  'You are a senior application security engineer summarizing patterns from past PR-scan ' +
@@ -548,30 +455,23 @@ You have access to Read, Grep, and Write tools:
548
455
  'vague to ground a specific rule, OMIT that bucket entirely — it is better to return zero ' +
549
456
  'bullets than a bullet the reviewer cannot act on. Output is constrained to the required ' +
550
457
  'JSON schema; emit nothing else.';
551
- const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 1;
552
- const options = {
553
- agents: {
554
- 'learned-guidance-synthesizer': {
555
- description: 'Synthesizes class-level learned-guidance bullets from per-CWE dismissal-signal buckets',
556
- prompt: systemPrompt,
557
- tools: [],
558
- model: this.model,
559
- maxTurns: resolvedMaxTurns,
560
- },
561
- },
458
+ return {
459
+ roleId: 'learned_guidance_synthesizer',
460
+ systemPrompt,
461
+ maxTurns: roleConfig?.options?.max_turns ?? 1,
462
+ agentName: 'learned-guidance-synthesizer',
463
+ agentDescription: 'Synthesizes class-level learned-guidance bullets from per-CWE dismissal-signal buckets',
464
+ capabilities: {},
465
+ allowedTools: [],
562
466
  permissionMode: 'bypassPermissions',
563
- outputFormat: {
564
- type: 'json_schema',
565
- schema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
566
- },
467
+ model: this.model,
468
+ outputSchema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
567
469
  };
568
- return options;
569
470
  }
570
- /**
571
- * pr_adversary: second pass that filters pr_reviewer findings using failure-path skepticism.
572
- * Output: same SECURITY_REPORT_SCHEMA with only surviving findings.
573
- */
574
- getPrAdversaryOptions(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
471
+ getLearnedGuidanceSynthesizerOptions(role = 'learned_guidance_synthesizer') {
472
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getLearnedGuidanceSynthesizerRoleSpec(role));
473
+ }
474
+ getPrAdversaryRoleSpec(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
575
475
  const roleConfig = this.confDict[this.environment]?.[role];
576
476
  let systemPrompt = roleConfig?.options?.system_prompt ||
577
477
  'You are a senior application security engineer performing an adversarial second pass on security findings. ' +
@@ -585,42 +485,25 @@ You have access to Read, Grep, and Write tools:
585
485
  systemPrompt +=
586
486
  '\n\n**Experiment (treatment):** Bias toward dropping borderline issues unless the diff plus quick repo checks show a real attack surface.';
587
487
  }
588
- const resolvedMaxTurns = maxTurns ?? roleConfig?.options?.max_turns ?? 15;
589
- const options = {
590
- agents: {
591
- 'pr-adversary': {
592
- description: 'Adversarial second pass: filters PR scan findings by concrete failure paths',
593
- prompt: systemPrompt,
594
- tools: ['Read', 'Grep'],
595
- model: this.model,
596
- maxTurns: resolvedMaxTurns,
597
- },
598
- },
488
+ const spec = {
489
+ roleId: 'pr_adversary',
490
+ systemPrompt,
491
+ maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 15,
492
+ agentName: 'pr-adversary',
493
+ agentDescription: 'Adversarial second pass: filters PR scan findings by concrete failure paths',
494
+ capabilities: { read: true, grep: true },
599
495
  permissionMode: 'bypassPermissions',
600
- outputFormat: {
601
- type: 'json_schema',
602
- schema: security_report_1.SECURITY_REPORT_SCHEMA,
603
- },
496
+ model: this.model,
497
+ outputSchema: security_report_1.SECURITY_REPORT_SCHEMA,
498
+ workingDirectory: srcDir ?? undefined,
604
499
  };
605
- attachMcpServerToOptions(options, mcpServerUrl, 'pr-adversary', mcpServerName, mcpServerBearer);
606
- return options;
500
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
501
+ return spec;
607
502
  }
608
- /**
609
- * fp_adversary (v2.8.0 / parent app full-repo Phase 2.5): second pass that
610
- * filters first-pass `code_reviewer` findings on full-repo scans by emitting
611
- * per-finding `(fingerprint, verdict, confidence, rationale)` verdicts. Output
612
- * is constrained to FP_ADVERSARY_REPORT_SCHEMA so the verdict contract stays
613
- * model-independent across Claude upgrades.
614
- *
615
- * Differences from `getPrAdversaryOptions`:
616
- * - Output schema is the dedicated `fp_adversary_report` (not
617
- * SECURITY_REPORT_SCHEMA) — verdicts round-trip on `fingerprint`, not `id`.
618
- * - No `experiment_enabled` plumbing (Lane-2 deliberately omits A/B per M4).
619
- * - System prompt references the four structured posture fields and
620
- * `similar_dismissed` precedent that the parent app threads in via
621
- * `--adversarial-context`.
622
- */
623
- getFpAdversaryOptions(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
503
+ getPrAdversaryOptions(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
504
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getPrAdversaryRoleSpec(role, srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer));
505
+ }
506
+ getFpAdversaryRoleSpec(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
624
507
  const roleConfig = this.confDict[this.environment]?.[role];
625
508
  let systemPrompt = roleConfig?.options?.system_prompt ||
626
509
  'You are a senior application security engineer performing an adversarial false-positive review on a full-repository security scan. ' +
@@ -636,25 +519,23 @@ You have access to Read, Grep, and Write tools:
636
519
  if (mcpServerUrl) {
637
520
  systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
638
521
  }
639
- const resolvedMaxTurns = maxTurns ?? roleConfig?.options?.max_turns ?? 15;
640
- const options = {
641
- agents: {
642
- 'fp-adversary': {
643
- description: 'Adversarial false-positive filter for full-repo scans: emits per-finding (verdict, confidence, rationale) verdicts',
644
- prompt: systemPrompt,
645
- tools: ['Read', 'Grep'],
646
- model: this.model,
647
- maxTurns: resolvedMaxTurns,
648
- },
649
- },
522
+ const spec = {
523
+ roleId: 'fp_adversary',
524
+ systemPrompt,
525
+ maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 15,
526
+ agentName: 'fp-adversary',
527
+ agentDescription: 'Adversarial false-positive filter for full-repo scans: emits per-finding (verdict, confidence, rationale) verdicts',
528
+ capabilities: { read: true, grep: true },
650
529
  permissionMode: 'bypassPermissions',
651
- outputFormat: {
652
- type: 'json_schema',
653
- schema: fp_adversary_pass_1.FP_ADVERSARY_REPORT_SCHEMA,
654
- },
530
+ model: this.model,
531
+ outputSchema: fp_adversary_pass_1.FP_ADVERSARY_REPORT_SCHEMA,
532
+ workingDirectory: srcDir ?? undefined,
655
533
  };
656
- attachMcpServerToOptions(options, mcpServerUrl, 'fp-adversary', mcpServerName, mcpServerBearer);
657
- return options;
534
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
535
+ return spec;
536
+ }
537
+ getFpAdversaryOptions(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
538
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getFpAdversaryRoleSpec(role, srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer));
658
539
  }
659
540
  }
660
541
  exports.AgentOptions = AgentOptions;