appsec-agent 2.8.0 → 3.1.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 (87) hide show
  1. package/README.md +105 -31
  2. package/conf/appsec_agent.yaml +7 -0
  3. package/dist/bin/agent-run.js +14 -17
  4. package/dist/bin/agent-run.js.map +1 -1
  5. package/dist/conf/appsec_agent.yaml +7 -0
  6. package/dist/src/__tests__/mocks/codex_sdk.d.ts +53 -0
  7. package/dist/src/__tests__/mocks/codex_sdk.d.ts.map +1 -0
  8. package/dist/src/__tests__/mocks/codex_sdk.js +8 -0
  9. package/dist/src/__tests__/mocks/codex_sdk.js.map +1 -0
  10. package/dist/src/agent_actions.d.ts +5 -1
  11. package/dist/src/agent_actions.d.ts.map +1 -1
  12. package/dist/src/agent_actions.js +103 -27
  13. package/dist/src/agent_actions.js.map +1 -1
  14. package/dist/src/agent_options.d.ts +20 -94
  15. package/dist/src/agent_options.d.ts.map +1 -1
  16. package/dist/src/agent_options.js +219 -311
  17. package/dist/src/agent_options.js.map +1 -1
  18. package/dist/src/index.d.ts +2 -1
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/index.js +6 -1
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/llm_query.d.ts +4 -43
  23. package/dist/src/llm_query.d.ts.map +1 -1
  24. package/dist/src/llm_query.js +4 -145
  25. package/dist/src/llm_query.js.map +1 -1
  26. package/dist/src/main.d.ts.map +1 -1
  27. package/dist/src/main.js +70 -10
  28. package/dist/src/main.js.map +1 -1
  29. package/dist/src/mcp_internal.d.ts +13 -0
  30. package/dist/src/mcp_internal.d.ts.map +1 -0
  31. package/dist/src/mcp_internal.js +34 -0
  32. package/dist/src/mcp_internal.js.map +1 -0
  33. package/dist/src/providers/claude_provider.d.ts +18 -0
  34. package/dist/src/providers/claude_provider.d.ts.map +1 -0
  35. package/dist/src/providers/claude_provider.js +27 -0
  36. package/dist/src/providers/claude_provider.js.map +1 -0
  37. package/dist/src/providers/claude_role_spec.d.ts +10 -0
  38. package/dist/src/providers/claude_role_spec.d.ts.map +1 -0
  39. package/dist/src/providers/claude_role_spec.js +85 -0
  40. package/dist/src/providers/claude_role_spec.js.map +1 -0
  41. package/dist/src/providers/codex_model.d.ts +12 -0
  42. package/dist/src/providers/codex_model.d.ts.map +1 -0
  43. package/dist/src/providers/codex_model.js +45 -0
  44. package/dist/src/providers/codex_model.js.map +1 -0
  45. package/dist/src/providers/codex_provider.d.ts +30 -0
  46. package/dist/src/providers/codex_provider.d.ts.map +1 -0
  47. package/dist/src/providers/codex_provider.js +170 -0
  48. package/dist/src/providers/codex_provider.js.map +1 -0
  49. package/dist/src/providers/codex_role_spec.d.ts +16 -0
  50. package/dist/src/providers/codex_role_spec.d.ts.map +1 -0
  51. package/dist/src/providers/codex_role_spec.js +63 -0
  52. package/dist/src/providers/codex_role_spec.js.map +1 -0
  53. package/dist/src/providers/query_message.d.ts +45 -0
  54. package/dist/src/providers/query_message.d.ts.map +1 -0
  55. package/dist/src/providers/query_message.js +8 -0
  56. package/dist/src/providers/query_message.js.map +1 -0
  57. package/dist/src/providers/resolve_provider.d.ts +10 -0
  58. package/dist/src/providers/resolve_provider.d.ts.map +1 -0
  59. package/dist/src/providers/resolve_provider.js +29 -0
  60. package/dist/src/providers/resolve_provider.js.map +1 -0
  61. package/dist/src/providers/role_spec.d.ts +39 -0
  62. package/dist/src/providers/role_spec.d.ts.map +1 -0
  63. package/dist/src/providers/role_spec.js +8 -0
  64. package/dist/src/providers/role_spec.js.map +1 -0
  65. package/dist/src/providers/structured_output.d.ts +21 -0
  66. package/dist/src/providers/structured_output.d.ts.map +1 -0
  67. package/dist/src/providers/structured_output.js +61 -0
  68. package/dist/src/providers/structured_output.js.map +1 -0
  69. package/dist/src/providers/types.d.ts +18 -0
  70. package/dist/src/providers/types.d.ts.map +1 -0
  71. package/dist/src/providers/types.js +15 -0
  72. package/dist/src/providers/types.js.map +1 -0
  73. package/dist/src/schemas/threat_adversary_pass.d.ts +18 -0
  74. package/dist/src/schemas/threat_adversary_pass.d.ts.map +1 -0
  75. package/dist/src/schemas/threat_adversary_pass.js +59 -0
  76. package/dist/src/schemas/threat_adversary_pass.js.map +1 -0
  77. package/dist/src/schemas/threat_model_report.d.ts +11 -0
  78. package/dist/src/schemas/threat_model_report.d.ts.map +1 -1
  79. package/dist/src/schemas/threat_model_report.js +21 -4
  80. package/dist/src/schemas/threat_model_report.js.map +1 -1
  81. package/dist/src/utils.js +1 -1
  82. package/dist/src/utils.js.map +1 -1
  83. package/package.json +3 -3
  84. package/dist/src/openai_tools.d.ts +0 -26
  85. package/dist/src/openai_tools.d.ts.map +0 -1
  86. package/dist/src/openai_tools.js +0 -194
  87. 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,87 @@ 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, maxTurnsOverride) {
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
- 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'
207
+ const resolvedMaxTurns = maxTurnsOverride ?? roleConfig?.options?.max_turns ?? 100;
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, maxTurnsOverride) {
232
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getThreatModelerRoleSpec(role, outputFormat, undefined, maxTurnsOverride));
233
+ }
234
+ getThreatAdversaryRoleSpec(role = 'threat_adversary', srcDir, maxTurns) {
235
+ const roleConfig = this.confDict[this.environment]?.[role];
236
+ let systemPrompt = roleConfig?.options?.system_prompt ||
237
+ 'You are a senior application security engineer performing an adversarial second pass on a STRIDE threat model. ' +
238
+ 'Skeptically verify each threat against the real codebase using Read and Grep. ' +
239
+ 'Keep only threats with a concrete, demonstrable attack path and confirmed source_locations. ' +
240
+ 'Drop generic, mitigated, or ungrounded threats. Reconcile risks and metadata counts.';
241
+ if (srcDir) {
242
+ systemPrompt += `\n\nSource code is available at: ${srcDir}. Use Read and Grep to verify code paths before keeping a threat.`;
243
+ }
244
+ const spec = {
245
+ roleId: 'threat_adversary',
246
+ systemPrompt,
247
+ maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 100,
248
+ agentName: 'threat-adversary',
249
+ agentDescription: 'Adversarial second pass: filters STRIDE threats by concrete code-grounded attack paths',
250
+ capabilities: { read: true, grep: true },
251
+ permissionMode: 'bypassPermissions',
252
+ model: this.model,
253
+ outputSchema: threat_model_report_1.THREAT_MODEL_REPORT_SCHEMA,
254
+ workingDirectory: srcDir ?? undefined,
255
+ };
256
+ return spec;
257
+ }
258
+ getThreatAdversaryOptions(role = 'threat_adversary', srcDir, maxTurns) {
259
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getThreatAdversaryRoleSpec(role, srcDir, maxTurns));
279
260
  }
280
261
  /**
281
262
  * Get options for PR diff-focused code reviewer
@@ -285,7 +266,7 @@ class AgentOptions {
285
266
  * @param srcDir - Optional source directory path
286
267
  * @param outputFormat - Output format (json, markdown, etc.)
287
268
  */
288
- getDiffReviewerOptions(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
269
+ getDiffReviewerRoleSpec(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
289
270
  const roleConfig = this.confDict[this.environment]?.[role];
290
271
  let systemPrompt;
291
272
  if (noTools) {
@@ -341,7 +322,6 @@ You have access to Read, Grep, and Write tools:
341
322
  if (srcDir) {
342
323
  systemPrompt += `\n\nSource directory available at: ${srcDir}`;
343
324
  }
344
- // Allow role config to override the system prompt
345
325
  if (roleConfig?.options?.diff_reviewer_system_prompt) {
346
326
  systemPrompt = roleConfig.options.diff_reviewer_system_prompt;
347
327
  }
@@ -356,76 +336,59 @@ You have access to Read, Grep, and Write tools:
356
336
  if (mcpServerUrl && role === 'pr_reviewer') {
357
337
  systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
358
338
  }
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'
339
+ const spec = {
340
+ roleId: role === 'pr_reviewer' ? 'pr_reviewer' : 'code_reviewer',
341
+ systemPrompt,
342
+ maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 10,
343
+ agentName: 'diff-reviewer',
344
+ agentDescription: 'Reviews PR diff changes for security vulnerabilities',
345
+ capabilities: noTools ? {} : { read: true, grep: true, write: true },
346
+ allowedTools: noTools ? ['Write'] : undefined,
347
+ permissionMode: 'bypassPermissions',
348
+ model: this.model,
349
+ workingDirectory: srcDir ?? undefined,
373
350
  };
374
- // Add JSON schema enforcement when output format is JSON
375
351
  if (outputFormat?.toLowerCase() === 'json') {
376
- options.outputFormat = {
377
- type: 'json_schema',
378
- schema: security_report_1.SECURITY_REPORT_SCHEMA
379
- };
352
+ spec.outputSchema = security_report_1.SECURITY_REPORT_SCHEMA;
380
353
  }
381
- attachMcpServerToOptions(options, mcpServerUrl, 'diff-reviewer', mcpServerName, mcpServerBearer);
382
- return options;
354
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
355
+ return spec;
383
356
  }
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) {
357
+ getDiffReviewerOptions(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
358
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getDiffReviewerRoleSpec(role, srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer));
359
+ }
360
+ getCodeFixerRoleSpec(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
392
361
  const roleConfig = this.confDict[this.environment]?.[role];
393
362
  let systemPrompt = roleConfig?.options?.system_prompt ||
394
363
  'You are an expert security engineer specializing in fixing vulnerabilities in code. ' +
395
364
  '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. ' +
365
+ "the security issue while preserving the original code's functionality and indentation. " +
397
366
  'Only modify the affected lines. Always use the recommended secure alternatives when applicable.';
398
367
  if (srcDir) {
399
368
  systemPrompt += `\n\nSource directory available at: ${srcDir}. You may read files for additional context if needed.`;
400
369
  }
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
- },
370
+ const spec = {
371
+ roleId: 'code_fixer',
372
+ systemPrompt,
373
+ maxTurns: roleConfig?.options?.max_turns ?? 10,
374
+ agentName: 'code-fixer',
375
+ agentDescription: 'Generates precise security fixes for code vulnerabilities',
376
+ capabilities: { read: true, grep: true },
412
377
  permissionMode: 'bypassPermissions',
413
- outputFormat: {
414
- type: 'json_schema',
415
- schema: security_fix_1.FIX_OUTPUT_SCHEMA
416
- }
378
+ model: this.model,
379
+ outputSchema: security_fix_1.FIX_OUTPUT_SCHEMA,
380
+ workingDirectory: srcDir ?? undefined,
417
381
  };
418
- attachMcpServerToOptions(options, mcpServerUrl, 'code-fixer', mcpServerName, mcpServerBearer);
419
- return options;
382
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
383
+ return spec;
420
384
  }
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) {
385
+ getCodeFixerOptions(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
386
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getCodeFixerRoleSpec(role, srcDir, mcpServerUrl, mcpServerName, mcpServerBearer));
387
+ }
388
+ getQaVerifierRoleSpec(role = 'qa_verifier', srcDir) {
426
389
  const roleConfig = this.confDict[this.environment]?.[role];
427
390
  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 ' +
391
+ "You are a QA verification engineer. Your task is to verify security fixes by running the project's test suite " +
429
392
  'and analyzing the results. You have access to the project source code and can execute shell commands to run tests. ' +
430
393
  'First, set up the environment (install dependencies if needed), then run the test suite. ' +
431
394
  'If tests fail, analyze the failures to determine if they are caused by the security fix or are pre-existing issues. ' +
@@ -433,30 +396,23 @@ You have access to Read, Grep, and Write tools:
433
396
  if (srcDir) {
434
397
  systemPrompt += `\n\nProject source code is available at: ${srcDir}. Use Read and Grep to inspect files, and Bash to execute commands.`;
435
398
  }
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
- },
399
+ return {
400
+ roleId: 'qa_verifier',
401
+ systemPrompt,
402
+ maxTurns: roleConfig?.options?.max_turns ?? 15,
403
+ agentName: 'qa-verifier',
404
+ agentDescription: 'Verifies security fixes by running project tests and analyzing results',
405
+ capabilities: { read: true, grep: true, shell: true },
447
406
  permissionMode: 'bypassPermissions',
448
- outputFormat: {
449
- type: 'json_schema',
450
- schema: qa_context_1.QA_VERDICT_SCHEMA
451
- }
407
+ model: this.model,
408
+ outputSchema: qa_context_1.QA_VERDICT_SCHEMA,
409
+ workingDirectory: srcDir ?? undefined,
452
410
  };
453
- return options;
454
411
  }
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') {
412
+ getQaVerifierOptions(role = 'qa_verifier', srcDir) {
413
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getQaVerifierRoleSpec(role, srcDir));
414
+ }
415
+ getContextExtractorRoleSpec(role = 'context_extractor') {
460
416
  const roleConfig = this.confDict[this.environment]?.[role];
461
417
  const systemPrompt = roleConfig?.options?.system_prompt ||
462
418
  'You are a security-aware software analyst. Your task is to analyze repository files and metadata ' +
@@ -470,25 +426,23 @@ You have access to Read, Grep, and Write tools:
470
426
  '(logs, uploads, work-dir, data), IDE config (.cursor, .vscode), utility scripts, and documentation. Use ' +
471
427
  'specific paths from the tree (e.g., "backend/scripts/**" not just "scripts/**"). Only suggest patterns NOT ' +
472
428
  '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
- },
429
+ return {
430
+ roleId: 'context_extractor',
431
+ systemPrompt,
432
+ maxTurns: 1,
433
+ agentName: 'context-extractor',
434
+ agentDescription: 'Extracts structured project intelligence from repository files',
435
+ capabilities: {},
436
+ allowedTools: [],
483
437
  permissionMode: 'bypassPermissions',
484
- outputFormat: {
485
- type: 'json_schema',
486
- schema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
487
- },
438
+ model: this.model,
439
+ outputSchema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
488
440
  };
489
- return options;
490
441
  }
491
- getFindingValidatorOptions(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
442
+ getContextExtractorOptions(role = 'context_extractor') {
443
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getContextExtractorRoleSpec(role));
444
+ }
445
+ getFindingValidatorRoleSpec(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
492
446
  const roleConfig = this.confDict[this.environment]?.[role];
493
447
  let systemPrompt = roleConfig?.options?.system_prompt ||
494
448
  'You are a security expert specializing in vulnerability validation. ' +
@@ -498,45 +452,25 @@ You have access to Read, Grep, and Write tools:
498
452
  if (srcDir) {
499
453
  systemPrompt += `\n\nSource code is available at: ${srcDir}. Use Read and Grep to inspect files for additional context if needed.`;
500
454
  }
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
- },
455
+ const spec = {
456
+ roleId: 'finding_validator',
457
+ systemPrompt,
458
+ maxTurns: roleConfig?.options?.max_turns ?? 5,
459
+ agentName: 'finding-validator',
460
+ agentDescription: 'Validates whether a previously detected security vulnerability is still present in code',
461
+ capabilities: { read: true, grep: true },
512
462
  permissionMode: 'bypassPermissions',
513
- outputFormat: {
514
- type: 'json_schema',
515
- schema: finding_validator_1.RETEST_VERDICT_SCHEMA
516
- }
463
+ model: this.model,
464
+ outputSchema: finding_validator_1.RETEST_VERDICT_SCHEMA,
465
+ workingDirectory: srcDir ?? undefined,
517
466
  };
518
- attachMcpServerToOptions(options, mcpServerUrl, 'finding-validator', mcpServerName, mcpServerBearer);
519
- return options;
467
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
468
+ return spec;
520
469
  }
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') {
470
+ getFindingValidatorOptions(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
471
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getFindingValidatorRoleSpec(role, srcDir, mcpServerUrl, mcpServerName, mcpServerBearer));
472
+ }
473
+ getLearnedGuidanceSynthesizerRoleSpec(role = 'learned_guidance_synthesizer') {
540
474
  const roleConfig = this.confDict[this.environment]?.[role];
541
475
  const systemPrompt = roleConfig?.options?.system_prompt ||
542
476
  'You are a senior application security engineer summarizing patterns from past PR-scan ' +
@@ -548,30 +482,23 @@ You have access to Read, Grep, and Write tools:
548
482
  'vague to ground a specific rule, OMIT that bucket entirely — it is better to return zero ' +
549
483
  'bullets than a bullet the reviewer cannot act on. Output is constrained to the required ' +
550
484
  '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
- },
485
+ return {
486
+ roleId: 'learned_guidance_synthesizer',
487
+ systemPrompt,
488
+ maxTurns: roleConfig?.options?.max_turns ?? 1,
489
+ agentName: 'learned-guidance-synthesizer',
490
+ agentDescription: 'Synthesizes class-level learned-guidance bullets from per-CWE dismissal-signal buckets',
491
+ capabilities: {},
492
+ allowedTools: [],
562
493
  permissionMode: 'bypassPermissions',
563
- outputFormat: {
564
- type: 'json_schema',
565
- schema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
566
- },
494
+ model: this.model,
495
+ outputSchema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
567
496
  };
568
- return options;
569
497
  }
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) {
498
+ getLearnedGuidanceSynthesizerOptions(role = 'learned_guidance_synthesizer') {
499
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getLearnedGuidanceSynthesizerRoleSpec(role));
500
+ }
501
+ getPrAdversaryRoleSpec(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
575
502
  const roleConfig = this.confDict[this.environment]?.[role];
576
503
  let systemPrompt = roleConfig?.options?.system_prompt ||
577
504
  'You are a senior application security engineer performing an adversarial second pass on security findings. ' +
@@ -585,42 +512,25 @@ You have access to Read, Grep, and Write tools:
585
512
  systemPrompt +=
586
513
  '\n\n**Experiment (treatment):** Bias toward dropping borderline issues unless the diff plus quick repo checks show a real attack surface.';
587
514
  }
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
- },
515
+ const spec = {
516
+ roleId: 'pr_adversary',
517
+ systemPrompt,
518
+ maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 15,
519
+ agentName: 'pr-adversary',
520
+ agentDescription: 'Adversarial second pass: filters PR scan findings by concrete failure paths',
521
+ capabilities: { read: true, grep: true },
599
522
  permissionMode: 'bypassPermissions',
600
- outputFormat: {
601
- type: 'json_schema',
602
- schema: security_report_1.SECURITY_REPORT_SCHEMA,
603
- },
523
+ model: this.model,
524
+ outputSchema: security_report_1.SECURITY_REPORT_SCHEMA,
525
+ workingDirectory: srcDir ?? undefined,
604
526
  };
605
- attachMcpServerToOptions(options, mcpServerUrl, 'pr-adversary', mcpServerName, mcpServerBearer);
606
- return options;
527
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
528
+ return spec;
607
529
  }
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) {
530
+ getPrAdversaryOptions(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
531
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getPrAdversaryRoleSpec(role, srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer));
532
+ }
533
+ getFpAdversaryRoleSpec(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
624
534
  const roleConfig = this.confDict[this.environment]?.[role];
625
535
  let systemPrompt = roleConfig?.options?.system_prompt ||
626
536
  'You are a senior application security engineer performing an adversarial false-positive review on a full-repository security scan. ' +
@@ -636,25 +546,23 @@ You have access to Read, Grep, and Write tools:
636
546
  if (mcpServerUrl) {
637
547
  systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
638
548
  }
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
- },
549
+ const spec = {
550
+ roleId: 'fp_adversary',
551
+ systemPrompt,
552
+ maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 15,
553
+ agentName: 'fp-adversary',
554
+ agentDescription: 'Adversarial false-positive filter for full-repo scans: emits per-finding (verdict, confidence, rationale) verdicts',
555
+ capabilities: { read: true, grep: true },
650
556
  permissionMode: 'bypassPermissions',
651
- outputFormat: {
652
- type: 'json_schema',
653
- schema: fp_adversary_pass_1.FP_ADVERSARY_REPORT_SCHEMA,
654
- },
557
+ model: this.model,
558
+ outputSchema: fp_adversary_pass_1.FP_ADVERSARY_REPORT_SCHEMA,
559
+ workingDirectory: srcDir ?? undefined,
655
560
  };
656
- attachMcpServerToOptions(options, mcpServerUrl, 'fp-adversary', mcpServerName, mcpServerBearer);
657
- return options;
561
+ (0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
562
+ return spec;
563
+ }
564
+ getFpAdversaryOptions(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
565
+ return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getFpAdversaryRoleSpec(role, srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer));
658
566
  }
659
567
  }
660
568
  exports.AgentOptions = AgentOptions;